Commit 98832d95e8a8b51d511ba28df8868fc7f5adae65

Authored by liuhanchen
1 parent 4bdc1d1a

feat: 优化表格渲染逻辑

packages/index.js
1 import Vue from 'vue'; 1 import Vue from 'vue';
2 -import ZTable from './table/index'; 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 ElImageViewer from './upload/image-viewer'; 5 import ElImageViewer from './upload/image-viewer';
packages/schema-page/index.vue
@@ -36,36 +36,35 @@ @@ -36,36 +36,35 @@
36 </slot> 36 </slot>
37 </div> 37 </div>
38 <!-- 表格内容 --> 38 <!-- 表格内容 -->
39 - <div v-if="schema.table || $scopedSlots.table" class="z-schema-page__table"> 39 + <div v-if="schema.table || $scopedSlots.table" class="z-schema-page__table" v-loading="schema.loading !== false ? tableLoading : false">
40 <slot name="table" v-bind="_slotScope"> 40 <slot name="table" v-bind="_slotScope">
41 - <z-schema-table  
42 - :size="_size"  
43 - :schema="tableSchemaDefaultProps(schema.table)"  
44 - v-model="tableData"  
45 - v-loading="schema.loading !== false ? tableLoading : false"  
46 - @selection-change="onTableSelectionChange"  
47 - >  
48 - <template #left>  
49 - <el-table-column v-if="schema.selection !== false" type="selection" width="40" align="center"></el-table-column> 41 + <z-schema-table :size="_size" :schema="tableSchemaDefaultProps(schema.table)" :data="tableData" @selection-change="onTableSelectionChange">
  42 + <template #prepend>
  43 + <slot name="table-prepend">
  44 + <el-table-column v-if="schema.selection !== false" type="selection" width="40" align="center"></el-table-column>
  45 + </slot>
50 </template> 46 </template>
51 <template v-for="item in getSlotKeys('table-')" #[item.name]="slotScope"> 47 <template v-for="item in getSlotKeys('table-')" #[item.name]="slotScope">
52 <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot> 48 <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot>
53 </template> 49 </template>
54 - <slot v-if="schema.operation !== false" name="operation" v-bind="_slotScope">  
55 - <el-table-column v-bind="{ label: '操作', width: '90', align: 'center', ...(schema.operation || {}) }">  
56 - <template #default="{ row, column, $index }">  
57 - <div class="z-schema-page__table-operation">  
58 - <slot name="operation-left" v-bind="{ ..._slotScope, row, column, $index }"></slot>  
59 - <slot name="operation-button" v-bind="{ ..._slotScope, row, column, $index }"></slot>  
60 - <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(row)"></el-button>  
61 - <el-popconfirm confirm-button-text="确定" cancel-button-text="取消" title="确定删除吗?" placement="top" @confirm="onDelete([row])">  
62 - <el-button slot="reference" type="text" icon="el-icon-delete" title="删除"></el-button>  
63 - </el-popconfirm>  
64 - <slot name="operation-right" v-bind="{ ..._slotScope, row, column, $index }"></slot>  
65 - </div>  
66 - </template>  
67 - </el-table-column>  
68 - </slot> 50 + <template #append>
  51 + <slot name="table-append" />
  52 + <slot v-if="schema.operation !== false" name="operation" v-bind="_slotScope">
  53 + <el-table-column v-bind="{ label: '操作', width: '90', align: 'center', ...(schema.operation || {}) }">
  54 + <template #default="{ row, column, $index }">
  55 + <div class="z-schema-page__table-operation">
  56 + <slot name="operation-left" v-bind="{ ..._slotScope, row, column, $index }"></slot>
  57 + <slot name="operation-button" v-bind="{ ..._slotScope, row, column, $index }"></slot>
  58 + <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(row)"></el-button>
  59 + <el-popconfirm confirm-button-text="确定" cancel-button-text="取消" title="确定删除吗?" placement="top" @confirm="onDelete([row])">
  60 + <el-button slot="reference" type="text" icon="el-icon-delete" title="删除"></el-button>
  61 + </el-popconfirm>
  62 + <slot name="operation-right" v-bind="{ ..._slotScope, row, column, $index }"></slot>
  63 + </div>
  64 + </template>
  65 + </el-table-column>
  66 + </slot>
  67 + </template>
69 </z-schema-table> 68 </z-schema-table>
70 </slot> 69 </slot>
71 </div> 70 </div>
packages/schema-table/index.vue
1 <script> 1 <script>
  2 +import { ref } from '../utils/vnode';
  3 +
2 export default { 4 export default {
3 name: 'SchemaTable', 5 name: 'SchemaTable',
4 - props: {  
5 - value: {  
6 - type: Array,  
7 - default() {  
8 - return [];  
9 - },  
10 - },  
11 - schema: {  
12 - required: true,  
13 - type: Object,  
14 - default() {  
15 - return {};  
16 - },  
17 - },  
18 - size: String,  
19 - },  
20 - data() {  
21 - return {  
22 - model: this.value,  
23 - };  
24 - },  
25 - watch: {  
26 - value(val = []) {  
27 - this.model = val;  
28 - },  
29 - model: {  
30 - handler(val) {  
31 - this.$emit('input', val);  
32 - },  
33 - deep: true,  
34 - },  
35 - },  
36 - render(h) {  
37 - const schema = this.schema || {};  
38 - const _props = schema.props || {};  
39 - const _on = schema.on || this.$listeners || {};  
40 - return h('z-table', { props: { value: this.model, size: this.size, columns: schema.items, ..._props }, on: _on, scopedSlots: this.$scopedSlots }); 6 + functional: true,
  7 + render(h, context) {
  8 + const props = context.props || {};
  9 + // 当前函数式组件特有props
  10 + const schema = props.schema;
  11 + // 解析schema参数,设置到即将生成的组件上下文中
  12 + context.props.columns = props.schema.items;
  13 + context.props = Object.assign(context.props, schema.props);
  14 + context.listeners = Object.assign(context.listeners, schema.on);
  15 + // 渲染组件时移除当前组件特有的props,避免透传不必要的参数
  16 + delete context.props.schema;
  17 + return ref('z-table', context);
41 }, 18 },
42 }; 19 };
43 </script> 20 </script>
packages/table copy/editable.vue 0 → 100644
@@ -0,0 +1,217 @@ @@ -0,0 +1,217 @@
  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 copy/index.js 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +import tableProps from './props';
  2 +
  3 +export default {
  4 + name: 'Table',
  5 + props: {
  6 + value: {
  7 + type: Array,
  8 + default() {
  9 + return [];
  10 + },
  11 + },
  12 + columns: {
  13 + type: Array,
  14 + default() {
  15 + return [];
  16 + },
  17 + },
  18 + editable: Boolean,
  19 + editall: Boolean,
  20 + clickable: Boolean,
  21 + disabled: Boolean,
  22 + ...tableProps,
  23 + },
  24 + render(h) {
  25 + return h(`z-table-${this.editable ? 'editable' : 'normal'}`, { props: { ...this._props }, scopedSlots: this.$scopedSlots, on: this.$listeners });
  26 + },
  27 +};
packages/table copy/normal.vue 0 → 100644
@@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
  1 +<template>
  2 + <el-table :data="tableData" :size="_elSize" v-bind="bindProps" v-on="$listeners">
  3 + <slot name="left"></slot>
  4 + <template v-for="(item, index) in columns">
  5 + <el-table-column v-bind="item" :key="index">
  6 + <slot :name="`header-${item.prop}`" slot="header"></slot>
  7 + <template v-if="$scopedSlots[`cell-${item.prop}`]" #default="{ row, column, $index }">
  8 + <slot :name="`cell-${item.prop}`" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></slot>
  9 + </template>
  10 + <template v-else-if="item.render && typeof item.render === 'function'" #default="{ row, column, $index }">
  11 + <cell-render :item="item" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></cell-render>
  12 + </template>
  13 + </el-table-column>
  14 + </template>
  15 + <slot></slot>
  16 + <slot name="append"></slot>
  17 + </el-table>
  18 +</template>
  19 +
  20 +<script>
  21 +import tableProps from './props';
  22 +import { get } from '../utils';
  23 +
  24 +export default {
  25 + name: 'TableNormal',
  26 + components: {
  27 + CellRender: {
  28 + functional: true,
  29 + render(h, context) {
  30 + const props = context.props;
  31 + const item = props.item || {};
  32 + const content = item.render(props.value, props.row, h, props.index);
  33 + return typeof content === 'string' ? h('span', {}, [content]) : content;
  34 + },
  35 + },
  36 + },
  37 + inject: {
  38 + elForm: {
  39 + default: '',
  40 + },
  41 + elFormItem: {
  42 + default: '',
  43 + },
  44 + },
  45 + props: {
  46 + value: {
  47 + type: Array,
  48 + default() {
  49 + return [];
  50 + },
  51 + },
  52 + columns: {
  53 + type: Array,
  54 + default() {
  55 + return [];
  56 + },
  57 + },
  58 + ...tableProps,
  59 + },
  60 + data() {
  61 + return {
  62 + tableData: this.value.length > 0 ? this.value : this.data,
  63 + };
  64 + },
  65 + watch: {
  66 + value(val) {
  67 + this.tableData = val || [];
  68 + },
  69 + data(val) {
  70 + this.tableData = val || [];
  71 + },
  72 + },
  73 + computed: {
  74 + _elFormItemSize() {
  75 + return (this.elFormItem || {}).elFormItemSize;
  76 + },
  77 + _elSize() {
  78 + return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size;
  79 + },
  80 + bindProps() {
  81 + const tablePropsKeys = Object.keys(tableProps);
  82 + let props = {};
  83 + Object.keys(this._props).forEach(key => {
  84 + if (tablePropsKeys.includes(key)) {
  85 + props[key] = this._props[key];
  86 + }
  87 + });
  88 + return props;
  89 + },
  90 + },
  91 + methods: {
  92 + get,
  93 + },
  94 +};
  95 +</script>
packages/table copy/props.js 0 → 100644
@@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
  1 +export default {
  2 + data: {
  3 + type: Array,
  4 + default: function() {
  5 + return [];
  6 + },
  7 + },
  8 + size: String,
  9 + width: [String, Number],
  10 + height: [String, Number],
  11 + maxHeight: [String, Number],
  12 + fit: {
  13 + type: Boolean,
  14 + default: true,
  15 + },
  16 + stripe: Boolean,
  17 + border: Boolean,
  18 + rowKey: [String, Function],
  19 + context: {},
  20 + showHeader: {
  21 + type: Boolean,
  22 + default: true,
  23 + },
  24 + showSummary: Boolean,
  25 + sumText: String,
  26 + summaryMethod: Function,
  27 + rowClassName: [String, Function],
  28 + rowStyle: [Object, Function],
  29 + cellClassName: [String, Function],
  30 + cellStyle: [Object, Function],
  31 + headerRowClassName: [String, Function],
  32 + headerRowStyle: [Object, Function],
  33 + headerCellClassName: [String, Function],
  34 + headerCellStyle: [Object, Function],
  35 + highlightCurrentRow: Boolean,
  36 + currentRowKey: [String, Number],
  37 + emptyText: String,
  38 + expandRowKeys: Array,
  39 + defaultExpandAll: Boolean,
  40 + defaultSort: Object,
  41 + tooltipEffect: String,
  42 + spanMethod: Function,
  43 + selectOnIndeterminate: {
  44 + type: Boolean,
  45 + default: true,
  46 + },
  47 + indent: {
  48 + type: Number,
  49 + default: 16,
  50 + },
  51 + treeProps: {
  52 + type: Object,
  53 + default() {
  54 + return {
  55 + hasChildren: 'hasChildren',
  56 + children: 'children',
  57 + };
  58 + },
  59 + },
  60 + lazy: Boolean,
  61 + load: Function,
  62 +};
packages/table/editable copy.vue 0 → 100644
@@ -0,0 +1,217 @@ @@ -0,0 +1,217 @@
  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/index.js
@@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
1 -import tableProps from './props';  
2 -  
3 -export default {  
4 - name: 'Table',  
5 - props: {  
6 - value: {  
7 - type: Array,  
8 - default() {  
9 - return [];  
10 - },  
11 - },  
12 - columns: {  
13 - type: Array,  
14 - default() {  
15 - return [];  
16 - },  
17 - },  
18 - editable: Boolean,  
19 - editall: Boolean,  
20 - clickable: Boolean,  
21 - disabled: Boolean,  
22 - ...tableProps,  
23 - },  
24 - render(h) {  
25 - return h(`z-table-${this.editable ? 'editable' : 'normal'}`, { props: { ...this._props }, scopedSlots: this.$scopedSlots, on: this.$listeners });  
26 - },  
27 -};  
packages/table/index.vue 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +<script>
  2 +import { ref } from '../utils/vnode';
  3 +
  4 +export default {
  5 + name: 'Table',
  6 + functional: true,
  7 + render(h, context) {
  8 + const props = context.props || {};
  9 + if (props.editable) {
  10 + return ref('z-table-editable', context);
  11 + }
  12 + return ref('z-table-normal', context);
  13 + },
  14 +};
  15 +</script>
packages/table/normal copy.vue 0 → 100644
@@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
  1 +<template>
  2 + <el-table :data="tableData" :size="_elSize" v-bind="bindProps" v-on="$listeners">
  3 + <slot name="left"></slot>
  4 + <template v-for="(item, index) in columns">
  5 + <el-table-column v-bind="item" :key="index">
  6 + <slot :name="`header-${item.prop}`" slot="header"></slot>
  7 + <template v-if="$scopedSlots[`cell-${item.prop}`]" #default="{ row, column, $index }">
  8 + <slot :name="`cell-${item.prop}`" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></slot>
  9 + </template>
  10 + <template v-else-if="item.render && typeof item.render === 'function'" #default="{ row, column, $index }">
  11 + <cell-render :item="item" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></cell-render>
  12 + </template>
  13 + </el-table-column>
  14 + </template>
  15 + <slot></slot>
  16 + <slot name="append"></slot>
  17 + </el-table>
  18 +</template>
  19 +
  20 +<script>
  21 +import tableProps from './props';
  22 +import { get } from '../utils';
  23 +
  24 +export default {
  25 + name: 'TableNormal',
  26 + components: {
  27 + CellRender: {
  28 + functional: true,
  29 + render(h, context) {
  30 + const props = context.props;
  31 + const item = props.item || {};
  32 + const content = item.render(props.value, props.row, h, props.index);
  33 + return typeof content === 'string' ? h('span', {}, [content]) : content;
  34 + },
  35 + },
  36 + },
  37 + inject: {
  38 + elForm: {
  39 + default: '',
  40 + },
  41 + elFormItem: {
  42 + default: '',
  43 + },
  44 + },
  45 + props: {
  46 + value: {
  47 + type: Array,
  48 + default() {
  49 + return [];
  50 + },
  51 + },
  52 + columns: {
  53 + type: Array,
  54 + default() {
  55 + return [];
  56 + },
  57 + },
  58 + ...tableProps,
  59 + },
  60 + data() {
  61 + return {
  62 + tableData: this.value.length > 0 ? this.value : this.data,
  63 + };
  64 + },
  65 + watch: {
  66 + value(val) {
  67 + this.tableData = val || [];
  68 + },
  69 + data(val) {
  70 + this.tableData = val || [];
  71 + },
  72 + },
  73 + computed: {
  74 + _elFormItemSize() {
  75 + return (this.elFormItem || {}).elFormItemSize;
  76 + },
  77 + _elSize() {
  78 + return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size;
  79 + },
  80 + bindProps() {
  81 + const tablePropsKeys = Object.keys(tableProps);
  82 + let props = {};
  83 + Object.keys(this._props).forEach(key => {
  84 + if (tablePropsKeys.includes(key)) {
  85 + props[key] = this._props[key];
  86 + }
  87 + });
  88 + return props;
  89 + },
  90 + },
  91 + methods: {
  92 + get,
  93 + },
  94 +};
  95 +</script>
packages/table/normal.vue
1 -<template>  
2 - <el-table :data="tableData" :size="_elSize" v-bind="bindProps" v-on="$listeners">  
3 - <slot name="left"></slot>  
4 - <template v-for="(item, index) in columns">  
5 - <el-table-column v-bind="item" :key="index">  
6 - <slot :name="`header-${item.prop}`" slot="header"></slot>  
7 - <template v-if="$scopedSlots[`cell-${item.prop}`]" #default="{ row, column, $index }">  
8 - <slot :name="`cell-${item.prop}`" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></slot>  
9 - </template>  
10 - <template v-else-if="item.render && typeof item.render === 'function'" #default="{ row, column, $index }">  
11 - <cell-render :item="item" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></cell-render>  
12 - </template>  
13 - </el-table-column>  
14 - </template>  
15 - <slot></slot>  
16 - <slot name="append"></slot>  
17 - </el-table>  
18 -</template>  
19 -  
20 <script> 1 <script>
21 -import tableProps from './props';  
22 import { get } from '../utils'; 2 import { get } from '../utils';
23 3
  4 +// 标题渲染
  5 +function headerRender(h, context, item) {
  6 + const headerSlot = context.scopedSlots[`header-${item.prop}`];
  7 + return function(scope) {
  8 + if (headerSlot) {
  9 + return headerSlot(scope);
  10 + }
  11 + return item.label;
  12 + };
  13 +}
  14 +
  15 +// 单元格渲染
  16 +function cellRender(h, context, item) {
  17 + const cellSlot = context.scopedSlots[`cell-${item.prop}`];
  18 + return function(scope) {
  19 + const value = get(scope.row, item.prop);
  20 + // 自定义具名插槽
  21 + if (cellSlot) {
  22 + return cellSlot({
  23 + item,
  24 + value,
  25 + index: scope.$index,
  26 + ...scope,
  27 + });
  28 + }
  29 + // 自定义渲染函数
  30 + if (item.render) {
  31 + return item.render(value, scope.row, h, scope.$index);
  32 + }
  33 + // 默认取值
  34 + return get(scope.row, item.prop);
  35 + };
  36 +}
  37 +
  38 +// 跟进columns生成列
  39 +function createElTableColumns(h, context, columns) {
  40 + return columns.map((item, index) => {
  41 + const { attrs, on, ...props } = item;
  42 + // 处理插槽
  43 + const scopedSlots = {
  44 + header: headerRender(h, context, item),
  45 + default: cellRender(h, context, item),
  46 + };
  47 + return h('el-table-column', { key: index, attrs, props, on, scopedSlots });
  48 + });
  49 +}
  50 +
24 export default { 51 export default {
25 name: 'TableNormal', 52 name: 'TableNormal',
26 - components: {  
27 - CellRender: {  
28 - functional: true,  
29 - render(h, context) {  
30 - const props = context.props;  
31 - const item = props.item || {};  
32 - const content = item.render(props.value, props.row, h, props.index);  
33 - return typeof content === 'string' ? h('span', {}, [content]) : content;  
34 - },  
35 - },  
36 - },  
37 - inject: {  
38 - elForm: {  
39 - default: '',  
40 - },  
41 - elFormItem: {  
42 - default: '',  
43 - },  
44 - },  
45 - props: {  
46 - value: {  
47 - type: Array,  
48 - default() {  
49 - return [];  
50 - },  
51 - },  
52 - columns: {  
53 - type: Array,  
54 - default() {  
55 - return [];  
56 - },  
57 - },  
58 - ...tableProps,  
59 - },  
60 - data() {  
61 - return {  
62 - tableData: this.value.length > 0 ? this.value : this.data,  
63 - };  
64 - },  
65 - watch: {  
66 - value(val) {  
67 - this.tableData = val || [];  
68 - },  
69 - data(val) {  
70 - this.tableData = val || [];  
71 - },  
72 - },  
73 - computed: {  
74 - _elFormItemSize() {  
75 - return (this.elFormItem || {}).elFormItemSize;  
76 - },  
77 - _elSize() {  
78 - return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size;  
79 - },  
80 - bindProps() {  
81 - const tablePropsKeys = Object.keys(tableProps);  
82 - let props = {};  
83 - Object.keys(this._props).forEach(key => {  
84 - if (tablePropsKeys.includes(key)) {  
85 - props[key] = this._props[key];  
86 - }  
87 - });  
88 - return props;  
89 - },  
90 - },  
91 - methods: {  
92 - get, 53 + functional: true,
  54 + render(h, context) {
  55 + const props = context.props || {};
  56 + let scopedSlots = context.scopedSlots || {};
  57 + // 如有默认插槽则相当于直接写el-table
  58 + if (scopedSlots.default) {
  59 + return h('el-table', context);
  60 + }
  61 + const columns = props.columns || [];
  62 + // 通过columns快速生成el-table-column
  63 + const elTableColumns = createElTableColumns(h, context, columns);
  64 + // 前置插槽
  65 + const prependSlot = scopedSlots.prepend ? scopedSlots.prepend() : '';
  66 + // 后置插槽
  67 + const appendSlot = scopedSlots.append ? scopedSlots.append() : '';
  68 + // 渲染组件时移除当前组件特有的props,避免透传不必要的参数
  69 + delete context.columns;
  70 + return h('el-table', context, [prependSlot, ...elTableColumns, appendSlot]);
93 }, 71 },
94 }; 72 };
95 </script> 73 </script>
packages/table/props.js
@@ -5,6 +5,7 @@ export default { @@ -5,6 +5,7 @@ export default {
5 return []; 5 return [];
6 }, 6 },
7 }, 7 },
  8 + mode: 'normal', // normal | edit-column | edit-cell | edit-row | edit-all
8 size: String, 9 size: String,
9 width: [String, Number], 10 width: [String, Number],
10 height: [String, Number], 11 height: [String, Number],
packages/utils/vnode.js 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +// 注册函数式组件ref
  2 +export function registerRef(vnode, context) {
  3 + if (!context.data.ref) {
  4 + return vnode;
  5 + }
  6 + // 备份vnode原有的insert周期函数
  7 + const hackInsert = vnode.data.hook.insert;
  8 + // 新的vnode的insert周期函数
  9 + vnode.data.hook.insert = function(config) {
  10 + hackInsert(config);
  11 + // 当vnode生成实例后,通过上下文反写入父组件的refs;
  12 + context.parent.$refs[context.data.ref] = config.componentInstance || config.elm; // ref本身就有组件实例和dom节点两种情况,优先取实例
  13 + };
  14 + return vnode;
  15 +}
  16 +
  17 +// 简写注册ref
  18 +export function ref(name, context) {
  19 + return registerRef(context._c(name, context), context);
  20 +}
  21 +
  22 +export default {
  23 + registerRef,
  24 + ref,
  25 +};