vue create kalacloud-vue3-element-plus-table // OR npx vue create kalacloud-vue3-element-plus-table然后安装 UI 框架 Element Plus:
npm install element-plus --save // OR yarn add element-plus安装完成后,在项目里导入 ElementPlus,修改 main.js 如下:
import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')导入后,就可以启动项目了,执行以下命令:
npm run serve
tableHeader: { name: "姓名", birth: "生日", address: "地址", age: "年龄", phone: "电话", }这个对象中的 key 对应表格数据中的 prop,value 对应实际显示的 label,这样通过一个简单的对象,就可以连接表头和 表格body 之间的关系。然后还需要后端返回具体的表格数据:
tableData: [{ name: '张三', date: '2016-05-02', address: '上海市普陀区金沙江路 1518 弄', age: 18, phone:"12345678910", }, { date: '2016-05-04', name: '李四', address: '上海市普陀区金沙江路 1517 弄', age: 19, phone:"12345678911", }, { date: '2016-05-01', name: '王五', address: '上海市普陀区金沙江路 1519 弄', age: 20, phone:"12345678912", }, { date: '2016-05-03', name: '赵六', address: '上海市普陀区金沙江路 1516 弄', age: 21, phone:"12345678913", }]实现表格列动态渲染的功能,需要用到一个很关键的 vue 指令,那就是 v-for,v-for 不仅可以遍历数据,也可以遍历对象:
<div v-for="(value, key) in object"> {{ key }}: {{ value }} </div>这里我们就需要用到这个特性,来对 tableHeader 进行遍历,获取 key 和 value。基于以上讲解,现在我们具体实践一下如何实现表格列的动态渲染。在 components 目录中新建 DynamicTable.vue:
<template> <div> <h2>Vue3 + Element plus 动态表格</h2> <el-table :data="tableData" style="width: 100%"> <el-table-column :prop="index" :label="item" v-for="(item, index) in tableHeader" :key="index" > </el-table-column> </el-table> </div> </template> <script> export default { name: "test", data() { return { tableHeader: { name: "姓名", birth: "生日", address: "地址", age: "年龄", phone: "电话", }, tableData: [{ name: '张三', address: '上海市普陀区金沙江路 1518 弄', birth: '2016-05-02', age: 18, phone: "12345678910", }, { name: '李四', birth: '2016-05-04', address: '上海市普陀区金沙江路 1517 弄', age: 19, phone: "12345678911", }, { name: '王五', birth: '2016-05-01', address: '上海市普陀区金沙江路 1519 弄', age: 20, phone: "12345678912", }, { name: '赵六', birth: '2016-05-03', address: '上海市普陀区金沙江路 1516 弄', age: 21, phone: "12345678913", }] } }, } </script>组件编写完成后,修改 App.vue,导入该组件即可:
<template> <DynamicTable /> </template> <script> import DynamicTable from './components/DynamicTable.vue' export default { name: 'App', components: { DynamicTable } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>保存后页面会自动热更新,实现效果如下:
tableHeader: { name: { label: "姓名", sort: true, }, birth: { label: "生日", sort: false, }, address: { label: "地址", sort: false, }, age: { label: "年龄", sort: true, }, phone: { label: "电话", sort: false, } },然后再改造 el-table 如下:
<el-table-column :prop="index" :label="item.label" v-for="(item, index) in tableHeader" :sortable="item.sort" :key="index" >
# NPM npm install @element-plus/icons-vue # Yarn yarn add @element-plus/icons-vue然后在 main.js 中导入:
import * as ElementPlusIconsVue from '@element-plus/icons-vue' const app = createApp(App) for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) }在 components 目录下新建 DynamicModifyTable.vue:
<template> <div> <h2>Vue3 + Element plus 动态修改表格</h2> <h3>「duidaima.com」</h3> <el-table :data="tableData" style="width: 100%"> <el-table-column :prop="item.prop" :label="item.label" v-for="(item, index) in tableHeader" :key="item.prop" > <template #default="scope"> <div v-show="item.editable || scope.row.editable" class="editable-row" > <template v-if="item.type === 'input'"> <el-input size="small" v-model="scope.row[item.prop]" :placeholder="`请输入${item.label}`" @change="handleEdit(scope.$index, scope.row)" /> </template> <template v-if="item.type === 'date'"> <el-date-picker v-model="scope.row[item.prop]" type="date" value-format="YYYY-MM-DD" :placeholder="`请输入${item.label}`" @change="handleEdit(scope.$index, scope.row)" /> </template> </div> <div v-show="!item.editable && !scope.row.editable" class="editable-row" > <span class="editable-row-span">{{ scope.row[item.prop] }}</span> <el-popover placement="right" :width="120" trigger="hover" content="this is content, this is content, this is content" > <template #reference> <el-icon class="icon" :size="18"> <Edit /> </el-icon> </template> <div class="menu-list"> <div class="menu-item" @click="prepend(scope.$index)" > 上方插入一行 </div> <div class="menu-item divider" @click="append(scope.$index)" > 下方插入一行 </div> <div class="menu-item" @click="deleteCurrentColumn(index)"> 删除当前列 </div> <div class="menu-item" @click="insertBefore(index)"> 前方插入一列 </div> <div class="menu-item" @click="insertAfter(index)"> 后方插入一列 </div> </div> </el-popover> </div> </template> </el-table-column> <el-table-column label="操作"> <template #default="scope"> <el-button v-show="!scope.row.editable" size="small" @click="scope.row.editable = true" >编辑</el-button > <el-button v-show="scope.row.editable" size="small" type="success" @click="scope.row.editable = false" >确定</el-button > <el-button size="small" type="danger" @click="handleDelete(scope.$index)" >删除</el-button > </template> </el-table-column> </el-table> </div> </template> <script> const item = { name: '', birth: '', province: "", city: "", address: '', phone: "", } const header = { prop: "key", label: "自定义", editable: false, type: "input", } export default { name: "DynamicModifyTable", data() { return { tableHeader: [ { prop: "name", label: "姓名", editable: false, type: "input", }, { prop: "birth", label: "生日", editable: false, type: "date" }, { prop: "phone", label: "电话", editable: false, type: "input" }, { prop: "province", label: "省份", editable: false, type: "input" }, { prop: "city", label: "市区", editable: false, type: "input" }, { prop: "address", label: "详细地址", editable: false, type: "input" } ], tableData: [{ name: '张三', province: "上海市", city: "普陀区", address: "金沙江路 1518 弄", birth: '2016-05-02', phone: "12345678910", }, { name: '李四', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1517 弄', age: 19, phone: "12345678911", }, { name: '王五', birth: '2016-05-01', province: "上海市", city: "普陀区", address: '金沙江路 1519 弄', phone: "12345678912", }, { name: '赵六', birth: '2016-05-03', province: "上海市", city: "普陀区", address: '金沙江路 1516 弄', phone: "12345678913", }] } }, methods: { handleEdit(row) { row.editable = true; }, handleDelete(index) { this.tableData.splice(index, 1); }, prepend(index) { item.editable = true this.tableData.splice(index, 0, item); }, append(index) { item.editable = true this.tableData.splice(index + 1, 0, item); }, deleteCurrentColumn(index) { this.tableHeader.splice(index, 1); }, insertBefore(index) { header.editable = true; this.tableHeader.splice(index, 0, header);Vue3 + Element Plus 创建动态多级表头
<template> <div> <h2>Vue3 + Element plus 动态表格</h2> <el-table :data="tableData" style="width: 100%"> <el-table-column :prop="item.prop" :label="item.label" v-for="(item, index) in tableHeader" :key="index" > <el-table-column :prop="child.prop" :label="child.label" v-for="(child, childIndex) in item.children" :key="childIndex" ></el-table-column> </el-table-column> </el-table> </div> </template> <script> export default { name: "MultiHeaderTable", data() { return { tableHeader: [ { prop: "name", label: "姓名", }, { prop: "birth", label: "生日", }, { prop: "phone", label: "电话", }, { label: "地址", children: [ { prop: "province", label: "省份", }, { prop: "city", label: "市区", }, { prop: "address", label: "详细地址", } ] } ], tableData: [{ name: '张三', province: "上海市", city: "普陀区", address: "金沙江路 1518 弄", birth: '2016-05-02', phone: "12345678910", }, { name: '李四', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1517 弄', age: 19, phone: "12345678911", }, { name: '王五', birth: '2016-05-01', province: "上海市", city: "普陀区", address: '金沙江路 1519 弄', phone: "12345678912", }, { name: '赵六', birth: '2016-05-03', province: "上海市", city: "普陀区", address: '金沙江路 1516 弄', phone: "12345678913", }] } }, } </script>对于 tableHeader 的定义,我们通过 children 字段来指定当前列的二级表头,但是会发现效果并不是我们预期的那样:
<template v-if="item.children"> <el-table-column :prop="child.prop" :label="child.label" v-for="(child, childIndex) in item.children" :key="childIndex" ></el-table-column> </template>如果不这么做,那就只能渲染有二级表头的列,无法正常显示一级表头的数据, 最后的效果如下:
Vue3 + Element Plus 表格中单元格行合并
我们先来看下如何实现行合并,行合并或者是列合并,都需要用到 el-table 中 span-method 这个方法,在官方的例子中,是通过固定返回 rowspan,colspan 来实现行合并的:const objectSpanMethod = ({ row, column, rowIndex, // 需要合并的开始行 columnIndex, // 需要合并的列 }) => { if (columnIndex === 0) { if (rowIndex % 2 === 0) { return { rowspan: 2, //合并的行数 colspan: 1, //合并的列数 } } else { return { rowspan: 0, colspan: 0, } } } }但是在动态数据的场景下,这种方法就不适用了,因为前端的表格数据往往是后端通过接口返回的。比如我们有以下数据结构:
tableData: [{ name: '张三', province: "上海市", city: "普陀区", address: "金沙江路 1518 弄", birth: '2016-05-02', phone: "12345678910", }, { name: '李四', birth: '2016-05-02', province: "上海市", city: "普陀区", address: '金沙江路 1517 弄', age: 19, phone: "12345678911", }, { name: '王五', birth: '2016-05-03', province: "上海市", city: "普陀区", address: '金沙江路 1519 弄', phone: "12345678912", }, { name: '赵六', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1516 弄', phone: "12345678913", }, { name: '孙七', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1516 弄', phone: "12345678913", }, { name: '周八', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1516 弄', phone: "12345678913", }, { name: '吴九', birth: '2016-05-06', province: "上海市", city: "普陀区", address: '金沙江路 1516 弄', phone: "12345678913", }]我们的需求是把相同 birth 进行合并。在 components 目录下新建 RowMergeTable.vue 文件:
<template> <div> <h2>Vue3 + Element plus 动态行合并表格</h2> <el-table :data="tableData" style="width: 100%" :span-method="objectSpanMethod" border > <el-table-column :prop="item.prop" :label="item.label" v-for="(item, index) in tableHeader" :key="index" ></el-table-column> </el-table> </div> </template> <script> export default { name: "RowMergeTable", data() { this.spanArr = []; return { tableHeader: [ { prop: "birth", label: "生日", }, { prop: "name", label: "姓名", }, { prop: "phone", label: "电话", }, { prop: "province", label: "省份", }, { prop: "city", label: "市区", }, { prop: "address", label: "详细地址", } ], tableData: [{ name: '张三', province: "上海市", city: "普陀区", address: "金沙江路 1518 弄", birth: '2016-05-02', phone: "12345678910", }, { name: '李四', birth: '2016-05-02', province: "上海市", city: "普陀区", address: '金沙江路 1517 弄', age: 19, phone: "12345678911", }, { name: '王五', birth: '2016-05-03', province: "上海市", city: "普陀区", address: '金沙江路 1519 弄', phone: "12345678912", }, { name: '赵六', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1520 弄', phone: "12345678913", }, { name: '孙七', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1521 弄', phone: "12345678913", }, { name: '周八', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1522 弄', phone: "12345678913", }, { name: '吴九', birth: '2016-05-06', province: "上海市", city: "普陀区", address: '金沙江路 1523 弄', phone: "12345678913", }] } }, created() { this.getSpanArr(this.tableData); }, methods: { getSpanArr(data) { for (var i = 0; i < data.length; i++) { if (i === 0) { this.spanArr.push(1); this.pos = 0 } else { if (data[i].birth === data[i - 1].birth) { this.spanArr[this.pos] += 1; this.spanArr.push(0); } else { this.spanArr.push(1); this.pos = i; } } } }, objectSpanMethod({ rowIndex, columnIndex }) { if (columnIndex === 0) { const _row = this.spanArr[rowIndex]; const _col = _row > 0 ? 1 : 0; return { rowspan: _row, colspan: _col } } } } } </script>上面的例子就实现了行合并的功能:
<template> <div> <h2>Vue3 + Element plus 动态多行合并表格</h2> <el-table :data="tableData" style="width: 100%" :span-method="objectSpanMethod" border > <el-table-column :prop="item.prop" :label="item.label" v-for="(item, index) in tableHeader" :key="index" ></el-table-column> </el-table> </div> </template> <script> export default { name: "MultiRowMergeTable", data() { this.spanMap = {}; this.mergedColumns = ["birth", "province", "city"] return { tableHeader: [ { prop: "birth", label: "生日", }, { prop: "name", label: "姓名", }, { prop: "phone", label: "电话", }, { prop: "province", label: "省份", }, { prop: "city", label: "市区", }, { prop: "address", label: "详细地址", } ], tableData: [{ name: '张三', province: "上海市", city: "普陀区", address: "金沙江路 1518 弄", birth: '2016-05-02', phone: "12345678910", }, { name: '李四', birth: '2016-05-02', province: "上海市", city: "普陀区", address: '金沙江路 1517 弄', age: 19, phone: "12345678911", }, { name: '王五', birth: '2016-05-03', province: "上海市", city: "普陀区", address: '金沙江路 1519 弄', phone: "12345678912", }, { name: '赵六', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1520 弄', phone: "12345678913", }, { name: '孙七', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1521 弄', phone: "12345678913", }, { name: '周八', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1522 弄', phone: "12345678913", }, { name: '吴九', birth: '2016-05-06', province: "上海市", city: "普陀区", address: '金沙江路 1523 弄', phone: "12345678913", }] } }, created() { this.getSpanArr(this.tableData); }, methods: { getSpanArr(data) { for (var i = 0; i < data.length; i++) { if (i === 0) { this.mergedColumns.forEach(column => { this.spanMap[column] = { spanArr: [1], pos: 0 } }) } else { this.mergedColumns.forEach(column => { if (data[i][column] === data[i - 1][column]) { this.spanMap[column].spanArr[this.spanMap[column].pos] += 1; this.spanMap[column].spanArr.push(0) } else { this.spanMap[column].spanArr.push(1); this.spanMap[column].pos = i; } }) } } }, objectSpanMethod({ column, rowIndex }) { if (this.spanMap[column.property]) { const _row = this.spanMap[column.property].spanArr[rowIndex]; const _col = _row > 0 ? 1 : 0; return { rowspan: _row, colspan: _col } } } } } </script>实现效果如下:
<template> <div> <h2>Vue3 + Element plus 动态表格列合并</h2> <el-table :data="tableData" style="width: 100%" :span-method="objectSpanMethod" border > <el-table-column :prop="item.prop" :label="item.label" v-for="(item, index) in tableHeader" :key="index" ></el-table-column> </el-table> </div> </template> <script> export default { name: "ColumnMergeTable", data() { return { tableHeader: [ { prop: "birth", label: "生日", }, { prop: "name", label: "姓名", }, { prop: "phone", label: "电话", }, { prop: "province", label: "省份", }, { prop: "city", label: "市区", }, { prop: "address", label: "详细地址", } ], tableData: [{ name: '张三', province: "上海市", city: "普陀区", address: "金沙江路 1518 弄", birth: '2016-05-02', phone: "12345678910", }, { name: '李四', birth: '2016-05-02', province: "上海市", city: "普陀区", address: '金沙江路 1517 弄', age: 19, phone: "12345678911", }, { name: '王五', birth: '2016-05-03', province: "上海市", city: "普陀区", address: '金沙江路 1519 弄', phone: "12345678912", }, { name: '赵六', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1520 弄', phone: "12345678913", }, { name: '孙七', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1521 弄', phone: "12345678913", }, { name: '周八', birth: '2016-05-04', province: "上海市", city: "普陀区", address: '金沙江路 1522 弄', phone: "12345678913", }, { name: '吴九', birth: '2016-05-06', province: "上海市", city: "普陀区", address: '金沙江路 1523 弄', phone: "12345678913", }] } }, methods: { objectSpanMethod({ rowIndex, columnIndex }) { // 隐藏第二行或者第三行的列 if (rowIndex === 1 || rowIndex === 2) { // 合并第二行 if (columnIndex === 1) { // 从第二列开始 return [1, 3] //或者返回如下形式也可以 // return { // rowspan: 1, // colspan: 3 // } // 这里的 else if 即使用来处理被合并列的原始数据的情况,需要隐藏原始单元格 } else if (columnIndex === 2 || columnIndex === 3) { return [0, 0] } } } } } </script>可以看出来,列合并其实比行合并简单一些,返回一个数组更容易理解一些,数组的第一项表示合并的起始列,第二项表示合并的终止列,其区间的所有列都会合并成一列,被合并的列还需要通过 [0, 0] 来隐藏对应的单元格,这个是和行合并不同的地方。