被调到新的项目组,开始财务模块开发,涉及到大量报表和数字。最近的需求涉及到element table,各种详细的要求,要求拓展目前现有的table内容,期间也遇到了很多问题,现在记录下来。

目录

表格编辑

image

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
<!DOCTYPE html>
<html >

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Demo</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
.el-table-add-row {
margin-top: 10px;
width: 100%;
height: 34px;
border: 1px dashed #c1c1cd;
border-radius: 3px;
cursor: pointer;
justify-content: center;
display: flex;
line-height: 34px;
}</style>
</head>

<body>
<div id="app">
<el-row>
<el-col span="24">
<el-table size="mini" :data="master_user.data" border style="width: 100%" highlight-current-row>
<el-table-column type="index"></el-table-column>
<el-table-column v-for="(v,i) in master_user.columns" :prop="v.field" :label="v.title" :width="v.width">
<template slot-scope="scope">
<span v-if="scope.row.isSet">
<el-input size="mini" placeholder="请输入内容" v-model="master_user.sel[v.field]">
</el-input>
</span>
<span v-else>{{scope.row[v.field]}}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<span class="el-tag el-tag--info el-tag--mini" style="cursor: pointer;" @click="pwdChange(scope.row,scope.$index,true)">
{{scope.row.isSet?'保存':"修改"}}
</span>
<span v-if="!scope.row.isSet" class="el-tag el-tag--danger el-tag--mini" style="cursor: pointer;">
删除
</span>
<span v-else class="el-tag el-tag--mini" style="cursor: pointer;" @click="pwdChange(scope.row,scope.$index,false)">
取消
</span>
</template>
</el-table-column>
</el-table>
</el-col>
<el-col span="24">
<div class="el-table-add-row" style="width: 99.2%;" @click="addMasterUser()"><span>+ 添加</span></div>
</el-col>
</el-row>

</div>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
//id生成工具 这个不用看 示例而已 模拟后台返回的id
var generateId = {
_count: 1,
get(){return ((+new Date()) + "_" + (this._count++))}
};
//主要内容
var app = new Vue({
el: "#app",
data: {
master_user: {
sel: null,//选中行
columns: [
{ field: "type", title: "远程类型", width: 120 },
{ field: "addport", title: "连接地址", width: 150 },
{ field: "user", title: "登录用户", width: 120 },
{ field: "pwd", title: "登录密码", width: 220 },
{ field: "info", title: "其他信息" }
],
data: [],
},
},
methods: {
//读取表格数据
readMasterUser() {
//根据实际情况,自己改下啊
app.master_user.data.map(i => {
i.id = generateId.get();//模拟后台插入成功后有了id
i.isSet=false;//给后台返回数据添加`isSet`标识
return i;
});
},
//添加账号
addMasterUser() {
for (let i of app.master_user.data) {
if (i.isSet) return app.$message.warning("请先保存当前编辑项");
}
let j = { id: 0, "type": "", "addport": "", "user": "", "pwd": "", "info": "", "isSet": true, "_temporary": true };
app.master_user.data.push(j);
app.master_user.sel = JSON.parse(JSON.stringify(j));
},
//修改
pwdChange(row, index, cg) {
//点击修改 判断是否已经保存所有操作
for (let i of app.master_user.data) {
if (i.isSet && i.id != row.id) {
app.$message.warning("请先保存当前编辑项");
return false;
}
}
//是否是取消操作
if (!cg) {
if (!app.master_user.sel.id) app.master_user.data.splice(index, 1);
return row.isSet = !row.isSet;
}
//提交数据
if (row.isSet) {
//项目是模拟请求操作 自己修改下
(function () {
let data = JSON.parse(JSON.stringify(app.master_user.sel));
for (let k in data) row[k] = data[k];
app.$message({
type: 'success',
message: "保存成功!"
});
//然后这边重新读取表格数据
app.readMasterUser();
row.isSet = false;
})();
} else {
app.master_user.sel = JSON.parse(JSON.stringify(row));
row.isSet = true;
}
}
}
});
</script>
</body>

</html>

表头合并、自定样式、表格附件上传

image

表头合并参考了element官方文档,只需要在 el-table-column 里面嵌套 el-table-column,就可以实现多级表头;
render-header 列标题 Label 区域渲染使用的 Function,就可以实现表头样式自定义

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
<el-table-column label="关停期间损失">
<el-table-column
prop="landlord_liquidated_damages"
:render-header="renderHeader"
label="赔付给房东的违约金(元)"
>
<template slot-scope="scope">
{{ format(scope.row.landlord_liquidated_damages) }}</template
>
</el-table-column>
<el-table-column
prop="tenant_liquidated_damages"
:render-header="renderHeader"
label="赔付给租客的违约金(元)"
>
<template slot-scope="scope">{{
format(scope.row.tenant_liquidated_damages)
}}</template>
</el-table-column>
<el-table-column
prop="client_liquidated_damages"
:render-header="renderHeader"
label="赔付给委托方的违约金(元)"
>
<template slot-scope="scope">{{
format(scope.row.client_liquidated_damages)
}}</template>
</el-table-column>
<el-table-column
prop="operating_loss"
:render-header="renderHeader"
label="经营亏损(元)"
>
<template slot-scope="scope">{{
format(scope.row.operating_loss)
}}</template>
</el-table-column>
<el-table-column
prop="additional_investment"
:render-header="renderHeader"
label="追加投资(元)"
>
<template slot-scope="scope">{{
format(scope.row.additional_investment)
}}</template>
</el-table-column>
</el-table-column>
<el-table-column prop="upload" label="附件">
<template slot-scope="scope">
<div
class="files-box"
v-for="(a, aIndex) in scope.row.upload"
:key="aIndex"
>
<div class="files-box__name" :title="a.name">{{ a.name }}</div>
<div class="files-box__btn">
<el-button type="text" @click="checkFile(a.url)">查看</el-button>
<el-button
v-if="!isDetail"
type="text"
@click="delFile(aIndex, scope.$index)"
>删除</el-button
>
</div>
</div>
</template>
</el-table-column>
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
/**
* 表头样式自定义
*/
renderHeader(h, { column, $index }) {
let str = ''
if (
column.label == '预计总投(万元)'
|| column.label == '预计NOI/GOP投资回报率(%)'
) {
str = '投资收益'
} else {
str = '关停信息'
}
return (
<span>
{str}
<br />
<span class="c6">{column.label}</span>
</span>
)
},

/**
* 删除附件
* @param {*} i 附件index
* @param {*} l list的index
*/
delFile(i, l) {
this.list[l].upload.splice(i, 1)
},
// 查看加密附件
async checkFile(url) {
try {
let res = await PmService.getSignedUrl(url)
window.open(res.data.url, res.data.url)
} catch (e) {
console.error(e)
}
return '#'
}
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
<style>
.c6 {
color: #606266;
}
.c6::before {
content: "*";
color: #f56c6c;
margin-right: 4px;
}
.files-box {
position: relative;
}
.files-box__name {
height: 40px;
line-height: 40px;
width: 110px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.files-box__btn {
position: absolute;
top: 0;
right: 0;
}
</style>

树状table,允许展开折叠操作

image

default-expand-all 是否默认展开所有行,当 Table 包含展开行存在或者为树形表格时有效

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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
<template>
<div class="f-mt">
<el-table
:data="data"
style="width: 100%;"
row-key="id"
:indent="6"
:default-expand-all="default_expand_all"
border
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="name" label="指标名称">
<template slot-scope="scope">
{{ scope.row.name }}
<f-tips v-if="scope.row.method" top="25%" :width="400">{{
scope.row.method
}}</f-tips>
</template>
</el-table-column>
<el-table-column prop="systemValue" label="前台系统取值(元)">
<template slot-scope="scope">
<!-- show_system_desc -->
<el-tooltip
effect="dark"
:content="scope.row.show_system_desc"
placement="top"
>
<el-button type="text">{{
format(scope.row.systemValue)
}}</el-button>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="manualValue" label="手工补录(元)">
<template slot-scope="scope">
<el-form>
<el-form-item
:key="scope.row.id"
:error="valid[scope.row.id + '_' + scope.row.name]"
>
<el-tooltip
effect="dark"
:content="scope.row.show_manual_desc"
placement="top"
>
<el-input
size="medium"
@input="
checkInput(
scope.row.manualValue,
scope.row.id,
scope.row.name
)
"
v-model="scope.row.manualValue"
placeholder="请输入"
></el-input>
</el-tooltip>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column prop="total" label="合计(元)">
<template slot-scope="scope">
<el-tooltip
effect="dark"
:content="scope.row.show_system_desc"
placement="top"
>
<span>
{{
scope.row.can_edit
? format(
(
Number(scope.row.systemValue) +
Number(scope.row.manualValue)
).toFixed(2)
)
: format(scope.row.total)
}}
</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="houses" label="楼栋均摊(元)" width="620">
<template slot-scope="scope">
<el-form :inline="true" label-position="right" label-width="120px">
<el-form-item
v-for="(item, index) in scope.row.show_houses"
:label="item.name"
:key="index"
:error="valid[item.id + '_' + scope.row.name]"
>
<el-tooltip
effect="dark"
placement="top"
:content="item.show_system_desc"
>
<el-input
size="medium"
style="width: 130px;"
:disabled="!item.can_edit"
@input="
checkInput(item.value, item.id, scope.row.name, scope.row)
"
v-model="item.value"
placeholder="请输入修改数值"
></el-input>
</el-tooltip>
</el-form-item>
<el-button
class="show-btn"
v-if="scope.row.houses && scope.row.houses.length > 2"
type="text"
@click="handleMore(scope.row)"
>查看更多</el-button
>
</el-form>
</template>
</el-table-column>
</el-table>
</div>
</template>

<script>
function makeMsg(oriMsg, msg) {
return `${oriMsg}${msg ? ',' + msg : ''}`
}
export default {
name: 'table-form',
props: {
data: {
type: [Array],
required: true
},
// 控制树展开折叠
default_expand_all: {
type: Boolean,
required: true
}
},
data() {
return {
valid: {},
title: '',
form: {
houses: []
},
rules: {},
visible: false,
active: ['1'],
error_info: [],
d_loading: false,
list: [],
field: {
visible: false,
loading: false,
title: ''
}
}
},
watch: {},
computed: {},
methods: {
/**
* 编辑 ,校验输入合法
* @param {*} value
* @param {*} id
* @param {*} name
* @param {*} item 当前编辑的item
*/
checkInput(value, id, name, item) {
let i = 14
let f = 2
let all_true = false
this.valid[id + '_' + name] = ''
let reg = new RegExp(`^-?\\d{1,${i}}(\\.\\d{1,${f}})?$`)
if (!reg.test(value)) {
this.valid[id + '_' + name] = `最多${i}位整数${f}位小数`
}
// 校验是否还存在其他valid
for (let v in this.valid) {
if (this.valid[v]) {
all_true = false
break
} else {
all_true = true
}
}
let res = 0
if (item.houses && item.houses.length) {
item.houses.forEach((h) => {
res = Utils.floatAdd(res, h.value, 2)
})
}
item.manualValue = res // 同步更新当前手工补录值
this.$emit('checkInput', all_true) // 通过输入合法性校验
},

// 查看更多楼栋信息
handleMore(data) {
this.form = _.clone(data)
this.title = data.name
this.visible = true
},

// 查看明细
goTo(url) {
this.$emit('goTo', url)
},

/**
* 正浮点数限制
* @param {Number} i 整数最大位数
* @param {Number} f 小数最大位数
* @param {Object} options 配置
* isMoreThanZero: 是否强制大于0
* canNegative: 是否允许输入负数
*/
float(i, f, options = {}) {
return {
validator: (rule, value, callback) => {
let reg
let msg
if (f == 0) {
reg = new RegExp(`^-?\\d{1,${i}}$`)
} else {
reg = new RegExp(`^-?\\d{1,${i}}(\\.\\d{1,${f}})?$`)
}
if (!value) {
callback()
} else {
if (reg.test(value)) {
if (
(options.isMoreThanZero && Number(value) <= 0)
|| (!options.canNegative && Number(value) < 0)
) {
msg = '请输入大于零的数值'
} else {
callback()
}
} else {
msg = `最多${i}位整数`
if (f != 0) {
msg += `${f}位小数`
}
}
}
if (msg) {
callback(new Error(makeMsg(msg, options.extraMsg)))
}
},
trigger: 'blur'
}
},
/*
不允许输入全部是空格
*/
notAllBlank(label) {
return {
validator: (rule, value = '', callback) => {
if (value.trim().length == 0) {
callback(new Error(`${label}不能全部是空格`))
} else {
callback()
}
},
trigger: 'blur'
}
},
/**
* 千分位
* @param {*} str 字符串数字
*/
format(str) {
if (!str) return str
str = str.toString()
let minus = false
if (/^-/.test(str)) {
minus = true
str = str.replace(/^-/, '')
}
let [int, float] = str.split('.')
let result = []
for (let i = 0; i < int.length; i++) {
result = [int[int.length - i - 1], ...result]
if (i % 3 == 2 && i != int.length - 1) {
result = [',', ...result]
}
}
if (float) {
return `${minus ? '-' : ''}${result.join('')}.${float}`
}
return `${minus ? '-' : ''}${result.join('')}`
}
}
}
</script>
<style lang="less" scoped>
@import '~__assets/var.less';
@import url('//at.alicdn.com/t/font_1712723_tp9lsy2fuua.css');

.f-fr {
float: right;
}

.f-tooltip {
max-width: 700px !important;
word-wrap: break-word !important;
}

.show-btn {
position: absolute;
right: 20px;
top: 13px;
}

/deep/ .cell {
user-select: text;
}

/deep/ .el-table__row {
td {
&:last-child {
.cell {
max-height: 60px;
}
}
}
}

.dialog-total {
height: 32px;
text-align: right;
}

.error-info {
color: @c-err;
font-size: 16px;
line-height: 24px;
}

.f-i-btn {
font-size: 16px;
}
</style>

参考链接: