效果图
无图言叼,直接上图:
应用场景
对于修改表格数据,比较主流的是通过模态框内嵌表单来修改。这样上手比较简单,验证功能很强大。如果通过表格行内编辑的话,上手稍微复杂一点,直接在列里面修改,会非常直观顺畅,但是有一定的缺陷,无法内嵌表单,验证起来比较不友好。
.Vue
<template>
<div>
<div class="page-location-wrapper">
<bread-nav :nav-list="['数据模版', '数据模版DEMO']"/>
<h1>数据模版DEMO</h1>
</div>
<div class="public-container">
<div class="public-operation-bar">
<div class="button-wrapper">
<a-button icon="plus-circle" type="primary"
@click="addRow">新增
</a-button>
<a-button type="primary">批量发布</a-button>
</div>
</div>
<a-divider/>
<div class="table-wrapper">
<!--每个列的宽度必须比列名总长度大才能使表格所有列对齐,留一个列不设置宽度-->
<a-table
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange, onSelectAll: onSelectAllChange}"
:columns="columns"
:dataSource="tableData"
bordered
size="middle"
:pagination="pagination"
:loading="tableLoading"
:scroll="{x: 2050}"
>
<template
v-for="col in customRenderList"
v-slot:[col]="text, record, index"
>
<div :key="col">
<a-input
:read-only="readonlyArr.includes(col)"
placeholder="请输入"
v-if="record.editable && inputScopedSlots.includes(col)"
:value="text"
@change="e => handleChange(e.target.value, record.key, col)"
/>
<a-date-picker
placeholder="请选择时间"
v-else-if="record.editable && dateScopedSlots.includes(col)"
:value="momentDateStr(text)"
@change="onChangeDate($event, record.key, col)"
/>
<a-select
placeholder="请选择"
style="display: block;"
v-else-if="record.editable && selectScopedSlots.includes(col)"
:value="text"
@change="onChangeSelect($event, record.key, col)"
>
<a-select-option value="lucy">Lucy</a-select-option>
<a-select-option value="jack">Jack</a-select-option>
</a-select>
<span v-else>{{text}}</span>
</div>
</template>
<template #action="text, record, index">
<div class="editable-row-operations">
<div v-if="record.editable">
<a @click="save(record.key)">保存</a>
<a-divider type="vertical"/>
<a @click="cancel(record.key)">取消</a>
</div>
<div v-else>
<a @click="edit(record.key)">编辑</a>
</div>
</div>
</template>
</a-table>
</div>
</div>
</div>
</template>
<script>
import Mixin from './mixin'
import Moment from 'moment'
export default {
name: "DataTemplateDemo",
mixins: [Mixin],
methods: {
// 将时间字符串 转换 为 Moment
momentDateStr(dateStr) {
return Moment(dateStr)
}
}
}
</script>
Mixin
import {clearReference} from '@/utils/utils'
const data = []
for (let i = 0; i < 10; i++) {
data.push({
key: i,
a: '2019-12-17',
b: 'lucy',
c: '22',
e: '22',
d: '22',
f: '22',
g: '22',
h: '22',
i: '22',
j: '22',
editable: false
})
}
const Mixin = {
data() {
return {
// 表格列 - 为了方便使 dataIndex === scopedSlots.customRender
columns: [
{title: '左边固定', width: 200, align: 'center', dataIndex: 'a', fixed: 'left', scopedSlots: {customRender: 'a'}},
{title: '测试列3', width: 200, dataIndex: 'b', scopedSlots: {customRender: 'b'}},
{title: '测试列4', width: 200, dataIndex: 'c', scopedSlots: {customRender: 'c'}},
{
title: '测试列5', width: 200, children: [
{title: '分组1', width: 200, dataIndex: 'e', scopedSlots: {customRender: 'e'}},
{title: '分组2', width: 200, dataIndex: 'j', scopedSlots: {customRender: 'j'}},
]
},
{title: '测试列6', width: 200, dataIndex: 'f', scopedSlots: {customRender: 'f'}},
{title: '测试列7', width: 200, dataIndex: 'g', scopedSlots: {customRender: 'g'}},
{title: '测试列8', width: 200, dataIndex: 'h', scopedSlots: {customRender: 'h'}},
{title: '测试列8', dataIndex: 'i', scopedSlots: {customRender: 'i'}},
{title: '右边固定2', width: 200, align: 'center', scopedSlots: {customRender: 'action'}, fixed: 'right'},
],
// 表格数据
tableData: [],
// 表格缓存的数据 - 用来点击取消时回显数据
cacheData: [],
// 每一列的插槽名 - 表格行内编辑用
customRenderList: ['a', 'b', 'c', 'e', 'f', 'g', 'h', 'i', 'j'],
// 用来匹配插槽中显示input框
inputScopedSlots: ['c', 'e', 'f', 'g', 'h', 'i', 'j'],
// 用来匹配插槽中显示date框
dateScopedSlots: ['a'],
// 用来匹配插槽中显示select框
selectScopedSlots: ['b'],
// 对于某些自动赋值的input框设为 只读
readonlyArr: ['c'],
// 表格分页
pagination: {
current: 1,
size: 'large',
pageSize: 10, // 默认每页显示数量
total: 0, // 总条数
showQuickJumper: true,
showTotal: (total, range) => `总共 ${total} 条记录,当前第 ${range[0]}条至第${range[1]}条`,
onChange: (page) => this.pageChange(page)
},
// 表格loading
tableLoading: false,
// 表格选中key
selectedRowKeys: []
}
},
created() {
// 此处模拟ajax,赋值(需清除引用)
this.tableData = clearReference(data)
this.cacheData = clearReference(data)
},
methods: {
// 分页事件
pageChange(page) {
this.pagination.current = page
},
// 表格选中事件
onSelectChange(selectedRowKeys) {
console.log(selectedRowKeys);
this.selectedRowKeys = selectedRowKeys;
},
// 表格点击全选事件
onSelectAllChange(selected, selectedRows) {
// 判断 全选并且第一行数据为新增 则不选中新增那一行
if (selected && selectedRows[0].key === 'newRow') {
this.selectedRowKeys.splice(0, 1)
}
},
// 添加一行
addRow() {
if (this.tableData.some(item => item.newRow === true)) return this.$message.info('请先编辑完毕后在再添加!');
// 新增时取消表格选中的key
this.selectedRowKeys = []
this.tableData.unshift({
key: 'newRow', // newRow 表示该行是新增的,提交成功后 key替换为数据库ID
newRow: true, //用来限制只能新增一行
a: this.$dateformat(new Date(), 'isoDate'),
b: undefined,
c: '',
e: '',
d: '',
f: '',
g: '',
h: '',
i: '',
j: '',
editable: true
})
this.cacheData.unshift({
key: 'newRow',
newRow: true,
a: this.$dateformat(new Date(), 'isoDate'),
b: undefined,
c: '',
e: '',
d: '',
f: '',
g: '',
h: '',
i: '',
j: '',
editable: false
})
},
// 编辑一行
edit(rowKey) {
const newData = [...this.tableData];
const target = newData.filter(item => rowKey === item.key)[0];
// 根据rowKey判断该行数据是否存在
if (target) {
target.editable = true; // 修改target可以直接影响到newData
this.tableData = newData;
}
},
// 点击保存
save(rowKey) {
const newData = [...this.tableData];
const target = newData.filter(item => rowKey === item.key)[0];
if (target) {
delete target.editable;
this.tableData = newData;
this.cacheData = newData.map(item => ({...item}));
}
},
// 点击取消
cancel(rowKey) {
const newData = [...this.tableData];
const target = newData.filter(item => rowKey === item.key)[0];
if (target) {
// 将缓存数据中匹配到的数据对象覆盖合并当前被修改的行
Object.assign(target, this.cacheData.filter(item => rowKey === item.key)[0]);
delete target.editable;
this.tableData = newData;
}
},
// input 输入change
handleChange(value, rowKey, colName) {
const newData = [...this.tableData];
const target = newData.filter(item => rowKey === item.key)[0];
if (target) {
target[colName] = value;
this.tableData = newData;
}
},
// 日期框 change
onChangeDate($event, rowKey, colName) {
const newData = [...this.tableData];
const target = newData.filter(item => rowKey === item.key)[0];
if (target) {
target[colName] = this.$dateformat($event, 'isoDate');
this.tableData = newData;
}
},
// select 框 change
onChangeSelect($event, rowKey, colName) {
const newData = [...this.tableData];
const target = newData.filter(item => rowKey === item.key)[0];
if (target) {
target[colName] = $event;
// 根据select框的值自动带出某个input的值 - 因为第三列列名为c
$event === 'lucy' ? target.c = '根据lucy带出的值' : target.c = '根据jack带出的值'
this.tableData = newData;
}
}
}
}
export default Mixin
实现原理
基于antV官方的一个 demo 加上自己一些新增的实现,具体实现在代码里面有非常详细的注释。无奈没有更多的时间去优化这篇长大粗的博文,目前只能简单粗暴的将demo代码粘上来。
补充
对于文中的 clearReference
方法其实就是 JSON.parse(JSON.stringify(obj))
补充于 2021年3月22日:this.$dateformat
是 https://www.npmjs.com/package/dateformat 插件