a-table 表格中的编辑修改交互

需求需求

  1. 右侧的编辑/新增/删除+input框/√/×的交互都在子组件中,真正的接口调用在父组件中

    因为是列表,涉及子组件的复用,所以交互在子组件中进行

  2. 点击右侧新增时,列表没有展开时,需要展开并新增节点,展开时则新增节点

    采用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)
      }
    },
    
  3. 列表展示,当有子选项时需要有可以点击展示子节点的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>
      }
    }
    
  4. 可以对表格内容进行修改,有一个临时状态

    对数据进行递归处理,给每个节点增加一个是否是编辑状态的属性isEdit(默认true)

  5. 可以保存,取消修改内容

    对数据进行递归处理,给每个节点增加一个属性copyWord,并将需要可编辑字段的值赋值给copyWord
    input绑定的是copyWord的值,点击√才会将copyWord的值赋值给可编辑字段,×时不进行任何操作

  6. 删除节点时不能刷新列表,以免影响其他可编辑的节点

    对数据进行递归处理,给每个节点增加一个该行是否显示的属性isShow(默认true)
    结合a-table的API(:rowClassName=“rowClassName”)+ css的display:none/ block进行判断是否隐藏

    // 判断加载哪个className
    rowClassName(record, index)  {
      return record.isShow ? 'tableShow' : 'tableHiddle'
    }
    
  7. 每个节点只能同时新增一个子节点,且子节点没有保存时不能新增子子节点

    对数据进行递归处理,给每个节点增加一个该行是否可以新增其他选项isOtherAdd(默认true)

  8. 当新增的节点,没有保存或者删除时,需要将父元素置灰的选项再次高亮

    注意:这里列表重新赋值时,父组件的值已修改,但子组件的值未修改的状态,采用强制更新视图方法无效
    6
    最后采用去子组件中修改值的方法实现

    // 父组件
    this.$refs[`tableItem${parentId}`].load()
    // 子组件
    load() {
      this.record.isOtherAdd = true
    }
    
  9. 对输入的内容进行正则校验

    子组件中判断是否要显示err

    this.isShowErr = !new RegExp(/^[a-zA-Z0-9_\u4e00-\u9fa5]{0,32}$/).test(this.record.copyWord)
    
  10. 点击后滚动到列表的最后位置

    采用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>