<script lang="ts" setup>
|
import type { Ref } from 'vue';
|
import { computed, provide, ref, watchEffect } from 'vue';
|
|
export type RequestResult<T> = T[];
|
|
export type RequestFunc<T> = () => Promise<RequestResult<T>> | RequestResult<T>;
|
|
export interface EditTableProvide {}
|
|
export interface EditActions {
|
addRow: (row?: Record<any, any>) => void;
|
deleteRow: (index: number) => void;
|
startEditable: (index: number) => void;
|
cancelEditable: (index: number) => void;
|
saveEditable: (index: number) => void;
|
}
|
|
export interface FormModelItem {
|
isEditing: boolean;
|
isNew: boolean;
|
data: Record<string | number | symbol, any>;
|
formData: Record<string | number | symbol, any>;
|
}
|
|
export interface FormModel {
|
model: FormModelItem[];
|
}
|
|
export type FormProps = Set<string>;
|
|
const props = withDefaults(
|
defineProps<{
|
dataSource?: any[];
|
request?: RequestFunc<any>;
|
}>(),
|
{
|
dataSource: () => [],
|
}
|
);
|
|
const formModel = ref<FormModel>({
|
model: [],
|
});
|
|
const form = ref();
|
|
const formProps = ref<FormProps>(new Set());
|
|
const tableData = computed(() => formModel.value.model.map(({ data }) => data));
|
|
const resultData = computed(() => {
|
return formModel.value.model.reduce((resultData: any[], model: FormModelItem) => {
|
if (model.isNew) {
|
return resultData;
|
}
|
resultData.push({
|
...model.data,
|
});
|
return resultData;
|
}, []);
|
});
|
|
const convertFormModel = (data: any[]): FormModelItem[] =>
|
data.map(
|
(row: any): FormModelItem => ({
|
data: { ...row },
|
formData: { ...row },
|
isEditing: false,
|
isNew: false,
|
})
|
);
|
|
watchEffect(async () => {
|
const model = [...props.dataSource];
|
if (typeof props.request === 'function') {
|
model.push(...(await Promise.resolve(props.request())));
|
}
|
formModel.value.model = convertFormModel(model);
|
});
|
|
const generateValidateFields = (index: number) => Array.from(formProps.value).map((prop) => `model.${index}.formData.${prop}`);
|
|
const startEditable = (index: number) => {
|
formModel.value.model[index].isEditing = true;
|
};
|
|
const deleteRow = (index: number) => {
|
formModel.value.model.splice(index, 1);
|
};
|
|
const addRow = (row: Record<any, any> = {}) => {
|
formModel.value.model.push({
|
data: { ...row },
|
formData: { ...row },
|
isEditing: true,
|
isNew: true,
|
});
|
};
|
|
const cancelEditable = (index: number) => {
|
if (!form.value) {
|
return;
|
}
|
|
form.value.resetFields && form.value.resetFields(generateValidateFields(index));
|
const formModelItem = formModel.value.model[index];
|
formModelItem.formData = { ...formModelItem.data };
|
if (formModelItem.isNew) {
|
formModel.value.model.splice(index, 1);
|
} else {
|
formModelItem.isEditing = false;
|
}
|
};
|
|
const saveEditable = (index: number) => {
|
if (!form.value) {
|
return;
|
}
|
|
form.value.validateField &&
|
form.value.validateField(generateValidateFields(index), (validated: boolean) => {
|
if (!validated) {
|
return;
|
}
|
const formModelItem = formModel.value.model[index];
|
formModelItem.data = { ...formModelItem.formData };
|
formModelItem.isEditing = false;
|
formModelItem.isNew = false;
|
});
|
};
|
|
const editActions: EditActions = {
|
addRow,
|
deleteRow,
|
startEditable,
|
cancelEditable,
|
saveEditable,
|
};
|
|
provide<Ref<FormModel>>('formModel', formModel);
|
|
provide<Ref<FormProps>>('formProps', formProps);
|
|
provide<EditActions>('editActions', editActions);
|
|
defineExpose({
|
resultData,
|
editActions,
|
});
|
</script>
|
|
<template>
|
<div>
|
<el-form ref="form" :model="formModel">
|
<el-table v-bind="$attrs" :data="tableData">
|
<slot> </slot>
|
</el-table>
|
</el-form>
|
</div>
|
</template>
|