简易表格
子组件(Table组件)
SchemaForm需要传入的属性
- ref
- getFormProps
- table-instance
- submit事件
table-instance包含的属性
- setProps
- reload
- fetchData
Table需要传入的属性
- ref
- tableProps
- columns
- data-source
- change事件
优化项
- SchemaForm显/隐控制(getProps.search判断)
<template>
<div>
<SchemaForm
v-if="getProps.search"
ref="queryFormRef"
v-bind="getFormProps"
:table-instance="tableAction"
@submit="handleSubmit"
>
</SchemaForm>
<Table
ref="tableRef"
v-bind="tableProps"
:columns="innerColumns"
:data-source="tableData"
@change="handleTableChange"
>
</Table>
</div>
</template>
<script lang="tsx" setup>
import { useSlots, computed } from 'vue';
import { Table } from 'ant-design-vue';
import {
useTableMethods,
createTableContext,
useTableForm,
useTableState,
useColumns,
} from './hooks';
import { dynamicTableProps, dynamicTableEmits } from './dynamic-table';
import type { TableActionType } from './types';
import { SchemaForm } from '@/components/core/schema-form';
defineOptions({
name: 'DynamicTable',
inheritAttrs: false,
});
const props = defineProps(dynamicTableProps);
const emit = defineEmits(dynamicTableEmits);
const slots = useSlots();
const tableState = useTableState({ props, slots });
const { tableRef, tableData, queryFormRef, getProps, getBindValues } = tableState;
const tableMethods = useTableMethods({ state: tableState, props, emit });
const { setProps, fetchData, handleSubmit, reload, handleTableChange } = tableMethods;
const tableAction: TableActionType = {
setProps,
reload,
fetchData,
isEditable: () => false,
};
const { innerColumns } = useColumns({
props,
slots,
state: tableState,
methods: tableMethods,
tableAction,
});
const tableForm = useTableForm({
tableState,
tableMethods,
slots,
});
const { getFormProps } = tableForm;
const instance = {
...props,
...tableState,
...tableForm,
...tableMethods,
emit,
};
createTableContext(instance);
fetchData();
defineExpose(instance);
const tableProps = computed(() => {
const { getExpandOption } = tableMethods;
return {
...getBindValues.value,
...getExpandOption.value,
};
});
</script>
父组件(业务组件)
<template>
<Card title="查询表单基本使用示例">
<DynamicTable size="small" bordered :data-request="loadData" :columns="columns" row-key="id">
</DynamicTable>
</Card>
</template>
<script lang="ts" setup>
import { Card } from 'ant-design-vue';
import { columns, tableData } from './columns';
import { useTable } from '@/components/core/dynamic-table';
const [DynamicTable] = useTable();
const loadData = async (params): Promise<API.TableListResult> => {
return {
...params,
items: tableData,
};
};
</script>
增加hooks
- 路径:@/components/core/dynamic-table/src/hooks/useTable.tsx
- 存储
Table的ref实例
import { nextTick, ref, unref, watch } from 'vue';
import { isEmpty } from 'lodash-es';
import DynamicTable from '../../index';
import type { FunctionalComponent, Ref } from 'vue';
import type { DynamicTableInstance, DynamicTableProps } from '../dynamic-table';
export function useTable(props?: Partial<DynamicTableProps>) {
const dynamicTableRef = ref<DynamicTableInstance>({} as DynamicTableInstance);
async function getTableInstance() {
await nextTick();
const table = unref(dynamicTableRef);
if (isEmpty(table)) {
console.error('未获取表格实例!');
}
return table;
}
watch(
() => props,
async () => {
if (props) {
await nextTick();
const tableInstance = await getTableInstance();
tableInstance?.setProps?.(props);
}
},
{
deep: true,
flush: 'post',
},
);
const methods = new Proxy<Ref<DynamicTableInstance>>(dynamicTableRef, {
get(target, key) {
if (Reflect.has(target, key)) {
return unref(target);
}
if (target.value && Reflect.has(target.value, key)) {
return Reflect.get(target.value, key);
}
return async (...rest) => {
const table = await getTableInstance();
return table?.[key]?.(...rest);
};
},
});
const DynamicTableRender: FunctionalComponent<DynamicTableProps> = (
compProps,
{ attrs, slots },
) => {
return (
<DynamicTable
ref={dynamicTableRef}
{...{ ...attrs, ...props, ...compProps }}
v-slots={slots}
></DynamicTable>
);
};
return [DynamicTableRender, unref(methods)] as const;
}
useTableState
import { computed, reactive, ref, unref, watch } from 'vue';
import { omit } from 'lodash-es';
import tableConfig from '../dynamic-table.config';
import { useScroll } from './useScroll';
import type { Slots } from 'vue';
import type { DynamicTableProps } from '../dynamic-table';
import type { SchemaFormInstance } from '@/components/core/schema-form';
import type { TableProps, Table } from 'ant-design-vue';
import { useI18n } from '@/hooks/useI18n';
export type Pagination = TableProps['pagination'];
export type TableState = ReturnType<typeof useTableState>;
export type UseTableStateParams = {
props: DynamicTableProps;
slots: Slots;
};
interface SearchState {
sortInfo: Recordable;
filterInfo: Record<string, string[]>;
}
export const useTableState = ({ props, slots }: UseTableStateParams) => {
const { t } = useI18n();
const { scroll } = useScroll({ props });
const tableRef = ref<InstanceType<typeof Table>>();
const queryFormRef = ref<SchemaFormInstance>();
const editTableFormRef = ref<SchemaFormInstance>();
const tableData = ref<any[]>([]);
const innerPropsRef = ref<Partial<DynamicTableProps>>();
const paginationRef = ref<NonNullable<Pagination>>(false);
const loadingRef = ref<boolean>(!!props.loading);
const editFormModel = ref<Recordable>({});
const editFormErrorMsgs = ref(new Map());
const editableRowKeys = ref(new Set<Key>());
const editableCellKeys = ref(new Set<Key>());
const searchState = reactive<SearchState>({
sortInfo: {},
filterInfo: {},
});
if (!Object.is(props.pagination, false)) {
paginationRef.value = {
current: 1,
pageSize: tableConfig.defaultPageSize,
total: 0,
pageSizeOptions: [...tableConfig.pageSizeOptions],
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total) => t('component.table.total', { total }),
...props.pagination,
};
}
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) };
});
const getBindValues = computed(() => {
const props = unref(getProps);
let propsData: Recordable = {
...props,
rowKey: props.rowKey ?? 'id',
loading: props.loading ?? unref(loadingRef),
pagination: unref(paginationRef),
tableLayout: props.tableLayout ?? 'fixed',
scroll: unref(scroll),
};
if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll');
}
propsData = omit(propsData, ['class', 'onChange', 'columns']);
return propsData;
});
watch(
() => props.dataSource,
(val) => {
if (val) {
tableData.value = val;
}
},
{
immediate: true,
deep: true,
},
);
watch(
() => props.columns,
(val) => {
if (val) {
innerPropsRef.value = {
...innerPropsRef.value,
columns: val,
};
}
},
{
immediate: true,
deep: true,
},
);
return {
tableRef,
editTableFormRef,
loadingRef,
tableData,
queryFormRef,
innerPropsRef,
getProps,
getBindValues,
paginationRef,
editFormModel,
editFormErrorMsgs,
editableCellKeys,
editableRowKeys,
searchState,
};
};
useTableMethods
import { unref, nextTick, getCurrentInstance, watch } from 'vue';
import { isObject, isFunction, isBoolean, get } from 'lodash-es';
import { useInfiniteScroll } from '@vueuse/core';
import tableConfig from '../dynamic-table.config';
import { useEditable } from './useEditable';
import { useTableExpand } from './useTableExpand';
import type { DynamicTableProps, DynamicTableEmitFn } from '../dynamic-table';
import type { OnChangeCallbackParams, TableColumn } from '../types/';
import type { Pagination, TableState } from './useTableState';
import type { FormProps } from 'ant-design-vue';
import { warn } from '@/utils/log';
export type UseInfiniteScrollParams = Parameters<typeof useInfiniteScroll>;
export type TableMethods = ReturnType<typeof useTableMethods>;
export type UseTableMethodsContext = {
state: TableState;
props: DynamicTableProps;
emit: DynamicTableEmitFn;
};
export const useTableMethods = ({ state, props, emit }: UseTableMethodsContext) => {
const {
innerPropsRef,
tableData,
loadingRef,
queryFormRef,
paginationRef,
editFormErrorMsgs,
searchState,
} = state;
const editableMethods = useEditable({ state, props });
const expandMethods = useTableExpand({ state, props, emit });
watch(
() => props.searchParams,
() => {
fetchData();
},
);
const setProps = (props: Partial<DynamicTableProps>) => {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
};
const handleSubmit = (params, page = 1) => {
updatePagination({
current: page,
});
fetchData(params);
};
const fetchData = async (params = {}) => {
const { dataRequest, dataSource, fetchConfig, searchParams } = props;
if (!dataRequest || !isFunction(dataRequest) || Array.isArray(dataSource)) {
return;
}
try {
let pageParams: Recordable = {};
const pagination = unref(paginationRef)!;
const { pageField, sizeField, listField, totalField } = {
...tableConfig.fetchConfig,
...fetchConfig,
};
const enablePagination = isObject(pagination);
if (enablePagination) {
pageParams = {
[pageField]: pagination.current,
[sizeField]: pagination.pageSize,
};
}
const { sortInfo = {}, filterInfo } = searchState;
let queryParams: Recordable = {
...pageParams,
...sortInfo,
...filterInfo,
...searchParams,
...params,
};
await nextTick();
if (queryFormRef.value) {
const values = await queryFormRef.value.validate();
queryParams = {
...queryFormRef.value.handleFormValues(values),
...queryParams,
};
}
loadingRef.value = true;
const res = await dataRequest(queryParams);
const isArrayResult = Array.isArray(res);
const resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
const resultTotal: number = isArrayResult ? res.length : Number(get(res, totalField));
if (enablePagination && resultTotal) {
const { current = 1, pageSize = tableConfig.defaultPageSize } = pagination;
const currentTotalPage = Math.ceil(resultTotal / pageSize);
if (current > currentTotalPage) {
updatePagination({
current: currentTotalPage,
});
return await fetchData(params);
}
}
tableData.value = resultItems;
updatePagination({ total: ~~resultTotal });
if (queryParams[pageField]) {
updatePagination({ current: queryParams[pageField] || 1 });
}
return tableData;
} catch (error) {
warn(`表格查询出错:${error}`);
emit('fetch-error', error);
tableData.value = [];
updatePagination({ total: 0 });
} finally {
loadingRef.value = false;
}
};
const reload = (resetPageIndex = false) => {
const pagination = unref(paginationRef);
if (Object.is(resetPageIndex, true) && isObject(pagination)) {
pagination.current = 1;
}
return fetchData();
};
const handleTableChange = async (...rest: OnChangeCallbackParams) => {
const [pagination, filters, sorter] = rest;
const { sortFn, filterFn } = props;
if (queryFormRef.value) {
await queryFormRef.value.validate();
}
updatePagination(pagination);
const params: Recordable = {};
if (sorter && isFunction(sortFn)) {
const sortInfo = sortFn(sorter);
searchState.sortInfo = sortInfo;
params.sortInfo = sortInfo;
}
if (filters && isFunction(filterFn)) {
const filterInfo = filterFn(filters);
searchState.filterInfo = filterInfo;
params.filterInfo = filterInfo;
}
await fetchData({});
emit('change', ...rest);
};
const getColumnKey = (column: TableColumn) => {
return (column?.key || column?.dataIndex) as string;
};
const handleEditFormValidate: FormProps['onValidate'] = (name, status, errorMsgs) => {
const key = Array.isArray(name) ? name.join('.') : name;
if (status) {
editFormErrorMsgs.value.delete(key);
} else {
editFormErrorMsgs.value.set(key, errorMsgs);
}
};
const updatePagination = (info: Pagination = paginationRef.value) => {
if (isBoolean(info)) {
paginationRef.value = info;
} else if (isObject(paginationRef.value)) {
paginationRef.value = {
...paginationRef.value,
...info,
};
}
};
const onInfiniteScroll = (
callback: UseInfiniteScrollParams[1],
options?: UseInfiniteScrollParams[2],
) => {
const el = getCurrentInstance()?.proxy?.$el.querySelector('.ant-table-body');
useInfiniteScroll(el, callback, options);
};
const getQueryFormRef = () => queryFormRef.value;
return {
...editableMethods,
...expandMethods,
setProps,
handleSubmit,
handleTableChange,
getColumnKey,
fetchData,
getQueryFormRef,
reload,
onInfiniteScroll,
handleEditFormValidate,
};
};
useColumns
import { ref, watchEffect, unref, useSlots, h } from 'vue';
import { cloneDeep, isFunction, mergeWith } from 'lodash-es';
import { EditableCell } from '../components';
import { ColumnKeyFlag, type CustomRenderParams } from '../types/column';
import tableConfig from '../dynamic-table.config';
import type { Slots } from 'vue';
import type {
TableActionType,
TableColumn,
TableMethods,
TableState,
DynamicTableProps,
} from '@/components/core/dynamic-table';
import type { FormSchema } from '@/components/core/schema-form';
import { isBoolean } from '@/utils/is';
import { TableAction } from '@/components/core/dynamic-table/src/components';
export type UseTableColumnsContext = {
state: TableState;
props: DynamicTableProps;
methods: TableMethods;
tableAction: TableActionType;
slots: Slots;
};
export const useColumns = ({ state, methods, props, tableAction }: UseTableColumnsContext) => {
const slots = useSlots();
const innerColumns = ref(props.columns);
const { getColumnKey } = methods;
const { getProps } = state;
const { isEditable } = tableAction;
watchEffect(() => {
const innerProps = { ...unref(getProps) };
const ColumnKeyFlags = Object.keys(ColumnKeyFlag);
const columns = cloneDeep(innerProps!.columns!.filter((n) => !n.hideInTable));
if (innerProps?.showIndex) {
columns.unshift({
dataIndex: 'ACTION',
title: '序号',
width: 60,
align: 'center',
fixed: 'left',
...innerProps?.indexColumnProps,
customRender: ({ index }) => {
const getPagination = unref(state.paginationRef);
if (isBoolean(getPagination)) {
return index + 1;
}
const { current = 1, pageSize = 10 } = getPagination!;
return ((current < 1 ? 1 : current) - 1) * pageSize + index + 1;
},
} as TableColumn);
}
innerColumns.value = columns.map((item) => {
const customRender = item.customRender;
const rowKey = props.rowKey as string;
const columnKey = getColumnKey(item) as string;
item.align ||= tableConfig.defaultAlign;
item.customRender = (options) => {
const { record, index, text } = options as CustomRenderParams<Recordable<any>>;
const isEditableRow = isEditable(record[rowKey]);
const isEditableCell = innerProps.editableType === 'cell';
const isCellEditable = isBoolean(item.editable)
? item.editable
: item.editable?.(options) ?? true;
const isShowEditable =
(isEditableRow || isEditableCell) &&
isCellEditable &&
!ColumnKeyFlags.includes(columnKey);
return isShowEditable
? h(
EditableCell,
{
schema: getColumnFormSchema(item, record) as any,
rowKey: record[rowKey] ?? index,
editableType: innerProps.editableType,
column: options,
},
{ default: () => customRender?.(options) ?? text, ...slots },
)
: customRender?.(options);
};
if (item.actions && columnKey === ColumnKeyFlag.ACTION) {
item.customRender = (options) => {
const { record, index } = options;
return h(TableAction, {
actions: item.actions!(options, tableAction),
rowKey: record[rowKey] ?? index,
columnParams: options,
});
};
}
return {
key: item.key ?? (item.dataIndex as Key),
dataIndex: item.dataIndex ?? (item.key as Key),
...item,
} as TableColumn;
});
});
function mergeCustomizer(objValue, srcValue, key) {
if (key === 'componentProps') {
return (...rest) => {
return {
...(isFunction(objValue) ? objValue(...rest) : objValue),
...(isFunction(srcValue) ? srcValue(...rest) : srcValue),
};
};
}
}
const getColumnFormSchema = (item: TableColumn, record: Recordable): FormSchema => {
const key = getColumnKey(item) as string;
const isExtendSearchFormProps = !Object.is(
item.editFormItemProps?.extendSearchFormProps,
false,
);
return {
field: `${record[props.rowKey as string]}.${item.searchField ?? key}`,
component: 'Input',
defaultValue: record[key],
colProps: {
span: unref(getProps).editableType === 'cell' ? 20 : 24,
},
formItemProps: {
help: '',
},
...(isExtendSearchFormProps
? mergeWith(cloneDeep(item.formItemProps), item.editFormItemProps, mergeCustomizer)
: item.editFormItemProps),
};
};
return {
innerColumns,
};
};
useTableForm
import { unref, computed, watchEffect } from 'vue';
import { ColumnKeyFlag } from '../types/column';
import type { TableMethods } from './useTableMethods';
import type { TableState } from './useTableState';
import type { ComputedRef, Slots } from 'vue';
import type { FormSchema, SchemaFormProps } from '@/components/core/schema-form';
export type TableForm = ReturnType<typeof useTableForm>;
export type UseTableFormContext = {
tableState: TableState;
tableMethods: TableMethods;
slots: Slots;
};
export function useTableForm({ tableState, slots, tableMethods }: UseTableFormContext) {
const { getProps, loadingRef } = tableState;
const { getColumnKey, getQueryFormRef } = tableMethods;
const getFormProps = computed((): SchemaFormProps => {
const { formProps } = unref(getProps);
const { submitButtonOptions } = formProps || {};
return {
showAdvancedButton: true,
layout: 'horizontal',
labelWidth: 100,
...formProps,
schemas: formProps?.schemas ?? unref(formSchemas),
submitButtonOptions: { loading: unref(loadingRef), ...submitButtonOptions },
compact: true,
};
});
const formSchemas = computed<FormSchema[]>(() => {
const columnKeyFlags = Object.keys(ColumnKeyFlag);
return unref(getProps)
.columns.filter((n) => {
const field = getColumnKey(n);
return !n.hideInSearch && !!field && !columnKeyFlags.includes(field as string);
})
.map((n) => {
return {
field: n.searchField ?? (getColumnKey(n) as string),
component: 'Input',
label: n.title as string,
colProps: {
span: 8,
},
...n.formItemProps,
};
})
.sort((a, b) => Number(a?.order) - Number(b?.order)) as FormSchema[];
});
watchEffect(() => getQueryFormRef()?.setSchemaFormProps(unref(getFormProps)), {
flush: 'post',
});
const getFormSlotKeys: ComputedRef<string[]> = computed(() => {
const keys = Object.keys(slots);
return keys
.map((item) => (item.startsWith('form-') ? item : null))
.filter((item): item is string => !!item);
});
function replaceFormSlotKey(key: string) {
if (!key) return '';
return key?.replace?.(/form-/, '') ?? '';
}
return {
getFormProps,
replaceFormSlotKey,
getFormSlotKeys,
};
}
优化项
表格内容自定义
父组件(业务组件)
<template>
<DynamicTable>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'name'">
{{ record.name }} <a class="text-red-500">[测试bodyCell]</a>
</template>
</template>
</DynamicTable>
</template>
子组件(Table组件)
<template>
<Table>
<template
v-for="(_, slotName) of $slots"
#[slotName]="slotData"
:key="slotName"
>
<slot :name="slotName" v-bind="slotData"></slot>
</template>
</Table>
</template>
单行编辑/多行编辑/可编辑单元格
父组件(业务组件)
<template>
<div>
<Alert message="可编辑行表格" type="info" show-icon>
<template #description> 可编辑行表格-可编辑行表格使用示例 </template>
</Alert>
<Card title="可编辑行表格基本使用示例" style="margin-top: 20px">
<DynamicTable
size="small"
bordered
:data-request="loadData"
:columns="tableColumns"
:editable-type="editableType"
:on-save="handleSave"
:on-cancel="handleCancelSave"
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.Option value="cell">可编辑单元格</Select.Option>
</Select>
</template>
</DynamicTable>
</Card>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { Alert, Card, Select, message } from 'ant-design-vue';
import { columns, tableData } from './columns';
import type { EditableType, OnSave, OnCancel } from '@/components/core/dynamic-table';
import { useTable } from '@/components/core/dynamic-table';
import { waitTime } from '@/utils/common';
defineOptions({
name: 'EditRowTable',
});
const [DynamicTable] = useTable();
const editableType = ref<EditableType>('single');
const loadData = async (params): Promise<API.TableListResult> => {
console.log('params', params);
await waitTime(500);
return {
...params,
items: tableData,
};
};
const tableColumns = computed<typeof columns>(() => [
...columns,
{
title: '操作',
hideInTable: editableType.value === 'cell',
width: 200,
dataIndex: 'ACTION',
actions: ({ record }, action) => {
const { startEditable, cancelEditable, isEditable, validateRow, reload } = action;
return isEditable(record.id)
? [
{
label: '保存',
onClick: async () => {
const result = await validateRow(record.id);
message.loading({ content: '保存中...', key: record.id });
console.log('保存', result);
await waitTime(2000);
cancelEditable(record.id);
reload();
message.success({ content: '保存成功!', key: record.id, duration: 2 });
},
},
{
label: '取消',
onClick: () => {
cancelEditable(record.id);
},
},
]
: [
{
label: '编辑',
onClick: () => {
startEditable(record.id, record);
},
},
];
},
},
]);
const handleCancelSave: OnCancel = (rowKey, record, originRow) => {
console.log('handleCancelSave', rowKey, record, originRow);
};
const handleSave: OnSave = async (rowKey, record, originRow) => {
console.log('handleSave', rowKey, record, originRow);
await waitTime(2000);
};
</script>
<style lang="less" scoped></style>
子组件(Table组件)
<template>
<div>
<SchemaForm
:table-instance="tableAction"
>
</SchemaForm>
</template>
<script lang="tsx" setup>
import {
useTableState,
useEditable,
} from './hooks';
import { dynamicTableProps } from './dynamic-table';
import type { TableActionType } from './types';
const props = defineProps(dynamicTableProps);
const slots = useSlots();
const tableState = useTableState({ props, slots });
const editableHooks = useEditable({ props, state: tableState });
const tableAction: TableActionType = {
...editableHooks,
};
const { innerColumns } = useColumns({
tableAction,
});
const instance = {
...props,
...tableState,
...tableForm,
...tableMethods,
...editableHooks,
...exportData2ExcelHooks,
emit,
};
</script>
useEditable
import { nextTick, watch } from 'vue';
import { cloneDeep } from 'lodash-es';
import { message } from 'ant-design-vue';
import type { DynamicTableProps } from '../dynamic-table';
import type { TableState } from './useTableState';
import type { TableColumn } from '@/components/core/dynamic-table/src/types/column';
type UseTableMethodsContext = {
state: TableState;
props: DynamicTableProps;
};
export type UseEditableType = ReturnType<typeof useEditable>;
export const useEditable = ({ state, props }: UseTableMethodsContext) => {
const {
tableData,
editFormModel,
editTableFormRef,
editFormErrorMsgs,
editableCellKeys,
editableRowKeys,
} = state;
watch(
() => props.editableType,
(type) => {
if (type === 'cell') {
editableRowKeys.value.clear();
} else {
editableCellKeys.value.clear();
}
},
);
const setEditFormModel = (recordKey: Key, editValue: Recordable) => {
Reflect.set(editFormModel.value, recordKey, editValue);
nextTick(() => {
editTableFormRef.value?.setFormModel(recordKey, editValue);
});
};
const getEditValue = (
recordKey: Key,
currentRow?: Recordable,
columns?: TableColumn<Recordable<any>>[],
) => {
const editValue = cloneDeep(
currentRow ?? tableData.value.find((n) => n[String(props.rowKey)] === recordKey),
);
columns?.forEach((item) => {
const { formItemProps, editFormItemProps } = item;
const field = (item.dataIndex || item.key) as string;
if (
!Object.is(editFormItemProps?.extendSearchFormProps, false) &&
formItemProps &&
Reflect.has(formItemProps, 'defaultValue')
) {
editValue[field] = formItemProps.defaultValue;
}
if (editFormItemProps && Reflect.has(editFormItemProps, 'defaultValue')) {
editValue[field] = editFormItemProps.defaultValue;
}
});
return editValue;
};
const startEditable = (recordKey: Key, currentRow?: Recordable) => {
editableCellKeys.value.clear();
if (editableRowKeys.value.size > 0 && props.editableType === 'single') {
message.warn(props.onlyOneLineEditorAlertMessage || '只能同时编辑一行');
return false;
}
const editValue = getEditValue(recordKey, currentRow, props.columns);
setEditFormModel(recordKey, editValue);
editableRowKeys.value.add(recordKey);
return true;
};
const startCellEditable = (recordKey: Key, dataIndex: Key, currentRow?: Recordable) => {
editableRowKeys.value.clear();
const targetColumn = props.columns.filter((n) => n.dataIndex === dataIndex);
const editValue = getEditValue(recordKey, currentRow, targetColumn);
setEditFormModel(recordKey, {
...(getEditFormModel(recordKey) || editValue),
[dataIndex]: editValue[dataIndex],
});
editableCellKeys.value.add(`${recordKey}.${dataIndex}`);
};
const cancelCellEditable = (recordKey: Key, dataIndex: Key) => {
editableCellKeys.value.delete(`${recordKey}.${dataIndex}`);
const formModel = getEditFormModel(recordKey);
const record = tableData.value.find((n) => n[String(props.rowKey)] === recordKey);
if (record) {
Reflect.set(formModel, dataIndex, record[dataIndex]);
}
editFormErrorMsgs.value.delete(`${recordKey}.${dataIndex}`);
};
const cancelEditable = (recordKey: Key) => {
editableRowKeys.value.delete(recordKey);
const formModel = getEditFormModel(recordKey);
Object.keys(formModel).forEach((field) =>
editFormErrorMsgs.value.delete(`${recordKey}.${field}`),
);
nextTick(() => {
editTableFormRef.value?.delFormModel?.(recordKey);
});
return Reflect.deleteProperty(editFormModel.value, recordKey);
};
const isEditable = (recordKey: Key) => editableRowKeys.value.has(recordKey);
const getEditFormModel = (recordKey: Key) => Reflect.get(editFormModel.value, recordKey);
const validateRow = async (recordKey: Key) => {
const nameList = Object.keys(getEditFormModel(recordKey)).map((n) => [String(recordKey), n]);
const result = await editTableFormRef.value?.validateFields(nameList);
return result?.[recordKey] ?? result;
};
const validateCell = async (recordKey: Key, dataIndex: Key) => {
const result = await editTableFormRef.value?.validateFields([[String(recordKey), dataIndex]]);
return result?.[recordKey] ?? result;
};
return {
setEditFormModel,
startEditable,
startCellEditable,
cancelCellEditable,
cancelEditable,
isEditable,
validateRow,
validateCell,
getEditFormModel,
};
};