Commit b1fcdaebabecd8a0623b3be0a3c5424fa745a9f6

Authored by 刘汉宸
1 parent 441f73b9

feat: 优化Scheme自定义渲染及详情和删除接口

examples/styles/index.scss
... ... @@ -53,6 +53,21 @@ body {
53 53 }
54 54 }
55 55  
  56 +.zee-scheme__view {
  57 + .el-form-item {
  58 + font-weight: normal !important;
  59 + margin-bottom: 0 !important;
  60 + &__label {
  61 + line-height: 1.5 !important;
  62 + color: #595959 !important;
  63 + }
  64 + &__content {
  65 + line-height: 1.5 !important;
  66 + color: #000 !important;
  67 + }
  68 + }
  69 +}
  70 +
56 71 @media only screen and (min-width: 1400px) {
57 72 .code-snippet-box {
58 73 .code-snippet {
... ...
examples/views/docs/component/scheme.md
... ... @@ -111,6 +111,18 @@ export default {
111 111 <template>
112 112 <z-scheme ref="scheme" :list="list" url="/customer" :http="$http" :alias="{ getUrl: '/getCustomerByCode', getKey: 'code' }" auto real-selection>
113 113 <el-table-column type="selection" align="center" width="40"></el-table-column>
  114 + <template #render-code="{ value, row, openView }">
  115 + <el-link type="primary" @click="openView(row)">{{ value }}</el-link>
  116 + </template>
  117 + <template #view-name="{ value }">
  118 + <el-tag size="mini">{{ value }}</el-tag>
  119 + </template>
  120 + <template #cell-name="{ value }">
  121 + <el-tag size="mini" type="danger">{{ value }}</el-tag>
  122 + </template>
  123 + <template #form-id="{ value }">
  124 + <el-tag size="mini">{{ value }}</el-tag>
  125 + </template>
114 126 </z-scheme>
115 127 </template>
116 128  
... ...
packages/filter/index.vue
1 1 <template>
2   - <z-form v-model="model" class="zee-filter" :list="formattedList" :span="span" :labelWidth="labelWidth" :colClass="colVisibleRender" :size="size">
  2 + <z-form v-model="model" class="zee-filter" :list="formattedList" :span="span" :labelWidth="labelWidth" :colClass="colVisibleRender" :size="size" :formProps="formProps">
3 3 <div slot="$operation" class="zee-filter__button-group">
4 4 <el-button-group :size="size">
5   - <el-button type="primary" @click="handleSearch" :loading="loading"><span>查询</span></el-button>
  5 + <el-button type="primary" @click="handleSearch" :loading="loading" icon="el-icon-search"><span>查询</span></el-button>
6 6 <el-button @click="handleReset"><span>重置</span></el-button>
7 7 <el-button v-if="showCollapsed" @click="collapsed = !collapsed">
8 8 <span>{{ collapsed ? '展开' : '收起' }}</span>
9   - <!-- <i :class="`el-icon-arrow-${collapsed ? 'down' : 'up'}`" class="el-icon--right"></i> -->
10 9 </el-button>
11 10 </el-button-group>
12 11 </div>
... ... @@ -36,6 +35,10 @@ export default {
36 35 default: 3,
37 36 },
38 37 loading: Boolean,
  38 + formProps: {
  39 + type: Object,
  40 + default: () => ({}),
  41 + },
39 42 },
40 43 data() {
41 44 return {
... ...
packages/form/form-render.vue
... ... @@ -47,7 +47,7 @@
47 47 :class="colClassRender(item, index, colClass)"
48 48 >
49 49 <el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.fullKey" :rules="item.rules" :class="itemClass || 'zee-form__item'">
50   - <slot v-if="$slots[item.fullKey]" :name="item.fullKey" :value="itemValue(item)" :model="value"></slot>
  50 + <slot v-if="$scopedSlots[item.fullKey]" :name="item.fullKey" :value="itemValue(item)" :model1="value"></slot>
51 51 <template v-else>
52 52 <!-- 自定义组件 -->
53 53 <dynamic-render
... ...
packages/form/index.vue
... ... @@ -16,7 +16,7 @@
16 16 </style>
17 17  
18 18 <template>
19   - <el-form ref="form" :size="size" :class="formClass" :model="formModel" :label-width="labelWidth" :label-position="labelPosition || labelWidth ? 'right' : 'top'">
  19 + <el-form ref="form" :size="size" :class="formClass" :model="formModel" :label-width="labelWidth" :label-position="labelPosition || labelWidth ? 'right' : 'top'" v-bind="formProps">
20 20 <form-render
21 21 :title-class="titleClass"
22 22 :content-class="contentClass"
... ... @@ -64,15 +64,23 @@ export default {
64 64 type: Number,
65 65 default: 24,
66 66 },
  67 + formProps: {
  68 + type: Object,
  69 + default: () => ({}),
  70 + },
67 71 },
68 72 data() {
69 73 return {
70   - slotKeys: Object.keys(this.$slots),
71 74 model: {},
72 75 formModel: {},
73 76 formList: [],
74 77 };
75 78 },
  79 + computed: {
  80 + slotKeys() {
  81 + return Object.keys(this.$scopedSlots);
  82 + },
  83 + },
76 84 watch: {
77 85 value: {
78 86 handler(val = {}) {
... ...
packages/scheme/index.vue
... ... @@ -4,44 +4,80 @@
4 4  
5 5 <template>
6 6 <div class="zee-scheme">
  7 + <!-- 头部内容 -->
7 8 <div v-if="$scopedSlots.header || $slots.header" class="zee-scheme__header">
8 9 <slot name="header" :filterModel="filterModel" v-bind="_slotScope"></slot>
9 10 </div>
  11 + <!-- 筛选组件 -->
10 12 <div class="zee-scheme__filter">
11   - <z-filter v-if="filter" :value="_filterModel" :list="listMap.filter | noRulesFilter" :size="size" @input="onFilterInput" @search="search" :loading="loading"></z-filter>
  13 + <z-filter
  14 + v-if="filter"
  15 + :value="_filterModel"
  16 + :list="filterList || listMap.filter | noRulesFilter"
  17 + :size="size"
  18 + @input="onFilterInput"
  19 + @search="search"
  20 + :loading="loading"
  21 + v-bind="filterProps"
  22 + ></z-filter>
12 23 </div>
  24 + <!-- 按钮区 -->
13 25 <div v-if="action" class="zee-scheme__action">
14 26 <slot v-if="hadSlot('action')" name="action" v-bind="_slotScope"></slot>
15 27 <template v-else>
16 28 <el-button :size="size" type="primary" @click="openNew">新增</el-button>
17   - <slot name="button" :size="size" v-bind="_slotScope"></slot>
  29 + <el-button :size="size" plain :disabled="selection.length === 0" @click="handleDeleteMul(selection)">删除</el-button>
  30 + <slot name="button" v-bind="_slotScope"></slot>
18 31 </template>
19 32 </div>
  33 + <!-- 表格内容 -->
20 34 <div class="zee-scheme__table">
21 35 <z-table
22 36 ref="table"
23 37 v-model="tableData"
24 38 v-loading="loading"
25   - :list="listMap.table"
  39 + :list="tableList || listMap.table"
26 40 :tableProps="{ border: true, 'row-key': 'id', ...tableProps }"
27 41 :size="size"
28 42 @selection-change="onTableSelectionChange"
29 43 @selection="onTableSelection"
30 44 >
31 45 <slot></slot>
  46 + <!-- 表格列内容渲染 -->
  47 + <template v-for="(item, index) in renderList">
  48 + <template v-if="$scopedSlots[`cell-${item.fullKey}`]">
  49 + <el-table-column :slot="item.fullKey" v-bind="item" :prop="item.agentKey || item.key" :key="`table-cell-${index}`">
  50 + <template slot-scope="{ row, column, $index }">
  51 + <slot :name="`cell-${item.fullKey}`" v-bind="{ ...item, ..._slotScope }" :row="row" :value="row[item.key]" :column="column" :index="$index"></slot>
  52 + </template>
  53 + </el-table-column>
  54 + </template>
  55 + <template v-else-if="$scopedSlots[`render-${item.fullKey}`]">
  56 + <el-table-column :slot="item.fullKey" v-bind="item" :prop="item.agentKey || item.key" :key="`table-render-${index}`">
  57 + <template slot-scope="{ row, column, $index }">
  58 + <slot :name="`render-${item.fullKey}`" v-bind="{ ...item, ..._slotScope }" :row="row" :value="row[item.key]" :column="column" :index="$index"></slot>
  59 + </template>
  60 + </el-table-column>
  61 + </template>
  62 + </template>
  63 + <slot slot="column-append" name="column-append" v-bind="_slotScope"></slot>
  64 + <!-- 表格尾追加操作列 -->
32 65 <template #column-end>
33   - <el-table-column prop="$operation" label="操作" :width="140" fixed="right">
  66 + <slot slot="column-end" name="column-end" v-bind="_slotScope"></slot>
  67 + <el-table-column v-if="operation" prop="$operation" label="操作" v-bind="{ width: 100, fixed: 'right', ...operationProps }">
34 68 <div class="zee-scheme__table-operation" slot-scope="slotScope">
35   - <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(slotScope)"></el-button>
36   - <el-popconfirm confirmButtonText="确定" cancelButtonText="取消" title="确定删除吗?" placement="top" @onConfirm="handleDelete(slotScope)">
  69 + <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(slotScope.row)"></el-button>
  70 + <el-popconfirm confirmButtonText="确定" cancelButtonText="取消" title="确定删除吗?" placement="top" @onConfirm="handleDelete([slotScope.row])">
37 71 <el-button slot="reference" type="text" icon="el-icon-delete" title="删除"></el-button>
38 72 </el-popconfirm>
  73 + <slot name="operation-button" v-bind="_slotScope"></slot>
39 74 </div>
40 75 </el-table-column>
41 76 </template>
42 77 </z-table>
43 78 </div>
44   - <div v-if="pagination" class="zee-scheme__footer">
  79 + <!-- 底部区域 -->
  80 + <div class="zee-scheme__footer">
45 81 <div v-if="selection.length > 0" class="selection-info">
46 82 <span>已选中</span>
47 83 <span class="num">{{ selection.length }}</span>
... ... @@ -50,7 +86,9 @@
50 86 <el-button slot="reference" :size="size" type="text">清除</el-button>
51 87 </el-popconfirm>
52 88 </div>
  89 + <!-- 分页器 -->
53 90 <el-pagination
  91 + v-if="pagination"
54 92 @size-change="handleSizeChange"
55 93 @current-change="handleCurrentChange"
56 94 :current-page="currentPage"
... ... @@ -61,12 +99,62 @@
61 99 >
62 100 </el-pagination>
63 101 </div>
64   - <el-dialog :visible.sync="dialogVisible" :title="dialogTitle" destroy-on-close append-to-body :lock-scroll="false" :close-on-click-modal="false" @closed="onDialogClosed">
  102 + <!-- 弹出框 -->
  103 + <el-dialog
  104 + :visible.sync="dialogVisible"
  105 + :title="dialogTitle"
  106 + destroy-on-close
  107 + append-to-body
  108 + :lock-scroll="false"
  109 + :close-on-click-modal="false"
  110 + @closed="onDialogClosed"
  111 + v-bind="dialogProps"
  112 + >
65 113 <div v-loading="dialogLoading">
  114 + <!-- 自定义弹出框标题 -->
66 115 <slot v-if="hadSlot('dialog-title')" slot="title" name="dialog-title" :dialogType="dialogType" v-bind="_slotScope"></slot>
67 116 <template v-if="dialogRender">
  117 + <!-- 自定义弹出框内容 -->
68 118 <slot v-if="hadSlot(`dialog-${dialogType}`)" :name="`dialog-${dialogType}`" :model="_formModel" v-bind="_slotScope"></slot>
69   - <z-form v-else ref="form" :value="_formModel" :list="listMap.form" label-width="80px" :span="12" @input="onFormInput" @validate="onFormValidate"></z-form>
  119 + <!-- 内置弹出框新增修改表单 -->
  120 + <template v-if="['new', 'edit'].includes(dialogType)">
  121 + <z-form
  122 + ref="form"
  123 + :value="_formModel"
  124 + :list="formList || listMap.form"
  125 + @input="onFormInput"
  126 + @validate="onFormValidate"
  127 + v-bind="{ span: 12, 'label-width': '80px', ...formProps }"
  128 + >
  129 + <!-- 表单自定义插槽 -->
  130 + <template v-for="item in renderList">
  131 + <template v-if="$scopedSlots[`form-${item.fullKey}`]">
  132 + <slot :slot="item.fullKey" :name="`form-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>
  133 + </template>
  134 + </template>
  135 + </z-form>
  136 + </template>
  137 + <!-- 内置弹出框详情表单 -->
  138 + <template v-else>
  139 + <z-form
  140 + ref="form"
  141 + class="zee-scheme__view"
  142 + :value="_formModel"
  143 + :list="viewList || listMap.form | viewTypeFilter | noRulesFilter"
  144 + v-bind="{ span: 12, 'label-width': '80px', ...viewProps }"
  145 + >
  146 + <!-- 详情自定义插槽渲染 -->
  147 + <template v-for="item in renderList">
  148 + <template v-if="$scopedSlots[`view-${item.fullKey}`]">
  149 + <slot :slot="item.fullKey" :name="`view-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>
  150 + </template>
  151 + <template v-else-if="$scopedSlots[`render-${item.fullKey}`]">
  152 + <slot :slot="item.fullKey" :name="`render-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>
  153 + </template>
  154 + </template>
  155 + </z-form>
  156 + </template>
  157 + <!-- 内置弹出框新增修改按钮 -->
70 158 <div class="zee-scheme__dialog-button" v-if="['new', 'edit'].includes(dialogType)">
71 159 <el-button :size="size" type="primary" @click="handleConfirm" :loading="submitting">确定</el-button>
72 160 <el-button :size="size" plain @click="closeDialog">取消</el-button>
... ... @@ -78,47 +166,52 @@
78 166 </template>
79 167  
80 168 <script>
81   -import { cloneDeep } from '../_utils';
  169 +import { cloneDeep, get } from '../_utils';
82 170 import { clear } from '../_utils/param';
83 171  
  172 +let propsMap = {};
  173 +const propsKeys = ['tableProps', 'filterProps', 'formProps', 'viewProps', 'dialogProps', 'operationProps'];
  174 +propsKeys.forEach(key => {
  175 + propsMap[key] = {
  176 + type: Object,
  177 + default: function() {
  178 + return {};
  179 + },
  180 + };
  181 +});
  182 +const apiKeys = ['searchApi', 'submitApi', 'addApi', 'modifyApi', 'getApi', 'viewApi', 'deleteApi'];
  183 +apiKeys.forEach(key => {
  184 + propsMap[key] = {
  185 + type: Function,
  186 + };
  187 +});
  188 +const blockKeys = ['filter', 'action', 'pagination', 'operation'];
  189 +blockKeys.forEach(key => {
  190 + propsMap[key] = {
  191 + type: Boolean,
  192 + default: true,
  193 + };
  194 +});
  195 +
84 196 export default {
85 197 name: 'Scheme',
86 198 props: {
  199 + ...propsMap,
87 200 list: Array,
88   - filter: {
89   - type: Boolean,
90   - default: true,
91   - },
92   - action: {
93   - type: Boolean,
94   - default: true,
95   - },
96   - pagination: {
97   - type: Boolean,
98   - default: true,
99   - },
  201 + filterList: Array,
  202 + tableList: Array,
  203 + formList: Array,
  204 + viewList: Array,
100 205 size: {
101 206 type: String,
102 207 default: 'mini',
103 208 },
104   - tableProps: {
105   - type: Object,
106   - default: () => ({}),
107   - },
108 209 formModel: Object,
109 210 filterModel: Object,
110 211 auto: Boolean,
111 212 realSelection: Boolean,
112   - /* 模板API */
113 213 url: String, // 请求地址
114 214 http: [Function, Promise], // http库
115   - /* 自定义API */
116   - searchApi: Function, // 搜索
117   - submitApi: Function, // 提交
118   - addApi: Function, // 新增
119   - modifyApi: Function, // 修改
120   - getApi: Function, // 查询详情
121   - deleteApi: Function, // 删除
122 215 alias: Object, // 别名配置
123 216 },
124 217 data() {
... ... @@ -161,6 +254,17 @@ export default {
161 254 clearRules(list);
162 255 return list;
163 256 },
  257 + // 详情类型过滤器
  258 + viewTypeFilter(val = []) {
  259 + let list = cloneDeep(val);
  260 + const clearRules = list => {
  261 + list.forEach(item => {
  262 + item.type = (h, { model, config }) => h('span', config, model[item.key]);
  263 + });
  264 + };
  265 + clearRules(list);
  266 + return list;
  267 + },
164 268 },
165 269 computed: {
166 270 listMap() {
... ... @@ -199,6 +303,28 @@ export default {
199 303 });
200 304 return array;
201 305 },
  306 + renderList() {
  307 + // 深度克隆传入的列表,避免原始值被修改
  308 + const newList = cloneDeep(this.list);
  309 + // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key
  310 + const generateFullKey = (list, parentKey) => {
  311 + list.forEach(item => {
  312 + if (item.group && item.list) {
  313 + if (item.group.key) {
  314 + item.fullKey = `${parentKey ? `${parentKey}-${item.group.key}` : item.group.key}`;
  315 + } else {
  316 + item.fullKey = parentKey || item.key;
  317 + }
  318 + generateFullKey(item.list, item.fullKey);
  319 + } else {
  320 + item.fullKey = `${parentKey ? `${parentKey}-${item.key}` : item.key}`;
  321 + }
  322 + });
  323 + };
  324 + // 生成fullKey
  325 + generateFullKey(newList);
  326 + return newList;
  327 + },
202 328 _filterModel() {
203 329 return this.filterModel || this.filterForm || {};
204 330 },
... ... @@ -209,6 +335,9 @@ export default {
209 335 return {
210 336 openDialog: this.openDialog,
211 337 closeDialog: this.closeDialog,
  338 + openView: this.openView,
  339 + handleDelete: this.handleDelete,
  340 + size: this.size,
212 341 };
213 342 },
214 343 _alias() {
... ... @@ -221,6 +350,7 @@ export default {
221 350 },
222 351 },
223 352 methods: {
  353 + get,
224 354 // 空Promise
225 355 emptyPromise() {
226 356 return new Promise(resolve => resolve());
... ... @@ -367,17 +497,44 @@ export default {
367 497 return undefined;
368 498 },
369 499 // 打开编辑弹出框
370   - openEdit({ row }) {
  500 + openEdit(row) {
371 501 this.dialogLoading = true;
372 502 this.openDialog('edit', '编辑');
373 503 const getRow = () =>
374 504 new Promise(resolve => {
375   - resolve({ ...row });
  505 + resolve(row);
376 506 });
377 507 const getAPI = this.getApi || this._getAPI || getRow;
378 508 getAPI(row)
379 509 .then(result => {
380   - this.editForm = { ...result };
  510 + this.editForm = result;
  511 + })
  512 + .finally(() => {
  513 + this.dialogLoading = false;
  514 + });
  515 + },
  516 + // 内置查询详情接口
  517 + _viewAPI(row) {
  518 + if (this.url && (this.http || this.zHttp)) {
  519 + const _http = this.http || this.zHttp;
  520 + const _viewKey = this._alias.viewKey || this._alias.getKey || this._alias.primaryKey || 'id';
  521 + const _resultKey = this._alias.result || 'result';
  522 + return _http({ url: `${clear(this.url)}/${this._alias.getUrl || 'queryById'}`, params: { [_viewKey]: row[_viewKey] } }).then(response => response[_resultKey] || {});
  523 + }
  524 + return undefined;
  525 + },
  526 + // 打开详情弹出框
  527 + openView(row) {
  528 + this.dialogLoading = true;
  529 + this.openDialog('view', '详情');
  530 + const getRow = () =>
  531 + new Promise(resolve => {
  532 + resolve(row);
  533 + });
  534 + const viewAPI = this.viewApi || this.getApi || this._viewAPI || this._getAPI || getRow;
  535 + viewAPI(row)
  536 + .then(result => {
  537 + this.editForm = result;
381 538 })
382 539 .finally(() => {
383 540 this.dialogLoading = false;
... ... @@ -392,7 +549,7 @@ export default {
392 549 return undefined;
393 550 },
394 551 // 删除
395   - handleDelete({ row, $index }) {
  552 + handleDelete(selection) {
396 553 const loading = this.$loading({
397 554 text: '处理中',
398 555 spinner: 'el-icon-loading',
... ... @@ -400,7 +557,7 @@ export default {
400 557 });
401 558 const deleteAPI = this.deleteApi || this._deleteAPI || this.emptyPromise;
402 559 const _deleteKey = this._alias.deleteKey || this._alias.primaryKey || 'id';
403   - const keys = [row[_deleteKey]];
  560 + const keys = selection.map(i => i[_deleteKey]);
404 561 deleteAPI(keys)
405 562 .then(() => {
406 563 this.search();
... ... @@ -410,16 +567,30 @@ export default {
410 567 loading.close();
411 568 });
412 569 },
  570 + // 批量删除
  571 + handleDeleteMul(selection) {
  572 + this.$confirm(`是否删除这 [${selection.length}] 项?`, '提示', {
  573 + confirmButtonText: '确定',
  574 + cancelButtonText: '取消',
  575 + type: 'warning',
  576 + })
  577 + .then(() => {
  578 + this.handleDelete(selection);
  579 + })
  580 + .catch(() => {});
  581 + },
413 582 // 打开弹出框
414 583 openDialog(type, title) {
415 584 this.dialogVisible = true;
416 585 this.dialogRender = true;
417 586 this.dialogType = type;
418 587 this.dialogTitle = title;
  588 + this.$emit('dialog-change', type);
419 589 },
420 590 // 关闭弹出框
421 591 closeDialog() {
422 592 this.dialogVisible = false;
  593 + this.$emit('dialog-change', 'none');
423 594 },
424 595 // 清空表单
425 596 clearEditForm() {
... ...
packages/table/index.vue
... ... @@ -82,9 +82,7 @@ export default {
82 82 // 表格参数
83 83 tableProps: {
84 84 type: Object,
85   - default() {
86   - return {};
87   - },
  85 + default: () => ({}),
88 86 },
89 87 // 表格事件
90 88 tableEvents: Object,
... ...