目录
- 前言
- 思考
- 实践
- filterPane.vue
- 明确目标
- 传入数据结构整理
- timeSelect
- elinput
- elselect
- 开始封装
- tablePane.vue
- 明确目标
- 传入数据结构整理
- tool
- cols
- pageData
- operation
- tablePane.vue配置项Cols详解
- 开始封装
- 实战
- 结尾
前言
由于重构后台管理项目中有好多表格页面, 举个栗子
这表格看着还挺好看,写起来叫人直呼XX,多动脑子少掉发,少走弯路多省鞋。
写了一个后感觉太麻烦了,于是我奋笔疾书,利用Vue+Element Table重新封装出了一套表格组件
3分钟就可以实现一个表格页面的骚操作,你值得拥有
思考
看了表格咱们简单的把它们划分了下功能区
- 最先入场的选手是最上方一排整整齐齐有点严肃的搜索功能区
- 其次入场的是略带个性的表格功能区
- 然后入场的是全能综合性型选手表格内容区域
- 最后入场的是有着长腿带着长队的分页组件区域
诶,让我们思考下最复杂的地方在哪里?
下面我用个图来标注下咱们接下来需要拿下的高地
总的来说表格内容这一块最不可控,他可能有多选、序号、图片、状态、时间、操作列......
实践
咱们把搜索层写成一个组件filterPane.vue
把表格分成一个组件tablePane.vue
表格组件tablePane.vue包括功能区、表格内容区、分页
filterPane.vue
明确目标
搜索层一般包括日期选择器、输入框、select下拉选择器等+搜索功能、重置功能
传入数据结构整理
// 搜索栏组件
filterData:{
timeSelect:true, //是否显示日期控件
elinput:[
{
name:'姓名', //提示语
key:'userName', //字段名
width: //宽度
}
],
elselect:[
{
name:'部门',
key:'department',
width:
option:[{
key:,
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:px"
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':'px'}"
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':'px'}"
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:,
// 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 >) {
val.elinput.map(item => {
this.listQuery[item.key] = ''
})
}
if (val.elselect.length >) {
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[] !== '') {
const startTime = this.$moment(this.dateRange[]).format('YYYY-MM-DD') + ' 00:00:00'
const endTime = this.$moment(this.dateRange[]).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:px;
display: inline-block;
}
.filter-container .filter-item:nth-of-type(){
margin-left:px;
}
.btn{
display: inline-block;
margin-left:px;
}
</style>
tablePane.vue
明确目标
实现表格功能行、实现表格基本功能、实现分页功能
传入数据结构整理
dataSource: {
tool:[
{
name: '新增用户', //按钮名称
key:, // 唯一标识符
permission:, // 权限点
type: '', // 使用element自带按钮类型
bgColor: '#c23a', // 自定义背景色
handleClick: this.handleAdd //自定义事件
},
]
data: [], // 表格数据
cols: [], // 表格的列数据
isSelection: false, // 表格有多选时设置
selectable: function(val) {//禁用部分行多选
if (val.isVideoStatus ===) {
return false
} else {
return true
}
},
handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组
isOperation: true, // 表格有操作列时设置
isIndex: true, // 列表序号
loading: true, // loading
pageData: {
total:, // 总条数
pageSize:, // 每页数量
pageNum: // 页码
}
operation: {
// 表格有操作列时设置
label: '操作', // 列名
width: '', // 根据实际情况给宽度
data: [
{
label: '冻结', // 操作名称
permission:'' //权限点
type: 'info', //按钮类型
handleRow: function(){} // 自定义事件
},
]
}
},
tool
- 类型 Array
- 默认值 [ ]
配置表格工具列
dataSource: {
tool:[
{
name: '新增用户', //按钮名称
key:, // 唯一标识符
permission:, // 权限点
type: '', // 使用element自带按钮类型
bgColor: '#c23a', // 自定义背景色
handleClick: this.handleAdd //自定义事件
},
]
}
cols
- 类型 Array
- 默认值 [ ] 配置表头
dataSource: {
cols:[
{
label: '标题', //列名
prop: 'belongUserId', //字段名称
width: //列宽度
},
{
label: '副标题(季)',
prop: 'subtitle',
isCodeTableFormatter: function(val) {//过滤器
if (val.subtitle ===) {
return '无'
} else {
return val.subtitle
}
},
width:
},
{
label: '创建时间',
prop: 'createTime',
isCodeTableFormatter: function(val) {//时间过滤器
return timeFormat(val.createTime)
},
width:
}
]
}
pageData
- 类型 Object
- 默认值 { } 配置分页
dataSource: {
pageData: {
total:, // 总条数
pageSize:, // 每页数量
pageNum:, // 页码
pageSize:[,10,15,20]// 每页数量
}
}
operation
- 类型 Object
- 默认值 { } 配置操作列
dataSource: {
operation: {
// 表格有操作列时设置
label: '操作', // 列名
width: '', // 根据实际情况给宽度
data: [
{
label: '修改', // 操作名称
permission:'' //权限点
type: 'info', //按钮类型icon为图表类型
handleRow: function(){} // 自定义事件
},
{
label: '修改', // 操作名称
permission:'' //权限点
type: 'icon', //按钮类型icon为图表类型
icon:'el-icon-plus'
handleRow: function(){} // 自定义事件
}
]
}
}
tablePane.vue配置项Cols详解
- 普通列
cols:[
{
label: '标题',
prop: 'title',
width:
}
]
- 普通列字体颜色改变
cols:[
{
label: '状态',
prop: 'status',
isTemplate: function(val) {
if (val ===) {
return '禁言中'
} else {
return '已解禁'
}
},
isTemplateClass: function(val) {
if (val ===) {
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===){
return '成功'
}else{
return '失败'
}
}
}
]
- 带图标列
cols:[
{
label: '目标类型',
prop: 'targetType',
isIcon: true,
filter: function(val) {
if (val ===) {
return '特定用户'
} else if (val ===) {
return '新注册用户'
} else if (val ===) {
return '标签用户'
} else if (val ===) {
return '全部用户'
}
},
icon: function(val) {
if (val ===) {
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:%;"
: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 ||"
align="center"
/>
<!-- 是否需要序号 -->
<el-table-column
v-if="dataSource.isIndex"
type="index"
label="序号"
width=""
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_'" alt="">
<img slot="reference" class="reference-img" :src="scope.row[scope.column.property]+'?x-oss-process=image/quality,q_'" 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:px"
class-name="handle-td"
label-class-name="tc"
:width="dataSource.operation.width"
:label="dataSource.operation.label"
align="center"
>
<!-- UI统一一排放个,4个以上出现更多 -->
<template slot-scope="scope">
<!-- 三个一排的情况,去掉隐藏的按钮后的长度 -->
<template v-if="dataSource.operation.data.length >">
<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>"
:current-page="dataSource.pageData.pageNum"
:page-sizes="dataSource.pageData.pageSizes?dataSource.pageData.pageSizes:[,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:, // 唯一标识符
// permission:, // 权限点
// type: '', // 使用element自带按钮类型
// bgColor: '#c23a', // 自定义背景色
// handleClick: this.handleAdd //自定义事件
// },
// ]
// data: [], // 表格数据
// cols: [], // 表格的列数据
// handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组
// isSelection: false, // 表格有多选时设置
// isOperation: true, // 表格有操作列时设置
// isIndex: true, // 列表序号
// loading: true, // loading
// pageData: {
// total:, // 总条数
// pageSize:, // 每页数量
// pageNum:, // 页码
// pageSize:[,10,15,20]// 每页数量
// }
// operation: {
// // 表格有操作列时设置
// label: '操作', // 列名
// width: '', // 根据实际情况给宽度
// 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:px;
}
.btn{
display: flex;
justify-content: center;
}
.btn div{
margin-left:px;
}
.reference-img{
width:px;
height:px;
background-size:% 100%;
border-radius:px;
}
.image-popover{
width:px;
height:px;
background-size:% 100%;
}
.icon {
width:px;
font-size:px;
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:,
key: 'platform',
option: [
{
key: '全部',
value: '全部'
},
{
key:,
value: 'IOS'
},
{
key:,
value: '安卓'
}
]
}
]
},
// 表格配置
dataSource: {
tool: [{
name: '新增版本',
key:,
permission:,
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 ===) {
return 'IOS'
} else {
return 'Android'
}
}
},
{
label: '是否自动更新',
prop: 'appAutoUpdate',
isCodeTableFormatter: function(val) {
if (val.appAutoUpdate ===) {
return '是'
} else {
return '否'
}
}
},
{
label: '更新描述',
prop: 'appDesc',
width:
},
{
label: '下载地址',
prop: 'downloadAddr'
},
{
label: '发布人',
prop: 'userName'
}
], // 表格的列数据
handleSelectionChange: this.handleSelectionChange,
isSelection: false, // 表格有多选时设置
isOperation: true, // 表格有操作列时设置
isIndex: true, // 列表序号
loading: true, // loading
pageData: {
total:, // 总条数
pageSize:, // 每页数量
pageNum: // 页码
},
operation: {
// 表格有操作列时设置
label: '操作', // 列名
width: '', // 根据实际情况给宽度
data: [
{
label: '删除', // 操作名称
type: 'danger',
permission: '', // 后期这个操作的权限,用来控制权限
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 =
} else if (this.msg.platform === '安卓') {
data.platform =
}
}
this.dataSource.loading = true
getVersionList(data).then(res => {
this.dataSource.loading = false
if (res.succeed) {
if (res.data.total >) {
this.dataSource.pageData.total = res.data.total
this.dataSource.data = res.data.data
} else {
this.dataSource.data = []
this.dataSource.pageData.total =
}
}
})
},
// 搜索层事件
filterMsg(msg) {
this.msg = msg
if (Object.keys(msg).length >) {
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开头代表快速的意思