Commit 9c2f3f30bbd8abcccc4f256183ed7794da7fcda2
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 | ## Wip | 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 | ### ✨ Features | 53 | ### ✨ Features |
4 | 54 | ||
5 | - 新增 `v-ripple`水波纹指令 | 55 | - 新增 `v-ripple`水波纹指令 |
@@ -12,14 +62,6 @@ | @@ -12,14 +62,6 @@ | ||
12 | - form: 新增远程下拉`ApiSelect`及示例 | 62 | - form: 新增远程下拉`ApiSelect`及示例 |
13 | - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 | 63 | - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 |
14 | - useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改 | 64 | - useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改 |
15 | -- table: 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选狂 | ||
16 | -- table: 监听行点击事件 | ||
17 | -- table: 表格列配置按钮增加 列拖拽,列固定功能。 | ||
18 | -- table:表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示 | ||
19 | - | ||
20 | -### ✨ Refactor | ||
21 | - | ||
22 | -- 重构表单,解决已知 bug | ||
23 | 65 | ||
24 | ### ⚡ Performance Improvements | 66 | ### ⚡ Performance Improvements |
25 | 67 | ||
@@ -30,6 +72,7 @@ | @@ -30,6 +72,7 @@ | ||
30 | ### 🎫 Chores | 72 | ### 🎫 Chores |
31 | 73 | ||
32 | - 升级`ant-design-vue`到`2.0.0-rc.7` | 74 | - 升级`ant-design-vue`到`2.0.0-rc.7` |
75 | +- 升级`vue`到`3.0.5` | ||
33 | 76 | ||
34 | ### 🐛 Bug Fixes | 77 | ### 🐛 Bug Fixes |
35 | 78 |
mock/demo/table-demo.ts
@@ -10,6 +10,14 @@ const demoList = (() => { | @@ -10,6 +10,14 @@ const demoList = (() => { | ||
10 | endTime: '@datetime', | 10 | endTime: '@datetime', |
11 | address: '@city()', | 11 | address: '@city()', |
12 | name: '@cname()', | 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 | 'no|100000-10000000': 100000, | 21 | 'no|100000-10000000': 100000, |
14 | 'status|1': ['normal', 'enable', 'disable'], | 22 | 'status|1': ['normal', 'enable', 'disable'], |
15 | }); | 23 | }); |
src/components/Form/index.ts
@@ -9,4 +9,7 @@ export * from './src/types/formItem'; | @@ -9,4 +9,7 @@ export * from './src/types/formItem'; | ||
9 | export { useComponentRegister } from './src/hooks/useComponentRegister'; | 9 | export { useComponentRegister } from './src/hooks/useComponentRegister'; |
10 | export { useForm } from './src/hooks/useForm'; | 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 | export { BasicForm }; | 15 | export { BasicForm }; |
src/components/Form/src/components/ApiSelect.vue
@@ -50,7 +50,8 @@ | @@ -50,7 +50,8 @@ | ||
50 | labelField: propTypes.string.def('label'), | 50 | labelField: propTypes.string.def('label'), |
51 | valueField: propTypes.string.def('value'), | 51 | valueField: propTypes.string.def('value'), |
52 | }, | 52 | }, |
53 | - setup(props) { | 53 | + emits: ['options-change', 'change'], |
54 | + setup(props, { emit }) { | ||
54 | const options = ref<OptionsItem[]>([]); | 55 | const options = ref<OptionsItem[]>([]); |
55 | const loading = ref(false); | 56 | const loading = ref(false); |
56 | const attrs = useAttrs(); | 57 | const attrs = useAttrs(); |
@@ -86,11 +87,13 @@ | @@ -86,11 +87,13 @@ | ||
86 | const res = await api(props.params); | 87 | const res = await api(props.params); |
87 | if (Array.isArray(res)) { | 88 | if (Array.isArray(res)) { |
88 | options.value = res; | 89 | options.value = res; |
90 | + emit('options-change', unref(options)); | ||
89 | return; | 91 | return; |
90 | } | 92 | } |
91 | if (props.resultField) { | 93 | if (props.resultField) { |
92 | options.value = get(res, props.resultField) || []; | 94 | options.value = get(res, props.resultField) || []; |
93 | } | 95 | } |
96 | + emit('options-change', unref(options)); | ||
94 | } catch (error) { | 97 | } catch (error) { |
95 | console.warn(error); | 98 | console.warn(error); |
96 | } finally { | 99 | } finally { |
src/components/Menu/src/useOpenKeys.ts
@@ -15,7 +15,7 @@ export function useOpenKeys( | @@ -15,7 +15,7 @@ export function useOpenKeys( | ||
15 | mode: Ref<MenuModeEnum>, | 15 | mode: Ref<MenuModeEnum>, |
16 | accordion: Ref<boolean> | 16 | accordion: Ref<boolean> |
17 | ) { | 17 | ) { |
18 | - const { getCollapsed } = useMenuSetting(); | 18 | + const { getCollapsed, getIsMixSidebar } = useMenuSetting(); |
19 | 19 | ||
20 | function setOpenKeys(path: string) { | 20 | function setOpenKeys(path: string) { |
21 | if (mode.value === MenuModeEnum.HORIZONTAL) { | 21 | if (mode.value === MenuModeEnum.HORIZONTAL) { |
@@ -30,7 +30,9 @@ export function useOpenKeys( | @@ -30,7 +30,9 @@ export function useOpenKeys( | ||
30 | } | 30 | } |
31 | 31 | ||
32 | const getOpenKeys = computed(() => { | 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,7 +44,7 @@ export function useOpenKeys( | ||
42 | } | 44 | } |
43 | 45 | ||
44 | function handleOpenChange(openKeys: string[]) { | 46 | function handleOpenChange(openKeys: string[]) { |
45 | - if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion)) { | 47 | + if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion) || unref(getIsMixSidebar)) { |
46 | menuState.openKeys = openKeys; | 48 | menuState.openKeys = openKeys; |
47 | } else { | 49 | } else { |
48 | // const menuList = toRaw(menus.value); | 50 | // const menuList = toRaw(menus.value); |
src/components/Table/index.ts
@@ -3,7 +3,6 @@ import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | @@ -3,7 +3,6 @@ import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | ||
3 | export { default as BasicTable } from './src/BasicTable.vue'; | 3 | export { default as BasicTable } from './src/BasicTable.vue'; |
4 | export { default as TableAction } from './src/components/TableAction.vue'; | 4 | export { default as TableAction } from './src/components/TableAction.vue'; |
5 | // export { default as TableImg } from './src/components/TableImg.vue'; | 5 | // export { default as TableImg } from './src/components/TableImg.vue'; |
6 | -export { renderEditableCell, renderEditableRow } from './src/components/renderEditable'; | ||
7 | export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; | 6 | export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; |
8 | 7 | ||
9 | export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue')); | 8 | export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue')); |
@@ -17,4 +16,4 @@ export { useTable } from './src/hooks/useTable'; | @@ -17,4 +16,4 @@ export { useTable } from './src/hooks/useTable'; | ||
17 | 16 | ||
18 | export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'; | 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,19 +34,19 @@ | ||
34 | <template #[item]="data" v-for="item in Object.keys($slots)"> | 34 | <template #[item]="data" v-for="item in Object.keys($slots)"> |
35 | <slot :name="item" v-bind="data" /> | 35 | <slot :name="item" v-bind="data" /> |
36 | </template> | 36 | </template> |
37 | + <template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex"> | ||
38 | + <HeaderCell :column="column" /> | ||
39 | + </template> | ||
37 | </Table> | 40 | </Table> |
38 | </div> | 41 | </div> |
39 | </template> | 42 | </template> |
40 | <script lang="ts"> | 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 | import { defineComponent, ref, computed, unref } from 'vue'; | 46 | import { defineComponent, ref, computed, unref } from 'vue'; |
45 | import { Table } from 'ant-design-vue'; | 47 | import { Table } from 'ant-design-vue'; |
46 | import { BasicForm, useForm } from '/@/components/Form/index'; | 48 | import { BasicForm, useForm } from '/@/components/Form/index'; |
47 | 49 | ||
48 | - import { isFunction } from '/@/utils/is'; | ||
49 | - | ||
50 | import { omit } from 'lodash-es'; | 50 | import { omit } from 'lodash-es'; |
51 | 51 | ||
52 | import { usePagination } from './hooks/usePagination'; | 52 | import { usePagination } from './hooks/usePagination'; |
@@ -61,15 +61,20 @@ | @@ -61,15 +61,20 @@ | ||
61 | import { createTableContext } from './hooks/useTableContext'; | 61 | import { createTableContext } from './hooks/useTableContext'; |
62 | import { useTableFooter } from './hooks/useTableFooter'; | 62 | import { useTableFooter } from './hooks/useTableFooter'; |
63 | import { useTableForm } from './hooks/useTableForm'; | 63 | import { useTableForm } from './hooks/useTableForm'; |
64 | + import { useExpose } from '/@/hooks/core/useExpose'; | ||
65 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
64 | 66 | ||
65 | import { basicProps } from './props'; | 67 | import { basicProps } from './props'; |
66 | - import { useExpose } from '/@/hooks/core/useExpose'; | 68 | + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
67 | 69 | ||
68 | import './style/index.less'; | 70 | import './style/index.less'; |
69 | - import { useDesign } from '/@/hooks/web/useDesign'; | ||
70 | export default defineComponent({ | 71 | export default defineComponent({ |
71 | props: basicProps, | 72 | props: basicProps, |
72 | - components: { Table, BasicForm }, | 73 | + components: { |
74 | + Table, | ||
75 | + BasicForm, | ||
76 | + HeaderCell: createAsyncComponent(() => import('./components/HeaderCell.vue')), | ||
77 | + }, | ||
73 | emits: [ | 78 | emits: [ |
74 | 'fetch-success', | 79 | 'fetch-success', |
75 | 'fetch-error', | 80 | 'fetch-error', |
@@ -80,6 +85,8 @@ | @@ -80,6 +85,8 @@ | ||
80 | 'row-contextmenu', | 85 | 'row-contextmenu', |
81 | 'row-mouseenter', | 86 | 'row-mouseenter', |
82 | 'row-mouseleave', | 87 | 'row-mouseleave', |
88 | + 'edit-end', | ||
89 | + 'edit-cancel', | ||
83 | ], | 90 | ], |
84 | setup(props, { attrs, emit, slots }) { | 91 | setup(props, { attrs, emit, slots }) { |
85 | const tableElRef = ref<ComponentRef>(null); | 92 | const tableElRef = ref<ComponentRef>(null); |
@@ -96,15 +103,19 @@ | @@ -96,15 +103,19 @@ | ||
96 | 103 | ||
97 | const { getLoading, setLoading } = useLoading(getProps); | 104 | const { getLoading, setLoading } = useLoading(getProps); |
98 | const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps); | 105 | const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps); |
106 | + | ||
99 | const { | 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 | const { | 117 | const { |
118 | + handleTableChange, | ||
108 | getDataSourceRef, | 119 | getDataSourceRef, |
109 | getDataSource, | 120 | getDataSource, |
110 | setTableData, | 121 | setTableData, |
@@ -112,6 +123,7 @@ | @@ -112,6 +123,7 @@ | ||
112 | getRowKey, | 123 | getRowKey, |
113 | reload, | 124 | reload, |
114 | getAutoCreateKey, | 125 | getAutoCreateKey, |
126 | + updateTableData, | ||
115 | } = useDataSource( | 127 | } = useDataSource( |
116 | getProps, | 128 | getProps, |
117 | { | 129 | { |
@@ -119,19 +131,15 @@ | @@ -119,19 +131,15 @@ | ||
119 | setLoading, | 131 | setLoading, |
120 | setPagination, | 132 | setPagination, |
121 | getFieldsValue: formActions.getFieldsValue, | 133 | getFieldsValue: formActions.getFieldsValue, |
134 | + clearSelectedRowKeys, | ||
122 | }, | 135 | }, |
123 | emit | 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 | const { getScrollRef, redoHeight } = useTableScroll( | 144 | const { getScrollRef, redoHeight } = useTableScroll( |
137 | getProps, | 145 | getProps, |
@@ -178,7 +186,7 @@ | @@ -178,7 +186,7 @@ | ||
178 | tableLayout: 'fixed', | 186 | tableLayout: 'fixed', |
179 | rowSelection: unref(getRowSelectionRef), | 187 | rowSelection: unref(getRowSelectionRef), |
180 | rowKey: unref(getRowKey), | 188 | rowKey: unref(getRowKey), |
181 | - columns: unref(getSortFixedColumns), | 189 | + columns: unref(getViewColumns), |
182 | pagination: unref(getPaginationInfo), | 190 | pagination: unref(getPaginationInfo), |
183 | dataSource: unref(getDataSourceRef), | 191 | dataSource: unref(getDataSourceRef), |
184 | footer: unref(getFooterProps), | 192 | footer: unref(getFooterProps), |
@@ -197,26 +205,6 @@ | @@ -197,26 +205,6 @@ | ||
197 | return !!unref(getDataSourceRef).length; | 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 | function setProps(props: Partial<BasicTableProps>) { | 208 | function setProps(props: Partial<BasicTableProps>) { |
221 | innerPropsRef.value = { ...unref(innerPropsRef), ...props }; | 209 | innerPropsRef.value = { ...unref(innerPropsRef), ...props }; |
222 | } | 210 | } |
@@ -239,6 +227,8 @@ | @@ -239,6 +227,8 @@ | ||
239 | getPaginationRef: getPagination, | 227 | getPaginationRef: getPagination, |
240 | getColumns, | 228 | getColumns, |
241 | getCacheColumns, | 229 | getCacheColumns, |
230 | + emit, | ||
231 | + updateTableData, | ||
242 | getSize: () => { | 232 | getSize: () => { |
243 | return unref(getBindValues).size as SizeType; | 233 | return unref(getBindValues).size as SizeType; |
244 | }, | 234 | }, |
@@ -265,6 +255,7 @@ | @@ -265,6 +255,7 @@ | ||
265 | replaceFormSlotKey, | 255 | replaceFormSlotKey, |
266 | getFormSlotKeys, | 256 | getFormSlotKeys, |
267 | prefixCls, | 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 | import { Input, Select, Checkbox, InputNumber, Switch } from 'ant-design-vue'; | 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 | const componentMap = new Map<ComponentType, Component>(); | 8 | const componentMap = new Map<ComponentType, Component>(); |
8 | 9 | ||
9 | componentMap.set('Input', Input); | 10 | componentMap.set('Input', Input); |
10 | -componentMap.set('InputPassword', Input.Password); | ||
11 | componentMap.set('InputNumber', InputNumber); | 11 | componentMap.set('InputNumber', InputNumber); |
12 | 12 | ||
13 | componentMap.set('Select', Select); | 13 | componentMap.set('Select', Select); |
14 | +componentMap.set('ApiSelect', ApiSelect); | ||
14 | componentMap.set('Switch', Switch); | 15 | componentMap.set('Switch', Switch); |
15 | componentMap.set('Checkbox', Checkbox); | 16 | componentMap.set('Checkbox', Checkbox); |
16 | -componentMap.set('CheckboxGroup', Checkbox.Group); | ||
17 | 17 | ||
18 | export function add(compName: ComponentType, component: Component) { | 18 | export function add(compName: ComponentType, component: Component) { |
19 | componentMap.set(compName, component); | 19 | componentMap.set(compName, component); |
src/components/Table/src/components/EditTableHeaderIcon.vue
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 | <template> | 1 | <template> |
2 | <div :class="[prefixCls, getAlign]"> | 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 | <PopConfirmButton v-bind="action"> | 4 | <PopConfirmButton v-bind="action"> |
5 | <Icon :icon="action.icon" class="mr-1" v-if="action.icon" /> | 5 | <Icon :icon="action.icon" class="mr-1" v-if="action.icon" /> |
6 | {{ action.label }} | 6 | {{ action.label }} |
7 | </PopConfirmButton> | 7 | </PopConfirmButton> |
8 | <Divider type="vertical" v-if="divider && index < getActions.length" /> | 8 | <Divider type="vertical" v-if="divider && index < getActions.length" /> |
9 | </template> | 9 | </template> |
10 | - | ||
11 | - <Dropdown :trigger="['hover']" :dropMenuList="getDropList"> | 10 | + <Dropdown :trigger="['hover']" :dropMenuList="getDropList" v-if="dropDownActions"> |
12 | <slot name="more" /> | 11 | <slot name="more" /> |
13 | <a-button type="link" size="small" v-if="!$slots.more"> | 12 | <a-button type="link" size="small" v-if="!$slots.more"> |
14 | <MoreOutlined class="icon-more" /> | 13 | <MoreOutlined class="icon-more" /> |
@@ -61,7 +60,7 @@ | @@ -61,7 +60,7 @@ | ||
61 | }); | 60 | }); |
62 | 61 | ||
63 | const getDropList = computed(() => { | 62 | const getDropList = computed(() => { |
64 | - return props.dropDownActions.map((action, index) => { | 63 | + return (props.dropDownActions || []).map((action, index) => { |
65 | const { label } = action; | 64 | const { label } = action; |
66 | return { | 65 | return { |
67 | ...action, | 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 || ' ' }} | ||
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
src/components/Table/src/components/settings/ColumnSetting.vue
@@ -184,7 +184,7 @@ | @@ -184,7 +184,7 @@ | ||
184 | const ret: Options[] = []; | 184 | const ret: Options[] = []; |
185 | table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { | 185 | table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { |
186 | ret.push({ | 186 | ret.push({ |
187 | - label: item.title as string, | 187 | + label: (item.title as string) || (item.customTitle as string), |
188 | value: (item.dataIndex || item.title) as string, | 188 | value: (item.dataIndex || item.title) as string, |
189 | ...item, | 189 | ...item, |
190 | }); | 190 | }); |
src/components/Table/src/const.ts
@@ -32,6 +32,10 @@ export function DEFAULT_SORT_FN(sortInfo: SorterResult) { | @@ -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 | export const DEFAULT_ALIGN = 'center'; | 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 | import { unref, ComputedRef, Ref, computed, watchEffect, ref, toRaw } from 'vue'; | 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 | import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const'; | 5 | import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const'; |
6 | import { useI18n } from '/@/hooks/web/useI18n'; | 6 | import { useI18n } from '/@/hooks/web/useI18n'; |
7 | import { isEqual, cloneDeep } from 'lodash-es'; | 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 | const { t } = useI18n(); | 12 | const { t } = useI18n(); |
10 | 13 | ||
@@ -127,8 +130,30 @@ export function useColumns( | @@ -127,8 +130,30 @@ export function useColumns( | ||
127 | return columns; | 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 | watchEffect(() => { | 159 | watchEffect(() => { |
@@ -191,7 +216,7 @@ export function useColumns( | @@ -191,7 +216,7 @@ export function useColumns( | ||
191 | } | 216 | } |
192 | 217 | ||
193 | if (sort) { | 218 | if (sort) { |
194 | - columns = useFixedColumn(columns); | 219 | + columns = sortFixedColumn(columns); |
195 | } | 220 | } |
196 | 221 | ||
197 | return columns; | 222 | return columns; |
@@ -200,10 +225,10 @@ export function useColumns( | @@ -200,10 +225,10 @@ export function useColumns( | ||
200 | return cacheColumns; | 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 | const fixedLeftColumns: BasicColumn[] = []; | 232 | const fixedLeftColumns: BasicColumn[] = []; |
208 | const fixedRightColumns: BasicColumn[] = []; | 233 | const fixedRightColumns: BasicColumn[] = []; |
209 | const defColumns: BasicColumn[] = []; | 234 | const defColumns: BasicColumn[] = []; |
@@ -224,3 +249,35 @@ export function useFixedColumn(columns: BasicColumn[]) { | @@ -224,3 +249,35 @@ export function useFixedColumn(columns: BasicColumn[]) { | ||
224 | 249 | ||
225 | return resultColumns; | 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 | import type { PaginationProps } from '../types/pagination'; | 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 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; | 6 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
7 | 7 | ||
@@ -16,12 +16,28 @@ interface ActionType { | @@ -16,12 +16,28 @@ interface ActionType { | ||
16 | setPagination: (info: Partial<PaginationProps>) => void; | 16 | setPagination: (info: Partial<PaginationProps>) => void; |
17 | setLoading: (loading: boolean) => void; | 17 | setLoading: (loading: boolean) => void; |
18 | getFieldsValue: () => Recordable; | 18 | getFieldsValue: () => Recordable; |
19 | + clearSelectedRowKeys: () => void; | ||
20 | +} | ||
21 | + | ||
22 | +interface SearchState { | ||
23 | + sortInfo: Recordable; | ||
24 | + filterInfo: Record<string, string[]>; | ||
19 | } | 25 | } |
20 | export function useDataSource( | 26 | export function useDataSource( |
21 | propsRef: ComputedRef<BasicTableProps>, | 27 | propsRef: ComputedRef<BasicTableProps>, |
22 | - { getPaginationInfo, setPagination, setLoading, getFieldsValue }: ActionType, | 28 | + { |
29 | + getPaginationInfo, | ||
30 | + setPagination, | ||
31 | + setLoading, | ||
32 | + getFieldsValue, | ||
33 | + clearSelectedRowKeys, | ||
34 | + }: ActionType, | ||
23 | emit: EmitType | 35 | emit: EmitType |
24 | ) { | 36 | ) { |
37 | + const searchState = reactive<SearchState>({ | ||
38 | + sortInfo: {}, | ||
39 | + filterInfo: {}, | ||
40 | + }); | ||
25 | const dataSourceRef = ref<Recordable[]>([]); | 41 | const dataSourceRef = ref<Recordable[]>([]); |
26 | 42 | ||
27 | watchEffect(() => { | 43 | watchEffect(() => { |
@@ -29,6 +45,32 @@ export function useDataSource( | @@ -29,6 +45,32 @@ export function useDataSource( | ||
29 | !api && dataSource && (dataSourceRef.value = dataSource); | 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 | function setTableKey(items: any[]) { | 74 | function setTableKey(items: any[]) { |
33 | if (!items || !Array.isArray(items)) return; | 75 | if (!items || !Array.isArray(items)) return; |
34 | items.forEach((item) => { | 76 | items.forEach((item) => { |
@@ -75,6 +117,14 @@ export function useDataSource( | @@ -75,6 +117,14 @@ export function useDataSource( | ||
75 | return unref(dataSourceRef); | 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 | async function fetch(opt?: FetchParams) { | 128 | async function fetch(opt?: FetchParams) { |
79 | const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref( | 129 | const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref( |
80 | propsRef | 130 | propsRef |
@@ -94,6 +144,8 @@ export function useDataSource( | @@ -94,6 +144,8 @@ export function useDataSource( | ||
94 | pageParams[sizeField] = pageSize; | 144 | pageParams[sizeField] = pageSize; |
95 | } | 145 | } |
96 | 146 | ||
147 | + const { sortInfo = {}, filterInfo } = searchState; | ||
148 | + | ||
97 | let params: Recordable = { | 149 | let params: Recordable = { |
98 | ...pageParams, | 150 | ...pageParams, |
99 | ...(useSearchForm ? getFieldsValue() : {}), | 151 | ...(useSearchForm ? getFieldsValue() : {}), |
@@ -101,6 +153,8 @@ export function useDataSource( | @@ -101,6 +153,8 @@ export function useDataSource( | ||
101 | ...(opt ? opt.searchInfo : {}), | 153 | ...(opt ? opt.searchInfo : {}), |
102 | ...(opt ? opt.sortInfo : {}), | 154 | ...(opt ? opt.sortInfo : {}), |
103 | ...(opt ? opt.filterInfo : {}), | 155 | ...(opt ? opt.filterInfo : {}), |
156 | + ...sortInfo, | ||
157 | + ...filterInfo, | ||
104 | }; | 158 | }; |
105 | if (beforeFetch && isFunction(beforeFetch)) { | 159 | if (beforeFetch && isFunction(beforeFetch)) { |
106 | params = beforeFetch(params) || params; | 160 | params = beforeFetch(params) || params; |
@@ -175,5 +229,7 @@ export function useDataSource( | @@ -175,5 +229,7 @@ export function useDataSource( | ||
175 | getAutoCreateKey, | 229 | getAutoCreateKey, |
176 | fetch, | 230 | fetch, |
177 | reload, | 231 | reload, |
232 | + updateTableData, | ||
233 | + handleTableChange, | ||
178 | }; | 234 | }; |
179 | } | 235 | } |
src/components/Table/src/hooks/useTable.ts
1 | import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'; | 1 | import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'; |
2 | import type { PaginationProps } from '../types/pagination'; | 2 | import type { PaginationProps } from '../types/pagination'; |
3 | +import type { DynamicProps } from '/@/types/utils'; | ||
4 | +import { getDynamicProps } from '/@/utils'; | ||
3 | 5 | ||
4 | import { ref, onUnmounted, unref } from 'vue'; | 6 | import { ref, onUnmounted, unref } from 'vue'; |
5 | import { isProdMode } from '/@/utils/env'; | 7 | import { isProdMode } from '/@/utils/env'; |
6 | import { isInSetup } from '/@/utils/helper/vueHelper'; | 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 | export function useTable( | 15 | export function useTable( |
9 | - tableProps?: Partial<BasicTableProps> | ||
10 | -): [(instance: TableActionType) => void, TableActionType] { | 16 | + tableProps?: Props |
17 | +): [(instance: TableActionType, formInstance: FormActionType) => void, TableActionType] { | ||
11 | isInSetup(); | 18 | isInSetup(); |
12 | 19 | ||
13 | const tableRef = ref<Nullable<TableActionType>>(null); | 20 | const tableRef = ref<Nullable<TableActionType>>(null); |
14 | const loadedRef = ref<Nullable<boolean>>(false); | 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 | isProdMode() && | 25 | isProdMode() && |
18 | onUnmounted(() => { | 26 | onUnmounted(() => { |
19 | tableRef.value = null; | 27 | tableRef.value = null; |
@@ -24,20 +32,29 @@ export function useTable( | @@ -24,20 +32,29 @@ export function useTable( | ||
24 | return; | 32 | return; |
25 | } | 33 | } |
26 | tableRef.value = instance; | 34 | tableRef.value = instance; |
27 | - tableProps && instance.setProps(tableProps); | 35 | + formRef.value = formInstance; |
36 | + // tableProps && instance.setProps(tableProps); | ||
28 | loadedRef.value = true; | 37 | loadedRef.value = true; |
38 | + | ||
39 | + watchEffect(() => { | ||
40 | + tableProps && instance.setProps(getDynamicProps(tableProps)); | ||
41 | + }); | ||
29 | } | 42 | } |
30 | 43 | ||
31 | function getTableInstance(): TableActionType { | 44 | function getTableInstance(): TableActionType { |
32 | const table = unref(tableRef); | 45 | const table = unref(tableRef); |
33 | if (!table) { | 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 | getTableInstance().reload(opt); | 58 | getTableInstance().reload(opt); |
42 | }, | 59 | }, |
43 | setProps: (props: Partial<BasicTableProps>) => { | 60 | setProps: (props: Partial<BasicTableProps>) => { |
@@ -54,7 +71,6 @@ export function useTable( | @@ -54,7 +71,6 @@ export function useTable( | ||
54 | }, | 71 | }, |
55 | getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => { | 72 | getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => { |
56 | const columns = getTableInstance().getColumns({ ignoreIndex }) || []; | 73 | const columns = getTableInstance().getColumns({ ignoreIndex }) || []; |
57 | - | ||
58 | return columns; | 74 | return columns; |
59 | }, | 75 | }, |
60 | setColumns: (columns: BasicColumn[]) => { | 76 | setColumns: (columns: BasicColumn[]) => { |
@@ -87,7 +103,19 @@ export function useTable( | @@ -87,7 +103,19 @@ export function useTable( | ||
87 | getSize: () => { | 103 | getSize: () => { |
88 | return getTableInstance().getSize(); | 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 | return [register, methods]; | 120 | return [register, methods]; |
93 | } | 121 | } |
src/components/Table/src/hooks/useTableScroll.ts
@@ -121,7 +121,7 @@ export function useTableScroll( | @@ -121,7 +121,7 @@ export function useTableScroll( | ||
121 | width += 60; | 121 | width += 60; |
122 | } | 122 | } |
123 | 123 | ||
124 | - // TODO props | 124 | + // TODO propsdth ?? 0; |
125 | const NORMAL_WIDTH = 150; | 125 | const NORMAL_WIDTH = 150; |
126 | 126 | ||
127 | const columns = unref(columnsRef); | 127 | const columns = unref(columnsRef); |
@@ -135,7 +135,10 @@ export function useTableScroll( | @@ -135,7 +135,10 @@ export function useTableScroll( | ||
135 | if (len !== 0) { | 135 | if (len !== 0) { |
136 | width += len * NORMAL_WIDTH; | 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 | const getScrollRef = computed(() => { | 144 | const getScrollRef = computed(() => { |
src/components/Table/src/props.ts
@@ -9,21 +9,29 @@ import type { | @@ -9,21 +9,29 @@ import type { | ||
9 | TableRowSelection, | 9 | TableRowSelection, |
10 | } from './types/table'; | 10 | } from './types/table'; |
11 | import type { FormProps } from '/@/components/Form'; | 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 | import { propTypes } from '/@/utils/propTypes'; | 13 | import { propTypes } from '/@/utils/propTypes'; |
14 | 14 | ||
15 | // 注释看 types/table | 15 | // 注释看 types/table |
16 | export const basicProps = { | 16 | export const basicProps = { |
17 | clickToRowSelect: propTypes.bool.def(true), | 17 | clickToRowSelect: propTypes.bool.def(true), |
18 | + | ||
18 | tableSetting: { | 19 | tableSetting: { |
19 | type: Object as PropType<TableSetting>, | 20 | type: Object as PropType<TableSetting>, |
20 | }, | 21 | }, |
22 | + | ||
21 | inset: propTypes.bool, | 23 | inset: propTypes.bool, |
24 | + | ||
22 | sortFn: { | 25 | sortFn: { |
23 | type: Function as PropType<(sortInfo: SorterResult) => any>, | 26 | type: Function as PropType<(sortInfo: SorterResult) => any>, |
24 | default: DEFAULT_SORT_FN, | 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 | showTableSetting: propTypes.bool, | 35 | showTableSetting: propTypes.bool, |
28 | autoCreateKey: propTypes.bool.def(true), | 36 | autoCreateKey: propTypes.bool.def(true), |
29 | striped: propTypes.bool.def(true), | 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,14 +133,18 @@ | ||
133 | overflow-y: scroll !important; | 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
src/components/Table/src/types/table.ts
@@ -6,9 +6,10 @@ import type { | @@ -6,9 +6,10 @@ import type { | ||
6 | TableRowSelection as ITableRowSelection, | 6 | TableRowSelection as ITableRowSelection, |
7 | } from 'ant-design-vue/lib/table/interface'; | 7 | } from 'ant-design-vue/lib/table/interface'; |
8 | import { ComponentType } from './componentType'; | 8 | import { ComponentType } from './componentType'; |
9 | +import { VueNode } from '/@/utils/propTypes'; | ||
9 | // import { ColumnProps } from './column'; | 10 | // import { ColumnProps } from './column'; |
10 | export declare type SortOrder = 'ascend' | 'descend'; | 11 | export declare type SortOrder = 'ascend' | 'descend'; |
11 | -export interface TableCurrentDataSource<T = any> { | 12 | +export interface TableCurrentDataSource<T = Recordable> { |
12 | currentDataSource: T[]; | 13 | currentDataSource: T[]; |
13 | } | 14 | } |
14 | 15 | ||
@@ -53,7 +54,7 @@ export interface ColumnFilterItem { | @@ -53,7 +54,7 @@ export interface ColumnFilterItem { | ||
53 | children?: any; | 54 | children?: any; |
54 | } | 55 | } |
55 | 56 | ||
56 | -export interface TableCustomRecord<T = any> { | 57 | +export interface TableCustomRecord<T = Recordable> { |
57 | record?: T; | 58 | record?: T; |
58 | index?: number; | 59 | index?: number; |
59 | } | 60 | } |
@@ -65,18 +66,11 @@ export interface SorterResult { | @@ -65,18 +66,11 @@ export interface SorterResult { | ||
65 | columnKey: string; | 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 | export interface FetchParams { | 69 | export interface FetchParams { |
76 | - searchInfo?: any; | 70 | + searchInfo?: Recordable; |
77 | page?: number; | 71 | page?: number; |
78 | - sortInfo?: any; | ||
79 | - filterInfo?: any; | 72 | + sortInfo?: Recordable; |
73 | + filterInfo?: Recordable; | ||
80 | } | 74 | } |
81 | 75 | ||
82 | export interface GetColumnsParams { | 76 | export interface GetColumnsParams { |
@@ -89,7 +83,7 @@ export type SizeType = 'default' | 'middle' | 'small' | 'large'; | @@ -89,7 +83,7 @@ export type SizeType = 'default' | 'middle' | 'small' | 'large'; | ||
89 | 83 | ||
90 | export interface TableActionType { | 84 | export interface TableActionType { |
91 | reload: (opt?: FetchParams) => Promise<void>; | 85 | reload: (opt?: FetchParams) => Promise<void>; |
92 | - getSelectRows: <T = any>() => T[]; | 86 | + getSelectRows: <T = Recordable>() => T[]; |
93 | clearSelectedRowKeys: () => void; | 87 | clearSelectedRowKeys: () => void; |
94 | getSelectRowKeys: () => string[]; | 88 | getSelectRowKeys: () => string[]; |
95 | deleteSelectRowByKey: (key: string) => void; | 89 | deleteSelectRowByKey: (key: string) => void; |
@@ -106,6 +100,8 @@ export interface TableActionType { | @@ -106,6 +100,8 @@ export interface TableActionType { | ||
106 | getSize: () => SizeType; | 100 | getSize: () => SizeType; |
107 | getRowSelection: () => TableRowSelection<Recordable>; | 101 | getRowSelection: () => TableRowSelection<Recordable>; |
108 | getCacheColumns: () => BasicColumn[]; | 102 | getCacheColumns: () => BasicColumn[]; |
103 | + emit?: EmitType; | ||
104 | + updateTableData: (index: number, key: string, value: any) => Recordable; | ||
109 | } | 105 | } |
110 | 106 | ||
111 | export interface FetchSetting { | 107 | export interface FetchSetting { |
@@ -131,6 +127,8 @@ export interface BasicTableProps<T = any> { | @@ -131,6 +127,8 @@ export interface BasicTableProps<T = any> { | ||
131 | clickToRowSelect?: boolean; | 127 | clickToRowSelect?: boolean; |
132 | // 自定义排序方法 | 128 | // 自定义排序方法 |
133 | sortFn?: (sortInfo: SorterResult) => any; | 129 | sortFn?: (sortInfo: SorterResult) => any; |
130 | + // 排序方法 | ||
131 | + filterFn?: (data: Partial<Recordable<string[]>>) => any; | ||
134 | // 取消表格的默认padding | 132 | // 取消表格的默认padding |
135 | inset?: boolean; | 133 | inset?: boolean; |
136 | // 显示表格设置 | 134 | // 显示表格设置 |
@@ -141,7 +139,7 @@ export interface BasicTableProps<T = any> { | @@ -141,7 +139,7 @@ export interface BasicTableProps<T = any> { | ||
141 | // 是否自动生成key | 139 | // 是否自动生成key |
142 | autoCreateKey?: boolean; | 140 | autoCreateKey?: boolean; |
143 | // 计算合计行的方法 | 141 | // 计算合计行的方法 |
144 | - summaryFunc?: (...arg: any) => any[]; | 142 | + summaryFunc?: (...arg: any) => Recordable[]; |
145 | // 是否显示合计行 | 143 | // 是否显示合计行 |
146 | showSummary?: boolean; | 144 | showSummary?: boolean; |
147 | // 是否可拖拽列 | 145 | // 是否可拖拽列 |
@@ -374,13 +372,43 @@ export interface BasicTableProps<T = any> { | @@ -374,13 +372,43 @@ export interface BasicTableProps<T = any> { | ||
374 | onExpandedRowsChange?: (expandedRows: string[] | number[]) => void; | 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 | export interface BasicColumn extends ColumnProps { | 381 | export interface BasicColumn extends ColumnProps { |
378 | children?: BasicColumn[]; | 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 | flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION'; | 392 | flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION'; |
393 | + customTitle?: VueNode; | ||
382 | 394 | ||
383 | slots?: Indexable; | 395 | slots?: Indexable; |
384 | 396 | ||
397 | + // Whether to hide the column by default, it can be displayed in the column configuration | ||
385 | defaultHidden?: boolean; | 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
src/router/menus/modules/demo/comp.ts
@@ -52,6 +52,9 @@ const menu: MenuModule = { | @@ -52,6 +52,9 @@ const menu: MenuModule = { | ||
52 | { | 52 | { |
53 | path: 'table', | 53 | path: 'table', |
54 | name: t('routes.demo.table.table'), | 54 | name: t('routes.demo.table.table'), |
55 | + tag: { | ||
56 | + dot: true, | ||
57 | + }, | ||
55 | children: [ | 58 | children: [ |
56 | { | 59 | { |
57 | path: 'basic', | 60 | path: 'basic', |
@@ -108,10 +111,16 @@ const menu: MenuModule = { | @@ -108,10 +111,16 @@ const menu: MenuModule = { | ||
108 | { | 111 | { |
109 | path: 'editCellTable', | 112 | path: 'editCellTable', |
110 | name: t('routes.demo.table.editCellTable'), | 113 | name: t('routes.demo.table.editCellTable'), |
114 | + tag: { | ||
115 | + dot: true, | ||
116 | + }, | ||
111 | }, | 117 | }, |
112 | { | 118 | { |
113 | path: 'editRowTable', | 119 | path: 'editRowTable', |
114 | name: t('routes.demo.table.editRowTable'), | 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 'moment'; | @@ -3,12 +3,15 @@ import moment from 'moment'; | ||
3 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; | 3 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; |
4 | const DATE_FORMAT = 'YYYY-MM-DD '; | 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 | export const formatAgo = (str: string | number) => { | 17 | export const formatAgo = (str: string | number) => { |
src/views/demo/table/EditCellTable.vue
1 | <template> | 1 | <template> |
2 | <div class="p-4"> | 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 | </BasicTable> | 4 | </BasicTable> |
11 | </div> | 5 | </div> |
12 | </template> | 6 | </template> |
13 | <script lang="ts"> | 7 | <script lang="ts"> |
14 | import { defineComponent } from 'vue'; | 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 | import { demoListApi } from '/@/api/demo/table'; | 12 | import { demoListApi } from '/@/api/demo/table'; |
24 | const columns: BasicColumn[] = [ | 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 | dataIndex: 'id', | 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 | export default defineComponent({ | 108 | export default defineComponent({ |
@@ -50,10 +113,21 @@ | @@ -50,10 +113,21 @@ | ||
50 | api: demoListApi, | 113 | api: demoListApi, |
51 | columns: columns, | 114 | columns: columns, |
52 | showIndexColumn: false, | 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 | return { | 127 | return { |
56 | registerTable, | 128 | registerTable, |
129 | + handleEditEnd, | ||
130 | + handleEditCancel, | ||
57 | }; | 131 | }; |
58 | }, | 132 | }, |
59 | }); | 133 | }); |
src/views/demo/table/EditRowTable.vue
@@ -15,24 +15,105 @@ | @@ -15,24 +15,105 @@ | ||
15 | TableAction, | 15 | TableAction, |
16 | BasicColumn, | 16 | BasicColumn, |
17 | ActionItem, | 17 | ActionItem, |
18 | - renderEditableRow, | ||
19 | EditTableHeaderIcon, | 18 | EditTableHeaderIcon, |
20 | EditRecordRow, | 19 | EditRecordRow, |
21 | } from '/@/components/Table'; | 20 | } from '/@/components/Table'; |
21 | + import { optionsListApi } from '/@/api/demo/select'; | ||
22 | 22 | ||
23 | import { demoListApi } from '/@/api/demo/table'; | 23 | import { demoListApi } from '/@/api/demo/table'; |
24 | const columns: BasicColumn[] = [ | 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 | dataIndex: 'id', | 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 | export default defineComponent({ | 119 | export default defineComponent({ |
@@ -55,19 +136,19 @@ | @@ -55,19 +136,19 @@ | ||
55 | 136 | ||
56 | function handleEdit(record: EditRecordRow) { | 137 | function handleEdit(record: EditRecordRow) { |
57 | currentEditKeyRef.value = record.key; | 138 | currentEditKeyRef.value = record.key; |
58 | - record.editable = true; | 139 | + record.onEdit?.(true); |
59 | } | 140 | } |
60 | 141 | ||
61 | function handleCancel(record: EditRecordRow) { | 142 | function handleCancel(record: EditRecordRow) { |
62 | currentEditKeyRef.value = ''; | 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 | function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] { | 154 | function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] { |
src/views/demo/table/FixedColumn.vue
@@ -41,7 +41,6 @@ | @@ -41,7 +41,6 @@ | ||
41 | { | 41 | { |
42 | title: '地址', | 42 | title: '地址', |
43 | dataIndex: 'address', | 43 | dataIndex: 'address', |
44 | - width: 260, | ||
45 | }, | 44 | }, |
46 | { | 45 | { |
47 | title: '编号', | 46 | title: '编号', |
@@ -67,6 +66,7 @@ | @@ -67,6 +66,7 @@ | ||
67 | api: demoListApi, | 66 | api: demoListApi, |
68 | columns: columns, | 67 | columns: columns, |
69 | rowSelection: { type: 'radio' }, | 68 | rowSelection: { type: 'radio' }, |
69 | + bordered: true, | ||
70 | actionColumn: { | 70 | actionColumn: { |
71 | width: 160, | 71 | width: 160, |
72 | title: 'Action', | 72 | title: 'Action', |
src/views/demo/table/tableData.tsx
@@ -7,12 +7,16 @@ export function getBasicColumns(): BasicColumn[] { | @@ -7,12 +7,16 @@ export function getBasicColumns(): BasicColumn[] { | ||
7 | title: 'ID', | 7 | title: 'ID', |
8 | dataIndex: 'id', | 8 | dataIndex: 'id', |
9 | fixed: 'left', | 9 | fixed: 'left', |
10 | - width: 400, | 10 | + width: 200, |
11 | }, | 11 | }, |
12 | { | 12 | { |
13 | title: '姓名', | 13 | title: '姓名', |
14 | dataIndex: 'name', | 14 | dataIndex: 'name', |
15 | width: 150, | 15 | width: 150, |
16 | + filters: [ | ||
17 | + { text: 'Male', value: 'male' }, | ||
18 | + { text: 'Female', value: 'female' }, | ||
19 | + ], | ||
16 | }, | 20 | }, |
17 | { | 21 | { |
18 | title: '地址', | 22 | title: '地址', |
@@ -22,11 +26,13 @@ export function getBasicColumns(): BasicColumn[] { | @@ -22,11 +26,13 @@ export function getBasicColumns(): BasicColumn[] { | ||
22 | title: '编号', | 26 | title: '编号', |
23 | dataIndex: 'no', | 27 | dataIndex: 'no', |
24 | width: 150, | 28 | width: 150, |
29 | + sorter: true, | ||
25 | defaultHidden: true, | 30 | defaultHidden: true, |
26 | }, | 31 | }, |
27 | { | 32 | { |
28 | title: '开始时间', | 33 | title: '开始时间', |
29 | width: 120, | 34 | width: 120, |
35 | + sorter: true, | ||
30 | dataIndex: 'beginTime', | 36 | dataIndex: 'beginTime', |
31 | }, | 37 | }, |
32 | { | 38 | { |