基于 ElementUI 封装的基础 table 和 form
一般做后台管理系统的项目, 用到最多的 UI
组件应该就是表格和表单吧.
一个最基础的例子就是上面一排操作按钮: 增删改查; 下面一个表格, 展示数据; 增改的时候弹出表单; 而且大部分情况下表格和表单的字段都是一样的, 但却要写两套代码, 作为一个崇尚简洁的程序员, 越简洁越好(才不是懒得写呢)
所以就想封装一下这两个组件, 最好一行代码就搞定了
# 分析现有代码
# table
先看 table
例子:
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="日期" width="180"> </el-table-column>
<el-table-column prop="name" label="姓名" width="180"> </el-table-column>
<el-table-column prop="address" label="地址"> </el-table-column>
</el-table>
2
3
4
5
我们可以把 column
这部分抽出一个数组, 数组中包含所有属性; 和 data
似的传入封装好的组件中, 组件自己遍历生成 el-table-column
, 那咱们调用组件的时候是不是就一行代码就搞定了呀:
<base-table :data="tableData" :columns="columns" style="width: 100%"></base-table>
# form
再看 form
例子:
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="活动名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="活动区域">
<el-select v-model="form.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间">
<el-col :span="11">
<el-date-picker
type="date"
placeholder="选择日期"
v-model="form.date1"
style="width: 100%;"
></el-date-picker>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-time-picker
placeholder="选择时间"
v-model="form.date2"
style="width: 100%;"
></el-time-picker>
</el-col>
</el-form-item>
<el-form-item label="即时配送">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="活动性质">
<el-checkbox-group v-model="form.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">立即创建</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
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
form
就比较难一些了: 不仅要判断表单类型( input
, select
, textarea
等等), select
等表单还需要 option
数组
为了实现一行代码就搞定, 所以我们最终调用的方式应该是这样的:
<base-form ref="form" :model="form" :form-items="formItems" label-width="80px"></base-form>
那就需要在 formItems
中做处理了: 首先需要区分表单类型; 对于 select
等这类表单, 还需要传入 option
选择数组
# 初稿
# table
table
就比较简单了
组件源码:
<el-table v-bind="$attrs">
<el-table-column
v-for="column in columns"
:key="column.prop"
v-bind="column"
>
</el-table-column>
</el-table>
2
3
4
5
6
7
8
name: 'BaseTable',
props: {
columns: {
type: Array,
default () {
return []
}
}
}
2
3
4
5
6
7
8
9
调用:
<base-table :data="tableData" :columns="columns" style="width: 100%"></base-table>
data() {
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}
],
columns: [
{
label: '日期',
prop: 'date',
width: 180
},
{
label: '姓名',
prop: 'name',
width: 180
},
{
label: '地址',
prop: 'address'
}
]
}
}
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
# form
form
需要做的事情比 table
多一些
组件源码:
<el-form v-bind="$attrs" :model="model">
<el-form-item v-for="item in formItems" :key="item.prop">
<el-input
v-if="item.type === 'input'"
v-model="model[item.prop]"
v-bind="item"
>
</el-input>
<el-select v-if="item.type === 'select'" v-model="model[item.prop]" v-bind="item">
<el-option
v-for="option in item.select"
:key="option.value"
:label="option.label"
:value="option.value">
</el-option>
</el-select>
<el-switch
v-if="item.type === 'switch'"
v-model="model[item.prop]"
v-bind="item"
>
</el-switch>
</el-form-item>
</el-form>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
其余的表单就不写了, 都差不多的样子
name: 'BaseForm',
props: {
modle: {
type: Object,
default() {
return {}
}
},
formItems: {
type: Array,
default() {
return []
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
调用:
<base-form ref="form" :model="form" :form-items="formItems" label-width="80px"></base-form>
data() {
return {
form: {
name: '',
region: ''
},
formItems: [
{
type: 'input',
label: '活动名称',
prop: 'name'
},
{
type: 'select',
label: '活动区域',
prop: 'region',
select: [
{
label: '区域一',
value: 'shanghai'
},
{
label: '区域二',
value: 'beijing'
}
]
}
]
}
}
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
# 优化
目前使用我们封装好的组件, 确实可以实现一行代码就搞定的效果, 而且如果 form
和 table
字段一样的话, formItems
和 columns
完全可以合并为一个数组. 但还有几个问题:
BaseTable
功能过于单一, 只能展示数据, 我们实际工作中还有可编辑的表格BaseForm
封装的组件重复代码太多, 判断太多, 很多时候只是组件名字不同, 里面内容完全一致, 比如el-input
和el-switch
; 现在把ElementUI
所有表单元素都写进去了, 但实际上就el-input
用的比较多, 而且可扩展性不好, 想要再加表单, 就需要修改封装的组件的源码
综合以上两个问题我们不难看出目前的主要问题就是这些可编辑的表单元素, 那我们解决了这个问题就可以了呀
只是组件名字不同, 里面内容完全一致
从这句话想到了什么吗? 对! 就是这个 Vue
内置组件: component (opens new window)
我们完全可以去掉那些 v-if
判断, 而只使用 component
即可, 这样可扩展性问题也解决了:
<el-form v-bind="$attrs" :model="model">
<el-form-item v-for="item in formItems" :key="item.prop">
<component :is="item.type" v-model="model[item.prop]" v-bind="item">
<el-option
v-for="option in item.select"
:key="option.value"
:label="option.label"
:value="option.value"
>
</el-option>
<el-radio
v-for="radio in item.radio"
:key="radio[item.value]"
:label="radio[item.value]"
>
{{ radio[item.label] }}
</el-radio>
</component>
</el-form-item>
</el-form>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
是不是一下就简单多了?
那么现在 item.type
就是组件的名字了, 比如 el-input
; 关于 el-select
这类还需要有选择数据的组件(el-option
, el-radio
等), 我目前是全部放到 component
里面的, 一来是不传这些选择数组的时候, 就不会渲染, 二来是就算渲染了, 组件内部 slot
如果不接收, 也不会显示出来, 三呢就是这些组件已经在项目中使用了, 改动的话有点麻烦, 所以如果是新项目, 这些也可以使用 component
那 form
的改完了, table
是不是也可以使用这个呢?
<el-table v-bind="$attrs" v-on="$listeners">
<template v-for="(column, index) in columns">
<el-table-column v-if="column.editable" :key="column.prop" v-bind="column">
<component
slot-scope="{ row }"
:is="column.type"
v-model="row[column.prop]"
v-bind="column"
>
<el-option
v-for="option in column.select"
:key="option.value"
:label="option.label"
:value="option.value"
>
</el-option>
<el-radio
v-for="radio in column.radio"
:key="radio[column.value]"
:label="radio[column.value]"
>
{{ radio[column.label] }}
</el-radio>
</component>
</el-table-column>
<el-table-column v-else :key="column.prop" v-bind="column"> </el-table-column>
</template>
</el-table>
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
一想到这个组件可能被很多地方使用, 那咱们是不是也需要将这个可编辑的组件也封装为一个组件呢?
# 封装可编辑组件
这个就没啥好说的了, 就是把上面的拿下来即可
<component :is="item.type" v-model="model[item.prop]" v-bind="item">
<el-option
v-for="option in item.select"
:key="option.value"
:label="option.label"
:value="option.value"
>
</el-option>
<el-radio
v-for="radio in item.radio"
:key="radio[item.value]"
:label="radio[item.value]"
>
{{ radio[item.label] }}
</el-radio>
</component>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: 'EditableElements',
props: {
item: {
type: Object,
default() {
return {}
}
},
model: {
type: Object,
default() {
return {}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
优化一下:
上面只有 radio
, 其实 checkbox
也是类似的, 所以对于这类组件应该也使用 component
处理, 而且 value
和 label
也需要做一下映射:
<template>
<component
:is="item.component"
v-model="model[item.prop]"
:key="item.prop"
v-bind="item"
v-focus="item.focus"
:placeholder="item.placeholder || `${handlePlaceholder(item.type)}${item.label}`"
v-on="{ ...$listeners, ...item.events }"
>
<!-- 只展示 -->
<text-ellipsis v-if="item.type === 'info'" :content="model[item.prop]"></text-ellipsis>
<!-- 这里由于当初设计的时候以为 el-select 和 el-radio 等会同时存在当前组件中,
所以规定 el-select 的下拉数据放在 select 中, el-radio 的放在 radio 中,
其实 el-select 和 el-radio 等不会同时存在,
为了 兼容以前写法 和 统一数据结构
现在增加一个参数: options, 存放供选择的数据, 所有通用
当然如果是新项目, 可以去掉 || 后面的代码, 统一使用 options -->
<template v-if="item.type === 'select'">
<el-option
v-for="option in item.options || item.select"
:key="option[listProps.value]"
:value="option[listProps.value]"
:label="option[listProps.label]"
:disabled="option.disabled"
></el-option>
</template>
<!-- radio / checkbox 等 -->
<template v-if="list.includes(item.type)">
<component
:is="`el-${item.type}`"
v-for="option in item.options || item[item.type]"
:key="option[listProps.value]"
:label="option[listProps.value]"
:disabled="option.disabled"
>
{{ ele[listProps.label] }}
</component>
</template>
<slot v-for="(value, key) in item.slots" :name="key" :slot="key">{{ value }}</slot>
</component>
</template>
<script>
import { handlePlaceholder } from 'utils'
export default {
name: 'EditableElements',
inheritAttrs: false,
props: {
item: {
type: Object,
default() {
return {}
}
},
model: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
list: ['radio', 'checkbox']
}
},
computed: {
listProps() {
// 这里的 props 和上面的 options 是相同的原因
const props = this.item.props || this.item[`${this.item.type}Props`]
if (!props) return { label: 'label', value: 'value' }
const { label = 'label', value = 'value', ...rest } = props
return {
label,
value,
...rest
}
}
},
methods: {
handlePlaceholder
},
directives: {
focus: {
// [vue v-focus v-show控制input的显示聚焦,第二次不生效问题_JavaScript_宣城-CSDN博客](https://blog.csdn.net/qq_37361812/article/details/93782340)
// [页面一刷新让文本框自动获取焦点-- 和自定义v-focus指令 - 明月人倚楼 - 博客园](https://www.cnblogs.com/IwishIcould/p/12006378.html)
update(el, { value, oldValue }) {
if (value && value !== oldValue) {
// 重点注意这里 当前元素是 div 所以要查到子元素中的 input
const dom = el.querySelector('input') || el.querySelector('textarea')
dom && dom.focus()
}
},
inserted(el, { value }) {
if (value) {
// 重点注意这里 当前元素是 div 所以要查到子元素中的 input
const dom = el.querySelector('input') || el.querySelector('textarea')
dom && dom.focus()
}
}
}
}
}
</script>
<style lang="less" scoped>
.editable-elements {
.el-select {
width: 100%;
}
}
</style>
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
# 终稿
到这里本次封装之旅差不多就结束了, 想想还有点小激动呢!
闲话不多说, 加了亿点点小细节, enjoy
这里是源码: BaseForm (opens new window), BaseTable (opens new window), EditableElements (opens new window)
以下为简略代码:
# form
<el-form v-bind="$attrs" :model="model" ref="elForm" class="base-form">
<slot name="prev"></slot>
<el-form-item
v-for="item in items"
:key="item.prop"
v-bind="item"
:rules="[
{
required: !item.noRequired,
message: item.ruleMessage || `${handlePlaceholder(item.type)}${item.label}`,
trigger: item.type === 'select' ? 'change' : 'blur'
},
...(rules[item.prop] || [])
]"
>
<editable-elements :model="model" :item="item" v-on="$listeners"></editable-elements>
</el-form-item>
<slot></slot>
</el-form>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
props: {
keyProps: {
type: Object,
default() {
return null
}
},
model: {
type: Object,
default() {
return {}
}
},
formItems: {
type: Array,
default() {
return []
}
},
rules: {
type: Object,
default() {
return {}
}
}
},
computed: {
items() {
return this.keyProps
? this.formItems.map(item => ({
...item,
prop: item[this.keyProps.prop || 'prop'],
label: item[this.keyProps.label || 'label']
}))
: this.formItems
}
}
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
# table
<el-table ref="elTable" class="base-table" v-bind="$attrs" v-on="$listeners">
<slot name="prev"></slot>
<template v-for="(column, index) in cols">
<el-table-column v-if="column.editable" :key="column.prop" v-bind="column">
<editable-elements
slot-scope="{ row, $index }"
:model="row"
:item="{ ...column, focus: index === focusCol && $index === focusRow }"
@change="change(row, $event, column)"
></editable-elements>
</el-table-column>
<el-table-column v-else :key="column.prop" v-bind="column"> </el-table-column>
</template>
<slot></slot>
</el-table>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
props: {
keyProps: {
type: Object,
default() {
return null
}
},
columns: {
type: Array,
default() {
return []
}
},
focusRow: {
type: Number,
default: 0
},
focusCol: {
type: Number,
default: 0
}
},
computed: {
cols() {
return this.keyProps
? this.columns.map(column => ({
...column,
prop: column[this.keyProps.prop || 'prop'],
label: column[this.keyProps.label || 'label']
}))
: this.columns
}
}
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
# editable-elements
<template>
<component
:is="item.component"
v-model="model[item.prop]"
:key="item.prop"
v-bind="item"
v-focus="item.focus"
:placeholder="item.placeholder || `${handlePlaceholder(item.type)}${item.label}`"
v-on="{ ...$listeners, ...item.events }"
>
<!-- 只展示 -->
<text-ellipsis v-if="item.type === 'info'" :content="model[item.prop]"></text-ellipsis>
<template v-if="item.type === 'select'">
<el-option
v-for="option in item.options || item.select"
:key="option[listProps.value]"
:value="option[listProps.value]"
:label="option[listProps.label]"
:disabled="option.disabled"
></el-option>
</template>
<!-- radio / checkbox 等 -->
<template v-if="list.includes(item.type)">
<component
:is="`el-${item.type}`"
v-for="option in item.options || item[item.type]"
:key="option[listProps.value]"
:label="option[listProps.value]"
:disabled="option.disabled"
>
{{ option[listProps.label] }}
</component>
</template>
<slot v-for="(value, key) in item.slots" :name="key" :slot="key">{{ value }}</slot>
</component>
</template>
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
# 增加功能
# BaseTable 增加 defaultCheckedKeys 和 currentNodeKey
最近做项目的时候遇到 table
初始化的时候要有默认选中和默认高亮行的需求, 那就参照 el-tree
的方式做一下
本以为和 el-tree
一样, 传入 key
后, el-table
内部会自己处理, 结果 el-table
只能通过传入的 row
来实现默认选中和默认高亮行, 这也不能理解, 毕竟 el-table
中很少用到 row-key
那咱们就通过传入的 key
遍历找到对应的 row
, 当然这种方式就必须传入 row-key
啦:
props: {
data: {
type: Array,
default() {
return []
}
},
defaultCheckedKeys: {
type: Array,
default() {
return []
}
},
currentNodeKey: {
type: [String, Number],
default: ''
}
},
watch: {
data: {
handler() {
this.setDefaultCheckedKeys()
this.setCurrentNodeKey()
},
immediate: true
},
rowKey: {
type: [String, Function],
default: 'id'
},
defaultCheckedKeys: {
handler: 'setDefaultCheckedKeys',
immediate: true
},
currentNodeKey: {
handler: 'setCurrentNodeKey',
immediate: true
}
},
methods: {
// 设置默认选中
setDefaultCheckedKeys() {
this.$nextTick(() => {
if (this.defaultCheckedKeys.length) {
const rows = this.data.filter(item => this.defaultCheckedKeys.includes(item[this.rowKey]))
rows.forEach(row => {
this.$refs.elTable.toggleRowSelection(row, true)
})
} else {
this.$refs.elTable.clearSelection()
}
})
},
setCurrentNodeKey() {
this.$nextTick(() => {
if (this.currentNodeKey) {
const row = this.data.find(item => this.currentNodeKey === item[this.rowKey])
if (row) {
this.$refs.elTable.setCurrentRow(row)
}
} else {
this.$refs.elTable.setCurrentRow(null)
}
})
}
}
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
# 实际工作中发现的问题
# 改变 editable
后页面没变化
最近项目增加了权限控制, 一些可编辑的表格需要先判断权限, 有权限才可以编辑.
所以需要动态修改 editable
的值, 初始为 false
, 后续根据权限改变值.
但发现明明有权限, 却还是不可编辑状态, 而且跟踪代码也发现值确实改变了, 但页面却没有改变
只有触发页面重绘后才会变成可编辑状态, 就好像是状态其实已经改变了, 就差最后的绘制了
这时候突然想到是不是表格复用了, 仍然用的是不可编辑状态的表格: 利用 v-if 动态渲染表格时, 在 el-table-column 中添加 key 属性防止表格复用
查看代码发现果然是复用了: 可编辑和不可编辑用的相同的 key
<el-table-column
v-if="column.editable"
:key="column.prop"
v-bind="column">
</el-table-column>
<el-table-column
v-else
:key="column.prop"
v-bind="column">
</el-table-column>
2
3
4
5
6
7
8
9
10
当初封装组件的时候想着他俩肯定只能存在一个, 用相同的 key
应该没什么问题, 却忘了切换 editable
后, 相同的 key
会复用的问题了
那稍微修改一下吧:
<el-table-column
v-if="column.editable"
:key="`${column.prop}-edit`"
v-bind="column">
</el-table-column>
<el-table-column
v-else
:key="column.prop"
v-bind="column">
</el-table-column>
2
3
4
5
6
7
8
9
10
所以以后尽量还是用不同的 key
吧, 即使是 if else
.
# 可编辑 table 有时候没有滚动条
引起这个问题主要有两个条件:
table
中有可编辑组件- 内容高度刚好超出设置高度一点点
当有可编辑组件时, tr
的高度比不可编辑时高了一点, 而 el-table
还是按不可编辑状态计算高度, 从而认为当前内容并没有超出设置高度, 所以没有出现滚动条
所以解决方法有两种:
要么将 tr
的高度设死, 这样可编辑和不可编辑高度一样, 就可以了; 不过样式可能不算美观
要么让 el-table
重新计算一下高度:
computed: {
isEditable() {
return this.columns.some(item => item.editable || item.editableMethod)
}
},
watch: {
data: {
handler() {
this.setDefaultCheckedKeys()
this.setCurrentNodeKey()
this.refreshLayout()
},
immediate: true
}
},
methods: {
refreshLayout() {
if (!this.isEditable) return
this.$nextTick(() => {
setTimeout(() => {
this.$refs.elTable.doLayout()
}, 200)
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# table 设置 reserve-selection 无效
复现路径: 设置了 reserve-selection
, 没有传入 defaultCheckedKeys
, 选中一个, 上下移并重新获取数据后, 没有选中了
是由于 else
代码引起的:
methods: {
// 设置默认选中
setDefaultCheckedKeys() {
this.$nextTick(() => {
if (this.defaultCheckedKeys.length) {
const rows = this.data.filter(item => this.defaultCheckedKeys.includes(item[this.rowKey]))
rows.forEach(row => {
this.$refs.elTable.toggleRowSelection(row, true)
})
} else {
this.$refs.elTable.clearSelection()
}
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
由于没有传入 defaultCheckedKeys
, 更新 data
数据后, 调用 setDefaultCheckedKeys
方法, 进入 else
判断, 清除所有选中, 导致 reserve-selection
失效
那把 else
干掉就好了
但有时候我确实想清除全部呢? 传入空数组后, 不进 if
判断, 不执行后面的逻辑, 相当于还是维持原样, 并没有达到清除全部的效果
那我们可以定义一个清空的关键字: clear
, 如果 defaultCheckedKeys
等于这个关键字, 就清空
methods: {
// 设置默认选中
setDefaultCheckedKeys() {
this.$nextTick(() => {
if (this.defaultCheckedKeys === 'clear') {
this.$refs.elTable.clearSelection()
return
}
if (this.defaultCheckedKeys.length) {
const rows = this.data.filter(item => this.defaultCheckedKeys.includes(item[this.rowKey]))
rows.forEach(row => {
this.$refs.elTable.toggleRowSelection(row, true)
})
}
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
同理, setCurrentNodeKey
也需要这样处理一下. 不过这个可能会有很小的概率和 row-key
的值相同, 这个就需要酌情处理了. 目前本项目暂无此问题
# BaseTable column 如何获取 row
使用 el-table
我们知道可以通过 slot-scope
获取 row
, 如下所示:
<el-table-column label="日期" width="180">
<template slot-scope="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>
</el-table-column>
2
3
4
5
6
那使用 BaseTable
如何获取 row
呢?
一开始想的是在 EditableElements
里处理, 这里以 disabled
为例:
EditableElements
简略代码:
<component
:is="item.component"
v-model="model[item.prop]"
:key="item.prop"
:disabled="typeof item.disabled === 'function' ? item.disabled(model) : !!item.disabled"
v-bind="item"
v-focus="item.focus"
:placeholder="item.placeholder || `${handlePlaceholder(item.type)}${item.label}`"
v-on="{ ...$listeners, ...item.events }"
></component>
2
3
4
5
6
7
8
9
10
使用方式:
columns: [
{
label: 'label',
prop: 'prop',
component: 'el-switch',
disabled({ canSet }) {
return !canSet
}
}
]
2
3
4
5
6
7
8
9
10
确实是可以了, 但这只是解决了一个 disabled
, 如果后面还有很多需要 row
的字段, 如何处理呢? 总不能挨个加吧
这时突然想到以前遇到的一个 this
问题:
{
label: 'label',
prop: 'prop',
component: 'el-switch',
disabled({ canSet }) {
console.log(this)
return !canSet
}
}
2
3
4
5
6
7
8
9
这里的 this
其实就是上面代码这整个对象, 也就是某一个 column
, 而不是 Vue
, 要想是 Vue
必须用箭头函数, 而不是这种方法简写方式
那咱们把这个 column
和 row
合并一下, this
是不是就都可以取到了呢?
而哪里有这两个数据呢? 必须是 EditableElements
里呀:
props: {
item: {
type: Object,
default() {
return {}
}
},
model: {
type: Object,
default() {
return {}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这里 item
就是 column
, model
就是 row
, 那合并一下是不是就可以啦
EditableElements
简略代码:
computed: {
assignItem() {
const assignItem = {}
for (const key in this.model) {
if (Object.hasOwnProperty.call(this.model, key)) {
assignItem[`_${key}`] = this.model[key]
}
}
return {...this.item, ...assignItem}
}
}
2
3
4
5
6
7
8
9
10
11
<component
:is="item.component"
v-model="model[item.prop]"
:key="item.prop"
v-bind="assignItem"
v-focus="item.focus"
:placeholder="item.placeholder || `${handlePlaceholder(item.type)}${item.label}`"
v-on="{ ...$listeners, ...item.events }"
></component>
2
3
4
5
6
7
8
9
使用方式:
columns: [
{
label: 'label',
prop: 'prop',
component: 'el-switch',
disabled() {
console.log(this)
return !this._canSet
}
}
]
2
3
4
5
6
7
8
9
10
11
这里发现了两个问题:
问题一: this
还是 column
, 而没有 row
, 其实看上面代码也可以发现, 合并只是创建了一个新对象, 而没有对 this.item
改变, 所以没有合并成功
需要改成这样:
computed: {
assignItem() {
const assignItem = {}
for (const key in this.model) {
if (Object.hasOwnProperty.call(this.model, key)) {
assignItem[`_${key}`] = this.model[key]
}
}
return Object.assign(this.item, assignItem)
}
}
2
3
4
5
6
7
8
9
10
11
或这样:
computed: {
assignItem() {
for (const key in this.model) {
if (Object.hasOwnProperty.call(this.model, key)) {
this.item[`_${key}`] = this.model[key]
}
}
return this.item
}
}
2
3
4
5
6
7
8
9
10
结果却出现一个有意思的现象: disabled
中打印的 this
有 _canSet
, 而实际上却是 undefined
.
其实这个问题是老生常谈了, 由于控制台输出的对象当我们点击时, 会取最新值, 但 disabled
调用的时候还没有合并(computed
只有在用到这个值时才执行), 所以还是取的合并前的值, 使用 JSON.stringify()
输出也可以看到是合并前的值
那我们就放到 created
中:
created() {
for (const key in this.model) {
if (Object.hasOwnProperty.call(this.model, key)) {
this.item[`_${key}`] = this.model[key]
}
}
}
2
3
4
5
6
7
这样就可以取得了
问题二: 报错: Invalid prop: type check failed for prop "disabled". Expected Boolean, got Function
人家要布尔值, 咱们却给人传了一个方法, 类型不符了, 那只能将方法变成布尔了
created() {
for (const key in this.model) {
if (Object.hasOwnProperty.call(this.model, key)) {
this.item[`_${key}`] = this.model[key]
}
}
for (const key in this.item) {
if (Object.hasOwnProperty.call(this.item, key)) {
if (typeof this.item[key] === 'function') {
this.item[key] = this.item[key]()
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这样就可以了, 但其实还有很多隐藏的问题: 万一 column
中还有别的方法, 而且这个方法不是立即执行, 或者有参数咋办?
那咱们是不是还要加一个字段, 定义要调用哪些方法
created() {
for (const key in this.model) {
if (Object.hasOwnProperty.call(this.model, key)) {
this.item[`_${key}`] = this.model[key]
}
}
for (const key in this.item) {
if (Object.hasOwnProperty.call(this.item, key)) {
if (
typeof this.item[key] === 'function' &&
this.item.callFun &&
this.item.callFun.includes(key)
) {
this.item[key] = this.item[key]()
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
突然发现, 咱们做了这么多, 不就是要在 column
中拿到 row
吗?
那我们直接在 created
中把 row
传进去不就可以了吗?
created() {
for (const key in this.item) {
if (Object.hasOwnProperty.call(this.item, key)) {
if (
typeof this.item[key] === 'function' &&
this.item.callFun &&
this.item.callFun.includes(key)
) {
this.item[key] = this.item[key](this.model)
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
使用方式:
columns: [
{
label: 'label',
prop: 'prop',
component: 'el-switch',
disabled(row) {
return !row.canSet
}
}
]
2
3
4
5
6
7
8
9
10
这样也不用把 row
挂在 column
上了.
但这只是在初始化的时候处理了, 如果 this.model
改变了, 仍要处理一下, 那只能在 watch
中处理了, 这样也不用在 created
中处理了, watch
设置 immediate: true
即可
但还是会报错: Invalid prop: type check failed for prop "disabled". Expected Boolean, got Function
, 应该是 this.model
改变后, 组件会立刻检查 disabled
, 发现是一个方法, 报错, 同时监听方法触发, disabled
又成了一个布尔值
虽然功能是可以了, 但报错忍受不了, 而且个人感觉这种方式也不太好
目前暂无好方法
- 01
- 搭配 Jenkins 实现自动化打包微前端多个项目09-15
- 02
- 自动化打包微前端多个项目09-15
- 03
- el-upload 直传阿里 oss 并且显示自带进度条和视频回显封面图06-05