a-table 表格中的编辑修改交互
需求
-
右侧的编辑/新增/删除+input框/√/×的交互都在子组件中,真正的接口调用在父组件中
因为是列表,涉及子组件的复用,所以交互在子组件中进行
-
点击右侧新增时,列表没有展开时,需要展开并新增节点,展开时则新增节点
采用a-table的API(:expandedRowKeys=“tableExpandedRowKeys” @expand=“onTableExpand”)
// table的展开收起事件 onTableExpand(expanded, record) { if (expanded) { this.tableExpandedRowKeys.push(record.key) } else { this.tableExpandedRowKeys.splice(this.tableExpandedRowKeys.indexOf(record.key), 1) } },
-
列表展示,当有子选项时需要有可以点击展示子节点的icon
采用a-table的API(:expandIcon=“expandIcon”)进行自定义icon
// 修改列表展开的icon expandIcon(props) { if (props.record.children && props.record.children.length > 0) { const list = props.record.children.filter((item) => item.isShow) // 判断是否有子节点 if (list.length === 0) { return <span style={{ 'margin-right': '19px' }}></span> } if (props.expanded) { return ( <span className="table-icon" onClick={(e) => { props.onExpand(props.record, e) }} > <a-icon type="down" /> </span> ) } else { return ( <span className="table-icon" onClick={(e) => { props.onExpand(props.record, e) }} > <a-icon type="right" /> </span> ) } } else { return <span style={{ 'margin-right': '19px' }}></span> } }
-
可以对表格内容进行修改,有一个临时状态
对数据进行递归处理,给每个节点增加一个是否是编辑状态的属性isEdit(默认true)
-
可以保存,取消修改内容
对数据进行递归处理,给每个节点增加一个属性copyWord,并将需要可编辑字段的值赋值给copyWord
input绑定的是copyWord的值,点击√才会将copyWord的值赋值给可编辑字段,×时不进行任何操作 -
删除节点时不能刷新列表,以免影响其他可编辑的节点
对数据进行递归处理,给每个节点增加一个该行是否显示的属性isShow(默认true)
结合a-table的API(:rowClassName=“rowClassName”)+ css的display:none/ block进行判断是否隐藏// 判断加载哪个className rowClassName(record, index) { return record.isShow ? 'tableShow' : 'tableHiddle' }
-
每个节点只能同时新增一个子节点,且子节点没有保存时不能新增子子节点
对数据进行递归处理,给每个节点增加一个该行是否可以新增其他选项isOtherAdd(默认true)
-
当新增的节点,没有保存或者删除时,需要将父元素置灰的选项再次高亮
注意:这里列表重新赋值时,父组件的值已修改,但子组件的值未修改的状态,采用强制更新视图方法无效
最后采用去子组件中修改值的方法实现// 父组件 this.$refs[`tableItem${parentId}`].load() // 子组件 load() { this.record.isOtherAdd = true }
-
对输入的内容进行正则校验
子组件中判断是否要显示err
this.isShowErr = !new RegExp(/^[a-zA-Z0-9_\u4e00-\u9fa5]{0,32}$/).test(this.record.copyWord)
-
点击后滚动到列表的最后位置
采用nextTick + scrollTo
handleClickAdd() { this.dataSource.push({ name: '未命名分类', key: this.dataSource.length + '', isEdit: false, children: [], isShow: true, isOtherAdd: false, // 是否可以新增其他选项 }) // 注意一定要在数据新增完,第一次dom加载完后进行滚动 this.$nextTick(() => { const ele = document.getElementById('app') window.scrollTo(0, document.body.scrollHeight - ele.clientHeight) }) },
完整封装
// 子组件
<template>
<div class="table-edit" :style="{ height: isShowErr && !record.isEdit ? '44px' : '' }">
<span v-show="record.isEdit"> {{ record[record.keyWord] }} </span>
<span v-show="!record.isEdit">
<a-input
v-model="record.copyWord"
:style="{ width: '170px', borderColor: isShowErr ? '#f5222d' : '' }"
size="small"
@change="isCheckOk"
/>
<a-icon type="check" class="mar-l-10" @click="handleClickOk" />
<a-icon type="close" class="mar-l-10" @click="handleClickCancle" />
<span
v-show="isShowErr && !record.isEdit"
class="err-msg"
>格式仅允许:大小写字母、数字、下划线,少于32个字符</span
>
</span>
<a-popover trigger="hover" placement="bottom">
<template slot="content">
<div class="action-line">
<a-button type="link" v-w-role="'DF_PL_LC200'" @click="record.isEdit = false">
<a-icon type="edit" :style="{ fontSize: '14px' }" />编辑分类
</a-button>
</div>
<div :class="['action-line', !record.isOtherAdd ? 'action-disabled' : '']">
<a-button :disabled="!record.isOtherAdd" type="link" v-w-role="'DF_PL_LC110'" @click="handleClickAdd">
<a-icon type="plus" :style="{ fontSize: '14px' }" />新增子分类
</a-button>
</div>
<div class="action-line">
<a-button type="link" v-w-role="'DF_PL_LC300'" @click="handleClickDel">
<a-icon type="delete" :style="{ fontSize: '14px' }" />删除分类
</a-button>
</div>
</template>
<a-icon type="ellipsis" class="table-edit-icon" :style="{ fontSize: '24px', color: '#999' }" />
</a-popover>
</div>
</template>
<script>
export default {
name: 'TableEdit',
props: {
recordTable: {
type: Object,
default: () => {},
},
},
data() {
return {
record: {},
isShowErr: false,
}
},
created() {
this.record = this.recordTable
},
methods: {
/**
* @description 重新可添加
*/
load() {
this.record.isOtherAdd = true
},
/**
* @description 编辑分类完成按钮
*/
handleClickOk() {
if (!this.isCheckOk()) {
this.record.isEdit = true
this.record[this.record.keyWord] = this.record.copyWord
// 点击ok时,有三种不同情况,需要调用接口
// if 1. 编辑时 else 2. 子类新增时 3. 父类新增时
if (this.record.id) {
this.$emit('edit-ok', this.record)
} else {
this.$emit('ok', this.record)
}
}
},
/**
* @description 编辑分类时取消按钮
*/
handleClickCancle() {
this.record.isEdit = true
// 判断是否有值,有值时清除值的内容,无值时直接删除
// 退出编辑模式
if (!this.record.id) {
this.record.isShow = false
this.$emit('delete', this.record)
}
},
/**
* @description 新增子分类
*/
handleClickAdd() {
const key = this.record.key + '-' + this.record.children.length
this.record.children.push({
name: '未命名子分类',
key: key,
isEdit: false,
children: [],
isShow: true,
isOtherAdd: false,
})
this.$emit('expand-row-keys', this.record.key)
// 禁用其他的子选择的新增功能
this.record.isOtherAdd = false
},
/**
* @description 删除子分类
*/
handleClickDel() {
this.record.isShow = false
this.$emit('delete', this.record)
},
/**
* @description 开发标识符的校验
*/
isCheckOk() {
if (!this.record.copyWord) {
this.isShowErr = false
} else {
this.isShowErr = !new RegExp(/^[a-zA-Z0-9_\u4e00-\u9fa5]{0,32}$/).test(this.record.copyWord)
}
return this.isShowErr
},
},
}
</script>
<style lang="less">
.table-edit {
height: 24px;
line-height: 24px;
width: 100%;
display: flex;
align-items: center;
}
.table-edit-icon {
position: absolute;
right: 0;
}
.tag-type .ant-table-row-cell-break-word {
display: flex;
align-items: center;
position: relative;
}
.err-msg {
color: #f5222d;
display: block;
}
.action-line.action-disabled {
background-color: #f5f5f5;
.ant-btn-link {
color: rgba(0, 0, 0, 0.25);
}
&:hover {
background-color: #f5f5f5;
.ant-btn-link {
color: rgba(0, 0, 0, 0.25);
}
}
}
</style>
// 父组件 a-table的用法 + 父组件的用法
<a-table
:columns="columns"
:data-source="dataSource"
:rowKey="(record) => record.key"
:rowClassName="
(record, index) => {
return record.isShow ? 'tableShow' : 'tableHiddle'
}
"
:pagination="false"
:expandIcon="expandIcon"
:expandedRowKeys="tableExpandedRowKeys"
:loading="loading"
@expand="onTableExpand"
childrenColumnName="children"
class="tag-type"
>
<table-edit
@expand-row-keys="
(key) => {
tableExpandedRowKeys.push(key)
}
"
@ok="handleClickOk"
@edit-ok="handleClickEditOk"
@delete="handleClickDelete"
:ref="`tableItem${record.id}`"
slot="name"
slot-scope="text, record"
:record-table="{ copyWord: record.name, keyWord: 'name', ...record }"
>
</table-edit>
</a-table>