无木
2021-06-05 fe2bcfc6f74159c355f3be153a316869fdb8b644
提交 | 用户 | age
9c2f3f 1 <template>
V 2   <div :class="prefixCls">
3     <div v-show="!isEdit" :class="`${prefixCls}__normal`" @click="handleEdit">
9ea257 4       {{ getValues || '&nbsp;' }}
9c2f3f 5       <FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
V 6     </div>
7
8     <div v-if="isEdit" :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
9       <CellComponent
10         v-bind="getComponentProps"
11         :component="getComponent"
12         :style="getWrapperStyle"
13         :popoverVisible="getRuleVisible"
14         :rule="getRule"
15         :ruleMessage="ruleMessage"
8eaf57 16         :class="getWrapperClass"
9c2f3f 17         size="small"
V 18         ref="elRef"
19         @change="handleChange"
20         @options-change="handleOptionsChange"
64533f 21         @pressEnter="handleEnter"
9edc28 22       />
9c2f3f 23       <div :class="`${prefixCls}__action`" v-if="!getRowEditable">
V 24         <CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmit" />
25         <CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
26       </div>
27     </div>
28   </div>
29 </template>
30 <script lang="ts">
31   import type { CSSProperties, PropType } from 'vue';
32   import type { BasicColumn } from '../../types/table';
4f8e1c 33   import type { EditRecordRow } from './index';
9c2f3f 34
4f8e1c 35   import { defineComponent, ref, unref, nextTick, computed, watchEffect, toRaw } from 'vue';
9c2f3f 36   import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
4f8e1c 37   import { CellComponent } from './CellComponent';
9c2f3f 38
V 39   import { useDesign } from '/@/hooks/web/useDesign';
4f8e1c 40   import { useTableContext } from '../../hooks/useTableContext';
V 41
9c2f3f 42   import clickOutside from '/@/directives/clickOutside';
V 43
44   import { propTypes } from '/@/utils/propTypes';
4f8e1c 45   import { isString, isBoolean, isFunction, isNumber, isArray } from '/@/utils/is';
9c2f3f 46   import { createPlaceholderMessage } from './helper';
V 47
48   export default defineComponent({
49     name: 'EditableCell',
50     components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent },
9edc28 51     directives: {
V 52       clickOutside,
53     },
9c2f3f 54     props: {
V 55       value: {
56         type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
57         default: '',
58       },
59       record: {
60         type: Object as PropType<EditRecordRow>,
61       },
62       column: {
63         type: Object as PropType<BasicColumn>,
8b2e0f 64         default: () => ({}),
9c2f3f 65       },
V 66       index: propTypes.number,
67     },
68     setup(props) {
69       const table = useTableContext();
70       const isEdit = ref(false);
3576d0 71       const elRef = ref();
9c2f3f 72       const ruleVisible = ref(false);
V 73       const ruleMessage = ref('');
74       const optionsRef = ref<LabelValueOptions>([]);
75       const currentValueRef = ref<any>(props.value);
76       const defaultValueRef = ref<any>(props.value);
77
78       const { prefixCls } = useDesign('editable-cell');
79
80       const getComponent = computed(() => props.column?.editComponent || 'Input');
81       const getRule = computed(() => props.column?.editRule);
82
83       const getRuleVisible = computed(() => {
84         return unref(ruleMessage) && unref(ruleVisible);
85       });
86
87       const getIsCheckComp = computed(() => {
88         const component = unref(getComponent);
89         return ['Checkbox', 'Switch'].includes(component);
90       });
91
92       const getComponentProps = computed(() => {
93         const compProps = props.column?.editComponentProps ?? {};
94         const component = unref(getComponent);
95         const apiSelectProps: Recordable = {};
96         if (component === 'ApiSelect') {
97           apiSelectProps.cache = true;
98         }
99
100         const isCheckValue = unref(getIsCheckComp);
101
102         const valueField = isCheckValue ? 'checked' : 'value';
103         const val = unref(currentValueRef);
104
105         const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
106
107         return {
108           placeholder: createPlaceholderMessage(unref(getComponent)),
109           ...apiSelectProps,
110           ...compProps,
111           [valueField]: value,
112         };
113       });
114
115       const getValues = computed(() => {
116         const { editComponentProps, editValueMap } = props.column;
117
118         const value = unref(currentValueRef);
119
120         if (editValueMap && isFunction(editValueMap)) {
121           return editValueMap(value);
122         }
123
124         const component = unref(getComponent);
125         if (!component.includes('Select')) {
126           return value;
127         }
4f8e1c 128
9c2f3f 129         const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
V 130         const option = options.find((item) => `${item.value}` === `${value}`);
4f8e1c 131
V 132         return option?.label ?? value;
9c2f3f 133       });
V 134
3ef508 135       const getWrapperStyle = computed((): CSSProperties => {
Z 136         if (unref(getIsCheckComp) || unref(getRowEditable)) {
137           return {};
9c2f3f 138         }
3ef508 139         return {
Z 140           width: 'calc(100% - 48px)',
141         };
8eaf57 142       });
143
144       const getWrapperClass = computed(() => {
145         const { align = 'center' } = props.column;
146         return `edit-cell-align-${align}`;
3ef508 147       });
9c2f3f 148
V 149       const getRowEditable = computed(() => {
150         const { editable } = props.record || {};
151         return !!editable;
152       });
153
154       watchEffect(() => {
155         defaultValueRef.value = props.value;
156       });
157
158       watchEffect(() => {
159         const { editable } = props.column;
160         if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
161           isEdit.value = !!editable || unref(getRowEditable);
162         }
163       });
164
165       function handleEdit() {
166         if (unref(getRowEditable) || unref(props.column?.editRow)) return;
167         ruleMessage.value = '';
168         isEdit.value = true;
169         nextTick(() => {
170           const el = unref(elRef);
171           el?.focus?.();
172         });
173       }
174
175       async function handleChange(e: any) {
176         const component = unref(getComponent);
fab7a6 177         if (!e) {
V 178           currentValueRef.value = e;
179         } else if (e?.target && Reflect.has(e.target, 'value')) {
9c2f3f 180           currentValueRef.value = (e as ChangeEvent).target.value;
fab7a6 181         } else if (component === 'Checkbox') {
9c2f3f 182           currentValueRef.value = (e as ChangeEvent).target.checked;
V 183         } else if (isString(e) || isBoolean(e) || isNumber(e)) {
184           currentValueRef.value = e;
185         }
fab7a6 186
4f8e1c 187         table.emit?.('edit-change', {
V 188           column: props.column,
189           value: unref(currentValueRef),
190           record: toRaw(props.record),
191         });
9c2f3f 192         handleSubmiRule();
V 193       }
194
195       async function handleSubmiRule() {
196         const { column, record } = props;
197         const { editRule } = column;
198         const currentValue = unref(currentValueRef);
199
200         if (editRule) {
201           if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
202             ruleVisible.value = true;
203             const component = unref(getComponent);
204             const message = createPlaceholderMessage(component);
205             ruleMessage.value = message;
206             return false;
207           }
208           if (isFunction(editRule)) {
209             const res = await editRule(currentValue, record as Recordable);
210             if (!!res) {
211               ruleMessage.value = res;
212               ruleVisible.value = true;
213               return false;
214             } else {
215               ruleMessage.value = '';
216               return true;
217             }
218           }
219         }
220         ruleMessage.value = '';
221         return true;
222       }
223
4f8e1c 224       async function handleSubmit(needEmit = true, valid = true) {
V 225         if (valid) {
226           const isPass = await handleSubmiRule();
227           if (!isPass) return false;
228         }
229
9c2f3f 230         const { column, index } = props;
V 231         const { key, dataIndex } = column;
1a85df 232         const value = unref(currentValueRef);
9c2f3f 233         if (!key || !dataIndex) return;
4f8e1c 234
9c2f3f 235         const dataKey = (dataIndex || key) as string;
V 236
1a85df 237         const record = await table.updateTableData(index, dataKey, value);
9edc28 238         needEmit && table.emit?.('edit-end', { record, index, key, value });
9c2f3f 239         isEdit.value = false;
V 240       }
241
64533f 242       async function handleEnter() {
V 243         if (props.column?.editRow) {
244           return;
245         }
246         handleSubmit();
247       }
248
9c2f3f 249       function handleCancel() {
V 250         isEdit.value = false;
251         currentValueRef.value = defaultValueRef.value;
252         table.emit?.('edit-cancel', unref(currentValueRef));
253       }
254
255       function onClickOutside() {
256         if (props.column?.editable || unref(getRowEditable)) {
257           return;
258         }
259         const component = unref(getComponent);
260
261         if (component.includes('Input')) {
262           handleCancel();
263         }
264       }
265
266       // only ApiSelect
267       function handleOptionsChange(options: LabelValueOptions) {
268         optionsRef.value = options;
269       }
270
271       function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
272         if (props.record) {
273           /* eslint-disable  */
274           isArray(props.record[cbs])
aa596a 275             ? props.record[cbs]?.push(handle)
9c2f3f 276             : (props.record[cbs] = [handle]);
V 277         }
278       }
279
280       if (props.record) {
281         initCbs('submitCbs', handleSubmit);
282         initCbs('validCbs', handleSubmiRule);
283         initCbs('cancelCbs', handleCancel);
284
fe2bcf 285         if (props.column.dataIndex) {
286           if (!props.record.editValueRefs) props.record.editValueRefs = {};
287           props.record.editValueRefs[props.column.dataIndex] = currentValueRef;
288         }
9c2f3f 289         /* eslint-disable  */
V 290         props.record.onCancelEdit = () => {
291           isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
292         };
293         /* eslint-disable */
294         props.record.onSubmitEdit = async () => {
295           if (isArray(props.record?.submitCbs)) {
aa596a 296             const validFns = (props.record?.validCbs || []).map((fn) => fn());
9c2f3f 297
aa596a 298             const res = await Promise.all(validFns);
4f8e1c 299
9c2f3f 300             const pass = res.every((item) => !!item);
V 301
302             if (!pass) return;
303             const submitFns = props.record?.submitCbs || [];
4f8e1c 304             submitFns.forEach((fn) => fn(false, false));
de5bf7 305             table.emit?.('edit-row-end');
9c2f3f 306             return true;
V 307           }
308         };
309       }
310
311       return {
312         isEdit,
313         prefixCls,
314         handleEdit,
315         currentValueRef,
316         handleSubmit,
317         handleChange,
318         handleCancel,
319         elRef,
320         getComponent,
321         getRule,
322         onClickOutside,
323         ruleMessage,
324         getRuleVisible,
325         getComponentProps,
326         handleOptionsChange,
327         getWrapperStyle,
8eaf57 328         getWrapperClass,
9c2f3f 329         getRowEditable,
9ea257 330         getValues,
64533f 331         handleEnter,
cdf0a6 332         // getSize,
9c2f3f 333       };
V 334     },
335   });
336 </script>
337 <style lang="less">
338   @prefix-cls: ~'@{namespace}-editable-cell';
339
8eaf57 340   .edit-cell-align-left {
341     text-align: left;
342
343     input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
344       text-align: left;
345     }
346   }
347
348   .edit-cell-align-center {
349     text-align: center;
350
351     input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
352       text-align: center;
353     }
354   }
355
356   .edit-cell-align-right {
357     text-align: right;
358
359     input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
360       text-align: right;
361     }
362   }
363
9c2f3f 364   .edit-cell-rule-popover {
V 365     .ant-popover-inner-content {
366       padding: 4px 8px;
367       color: @error-color;
368       // border: 1px solid @error-color;
369       border-radius: 2px;
370     }
371   }
372   .@{prefix-cls} {
373     position: relative;
374
375     &__wrapper {
376       display: flex;
377       align-items: center;
378       justify-content: center;
4f8e1c 379
V 380       > .ant-select {
381         min-width: calc(100% - 50px);
382       }
9c2f3f 383     }
V 384
385     &__icon {
386       &:hover {
387         transform: scale(1.2);
388
389         svg {
390           color: @primary-color;
391         }
392       }
393     }
394
395     &__normal {
396       &-icon {
397         position: absolute;
398         top: 4px;
399         right: 0;
400         display: none;
401         width: 20px;
402         cursor: pointer;
403       }
404     }
405
406     &:hover {
407       .@{prefix-cls}__normal-icon {
408         display: inline-block;
409       }
410     }
411   }
412 </style>