Commit b8413d198fa68bd543cbde6543f90886bacfdc5b
1 parent
841b3ab4
Exists in
master
and in
1 other branch
新式表格支持行内新增编辑
Showing
3 changed files
with
129 additions
and
20 deletions
Show diff stats
examples/views/page/other.vue
| ... | ... | @@ -13,7 +13,7 @@ |
| 13 | 13 | <template> |
| 14 | 14 | <div> |
| 15 | 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 | 17 | <el-table-column type="selection"></el-table-column> |
| 18 | 18 | <template #value-location-locationMin="{ value }"> |
| 19 | 19 | <el-tag v-if="value" size="mini" disable-transitions>{{ value }}</el-tag> |
| ... | ... | @@ -27,10 +27,8 @@ |
| 27 | 27 | </el-table-column> |
| 28 | 28 | </template> --> |
| 29 | 29 | </eg-table> |
| 30 | - <!-- <p>这是一个非markdown页面</p> | |
| 31 | - <pre>{{ model }}</pre> | |
| 32 | 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 | 32 | <eg-form |
| 35 | 33 | ref="form" v-model="model" :list="option.list" @validate="onValidate" :span="4" |
| 36 | 34 | form-class="custom-form" title-class="custom-title" content-class="custom-content" item-class="custom-item" |
| ... | ... | @@ -73,9 +71,10 @@ export default { |
| 73 | 71 | { |
| 74 | 72 | group: { key: 'district' }, |
| 75 | 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 | 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 | 78 | render: { |
| 80 | 79 | type: 'a', props: { href: 'https:///www.baidu.com/', target: '_blank' }, style: { color: 'red' }, |
| 81 | 80 | children({ row }) { |
| ... | ... | @@ -161,6 +160,9 @@ export default { |
| 161 | 160 | }, |
| 162 | 161 | handleGetValue() { |
| 163 | 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 | 18 | <component |
| 19 | 19 | :value="$_get(row, item.fullKey)" |
| 20 | 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 | 22 | ></component> |
| 23 | - <span @click="$emit('done')"> | |
| 23 | + <span v-if="btnVisible !== false" @click="$emit('done')"> | |
| 24 | 24 | <svg class="eagle-table-cell-editable__icon" viewBox="0 0 1024 1024" width="24" height="24"> |
| 25 | 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 | 26 | </svg> |
| ... | ... | @@ -49,9 +49,23 @@ export default { |
| 49 | 49 | row: Object, |
| 50 | 50 | item: Object, |
| 51 | 51 | editable: Boolean, |
| 52 | + btnVisible: Boolean, | |
| 52 | 53 | }, |
| 53 | 54 | methods: { |
| 54 | 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 | 71 | </script> |
| 58 | 72 | \ No newline at end of file | ... | ... |
examples/views/page/table-new/index.vue
| ... | ... | @@ -5,8 +5,16 @@ |
| 5 | 5 | </style> |
| 6 | 6 | |
| 7 | 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 | 19 | <slot name="default"></slot> |
| 12 | 20 | <!-- 根据配置列表生成表格列 --> |
| ... | ... | @@ -16,11 +24,11 @@ |
| 16 | 24 | <!-- 如果有表格列具名插槽 --> |
| 17 | 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 | 28 | <template #default="{ row, column, $index }"> |
| 21 | 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 | 34 | <slot v-if="$scopedSlots[`value-${item.keyPath.join('-')}`]" :name="`value-${item.keyPath.join('-')}`" v-bind="item" |
| ... | ... | @@ -69,12 +77,16 @@ export default { |
| 69 | 77 | tableEvents: Object, |
| 70 | 78 | // 是否可编辑 |
| 71 | 79 | editable: Boolean, |
| 80 | + // 列宽 | |
| 81 | + minWidth: Number, | |
| 72 | 82 | }, |
| 73 | 83 | data() { |
| 74 | 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 | 92 | computed: { |
| ... | ... | @@ -83,6 +95,28 @@ export default { |
| 83 | 95 | get() { |
| 84 | 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 | 122 | watch: { |
| ... | ... | @@ -115,6 +149,7 @@ export default { |
| 115 | 149 | generateFullKey(newList); |
| 116 | 150 | // 创建输出列表 |
| 117 | 151 | const result = []; |
| 152 | + const tableRowTemplate = {}; | |
| 118 | 153 | // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key |
| 119 | 154 | const generateFlatList = (list) => { |
| 120 | 155 | list.forEach(item => { |
| ... | ... | @@ -122,17 +157,59 @@ export default { |
| 122 | 157 | generateFlatList(item.list); |
| 123 | 158 | } else if (!item.group && !item.list) { |
| 124 | 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 | 165 | generateFlatList(newList); |
| 129 | 166 | this.tableList = result; |
| 167 | + this.tableRowTemplate = tableRowTemplate; | |
| 130 | 168 | }, |
| 131 | 169 | immediate: true |
| 132 | 170 | } |
| 133 | 171 | }, |
| 134 | 172 | methods: { |
| 135 | 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 | 214 | bindItemVisible(visible = true) { |
| 138 | 215 | let result = visible; |
| ... | ... | @@ -147,19 +224,35 @@ export default { |
| 147 | 224 | }, |
| 148 | 225 | // 双击单元格 |
| 149 | 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 | 233 | const tableData = cloneDeep(this.tableData); |
| 155 | 234 | const tableRow = tableData[row.$index]; |
| 156 | - set(tableRow, key, value); | |
| 235 | + set(tableRow, fullKey, value); | |
| 157 | 236 | tableData[row.$index] = tableRow; |
| 158 | 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 | 244 | } else { |
| 161 | 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 | }; | ... | ... |