Commit 441f73b946ac046545f123693bf1215f48a1f5a6

Authored by 刘汉宸
1 parent 4f38f3a4

refactor: 重构Scheme支持分作用域及内外接口替换

examples/main.js
@@ -2,6 +2,7 @@ import Vue from 'vue'; @@ -2,6 +2,7 @@ import Vue from 'vue';
2 import App from '@/App.vue'; 2 import App from '@/App.vue';
3 import router from '@/router'; 3 import router from '@/router';
4 import store from '@/store'; 4 import store from '@/store';
  5 +import request from '@/utils/request';
5 import ElementUI from 'element-ui'; 6 import ElementUI from 'element-ui';
6 import Zee from '../packages'; 7 import Zee from '../packages';
7 import NProgress from 'nprogress'; 8 import NProgress from 'nprogress';
@@ -18,8 +19,12 @@ Vue.component('code-snippet', CodeSnippet); @@ -18,8 +19,12 @@ Vue.component('code-snippet', CodeSnippet);
18 // 注册饿了么UI 19 // 注册饿了么UI
19 Vue.use(ElementUI); 20 Vue.use(ElementUI);
20 // 注册Zee组件库 21 // 注册Zee组件库
21 -Vue.use(Zee); 22 +Vue.use(Zee, {
  23 + alias: { list: 'result', total: 'totalCount' },
  24 + http: request,
  25 +});
22 26
  27 +Vue.prototype.$http = request;
23 Vue.config.productionTip = false; 28 Vue.config.productionTip = false;
24 29
25 new Vue({ 30 new Vue({
examples/utils/cache.js 0 → 100644
@@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
  1 +const __DEBUG__ = process.env.NODE_ENV !== 'production';
  2 +/**
  3 + * 缓存数据优化
  4 + * import cache from '@/utils/cache'
  5 + * 使用方法 【
  6 + * 一、设置缓存
  7 + * string cache.put('k', 'string你好啊');
  8 + * json cache.put('k', { "b": "3" }, 2);
  9 + * array cache.put('k', [1, 2, 3]);
  10 + * boolean cache.put('k', true);
  11 + * 二、读取缓存
  12 + * 默认值 cache.get('k')
  13 + * string cache.get('k', '你好')
  14 + * json cache.get('k', { "a": "1" })
  15 + * 三、移除/清理
  16 + * 移除: cache.remove('k');
  17 + * 清理:cache.clear();
  18 + * 】
  19 + * @type {String}
  20 + */
  21 +const prefix = __DEBUG__ ? 'ZY_AUTH_DEV_' : 'ZY_AUTH_'; // 缓存前缀
  22 +const postfix = '_SEED';
  23 +/**
  24 + * 设置缓存
  25 + * @param {[type]} k [键名]
  26 + * @param {[type]} v [键值]
  27 + * @param {[type]} t [时间、单位秒]
  28 + */
  29 +function put(k, v, t) {
  30 + localStorage.setItem(prefix + k, JSON.stringify(v));
  31 + var seconds = parseInt(t);
  32 + if (seconds > 0) {
  33 + var timestamp = Date.parse(new Date());
  34 + timestamp = timestamp / 1000 + seconds;
  35 + localStorage.setItem(prefix + k + postfix, JSON.stringify(timestamp));
  36 + } else {
  37 + localStorage.removeItem(prefix + k + postfix);
  38 + }
  39 +}
  40 +
  41 +/**
  42 + * 获取缓存
  43 + * @param {[type]} k [键名]
  44 + * @param {[type]} def [获取为空时默认]
  45 + */
  46 +function get(k, def) {
  47 + var deadtime = parseInt(localStorage.getItem(prefix + k + postfix));
  48 + if (deadtime) {
  49 + if (parseInt(deadtime) < Date.parse(new Date()) / 1000) {
  50 + if (def) {
  51 + return def;
  52 + } else {
  53 + return false;
  54 + }
  55 + }
  56 + }
  57 + var res = localStorage.getItem(prefix + k);
  58 + if (res) {
  59 + return JSON.parse(res);
  60 + } else {
  61 + if (def == undefined || def == '') {
  62 + def = false;
  63 + }
  64 + return def;
  65 + }
  66 +}
  67 +
  68 +function remove(k) {
  69 + localStorage.removeItem(prefix + k);
  70 + localStorage.removeItem(prefix + k + postfix);
  71 +}
  72 +
  73 +/**
  74 + * 清理所有缓存
  75 + * @return {[type]} [description]
  76 + */
  77 +function clear() {
  78 + localStorage.clear();
  79 +}
  80 +
  81 +module.exports = {
  82 + put: put,
  83 + get: get,
  84 + remove: remove,
  85 + clear: clear,
  86 +};
examples/utils/param.js 0 → 100644
@@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
  1 +export const stringify = json => {
  2 + const urlEncode = (param, key, encode) => {
  3 + if (param === null) return '';
  4 + let paramStr = '';
  5 + const t = typeof param;
  6 + if (t === 'string' || t === 'number' || t === 'boolean') {
  7 + paramStr = `&${key}=${encode === null || encode ? encodeURIComponent(param) : param}`;
  8 + } else {
  9 + for (const i in param) {
  10 + if (i) {
  11 + if (param[i] !== undefined && param[i] !== '' && !(param[i] && typeof param[i] === 'string' && /^\s+$/.test(param[i]))) {
  12 + const k = key == null ? i : `${key}${param instanceof Array ? `[${i}]` : `.${i}`}`;
  13 + paramStr += urlEncode(param[i], k, encode);
  14 + }
  15 + }
  16 + }
  17 + }
  18 + return paramStr;
  19 + };
  20 + return urlEncode(json).substring(1);
  21 +};
  22 +
  23 +export const parse = url => {
  24 + let obj = {}; // 创建一个Object
  25 + let reg = /[?&][^?&]+=[^?&]+/g; // 正则匹配 ?&开始 =拼接 非?&结束 的参数
  26 + let arr = url.match(reg); // match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
  27 + // arr数组形式 ['?id=12345','&a=b']
  28 + if (arr) {
  29 + arr.forEach(item => {
  30 + /**
  31 + * tempArr数组 ['id','12345']和['a','b']
  32 + * 第一个是key,第二个是value
  33 + * */
  34 + let tempArr = item.substring(1).split('=');
  35 + let key = decodeURIComponent(tempArr[0]);
  36 + let val = decodeURIComponent(tempArr[1]);
  37 + obj[key] = val;
  38 + });
  39 + }
  40 + return obj;
  41 +};
  42 +
  43 +export const urlParam = data => {
  44 + return `${data ? `?${stringify(data)}` : ''}`;
  45 +};
  46 +
  47 +/**
  48 + * 清除首尾斜杠,便于拼接字符串
  49 + * @param {*} str URL
  50 + */
  51 +export const clear = str => {
  52 + return str.replace(/^(\s|\/)+|(\s|\/)+$/g, '');
  53 +};
examples/utils/request.js 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +import axios from 'axios';
  2 +
  3 +const request = axios.create({
  4 + baseURL: 'http://47.98.245.217:7070/tms-web-api/',
  5 + timeout: 1000 * 60,
  6 + withCredentials: true,
  7 + headers: {
  8 + 'Content-Type': 'application/json; charset=utf-8',
  9 + Authorization: 'Bearer cd355920-ff83-445e-87e1-39d7ecfb8566',
  10 + },
  11 +});
  12 +
  13 +// respone 拦截器
  14 +request.interceptors.response.use(
  15 + response => {
  16 + const { data = {}, config } = response;
  17 + const { businessException, errorCode, message, success } = data;
  18 + if (config && config.interceptors === false) {
  19 + // 请求配置不做返回拦截的情况
  20 + return response;
  21 + } else {
  22 + if (success) {
  23 + return data;
  24 + } else {
  25 + return Promise.reject(response);
  26 + }
  27 + }
  28 + },
  29 + error => {
  30 + return Promise.reject(error);
  31 + },
  32 +);
  33 +
  34 +export default request;
examples/views/docs/component/scheme.md
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 10
11 ```html 11 ```html
12 <template> 12 <template>
13 - <z-scheme :list="list" :searchAPI="searchAPI" :getAPI="getAPI" :submitAPI="submitAPI" :deleteAPI="deleteAPI" auto real-selection> 13 + <z-scheme :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> 14 <el-table-column type="selection" align="center" width="40"></el-table-column>
15 <template #header> 15 <template #header>
16 <el-tabs v-model="activeName"> 16 <el-tabs v-model="activeName">
@@ -63,7 +63,7 @@ export default { @@ -63,7 +63,7 @@ export default {
63 { id: '8', name: '卢纳', age: 55 }, 63 { id: '8', name: '卢纳', age: 55 },
64 ] 64 ]
65 resolve({ 65 resolve({
66 - result: params.currentPage === 1 ? list.slice(0, 5) : list.slice(5), 66 + result: list,
67 totalCount: list.length 67 totalCount: list.length
68 }); 68 });
69 }, 1500); 69 }, 1500);
@@ -101,6 +101,39 @@ export default { @@ -101,6 +101,39 @@ export default {
101 101
102 ::: 102 :::
103 103
  104 +## 内置接口逻辑
  105 +
  106 +如果CURD的接口都是同一路径下,可以使用内置接口逻辑快速对接
  107 +
  108 +::: snippet 通过`url`配置接口路径,`http`设置Promise形式的HTTP请求库
  109 +
  110 +```html
  111 +<template>
  112 + <z-scheme ref="scheme" :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 + </z-scheme>
  115 +</template>
  116 +
  117 +<script>
  118 +export default {
  119 + data() {
  120 + return {
  121 + activeName: 'wait',
  122 + list: [
  123 + { type: 'el-input', label: 'ID', key: 'id', props: { disabled: true }, include: 'form', visible: () => this.$refs.scheme.dialogType === 'edit' },
  124 + { type: 'el-input', label: '编号', key: 'code' },
  125 + { type: 'el-input', label: '名称', key: 'name' },
  126 + ]
  127 + }
  128 + },
  129 + methods: {
  130 + }
  131 +}
  132 +</script>
  133 +```
  134 +
  135 +:::
  136 +
104 ## API 137 ## API
105 138
106 ## Attribute 属性 139 ## Attribute 属性
packages/_utils/index.js 0 → 100644
@@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
  1 +/**
  2 + * 深度克隆对象
  3 + * @param {Object} obj 目标对象
  4 + * @returns {Object} 克隆的新对象
  5 + */
  6 +export const cloneDeep = obj => {
  7 + if (typeof obj !== 'object') {
  8 + return obj;
  9 + }
  10 + if (!obj) {
  11 + return obj;
  12 + }
  13 + if (obj instanceof Date) {
  14 + return new Date(obj);
  15 + }
  16 + if (obj instanceof RegExp) {
  17 + return new RegExp(obj);
  18 + }
  19 + if (obj instanceof Function) {
  20 + return obj;
  21 + }
  22 + let newObj;
  23 + if (obj instanceof Array) {
  24 + newObj = [];
  25 + for (let i = 0, len = obj.length; i < len; i++) {
  26 + newObj.push(cloneDeep(obj[i]));
  27 + }
  28 + return newObj;
  29 + }
  30 + newObj = {};
  31 + for (let key in obj) {
  32 + if (Object.prototype.hasOwnProperty.call(obj, key)) {
  33 + if (typeof obj[key] !== 'object') {
  34 + newObj[key] = obj[key];
  35 + } else {
  36 + newObj[key] = cloneDeep(obj[key]);
  37 + }
  38 + }
  39 + }
  40 + return newObj;
  41 +};
  42 +
  43 +/**
  44 + * 对象深度取值
  45 + * @desctiption 来源于"typy.js"中src/util的getNestedObject函数
  46 + * @param {Object} obj 目标对象
  47 + * @param {String} dotSeparatedKeys 用分隔符".", "[", "]", "'", """隔开的取值路径
  48 + * @example get({ a: { b: { c: ['d'] } } }, 'a.b.c.0')
  49 + */
  50 +export const get = (obj, dotSeparatedKeys) => {
  51 + if (dotSeparatedKeys !== undefined && typeof dotSeparatedKeys !== 'string') return undefined;
  52 + if (typeof obj !== 'undefined' && typeof dotSeparatedKeys === 'string') {
  53 + // eslint-disable-next-line no-useless-escape
  54 + const splitRegex = /[.\[\]'"]/g;
  55 + const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== '');
  56 + obj = pathArr.reduce((o, key) => (o && o[key] !== undefined ? o[key] : undefined), obj);
  57 + }
  58 + return obj;
  59 +};
  60 +
  61 +/**
  62 + * 对象深度赋值
  63 + * @description 改写自get方法
  64 + * @param {Object} obj 目标对像
  65 + * @param {String} dotSeparatedKeys 用分隔符".", "[", "]", "'", """隔开的赋值路径
  66 + * @param {*} value 目标值
  67 + * @example set(obj, 'a.b.c', 'd')
  68 + */
  69 +export const set = (obj, dotSeparatedKeys, value) => {
  70 + // eslint-disable-next-line no-useless-escape
  71 + const splitRegex = /[.\[\]'"]/g;
  72 + const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== '');
  73 + const key = pathArr.pop();
  74 + pathArr.reduce((o, k) => {
  75 + if ((o && o[k] === undefined) || o[k] === null) {
  76 + o[k] = !isNaN(Number(key)) ? [] : {};
  77 + }
  78 + return o[k];
  79 + }, obj)[key] = value;
  80 +};
  81 +
  82 +export default {
  83 + cloneDeep,
  84 + get,
  85 + set,
  86 +};
packages/_utils/param.js 0 → 100644
@@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
  1 +export const stringify = json => {
  2 + const urlEncode = (param, key, encode) => {
  3 + if (param === null) return '';
  4 + let paramStr = '';
  5 + const t = typeof param;
  6 + if (t === 'string' || t === 'number' || t === 'boolean') {
  7 + paramStr = `&${key}=${encode === null || encode ? encodeURIComponent(param) : param}`;
  8 + } else {
  9 + for (const i in param) {
  10 + if (i) {
  11 + if (param[i] !== undefined && param[i] !== '' && !(param[i] && typeof param[i] === 'string' && /^\s+$/.test(param[i]))) {
  12 + const k = key == null ? i : `${key}${param instanceof Array ? `[${i}]` : `.${i}`}`;
  13 + paramStr += urlEncode(param[i], k, encode);
  14 + }
  15 + }
  16 + }
  17 + }
  18 + return paramStr;
  19 + };
  20 + return urlEncode(json).substring(1);
  21 +};
  22 +
  23 +export const parse = url => {
  24 + let obj = {}; // 创建一个Object
  25 + let reg = /[?&][^?&]+=[^?&]+/g; // 正则匹配 ?&开始 =拼接 非?&结束 的参数
  26 + let arr = url.match(reg); // match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
  27 + // arr数组形式 ['?id=12345','&a=b']
  28 + if (arr) {
  29 + arr.forEach(item => {
  30 + /**
  31 + * tempArr数组 ['id','12345']和['a','b']
  32 + * 第一个是key,第二个是value
  33 + * */
  34 + let tempArr = item.substring(1).split('=');
  35 + let key = decodeURIComponent(tempArr[0]);
  36 + let val = decodeURIComponent(tempArr[1]);
  37 + obj[key] = val;
  38 + });
  39 + }
  40 + return obj;
  41 +};
  42 +
  43 +export const urlParam = data => {
  44 + return `${data ? `?${stringify(data)}` : ''}`;
  45 +};
  46 +
  47 +/**
  48 + * 清除首尾斜杠,便于拼接字符串
  49 + * @param {*} str URL
  50 + */
  51 +export const clear = str => {
  52 + return str.replace(/^(\s|\/)+|(\s|\/)+$/g, '');
  53 +};
packages/form/form-render.vue
@@ -36,46 +36,49 @@ @@ -36,46 +36,49 @@
36 </component> 36 </component>
37 </component> 37 </component>
38 <!-- 正常无分组表单项 --> 38 <!-- 正常无分组表单项 -->
39 - <component  
40 - :is="colComponent"  
41 - v-else  
42 - :span="type === 'div' ? undefined : item.span || span"  
43 - :key="index"  
44 - :style="{ width: type === 'div' && item.style && item.style.width.includes('%') ? item.style.width : undefined, paddingRight: '10px' }"  
45 - :class="colClassRender(item, index, colClass)"  
46 - >  
47 - <el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.fullKey" :rules="item.rules" :class="itemClass || 'zee-form__item'">  
48 - <slot v-if="$slots[item.fullKey]" :name="item.fullKey" :value="itemValue(item)" :model="value"></slot>  
49 - <template v-else>  
50 - <!-- 自定义组件 -->  
51 - <dynamic-render  
52 - v-if="typeof item.type === 'function'"  
53 - :render="  
54 - item.type($createElement, {  
55 - model: value,  
56 - config: {  
57 - props: { ...item.props, value: itemValue(item) },  
58 - style: item.style || { maxWidth: '100%' },  
59 - on: {  
60 - ...bindItemEvent(item),  
61 - input: v => onInput({ value: v, item }), 39 + <template v-else>
  40 + <component
  41 + :is="colComponent"
  42 + v-if="bindItemVisible(item, 'visible')"
  43 + v-show="bindItemVisible(item, 'show')"
  44 + :span="type === 'div' ? undefined : item.span || span"
  45 + :key="index"
  46 + :style="{ width: type === 'div' && item.style && item.style.width.includes('%') ? item.style.width : undefined, paddingRight: '10px' }"
  47 + :class="colClassRender(item, index, colClass)"
  48 + >
  49 + <el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.fullKey" :rules="item.rules" :class="itemClass || 'zee-form__item'">
  50 + <slot v-if="$slots[item.fullKey]" :name="item.fullKey" :value="itemValue(item)" :model="value"></slot>
  51 + <template v-else>
  52 + <!-- 自定义组件 -->
  53 + <dynamic-render
  54 + v-if="typeof item.type === 'function'"
  55 + :render="
  56 + item.type($createElement, {
  57 + model: value,
  58 + config: {
  59 + props: { ...item.props, value: itemValue(item) },
  60 + style: item.style || { maxWidth: '100%' },
  61 + on: {
  62 + ...bindItemEvent(item),
  63 + input: v => onInput({ value: v, item }),
  64 + },
62 }, 65 },
63 - },  
64 - })  
65 - "  
66 - ></dynamic-render>  
67 - <component  
68 - v-else  
69 - :is="item.type"  
70 - :value="itemValue(item)"  
71 - @input="v => onInput({ value: v, item })"  
72 - v-on="bindItemEvent(item)"  
73 - v-bind="item.props"  
74 - :style="item.style || { maxWidth: '100%' }"  
75 - ></component>  
76 - </template>  
77 - </el-form-item>  
78 - </component> 66 + })
  67 + "
  68 + ></dynamic-render>
  69 + <component
  70 + v-else
  71 + :is="item.type"
  72 + :value="itemValue(item)"
  73 + @input="v => onInput({ value: v, item })"
  74 + v-on="bindItemEvent(item)"
  75 + v-bind="item.props"
  76 + :style="item.style || { maxWidth: '100%' }"
  77 + ></component>
  78 + </template>
  79 + </el-form-item>
  80 + </component>
  81 + </template>
79 </template> 82 </template>
80 </component> 83 </component>
81 </template> 84 </template>
@@ -190,6 +193,19 @@ export default { @@ -190,6 +193,19 @@ export default {
190 return undefined; 193 return undefined;
191 } 194 }
192 }, 195 },
  196 + /**
  197 + * @description 绑定表单项显示状态
  198 + * @param {Object} item 表单项配置
  199 + * @param {String} type Vue显示类型,可选值:visible、show
  200 + * @returns {Boolean} 显示状态
  201 + */
  202 + bindItemVisible(item, type) {
  203 + const visible = item[type];
  204 + if (typeof visible === 'function') {
  205 + return visible(this.model);
  206 + }
  207 + return item[type] !== false;
  208 + },
193 }, 209 },
194 }; 210 };
195 </script> 211 </script>
packages/form/index.vue
@@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
39 39
40 <script> 40 <script>
41 import FormRender from './form-render'; 41 import FormRender from './form-render';
42 -import { cloneDeep, set } from './util'; 42 +import { cloneDeep, set } from '../_utils';
43 43
44 export default { 44 export default {
45 name: 'Form', 45 name: 'Form',
packages/form/util.js
@@ -1,86 +0,0 @@ @@ -1,86 +0,0 @@
1 -/**  
2 - * 深度克隆对象  
3 - * @param {Object} obj 目标对象  
4 - * @returns {Object} 克隆的新对象  
5 - */  
6 -export const cloneDeep = obj => {  
7 - if (typeof obj !== 'object') {  
8 - return obj;  
9 - }  
10 - if (!obj) {  
11 - return obj;  
12 - }  
13 - if (obj instanceof Date) {  
14 - return new Date(obj);  
15 - }  
16 - if (obj instanceof RegExp) {  
17 - return new RegExp(obj);  
18 - }  
19 - if (obj instanceof Function) {  
20 - return obj;  
21 - }  
22 - let newObj;  
23 - if (obj instanceof Array) {  
24 - newObj = [];  
25 - for (let i = 0, len = obj.length; i < len; i++) {  
26 - newObj.push(cloneDeep(obj[i]));  
27 - }  
28 - return newObj;  
29 - }  
30 - newObj = {};  
31 - for (let key in obj) {  
32 - if (Object.prototype.hasOwnProperty.call(obj, key)) {  
33 - if (typeof obj[key] !== 'object') {  
34 - newObj[key] = obj[key];  
35 - } else {  
36 - newObj[key] = cloneDeep(obj[key]);  
37 - }  
38 - }  
39 - }  
40 - return newObj;  
41 -};  
42 -  
43 -/**  
44 - * 对象深度取值  
45 - * @desctiption 来源于"typy.js"中src/util的getNestedObject函数  
46 - * @param {Object} obj 目标对象  
47 - * @param {String} dotSeparatedKeys 用分隔符".", "[", "]", "'", """隔开的取值路径  
48 - * @example get({ a: { b: { c: ['d'] } } }, 'a.b.c.0')  
49 - */  
50 -export const get = (obj, dotSeparatedKeys) => {  
51 - if (dotSeparatedKeys !== undefined && typeof dotSeparatedKeys !== 'string') return undefined;  
52 - if (typeof obj !== 'undefined' && typeof dotSeparatedKeys === 'string') {  
53 - // eslint-disable-next-line no-useless-escape  
54 - const splitRegex = /[.\[\]'"]/g;  
55 - const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== '');  
56 - obj = pathArr.reduce((o, key) => (o && o[key] !== undefined ? o[key] : undefined), obj);  
57 - }  
58 - return obj;  
59 -};  
60 -  
61 -/**  
62 - * 对象深度赋值  
63 - * @description 改写自get方法  
64 - * @param {Object} obj 目标对像  
65 - * @param {String} dotSeparatedKeys 用分隔符".", "[", "]", "'", """隔开的赋值路径  
66 - * @param {*} value 目标值  
67 - * @example set(obj, 'a.b.c', 'd')  
68 - */  
69 -export const set = (obj, dotSeparatedKeys, value) => {  
70 - // eslint-disable-next-line no-useless-escape  
71 - const splitRegex = /[.\[\]'"]/g;  
72 - const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== '');  
73 - const key = pathArr.pop();  
74 - pathArr.reduce((o, k) => {  
75 - if ((o && o[k] === undefined) || o[k] === null) {  
76 - o[k] = !isNaN(Number(key)) ? [] : {};  
77 - }  
78 - return o[k];  
79 - }, obj)[key] = value;  
80 -};  
81 -  
82 -export default {  
83 - cloneDeep,  
84 - get,  
85 - set,  
86 -};  
packages/index.js
@@ -14,6 +14,15 @@ const install = function(Vue, opts = {}) { @@ -14,6 +14,15 @@ const install = function(Vue, opts = {}) {
14 // 配置组件名称 14 // 配置组件名称
15 const name = prefix + component.name; 15 const name = prefix + component.name;
16 component.name = name; 16 component.name = name;
  17 + if (component.computed) {
  18 + component.computed.zAlias = () => opts.alias || {};
  19 + component.computed.zHttp = () => opts.http;
  20 + } else {
  21 + component.computed = {
  22 + zAlias: () => {},
  23 + zHttp: () => opts.http,
  24 + };
  25 + }
17 // 给每个子组件配置install方法 26 // 给每个子组件配置install方法
18 component.install = function(Vue) { 27 component.install = function(Vue) {
19 Vue.component(name, component); 28 Vue.component(name, component);
packages/scheme/index.scss 0 → 100644
@@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
  1 +.zee-scheme {
  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/scheme/index.vue
1 <style lang="scss"> 1 <style lang="scss">
2 -.zee-scheme {  
3 - &__header {  
4 - margin-bottom: 10px;  
5 - }  
6 - &__filter {  
7 - border: 1px solid #ebeef5;  
8 - padding-top: 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: flex-start;  
32 - .el-button + .el-button {  
33 - margin-left: 0;  
34 - }  
35 - .el-button {  
36 - margin-right: 10px;  
37 - }  
38 - }  
39 - }  
40 - &__dialog-button {  
41 - display: flex;  
42 - align-items: center;  
43 - justify-content: center;  
44 - padding-top: 10px;  
45 - }  
46 - &__footer {  
47 - margin-top: 10px;  
48 - text-align: right;  
49 - display: flex;  
50 - justify-content: space-between;  
51 - align-items: center;  
52 - .selection-info {  
53 - word-break: break-all;  
54 - white-space: nowrap;  
55 - font-size: 12px;  
56 - color: #606266;  
57 - .num {  
58 - color: #000;  
59 - font-weight: bold;  
60 - padding: 0 5px;  
61 - font-size: 16px;  
62 - }  
63 - .el-button {  
64 - margin-left: 5px;  
65 - }  
66 - }  
67 - .el-pagination {  
68 - flex: auto;  
69 - }  
70 - }  
71 -} 2 +@import './index.scss';
72 </style> 3 </style>
73 4
74 <template> 5 <template>
@@ -77,7 +8,7 @@ @@ -77,7 +8,7 @@
77 <slot name="header" :filterModel="filterModel" v-bind="_slotScope"></slot> 8 <slot name="header" :filterModel="filterModel" v-bind="_slotScope"></slot>
78 </div> 9 </div>
79 <div class="zee-scheme__filter"> 10 <div class="zee-scheme__filter">
80 - <z-filter v-if="filter" :value="_filterModel" :list="list | noRulesFilter" :size="size" @input="onFilterInput" @search="search" :loading="loading"></z-filter> 11 + <z-filter v-if="filter" :value="_filterModel" :list="listMap.filter | noRulesFilter" :size="size" @input="onFilterInput" @search="search" :loading="loading"></z-filter>
81 </div> 12 </div>
82 <div v-if="action" class="zee-scheme__action"> 13 <div v-if="action" class="zee-scheme__action">
83 <slot v-if="hadSlot('action')" name="action" v-bind="_slotScope"></slot> 14 <slot v-if="hadSlot('action')" name="action" v-bind="_slotScope"></slot>
@@ -91,7 +22,7 @@ @@ -91,7 +22,7 @@
91 ref="table" 22 ref="table"
92 v-model="tableData" 23 v-model="tableData"
93 v-loading="loading" 24 v-loading="loading"
94 - :list="list" 25 + :list="listMap.table"
95 :tableProps="{ border: true, 'row-key': 'id', ...tableProps }" 26 :tableProps="{ border: true, 'row-key': 'id', ...tableProps }"
96 :size="size" 27 :size="size"
97 @selection-change="onTableSelectionChange" 28 @selection-change="onTableSelectionChange"
@@ -135,7 +66,7 @@ @@ -135,7 +66,7 @@
135 <slot v-if="hadSlot('dialog-title')" slot="title" name="dialog-title" :dialogType="dialogType" v-bind="_slotScope"></slot> 66 <slot v-if="hadSlot('dialog-title')" slot="title" name="dialog-title" :dialogType="dialogType" v-bind="_slotScope"></slot>
136 <template v-if="dialogRender"> 67 <template v-if="dialogRender">
137 <slot v-if="hadSlot(`dialog-${dialogType}`)" :name="`dialog-${dialogType}`" :model="_formModel" v-bind="_slotScope"></slot> 68 <slot v-if="hadSlot(`dialog-${dialogType}`)" :name="`dialog-${dialogType}`" :model="_formModel" v-bind="_slotScope"></slot>
138 - <z-form v-else ref="form" :value="_formModel" :list="list" label-width="80px" :span="12" @input="onFormInput" @validate="onFormValidate"></z-form> 69 + <z-form v-else ref="form" :value="_formModel" :list="listMap.form" label-width="80px" :span="12" @input="onFormInput" @validate="onFormValidate"></z-form>
139 <div class="zee-scheme__dialog-button" v-if="['new', 'edit'].includes(dialogType)"> 70 <div class="zee-scheme__dialog-button" v-if="['new', 'edit'].includes(dialogType)">
140 <el-button :size="size" type="primary" @click="handleConfirm" :loading="submitting">确定</el-button> 71 <el-button :size="size" type="primary" @click="handleConfirm" :loading="submitting">确定</el-button>
141 <el-button :size="size" plain @click="closeDialog">取消</el-button> 72 <el-button :size="size" plain @click="closeDialog">取消</el-button>
@@ -147,7 +78,8 @@ @@ -147,7 +78,8 @@
147 </template> 78 </template>
148 79
149 <script> 80 <script>
150 -import { cloneDeep } from '../form/util'; 81 +import { cloneDeep } from '../_utils';
  82 +import { clear } from '../_utils/param';
151 83
152 export default { 84 export default {
153 name: 'Scheme', 85 name: 'Scheme',
@@ -177,10 +109,17 @@ export default { @@ -177,10 +109,17 @@ export default {
177 filterModel: Object, 109 filterModel: Object,
178 auto: Boolean, 110 auto: Boolean,
179 realSelection: Boolean, 111 realSelection: Boolean,
180 - searchAPI: Function,  
181 - submitAPI: Function,  
182 - getAPI: Function,  
183 - deleteAPI: Function, 112 + /* 模板API */
  113 + url: String, // 请求地址
  114 + http: [Function, Promise], // http库
  115 + /* 自定义API */
  116 + searchApi: Function, // 搜索
  117 + submitApi: Function, // 提交
  118 + addApi: Function, // 新增
  119 + modifyApi: Function, // 修改
  120 + getApi: Function, // 查询详情
  121 + deleteApi: Function, // 删除
  122 + alias: Object, // 别名配置
184 }, 123 },
185 data() { 124 data() {
186 return { 125 return {
@@ -192,11 +131,9 @@ export default { @@ -192,11 +131,9 @@ export default {
192 dialogLoading: false, 131 dialogLoading: false,
193 dialogTitle: '', 132 dialogTitle: '',
194 currentPage: 1, 133 currentPage: 1,
195 - // pageSize: 10,  
196 - pageSize: 5,  
197 - total: 127,  
198 - // pageSizes: [10, 20, 50],  
199 - pageSizes: [1, 2, 5], 134 + pageSize: 10,
  135 + total: 0,
  136 + pageSizes: [10, 20, 50],
200 tableData: [], 137 tableData: [],
201 submitting: false, 138 submitting: false,
202 loading: false, 139 loading: false,
@@ -209,6 +146,7 @@ export default { @@ -209,6 +146,7 @@ export default {
209 } 146 }
210 }, 147 },
211 filters: { 148 filters: {
  149 + // 无规则过滤器,过滤掉筛选条件表单中的必填规则等
212 noRulesFilter(val = []) { 150 noRulesFilter(val = []) {
213 let list = cloneDeep(val); 151 let list = cloneDeep(val);
214 const clearRules = list => { 152 const clearRules = list => {
@@ -225,6 +163,42 @@ export default { @@ -225,6 +163,42 @@ export default {
225 }, 163 },
226 }, 164 },
227 computed: { 165 computed: {
  166 + listMap() {
  167 + // 默认作用域
  168 + const LIST_SPACE = ['filter', 'form', 'table'];
  169 + const array = {
  170 + filter: [], // 筛选
  171 + form: [], // 表单
  172 + table: [], // 表格
  173 + };
  174 + this.list.forEach(item => {
  175 + // 可以在列表中通过include或exclude设置当前配置的作用域
  176 + const { include = LIST_SPACE, exclude = [] } = item;
  177 + // 判断include
  178 + let _inclue = [];
  179 + if (include instanceof String || typeof include === 'string') {
  180 + _inclue = [include];
  181 + } else if (include instanceof Array && typeof include === 'object') {
  182 + _inclue = include;
  183 + }
  184 + // 判断exclude转换为include的情况
  185 + let _exclude_include = [];
  186 + if (exclude instanceof String || typeof exclude === 'string') {
  187 + _exclude_include = LIST_SPACE.filter(item => item !== exclude);
  188 + } else if (exclude instanceof Array && typeof exclude === 'object') {
  189 + _exclude_include = LIST_SPACE.filter(item => !exclude.includes(item));
  190 + }
  191 + // 作用域交集
  192 + const _intersection = _inclue.filter(v => _exclude_include.includes(v));
  193 + // 返回改配置项的作用域
  194 + const _list_space = cloneDeep(_intersection);
  195 + // 将配置项按需分配至各作用域下
  196 + _list_space.forEach(name => {
  197 + array[name].push({ ...item, ...(item[name] || {}) });
  198 + });
  199 + });
  200 + return array;
  201 + },
228 _filterModel() { 202 _filterModel() {
229 return this.filterModel || this.filterForm || {}; 203 return this.filterModel || this.filterForm || {};
230 }, 204 },
@@ -237,15 +211,21 @@ export default { @@ -237,15 +211,21 @@ export default {
237 closeDialog: this.closeDialog, 211 closeDialog: this.closeDialog,
238 }; 212 };
239 }, 213 },
  214 + _alias() {
  215 + const alias = this.alias;
  216 + const zAlias = this.zAlias;
  217 + if (alias && zAlias) {
  218 + return { ...zAlias, ...alias };
  219 + }
  220 + return this.alias || this.zAlias || {};
  221 + },
240 }, 222 },
241 methods: { 223 methods: {
242 // 空Promise 224 // 空Promise
243 emptyPromise() { 225 emptyPromise() {
244 - return new Promise(resolve => {  
245 - resolve();  
246 - }); 226 + return new Promise(resolve => resolve());
247 }, 227 },
248 - // 设置第二行选中 228 + // 设置表格选中行
249 toggleRowSelection() { 229 toggleRowSelection() {
250 this.tableData.forEach(row => { 230 this.tableData.forEach(row => {
251 if (this.selection.find(item => item.id === row.id)) { 231 if (this.selection.find(item => item.id === row.id)) {
@@ -282,6 +262,14 @@ export default { @@ -282,6 +262,14 @@ export default {
282 this.$refs.table && this.$refs.table.clearSelection(); 262 this.$refs.table && this.$refs.table.clearSelection();
283 this.selection = []; 263 this.selection = [];
284 }, 264 },
  265 + // 内置搜索接口
  266 + _searchAPI(params) {
  267 + if (this.url && (this.http || this.zHttp)) {
  268 + const _http = this.http || this.zHttp;
  269 + return _http({ url: `${clear(this.url)}/${this._alias.pageUrl || 'page'}`, params });
  270 + }
  271 + return undefined;
  272 + },
285 // 搜索 273 // 搜索
286 async search() { 274 async search() {
287 this.loading = true; 275 this.loading = true;
@@ -290,18 +278,16 @@ export default { @@ -290,18 +278,16 @@ export default {
290 currentPage: this.currentPage, 278 currentPage: this.currentPage,
291 pageSize: this.pageSize, 279 pageSize: this.pageSize,
292 }; 280 };
293 - const searchAPI = this.searchAPI || this.emptyPromise; 281 + const searchAPI = this.searchApi || this._searchAPI || this.emptyPromise;
294 await searchAPI(params) 282 await searchAPI(params)
295 - .then(response => {  
296 - const { result, totalCount } = response || {};  
297 - this.tableData = result;  
298 - this.total = totalCount;  
299 - this.$nextTick(() => {  
300 - this.toggleRowSelection();  
301 - }); 283 + .then(res => {
  284 + const response = res || {};
  285 + this.tableData = response[this._alias.list || 'list'] || [];
  286 + this.total = response[this._alias.total || 'total'] || 0;
  287 + this.$nextTick(this.toggleRowSelection);
302 }) 288 })
303 .catch(() => { 289 .catch(() => {
304 - this.$message.error('失败'); 290 + this.$message.error('查询失败');
305 }); 291 });
306 this.loading = false; 292 this.loading = false;
307 }, 293 },
@@ -315,19 +301,43 @@ export default { @@ -315,19 +301,43 @@ export default {
315 this.editForm = val || {}; 301 this.editForm = val || {};
316 this.$emit('update:formModel', val || {}); 302 this.$emit('update:formModel', val || {});
317 }, 303 },
  304 + // 内置新增保存接口
  305 + _addAPI(data) {
  306 + if (this.url && (this.http || this.zHttp)) {
  307 + const _http = this.http || this.zHttp;
  308 + return _http({ url: `${clear(this.url)}/${this._alias.addUrl || 'add'}`, method: 'post', data });
  309 + }
  310 + return undefined;
  311 + },
  312 + // 内置修改保存接口
  313 + _modifyAPI(data) {
  314 + if (this.url && (this.http || this.zHttp)) {
  315 + const _http = this.http || this.zHttp;
  316 + return _http({ url: `${clear(this.url)}/${this._alias.modifyUrl || 'modify'}`, method: 'post', data });
  317 + }
  318 + return undefined;
  319 + },
318 // 表单提交且通过校验 320 // 表单提交且通过校验
319 async onFormValidate(valid, model) { 321 async onFormValidate(valid, model) {
320 if (valid) { 322 if (valid) {
321 this.submitting = true; 323 this.submitting = true;
322 - const submitAPI = this.submitAPI || this.emptyPromise;  
323 - await submitAPI(model, { type: this.dialogType }) 324 + let submitAPI = this.submitApi || this.emptyPromise;
  325 + if (this.dialogType === 'new') {
  326 + submitAPI = this.addApi || this.submitApi || this._addAPI || this.emptyPromise;
  327 + } else if (this.dialogType === 'edit') {
  328 + submitAPI = this.modifyApi || this.submitApi || this._modifyAPI || this.emptyPromise;
  329 + }
  330 + submitAPI(model, { type: this.dialogType })
324 .then(() => { 331 .then(() => {
  332 + this.$message.success('保存成功');
325 this.closeDialog(); 333 this.closeDialog();
326 }) 334 })
327 .catch(() => { 335 .catch(() => {
328 - this.$message.error('失败'); 336 + this.$message.error('保存失败');
  337 + })
  338 + .finally(() => {
  339 + this.submitting = false;
329 }); 340 });
330 - this.submitting = false;  
331 } 341 }
332 }, 342 },
333 // 表单按钮确定 343 // 表单按钮确定
@@ -346,32 +356,59 @@ export default { @@ -346,32 +356,59 @@ export default {
346 openNew() { 356 openNew() {
347 this.openDialog('new', '新增'); 357 this.openDialog('new', '新增');
348 }, 358 },
  359 + // 内置查询详情接口
  360 + _getAPI(row) {
  361 + if (this.url && (this.http || this.zHttp)) {
  362 + const _http = this.http || this.zHttp;
  363 + const _getKey = this._alias.getKey || this._alias.primaryKey || 'id';
  364 + const _resultKey = this._alias.result || 'result';
  365 + return _http({ url: `${clear(this.url)}/${this._alias.getUrl || 'queryById'}`, params: { [_getKey]: row[_getKey] } }).then(response => response[_resultKey] || {});
  366 + }
  367 + return undefined;
  368 + },
349 // 打开编辑弹出框 369 // 打开编辑弹出框
350 - async openEdit({ row }) { 370 + openEdit({ row }) {
351 this.dialogLoading = true; 371 this.dialogLoading = true;
352 this.openDialog('edit', '编辑'); 372 this.openDialog('edit', '编辑');
353 - const getRow = () => {  
354 - this.editForm = { ...row };  
355 - };  
356 - const getAPI = this.getAPI || getRow;  
357 - await getAPI(row).then(result => {  
358 - this.editForm = { ...result };  
359 - });  
360 - this.dialogLoading = false; 373 + const getRow = () =>
  374 + new Promise(resolve => {
  375 + resolve({ ...row });
  376 + });
  377 + const getAPI = this.getApi || this._getAPI || getRow;
  378 + getAPI(row)
  379 + .then(result => {
  380 + this.editForm = { ...result };
  381 + })
  382 + .finally(() => {
  383 + this.dialogLoading = false;
  384 + });
  385 + },
  386 + // 内置删除接口
  387 + _deleteAPI(keys) {
  388 + if (this.url && (this.http || this.zHttp)) {
  389 + const _http = this.http || this.zHttp;
  390 + return _http({ url: `${clear(this.url)}/${this._alias.modifyUrl || 'delete'}`, method: 'post', data: keys });
  391 + }
  392 + return undefined;
361 }, 393 },
362 // 删除 394 // 删除
363 - async handleDelete({ row, $index }) {  
364 - const deleteAPI = this.deleteAPI || this.emptyPromise; 395 + handleDelete({ row, $index }) {
365 const loading = this.$loading({ 396 const loading = this.$loading({
366 text: '处理中', 397 text: '处理中',
367 spinner: 'el-icon-loading', 398 spinner: 'el-icon-loading',
368 background: 'rgba(255, 255, 255, 0.5)', 399 background: 'rgba(255, 255, 255, 0.5)',
369 }); 400 });
370 - await deleteAPI(row).then(() => {  
371 - this.tableData.splice($index, 1);  
372 - this.$message.success('删除成功');  
373 - });  
374 - loading.close(); 401 + const deleteAPI = this.deleteApi || this._deleteAPI || this.emptyPromise;
  402 + const _deleteKey = this._alias.deleteKey || this._alias.primaryKey || 'id';
  403 + const keys = [row[_deleteKey]];
  404 + deleteAPI(keys)
  405 + .then(() => {
  406 + this.search();
  407 + this.$message.success('删除成功');
  408 + })
  409 + .finally(() => {
  410 + loading.close();
  411 + });
375 }, 412 },
376 // 打开弹出框 413 // 打开弹出框
377 openDialog(type, title) { 414 openDialog(type, title) {
packages/table/cell-editable.vue
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 </template> 37 </template>
38 38
39 <script> 39 <script>
40 -import { get } from '../form/util'; 40 +import { get } from '../_utils';
41 41
42 export default { 42 export default {
43 name: 'cellEditable', 43 name: 'cellEditable',
packages/table/cell-value-render.js
1 -import { get } from '../form/util'; 1 +import { get } from '../_utils';
2 2
3 export default { 3 export default {
4 props: { row: Object, column: Object, index: [Number, String], item: Object }, 4 props: { row: Object, column: Object, index: [Number, String], item: Object },
packages/table/editable.vue
@@ -70,7 +70,7 @@ @@ -70,7 +70,7 @@
70 </template> 70 </template>
71 71
72 <script> 72 <script>
73 -import { cloneDeep, get, set } from '../form/util'; 73 +import { cloneDeep, get, set } from '../_utils';
74 import CellEditable from './cell-editable'; 74 import CellEditable from './cell-editable';
75 import CellValueRender from './cell-value-render'; 75 import CellValueRender from './cell-value-render';
76 76
packages/table/index.vue
@@ -53,7 +53,7 @@ @@ -53,7 +53,7 @@
53 </template> 53 </template>
54 54
55 <script> 55 <script>
56 -import { cloneDeep, get, set } from '../form/util'; 56 +import { cloneDeep, get, set } from '../_utils';
57 import CellValueRender from './cell-value-render'; 57 import CellValueRender from './cell-value-render';
58 58
59 export default { 59 export default {