Vue技巧Element Table二次封裝實戰(zhàn)示例
前言
由于重構(gòu)后臺管理項目中有好多表格頁面, 舉個栗子

這表格看著還挺好看,寫起來叫人直呼XX,多動腦子少掉發(fā),少走彎路多省鞋。
寫了一個后感覺太麻煩了,于是我奮筆疾書,利用Vue+Element Table重新封裝出了一套表格組件
3分鐘就可以實現(xiàn)一個表格頁面的騷操作,你值得擁有
思考
看了表格咱們簡單的把它們劃分了下功能區(qū)
- 最先入場的選手是最上方一排整整齊齊有點(diǎn)嚴(yán)肅的搜索功能區(qū)
- 其次入場的是略帶個性的表格功能區(qū)
- 然后入場的是全能綜合性型選手表格內(nèi)容區(qū)域
- 最后入場的是有著長腿帶著長隊的分頁組件區(qū)域
誒,讓我們思考下最復(fù)雜的地方在哪里?
下面我用個圖來標(biāo)注下咱們接下來需要拿下的高地
總的來說表格內(nèi)容這一塊最不可控,他可能有多選、序號、圖片、狀態(tài)、時間、操作列......

實踐
咱們把搜索層寫成一個組件filterPane.vue
把表格分成一個組件tablePane.vue
表格組件tablePane.vue包括功能區(qū)、表格內(nèi)容區(qū)、分頁
filterPane.vue
明確目標(biāo)
搜索層一般包括日期選擇器、輸入框、select下拉選擇器等+搜索功能、重置功能
傳入數(shù)據(jù)結(jié)構(gòu)整理
// 搜索欄組件
filterData:{
timeSelect:true, //是否顯示日期控件
elinput:[
{
name:'姓名', //提示語
key:'userName', //字段名
width:100 //寬度
}
],
elselect:[
{
name:'部門',
key:'department',
width:100
option:[{
key:1,
value:'技術(shù)部'
}]
}
]
}
timeSelect
- 類型
Boolean是否顯示時間選擇器
elinput
- 類型
Array輸入框選項,子對象內(nèi)
name為輸入框的placeholder
key為字段名
elselect
- 類型
Arrayselect下拉框選項,子對象內(nèi)
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="結(jié)束日期"
: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:'技術(shù)部'
// }]
// }
// ]
// }
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] = ''
})
}
},
//緩存進(jìn)頁面想清空可用
'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
明確目標(biāo)
實現(xiàn)表格功能行、實現(xiàn)表格基本功能、實現(xiàn)分頁功能
傳入數(shù)據(jù)結(jié)構(gòu)整理
dataSource: {
tool:[
{
name: '新增用戶', //按鈕名稱
key: 1, // 唯一標(biāo)識符
permission: 2010106, // 權(quán)限點(diǎn)
type: '', // 使用element自帶按鈕類型
bgColor: '#67c23a', // 自定義背景色
handleClick: this.handleAdd //自定義事件
},
]
data: [], // 表格數(shù)據(jù)
cols: [], // 表格的列數(shù)據(jù)
isSelection: false, // 表格有多選時設(shè)置
selectable: function(val) {//禁用部分行多選
if (val.isVideoStatus === 1) {
return false
} else {
return true
}
},
handleSelectionChange:(val)=>{} //點(diǎn)擊行選中多選返回選中數(shù)組
isOperation: true, // 表格有操作列時設(shè)置
isIndex: true, // 列表序號
loading: true, // loading
pageData: {
total: 0, // 總條數(shù)
pageSize: 10, // 每頁數(shù)量
pageNum: 1 // 頁碼
}
operation: {
// 表格有操作列時設(shè)置
label: '操作', // 列名
width: '350', // 根據(jù)實際情況給寬度
data: [
{
label: '凍結(jié)', // 操作名稱
permission:'' //權(quán)限點(diǎn)
type: 'info', //按鈕類型
handleRow: function(){} // 自定義事件
},
]
}
},
tool
- 類型
Array - 默認(rèn)值
[ ]
配置表格工具列
dataSource: {
tool:[
{
name: '新增用戶', //按鈕名稱
key: 1, // 唯一標(biāo)識符
permission: 2010106, // 權(quán)限點(diǎn)
type: '', // 使用element自帶按鈕類型
bgColor: '#67c23a', // 自定義背景色
handleClick: this.handleAdd //自定義事件
},
]
}
cols
- 類型
Array - 默認(rèn)值
[ ]配置表頭
dataSource: {
cols:[
{
label: '標(biāo)題', //列名
prop: 'belongUserId', //字段名稱
width: 100 //列寬度
},
{
label: '副標(biāo)題(季)',
prop: 'subtitle',
isCodeTableFormatter: function(val) {//過濾器
if (val.subtitle === 0) {
return '無'
} else {
return val.subtitle
}
},
width: 100
},
{
label: '創(chuàng)建時間',
prop: 'createTime',
isCodeTableFormatter: function(val) {//時間過濾器
return timeFormat(val.createTime)
},
width: 150
}
]
}
pageData
- 類型
Object - 默認(rèn)值
{ }配置分頁
dataSource: {
pageData: {
total: 0, // 總條數(shù)
pageSize: 10, // 每頁數(shù)量
pageNum: 1, // 頁碼
pageSize:[5,10,15,20]// 每頁數(shù)量
}
}
operation
- 類型
Object - 默認(rèn)值
{ }配置操作列
dataSource: {
operation: {
// 表格有操作列時設(shè)置
label: '操作', // 列名
width: '350', // 根據(jù)實際情況給寬度
data: [
{
label: '修改', // 操作名稱
permission:'1001' //權(quán)限點(diǎn)
type: 'info', //按鈕類型icon為圖表類型
handleRow: function(){} // 自定義事件
},
{
label: '修改', // 操作名稱
permission:'1001' //權(quán)限點(diǎn)
type: 'icon', //按鈕類型icon為圖表類型
icon:'el-icon-plus'
handleRow: function(){} // 自定義事件
}
]
}
}
tablePane.vue配置項Cols詳解
- 普通列
cols:[
{
label: '標(biāo)題',
prop: 'title',
width: 200
}
]
- 普通列字體顏色改變
cols:[
{
label: '狀態(tài)',
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: '狀態(tài)',
prop: 'status',
isCodeTableFormatter: function(val) {
if(val.status===1){
return '成功'
}else{
return '失敗'
}
}
}
]
- 帶圖標(biāo)列
cols:[
{
label: '目標(biāo)類型',
prop: 'targetType',
isIcon: true,
filter: function(val) {
if (val === 4) {
return '特定用戶'
} else if (val === 3) {
return '新注冊用戶'
} else if (val === 2) {
return '標(biāo)簽用戶'
} 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>
<!-- 需要帶圖標(biāo)的某列,帶回調(diào)事件-->
<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>
<!-- 是否有操作列 -->
<!-- 沒有數(shù)據(jù)時候不固定列 -->
<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統(tǒng)一一排放3個,4個以上出現(xiàn)更多 -->
<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, // 唯一標(biāo)識符
// permission: 2010106, // 權(quán)限點(diǎn)
// type: '', // 使用element自帶按鈕類型
// bgColor: '#67c23a', // 自定義背景色
// handleClick: this.handleAdd //自定義事件
// },
// ]
// data: [], // 表格數(shù)據(jù)
// cols: [], // 表格的列數(shù)據(jù)
// handleSelectionChange:(val)=>{} //點(diǎn)擊行選中多選返回選中數(shù)組
// isSelection: false, // 表格有多選時設(shè)置
// isOperation: true, // 表格有操作列時設(shè)置
// isIndex: true, // 列表序號
// loading: true, // loading
// pageData: {
// total: 0, // 總條數(shù)
// pageSize: 10, // 每頁數(shù)量
// pageNum: 1, // 頁碼
// pageSize:[5,10,15,20]// 每頁數(shù)量
// }
// operation: {
// // 表格有操作列時設(shè)置
// label: '操作', // 列名
// width: '350', // 根據(jù)實際情況給寬度
// data: [
// {
// label: '凍結(jié)', // 操作名稱
// permission:'' //權(quán)限點(diǎn)
// type: 'info', //按鈕類型
// handleRow: function(){} // 自定義事件
// },
// ]
// }
// },
export default {
// 接收父組件傳遞過來的值
props: {
// 表格數(shù)據(jù)和表格部分屬性的對象
// eslint-disable-next-line vue/require-default-prop
dataSource: {
type: Object
}
},
data() {
return {
}
},
watch: {
'dataSource.cols': { // 監(jiān)聽表格列變化
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(`當(dāng)前頁: ${val}`)
},
// 點(diǎn)擊行即可選中
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>
實戰(zhàn)
配置某頁面,咱先看配置圖片是不是省事多了,而且條理清楚


<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: '狀態(tài)',
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: [], // 表格數(shù)據(jù)
cols: [
{
label: '發(fā)布時間',
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: '發(fā)布人',
prop: 'userName'
}
], // 表格的列數(shù)據(jù)
handleSelectionChange: this.handleSelectionChange,
isSelection: false, // 表格有多選時設(shè)置
isOperation: true, // 表格有操作列時設(shè)置
isIndex: true, // 列表序號
loading: true, // loading
pageData: {
total: 0, // 總條數(shù)
pageSize: 10, // 每頁數(shù)量
pageNum: 1 // 頁碼
},
operation: {
// 表格有操作列時設(shè)置
label: '操作', // 列名
width: '100', // 根據(jù)實際情況給寬度
data: [
{
label: '刪除', // 操作名稱
type: 'danger',
permission: '2010702', // 后期這個操作的權(quán)限,用來控制權(quán)限
handleRow: this.handleRow
}
]
}
},
dialogAdd: false,
msg: {},
selected: []
}
},
created() {
this.getList()
},
methods: {
// 獲取列表數(shù)據(jù)
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()
}
},
// 改變每頁數(shù)量
changeSize(size) {
this.dataSource.pageData.pageSize = size
this.getList()
},
// 改變頁碼
changeNum(pageNum) {
this.dataSource.pageData.pageNum = pageNum
this.getList()
},
// 多選事件
handleSelectionChange(val) {
this.selected = val
},
// 表格上方工具欄回調(diào)
handleAdd(index, row) {
this.dialogAdd = true
},
// 表格操作列回調(diào)
handleRow(index, row, lable) {
if (lable === '刪除') {
this.$confirm('確認(rèn)刪除該版本?', '溫馨提示', {
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>
結(jié)尾
filterPane.vue、tablePane.vue已完成,有些特殊頁面只需要復(fù)制下到當(dāng)前特殊頁面的components里改動下就
可以了,目前還在不斷完善中,大家有什么問題可以提出來,也好進(jìn)一步優(yōu)化。
完整源文件在gitHub,可以下載直接使用,后續(xù)會持續(xù)更新
我給起了個名k-table以k開頭代表快速的意思
以上就是Vue技巧Element Table二次封裝實戰(zhàn)示例的詳細(xì)內(nèi)容,更多關(guān)于Vue Element Table二次封裝的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用element+vuedraggable實現(xiàn)圖片上傳拖拽排序
這篇文章主要為大家詳細(xì)介紹了使用element+vuedraggable實現(xiàn)圖片上傳拖拽排序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04
對Vue beforeRouteEnter 的next執(zhí)行時機(jī)詳解
今天小編就為大家分享一篇對Vue beforeRouteEnter 的next執(zhí)行時機(jī)詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
Vue.js項目實戰(zhàn)之多語種網(wǎng)站的功能實現(xiàn)(租車)
這篇文章主要介紹了Vue.js項目實戰(zhàn)之多語種網(wǎng)站(租車)的功能實現(xiàn) ,需要的朋友可以參考下2019-08-08

