Commit 441f73b946ac046545f123693bf1215f48a1f5a6

Authored by 刘汉宸
1 parent 4f38f3a4

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

examples/main.js
... ... @@ -2,6 +2,7 @@ import Vue from 'vue';
2 2 import App from '@/App.vue';
3 3 import router from '@/router';
4 4 import store from '@/store';
  5 +import request from '@/utils/request';
5 6 import ElementUI from 'element-ui';
6 7 import Zee from '../packages';
7 8 import NProgress from 'nprogress';
... ... @@ -18,8 +19,12 @@ Vue.component('code-snippet', CodeSnippet);
18 19 // 注册饿了么UI
19 20 Vue.use(ElementUI);
20 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 28 Vue.config.productionTip = false;
24 29  
25 30 new Vue({
... ...
examples/utils/cache.js 0 → 100644
... ... @@ -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 @@
  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 @@
  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 10  
11 11 ```html
12 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 14 <el-table-column type="selection" align="center" width="40"></el-table-column>
15 15 <template #header>
16 16 <el-tabs v-model="activeName">
... ... @@ -63,7 +63,7 @@ export default {
63 63 { id: '8', name: '卢纳', age: 55 },
64 64 ]
65 65 resolve({
66   - result: params.currentPage === 1 ? list.slice(0, 5) : list.slice(5),
  66 + result: list,
67 67 totalCount: list.length
68 68 });
69 69 }, 1500);
... ... @@ -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 137 ## API
105 138  
106 139 ## Attribute 属性
... ...
packages/_utils/index.js 0 → 100644
... ... @@ -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 @@
  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 36 </component>
37 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 82 </template>
80 83 </component>
81 84 </template>
... ... @@ -190,6 +193,19 @@ export default {
190 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 211 </script>
... ...
packages/form/index.vue
... ... @@ -39,7 +39,7 @@
39 39  
40 40 <script>
41 41 import FormRender from './form-render';
42   -import { cloneDeep, set } from './util';
  42 +import { cloneDeep, set } from '../_utils';
43 43  
44 44 export default {
45 45 name: 'Form',
... ...
packages/form/util.js
... ... @@ -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 14 // 配置组件名称
15 15 const name = prefix + component.name;
16 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 26 // 给每个子组件配置install方法
18 27 component.install = function(Vue) {
19 28 Vue.component(name, component);
... ...
packages/scheme/index.scss 0 → 100644
... ... @@ -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 73 \ No newline at end of file
... ...
packages/scheme/index.vue
1 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 3 </style>
73 4  
74 5 <template>
... ... @@ -77,7 +8,7 @@
77 8 <slot name="header" :filterModel="filterModel" v-bind="_slotScope"></slot>
78 9 </div>
79 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 12 </div>
82 13 <div v-if="action" class="zee-scheme__action">
83 14 <slot v-if="hadSlot('action')" name="action" v-bind="_slotScope"></slot>
... ... @@ -91,7 +22,7 @@
91 22 ref="table"
92 23 v-model="tableData"
93 24 v-loading="loading"
94   - :list="list"
  25 + :list="listMap.table"
95 26 :tableProps="{ border: true, 'row-key': 'id', ...tableProps }"
96 27 :size="size"
97 28 @selection-change="onTableSelectionChange"
... ... @@ -135,7 +66,7 @@
135 66 <slot v-if="hadSlot('dialog-title')" slot="title" name="dialog-title" :dialogType="dialogType" v-bind="_slotScope"></slot>
136 67 <template v-if="dialogRender">
137 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 70 <div class="zee-scheme__dialog-button" v-if="['new', 'edit'].includes(dialogType)">
140 71 <el-button :size="size" type="primary" @click="handleConfirm" :loading="submitting">确定</el-button>
141 72 <el-button :size="size" plain @click="closeDialog">取消</el-button>
... ... @@ -147,7 +78,8 @@
147 78 </template>
148 79  
149 80 <script>
150   -import { cloneDeep } from '../form/util';
  81 +import { cloneDeep } from '../_utils';
  82 +import { clear } from '../_utils/param';
151 83  
152 84 export default {
153 85 name: 'Scheme',
... ... @@ -177,10 +109,17 @@ export default {
177 109 filterModel: Object,
178 110 auto: Boolean,
179 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 124 data() {
186 125 return {
... ... @@ -192,11 +131,9 @@ export default {
192 131 dialogLoading: false,
193 132 dialogTitle: '',
194 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 137 tableData: [],
201 138 submitting: false,
202 139 loading: false,
... ... @@ -209,6 +146,7 @@ export default {
209 146 }
210 147 },
211 148 filters: {
  149 + // 无规则过滤器,过滤掉筛选条件表单中的必填规则等
212 150 noRulesFilter(val = []) {
213 151 let list = cloneDeep(val);
214 152 const clearRules = list => {
... ... @@ -225,6 +163,42 @@ export default {
225 163 },
226 164 },
227 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 202 _filterModel() {
229 203 return this.filterModel || this.filterForm || {};
230 204 },
... ... @@ -237,15 +211,21 @@ export default {
237 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 223 methods: {
242 224 // 空Promise
243 225 emptyPromise() {
244   - return new Promise(resolve => {
245   - resolve();
246   - });
  226 + return new Promise(resolve => resolve());
247 227 },
248   - // 设置第二行选中
  228 + // 设置表格选中行
249 229 toggleRowSelection() {
250 230 this.tableData.forEach(row => {
251 231 if (this.selection.find(item => item.id === row.id)) {
... ... @@ -282,6 +262,14 @@ export default {
282 262 this.$refs.table && this.$refs.table.clearSelection();
283 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 274 async search() {
287 275 this.loading = true;
... ... @@ -290,18 +278,16 @@ export default {
290 278 currentPage: this.currentPage,
291 279 pageSize: this.pageSize,
292 280 };
293   - const searchAPI = this.searchAPI || this.emptyPromise;
  281 + const searchAPI = this.searchApi || this._searchAPI || this.emptyPromise;
294 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 289 .catch(() => {
304   - this.$message.error('失败');
  290 + this.$message.error('查询失败');
305 291 });
306 292 this.loading = false;
307 293 },
... ... @@ -315,19 +301,43 @@ export default {
315 301 this.editForm = val || {};
316 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 321 async onFormValidate(valid, model) {
320 322 if (valid) {
321 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 331 .then(() => {
  332 + this.$message.success('保存成功');
325 333 this.closeDialog();
326 334 })
327 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 356 openNew() {
347 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 371 this.dialogLoading = true;
352 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 396 const loading = this.$loading({
366 397 text: '处理中',
367 398 spinner: 'el-icon-loading',
368 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 414 openDialog(type, title) {
... ...
packages/table/cell-editable.vue
... ... @@ -37,7 +37,7 @@
37 37 </template>
38 38  
39 39 <script>
40   -import { get } from '../form/util';
  40 +import { get } from '../_utils';
41 41  
42 42 export default {
43 43 name: 'cellEditable',
... ...
packages/table/cell-value-render.js
1   -import { get } from '../form/util';
  1 +import { get } from '../_utils';
2 2  
3 3 export default {
4 4 props: { row: Object, column: Object, index: [Number, String], item: Object },
... ...
packages/table/editable.vue
... ... @@ -70,7 +70,7 @@
70 70 </template>
71 71  
72 72 <script>
73   -import { cloneDeep, get, set } from '../form/util';
  73 +import { cloneDeep, get, set } from '../_utils';
74 74 import CellEditable from './cell-editable';
75 75 import CellValueRender from './cell-value-render';
76 76  
... ...
packages/table/index.vue
... ... @@ -53,7 +53,7 @@
53 53 </template>
54 54  
55 55 <script>
56   -import { cloneDeep, get, set } from '../form/util';
  56 +import { cloneDeep, get, set } from '../_utils';
57 57 import CellValueRender from './cell-value-render';
58 58  
59 59 export default {
... ...