Skip to content

Commit

Permalink
feat: edit-row-table support save loading
Browse files Browse the repository at this point in the history
  • Loading branch information
buqiyuan committed Aug 24, 2022
1 parent 162e16e commit b24e25a
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 327 deletions.
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"dependencies": {
"@ant-design/icons-vue": "~6.1.0",
"@vueuse/core": "~9.1.0",
"@vueuse/core": "~9.1.1",
"ant-design-vue": "3.2.11",
"axios": "~0.27.2",
"core-js": "~3.24.1",
Expand All @@ -58,10 +58,10 @@
"@commitlint/cli": "~17.0.3",
"@commitlint/config-conventional": "~17.0.3",
"@types/lodash-es": "~4.17.6",
"@types/node": "~18.7.6",
"@types/node": "~18.7.13",
"@types/webpack-env": "~1.18.0",
"@typescript-eslint/eslint-plugin": "~5.33.1",
"@typescript-eslint/parser": "~5.33.1",
"@typescript-eslint/eslint-plugin": "~5.34.0",
"@typescript-eslint/parser": "~5.34.0",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8",
Expand All @@ -72,13 +72,13 @@
"babel-plugin-import": "~1.13.5",
"conventional-changelog-cli": "~2.2.2",
"cross-env": "~7.0.3",
"cz-git": "~1.3.10",
"czg": "~1.3.10",
"cz-git": "~1.3.11",
"czg": "~1.3.11",
"eslint": "~8.22.0",
"eslint-config-prettier": "~8.5.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-prettier": "~4.2.1",
"eslint-plugin-vue": "~9.3.0",
"eslint-plugin-vue": "~9.4.0",
"husky": "~8.0.1",
"less": "~4.1.3",
"less-loader": "11.0.0",
Expand All @@ -95,7 +95,7 @@
"stylelint-config-prettier": "~9.0.3",
"stylelint-config-recommended": "~9.0.0",
"stylelint-config-recommended-vue": "~1.4.0",
"stylelint-config-standard": "~27.0.0",
"stylelint-config-standard": "~28.0.0",
"stylelint-order": "~5.0.0",
"svg-sprite-loader": "~6.0.11",
"typescript": "~4.7.4",
Expand Down
115 changes: 79 additions & 36 deletions src/components/core/dynamic-table/src/components/table-action.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,98 @@
<template>
<template v-for="(actionItem, _index) in actionFilters" :key="`${_index}-${actionItem.label}`">
<template v-for="(actionItem, index) in actionFilters" :key="`${index}-${actionItem.label}`">
<component
:is="actionItem.popConfirm ? Popconfirm : 'span'"
:is="actionItem.popConfirm ? 'a-popconfirm' : 'span'"
:title="actionItem.title"
v-bind="actionItem.popConfirm"
>
<a-button type="link" v-bind="actionItem">{{ actionItem.label }}</a-button>
<a-button
type="link"
:loading="loadingMap.get(getKey(actionItem, index))"
v-bind="actionItem"
>
{{ actionItem.label }}
</a-button>
</component>
</template>
</template>

<script lang="ts" setup>
import { computed } from 'vue';
<script lang="ts">
import { defineComponent, computed, ref } from 'vue';
import { Popconfirm } from 'ant-design-vue';
import type { PropType } from 'vue';
import type { ActionItem } from '../types/tableAction';
import type { CustomRenderParams } from '../types/column';
import { verifyAuth } from '@/core/permission/';
import { isString, isObject } from '@/utils/is';
import { isString, isObject, isAsyncFunction } from '@/utils/is';
const props = defineProps({
actions: {
// 表格行动作
type: Array as PropType<ActionItem[]>,
default: () => [],
export default defineComponent({
components: { [Popconfirm.name]: Popconfirm },
props: {
actions: {
// 表格行动作
type: Array as PropType<ActionItem[]>,
default: () => [],
},
columnParams: {
type: Object as PropType<CustomRenderParams>,
default: () => ({}),
},
rowKey: [String, Number] as PropType<Key>,
},
});
setup(props) {
const loadingMap = ref(new Map<string, boolean>());
const actionFilters = computed(() => {
return props.actions
.filter((item) => {
const auth = item.auth;
if (Object.is(auth, undefined)) {
return true;
}
if (isString(auth)) {
const isValid = verifyAuth(auth);
item.disabled ??= !isValid;
if (item.disabled && !isValid) {
item.title = '对不起,您没有该操作权限!';
}
return isValid;
}
if (isObject(auth)) {
const isValid = verifyAuth(auth.perm);
const isDisable = auth.effect !== 'delete';
item.disabled ??= !isValid && isDisable;
if (item.disabled && !isValid) {
item.title = '对不起,您没有该操作权限!';
}
return isValid || isDisable;
}
})
.map((item, index) => {
const onClick = item.onClick;
const actionFilters = computed(() => {
return props.actions.filter((item) => {
const auth = item.auth;
if (isAsyncFunction(onClick)) {
item.onClick = async () => {
const key = getKey(item, index);
loadingMap.value.set(key, true);
await onClick(props.columnParams).finally(() => {
loadingMap.value.delete(key);
});
};
}
return item;
});
});
if (Object.is(auth, undefined)) {
return true;
}
if (isString(auth)) {
const isValid = verifyAuth(auth);
item.disabled ??= !isValid;
if (item.disabled && !isValid) {
item.title = '对不起,您没有该操作权限!';
}
return isValid;
}
if (isObject(auth)) {
const isValid = verifyAuth(auth.perm);
const isDisable = auth.effect !== 'delete';
item.disabled ??= !isValid && isDisable;
if (item.disabled && !isValid) {
item.title = '对不起,您没有该操作权限!';
}
return isValid || isDisable;
}
});
const getKey = (actionItem: ActionItem, index: number) => {
return `${props.rowKey}${index}${actionItem.label}`;
};
return {
actionFilters,
loadingMap,
getKey,
};
},
});
</script>
18 changes: 12 additions & 6 deletions src/components/core/dynamic-table/src/hooks/useColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export const useColumns = ({ state, methods, props, tableAction }: UseTableColum
return columns.map((item) => {
const customRender = item.customRender;

const rowKey = props.rowKey as string;
const columnKey = getColumnKey(item) as string;

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

// 操作列
if (item.actions && columnKey === ColumnKeyFlag.ACTION) {
item.customRender = (columnParams) => {
// @ts-ignore
return <TableAction actions={item.actions!(columnParams, tableAction)} />;
item.customRender = (options) => {
const { record, index } = options;
return (
<TableAction
actions={item.actions!(options, tableAction)}
rowKey={record[rowKey] ?? index}
columnParams={options}
/>
);
};
}
return {
Expand Down
6 changes: 4 additions & 2 deletions src/components/core/dynamic-table/src/types/tableAction.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { Ref } from 'vue';
import type { CustomRenderParams } from './column';
import type { PopconfirmProps } from 'ant-design-vue/es/popconfirm';
import type { ButtonProps, TooltipProps } from 'ant-design-vue/es/components';
import type { PermissionType } from '@/core/permission/modules/types';
import type { TableMethods, UseEditableType } from '../hooks/';

export interface ActionItem extends Omit<ButtonProps, 'onClick'> {
export type ActionItem = Omit<ButtonProps, 'onClick' | 'loading'> & {
onClick?: Fn<CustomRenderParams, any>;
label?: string;
color?: 'success' | 'error' | 'warning';
loading?: Ref<ButtonProps['loading']> | ButtonProps['loading'];
icon?: string;
popConfirm?: PopConfirm;
disabled?: boolean;
Expand All @@ -24,7 +26,7 @@ export interface ActionItem extends Omit<ButtonProps, 'onClick'> {
perm: PermissionType;
effect?: 'delete' | 'disable';
};
}
};

export type PopConfirm = PopconfirmProps & {
title: string;
Expand Down
6 changes: 6 additions & 0 deletions src/utils/is/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const toString = Object.prototype.toString;

const AsyncFunction = async function () {}.constructor;

export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`;
}
Expand Down Expand Up @@ -68,6 +70,10 @@ export function isFunction(val: unknown): val is Function {
return typeof val === 'function';
}

export function isAsyncFunction(val: unknown): val is Function {
return val instanceof AsyncFunction || (isFunction(val) && isPromise(val));
}

export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean');
}
Expand Down
12 changes: 7 additions & 5 deletions src/views/demos/tables/edit-row-table/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getClothesByGender,
tableData,
} from '@/views/demos/tables/search-table/columns';
import { waitTime } from '@/utils/common';

export { tableData };

Expand Down Expand Up @@ -87,6 +88,9 @@ export const columns: TableColumn<ListItemType>[] = [
dataIndex: 'clothes',
formItemProps: {
component: 'Select',
componentProps: ({ formModel }) => ({
options: getClothesByGender(formModel.gender),
}),
},
},
{
Expand Down Expand Up @@ -146,7 +150,6 @@ export const columns: TableColumn<ListItemType>[] = [
dataIndex: 'ACTION',
actions: ({ record }, action) => {
const { startEditable, cancelEditable, isEditable, getEditFormModel, validateRow } = action;

return isEditable(record.id)
? [
{
Expand All @@ -156,10 +159,9 @@ export const columns: TableColumn<ListItemType>[] = [
message.loading({ content: '保存中...', key: record.id });
console.log('result', result);
console.log('保存', getEditFormModel(record.id));
setTimeout(() => {
cancelEditable(record.id);
message.success({ content: '保存成功!', key: record.id, duration: 2 });
}, 1500);
await waitTime(2000);
cancelEditable(record.id);
message.success({ content: '保存成功!', key: record.id, duration: 2 });
},
},
{
Expand Down
20 changes: 18 additions & 2 deletions src/views/demos/tables/edit-row-table/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@
<template #description> 可编辑行表格-可编辑行表格使用示例 </template>
</Alert>
<Card title="可编辑行表格基本使用示例" style="margin-top: 20px">
<DynamicTable size="small" bordered :data-request="loadData" :columns="columns" row-key="id">
<DynamicTable
size="small"
bordered
:data-request="loadData"
:columns="columns"
:editable-type="editableType"
row-key="id"
>
<template #toolbar>
<Select ref="select" v-model:value="editableType">
<Select.Option value="single">单行编辑</Select.Option>
<Select.Option value="multiple">多行编辑</Select.Option>
</Select>
</template>
</DynamicTable>
</Card>
</div>
</template>

<script lang="ts" setup>
import { Alert, Card } from 'ant-design-vue';
import { ref } from 'vue';
import { Alert, Card, Select } from 'ant-design-vue';
import { columns, tableData } from './columns';
import { useTable, type OnChangeCallbackParams } from '@/components/core/dynamic-table';
Expand All @@ -21,6 +35,8 @@
const [DynamicTable, dynamicTableInstance] = useTable();
const editableType = ref('single' as const);
const loadData = async (
params,
onChangeParams: OnChangeCallbackParams,
Expand Down
23 changes: 13 additions & 10 deletions src/views/demos/tables/search-table/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ import type { TableColumn } from '@/components/core/dynamic-table';
import { waitTime } from '@/utils/common';

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

export const fetchStatusMapData = (keyword = '') => {
const data = [
Expand Down Expand Up @@ -58,6 +48,19 @@ export const getClothesByGender = (gender: number) => {
return [];
};

export const tableData = Array.from({ length: 30 }).map((_, i) => {
const gender = ~~(Math.random() * 2);
return {
id: i + 1,
date: new Date().toLocaleString(),
name: names[~~(Math.random() * 4)],
clothes: getClothesByGender(gender)[~~(Math.random() * 2)].label,
price: ~~(Math.random() * 1000),
gender,
status: ~~(Math.random() * 2),
};
});

// 数据项类型
export type ListItemType = typeof tableData[number];
// 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示
Expand Down

1 comment on commit b24e25a

@vercel
Copy link

@vercel vercel bot commented on b24e25a Aug 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

vue3-antd-admin – ./

vue3-antd-admin-buqiyuan.vercel.app
vue3-antd-admin.vercel.app
vue3-antd-admin-git-main-buqiyuan.vercel.app

Please sign in to comment.