Commit db24c0878f51027164b5ba85449135b7b22384a5
1 parent
bc4f9b65
Exists in
master
and in
2 other branches
feat: 修改SchemaTable组件
Showing
11 changed files
with
192 additions
and
968 deletions
Show diff stats
examples/views/docs/component/schema-filter.md
examples/views/docs/component/schema-table.md
| 1 | -# Schema Table 表格 | 1 | +# Schema Table 方案表格 |
| 2 | 2 | ||
| 3 | -根据JSON Schema配置自动生成表格 | 3 | +通过配置JSON Schema的方式快速生成一个表格 |
| 4 | 4 | ||
| 5 | ## 基础用法 | 5 | ## 基础用法 |
| 6 | 6 | ||
| 7 | -配置`list`属性设置JSON Schema配置列表 | 7 | +`schema`设置配置项,其中**props**参数与`z-table`组件参数相同,**items**则对应`z-table`组件的`columns`。 |
| 8 | 8 | ||
| 9 | -::: snippet `tableProps`设置表格参数 | 9 | +::: snippet 本质上是通过`schema`的方式实现生成一个`z-table` |
| 10 | 10 | ||
| 11 | ```html | 11 | ```html |
| 12 | <template> | 12 | <template> |
| 13 | - <z-schema-table v-model="tableData" :list="list" :tableProps="{ border: true }"></z-schema-table> | 13 | + <z-schema-table v-model="model" :schema="schema"></z-schema-table> |
| 14 | </template> | 14 | </template> |
| 15 | 15 | ||
| 16 | <script> | 16 | <script> |
| 17 | export default { | 17 | export default { |
| 18 | data() { | 18 | data() { |
| 19 | return { | 19 | return { |
| 20 | - tableData: [ | 20 | + model: [ |
| 21 | { name: '张三', age: 16 }, | 21 | { name: '张三', age: 16 }, |
| 22 | { name: '李四', age: 24 } | 22 | { name: '李四', age: 24 } |
| 23 | ], | 23 | ], |
| 24 | - list: [ | ||
| 25 | - { label: '姓名', key: 'name' }, | ||
| 26 | - { label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 27 | - ] | 24 | + schema: { |
| 25 | + items: [ | ||
| 26 | + { label: '姓名', prop: 'name' }, | ||
| 27 | + { label: '年龄', prop: 'age' }, | ||
| 28 | + ] | ||
| 29 | + } | ||
| 28 | } | 30 | } |
| 29 | } | 31 | } |
| 30 | } | 32 | } |
| @@ -33,17 +35,20 @@ export default { | @@ -33,17 +35,20 @@ export default { | ||
| 33 | 35 | ||
| 34 | ::: | 36 | ::: |
| 35 | 37 | ||
| 36 | -## 自定义渲染 | ||
| 37 | - | ||
| 38 | -配置`list`属性设置JSON Schema配置列表 | 38 | +## 自定义列 |
| 39 | 39 | ||
| 40 | -<div class="code-snippet-box"> | 40 | +支持自定义列的内容 |
| 41 | 41 | ||
| 42 | -::: snippet `render`函数式渲染 | 42 | +::: snippet 插槽`header-列字段名`可自定义表头的内容,插槽`cell-列字段名`可自定义列单元格的内容,用法与`z-table`相同。 |
| 43 | 43 | ||
| 44 | ```html | 44 | ```html |
| 45 | <template> | 45 | <template> |
| 46 | - <z-schema-table v-model="tableData" :list="list"></z-schema-table> | 46 | + <z-schema-table v-model="tableData" :schema="schema"> |
| 47 | + <template #header-age>年龄 <i class="el-icon-question"></i></template> | ||
| 48 | + <template #cell-gender="{ value }"> | ||
| 49 | + <el-tag size="mini">{{ value }}</el-tag> | ||
| 50 | + </template> | ||
| 51 | + </z-schema-table> | ||
| 47 | </template> | 52 | </template> |
| 48 | 53 | ||
| 49 | <script> | 54 | <script> |
| @@ -51,30 +56,35 @@ export default { | @@ -51,30 +56,35 @@ export default { | ||
| 51 | data() { | 56 | data() { |
| 52 | return { | 57 | return { |
| 53 | tableData: [ | 58 | tableData: [ |
| 54 | - { name: '张三', age: 16 }, | ||
| 55 | - { name: '李四', age: 24 } | 59 | + { name: '张三', age: '31', gender: '男' }, |
| 60 | + { name: '李四', age: '27', gender: '女' }, | ||
| 61 | + { name: '王五', age: '16', gender: '男' }, | ||
| 56 | ], | 62 | ], |
| 57 | - list: [ | ||
| 58 | - { label: '姓名', key: 'name', render: (h, { value }) => { return h('span', { style: { background: 'rgba(255, 0, 0, 0.5)', color: '#fff' } }, value) } }, | ||
| 59 | - { label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 60 | - ] | ||
| 61 | - } | ||
| 62 | - } | 63 | + schema: { |
| 64 | + props: { border: true, size: 'mini' }, | ||
| 65 | + items: [ | ||
| 66 | + { prop: 'name', label: '姓名' }, | ||
| 67 | + { prop: 'age', label: '年龄' }, | ||
| 68 | + { prop: 'gender', label: '性别' }, | ||
| 69 | + ] | ||
| 70 | + } | ||
| 71 | + }; | ||
| 72 | + }, | ||
| 63 | } | 73 | } |
| 64 | </script> | 74 | </script> |
| 65 | ``` | 75 | ``` |
| 66 | 76 | ||
| 67 | ::: | 77 | ::: |
| 68 | 78 | ||
| 69 | -::: snippet `slot`插槽式渲染 | 79 | +## 列渲染 |
| 80 | + | ||
| 81 | +除了使用插槽自定义列的内容之外,也支持直接在配置项中写渲染函数 | ||
| 82 | + | ||
| 83 | +::: snippet 配置项中的`render`可以设置对应列单元格的渲染 | ||
| 70 | 84 | ||
| 71 | ```html | 85 | ```html |
| 72 | <template> | 86 | <template> |
| 73 | - <z-schema-table v-model="tableData" :list="list"> | ||
| 74 | - <template #value-age="{ value }"> | ||
| 75 | - <el-tag v-if="value" :type="value > 16 ? 'success' : 'danger'" size="mini" disable-transitions>{{ value }}</el-tag> | ||
| 76 | - </template> | ||
| 77 | - </z-schema-table> | 87 | + <z-schema-table v-model="tableData" :schema="schema"></z-schema-table> |
| 78 | </template> | 88 | </template> |
| 79 | 89 | ||
| 80 | <script> | 90 | <script> |
| @@ -82,32 +92,50 @@ export default { | @@ -82,32 +92,50 @@ export default { | ||
| 82 | data() { | 92 | data() { |
| 83 | return { | 93 | return { |
| 84 | tableData: [ | 94 | tableData: [ |
| 85 | - { name: '张三', age: 16 }, | ||
| 86 | - { name: '李四', age: 24 } | 95 | + { name: '张三', age: '31', gender: '男', ticket: true }, |
| 96 | + { name: '李四', age: '27', gender: '女', ticket: false }, | ||
| 97 | + { name: '王五', age: '16', gender: '男', ticket: false }, | ||
| 87 | ], | 98 | ], |
| 88 | - list: [ | ||
| 89 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 90 | - { type: 'el-input-number', label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 91 | - ] | ||
| 92 | - } | ||
| 93 | - } | 99 | + schema: { |
| 100 | + props: { border: true, size: 'mini' }, | ||
| 101 | + items: [ | ||
| 102 | + { prop: 'name', label: '姓名' }, | ||
| 103 | + { prop: 'age', label: '年龄', render: (value, row, h) => h(Number(value) > 20 ? 'b' : 'span', { style: { color: Number(value) > 20 ? 'red' : 'green' } }, value) }, | ||
| 104 | + { prop: 'gender', label: '性别', render: (value, row, h) => h('el-tag', { props: { size: 'mini', type: 'info' } }, value) }, | ||
| 105 | + { prop: 'ticket', label: '开票', render: value => value ? '是' : '否' }, | ||
| 106 | + ] | ||
| 107 | + } | ||
| 108 | + }; | ||
| 109 | + }, | ||
| 94 | } | 110 | } |
| 95 | </script> | 111 | </script> |
| 96 | ``` | 112 | ``` |
| 97 | 113 | ||
| 98 | ::: | 114 | ::: |
| 99 | 115 | ||
| 100 | -</div> | 116 | +## 追加列 |
| 101 | 117 | ||
| 102 | -## 层级分组 | 118 | +使用配置项时,**新增的列**则默认追加在**配置项列**之后,使用`left`插槽可在表格的最左侧插入列,顺序在**配置项列**之前 |
| 103 | 119 | ||
| 104 | -支持`group`分组,兼容form组件,使同一个schema能够同时复用在`table`、`form`上 | ||
| 105 | - | ||
| 106 | -::: snippet `group`设置分组,其中`key`设置分组对象名称 | 120 | +::: snippet 用法与`z-table`相同 |
| 107 | 121 | ||
| 108 | ```html | 122 | ```html |
| 109 | <template> | 123 | <template> |
| 110 | - <z-schema-table v-model="tableData" :list="list" :tableProps="{ border: true }"></z-schema-table> | 124 | + <z-schema-table v-model="tableData" :schema="schema"> |
| 125 | + <template #left> | ||
| 126 | + <el-table-column type="selection" width="40"></el-table-column> | ||
| 127 | + <el-table-column label="左侧的列"> | ||
| 128 | + <template #default="{ $index }"> | ||
| 129 | + 内容 {{ $index }} | ||
| 130 | + </template> | ||
| 131 | + </el-table-column> | ||
| 132 | + </template> | ||
| 133 | + <el-table-column label="操作" width="120"> | ||
| 134 | + <template #default="{ $index }"> | ||
| 135 | + <el-button type="text">编辑第{{ $index + 1 }}行</el-button> | ||
| 136 | + </template> | ||
| 137 | + </el-table-column> | ||
| 138 | + </z-schema-table> | ||
| 111 | </template> | 139 | </template> |
| 112 | 140 | ||
| 113 | <script> | 141 | <script> |
| @@ -115,22 +143,20 @@ export default { | @@ -115,22 +143,20 @@ export default { | ||
| 115 | data() { | 143 | data() { |
| 116 | return { | 144 | return { |
| 117 | tableData: [ | 145 | tableData: [ |
| 118 | - { name: '张三', age: 16, location: { city: '上海', address: '青浦区' } }, | ||
| 119 | - { name: '李四', age: 24, location: { city: '北京', address: '朝阳区' } } | 146 | + { name: '张三', age: '31', gender: '男' }, |
| 147 | + { name: '李四', age: '27', gender: '女' }, | ||
| 148 | + { name: '王五', age: '16', gender: '男' }, | ||
| 120 | ], | 149 | ], |
| 121 | - list: [ | ||
| 122 | - { label: '姓名', key: 'name' }, | ||
| 123 | - { label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 124 | - { | ||
| 125 | - group: { key: 'location' }, | ||
| 126 | - list: [ | ||
| 127 | - { label: '城市', key: 'city' }, | ||
| 128 | - ] | ||
| 129 | - }, | ||
| 130 | - { label: '地址', key: 'location.address' }, | ||
| 131 | - ] | ||
| 132 | - } | ||
| 133 | - } | 150 | + schema: { |
| 151 | + props: { size: 'mini', border: true } , | ||
| 152 | + items: [ | ||
| 153 | + { prop: 'name', label: '姓名' }, | ||
| 154 | + { prop: 'age', label: '年龄' }, | ||
| 155 | + { prop: 'gender', label: '性别' }, | ||
| 156 | + ] | ||
| 157 | + } | ||
| 158 | + }; | ||
| 159 | + }, | ||
| 134 | } | 160 | } |
| 135 | </script> | 161 | </script> |
| 136 | ``` | 162 | ``` |
| @@ -144,10 +170,7 @@ export default { | @@ -144,10 +170,7 @@ export default { | ||
| 144 | 参数|说明|类型|可选值|默认值 | 170 | 参数|说明|类型|可选值|默认值 |
| 145 | -|-|-|-|- | 171 | -|-|-|-|- |
| 146 | value | 表格数据 | Array | - | - | 172 | value | 表格数据 | Array | - | - |
| 147 | -list | JSON Schema配置项列表 | Array | - | [] | ||
| 148 | -tableProps | 表格参数 | Object | - | - | ||
| 149 | -tableEvents | 表格参数 | Object | - | - | ||
| 150 | -minWidth | 列宽 | Number | - | - | 173 | +schema | JSON Schema配置项列表 | Array | - | [] |
| 151 | 174 | ||
| 152 | ## Events 事件 | 175 | ## Events 事件 |
| 153 | 176 |
examples/views/docs/component/table.md
| @@ -132,15 +132,19 @@ export default { | @@ -132,15 +132,19 @@ export default { | ||
| 132 | <div> | 132 | <div> |
| 133 | <z-form span="6"> | 133 | <z-form span="6"> |
| 134 | <z-form-item>编辑模式:<el-switch v-model="editable"></el-switch></z-form-item> | 134 | <z-form-item>编辑模式:<el-switch v-model="editable"></el-switch></z-form-item> |
| 135 | + <z-form-item>编辑全部:<el-switch v-model="editall"></el-switch></z-form-item> | ||
| 135 | <z-form-item>双击编辑:<el-switch v-model="clickable"></el-switch></z-form-item> | 136 | <z-form-item>双击编辑:<el-switch v-model="clickable"></el-switch></z-form-item> |
| 136 | </z-form> | 137 | </z-form> |
| 137 | - <z-table v-model="tableData" :editable="editable" :columns="columns" border :clickable="clickable" @cell-edit-confirm="onCellEditConfirm"> | 138 | + <z-table v-model="tableData" size="mini" :editable="editable" :columns="columns" border :clickable="clickable" @cell-edit-confirm="onCellEditConfirm" :editall="editall"> |
| 138 | <template #editor-gender="{ value, onInput, index }"> | 139 | <template #editor-gender="{ value, onInput, index }"> |
| 139 | <el-radio-group size="mini" :value="value" @input="onInput"> | 140 | <el-radio-group size="mini" :value="value" @input="onInput"> |
| 140 | <el-radio-button label="男">男</el-radio-button> | 141 | <el-radio-button label="男">男</el-radio-button> |
| 141 | <el-radio-button label="女">女</el-radio-button> | 142 | <el-radio-button label="女">女</el-radio-button> |
| 142 | </el-radio-group> | 143 | </el-radio-group> |
| 143 | </template> | 144 | </template> |
| 145 | + <template #cell-gender="{ value }"> | ||
| 146 | + <el-tag size="mini" type="info" disable-transitions>{{ value }}</el-tag> | ||
| 147 | + </template> | ||
| 144 | <el-table-column label="操作" width="80"> | 148 | <el-table-column label="操作" width="80"> |
| 145 | <template #default="{ $index }"> | 149 | <template #default="{ $index }"> |
| 146 | <el-button type="text" @click="deleteRow($index)">删除</el-button> | 150 | <el-button type="text" @click="deleteRow($index)">删除</el-button> |
| @@ -154,6 +158,7 @@ export default { | @@ -154,6 +158,7 @@ export default { | ||
| 154 | export default { | 158 | export default { |
| 155 | data() { | 159 | data() { |
| 156 | return { | 160 | return { |
| 161 | + editall: false, | ||
| 157 | editable: true, | 162 | editable: true, |
| 158 | clickable: true, | 163 | clickable: true, |
| 159 | tableData: [ | 164 | tableData: [ |
packages/schema-table/cell-editable.vue
| @@ -1,90 +0,0 @@ | @@ -1,90 +0,0 @@ | ||
| 1 | -<style> | ||
| 2 | -.z-table-cell-editable { | ||
| 3 | - display: flex; | ||
| 4 | - align-items: center; | ||
| 5 | -} | ||
| 6 | -.z-table-cell-editable .z-table-cell-editable__icon { | ||
| 7 | - cursor: pointer; | ||
| 8 | - vertical-align: middle; | ||
| 9 | - padding-left: 5px; | ||
| 10 | - fill: #2f54eb; | ||
| 11 | -} | ||
| 12 | -</style> | ||
| 13 | - | ||
| 14 | -<template> | ||
| 15 | - <div> | ||
| 16 | - <!-- 可编辑状态 --> | ||
| 17 | - <div v-if="editable" class="z-table-cell-editable"> | ||
| 18 | - <component :value="$_get(row, item.fullKey)" :is="item.type" v-bind="item.props" :style="item.style" size="mini" @input="onInput"></component> | ||
| 19 | - <span v-if="btnVisible !== false" @click="onConfirm"> | ||
| 20 | - <svg class="z-table-cell-editable__icon" viewBox="0 0 1024 1024" width="24" height="24"> | ||
| 21 | - <path d="M235.946667 472.938667l-45.226667 45.312 210.090667 209.514666 432.362666-427.690666-45.013333-45.482667-387.157333 382.976z"></path> | ||
| 22 | - </svg> | ||
| 23 | - </span> | ||
| 24 | - </div> | ||
| 25 | - <!-- 渲染状态 --> | ||
| 26 | - <template v-else> | ||
| 27 | - <!-- 渲染插槽 --> | ||
| 28 | - <template v-if="$scopedSlots['default']"> | ||
| 29 | - <slot></slot> | ||
| 30 | - </template> | ||
| 31 | - <!-- 默认渲染 --> | ||
| 32 | - <template v-else> | ||
| 33 | - {{ $_get(row, item.agentKey || item.fullKey) }} | ||
| 34 | - </template> | ||
| 35 | - </template> | ||
| 36 | - </div> | ||
| 37 | -</template> | ||
| 38 | - | ||
| 39 | -<script> | ||
| 40 | -import { get } from '../utils'; | ||
| 41 | - | ||
| 42 | -export default { | ||
| 43 | - name: 'cellEditable', | ||
| 44 | - props: { | ||
| 45 | - row: Object, | ||
| 46 | - item: Object, | ||
| 47 | - editable: Boolean, | ||
| 48 | - btnVisible: Boolean, | ||
| 49 | - }, | ||
| 50 | - data() { | ||
| 51 | - return { | ||
| 52 | - oldValue: undefined, | ||
| 53 | - value: undefined, | ||
| 54 | - confirm: false, | ||
| 55 | - }; | ||
| 56 | - }, | ||
| 57 | - watch: { | ||
| 58 | - editable(val) { | ||
| 59 | - if (val) { | ||
| 60 | - this.confirm = false; | ||
| 61 | - this.oldValue = get(this.row, this.item.agentKey || this.item.fullKey); | ||
| 62 | - this.value = get(this.row, this.item.agentKey || this.item.fullKey); | ||
| 63 | - } else { | ||
| 64 | - if (!this.confirm) { | ||
| 65 | - this.$emit('cancel', this.emitData); | ||
| 66 | - } | ||
| 67 | - } | ||
| 68 | - }, | ||
| 69 | - }, | ||
| 70 | - computed: { | ||
| 71 | - emitData() { | ||
| 72 | - const { oldValue, value, row, item } = this; | ||
| 73 | - return { oldValue, value, row, key: item.key, fullKey: item.fullKey }; | ||
| 74 | - }, | ||
| 75 | - }, | ||
| 76 | - methods: { | ||
| 77 | - $_get: get, | ||
| 78 | - // 组件触发input事件 | ||
| 79 | - onInput(value) { | ||
| 80 | - this.value = value; | ||
| 81 | - this.$emit('update', this.emitData); | ||
| 82 | - }, | ||
| 83 | - // 当点击确认 | ||
| 84 | - onConfirm() { | ||
| 85 | - this.confirm = true; | ||
| 86 | - this.$emit('done', this.emitData); | ||
| 87 | - }, | ||
| 88 | - }, | ||
| 89 | -}; | ||
| 90 | -</script> |
packages/schema-table/cell-value-render.js
| @@ -1,20 +0,0 @@ | @@ -1,20 +0,0 @@ | ||
| 1 | -import { get } from '../utils'; | ||
| 2 | - | ||
| 3 | -export default { | ||
| 4 | - props: { row: Object, column: Object, index: [Number, String], item: Object }, | ||
| 5 | - render(h) { | ||
| 6 | - const { row, column, index, item } = this; | ||
| 7 | - if (typeof item.render === 'function') { | ||
| 8 | - return item.render(h, { row, value: get(row, item.fullKey), $index: index, column }); | ||
| 9 | - } else { | ||
| 10 | - if (item.render.children instanceof Function) { | ||
| 11 | - return h( | ||
| 12 | - item.render.type, | ||
| 13 | - { props: item.render.props, attrs: item.render.props, style: item.render.style }, | ||
| 14 | - item.render.children({ row, value: get(row, item.fullKey), $index: index, column }), | ||
| 15 | - ); | ||
| 16 | - } | ||
| 17 | - return h(item.render.type, { props: item.render.props, attrs: item.render.props, style: item.render.style }, item.render.children || get(row, item.fullKey)); | ||
| 18 | - } | ||
| 19 | - }, | ||
| 20 | -}; |
packages/schema-table/editable.vue
| @@ -1,319 +0,0 @@ | @@ -1,319 +0,0 @@ | ||
| 1 | -<style> | ||
| 2 | -.z-table { | ||
| 3 | - width: 100%; | ||
| 4 | -} | ||
| 5 | -</style> | ||
| 6 | - | ||
| 7 | -<template> | ||
| 8 | - <div @keyup.enter="tableEditCell = {}"> | ||
| 9 | - <div style="padding-bottom: 10px;"> | ||
| 10 | - <el-button @click="handleNew" size="mini" v-if="!rowEdit">新增</el-button> | ||
| 11 | - <el-button @click="handleEdit" size="mini" v-if="!rowEditable && tableSelection.length > 0">编辑</el-button> | ||
| 12 | - <el-button @click="handleSave" size="mini" type="primary" v-if="rowEditable">完成</el-button> | ||
| 13 | - <el-button @click="handleCancel" size="mini" type="plain" v-if="rowNew && !rowEdit">取消</el-button> | ||
| 14 | - <el-button @click="handleDelete" size="mini" type="danger" v-if="tableSelection.length > 0">删除</el-button> | ||
| 15 | - </div> | ||
| 16 | - <el-table | ||
| 17 | - class="z-table" | ||
| 18 | - ref="table" | ||
| 19 | - :data="tableData" | ||
| 20 | - v-bind="{ size: 'small', ...tableProps }" | ||
| 21 | - v-on="tableEvents" | ||
| 22 | - @cell-dblclick="onCellDblclick" | ||
| 23 | - @selection-change="onSelectionChange" | ||
| 24 | - > | ||
| 25 | - <el-table-column type="selection" :selectable="r => (rowNew ? r.$new : true)"></el-table-column> | ||
| 26 | - <!-- 默认表格插槽 --> | ||
| 27 | - <slot name="default"></slot> | ||
| 28 | - <!-- 根据配置列表生成表格列 --> | ||
| 29 | - <template v-if="tableList && tableList.length > 0"> | ||
| 30 | - <template v-for="(item, index) in tableList"> | ||
| 31 | - <template v-if="bindItemVisible(item.visible)"> | ||
| 32 | - <!-- 如果有表格列具名插槽 --> | ||
| 33 | - <slot v-if="$scopedSlots[item.keyPath.join('-')]" :name="item.keyPath.join('-')" v-bind="item"></slot> | ||
| 34 | - <!-- 默认表格列渲染 --> | ||
| 35 | - <el-table-column v-else v-bind="item" :prop="item.fullKey || item.key" :key="index" :min-width="minWidth || item.minWidth || item['min-width'] || (editable ? 140 : undefined)"> | ||
| 36 | - <template #default="{ row, column, $index }"> | ||
| 37 | - <cell-editable | ||
| 38 | - :editable="row.$editable || (editable && tableEditCell.index === row.$index && tableEditCell.key === (item.agentKey || item.fullKey || item.key))" | ||
| 39 | - :row="row" | ||
| 40 | - :item="item" | ||
| 41 | - @update="onCellUpdate" | ||
| 42 | - @done="onCellUpdateDone" | ||
| 43 | - @cancel="onCellUpdateCancel" | ||
| 44 | - :btn-visible="!row.$editable" | ||
| 45 | - > | ||
| 46 | - <!-- 如果有表格列值渲染具名插槽 --> | ||
| 47 | - <slot | ||
| 48 | - v-if="$scopedSlots[`value-${item.keyPath.join('-')}`]" | ||
| 49 | - :name="`value-${item.keyPath.join('-')}`" | ||
| 50 | - v-bind="item" | ||
| 51 | - :row="row" | ||
| 52 | - :value="$_get(row, item.fullKey)" | ||
| 53 | - :column="column" | ||
| 54 | - :index="$index" | ||
| 55 | - ></slot> | ||
| 56 | - <!-- 如果表格列配置了值渲染参数 --> | ||
| 57 | - <cell-value-render v-else-if="item.render" :row="row" :column="column" :index="$index" :item="item" /> | ||
| 58 | - </cell-editable> | ||
| 59 | - </template> | ||
| 60 | - </el-table-column> | ||
| 61 | - </template> | ||
| 62 | - </template> | ||
| 63 | - </template> | ||
| 64 | - <!-- 已生成列表后的追加列插槽 --> | ||
| 65 | - <slot name="column-append"></slot> | ||
| 66 | - <!-- 末尾列插槽 --> | ||
| 67 | - <slot name="column-end"></slot> | ||
| 68 | - </el-table> | ||
| 69 | - </div> | ||
| 70 | -</template> | ||
| 71 | - | ||
| 72 | -<script> | ||
| 73 | -import { cloneDeep, get, set } from '../utils'; | ||
| 74 | -import CellEditable from './cell-editable'; | ||
| 75 | -import CellValueRender from './cell-value-render'; | ||
| 76 | - | ||
| 77 | -const listHasKey = (list, name) => { | ||
| 78 | - let result = false; | ||
| 79 | - for (const row of list) { | ||
| 80 | - if (row[name]) { | ||
| 81 | - result = true; | ||
| 82 | - break; | ||
| 83 | - } | ||
| 84 | - } | ||
| 85 | - return result; | ||
| 86 | -}; | ||
| 87 | - | ||
| 88 | -export default { | ||
| 89 | - name: 'TableNew', | ||
| 90 | - components: { | ||
| 91 | - CellEditable, | ||
| 92 | - CellValueRender, | ||
| 93 | - }, | ||
| 94 | - props: { | ||
| 95 | - // 用于实例化本组件绑定v-model的值 | ||
| 96 | - value: Array, | ||
| 97 | - // 配置列表 | ||
| 98 | - list: { | ||
| 99 | - type: Array, | ||
| 100 | - required: true, | ||
| 101 | - }, | ||
| 102 | - // 表格参数 | ||
| 103 | - tableProps: { | ||
| 104 | - type: Object, | ||
| 105 | - default() { | ||
| 106 | - return {}; | ||
| 107 | - }, | ||
| 108 | - }, | ||
| 109 | - // 表格事件 | ||
| 110 | - tableEvents: Object, | ||
| 111 | - // 是否可编辑 | ||
| 112 | - editable: Boolean, | ||
| 113 | - // 列宽 | ||
| 114 | - minWidth: Number, | ||
| 115 | - }, | ||
| 116 | - data() { | ||
| 117 | - return { | ||
| 118 | - tableList: [], // 表格配置列表 | ||
| 119 | - tableData: [], // 表格数据 | ||
| 120 | - tableRowTemplate: { $editable: true }, // 行数据模板 | ||
| 121 | - tableEditCell: {}, // 正在编辑的单元格 | ||
| 122 | - tableSelection: [], // 表格已选中 | ||
| 123 | - }; | ||
| 124 | - }, | ||
| 125 | - computed: { | ||
| 126 | - // 表格实体 | ||
| 127 | - instance: { | ||
| 128 | - get() { | ||
| 129 | - return this.$refs.table; | ||
| 130 | - }, | ||
| 131 | - }, | ||
| 132 | - // 行编辑状态 | ||
| 133 | - rowEditable() { | ||
| 134 | - return listHasKey(this.tableData, '$editable'); | ||
| 135 | - }, | ||
| 136 | - // 存在新行 | ||
| 137 | - rowNew() { | ||
| 138 | - return listHasKey(this.tableData, '$new'); | ||
| 139 | - }, | ||
| 140 | - // 存在编辑行 | ||
| 141 | - rowEdit() { | ||
| 142 | - return listHasKey(this.tableData, '$edit'); | ||
| 143 | - }, | ||
| 144 | - }, | ||
| 145 | - watch: { | ||
| 146 | - value: { | ||
| 147 | - handler(val = []) { | ||
| 148 | - this.tableData = val.map((o, i) => { | ||
| 149 | - return { ...o, $index: i, $editable: undefined, $new: undefined }; | ||
| 150 | - }); | ||
| 151 | - }, | ||
| 152 | - immediate: true, | ||
| 153 | - }, | ||
| 154 | - list: { | ||
| 155 | - handler(val) { | ||
| 156 | - // 深度克隆传入的列表,避免原始值被修改 | ||
| 157 | - const newList = cloneDeep(this.list); | ||
| 158 | - // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key | ||
| 159 | - const generateFullKey = (list, parentKey) => { | ||
| 160 | - list.forEach(item => { | ||
| 161 | - if (item.group && item.list) { | ||
| 162 | - if (item.group.key) { | ||
| 163 | - item.fullKey = `${parentKey ? `${parentKey}.${item.group.key}` : item.group.key}`; | ||
| 164 | - } else { | ||
| 165 | - item.fullKey = parentKey || item.key; | ||
| 166 | - } | ||
| 167 | - generateFullKey(item.list, item.fullKey); | ||
| 168 | - } else { | ||
| 169 | - item.fullKey = `${parentKey ? `${parentKey}.${item.key}` : item.key}`; | ||
| 170 | - } | ||
| 171 | - }); | ||
| 172 | - }; | ||
| 173 | - // 生成fullKey | ||
| 174 | - generateFullKey(newList); | ||
| 175 | - // 创建输出列表 | ||
| 176 | - const result = []; | ||
| 177 | - const tableRowTemplate = {}; | ||
| 178 | - // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key | ||
| 179 | - const generateFlatList = list => { | ||
| 180 | - list.forEach(item => { | ||
| 181 | - if (item.group && item.list) { | ||
| 182 | - generateFlatList(item.list); | ||
| 183 | - } else if (!item.group && !item.list) { | ||
| 184 | - result.push({ ...item, keyPath: item.fullKey.split('.') }); | ||
| 185 | - set(tableRowTemplate, item.fullKey, undefined); | ||
| 186 | - tableRowTemplate.$editable = true; | ||
| 187 | - } | ||
| 188 | - }); | ||
| 189 | - }; | ||
| 190 | - generateFlatList(newList); | ||
| 191 | - this.tableList = result; | ||
| 192 | - this.tableRowTemplate = tableRowTemplate; | ||
| 193 | - }, | ||
| 194 | - immediate: true, | ||
| 195 | - }, | ||
| 196 | - }, | ||
| 197 | - methods: { | ||
| 198 | - $_get: get, | ||
| 199 | - // 处理新增逻辑 | ||
| 200 | - handleNew() { | ||
| 201 | - const tableData = cloneDeep(this.tableData); | ||
| 202 | - tableData.push({ ...this.tableRowTemplate, $new: true }); | ||
| 203 | - this.tableEditCell = {}; | ||
| 204 | - this.tableData = tableData.map((o, i) => ({ ...o, $index: i })); | ||
| 205 | - }, | ||
| 206 | - // 处理编辑逻辑 | ||
| 207 | - handleEdit() { | ||
| 208 | - const tableData = cloneDeep(this.tableData); | ||
| 209 | - const selectionIndexArr = this.tableSelection.map(i => i.$index); | ||
| 210 | - tableData.forEach((r, i) => { | ||
| 211 | - if (selectionIndexArr.includes(r.$index)) { | ||
| 212 | - tableData[i].$editable = true; | ||
| 213 | - tableData[i].$edit = true; | ||
| 214 | - tableData[i].$new = true; | ||
| 215 | - } | ||
| 216 | - }); | ||
| 217 | - this.tableEditCell = {}; | ||
| 218 | - this.tableData = tableData.map((o, i) => ({ ...o, $index: i })); | ||
| 219 | - }, | ||
| 220 | - // 处理保存逻辑 | ||
| 221 | - handleSave() { | ||
| 222 | - const tableData = cloneDeep(this.tableData); | ||
| 223 | - tableData.forEach((r, i) => { | ||
| 224 | - delete tableData[i].$editable; | ||
| 225 | - }); | ||
| 226 | - this.tableEditCell = {}; | ||
| 227 | - this.tableData = tableData.map((o, i) => ({ ...o, $index: i })); | ||
| 228 | - this.$nextTick(() => { | ||
| 229 | - this.emitTableData(this.tableData); | ||
| 230 | - this.$emit( | ||
| 231 | - this.rowEdit ? 'row-edit' : 'row-new', | ||
| 232 | - this.tableData | ||
| 233 | - .filter(d => d.$new) | ||
| 234 | - .map(d => { | ||
| 235 | - delete d.$new; | ||
| 236 | - return d; | ||
| 237 | - }), | ||
| 238 | - ); | ||
| 239 | - }); | ||
| 240 | - }, | ||
| 241 | - // 处理取消逻辑 | ||
| 242 | - handleCancel() { | ||
| 243 | - let tableData = cloneDeep(this.tableData); | ||
| 244 | - tableData = tableData.filter(row => !row.$new); | ||
| 245 | - this.emitTableData(tableData); | ||
| 246 | - }, | ||
| 247 | - // 处理删除逻辑 | ||
| 248 | - handleDelete() { | ||
| 249 | - const tableData = cloneDeep(this.tableData); | ||
| 250 | - const selectionIndexArr = this.tableSelection.map(i => i.$index); | ||
| 251 | - if (!this.rowNew && !this.rowEdit) { | ||
| 252 | - this.$emit('row-delete', this.tableSelection); | ||
| 253 | - } | ||
| 254 | - this.tableEditCell = {}; | ||
| 255 | - this.tableData = tableData.filter((d, i) => !selectionIndexArr.includes(i)).map((o, i) => ({ ...o, $index: i })); | ||
| 256 | - }, | ||
| 257 | - // 更新表格数据 | ||
| 258 | - emitTableData(tableData) { | ||
| 259 | - if (this.$listeners['input']) { | ||
| 260 | - this.$emit( | ||
| 261 | - 'input', | ||
| 262 | - tableData.map((o, i) => { | ||
| 263 | - return { ...o, $index: i, $editable: undefined, $new: undefined, $edit: undefined }; | ||
| 264 | - }), | ||
| 265 | - ); | ||
| 266 | - } else { | ||
| 267 | - this.tableData = tableData; | ||
| 268 | - } | ||
| 269 | - }, | ||
| 270 | - // 绑定表格列显示隐藏状态 | ||
| 271 | - bindItemVisible(visible = true) { | ||
| 272 | - let result = visible; | ||
| 273 | - if (typeof visible === 'function') { | ||
| 274 | - result = visible(this.tableData); | ||
| 275 | - } | ||
| 276 | - return result; | ||
| 277 | - }, | ||
| 278 | - // 双击单元格 | ||
| 279 | - onCellDblclick(row, column, cell, event) { | ||
| 280 | - if (this.editable && !this.rowNew) { | ||
| 281 | - this.tableEditCell = { index: row.$index, key: column.property }; | ||
| 282 | - } | ||
| 283 | - }, | ||
| 284 | - // 编辑表格更新值 | ||
| 285 | - onCellUpdate({ oldValue, value, row, key, fullKey }) { | ||
| 286 | - this.setCellValue({ value, row, key, fullKey }); | ||
| 287 | - }, | ||
| 288 | - // 编辑表格确认 | ||
| 289 | - onCellUpdateDone({ oldValue, value, row, key, fullKey }) { | ||
| 290 | - this.tableEditCell = {}; | ||
| 291 | - if (this.$listeners['cell-edit']) { | ||
| 292 | - const { tableData } = this.setCellValue({ value, row, key, fullKey }); | ||
| 293 | - this.emitTableData(tableData); | ||
| 294 | - this.$emit('cell-edit', { row, key, fullKey, value }); | ||
| 295 | - } | ||
| 296 | - }, | ||
| 297 | - // 表格取消编辑 | ||
| 298 | - onCellUpdateCancel({ oldValue, value, row, key, fullKey }) { | ||
| 299 | - if (row.$new !== true) { | ||
| 300 | - this.setCellValue({ value: oldValue, row, key, fullKey }); | ||
| 301 | - } | ||
| 302 | - }, | ||
| 303 | - // 设置表格值 | ||
| 304 | - setCellValue({ value, row, key, fullKey }) { | ||
| 305 | - const tableData = cloneDeep(this.tableData); | ||
| 306 | - const tableRow = tableData[row.$index]; | ||
| 307 | - set(tableRow, fullKey, value); | ||
| 308 | - tableData[row.$index] = tableRow; | ||
| 309 | - this.$set(this.tableData, row.$index, tableRow); | ||
| 310 | - return { tableData, tableRow }; | ||
| 311 | - }, | ||
| 312 | - // 表格选中 | ||
| 313 | - onSelectionChange(selection) { | ||
| 314 | - this.tableSelection = selection; | ||
| 315 | - this.$emit('selection', selection); | ||
| 316 | - }, | ||
| 317 | - }, | ||
| 318 | -}; | ||
| 319 | -</script> |
packages/schema-table/index.vue
| 1 | -<style> | ||
| 2 | -.z-schema-table { | ||
| 3 | - width: 100%; | ||
| 4 | -} | ||
| 5 | -</style> | ||
| 6 | - | ||
| 7 | -<template> | ||
| 8 | - <el-table | ||
| 9 | - class="z-schema-table" | ||
| 10 | - ref="table" | ||
| 11 | - :data="tableData" | ||
| 12 | - v-bind="{ size, ...tableProps }" | ||
| 13 | - v-on="tableEvents" | ||
| 14 | - @select="onSelect" | ||
| 15 | - @select-all="onSelectAll" | ||
| 16 | - @selection-change="onSelectionChange" | ||
| 17 | - > | ||
| 18 | - <!-- 默认表格插槽 --> | ||
| 19 | - <slot name="default"></slot> | ||
| 20 | - <!-- 根据配置列表生成表格列 --> | ||
| 21 | - <template v-if="tableList && tableList.length > 0"> | ||
| 22 | - <template v-for="(item, index) in tableList"> | ||
| 23 | - <template v-if="bindItemVisible(item.visible)"> | ||
| 24 | - <!-- 如果有表格列具名插槽 --> | ||
| 25 | - <slot v-if="$scopedSlots[item.keyPath.join('-')]" :name="item.keyPath.join('-')" v-bind="item"></slot> | ||
| 26 | - <!-- 默认表格列渲染 --> | ||
| 27 | - <el-table-column v-else v-bind="{ ...item, type: undefined }" :prop="item.fullKey || item.key" :key="index" :min-width="minWidth || item.minWidth || item['min-width']"> | ||
| 28 | - <template #default="{ row, column, $index }"> | ||
| 29 | - <cell-render :row="row" :item="item"> | ||
| 30 | - <!-- 如果有表格列值渲染具名插槽 --> | ||
| 31 | - <slot | ||
| 32 | - v-if="$scopedSlots[`value-${item.keyPath.join('-')}`]" | ||
| 33 | - :name="`value-${item.keyPath.join('-')}`" | ||
| 34 | - v-bind="item" | ||
| 35 | - :row="row" | ||
| 36 | - :value="$_get(row, item.fullKey)" | ||
| 37 | - :column="column" | ||
| 38 | - :index="$index" | ||
| 39 | - ></slot> | ||
| 40 | - <!-- 如果表格列配置了值渲染参数 --> | ||
| 41 | - <cell-value-render v-else-if="item.render" :row="row" :column="column" :index="$index" :item="item" /> | ||
| 42 | - </cell-render> | ||
| 43 | - </template> | ||
| 44 | - </el-table-column> | ||
| 45 | - </template> | ||
| 46 | - </template> | ||
| 47 | - </template> | ||
| 48 | - <!-- 已生成列表后的追加列插槽 --> | ||
| 49 | - <slot name="column-append"></slot> | ||
| 50 | - <!-- 末尾列插槽 --> | ||
| 51 | - <slot name="column-end"></slot> | ||
| 52 | - </el-table> | ||
| 53 | -</template> | ||
| 54 | - | ||
| 55 | <script> | 1 | <script> |
| 56 | -import { cloneDeep, get, set } from '../utils'; | ||
| 57 | -import CellValueRender from './cell-value-render'; | ||
| 58 | - | ||
| 59 | export default { | 2 | export default { |
| 60 | name: 'SchemaTable', | 3 | name: 'SchemaTable', |
| 61 | - components: { | ||
| 62 | - CellRender: { | ||
| 63 | - props: { row: Object, item: Object }, | ||
| 64 | - render(h) { | ||
| 65 | - if (this.$scopedSlots.default) { | ||
| 66 | - return h('span', this.$scopedSlots.default()); | ||
| 67 | - } else { | ||
| 68 | - return h('span', get(this.row, this.item.agentKey || this.item.fullKey)); | ||
| 69 | - } | ||
| 70 | - }, | ||
| 71 | - }, | ||
| 72 | - CellValueRender, | ||
| 73 | - }, | ||
| 74 | props: { | 4 | props: { |
| 75 | - // 用于实例化本组件绑定v-model的值 | ||
| 76 | - value: Array, | ||
| 77 | - // 配置列表 | ||
| 78 | - list: { | 5 | + value: { |
| 79 | type: Array, | 6 | type: Array, |
| 80 | - required: true, | 7 | + default() { |
| 8 | + return []; | ||
| 9 | + }, | ||
| 81 | }, | 10 | }, |
| 82 | - // 表格参数 | ||
| 83 | - tableProps: { | 11 | + schema: { |
| 84 | type: Object, | 12 | type: Object, |
| 85 | - default: () => ({}), | ||
| 86 | - }, | ||
| 87 | - // 表格事件 | ||
| 88 | - tableEvents: Object, | ||
| 89 | - // 列宽 | ||
| 90 | - minWidth: Number, | ||
| 91 | - // 大小 | ||
| 92 | - size: { | ||
| 93 | - type: String, | ||
| 94 | - default: 'small', | 13 | + required: true, |
| 95 | }, | 14 | }, |
| 96 | }, | 15 | }, |
| 97 | data() { | 16 | data() { |
| 98 | return { | 17 | return { |
| 99 | - tableList: [], // 表格配置列表 | ||
| 100 | - tableData: [], // 表格数据 | 18 | + model: this.value, |
| 101 | }; | 19 | }; |
| 102 | }, | 20 | }, |
| 103 | - computed: { | ||
| 104 | - // 表格实体 | ||
| 105 | - instance: { | ||
| 106 | - get() { | ||
| 107 | - return this.$refs.table; | ||
| 108 | - }, | ||
| 109 | - }, | ||
| 110 | - }, | ||
| 111 | watch: { | 21 | watch: { |
| 112 | - value: { | ||
| 113 | - handler(val = []) { | ||
| 114 | - this.tableData = val; | ||
| 115 | - }, | ||
| 116 | - immediate: true, | 22 | + value(val = []) { |
| 23 | + this.model = val; | ||
| 117 | }, | 24 | }, |
| 118 | - list: { | 25 | + model: { |
| 119 | handler(val) { | 26 | handler(val) { |
| 120 | - // 深度克隆传入的列表,避免原始值被修改 | ||
| 121 | - const newList = cloneDeep(this.list); | ||
| 122 | - // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key | ||
| 123 | - const generateFullKey = (list, parentKey) => { | ||
| 124 | - list.forEach(item => { | ||
| 125 | - if (item.group && item.list) { | ||
| 126 | - if (item.group.key) { | ||
| 127 | - item.fullKey = `${parentKey ? `${parentKey}.${item.group.key}` : item.group.key}`; | ||
| 128 | - } else { | ||
| 129 | - item.fullKey = parentKey || item.key; | ||
| 130 | - } | ||
| 131 | - generateFullKey(item.list, item.fullKey); | ||
| 132 | - } else { | ||
| 133 | - item.fullKey = `${parentKey ? `${parentKey}.${item.key}` : item.key}`; | ||
| 134 | - } | ||
| 135 | - }); | ||
| 136 | - }; | ||
| 137 | - // 生成fullKey | ||
| 138 | - generateFullKey(newList); | ||
| 139 | - // 创建输出列表 | ||
| 140 | - const result = []; | ||
| 141 | - // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key | ||
| 142 | - const generateFlatList = list => { | ||
| 143 | - list.forEach(item => { | ||
| 144 | - if (item.group && item.list) { | ||
| 145 | - generateFlatList(item.list); | ||
| 146 | - } else if (!item.group && !item.list) { | ||
| 147 | - result.push({ ...item, keyPath: item.fullKey.split('.') }); | ||
| 148 | - } | ||
| 149 | - }); | ||
| 150 | - }; | ||
| 151 | - generateFlatList(newList); | ||
| 152 | - this.tableList = result; | 27 | + this.$emit('input', val); |
| 153 | }, | 28 | }, |
| 154 | - immediate: true, | 29 | + deep: true, |
| 155 | }, | 30 | }, |
| 156 | }, | 31 | }, |
| 157 | - methods: { | ||
| 158 | - $_get: get, | ||
| 159 | - // 绑定表格列显示隐藏状态 | ||
| 160 | - bindItemVisible(visible = true) { | ||
| 161 | - let result = visible; | ||
| 162 | - if (typeof visible === 'function') { | ||
| 163 | - result = visible(this.tableData); | ||
| 164 | - } | ||
| 165 | - return result; | ||
| 166 | - }, | ||
| 167 | - // 选中单行 | ||
| 168 | - onSelect(selection, row) { | ||
| 169 | - if (selection.find(i => i.id === row.id)) { | ||
| 170 | - this.$emit('selection-change', [row], 'check'); | ||
| 171 | - } else { | ||
| 172 | - this.$emit('selection-change', [row], 'uncheck'); | ||
| 173 | - } | ||
| 174 | - }, | ||
| 175 | - // 切换全选 | ||
| 176 | - onSelectAll(selection) { | ||
| 177 | - if (selection && selection.length > 0) { | ||
| 178 | - this.$emit('selection-change', selection, 'check'); | ||
| 179 | - } else { | ||
| 180 | - this.$emit('selection-change', this.tableData, 'uncheck'); | ||
| 181 | - } | ||
| 182 | - }, | ||
| 183 | - // 表格选中 | ||
| 184 | - onSelectionChange(selection) { | ||
| 185 | - this.$emit('selection', selection); | ||
| 186 | - }, | ||
| 187 | - // 切换某行选中状态 | ||
| 188 | - toggleRowSelection(row, selected) { | ||
| 189 | - this.$refs.table && this.$refs.table.toggleRowSelection(row, selected); | ||
| 190 | - }, | ||
| 191 | - // 清除表格选中 | ||
| 192 | - clearSelection() { | ||
| 193 | - this.$refs.table && this.$refs.table.clearSelection(); | ||
| 194 | - }, | 32 | + render(h) { |
| 33 | + const schema = this.schema || {}; | ||
| 34 | + const _props = schema.props || {}; | ||
| 35 | + const _on = schema.on || {}; | ||
| 36 | + return h('z-table', { props: { value: this.model, columns: schema.items, ..._props }, on: _on, scopedSlots: this.$scopedSlots }); | ||
| 195 | }, | 37 | }, |
| 196 | }; | 38 | }; |
| 197 | </script> | 39 | </script> |
packages/select/index.bak.vue
| @@ -1,268 +0,0 @@ | @@ -1,268 +0,0 @@ | ||
| 1 | -<template> | ||
| 2 | - <el-select | ||
| 3 | - class="z-select" | ||
| 4 | - :disabled="disabled" | ||
| 5 | - :value-key="valueKey" | ||
| 6 | - :filterable="filterable" | ||
| 7 | - :remote="remote" | ||
| 8 | - :reserve-keyword="reserveKeyword" | ||
| 9 | - :clearable="clearable" | ||
| 10 | - :placeholder="placeholder" | ||
| 11 | - :remote-method="remoteMethod" | ||
| 12 | - :loading="loading" | ||
| 13 | - :size="size" | ||
| 14 | - :multiple="multiple" | ||
| 15 | - v-model="model" | ||
| 16 | - v-on="bindEvents" | ||
| 17 | - v-bind="selectProps" | ||
| 18 | - > | ||
| 19 | - <el-option v-for="item in optionsCurrent" :key="item.id" :label="labelFormat ? labelFormat(item) : item[labelKey]" :value="raw ? item : item[valueKey]" :disabled="item.disabled"> | ||
| 20 | - <slot :item="item" :value="model"></slot> | ||
| 21 | - </el-option> | ||
| 22 | - <slot name="empty" slot="empty"></slot> | ||
| 23 | - <template slot="prefix"> | ||
| 24 | - <i v-if="initing" class="el-icon-loading"></i> | ||
| 25 | - <slot v-else name="prefix"></slot> | ||
| 26 | - </template> | ||
| 27 | - </el-select> | ||
| 28 | -</template> | ||
| 29 | - | ||
| 30 | -<script> | ||
| 31 | -export default { | ||
| 32 | - name: 'Select', | ||
| 33 | - props: { | ||
| 34 | - value: [String, Number, Boolean, Object, Array], | ||
| 35 | - // 占位符 | ||
| 36 | - placeholder: { | ||
| 37 | - type: String, | ||
| 38 | - default: '请选择', | ||
| 39 | - }, | ||
| 40 | - // 选项数组 | ||
| 41 | - options: { | ||
| 42 | - type: Array, | ||
| 43 | - default: () => [], | ||
| 44 | - }, | ||
| 45 | - // 选中Label格式化方法 | ||
| 46 | - labelFormat: Function, | ||
| 47 | - labelKey: { | ||
| 48 | - type: String, | ||
| 49 | - default: 'name', | ||
| 50 | - }, | ||
| 51 | - valueKey: { | ||
| 52 | - type: String, | ||
| 53 | - default: 'code', | ||
| 54 | - }, | ||
| 55 | - searchKey: { | ||
| 56 | - type: String, | ||
| 57 | - default: 'query', | ||
| 58 | - }, | ||
| 59 | - size: { | ||
| 60 | - type: String, | ||
| 61 | - default: 'mini', | ||
| 62 | - }, | ||
| 63 | - multiple: Boolean, | ||
| 64 | - disabled: Boolean, | ||
| 65 | - clearable: { | ||
| 66 | - type: Boolean, | ||
| 67 | - default: true, | ||
| 68 | - }, | ||
| 69 | - filterable: { | ||
| 70 | - type: Boolean, | ||
| 71 | - default: true, | ||
| 72 | - }, | ||
| 73 | - reserveKeyword: { | ||
| 74 | - type: Boolean, | ||
| 75 | - default: true, | ||
| 76 | - }, | ||
| 77 | - selectProps: Object, | ||
| 78 | - // 保持原值,即绑定值为对象 | ||
| 79 | - raw: Boolean, | ||
| 80 | - // 远程搜索URL | ||
| 81 | - url: String, | ||
| 82 | - // HTTP请求库 | ||
| 83 | - http: Function, | ||
| 84 | - // 请求参数 | ||
| 85 | - params: { | ||
| 86 | - type: Object, | ||
| 87 | - default: () => ({}), | ||
| 88 | - }, | ||
| 89 | - // 请求配置 | ||
| 90 | - config: { | ||
| 91 | - type: Object, | ||
| 92 | - default: () => ({}), | ||
| 93 | - }, | ||
| 94 | - // 自定义接口 | ||
| 95 | - queryApi: Function, | ||
| 96 | - // 触发远程搜索的字段长度 | ||
| 97 | - triggerSize: { | ||
| 98 | - type: Number, | ||
| 99 | - default: 0, | ||
| 100 | - }, | ||
| 101 | - auto: { | ||
| 102 | - type: Boolean, | ||
| 103 | - default: true, | ||
| 104 | - }, | ||
| 105 | - update: Boolean, | ||
| 106 | - updateOnce: Boolean, | ||
| 107 | - beforeQuery: { | ||
| 108 | - type: Function, | ||
| 109 | - default: () => true, | ||
| 110 | - }, | ||
| 111 | - }, | ||
| 112 | - data() { | ||
| 113 | - return { | ||
| 114 | - model: this.value, | ||
| 115 | - optionsDataSource: this.fixOptions(this.options), | ||
| 116 | - optionsCurrent: this.fixOptions(this.options), | ||
| 117 | - loading: false, | ||
| 118 | - initing: false, | ||
| 119 | - loaded: false, | ||
| 120 | - }; | ||
| 121 | - }, | ||
| 122 | - created() { | ||
| 123 | - if (this.remote && this.auto) { | ||
| 124 | - this.initing = true; | ||
| 125 | - this.remoteMethod(); | ||
| 126 | - } | ||
| 127 | - }, | ||
| 128 | - watch: { | ||
| 129 | - value(val) { | ||
| 130 | - this.model = val; | ||
| 131 | - }, | ||
| 132 | - options(val) { | ||
| 133 | - if (val) { | ||
| 134 | - this.optionsCurrent = this.fixOptions(this.optionsDataSource); | ||
| 135 | - } | ||
| 136 | - }, | ||
| 137 | - }, | ||
| 138 | - computed: { | ||
| 139 | - request() { | ||
| 140 | - return this.http || this.zHttp; | ||
| 141 | - }, | ||
| 142 | - remote() { | ||
| 143 | - return Boolean(this.queryApi || (this.url && (this.http || this.zHttp))); | ||
| 144 | - }, | ||
| 145 | - // Hack绑定事件,即令el-select组件绑定事件与当前z-select组件相同,且扩展部分事件返回值 | ||
| 146 | - bindEvents() { | ||
| 147 | - let _events = {}; | ||
| 148 | - Object.keys(this.$listeners || {}).forEach(key => { | ||
| 149 | - // 非绑定对象的情况下,通过change事件向上emit出当前选中项 | ||
| 150 | - if (key === 'change' && !this.raw) { | ||
| 151 | - // 给el-select绑定change事件,且emit到z-select的change事件并拓展 | ||
| 152 | - _events[key] = value => { | ||
| 153 | - this.$emit( | ||
| 154 | - key, | ||
| 155 | - value, | ||
| 156 | - this.multiple | ||
| 157 | - ? this.optionsCurrent.reduce((result, item) => { | ||
| 158 | - if (value.includes(item[this.valueKey])) { | ||
| 159 | - result.push(item); | ||
| 160 | - } | ||
| 161 | - return result; | ||
| 162 | - }, []) | ||
| 163 | - : this.optionsCurrent.find(item => item[this.valueKey] === value), | ||
| 164 | - ); | ||
| 165 | - }; | ||
| 166 | - } else { | ||
| 167 | - _events[key] = e => { | ||
| 168 | - this.$emit(key, e); | ||
| 169 | - }; | ||
| 170 | - } | ||
| 171 | - }); | ||
| 172 | - return { | ||
| 173 | - ..._events, | ||
| 174 | - 'visible-change': show => { | ||
| 175 | - if (this.remote && show) { | ||
| 176 | - if (this.updateOnce) { | ||
| 177 | - if (!this.loaded) { | ||
| 178 | - this.remoteMethod(); | ||
| 179 | - } | ||
| 180 | - } else if (this.update) { | ||
| 181 | - this.remoteMethod(); | ||
| 182 | - } | ||
| 183 | - } | ||
| 184 | - _events['visible-change'] && _events['visible-change'](show); | ||
| 185 | - }, | ||
| 186 | - }; | ||
| 187 | - }, | ||
| 188 | - }, | ||
| 189 | - methods: { | ||
| 190 | - // 修复当前数据源包含当前选中项 | ||
| 191 | - fixOptions(list) { | ||
| 192 | - let fixOptions = []; | ||
| 193 | - if (this.raw) { | ||
| 194 | - if (this.multiple) { | ||
| 195 | - fixOptions = this.value && this.value.length > 0 ? [...this.value] : []; | ||
| 196 | - } else { | ||
| 197 | - fixOptions = this.value && Object.keys(this.value).length > 0 ? [this.value] : []; | ||
| 198 | - } | ||
| 199 | - } | ||
| 200 | - let hash = {}; | ||
| 201 | - const options = [...fixOptions, ...this.options, ...list].reduce((result, item) => { | ||
| 202 | - if (!hash[item[this.valueKey]]) { | ||
| 203 | - // 如果当前元素的key值没有在hash对象里,则可放入最终结果数组 | ||
| 204 | - hash[item[this.valueKey]] = true; // 把当前元素key值添加到hash对象 | ||
| 205 | - item[this.valueKey] && item[this.labelKey] && result.push(item); // 把当前元素放入结果数组 | ||
| 206 | - } | ||
| 207 | - return result; // 返回结果数组 | ||
| 208 | - }, []); | ||
| 209 | - return options; | ||
| 210 | - }, | ||
| 211 | - // 远程加载 | ||
| 212 | - remoteMethod(val = '') { | ||
| 213 | - const searchText = val.trim(); | ||
| 214 | - const isQueryValid = this.beforeQuery(searchText); | ||
| 215 | - if (isQueryValid) { | ||
| 216 | - if (searchText.length >= this.triggerSize) { | ||
| 217 | - this.loading = true; | ||
| 218 | - let requestPrimise; | ||
| 219 | - if (this.queryApi) { | ||
| 220 | - requestPrimise = this.queryApi(searchText); | ||
| 221 | - } else { | ||
| 222 | - requestPrimise = this.request({ | ||
| 223 | - url: this.url, | ||
| 224 | - method: 'get', | ||
| 225 | - params: searchText ? { [this.searchKey]: searchText, ...this.params } : this.params, | ||
| 226 | - ...this.config, | ||
| 227 | - }); | ||
| 228 | - } | ||
| 229 | - requestPrimise | ||
| 230 | - .then(res => { | ||
| 231 | - const response = res || {}; | ||
| 232 | - const options = this.fixOptions(response.result); | ||
| 233 | - this.optionsDataSource = options; | ||
| 234 | - this.optionsCurrent = options; | ||
| 235 | - }) | ||
| 236 | - .finally(() => { | ||
| 237 | - this.loading = false; | ||
| 238 | - this.initing = false; | ||
| 239 | - this.loaded = true; | ||
| 240 | - }); | ||
| 241 | - } else { | ||
| 242 | - this.optionsCurrent = this.optionsDataSource.filter(item => { | ||
| 243 | - return item[this.labelKey].includes(val); | ||
| 244 | - }); | ||
| 245 | - } | ||
| 246 | - } else { | ||
| 247 | - this.loading = false; | ||
| 248 | - this.initing = false; | ||
| 249 | - this.loaded = true; | ||
| 250 | - } | ||
| 251 | - }, | ||
| 252 | - }, | ||
| 253 | -}; | ||
| 254 | -</script> | ||
| 255 | - | ||
| 256 | -<style lang="scss"> | ||
| 257 | -.z-select { | ||
| 258 | - .el-input__prefix { | ||
| 259 | - height: 100%; | ||
| 260 | - display: flex; | ||
| 261 | - align-items: center; | ||
| 262 | - justify-content: center; | ||
| 263 | - .el-icon-loading { | ||
| 264 | - font-size: 16px; | ||
| 265 | - } | ||
| 266 | - } | ||
| 267 | -} | ||
| 268 | -</style> |
packages/table/editable.vue
| @@ -27,23 +27,26 @@ | @@ -27,23 +27,26 @@ | ||
| 27 | </style> | 27 | </style> |
| 28 | 28 | ||
| 29 | <template> | 29 | <template> |
| 30 | - <el-table :data="tableData | tableDataFilter" :size="tableSize" v-bind="_props" @header-click="onHeaderClick" @cell-click="onCellClick" @cell-dblclick="onCellDblclick"> | 30 | + <el-table :data="tableData | tableDataFilter" :size="tableSize" v-bind="bindProps" @header-click="onHeaderClick" @cell-click="onCellClick" @cell-dblclick="onCellDblclick"> |
| 31 | <slot name="left"></slot> | 31 | <slot name="left"></slot> |
| 32 | <template v-for="(item, index) in columns"> | 32 | <template v-for="(item, index) in columns"> |
| 33 | <el-table-column v-bind="item" :key="index"> | 33 | <el-table-column v-bind="item" :key="index"> |
| 34 | <slot :name="`header-${item.prop}`" slot="header"></slot> | 34 | <slot :name="`header-${item.prop}`" slot="header"></slot> |
| 35 | - <template #default="{ row, column }"> | 35 | + <template #default="{ row, column, $index }"> |
| 36 | <cell-editor | 36 | <cell-editor |
| 37 | - :disabled="disabled || item.editable === false" | ||
| 38 | - :editable="item.editable !== false && (row.$editable || (tableEditCell.index === row.$index && tableEditCell.prop === item.prop))" | 37 | + :disabled="editall || disabled || item.editable === false" |
| 38 | + :editable="editall || (item.editable !== false && row.$editor && row.$editor.includes(item.prop))" | ||
| 39 | :component="item.component" | 39 | :component="item.component" |
| 40 | :value="row[column.property]" | 40 | :value="row[column.property]" |
| 41 | - @input="onCellInput" | ||
| 42 | - @edit-click="setEditCell(row, column)" | ||
| 43 | - @edit-confirm="onEditConfirm" | 41 | + @input="value => onCellInput(value, row, column, $index)" |
| 42 | + @edit-click="setRowEditor(row, column, $index)" | ||
| 43 | + @edit-confirm="value => onEditConfirm(value, row, column, $index)" | ||
| 44 | > | 44 | > |
| 45 | - <template v-if="$scopedSlots[`editor-${item.prop}`]"> | ||
| 46 | - <slot :name="`editor-${item.prop}`" :value="row[column.property]" :onInput="onCellInput"></slot> | 45 | + <template v-if="$scopedSlots[`editor-${item.prop}`]" slot="editor"> |
| 46 | + <slot :name="`editor-${item.prop}`" :value="row[column.property]" :onInput="value => onCellInput(value, row, column, $index)"></slot> | ||
| 47 | + </template> | ||
| 48 | + <template v-if="$scopedSlots[`cell-${item.prop}`]"> | ||
| 49 | + <slot :name="`cell-${item.prop}`" :value="row[column.property]" :row="row" :index="$index"></slot> | ||
| 47 | </template> | 50 | </template> |
| 48 | </cell-editor> | 51 | </cell-editor> |
| 49 | </template> | 52 | </template> |
| @@ -72,7 +75,7 @@ export default { | @@ -72,7 +75,7 @@ export default { | ||
| 72 | }, | 75 | }, |
| 73 | watch: { | 76 | watch: { |
| 74 | editable(val) { | 77 | editable(val) { |
| 75 | - if (val && this.component === 'el-input') { | 78 | + if (!this.disabled && val && this.component === 'el-input') { |
| 76 | this.$nextTick(() => { | 79 | this.$nextTick(() => { |
| 77 | this.$children[0] && this.$children[0].focus && this.$children[0].focus(); | 80 | this.$children[0] && this.$children[0].focus && this.$children[0].focus(); |
| 78 | }); | 81 | }); |
| @@ -91,8 +94,8 @@ export default { | @@ -91,8 +94,8 @@ export default { | ||
| 91 | }, | 94 | }, |
| 92 | }), | 95 | }), |
| 93 | ]; | 96 | ]; |
| 94 | - if (this.$scopedSlots.default) { | ||
| 95 | - editorRender = [this.$scopedSlots.default()]; | 97 | + if (this.$scopedSlots.editor) { |
| 98 | + editorRender = [this.$scopedSlots.editor()]; | ||
| 96 | } | 99 | } |
| 97 | if (!this.disabled) { | 100 | if (!this.disabled) { |
| 98 | const handlerItems = [h('i', { attrs: { title: '确定', class: 'el-icon-check' }, on: { click: () => this.$emit('edit-confirm', this.value) } })]; | 101 | const handlerItems = [h('i', { attrs: { title: '确定', class: 'el-icon-check' }, on: { click: () => this.$emit('edit-confirm', this.value) } })]; |
| @@ -102,7 +105,10 @@ export default { | @@ -102,7 +105,10 @@ export default { | ||
| 102 | } | 105 | } |
| 103 | return h('span', { class: 'z-table-column__cell-editable' }, editorRender); | 106 | return h('span', { class: 'z-table-column__cell-editable' }, editorRender); |
| 104 | } | 107 | } |
| 105 | - const valueRender = [h('span', this.value)]; | 108 | + let valueRender = [h('span', this.value)]; |
| 109 | + if (this.$scopedSlots.default) { | ||
| 110 | + valueRender = [this.$scopedSlots.default()]; | ||
| 111 | + } | ||
| 106 | if (!this.disabled) { | 112 | if (!this.disabled) { |
| 107 | valueRender.push(h('i', { attrs: { title: '编辑', class: 'el-icon-edit' }, on: { click: () => this.$emit('edit-click') } })); | 113 | valueRender.push(h('i', { attrs: { title: '编辑', class: 'el-icon-edit' }, on: { click: () => this.$emit('edit-click') } })); |
| 108 | } | 114 | } |
| @@ -123,6 +129,7 @@ export default { | @@ -123,6 +129,7 @@ export default { | ||
| 123 | return []; | 129 | return []; |
| 124 | }, | 130 | }, |
| 125 | }, | 131 | }, |
| 132 | + editall: Boolean, | ||
| 126 | clickable: Boolean, | 133 | clickable: Boolean, |
| 127 | disabled: Boolean, | 134 | disabled: Boolean, |
| 128 | ...tableProps, | 135 | ...tableProps, |
| @@ -134,13 +141,13 @@ export default { | @@ -134,13 +141,13 @@ export default { | ||
| 134 | data(val) { | 141 | data(val) { |
| 135 | this.tableData = val || []; | 142 | this.tableData = val || []; |
| 136 | }, | 143 | }, |
| 144 | + tableData(val) { | ||
| 145 | + this.$emit('input', val || []); | ||
| 146 | + }, | ||
| 137 | }, | 147 | }, |
| 138 | data() { | 148 | data() { |
| 139 | return { | 149 | return { |
| 140 | tableData: this.value, | 150 | tableData: this.value, |
| 141 | - tableRowTemplate: { $editable: true }, // 行数据模板 | ||
| 142 | - tableEditCell: {}, // 正在编辑的单元格 | ||
| 143 | - tableSelection: [], // 表格已选中 | ||
| 144 | }; | 151 | }; |
| 145 | }, | 152 | }, |
| 146 | filters: { | 153 | filters: { |
| @@ -156,32 +163,51 @@ export default { | @@ -156,32 +163,51 @@ export default { | ||
| 156 | }, | 163 | }, |
| 157 | onCellClick(row, column) { | 164 | onCellClick(row, column) { |
| 158 | if (this.clickable) { | 165 | if (this.clickable) { |
| 159 | - if (row.$index !== this.tableEditCell.index || column.property !== this.tableEditCell.prop) { | ||
| 160 | - this.tableEditCell = {}; | ||
| 161 | - } | 166 | + const prop = column.property; |
| 167 | + let tableData = cloneDeep(this.tableData); | ||
| 168 | + tableData.forEach((item, index) => { | ||
| 169 | + if (!(index === row.$index && item.$editor && item.$editor.includes(prop))) { | ||
| 170 | + item.$editor = []; | ||
| 171 | + } | ||
| 172 | + }); | ||
| 173 | + this.tableData = tableData; | ||
| 162 | } | 174 | } |
| 163 | }, | 175 | }, |
| 164 | onCellDblclick(row, column) { | 176 | onCellDblclick(row, column) { |
| 165 | if (this.clickable) { | 177 | if (this.clickable) { |
| 166 | - this.setEditCell(row, column); | 178 | + this.setRowEditor(row, column, row.$index); |
| 167 | } | 179 | } |
| 168 | }, | 180 | }, |
| 169 | - setEditCell(row, column) { | ||
| 170 | - this.tableEditCell = { index: row.$index, prop: column.property }; | 181 | + setRowEditor(row, column, index) { |
| 182 | + this.cancelEditCell(); | ||
| 183 | + let tableRow = this.tableData[index]; | ||
| 184 | + if (tableRow) { | ||
| 185 | + if (tableRow.$editor) { | ||
| 186 | + tableRow.$editor = [...tableRow.$editor, column.property]; | ||
| 187 | + } else { | ||
| 188 | + tableRow.$editor = [column.property]; | ||
| 189 | + } | ||
| 190 | + this.$set(this.tableData, index, tableRow); | ||
| 191 | + } | ||
| 171 | }, | 192 | }, |
| 172 | - onEditConfirm(value) { | ||
| 173 | - this.$emit('cell-edit-confirm', { ...this.tableEditCell, value }); | 193 | + onEditConfirm(value, row, column, index) { |
| 194 | + this.$emit('cell-edit-confirm', { row, index, prop: column.property, value }); | ||
| 174 | this.cancelEditCell(); | 195 | this.cancelEditCell(); |
| 175 | }, | 196 | }, |
| 176 | cancelEditCell() { | 197 | cancelEditCell() { |
| 177 | - this.tableEditCell = {}; | 198 | + this.tableData = this.tableData.map((item, index) => { |
| 199 | + const newItem = cloneDeep(item); | ||
| 200 | + delete newItem.$index; | ||
| 201 | + delete newItem.$editor; | ||
| 202 | + return newItem; | ||
| 203 | + }); | ||
| 178 | }, | 204 | }, |
| 179 | - onCellInput(value) { | 205 | + onCellInput(value, row, column, index) { |
| 180 | const tableData = cloneDeep(this.tableData); | 206 | const tableData = cloneDeep(this.tableData); |
| 181 | - const tableRow = tableData[this.tableEditCell.index]; | ||
| 182 | - set(tableRow, this.tableEditCell.prop, value); | ||
| 183 | - tableData[this.tableEditCell.index] = tableRow; | ||
| 184 | - this.$set(this.tableData, this.tableEditCell.index, tableRow); | 207 | + const tableRow = tableData[index]; |
| 208 | + set(tableRow, column.property, value); | ||
| 209 | + tableData[index] = tableRow; | ||
| 210 | + this.$set(this.tableData, index, tableRow); | ||
| 185 | }, | 211 | }, |
| 186 | }, | 212 | }, |
| 187 | }; | 213 | }; |
packages/table/index.js
packages/table/normal.vue
| 1 | <template> | 1 | <template> |
| 2 | - <el-table :data="tableData" :size="tableSize" v-bind="_props"> | 2 | + <el-table :data="tableData" :size="tableSize" v-bind="bindProps"> |
| 3 | <slot name="left"></slot> | 3 | <slot name="left"></slot> |
| 4 | <template v-for="(item, index) in columns"> | 4 | <template v-for="(item, index) in columns"> |
| 5 | <el-table-column v-bind="item" :key="index"> | 5 | <el-table-column v-bind="item" :key="index"> |
| @@ -7,6 +7,9 @@ | @@ -7,6 +7,9 @@ | ||
| 7 | <template v-if="$scopedSlots[`cell-${item.prop}`]" #default="{ row, column, $index }"> | 7 | <template v-if="$scopedSlots[`cell-${item.prop}`]" #default="{ row, column, $index }"> |
| 8 | <slot :name="`cell-${item.prop}`" :value="row[column.property]" :row="row" :index="$index"></slot> | 8 | <slot :name="`cell-${item.prop}`" :value="row[column.property]" :row="row" :index="$index"></slot> |
| 9 | </template> | 9 | </template> |
| 10 | + <template v-else-if="item.render && typeof item.render === 'function'" #default="{ row, column, $index }"> | ||
| 11 | + <cell-render :item="item" :value="row[item.prop]" :row="row" :column="column" :index="$index"></cell-render> | ||
| 12 | + </template> | ||
| 10 | </el-table-column> | 13 | </el-table-column> |
| 11 | </template> | 14 | </template> |
| 12 | <slot></slot> | 15 | <slot></slot> |
| @@ -19,6 +22,17 @@ import tableProps from './props'; | @@ -19,6 +22,17 @@ import tableProps from './props'; | ||
| 19 | 22 | ||
| 20 | export default { | 23 | export default { |
| 21 | name: 'TableNormal', | 24 | name: 'TableNormal', |
| 25 | + components: { | ||
| 26 | + CellRender: { | ||
| 27 | + functional: true, | ||
| 28 | + render(h, context) { | ||
| 29 | + const props = context.props; | ||
| 30 | + const item = props.item || {}; | ||
| 31 | + const content = item.render(props.value, props.row, h, props.index); | ||
| 32 | + return typeof content === 'string' ? h('span', {}, [content]) : content; | ||
| 33 | + }, | ||
| 34 | + }, | ||
| 35 | + }, | ||
| 22 | inject: { | 36 | inject: { |
| 23 | elForm: { | 37 | elForm: { |
| 24 | default: '', | 38 | default: '', |
| @@ -62,6 +76,16 @@ export default { | @@ -62,6 +76,16 @@ export default { | ||
| 62 | tableSize() { | 76 | tableSize() { |
| 63 | return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size; | 77 | return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size; |
| 64 | }, | 78 | }, |
| 79 | + bindProps() { | ||
| 80 | + const tablePropsKeys = Object.keys(tableProps); | ||
| 81 | + let props = {}; | ||
| 82 | + Object.keys(this._props).forEach(key => { | ||
| 83 | + if (tablePropsKeys.includes(key)) { | ||
| 84 | + props[key] = this._props[key]; | ||
| 85 | + } | ||
| 86 | + }); | ||
| 87 | + return props; | ||
| 88 | + }, | ||
| 65 | }, | 89 | }, |
| 66 | }; | 90 | }; |
| 67 | </script> | 91 | </script> |