Skip to content

Commit b24e25a

Browse files
committed
feat: edit-row-table support save loading
1 parent 162e16e commit b24e25a

File tree

9 files changed

+315
-327
lines changed

9 files changed

+315
-327
lines changed

package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
},
3535
"dependencies": {
3636
"@ant-design/icons-vue": "~6.1.0",
37-
"@vueuse/core": "~9.1.0",
37+
"@vueuse/core": "~9.1.1",
3838
"ant-design-vue": "3.2.11",
3939
"axios": "~0.27.2",
4040
"core-js": "~3.24.1",
@@ -58,10 +58,10 @@
5858
"@commitlint/cli": "~17.0.3",
5959
"@commitlint/config-conventional": "~17.0.3",
6060
"@types/lodash-es": "~4.17.6",
61-
"@types/node": "~18.7.6",
61+
"@types/node": "~18.7.13",
6262
"@types/webpack-env": "~1.18.0",
63-
"@typescript-eslint/eslint-plugin": "~5.33.1",
64-
"@typescript-eslint/parser": "~5.33.1",
63+
"@typescript-eslint/eslint-plugin": "~5.34.0",
64+
"@typescript-eslint/parser": "~5.34.0",
6565
"@vue/cli-plugin-babel": "~5.0.8",
6666
"@vue/cli-plugin-eslint": "~5.0.8",
6767
"@vue/cli-plugin-router": "~5.0.8",
@@ -72,13 +72,13 @@
7272
"babel-plugin-import": "~1.13.5",
7373
"conventional-changelog-cli": "~2.2.2",
7474
"cross-env": "~7.0.3",
75-
"cz-git": "~1.3.10",
76-
"czg": "~1.3.10",
75+
"cz-git": "~1.3.11",
76+
"czg": "~1.3.11",
7777
"eslint": "~8.22.0",
7878
"eslint-config-prettier": "~8.5.0",
7979
"eslint-plugin-import": "2.26.0",
8080
"eslint-plugin-prettier": "~4.2.1",
81-
"eslint-plugin-vue": "~9.3.0",
81+
"eslint-plugin-vue": "~9.4.0",
8282
"husky": "~8.0.1",
8383
"less": "~4.1.3",
8484
"less-loader": "11.0.0",
@@ -95,7 +95,7 @@
9595
"stylelint-config-prettier": "~9.0.3",
9696
"stylelint-config-recommended": "~9.0.0",
9797
"stylelint-config-recommended-vue": "~1.4.0",
98-
"stylelint-config-standard": "~27.0.0",
98+
"stylelint-config-standard": "~28.0.0",
9999
"stylelint-order": "~5.0.0",
100100
"svg-sprite-loader": "~6.0.11",
101101
"typescript": "~4.7.4",
Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,98 @@
11
<template>
2-
<template v-for="(actionItem, _index) in actionFilters" :key="`${_index}-${actionItem.label}`">
2+
<template v-for="(actionItem, index) in actionFilters" :key="`${index}-${actionItem.label}`">
33
<component
4-
:is="actionItem.popConfirm ? Popconfirm : 'span'"
4+
:is="actionItem.popConfirm ? 'a-popconfirm' : 'span'"
55
:title="actionItem.title"
66
v-bind="actionItem.popConfirm"
77
>
8-
<a-button type="link" v-bind="actionItem">{{ actionItem.label }}</a-button>
8+
<a-button
9+
type="link"
10+
:loading="loadingMap.get(getKey(actionItem, index))"
11+
v-bind="actionItem"
12+
>
13+
{{ actionItem.label }}
14+
</a-button>
915
</component>
1016
</template>
1117
</template>
1218

13-
<script lang="ts" setup>
14-
import { computed } from 'vue';
19+
<script lang="ts">
20+
import { defineComponent, computed, ref } from 'vue';
1521
import { Popconfirm } from 'ant-design-vue';
1622
import type { PropType } from 'vue';
1723
import type { ActionItem } from '../types/tableAction';
24+
import type { CustomRenderParams } from '../types/column';
1825
import { verifyAuth } from '@/core/permission/';
19-
import { isString, isObject } from '@/utils/is';
26+
import { isString, isObject, isAsyncFunction } from '@/utils/is';
2027
21-
const props = defineProps({
22-
actions: {
23-
// 表格行动作
24-
type: Array as PropType<ActionItem[]>,
25-
default: () => [],
28+
export default defineComponent({
29+
components: { [Popconfirm.name]: Popconfirm },
30+
props: {
31+
actions: {
32+
// 表格行动作
33+
type: Array as PropType<ActionItem[]>,
34+
default: () => [],
35+
},
36+
columnParams: {
37+
type: Object as PropType<CustomRenderParams>,
38+
default: () => ({}),
39+
},
40+
rowKey: [String, Number] as PropType<Key>,
2641
},
27-
});
42+
setup(props) {
43+
const loadingMap = ref(new Map<string, boolean>());
44+
45+
const actionFilters = computed(() => {
46+
return props.actions
47+
.filter((item) => {
48+
const auth = item.auth;
49+
50+
if (Object.is(auth, undefined)) {
51+
return true;
52+
}
53+
if (isString(auth)) {
54+
const isValid = verifyAuth(auth);
55+
item.disabled ??= !isValid;
56+
if (item.disabled && !isValid) {
57+
item.title = '对不起,您没有该操作权限!';
58+
}
59+
return isValid;
60+
}
61+
if (isObject(auth)) {
62+
const isValid = verifyAuth(auth.perm);
63+
const isDisable = auth.effect !== 'delete';
64+
item.disabled ??= !isValid && isDisable;
65+
if (item.disabled && !isValid) {
66+
item.title = '对不起,您没有该操作权限!';
67+
}
68+
return isValid || isDisable;
69+
}
70+
})
71+
.map((item, index) => {
72+
const onClick = item.onClick;
2873
29-
const actionFilters = computed(() => {
30-
return props.actions.filter((item) => {
31-
const auth = item.auth;
74+
if (isAsyncFunction(onClick)) {
75+
item.onClick = async () => {
76+
const key = getKey(item, index);
77+
loadingMap.value.set(key, true);
78+
await onClick(props.columnParams).finally(() => {
79+
loadingMap.value.delete(key);
80+
});
81+
};
82+
}
83+
return item;
84+
});
85+
});
3286
33-
if (Object.is(auth, undefined)) {
34-
return true;
35-
}
36-
if (isString(auth)) {
37-
const isValid = verifyAuth(auth);
38-
item.disabled ??= !isValid;
39-
if (item.disabled && !isValid) {
40-
item.title = '对不起,您没有该操作权限!';
41-
}
42-
return isValid;
43-
}
44-
if (isObject(auth)) {
45-
const isValid = verifyAuth(auth.perm);
46-
const isDisable = auth.effect !== 'delete';
47-
item.disabled ??= !isValid && isDisable;
48-
if (item.disabled && !isValid) {
49-
item.title = '对不起,您没有该操作权限!';
50-
}
51-
return isValid || isDisable;
52-
}
53-
});
87+
const getKey = (actionItem: ActionItem, index: number) => {
88+
return `${props.rowKey}${index}${actionItem.label}`;
89+
};
90+
91+
return {
92+
actionFilters,
93+
loadingMap,
94+
getKey,
95+
};
96+
},
5497
});
5598
</script>

src/components/core/dynamic-table/src/hooks/useColumns.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ export const useColumns = ({ state, methods, props, tableAction }: UseTableColum
5656
return columns.map((item) => {
5757
const customRender = item.customRender;
5858

59+
const rowKey = props.rowKey as string;
5960
const columnKey = getColumnKey(item) as string;
6061

6162
item.customRender = (options) => {
62-
const { record } = options;
63-
const rowKey = props.rowKey as string;
63+
const { record, index } = options;
6464
/** 当前行是否开启了编辑行模式 */
6565
const isEditableRow = isEditable(record[rowKey]);
6666
/** 当前单元格是否允许被编辑 */
@@ -74,7 +74,7 @@ export const useColumns = ({ state, methods, props, tableAction }: UseTableColum
7474
// @ts-ignore
7575
<EditableCell
7676
schema={getColumnFormSchema(item, record)}
77-
rowKey={record[rowKey]}
77+
rowKey={record[rowKey] ?? index}
7878
v-slots={slots}
7979
></EditableCell>
8080
) : (
@@ -84,9 +84,15 @@ export const useColumns = ({ state, methods, props, tableAction }: UseTableColum
8484

8585
// 操作列
8686
if (item.actions && columnKey === ColumnKeyFlag.ACTION) {
87-
item.customRender = (columnParams) => {
88-
// @ts-ignore
89-
return <TableAction actions={item.actions!(columnParams, tableAction)} />;
87+
item.customRender = (options) => {
88+
const { record, index } = options;
89+
return (
90+
<TableAction
91+
actions={item.actions!(options, tableAction)}
92+
rowKey={record[rowKey] ?? index}
93+
columnParams={options}
94+
/>
95+
);
9096
};
9197
}
9298
return {

src/components/core/dynamic-table/src/types/tableAction.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import type { Ref } from 'vue';
12
import type { CustomRenderParams } from './column';
23
import type { PopconfirmProps } from 'ant-design-vue/es/popconfirm';
34
import type { ButtonProps, TooltipProps } from 'ant-design-vue/es/components';
45
import type { PermissionType } from '@/core/permission/modules/types';
56
import type { TableMethods, UseEditableType } from '../hooks/';
67

7-
export interface ActionItem extends Omit<ButtonProps, 'onClick'> {
8+
export type ActionItem = Omit<ButtonProps, 'onClick' | 'loading'> & {
89
onClick?: Fn<CustomRenderParams, any>;
910
label?: string;
1011
color?: 'success' | 'error' | 'warning';
12+
loading?: Ref<ButtonProps['loading']> | ButtonProps['loading'];
1113
icon?: string;
1214
popConfirm?: PopConfirm;
1315
disabled?: boolean;
@@ -24,7 +26,7 @@ export interface ActionItem extends Omit<ButtonProps, 'onClick'> {
2426
perm: PermissionType;
2527
effect?: 'delete' | 'disable';
2628
};
27-
}
29+
};
2830

2931
export type PopConfirm = PopconfirmProps & {
3032
title: string;

src/utils/is/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const toString = Object.prototype.toString;
22

3+
const AsyncFunction = async function () {}.constructor;
4+
35
export function is(val: unknown, type: string) {
46
return toString.call(val) === `[object ${type}]`;
57
}
@@ -68,6 +70,10 @@ export function isFunction(val: unknown): val is Function {
6870
return typeof val === 'function';
6971
}
7072

73+
export function isAsyncFunction(val: unknown): val is Function {
74+
return val instanceof AsyncFunction || (isFunction(val) && isPromise(val));
75+
}
76+
7177
export function isBoolean(val: unknown): val is boolean {
7278
return is(val, 'Boolean');
7379
}

src/views/demos/tables/edit-row-table/columns.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getClothesByGender,
88
tableData,
99
} from '@/views/demos/tables/search-table/columns';
10+
import { waitTime } from '@/utils/common';
1011

1112
export { tableData };
1213

@@ -87,6 +88,9 @@ export const columns: TableColumn<ListItemType>[] = [
8788
dataIndex: 'clothes',
8889
formItemProps: {
8990
component: 'Select',
91+
componentProps: ({ formModel }) => ({
92+
options: getClothesByGender(formModel.gender),
93+
}),
9094
},
9195
},
9296
{
@@ -146,7 +150,6 @@ export const columns: TableColumn<ListItemType>[] = [
146150
dataIndex: 'ACTION',
147151
actions: ({ record }, action) => {
148152
const { startEditable, cancelEditable, isEditable, getEditFormModel, validateRow } = action;
149-
150153
return isEditable(record.id)
151154
? [
152155
{
@@ -156,10 +159,9 @@ export const columns: TableColumn<ListItemType>[] = [
156159
message.loading({ content: '保存中...', key: record.id });
157160
console.log('result', result);
158161
console.log('保存', getEditFormModel(record.id));
159-
setTimeout(() => {
160-
cancelEditable(record.id);
161-
message.success({ content: '保存成功!', key: record.id, duration: 2 });
162-
}, 1500);
162+
await waitTime(2000);
163+
cancelEditable(record.id);
164+
message.success({ content: '保存成功!', key: record.id, duration: 2 });
163165
},
164166
},
165167
{

src/views/demos/tables/edit-row-table/index.vue

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,28 @@
44
<template #description> 可编辑行表格-可编辑行表格使用示例 </template>
55
</Alert>
66
<Card title="可编辑行表格基本使用示例" style="margin-top: 20px">
7-
<DynamicTable size="small" bordered :data-request="loadData" :columns="columns" row-key="id">
7+
<DynamicTable
8+
size="small"
9+
bordered
10+
:data-request="loadData"
11+
:columns="columns"
12+
:editable-type="editableType"
13+
row-key="id"
14+
>
15+
<template #toolbar>
16+
<Select ref="select" v-model:value="editableType">
17+
<Select.Option value="single">单行编辑</Select.Option>
18+
<Select.Option value="multiple">多行编辑</Select.Option>
19+
</Select>
20+
</template>
821
</DynamicTable>
922
</Card>
1023
</div>
1124
</template>
1225

1326
<script lang="ts" setup>
14-
import { Alert, Card } from 'ant-design-vue';
27+
import { ref } from 'vue';
28+
import { Alert, Card, Select } from 'ant-design-vue';
1529
import { columns, tableData } from './columns';
1630
import { useTable, type OnChangeCallbackParams } from '@/components/core/dynamic-table';
1731
@@ -21,6 +35,8 @@
2135
2236
const [DynamicTable, dynamicTableInstance] = useTable();
2337
38+
const editableType = ref('single' as const);
39+
2440
const loadData = async (
2541
params,
2642
onChangeParams: OnChangeCallbackParams,

src/views/demos/tables/search-table/columns.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,6 @@ import type { TableColumn } from '@/components/core/dynamic-table';
44
import { waitTime } from '@/utils/common';
55

66
const names = ['王路飞', '王大蛇', '李白', '刺客伍六七'];
7-
const clothes = ['西装', '领带', '裙子', '包包'];
8-
export const tableData = Array.from({ length: 30 }).map((_, i) => ({
9-
id: i + 1,
10-
date: new Date().toLocaleString(),
11-
name: names[~~(Math.random() * 4)],
12-
clothes: clothes[~~(Math.random() * 4)],
13-
price: ~~(Math.random() * 1000),
14-
gender: ~~(Math.random() * 2),
15-
status: ~~(Math.random() * 2),
16-
}));
177

188
export const fetchStatusMapData = (keyword = '') => {
199
const data = [
@@ -58,6 +48,19 @@ export const getClothesByGender = (gender: number) => {
5848
return [];
5949
};
6050

51+
export const tableData = Array.from({ length: 30 }).map((_, i) => {
52+
const gender = ~~(Math.random() * 2);
53+
return {
54+
id: i + 1,
55+
date: new Date().toLocaleString(),
56+
name: names[~~(Math.random() * 4)],
57+
clothes: getClothesByGender(gender)[~~(Math.random() * 2)].label,
58+
price: ~~(Math.random() * 1000),
59+
gender,
60+
status: ~~(Math.random() * 2),
61+
};
62+
});
63+
6164
// 数据项类型
6265
export type ListItemType = typeof tableData[number];
6366
// 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示

0 commit comments

Comments
 (0)