Vue技巧Element Table二次封装实战示例
凉城a 人气:0前言
由于重构后台管理项目中有好多表格页面, 举个栗子
这表格看着还挺好看,写起来叫人直呼XX,多动脑子少掉发,少走弯路多省鞋。
写了一个后感觉太麻烦了,于是我奋笔疾书,利用Vue+Element Table
重新封装出了一套表格组件
3分钟就可以实现一个表格页面的骚操作,你值得拥有
思考
看了表格咱们简单的把它们划分了下功能区
- 最先入场的选手是最上方一排整整齐齐有点严肃的搜索功能区
- 其次入场的是略带个性的表格功能区
- 然后入场的是全能综合性型选手表格内容区域
- 最后入场的是有着长腿带着长队的分页组件区域
诶,让我们思考下最复杂的地方在哪里?
下面我用个图来标注下咱们接下来需要拿下的高地
总的来说表格内容这一块最不可控,他可能有多选、序号、图片、状态、时间、操作列......
实践
咱们把搜索层写成一个组件filterPane.vue
把表格分成一个组件tablePane.vue
表格组件tablePane.vue
包括功能区、表格内容区、分页
filterPane.vue
明确目标
搜索层一般包括日期选择器、输入框、select下拉选择器等+搜索功能、重置功能
传入数据结构整理
// 搜索栏组件 filterData:{ timeSelect:true, //是否显示日期控件 elinput:[ { name:'姓名', //提示语 key:'userName', //字段名 width:100 //宽度 } ], elselect:[ { name:'部门', key:'department', width:100 option:[{ key:1, value:'技术部' }] } ] }
timeSelect
- 类型
Boolean
是否显示时间选择器
elinput
- 类型
Array
输入框选项,子对象内
name为输入框的placeholder
key为字段名
elselect
- 类型
Array
select下拉框选项,子对象内
name为输入框的placeholder
key为字段名
option为select下拉选项
开始封装
<template> <div> <div class="filter-container"> <el-date-picker v-if="filterData.timeSelect" v-model="dateRange" style="width: 300px" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['', '']" :picker-options="pickerOptions" class="filter-item" /> <template v-if="filterData.elinput"> <el-input v-for="(item,index) in filterData.elinput" :key="index" v-model="listQuery[item.key]" :placeholder="item.name" :style="{'width':item.width?item.width+'px':'200px'}" class="filter-item" /> </template> <template v-if="filterData.elselect"> <el-select v-for="(item,index) in filterData.elselect" :key="index" v-model="listQuery[item.key]" :placeholder="item.name" clearable :style="{'width':item.width?item.width+'px':'90px'}" class="filter-item" > <el-option v-for="i in item.option" :key="i.key" :label="i.value" :value="i.key" /> </el-select> </template> <div class="btn"> <el-button class="filter-item" type="primary" @click="handleSearch"> 搜索 </el-button> <el-button class="filter-item" type="warning" @click="handleRest"> 重置 </el-button> </div> </div> </div> </template> <script> // 搜索栏组件 // filterData:{ // timeSelect:true, // elinput:[ // { // name:'姓名', // key:'userName' // } // ], // elselect:[ // { // name:'部门', // key:'department' // option:[{ // key:1, // value:'技术部' // }] // } // ] // } export default { props: { // eslint-disable-next-line vue/require-default-prop filterData: { type: Object } }, data() { return { pickerOptions: { disabledDate(time) { return time.getTime() > Date.now() } }, dateRange: ['', ''], listQuery: {} } }, watch: { 'filterData'(val) { console.log(val) if (val.elinput.length > 0) { val.elinput.map(item => { this.listQuery[item.key] = '' }) } if (val.elselect.length > 0) { val.elinput.map(item => { this.listQuery[item.key] = '' }) } }, //缓存进页面想清空可用 'filterData.rest': { handler: function(val) { if (val) { this.handleRest() } }, deep: true } }, methods: { handleSearch() { console.log('搜索成功', this.listQuery) const data = this.$global.deepClone(this.listQuery) if (this.dateRange && this.dateRange[0] !== '') { const startTime = this.$moment(this.dateRange[0]).format('YYYY-MM-DD') + ' 00:00:00' const endTime = this.$moment(this.dateRange[1]).format('YYYY-MM-DD') + ' 23:59:59' data.beginDate = startTime data.endDate = endTime } Object.keys(data).forEach(function(key) { if (data[key] === '') { delete data[key] } }) this.$emit('filterMsg', data) }, handleRest() { const data = this.$global.deepClone(this.listQuery) Object.keys(data).forEach(function(key) { data[key] = '' }) this.listQuery = data this.dateRange = ['', ''] console.log('重置成功', this.listQuery) } } } </script> <style scoped lang='scss'> .filter-item{ margin-left: 10px; display: inline-block; } .filter-container .filter-item:nth-of-type(1){ margin-left: 0px; } .btn{ display: inline-block; margin-left: 10px; } </style>
tablePane.vue
明确目标
实现表格功能行、实现表格基本功能、实现分页功能
传入数据结构整理
dataSource: { tool:[ { name: '新增用户', //按钮名称 key: 1, // 唯一标识符 permission: 2010106, // 权限点 type: '', // 使用element自带按钮类型 bgColor: '#67c23a', // 自定义背景色 handleClick: this.handleAdd //自定义事件 }, ] data: [], // 表格数据 cols: [], // 表格的列数据 isSelection: false, // 表格有多选时设置 selectable: function(val) {//禁用部分行多选 if (val.isVideoStatus === 1) { return false } else { return true } }, handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组 isOperation: true, // 表格有操作列时设置 isIndex: true, // 列表序号 loading: true, // loading pageData: { total: 0, // 总条数 pageSize: 10, // 每页数量 pageNum: 1 // 页码 } operation: { // 表格有操作列时设置 label: '操作', // 列名 width: '350', // 根据实际情况给宽度 data: [ { label: '冻结', // 操作名称 permission:'' //权限点 type: 'info', //按钮类型 handleRow: function(){} // 自定义事件 }, ] } },
tool
- 类型
Array
- 默认值
[ ]
配置表格工具列
dataSource: { tool:[ { name: '新增用户', //按钮名称 key: 1, // 唯一标识符 permission: 2010106, // 权限点 type: '', // 使用element自带按钮类型 bgColor: '#67c23a', // 自定义背景色 handleClick: this.handleAdd //自定义事件 }, ] }
cols
- 类型
Array
- 默认值
[ ]
配置表头
dataSource: { cols:[ { label: '标题', //列名 prop: 'belongUserId', //字段名称 width: 100 //列宽度 }, { label: '副标题(季)', prop: 'subtitle', isCodeTableFormatter: function(val) {//过滤器 if (val.subtitle === 0) { return '无' } else { return val.subtitle } }, width: 100 }, { label: '创建时间', prop: 'createTime', isCodeTableFormatter: function(val) {//时间过滤器 return timeFormat(val.createTime) }, width: 150 } ] }
pageData
- 类型
Object
- 默认值
{ }
配置分页
dataSource: { pageData: { total: 0, // 总条数 pageSize: 10, // 每页数量 pageNum: 1, // 页码 pageSize:[5,10,15,20]// 每页数量 } }
operation
- 类型
Object
- 默认值
{ }
配置操作列
dataSource: { operation: { // 表格有操作列时设置 label: '操作', // 列名 width: '350', // 根据实际情况给宽度 data: [ { label: '修改', // 操作名称 permission:'1001' //权限点 type: 'info', //按钮类型icon为图表类型 handleRow: function(){} // 自定义事件 }, { label: '修改', // 操作名称 permission:'1001' //权限点 type: 'icon', //按钮类型icon为图表类型 icon:'el-icon-plus' handleRow: function(){} // 自定义事件 } ] } }
tablePane.vue配置项Cols详解
- 普通列
cols:[ { label: '标题', prop: 'title', width: 200 } ]
- 普通列字体颜色改变
cols:[ { label: '状态', prop: 'status', isTemplate: function(val) { if (val === 1) { return '禁言中' } else { return '已解禁' } }, isTemplateClass: function(val) { if (val === 1) { return 'color-red' } else { return 'color-green' } } } ]
- 带filter过滤器列
cols:[ { label: '推送时间', prop: 'pushTime', isCodeTableFormatter: function(val) { return timeFormat(val.pushTime) } }, { label: '状态', prop: 'status', isCodeTableFormatter: function(val) { if(val.status===1){ return '成功' }else{ return '失败' } } } ]
- 带图标列
cols:[ { label: '目标类型', prop: 'targetType', isIcon: true, filter: function(val) { if (val === 4) { return '特定用户' } else if (val === 3) { return '新注册用户' } else if (val === 2) { return '标签用户' } else if (val === 1) { return '全部用户' } }, icon: function(val) { if (val === 4) { return 'el-icon-mobile' } else { return false } }, handlerClick: this.handlerClick } ]
开始封装
<template> <div> <div v-if="dataSource.tool" class="tool"> <el-button v-for="(item) in dataSource.tool" :key="item.key" v-permission="item.permission" class="filter-item" :style="{'background':item.bgColor,borderColor:item.bgColor}" :type="item.type || 'primary'" @click="item.handleClick(item.name,$event)" > {{ item.name }} </el-button> </div> <el-table ref="table" v-loading="dataSource.loading" style="width: 100%;" :class="{ 'no-data': !dataSource.data || !dataSource.data.length }" :data="dataSource.data" @row-click="getRowData" @selection-change="dataSource.handleSelectionChange" > <!-- 是否有多选 --> <el-table-column v-if="dataSource.isSelection" :selectable="dataSource.selectable" type="selection" :width="dataSource.selectionWidth || 50" align="center" /> <!-- 是否需要序号 --> <el-table-column v-if="dataSource.isIndex" type="index" label="序号" width="55" align="center" /> <template v-for="item in dataSource.cols"> <!-- 表格的列展示 特殊情况处理 比如要输入框 显示图片 --> <el-table-column v-if="item.isTemplate" :key="item.prop" v-bind="item" > <template slot-scope="scope"> <!-- 比如要输入框 显示图片等等 自己定义 --> <slot :name="item.prop" :scope="scope" /> </template> </el-table-column> <!-- 需要特殊颜色显示字体--> <el-table-column v-if="item.isSpecial" :key="item.prop" v-bind="item" align="center" > <template slot-scope="scope"> <span :class="item.isSpecialClass(scope.row[scope.column.property])">{{ item.isSpecial(scope.row[scope.column.property]) }}</span> </template> </el-table-column> <!-- 需要带图标的某列,带回调事件--> <el-table-column v-if="item.isIcon" :key="item.prop" v-bind="item" align="center" > <template slot-scope="scope"> <span> <span>{{ item.filter(scope.row[scope.column.property]) }}</span> <i v-if="item.icon" :class="[item.icon(scope.row[scope.column.property]),'icon-normal']" @click="item.handlerClick(scope.row)" /> </span> <!-- 比如要输入框 显示图片等等 自己定义 --> <slot :name="item.prop" :scope="scope" /> </template> </el-table-column> <!-- 图片带tooltip --> <el-table-column v-if="item.isImagePopover" :key="item.prop" v-bind="item" align="center" > <template slot-scope="scope"> <el-popover placement="right" title="" trigger="hover" > <img class="image-popover" :src="scope.row[scope.column.property]+'?x-oss-process=image/quality,q_60'" alt=""> <img slot="reference" class="reference-img" :src="scope.row[scope.column.property]+'?x-oss-process=image/quality,q_10'" alt=""> </el-popover> </template> </el-table-column> <!-- 大部分适用 --> <el-table-column v-if="!item.isImagePopover && !item.isTemplate && !item.isSpecial&&!item.isIcon" :key="item.prop" v-bind="item.isCodeTableFormatter ? Object.assign({ formatter: item.isCodeTableFormatter }, item) : item" align="center" show-overflow-tooltip /> </template> <!-- 是否有操作列 --> <!-- 没有数据时候不固定列 --> <el-table-column v-if="dataSource.isOperation" :show-overflow-tooltip="dataSource.operation.overflowTooltip" v-bind="dataSource.data && dataSource.data.length ? { fixed: 'right' } : null" style="margin-right:20px" class-name="handle-td" label-class-name="tc" :width="dataSource.operation.width" :label="dataSource.operation.label" align="center" > <!-- UI统一一排放3个,4个以上出现更多 --> <template slot-scope="scope"> <!-- 三个一排的情况,去掉隐藏的按钮后的长度 --> <template v-if="dataSource.operation.data.length > 0"> <div class="btn"> <div v-for="(item) in dataSource.operation.data" :key="item.label"> <template v-if="item.type!=='icon'"> <el-button v-permission="item.permission" v-bind="item" :type="item.type?item.type:''" size="mini" @click.native.prevent="item.handleRow(scope.$index, scope.row, item.label)" > {{ item.label }} </el-button> </template> <template v-else> <i :class="[icon,item.icon]" v-bind="item" @click="item.handleRow(scope.$index, scope.row, item.label)" /> </template> </div> </div> </template> </template> </el-table-column> </el-table> <div class="page"> <el-pagination v-if="dataSource.pageData.total>0" :current-page="dataSource.pageData.pageNum" :page-sizes="dataSource.pageData.pageSizes?dataSource.pageData.pageSizes:[5,10,15,20]" :page-size="dataSource.pageData.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="dataSource.pageData.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </div> </template> <script> // dataSource: { // tool:[ // { // name: '新增用户', //按钮名称 // key: 1, // 唯一标识符 // permission: 2010106, // 权限点 // type: '', // 使用element自带按钮类型 // bgColor: '#67c23a', // 自定义背景色 // handleClick: this.handleAdd //自定义事件 // }, // ] // data: [], // 表格数据 // cols: [], // 表格的列数据 // handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组 // isSelection: false, // 表格有多选时设置 // isOperation: true, // 表格有操作列时设置 // isIndex: true, // 列表序号 // loading: true, // loading // pageData: { // total: 0, // 总条数 // pageSize: 10, // 每页数量 // pageNum: 1, // 页码 // pageSize:[5,10,15,20]// 每页数量 // } // operation: { // // 表格有操作列时设置 // label: '操作', // 列名 // width: '350', // 根据实际情况给宽度 // data: [ // { // label: '冻结', // 操作名称 // permission:'' //权限点 // type: 'info', //按钮类型 // handleRow: function(){} // 自定义事件 // }, // ] // } // }, export default { // 接收父组件传递过来的值 props: { // 表格数据和表格部分属性的对象 // eslint-disable-next-line vue/require-default-prop dataSource: { type: Object } }, data() { return { } }, watch: { 'dataSource.cols': { // 监听表格列变化 deep: true, handler() { // 解决表格列变动的抖动问题 this.$nextTick(this.$refs.table.doLayout) } } }, methods: { handleAdd(name) { console.log(name) this.$emit('toolMsg', name) }, handleRow(index, row, lable) { console.log(index, row, lable) }, handleSizeChange(val) { this.$emit('changeSize', val) console.log(`每页 ${val} 条`) }, handleCurrentChange(val) { this.$emit('changeNum', val) console.log(`当前页: ${val}`) }, // 点击行即可选中 getRowData(row) { this.$refs.table.toggleRowSelection(row) } } } </script> <style lang="scss" scoped> .page{ margin-top: 20px; } .btn{ display: flex; justify-content: center; } .btn div{ margin-left: 5px; } .reference-img{ width: 40px; height: 40px; background-size:100% 100%; border-radius: 4px; } .image-popover{ width: 200px; height: 200px; background-size:100% 100%; } .icon { width: 25px; font-size: 20px; font-weight: bold; } </style>
实战
配置某页面,咱先看配置图片是不是省事多了,而且条理清楚
<template> <div class="app-container"> <filter-pane :filter-data="filterData" @filterMsg="filterMsg" /> <table-pane :data-source="dataSource" @changeSize="changeSize" @changeNum="changeNum" /> <add :dialog-add="dialogAdd" @childMsg="childMsg" /> </div> </template> <script> import filterPane from '@/components/Table/filterPane' import tablePane from '@/components/Table/tablePane' import add from './components/add' import { getVersionList, delVersion } from '@/api/user' import { timeFormat } from '@/filters/index' export default { name: 'Suggestion', components: { filterPane, tablePane, add }, data() { return { // 搜索栏配置 filterData: { timeSelect: false, elselect: [ { name: '状态', width: 120, key: 'platform', option: [ { key: '全部', value: '全部' }, { key: 1, value: 'IOS' }, { key: 2, value: '安卓' } ] } ] }, // 表格配置 dataSource: { tool: [{ name: '新增版本', key: 1, permission: 2010701, handleClick: this.handleAdd }], data: [], // 表格数据 cols: [ { label: '发布时间', prop: 'appIssueTime', isCodeTableFormatter: function(val) { return timeFormat(val.appIssueTime) } }, { label: 'APP名称', prop: 'appName' }, { label: 'APP版本', prop: 'appVersion' }, { label: '平台', prop: 'appPlatform', isCodeTableFormatter: function(val) { if (val.appPlatform === 1) { return 'IOS' } else { return 'Android' } } }, { label: '是否自动更新', prop: 'appAutoUpdate', isCodeTableFormatter: function(val) { if (val.appAutoUpdate === 1) { return '是' } else { return '否' } } }, { label: '更新描述', prop: 'appDesc', width: 300 }, { label: '下载地址', prop: 'downloadAddr' }, { label: '发布人', prop: 'userName' } ], // 表格的列数据 handleSelectionChange: this.handleSelectionChange, isSelection: false, // 表格有多选时设置 isOperation: true, // 表格有操作列时设置 isIndex: true, // 列表序号 loading: true, // loading pageData: { total: 0, // 总条数 pageSize: 10, // 每页数量 pageNum: 1 // 页码 }, operation: { // 表格有操作列时设置 label: '操作', // 列名 width: '100', // 根据实际情况给宽度 data: [ { label: '删除', // 操作名称 type: 'danger', permission: '2010702', // 后期这个操作的权限,用来控制权限 handleRow: this.handleRow } ] } }, dialogAdd: false, msg: {}, selected: [] } }, created() { this.getList() }, methods: { // 获取列表数据 getList() { const data = { pageSize: this.dataSource.pageData.pageSize, pageNum: this.dataSource.pageData.pageNum } if (this.msg) { if (this.msg.platform === 'IOS') { data.platform = 1 } else if (this.msg.platform === '安卓') { data.platform = 2 } } this.dataSource.loading = true getVersionList(data).then(res => { this.dataSource.loading = false if (res.succeed) { if (res.data.total > 0) { this.dataSource.pageData.total = res.data.total this.dataSource.data = res.data.data } else { this.dataSource.data = [] this.dataSource.pageData.total = 0 } } }) }, // 搜索层事件 filterMsg(msg) { this.msg = msg if (Object.keys(msg).length > 0) { this.getList(msg) } else { this.getList() } }, // 子组件通信 childMsg(msg) { if (msg.dialogAdd === false) { this.dialogAdd = false } else if (msg.refreshList) { this.getList() } }, // 改变每页数量 changeSize(size) { this.dataSource.pageData.pageSize = size this.getList() }, // 改变页码 changeNum(pageNum) { this.dataSource.pageData.pageNum = pageNum this.getList() }, // 多选事件 handleSelectionChange(val) { this.selected = val }, // 表格上方工具栏回调 handleAdd(index, row) { this.dialogAdd = true }, // 表格操作列回调 handleRow(index, row, lable) { if (lable === '删除') { this.$confirm('确认删除该版本?', '温馨提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { delVersion({ versionId: row.id }).then(res => { if (res.succeed) { this.$message.success('删除成功') this.getList() } }) }).catch(() => { }) } } } } </script> <style scoped lang='scss'> </style>
结尾
filterPane.vue
、tablePane.vue
已完成,有些特殊页面只需要复制下到当前特殊页面的components
里改动下就
可以了,目前还在不断完善中,大家有什么问题可以提出来,也好进一步优化。
完整源文件在gitHub,可以下载直接使用,后续会持续更新
我给起了个名k-table以k开头代表快速的意思
加载全部内容