基于vue3 实现可编辑单元格,自定义数据化结构(新增、删除、取消)
需求背景
因公司的发展需要,需要自已研发一套erp系统,供公司内部人员使用,在其中涉及到大量的可编辑表格,而Element的表格相对来说比较简单,有些复杂的功能不满足,因此自己基于Element-plus进行了二次封装,在这基础上添加所需要的功能,达到公司的需求。
使用技术
- element-plus 的 table
- vue3
使用方法
<edit-table
:mode="radio" // 代表方向 top bottom hide
:columns="column" // 传入的表头
:data="list" // 数据
@add="add" // 新增加一行会触发
ref="table" //重置方法 table.value.reset()
:editableKeys="editableKeys" // 控制是否可编辑
@onChange="onChange" // 数据改变的时候
@del="deleteAction" // 删除触发
/>
主要内容
- 可编辑单元格、支持输入框input、支持时间、支持下拉选择框
- 数据化结构,可以满足自定义下拉框,自定义数据结构格式
- 自定义显示单元格内容
- 保存、删除、编辑、取消
- 新增加一行 、提交数据
思路
- 用columns来代替 el-table-column 进行循环
- 给每个item 添加edit 代表是否需要编辑
- editableKeys 初始化传入keys 默认需要编辑的行
- 点击保存、取消来控制edit的展示
完整代码实现
<template>
<div class="m-edit-table">
<div style="margin-top: 15px;margin-bottom: 15px" v-if="mode!=='hide'&&mode!=='bottom'">
<el-button style="width: 100%" @click="add">
<el-icon style="margin-right: 4px"><plus /></el-icon> 添加一行数据</el-button>
</div>
<el-table :data="transData" style="width: 100%" row-key="id" border>
<template v-for="item in columns" >
<el-table-column v-if="item.type" :type="item.type" :width="item.width" :align="item.align" :fixed="item.fixed" :label="item.label"/>
<el-table-column
v-else
:prop="item.name" :width="item.width" :align="item.align" :fixed="item.fixed" :label="item.label">
<template #default="scope">
<template v-if="!item.slot">
<template v-if="item.readonly">
{{ scope.row[item.name] }}
</template>
<template v-else-if="item.valueType==='select'">
<el-select
clearable
:placeholder="`请选择`" v-model="scope.row[item.name]" v-if="scope.row.edit">
<el-option
v-for="ite in item.options"
:key="ite.value"
:label="ite.label"
:value="ite.value"
/>
</el-select>
<span v-else>{{filterOption(item,scope)}}</span>
</template>
<template v-else-if="item.valueType==='date'">
<el-date-picker
v-model="scope.row[item.name]"
type="date"
clearable
placeholder="请选择"
v-if="scope.row.edit"
/>
<span v-else>{{scope.row[item.name]||'--'}}</span>
</template>
<template v-else>
<el-input clearable
placeholder="请输入" v-model="scope.row[item.name]" v-if="scope.row.edit"></el-input>
<span v-else>{{scope.row[item.name]||'--'}}</span>
</template>
</template>
<slot v-else :name="item.name" :item="item" :row="scope.row"></slot>
</template>
</el-table-column>
</template>
<el-table-column prop="operator" label="操作" width="250px" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.edit"
type="success"
size="small"
icon="CircleCheckFilled"
@click="confirmEdit(scope.row)"
>
保存
</el-button>
<el-button
v-else
type="primary"
size="small"
icon="Edit"
@click="scope.row.edit=!scope.row.edit"
>
编辑
</el-button>
<el-popover
trigger="click"
v-model:visible="scope.row.visible" placement="top" :width="160">
<p style="display: flex;align-items: center;margin-bottom: 10px">
<el-icon color="#faad14" style="margin-right: 10px"><warning-filled /></el-icon> 删除此行?</p>
<div style="text-align: right; margin: 0">
<el-button size="small" @click="scope.row.visible = false"
>取消</el-button
>
<el-button size="small" type="primary" @click="deleteAction(scope.row)"
>确定</el-button
>
</div>
<template #reference>
<el-button
icon="Delete"
@click="scope.row.visible = true" type="danger" size="small">删除</el-button>
</template>
</el-popover>
<el-button
v-if="scope.row.edit"
type="primary"
size="small"
icon="Edit"
@click="cancelEdit(scope.row)"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 15px" v-if="mode!=='hide'&&mode!=='top'">
<el-button style="width: 100%" @click="add">
<el-icon style="margin-right: 4px"><plus /></el-icon> 添加一行数据</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import {computed, onMounted, ref, watch} from "vue";
import {deepObjClone} from '@/utils/index'
import { ElMessage,ElMessageBox } from 'element-plus'
import { reactive } from 'vue'
const emit = defineEmits(['del','add','onChange'])
let transData = ref([])
let props = defineProps({
columns:{
type:Array,
default:()=>[]
},
data:{
type:Array,
default:()=>[]
},
editableKeys:{
type:Array,
default:()=>[]
},
mode:{
type:String,
default:'bottom'
}
})
const getData = ()=>{
let arr = deepObjClone(transData.value)
for(let item of arr){
for(let attr in item){
if(attr.includes('te__mp')){
delete item[attr]
}
}
}
emit('onChange',arr)
}
let obj={}
for(let item of props.columns){
props.data.forEach(it=>{
if(!obj[item.name]){
obj[item.name] = null
}
})
}
const reset = ()=>{
transData.value = props.data
getData()
}
onMounted(()=>{
watch(()=>props.data,(val)=>{
// // 转换数据
transData.value = deepObjClone(val)
// 存储一个临时变量
for(let item of transData.value){
if(props.editableKeys.includes(item.id)){
item.edit = true
}
for(let attr in item){
let temp = `${attr}te__mp`
item[temp] = item[attr]
}
}
// console.log('transData',transData)
},{
immediate:true,
deep:true
})
})
const visible = ref(false)
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
const listLoading = ref(false)
// 保存
const confirmEdit = (row)=>{
row.edit = false
for(let attr in row){
if(attr.includes('te__mp')){
row[(attr)] = row[(attr.replace('te__mp',''))]
}
}
getData()
}
// 取消
const cancelEdit = (row)=>{
row.edit=!row.edit
for(let attr in row){
if(!attr.includes('te__mp')){
row[attr] = row[(attr+'te__mp')]
}
}
}
const formInline = reactive({
user: '',
region: '',
})
const onSubmit = () => {
console.log('submit!')
}
const deleteAction = (row)=>{
row.visible = false
transData.value = transData.value.filter((item)=>item.id!==row.id)
emit('del',row)
}
const add = ()=>{
let id = ~~(Math.random() * 1000000).toFixed(0)
let obj1 = Object.assign({},obj,{
id:id,
edit:true,
visible:false,
})
for(let attr in obj1){
let temp = `${attr}te__mp`
obj1[temp] = obj1[attr]
}
if(props.mode==='bottom'){
transData.value.push(obj1)
}
if(props.mode==='top'){
transData.value.unshift(obj1)
}
}
const filterOption = (item,scope)=>{
let obj = item.options.find(ite=>ite.value===scope.row[item.name])
if(obj){
return obj.label
}
return '--'
}
defineExpose({
reset
})
</script>
<style scoped>
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
.inline-edit-table{
width: 100%;
}
</style>
转载自:https://juejin.cn/post/7091911093546663949