Ant Design Vue 表格行内编辑!

效果图

无图言叼,直接上图:
在这里插入图片描述
在这里插入图片描述

应用场景

对于修改表格数据,比较主流的是通过模态框内嵌表单来修改。这样上手比较简单,验证功能很强大。如果通过表格行内编辑的话,上手稍微复杂一点,直接在列里面修改,会非常直观顺畅,但是有一定的缺陷,无法内嵌表单,验证起来比较不友好。

.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))

猜你喜欢

转载自blog.csdn.net/dizuncainiao/article/details/103599434