likes
comments
collection
share

基于vue3 实现可编辑单元格,自定义数据化结构(新增、删除、取消)

作者站长头像
站长
· 阅读数 46

基于vue3 实现可编辑单元格,自定义数据化结构(新增、删除、取消)

需求背景

因公司的发展需要,需要自已研发一套erp系统,供公司内部人员使用,在其中涉及到大量的可编辑表格,而Element的表格相对来说比较简单,有些复杂的功能不满足,因此自己基于Element-plus进行了二次封装,在这基础上添加所需要的功能,达到公司的需求。

在线demo展示 感兴趣的话可以给个star--》源码地址

使用技术

  • 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
评论
请登录