Commit 8104d35f08d3dec4ac51d9c2f604181275bfe37a

Authored by liuhanchen
1 parent a57cd44f

feat: 兼容旧版表格编辑器

packages/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue'; @@ -2,6 +2,7 @@ import Vue from 'vue';
2 import ZTable from './table/index.vue'; 2 import ZTable from './table/index.vue';
3 import ZTableNormal from './table/normal'; 3 import ZTableNormal from './table/normal';
4 import ZTableEditable from './table/editable'; 4 import ZTableEditable from './table/editable';
  5 +import ZTableEditor from './table/editor';
5 import ElImageViewer from './upload/image-viewer'; 6 import ElImageViewer from './upload/image-viewer';
6 7
7 let components = {}; 8 let components = {};
@@ -17,6 +18,7 @@ const install = function(Vue, opts = {}) { @@ -17,6 +18,7 @@ const install = function(Vue, opts = {}) {
17 components[ZTable.name] = ZTable; 18 components[ZTable.name] = ZTable;
18 components[ZTableNormal.name] = ZTableNormal; 19 components[ZTableNormal.name] = ZTableNormal;
19 components[ZTableEditable.name] = ZTableEditable; 20 components[ZTableEditable.name] = ZTableEditable;
  21 + components[ZTableEditor.name] = ZTableEditor;
20 Object.values(components).forEach(component => { 22 Object.values(components).forEach(component => {
21 // 组件前缀 23 // 组件前缀
22 const prefix = opts.name || 'z'; 24 const prefix = opts.name || 'z';
packages/table/editable copy.vue
@@ -1,217 +0,0 @@ @@ -1,217 +0,0 @@
1 -<style lang="scss">  
2 -.z-table-column__cell-editable {  
3 - display: inline-flex;  
4 - align-items: center;  
5 - justify-content: space-between;  
6 - width: 100%;  
7 - .el-icon-edit {  
8 - color: rgba(151, 151, 151, 0.5);  
9 - &:hover {  
10 - color: $primary;  
11 - }  
12 - }  
13 - .el-icon-check {  
14 - color: $green;  
15 - }  
16 - .el-icon-close {  
17 - color: $red;  
18 - }  
19 - .el-icon-edit,  
20 - .el-icon-check,  
21 - .el-icon-close {  
22 - cursor: pointer;  
23 - margin-left: 5px;  
24 - font-size: 14px;  
25 - }  
26 -}  
27 -</style>  
28 -  
29 -<template>  
30 - <el-table :data="tableData | tableDataFilter" :size="_elSize" v-bind="bindProps" v-on="$listeners" @header-click="onHeaderClick" @cell-click="onCellClick" @cell-dblclick="onCellDblclick">  
31 - <slot name="left"></slot>  
32 - <template v-for="(item, index) in columns">  
33 - <el-table-column v-bind="item" :key="index">  
34 - <slot :name="`header-${item.prop}`" slot="header"></slot>  
35 - <template #default="{ row, column, $index }">  
36 - <cell-editor  
37 - :disabled="item.editalways || editall || disabled || item.editable === false"  
38 - :editable="item.editalways || editall || (item.editable !== false && row.$editor && row.$editor.includes(item.prop))"  
39 - :component="item.component"  
40 - :value="row[column.property]"  
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 - >  
45 - <template v-if="$scopedSlots[`editor-${item.prop}`]" slot="editor">  
46 - <slot :name="`editor-${item.prop}`" :value="row[column.property]" :row="row" :index="$index" :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>  
50 - </template>  
51 - <template v-else-if="item.render && typeof item.render === 'function'" #default="{ row, column, $index }">  
52 - <cell-render :item="item" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></cell-render>  
53 - </template>  
54 - </cell-editor>  
55 - </template>  
56 - </el-table-column>  
57 - </template>  
58 - <slot></slot>  
59 - <slot name="append"></slot>  
60 - </el-table>  
61 -</template>  
62 -  
63 -<script>  
64 -import TableNormal from './normal';  
65 -import tableProps from './props';  
66 -import { cloneDeep, get, set } from '../utils';  
67 -  
68 -export default {  
69 - name: 'TableEditable',  
70 - extends: TableNormal,  
71 - components: {  
72 - cellEditor: {  
73 - props: {  
74 - value: [String, Number, Array, Object, Boolean],  
75 - component: { type: String, default: 'el-input' },  
76 - editable: Boolean,  
77 - disabled: Boolean,  
78 - },  
79 - watch: {  
80 - editable(val) {  
81 - if (!this.disabled && val && this.component === 'el-input') {  
82 - this.$nextTick(() => {  
83 - this.$children[0] && this.$children[0].focus && this.$children[0].focus();  
84 - });  
85 - }  
86 - },  
87 - },  
88 - render(h) {  
89 - if (this.editable) {  
90 - let editorRender = [  
91 - h(this.component, {  
92 - props: { value: this.value, size: 'mini' },  
93 - on: {  
94 - input: value => {  
95 - this.$emit('input', value);  
96 - },  
97 - },  
98 - }),  
99 - ];  
100 - if (this.$scopedSlots.editor) {  
101 - editorRender = [this.$scopedSlots.editor()];  
102 - }  
103 - if (!this.disabled) {  
104 - const handlerItems = [h('i', { attrs: { title: '确定', class: 'el-icon-check' }, on: { click: () => this.$emit('edit-confirm', this.value) } })];  
105 - // handlerItems.push(h('i', { attrs: { title: '取消', class: 'el-icon-close' }, on: { click: () => this.$emit('edit-confirm') } }));  
106 - const handler = h('span', handlerItems);  
107 - editorRender.push(handler);  
108 - }  
109 - return h('span', { class: 'z-table-column__cell-editable' }, editorRender);  
110 - }  
111 - let valueRender = [h('span', this.value)];  
112 - if (this.$scopedSlots.default) {  
113 - valueRender = [this.$scopedSlots.default()];  
114 - }  
115 - if (!this.disabled) {  
116 - valueRender.push(h('i', { attrs: { title: '编辑', class: 'el-icon-edit' }, on: { click: () => this.$emit('edit-click') } }));  
117 - }  
118 - return h('span', { class: 'z-table-column__cell-editable' }, valueRender);  
119 - },  
120 - },  
121 - },  
122 - props: {  
123 - value: {  
124 - type: Array,  
125 - default() {  
126 - return [];  
127 - },  
128 - },  
129 - columns: {  
130 - type: Array,  
131 - default() {  
132 - return [];  
133 - },  
134 - },  
135 - editall: Boolean,  
136 - clickable: Boolean,  
137 - disabled: Boolean,  
138 - ...tableProps,  
139 - },  
140 - watch: {  
141 - value(val) {  
142 - this.tableData = val || [];  
143 - },  
144 - data(val) {  
145 - this.tableData = val || [];  
146 - },  
147 - tableData(val) {  
148 - this.$emit('input', val || []);  
149 - },  
150 - },  
151 - data() {  
152 - return {  
153 - tableData: this.value,  
154 - };  
155 - },  
156 - filters: {  
157 - tableDataFilter(value) {  
158 - return value.map((item, index) => ({ ...item, $index: index }));  
159 - },  
160 - },  
161 - methods: {  
162 - onHeaderClick() {  
163 - if (this.clickable) {  
164 - this.cancelEditCell();  
165 - }  
166 - },  
167 - onCellClick(row, column) {  
168 - if (this.clickable) {  
169 - const prop = column.property;  
170 - let tableData = cloneDeep(this.tableData);  
171 - tableData.forEach((item, index) => {  
172 - if (!(index === row.$index && item.$editor && item.$editor.includes(prop))) {  
173 - item.$editor = [];  
174 - }  
175 - });  
176 - this.tableData = tableData;  
177 - }  
178 - },  
179 - onCellDblclick(row, column) {  
180 - if (this.clickable) {  
181 - this.setRowEditor(row, column, row.$index);  
182 - }  
183 - },  
184 - setRowEditor(row, column, index) {  
185 - this.cancelEditCell();  
186 - let tableRow = this.tableData[index];  
187 - if (tableRow) {  
188 - if (tableRow.$editor) {  
189 - tableRow.$editor = [...tableRow.$editor, column.property];  
190 - } else {  
191 - tableRow.$editor = [column.property];  
192 - }  
193 - this.$set(this.tableData, index, tableRow);  
194 - }  
195 - },  
196 - onEditConfirm(value, row, column, index) {  
197 - this.$emit('cell-edit-confirm', { row, index, prop: column.property, value });  
198 - this.cancelEditCell();  
199 - },  
200 - cancelEditCell() {  
201 - this.tableData = this.tableData.map((item, index) => {  
202 - const newItem = cloneDeep(item);  
203 - delete newItem.$index;  
204 - delete newItem.$editor;  
205 - return newItem;  
206 - });  
207 - },  
208 - onCellInput(value, row, column, index) {  
209 - const tableData = cloneDeep(this.tableData);  
210 - const tableRow = tableData[index];  
211 - set(tableRow, column.property, value);  
212 - tableData[index] = tableRow;  
213 - this.$set(this.tableData, index, tableRow);  
214 - },  
215 - },  
216 -};  
217 -</script>  
packages/table/editable.vue
@@ -61,14 +61,21 @@ @@ -61,14 +61,21 @@
61 </template> 61 </template>
62 62
63 <script> 63 <script>
64 -import TableNormal from './normal';  
65 import tableProps from './props'; 64 import tableProps from './props';
66 import { cloneDeep, get, set } from '../utils'; 65 import { cloneDeep, get, set } from '../utils';
67 66
68 export default { 67 export default {
69 name: 'TableEditable', 68 name: 'TableEditable',
70 - extends: TableNormal,  
71 components: { 69 components: {
  70 + CellRender: {
  71 + functional: true,
  72 + render(h, context) {
  73 + const props = context.props;
  74 + const item = props.item || {};
  75 + const content = item.render(props.value, props.row, h, props.index);
  76 + return typeof content === 'string' ? h('span', {}, [content]) : content;
  77 + },
  78 + },
72 cellEditor: { 79 cellEditor: {
73 props: { 80 props: {
74 value: [String, Number, Array, Object, Boolean], 81 value: [String, Number, Array, Object, Boolean],
@@ -119,6 +126,14 @@ export default { @@ -119,6 +126,14 @@ export default {
119 }, 126 },
120 }, 127 },
121 }, 128 },
  129 + inject: {
  130 + elForm: {
  131 + default: '',
  132 + },
  133 + elFormItem: {
  134 + default: '',
  135 + },
  136 + },
122 props: { 137 props: {
123 value: { 138 value: {
124 type: Array, 139 type: Array,
@@ -153,12 +168,31 @@ export default { @@ -153,12 +168,31 @@ export default {
153 tableData: this.value, 168 tableData: this.value,
154 }; 169 };
155 }, 170 },
  171 + computed: {
  172 + _elFormItemSize() {
  173 + return (this.elFormItem || {}).elFormItemSize;
  174 + },
  175 + _elSize() {
  176 + return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size;
  177 + },
  178 + bindProps() {
  179 + const tablePropsKeys = Object.keys(tableProps);
  180 + let props = {};
  181 + Object.keys(this._props).forEach(key => {
  182 + if (tablePropsKeys.includes(key)) {
  183 + props[key] = this._props[key];
  184 + }
  185 + });
  186 + return props;
  187 + },
  188 + },
156 filters: { 189 filters: {
157 tableDataFilter(value) { 190 tableDataFilter(value) {
158 return value.map((item, index) => ({ ...item, $index: index })); 191 return value.map((item, index) => ({ ...item, $index: index }));
159 }, 192 },
160 }, 193 },
161 methods: { 194 methods: {
  195 + get,
162 onHeaderClick() { 196 onHeaderClick() {
163 if (this.clickable) { 197 if (this.clickable) {
164 this.cancelEditCell(); 198 this.cancelEditCell();
packages/table/editor.vue 0 → 100644
@@ -0,0 +1,130 @@ @@ -0,0 +1,130 @@
  1 +<script>
  2 +import { get, set, cloneDeep } from '../utils';
  3 +import { renderContext } from '../utils/vnode';
  4 +
  5 +// 标题渲染
  6 +function headerRender(h, context, item) {
  7 + const headerSlot = context.scopedSlots[`header-${item.prop}`];
  8 + return function(scope) {
  9 + // 自定义具名插槽
  10 + if (headerSlot) {
  11 + return headerSlot(scope);
  12 + }
  13 + // 自定义渲染函数
  14 + if (item.header) {
  15 + return item.header(item, h, scope);
  16 + }
  17 + // 默认取值
  18 + return item.label;
  19 + };
  20 +}
  21 +
  22 +// 单元格渲染
  23 +function cellRender(h, context, item) {
  24 + const cellSlot = context.scopedSlots[`cell-${item.prop}`];
  25 + return function(scope) {
  26 + const value = get(scope.row, item.prop);
  27 + // 自定义具名插槽
  28 + if (cellSlot) {
  29 + return cellSlot({ item, value, index: scope.$index, ...scope });
  30 + }
  31 + // 自定义渲染函数
  32 + if (item.render) {
  33 + return item.render(value, scope.row, h, scope.$index);
  34 + }
  35 + // 默认取值
  36 + return get(scope.row, item.prop);
  37 + };
  38 +}
  39 +
  40 +// 编辑器渲染
  41 +function editorRender(h, context, item) {
  42 + const editorSlot = context.scopedSlots[`editor-${item.prop}`];
  43 + const contentProps = context.props || {};
  44 + return function(scope) {
  45 + const value = get(scope.row, item.prop);
  46 + // 自定义具名插槽
  47 + if (editorSlot) {
  48 + return editorSlot({ item, value, index: scope.$index, ...scope });
  49 + }
  50 + // 默认
  51 + const vnode = h(item.is, {
  52 + attrs: item.attrs,
  53 + props: { ...(item.props || {}), value },
  54 + on: {
  55 + input(val) {
  56 + if (get(contentProps, 'editor.deep') === true) {
  57 + if (item.prop.indexOf('.') > -1 || item.prop.indexOf('[') > -1) {
  58 + let separator = '';
  59 + if (item.prop.indexOf('.') > -1) {
  60 + separator = '.';
  61 + } else if (item.prop.indexOf('[') > -1) {
  62 + separator = '[';
  63 + }
  64 + const path = item.prop.split(separator);
  65 + const bindProp = path[0];
  66 + const propValue = cloneDeep(scope.row);
  67 + set(propValue, item.prop, val);
  68 + vnode.componentInstance.$set(scope.row, bindProp, propValue[bindProp]);
  69 + } else {
  70 + // set(scope.row, item.prop, val);
  71 + scope.row[item.prop] = val;
  72 + }
  73 + } else {
  74 + scope.row[item.prop] = val;
  75 + // set(contentProps.data, `[${[scope.$index]}]${item.prop}`, val);
  76 + }
  77 + if (item.on && item.on.input) {
  78 + item.on.input(val);
  79 + }
  80 + },
  81 + },
  82 + });
  83 + return vnode;
  84 + };
  85 +}
  86 +
  87 +// 跟进columns生成列
  88 +function createElTableColumns(h, context, columns) {
  89 + const props = context.props || {};
  90 + const editorConfig = props.editor || {};
  91 + return columns.map((item, index) => {
  92 + const { attrs, on, ...props } = item;
  93 + const editorMatch = editorConfig.inputs.find(i => i.prop === item.prop);
  94 + // 处理插槽
  95 + const scopedSlots = {
  96 + header: headerRender(h, context, item),
  97 + default: editorMatch ? editorRender(h, context, editorMatch) : cellRender(h, context, item),
  98 + };
  99 + return h('el-table-column', { key: index, attrs, props, on, scopedSlots });
  100 + });
  101 +}
  102 +
  103 +export default {
  104 + name: 'TableEditor',
  105 + functional: true,
  106 + render(h, context) {
  107 + console.log(context);
  108 + const props = context.props || {};
  109 + let scopedSlots = context.scopedSlots || {};
  110 + // 如有默认插槽则相当于直接写el-table
  111 + if (scopedSlots.default) {
  112 + return h('el-table', context);
  113 + }
  114 + const columns = props.columns || [];
  115 + // 通过columns快速生成el-table-column
  116 + const elTableColumns = createElTableColumns(h, context, columns);
  117 + // 前置插槽
  118 + const prependSlot = scopedSlots.prepend ? scopedSlots.prepend() : '';
  119 + // 左侧插槽
  120 + const leftSlot = scopedSlots.left ? scopedSlots.left() : '';
  121 + // 右侧插槽
  122 + const rightSlot = scopedSlots.right ? scopedSlots.right() : '';
  123 + // 后置插槽
  124 + const appendSlot = scopedSlots.append ? scopedSlots.append() : '';
  125 + // 渲染组件时移除当前组件特有的props,避免透传不必要的参数
  126 + delete context.columns;
  127 + return h('el-table', renderContext(context), [prependSlot, leftSlot, ...elTableColumns, rightSlot, appendSlot]);
  128 + },
  129 +};
  130 +</script>
packages/table/index.vue
@@ -6,8 +6,11 @@ export default { @@ -6,8 +6,11 @@ export default {
6 functional: true, 6 functional: true,
7 render(h, context) { 7 render(h, context) {
8 const props = context.props || {}; 8 const props = context.props || {};
9 - if (props.editable) {  
10 - return ref('z-table-editable', context); 9 + if (Object.prototype.hasOwnProperty.call(props, 'editable') && props.editable !== false) {
  10 + return h('z-table-editable', { props, scopedSlots: context.scopedSlots, on: context.listeners });
  11 + }
  12 + if (props.editor) {
  13 + return ref('z-table-editor', context);
11 } 14 }
12 return ref('z-table-normal', context); 15 return ref('z-table-normal', context);
13 }, 16 },
packages/table/normal.vue
@@ -6,9 +6,15 @@ import { renderContext } from &#39;../utils/vnode&#39;; @@ -6,9 +6,15 @@ import { renderContext } from &#39;../utils/vnode&#39;;
6 function headerRender(h, context, item) { 6 function headerRender(h, context, item) {
7 const headerSlot = context.scopedSlots[`header-${item.prop}`]; 7 const headerSlot = context.scopedSlots[`header-${item.prop}`];
8 return function(scope) { 8 return function(scope) {
  9 + // 自定义具名插槽
9 if (headerSlot) { 10 if (headerSlot) {
10 return headerSlot(scope); 11 return headerSlot(scope);
11 } 12 }
  13 + // 自定义渲染函数
  14 + if (item.header) {
  15 + return item.header(item, h, scope);
  16 + }
  17 + // 默认取值
12 return item.label; 18 return item.label;
13 }; 19 };
14 } 20 }
packages/utils/vnode.js
1 // 注册函数式组件ref 1 // 注册函数式组件ref
2 export function registerRef(vnode, context) { 2 export function registerRef(vnode, context) {
3 - if (!context.data.ref) { 3 + if (!context.data.ref || !vnode.data.hook) {
4 return vnode; 4 return vnode;
5 } 5 }
6 // 备份vnode原有的insert周期函数 6 // 备份vnode原有的insert周期函数