提交 116a1f77 编写于 作者: V vben

wip(table): perf table #136,146,134

上级 405d7466
......@@ -14,6 +14,12 @@
- useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
- table: 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选狂
- table: 监听行点击事件
- table: 表格列配置按钮增加 列拖拽,列固定功能。
- table:表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示
### ✨ Refactor
- 重构表单,解决已知 bug
### ⚡ Performance Improvements
......
<template>
<ConfigProvider
v-bind="lockEvent"
:locale="antConfigLocale"
:transform-cell-text="transformCellText"
>
<ConfigProvider v-bind="lockEvent" :locale="antConfigLocale">
<AppProvider>
<router-view />
</AppProvider>
......@@ -14,7 +10,7 @@
import { defineComponent } from 'vue';
import { ConfigProvider } from 'ant-design-vue';
import { getConfigProvider, initAppConfigStore } from '/@/setup/App';
import { initAppConfigStore } from '/@/setup/App';
import { useLockPage } from '/@/hooks/web/useLockPage';
import { useLocale } from '/@/hooks/web/useLocale';
......@@ -28,9 +24,6 @@
// Initialize vuex internal system configuration
initAppConfigStore();
// Get ConfigProvider configuration
const { transformCellText } = getConfigProvider();
// Create a lock screen monitor
const lockEvent = useLockPage();
......@@ -38,7 +31,6 @@
const { antConfigLocale } = useLocale();
return {
transformCellText,
antConfigLocale,
lockEvent,
};
......
import Button from './src/BasicButton.vue';
import PopConfirmButton from './src/PopConfirmButton.vue';
import { withInstall } from '../util';
withInstall(Button);
export { Button };
withInstall(Button, PopConfirmButton);
export { Button, PopConfirmButton };
<script lang="ts">
import { defineComponent, h, unref } from 'vue';
import { Popconfirm } from 'ant-design-vue';
import BasicButton from './BasicButton.vue';
import { propTypes } from '/@/utils/propTypes';
import { useI18n } from '/@/hooks/web/useI18n';
import { extendSlots } from '/@/utils/helper/tsxHelper';
import { omit } from 'lodash-es';
const { t } = useI18n();
export default defineComponent({
name: 'PopButton',
inheritAttrs: false,
components: { Popconfirm, BasicButton },
props: {
enable: propTypes.bool.def(true),
okText: propTypes.string.def(t('component.drawer.okText')),
cancelText: propTypes.string.def(t('component.drawer.cancelText')),
},
setup(props, { slots, attrs }) {
return () => {
const popValues = { ...props, ...unref(attrs) };
const Button = h(BasicButton, omit(unref(attrs), 'icon'), extendSlots(slots));
if (!props.enable) {
return Button;
}
return h(Popconfirm, omit(popValues, 'icon'), { default: () => Button });
};
},
});
</script>
<template>
<Scrollbar
ref="scrollbarRef"
:wrapClass="`scrollbar__wrap`"
:viewClass="`scrollbar__view`"
class="scroll-container"
v-bind="$attrs"
>
<Scrollbar ref="scrollbarRef" class="scroll-container" v-bind="$attrs">
<slot />
</Scrollbar>
</template>
......
......@@ -6,7 +6,7 @@
<template #overlay>
<a-menu :selectedKeys="selectedKeys">
<template v-for="item in getMenuList" :key="`${item.event}`">
<a-menu-item @click="handleClickMenu({ key: item.event })" :disabled="item.disabled">
<a-menu-item @click="handleClickMenu(item)" :disabled="item.disabled">
<Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span>
</a-menu-item>
......@@ -59,9 +59,11 @@
setup(props, { emit }) {
const getMenuList = computed(() => props.dropMenuList);
function handleClickMenu({ key }: { key: string }) {
const menu = unref(getMenuList).find((item) => `${item.event}` === `${key}`);
function handleClickMenu(item: DropMenu) {
const { event } = item;
const menu = unref(getMenuList).find((item) => `${item.event}` === `${event}`);
emit('menuEvent', menu);
item.onClick?.();
}
return { handleClickMenu, getMenuList };
......
export interface DropMenu {
onClick?: Fn;
to?: string;
icon?: string;
event: string | number;
......
<template>
<Select v-bind="attrs" :options="options" v-model:value="state">
<Select v-bind="attrs" :options="getOptions" v-model:value="state">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data" />
</template>
......@@ -15,7 +15,7 @@
</Select>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watchEffect } from 'vue';
import { defineComponent, PropType, ref, watchEffect, computed, unref } from 'vue';
import { Select } from 'ant-design-vue';
import { isFunction } from '/@/utils/is';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
......@@ -24,31 +24,31 @@
import { LoadingOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
type OptionsItem = { label: string; value: string; disabled?: boolean };
export default defineComponent({
name: 'RadioButtonGroup',
name: 'ApiSelect',
components: {
Select,
LoadingOutlined,
},
props: {
value: {
type: String as PropType<string>,
},
value: propTypes.string,
api: {
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
default: null,
},
// api params
params: {
type: Object as PropType<Recordable>,
default: () => {},
},
resultField: {
type: String as PropType<string>,
default: '',
},
// support xxx.xxx.xx
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'),
},
setup(props) {
const options = ref<OptionsItem[]>([]);
......@@ -59,6 +59,20 @@
// Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props);
const getOptions = computed(() => {
const { labelField, valueField } = props;
return unref(options).reduce((prev, next: Recordable) => {
if (next) {
prev.push({
label: next[labelField],
value: next[valueField],
});
}
return prev;
}, [] as OptionsItem[]);
});
watchEffect(() => {
fetch();
});
......@@ -83,7 +97,7 @@
loading.value = false;
}
}
return { state, attrs, options, loading, t };
return { state, attrs, getOptions, loading, t };
},
});
</script>
......@@ -117,7 +117,7 @@ export function useFormEvents({
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
const index = schemaList.findIndex((schema) => schema.field === prefixField);
const hasInList = schemaList.some((item) => item.field === prefixField);
const hasInList = schemaList.some((item) => item.field === prefixField || schema.field);
if (!hasInList) return;
......@@ -147,6 +147,7 @@ export function useFormEvents({
error(
'All children of the form Schema array that need to be updated must contain the `field` field'
);
return;
}
const schema: FormSchema[] = [];
updateData.forEach((item) => {
......
export { createImgPreview } from './src/functional';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export const ImagePreview = createAsyncComponent(() => import('./src/index.vue'));
<template>
<PreviewGroup :class="prefixCls">
<slot v-if="!imageList || $slots.default" />
<template v-else>
<template v-for="item in getImageList" :key="item.src">
<Image v-bind="item">
<template #placeholder v-if="item.placeholder">
<Image v-bind="item" :src="item.placeholder" :preview="false" />
</template>
</Image>
</template>
</template>
</PreviewGroup>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, computed } from 'vue';
import { Image } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes';
import { ImageItem } from './types';
import { isString } from '/@/utils/is';
export default defineComponent({
name: 'ImagePreview',
components: {
Image,
PreviewGroup: Image.PreviewGroup,
},
props: {
functional: propTypes.bool,
imageList: {
type: Array as PropType<ImageItem[]>,
},
},
setup(props) {
const { prefixCls } = useDesign('image-preview');
const getImageList = computed(() => {
const { imageList } = props;
if (!imageList) {
return [];
}
return imageList.map((item) => {
if (isString(item)) {
return {
src: item,
placeholder: false,
};
}
return item;
});
});
return { prefixCls, getImageList };
},
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-image-preview';
.@{prefix-cls} {
.ant-image-preview-operations {
background: rgba(0, 0, 0, 0.4);
}
}
</style>
......@@ -10,3 +10,21 @@ export interface Props {
imageList: string[];
index: number;
}
export interface ImageProps {
alt?: string;
fallback?: string;
src: string;
width: string | number;
height?: string | number;
placeholder?: string | boolean;
preview?:
| boolean
| {
visible?: boolean;
onVisibleChange?: (visible: boolean, prevVisible: boolean) => void;
getContainer: string | HTMLElement | (() => HTMLElement);
};
}
export type ImageItem = string | ImageProps;
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export { default as BasicTable } from './src/BasicTable.vue';
export { default as TableAction } from './src/components/TableAction';
export { default as TableImg } from './src/components/TableImg.vue';
export { default as TableAction } from './src/components/TableAction.vue';
// export { default as TableImg } from './src/components/TableImg.vue';
export { renderEditableCell, renderEditableRow } from './src/components/renderEditable';
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue'));
// export const TableAction = createAsyncComponent(() => import('./src/components/TableAction.vue'));
export * from './src/types/table';
export * from './src/types/pagination';
export * from './src/types/tableAction';
......
<template>
<div
ref="wrapRef"
class="basic-table"
:class="{
'table-form-container': getBindValues.useSearchForm,
inset: getBindValues.inset,
}"
:class="[
prefixCls,
{
[`${prefixCls}-form-container`]: getBindValues.useSearchForm,
[`${prefixCls}--inset`]: getBindValues.inset,
},
]"
>
<BasicForm
:submitOnReset="true"
submitOnReset
v-bind="getFormProps"
v-if="getBindValues.useSearchForm"
:submitButtonOptions="{ loading: getLoading }"
......@@ -17,10 +19,11 @@
@submit="handleSearchInfoChange"
@advanced-change="redoHeight"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="`form-${item}`" v-bind="data" />
<template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
<slot :name="item" v-bind="data" />
</template>
</BasicForm>
<Table
ref="tableElRef"
v-bind="getBindValues"
......@@ -38,15 +41,12 @@
import type { BasicTableProps, TableActionType, SizeType, SorterResult } from './types/table';
import { PaginationProps } from './types/pagination';
import { defineComponent, ref, computed, unref, watch, nextTick } from 'vue';
import { defineComponent, ref, computed, unref } from 'vue';
import { Table } from 'ant-design-vue';
import renderTitle from './components/renderTitle';
import renderFooter from './components/renderFooter';
import renderExpandIcon from './components/renderExpandIcon';
import { BasicForm, FormProps, useForm } from '/@/components/Form/index';
import { BasicForm, useForm } from '/@/components/Form/index';
import { isFunction } from '/@/utils/is';
import { isFunction, isString } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import { omit } from 'lodash-es';
import { usePagination } from './hooks/usePagination';
......@@ -55,15 +55,18 @@
import { useLoading } from './hooks/useLoading';
import { useRowSelection } from './hooks/useRowSelection';
import { useTableScroll } from './hooks/useTableScroll';
import { provideTable } from './hooks/useProvinceTable';
import { useCustomRow } from './hooks/useCustomRow';
import { useTableStyle } from './hooks/useTableStyle';
import { useTableHeader } from './hooks/useTableHeader';
import { createTableContext } from './hooks/useTableContext';
import { useTableFooter } from './hooks/useTableFooter';
import { useTableForm } from './hooks/useTableForm';
import { useEventListener } from '/@/hooks/event/useEventListener';
import { basicProps } from './props';
import { useExpose } from '/@/hooks/core/useExpose';
import './style/index.less';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
props: basicProps,
components: { Table, BasicForm },
......@@ -84,7 +87,8 @@
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const [registerForm, { getFieldsValue }] = useForm();
const { prefixCls } = useDesign('basic-table');
const [registerForm, formActions] = useForm();
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
......@@ -92,7 +96,14 @@
const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
const { getColumnsRef, getColumns, setColumns } = useColumns(getProps, getPaginationInfo);
const {
getSortFixedColumns,
getColumns,
setColumns,
getColumnsRef,
getCacheColumns,
} = useColumns(getProps, getPaginationInfo);
const {
getDataSourceRef,
getDataSource,
......@@ -107,14 +118,13 @@
getPaginationInfo,
setLoading,
setPagination,
getFieldsValue,
getFieldsValue: formActions.getFieldsValue,
},
emit
);
const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef);
const {
getRowSelection,
getRowSelectionRef,
getSelectRows,
clearSelectedRowKeys,
......@@ -123,6 +133,13 @@
setSelectedRowKeys,
} = useRowSelection(getProps, emit);
const { getScrollRef, redoHeight } = useTableScroll(
getProps,
tableElRef,
getColumnsRef,
getRowSelectionRef
);
const { customRow } = useCustomRow(getProps, {
setSelectedRowKeys,
getSelectRowKeys,
......@@ -131,74 +148,47 @@
emit,
});
const { getRowClassName } = useTableStyle(getProps);
const { getRowClassName } = useTableStyle(getProps, prefixCls);
const getTitleProps = computed(
(): Recordable => {
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(getProps);
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
if (hideTitle && !isString(title)) {
return {};
}
return {
title: hideTitle
? null
: renderTitle.bind(
null,
title,
titleHelpMessage,
slots,
showTableSetting,
tableSetting
),
};
}
const { getHeaderProps } = useTableHeader(getProps, slots);
const { getFooterProps } = useTableFooter(
getProps,
getScrollRef,
tableElRef,
getDataSourceRef
);
const getBindValues = computed(() => {
const { showSummary } = unref(getProps);
const {
getFormProps,
replaceFormSlotKey,
getFormSlotKeys,
handleSearchInfoChange,
} = useTableForm(getProps, slots, fetch);
const getBindValues = computed(() => {
let propsData: Recordable = {
size: 'middle',
...(slots.expandedRowRender ? { expandIcon: renderExpandIcon() } : {}),
...attrs,
customRow,
...unref(getProps),
...unref(getTitleProps),
...unref(getHeaderProps),
scroll: unref(getScrollRef),
loading: unref(getLoading),
tableLayout: 'fixed',
rowSelection: unref(getRowSelectionRef),
rowKey: unref(getRowKey),
columns: unref(getColumnsRef),
columns: unref(getSortFixedColumns),
pagination: unref(getPaginationInfo),
dataSource: unref(getDataSourceRef),
footer: unref(getFooterProps),
};
if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll');
}
if (showSummary) {
propsData.footer = renderFooter.bind(null, {
scroll: scroll as any,
columnsRef: getColumnsRef,
summaryFunc: unref(getProps).summaryFunc,
dataSourceRef: getDataSourceRef,
rowSelectionRef: getRowSelectionRef,
});
}
return propsData;
});
const getFormProps = computed(() => {
const { formConfig } = unref(getProps);
const formProps: Partial<FormProps> = {
showAdvancedButton: true,
...formConfig,
compact: true,
};
return formProps;
});
const getEmptyDataIsShowTable = computed(() => {
const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
if (emptyDataIsShowTable || !useSearchForm) {
......@@ -207,22 +197,6 @@
return !!unref(getDataSourceRef).length;
});
watch(
() => unref(getDataSourceRef),
() => {
handleSummary();
},
{ immediate: true }
);
function handleSearchInfoChange(info: any) {
const { handleSearchInfoFn } = unref(getProps);
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
info = handleSearchInfoFn(info) || info;
}
fetch({ searchInfo: info, page: 1 });
}
function handleTableChange(
pagination: PaginationProps,
// @ts-ignore
......@@ -243,32 +217,8 @@
fetch();
}
function handleSummary() {
if (unref(getProps).showSummary) {
nextTick(() => {
const tableEl = unref(tableElRef);
if (!tableEl) return;
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
const bodyDom = bodyDomList[0];
useEventListener({
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-body'
) as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
},
wait: 0,
options: true,
});
});
}
}
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = deepMerge(unref(innerPropsRef) || {}, props);
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
}
const tableAction: TableActionType = {
......@@ -285,21 +235,19 @@
setLoading,
getDataSource,
setProps,
getRowSelection,
getPaginationRef: getPagination,
getColumns,
getCacheColumns,
getSize: () => {
return unref(getBindValues).size as SizeType;
},
};
provideTable({
...tableAction,
wrapRef,
});
createTableContext({ ...tableAction, wrapRef, getBindValues });
useExpose<TableActionType>(tableAction);
emit('register', tableAction);
emit('register', tableAction, formActions);
return {
tableElRef,
......@@ -307,13 +255,16 @@
getLoading,
registerForm,
handleSearchInfoChange,
getFormProps,
getEmptyDataIsShowTable,
handleTableChange,
getRowClassName,
wrapRef,
tableAction,
redoHeight,
getFormProps,
replaceFormSlotKey,
getFormSlotKeys,
prefixCls,
};
},
});
......
import { defineComponent, PropType } from 'vue';
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
import Icon from '/@/components/Icon/index';
import { DownOutlined } from '@ant-design/icons-vue';
import { ActionItem } from '/@/components/Table';
import { Button } from '/@/components/Button';
import { snowUuid } from '/@/utils/uuid';
const prefixCls = 'basic-table-action';
export default defineComponent({
name: 'TableAction',
props: {
actions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
dropDownActions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
moreText: {
type: String as PropType<string>,
default: '更多',
},
},
setup(props) {
function renderButton(action: ActionItem) {
const { disabled = false, label, icon, color = '', type = 'link', ...actionProps } = action;
const button = (
<Button
type={type}
size="small"
disabled={disabled}
color={color}
{...actionProps}
key={`${snowUuid()}`}
>
{() => (
<>
{icon && <Icon icon={icon} class="mr-1" />}
{label}
</>
)}
</Button>
);
return button;
}
function renderPopConfirm(action: ActionItem) {
const { popConfirm = null } = action;
if (!popConfirm) {
return renderButton(action);
}
const {
title,
okText = '确定',
cancelText = '取消',
confirm = () => {},
cancel = () => {},
icon = '',
} = popConfirm;
return (
<Popconfirm
key={`${snowUuid()}`}
title={title}
onConfirm={confirm}
onCancel={cancel}
okText={okText}
cancelText={cancelText}
icon={icon}
>
{() => renderButton(action)}
</Popconfirm>
);
}
const dropdownDefaultSLot = () => (
<Button type="link" size="small">
{{
default: () => (
<>
{props.moreText}
<DownOutlined />
</>
),
}}
</Button>
);
// 增加按钮的TYPE和COLOR
return () => {
const { dropDownActions = [], actions } = props;
return (
<div class={prefixCls}>
{actions &&
actions.map((action) => {
return renderPopConfirm(action);
})}
{dropDownActions && dropDownActions.length && (
<Dropdown overlayClassName="basic-tale-action-dropdown">
{{
default: dropdownDefaultSLot,
overlay: () => {
return (
<Menu>
{{
default: () => {
return dropDownActions.map((action) => {
const { disabled = false } = action;
action.ghost = true;
return (
<Menu.Item key={`${snowUuid()}`} disabled={disabled}>
{() => {
return renderPopConfirm(action);
}}
</Menu.Item>
);
});
},
}}
</Menu>
);
},
}}
</Dropdown>
)}
</div>
);
};
},
});
<template>
<div :class="[prefixCls, getAlign]">
<template v-for="(action, index) in getActions" :key="`${index}`">
<PopConfirmButton v-bind="action">
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
{{ action.label }}
</PopConfirmButton>
<Divider type="vertical" v-if="divider && index < getActions.length" />
</template>
<Dropdown :trigger="['hover']" :dropMenuList="getDropList">
<slot name="more" />
<a-button type="link" size="small" v-if="!$slots.more">
<MoreOutlined class="icon-more" />
</a-button>
</Dropdown>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import Icon from '/@/components/Icon/index';
import { ActionItem } from '/@/components/Table';
import { PopConfirmButton } from '/@/components/Button';
import { Divider } from 'ant-design-vue';
import { Dropdown } from '/@/components/Dropdown';
import { useDesign } from '/@/hooks/web/useDesign';
import { MoreOutlined } from '@ant-design/icons-vue';
import { propTypes } from '/@/utils/propTypes';
import { useTableContext } from '../hooks/useTableContext';
import { ACTION_COLUMN_FLAG } from '../const';
export default defineComponent({
name: 'TableAction',
components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined },
props: {
actions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
dropDownActions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
divider: propTypes.bool.def(true),
},
setup(props) {
const { prefixCls } = useDesign('basic-table-action');
const table = useTableContext();
const getActions = computed(() => {
return props.actions.map((action) => {
const { popConfirm } = action;
return {
...action,
...(popConfirm || {}),
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
enable: !!popConfirm,
type: 'link',
size: 'small',
};
});
});
const getDropList = computed(() => {
return props.dropDownActions.map((action, index) => {
const { label } = action;
return {
...action,
text: label,
divider: index < props.dropDownActions.length - 1 ? props.divider : false,
};
});
});
const getAlign = computed(() => {
const columns = table.getColumns();
const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
return actionColumn?.align ?? 'left';
});
return { prefixCls, getActions, getDropList, getAlign };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-action';
.@{prefix-cls} {
display: flex;
align-items: center;
&.left {
justify-content: flex-start;
}
&.center {
justify-content: center;
}
&.right {
justify-content: flex-end;
}
button {
display: flex;
align-items: center;
span {
margin-left: 0 !important;
}
}
.ant-divider,
.ant-divider-vertical {
margin: 0 2px;
}
.icon-more {
transform: rotate(90deg);
svg {
font-size: 1.1em;
font-weight: 700;
}
}
}
</style>
<template>
<Table
v-if="summaryFunc"
:showHeader="false"
:bordered="false"
:pagination="false"
:dataSource="getDataSource"
:rowKey="(r) => r[rowKey]"
:columns="getColumns"
tableLayout="fixed"
:scroll="scroll"
/>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, unref, computed, toRaw } from 'vue';
import { Table } from 'ant-design-vue';
import { cloneDeep } from 'lodash-es';
import { isFunction } from '/@/utils/is';
import type { BasicColumn } from '../types/table';
import { INDEX_COLUMN_FLAG } from '../const';
import { propTypes } from '/@/utils/propTypes';
import { useTableContext } from '../hooks/useTableContext';
const SUMMARY_ROW_KEY = '_row';
const SUMMARY_INDEX_KEY = '_index';
export default defineComponent({
name: 'BasicTableFooter',
components: { Table },
props: {
summaryFunc: {
type: Function as PropType<Fn>,
},
scroll: {
type: Object as PropType<Recordable>,
},
rowKey: propTypes.string.def('key'),
},
setup(props) {
const table = useTableContext();
const getDataSource = computed((): Recordable[] => {
const { summaryFunc } = props;
if (!isFunction(summaryFunc)) {
return [];
}
let dataSource = toRaw(unref(table.getDataSource()));
dataSource = summaryFunc(dataSource);
dataSource.forEach((item, i) => {
item[props.rowKey] = `${i}`;
});
return dataSource;
});
const getColumns = computed(() => {
const dataSource = unref(getDataSource);
const columns: BasicColumn[] = cloneDeep(table.getColumns());
const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
if (index !== -1) {
if (hasIndexSummary) {
columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
columns[index].ellipsis = false;
} else {
Reflect.deleteProperty(columns[index], 'customRender');
}
}
if (table.getRowSelection() && hasRowSummary) {
columns.unshift({
width: 60,
title: 'selection',
key: 'selectionKey',
align: 'center',
customRender: ({ record }) => record[SUMMARY_ROW_KEY],
});
}
return columns;
});
return { getColumns, getDataSource };
},
});
</script>
<template>
<slot name="tableTitle" v-if="$slots.tableTitle" />
<TableTitle :helpMessage="titleHelpMessage" :title="title" v-if="!$slots.tableTitle && title" />
<div :class="`${prefixCls}__toolbar`">
<slot name="toolbar" />
<Divider type="vertical" v-if="$slots.toolbar" />
<TableSetting :setting="tableSetting" v-if="showTableSetting" />
</div>
</template>
<script lang="ts">
import type { TableSetting } from '../types/table';
import type { PropType } from 'vue';
import { Divider } from 'ant-design-vue';
import { defineComponent } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import TableSettingComp from './settings/index.vue';
import TableTitle from './TableTitle.vue';
export default defineComponent({
name: 'BasicTableHeader',
components: {
Divider,
TableTitle,
TableSetting: TableSettingComp,
},
props: {
title: {
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
},
tableSetting: {
type: Object as PropType<TableSetting>,
},
showTableSetting: {
type: Boolean,
},
titleHelpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
},
setup() {
const { prefixCls } = useDesign('basic-table-header');
return { prefixCls };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header';
.@{prefix-cls} {
&__toolbar {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
> * {
margin-right: 8px;
}
}
}
</style>
<template>
<div class="basic-table-img__preview" v-if="imgList && imgList.length">
<template v-for="(img, index) in imgList" :key="img">
<img :width="size" @click="handlePreview(index)" :src="img" />
</template>
<div :class="prefixCls" v-if="imgList && imgList.length">
<PreviewGroup>
<template v-for="img in imgList" :key="img">
<Image :width="size" :src="img" />
</template>
</PreviewGroup>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { createImgPreview } from '/@/components/Preview/index';
import { useDesign } from '/@/hooks/web/useDesign';
import { Image } from 'ant-design-vue';
export default defineComponent({
name: 'TableAction',
name: 'TableImage',
components: { Image, PreviewGroup: Image.PreviewGroup },
props: {
imgList: {
type: Array as PropType<string[]>,
......@@ -21,16 +26,25 @@
default: 40,
},
},
setup(props) {
function handlePreview(index: number) {
const { imgList } = props;
createImgPreview({
imageList: imgList as string[],
index: index,
});
}
return { handlePreview };
setup() {
const { prefixCls } = useDesign('basic-table-img');
return { prefixCls };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-img';
.@{prefix-cls} {
display: flex;
.ant-image {
margin-right: 4px;
cursor: zoom-in;
img {
border-radius: 2px;
}
}
}
</style>
<template>
<div class="table-settings">
<Divider type="vertical" />
<Tooltip placement="top" v-if="getSetting.redo">
<template #title>
<span>{{ t('component.table.settingRedo') }}</span>
</template>
<RedoOutlined @click="redo" />
</Tooltip>
<Tooltip placement="top" v-if="getSetting.size">
<template #title>
<span>{{ t('component.table.settingDens') }}</span>
</template>
<Dropdown placement="bottomCenter" :trigger="['click']">
<ColumnHeightOutlined />
<template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
<MenuItem key="default">
<span>{{ t('component.table.settingDensDefault') }}</span>
</MenuItem>
<MenuItem key="middle">
<span>{{ t('component.table.settingDensMiddle') }}</span>
</MenuItem>
<MenuItem key="small">
<span>{{ t('component.table.settingDensSmall') }}</span>
</MenuItem>
</Menu>
</template>
</Dropdown>
</Tooltip>
<Tooltip placement="top" v-if="getSetting.setting">
<template #title>
<span>{{ t('component.table.settingColumn') }}</span>
</template>
<Popover
placement="bottomLeft"
trigger="click"
overlayClassName="table-settings__cloumn-list"
>
<template #content>
<CheckboxGroup v-model:value="checkedList" @change="onChange">
<template v-for="item in plainOptions" :key="item.value">
<div class="table-settings__check-item">
<Checkbox :value="item.value">
{{ item.label }}
</Checkbox>
</div>
</template>
</CheckboxGroup>
</template>
<template #title>
<div class="table-settings__popover-title">
<Checkbox
:indeterminate="indeterminate"
v-model:checked="checkAll"
@change="onCheckAllChange"
>
{{ t('component.table.settingColumnShow') }}
</Checkbox>
<a-button size="small" type="link" @click="reset">
{{ t('component.table.settingReset') }}</a-button
>
</div>
</template>
<SettingOutlined />
</Popover>
</Tooltip>
<Tooltip placement="top" v-if="getSetting.fullScreen">
<template #title>
<span>{{ t('component.table.settingFullScreen') }}</span>
</template>
<FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" />
<FullscreenExitOutlined @click="handleFullScreen" v-else />
</Tooltip>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, toRefs, PropType, computed, watchEffect } from 'vue';
import { injectTable } from '../hooks/useProvinceTable';
import { Tooltip, Divider, Dropdown, Menu, Popover, Checkbox } from 'ant-design-vue';
import {
RedoOutlined,
ColumnHeightOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
SettingOutlined,
} from '@ant-design/icons-vue';
import { useFullscreen } from '/@/hooks/web/useFullScreen';
import type { SizeType, TableSetting } from '../types/table';
import { useI18n } from '/@/hooks/web/useI18n';
interface Options {
label: string;
value: string;
}
interface State {
indeterminate: boolean;
checkAll: boolean;
// defaultColumns: BasicColumn[];
// columns: BasicColumn[];
checkedList: string[];
defaultCheckList: string[];
}
export default defineComponent({
name: 'TableSetting',
components: {
RedoOutlined,
ColumnHeightOutlined,
FullscreenExitOutlined,
FullscreenOutlined,
SettingOutlined,
Popover,
Tooltip,
Divider,
Dropdown,
Checkbox,
CheckboxGroup: Checkbox.Group,
Menu,
MenuItem: Menu.Item,
},
props: {
setting: {
type: Object as PropType<TableSetting>,
default: {},
},
},
setup(props) {
const table = injectTable();
const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef);
const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
const plainOptions = ref<Options[]>([]);
const state = reactive<State>({
indeterminate: false,
checkAll: true,
checkedList: [],
defaultCheckList: [],
});
const { t } = useI18n();
watchEffect(() => {
const columns = table.getColumns();
if (columns.length) {
init();
}
});
function init() {
let ret: Options[] = [];
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
ret.push({
label: item.title as string,
value: (item.dataIndex || item.title) as string,
});
});
if (!plainOptions.value.length) {
plainOptions.value = ret;
}
const checkList = table
.getColumns()
.map((item) => item.dataIndex || item.title) as string[];
state.checkedList = checkList;
state.defaultCheckList = checkList;
}
function handleTitleClick({ key }: { key: SizeType }) {
selectedKeysRef.value = [key];
table.setProps({
size: key,
});
}
function handleFullScreen() {
toggleFullscreen();
}
function onCheckAllChange(e: ChangeEvent) {
state.indeterminate = false;
const checkList = plainOptions.value.map((item) => item.value);
if (e.target.checked) {
state.checkedList = checkList;
table.setColumns(checkList);
} else {
state.checkedList = [];
table.setColumns([]);
}
}
function onChange(checkedList: string[]) {
const len = plainOptions.value.length;
state.indeterminate = !!checkedList.length && checkedList.length < len;
state.checkAll = checkedList.length === len;
table.setColumns(checkedList);
}
function reset() {
if (state.checkAll) return;
state.checkedList = [...state.defaultCheckList];
state.checkAll = true;
state.indeterminate = false;
table.setColumns(state.defaultCheckList);
}
const getSetting = computed(
(): TableSetting => {
return {
redo: true,
size: true,
setting: true,
fullScreen: true,
...props.setting,
};
}
);
return {
redo: () => table.reload(),
handleTitleClick,
selectedKeysRef,
handleFullScreen,
isFullscreenRef,
onCheckAllChange,
onChange,
plainOptions,
reset,
getSetting,
...toRefs(state),
t,
};
},
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
.table-settings {
& > * {
margin-right: 12px;
}
svg {
width: 1.2em;
height: 1.2em;
}
&__popover-title {
display: flex;
align-items: center;
justify-content: space-between;
}
&__check-item {
width: 100%;
padding: 4px 16px 4px 16px;
.ant-checkbox-wrapper {
width: 100%;
}
&:hover {
background: fade(@primary-color, 10%);
}
}
&__cloumn-list {
.ant-popover-inner-content {
max-height: 360px;
padding-right: 0;
padding-left: 0;
overflow: auto;
}
.ant-checkbox-group {
width: 100%;
}
}
}
</style>
<template>
<BasicTitle class="basic-table-title" v-if="tableTitle" :helpMessage="helpMessage">
{{ tableTitle }}
<BasicTitle :class="prefixCls" v-if="getTitle" :helpMessage="helpMessage">
{{ getTitle }}
</BasicTitle>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import { BasicTitle } from '/@/components/Basic/index';
import { useDesign } from '/@/hooks/web/useDesign';
import { isFunction } from '/@/utils/is';
export default defineComponent({
name: 'TableTitle',
name: 'BasicTableTitle',
components: { BasicTitle },
props: {
title: {
......@@ -23,7 +24,9 @@
},
},
setup(props) {
const tableTitle = computed(() => {
const { prefixCls } = useDesign('basic-table-title');
const getTitle = computed(() => {
const { title, getSelectRows = () => {} } = props;
let tit = title;
......@@ -35,7 +38,16 @@
return tit;
});
return { tableTitle };
return { getTitle, prefixCls };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-title';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
import { Table } from 'ant-design-vue';
import { cloneDeep } from 'lodash-es';
import { unref, ComputedRef } from 'vue';
import { isFunction } from '/@/utils/is';
import type { BasicColumn, TableRowSelection } from '../types/table';
export default ({
scroll = {},
columnsRef,
summaryFunc,
rowKey = 'key',
dataSourceRef,
rowSelectionRef,
}: {
scroll: { x?: number | true; y?: number };
columnsRef: ComputedRef<BasicColumn[]>;
summaryFunc: any;
rowKey?: string;
dataSourceRef: ComputedRef<any[]>;
rowSelectionRef: ComputedRef<TableRowSelection | null>;
}) => {
if (!summaryFunc) {
return;
}
const dataSource: any[] = isFunction(summaryFunc) ? summaryFunc(unref(dataSourceRef)) : [];
const columns: BasicColumn[] = cloneDeep(unref(columnsRef));
const index = columns.findIndex((item) => item.flag === 'INDEX');
const hasRowSummary = dataSource.some((item) => Reflect.has(item, '_row'));
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, '_index'));
if (index !== -1) {
if (hasIndexSummary) {
columns[index].customRender = ({ record }) => record._index;
columns[index].ellipsis = false;
} else {
Reflect.deleteProperty(columns[index], 'customRender');
}
}
if (unref(rowSelectionRef) && hasRowSummary) {
columns.unshift({
width: 60,
title: 'selection',
key: 'selectionKey',
align: 'center',
customRender: ({ record }) => record._row,
});
}
dataSource.forEach((item, i) => {
item[rowKey] = i;
});
return (
<Table
showHeader={false}
bordered={false}
pagination={false}
dataSource={dataSource}
rowKey={rowKey}
columns={columns}
tableLayout="fixed"
scroll={scroll as any}
/>
);
};
import { Slots } from 'vue';
import TableTitle from './TableTitle.vue';
import { getSlot } from '/@/utils/helper/tsxHelper';
import TableSettingComp from './TableSetting.vue';
import type { TableSetting } from '../types/table';
export default (
title: any,
titleHelpMessage: string | string[],
slots: Slots,
showTableSetting: boolean,
tableSetting: TableSetting
) => {
return (
<>
{getSlot(slots, 'tableTitle') ||
(title && <TableTitle helpMessage={titleHelpMessage} title={title} />) || (
<span>&nbsp;</span>
)}
{
<div class="basic-table-toolbar">
{slots.toolbar && getSlot(slots, 'toolbar')}
{showTableSetting && <TableSettingComp setting={tableSetting} />}
</div>
}
</>
);
};
<template>
<Tooltip placement="top">
<template #title>
<span>{{ t('component.table.settingColumn') }}</span>
</template>
<Popover
:getPopupContainer="getPopupContainer"
placement="bottomLeft"
trigger="click"
@visibleChange="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`"
>
<template #title>
<div :class="`${prefixCls}__popover-title`">
<Checkbox
:indeterminate="indeterminate"
v-model:checked="checkAll"
@change="onCheckAllChange"
>
{{ t('component.table.settingColumnShow') }}
</Checkbox>
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
{{ t('component.table.settingIndexColumnShow') }}
</Checkbox>
<Checkbox
v-model:checked="checkSelect"
@change="handleSelectCheckChange"
:disabled="!defaultRowSelection"
>
{{ t('component.table.settingSelectColumnShow') }}
</Checkbox>
<a-button size="small" type="link" @click="reset">
{{ t('component.table.settingReset') }}
</a-button>
</div>
</template>
<template #content>
<ScrollContainer>
<CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef">
<template v-for="item in plainOptions" :key="item.value">
<div :class="`${prefixCls}__check-item`">
<DragOutlined class="table-coulmn-drag-icon" />
<Checkbox :value="item.value"> {{ item.label }} </Checkbox>
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4">
<template #title> {{ t('component.table.settingFixedLeft') }}</template>
<Icon
icon="line-md:arrow-align-left"
:class="[
`${prefixCls}__fixed-left`,
{
active: item.fixed === 'left',
disabled: !checkedList.includes(item.value),
},
]"
@click="handleColumnFixed(item, 'left')"
/>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4">
<template #title> {{ t('component.table.settingFixedRight') }}</template>
<Icon
icon="line-md:arrow-align-left"
:class="[
`${prefixCls}__fixed-right`,
{
active: item.fixed === 'right',
disabled: !checkedList.includes(item.value),
},
]"
@click="handleColumnFixed(item, 'right')"
/>
</Tooltip>
</div>
</template>
</CheckboxGroup>
</ScrollContainer>
</template>
<SettingOutlined />
</Popover>
</Tooltip>
</template>
<script lang="ts">
import {
defineComponent,
ref,
reactive,
toRefs,
watchEffect,
nextTick,
unref,
computed,
} from 'vue';
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
import { Icon } from '/@/components/Icon';
import { ScrollContainer } from '/@/components/Container';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
import { useDesign } from '/@/hooks/web/useDesign';
import { useSortable } from '/@/hooks/web/useSortable';
import { isNullAndUnDef } from '/@/utils/is';
import { getPopupContainer } from '/@/utils';
import type { BasicColumn } from '../../types/table';
interface State {
indeterminate: boolean;
checkAll: boolean;
checkedList: string[];
defaultCheckList: string[];
}
interface Options {
label: string;
value: string;
fixed?: boolean | 'left' | 'right';
}
export default defineComponent({
name: 'ColumnSetting',
components: {
SettingOutlined,
Popover,
Tooltip,
Checkbox,
CheckboxGroup: Checkbox.Group,
DragOutlined,
ScrollContainer,
Divider,
Icon,
},
setup() {
const { t } = useI18n();
const table = useTableContext();
const defaultRowSelection = table.getRowSelection();
let inited = false;
const cachePlainOptions = ref<Options[]>([]);
const plainOptions = ref<Options[]>([]);
const plainSortOptions = ref<Options[]>([]);
const columnListRef = ref<ComponentRef>(null);
const state = reactive<State>({
indeterminate: false,
checkAll: true,
checkedList: [],
defaultCheckList: [],
});
const checkIndex = ref(false);
const checkSelect = ref(false);
const { prefixCls } = useDesign('basic-column-setting');
const getValues = computed(() => {
return unref(table?.getBindValues) || {};
});
watchEffect(() => {
const columns = table.getColumns();
if (columns.length) {
init();
}
});
watchEffect(() => {
const values = unref(getValues);
checkIndex.value = !!values.showIndexColumn;
checkSelect.value = !!values.rowSelection;
});
function getColumns() {
const ret: Options[] = [];
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
ret.push({
label: item.title as string,
value: (item.dataIndex || item.title) as string,
...item,
});
});
return ret;
}
function init() {
const columns = getColumns();
const checkList = table
.getColumns()
.map((item) => {
if (item.defaultHidden) {
return '';
}
return item.dataIndex || item.title;
})
.filter(Boolean) as string[];
if (!plainOptions.value.length) {
plainOptions.value = columns;
plainSortOptions.value = columns;
cachePlainOptions.value = columns;
state.defaultCheckList = checkList;
} else {
const fixedColumns = columns.filter((item) =>
Reflect.has(item, 'fixed')
) as BasicColumn[];
unref(plainOptions).forEach((item: BasicColumn) => {
const findItem = fixedColumns.find((fCol) => fCol.dataIndex === item.dataIndex);
if (findItem) {
item.fixed = findItem.fixed;
}
});
}
state.checkedList = checkList;
}
// checkAll change
function onCheckAllChange(e: ChangeEvent) {
state.indeterminate = false;
const checkList = plainOptions.value.map((item) => item.value);
if (e.target.checked) {
state.checkedList = checkList;
table.setColumns(checkList);
} else {
state.checkedList = [];
table.setColumns([]);
}
}
// Trigger when check/uncheck a column
function onChange(checkedList: string[]) {
const len = plainOptions.value.length;
state.indeterminate = !!checkedList.length && checkedList.length < len;
state.checkAll = checkedList.length === len;
const sortList = unref(plainSortOptions).map((item) => item.value);
checkedList.sort((prev, next) => {
return sortList.indexOf(prev) - sortList.indexOf(next);
});
table.setColumns(checkedList);
}
// reset columns
function reset() {
state.checkedList = [...state.defaultCheckList];
state.checkAll = true;
state.indeterminate = false;
plainOptions.value = unref(cachePlainOptions);
plainSortOptions.value = unref(cachePlainOptions);
table.setColumns(table.getCacheColumns());
}
// Open the pop-up window for drag and drop initialization
function handleVisibleChange() {
if (inited) return;
nextTick(() => {
const columnListEl = unref(columnListRef);
if (!columnListEl) return;
const el = columnListEl.$el;
if (!el) return;
// Drag and drop sort
const { initSortable } = useSortable(el, {
handle: '.table-coulmn-drag-icon ',
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return;
}
// Sort column
const columns = getColumns();
if (oldIndex > newIndex) {
columns.splice(newIndex, 0, columns[oldIndex]);
columns.splice(oldIndex + 1, 1);
} else {
columns.splice(newIndex + 1, 0, columns[oldIndex]);
columns.splice(oldIndex, 1);
}
plainSortOptions.value = columns;
plainOptions.value = columns;
table.setColumns(columns);
},
});
initSortable();
inited = true;
});
}
// Control whether the serial number column is displayed
function handleIndexCheckChange(e: ChangeEvent) {
table.setProps({
showIndexColumn: e.target.checked,
});
}
// Control whether the check box is displayed
function handleSelectCheckChange(e: ChangeEvent) {
table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined,
});
}
function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
if (!state.checkedList.includes(item.dataIndex as string)) return;
const columns = getColumns() as BasicColumn[];
const isFixed = item.fixed === fixed ? false : fixed;
const index = columns.findIndex((col) => col.dataIndex === item.dataIndex);
if (index !== -1) {
columns[index].fixed = isFixed;
}
item.fixed = isFixed;
if (isFixed && !item.width) {
item.width = 100;
}
table.setColumns(columns);
}
return {
t,
...toRefs(state),
onCheckAllChange,
onChange,
plainOptions,
reset,
prefixCls,
columnListRef,
handleVisibleChange,
checkIndex,
checkSelect,
handleIndexCheckChange,
handleSelectCheckChange,
defaultRowSelection,
handleColumnFixed,
getPopupContainer,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-column-setting';
.table-coulmn-drag-icon {
margin: 0 5px;
cursor: move;
}
.@{prefix-cls} {
&__popover-title {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
}
&__check-item {
display: flex;
align-items: center;
min-width: 100%;
padding: 4px 16px 8px 0;
.ant-checkbox-wrapper {
width: 100%;
&:hover {
color: @primary-color;
}
}
}
&__fixed-left,
&__fixed-right {
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
&.active,
&:hover {
color: @primary-color;
}
&.disabled {
color: @disabled-color;
cursor: not-allowed;
}
}
&__fixed-right {
transform: rotate(180deg);
}
&__cloumn-list {
svg {
width: 1em !important;
height: 1em !important;
}
.ant-popover-inner-content {
// max-height: 360px;
padding-right: 0;
padding-left: 0;
// overflow: auto;
}
.ant-checkbox-group {
width: 100%;
min-width: 260px;
// flex-wrap: wrap;
}
.scroll-container {
height: 220px;
}
}
}
</style>
<template>
<Tooltip placement="top">
<template #title>
<span>{{ t('component.table.settingFullScreen') }}</span>
</template>
<FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" />
<FullscreenExitOutlined @click="handleFullScreen" v-else />
</Tooltip>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useTableContext } from '../../hooks/useTableContext';
import { Tooltip } from 'ant-design-vue';
import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
import { useFullscreen } from '/@/hooks/web/useFullScreen';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
name: 'FullScreenSetting',
components: {
FullscreenExitOutlined,
FullscreenOutlined,
Tooltip,
},
setup() {
const table = useTableContext();
const { t } = useI18n();
const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef);
function handleFullScreen() {
toggleFullscreen();
}
return {
handleFullScreen,
isFullscreenRef,
t,
};
},
});
</script>
<template>
<Tooltip placement="top">
<template #title>
<span>{{ t('component.table.settingRedo') }}</span>
</template>
<RedoOutlined @click="redo" />
</Tooltip>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useTableContext } from '../../hooks/useTableContext';
import { Tooltip } from 'ant-design-vue';
import { RedoOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
name: 'RedoSetting',
components: {
RedoOutlined,
Tooltip,
},
setup() {
const table = useTableContext();
const { t } = useI18n();
function redo() {
table.reload();
}
return {
redo,
t,
};
},
});
</script>
<template>
<Tooltip placement="top">
<template #title>
<span>{{ t('component.table.settingDens') }}</span>
</template>
<Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer">
<ColumnHeightOutlined />
<template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
<MenuItem key="default">
<span>{{ t('component.table.settingDensDefault') }}</span>
</MenuItem>
<MenuItem key="middle">
<span>{{ t('component.table.settingDensMiddle') }}</span>
</MenuItem>
<MenuItem key="small">
<span>{{ t('component.table.settingDensSmall') }}</span>
</MenuItem>
</Menu>
</template>
</Dropdown>
</Tooltip>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { useTableContext } from '../../hooks/useTableContext';
import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
import { ColumnHeightOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { getPopupContainer } from '/@/utils';
import type { SizeType } from '../../types/table';
export default defineComponent({
name: 'SizeSetting',
components: {
ColumnHeightOutlined,
Tooltip,
Dropdown,
Menu,
MenuItem: Menu.Item,
},
setup() {
const table = useTableContext();
const { t } = useI18n();
const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
function handleTitleClick({ key }: { key: SizeType }) {
selectedKeysRef.value = [key];
table.setProps({
size: key,
});
}
return {
handleTitleClick,
selectedKeysRef,
getPopupContainer,
t,
};
},
});
</script>
<template>
<div class="table-settings">
<RedoSetting v-if="getSetting.size" />
<SizeSetting v-if="getSetting.redo" />
<ColumnSetting v-if="getSetting.setting" />
<FullScreenSetting v-if="getSetting.fullScreen" />
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import type { TableSetting } from '../../types/table';
import { useI18n } from '/@/hooks/web/useI18n';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import ColumnSetting from './ColumnSetting.vue';
export default defineComponent({
name: 'TableSetting',
components: {
ColumnSetting,
SizeSetting: createAsyncComponent(() => import('./SizeSetting.vue')),
RedoSetting: createAsyncComponent(() => import('./RedoSetting.vue')),
FullScreenSetting: createAsyncComponent(() => import('./FullScreenSetting.vue')),
},
props: {
setting: {
type: Object as PropType<TableSetting>,
default: {},
},
},
setup(props) {
const { t } = useI18n();
const getSetting = computed(
(): TableSetting => {
return {
redo: true,
size: true,
setting: true,
fullScreen: true,
...props.setting,
};
}
);
return { getSetting, t };
},
});
</script>
<style lang="less">
.table-settings {
& > * {
margin-right: 12px;
}
svg {
width: 1.3em;
height: 1.3em;
}
}
</style>
import { BasicColumn, BasicTableProps, GetColumnsParams } from '../types/table';
import { PaginationProps } from '../types/pagination';
import { unref, ComputedRef, Ref, computed, watchEffect, ref, toRaw } from 'vue';
import { isBoolean, isArray, isObject } from '/@/utils/is';
import { isBoolean, isArray, isString } from '/@/utils/is';
import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const';
import { useI18n } from '/@/hooks/web/useI18n';
import { isEqual, cloneDeep } from 'lodash-es';
const { t } = useI18n();
......@@ -107,27 +108,31 @@ export function useColumns(
const getColumnsRef = computed(() => {
const columns = unref(columnsRef);
if (!columns) {
return [];
}
handleIndexColumn(propsRef, getPaginationRef, columns);
handleActionColumn(propsRef, columns);
if (!columns) {
return [];
}
return columns;
});
const getSortFixedColumns = computed(() => {
return useFixedColumn(unref(getColumnsRef));
});
watchEffect(() => {
const columns = toRaw(unref(propsRef).columns);
columnsRef.value = columns;
cacheColumns = columns;
cacheColumns = columns?.filter((item) => !item.flag) ?? [];
});
/**
* set columns
* @param columns key|column
* @param columnList key|column
*/
function setColumns(columns: Partial<BasicColumn>[] | string[]) {
function setColumns(columnList: Partial<BasicColumn>[] | string[]) {
const columns = cloneDeep(columnList);
if (!isArray(columns)) return;
if (columns.length <= 0) {
......@@ -137,20 +142,36 @@ export function useColumns(
const firstColumn = columns[0];
if (isObject(firstColumn)) {
const cacheKeys = cacheColumns.map((item) => item.dataIndex);
if (!isString(firstColumn)) {
columnsRef.value = columns as BasicColumn[];
} else {
const newColumns = cacheColumns.filter(
(item) =>
(item.dataIndex || `${item.key}`) &&
(columns as string[]).includes(`${item.key}`! || item.dataIndex!)
);
const columnKeys = columns as string[];
const newColumns: BasicColumn[] = [];
cacheColumns.forEach((item) => {
if (columnKeys.includes(`${item.key}`! || item.dataIndex!)) {
newColumns.push({
...item,
defaultHidden: false,
});
}
});
// Sort according to another array
if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => {
return (
columnKeys.indexOf(prev.dataIndex as string) -
columnKeys.indexOf(next.dataIndex as string)
);
});
}
columnsRef.value = newColumns;
}
}
function getColumns(opt?: GetColumnsParams) {
const { ignoreIndex, ignoreAction } = opt || {};
const { ignoreIndex, ignoreAction, sort } = opt || {};
let columns = toRaw(unref(getColumnsRef));
if (ignoreIndex) {
columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG);
......@@ -158,8 +179,38 @@ export function useColumns(
if (ignoreAction) {
columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG);
}
if (sort) {
columns = useFixedColumn(columns);
}
return columns;
}
function getCacheColumns() {
return cacheColumns;
}
return { getColumnsRef, getCacheColumns, getColumns, setColumns, getSortFixedColumns };
}
export function useFixedColumn(columns: BasicColumn[]) {
const fixedLeftColumns: BasicColumn[] = [];
const fixedRightColumns: BasicColumn[] = [];
const defColumns: BasicColumn[] = [];
for (const column of columns) {
if (column.fixed === 'left') {
fixedLeftColumns.push(column);
continue;
}
if (column.fixed === 'right') {
fixedRightColumns.push(column);
continue;
}
defColumns.push(column);
}
const resultColumns = [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(
(item) => !item.defaultHidden
);
return { getColumnsRef, getColumns, setColumns };
return resultColumns;
}
......@@ -53,7 +53,12 @@ export function useRowSelection(propsRef: ComputedRef<BasicTableProps>, emit: Em
return unref(selectedRowRef) as T[];
}
function getRowSelection() {
return unref(getRowSelectionRef)!;
}
return {
getRowSelection,
getRowSelectionRef,
getSelectRows,
getSelectRowKeys,
......
import type { Ref } from 'vue';
import type { BasicTableProps, TableActionType } from '../types/table';
import { provide, inject, ComputedRef } from 'vue';
const key = Symbol('basic-table');
type Instance = TableActionType & {
wrapRef: Ref<Nullable<HTMLElement>>;
getBindValues: ComputedRef<Recordable>;
};
type RetInstance = Omit<Instance, 'getBindValues'> & {
getBindValues: ComputedRef<BasicTableProps>;
};
export function createTableContext(instance: Instance) {
provide(key, instance);
}
export function useTableContext(): RetInstance {
return inject(key) as RetInstance;
}
import type { ComputedRef, Ref } from 'vue';
import type { BasicTableProps } from '../types/table';
import { unref, computed, h, nextTick, watchEffect } from 'vue';
import TableFooter from '../components/TableFooter.vue';
import { useEventListener } from '/@/hooks/event/useEventListener';
export function useTableFooter(
propsRef: ComputedRef<BasicTableProps>,
scrollRef: ComputedRef<{
x: string | number | true;
y: Nullable<number>;
scrollToFirstRowOnChange: boolean;
}>,
tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable>
) {
const getIsEmptyData = computed(() => {
return (unref(getDataSourceRef) || []).length === 0;
});
const getFooterProps = computed((): Recordable | undefined => {
const { summaryFunc, showSummary } = unref(propsRef);
return showSummary && !unref(getIsEmptyData)
? () => h(TableFooter, { summaryFunc, scroll: unref(scrollRef) })
: undefined;
});
watchEffect(() => {
handleSummary();
});
function handleSummary() {
const { showSummary } = unref(propsRef);
if (!showSummary || unref(getIsEmptyData)) return;
nextTick(() => {
const tableEl = unref(tableElRef);
if (!tableEl) return;
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
const bodyDom = bodyDomList[0];
useEventListener({
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-body'
) as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
},
wait: 0,
options: true,
});
});
}
return { getFooterProps };
}
import type { ComputedRef, Slots } from 'vue';
import type { BasicTableProps, FetchParams } from '../types/table';
import { unref, computed } from 'vue';
import type { FormProps } from '/@/components/Form';
import { isFunction } from '/@/utils/is';
export function useTableForm(
propsRef: ComputedRef<BasicTableProps>,
slots: Slots,
fetch: (opt?: FetchParams | undefined) => Promise<void>
) {
const getFormProps = computed(
(): Partial<FormProps> => {
const { formConfig } = unref(propsRef);
return {
showAdvancedButton: true,
...formConfig,
compact: true,
};
}
);
const getFormSlotKeys = computed(() => {
const keys = Object.keys(slots);
return keys.map((item) => (item.startsWith('form-') ? item : null)).filter(Boolean);
});
function replaceFormSlotKey(key: string) {
if (!key) return '';
return key?.replace?.(/form\-/, '') ?? '';
}
function handleSearchInfoChange(info: Recordable) {
const { handleSearchInfoFn } = unref(propsRef);
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
info = handleSearchInfoFn(info) || info;
}
fetch({ searchInfo: info, page: 1 });
}
return {
getFormProps,
replaceFormSlotKey,
getFormSlotKeys,
handleSearchInfoChange,
};
}
import type { ComputedRef, Slots } from 'vue';
import type { BasicTableProps } from '../types/table';
import { unref, computed, h } from 'vue';
import { isString } from '/@/utils/is';
import TableHeader from '../components/TableHeader.vue';
import { getSlot } from '../../../../utils/helper/tsxHelper';
export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots) {
const getHeaderProps = computed(
(): Recordable => {
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
if (hideTitle && !isString(title)) {
return {};
}
return {
title: hideTitle
? null
: () =>
h(
TableHeader,
{
title,
titleHelpMessage,
showTableSetting,
tableSetting,
},
{
...(slots.toolbar
? {
toolbar: () => getSlot(slots, 'toolbar'),
}
: {}),
...(slots.tableTitle
? {
tableTitle: () => getSlot(slots, 'tableTitle'),
}
: {}),
}
),
};
}
);
return { getHeaderProps };
}
import type { BasicTableProps } from '../types/table';
import type { BasicTableProps, TableRowSelection } from '../types/table';
import type { Ref, ComputedRef } from 'vue';
import { computed, unref, ref, nextTick, watchEffect } from 'vue';
......@@ -7,22 +7,29 @@ import { isBoolean } from '/@/utils/is';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { useModalContext } from '/@/components/Modal';
import { useDebounce } from '/@/hooks/core/useDebounce';
import type { BasicColumn } from '/@/components/Table';
export function useTableScroll(
propsRef: ComputedRef<BasicTableProps>,
tableElRef: Ref<ComponentRef>
tableElRef: Ref<ComponentRef>,
columnsRef: ComputedRef<BasicColumn[]>,
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>
) {
const tableHeightRef: Ref<Nullable<number>> = ref(null);
const modalFn = useModalContext();
// const [debounceCalcTableHeight] = useDebounce(calcTableHeight, 80);
const [debounceRedoHeight] = useDebounce(redoHeight, 250);
const getCanResize = computed(() => {
const { canResize, scroll } = unref(propsRef);
return canResize && !(scroll || {}).y;
});
watchEffect(() => {
redoHeight();
unref(getCanResize) && debounceRedoHeight();
});
function redoHeight() {
......@@ -33,6 +40,12 @@ export function useTableScroll(
}
}
function setHeight(heigh: number) {
tableHeightRef.value = heigh;
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
modalFn?.redoModalHeight?.();
}
// No need to repeat queries
let paginationEl: HTMLElement | null;
let footerEl: HTMLElement | null;
......@@ -87,7 +100,7 @@ export function useTableScroll(
headerHeight = (headEl as HTMLElement).offsetHeight;
}
const height =
let height =
bottomIncludeBody -
(resizeHeightOffset || 0) -
paddingHeight -
......@@ -96,21 +109,41 @@ export function useTableScroll(
footerHeight -
headerHeight;
setTimeout(() => {
tableHeightRef.value = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
modalFn?.redoModalHeight?.();
}, 0);
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
setHeight(height);
}
useWindowSizeFn(calcTableHeight, 100);
useWindowSizeFn(calcTableHeight, 200);
const getScrollX = computed(() => {
let width = 0;
if (unref(rowSelectionRef)) {
width += 60;
}
// TODO props
const NORMAL_WIDTH = 150;
const columns = unref(columnsRef);
columns.forEach((item) => {
width += Number.parseInt(item.width as string) || 0;
});
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
const len = unsetWidthColumns.length;
if (len !== 0) {
width += len * NORMAL_WIDTH;
}
return width;
});
const getScrollRef = computed(() => {
const tableHeight = unref(tableHeightRef);
const { canResize, scroll } = unref(propsRef);
return {
x: '100%',
x: unref(getScrollX),
y: canResize ? tableHeight : null,
scrollToFirstRowOnChange: false,
...scroll,
......
......@@ -2,14 +2,14 @@ import type { ComputedRef } from 'vue';
import type { BasicTableProps, TableCustomRecord } from '../types/table';
import { unref } from 'vue';
import { isFunction } from '/@/utils/is';
export function useTableStyle(propsRef: ComputedRef<BasicTableProps>) {
export function useTableStyle(propsRef: ComputedRef<BasicTableProps>, prefixCls: string) {
function getRowClassName(record: TableCustomRecord, index: number) {
const { striped, rowClassName } = unref(propsRef);
if (!striped) return;
if (rowClassName && isFunction(rowClassName)) {
return rowClassName(record);
}
return (index || 0) % 2 === 1 ? 'basic-table-row__striped' : '';
return (index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : '';
}
return {
......
......@@ -75,7 +75,7 @@ export const basicProps = {
},
columns: {
type: [Array] as PropType<BasicColumn[]>,
default: null,
default: () => [],
},
showIndexColumn: propTypes.bool.def(true),
indexColumnProps: {
......@@ -95,7 +95,7 @@ export const basicProps = {
default: null,
},
title: {
type: [String, Function] as PropType<string | ((data: any) => any)>,
type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
default: null,
},
titleHelpMessage: {
......
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'editable-cell';
.@{prefix-cls} {
......
@import (reference) '../../../../design/index.less';
@border-color: #cecece4d;
.basic-table {
&-title {
display: flex;
justify-content: space-between;
align-items: center;
@prefix-cls: ~'@{namespace}-basic-table';
.@{prefix-cls} {
&-form-container {
padding: 16px;
.ant-form {
padding: 20px 20px 4px 12px;
margin-bottom: 18px;
background: #fff;
border-radius: 4px;
}
.ant-table-wrapper {
border-radius: 2px;
}
}
&-row__striped {
......@@ -22,28 +32,16 @@
}
}
&-action {
display: flex;
button {
display: flex;
align-items: center;
}
}
&-toolbar {
display: flex;
align-items: center;
> * {
margin-right: 10px;
&--inset {
.ant-table-wrapper {
padding: 0;
}
}
.ant-table-wrapper {
padding: 8px;
background: #fff;
border-radius: 2px;
border-radius: 4px;
.ant-table-title {
padding: 0 0 8px 0 !important;
......@@ -54,12 +52,6 @@
}
}
&.inset {
.ant-table-wrapper {
padding: 0;
}
}
//
.ant-table {
border: none;
......@@ -194,18 +186,3 @@
}
}
}
.table-form-container {
padding: 16px;
.ant-form {
padding: 20px 20px 4px 12px;
margin-bottom: 18px;
background: #fff;
border-radius: 2px;
}
.ant-table-wrapper {
border-radius: 2px;
}
}
......@@ -82,6 +82,7 @@ export interface FetchParams {
export interface GetColumnsParams {
ignoreIndex?: boolean;
ignoreAction?: boolean;
sort?: boolean;
}
export type SizeType = 'default' | 'middle' | 'small' | 'large';
......@@ -93,16 +94,18 @@ export interface TableActionType {
getSelectRowKeys: () => string[];
deleteSelectRowByKey: (key: string) => void;
setPagination: (info: Partial<PaginationProps>) => void;
setTableData: <T = any>(values: T[]) => void;
setTableData: <T = Recordable>(values: T[]) => void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void;
getDataSource: <T = any>() => T[];
getDataSource: <T = Recordable>() => T[];
setLoading: (loading: boolean) => void;
setProps: (props: Partial<BasicTableProps>) => void;
redoHeight: () => void;
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
getPaginationRef: () => PaginationProps | boolean;
getSize: () => SizeType;
getRowSelection: () => TableRowSelection<Recordable>;
getCacheColumns: () => BasicColumn[];
}
export interface FetchSetting {
......@@ -308,7 +311,7 @@ export interface BasicTableProps<T = any> {
* Table title renderer
* @type Function | ScopedSlot
*/
title?: VNodeChild | JSX.Element;
title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string);
/**
* Set props on per header row
......@@ -378,4 +381,6 @@ export interface BasicColumn extends ColumnProps {
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
slots?: Indexable;
defaultHidden?: boolean;
}
......@@ -5,6 +5,8 @@ export interface ActionItem extends ButtonProps {
color?: 'success' | 'error' | 'warning';
icon?: string;
popConfirm?: PopConfirm;
disabled?: boolean;
divider?: boolean;
}
export interface PopConfirm {
......
......@@ -18,6 +18,10 @@
}
}
.ant-image-preview-operations {
background: rgba(0, 0, 0, 0.3);
}
// =================================
// ==============descriptions=======
// =================================
......
@import 'color.less';
@import 'var/index.less';
@import 'mixins.less';
@import './transition/index.less';
@import 'transition/index.less';
@import 'var/index.less';
@import 'public.less';
@import 'mixins.less';
@import 'ant/index.less';
@import './global.less';
@import 'global.less';
*,
*::before,
......
......@@ -34,3 +34,9 @@
// left-menu
@app-menu-item-height: 42px;
.bem(@n;@content) {
@{namespace}-@{n} {
@content();
}
}
import type { UnwrapRef } from 'vue';
import { reactive, readonly, computed, getCurrentInstance } from 'vue';
import { reactive, readonly, computed, getCurrentInstance, watchEffect } from 'vue';
import { isEqual } from 'lodash-es';
......@@ -20,6 +20,11 @@ export function useRuleFormItem<T extends Indexable>(
const setState = (val: UnwrapRef<T[keyof T]>) => {
innerState.value = val as T[keyof T];
};
watchEffect(() => {
innerState.value = props[key];
});
const state = computed({
get() {
return innerState.value;
......
......@@ -25,7 +25,6 @@ export function createContext<T>(
const { readonly = true, createProvider = false } = options;
const state = reactive(context);
const provideData = readonly ? defineReadonly(state) : state;
!createProvider && provide(key, provideData);
......
......@@ -17,7 +17,7 @@ type FSEPropName =
| 'fullscreenElement';
export function useFullscreen(
target: Ref<Nullable<HTMLElement>> = ref(document.documentElement),
target: Ref<Nullable<HTMLElement>> | Nullable<HTMLElement> = ref(document.documentElement),
options?: FullscreenOptions
) {
const isFullscreenRef = ref(false);
......@@ -43,7 +43,7 @@ export function useFullscreen(
}
function enterFullscreen(): Promise<void> {
isFullscreenRef.value = true;
return (target.value as any)[RFC_METHOD_NAME](options);
return (unref(target) as any)[RFC_METHOD_NAME](options);
}
function exitFullscreen(): Promise<void> {
......@@ -55,7 +55,9 @@ export function useFullscreen(
return unref(target) === (document as any)[FSE_PROP_NAME];
}
function toggleFullscreen(): Promise<void> {
async function toggleFullscreen(): Promise<void> {
if (!unref(target)) return;
if (isFullscreen()) {
return exitFullscreen();
} else {
......
import Sortable from 'sortablejs';
import { nextTick, unref } from 'vue';
import type { Ref } from 'vue';
export function useSortable(el: HTMLElement | Ref<HTMLElement>, options?: Sortable.Options) {
function initSortable() {
nextTick(() => {
if (!el) return;
Sortable.create(unref(el), {
animation: 500,
delay: 400,
delayOnTouchOnly: true,
...options,
});
});
}
return { initSortable };
}
import Sortable from 'sortablejs';
import { toRaw, ref, nextTick, onMounted } from 'vue';
import { toRaw, ref, nextTick } from 'vue';
import { RouteLocationNormalized } from 'vue-router';
import { useProjectSetting } from '/@/hooks/setting';
import { useDesign } from '/@/hooks/web/useDesign';
import { useSortable } from '/@/hooks/web/useSortable';
import router from '/@/router';
import { tabStore } from '/@/store/modules/tab';
import { isNullAndUnDef } from '/@/utils/is';
......@@ -50,36 +50,25 @@ export function useTabsDrag(affixTextList: string[]) {
const { multiTabsSetting } = useProjectSetting();
const { prefixCls } = useDesign('multiple-tabs');
function initSortableTabs() {
nextTick(() => {
if (!multiTabsSetting.canDrag) return;
nextTick(() => {
const el = document.querySelectorAll(`.${prefixCls} .ant-tabs-nav > div`)?.[0] as HTMLElement;
if (!el) return;
Sortable.create(el, {
animation: 500,
delay: 400,
delayOnTouchOnly: true,
filter: (e: ChangeEvent) => {
const text = e?.target?.innerText;
if (!text) return false;
return affixTextList.includes(text);
},
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
const el = document.querySelectorAll(`.${prefixCls} .ant-tabs-nav > div`)?.[0] as HTMLElement;
const { initSortable } = useSortable(el, {
filter: (e: ChangeEvent) => {
const text = e?.target?.innerText;
if (!text) return false;
return affixTextList.includes(text);
},
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return;
}
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return;
}
tabStore.commitSortTabs({ oldIndex, newIndex });
},
});
tabStore.commitSortTabs({ oldIndex, newIndex });
},
});
}
onMounted(() => {
initSortableTabs();
initSortable();
});
}
......@@ -5,4 +5,7 @@ export default {
toSearch: 'to search',
toNavigate: 'to navigate',
toClose: 'to close',
okText: 'Confirm',
cancelText: 'Cancel',
};
......@@ -6,7 +6,11 @@ export default {
settingDensSmall: 'Compact',
settingColumn: 'Column settings',
settingColumnShow: 'Column display',
settingIndexColumnShow: 'Index Column',
settingSelectColumnShow: 'Selection Column',
settingReset: 'Reset',
settingFixedLeft: 'Fixed Left',
settingFixedRight: 'Fixed Right',
settingFullScreen: 'Full Screen',
index: 'Index',
......
......@@ -5,4 +5,6 @@ export default {
toSearch: '确认',
toNavigate: '切换',
toClose: '关闭',
okText: '确认',
cancelText: '取消',
};
......@@ -7,6 +7,10 @@ export default {
settingColumn: '列设置',
settingColumnShow: '列展示',
settingReset: '重置',
settingIndexColumnShow: '序号列',
settingSelectColumnShow: '勾选列',
settingFixedLeft: '固定到左侧',
settingFixedRight: '固定到右侧',
settingFullScreen: '全屏',
index: '序号',
......
......@@ -11,7 +11,6 @@ import { PROJ_CFG_KEY } from '/@/enums/cacheEnum';
import projectSetting from '/@/settings/projectSetting';
import { getLocal } from '/@/utils/helper/persistent';
import { isUnDef, isNull } from '/@/utils/is';
import {
updateGrayMode,
updateColorWeak,
......@@ -76,16 +75,3 @@ export function initAppConfigStore() {
}
appStore.commitProjectConfigState(projCfg);
}
// antdv Config Provider
export function getConfigProvider() {
function transformCellText({ text }: { text: string }) {
if (isNull(text) || isUnDef(text)) {
return ' - ';
}
return text;
}
return {
transformCellText,
};
}
......@@ -182,7 +182,7 @@ class Tab extends VuexModule {
@Action
addTabAction(route: RouteLocationNormalized) {
const { path, name } = route;
// 404 页面不需要添加tab
// 404 The page does not need to add a tab
if (
path === PageEnum.ERROR_PAGE ||
!name ||
......
......@@ -106,12 +106,13 @@ class User extends VuexModule {
const data = await loginApi(loginParams, mode);
const { token, userId } = data;
// get user info
const userInfo = await this.getUserInfoAction({ userId });
// save token
this.commitTokenState(token);
// get user info
const userInfo = await this.getUserInfoAction({ userId });
// const name = FULL_PAGE_NOT_FOUND_ROUTE.name;
// name && router.removeRoute(name);
goHome && (await router.replace(PageEnum.BASE_HOME));
......
......@@ -8,10 +8,7 @@ export const now = () => Date.now();
* @description: Set ui mount node
*/
export function getPopupContainer(node?: HTMLElement): HTMLElement {
if (node) {
return node.parentNode as HTMLElement;
}
return document.body;
return (node?.parentNode as HTMLElement) ?? document.body;
}
/**
......
......@@ -224,6 +224,7 @@
colProps: {
span: 8,
},
defaultValue: '0',
},
{
field: 'field20',
......
<template>
<BasicTable @register="registerTable" />
<BasicTable @register="registerTable">
<template #form-custom> custom-slot</template>
</BasicTable>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
......@@ -18,6 +20,7 @@
useSearchForm: true,
formConfig: getFormConfig(),
showTableSetting: true,
rowSelection: { type: 'checkbox' },
});
return {
......
......@@ -6,7 +6,8 @@ export function getBasicColumns(): BasicColumn[] {
{
title: 'ID',
dataIndex: 'id',
width: 150,
fixed: 'left',
width: 400,
},
{
title: '姓名',
......@@ -21,6 +22,7 @@ export function getBasicColumns(): BasicColumn[] {
title: '编号',
dataIndex: 'no',
width: 150,
defaultHidden: true,
},
{
title: '开始时间',
......@@ -42,6 +44,8 @@ export function getBasicShortColumns(): BasicColumn[] {
title: 'ID',
width: 150,
dataIndex: 'id',
sorter: true,
sortOrder: 'ascend',
},
{
title: '姓名',
......@@ -118,6 +122,7 @@ export function getCustomHeaderColumns(): BasicColumn[] {
{
// title: '地址',
dataIndex: 'address',
width: 120,
slots: { title: 'customAddress' },
sorter: true,
},
......@@ -236,6 +241,7 @@ export function getFormConfig(): Partial<FormProps> {
label: `字段33`,
component: 'Select',
defaultValue: '1',
slot: 'custom',
componentProps: {
options: [
{
......
......@@ -78,7 +78,11 @@ export default (mode: 'development' | 'production'): UserConfig => {
cssPreprocessOptions: {
less: {
modifyVars: modifyVars,
modifyVars: {
// reference : Avoid repeated references
hack: `true; @import (reference) "${resolve('src/design/config.less')}";`,
...modifyVars,
},
javascriptEnabled: true,
},
},
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册