Commit 8104d35f08d3dec4ac51d9c2f604181275bfe37a

Authored by liuhanchen
1 parent a57cd44f

feat: 兼容旧版表格编辑器

packages/index.js
... ... @@ -2,6 +2,7 @@ import Vue from 'vue';
2 2 import ZTable from './table/index.vue';
3 3 import ZTableNormal from './table/normal';
4 4 import ZTableEditable from './table/editable';
  5 +import ZTableEditor from './table/editor';
5 6 import ElImageViewer from './upload/image-viewer';
6 7  
7 8 let components = {};
... ... @@ -17,6 +18,7 @@ const install = function(Vue, opts = {}) {
17 18 components[ZTable.name] = ZTable;
18 19 components[ZTableNormal.name] = ZTableNormal;
19 20 components[ZTableEditable.name] = ZTableEditable;
  21 + components[ZTableEditor.name] = ZTableEditor;
20 22 Object.values(components).forEach(component => {
21 23 // 组件前缀
22 24 const prefix = opts.name || 'z';
... ...
packages/table/editable copy.vue
... ... @@ -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 61 </template>
62 62  
63 63 <script>
64   -import TableNormal from './normal';
65 64 import tableProps from './props';
66 65 import { cloneDeep, get, set } from '../utils';
67 66  
68 67 export default {
69 68 name: 'TableEditable',
70   - extends: TableNormal,
71 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 79 cellEditor: {
73 80 props: {
74 81 value: [String, Number, Array, Object, Boolean],
... ... @@ -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 137 props: {
123 138 value: {
124 139 type: Array,
... ... @@ -153,12 +168,31 @@ export default {
153 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 189 filters: {
157 190 tableDataFilter(value) {
158 191 return value.map((item, index) => ({ ...item, $index: index }));
159 192 },
160 193 },
161 194 methods: {
  195 + get,
162 196 onHeaderClick() {
163 197 if (this.clickable) {
164 198 this.cancelEditCell();
... ...
packages/table/editor.vue 0 → 100644
... ... @@ -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 6 functional: true,
7 7 render(h, context) {
8 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 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 6 function headerRender(h, context, item) {
7 7 const headerSlot = context.scopedSlots[`header-${item.prop}`];
8 8 return function(scope) {
  9 + // 自定义具名插槽
9 10 if (headerSlot) {
10 11 return headerSlot(scope);
11 12 }
  13 + // 自定义渲染函数
  14 + if (item.header) {
  15 + return item.header(item, h, scope);
  16 + }
  17 + // 默认取值
12 18 return item.label;
13 19 };
14 20 }
... ...
packages/utils/vnode.js
1 1 // 注册函数式组件ref
2 2 export function registerRef(vnode, context) {
3   - if (!context.data.ref) {
  3 + if (!context.data.ref || !vnode.data.hook) {
4 4 return vnode;
5 5 }
6 6 // 备份vnode原有的insert周期函数
... ...