Commit 2c867b3d636d57cdc526a4ca600af7d747b7d833

Authored by 无木
1 parent fb43fad5

feat(table): add `beforeEditSubmit` for editable cell

单元格编辑功能新增提交回调
CHANGELOG.zh_CN.md
1 ### ✨ Features 1 ### ✨ Features
2 2
3 - **BasicForm** 表单组件新增`Divider`,用于较长表单的区域分割 3 - **BasicForm** 表单组件新增`Divider`,用于较长表单的区域分割
  4 +- **BasicTable** 单元格编辑新增提交回调,将根据回调函数返回的结果来决定是否将数据提交到表格
4 5
5 ### 🐛 Bug Fixes 6 ### 🐛 Bug Fixes
6 7
src/components/Table/src/components/editable/EditableCell.vue
@@ -11,25 +11,27 @@ @@ -11,25 +11,27 @@
11 <FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" /> 11 <FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
12 </div> 12 </div>
13 13
14 - <div v-if="isEdit" :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">  
15 - <CellComponent  
16 - v-bind="getComponentProps"  
17 - :component="getComponent"  
18 - :style="getWrapperStyle"  
19 - :popoverVisible="getRuleVisible"  
20 - :rule="getRule"  
21 - :ruleMessage="ruleMessage"  
22 - :class="getWrapperClass"  
23 - ref="elRef"  
24 - @change="handleChange"  
25 - @options-change="handleOptionsChange"  
26 - @pressEnter="handleEnter"  
27 - />  
28 - <div :class="`${prefixCls}__action`" v-if="!getRowEditable">  
29 - <CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmitClick" />  
30 - <CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" /> 14 + <a-spin v-if="isEdit" :spinning="spinning">
  15 + <div :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
  16 + <CellComponent
  17 + v-bind="getComponentProps"
  18 + :component="getComponent"
  19 + :style="getWrapperStyle"
  20 + :popoverVisible="getRuleVisible"
  21 + :rule="getRule"
  22 + :ruleMessage="ruleMessage"
  23 + :class="getWrapperClass"
  24 + ref="elRef"
  25 + @change="handleChange"
  26 + @options-change="handleOptionsChange"
  27 + @pressEnter="handleEnter"
  28 + />
  29 + <div :class="`${prefixCls}__action`" v-if="!getRowEditable">
  30 + <CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmitClick" />
  31 + <CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
  32 + </div>
31 </div> 33 </div>
32 - </div> 34 + </a-spin>
33 </div> 35 </div>
34 </template> 36 </template>
35 <script lang="ts"> 37 <script lang="ts">
@@ -48,12 +50,13 @@ @@ -48,12 +50,13 @@
48 import { propTypes } from '/@/utils/propTypes'; 50 import { propTypes } from '/@/utils/propTypes';
49 import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is'; 51 import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is';
50 import { createPlaceholderMessage } from './helper'; 52 import { createPlaceholderMessage } from './helper';
51 - import { omit, set } from 'lodash-es'; 53 + import { omit, pick, set } from 'lodash-es';
52 import { treeToList } from '/@/utils/helper/treeHelper'; 54 import { treeToList } from '/@/utils/helper/treeHelper';
  55 + import { Spin } from 'ant-design-vue';
53 56
54 export default defineComponent({ 57 export default defineComponent({
55 name: 'EditableCell', 58 name: 'EditableCell',
56 - components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent }, 59 + components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, ASpin: Spin },
57 directives: { 60 directives: {
58 clickOutside, 61 clickOutside,
59 }, 62 },
@@ -80,6 +83,7 @@ @@ -80,6 +83,7 @@
80 const optionsRef = ref<LabelValueOptions>([]); 83 const optionsRef = ref<LabelValueOptions>([]);
81 const currentValueRef = ref<any>(props.value); 84 const currentValueRef = ref<any>(props.value);
82 const defaultValueRef = ref<any>(props.value); 85 const defaultValueRef = ref<any>(props.value);
  86 + const spinning = ref<boolean>(false);
83 87
84 const { prefixCls } = useDesign('editable-cell'); 88 const { prefixCls } = useDesign('editable-cell');
85 89
@@ -246,6 +250,35 @@ @@ -246,6 +250,35 @@
246 250
247 const dataKey = (dataIndex || key) as string; 251 const dataKey = (dataIndex || key) as string;
248 252
  253 + if (!record.editable) {
  254 + const { getBindValues } = table;
  255 +
  256 + const { beforeEditSubmit, columns } = unref(getBindValues);
  257 +
  258 + if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
  259 + spinning.value = true;
  260 + const keys: string[] = columns
  261 + .map((_column) => _column.dataIndex)
  262 + .filter((field) => !!field) as string[];
  263 + let result: any = true;
  264 + try {
  265 + result = await beforeEditSubmit({
  266 + record: pick(record, keys),
  267 + index,
  268 + key,
  269 + value,
  270 + });
  271 + } catch (e) {
  272 + result = false;
  273 + } finally {
  274 + spinning.value = false;
  275 + }
  276 + if (result === false) {
  277 + return;
  278 + }
  279 + }
  280 + }
  281 +
249 set(record, dataKey, value); 282 set(record, dataKey, value);
250 //const record = await table.updateTableData(index, dataKey, value); 283 //const record = await table.updateTableData(index, dataKey, value);
251 needEmit && table.emit?.('edit-end', { record, index, key, value }); 284 needEmit && table.emit?.('edit-end', { record, index, key, value });
@@ -368,6 +401,7 @@ @@ -368,6 +401,7 @@
368 getValues, 401 getValues,
369 handleEnter, 402 handleEnter,
370 handleSubmitClick, 403 handleSubmitClick,
  404 + spinning,
371 }; 405 };
372 }, 406 },
373 }); 407 });
src/components/Table/src/props.ts
@@ -126,4 +126,14 @@ export const basicProps = { @@ -126,4 +126,14 @@ export const basicProps = {
126 type: Object as PropType<{ x: number | true; y: number }>, 126 type: Object as PropType<{ x: number | true; y: number }>,
127 default: null, 127 default: null,
128 }, 128 },
  129 + beforeEditSubmit: {
  130 + type: Function as PropType<
  131 + (data: {
  132 + record: Recordable;
  133 + index: number;
  134 + key: string | number;
  135 + value: any;
  136 + }) => Promise<any>
  137 + >,
  138 + },
129 }; 139 };
src/components/Table/src/types/table.ts
@@ -363,6 +363,18 @@ export interface BasicTableProps&lt;T = any&gt; { @@ -363,6 +363,18 @@ export interface BasicTableProps&lt;T = any&gt; {
363 transformCellText?: Function; 363 transformCellText?: Function;
364 364
365 /** 365 /**
  366 + * Callback executed before editable cell submit value, not for row-editor
  367 + *
  368 + * The cell will not submit data while callback return false
  369 + */
  370 + beforeEditSubmit?: (data: {
  371 + record: Recordable;
  372 + index: number;
  373 + key: string | number;
  374 + value: any;
  375 + }) => Promise<any>;
  376 +
  377 + /**
366 * Callback executed when pagination, filters or sorter is changed 378 * Callback executed when pagination, filters or sorter is changed
367 * @param pagination 379 * @param pagination
368 * @param filters 380 * @param filters
src/views/demo/table/EditCellTable.vue
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 @register="registerTable" 4 @register="registerTable"
5 @edit-end="handleEditEnd" 5 @edit-end="handleEditEnd"
6 @edit-cancel="handleEditCancel" 6 @edit-cancel="handleEditCancel"
  7 + :beforeEditSubmit="beforeEditSubmit"
7 /> 8 />
8 </div> 9 </div>
9 </template> 10 </template>
@@ -14,6 +15,7 @@ @@ -14,6 +15,7 @@
14 15
15 import { demoListApi } from '/@/api/demo/table'; 16 import { demoListApi } from '/@/api/demo/table';
16 import { treeOptionsListApi } from '/@/api/demo/tree'; 17 import { treeOptionsListApi } from '/@/api/demo/tree';
  18 + import { useMessage } from '/@/hooks/web/useMessage';
17 const columns: BasicColumn[] = [ 19 const columns: BasicColumn[] = [
18 { 20 {
19 title: '输入框', 21 title: '输入框',
@@ -93,7 +95,7 @@ @@ -93,7 +95,7 @@
93 }, 95 },
94 { 96 {
95 title: '远程下拉树', 97 title: '远程下拉树',
96 - dataIndex: 'name7', 98 + dataIndex: 'name71',
97 edit: true, 99 edit: true,
98 editComponent: 'ApiTreeSelect', 100 editComponent: 'ApiTreeSelect',
99 editRule: false, 101 editRule: false,
@@ -157,8 +159,44 @@ @@ -157,8 +159,44 @@
157 bordered: true, 159 bordered: true,
158 }); 160 });
159 161
  162 + const { createMessage } = useMessage();
  163 +
160 function handleEditEnd({ record, index, key, value }: Recordable) { 164 function handleEditEnd({ record, index, key, value }: Recordable) {
161 console.log(record, index, key, value); 165 console.log(record, index, key, value);
  166 + return false;
  167 + }
  168 +
  169 + // 模拟将指定数据保存
  170 + function feakSave({ value, key, id }) {
  171 + createMessage.loading({
  172 + content: `正在模拟保存${key}`,
  173 + key: '_save_fake_data',
  174 + duration: 0,
  175 + });
  176 + return new Promise((resolve) => {
  177 + setTimeout(() => {
  178 + if (value === '') {
  179 + createMessage.error({
  180 + content: '保存失败:不能为空',
  181 + key: '_save_fake_data',
  182 + duration: 2,
  183 + });
  184 + resolve(false);
  185 + } else {
  186 + createMessage.success({
  187 + content: `记录${id}的${key}已保存`,
  188 + key: '_save_fake_data',
  189 + duration: 2,
  190 + });
  191 + resolve(true);
  192 + }
  193 + }, 2000);
  194 + });
  195 + }
  196 +
  197 + async function beforeEditSubmit({ record, index, key, value }) {
  198 + console.log('单元格数据正在准备提交', { record, index, key, value });
  199 + return await feakSave({ id: record.id, key, value });
162 } 200 }
163 201
164 function handleEditCancel() { 202 function handleEditCancel() {
@@ -169,6 +207,7 @@ @@ -169,6 +207,7 @@
169 registerTable, 207 registerTable,
170 handleEditEnd, 208 handleEditEnd,
171 handleEditCancel, 209 handleEditCancel,
  210 + beforeEditSubmit,
172 }; 211 };
173 }, 212 },
174 }); 213 });