Commit 57f06f4f36d0623a9f6a86c7e803eef6824b12ab

Authored by 刘汉宸
1 parent db24c087

feat: 修改SchemaPage组件

examples/router/routes.js
1 import DefaultLayout from '@/views/layout/default'; 1 import DefaultLayout from '@/views/layout/default';
2 import ComponentLayout from '@/views/layout/component'; 2 import ComponentLayout from '@/views/layout/component';
3 import DevelopLayout from '@/views/layout/develop'; 3 import DevelopLayout from '@/views/layout/develop';
  4 +import DesignLayout from '@/views/layout/design';
4 5
5 // 开发指南的文档 6 // 开发指南的文档
6 7
@@ -89,10 +90,10 @@ const _components = [ @@ -89,10 +90,10 @@ const _components = [
89 component: () => import('@/views/docs/component/schema-table.md'), 90 component: () => import('@/views/docs/component/schema-table.md'),
90 }, 91 },
91 { 92 {
92 - path: 'schema',  
93 - name: 'schema',  
94 - meta: { title: 'Schema 方案' },  
95 - component: () => import('@/views/docs/component/schema.md'), 93 + path: 'schema-page',
  94 + name: 'schema-page',
  95 + meta: { title: 'Schema Page 页面' },
  96 + component: () => import('@/views/docs/component/schema-page.md'),
96 }, 97 },
97 ], 98 ],
98 }, 99 },
@@ -139,24 +140,6 @@ const _develops = [ @@ -139,24 +140,6 @@ const _develops = [
139 meta: { title: 'Schema 介绍' }, 140 meta: { title: 'Schema 介绍' },
140 component: () => import('@/views/docs/develop/schema/introduce.md'), 141 component: () => import('@/views/docs/develop/schema/introduce.md'),
141 }, 142 },
142 - {  
143 - path: 'schema-form',  
144 - name: 'developSchemaForm',  
145 - meta: { title: 'Schema Form 方案表单' },  
146 - component: () => import('@/views/docs/develop/schema/schema-form.md'),  
147 - },  
148 - {  
149 - path: 'schema-table',  
150 - name: 'developSchemaTable',  
151 - meta: { title: 'Schema Table 方案表格' },  
152 - component: () => import('@/views/docs/develop/schema/schema-table.md'),  
153 - },  
154 - {  
155 - path: 'schema-page',  
156 - name: 'developSchemaPage',  
157 - meta: { title: 'Schema Page 方案页面' },  
158 - component: () => import('@/views/docs/develop/schema/schema-page.md'),  
159 - },  
160 ], 143 ],
161 }, 144 },
162 ]; 145 ];
@@ -166,6 +149,25 @@ _develops.forEach(data => { @@ -166,6 +149,25 @@ _develops.forEach(data => {
166 _develops_children = [..._develops_children, ...data.children]; 149 _develops_children = [..._develops_children, ...data.children];
167 }); 150 });
168 151
  152 +const _designs = [
  153 + {
  154 + group: '表格',
  155 + children: [
  156 + {
  157 + path: 'table',
  158 + name: 'designTable',
  159 + meta: { title: '表格设计规范' },
  160 + component: () => import('@/views/docs/design/table.md'),
  161 + },
  162 + ],
  163 + },
  164 +];
  165 +
  166 +let _designs_children = [];
  167 +_designs.forEach(data => {
  168 + _designs_children = [..._designs_children, ...data.children];
  169 +});
  170 +
169 // 用于导航的页面 171 // 用于导航的页面
170 const _pages = [ 172 const _pages = [
171 { 173 {
@@ -190,6 +192,14 @@ const _pages = [ @@ -190,6 +192,14 @@ const _pages = [
190 children: [..._components_children, ..._guides, ..._others], 192 children: [..._components_children, ..._guides, ..._others],
191 }, 193 },
192 { 194 {
  195 + path: '/design',
  196 + name: 'design',
  197 + meta: { title: '设计', path: '/design' },
  198 + component: DesignLayout,
  199 + redirect: `/design/${_designs[0].children[0].path}`,
  200 + children: [..._designs_children],
  201 + },
  202 + {
193 path: '/develop', 203 path: '/develop',
194 name: 'develop', 204 name: 'develop',
195 meta: { title: '开发', path: '/develop' }, 205 meta: { title: '开发', path: '/develop' },
@@ -204,6 +214,7 @@ export const guides = _guides; @@ -204,6 +214,7 @@ export const guides = _guides;
204 export const components = _components; 214 export const components = _components;
205 export const others = _others; 215 export const others = _others;
206 export const develops = _develops; 216 export const develops = _develops;
  217 +export const designs = _designs;
207 218
208 export default [ 219 export default [
209 { path: '*', redirect: '/404', hidden: true }, 220 { path: '*', redirect: '/404', hidden: true },
examples/views/docs/component/schema-page.md 0 → 100644
@@ -0,0 +1,573 @@ @@ -0,0 +1,573 @@
  1 +# Schema Page 方案页面
  2 +
  3 +根据JSON Schema配置自动生成一个包含搜索、表格、表单、详情功能的页面
  4 +
  5 +## 基础用法
  6 +
  7 +`schema`设置配置项,其中包括`filter`、`table`、`form`三个基本schema配置,配置方式分别对应`z-schema-filter`、`z-schema-table`、`z-schema-form`,绑定值则分别对应`value-filter`、`value-table`、`value-form`,由于是多个双向绑定的值,所以使用`sync`修饰符来做双向绑定。
  8 +
  9 +::: snippet 基础示例
  10 +
  11 +```html
  12 +<template>
  13 + <z-schema-page :schema="schema" :value-filter.sync="filterModel" :value-table.sync="tableModel" :value-form.sync="formModel"></z-schema-page>
  14 +</template>
  15 +
  16 +<script>
  17 +export default {
  18 + data() {
  19 + return {
  20 + schema: {
  21 + filter: {
  22 + items: [
  23 + { is: 'el-input', prop: 'name', label: '姓名' },
  24 + ]
  25 + },
  26 + table: {
  27 + props: { size: 'mini', border: true } ,
  28 + items: [
  29 + { prop: 'name', label: '姓名' },
  30 + { prop: 'age', label: '年龄' },
  31 + { prop: 'address', label: '地址' },
  32 + { prop: 'status', label: '状态' },
  33 + ]
  34 + },
  35 + form: {
  36 + props: { labelWidth: '70px', size: 'small', span: 12 },
  37 + items: [
  38 + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] },
  39 + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] },
  40 + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 },
  41 + ]
  42 + },
  43 + },
  44 + filterModel: { name: '', age: '', gender: '', address: '' },
  45 + formModel: { name: '', age: '', address: '' },
  46 + tableModel: [
  47 + { id: '0', name: '李饼', age: 32, address: '地址0', status: '正常' },
  48 + { id: '1', name: '陈拾', age: 20, address: '地址1', status: '正常' },
  49 + { id: '3', name: '王七', age: 26, address: '地址3', status: '正常' },
  50 + { id: '4', name: '崔倍', age: 27, address: '地址4', status: '正常' },
  51 + { id: '5', name: '孙豹', age: 38, address: '地址5', status: '正常' },
  52 + ],
  53 + }
  54 + },
  55 +}
  56 +</script>
  57 +```
  58 +
  59 +:::
  60 +
  61 +## 详情入口
  62 +
  63 +可以通过自定义插槽的方式,在单元格内指定详情入口。由于三个子组件均支持自定义插槽,所以在本组件中使用自定义插槽时需要加上子组件**前缀**。
  64 +
  65 +::: snippet 配置表格中的详情入口可以使用`table-cell-`插槽。
  66 +
  67 +```html
  68 +<template>
  69 + <z-schema-page :schema="schema" :value-filter.sync="filterModel" :value-table.sync="tableModel" :value-form.sync="formModel">
  70 + <template #table-cell-name="{ value, row, openDetail }">
  71 + <el-link type="primary" @click="openDetail(row)">{{ value }}</el-link>
  72 + </template>
  73 + </z-schema-page>
  74 +</template>
  75 +
  76 +<script>
  77 +export default {
  78 + data() {
  79 + return {
  80 + schema: {
  81 + filter: {
  82 + items: [
  83 + { is: 'el-input', prop: 'name', label: '姓名' },
  84 + ]
  85 + },
  86 + table: {
  87 + props: { size: 'mini', border: true } ,
  88 + items: [
  89 + { prop: 'name', label: '姓名' },
  90 + { prop: 'age', label: '年龄' },
  91 + { prop: 'address', label: '地址' },
  92 + { prop: 'status', label: '状态' },
  93 + ]
  94 + },
  95 + form: {
  96 + props: { labelWidth: '70px', size: 'small', span: 12 },
  97 + items: [
  98 + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] },
  99 + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] },
  100 + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 },
  101 + ]
  102 + },
  103 + },
  104 + filterModel: { name: '', age: '', gender: '', address: '' },
  105 + formModel: { name: '', age: '', address: '' },
  106 + tableModel: [
  107 + { id: '0', name: '李饼', age: 32, address: '地址0', status: '正常' },
  108 + { id: '1', name: '陈拾', age: 20, address: '地址1', status: '正常' },
  109 + { id: '3', name: '王七', age: 26, address: '地址3', status: '正常' },
  110 + { id: '4', name: '崔倍', age: 27, address: '地址4', status: '正常' },
  111 + { id: '5', name: '孙豹', age: 38, address: '地址5', status: '正常' },
  112 + ],
  113 + }
  114 + },
  115 +}
  116 +</script>
  117 +```
  118 +
  119 +:::
  120 +
  121 +## 对接接口
  122 +
  123 +本组件预置了增删改查逻辑,因此分别对应`api-search`、`api-new`、`api-edit`、`api-get`、`api-delete`五个基本接口。
  124 +
  125 +::: snippet 接口格式为返回一个**Promise**对象的**Function**。其中,`api-search`的执行结果必须是{ list: [...], total: n }的格式,`api-get`的执行结果必须与`valur-form`相对应,`api-new`、`api-edit`、`api-delete`保持默认的**Promise**的resolve或reject逻辑即可,详情见示例。
  126 +
  127 +```html
  128 +<template>
  129 + <z-schema-page
  130 + :schema="schema"
  131 + :value-filter.sync="filterModel"
  132 + :value-form.sync="formModel"
  133 + :api-search="searchAPI"
  134 + :api-get="getAPI"
  135 + :api-delete="deleteAPI"
  136 + :api-new="newAPI"
  137 + :api-edit="editAPI"
  138 + >
  139 + <template #table-cell-name="{ value, row, openDetail }">
  140 + <el-link type="primary" @click="openDetail(row)">{{ value }}</el-link>
  141 + </template>
  142 + </z-schema-page>
  143 +</template>
  144 +
  145 +<script>
  146 +export default {
  147 + data() {
  148 + return {
  149 + schema: {
  150 + auto: true,
  151 + filter: {
  152 + items: [
  153 + { is: 'el-input', prop: 'name', label: '姓名' },
  154 + ]
  155 + },
  156 + table: {
  157 + props: { size: 'mini', border: true } ,
  158 + items: [
  159 + { prop: 'name', label: '姓名' },
  160 + { prop: 'age', label: '年龄' },
  161 + { prop: 'address', label: '地址' },
  162 + { prop: 'status', label: '状态' },
  163 + ]
  164 + },
  165 + form: {
  166 + props: { labelWidth: '70px', size: 'small', span: 12 },
  167 + items: [
  168 + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] },
  169 + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] },
  170 + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 },
  171 + ]
  172 + },
  173 + },
  174 + filterModel: { name: '', age: '', gender: '', address: '' },
  175 + formModel: { name: '', age: '', address: '' },
  176 + }
  177 + },
  178 + methods: {
  179 + searchAPI(params) {
  180 + console.log('search', params);
  181 + return new Promise(resolve => {
  182 + setTimeout(() => {
  183 + const list = [
  184 + { id: '0', name: '李饼', age: 32, address: '地址0', status: '正常' },
  185 + { id: '1', name: '陈拾', age: 20, address: '地址1', status: '正常' },
  186 + { id: '3', name: '王七', age: 26, address: '地址3', status: '正常' },
  187 + { id: '4', name: '崔倍', age: 27, address: '地址4', status: '正常' },
  188 + { id: '5', name: '孙豹', age: 38, address: '地址5', status: '正常' },
  189 + ]
  190 + resolve({
  191 + list,
  192 + total: list.length
  193 + });
  194 + }, 500);
  195 + });
  196 + },
  197 + getAPI(row) {
  198 + console.log('get', row);
  199 + return new Promise((resolve, reject) => {
  200 + setTimeout(() => {
  201 + resolve({ ...row, name: `[${row.name}]` });
  202 + }, 1500);
  203 + });
  204 + },
  205 + newAPI(model) {
  206 + console.log('new', model);
  207 + return new Promise((resolve, reject) => {
  208 + setTimeout(() => {
  209 + resolve();
  210 + }, 1500);
  211 + });
  212 + },
  213 + editAPI(model) {
  214 + console.log('edit', model);
  215 + return new Promise((resolve, reject) => {
  216 + setTimeout(() => {
  217 + reject();
  218 + }, 1500);
  219 + });
  220 + },
  221 + deleteAPI(selection) {
  222 + console.log('delete', selection);
  223 + return new Promise((resolve, reject) => {
  224 + setTimeout(() => {
  225 + resolve();
  226 + }, 1500);
  227 + });
  228 + }
  229 + }
  230 +}
  231 +</script>
  232 +```
  233 +
  234 +:::
  235 +
  236 +## 详情接口
  237 +
  238 +由于可能出现详情与表单不同的情况,因此本组件提供了`value-detail`属性和`api-detail`接口可独立维护详情页面,若详情表单项也不同,则可以在`schema`中配置`detail`来设置。
  239 +
  240 +::: snippet 若没有详情绑定值属性和接口,则默认以表单的值为准。
  241 +
  242 +```html
  243 +<template>
  244 + <z-schema-page
  245 + :schema="schema"
  246 + :value-filter.sync="filterModel"
  247 + :value-form.sync="formModel"
  248 + :value-detail.sync="detailModel"
  249 + :api-search="searchAPI"
  250 + :api-get="getAPI"
  251 + :api-detail="detailAPI"
  252 + :api-delete="deleteAPI"
  253 + :api-new="newAPI"
  254 + :api-edit="editAPI"
  255 + >
  256 + <template #table-cell-name="{ value, row, openDetail }">
  257 + <el-link type="primary" @click="openDetail(row)">{{ value }}</el-link>
  258 + </template>
  259 + <template #detail-packages="{ value }">
  260 + <el-tag v-for="item in value" size="mini" type="info" style="margin-right: 10px">{{ item }}</el-tag>
  261 + </template>
  262 + </z-schema-page>
  263 +</template>
  264 +
  265 +<script>
  266 +export default {
  267 + data() {
  268 + return {
  269 + schema: {
  270 + auto: true,
  271 + filter: {
  272 + items: [
  273 + { is: 'el-input', prop: 'name', label: '姓名' },
  274 + ]
  275 + },
  276 + table: {
  277 + props: { size: 'mini', border: true } ,
  278 + items: [
  279 + { prop: 'name', label: '姓名' },
  280 + { prop: 'age', label: '年龄' },
  281 + { prop: 'address', label: '地址' },
  282 + { prop: 'status', label: '状态' },
  283 + ]
  284 + },
  285 + form: {
  286 + props: { labelWidth: '70px', size: 'small', span: 12 },
  287 + items: [
  288 + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] },
  289 + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] },
  290 + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 },
  291 + ]
  292 + },
  293 + detail: {
  294 + props: { labelWidth: '70px', size: 'small', span: 12 },
  295 + items: [
  296 + { prop: 'name', label: '姓名' },
  297 + { prop: 'age', label: '年龄' },
  298 + { props: { type: 'textarea' }, prop: 'address', label: '住址' },
  299 + { prop: 'packages', label: '包裹', span: 24 }
  300 + ]
  301 + }
  302 + },
  303 + filterModel: { name: '', age: '', gender: '', address: '' },
  304 + formModel: { name: '', age: '', address: '' },
  305 + detailModel: { name: '', age: '', address: '', packages: [] },
  306 + }
  307 + },
  308 + methods: {
  309 + searchAPI(params) {
  310 + console.log('search', params);
  311 + return new Promise(resolve => {
  312 + setTimeout(() => {
  313 + const list = [
  314 + { id: '0', name: '李饼', age: 32, address: '地址0', status: '正常' },
  315 + { id: '1', name: '陈拾', age: 20, address: '地址1', status: '正常' },
  316 + { id: '3', name: '王七', age: 26, address: '地址3', status: '正常' },
  317 + { id: '4', name: '崔倍', age: 27, address: '地址4', status: '正常' },
  318 + { id: '5', name: '孙豹', age: 38, address: '地址5', status: '正常' },
  319 + ]
  320 + resolve({
  321 + list,
  322 + total: list.length
  323 + });
  324 + }, 500);
  325 + });
  326 + },
  327 + getAPI(row) {
  328 + console.log('get', row);
  329 + return new Promise((resolve, reject) => {
  330 + setTimeout(() => {
  331 + resolve({ ...row, name: `[${row.name}]` });
  332 + }, 1500);
  333 + });
  334 + },
  335 + detailAPI(row) {
  336 + console.log('detail', row);
  337 + return new Promise((resolve, reject) => {
  338 + setTimeout(() => {
  339 + resolve({ ...row, packages: ['手机', '电脑'], address: '上海市青浦区华新镇纪鹤公路1988号' });
  340 + }, 1500);
  341 + });
  342 + },
  343 + newAPI(model) {
  344 + console.log('new', model);
  345 + return new Promise((resolve, reject) => {
  346 + setTimeout(() => {
  347 + resolve();
  348 + }, 1500);
  349 + });
  350 + },
  351 + editAPI(model) {
  352 + console.log('edit', model);
  353 + return new Promise((resolve, reject) => {
  354 + setTimeout(() => {
  355 + reject();
  356 + }, 1500);
  357 + });
  358 + },
  359 + deleteAPI(selection) {
  360 + console.log('delete', selection);
  361 + return new Promise((resolve, reject) => {
  362 + setTimeout(() => {
  363 + resolve();
  364 + }, 1500);
  365 + });
  366 + }
  367 + }
  368 +}
  369 +</script>
  370 +```
  371 +
  372 +:::
  373 +
  374 +## 弹窗类型
  375 +
  376 +除了本组件内置的`new`、`edit`、`detail`三种弹出框模式之外,还可以通过任意插槽打开任意自定义弹出框。也支持重新定义原有的三种弹框,同时也需要重新自定义表单校验和提交等逻辑。
  377 +
  378 +::: snippet 插槽提供`openDialog`打开弹出框方法,参数类型为(type: 弹出框类型, title: 弹出框标题, config: 弹出框配置),弹出框主体通过`dialog-`插槽定义。`closeDialog`为关闭弹出框。
  379 +
  380 +```html
  381 +<template>
  382 + <z-schema-page :schema="schema" :value-table.sync="tableModel">
  383 + <template #action-button="{ size, openDialog }">
  384 + <el-button :size="size" @click="openDialog('bill', '账单', { width: '70%' })">账单</el-button>
  385 + </template>
  386 + <template #operation-button="{ openDialog, row }">
  387 + <el-button type="text" @click="openDialog('load', `配置-${row.name}`, { width: '400px' })">配载</el-button>
  388 + </template>
  389 + <template #table-cell-name="{ value, row, openDetail }">
  390 + <el-link type="primary" @click="openDetail(row)">{{ value }}</el-link>
  391 + </template>
  392 + <template #dialog-bill="{ closeDialog }">
  393 + <z-schema-table v-model="billData" :schema="billSchema"></z-schema-table>
  394 + <div style="text-align: center; margin-top: 20px">
  395 + <el-button plain @click="closeDialog">关闭</el-button>
  396 + </div>
  397 + </template>
  398 + <template #dialog-load="{ closeDialog }">
  399 + <el-alert title="这里是一段自定义信息" type="error" show-icon style="margin-bottom: 20px"></el-alert>
  400 + <z-schema-form v-model="loadModel" :schema="loadSchema"></z-schema-form>
  401 + <el-button type="primary" @click="closeDialog">关闭弹出框</el-button>
  402 + </template>
  403 + <template #dialog-new="{ closeDialog }">
  404 + <el-alert title="这里是自定义新增弹窗" type="success" show-icon style="margin-bottom: 20px"></el-alert>
  405 + <el-button plain @click="closeDialog">关闭</el-button>
  406 + </template>
  407 + <template #dialog-edit="{ closeDialog }">
  408 + <el-alert title="这里是自定义编辑弹窗" type="warning" show-icon style="margin-bottom: 20px"></el-alert>
  409 + <el-button plain @click="closeDialog">关闭</el-button>
  410 + </template>
  411 + <template #dialog-detail="{ closeDialog }">
  412 + <el-alert title="这里是自定义详情弹窗" type="info" show-icon style="margin-bottom: 20px"></el-alert>
  413 + <el-button plain @click="closeDialog">关闭</el-button>
  414 + </template>
  415 + </z-schema-page>
  416 +</template>
  417 +
  418 +<script>
  419 +export default {
  420 + data() {
  421 + return {
  422 + schema: {
  423 + filter: {
  424 + items: [
  425 + { is: 'el-input', prop: 'name', label: '姓名' },
  426 + ]
  427 + },
  428 + table: {
  429 + props: { size: 'mini', border: true } ,
  430 + items: [
  431 + { prop: 'name', label: '姓名' },
  432 + { prop: 'age', label: '年龄' },
  433 + { prop: 'address', label: '地址' },
  434 + { prop: 'status', label: '状态' },
  435 + ]
  436 + },
  437 + form: {
  438 + props: { labelWidth: '70px', size: 'small', span: 12 },
  439 + items: [
  440 + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] },
  441 + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] },
  442 + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 },
  443 + ]
  444 + },
  445 + operation: { width: 120 }
  446 + },
  447 + tableModel: [
  448 + { id: '0', name: '李饼', age: 32, address: '地址0', status: '正常' },
  449 + { id: '1', name: '陈拾', age: 20, address: '地址1', status: '正常' },
  450 + { id: '3', name: '王七', age: 26, address: '地址3', status: '正常' },
  451 + { id: '4', name: '崔倍', age: 27, address: '地址4', status: '正常' },
  452 + { id: '5', name: '孙豹', age: 38, address: '地址5', status: '正常' },
  453 + ],
  454 + loadModel: {
  455 + name: '',
  456 + count: 1,
  457 + packages: []
  458 + },
  459 + loadSchema: {
  460 + props: { 'label-width': '70px', size: 'small' },
  461 + items: [
  462 + { is: 'el-input', prop: 'name', label: '名称' },
  463 + { is: 'el-input-number', prop: 'count', label: '数量' },
  464 + {
  465 + is: 'el-checkbox-group',
  466 + prop: 'packages',
  467 + label: '包裹',
  468 + children: [
  469 + { is: 'el-checkbox', props: { label: '零食' } },
  470 + { is: 'el-checkbox', props: { label: '手机' } },
  471 + { is: 'el-checkbox', props: { label: '电脑' } },
  472 + ],
  473 + },
  474 + ]
  475 + },
  476 + billData: [
  477 + { billno: 'B20210401000001', amount: '18750' },
  478 + { billno: 'B20210401000002', amount: '637' },
  479 + ],
  480 + billSchema: {
  481 + items: [
  482 + { label: '单号', prop: 'billno' },
  483 + { label: '金额', prop: 'amount' },
  484 + ]
  485 + }
  486 + }
  487 + },
  488 +}
  489 +</script>
  490 +```
  491 +
  492 +:::
  493 +
  494 +## 按钮权限
  495 +
  496 +本组件不包含自定义业务逻辑,因此配置项不包含权限判断,如果需要按钮的权限判断,可以通过`action`插槽和`operation`插槽将渲染逻辑暴露在视图模板中,然后进行自定义判断。
  497 +
  498 +::: snippet 本示例项目中没有`v-permission`等自定义权限指令,使用时根据实际情况在对应的按钮加上判断即可
  499 +
  500 +```html
  501 +<template>
  502 + <z-schema-page :schema="schema" :value-filter.sync="filterModel" :value-table.sync="tableModel" :value-form.sync="formModel">
  503 + <template #action="{ size, selection, openNew, onDeleteMultiple }">
  504 + <el-button :size="size" type="primary" @click="openNew">新增</el-button>
  505 + <el-button :size="size" plain :disabled="selection.length === 0" @click="onDeleteMultiple(selection)">删除</el-button>
  506 + </template>
  507 + <template #operation="{ openEdit, onDelete }">
  508 + <el-table-column label="操作" width="90" align="center">
  509 + <template #default="{ row, column, $index }">
  510 + <div class="z-schema-page__table-operation">
  511 + <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(row)"></el-button>
  512 + <el-popconfirm confirm-button-text="确定" cancel-button-text="取消" title="确定删除吗?" placement="top" @confirm="onDelete([row])">
  513 + <el-button slot="reference" type="text" icon="el-icon-delete" title="删除"></el-button>
  514 + </el-popconfirm>
  515 + </div>
  516 + </template>
  517 + </el-table-column>
  518 + </template>
  519 + </z-schema-page>
  520 +</template>
  521 +
  522 +<script>
  523 +export default {
  524 + data() {
  525 + return {
  526 + schema: {
  527 + filter: {
  528 + items: [
  529 + { is: 'el-input', prop: 'name', label: '姓名' },
  530 + ]
  531 + },
  532 + table: {
  533 + props: { size: 'mini', border: true } ,
  534 + items: [
  535 + { prop: 'name', label: '姓名' },
  536 + { prop: 'age', label: '年龄' },
  537 + { prop: 'address', label: '地址' },
  538 + { prop: 'status', label: '状态' },
  539 + ]
  540 + },
  541 + form: {
  542 + props: { labelWidth: '70px', size: 'small', span: 12 },
  543 + items: [
  544 + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] },
  545 + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] },
  546 + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 },
  547 + ]
  548 + },
  549 + },
  550 + filterModel: { name: '', age: '', gender: '', address: '' },
  551 + formModel: { name: '', age: '', address: '' },
  552 + tableModel: [
  553 + { id: '0', name: '李饼', age: 32, address: '地址0', status: '正常' },
  554 + { id: '1', name: '陈拾', age: 20, address: '地址1', status: '正常' },
  555 + { id: '3', name: '王七', age: 26, address: '地址3', status: '正常' },
  556 + { id: '4', name: '崔倍', age: 27, address: '地址4', status: '正常' },
  557 + { id: '5', name: '孙豹', age: 38, address: '地址5', status: '正常' },
  558 + ],
  559 + }
  560 + },
  561 +}
  562 +</script>
  563 +```
  564 +
  565 +:::
  566 +
  567 +## API
  568 +
  569 +## Attribute 属性
  570 +
  571 +参数|说明|类型|可选值|默认值
  572 +-|-|-|-|-
  573 +schema | JSON Schema配置项列表 | Array | - | []
examples/views/docs/component/schema.md
@@ -1,155 +0,0 @@ @@ -1,155 +0,0 @@
1 -# Schema 方案  
2 -  
3 -根据JSON Schema配置自动生成一个包含搜索条件、表格、编辑表单、详情表单的页面  
4 -  
5 -## 基础用法  
6 -  
7 -通过配置`JSON Schema`快速生成CURD逻辑  
8 -  
9 -::: snippet 通过`list`配置项目  
10 -  
11 -```html  
12 -<template>  
13 - <z-schema :list="list" :search-api="searchAPI" :get-api="getAPI" :submit-api="submitAPI" :delete-api="deleteAPI" auto real-selection>  
14 - <el-table-column type="selection" align="center" width="40"></el-table-column>  
15 - <template #header>  
16 - <el-tabs v-model="activeName">  
17 - <el-tab-pane label="待执行" name="wait"></el-tab-pane>  
18 - <el-tab-pane label="已执行" name="done"></el-tab-pane>  
19 - </el-tabs>  
20 - </template>  
21 - <template #button="{ size, openDialog }">  
22 - <el-button :size="size" plain>删除</el-button>  
23 - <el-button :size="size" plain @click="openDialog('other', '追加')">追加操作</el-button>  
24 - </template>  
25 - <template #dialog-other="{ model, closeDialog }">  
26 - <el-tag type="success">其它页面</el-tag>  
27 - <el-input  
28 - type="textarea"  
29 - :rows="2"  
30 - placeholder="请输入内容"  
31 - v-model="model.name">  
32 - </el-input>  
33 - <el-button @click="closeDialog">关闭弹出框</el-button>  
34 - </template>  
35 - </z-schema>  
36 -</template>  
37 -  
38 -<script>  
39 -export default {  
40 - data() {  
41 - return {  
42 - activeName: 'wait',  
43 - list: [  
44 - { type: 'el-input', label: '姓名', key: 'name', rules: [{ required: true, message: '请输入', trigger: 'change' }] },  
45 - { type: 'el-input', label: '年龄', key: 'age' },  
46 - ]  
47 - }  
48 - },  
49 - methods: {  
50 - searchAPI(params) {  
51 - console.log(params);  
52 - return new Promise(resolve => {  
53 - setTimeout(() => {  
54 - const list = [  
55 - { id: '0', name: '李饼', age: 32, location: { lat: 0, lng: 0 } },  
56 - { id: '1', name: '陈拾', age: 20 },  
57 - { id: '2', name: '阿里巴巴', age: 24 },  
58 - { id: '3', name: '王七', age: 26 },  
59 - { id: '4', name: '崔倍', age: 27 },  
60 - { id: '5', name: '孙豹', age: 38 },  
61 - { id: '6', name: '庞柏', age: 42 },  
62 - { id: '7', name: '蔡疏', age: 60 },  
63 - { id: '8', name: '卢纳', age: 55 },  
64 - ]  
65 - resolve({  
66 - result: list,  
67 - totalCount: list.length  
68 - });  
69 - }, 1500);  
70 - });  
71 - },  
72 - getAPI(row) {  
73 - return new Promise((resolve, reject) => {  
74 - setTimeout(() => {  
75 - resolve({ ...row, name: `[${row.name}]` });  
76 - }, 1500);  
77 - });  
78 - },  
79 - submitAPI(model, config) {  
80 - console.log(JSON.parse(JSON.stringify(model)));  
81 - console.log(config);  
82 - console.log('start');  
83 - return new Promise((resolve, reject) => {  
84 - setTimeout(() => {  
85 - console.log('done');  
86 - reject();  
87 - }, 1500);  
88 - });  
89 - },  
90 - deleteAPI() {  
91 - return new Promise((resolve, reject) => {  
92 - setTimeout(() => {  
93 - resolve();  
94 - }, 1500);  
95 - });  
96 - }  
97 - }  
98 -}  
99 -</script>  
100 -```  
101 -  
102 -:::  
103 -  
104 -## 内置接口逻辑  
105 -  
106 -如果CURD的接口都是同一路径下,可以使用内置接口逻辑快速对接  
107 -  
108 -::: snippet 通过`url`配置接口路径,`http`设置Promise形式的HTTP请求库  
109 -  
110 -```html  
111 -<template>  
112 - <z-schema ref="schema" :list="list" url="/customer" :http="$http" :alias="{ getUrl: '/getCustomerByCode', getKey: 'code' }" auto real-selection>  
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>  
126 - </z-schema>  
127 -</template>  
128 -  
129 -<script>  
130 -export default {  
131 - data() {  
132 - return {  
133 - activeName: 'wait',  
134 - list: [  
135 - { type: 'el-input', label: 'ID', key: 'id', props: { disabled: true }, include: 'form', visible: () => this.$refs.schema.dialogType === 'edit' },  
136 - { type: 'el-input', label: '编号', key: 'code' },  
137 - { type: 'el-input', label: '名称', key: 'name' },  
138 - ]  
139 - }  
140 - },  
141 - methods: {  
142 - }  
143 -}  
144 -</script>  
145 -```  
146 -  
147 -:::  
148 -  
149 -## API  
150 -  
151 -## Attribute 属性  
152 -  
153 -参数|说明|类型|可选值|默认值  
154 --|-|-|-|-  
155 -list | JSON Schema配置项列表 | Array | - | []  
examples/views/docs/design/table.md 0 → 100644
No preview for this file type
examples/views/docs/develop/schema/schema-form.md
@@ -1,75 +0,0 @@ @@ -1,75 +0,0 @@
1 -# Schema Form 方案表单  
2 -  
3 -通过配置JSON Schema的方式快速生成一个表单  
4 -  
5 -## 基础用法  
6 -  
7 -::: snippet 预置了许多业务逻辑,避免重复维护相同的业务逻辑  
8 -  
9 -```html  
10 -<template>  
11 - <div>  
12 - <div>{{ form }}</div>  
13 - <z-schema-form v-model="form" :schema="schema" @submit="onSubmit" @cancel="onCancel">  
14 - <template #error-name>  
15 - <div><span>请输入</span><el-tag size="mini">格式正确</el-tag><span>的姓名</span></div>  
16 - </template>  
17 - <template #label-packages>  
18 - <el-tooltip content="详情" placement="top" effect="light">  
19 - <i class="el-icon-question"></i>  
20 - </el-tooltip>  
21 - <span>包裹</span>  
22 - </template>  
23 - </z-schema-form>  
24 - </div>  
25 -</template>  
26 -  
27 -<script>  
28 -export default {  
29 - data() {  
30 - return {  
31 - form: {  
32 - name: '张三',  
33 - age: 27,  
34 - address: '上海市青浦区华新镇纪鹤公路1988号',  
35 - packages: ['零食'],  
36 - info: {  
37 - mobile: '18888888888',  
38 - }  
39 - },  
40 - schema: {  
41 - props: { labelWidth: '70px', size: 'small', span: 12 },  
42 - items: [  
43 - { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] },  
44 - { is: 'el-input-number', prop: 'age', label: '年龄' },  
45 - { is: 'el-input', prop: 'info.mobile', label: '电话' },  
46 - { is: 'el-divider', props: { 'content-position': 'left' }, render: '收货信息', span: 24, labelWidth: '0px' },  
47 - { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 },  
48 - {  
49 - is: 'el-checkbox-group',  
50 - prop: 'packages',  
51 - label: '包裹',  
52 - children: [  
53 - { is: 'el-checkbox', props: { label: '零食' } },  
54 - { is: 'el-checkbox', props: { label: '手机' } },  
55 - { is: 'el-checkbox', props: { label: '电脑' } },  
56 - ],  
57 - span: 24  
58 - },  
59 - ]  
60 - },  
61 - };  
62 - },  
63 - methods: {  
64 - onSubmit(value) {  
65 - console.log(value);  
66 - },  
67 - onCancel() {  
68 - console.log('cancal');  
69 - }  
70 - }  
71 -};  
72 -</script>  
73 -```  
74 -  
75 -:::  
76 \ No newline at end of file 0 \ No newline at end of file
examples/views/docs/develop/schema/schema-page.md
@@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
1 -# Schema 方案开发  
2 -  
3 -通过配置JSON Schema的方式快速生成一些业务组件  
4 -  
5 -## 基础用法  
6 \ No newline at end of file 0 \ No newline at end of file
examples/views/docs/develop/schema/schema-table.md
@@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
1 -# Schema 方案开发  
2 -  
3 -通过配置JSON Schema的方式快速生成一些业务组件  
4 -  
5 -## 基础用法  
6 \ No newline at end of file 0 \ No newline at end of file
examples/views/layout/design.vue 0 → 100644
@@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
  1 +<template>
  2 + <el-container>
  3 + <layout-header @menu-change="handleSelect"></layout-header>
  4 + <el-container class="layout-container__component">
  5 + <el-aside class="layout-aside__component" width="300px">
  6 + <el-menu :default-active="activeMenu" class="layout-aside-menu__component" @select="handleSelect">
  7 + <h4 class="side-menu__group">设计指南</h4>
  8 + <el-menu-item-group v-for="(design, idx) in designList" :key="idx">
  9 + <template slot="title">{{ design.group }}</template>
  10 + <el-menu-item v-for="(data, index) in design.children" :key="index" :index="data.name">{{ data.meta.title }}</el-menu-item>
  11 + </el-menu-item-group>
  12 + </el-menu>
  13 + </el-aside>
  14 + <el-main class="layout-main__component">
  15 + <router-view></router-view>
  16 + </el-main>
  17 + <el-aside class="layout-aside__preview" width="200px">
  18 + <a class="anchor" :class="{ active: item.hash === currentAnchor }" v-for="(item, index) in anchorList" :key="index" @click="jumpAnchor(item)">{{ item.text }}</a>
  19 + </el-aside>
  20 + </el-container>
  21 + </el-container>
  22 +</template>
  23 +
  24 +<script>
  25 +import ComponentLayout from './component';
  26 +import { designs } from '@/router/routes';
  27 +
  28 +export default {
  29 + extends: ComponentLayout,
  30 + data() {
  31 + return {
  32 + designList: designs,
  33 + };
  34 + },
  35 +};
  36 +</script>
packages/form-item/index.vue
@@ -4,7 +4,7 @@ export default { @@ -4,7 +4,7 @@ export default {
4 props: { 4 props: {
5 label: String, 5 label: String,
6 labelWidth: String, 6 labelWidth: String,
7 - value: [Number, String], 7 + value: [Number, String, Array, Object],
8 prop: String, 8 prop: String,
9 span: { 9 span: {
10 type: [Number, String], 10 type: [Number, String],
packages/mixins/origin.js 0 → 100644
@@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
  1 +export default {
  2 + data() {
  3 + return {
  4 + originData: {},
  5 + originProps: {},
  6 + };
  7 + },
  8 + created() {
  9 + const { originData, originProps, ...other } = this._data;
  10 + this.originData = this.cloneDeep(other);
  11 + this.originProps = this.cloneDeep(this._props);
  12 + },
  13 + methods: {
  14 + // 深克隆对象
  15 + cloneDeep(obj) {
  16 + if (typeof obj !== 'object') {
  17 + return obj;
  18 + }
  19 + if (!obj) {
  20 + return obj;
  21 + }
  22 + if (obj instanceof Date) {
  23 + return new Date(obj);
  24 + }
  25 + if (obj instanceof RegExp) {
  26 + return new RegExp(obj);
  27 + }
  28 + if (obj instanceof Function) {
  29 + return obj;
  30 + }
  31 + let newObj;
  32 + if (obj instanceof Array) {
  33 + newObj = [];
  34 + for (let i = 0, len = obj.length; i < len; i++) {
  35 + newObj.push(this.cloneDeep(obj[i]));
  36 + }
  37 + return newObj;
  38 + }
  39 + newObj = {};
  40 + for (let key in obj) {
  41 + if (Object.prototype.hasOwnProperty.call(obj, key)) {
  42 + if (typeof obj[key] !== 'object') {
  43 + newObj[key] = obj[key];
  44 + } else {
  45 + newObj[key] = this.cloneDeep(obj[key]);
  46 + }
  47 + }
  48 + }
  49 + return newObj;
  50 + },
  51 + // 获取初始值
  52 + getOriginData(key) {
  53 + if (key) {
  54 + return this.cloneDeep(this.originData)[key];
  55 + }
  56 + return this.cloneDeep(this.originData);
  57 + },
  58 + },
  59 +};
packages/schema-form/index.vue
@@ -34,7 +34,8 @@ export default { @@ -34,7 +34,8 @@ export default {
34 const item = props.item || {}; 34 const item = props.item || {};
35 let content = []; 35 let content = [];
36 if (item.render && typeof item.render === 'function') { 36 if (item.render && typeof item.render === 'function') {
37 - content = context.props.render; 37 + console.log('render');
  38 + content = item.render(props.value, h);
38 } 39 }
39 if (item.children) { 40 if (item.children) {
40 if (Array.isArray(item.children)) { 41 if (Array.isArray(item.children)) {
packages/schema-page/index copy.scss 0 → 100644
@@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
  1 +.z-schema {
  2 + &__header {
  3 + margin-bottom: 10px;
  4 + }
  5 + &__filter {
  6 + border: 1px solid #ebeef5;
  7 + padding-top: 10px;
  8 + border-radius: 4px;
  9 + margin-bottom: 10px;
  10 + }
  11 + &__action {
  12 + display: flex;
  13 + flex-wrap: wrap;
  14 + align-items: center;
  15 + justify-content: flex-start;
  16 + line-height: 1;
  17 + .el-button + .el-button {
  18 + margin-left: 0;
  19 + }
  20 + .el-button {
  21 + margin-right: 10px;
  22 + margin-bottom: 10px;
  23 + }
  24 + }
  25 + &__table {
  26 + &-operation {
  27 + display: flex;
  28 + flex-wrap: wrap;
  29 + align-items: center;
  30 + justify-content: flex-start;
  31 + .el-button + .el-button {
  32 + margin-left: 0;
  33 + }
  34 + .el-button {
  35 + margin-right: 10px;
  36 + padding-top: 6px;
  37 + padding-bottom: 6px;
  38 + }
  39 + }
  40 + }
  41 + &__dialog-button {
  42 + display: flex;
  43 + align-items: center;
  44 + justify-content: center;
  45 + padding-top: 10px;
  46 + }
  47 + &__footer {
  48 + margin-top: 10px;
  49 + text-align: right;
  50 + display: flex;
  51 + justify-content: space-between;
  52 + align-items: center;
  53 + .selection-info {
  54 + word-break: break-all;
  55 + white-space: nowrap;
  56 + font-size: 12px;
  57 + color: #606266;
  58 + .num {
  59 + color: #000;
  60 + font-weight: bold;
  61 + padding: 0 5px;
  62 + font-size: 16px;
  63 + }
  64 + .el-button {
  65 + margin-left: 5px;
  66 + }
  67 + }
  68 + .el-pagination {
  69 + flex: auto;
  70 + }
  71 + }
  72 +}
0 \ No newline at end of file 73 \ No newline at end of file
packages/schema-page/index copy.vue 0 → 100644
@@ -0,0 +1,649 @@ @@ -0,0 +1,649 @@
  1 +<style lang="scss">
  2 +@import './index.scss';
  3 +</style>
  4 +
  5 +<template>
  6 + <div class="z-schema">
  7 + <!-- 头部内容 -->
  8 + <div v-if="$scopedSlots.header || $slots.header" class="z-schema__header">
  9 + <slot name="header" :filterModel="filterModel" v-bind="_slotScope"></slot>
  10 + </div>
  11 + <!-- 筛选组件 -->
  12 + <div v-if="filter" class="z-schema__filter">
  13 + <z-schema-filter
  14 + :value="_filterModel"
  15 + :list="filterList || listMap.filter | noRulesFilter"
  16 + :size="size"
  17 + @input="onFilterInput"
  18 + @search="onSearch"
  19 + :loading="loading"
  20 + v-bind="filterProps"
  21 + :params="_slotScope"
  22 + ></z-schema-filter>
  23 + </div>
  24 + <!-- 按钮区 -->
  25 + <div v-if="action" class="z-schema__action">
  26 + <slot v-if="hadSlot('action')" name="action" v-bind="_slotScope"></slot>
  27 + <template v-else>
  28 + <el-button :size="size" type="primary" @click="openNew">新增</el-button>
  29 + <el-button :size="size" plain :disabled="selection.length === 0" @click="handleDeleteMul(selection)">删除</el-button>
  30 + <slot name="button" v-bind="_slotScope"></slot>
  31 + </template>
  32 + </div>
  33 + <!-- 表格内容 -->
  34 + <div class="z-schema__table">
  35 + <z-schema-table
  36 + ref="table"
  37 + v-model="tableData"
  38 + v-loading="loading"
  39 + :list="tableList || listMap.table"
  40 + :tableProps="{ border: true, 'row-key': 'id', 'highlight-current-row': true, ...tableProps }"
  41 + :size="size"
  42 + @selection-change="onTableSelectionChange"
  43 + @selection="onTableSelection"
  44 + >
  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 + <!-- 表格尾追加操作列 -->
  65 + <template #column-end>
  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 }">
  68 + <div class="z-schema__table-operation" slot-scope="slotScope">
  69 + <slot name="operation-button" v-bind="{ ..._slotScope, slotScope }"></slot>
  70 + <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(slotScope.row)"></el-button>
  71 + <el-popconfirm confirmButtonText="确定" cancelButtonText="取消" title="确定删除吗?" placement="top" @confirm="handleDelete([slotScope.row])">
  72 + <el-button slot="reference" type="text" icon="el-icon-delete" title="删除"></el-button>
  73 + </el-popconfirm>
  74 + <slot name="operation-button-append" v-bind="{ ..._slotScope, slotScope }"></slot>
  75 + </div>
  76 + </el-table-column>
  77 + </template>
  78 + </z-schema-table>
  79 + </div>
  80 + <!-- 底部区域 -->
  81 + <div class="z-schema__footer">
  82 + <div v-if="selection.length > 0" class="selection-info">
  83 + <span>已选中</span>
  84 + <span class="num">{{ selection.length }}</span>
  85 + <span>项</span>
  86 + <el-popconfirm confirmButtonText="确定" cancelButtonText="取消" title="确定清除吗?" placement="top" @confirm="clearSelection">
  87 + <el-button slot="reference" :size="size" type="text">清除</el-button>
  88 + </el-popconfirm>
  89 + </div>
  90 + <!-- 分页器 -->
  91 + <el-pagination
  92 + v-if="pagination"
  93 + @size-change="handleSizeChange"
  94 + @current-change="handleCurrentChange"
  95 + :current-page="currentPage"
  96 + :page-sizes="pageSizes"
  97 + :page-size="pageSize"
  98 + layout="total, sizes, prev, pager, next, jumper"
  99 + :total="total"
  100 + >
  101 + </el-pagination>
  102 + </div>
  103 + <!-- 弹出框 -->
  104 + <el-dialog
  105 + :visible.sync="dialogVisible"
  106 + :title="dialogTitle"
  107 + destroy-on-close
  108 + append-to-body
  109 + :lock-scroll="false"
  110 + :close-on-click-modal="false"
  111 + @closed="onDialogClosed"
  112 + @close="onDialogClose"
  113 + v-bind="_dialogProps"
  114 + >
  115 + <div v-loading="dialogLoading">
  116 + <!-- 自定义弹出框标题 -->
  117 + <slot v-if="hadSlot('dialog-title')" slot="title" name="dialog-title" :dialogType="dialogType" v-bind="_slotScope"></slot>
  118 + <template v-if="dialogRender">
  119 + <!-- 自定义弹出框内容 -->
  120 + <slot v-if="hadSlot(`dialog-${dialogType}`)" :name="`dialog-${dialogType}`" :model="_formModel" v-bind="_slotScope"></slot>
  121 + <template v-else>
  122 + <!-- 内置弹出框新增修改表单 -->
  123 + <template v-if="['new', 'edit'].includes(dialogType)">
  124 + <z-form
  125 + ref="form"
  126 + :value="_formModel"
  127 + :list="formList || listMap.form"
  128 + @input="onFormInput"
  129 + @validate="onFormValidate"
  130 + v-bind="{ span: 12, 'label-width': '110px', ...formProps }"
  131 + :params="_slotScope"
  132 + >
  133 + <!-- 表单自定义插槽 -->
  134 + <template v-for="item in renderList">
  135 + <template v-if="$scopedSlots[`form-${item.fullKey}`]">
  136 + <slot :slot="item.fullKey" :name="`form-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>
  137 + </template>
  138 + </template>
  139 + </z-form>
  140 + </template>
  141 + <!-- 内置弹出框详情表单 -->
  142 + <template v-else>
  143 + <z-form
  144 + ref="form"
  145 + class="z-schema__view"
  146 + :value="_formModel"
  147 + :list="viewList || listMap.form | viewTypeFilter | noRulesFilter"
  148 + v-bind="{ span: 12, 'label-width': '110px', ...viewProps }"
  149 + :params="_slotScope"
  150 + >
  151 + <!-- 详情自定义插槽渲染 -->
  152 + <template v-for="item in renderList">
  153 + <template v-if="$scopedSlots[`view-${item.fullKey}`]">
  154 + <slot :slot="item.fullKey" :name="`view-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>
  155 + </template>
  156 + <template v-else-if="$scopedSlots[`render-${item.fullKey}`]">
  157 + <slot :slot="item.fullKey" :name="`render-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>
  158 + </template>
  159 + </template>
  160 + </z-form>
  161 + </template>
  162 + </template>
  163 + <!-- 内置弹出框新增修改按钮 -->
  164 + <div class="z-schema__dialog-button" v-if="['new', 'edit'].includes(dialogType)">
  165 + <el-button :size="size" type="primary" @click="handleConfirm" :loading="submitting">确定</el-button>
  166 + <el-button :size="size" plain @click="closeDialog">取消</el-button>
  167 + </div>
  168 + </template>
  169 + </div>
  170 + </el-dialog>
  171 + </div>
  172 +</template>
  173 +
  174 +<script>
  175 +import { cloneDeep, get } from '../utils';
  176 +import { clear } from '../utils/param';
  177 +
  178 +let propsMap = {};
  179 +const propsKeys = ['tableProps', 'filterProps', 'formProps', 'viewProps', 'dialogProps', 'operationProps'];
  180 +propsKeys.forEach(key => {
  181 + propsMap[key] = {
  182 + type: Object,
  183 + default: () => ({}),
  184 + };
  185 +});
  186 +const apiKeys = ['searchApi', 'submitApi', 'addApi', 'modifyApi', 'getApi', 'viewApi', 'deleteApi'];
  187 +apiKeys.forEach(key => {
  188 + propsMap[key] = {
  189 + type: Function,
  190 + };
  191 +});
  192 +const blockKeys = ['filter', 'action', 'pagination', 'operation'];
  193 +blockKeys.forEach(key => {
  194 + propsMap[key] = {
  195 + type: Boolean,
  196 + default: true,
  197 + };
  198 +});
  199 +
  200 +export default {
  201 + name: 'SchemaPage',
  202 + props: {
  203 + ...propsMap,
  204 + list: Array,
  205 + filterList: Array,
  206 + tableList: Array,
  207 + formList: Array,
  208 + viewList: Array,
  209 + size: {
  210 + type: String,
  211 + default: 'mini',
  212 + },
  213 + formModel: Object,
  214 + filterModel: Object,
  215 + auto: Boolean,
  216 + realSelection: Boolean,
  217 + url: String, // 请求地址
  218 + http: Function, // http库
  219 + alias: Object, // 别名配置
  220 + },
  221 + data() {
  222 + return {
  223 + filterForm: {},
  224 + editForm: {},
  225 + dialogVisible: false,
  226 + dialogRender: true,
  227 + dialogType: 'none',
  228 + dialogLoading: false,
  229 + dialogTitle: '',
  230 + dialogPropsHack: {},
  231 + currentPage: 1,
  232 + pageSize: 10,
  233 + total: 0,
  234 + pageSizes: [10, 20, 50],
  235 + tableData: [],
  236 + submitting: false,
  237 + loading: false,
  238 + selection: [],
  239 + };
  240 + },
  241 + created() {
  242 + if (this.auto) {
  243 + this.search();
  244 + }
  245 + },
  246 + filters: {
  247 + // 无规则过滤器,过滤掉筛选条件表单中的必填规则等
  248 + noRulesFilter(val = []) {
  249 + let list = cloneDeep(val);
  250 + const clearRules = list => {
  251 + list.forEach(item => {
  252 + if (item.list) {
  253 + clearRules(item.list);
  254 + } else {
  255 + delete item.rules;
  256 + }
  257 + });
  258 + };
  259 + clearRules(list);
  260 + return list;
  261 + },
  262 + // 详情类型过滤器
  263 + viewTypeFilter(val = []) {
  264 + let list = cloneDeep(val);
  265 + const clearRules = list => {
  266 + list.forEach(item => {
  267 + item.type = (h, { model, config }) => h('span', config, model[item.key]);
  268 + });
  269 + };
  270 + clearRules(list);
  271 + return list;
  272 + },
  273 + },
  274 + computed: {
  275 + listMap() {
  276 + // 默认作用域
  277 + const LIST_SPACE = ['filter', 'form', 'table'];
  278 + const array = {
  279 + filter: [], // 筛选
  280 + form: [], // 表单
  281 + table: [], // 表格
  282 + };
  283 + this.list.forEach(item => {
  284 + // 可以在列表中通过include或exclude设置当前配置的作用域
  285 + const { include = LIST_SPACE, exclude = [] } = item;
  286 + // 判断include
  287 + let _inclue = [];
  288 + if (include instanceof String || typeof include === 'string') {
  289 + _inclue = [include];
  290 + } else if (include instanceof Array && typeof include === 'object') {
  291 + _inclue = include;
  292 + }
  293 + // 判断exclude转换为include的情况
  294 + let _exclude_include = [];
  295 + if (exclude instanceof String || typeof exclude === 'string') {
  296 + _exclude_include = LIST_SPACE.filter(item => item !== exclude);
  297 + } else if (exclude instanceof Array && typeof exclude === 'object') {
  298 + _exclude_include = LIST_SPACE.filter(item => !exclude.includes(item));
  299 + }
  300 + // 作用域交集
  301 + const _intersection = _inclue.filter(v => _exclude_include.includes(v));
  302 + // 返回改配置项的作用域
  303 + const _list_space = cloneDeep(_intersection);
  304 + // 将配置项按需分配至各作用域下
  305 + _list_space.forEach(name => {
  306 + array[name].push({ ...item, ...(item[name] || {}) });
  307 + });
  308 + });
  309 + return array;
  310 + },
  311 + renderList() {
  312 + // 深度克隆传入的列表,避免原始值被修改
  313 + const newList = cloneDeep(this.list);
  314 + // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key
  315 + const generateFullKey = (list, parentKey) => {
  316 + list.forEach(item => {
  317 + if (item.group && item.list) {
  318 + if (item.group.key) {
  319 + item.fullKey = `${parentKey ? `${parentKey}-${item.group.key}` : item.group.key}`;
  320 + } else {
  321 + item.fullKey = parentKey || item.key;
  322 + }
  323 + generateFullKey(item.list, item.fullKey);
  324 + } else {
  325 + item.fullKey = `${parentKey ? `${parentKey}-${item.key}` : item.key}`;
  326 + }
  327 + });
  328 + };
  329 + // 生成fullKey
  330 + generateFullKey(newList);
  331 + return newList;
  332 + },
  333 + _filterModel() {
  334 + return this.filterModel || this.filterForm || {};
  335 + },
  336 + _formModel() {
  337 + return this.formModel || this.editForm || {};
  338 + },
  339 + _slotScope() {
  340 + return {
  341 + handleSearch: this.search,
  342 + openDialog: this.openDialog,
  343 + closeDialog: this.closeDialog,
  344 + openView: this.openView,
  345 + openEdit: this.openEdit,
  346 + openNew: this.openNew,
  347 + handleDelete: this.handleDelete,
  348 + handleDeleteMul: this.handleDeleteMul,
  349 + size: this.size,
  350 + dialogType: this.dialogType,
  351 + selection: this.selection,
  352 + };
  353 + },
  354 + _alias() {
  355 + const alias = this.alias;
  356 + const zAlias = this.zAlias;
  357 + if (alias && zAlias) {
  358 + return { ...zAlias, ...alias };
  359 + }
  360 + return this.alias || this.zAlias || {};
  361 + },
  362 + _dialogProps() {
  363 + return {
  364 + ...this.dialogProps,
  365 + ...this.dialogPropsHack,
  366 + };
  367 + },
  368 + },
  369 + methods: {
  370 + get,
  371 + // 空Promise
  372 + emptyPromise() {
  373 + return new Promise(resolve => resolve());
  374 + },
  375 + // 设置表格选中行
  376 + toggleRowSelection() {
  377 + this.tableData.forEach(row => {
  378 + if (this.selection.find(item => item.id === row.id)) {
  379 + this.$refs.table && this.$refs.table.toggleRowSelection(row);
  380 + }
  381 + });
  382 + },
  383 + // 表格选中状态
  384 + onTableSelectionChange(selection, type) {
  385 + if (this.realSelection) {
  386 + if (type === 'check') {
  387 + const result = this.selection || [];
  388 + selection.forEach(item => {
  389 + if (!result.find(i => i.id === item.id)) {
  390 + result.push(item);
  391 + }
  392 + });
  393 + this.selection = result;
  394 + } else if (type === 'uncheck') {
  395 + selection.forEach(i => {
  396 + this.selection = this.selection.filter(item => item.id !== i.id);
  397 + });
  398 + }
  399 + }
  400 + },
  401 + // 表格选中
  402 + onTableSelection(selection) {
  403 + if (!this.realSelection) {
  404 + this.selection = selection;
  405 + }
  406 + },
  407 + // 清除表格选中
  408 + clearSelection() {
  409 + this.$refs.table && this.$refs.table.clearSelection();
  410 + this.selection = [];
  411 + },
  412 + // 内置搜索接口
  413 + _searchAPI(params) {
  414 + if (this.url && (this.http || this.zHttp)) {
  415 + const _http = this.http || this.zHttp;
  416 + return _http({ url: `${clear(this.url)}/${this._alias.pageUrl || 'page'}`, params });
  417 + }
  418 + return undefined;
  419 + },
  420 + // 重置查询
  421 + onSearch() {
  422 + this.currentPage = 1;
  423 + this.search();
  424 + },
  425 + // 搜索
  426 + async search() {
  427 + this.loading = true;
  428 + const params = {
  429 + ...this._filterModel,
  430 + currentPage: this.currentPage,
  431 + pageSize: this.pageSize,
  432 + };
  433 + const searchAPI = this.searchApi || this._searchAPI || this.emptyPromise;
  434 + await searchAPI(params)
  435 + .then(res => {
  436 + const response = res || {};
  437 + this.tableData = response[this._alias.list || 'list'] || [];
  438 + this.total = response[this._alias.total || 'total'] || 0;
  439 + this.$nextTick(this.toggleRowSelection);
  440 + })
  441 + .catch(() => {
  442 + this.$message.error('查询失败');
  443 + });
  444 + this.loading = false;
  445 + },
  446 + // 更新筛选model
  447 + onFilterInput(val) {
  448 + this.filterForm = val || {};
  449 + this.$emit('update:filterModel', val || {});
  450 + },
  451 + // 更新表单model
  452 + onFormInput(val) {
  453 + this.editForm = val || {};
  454 + this.$emit('update:formModel', val || {});
  455 + },
  456 + // 内置新增保存接口
  457 + _addAPI(data) {
  458 + if (this.url && (this.http || this.zHttp)) {
  459 + const _http = this.http || this.zHttp;
  460 + return _http({ url: `${clear(this.url)}/${this._alias.addUrl || 'add'}`, method: 'post', data });
  461 + }
  462 + return undefined;
  463 + },
  464 + // 内置修改保存接口
  465 + _modifyAPI(data) {
  466 + if (this.url && (this.http || this.zHttp)) {
  467 + const _http = this.http || this.zHttp;
  468 + return _http({ url: `${clear(this.url)}/${this._alias.modifyUrl || 'modify'}`, method: 'post', data });
  469 + }
  470 + return undefined;
  471 + },
  472 + // 表单提交且通过校验
  473 + async onFormValidate(valid, model) {
  474 + if (valid) {
  475 + this.submitting = true;
  476 + let submitAPI = this.submitApi || this.emptyPromise;
  477 + if (this.dialogType === 'new') {
  478 + submitAPI = this.addApi || this.submitApi || this._addAPI || this.emptyPromise;
  479 + } else if (this.dialogType === 'edit') {
  480 + submitAPI = this.modifyApi || this.submitApi || this._modifyAPI || this.emptyPromise;
  481 + }
  482 + submitAPI(model, { type: this.dialogType })
  483 + .then(() => {
  484 + this.$message.success('保存成功');
  485 + this.closeDialog();
  486 + this.search();
  487 + })
  488 + .catch(() => {
  489 + this.$message.error('保存失败');
  490 + })
  491 + .finally(() => {
  492 + this.submitting = false;
  493 + });
  494 + }
  495 + },
  496 + // 表单按钮确定
  497 + handleConfirm() {
  498 + this.$refs.form && this.$refs.form.validate();
  499 + },
  500 + // 表单按钮取消
  501 + handleCancel() {
  502 + this.closeDialog();
  503 + },
  504 + // 查询是否有某个插槽
  505 + hadSlot(name) {
  506 + return !!this.$slots[name] || !!this.$scopedSlots[name];
  507 + },
  508 + // 打开新增弹出框
  509 + openNew() {
  510 + this.openDialog('new', '新增');
  511 + },
  512 + // 内置查询详情接口
  513 + _getAPI(row) {
  514 + if (this.url && (this.http || this.zHttp)) {
  515 + const _http = this.http || this.zHttp;
  516 + const _getKey = this._alias.getKey || this._alias.primaryKey || 'id';
  517 + const _resultKey = this._alias.result || 'result';
  518 + return _http({ url: `${clear(this.url)}/${this._alias.getUrl || 'queryById'}`, params: { [_getKey]: row[_getKey] } }).then(response => response[_resultKey] || {});
  519 + }
  520 + return undefined;
  521 + },
  522 + // 打开编辑弹出框
  523 + openEdit(row) {
  524 + this.dialogLoading = true;
  525 + this.openDialog('edit', '编辑');
  526 + const getRow = () =>
  527 + new Promise(resolve => {
  528 + resolve(row);
  529 + });
  530 + const getAPI = this.getApi || this._getAPI || getRow;
  531 + getAPI(row)
  532 + .then(result => {
  533 + this.editForm = result;
  534 + this.$emit('update:formModel', result || {});
  535 + })
  536 + .finally(() => {
  537 + this.dialogLoading = false;
  538 + });
  539 + },
  540 + // 内置查询详情接口
  541 + _viewAPI(row) {
  542 + if (this.url && (this.http || this.zHttp)) {
  543 + const _http = this.http || this.zHttp;
  544 + const _viewKey = this._alias.viewKey || this._alias.getKey || this._alias.primaryKey || 'id';
  545 + const _resultKey = this._alias.result || 'result';
  546 + return _http({ url: `${clear(this.url)}/${this._alias.getUrl || 'queryById'}`, params: { [_viewKey]: row[_viewKey] } }).then(response => response[_resultKey] || {});
  547 + }
  548 + return undefined;
  549 + },
  550 + // 打开详情弹出框
  551 + openView(row) {
  552 + this.dialogLoading = true;
  553 + this.openDialog('view', '详情');
  554 + const getRow = () =>
  555 + new Promise(resolve => {
  556 + resolve(row);
  557 + });
  558 + const viewAPI = this.viewApi || this.getApi || this._viewAPI || this._getAPI || getRow;
  559 + viewAPI(row)
  560 + .then(result => {
  561 + this.editForm = result;
  562 + this.$emit('update:formModel', result || {});
  563 + })
  564 + .finally(() => {
  565 + this.dialogLoading = false;
  566 + });
  567 + },
  568 + // 内置删除接口
  569 + _deleteAPI(keys) {
  570 + if (this.url && (this.http || this.zHttp)) {
  571 + const _http = this.http || this.zHttp;
  572 + return _http({ url: `${clear(this.url)}/${this._alias.modifyUrl || 'delete'}`, method: 'post', data: keys });
  573 + }
  574 + return undefined;
  575 + },
  576 + // 删除
  577 + handleDelete(selection) {
  578 + const loading = this.$loading({
  579 + text: '处理中',
  580 + spinner: 'el-icon-loading',
  581 + background: 'rgba(255, 255, 255, 0.5)',
  582 + });
  583 + const deleteAPI = this.deleteApi || this._deleteAPI || this.emptyPromise;
  584 + const _deleteKey = this._alias.deleteKey || this._alias.primaryKey || 'id';
  585 + const keys = selection.map(i => i[_deleteKey]);
  586 + deleteAPI(keys)
  587 + .then(() => {
  588 + this.search();
  589 + this.$message.success('删除成功');
  590 + })
  591 + .finally(() => {
  592 + loading.close();
  593 + });
  594 + },
  595 + // 批量删除
  596 + handleDeleteMul(selection) {
  597 + this.$confirm(`是否删除这 [${selection.length}] 项?`, '提示', {
  598 + confirmButtonText: '确定',
  599 + cancelButtonText: '取消',
  600 + type: 'warning',
  601 + })
  602 + .then(() => {
  603 + this.handleDelete(selection);
  604 + })
  605 + .catch(() => {});
  606 + },
  607 + // 打开弹出框
  608 + openDialog(type, title, config) {
  609 + this.dialogVisible = true;
  610 + this.dialogRender = true;
  611 + this.dialogType = type;
  612 + this.dialogTitle = title;
  613 + this.dialogPropsHack = config || {};
  614 + this.$emit('dialog-change', type);
  615 + },
  616 + // 关闭弹出框
  617 + closeDialog() {
  618 + this.dialogVisible = false;
  619 + },
  620 + // 清空表单
  621 + clearEditForm() {
  622 + this.editForm = {};
  623 + this.$emit('update:formModel', {});
  624 + },
  625 + // 弹出框关闭
  626 + onDialogClose() {
  627 + this.dialogType = 'none';
  628 + this.dialogRender = false;
  629 + this.$emit('dialog-change', 'none');
  630 + },
  631 + // 弹出框关闭动画结束
  632 + onDialogClosed() {
  633 + this.clearEditForm();
  634 + this.dialogPropsHack = {};
  635 + },
  636 + // 分页-每页个数
  637 + handleSizeChange(val) {
  638 + this.pageSize = val;
  639 + this.currentPage = 1;
  640 + this.$nextTick(this.search);
  641 + },
  642 + // 分页-当前页数
  643 + handleCurrentChange(val) {
  644 + this.currentPage = val;
  645 + this.$nextTick(this.search);
  646 + },
  647 + },
  648 +};
  649 +</script>
packages/schema-page/index.scss 0 → 100644
@@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
  1 +.z-schema-page {
  2 + &__header {
  3 + margin-bottom: 10px;
  4 + }
  5 + &__filter {
  6 + border: 1px solid #ebeef5;
  7 + padding-top: 10px;
  8 + padding-right: 10px;
  9 + border-radius: 4px;
  10 + margin-bottom: 10px;
  11 + }
  12 + &__action {
  13 + display: flex;
  14 + flex-wrap: wrap;
  15 + align-items: center;
  16 + justify-content: flex-start;
  17 + line-height: 1;
  18 + .el-button + .el-button {
  19 + margin-left: 0;
  20 + }
  21 + .el-button {
  22 + margin-right: 10px;
  23 + margin-bottom: 10px;
  24 + }
  25 + }
  26 + &__table {
  27 + &-operation {
  28 + display: flex;
  29 + flex-wrap: wrap;
  30 + align-items: center;
  31 + justify-content: center;
  32 + .el-button + .el-button {
  33 + margin-left: 0;
  34 + }
  35 + .el-button {
  36 + margin-right: 8px;
  37 + padding-top: 6px;
  38 + padding-bottom: 6px;
  39 + }
  40 + }
  41 + }
  42 + &__dialog-button {
  43 + display: flex;
  44 + align-items: center;
  45 + justify-content: center;
  46 + padding-top: 10px;
  47 + }
  48 + &__footer {
  49 + margin-top: 10px;
  50 + text-align: right;
  51 + display: flex;
  52 + justify-content: space-between;
  53 + align-items: center;
  54 + .selection-info {
  55 + word-break: break-all;
  56 + white-space: nowrap;
  57 + font-size: 12px;
  58 + color: #606266;
  59 + .num {
  60 + color: #333;
  61 + font-weight: bold;
  62 + padding: 0 5px;
  63 + font-size: 16px;
  64 + }
  65 + .el-button {
  66 + margin-left: 5px;
  67 + }
  68 + }
  69 + .el-pagination {
  70 + flex: auto;
  71 + }
  72 + }
  73 +}
  74 +
  75 +.z-loading-toast {
  76 + $toast-color: #fff;
  77 + $toast-bg-color: #000;
  78 + background-color: rgba($toast-bg-color, 0);
  79 + .el-loading-spinner {
  80 + width: 200px;
  81 + height: 120px;
  82 + margin: auto;
  83 + background-color: rgba($toast-bg-color, 0.7);
  84 + color: $toast-color;
  85 + left: 50%;
  86 + transform: translateX(-50%);
  87 + border-radius: 16px;
  88 + display: flex;
  89 + flex-direction: column;
  90 + align-items: center;
  91 + justify-content: center;
  92 + .path {
  93 + stroke: $toast-color;
  94 + }
  95 + .el-icon-loading {
  96 + font-size: 32px;
  97 + color: $toast-color;
  98 + }
  99 + .el-loading-text {
  100 + font-size: 14px;
  101 + color: $toast-color;
  102 + margin-top: 10px;
  103 + }
  104 + }
  105 +}
0 \ No newline at end of file 106 \ No newline at end of file
packages/schema-page/index.vue 0 → 100644
@@ -0,0 +1,436 @@ @@ -0,0 +1,436 @@
  1 +<style lang="scss">
  2 +@import './index.scss';
  3 +</style>
  4 +
  5 +<template>
  6 + <div class="z-schema-page">
  7 + <!-- 筛选组件 -->
  8 + <div v-if="schema.filter" class="z-schema-page__filter">
  9 + <z-schema-filter :schema="schema.filter" :value="valueFilter" @input="e => $emit('update:value-filter', e)" :loading="loading" @search="onSearch">
  10 + <template v-for="item in getSlotKeys('filter-')" #[item.name]="slotScope">
  11 + <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot>
  12 + </template>
  13 + </z-schema-filter>
  14 + </div>
  15 + <!-- 按钮区 -->
  16 + <div v-if="schema.action !== false" class="z-schema-page__action">
  17 + <slot name="action" v-bind="_slotScope">
  18 + <el-button :size="_size" type="primary" @click="openNew">新增</el-button>
  19 + <el-button :size="_size" plain :disabled="selection.length === 0" @click="onDeleteMultiple(selection)">删除</el-button>
  20 + <slot name="action-button" v-bind="_slotScope"></slot>
  21 + </slot>
  22 + </div>
  23 + <!-- 表格内容 -->
  24 + <div v-if="schema.table" class="z-schema-page__table">
  25 + <z-schema-table :schema="schema.table" v-model="tableData" v-loading="loading" @selection-change="onTableSelectionChange">
  26 + <template #left>
  27 + <el-table-column v-if="schema.selectable !== false" type="selection" width="40" align="center"></el-table-column>
  28 + </template>
  29 + <template v-for="item in getSlotKeys('table-')" #[item.name]="slotScope">
  30 + <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot>
  31 + </template>
  32 + <slot v-if="schema.operation !== false" name="operation" v-bind="_slotScope">
  33 + <el-table-column v-bind="{ label: '操作', width: '90', align: 'center', ...(schema.operation || {}) }">
  34 + <template #default="{ row, column, $index }">
  35 + <div class="z-schema-page__table-operation">
  36 + <slot name="operation-left" v-bind="{ ..._slotScope, row, column, $index }"></slot>
  37 + <slot name="operation-button" v-bind="{ ..._slotScope, row, column, $index }"></slot>
  38 + <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(row)"></el-button>
  39 + <el-popconfirm confirm-button-text="确定" cancel-button-text="取消" title="确定删除吗?" placement="top" @confirm="onDelete([row])">
  40 + <el-button slot="reference" type="text" icon="el-icon-delete" title="删除"></el-button>
  41 + </el-popconfirm>
  42 + <slot name="operation-right" v-bind="{ ..._slotScope, row, column, $index }"></slot>
  43 + </div>
  44 + </template>
  45 + </el-table-column>
  46 + </slot>
  47 + </z-schema-table>
  48 + </div>
  49 + <!-- 底部区域 -->
  50 + <div class="z-schema-page__footer">
  51 + <slot name="pagination" v-bind="_slotScope">
  52 + <div v-if="selection.length > 0" class="selection-info">
  53 + <span>已选中</span>
  54 + <span class="num">{{ selection.length }}</span>
  55 + <span>项</span>
  56 + </div>
  57 + <!-- 分页器 -->
  58 + <el-pagination
  59 + v-if="schema.pagination !== false"
  60 + @size-change="onSizeChange"
  61 + @current-change="onCurrentChange"
  62 + :current-page="currentPage"
  63 + :page-sizes="pageSizes"
  64 + :page-size="pageSize"
  65 + :layout="layout"
  66 + :total="total"
  67 + >
  68 + </el-pagination>
  69 + </slot>
  70 + </div>
  71 + <el-dialog :title="elDialogTitle" :visible.sync="visible" v-bind="_dialogProps" @update:visible="onVisibleUpdate" @close="onDialogClose" @closed="onDialogClosed">
  72 + <template v-if="elDialogRender">
  73 + <slot v-if="getSlot(`dialog-${elDialogType}`)" :name="`dialog-${elDialogType}`" v-bind="_slotScope"></slot>
  74 + <div v-else v-loading="dialogLoading">
  75 + <template v-if="['new', 'edit'].includes(elDialogType)">
  76 + <z-schema-form
  77 + :key="`form-${elDialogType}`"
  78 + ref="form"
  79 + :value="valueForm"
  80 + @input="e => $emit('update:value-form', e)"
  81 + :schema="schema.form"
  82 + @submit="onFormSubmit"
  83 + @cancel="closeDialog"
  84 + >
  85 + <template v-for="item in getSlotKeys('form-')" #[item.name]="slotScope">
  86 + <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot>
  87 + </template>
  88 + <template #footer="{ submit, cancel }">
  89 + <div style="text-align: center">
  90 + <el-button :size="_size" type="primary" @click="submit" :loading="submitting">确定</el-button>
  91 + <el-button :size="_size" plain @click="cancel">取消</el-button>
  92 + </div>
  93 + </template>
  94 + </z-schema-form>
  95 + </template>
  96 + <template v-else-if="elDialogType === 'detail'">
  97 + <z-schema-form key="form-detail" ref="form" v-model="detail" :schema="schema.detail || detailSchema">
  98 + <template v-for="item in getSlotKeys('detail-')" #[item.name]="slotScope">
  99 + <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot>
  100 + </template>
  101 + </z-schema-form>
  102 + </template>
  103 + </div>
  104 + </template>
  105 + </el-dialog>
  106 + </div>
  107 +</template>
  108 +
  109 +<script>
  110 +import { cloneDeep, get } from '../utils';
  111 +import { filterout } from '../utils/schema';
  112 +import MIX_ORGIN from '../mixins/origin';
  113 +
  114 +let propsMap = {};
  115 +
  116 +const setKeysDefault = function(keys, value) {
  117 + keys.reduce(function(result, current) {
  118 + result[current] = value;
  119 + return result;
  120 + }, propsMap);
  121 +};
  122 +setKeysDefault(['value-filter', 'value-form', 'value-detail'], {
  123 + type: Object,
  124 + default() {
  125 + return {};
  126 + },
  127 +});
  128 +setKeysDefault(['value-table'], {
  129 + type: Array,
  130 + default() {
  131 + return [];
  132 + },
  133 +});
  134 +setKeysDefault(['size', 'dialogTitle', 'dialogType'], String);
  135 +setKeysDefault(['dialogVisible', 'auto'], Boolean);
  136 +setKeysDefault(['api-search', 'api-submit', 'api-new', 'api-edit', 'api-get', 'api-detail', 'api-delete'], Function);
  137 +
  138 +export default {
  139 + name: 'SchemaPage',
  140 + mixins: [MIX_ORGIN],
  141 + props: {
  142 + ...propsMap,
  143 + schema: {
  144 + type: Object,
  145 + required: true,
  146 + },
  147 + },
  148 + data() {
  149 + return {
  150 + selection: [],
  151 + // 分页参数
  152 + currentPage: get(this.schema, 'pagination.currentPage') || 1,
  153 + pageSizes: get(this.schema, 'pagination.pageSizes') || [10, 20, 50, 100],
  154 + pageSize: get(this.schema, 'pagination.pageSize') || 10,
  155 + layout: get(this.schema, 'pagination.layout') || 'total, sizes, prev, pager, next, jumper',
  156 + total: get(this.schema, 'pagination.total') || 0,
  157 + visible: this.dialogVisible,
  158 + elDialogRender: true,
  159 + elDialogType: this.dialogType || 'none',
  160 + elDialogTitle: this.dialogTitle || '',
  161 + elDialogProps: {},
  162 + detailSchema: filterout(cloneDeep(this.schema.form), ['is', 'rules']),
  163 + detail: this.valueDetail || {},
  164 + tableData: this.valueTable || [],
  165 + loading: false,
  166 + submitting: false,
  167 + dialogLoading: false,
  168 + };
  169 + },
  170 + created() {
  171 + if (this.auto || this.schema.auto) {
  172 + this.onSearch();
  173 + }
  174 + console.log(this);
  175 + },
  176 + watch: {
  177 + detailValue(val) {
  178 + this.detail = val;
  179 + },
  180 + detail(val) {
  181 + this.$emit('update:value-detail', val);
  182 + },
  183 + dialogVisible(val) {
  184 + this.visible = val;
  185 + },
  186 + visible(val) {
  187 + this.$emit('update:dialog-visible', val);
  188 + },
  189 + dialogType(val) {
  190 + this.elDialogType = val;
  191 + },
  192 + elDialogType(val) {
  193 + this.$emit('update:dialog-type', val);
  194 + },
  195 + dialogTitle(val) {
  196 + this.elDialogTitle = val;
  197 + },
  198 + elDialogTitle(val) {
  199 + this.$emit('update:dialog-title', val);
  200 + },
  201 + valueTable(val) {
  202 + this.tableData = val;
  203 + },
  204 + tableData(val) {
  205 + this.$emit('update:value-table', val);
  206 + },
  207 + },
  208 + computed: {
  209 + slotKeys() {
  210 + return Object.keys(this.$scopedSlots);
  211 + },
  212 + _size() {
  213 + return this.size || get(this.schema, 'props.size') || (this.$ELEMENT || {}).size || 'small';
  214 + },
  215 + _slotScope() {
  216 + const properties = ['selection', 'currentPage', 'pageSizes', 'pageSize', 'layout', 'total', 'loading'];
  217 + const methods = ['search', 'onSearch', 'onDelete', 'onDeleteMultiple', 'openNew', 'openEdit', 'openDetail', 'openDialog', 'closeDialog'];
  218 + const defaultScope = {
  219 + size: this._size,
  220 + };
  221 + return [...properties, ...methods].reduce((result, current) => {
  222 + result[current] = this[current];
  223 + return result;
  224 + }, defaultScope);
  225 + },
  226 + _dialogProps() {
  227 + return {
  228 + 'destroy-on-close': true,
  229 + 'append-to-body': true,
  230 + 'lock-scroll': false,
  231 + 'close-on-click-modal': false,
  232 + ...(this.schema.dialog || {}),
  233 + ...this.elDialogProps,
  234 + };
  235 + },
  236 + },
  237 + methods: {
  238 + getSlot(name) {
  239 + return this.$slots[name] || this.$scopedSlots[name];
  240 + },
  241 + getSlotKeys(prefix) {
  242 + return this.slotKeys.reduce((result, current) => {
  243 + if (current.indexOf(prefix) === 0) {
  244 + result.push({
  245 + slot: current,
  246 + name: current.substring(prefix.length),
  247 + });
  248 + }
  249 + return result;
  250 + }, []);
  251 + },
  252 + // 空Promise
  253 + emptyPromise() {
  254 + return new Promise(resolve => resolve());
  255 + },
  256 + // 查询
  257 + search() {
  258 + if (!this.loading) {
  259 + this.loading = true;
  260 + const params = {
  261 + ...this.valueFilter,
  262 + currentPage: this.currentPage,
  263 + pageSize: this.pageSize,
  264 + };
  265 + const searchAPI = this.apiSearch || this.emptyPromise;
  266 + searchAPI(params)
  267 + .then(res => {
  268 + const response = res || {};
  269 + this.tableData = response[this.schema.listKey || 'list'] || [];
  270 + this.total = response[this.schema.totalKey || 'total'] || 0;
  271 + })
  272 + .finally(() => {
  273 + this.loading = false;
  274 + });
  275 + }
  276 + },
  277 + // 重置查询
  278 + onSearch() {
  279 + this.currentPage = 1;
  280 + this.search();
  281 + },
  282 + // 表单提交
  283 + onFormSubmit(value) {
  284 + if (this.$listeners['form-submit']) {
  285 + this.$emit('form-submit', value);
  286 + } else {
  287 + this.submitting = true;
  288 + let submitAPI = this.apiSubmit || this.emptyPromise;
  289 + if (this.elDialogType === 'new') {
  290 + submitAPI = this.apiNew || this.apiSubmit || this.emptyPromise;
  291 + } else if (this.elDialogType === 'edit') {
  292 + submitAPI = this.apiEdit || this.apiSubmit || this.emptyPromise;
  293 + }
  294 + submitAPI(this.valueForm, { type: this.elDialogType })
  295 + .then(() => {
  296 + if (this.$listeners['submit-success']) {
  297 + this.$emit('submit-success');
  298 + } else {
  299 + this.$message.success('保存成功');
  300 + }
  301 + this.closeDialog();
  302 + this.search();
  303 + })
  304 + .finally(() => {
  305 + this.submitting = false;
  306 + });
  307 + }
  308 + },
  309 + // 打开新增
  310 + openNew() {
  311 + this.openDialog('new', '新增');
  312 + },
  313 + // 打开编辑
  314 + openEdit(row) {
  315 + this.dialogLoading = true;
  316 + this.openDialog('edit', '编辑');
  317 + const getRow = () =>
  318 + new Promise(resolve => {
  319 + resolve(cloneDeep(row));
  320 + });
  321 + const getAPI = this.apiGet || getRow;
  322 + getAPI(cloneDeep(row))
  323 + .then(result => {
  324 + if (result) {
  325 + this.$emit('update:value-form', result);
  326 + }
  327 + })
  328 + .finally(() => {
  329 + this.dialogLoading = false;
  330 + });
  331 + },
  332 + // 打开详情
  333 + openDetail(row) {
  334 + this.dialogLoading = true;
  335 + this.openDialog('detail', '详情');
  336 + const getRow = () =>
  337 + new Promise(resolve => {
  338 + resolve(cloneDeep(row));
  339 + });
  340 + const getAPI = this.apiDetail || this.apiGet || getRow;
  341 + getAPI(cloneDeep(row))
  342 + .then(result => {
  343 + if (result) {
  344 + this.detail = result;
  345 + this.$emit('update:value-detail', result);
  346 + }
  347 + })
  348 + .finally(() => {
  349 + this.dialogLoading = false;
  350 + });
  351 + },
  352 + // 打开弹出框
  353 + openDialog(type, title, config) {
  354 + this.elDialogRender = true;
  355 + this.elDialogType = type;
  356 + this.elDialogTitle = title;
  357 + this.elDialogProps = config || {};
  358 + this.visible = true;
  359 + this.$emit('dialog-change', type);
  360 + },
  361 + // 关闭弹出框
  362 + closeDialog() {
  363 + this.visible = false;
  364 + },
  365 + // 弹出框显示状态更新
  366 + onVisibleUpdate(visible) {
  367 + this.visible = visible;
  368 + },
  369 + // 弹出框关闭
  370 + onDialogClose() {
  371 + this.elDialogType = 'none';
  372 + this.$emit('dialog-change', 'none');
  373 + },
  374 + // 弹出框关闭动画结束
  375 + onDialogClosed() {
  376 + if (this.$refs.form) {
  377 + this.$refs.form.resetFields();
  378 + }
  379 + this.elDialogRender = false;
  380 + this.elDialogProps = {};
  381 + this.$emit('update:value-form', this.cloneDeep(this.originProps).valueForm);
  382 + this.$emit('update:value-detail', this.cloneDeep(this.originProps).detailValue);
  383 + },
  384 + // 表格选中状态
  385 + onTableSelectionChange(selection, type) {
  386 + this.selection = selection;
  387 + },
  388 + // 分页-每页个数
  389 + onSizeChange(val) {
  390 + this.pageSize = val;
  391 + this.currentPage = 1;
  392 + this.$nextTick(this.search);
  393 + },
  394 + // 分页-当前页数
  395 + onCurrentChange(val) {
  396 + this.currentPage = val;
  397 + this.$nextTick(this.search);
  398 + },
  399 + // 删除数据
  400 + onDelete(selection) {
  401 + const loading = this.$loading({
  402 + lock: true,
  403 + text: '处理中',
  404 + spinner: 'el-icon-loading',
  405 + customClass: 'z-loading-toast',
  406 + background: 'rgba(0, 0, 0, 0)',
  407 + });
  408 + const deleteAPI = this.apiDelete || this.emptyPromise;
  409 + deleteAPI(selection)
  410 + .then(() => {
  411 + this.search();
  412 + if (this.$listeners['delete-success']) {
  413 + this.$emit('delete-success');
  414 + } else {
  415 + this.$message.success('删除成功');
  416 + }
  417 + })
  418 + .finally(() => {
  419 + loading.close();
  420 + });
  421 + },
  422 + // 批量删除
  423 + onDeleteMultiple(selection) {
  424 + this.$confirm(`是否删除这 [${selection.length}] 项?`, '提示', {
  425 + confirmButtonText: '确定',
  426 + cancelButtonText: '取消',
  427 + type: 'warning',
  428 + })
  429 + .then(() => {
  430 + this.onDelete(selection);
  431 + })
  432 + .catch(() => {});
  433 + },
  434 + },
  435 +};
  436 +</script>
packages/schema-table/index.vue
@@ -32,7 +32,7 @@ export default { @@ -32,7 +32,7 @@ export default {
32 render(h) { 32 render(h) {
33 const schema = this.schema || {}; 33 const schema = this.schema || {};
34 const _props = schema.props || {}; 34 const _props = schema.props || {};
35 - const _on = schema.on || {}; 35 + const _on = schema.on || this.$listeners || {};
36 return h('z-table', { props: { value: this.model, columns: schema.items, ..._props }, on: _on, scopedSlots: this.$scopedSlots }); 36 return h('z-table', { props: { value: this.model, columns: schema.items, ..._props }, on: _on, scopedSlots: this.$scopedSlots });
37 }, 37 },
38 }; 38 };
packages/schema/index.scss
@@ -1,72 +0,0 @@ @@ -1,72 +0,0 @@
1 -.z-schema {  
2 - &__header {  
3 - margin-bottom: 10px;  
4 - }  
5 - &__filter {  
6 - border: 1px solid #ebeef5;  
7 - padding-top: 10px;  
8 - border-radius: 4px;  
9 - margin-bottom: 10px;  
10 - }  
11 - &__action {  
12 - display: flex;  
13 - flex-wrap: wrap;  
14 - align-items: center;  
15 - justify-content: flex-start;  
16 - line-height: 1;  
17 - .el-button + .el-button {  
18 - margin-left: 0;  
19 - }  
20 - .el-button {  
21 - margin-right: 10px;  
22 - margin-bottom: 10px;  
23 - }  
24 - }  
25 - &__table {  
26 - &-operation {  
27 - display: flex;  
28 - flex-wrap: wrap;  
29 - align-items: center;  
30 - justify-content: flex-start;  
31 - .el-button + .el-button {  
32 - margin-left: 0;  
33 - }  
34 - .el-button {  
35 - margin-right: 10px;  
36 - padding-top: 6px;  
37 - padding-bottom: 6px;  
38 - }  
39 - }  
40 - }  
41 - &__dialog-button {  
42 - display: flex;  
43 - align-items: center;  
44 - justify-content: center;  
45 - padding-top: 10px;  
46 - }  
47 - &__footer {  
48 - margin-top: 10px;  
49 - text-align: right;  
50 - display: flex;  
51 - justify-content: space-between;  
52 - align-items: center;  
53 - .selection-info {  
54 - word-break: break-all;  
55 - white-space: nowrap;  
56 - font-size: 12px;  
57 - color: #606266;  
58 - .num {  
59 - color: #000;  
60 - font-weight: bold;  
61 - padding: 0 5px;  
62 - font-size: 16px;  
63 - }  
64 - .el-button {  
65 - margin-left: 5px;  
66 - }  
67 - }  
68 - .el-pagination {  
69 - flex: auto;  
70 - }  
71 - }  
72 -}  
73 \ No newline at end of file 0 \ No newline at end of file
packages/schema/index.vue
@@ -1,649 +0,0 @@ @@ -1,649 +0,0 @@
1 -<style lang="scss">  
2 -@import './index.scss';  
3 -</style>  
4 -  
5 -<template>  
6 - <div class="z-schema">  
7 - <!-- 头部内容 -->  
8 - <div v-if="$scopedSlots.header || $slots.header" class="z-schema__header">  
9 - <slot name="header" :filterModel="filterModel" v-bind="_slotScope"></slot>  
10 - </div>  
11 - <!-- 筛选组件 -->  
12 - <div v-if="filter" class="z-schema__filter">  
13 - <z-schema-filter  
14 - :value="_filterModel"  
15 - :list="filterList || listMap.filter | noRulesFilter"  
16 - :size="size"  
17 - @input="onFilterInput"  
18 - @search="onSearch"  
19 - :loading="loading"  
20 - v-bind="filterProps"  
21 - :params="_slotScope"  
22 - ></z-schema-filter>  
23 - </div>  
24 - <!-- 按钮区 -->  
25 - <div v-if="action" class="z-schema__action">  
26 - <slot v-if="hadSlot('action')" name="action" v-bind="_slotScope"></slot>  
27 - <template v-else>  
28 - <el-button :size="size" type="primary" @click="openNew">新增</el-button>  
29 - <el-button :size="size" plain :disabled="selection.length === 0" @click="handleDeleteMul(selection)">删除</el-button>  
30 - <slot name="button" v-bind="_slotScope"></slot>  
31 - </template>  
32 - </div>  
33 - <!-- 表格内容 -->  
34 - <div class="z-schema__table">  
35 - <z-schema-table  
36 - ref="table"  
37 - v-model="tableData"  
38 - v-loading="loading"  
39 - :list="tableList || listMap.table"  
40 - :tableProps="{ border: true, 'row-key': 'id', 'highlight-current-row': true, ...tableProps }"  
41 - :size="size"  
42 - @selection-change="onTableSelectionChange"  
43 - @selection="onTableSelection"  
44 - >  
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 - <!-- 表格尾追加操作列 -->  
65 - <template #column-end>  
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 }">  
68 - <div class="z-schema__table-operation" slot-scope="slotScope">  
69 - <slot name="operation-button" v-bind="{ ..._slotScope, slotScope }"></slot>  
70 - <el-button type="text" icon="el-icon-edit" title="编辑" @click="openEdit(slotScope.row)"></el-button>  
71 - <el-popconfirm confirmButtonText="确定" cancelButtonText="取消" title="确定删除吗?" placement="top" @confirm="handleDelete([slotScope.row])">  
72 - <el-button slot="reference" type="text" icon="el-icon-delete" title="删除"></el-button>  
73 - </el-popconfirm>  
74 - <slot name="operation-button-append" v-bind="{ ..._slotScope, slotScope }"></slot>  
75 - </div>  
76 - </el-table-column>  
77 - </template>  
78 - </z-schema-table>  
79 - </div>  
80 - <!-- 底部区域 -->  
81 - <div class="z-schema__footer">  
82 - <div v-if="selection.length > 0" class="selection-info">  
83 - <span>已选中</span>  
84 - <span class="num">{{ selection.length }}</span>  
85 - <span>项</span>  
86 - <el-popconfirm confirmButtonText="确定" cancelButtonText="取消" title="确定清除吗?" placement="top" @confirm="clearSelection">  
87 - <el-button slot="reference" :size="size" type="text">清除</el-button>  
88 - </el-popconfirm>  
89 - </div>  
90 - <!-- 分页器 -->  
91 - <el-pagination  
92 - v-if="pagination"  
93 - @size-change="handleSizeChange"  
94 - @current-change="handleCurrentChange"  
95 - :current-page="currentPage"  
96 - :page-sizes="pageSizes"  
97 - :page-size="pageSize"  
98 - layout="total, sizes, prev, pager, next, jumper"  
99 - :total="total"  
100 - >  
101 - </el-pagination>  
102 - </div>  
103 - <!-- 弹出框 -->  
104 - <el-dialog  
105 - :visible.sync="dialogVisible"  
106 - :title="dialogTitle"  
107 - destroy-on-close  
108 - append-to-body  
109 - :lock-scroll="false"  
110 - :close-on-click-modal="false"  
111 - @closed="onDialogClosed"  
112 - @close="onDialogClose"  
113 - v-bind="_dialogProps"  
114 - >  
115 - <div v-loading="dialogLoading">  
116 - <!-- 自定义弹出框标题 -->  
117 - <slot v-if="hadSlot('dialog-title')" slot="title" name="dialog-title" :dialogType="dialogType" v-bind="_slotScope"></slot>  
118 - <template v-if="dialogRender">  
119 - <!-- 自定义弹出框内容 -->  
120 - <slot v-if="hadSlot(`dialog-${dialogType}`)" :name="`dialog-${dialogType}`" :model="_formModel" v-bind="_slotScope"></slot>  
121 - <template v-else>  
122 - <!-- 内置弹出框新增修改表单 -->  
123 - <template v-if="['new', 'edit'].includes(dialogType)">  
124 - <z-form  
125 - ref="form"  
126 - :value="_formModel"  
127 - :list="formList || listMap.form"  
128 - @input="onFormInput"  
129 - @validate="onFormValidate"  
130 - v-bind="{ span: 12, 'label-width': '110px', ...formProps }"  
131 - :params="_slotScope"  
132 - >  
133 - <!-- 表单自定义插槽 -->  
134 - <template v-for="item in renderList">  
135 - <template v-if="$scopedSlots[`form-${item.fullKey}`]">  
136 - <slot :slot="item.fullKey" :name="`form-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>  
137 - </template>  
138 - </template>  
139 - </z-form>  
140 - </template>  
141 - <!-- 内置弹出框详情表单 -->  
142 - <template v-else>  
143 - <z-form  
144 - ref="form"  
145 - class="z-schema__view"  
146 - :value="_formModel"  
147 - :list="viewList || listMap.form | viewTypeFilter | noRulesFilter"  
148 - v-bind="{ span: 12, 'label-width': '110px', ...viewProps }"  
149 - :params="_slotScope"  
150 - >  
151 - <!-- 详情自定义插槽渲染 -->  
152 - <template v-for="item in renderList">  
153 - <template v-if="$scopedSlots[`view-${item.fullKey}`]">  
154 - <slot :slot="item.fullKey" :name="`view-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>  
155 - </template>  
156 - <template v-else-if="$scopedSlots[`render-${item.fullKey}`]">  
157 - <slot :slot="item.fullKey" :name="`render-${item.fullKey}`" :value="get(_formModel, item.fullKey)" :row="_formModel" :model="_formModel" v-bind="_slotScope"></slot>  
158 - </template>  
159 - </template>  
160 - </z-form>  
161 - </template>  
162 - </template>  
163 - <!-- 内置弹出框新增修改按钮 -->  
164 - <div class="z-schema__dialog-button" v-if="['new', 'edit'].includes(dialogType)">  
165 - <el-button :size="size" type="primary" @click="handleConfirm" :loading="submitting">确定</el-button>  
166 - <el-button :size="size" plain @click="closeDialog">取消</el-button>  
167 - </div>  
168 - </template>  
169 - </div>  
170 - </el-dialog>  
171 - </div>  
172 -</template>  
173 -  
174 -<script>  
175 -import { cloneDeep, get } from '../utils';  
176 -import { clear } from '../utils/param';  
177 -  
178 -let propsMap = {};  
179 -const propsKeys = ['tableProps', 'filterProps', 'formProps', 'viewProps', 'dialogProps', 'operationProps'];  
180 -propsKeys.forEach(key => {  
181 - propsMap[key] = {  
182 - type: Object,  
183 - default: () => ({}),  
184 - };  
185 -});  
186 -const apiKeys = ['searchApi', 'submitApi', 'addApi', 'modifyApi', 'getApi', 'viewApi', 'deleteApi'];  
187 -apiKeys.forEach(key => {  
188 - propsMap[key] = {  
189 - type: Function,  
190 - };  
191 -});  
192 -const blockKeys = ['filter', 'action', 'pagination', 'operation'];  
193 -blockKeys.forEach(key => {  
194 - propsMap[key] = {  
195 - type: Boolean,  
196 - default: true,  
197 - };  
198 -});  
199 -  
200 -export default {  
201 - name: 'Schema',  
202 - props: {  
203 - ...propsMap,  
204 - list: Array,  
205 - filterList: Array,  
206 - tableList: Array,  
207 - formList: Array,  
208 - viewList: Array,  
209 - size: {  
210 - type: String,  
211 - default: 'mini',  
212 - },  
213 - formModel: Object,  
214 - filterModel: Object,  
215 - auto: Boolean,  
216 - realSelection: Boolean,  
217 - url: String, // 请求地址  
218 - http: Function, // http库  
219 - alias: Object, // 别名配置  
220 - },  
221 - data() {  
222 - return {  
223 - filterForm: {},  
224 - editForm: {},  
225 - dialogVisible: false,  
226 - dialogRender: true,  
227 - dialogType: 'none',  
228 - dialogLoading: false,  
229 - dialogTitle: '',  
230 - dialogPropsHack: {},  
231 - currentPage: 1,  
232 - pageSize: 10,  
233 - total: 0,  
234 - pageSizes: [10, 20, 50],  
235 - tableData: [],  
236 - submitting: false,  
237 - loading: false,  
238 - selection: [],  
239 - };  
240 - },  
241 - created() {  
242 - if (this.auto) {  
243 - this.search();  
244 - }  
245 - },  
246 - filters: {  
247 - // 无规则过滤器,过滤掉筛选条件表单中的必填规则等  
248 - noRulesFilter(val = []) {  
249 - let list = cloneDeep(val);  
250 - const clearRules = list => {  
251 - list.forEach(item => {  
252 - if (item.list) {  
253 - clearRules(item.list);  
254 - } else {  
255 - delete item.rules;  
256 - }  
257 - });  
258 - };  
259 - clearRules(list);  
260 - return list;  
261 - },  
262 - // 详情类型过滤器  
263 - viewTypeFilter(val = []) {  
264 - let list = cloneDeep(val);  
265 - const clearRules = list => {  
266 - list.forEach(item => {  
267 - item.type = (h, { model, config }) => h('span', config, model[item.key]);  
268 - });  
269 - };  
270 - clearRules(list);  
271 - return list;  
272 - },  
273 - },  
274 - computed: {  
275 - listMap() {  
276 - // 默认作用域  
277 - const LIST_SPACE = ['filter', 'form', 'table'];  
278 - const array = {  
279 - filter: [], // 筛选  
280 - form: [], // 表单  
281 - table: [], // 表格  
282 - };  
283 - this.list.forEach(item => {  
284 - // 可以在列表中通过include或exclude设置当前配置的作用域  
285 - const { include = LIST_SPACE, exclude = [] } = item;  
286 - // 判断include  
287 - let _inclue = [];  
288 - if (include instanceof String || typeof include === 'string') {  
289 - _inclue = [include];  
290 - } else if (include instanceof Array && typeof include === 'object') {  
291 - _inclue = include;  
292 - }  
293 - // 判断exclude转换为include的情况  
294 - let _exclude_include = [];  
295 - if (exclude instanceof String || typeof exclude === 'string') {  
296 - _exclude_include = LIST_SPACE.filter(item => item !== exclude);  
297 - } else if (exclude instanceof Array && typeof exclude === 'object') {  
298 - _exclude_include = LIST_SPACE.filter(item => !exclude.includes(item));  
299 - }  
300 - // 作用域交集  
301 - const _intersection = _inclue.filter(v => _exclude_include.includes(v));  
302 - // 返回改配置项的作用域  
303 - const _list_space = cloneDeep(_intersection);  
304 - // 将配置项按需分配至各作用域下  
305 - _list_space.forEach(name => {  
306 - array[name].push({ ...item, ...(item[name] || {}) });  
307 - });  
308 - });  
309 - return array;  
310 - },  
311 - renderList() {  
312 - // 深度克隆传入的列表,避免原始值被修改  
313 - const newList = cloneDeep(this.list);  
314 - // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key  
315 - const generateFullKey = (list, parentKey) => {  
316 - list.forEach(item => {  
317 - if (item.group && item.list) {  
318 - if (item.group.key) {  
319 - item.fullKey = `${parentKey ? `${parentKey}-${item.group.key}` : item.group.key}`;  
320 - } else {  
321 - item.fullKey = parentKey || item.key;  
322 - }  
323 - generateFullKey(item.list, item.fullKey);  
324 - } else {  
325 - item.fullKey = `${parentKey ? `${parentKey}-${item.key}` : item.key}`;  
326 - }  
327 - });  
328 - };  
329 - // 生成fullKey  
330 - generateFullKey(newList);  
331 - return newList;  
332 - },  
333 - _filterModel() {  
334 - return this.filterModel || this.filterForm || {};  
335 - },  
336 - _formModel() {  
337 - return this.formModel || this.editForm || {};  
338 - },  
339 - _slotScope() {  
340 - return {  
341 - handleSearch: this.search,  
342 - openDialog: this.openDialog,  
343 - closeDialog: this.closeDialog,  
344 - openView: this.openView,  
345 - openEdit: this.openEdit,  
346 - openNew: this.openNew,  
347 - handleDelete: this.handleDelete,  
348 - handleDeleteMul: this.handleDeleteMul,  
349 - size: this.size,  
350 - dialogType: this.dialogType,  
351 - selection: this.selection,  
352 - };  
353 - },  
354 - _alias() {  
355 - const alias = this.alias;  
356 - const zAlias = this.zAlias;  
357 - if (alias && zAlias) {  
358 - return { ...zAlias, ...alias };  
359 - }  
360 - return this.alias || this.zAlias || {};  
361 - },  
362 - _dialogProps() {  
363 - return {  
364 - ...this.dialogProps,  
365 - ...this.dialogPropsHack,  
366 - };  
367 - },  
368 - },  
369 - methods: {  
370 - get,  
371 - // 空Promise  
372 - emptyPromise() {  
373 - return new Promise(resolve => resolve());  
374 - },  
375 - // 设置表格选中行  
376 - toggleRowSelection() {  
377 - this.tableData.forEach(row => {  
378 - if (this.selection.find(item => item.id === row.id)) {  
379 - this.$refs.table && this.$refs.table.toggleRowSelection(row);  
380 - }  
381 - });  
382 - },  
383 - // 表格选中状态  
384 - onTableSelectionChange(selection, type) {  
385 - if (this.realSelection) {  
386 - if (type === 'check') {  
387 - const result = this.selection || [];  
388 - selection.forEach(item => {  
389 - if (!result.find(i => i.id === item.id)) {  
390 - result.push(item);  
391 - }  
392 - });  
393 - this.selection = result;  
394 - } else if (type === 'uncheck') {  
395 - selection.forEach(i => {  
396 - this.selection = this.selection.filter(item => item.id !== i.id);  
397 - });  
398 - }  
399 - }  
400 - },  
401 - // 表格选中  
402 - onTableSelection(selection) {  
403 - if (!this.realSelection) {  
404 - this.selection = selection;  
405 - }  
406 - },  
407 - // 清除表格选中  
408 - clearSelection() {  
409 - this.$refs.table && this.$refs.table.clearSelection();  
410 - this.selection = [];  
411 - },  
412 - // 内置搜索接口  
413 - _searchAPI(params) {  
414 - if (this.url && (this.http || this.zHttp)) {  
415 - const _http = this.http || this.zHttp;  
416 - return _http({ url: `${clear(this.url)}/${this._alias.pageUrl || 'page'}`, params });  
417 - }  
418 - return undefined;  
419 - },  
420 - // 重置查询  
421 - onSearch() {  
422 - this.currentPage = 1;  
423 - this.search();  
424 - },  
425 - // 搜索  
426 - async search() {  
427 - this.loading = true;  
428 - const params = {  
429 - ...this._filterModel,  
430 - currentPage: this.currentPage,  
431 - pageSize: this.pageSize,  
432 - };  
433 - const searchAPI = this.searchApi || this._searchAPI || this.emptyPromise;  
434 - await searchAPI(params)  
435 - .then(res => {  
436 - const response = res || {};  
437 - this.tableData = response[this._alias.list || 'list'] || [];  
438 - this.total = response[this._alias.total || 'total'] || 0;  
439 - this.$nextTick(this.toggleRowSelection);  
440 - })  
441 - .catch(() => {  
442 - this.$message.error('查询失败');  
443 - });  
444 - this.loading = false;  
445 - },  
446 - // 更新筛选model  
447 - onFilterInput(val) {  
448 - this.filterForm = val || {};  
449 - this.$emit('update:filterModel', val || {});  
450 - },  
451 - // 更新表单model  
452 - onFormInput(val) {  
453 - this.editForm = val || {};  
454 - this.$emit('update:formModel', val || {});  
455 - },  
456 - // 内置新增保存接口  
457 - _addAPI(data) {  
458 - if (this.url && (this.http || this.zHttp)) {  
459 - const _http = this.http || this.zHttp;  
460 - return _http({ url: `${clear(this.url)}/${this._alias.addUrl || 'add'}`, method: 'post', data });  
461 - }  
462 - return undefined;  
463 - },  
464 - // 内置修改保存接口  
465 - _modifyAPI(data) {  
466 - if (this.url && (this.http || this.zHttp)) {  
467 - const _http = this.http || this.zHttp;  
468 - return _http({ url: `${clear(this.url)}/${this._alias.modifyUrl || 'modify'}`, method: 'post', data });  
469 - }  
470 - return undefined;  
471 - },  
472 - // 表单提交且通过校验  
473 - async onFormValidate(valid, model) {  
474 - if (valid) {  
475 - this.submitting = true;  
476 - let submitAPI = this.submitApi || this.emptyPromise;  
477 - if (this.dialogType === 'new') {  
478 - submitAPI = this.addApi || this.submitApi || this._addAPI || this.emptyPromise;  
479 - } else if (this.dialogType === 'edit') {  
480 - submitAPI = this.modifyApi || this.submitApi || this._modifyAPI || this.emptyPromise;  
481 - }  
482 - submitAPI(model, { type: this.dialogType })  
483 - .then(() => {  
484 - this.$message.success('保存成功');  
485 - this.closeDialog();  
486 - this.search();  
487 - })  
488 - .catch(() => {  
489 - this.$message.error('保存失败');  
490 - })  
491 - .finally(() => {  
492 - this.submitting = false;  
493 - });  
494 - }  
495 - },  
496 - // 表单按钮确定  
497 - handleConfirm() {  
498 - this.$refs.form && this.$refs.form.validate();  
499 - },  
500 - // 表单按钮取消  
501 - handleCancel() {  
502 - this.closeDialog();  
503 - },  
504 - // 查询是否有某个插槽  
505 - hadSlot(name) {  
506 - return !!this.$slots[name] || !!this.$scopedSlots[name];  
507 - },  
508 - // 打开新增弹出框  
509 - openNew() {  
510 - this.openDialog('new', '新增');  
511 - },  
512 - // 内置查询详情接口  
513 - _getAPI(row) {  
514 - if (this.url && (this.http || this.zHttp)) {  
515 - const _http = this.http || this.zHttp;  
516 - const _getKey = this._alias.getKey || this._alias.primaryKey || 'id';  
517 - const _resultKey = this._alias.result || 'result';  
518 - return _http({ url: `${clear(this.url)}/${this._alias.getUrl || 'queryById'}`, params: { [_getKey]: row[_getKey] } }).then(response => response[_resultKey] || {});  
519 - }  
520 - return undefined;  
521 - },  
522 - // 打开编辑弹出框  
523 - openEdit(row) {  
524 - this.dialogLoading = true;  
525 - this.openDialog('edit', '编辑');  
526 - const getRow = () =>  
527 - new Promise(resolve => {  
528 - resolve(row);  
529 - });  
530 - const getAPI = this.getApi || this._getAPI || getRow;  
531 - getAPI(row)  
532 - .then(result => {  
533 - this.editForm = result;  
534 - this.$emit('update:formModel', result || {});  
535 - })  
536 - .finally(() => {  
537 - this.dialogLoading = false;  
538 - });  
539 - },  
540 - // 内置查询详情接口  
541 - _viewAPI(row) {  
542 - if (this.url && (this.http || this.zHttp)) {  
543 - const _http = this.http || this.zHttp;  
544 - const _viewKey = this._alias.viewKey || this._alias.getKey || this._alias.primaryKey || 'id';  
545 - const _resultKey = this._alias.result || 'result';  
546 - return _http({ url: `${clear(this.url)}/${this._alias.getUrl || 'queryById'}`, params: { [_viewKey]: row[_viewKey] } }).then(response => response[_resultKey] || {});  
547 - }  
548 - return undefined;  
549 - },  
550 - // 打开详情弹出框  
551 - openView(row) {  
552 - this.dialogLoading = true;  
553 - this.openDialog('view', '详情');  
554 - const getRow = () =>  
555 - new Promise(resolve => {  
556 - resolve(row);  
557 - });  
558 - const viewAPI = this.viewApi || this.getApi || this._viewAPI || this._getAPI || getRow;  
559 - viewAPI(row)  
560 - .then(result => {  
561 - this.editForm = result;  
562 - this.$emit('update:formModel', result || {});  
563 - })  
564 - .finally(() => {  
565 - this.dialogLoading = false;  
566 - });  
567 - },  
568 - // 内置删除接口  
569 - _deleteAPI(keys) {  
570 - if (this.url && (this.http || this.zHttp)) {  
571 - const _http = this.http || this.zHttp;  
572 - return _http({ url: `${clear(this.url)}/${this._alias.modifyUrl || 'delete'}`, method: 'post', data: keys });  
573 - }  
574 - return undefined;  
575 - },  
576 - // 删除  
577 - handleDelete(selection) {  
578 - const loading = this.$loading({  
579 - text: '处理中',  
580 - spinner: 'el-icon-loading',  
581 - background: 'rgba(255, 255, 255, 0.5)',  
582 - });  
583 - const deleteAPI = this.deleteApi || this._deleteAPI || this.emptyPromise;  
584 - const _deleteKey = this._alias.deleteKey || this._alias.primaryKey || 'id';  
585 - const keys = selection.map(i => i[_deleteKey]);  
586 - deleteAPI(keys)  
587 - .then(() => {  
588 - this.search();  
589 - this.$message.success('删除成功');  
590 - })  
591 - .finally(() => {  
592 - loading.close();  
593 - });  
594 - },  
595 - // 批量删除  
596 - handleDeleteMul(selection) {  
597 - this.$confirm(`是否删除这 [${selection.length}] 项?`, '提示', {  
598 - confirmButtonText: '确定',  
599 - cancelButtonText: '取消',  
600 - type: 'warning',  
601 - })  
602 - .then(() => {  
603 - this.handleDelete(selection);  
604 - })  
605 - .catch(() => {});  
606 - },  
607 - // 打开弹出框  
608 - openDialog(type, title, config) {  
609 - this.dialogVisible = true;  
610 - this.dialogRender = true;  
611 - this.dialogType = type;  
612 - this.dialogTitle = title;  
613 - this.dialogPropsHack = config || {};  
614 - this.$emit('dialog-change', type);  
615 - },  
616 - // 关闭弹出框  
617 - closeDialog() {  
618 - this.dialogVisible = false;  
619 - },  
620 - // 清空表单  
621 - clearEditForm() {  
622 - this.editForm = {};  
623 - this.$emit('update:formModel', {});  
624 - },  
625 - // 弹出框关闭  
626 - onDialogClose() {  
627 - this.dialogType = 'none';  
628 - this.dialogRender = false;  
629 - this.$emit('dialog-change', 'none');  
630 - },  
631 - // 弹出框关闭动画结束  
632 - onDialogClosed() {  
633 - this.clearEditForm();  
634 - this.dialogPropsHack = {};  
635 - },  
636 - // 分页-每页个数  
637 - handleSizeChange(val) {  
638 - this.pageSize = val;  
639 - this.currentPage = 1;  
640 - this.$nextTick(this.search);  
641 - },  
642 - // 分页-当前页数  
643 - handleCurrentChange(val) {  
644 - this.currentPage = val;  
645 - this.$nextTick(this.search);  
646 - },  
647 - },  
648 -};  
649 -</script>  
packages/table/editable.vue
@@ -27,7 +27,15 @@ @@ -27,7 +27,15 @@
27 </style> 27 </style>
28 28
29 <template> 29 <template>
30 - <el-table :data="tableData | tableDataFilter" :size="tableSize" v-bind="bindProps" @header-click="onHeaderClick" @cell-click="onCellClick" @cell-dblclick="onCellDblclick"> 30 + <el-table
  31 + :data="tableData | tableDataFilter"
  32 + :size="tableSize"
  33 + v-bind="bindProps"
  34 + v-on="$listeners"
  35 + @header-click="onHeaderClick"
  36 + @cell-click="onCellClick"
  37 + @cell-dblclick="onCellDblclick"
  38 + >
31 <slot name="left"></slot> 39 <slot name="left"></slot>
32 <template v-for="(item, index) in columns"> 40 <template v-for="(item, index) in columns">
33 <el-table-column v-bind="item" :key="index"> 41 <el-table-column v-bind="item" :key="index">
@@ -48,6 +56,9 @@ @@ -48,6 +56,9 @@
48 <template v-if="$scopedSlots[`cell-${item.prop}`]"> 56 <template v-if="$scopedSlots[`cell-${item.prop}`]">
49 <slot :name="`cell-${item.prop}`" :value="row[column.property]" :row="row" :index="$index"></slot> 57 <slot :name="`cell-${item.prop}`" :value="row[column.property]" :row="row" :index="$index"></slot>
50 </template> 58 </template>
  59 + <template v-else-if="item.render && typeof item.render === 'function'" #default="{ row, column, $index }">
  60 + <cell-render :item="item" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></cell-render>
  61 + </template>
51 </cell-editor> 62 </cell-editor>
52 </template> 63 </template>
53 </el-table-column> 64 </el-table-column>
packages/table/index.js
@@ -22,8 +22,6 @@ export default { @@ -22,8 +22,6 @@ export default {
22 ...tableProps, 22 ...tableProps,
23 }, 23 },
24 render(h) { 24 render(h) {
25 - const scopedSlots = this.$scopedSlots;  
26 - const listeners = this.$listeners;  
27 - return h(`z-table-${this.editable ? 'editable' : 'normal'}`, { props: { ...this._props }, scopedSlots, on: listeners }); 25 + return h(`z-table-${this.editable ? 'editable' : 'normal'}`, { props: { ...this._props }, scopedSlots: this.$scopedSlots, on: this.$listeners });
28 }, 26 },
29 }; 27 };
packages/table/normal.vue
1 <template> 1 <template>
2 - <el-table :data="tableData" :size="tableSize" v-bind="bindProps"> 2 + <el-table :data="tableData" :size="tableSize" v-bind="bindProps" v-on="$listeners">
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">
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
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 }"> 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> 11 + <cell-render :item="item" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></cell-render>
12 </template> 12 </template>
13 </el-table-column> 13 </el-table-column>
14 </template> 14 </template>
@@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
19 19
20 <script> 20 <script>
21 import tableProps from './props'; 21 import tableProps from './props';
  22 +import { get } from '../utils';
22 23
23 export default { 24 export default {
24 name: 'TableNormal', 25 name: 'TableNormal',
@@ -87,5 +88,8 @@ export default { @@ -87,5 +88,8 @@ export default {
87 return props; 88 return props;
88 }, 89 },
89 }, 90 },
  91 + methods: {
  92 + get,
  93 + },
90 }; 94 };
91 </script> 95 </script>
packages/utils/schema.js 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +export const filterout = (schema, key) => {
  2 + if (schema.items) {
  3 + schema.items = schema.items.map(item => {
  4 + if (Array.isArray(key)) {
  5 + key.forEach(k => {
  6 + delete item[k];
  7 + });
  8 + } else {
  9 + delete item[key];
  10 + }
  11 + return item;
  12 + });
  13 + }
  14 + return schema;
  15 +};