Commit 9c2f3f30bbd8abcccc4f256183ed7794da7fcda2

Authored by vben
1 parent f3a70eed

refactor(table): refactor table #150 #148 #146 #130 #76

Showing 35 changed files with 1099 additions and 454 deletions
CHANGELOG.zh_CN.md
1 1 ## Wip
2 2  
  3 +### ✨ 表格破坏性更新
  4 +
  5 +- 重构了可编辑单元格及可编辑行。具体看示例。写法已改变。针对可编辑表格。
  6 +
  7 +- 表格编辑支持表单校验
  8 +
  9 +- 在表格列配置增加了以下配置
  10 +
  11 +```bash
  12 +{
  13 +
  14 + # 默认是否显示列。不显示的可以在列配置打开
  15 + defaultHidden?: boolean;
  16 + # 列头右侧帮助文本
  17 + helpMessage?: string | string[];
  18 + # 自定义格式化 单元格内容。 支持时间/枚举自动转化
  19 + format?: CellFormat;
  20 +
  21 + # Editable
  22 + # 是否是可编辑单元格
  23 + edit?: boolean;
  24 + # 是否是可编辑行
  25 + editRow?: boolean;
  26 + # 编辑状态。
  27 + editable?: boolean;
  28 + # 编辑组件
  29 + editComponent?: ComponentType;
  30 + # 所对应组件的参数
  31 + editComponentProps?: Recordable;
  32 + # 校验
  33 + editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
  34 + # 值枚举转化
  35 + editValueMap?: (value: any) => string;
  36 + # 触发编辑正航
  37 + record.onEditRow?: () => void;
  38 +}
  39 +
  40 +```
  41 +
  42 +### ✨ 表格重构
  43 +
  44 +- 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选框
  45 +- 监听行点击事件
  46 +- 表格列配置按钮增加 列拖拽,列固定功能。
  47 +- 表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示
  48 +- 更强大的列配置
  49 +- useTable:支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
  50 +- useTable:新增返回 `getForm`函数。可以用于操作表格内的表单
  51 +- 修复表格已知的问题
  52 +
3 53 ### ✨ Features
4 54  
5 55 - 新增 `v-ripple`水波纹指令
... ... @@ -12,14 +62,6 @@
12 62 - form: 新增远程下拉`ApiSelect`及示例
13 63 - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
14 64 - useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
15   -- table: 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选狂
16   -- table: 监听行点击事件
17   -- table: 表格列配置按钮增加 列拖拽,列固定功能。
18   -- table:表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示
19   -
20   -### ✨ Refactor
21   -
22   -- 重构表单,解决已知 bug
23 65  
24 66 ### ⚡ Performance Improvements
25 67  
... ... @@ -30,6 +72,7 @@
30 72 ### 🎫 Chores
31 73  
32 74 - 升级`ant-design-vue`到`2.0.0-rc.7`
  75 +- 升级`vue`到`3.0.5`
33 76  
34 77 ### 🐛 Bug Fixes
35 78  
... ...
mock/demo/table-demo.ts
... ... @@ -10,6 +10,14 @@ const demoList = (() =&gt; {
10 10 endTime: '@datetime',
11 11 address: '@city()',
12 12 name: '@cname()',
  13 + name1: '@cname()',
  14 + name2: '@cname()',
  15 + name3: '@cname()',
  16 + name4: '@cname()',
  17 + name5: '@cname()',
  18 + name6: '@cname()',
  19 + name7: '@cname()',
  20 + name8: '@cname()',
13 21 'no|100000-10000000': 100000,
14 22 'status|1': ['normal', 'enable', 'disable'],
15 23 });
... ...
src/components/Form/index.ts
... ... @@ -9,4 +9,7 @@ export * from &#39;./src/types/formItem&#39;;
9 9 export { useComponentRegister } from './src/hooks/useComponentRegister';
10 10 export { useForm } from './src/hooks/useForm';
11 11  
  12 +export { default as ApiSelect } from './src/components/ApiSelect.vue';
  13 +export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
  14 +
12 15 export { BasicForm };
... ...
src/components/Form/src/components/ApiSelect.vue
... ... @@ -50,7 +50,8 @@
50 50 labelField: propTypes.string.def('label'),
51 51 valueField: propTypes.string.def('value'),
52 52 },
53   - setup(props) {
  53 + emits: ['options-change', 'change'],
  54 + setup(props, { emit }) {
54 55 const options = ref<OptionsItem[]>([]);
55 56 const loading = ref(false);
56 57 const attrs = useAttrs();
... ... @@ -86,11 +87,13 @@
86 87 const res = await api(props.params);
87 88 if (Array.isArray(res)) {
88 89 options.value = res;
  90 + emit('options-change', unref(options));
89 91 return;
90 92 }
91 93 if (props.resultField) {
92 94 options.value = get(res, props.resultField) || [];
93 95 }
  96 + emit('options-change', unref(options));
94 97 } catch (error) {
95 98 console.warn(error);
96 99 } finally {
... ...
src/components/Menu/src/useOpenKeys.ts
... ... @@ -15,7 +15,7 @@ export function useOpenKeys(
15 15 mode: Ref<MenuModeEnum>,
16 16 accordion: Ref<boolean>
17 17 ) {
18   - const { getCollapsed } = useMenuSetting();
  18 + const { getCollapsed, getIsMixSidebar } = useMenuSetting();
19 19  
20 20 function setOpenKeys(path: string) {
21 21 if (mode.value === MenuModeEnum.HORIZONTAL) {
... ... @@ -30,7 +30,9 @@ export function useOpenKeys(
30 30 }
31 31  
32 32 const getOpenKeys = computed(() => {
33   - return unref(getCollapsed) ? menuState.collapsedOpenKeys : menuState.openKeys;
  33 + const collapse = unref(getIsMixSidebar) ? false : unref(getCollapsed);
  34 +
  35 + return collapse ? menuState.collapsedOpenKeys : menuState.openKeys;
34 36 });
35 37  
36 38 /**
... ... @@ -42,7 +44,7 @@ export function useOpenKeys(
42 44 }
43 45  
44 46 function handleOpenChange(openKeys: string[]) {
45   - if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion)) {
  47 + if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion) || unref(getIsMixSidebar)) {
46 48 menuState.openKeys = openKeys;
47 49 } else {
48 50 // const menuList = toRaw(menus.value);
... ...
src/components/Table/index.ts
... ... @@ -3,7 +3,6 @@ import { createAsyncComponent } from &#39;/@/utils/factory/createAsyncComponent&#39;;
3 3 export { default as BasicTable } from './src/BasicTable.vue';
4 4 export { default as TableAction } from './src/components/TableAction.vue';
5 5 // export { default as TableImg } from './src/components/TableImg.vue';
6   -export { renderEditableCell, renderEditableRow } from './src/components/renderEditable';
7 6 export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
8 7  
9 8 export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue'));
... ... @@ -17,4 +16,4 @@ export { useTable } from &#39;./src/hooks/useTable&#39;;
17 16  
18 17 export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';
19 18  
20   -export type { EditRecordRow } from './src/components/renderEditable';
  19 +export type { EditRecordRow } from './src/components/editable';
... ...
src/components/Table/src/BasicTable.vue
... ... @@ -34,19 +34,19 @@
34 34 <template #[item]="data" v-for="item in Object.keys($slots)">
35 35 <slot :name="item" v-bind="data" />
36 36 </template>
  37 + <template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex">
  38 + <HeaderCell :column="column" />
  39 + </template>
37 40 </Table>
38 41 </div>
39 42 </template>
40 43 <script lang="ts">
41   - import type { BasicTableProps, TableActionType, SizeType, SorterResult } from './types/table';
42   - import { PaginationProps } from './types/pagination';
  44 + import type { BasicTableProps, TableActionType, SizeType } from './types/table';
43 45  
44 46 import { defineComponent, ref, computed, unref } from 'vue';
45 47 import { Table } from 'ant-design-vue';
46 48 import { BasicForm, useForm } from '/@/components/Form/index';
47 49  
48   - import { isFunction } from '/@/utils/is';
49   -
50 50 import { omit } from 'lodash-es';
51 51  
52 52 import { usePagination } from './hooks/usePagination';
... ... @@ -61,15 +61,20 @@
61 61 import { createTableContext } from './hooks/useTableContext';
62 62 import { useTableFooter } from './hooks/useTableFooter';
63 63 import { useTableForm } from './hooks/useTableForm';
  64 + import { useExpose } from '/@/hooks/core/useExpose';
  65 + import { useDesign } from '/@/hooks/web/useDesign';
64 66  
65 67 import { basicProps } from './props';
66   - import { useExpose } from '/@/hooks/core/useExpose';
  68 + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
67 69  
68 70 import './style/index.less';
69   - import { useDesign } from '/@/hooks/web/useDesign';
70 71 export default defineComponent({
71 72 props: basicProps,
72   - components: { Table, BasicForm },
  73 + components: {
  74 + Table,
  75 + BasicForm,
  76 + HeaderCell: createAsyncComponent(() => import('./components/HeaderCell.vue')),
  77 + },
73 78 emits: [
74 79 'fetch-success',
75 80 'fetch-error',
... ... @@ -80,6 +85,8 @@
80 85 'row-contextmenu',
81 86 'row-mouseenter',
82 87 'row-mouseleave',
  88 + 'edit-end',
  89 + 'edit-cancel',
83 90 ],
84 91 setup(props, { attrs, emit, slots }) {
85 92 const tableElRef = ref<ComponentRef>(null);
... ... @@ -96,15 +103,19 @@
96 103  
97 104 const { getLoading, setLoading } = useLoading(getProps);
98 105 const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
  106 +
99 107 const {
100   - getSortFixedColumns,
101   - getColumns,
102   - setColumns,
103   - getColumnsRef,
104   - getCacheColumns,
105   - } = useColumns(getProps, getPaginationInfo);
  108 + getRowSelection,
  109 + getRowSelectionRef,
  110 + getSelectRows,
  111 + clearSelectedRowKeys,
  112 + getSelectRowKeys,
  113 + deleteSelectRowByKey,
  114 + setSelectedRowKeys,
  115 + } = useRowSelection(getProps, emit);
106 116  
107 117 const {
  118 + handleTableChange,
108 119 getDataSourceRef,
109 120 getDataSource,
110 121 setTableData,
... ... @@ -112,6 +123,7 @@
112 123 getRowKey,
113 124 reload,
114 125 getAutoCreateKey,
  126 + updateTableData,
115 127 } = useDataSource(
116 128 getProps,
117 129 {
... ... @@ -119,19 +131,15 @@
119 131 setLoading,
120 132 setPagination,
121 133 getFieldsValue: formActions.getFieldsValue,
  134 + clearSelectedRowKeys,
122 135 },
123 136 emit
124 137 );
125 138  
126   - const {
127   - getRowSelection,
128   - getRowSelectionRef,
129   - getSelectRows,
130   - clearSelectedRowKeys,
131   - getSelectRowKeys,
132   - deleteSelectRowByKey,
133   - setSelectedRowKeys,
134   - } = useRowSelection(getProps, emit);
  139 + const { getViewColumns, getColumns, setColumns, getColumnsRef, getCacheColumns } = useColumns(
  140 + getProps,
  141 + getPaginationInfo
  142 + );
135 143  
136 144 const { getScrollRef, redoHeight } = useTableScroll(
137 145 getProps,
... ... @@ -178,7 +186,7 @@
178 186 tableLayout: 'fixed',
179 187 rowSelection: unref(getRowSelectionRef),
180 188 rowKey: unref(getRowKey),
181   - columns: unref(getSortFixedColumns),
  189 + columns: unref(getViewColumns),
182 190 pagination: unref(getPaginationInfo),
183 191 dataSource: unref(getDataSourceRef),
184 192 footer: unref(getFooterProps),
... ... @@ -197,26 +205,6 @@
197 205 return !!unref(getDataSourceRef).length;
198 206 });
199 207  
200   - function handleTableChange(
201   - pagination: PaginationProps,
202   - // @ts-ignore
203   - filters: Partial<Recordable<string[]>>,
204   - sorter: SorterResult
205   - ) {
206   - const { clearSelectOnPageChange, sortFn } = unref(getProps);
207   - if (clearSelectOnPageChange) {
208   - clearSelectedRowKeys();
209   - }
210   - setPagination(pagination);
211   -
212   - if (sorter && isFunction(sortFn)) {
213   - const sortInfo = sortFn(sorter);
214   - fetch({ sortInfo });
215   - return;
216   - }
217   - fetch();
218   - }
219   -
220 208 function setProps(props: Partial<BasicTableProps>) {
221 209 innerPropsRef.value = { ...unref(innerPropsRef), ...props };
222 210 }
... ... @@ -239,6 +227,8 @@
239 227 getPaginationRef: getPagination,
240 228 getColumns,
241 229 getCacheColumns,
  230 + emit,
  231 + updateTableData,
242 232 getSize: () => {
243 233 return unref(getBindValues).size as SizeType;
244 234 },
... ... @@ -265,6 +255,7 @@
265 255 replaceFormSlotKey,
266 256 getFormSlotKeys,
267 257 prefixCls,
  258 + columns: getViewColumns,
268 259 };
269 260 },
270 261 });
... ...
src/components/Table/src/componentMap.ts
1   -import { Component } from 'vue';
  1 +import type { Component } from 'vue';
2 2  
3 3 import { Input, Select, Checkbox, InputNumber, Switch } from 'ant-design-vue';
4 4  
5   -import { ComponentType } from './types/componentType';
  5 +import type { ComponentType } from './types/componentType';
  6 +import { ApiSelect } from '/@/components/Form';
6 7  
7 8 const componentMap = new Map<ComponentType, Component>();
8 9  
9 10 componentMap.set('Input', Input);
10   -componentMap.set('InputPassword', Input.Password);
11 11 componentMap.set('InputNumber', InputNumber);
12 12  
13 13 componentMap.set('Select', Select);
  14 +componentMap.set('ApiSelect', ApiSelect);
14 15 componentMap.set('Switch', Switch);
15 16 componentMap.set('Checkbox', Checkbox);
16   -componentMap.set('CheckboxGroup', Checkbox.Group);
17 17  
18 18 export function add(compName: ComponentType, component: Component) {
19 19 componentMap.set(compName, component);
... ...
src/components/Table/src/components/EditTableHeaderIcon.vue
1 1 <template>
2 2 <span>
  3 + <slot />
3 4 {{ title }}
4   - <FormOutlined class="ml-2" />
  5 + <FormOutlined />
5 6 </span>
6 7 </template>
7 8 <script lang="ts">
... ...
src/components/Table/src/components/HeaderCell.vue 0 → 100644
  1 +<template>
  2 + <EditTableHeaderCell v-if="getIsEdit">
  3 + {{ getTitle }}
  4 + </EditTableHeaderCell>
  5 + <span v-else>{{ getTitle }}</span>
  6 + <BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
  7 +</template>
  8 +<script lang="ts">
  9 + import type { PropType } from 'vue';
  10 + import type { BasicColumn } from '../types/table';
  11 +
  12 + import { defineComponent, computed } from 'vue';
  13 +
  14 + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  15 + import { useDesign } from '/@/hooks/web/useDesign';
  16 + export default defineComponent({
  17 + name: 'TableHeaderCell',
  18 + components: {
  19 + EditTableHeaderCell: createAsyncComponent(() => import('./EditTableHeaderIcon.vue')),
  20 + BasicHelp: createAsyncComponent(() => import('/@/components/Basic/src/BasicHelp.vue')),
  21 + },
  22 + props: {
  23 + column: {
  24 + type: Object as PropType<BasicColumn>,
  25 + default: {},
  26 + },
  27 + },
  28 + setup(props) {
  29 + const { prefixCls } = useDesign('basic-table-header-cell');
  30 + const getIsEdit = computed(() => {
  31 + return !!props.column?.edit;
  32 + });
  33 +
  34 + const getTitle = computed(() => {
  35 + return props.column?.customTitle;
  36 + });
  37 +
  38 + const getHelpMessage = computed(() => {
  39 + return props.column?.helpMessage;
  40 + });
  41 +
  42 + return { prefixCls, getIsEdit, getTitle, getHelpMessage };
  43 + },
  44 + });
  45 +</script>
  46 +<style lang="less">
  47 + @prefix-cls: ~'@{namespace}-basic-table-header-cell';
  48 +
  49 + .@{prefix-cls} {
  50 + &__help {
  51 + margin-left: 8px;
  52 + color: rgba(0, 0, 0, 0.65) !important;
  53 + }
  54 + }
  55 +</style>
... ...
src/components/Table/src/components/TableAction.vue
1 1 <template>
2 2 <div :class="[prefixCls, getAlign]">
3   - <template v-for="(action, index) in getActions" :key="`${index}`">
  3 + <template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
4 4 <PopConfirmButton v-bind="action">
5 5 <Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
6 6 {{ action.label }}
7 7 </PopConfirmButton>
8 8 <Divider type="vertical" v-if="divider && index < getActions.length" />
9 9 </template>
10   -
11   - <Dropdown :trigger="['hover']" :dropMenuList="getDropList">
  10 + <Dropdown :trigger="['hover']" :dropMenuList="getDropList" v-if="dropDownActions">
12 11 <slot name="more" />
13 12 <a-button type="link" size="small" v-if="!$slots.more">
14 13 <MoreOutlined class="icon-more" />
... ... @@ -61,7 +60,7 @@
61 60 });
62 61  
63 62 const getDropList = computed(() => {
64   - return props.dropDownActions.map((action, index) => {
  63 + return (props.dropDownActions || []).map((action, index) => {
65 64 const { label } = action;
66 65 return {
67 66 ...action,
... ...
src/components/Table/src/components/editable/CellComponent.ts 0 → 100644
  1 +import type { FunctionalComponent, defineComponent } from 'vue';
  2 +import type { ComponentType } from '../../types/componentType';
  3 +import { componentMap } from '/@/components/Table/src/componentMap';
  4 +
  5 +import { Popover } from 'ant-design-vue';
  6 +import { h } from 'vue';
  7 +
  8 +export interface ComponentProps {
  9 + component: ComponentType;
  10 + rule: boolean;
  11 + popoverVisible: boolean;
  12 + ruleMessage: string;
  13 +}
  14 +
  15 +export const CellComponent: FunctionalComponent = (
  16 + { component = 'Input', rule = true, ruleMessage, popoverVisible }: ComponentProps,
  17 + { attrs }
  18 +) => {
  19 + const Comp = componentMap.get(component) as typeof defineComponent;
  20 +
  21 + const DefaultComp = h(Comp, attrs);
  22 + if (!rule) {
  23 + return DefaultComp;
  24 + }
  25 + return h(
  26 + Popover,
  27 + { overlayClassName: 'edit-cell-rule-popover', visible: !!popoverVisible },
  28 + {
  29 + default: () => DefaultComp,
  30 + content: () => ruleMessage,
  31 + }
  32 + );
  33 +};
... ...
src/components/Table/src/components/editable/EditableCell.vue 0 → 100644
  1 +<template>
  2 + <div :class="prefixCls">
  3 + <div v-show="!isEdit" :class="`${prefixCls}__normal`" @click="handleEdit">
  4 + {{ value || '&nbsp;' }}
  5 + <FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
  6 + </div>
  7 +
  8 + <div v-if="isEdit" :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
  9 + <CellComponent
  10 + v-bind="getComponentProps"
  11 + :component="getComponent"
  12 + :style="getWrapperStyle"
  13 + :popoverVisible="getRuleVisible"
  14 + :rule="getRule"
  15 + :ruleMessage="ruleMessage"
  16 + size="small"
  17 + ref="elRef"
  18 + @change="handleChange"
  19 + @options-change="handleOptionsChange"
  20 + @pressEnter="handleSubmit"
  21 + >
  22 + </CellComponent>
  23 + <div :class="`${prefixCls}__action`" v-if="!getRowEditable">
  24 + <CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmit" />
  25 + <CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
  26 + </div>
  27 + </div>
  28 + </div>
  29 +</template>
  30 +<script lang="ts">
  31 + import type { CSSProperties, PropType } from 'vue';
  32 + import type { BasicColumn } from '../../types/table';
  33 +
  34 + import { defineComponent, ref, unref, nextTick, computed, watchEffect, toRaw } from 'vue';
  35 + import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
  36 +
  37 + import { useDesign } from '/@/hooks/web/useDesign';
  38 + import { isString, isBoolean, isFunction, isNumber, isArray } from '/@/utils/is';
  39 + import clickOutside from '/@/directives/clickOutside';
  40 +
  41 + import { CellComponent } from './CellComponent';
  42 + import { useTableContext } from '../../hooks/useTableContext';
  43 + import { propTypes } from '/@/utils/propTypes';
  44 + import { createPlaceholderMessage } from './helper';
  45 +
  46 + import type { EditRecordRow } from './index';
  47 +
  48 + export default defineComponent({
  49 + name: 'EditableCell',
  50 + components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent },
  51 + props: {
  52 + value: {
  53 + type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
  54 + default: '',
  55 + },
  56 + record: {
  57 + type: Object as PropType<EditRecordRow>,
  58 + },
  59 + column: {
  60 + type: Object as PropType<BasicColumn>,
  61 + default: {},
  62 + },
  63 + index: propTypes.number,
  64 + },
  65 + directives: {
  66 + clickOutside,
  67 + },
  68 +
  69 + setup(props) {
  70 + const table = useTableContext();
  71 + const isEdit = ref(false);
  72 + const elRef = ref<any>(null);
  73 + const ruleVisible = ref(false);
  74 + const ruleMessage = ref('');
  75 + const optionsRef = ref<LabelValueOptions>([]);
  76 + const currentValueRef = ref<any>(props.value);
  77 + const defaultValueRef = ref<any>(props.value);
  78 +
  79 + const { prefixCls } = useDesign('editable-cell');
  80 +
  81 + const getComponent = computed(() => props.column?.editComponent || 'Input');
  82 + const getRule = computed(() => props.column?.editRule);
  83 +
  84 + const getRuleVisible = computed(() => {
  85 + return unref(ruleMessage) && unref(ruleVisible);
  86 + });
  87 +
  88 + const getIsCheckComp = computed(() => {
  89 + const component = unref(getComponent);
  90 + return ['Checkbox', 'Switch'].includes(component);
  91 + });
  92 +
  93 + const getComponentProps = computed(() => {
  94 + const compProps = props.column?.editComponentProps ?? {};
  95 + const component = unref(getComponent);
  96 + const apiSelectProps: Recordable = {};
  97 + if (component === 'ApiSelect') {
  98 + apiSelectProps.cache = true;
  99 + }
  100 +
  101 + const isCheckValue = unref(getIsCheckComp);
  102 +
  103 + const valueField = isCheckValue ? 'checked' : 'value';
  104 + const val = unref(currentValueRef);
  105 +
  106 + const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
  107 +
  108 + return {
  109 + placeholder: createPlaceholderMessage(unref(getComponent)),
  110 + ...apiSelectProps,
  111 + ...compProps,
  112 + [valueField]: value,
  113 + };
  114 + });
  115 +
  116 + const getValues = computed(() => {
  117 + const { editComponentProps, editValueMap } = props.column;
  118 +
  119 + const value = unref(currentValueRef);
  120 +
  121 + if (editValueMap && isFunction(editValueMap)) {
  122 + return editValueMap(value);
  123 + }
  124 +
  125 + const component = unref(getComponent);
  126 + if (!component.includes('Select')) {
  127 + return value;
  128 + }
  129 + const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
  130 + const option = options.find((item) => `${item.value}` === `${value}`);
  131 + return option?.label;
  132 + });
  133 +
  134 + const getWrapperStyle = computed(
  135 + (): CSSProperties => {
  136 + if (unref(getIsCheckComp) || unref(getRowEditable)) {
  137 + return {};
  138 + }
  139 + return {
  140 + width: 'calc(100% - 48px)',
  141 + };
  142 + }
  143 + );
  144 +
  145 + const getRowEditable = computed(() => {
  146 + const { editable } = props.record || {};
  147 + return !!editable;
  148 + });
  149 +
  150 + watchEffect(() => {
  151 + defaultValueRef.value = props.value;
  152 + });
  153 +
  154 + watchEffect(() => {
  155 + const { editable } = props.column;
  156 + if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
  157 + isEdit.value = !!editable || unref(getRowEditable);
  158 + }
  159 + });
  160 +
  161 + function handleEdit() {
  162 + if (unref(getRowEditable) || unref(props.column?.editRow)) return;
  163 + ruleMessage.value = '';
  164 + isEdit.value = true;
  165 + nextTick(() => {
  166 + const el = unref(elRef);
  167 + el?.focus?.();
  168 + });
  169 + }
  170 +
  171 + async function handleChange(e: any) {
  172 + const component = unref(getComponent);
  173 + if (e?.target && Reflect.has(e.target, 'value')) {
  174 + currentValueRef.value = (e as ChangeEvent).target.value;
  175 + }
  176 + if (component === 'Checkbox') {
  177 + currentValueRef.value = (e as ChangeEvent).target.checked;
  178 + } else if (isString(e) || isBoolean(e) || isNumber(e)) {
  179 + currentValueRef.value = e;
  180 + }
  181 + handleSubmiRule();
  182 + }
  183 +
  184 + async function handleSubmiRule() {
  185 + const { column, record } = props;
  186 + const { editRule } = column;
  187 + const currentValue = unref(currentValueRef);
  188 +
  189 + if (editRule) {
  190 + if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
  191 + ruleVisible.value = true;
  192 + const component = unref(getComponent);
  193 + const message = createPlaceholderMessage(component);
  194 + ruleMessage.value = message;
  195 + return false;
  196 + }
  197 + if (isFunction(editRule)) {
  198 + const res = await editRule(currentValue, record as Recordable);
  199 + if (!!res) {
  200 + ruleMessage.value = res;
  201 + ruleVisible.value = true;
  202 + return false;
  203 + } else {
  204 + ruleMessage.value = '';
  205 + return true;
  206 + }
  207 + }
  208 + }
  209 + ruleMessage.value = '';
  210 + return true;
  211 + }
  212 +
  213 + async function handleSubmit() {
  214 + const isPass = await handleSubmiRule();
  215 + if (!isPass) return false;
  216 + const { column, index } = props;
  217 + const { key, dataIndex } = column;
  218 + // const value = unref(currentValueRef);
  219 + if (!key || !dataIndex) return;
  220 + const dataKey = (dataIndex || key) as string;
  221 +
  222 + const record = await table.updateTableData(index, dataKey, unref(getValues));
  223 + table.emit?.('edit-end', { record, index, key, value: unref(currentValueRef) });
  224 + isEdit.value = false;
  225 + }
  226 +
  227 + function handleCancel() {
  228 + isEdit.value = false;
  229 + currentValueRef.value = defaultValueRef.value;
  230 + table.emit?.('edit-cancel', unref(currentValueRef));
  231 + }
  232 +
  233 + function onClickOutside() {
  234 + if (props.column?.editable || unref(getRowEditable)) {
  235 + return;
  236 + }
  237 + const component = unref(getComponent);
  238 +
  239 + if (component.includes('Input')) {
  240 + handleCancel();
  241 + }
  242 + }
  243 +
  244 + // only ApiSelect
  245 + function handleOptionsChange(options: LabelValueOptions) {
  246 + optionsRef.value = options;
  247 + }
  248 +
  249 + function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
  250 + if (props.record) {
  251 + /* eslint-disable */
  252 + isArray(props.record[cbs])
  253 + ? props.record[cbs].push(handle)
  254 + : (props.record[cbs] = [handle]);
  255 + }
  256 + }
  257 +
  258 + if (props.record) {
  259 + initCbs('submitCbs', handleSubmit);
  260 + initCbs('validCbs', handleSubmiRule);
  261 + initCbs('cancelCbs', handleCancel);
  262 +
  263 + /* eslint-disable */
  264 + props.record.onCancelEdit = () => {
  265 + isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
  266 + };
  267 + /* eslint-disable */
  268 + props.record.onSubmitEdit = async () => {
  269 + if (isArray(props.record?.submitCbs)) {
  270 + const validFns = props.record?.validCbs || [];
  271 +
  272 + const res = await Promise.all(validFns.map((fn) => fn()));
  273 + const pass = res.every((item) => !!item);
  274 +
  275 + if (!pass) return;
  276 + const submitFns = props.record?.submitCbs || [];
  277 + submitFns.forEach((fn) => fn());
  278 + return true;
  279 + }
  280 + // isArray(props.record?.submitCbs) && props.record?.submitCbs.forEach((fn) => fn());
  281 + };
  282 + }
  283 +
  284 + return {
  285 + isEdit,
  286 + prefixCls,
  287 + handleEdit,
  288 + currentValueRef,
  289 + handleSubmit,
  290 + handleChange,
  291 + handleCancel,
  292 + elRef,
  293 + getComponent,
  294 + getRule,
  295 + onClickOutside,
  296 + ruleMessage,
  297 + getRuleVisible,
  298 + getComponentProps,
  299 + handleOptionsChange,
  300 + getWrapperStyle,
  301 + getRowEditable,
  302 + };
  303 + },
  304 + });
  305 +</script>
  306 +<style lang="less">
  307 + @prefix-cls: ~'@{namespace}-editable-cell';
  308 +
  309 + .edit-cell-rule-popover {
  310 + // .ant-popover-arrow {
  311 + // // border-color: transparent @error-color @error-color transparent !important;
  312 + // }
  313 +
  314 + .ant-popover-inner-content {
  315 + padding: 4px 8px;
  316 + color: @error-color;
  317 + // border: 1px solid @error-color;
  318 + border-radius: 2px;
  319 + }
  320 + }
  321 + .@{prefix-cls} {
  322 + position: relative;
  323 +
  324 + &__wrapper {
  325 + display: flex;
  326 + align-items: center;
  327 + justify-content: center;
  328 + }
  329 +
  330 + &__icon {
  331 + &:hover {
  332 + transform: scale(1.2);
  333 +
  334 + svg {
  335 + color: @primary-color;
  336 + }
  337 + }
  338 + }
  339 +
  340 + &__normal {
  341 + padding-right: 48px;
  342 +
  343 + &-icon {
  344 + position: absolute;
  345 + top: 4px;
  346 + right: 0;
  347 + display: none;
  348 + width: 20px;
  349 + cursor: pointer;
  350 + }
  351 + }
  352 +
  353 + &:hover {
  354 + .@{prefix-cls}__normal-icon {
  355 + display: inline-block;
  356 + }
  357 + }
  358 + }
  359 +</style>
... ...
src/components/Table/src/components/editable/helper.ts 0 → 100644
  1 +import { ComponentType } from '../../types/componentType';
  2 +import { useI18n } from '/@/hooks/web/useI18n';
  3 +
  4 +const { t } = useI18n();
  5 +
  6 +/**
  7 + * @description: 生成placeholder
  8 + */
  9 +export function createPlaceholderMessage(component: ComponentType) {
  10 + if (component.includes('Input')) {
  11 + return t('component.form.input');
  12 + }
  13 + if (component.includes('Picker')) {
  14 + return t('component.form.choose');
  15 + }
  16 +
  17 + if (
  18 + component.includes('Select') ||
  19 + component.includes('Checkbox') ||
  20 + component.includes('Radio') ||
  21 + component.includes('Switch')
  22 + ) {
  23 + return t('component.form.choose');
  24 + }
  25 + return '';
  26 +}
... ...
src/components/Table/src/components/editable/index.ts 0 → 100644
  1 +import type { BasicColumn } from '/@/components/Table/src/types/table';
  2 +
  3 +import { h } from 'vue';
  4 +
  5 +import EditableCell from './EditableCell.vue';
  6 +
  7 +interface Params {
  8 + text: string;
  9 + record: Recordable;
  10 + index: number;
  11 +}
  12 +
  13 +export function renderEditCell(column: BasicColumn) {
  14 + return ({ text: value, record, index }: Params) => {
  15 + record.onEdit = async (edit: boolean, submit = false) => {
  16 + if (!submit) {
  17 + record.editable = edit;
  18 + }
  19 +
  20 + if (!edit && submit) {
  21 + const res = await record.onSubmitEdit?.();
  22 + if (res) {
  23 + record.editable = false;
  24 + return true;
  25 + }
  26 + return false;
  27 + }
  28 + // cancel
  29 + if (!edit && !submit) {
  30 + record.onCancelEdit?.();
  31 + }
  32 + return true;
  33 + };
  34 +
  35 + return h(EditableCell, {
  36 + value,
  37 + record,
  38 + column,
  39 + index,
  40 + });
  41 + };
  42 +}
  43 +
  44 +export type EditRecordRow<T = Hash<any>> = {
  45 + onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
  46 + editable: boolean;
  47 + onCancel: Fn;
  48 + onSubmit: Fn;
  49 + submitCbs: Fn[];
  50 + cancelCbs: Fn[];
  51 + validCbs: Fn[];
  52 +} & T;
... ...
src/components/Table/src/components/renderEditable.tsx deleted 100644 → 0
1   -import '../style/editable-cell.less';
2   -
3   -import { defineComponent, PropType, ref, unref, nextTick, watchEffect } from 'vue';
4   -import { ClickOutSide } from '/@/components/ClickOutSide';
5   -
6   -import { RenderEditableCellParams } from '../types/table';
7   -import { ComponentType } from '../types/componentType';
8   -
9   -import { componentMap } from '../componentMap';
10   -import { isString, isBoolean, isArray } from '/@/utils/is';
11   -import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
12   -
13   -const prefixCls = 'editable-cell';
14   -const EditableCell = defineComponent({
15   - name: 'EditableCell',
16   - props: {
17   - value: {
18   - type: String as PropType<string>,
19   - default: '',
20   - },
21   - componentProps: {
22   - type: Object as PropType<any>,
23   - default: null,
24   - },
25   -
26   - dataKey: {
27   - type: String as PropType<string>,
28   - default: '',
29   - },
30   -
31   - dataIndex: {
32   - type: String as PropType<string>,
33   - default: '',
34   - },
35   -
36   - component: {
37   - type: String as PropType<ComponentType>,
38   - default: 'Input',
39   - },
40   - editable: {
41   - type: Boolean as PropType<boolean>,
42   - default: false,
43   - },
44   - editRow: {
45   - type: Boolean as PropType<boolean>,
46   - default: false,
47   - },
48   - record: {
49   - type: Object as PropType<EditRecordRow>,
50   - },
51   - placeholder: {
52   - type: String as PropType<string>,
53   - default: '',
54   - },
55   - },
56   - emits: ['submit', 'cancel'],
57   - setup(props, { attrs, emit }) {
58   - const elRef = ref<any>(null);
59   -
60   - const isEditRef = ref(false);
61   - const currentValueRef = ref<string | boolean>(props.value);
62   - const defaultValueRef = ref<string | boolean>(props.value);
63   -
64   - watchEffect(() => {
65   - defaultValueRef.value = props.value;
66   - if (isBoolean(props.editable)) {
67   - isEditRef.value = props.editable;
68   - }
69   - });
70   -
71   - function handleChange(e: any) {
72   - if (e && e.target && Reflect.has(e.target, 'value')) {
73   - currentValueRef.value = (e as ChangeEvent).target.value;
74   - }
75   - if (isString(e) || isBoolean(e)) {
76   - currentValueRef.value = e;
77   - }
78   - }
79   -
80   - function handleEdit() {
81   - isEditRef.value = true;
82   - nextTick(() => {
83   - const el = unref(elRef);
84   - el && el.focus();
85   - });
86   - }
87   -
88   - function handleCancel() {
89   - isEditRef.value = false;
90   - currentValueRef.value = defaultValueRef.value;
91   - emit('cancel');
92   - }
93   -
94   - if (props.record) {
95   - /* eslint-disable */
96   - isArray(props.record.submitCbs)
97   - ? props.record.submitCbs.push(handleSubmit)
98   - : (props.record.submitCbs = [handleSubmit]);
99   - /* eslint-disable */
100   - isArray(props.record.cancelCbs)
101   - ? props.record.cancelCbs.push(handleCancel)
102   - : (props.record.cancelCbs = [handleCancel]);
103   -
104   - /* eslint-disable */
105   - props.record.onCancel = () => {
106   - isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
107   - };
108   - /* eslint-disable */
109   - props.record.onSubmit = () => {
110   - isArray(props.record?.submitCbs) && props.record?.submitCbs.forEach((fn) => fn());
111   - };
112   - }
113   -
114   - function handleSubmit() {
115   - const { dataKey, dataIndex } = props;
116   - if (!dataKey || !dataIndex) return;
117   -
118   - if (props.record) {
119   - /* eslint-disable */
120   - props.record[dataIndex] = unref(currentValueRef) as string;
121   - }
122   - isEditRef.value = false;
123   - }
124   -
125   - function onClickOutside() {
126   - if (props.editRow) return;
127   - const { component } = props;
128   -
129   - if (component && component.includes('Input')) {
130   - handleCancel();
131   - }
132   - }
133   -
134   - function renderValue() {
135   - const { value } = props;
136   - if (props.editRow) {
137   - return !unref(isEditRef) ? value : null;
138   - }
139   - return (
140   - !unref(isEditRef) && (
141   - <div class={`${prefixCls}__normal`} onClick={handleEdit}>
142   - {value}
143   - <FormOutlined class={`${prefixCls}__normal-icon`} />
144   - </div>
145   - )
146   - );
147   - }
148   - return () => {
149   - const { component, componentProps = {} } = props;
150   -
151   - const Comp = componentMap.get(component!) as any;
152   - return (
153   - <div class={prefixCls}>
154   - {unref(isEditRef) && (
155   - <ClickOutSide onClickOutside={onClickOutside}>
156   - {() => (
157   - <div class={`${prefixCls}__wrapper`}>
158   - <Comp
159   - placeholder={props.placeholder}
160   - {...{
161   - ...attrs,
162   - ...componentProps,
163   - }}
164   - style={{ width: 'calc(100% - 48px)' }}
165   - ref={elRef}
166   - value={unref(currentValueRef)}
167   - size="small"
168   - onChange={handleChange}
169   - onPressEnter={handleSubmit}
170   - />
171   - {!props.editRow && (
172   - <div class={`${prefixCls}__action`}>
173   - <CheckOutlined
174   - class={[`${prefixCls}__icon`, 'mx-2']}
175   - onClick={handleSubmit}
176   - />
177   - <CloseOutlined class={[`${prefixCls}__icon `]} onClick={handleCancel} />
178   - </div>
179   - )}
180   - </div>
181   - )}
182   - </ClickOutSide>
183   - )}
184   - {renderValue()}
185   - </div>
186   - );
187   - };
188   - },
189   -});
190   -
191   -export function renderEditableCell({
192   - dataIndex,
193   - component,
194   - componentProps = {},
195   - placeholder,
196   -}: RenderEditableCellParams) {
197   - return ({ text, record }: { text: string; record: EditRecordRow }) => {
198   - return (
199   - <EditableCell
200   - {...componentProps}
201   - placeholder={placeholder}
202   - value={text}
203   - record={record}
204   - dataKey={record.key}
205   - dataIndex={dataIndex}
206   - component={component}
207   - />
208   - );
209   - };
210   -}
211   -
212   -export function renderEditableRow({
213   - dataIndex,
214   - component,
215   - componentProps = {},
216   - placeholder,
217   -}: RenderEditableCellParams) {
218   - return ({ text, record }: { text: string; record: EditRecordRow }) => {
219   - return (
220   - <EditableCell
221   - {...componentProps}
222   - value={text}
223   - placeholder={placeholder}
224   - editRow={true}
225   - editable={record.editable}
226   - dataKey={record.key}
227   - record={record}
228   - dataIndex={dataIndex}
229   - component={component}
230   - />
231   - );
232   - };
233   -}
234   -
235   -export type EditRecordRow<T = Hash<any>> = {
236   - editable: boolean;
237   - onCancel: Fn;
238   - onSubmit: Fn;
239   - submitCbs: Fn[];
240   - cancelCbs: Fn[];
241   -} & T;
src/components/Table/src/components/renderExpandIcon.tsx deleted 100644 → 0
1   -import { BasicArrow } from '/@/components/Basic';
2   -
3   -export default () => {
4   - return (props: Recordable) => {
5   - return (
6   - <BasicArrow
7   - onClick={(e: Event) => {
8   - props.onExpand(props.record, e);
9   - }}
10   - expand={props.expanded}
11   - />
12   - );
13   - };
14   -};
src/components/Table/src/components/settings/ColumnSetting.vue
... ... @@ -184,7 +184,7 @@
184 184 const ret: Options[] = [];
185 185 table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
186 186 ret.push({
187   - label: item.title as string,
  187 + label: (item.title as string) || (item.customTitle as string),
188 188 value: (item.dataIndex || item.title) as string,
189 189 ...item,
190 190 });
... ...
src/components/Table/src/const.ts
... ... @@ -32,6 +32,10 @@ export function DEFAULT_SORT_FN(sortInfo: SorterResult) {
32 32 };
33 33 }
34 34  
  35 +export function DEFAULT_FILTER_FN(data: Partial<Recordable<string[]>>) {
  36 + return data;
  37 +}
  38 +
35 39 // 表格单元格默认布局
36 40 export const DEFAULT_ALIGN = 'center';
37 41  
... ...
src/components/Table/src/hooks/useColumns.ts
1   -import { BasicColumn, BasicTableProps, GetColumnsParams } from '../types/table';
2   -import { PaginationProps } from '../types/pagination';
  1 +import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
  2 +import type { PaginationProps } from '../types/pagination';
3 3 import { unref, ComputedRef, Ref, computed, watchEffect, ref, toRaw } from 'vue';
4   -import { isBoolean, isArray, isString } from '/@/utils/is';
  4 +import { isBoolean, isArray, isString, isObject } from '/@/utils/is';
5 5 import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const';
6 6 import { useI18n } from '/@/hooks/web/useI18n';
7 7 import { isEqual, cloneDeep } from 'lodash-es';
  8 +import { isFunction } from '/@/utils/is';
  9 +import { formatToDate } from '/@/utils/dateUtil';
  10 +import { renderEditCell } from '../components/editable';
8 11  
9 12 const { t } = useI18n();
10 13  
... ... @@ -127,8 +130,30 @@ export function useColumns(
127 130 return columns;
128 131 });
129 132  
130   - const getSortFixedColumns = computed(() => {
131   - return useFixedColumn(unref(getColumnsRef));
  133 + const getViewColumns = computed(() => {
  134 + const viewColumns = sortFixedColumn(unref(getColumnsRef));
  135 +
  136 + viewColumns.forEach((column) => {
  137 + const { slots, dataIndex, customRender, format, edit, editRow, flag } = column;
  138 +
  139 + if (!slots || !slots?.title) {
  140 + column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
  141 + column.customTitle = column.title;
  142 + Reflect.deleteProperty(column, 'title');
  143 + }
  144 + const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
  145 + if (!customRender && format && !edit && !isDefaultAction) {
  146 + column.customRender = ({ text, record, index }) => {
  147 + return formatCell(text, format, record, index);
  148 + };
  149 + }
  150 +
  151 + // edit table
  152 + if ((edit || editRow) && !isDefaultAction) {
  153 + column.customRender = renderEditCell(column);
  154 + }
  155 + });
  156 + return viewColumns;
132 157 });
133 158  
134 159 watchEffect(() => {
... ... @@ -191,7 +216,7 @@ export function useColumns(
191 216 }
192 217  
193 218 if (sort) {
194   - columns = useFixedColumn(columns);
  219 + columns = sortFixedColumn(columns);
195 220 }
196 221  
197 222 return columns;
... ... @@ -200,10 +225,10 @@ export function useColumns(
200 225 return cacheColumns;
201 226 }
202 227  
203   - return { getColumnsRef, getCacheColumns, getColumns, setColumns, getSortFixedColumns };
  228 + return { getColumnsRef, getCacheColumns, getColumns, setColumns, getViewColumns };
204 229 }
205 230  
206   -export function useFixedColumn(columns: BasicColumn[]) {
  231 +function sortFixedColumn(columns: BasicColumn[]) {
207 232 const fixedLeftColumns: BasicColumn[] = [];
208 233 const fixedRightColumns: BasicColumn[] = [];
209 234 const defColumns: BasicColumn[] = [];
... ... @@ -224,3 +249,35 @@ export function useFixedColumn(columns: BasicColumn[]) {
224 249  
225 250 return resultColumns;
226 251 }
  252 +
  253 +// format cell
  254 +export function formatCell(text: string, format: CellFormat, record: Recordable, index: number) {
  255 + if (!format) {
  256 + return text;
  257 + }
  258 +
  259 + // custom function
  260 + if (isFunction(format)) {
  261 + return format(text, record, index);
  262 + }
  263 +
  264 + try {
  265 + // date type
  266 + const DATE_FORMAT_PREFIX = 'date|';
  267 + if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) {
  268 + const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
  269 +
  270 + if (!dateFormat) {
  271 + return text;
  272 + }
  273 + return formatToDate(text, dateFormat);
  274 + }
  275 +
  276 + // enum
  277 + if (isObject(format) && Reflect.has(format, 'size')) {
  278 + return format.get(text);
  279 + }
  280 + } catch (error) {
  281 + return text;
  282 + }
  283 +}
... ...
src/components/Table/src/hooks/useDataSource.ts
1   -import type { BasicTableProps, FetchParams } from '../types/table';
  1 +import type { BasicTableProps, FetchParams, SorterResult } from '../types/table';
2 2 import type { PaginationProps } from '../types/pagination';
3 3  
4   -import { ref, unref, ComputedRef, computed, onMounted, watchEffect } from 'vue';
  4 +import { ref, unref, ComputedRef, computed, onMounted, watchEffect, reactive } from 'vue';
5 5  
6 6 import { useTimeoutFn } from '/@/hooks/core/useTimeout';
7 7  
... ... @@ -16,12 +16,28 @@ interface ActionType {
16 16 setPagination: (info: Partial<PaginationProps>) => void;
17 17 setLoading: (loading: boolean) => void;
18 18 getFieldsValue: () => Recordable;
  19 + clearSelectedRowKeys: () => void;
  20 +}
  21 +
  22 +interface SearchState {
  23 + sortInfo: Recordable;
  24 + filterInfo: Record<string, string[]>;
19 25 }
20 26 export function useDataSource(
21 27 propsRef: ComputedRef<BasicTableProps>,
22   - { getPaginationInfo, setPagination, setLoading, getFieldsValue }: ActionType,
  28 + {
  29 + getPaginationInfo,
  30 + setPagination,
  31 + setLoading,
  32 + getFieldsValue,
  33 + clearSelectedRowKeys,
  34 + }: ActionType,
23 35 emit: EmitType
24 36 ) {
  37 + const searchState = reactive<SearchState>({
  38 + sortInfo: {},
  39 + filterInfo: {},
  40 + });
25 41 const dataSourceRef = ref<Recordable[]>([]);
26 42  
27 43 watchEffect(() => {
... ... @@ -29,6 +45,32 @@ export function useDataSource(
29 45 !api && dataSource && (dataSourceRef.value = dataSource);
30 46 });
31 47  
  48 + function handleTableChange(
  49 + pagination: PaginationProps,
  50 + filters: Partial<Recordable<string[]>>,
  51 + sorter: SorterResult
  52 + ) {
  53 + const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef);
  54 + if (clearSelectOnPageChange) {
  55 + clearSelectedRowKeys();
  56 + }
  57 + setPagination(pagination);
  58 +
  59 + const params: Recordable = {};
  60 + if (sorter && isFunction(sortFn)) {
  61 + const sortInfo = sortFn(sorter);
  62 + searchState.sortInfo = sortInfo;
  63 + params.sortInfo = sortInfo;
  64 + }
  65 +
  66 + if (filters && isFunction(filterFn)) {
  67 + const filterInfo = filterFn(filters);
  68 + searchState.filterInfo = filterInfo;
  69 + params.filterInfo = filterInfo;
  70 + }
  71 + fetch(params);
  72 + }
  73 +
32 74 function setTableKey(items: any[]) {
33 75 if (!items || !Array.isArray(items)) return;
34 76 items.forEach((item) => {
... ... @@ -75,6 +117,14 @@ export function useDataSource(
75 117 return unref(dataSourceRef);
76 118 });
77 119  
  120 + async function updateTableData(index: number, key: string, value: any) {
  121 + const record = dataSourceRef.value[index];
  122 + if (record) {
  123 + dataSourceRef.value[index][key] = value;
  124 + }
  125 + return dataSourceRef.value[index];
  126 + }
  127 +
78 128 async function fetch(opt?: FetchParams) {
79 129 const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref(
80 130 propsRef
... ... @@ -94,6 +144,8 @@ export function useDataSource(
94 144 pageParams[sizeField] = pageSize;
95 145 }
96 146  
  147 + const { sortInfo = {}, filterInfo } = searchState;
  148 +
97 149 let params: Recordable = {
98 150 ...pageParams,
99 151 ...(useSearchForm ? getFieldsValue() : {}),
... ... @@ -101,6 +153,8 @@ export function useDataSource(
101 153 ...(opt ? opt.searchInfo : {}),
102 154 ...(opt ? opt.sortInfo : {}),
103 155 ...(opt ? opt.filterInfo : {}),
  156 + ...sortInfo,
  157 + ...filterInfo,
104 158 };
105 159 if (beforeFetch && isFunction(beforeFetch)) {
106 160 params = beforeFetch(params) || params;
... ... @@ -175,5 +229,7 @@ export function useDataSource(
175 229 getAutoCreateKey,
176 230 fetch,
177 231 reload,
  232 + updateTableData,
  233 + handleTableChange,
178 234 };
179 235 }
... ...
src/components/Table/src/hooks/useTable.ts
1 1 import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
2 2 import type { PaginationProps } from '../types/pagination';
  3 +import type { DynamicProps } from '/@/types/utils';
  4 +import { getDynamicProps } from '/@/utils';
3 5  
4 6 import { ref, onUnmounted, unref } from 'vue';
5 7 import { isProdMode } from '/@/utils/env';
6 8 import { isInSetup } from '/@/utils/helper/vueHelper';
  9 +import { error } from '/@/utils/log';
  10 +import { watchEffect } from 'vue';
  11 +import type { FormActionType } from '/@/components/Form';
  12 +
  13 +type Props = Partial<DynamicProps<BasicTableProps>>;
7 14  
8 15 export function useTable(
9   - tableProps?: Partial<BasicTableProps>
10   -): [(instance: TableActionType) => void, TableActionType] {
  16 + tableProps?: Props
  17 +): [(instance: TableActionType, formInstance: FormActionType) => void, TableActionType] {
11 18 isInSetup();
12 19  
13 20 const tableRef = ref<Nullable<TableActionType>>(null);
14 21 const loadedRef = ref<Nullable<boolean>>(false);
  22 + const formRef = ref<Nullable<FormActionType>>(null);
15 23  
16   - function register(instance: TableActionType) {
  24 + function register(instance: TableActionType, formInstance: FormActionType) {
17 25 isProdMode() &&
18 26 onUnmounted(() => {
19 27 tableRef.value = null;
... ... @@ -24,20 +32,29 @@ export function useTable(
24 32 return;
25 33 }
26 34 tableRef.value = instance;
27   - tableProps && instance.setProps(tableProps);
  35 + formRef.value = formInstance;
  36 + // tableProps && instance.setProps(tableProps);
28 37 loadedRef.value = true;
  38 +
  39 + watchEffect(() => {
  40 + tableProps && instance.setProps(getDynamicProps(tableProps));
  41 + });
29 42 }
30 43  
31 44 function getTableInstance(): TableActionType {
32 45 const table = unref(tableRef);
33 46 if (!table) {
34   - throw new Error('table is undefined!');
  47 + error(
  48 + 'The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!'
  49 + );
35 50 }
36   - return table;
  51 + return table as TableActionType;
37 52 }
38 53  
39   - const methods: TableActionType = {
40   - reload: (opt?: FetchParams) => {
  54 + const methods: TableActionType & {
  55 + getForm: () => FormActionType;
  56 + } = {
  57 + reload: async (opt?: FetchParams) => {
41 58 getTableInstance().reload(opt);
42 59 },
43 60 setProps: (props: Partial<BasicTableProps>) => {
... ... @@ -54,7 +71,6 @@ export function useTable(
54 71 },
55 72 getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
56 73 const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
57   -
58 74 return columns;
59 75 },
60 76 setColumns: (columns: BasicColumn[]) => {
... ... @@ -87,7 +103,19 @@ export function useTable(
87 103 getSize: () => {
88 104 return getTableInstance().getSize();
89 105 },
90   - } as TableActionType;
  106 + updateTableData: (index: number, key: string, value: any) => {
  107 + return getTableInstance().updateTableData(index, key, value);
  108 + },
  109 + getRowSelection: () => {
  110 + return getTableInstance().getRowSelection();
  111 + },
  112 + getCacheColumns: () => {
  113 + return getTableInstance().getCacheColumns();
  114 + },
  115 + getForm: () => {
  116 + return unref(formRef) as FormActionType;
  117 + },
  118 + };
91 119  
92 120 return [register, methods];
93 121 }
... ...
src/components/Table/src/hooks/useTableScroll.ts
... ... @@ -121,7 +121,7 @@ export function useTableScroll(
121 121 width += 60;
122 122 }
123 123  
124   - // TODO props
  124 + // TODO propsdth ?? 0;
125 125 const NORMAL_WIDTH = 150;
126 126  
127 127 const columns = unref(columnsRef);
... ... @@ -135,7 +135,10 @@ export function useTableScroll(
135 135 if (len !== 0) {
136 136 width += len * NORMAL_WIDTH;
137 137 }
138   - return width;
  138 +
  139 + const table = unref(tableElRef);
  140 + const tableWidth = table?.$el?.offsetWidth ?? 0;
  141 + return tableWidth > width ? tableWidth - 24 : width;
139 142 });
140 143  
141 144 const getScrollRef = computed(() => {
... ...
src/components/Table/src/props.ts
... ... @@ -9,21 +9,29 @@ import type {
9 9 TableRowSelection,
10 10 } from './types/table';
11 11 import type { FormProps } from '/@/components/Form';
12   -import { DEFAULT_SORT_FN, FETCH_SETTING } from './const';
  12 +import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING } from './const';
13 13 import { propTypes } from '/@/utils/propTypes';
14 14  
15 15 // 注释看 types/table
16 16 export const basicProps = {
17 17 clickToRowSelect: propTypes.bool.def(true),
  18 +
18 19 tableSetting: {
19 20 type: Object as PropType<TableSetting>,
20 21 },
  22 +
21 23 inset: propTypes.bool,
  24 +
22 25 sortFn: {
23 26 type: Function as PropType<(sortInfo: SorterResult) => any>,
24 27 default: DEFAULT_SORT_FN,
25 28 },
26 29  
  30 + filterFn: {
  31 + type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
  32 + default: DEFAULT_FILTER_FN,
  33 + },
  34 +
27 35 showTableSetting: propTypes.bool,
28 36 autoCreateKey: propTypes.bool.def(true),
29 37 striped: propTypes.bool.def(true),
... ...
src/components/Table/src/style/editable-cell.less deleted 100644 → 0
1   -@prefix-cls: ~'editable-cell';
2   -
3   -.@{prefix-cls} {
4   - position: relative;
5   -
6   - &__wrapper {
7   - display: flex;
8   - align-items: center;
9   - }
10   -
11   - &__icon {
12   - &:hover {
13   - transform: scale(1.2);
14   -
15   - svg {
16   - color: @primary-color;
17   - }
18   - }
19   - }
20   -
21   - &__normal {
22   - padding-right: 48px;
23   -
24   - &-icon {
25   - position: absolute;
26   - top: 4px;
27   - right: 0;
28   - display: none;
29   - width: 20px;
30   - cursor: pointer;
31   - }
32   - }
33   -
34   - &:hover {
35   - .@{prefix-cls}__normal-icon {
36   - display: inline-block;
37   - }
38   - }
39   -}
src/components/Table/src/style/index.less
... ... @@ -133,14 +133,18 @@
133 133 overflow-y: scroll !important;
134 134 }
135 135  
136   - .ant-table-fixed-right .ant-table-header {
137   - border-left: 1px solid @border-color !important;
  136 + .ant-table-fixed-right {
  137 + right: -1px;
138 138  
139   - .ant-table-fixed {
140   - border-bottom: none;
  139 + .ant-table-header {
  140 + border-left: 1px solid @border-color !important;
  141 +
  142 + .ant-table-fixed {
  143 + border-bottom: none;
141 144  
142   - .ant-table-thead th {
143   - background: rgb(241, 243, 244);
  145 + .ant-table-thead th {
  146 + background: rgb(241, 243, 244);
  147 + }
144 148 }
145 149 }
146 150 }
... ...
src/components/Table/src/types/componentType.ts
1 1 export type ComponentType =
2 2 | 'Input'
3   - | 'InputPassword'
4 3 | 'InputNumber'
5 4 | 'Select'
  5 + | 'ApiSelect'
6 6 | 'Checkbox'
7   - | 'CheckboxGroup'
8 7 | 'Switch';
... ...
src/components/Table/src/types/table.ts
... ... @@ -6,9 +6,10 @@ import type {
6 6 TableRowSelection as ITableRowSelection,
7 7 } from 'ant-design-vue/lib/table/interface';
8 8 import { ComponentType } from './componentType';
  9 +import { VueNode } from '/@/utils/propTypes';
9 10 // import { ColumnProps } from './column';
10 11 export declare type SortOrder = 'ascend' | 'descend';
11   -export interface TableCurrentDataSource<T = any> {
  12 +export interface TableCurrentDataSource<T = Recordable> {
12 13 currentDataSource: T[];
13 14 }
14 15  
... ... @@ -53,7 +54,7 @@ export interface ColumnFilterItem {
53 54 children?: any;
54 55 }
55 56  
56   -export interface TableCustomRecord<T = any> {
  57 +export interface TableCustomRecord<T = Recordable> {
57 58 record?: T;
58 59 index?: number;
59 60 }
... ... @@ -65,18 +66,11 @@ export interface SorterResult {
65 66 columnKey: string;
66 67 }
67 68  
68   -export interface RenderEditableCellParams {
69   - dataIndex: string;
70   - component?: ComponentType;
71   - componentProps?: any;
72   - placeholder?: string;
73   -}
74   -
75 69 export interface FetchParams {
76   - searchInfo?: any;
  70 + searchInfo?: Recordable;
77 71 page?: number;
78   - sortInfo?: any;
79   - filterInfo?: any;
  72 + sortInfo?: Recordable;
  73 + filterInfo?: Recordable;
80 74 }
81 75  
82 76 export interface GetColumnsParams {
... ... @@ -89,7 +83,7 @@ export type SizeType = &#39;default&#39; | &#39;middle&#39; | &#39;small&#39; | &#39;large&#39;;
89 83  
90 84 export interface TableActionType {
91 85 reload: (opt?: FetchParams) => Promise<void>;
92   - getSelectRows: <T = any>() => T[];
  86 + getSelectRows: <T = Recordable>() => T[];
93 87 clearSelectedRowKeys: () => void;
94 88 getSelectRowKeys: () => string[];
95 89 deleteSelectRowByKey: (key: string) => void;
... ... @@ -106,6 +100,8 @@ export interface TableActionType {
106 100 getSize: () => SizeType;
107 101 getRowSelection: () => TableRowSelection<Recordable>;
108 102 getCacheColumns: () => BasicColumn[];
  103 + emit?: EmitType;
  104 + updateTableData: (index: number, key: string, value: any) => Recordable;
109 105 }
110 106  
111 107 export interface FetchSetting {
... ... @@ -131,6 +127,8 @@ export interface BasicTableProps&lt;T = any&gt; {
131 127 clickToRowSelect?: boolean;
132 128 // 自定义排序方法
133 129 sortFn?: (sortInfo: SorterResult) => any;
  130 + // 排序方法
  131 + filterFn?: (data: Partial<Recordable<string[]>>) => any;
134 132 // 取消表格的默认padding
135 133 inset?: boolean;
136 134 // 显示表格设置
... ... @@ -141,7 +139,7 @@ export interface BasicTableProps&lt;T = any&gt; {
141 139 // 是否自动生成key
142 140 autoCreateKey?: boolean;
143 141 // 计算合计行的方法
144   - summaryFunc?: (...arg: any) => any[];
  142 + summaryFunc?: (...arg: any) => Recordable[];
145 143 // 是否显示合计行
146 144 showSummary?: boolean;
147 145 // 是否可拖拽列
... ... @@ -374,13 +372,43 @@ export interface BasicTableProps&lt;T = any&gt; {
374 372 onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
375 373 }
376 374  
  375 +export type CellFormat =
  376 + | string
  377 + | ((text: string, record: Recordable, index: number) => string | number)
  378 + | Map<string | number, any>;
  379 +
  380 +// @ts-ignore
377 381 export interface BasicColumn extends ColumnProps {
378 382 children?: BasicColumn[];
  383 + filters?: {
  384 + text: string;
  385 + value: string;
  386 + children?:
  387 + | unknown[]
  388 + | (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]));
  389 + }[];
379 390  
380 391 //
381 392 flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
  393 + customTitle?: VueNode;
382 394  
383 395 slots?: Indexable;
384 396  
  397 + // Whether to hide the column by default, it can be displayed in the column configuration
385 398 defaultHidden?: boolean;
  399 +
  400 + // Help text for table column header
  401 + helpMessage?: string | string[];
  402 +
  403 + format?: CellFormat;
  404 +
  405 + // Editable
  406 + edit?: boolean;
  407 + editRow?: boolean;
  408 + editable?: boolean;
  409 + editComponent?: ComponentType;
  410 + editComponentProps?: Recordable;
  411 + editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
  412 + editValueMap?: (value: any) => string;
  413 + onEditRow?: () => void;
386 414 }
... ...
src/layouts/default/sider/MixSider.vue
... ... @@ -351,6 +351,11 @@
351 351 position: absolute;
352 352 top: 10px;
353 353 right: 30px;
  354 +
  355 + &--dot {
  356 + top: 50%;
  357 + margin-top: -3px;
  358 + }
354 359 }
355 360  
356 361 &__title {
... ...
src/router/menus/modules/demo/comp.ts
... ... @@ -52,6 +52,9 @@ const menu: MenuModule = {
52 52 {
53 53 path: 'table',
54 54 name: t('routes.demo.table.table'),
  55 + tag: {
  56 + dot: true,
  57 + },
55 58 children: [
56 59 {
57 60 path: 'basic',
... ... @@ -108,10 +111,16 @@ const menu: MenuModule = {
108 111 {
109 112 path: 'editCellTable',
110 113 name: t('routes.demo.table.editCellTable'),
  114 + tag: {
  115 + dot: true,
  116 + },
111 117 },
112 118 {
113 119 path: 'editRowTable',
114 120 name: t('routes.demo.table.editRowTable'),
  121 + tag: {
  122 + dot: true,
  123 + },
115 124 },
116 125 ],
117 126 },
... ...
src/utils/dateUtil.ts
... ... @@ -3,12 +3,15 @@ import moment from &#39;moment&#39;;
3 3 const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
4 4 const DATE_FORMAT = 'YYYY-MM-DD ';
5 5  
6   -export function formatToDateTime(date: moment.MomentInput = null): string {
7   - return moment(date).format(DATE_TIME_FORMAT);
  6 +export function formatToDateTime(
  7 + date: moment.MomentInput = null,
  8 + format = DATE_TIME_FORMAT
  9 +): string {
  10 + return moment(date).format(format);
8 11 }
9 12  
10   -export function formatToDate(date: moment.MomentInput = null): string {
11   - return moment(date).format(DATE_FORMAT);
  13 +export function formatToDate(date: moment.MomentInput = null, format = DATE_FORMAT): string {
  14 + return moment(date).format(format);
12 15 }
13 16  
14 17 export const formatAgo = (str: string | number) => {
... ...
src/views/demo/table/EditCellTable.vue
1 1 <template>
2 2 <div class="p-4">
3   - <BasicTable @register="registerTable">
4   - <template #customId>
5   - <EditTableHeaderIcon title="Id" />
6   - </template>
7   - <template #customName>
8   - <EditTableHeaderIcon title="姓名" />
9   - </template>
  3 + <BasicTable @register="registerTable" @edit-end="handleEditEnd" @edit-cancel="handleEditCancel">
10 4 </BasicTable>
11 5 </div>
12 6 </template>
13 7 <script lang="ts">
14 8 import { defineComponent } from 'vue';
15   - import {
16   - BasicTable,
17   - useTable,
18   - BasicColumn,
19   - renderEditableCell,
20   - EditTableHeaderIcon,
21   - } from '/@/components/Table';
  9 + import { BasicTable, useTable, BasicColumn, EditTableHeaderIcon } from '/@/components/Table';
  10 + import { optionsListApi } from '/@/api/demo/select';
22 11  
23 12 import { demoListApi } from '/@/api/demo/table';
24 13 const columns: BasicColumn[] = [
25 14 {
26   - // title: 'ID',
  15 + title: '输入框',
  16 + dataIndex: 'name',
  17 + edit: true,
  18 + editComponentProps: {
  19 + prefix: '$',
  20 + },
  21 + width: 200,
  22 + },
  23 + {
  24 + title: '默认输入状态',
  25 + dataIndex: 'name7',
  26 + edit: true,
  27 + editable: true,
  28 + width: 200,
  29 + },
  30 + {
  31 + title: '输入框校验',
  32 + dataIndex: 'name1',
  33 + edit: true,
  34 + // 默认必填校验
  35 + editRule: true,
  36 + width: 200,
  37 + },
  38 + {
  39 + title: '输入框函数校验',
  40 + dataIndex: 'name2',
  41 + edit: true,
  42 + editRule: async (text) => {
  43 + if (text === '2') {
  44 + return '不能输入该值';
  45 + }
  46 + return '';
  47 + },
  48 + width: 200,
  49 + },
  50 + {
  51 + title: '数字输入框',
27 52 dataIndex: 'id',
28   - slots: { title: 'customId' },
29   - customRender: renderEditableCell({ dataIndex: 'id' }),
  53 + edit: true,
  54 + editRule: true,
  55 + editComponent: 'InputNumber',
  56 + width: 200,
30 57 },
31 58 {
32   - // title: '姓名',
33   - dataIndex: 'name',
34   - slots: { title: 'customName' },
35   - customRender: renderEditableCell({
36   - dataIndex: 'name',
37   - }),
  59 + title: '下拉框',
  60 + dataIndex: 'name3',
  61 + edit: true,
  62 + editComponent: 'Select',
  63 + editComponentProps: {
  64 + options: [
  65 + {
  66 + label: 'Option1',
  67 + value: '1',
  68 + },
  69 + {
  70 + label: 'Option2',
  71 + value: '2',
  72 + },
  73 + ],
  74 + },
  75 + width: 200,
  76 + },
  77 + {
  78 + title: '远程下拉',
  79 + dataIndex: 'name4',
  80 + edit: true,
  81 + editComponent: 'ApiSelect',
  82 + editComponentProps: {
  83 + api: optionsListApi,
  84 + },
  85 + width: 200,
38 86 },
39 87 {
40   - title: '地址',
41   - dataIndex: 'address',
42   - sorter: true,
  88 + title: '勾选框',
  89 + dataIndex: 'name5',
  90 + edit: true,
  91 + editComponent: 'Checkbox',
  92 + editValueMap: (value) => {
  93 + return value ? '是' : '否';
  94 + },
  95 + width: 200,
  96 + },
  97 + {
  98 + title: '开关',
  99 + dataIndex: 'name6',
  100 + edit: true,
  101 + editComponent: 'Switch',
  102 + editValueMap: (value) => {
  103 + return value ? '开' : '关';
  104 + },
  105 + width: 200,
43 106 },
44 107 ];
45 108 export default defineComponent({
... ... @@ -50,10 +113,21 @@
50 113 api: demoListApi,
51 114 columns: columns,
52 115 showIndexColumn: false,
  116 + bordered: true,
53 117 });
54 118  
  119 + function handleEditEnd({ record, index, key, value }: Recordable) {
  120 + console.log(record, index, key, value);
  121 + }
  122 +
  123 + function handleEditCancel() {
  124 + console.log('cancel');
  125 + }
  126 +
55 127 return {
56 128 registerTable,
  129 + handleEditEnd,
  130 + handleEditCancel,
57 131 };
58 132 },
59 133 });
... ...
src/views/demo/table/EditRowTable.vue
... ... @@ -15,24 +15,105 @@
15 15 TableAction,
16 16 BasicColumn,
17 17 ActionItem,
18   - renderEditableRow,
19 18 EditTableHeaderIcon,
20 19 EditRecordRow,
21 20 } from '/@/components/Table';
  21 + import { optionsListApi } from '/@/api/demo/select';
22 22  
23 23 import { demoListApi } from '/@/api/demo/table';
24 24 const columns: BasicColumn[] = [
25 25 {
26   - title: 'ID',
  26 + title: '输入框',
  27 + dataIndex: 'name',
  28 + editRow: true,
  29 + editComponentProps: {
  30 + prefix: '$',
  31 + },
  32 + width: 200,
  33 + },
  34 + {
  35 + title: '默认输入状态',
  36 + dataIndex: 'name7',
  37 + editRow: true,
  38 + width: 200,
  39 + },
  40 + {
  41 + title: '输入框校验',
  42 + dataIndex: 'name1',
  43 + editRow: true,
  44 + // 默认必填校验
  45 + editRule: true,
  46 + width: 200,
  47 + },
  48 + {
  49 + title: '输入框函数校验',
  50 + dataIndex: 'name2',
  51 + editRow: true,
  52 + editRule: async (text) => {
  53 + if (text === '2') {
  54 + return '不能输入该值';
  55 + }
  56 + return '';
  57 + },
  58 + width: 200,
  59 + },
  60 + {
  61 + title: '数字输入框',
27 62 dataIndex: 'id',
28   - customRender: renderEditableRow({ dataIndex: 'id' }),
  63 + editRow: true,
  64 + editRule: true,
  65 + editComponent: 'InputNumber',
  66 + width: 200,
29 67 },
30 68 {
31   - title: '姓名',
32   - dataIndex: 'name',
33   - customRender: renderEditableRow({
34   - dataIndex: 'name',
35   - }),
  69 + title: '下拉框',
  70 + dataIndex: 'name3',
  71 + editRow: true,
  72 + editComponent: 'Select',
  73 + editComponentProps: {
  74 + options: [
  75 + {
  76 + label: 'Option1',
  77 + value: '1',
  78 + },
  79 + {
  80 + label: 'Option2',
  81 + value: '2',
  82 + },
  83 + ],
  84 + },
  85 + width: 200,
  86 + },
  87 + {
  88 + title: '远程下拉',
  89 + dataIndex: 'name4',
  90 + editRow: true,
  91 + editComponent: 'ApiSelect',
  92 + editComponentProps: {
  93 + api: optionsListApi,
  94 + },
  95 + width: 200,
  96 + },
  97 + {
  98 + title: '勾选框',
  99 + dataIndex: 'name5',
  100 + editRow: true,
  101 +
  102 + editComponent: 'Checkbox',
  103 + editValueMap: (value) => {
  104 + return value ? '是' : '否';
  105 + },
  106 + width: 200,
  107 + },
  108 + {
  109 + title: '开关',
  110 + dataIndex: 'name6',
  111 + editRow: true,
  112 + editComponent: 'Switch',
  113 + editValueMap: (value) => {
  114 + return value ? '开' : '关';
  115 + },
  116 + width: 200,
36 117 },
37 118 ];
38 119 export default defineComponent({
... ... @@ -55,19 +136,19 @@
55 136  
56 137 function handleEdit(record: EditRecordRow) {
57 138 currentEditKeyRef.value = record.key;
58   - record.editable = true;
  139 + record.onEdit?.(true);
59 140 }
60 141  
61 142 function handleCancel(record: EditRecordRow) {
62 143 currentEditKeyRef.value = '';
63   - record.editable = false;
64   - record.onCancel && record.onCancel();
  144 + record.onEdit?.(false, true);
65 145 }
66 146  
67   - function handleSave(record: EditRecordRow) {
68   - currentEditKeyRef.value = '';
69   - record.editable = false;
70   - record.onSubmit && record.onSubmit();
  147 + async function handleSave(record: EditRecordRow) {
  148 + const pass = await record.onEdit?.(false, true);
  149 + if (pass) {
  150 + currentEditKeyRef.value = '';
  151 + }
71 152 }
72 153  
73 154 function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
... ...
src/views/demo/table/FixedColumn.vue
... ... @@ -41,7 +41,6 @@
41 41 {
42 42 title: '地址',
43 43 dataIndex: 'address',
44   - width: 260,
45 44 },
46 45 {
47 46 title: '编号',
... ... @@ -67,6 +66,7 @@
67 66 api: demoListApi,
68 67 columns: columns,
69 68 rowSelection: { type: 'radio' },
  69 + bordered: true,
70 70 actionColumn: {
71 71 width: 160,
72 72 title: 'Action',
... ...
src/views/demo/table/tableData.tsx
... ... @@ -7,12 +7,16 @@ export function getBasicColumns(): BasicColumn[] {
7 7 title: 'ID',
8 8 dataIndex: 'id',
9 9 fixed: 'left',
10   - width: 400,
  10 + width: 200,
11 11 },
12 12 {
13 13 title: '姓名',
14 14 dataIndex: 'name',
15 15 width: 150,
  16 + filters: [
  17 + { text: 'Male', value: 'male' },
  18 + { text: 'Female', value: 'female' },
  19 + ],
16 20 },
17 21 {
18 22 title: '地址',
... ... @@ -22,11 +26,13 @@ export function getBasicColumns(): BasicColumn[] {
22 26 title: '编号',
23 27 dataIndex: 'no',
24 28 width: 150,
  29 + sorter: true,
25 30 defaultHidden: true,
26 31 },
27 32 {
28 33 title: '开始时间',
29 34 width: 120,
  35 + sorter: true,
30 36 dataIndex: 'beginTime',
31 37 },
32 38 {
... ...