Vue 中后台表格的增删改查同一解决方案(组件封装版)
https://www.yuque.com/docs/share/10067c29-d80c-4d39-a624-32881f8fad58?# (opens new window) 《Vue 中后台表格的增删改查统一解决方案(mixin版)》
# 前言
个人是比较喜欢这一版的, 也是最开始做程序员写的,
# 使用方法
部分中后台页面都是非常同质化的 CRUD 组成的,很多时候都是一个 Table,然后提供一些操作按钮,并且有一个新增表单。看起来就像这样
# 组件的封装
# table filter
参数配置:
tableFilters: [
{
is: 'el-input',
prop: 'title',
attrs: { placeHolder: '请输入景点名称', style: 'width: 200px;' }
},
{
is: 'el-input',
prop: 'merchant',
attrs: { placeHolder: '请选择商户', style: 'width: 200px;' }
}
]
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
<div class="filter-container d-flex align-items-center justify-content-between pr-20">
<section>
<component :is="filter.is" v-for="(filter, index) in filters" :key="index" v-model="filterForm[filter.prop]" v-bind="filter.attrs" class="filter-item mr-10">
<template v-if="filter.options">
<el-option v-for="(option, i) in filter.options" :key="`${index}_${i}`" :value="option.value" :label="option.label" />
</template>
</component>
</section>
<section>
<el-button class="filter-item" type="warning" icon="el-icon-refresh-left" @click="filterForm={};getList()">重置</el-button>
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
<slot name="action">
<el-button v-if="crud.includes('c')" class="filter-item" style="margin-left: 10px;" type="success" icon="el-icon-plus" @click="$emit('createItem')">添加</el-button>
</slot>
</section>
</div>
----------------
filters: { type: Array, default: () => [] }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# table columns
传入参数:
tableColumns: [
{ label: '名称', prop: 'title', width: 130 },
{ label: '联系人', prop: 'name', width: 60, align: 'center' },
{ label: '电话', prop: 'phone', width: 130, align: 'center' },
{ label: '账号', prop: 'acount', width: 140 },
{ label: '人数', prop: 'count', width: 50, align: 'center' },
{ label: '地址', prop: 'address', width: 'auto' },
{
label: '状态', prop: 'status', width: 80, align: 'center', render: (h, { row }) => {
return h('el-tag', { attrs: { type: row.status === '正常' ? 'success' : 'info', size: 'small' }}, row.status)
}
},
{ label: '创建时间', prop: 'createTime', width: 160, align: 'center' }
],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
<el-table-column v-for="col in columns" :key="col.prop" v-bind="col">
<template slot-scope="{row}">
<template v-if="'render' in col">
<Render :row="row" :render="col.render" />
</template>
<span v-else>{{ col.formatter ? col.formatter(row) : row[col.prop] }}</span>
</template>
</el-table-column>
------------------
columns: { type: Array, default: () => [] },
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# table item handler
传入参数:
tableHandle: [
{
label: '修改',
type: 'primary',
isPop: false,
method: row => {
this.editItem(row)
}
},
{
label: '删除',
type: 'danger',
isPop: true,
method: row => {
this.deleteItem(row)
}
}
],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<el-table-column v-if="handle.length" v-bind="handleColumn" :width="(handle.length * 80)+'px'">
<template slot-scope="scope">
<template v-for="(btn, index) in handle">
<el-button v-if="!btn.isPop" :key="index" style="margin: 5px;" size="mini" :type="btn.type" @click.native.prevent="btn.method(scope.row,scope)">{{ btn.label }}</el-button>
<el-popconfirm v-if="btn.isPop" :key="index" placement="right" confirm-button-text="确定" cancel-button-text="取消" icon="el-icon-info" icon-color="red" title="确定删除吗?" @onConfirm="$message.success('操作成功');getList();btn.method(scope.row, scope)">
<el-button slot="reference" style="margin: 5px;" size="mini" :type="btn.type">{{ btn.label }}</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
-------------------
handle: { type: Array, default: () => [] },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# table pagination
<pagination v-show="total>listQuery.size" style="padding: 8px 16px; margin-top: 10px;" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.size" @pagination="getList" />
---------
total: 0,
listQuery: {
page: 1,
size: 20
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# all page
使用页面(参数传入):
<!--
* @Description:
* @Author: 仲灏<izhaong 164165005@qq.com>
* @Date: 2020-11-17 17:49:51
* @LastEditors: 仲灏<izhaong 164165005@qq.com>
* @LastEditTime: 2020-11-20 14:11:38
-->
<template>
<div class="app-container">
<complex-table ref="table" :columns="tableColumns" :handle="tableHandle" :filters="tableFilters" :api="api" @createItem="$router.push('/merchant/create')" />
</div>
</template>
<script>
import ComplexTable from '../../components/ComplexTable'
import { fetchList } from '@/api/merchant'
export default {
name: 'MerchantList',
components: { ComplexTable },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
api: fetchList,
tableColumns: [
{ label: '名称', prop: 'title', width: 130 },
{ label: '联系人', prop: 'name', width: 60, align: 'center' },
{ label: '电话', prop: 'phone', width: 130, align: 'center' },
{ label: '账号', prop: 'acount', width: 140 },
{ label: '人数', prop: 'count', width: 50, align: 'center' },
{ label: '地址', prop: 'address', width: 'auto' },
{
label: '状态', prop: 'status', width: 80, align: 'center', render: (h, { row }) => {
return h('el-tag', { attrs: { type: row.status === '正常' ? 'success' : 'info', size: 'small' }}, row.status)
}
},
{ label: '创建时间', prop: 'createTime', width: 160, align: 'center' }
],
tableHandle: [
{
label: '修改',
type: 'primary',
isPop: false,
method: row => {
this.editItem(row)
}
},
{
label: '删除',
type: 'danger',
isPop: true,
method: row => {
this.deleteItem(row)
}
}
],
tableFilters: [
{
is: 'el-input',
prop: 'title',
attrs: { placeHolder: '请输入商户名称', style: 'width: 200px;' }
},
{
is: 'el-input',
prop: 'name',
attrs: { placeHolder: '请输入联系人姓名', style: 'width: 200px;' }
},
{
is: 'el-input',
prop: 'phone',
attrs: { placeHolder: '请输入联系人电话', style: 'width: 200px;' }
}
]
}
},
methods: {
editItem(row) {
console.log(row)
this.$router.push(`/merchant/edit/123456789`)
},
deleteItem(row) {
console.log(row)
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
组件页面
<!--
* @Descripttion: 数据化表格
* @version: 1.0.0
* @Author: 仲灏 Izhaong<164165005@qq.com>
* @Date: 2020-06-27 15:13:00
* @LastEditors: 仲灏<izhaong 164165005@qq.com>
* @LastEditTime: 2020-11-23 15:39:49
-->
<template>
<div class="complex-table_container app-container">
<div class="filter-container d-flex align-items-center justify-content-between pr-20">
<section>
<component :is="filter.is" v-for="(filter, index) in filters" :key="index" v-model="filterForm[filter.prop]" v-bind="filter.attrs" class="filter-item mr-10">
<template v-if="filter.options">
<el-option v-for="(option, i) in filter.options" :key="`${index}_${i}`" :value="option.value" :label="option.label" />
</template>
</component>
</section>
<section>
<el-button class="filter-item" type="warning" icon="el-icon-refresh-left" @click="filterForm={};getList()">重置</el-button>
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
<slot name="action">
<el-button v-if="crud.includes('c')" class="filter-item" style="margin-left: 10px;" type="success" icon="el-icon-plus" @click="$emit('createItem')">添加</el-button>
</slot>
</section>
</div>
<el-table :key="tableKey" v-loading="listLoading" :height="tableHeight" :data="list" size="small" style="width: 100%;">
<el-table-column
type="index"
width="50"
label="序列"
align="center"
/>
<el-table-column v-for="col in columns" :key="col.prop" v-bind="col">
<template slot-scope="{row}">
<template v-if="'render' in col">
<Render :row="row" :render="col.render" />
</template>
<span v-else>{{ col.formatter ? col.formatter(row) : row[col.prop] }}</span>
</template>
</el-table-column>
<el-table-column v-if="handle.length" v-bind="handleColumn" :width="(handle.length * 80)+'px'">
<template slot-scope="scope">
<template v-for="(btn, index) in handle">
<el-button v-if="!btn.isPop" :key="index" style="margin: 5px;" size="mini" :type="btn.type" @click.native.prevent="btn.method(scope.row,scope)">{{ btn.label }}</el-button>
<el-popconfirm v-if="btn.isPop" :key="index" placement="right" confirm-button-text="确定" cancel-button-text="取消" icon="el-icon-info" icon-color="red" title="确定删除吗?" @onConfirm="$message.success('操作成功');getList();btn.method(scope.row, scope)">
<el-button slot="reference" style="margin: 5px;" size="mini" :type="btn.type">{{ btn.label }}</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>listQuery.size" style="padding: 8px 16px; margin-top: 10px;" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.size" @pagination="getList" />
</div>
</template>
<script>
import Pagination from '@/components/Pagination'
import Render from './render'
export default {
name: 'ComplexTable',
components: { Pagination, Render },
props: {
columns: { type: Array, default: () => [] },
operates: { type: Array, default: () => [] },
handle: { type: Array, default: () => [] },
filters: { type: Array, default: () => [] },
crud: { type: String, default: 'crud' },
actionsColumn: {
type: Object,
default: () => ({
label: '操作',
align: 'center'
})
},
handleColumn: {
type: Object,
default: () => ({
label: '操作',
align: 'center'
})
},
// eslint-disable-next-line vue/require-default-prop
api: [Function, Object]
},
data() {
return {
filterForm: {},
tableKey: 0,
list: null,
total: 0,
listLoading: true,
listQuery: {
page: 1,
size: 20
}
}
},
computed: {
tableHeight() {
if (this.total > this.listQuery.size) {
return window.document.body.clientHeight - 242
} else {
return window.document.body.clientHeight - 180
}
}
},
created() {
this.initFilters()
this.getList()
},
methods: {
inpubr($event) {
event.target.blur()
},
initFilters() {
const props = this.filters.map((item) => item.prop)
props.forEach((key) => {
this.$set(this.filterForm, key, '')
})
},
getList() {
this.listLoading = true
this.api({ ...this.listQuery, ...this.filterForm }).then((response) => {
this.list = response.data.items
this.total = response.data.total
this.listLoading = false
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
}
}
}
</script>
<style lang="scss">
.complex-table_container {
.el-table__body-wrapper {
&::-webkit-scrollbar {
/*滚动条整体样式*/
width: 6px !important;
/*高宽分别对应横竖滚动条的尺寸*/
height: 6px !important;
background: #ffffff !important;
cursor: pointer !important;
}
&::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 5px !important;
-webkit-box-shadow: inset 0 0 5px rgba(240, 240, 240, 0.5) !important;
background: rgba(63, 98, 131, 0.8) !important;
cursor: pointer !important;
}
&::-webkit-scrollbar-track {
/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 5px rgba(240, 240, 240, 0.5) !important;
border-radius: 0 !important;
background: rgba(240, 240, 240, 0.5) !important;
cursor: pointer !important;
}
}
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
上次更新: 2022/06/05, 20:31:36