Commit b8413d198fa68bd543cbde6543f90886bacfdc5b

Authored by Aaron
1 parent 841b3ab4
Exists in master and in 1 other branch legacy

新式表格支持行内新增编辑

examples/views/page/other.vue
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 <template> 13 <template>
14 <div> 14 <div>
15 <pre>{{ model }}</pre> 15 <pre>{{ model }}</pre>
16 - <eg-table v-model="tableData" :list="option.list" :tableProps="{ border: true }" editable> 16 + <eg-table v-model="tableData" :list="option.list" :tableProps="{ border: true }" editable @cell-edit="onCellEdit">
17 <el-table-column type="selection"></el-table-column> 17 <el-table-column type="selection"></el-table-column>
18 <template #value-location-locationMin="{ value }"> 18 <template #value-location-locationMin="{ value }">
19 <el-tag v-if="value" size="mini" disable-transitions>{{ value }}</el-tag> 19 <el-tag v-if="value" size="mini" disable-transitions>{{ value }}</el-tag>
@@ -27,10 +27,8 @@ @@ -27,10 +27,8 @@
27 </el-table-column> 27 </el-table-column>
28 </template> --> 28 </template> -->
29 </eg-table> 29 </eg-table>
30 - <!-- <p>这是一个非markdown页面</p>  
31 - <pre>{{ model }}</pre>  
32 <el-button size="mini" @click="handleGetValue">校验</el-button> 30 <el-button size="mini" @click="handleGetValue">校验</el-button>
33 - <eg-form ref="form" v-model="model" :list="option.list" @validate="onValidate" label-width="80px" :span="6" type="div"></eg-form> --> 31 + <!-- <eg-form ref="form" v-model="model" :list="option.list" @validate="onValidate" label-width="80px" :span="6" type="div"></eg-form> -->
34 <eg-form 32 <eg-form
35 ref="form" v-model="model" :list="option.list" @validate="onValidate" :span="4" 33 ref="form" v-model="model" :list="option.list" @validate="onValidate" :span="4"
36 form-class="custom-form" title-class="custom-title" content-class="custom-content" item-class="custom-item" 34 form-class="custom-form" title-class="custom-title" content-class="custom-content" item-class="custom-item"
@@ -73,9 +71,10 @@ export default { @@ -73,9 +71,10 @@ export default {
73 { 71 {
74 group: { key: 'district' }, 72 group: { key: 'district' },
75 list: [ 73 list: [
76 - { type: 'eagle-select', label: '省', key: 'province', props: { dataSource: [{ label: '上海', value: '上海' }, { label: '北京', value: '北京' }] }, rules: [{ required: true, message: '请输入省', trigger: 'change' }], minWidth: 120 }, 74 + { type: 'eagle-select', label: '省', key: 'province', props: { dataSource: [{ label: '新疆', value: '新疆' }, { label: '四川', value: '四川' }] }, rules: [{ required: true, message: '请输入省', trigger: 'change' }], minWidth: 120 },
77 // { type: 'el-input', label: '市', key: 'city', render: { type: 'el-tag', props: { type: 'danger', size: 'mini' } } }, 75 // { type: 'el-input', label: '市', key: 'city', render: { type: 'el-tag', props: { type: 'danger', size: 'mini' } } },
78 - { type: 'el-input', label: '市', key: 'city', 76 + { type: 'eagle-select', label: '市', key: 'city',
  77 + props: { dataSource: [{ label: '上海', value: '上海' }, { label: '北京', value: '北京' }] },
79 render: { 78 render: {
80 type: 'a', props: { href: 'https:///www.baidu.com/', target: '_blank' }, style: { color: 'red' }, 79 type: 'a', props: { href: 'https:///www.baidu.com/', target: '_blank' }, style: { color: 'red' },
81 children({ row }) { 80 children({ row }) {
@@ -161,6 +160,9 @@ export default { @@ -161,6 +160,9 @@ export default {
161 }, 160 },
162 handleGetValue() { 161 handleGetValue() {
163 this.$refs.form.validate(); 162 this.$refs.form.validate();
  163 + },
  164 + onCellEdit(e) {
  165 + console.log(e)
164 } 166 }
165 } 167 }
166 } 168 }
examples/views/page/table-new/cell-editable.vue
@@ -18,9 +18,9 @@ @@ -18,9 +18,9 @@
18 <component 18 <component
19 :value="$_get(row, item.fullKey)" 19 :value="$_get(row, item.fullKey)"
20 :is="item.type" v-bind="item.props" :style="item.style" size="mini" 20 :is="item.type" v-bind="item.props" :style="item.style" size="mini"
21 - @input="v => $emit('update', { value: v, row, key: item.fullKey })" 21 + @input="v => $emit('update', { oldValue, value: v, row, key: item.key, fullKey: item.fullKey })"
22 ></component> 22 ></component>
23 - <span @click="$emit('done')"> 23 + <span v-if="btnVisible !== false" @click="$emit('done')">
24 <svg class="eagle-table-cell-editable__icon" viewBox="0 0 1024 1024" width="24" height="24"> 24 <svg class="eagle-table-cell-editable__icon" viewBox="0 0 1024 1024" width="24" height="24">
25 <path d="M235.946667 472.938667l-45.226667 45.312 210.090667 209.514666 432.362666-427.690666-45.013333-45.482667-387.157333 382.976z"></path> 25 <path d="M235.946667 472.938667l-45.226667 45.312 210.090667 209.514666 432.362666-427.690666-45.013333-45.482667-387.157333 382.976z"></path>
26 </svg> 26 </svg>
@@ -49,9 +49,23 @@ export default { @@ -49,9 +49,23 @@ export default {
49 row: Object, 49 row: Object,
50 item: Object, 50 item: Object,
51 editable: Boolean, 51 editable: Boolean,
  52 + btnVisible: Boolean,
52 }, 53 },
53 methods: { 54 methods: {
54 $_get: get, 55 $_get: get,
  56 + },
  57 + data() {
  58 + return {
  59 + oldValue: undefined,
  60 + }
  61 + },
  62 + watch: {
  63 + editable: {
  64 + handler() {
  65 + this.oldValue = get(this.row, this.item.agentKey || this.item.fullKey);
  66 + },
  67 + immediate: true,
  68 + }
55 } 69 }
56 } 70 }
57 </script> 71 </script>
58 \ No newline at end of file 72 \ No newline at end of file
examples/views/page/table-new/index.vue
@@ -5,8 +5,16 @@ @@ -5,8 +5,16 @@
5 </style> 5 </style>
6 6
7 <template> 7 <template>
8 - <div @keyup.enter="editCell = {}">  
9 - <el-table class="eagle-table" ref="table" :data="tableData" v-bind="{ size: 'small', ...tableProps }" v-on="tableEvents" @cell-dblclick="onCellDblclick"> 8 + <div @keyup.enter="tableEditCell = {}">
  9 + <div style="padding-bottom: 10px;">
  10 + <el-button @click="handleNew" size="mini">新增</el-button>
  11 + <el-button @click="handleEdit" size="mini" v-if="!rowEditable && tableSelection.length > 0">编辑</el-button>
  12 + <el-button @click="handleSave" size="mini" type="primary" v-if="rowEditable">完成</el-button>
  13 + <el-button @click="handleCancel" size="mini" type="plain" v-if="rowNew">取消</el-button>
  14 + </div>
  15 + <el-table class="eagle-table" ref="table" :data="tableData" v-bind="{ size: 'small', ...tableProps }"
  16 + v-on="tableEvents" @cell-dblclick="onCellDblclick" @selection-change="onSelectionChange"
  17 + >
10 <!-- 默认表格插槽 --> 18 <!-- 默认表格插槽 -->
11 <slot name="default"></slot> 19 <slot name="default"></slot>
12 <!-- 根据配置列表生成表格列 --> 20 <!-- 根据配置列表生成表格列 -->
@@ -16,11 +24,11 @@ @@ -16,11 +24,11 @@
16 <!-- 如果有表格列具名插槽 --> 24 <!-- 如果有表格列具名插槽 -->
17 <slot v-if="$scopedSlots[item.keyPath.join('-')]" :name="item.keyPath.join('-')" v-bind="item"></slot> 25 <slot v-if="$scopedSlots[item.keyPath.join('-')]" :name="item.keyPath.join('-')" v-bind="item"></slot>
18 <!-- 默认表格列渲染 --> 26 <!-- 默认表格列渲染 -->
19 - <el-table-column v-else v-bind="item" :prop="item.agentKey || item.fullKey || item.key" :key="index" :min-width="item.minWidth || item['min-width'] || 140"> 27 + <el-table-column v-else v-bind="item" :prop="item.fullKey || item.key" :key="index" :min-width="minWidth || item.minWidth || item['min-width'] || (editable ? 140 : undefined)">
20 <template #default="{ row, column, $index }"> 28 <template #default="{ row, column, $index }">
21 <cell-editable 29 <cell-editable
22 - :editable="editable && (editCell.index === row.$index && editCell.key === (item.agentKey || item.fullKey || item.key))"  
23 - :row="row" :item="item" @update="onCellUpdate" @done="editCell = {}" 30 + :editable="row.$editable || (editable && (tableEditCell.index === row.$index && tableEditCell.key === (item.agentKey || item.fullKey || item.key)))"
  31 + :row="row" :item="item" @update="onCellUpdate" @done="onCellUpdateDone" :btn-visible="!row.$editable"
24 > 32 >
25 <!-- 如果有表格列值渲染具名插槽 --> 33 <!-- 如果有表格列值渲染具名插槽 -->
26 <slot v-if="$scopedSlots[`value-${item.keyPath.join('-')}`]" :name="`value-${item.keyPath.join('-')}`" v-bind="item" 34 <slot v-if="$scopedSlots[`value-${item.keyPath.join('-')}`]" :name="`value-${item.keyPath.join('-')}`" v-bind="item"
@@ -69,12 +77,16 @@ export default { @@ -69,12 +77,16 @@ export default {
69 tableEvents: Object, 77 tableEvents: Object,
70 // 是否可编辑 78 // 是否可编辑
71 editable: Boolean, 79 editable: Boolean,
  80 + // 列宽
  81 + minWidth: Number,
72 }, 82 },
73 data() { 83 data() {
74 return { 84 return {
75 - tableList: [],  
76 - tableData: [],  
77 - editCell: {}, 85 + tableList: [], // 表格配置列表
  86 + tableData: [], // 表格数据
  87 + tableRowTemplate: { $editable: true }, // 行数据模板
  88 + tableEditCell: {}, // 正在编辑的单元格
  89 + tableSelection: [], // 表格已选中
78 }; 90 };
79 }, 91 },
80 computed: { 92 computed: {
@@ -83,6 +95,28 @@ export default { @@ -83,6 +95,28 @@ export default {
83 get() { 95 get() {
84 return this.$refs.table; 96 return this.$refs.table;
85 } 97 }
  98 + },
  99 + // 行编辑状态
  100 + rowEditable() {
  101 + let result = false;
  102 + for (const row of this.tableData) {
  103 + if (row.$editable) {
  104 + result = true;
  105 + break;
  106 + }
  107 + }
  108 + return result;
  109 + },
  110 + // 存在新行
  111 + rowNew() {
  112 + let result = false;
  113 + for (const row of this.tableData) {
  114 + if (row.$new) {
  115 + result = true;
  116 + break;
  117 + }
  118 + }
  119 + return result;
86 } 120 }
87 }, 121 },
88 watch: { 122 watch: {
@@ -115,6 +149,7 @@ export default { @@ -115,6 +149,7 @@ export default {
115 generateFullKey(newList); 149 generateFullKey(newList);
116 // 创建输出列表 150 // 创建输出列表
117 const result = []; 151 const result = [];
  152 + const tableRowTemplate = {};
118 // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key 153 // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key
119 const generateFlatList = (list) => { 154 const generateFlatList = (list) => {
120 list.forEach(item => { 155 list.forEach(item => {
@@ -122,17 +157,59 @@ export default { @@ -122,17 +157,59 @@ export default {
122 generateFlatList(item.list); 157 generateFlatList(item.list);
123 } else if (!item.group && !item.list) { 158 } else if (!item.group && !item.list) {
124 result.push({ ...item, keyPath: item.fullKey.split('.') }); 159 result.push({ ...item, keyPath: item.fullKey.split('.') });
  160 + set(tableRowTemplate, item.fullKey, undefined);
  161 + tableRowTemplate.$editable = true;
125 } 162 }
126 }); 163 });
127 }; 164 };
128 generateFlatList(newList); 165 generateFlatList(newList);
129 this.tableList = result; 166 this.tableList = result;
  167 + this.tableRowTemplate = tableRowTemplate;
130 }, 168 },
131 immediate: true 169 immediate: true
132 } 170 }
133 }, 171 },
134 methods: { 172 methods: {
135 $_get: get, 173 $_get: get,
  174 + handleNew() {
  175 + const tableData = cloneDeep(this.tableData);
  176 + tableData.push({ ...this.tableRowTemplate, $new: true });
  177 + this.tableEditCell = {};
  178 + this.emitTableData(tableData);
  179 + },
  180 + handleEdit() {
  181 + const tableData = cloneDeep(this.tableData);
  182 + const selectionIndexArr = this.tableSelection.map(i => i.$index);
  183 + tableData.forEach((r, i) => {
  184 + if (selectionIndexArr.includes(r.$index)) {
  185 + tableData[i].$editable = true;
  186 + }
  187 + });
  188 + this.tableEditCell = {};
  189 + this.emitTableData(tableData);
  190 + },
  191 + handleSave() {
  192 + const tableData = cloneDeep(this.tableData);
  193 + tableData.forEach((r, i) => {
  194 + delete tableData[i].$editable;
  195 + delete tableData[i].$new;
  196 + });
  197 + this.tableEditCell = {};
  198 + this.emitTableData(tableData);
  199 + },
  200 + handleCancel() {
  201 + let tableData = cloneDeep(this.tableData);
  202 + tableData = tableData.filter(row => !row.$new);
  203 + this.emitTableData(tableData);
  204 + },
  205 + // 更新表格数据
  206 + emitTableData(tableData) {
  207 + if (this.$listeners['input']) {
  208 + this.$emit('input', tableData);
  209 + } else {
  210 + this.tableData = tableData;
  211 + }
  212 + },
136 // 绑定表格列显示隐藏状态 213 // 绑定表格列显示隐藏状态
137 bindItemVisible(visible = true) { 214 bindItemVisible(visible = true) {
138 let result = visible; 215 let result = visible;
@@ -147,19 +224,35 @@ export default { @@ -147,19 +224,35 @@ export default {
147 }, 224 },
148 // 双击单元格 225 // 双击单元格
149 onCellDblclick(row, column, cell, event) { 226 onCellDblclick(row, column, cell, event) {
150 - this.editCell = { index: row.$index, key: column.property }; 227 + if (this.editable) {
  228 + this.tableEditCell = { index: row.$index, key: column.property };
  229 + }
151 }, 230 },
152 // 编辑表格更新值 231 // 编辑表格更新值
153 - onCellUpdate({ value, row, key }) { 232 + onCellUpdate({ oldValue, value, row, key, fullKey }) {
154 const tableData = cloneDeep(this.tableData); 233 const tableData = cloneDeep(this.tableData);
155 const tableRow = tableData[row.$index]; 234 const tableRow = tableData[row.$index];
156 - set(tableRow, key, value); 235 + set(tableRow, fullKey, value);
157 tableData[row.$index] = tableRow; 236 tableData[row.$index] = tableRow;
158 if (this.$listeners['input']) { 237 if (this.$listeners['input']) {
159 - this.$emit('input', tableData); 238 + if (this.$listeners['input']) {
  239 + this.$set(this.tableData, row.$index, tableRow);
  240 + this.$emit('cell-edit', { row: tableRow, key, fullKey, oldValue, value });
  241 + } else {
  242 + this.$emit('input', tableData);
  243 + }
160 } else { 244 } else {
161 this.$set(this.tableData, row.$index, tableRow); 245 this.$set(this.tableData, row.$index, tableRow);
162 } 246 }
  247 + },
  248 + // 编辑表格确认
  249 + onCellUpdateDone() {
  250 + this.tableEditCell = {}
  251 + },
  252 + // 表格选中
  253 + onSelectionChange(selection) {
  254 + this.tableSelection = selection;
  255 + this.$emit('selection', selection);
163 } 256 }
164 } 257 }
165 }; 258 };