el-table 树级组件封装(一)

文章展示了一个使用Vue.js和ElementUI的el-table组件来展示带有层级关系的数据。表格支持添加、删除、编辑功能,用户可以对元数据的属性名和属性值进行修改。同时,提供了获取行键值的方法和自定义的行样式处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<template>
  <div>
    <el-table v-loading="loading" element-loading-background="rgba(255, 255, 255, 0.7)" :data="foldTableData" border
      style="width: 100%" :row-class-name="tableRowClassName" :row-key="getRowKeys" :span-method="arraySpanMethod"
      :expand-row-keys="expands" :tree-props="{ children: 'children' }">
      <el-table-column align="center" type="text" label-class-name="DisabledSelection" width="50" v-if="editState">
        <template slot-scope="scope" v-if="scope.row.addRow">
          <span @click="addRow(scope.row)" v-if="scope.row.parentType" class="hand el-icon-circle-plus"></span>
        </template>
      </el-table-column>
      <!-- <el-table-column align="center" prop="index" label="序号" width="50"> </el-table-column> -->
      <el-table-column align="left" prop="label" label="元数据类别" width="230"> </el-table-column>
      <el-table-column align="center" prop="keys" label="属性名" width="300">
        <template slot-scope="scope">
          <div v-if="!scope.row.parentType">
            <el-input v-if="scope.row.ModifyKeys" v-model="scope.row.keys" type="text" />
            <span v-else>{{ scope.row.keys }}</span>
          </div>
        </template>
      </el-table-column>
      <el-table-column align="center" prop="value" label="属性值">
        <template slot-scope="scope" v-if="!scope.row.parentType">
          <el-input v-model="scope.row.value" type="textarea" />
          <!-- <span v-else>{{ scope.row.value }}</span> -->
        </template>
      </el-table-column>
      <el-table-column align="center" label="操作" v-if="editState">
        <template slot-scope="scope" v-if="!scope.row.parentType">
          <!-- <el-button v-if="scope.row.modify" type="text" @click="handleSave(scope.row)">保存</el-button>
          <el-button v-else type="text" @click="handleEdit(scope.row, true)">编辑</el-button> -->
          <el-button v-if="scope.row.ModifyKeys" @click="handleDelete(scope.row)" type="text">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import { v4 as uuidv4 } from 'uuid';
export default {
  dicts: ['metadate_type'],
  data() {
    return {
      loading: false,
      num: 10000,
      expands: [],
      foldTableData: [
        {
          children: [
            {
              ModifyKeys: false,
              id:9,
              keys:"子集",
              value:"值"
            },
            {
              ModifyKeys: false,
              id: 19,
              keys: "子集",
              value: "值"
            }
          ],
          parentType: true,
          id: 1,
          label:'父级'
        },
         {
          children: [
            {
              ModifyKeys: false,
              id: 39,
              keys: "子集",
              value: "值"
            },
            {
              ModifyKeys: false,
              id: 49,
              keys: "子集",
              value: "值"
            }
          ],
          parentType: true,
          id: 2,
          label: '父级'
        }
      ]
    };
  },
  mounted() {
    this.expands = [this.foldTableData[0]?.id.toString()]
  },
  methods: {
    // 获取row的key值
    getRowKeys(row) {
      return '' + row.id + row.label + row.keys;
    },
    // 合并
    arraySpanMethod({ row, column, rowIndex, columnIndex }) {
      // 合并岗位
      if (row.parentType) {
        if (columnIndex === 1) {
          return {
            rowspan: 1,
            colspan: 6
          }
        }
      }
    },

    // 新增
    addRow(row) {
      this.expands = [row.id.toString()]
      let obj = {
        keys: '',
        value: '',
        id: uuidv4(),
        ModifyKeys: true,
      }
      this.foldTableData.forEach(o => {
        if (o.id === 9999999999999) {
          o.children.push(obj)
        }
      })
    },

    // 删除
    handleDelete(row) {
      let curObj = this.foldTableData.find(o => o.id === 9999999999999)
      let index = curObj?.children.findIndex(i => i.id === row.id)
      curObj?.children.splice(index, 1)

    },
    // 设置失效行的class
    tableRowClassName({ row, rowIndex }) {
      if (!row.type && row.takeEffect === false) {
        return 'warning-row';
      }
      return '';
    },
  }
};
</script>

<style lang='scss' scoped>
.hand {
  cursor: pointer;
  font-size: 18px;
}
</style>

 

<think>我们参考了引用[3]中对el-table进行二次封装的经验,以及引用[4]中关于el-table-v2的多选框封装Hook的思路。我们的目标是将el-table-v2封装成与el-table相同的使用方式,包括支持类似的列定义方式(包括插槽)和分页等。 分析: 1. el-table使用template来定义列,而el-table-v2使用一个列数组(columns)来定义列,并且插槽的使用方式也不同。 2. 我们需要设计一个组件,它能够接受类似el-table的列定义(通过template)并转换为el-table-v2所需的columns数组。 3. 同时,我们还需要处理分页(参考引用[4]中的跨页多选思路)以及其他el-table的常用特性。 设计思路: 1. 使用渲染函数(render function)或作用域插槽(scoped slot)来模拟el-table的列插槽。 2. 创建一个包装组件,该组件接受类似el-table的列定义(通过插槽),然后在内部将这些列定义转换为el-table-v2的columns配置。 3. 对于分页,我们可以封装一个分页组件,并处理跨页多选(如果需要)的逻辑。 步骤: 1. 定义组件的props,包括数据源(dataSource)、列定义(columns,可选,如果使用插槽则不需要直接传入列配置)、分页配置等。 2. 使用作用域插槽来获取列定义(类似于el-table的写法),但这里我们也可以考虑使用函数式组件或渲染函数来动态生成列。 3.组件内部,将用户通过插槽定义的列转换为el-table-v2所需的列配置数组。 4. 处理分页逻辑,包括分页切换、跨页选择等(参考引用[4]中的设计)。 具体实现: 由于el-table-v2要求使用一个数组来定义列,我们可以这样设计: -封装组件中,我们使用一个默认插槽,允许用户像使用el-table样定义列,例如: <template> <my-table-v2 :data="data"> <el-table-column prop="name" label="姓名" /> <el-table-column label="操作"> <template #default="scope"> <button @click="handleClick(scope.row)">操作</button> </template> </el-table-column> </my-table-v2> </template> - 在my-table-v2组件内部,我们需要遍历插槽中的el-table-column组件实例,提取出prop、label、width等信息,并且对于有插槽的列,我们需要自定义cellRenderer。 但是,在Vue3中,我们可以使用`useSlots`来获取插槽,然后动态生成列配置。 然而,el-table-v2的列配置中,自定义单元格渲染器(cellRenderer)是一个函数,我们可以在这个函数中渲染用户定义的插槽内容。 实现难点:如何将用户定义的插槽内容转换为cellRenderer函数? 解决方案: - 我们可以创建一个Vue组件来包装用户定义的插槽,然后在cellRenderer函数中使用`h`函数(createVNode)来渲染这个组件,并传递当前行数据等。 步骤: 1.封装组件中,我们获取默认插槽(即列定义)。 2. 遍历每个列,提取属性(prop, label, width等)。 3. 对于有默认插槽的列,我们创建一个自定义的cellRenderer函数,该函数会使用一个Vue组件(比如叫做SlotRenderer)来渲染插槽内容,并将当前行的数据作为作用域插槽的参数传递进去。 示例代码(使用Vue3的Composition API): - 定义SlotRenderer组件(用于渲染插槽): const SlotRenderer = defineComponent({ props: ['row', 'column'], setup(props) { // 这里我们不需要具体的逻辑,只需要在渲染函数中返回插槽内容 }, render() { // 通过this.$slots.default 来渲染插槽,并将row作为参数传递 return this.$slots.default?.(this.row); } }); -封装的表格组件中,构建列配置: import { h, useSlots } from 'vue'; setup(props, { slots }) { const slotsData = useSlots(); const columns = []; // 遍历默认插槽中的节点(假设都是el-table-column组件) slotsData.default?.().forEach(vnode => { // 获取vnode的props(即el-table-column的prop, label, width等) const columnProps = vnode.props; const column = { key: columnProps.prop || columnProps.label, // 需要唯标识 dataKey: columnProps.prop, title: columnProps.label, width: columnProps.width || 100, }; // 检查这个列是否有默认插槽(即自定义内容) const slotName = columnProps.prop ? `default` : undefined; // 注意:el-table-column的插槽名为default,但如果没有prop,我们可能需要特殊处理 if (vnode.children && slotName) { // 获取这个列的作用域插槽函数 const slotFn = vnode.children[slotName]; if (slotFn) { column.cellRenderer = ({ rowData }) => { // 使用SlotRenderer组件,并将插槽函数作为其子节点 return h(SlotRenderer, { row: rowData }, { default: () => slotFn({ row: rowData }) // 这里slotFn是一个函数,返回VNode数组 }); }; } } columns.push(column); }); return { columns }; } 4. 在模板中,使用el-table-v2并传入转换后的columns和data。 注意:上述转换过程需要处理更复杂的情况,例如列可能有多个插槽(比如header插槽),但我们的目标是模拟el-table,所以主要关注default插槽。 5. 分页处理: 我们可以将分页组件独立出来,并处理跨页多选。参考引用[4]中的Hook,我们可以封装一个useSelection钩子来处理跨页选择。 6. 最终,我们封装组件应该提供类似于el-table的API,包括事件(如row-click)和属性(如height, max-height等)。 考虑到复杂度,我们可以分步骤实现,先实现基本表格展示和自定义内容,再逐步添加分页、多选等功能。 示例组件结构: <template> <el-table-v2 :columns="innerColumns" :data="innerData" v-bind="otherProps" /> <!-- 分页组件 --> <el-pagination v-if="showPagination" ... /> </template> 其中,innerColumns是通过上述方法转换得到的列配置,innerData是当前页的数据(如果分页的话)。 关于数据加载: 我们可以设计一个fetchData属性(函数),当需要分页时,由组件内部调用该函数获取数据,并处理分页参数。 总结:封装的关键点在于将模板中的列定义转换为el-table-v2的列配置数组,并支持插槽的自定义渲染。同时,分页和多选等功能可以借鉴已有的封装经验。 相关问题: 1.封装el-table-v2时如何处理复杂的表头(如合并表头)? 2. 如何实现跨页多选功能? 3. 如何优化el-table-v2在大数据量下的性能?</think>### 将 el-table-v2 封装el-table 使用方式 要封装 `el-table-v2` 实现类似 `el-table` 的使用方式,核心在于**设计统的列声明接口**和**插槽兼容机制**。以下是实现方案: #### 1. 核心设计思路 ```tsx // 封装组件 TableWrapper.vue <template> <el-table-v2 :columns="processedColumns" :data="innerData" v-bind="attrs" > <!-- 动态生成列插槽 --> <template v-for="col in columns" #[col.slotName]="{ row }"> <slot :name="col.slotName" :row="row" /> </template> </el-table-v2> </template> <script setup> import { computed, useAttrs } from 'vue' const props = defineProps({ columns: { // 关键:兼容 el-table 的列定义方式 type: Array, default: () => [], validator: cols => cols.every(c => 'prop' in c && 'label' in c) }, dataSource: Array }) const attrs = useAttrs() const innerData = ref([]) // 转换列配置为 el-table-v2 格式 const processedColumns = computed(() => { return props.columns.map(col => ({ key: col.prop, dataKey: col.prop, title: col.label, width: col.width, // 标识插槽名称 slotName: col.slotName || `col-${col.prop}` })) }) </script> ``` #### 2. 插槽兼容方案 **使用方式对比**: ```html <!-- 原生 el-table --> <el-table :data="data"> <el-table-column prop="name" label="姓名"> <template #default="{ row }"> <a @click="handleClick(row)">{{ row.name }}</a> </template> </el-table-column> </el-table> <!-- 封装后使用 --> <TableWrapper :columns="columns" :data-source="data"> <template #col-name="{ row }"> <a @click="handleClick(row)">{{ row.name }}</a> </template> </TableWrapper> ``` #### 3. 分页与多选集成 参考引用[4]的跨页多选方案: ```ts // 分页多选Hook (useSelection.js) export function useSelection(options) { const selected = ref(new Map()) const toggleSelection = (row, page) => { const key = `${page}-${row.id}` if (selected.value.has(key)) { selected.value.delete(key) } else { selected.value.set(key, row) } } const getSelectedRows = () => [...selected.value.values()] return { selected, toggleSelection, getSelectedRows } } ``` #### 4. 完整特性支持 | 功能 | 实现方案 | |-------------------|------------------------------------------------------------------------| | 列定义 | 通过 `columns` prop 接收类 el-table-column 配置[^1] | | 作用域插槽 | 动态生成 `#col-{prop}` 格式的插槽[^2] | | 分页 | 集成 el-pagination 并处理跨页数据[^4] | | 跨页多选 | 使用 Map 存储分页标识的选中状态[^4] | | 属性透传 | 通过 `v-bind="attrs"` 传递原生 el-table-v2 属性 | | 异步数据加载 | 实现 `fetchData` 回调函数支持 | #### 5. 使用示例 ```vue <TableWrapper :columns="[ { prop: 'name', label: '姓名', slotName: 'customName' }, { prop: 'age', label: '年龄' } ]" :data-source="tableData" row-key="id" @selection-change="handleSelect" > <!-- 自定义姓名列 --> <template #customName="{ row }"> <el-tag>{{ row.name }}</el-tag> </template> </TableWrapper> ``` ### 关键技术点 1. **列配置转换器**:将 el-table 风格的列配置转换为 el-table-v2 需要的格式 2. **动态插槽映射**:根据列配置自动生成对应作用域插槽 3. **状态隔离**:使用分页标识符(`${page}-${id}`)实现跨页数据持久化 4. **属性透传**:利用 Vue 的 `useAttrs()` 实现原生属性继承 > 通过这种封装方式,开发者可以保持 el-table 的使用习惯,同时获得 el-table-v2 的虚拟滚动性能优势。实际项目中可结合引用[3]的封装经验,添加统的样式规范和预置功能模块[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值