Browse Source

add demo 添加demo (事件未添加全)

xiaoyang 5 years ago
parent
commit
fa73805b8f
56 changed files with 5381 additions and 165 deletions
  1. 1 1
      package.json
  2. 193 0
      src/common/api/index.js
  3. 19 0
      src/common/bootstrap.js
  4. 29 0
      src/common/directives/index.js
  5. 291 0
      src/common/filters/common.js
  6. 8 0
      src/common/filters/index.js
  7. 32 0
      src/common/filters/status.js
  8. 24 0
      src/common/router/defaultConfig.js
  9. 13 0
      src/common/router/i18n.js
  10. 37 0
      src/common/router/index.js
  11. 221 0
      src/common/router/routerUtil.js
  12. 115 0
      src/common/theme/colors.js
  13. 94 0
      src/common/theme/theme-color-replacer-extend.js
  14. 72 0
      src/common/theme/themeUtil.js
  15. 175 0
      src/common/utils/canvas.js
  16. 160 0
      src/common/utils/dom.js
  17. 163 0
      src/common/utils/image.js
  18. 556 0
      src/common/utils/index.js
  19. 20 0
      src/common/utils/others/device.js
  20. 80 0
      src/common/utils/others/i18n.js
  21. 101 0
      src/common/utils/weChat/qywx.js
  22. 147 0
      src/common/utils/weChat/wechatConfig.js
  23. 57 0
      src/common/utils/webViewBridge/base/WebViewBridge.js
  24. 26 0
      src/common/utils/webViewBridge/index.js
  25. 14 0
      src/common/utils/webViewBridge/plugins/DJSettingPlugin.js
  26. 23 0
      src/common/utils/webViewBridge/plugins/DevicePlugin.js
  27. 27 0
      src/common/utils/webViewBridge/plugins/FacePlugin.js
  28. 21 0
      src/common/utils/webViewBridge/plugins/ImagePickerPlugin.js
  29. 18 0
      src/common/utils/webViewBridge/plugins/JpushPlugin.js
  30. 27 0
      src/common/utils/webViewBridge/plugins/LocationPlugin.js
  31. 24 0
      src/common/utils/webViewBridge/plugins/OCRPlugin.js
  32. 42 0
      src/common/utils/webViewBridge/plugins/PDFPlugin.js
  33. 25 0
      src/common/utils/webViewBridge/plugins/SharePlugin.js
  34. 157 0
      src/common/validate/index.js
  35. 344 0
      src/common/validate/validate.js
  36. 6 0
      src/components/DrawFlow/index.js
  37. 354 0
      src/components/DrawFlow/src/DrawFlow.vue
  38. 128 0
      src/components/DrawFlow/src/components/AddNodeBtn.vue
  39. 96 0
      src/components/DrawFlow/src/components/DrawAddSelectBox/DrawAddBox.vue
  40. 13 0
      src/components/DrawFlow/src/components/DrawAddSelectBox/NextNode.js
  41. 53 0
      src/components/DrawFlow/src/components/DrawAddSelectBox/addBox.less
  42. 123 0
      src/components/DrawFlow/src/components/DrawCol/FactoryCol.js
  43. 254 0
      src/components/DrawFlow/src/components/DrawCol/layout.less
  44. 46 0
      src/components/DrawFlow/src/components/DrawRow/FactoryRow.js
  45. 195 0
      src/components/DrawFlow/src/components/DrawRow/FlowNode.vue
  46. 19 0
      src/components/DrawFlow/src/components/DrawRow/row.less
  47. 58 0
      src/components/DrawFlow/src/components/NodeConfigFactory/NodeFactory.js
  48. 19 0
      src/components/DrawFlow/src/components/factory.js
  49. 30 0
      src/components/DrawFlow/src/mixin/factory.vue
  50. 203 0
      src/components/DrawFlow/src/utils/index.js
  51. 0 130
      src/components/HelloWorld.vue
  52. 3 1
      src/main.js
  53. 48 7
      src/views/Home.vue
  54. 31 0
      src/views/getNode.js
  55. 157 0
      vue.config.js
  56. 189 26
      yarn.lock

+ 1 - 1
package.json

@@ -25,7 +25,7 @@
     "eslint": "^6.7.2",
     "eslint-plugin-prettier": "^3.1.3",
     "eslint-plugin-vue": "^6.2.2",
-    "less": "^3.0.4",
+    "less": "^2.7.2",
     "less-loader": "^5.0.0",
     "prettier": "^1.19.1",
     "vue-template-compiler": "^2.6.11"

+ 193 - 0
src/common/api/index.js

@@ -0,0 +1,193 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec 全局API插件
+ */
+import axios from "axios";
+import Cookie from "js-cookie";
+import message from "ant-design-vue/es/message";
+
+// 跨域认证信息 header 名
+const TOKEN = "UTOKEN";
+
+axios.defaults.timeout = 20000;
+
+// 自定义配置新建一个axios实例
+const service = axios.create({});
+/**
+ * 添加请求拦截器
+ */
+service.interceptors.request.use(config => {
+  const token = getAuthorization(TOKEN);
+  // 如果 token 存在
+  // 让每个请求携带自定义 token 请根据实际情况自行修改
+  if (token) {
+    config.headers[TOKEN] = token;
+  }
+  return config;
+});
+/**
+ * 添加响应拦截器
+ */
+// 重新登录
+const logout = () => {
+  // 从 localstorage 获取 token
+  const token = getAuthorization(TOKEN);
+  if (token) {
+    removeAuthorization();
+    window.location.reload();
+  }
+};
+service.interceptors.response.use(
+  response => {
+    if (
+      response.headers["content-type"] ===
+      "application/vnd.ms-excel;charset=utf-8"
+    ) {
+      return Promise.resolve(response);
+    }
+    if (response.data.code !== 0) {
+      if (response.data.code === 402) {
+        logout();
+        message.warning("登录失效请重新登录");
+        return Promise.reject(response);
+      }
+      message.warning(response.data.msg);
+      return Promise.reject(response);
+    } else {
+      return Promise.resolve(response);
+    }
+  },
+  error => {
+    if (!error.response) {
+      message.warning("抱歉,服务器走丢了!");
+      return Promise.reject(error);
+    }
+    switch (error.response.status) {
+      case 400:
+        error.message = "请求错误!";
+        break;
+      case 401:
+        error.message = "登录超时!";
+        logout();
+        break;
+      case 402:
+        error.message = "认证失败!";
+        logout();
+        break;
+      case 403:
+        error.message = "拒绝访问!";
+        break;
+      case 404:
+        error.message = "请求地址出错!";
+        break;
+      case 408:
+        error.message = "请求超时!";
+        break;
+      case 500:
+        error.message = "服务器内部错误!";
+        break;
+      case 501:
+        error.message = "服务未实现!";
+        break;
+      case 502:
+        error.message = "网关错误!";
+        break;
+      case 503:
+        error.message = "服务不可用!";
+        break;
+      case 504:
+        error.message = "网关超时!";
+        break;
+      case 505:
+        error.message = "HTTP版本不受支持!";
+        break;
+      default:
+    }
+    message.warning("error.message!");
+    return Promise.reject(error);
+  }
+);
+
+/**
+ * 设置认证信息
+ * @param auth {Object}
+ * @param authType {AUTH_TYPE} 认证类型,默认:{AUTH_TYPE.BEARER}
+ */
+export function setAuthorization(auth) {
+  Cookie.set(TOKEN, auth.token, {
+    expires: auth.expireAt
+  });
+}
+
+/**
+ * 设置认证信息
+ * @param auth {Object}
+ * @param authType {AUTH_TYPE} 认证类型,默认:{AUTH_TYPE.BEARER}
+ */
+export function getAuthorization() {
+  return Cookie.get(TOKEN);
+}
+/**
+ * 移出认证信息
+ * @param authType {AUTH_TYPE} 认证类型
+ */
+export function removeAuthorization() {
+  Cookie.remove(TOKEN);
+}
+
+/**
+ * 检查认证信息
+ * @param authType
+ * @returns {boolean}
+ */
+export function checkAuthorization() {
+  if (Cookie.get(TOKEN)) {
+    return true;
+  }
+}
+
+/**
+ * API请求封装
+ * @param  {String} url api请求url
+ * @param  {String} method 请求方法,默认为post
+ * @param  {String} middle
+ * @param  {Object} params 入参
+ * @param  {String} isPost 请求方法
+ * @return 返回一个经加工的axios实例
+ */
+export default function({
+  url,
+  method,
+  middle,
+  params = {},
+  isPost,
+  headers = {},
+  responseType
+}) {
+  let _config = {
+    url: url,
+    method: method,
+    headers,
+    responseType
+  };
+  let _params = params
+    ? middle
+      ? middle.request(JSON.parse(JSON.stringify(params)))
+      : params
+    : "";
+  if (method.toLowerCase() === "post" || isPost) {
+    _config.data = _params;
+  } else {
+    _config.params = _params;
+  }
+  return new Promise((resolve, reject) => {
+    return service(_config)
+      .then(response => {
+        resolve(middle ? middle.response(response.data) : response.data);
+      })
+      .catch(error => {
+        reject(error);
+      });
+  });
+}

+ 19 - 0
src/common/bootstrap.js

@@ -0,0 +1,19 @@
+import { loadRoutes, loginGuard, authorityGuard } from "./router/routerUtil";
+
+/**
+ * 启动引导方法
+ * 应用启动时需要执行的操作放在这里
+ * @param router 应用的路由实例
+ * @param store 应用的 vuex.store 实例
+ * @param i18n 应用的 vue-i18n 实例
+ */
+function bootstrap({ router, store, i18n }) {
+  // 加载路由
+  loadRoutes({ router, store, i18n });
+  // 添加路由守卫
+  loginGuard(router);
+  // 添加权限守卫
+  authorityGuard(router, store);
+}
+
+export default bootstrap;

+ 29 - 0
src/common/directives/index.js

@@ -0,0 +1,29 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec 全局指令,该页面指令会在全局引入vue
+ */
+// import store from "@/data/store";
+export default {
+  // 是否有按钮权限判定,自定义按钮权限指令
+  btnPermission: {
+    /**
+     * @param  el 指令所绑定的元素,可以用来直接操作DOM
+     * @param  {Object} binding 对象
+     * @param  vnode Vue编译生成的虚拟节点
+     */
+    inserted() {
+      // 设置vnode key 强制更新指令
+      // vnode.key = Math.random()
+      //   .toString()
+      //   .replace("0.", "_");
+      // if (
+      //   !store.getters.permission.btnPermissions.includes(binding.expression)
+      // ) {
+      //   el.style.display = "none";
+      // } else {
+      //   el.style.display = "block";
+      // }
+    }
+  }
+};

+ 291 - 0
src/common/filters/common.js

@@ -0,0 +1,291 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec 核心字符过滤转换方法
+ */
+
+import moment from "moment";
+import * as utils from "@/common/utils";
+/**
+ * 格式化时间
+ * @param {Date|String} date 格式化日期对象
+ * @param {String} cFormat 格式化日期的格式YYYY-MM-DD
+ * @return {String} 格式化后的日期
+ */
+export const parseDate = function(val, { format = "Y-MM-DD" } = {}) {
+  if (!val) return "";
+  return moment(val).format(format);
+};
+
+/**
+ * 过滤空
+ * @param  {undefined||null||String||Number} val 入参
+ * @param  {undefined} str
+ * @return {String||Number} 入参如果为undefined||null||""则返回"-",否则返回val
+ */
+export function empty(val, str) {
+  if (utils.isEmpty(val)) {
+    return str || "-";
+  } else {
+    return val;
+  }
+}
+
+/**
+ * 转换金额
+ * @param  {String||Number} val 入参为需要转换的金额
+ * @return {String} _amount 如果val为空则返回"-",否则返回转换之后的金额
+ */
+export function amount(val) {
+  val = Number(val);
+  if (isNaN(val)) {
+    return "-";
+  }
+  let _amount;
+  if (val >= 10000 && val < 100000000) {
+    _amount = utils.formatFloat(val / 10000) + " 万元";
+  } else if (val >= 100000000) {
+    _amount = utils.formatFloat(val / 100000000) + " 亿元";
+  } else {
+    _amount = utils.formatFloat(val) + "元";
+  }
+  return _amount;
+}
+
+/**
+ * 转换数字
+ * @param {Number|String} val 目标数据
+ * @param {Number} digit 保留小数
+ * @param {Boolean} isNotRound 是否四舍五入
+ * @param {Array} [individual tenThousand, Billion] 分别对应转换单位中文描述,默认“万”/“亿”
+ * @param {Boolean} retrunArray 如果为true,则返回一个数组,分别为值和单位
+ */
+export function conversionUnit({
+  val,
+  digit = 2,
+  isNotRound = false,
+  unit = ["", " 万", " 亿"],
+  retrunArray = false
+}) {
+  let _n;
+  val = Number(val);
+  if (!retrunArray) {
+    if (val >= 10000 && val < 100000000) {
+      _n = utils.formatFloat(val / 10000, digit, isNotRound) + unit[1];
+    } else if (val >= 100000000) {
+      _n = utils.formatFloat(val / 100000000, digit, isNotRound) + unit[2];
+    } else {
+      _n = utils.formatFloat(val, digit, isNotRound) + unit[0];
+    }
+    return _n;
+  } else {
+    if (val >= 10000 && val < 100000000) {
+      return [utils.formatFloat(val / 10000, digit, isNotRound), unit[1]];
+    } else if (val >= 100000000) {
+      return [utils.formatFloat(val / 100000000, digit, isNotRound), unit[2]];
+    } else {
+      return [utils.formatFloat(val, digit, isNotRound), unit[0]];
+    }
+  }
+}
+
+/**
+ * 根据出生日期计算年龄
+ * @param {Date|String} birthday
+ */
+export function getAge(birthday) {
+  let age;
+  if (!birthday) return 0;
+  birthday =
+    birthday.indexOf("-") >= 0
+      ? birthday
+      : moment(birthday).format("YYYY-MM-DD");
+  const birthdayArr = birthday.split("-");
+  const birthdayYear = birthdayArr[0];
+  const birthdayMonth = parseInt(birthdayArr[1]);
+  const birthdayDay = parseInt(birthdayArr[2]);
+  const today = new Date();
+  const nowYear = today.getFullYear();
+  const nowMonth = today.getMonth() + 1;
+  const nowDay = today.getDate();
+  if (nowYear == birthdayYear) {
+    age = 0; // 同年 则为0岁
+  } else {
+    const ageDiff = nowYear - birthdayYear; // 年之差
+    if (ageDiff > 0) {
+      if (nowMonth === birthdayMonth) {
+        const dayDiff = nowDay - birthdayDay; // 日之差
+        if (dayDiff < 0) {
+          age = ageDiff - 1;
+        } else if (dayDiff > 0) {
+          age = ageDiff;
+        } else {
+          age = ageDiff;
+        }
+      } else {
+        const monthDiff = nowMonth - birthdayMonth; // 月之差
+        if (monthDiff < 0) {
+          age = ageDiff - 1;
+        } else if (monthDiff > 0) {
+          age = ageDiff;
+        } else {
+          age = ageDiff;
+        }
+      }
+    } else {
+      age = "未知"; // 0岁
+    }
+  }
+  return age; // 返回周岁年龄
+}
+
+/**
+ * 字符串千分位格式化
+ * @export
+ * @param {*} string 要格式化的字符串
+ * @returns
+ */
+export function commafy(string) {
+  return string
+    ? string.toString().replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, "$1,")
+    : "";
+}
+/**
+ * 千分位字符串还原
+ * @export
+ * @param {*} num
+ * @returns
+ */
+export function delcommafy(num) {
+  //去除千分位中的‘,’
+  if (num && num != "undefined" && num != "null") {
+    let numS = num;
+    numS = numS.toString();
+    numS = numS.replace(/,/gi, "");
+    return numS;
+  } else {
+    return num;
+  }
+}
+
+/**
+ * 数字转大写
+ * @export
+ * @param {*} str
+ * @returns
+ */
+export function numberChinese(str) {
+  try {
+    let num = parseFloat(str);
+    let strOutput = "",
+      strUnit = "仟佰拾亿仟佰拾万仟佰拾元角分";
+    num += "00";
+    let intPos = num.indexOf(".");
+    if (intPos >= 0) {
+      num = num.substring(0, intPos) + num.substr(intPos + 1, 2);
+    }
+    strUnit = strUnit.substr(strUnit.length - num.length);
+    for (let i = 0; i < num.length; i++) {
+      strOutput +=
+        "零壹贰叁肆伍陆柒捌玖".substr(num.substr(i, 1), 1) +
+        strUnit.substr(i, 1);
+    }
+    return strOutput
+      .replace(/零角零分$/, "整")
+      .replace(/零[仟佰拾]/g, "零")
+      .replace(/零{2,}/g, "零")
+      .replace(/零([亿|万])/g, "$1")
+      .replace(/零+元/, "元")
+      .replace(/亿零{0,3}万/, "亿")
+      .replace(/^元/, "零元");
+  } catch (error) {
+    return str;
+  }
+}
+
+/**
+ * 手机号脱敏
+ * @param number 手机号
+ * */
+export const cellphoneNumberDesensitization = number => {
+  if (!number) return "";
+  const pat = /(\d{3})\d*(\d{4})/;
+  return number.replace(pat, "$1****$2");
+};
+
+/**
+ * 身份证号脱敏
+ * @param number 身份证号
+ * */
+export const idNumberDesensitization = number => {
+  if (!number) return "";
+  return number.replace(/^(.{6})(?:\d+)(.{2})(?:\d)$/, "$1********$2**");
+};
+
+/**
+ * 根据时间换算是否是今天明天后天
+ * @param {Date|String} date 日期
+ * @param {String} format 日期格式
+ */
+export const isToday = (date, format) => {
+  if (typeof date === "string") {
+    date = date.replace(/-/g, "/");
+  }
+  date = new Date(date);
+  let ptime = date.getTime();
+  const oneDay = 1000 * 60 * 60 * 24;
+  const year = new Date().getFullYear();
+  const month = new Date().getMonth() + 1;
+  const day = new Date().getDate();
+  const today = `${year}/${month}/${day}`;
+  const todayTime = new Date(today).getTime();
+  if (ptime >= todayTime) {
+    return "今天";
+  } else if ((todayTime - ptime) / oneDay <= 1) {
+    return "昨天";
+  } else {
+    return parseDate(date, { format: format || "YYYY年MM月DD" });
+  }
+};
+
+/**
+ * 换算几分钟前,刚刚
+ * @param {Date|String} date 日期
+ * @param {String} format 日期格式
+ * */
+
+export const timeago = (date, format) => {
+  if (typeof date === "string") {
+    date = date.replace(/-/g, "/");
+  }
+  if (isToday(date) === "今天") {
+    let minute = 1000 * 60;
+    let hour = minute * 60;
+    let time1 = new Date().getTime(); //当前的时间戳
+    let time2 = new Date(date).getTime(); //指定时间的时间戳
+    let time = time1 - time2;
+    let result = null;
+    if (time < 0) {
+      result = parseDate(date, { format: format || "HH:mm:ss" });
+    } else if (time / hour >= 1) {
+      result = parseInt(time / hour) + "小时前";
+    } else if (time / minute >= 1) {
+      result = parseInt(time / minute) + "分钟前";
+    } else {
+      result = "刚刚";
+    }
+    return result;
+  } else {
+    return parseDate(date, { format: format || "HH:mm:ss" });
+  }
+};
+
+/**
+ * 过滤HTML
+ * @param {String} val html片段
+ */
+export function html2Text(val) {
+  const div = document.createElement("div");
+  div.innerHTML = val;
+  return div.textContent || div.innerText;
+}

+ 8 - 0
src/common/filters/index.js

@@ -0,0 +1,8 @@
+// 常用方法
+import * as commons from "./common";
+// 状态
+import * as status from "./status";
+export default {
+  ...commons,
+  ...status
+};

+ 32 - 0
src/common/filters/status.js

@@ -0,0 +1,32 @@
+/**
+ * 状态码配置
+ */
+export const STATUS = {
+  // 某某状态
+  someStatus: [
+    {
+      name: "生效中",
+      value: 1
+    },
+    {
+      name: "未生效",
+      value: 2
+    },
+    {
+      name: "已失效",
+      value: 0
+    }
+  ]
+};
+/**
+ * 全局状态码过滤
+ * @param  {Number||String} val 状态码
+ * @return {String} 返回code对应的value值
+ */
+export function statusFilters(val, type) {
+  if (STATUS[type]) {
+    return STATUS[type].filter(item => item.value === val)[0].name;
+  } else {
+    return "";
+  }
+}

+ 24 - 0
src/common/router/defaultConfig.js

@@ -0,0 +1,24 @@
+/**
+ * @author 明浩
+ * @time 2020-8-16
+ * @dec 全局默认路由
+ * */
+const routes = [
+  {
+    path: "/login",
+    name: "登录页",
+    component: () => import("@/pages/common/views/Login")
+  },
+  {
+    path: "*",
+    name: "404",
+    component: () => import("@/pages/common/views/Exception/404")
+  },
+  {
+    path: "/403",
+    name: "403",
+    component: () => import("@/pages/common/views/Exception/403")
+  }
+];
+
+export default routes;

+ 13 - 0
src/common/router/i18n.js

@@ -0,0 +1,13 @@
+module.exports = {
+  messages: {
+    CN: {
+      home: { name: "首页" }
+    },
+    US: {
+      home: { name: "home" }
+    },
+    HK: {
+      home: { name: "首頁" }
+    }
+  }
+};

+ 37 - 0
src/common/router/index.js

@@ -0,0 +1,37 @@
+import Vue from "vue";
+import Router from "vue-router";
+import { formatAuthority } from "./routerUtil";
+import { store } from "@/data/store";
+
+Vue.use(Router);
+
+// 不需要登录拦截的路由配置
+const loginIgnore = {
+  names: ["404", "403"], //根据路由名称匹配
+  paths: ["/login"], //根据路由fullPath匹配
+  /**
+   * 判断路由是否包含在该配置中
+   * @param route vue-router 的 route 对象
+   * @returns {boolean}
+   */
+  includes(route) {
+    return this.names.includes(route.name) || this.paths.includes(route.path);
+  }
+};
+
+/**
+ * 初始化路由实例
+ * @param isAsync 是否异步路由模式
+ * @returns {VueRouter}
+ */
+function initRouter(config) {
+  store.commit("account/setRouterMap", config);
+  const defaultConfig = require("./defaultConfig").default;
+  const routes = defaultConfig.concat(config);
+  const permissions = store.getters["account/permissions"];
+  formatAuthority(routes, permissions);
+  return new Router({
+    routes
+  });
+}
+export { loginIgnore, initRouter };

+ 221 - 0
src/common/router/routerUtil.js

@@ -0,0 +1,221 @@
+import { mergeI18nFromRoutes } from "@/common/utils/others/i18n";
+import Router from "vue-router";
+import { loginIgnore } from "./index.js";
+import { checkAuthorization } from "@/common/api";
+
+/**
+ * 根据 路由配置 和 路由组件注册 解析路由
+ * @param routesConfig 路由配置
+ */
+function parseRoutes(routesConfig, routerMap = []) {
+  function findRouterCfg(routesConfig, router) {
+    let _r = {};
+    routesConfig.forEach(item => {
+      let _authority =
+        router.meta &&
+        router.meta.authority &&
+        router.meta.authority.permission;
+      if (item.permissionString === _authority) {
+        _r = item;
+        return _r;
+      } else if (item.children && item.children.length) {
+        _r = findRouterCfg(item.children, router);
+      }
+    });
+    return _r;
+  }
+  routerMap = routerMap.map(router => {
+    let routeCfg = findRouterCfg(routesConfig, router);
+    const route = {
+      path: routeCfg.url || router.path,
+      name: routeCfg.name || router.name,
+      component: router.component,
+      redirect: router.redirect,
+      meta: {
+        authority:
+          routeCfg.permissionString ||
+          (router.meta &&
+            router.meta.authority &&
+            router.meta.authority.permission) ||
+          "*",
+        icon: routeCfg.icon || router.meta.icon,
+        sortIndex: routeCfg.sortIndex || 9990
+      }
+    };
+    if (routeCfg.invisible || router.invisible) {
+      route.meta.invisible = true;
+    }
+    if (router.children && router.children.length > 0) {
+      route.children = parseRoutes(routesConfig, router.children);
+    }
+    return route;
+  });
+  return routerMap;
+}
+
+/**
+ * 加载路由
+ * @param router 应用路由实例
+ * @param store 应用的 vuex.store 实例
+ * @param i18n 应用的 vue-i18n 实例
+ * @param routesConfig 路由配置
+ */
+function loadRoutes({ router, store, i18n }, routesConfig) {
+  // 如果 routesConfig 有值,则更新到本地,否则从本地获取
+  if (routesConfig) {
+    store.commit("account/setRoutesConfig", routesConfig);
+  } else {
+    routesConfig = store.getters["account/routesConfig"];
+  }
+  if (routesConfig && routesConfig.length > 0) {
+    const routes = parseRoutes(routesConfig, store.state.account["routerMap"]);
+    const permissions = store.getters["account/permissions"];
+    formatAuthority(routes, permissions);
+    const finalRoutes = mergeRoutes(router.options.routes, routes);
+    router.options = { ...router.options, routes: finalRoutes };
+    router.matcher = new Router({ ...router.options, routes: [] }).matcher;
+    router.addRoutes(finalRoutes);
+  }
+  // 提取路由国际化数据
+  mergeI18nFromRoutes(i18n, router.options.routes);
+  // 初始化Admin后台菜单数据
+  const rootRoute = router.options.routes.find(item => item.path === "/");
+  const menuRoutes = rootRoute && rootRoute.children;
+  if (menuRoutes) {
+    store.commit("setting/setMenuData", menuRoutes);
+  }
+}
+
+/**
+ * 合并路由
+ * @param target {Route[]}
+ * @param source {Route[]}
+ * @returns {Route[]}
+ */
+function mergeRoutes(target, source) {
+  const routesMap = {};
+  target.forEach(item => (routesMap[item.path] = item));
+  source.forEach(item => (routesMap[item.path] = item));
+  return Object.values(routesMap);
+}
+
+/**
+ * 登录守卫
+ * @param router 应用路由实例
+ */
+function loginGuard(router) {
+  router.beforeEach((to, from, next) => {
+    if (!loginIgnore.includes(to) && !checkAuthorization()) {
+      next({ path: "/login" });
+    } else {
+      next();
+    }
+  });
+}
+
+/**
+ * 权限守卫
+ * @param router 应用路由实例
+ * @param store 应用的 vuex.store 实例
+ */
+function authorityGuard(router, store) {
+  router.beforeEach((to, form, next) => {
+    const permissions = store.getters["account/permissions"];
+    const roles = store.getters["account/roles"];
+    if (!hasPermission(to, permissions) && !hasRole(to, roles)) {
+      next({ path: "/403" });
+    } else {
+      next();
+    }
+  });
+}
+
+/**
+ * 判断是否有路由的权限
+ * @param route 路由
+ * @param permissions 用户权限集合
+ * @returns {boolean|*}
+ */
+function hasPermission(route, permissions) {
+  const authority = route.meta.authority || "*";
+  let required = "*";
+  if (typeof authority === "string") {
+    required = authority;
+  } else if (typeof authority === "object") {
+    required = authority.permission;
+  }
+  return (
+    required === "*" ||
+    (permissions &&
+      permissions.findIndex(
+        item => item === required || item.id === required
+      ) !== -1)
+  );
+}
+
+/**
+ * 判断是否有路由需要的角色
+ * @param route 路由
+ * @param roles 用户角色集合
+ */
+function hasRole(route, roles) {
+  const authority = route.meta.authority || "*";
+  let required = undefined;
+  if (typeof authority === "object") {
+    required = authority.role;
+  }
+  return (
+    authority === "*" ||
+    (required &&
+      roles &&
+      roles.findIndex(item => item === required || item.id === required) !== -1)
+  );
+}
+
+/**
+ * 格式化路由的权限配置
+ * @param routes
+ */
+function formatAuthority(routes, permissions) {
+  routes.forEach(route => {
+    const meta = route.meta;
+    if (meta) {
+      let authority = {};
+      if (!meta.authority) {
+        authority.permission = "*";
+      } else if (typeof meta.authority === "string") {
+        authority.permission = meta.authority;
+      } else if (typeof meta.authority === "object") {
+        authority = meta.authority;
+      } else {
+        console.log(typeof meta.authority);
+      }
+      meta.authority = authority;
+    } else {
+      route.meta = {
+        authority: { permission: "*" }
+      };
+    }
+    if (!hasPermission(route, permissions)) {
+      route.meta.invisible = true;
+    }
+    if (route.children) {
+      formatAuthority(route.children, permissions);
+    }
+  });
+}
+
+/**
+ * 从路由 path 解析 i18n key
+ * @param path
+ * @returns {*}
+ */
+function getI18nKey(path) {
+  const keys = path
+    .split("/")
+    .filter(item => !item.startsWith(":") && item != "");
+  keys.push("name");
+  return keys.join(".");
+}
+
+export { loadRoutes, loginGuard, authorityGuard, formatAuthority, getI18nKey };

+ 115 - 0
src/common/theme/colors.js

@@ -0,0 +1,115 @@
+const varyColor = require("webpack-theme-color-replacer/client/varyColor");
+const { generate } = require("@ant-design/colors");
+const { ADMIN, ANTD } = require("../../config/default");
+const Config = require("../../config");
+
+const themeMode = ADMIN.theme.mode;
+
+// 获取 ant design 色系
+function getAntdColors(color, mode) {
+  let options = mode && mode == themeMode.NIGHT ? { theme: "dark" } : undefined;
+  return generate(color, options);
+}
+
+// 获取功能性颜色
+function getFunctionalColors(mode) {
+  let options = mode && mode == themeMode.NIGHT ? { theme: "dark" } : undefined;
+  let { success, warning, error } = ANTD.primary;
+  const { success: s1, warning: w1, error: e1 } = Config.theme;
+  success = success && s1;
+  warning = success && w1;
+  error = success && e1;
+  const successColors = generate(success, options);
+  const warningColors = generate(warning, options);
+  const errorColors = generate(error, options);
+  return {
+    success: [...successColors.slice(0, 3), successColors[5]],
+    warning: [...warningColors.slice(0, 3), warningColors[5]],
+    error: [...errorColors.slice(0, 3), errorColors[5]]
+  };
+}
+
+// 获取菜单色系
+function getMenuColors(color, mode) {
+  if (mode == themeMode.NIGHT) {
+    return ANTD.primary.night.menuColors;
+  } else if (color == ANTD.primary.color) {
+    return ANTD.primary.dark.menuColors;
+  } else {
+    return [
+      varyColor.darken(color, 0.93),
+      varyColor.darken(color, 0.83),
+      varyColor.darken(color, 0.73)
+    ];
+  }
+}
+
+// 获取主题模式切换色系
+function getThemeToggleColors(color, mode) {
+  //主色系
+  const mainColors = getAntdColors(color, mode);
+  const primary = mainColors[5];
+  //辅助色系,因为 antd 目前没针对夜间模式设计,所以增加辅助色系以保证夜间模式的正常切换
+  const subColors = getAntdColors(primary, themeMode.LIGHT);
+  //菜单色系
+  const menuColors = getMenuColors(color, mode);
+  //内容色系(包含背景色、文字颜色等)
+  const themeCfg = ANTD.theme[mode];
+  let contentColors = Object.keys(themeCfg)
+    .map(key => themeCfg[key])
+    .map(color => (isHex(color) ? color : toNum3(color).join(",")));
+  // 内容色去重
+  contentColors = [...new Set(contentColors)];
+  // rgb 格式的主题色
+  let rgbColors = [toNum3(primary).join(",")];
+  let functionalColors = getFunctionalColors(mode);
+  return {
+    primary,
+    mainColors,
+    subColors,
+    menuColors,
+    contentColors,
+    rgbColors,
+    functionalColors
+  };
+}
+
+function toNum3(color) {
+  if (isHex(color)) {
+    return varyColor.toNum3(color);
+  }
+  let colorStr = "";
+  if (isRgb(color)) {
+    colorStr = color.slice(5, color.length);
+  } else if (isRgba(color)) {
+    colorStr = color.slice(6, color.lastIndexOf(","));
+  }
+  let rgb = colorStr.split(",");
+  const r = parseInt(rgb[0]);
+  const g = parseInt(rgb[1]);
+  const b = parseInt(rgb[2]);
+  return [r, g, b];
+}
+
+function isHex(color) {
+  return color.length >= 4 && color[0] == "#";
+}
+
+function isRgb(color) {
+  return color.length >= 10 && color.slice(0, 3) == "rgb";
+}
+
+function isRgba(color) {
+  return color.length >= 13 && color.slice(0, 4) == "rgba";
+}
+
+module.exports = {
+  isHex,
+  isRgb,
+  isRgba,
+  toNum3,
+  getAntdColors,
+  getMenuColors,
+  getThemeToggleColors,
+  getFunctionalColors
+};

+ 94 - 0
src/common/theme/theme-color-replacer-extend.js

@@ -0,0 +1,94 @@
+const { cssResolve } = require("../../config/replacer");
+// 修正 webpack-theme-color-replacer 插件提取的 css 结果
+function resolveCss(output, srcArr) {
+  let regExps = [];
+  // 提取 resolve 配置中所有的正则配置
+  Object.keys(cssResolve).forEach(key => {
+    let isRegExp = false;
+    let reg = {};
+    try {
+      reg = eval(key);
+      isRegExp = reg instanceof RegExp;
+    } catch (e) {
+      isRegExp = false;
+    }
+    if (isRegExp) {
+      regExps.push([reg, cssResolve[key]]);
+    }
+  });
+
+  // 去重
+  srcArr = dropDuplicate(srcArr);
+
+  // 处理 css
+  let outArr = [];
+  srcArr.forEach(text => {
+    // 转换为 css 对象
+    let cssObj = parseCssObj(text);
+    // 根据selector匹配配置,匹配成功,则按配置处理 css
+    if (cssResolve[cssObj.selector] != undefined) {
+      let cfg = cssResolve[cssObj.selector];
+      if (cfg) {
+        outArr.push(cfg.resolve(text, cssObj));
+      }
+    } else {
+      let cssText = "";
+      // 匹配不成功,则测试是否有匹配的正则配置,有则按正则对应的配置处理
+      for (let regExp of regExps) {
+        if (regExp[0].test(cssObj.selector)) {
+          let cssCfg = regExp[1];
+          cssText = cssCfg ? cssCfg.resolve(text, cssObj) : "";
+          break;
+        }
+        // 未匹配到正则,则设置 cssText 为默认的 css(即不处理)
+        cssText = text;
+      }
+      if (cssText != "") {
+        outArr.push(cssText);
+      }
+    }
+  });
+  output = outArr.join("\n");
+  return output;
+}
+
+// 数组去重
+function dropDuplicate(arr) {
+  let map = {};
+  let r = [];
+  for (let s of arr) {
+    if (!map[s]) {
+      r.push(s);
+      map[s] = 1;
+    }
+  }
+  return r;
+}
+
+/**
+ * 从字符串解析 css 对象
+ * @param cssText
+ * @returns {{
+ *   name: String,
+ *   rules: Array[String],
+ *   toText: function
+ * }}
+ */
+function parseCssObj(cssText) {
+  let css = {};
+  const ruleIndex = cssText.indexOf("{");
+  css.selector = cssText.substring(0, ruleIndex);
+  const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1);
+  const rules = ruleBody.split(";");
+  css.rules = rules;
+  css.toText = function() {
+    let body = "";
+    this.rules.forEach(item => {
+      body += item + ";";
+    });
+    return `${this.selector}{${body}}`;
+  };
+  return css;
+}
+
+module.exports = { resolveCss };

+ 72 - 0
src/common/theme/themeUtil.js

@@ -0,0 +1,72 @@
+const client = require("webpack-theme-color-replacer/client");
+const { theme } = require("../../config");
+const {
+  getMenuColors,
+  getAntdColors,
+  getThemeToggleColors,
+  getFunctionalColors
+} = require("./colors");
+const { ANTD } = require("../../config/default");
+
+module.exports = {
+  getThemeColors(color, $theme) {
+    const _color = color || theme.color;
+    const mode = $theme || theme.mode;
+    const replaceColors = getThemeToggleColors(_color, mode);
+    const themeColors = [
+      ...replaceColors.mainColors,
+      ...replaceColors.subColors,
+      ...replaceColors.menuColors,
+      ...replaceColors.contentColors,
+      ...replaceColors.rgbColors,
+      ...replaceColors.functionalColors.success,
+      ...replaceColors.functionalColors.warning,
+      ...replaceColors.functionalColors.error
+    ];
+    return themeColors;
+  },
+  changeThemeColor(newColor, $theme) {
+    let promise = client.changer.changeColor({
+      newColors: this.getThemeColors(newColor, $theme)
+    });
+    return promise;
+  },
+  modifyVars(color) {
+    let _color = color || theme.color;
+    const palettes = getAntdColors(_color, theme.mode);
+    const menuColors = getMenuColors(_color, theme.mode);
+    const { success, warning, error } = getFunctionalColors(theme.mode);
+    const primary = palettes[5];
+    return {
+      "primary-color": primary,
+      "primary-1": palettes[0],
+      "primary-2": palettes[1],
+      "primary-3": palettes[2],
+      "primary-4": palettes[3],
+      "primary-5": palettes[4],
+      "primary-6": palettes[5],
+      "primary-7": palettes[6],
+      "primary-8": palettes[7],
+      "primary-9": palettes[8],
+      "primary-10": palettes[9],
+      "info-color": primary,
+      "success-color": success[3],
+      "warning-color": warning[3],
+      "error-color": error[3],
+      "alert-info-bg-color": palettes[0],
+      "alert-info-border-color": palettes[2],
+      "alert-success-bg-color": success[0],
+      "alert-success-border-color": success[2],
+      "alert-warning-bg-color": warning[0],
+      "alert-warning-border-color": warning[2],
+      "alert-error-bg-color": error[0],
+      "alert-error-border-color": error[2],
+      "processing-color": primary,
+      "menu-dark-submenu-bg": menuColors[0],
+      "layout-header-background": menuColors[1],
+      "layout-trigger-background": menuColors[2],
+      ...ANTD.theme[theme.mode],
+      ...ANTD.theme["common"]
+    };
+  }
+};

+ 175 - 0
src/common/utils/canvas.js

@@ -0,0 +1,175 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec canvas 常用方法
+ */
+
+/**
+ * 画线的方法
+ * @param {Number} xa 起点坐标x
+ * @param {Number} ya 起点坐标y
+ * @param {Number} xb 终点坐标x
+ * @param {Number} yb 终点坐标y
+ */
+export function drawLine({
+  ctx,
+  xa = 0,
+  ya = 0,
+  xb,
+  yb,
+  gradient = { color: "#000" }
+}) {
+  ctx.fillStyle = gradient.color;
+  ctx.strokeStyle = gradient.color;
+  ctx.beginPath();
+  ctx.moveTo(xa, ya);
+  ctx.lineTo(xb, yb);
+  ctx.stroke();
+  ctx.closePath();
+}
+
+/**
+ * 绘制方块
+ * @param {Number} xa 起点坐标x
+ * @param {Number} ya 起点坐标y
+ * @param {Number} xb 终点坐标x
+ * @param {Number} yb 终点坐标y
+ * @param {Object} mousePosition 是否是鼠标滑过 position x y为鼠标滑过坐标
+ * @param {Object} gradient 填充颜色 color为默认颜色,hover为渐变颜色
+ */
+export function drawRect({
+  ctx,
+  xa = 0,
+  ya = 0,
+  xb,
+  yb,
+  mousePosition = null,
+  gradient
+}) {
+  let ishover = false;
+  ctx.beginPath();
+  ctx.rect(xa, ya, xb, yb);
+  ctx.fillStyle = gradient.color;
+  ctx.strokeStyle = gradient.color;
+  // 如果是鼠标移动的到柱状图上,重新绘制图表
+  if (mousePosition && ctx.isPointInPath(mousePosition.x, mousePosition.y)) {
+    ishover = true;
+    ctx.fillStyle = gradient.hover;
+  }
+
+  ctx.fill();
+  ctx.closePath();
+  return ishover;
+}
+
+/**
+ * 绘制文本
+ * @param {Number} xa 起点坐标x
+ * @param {Number} ya 起点坐标y
+ * @param {Number} xb 终点坐标x
+ * @param {Number} yb 终点坐标y
+ * @param {Object} textStyle 文本颜色 bgcolor 背景 borderColor 边框颜色 color字体颜色 data 数据
+ */
+export function drawText({ ctx, xa = 0, ya = 0, textStyle }) {
+  ctx.beginPath();
+  drawRadiusRect({
+    ctx,
+    x: xa,
+    y: ya,
+    width: textStyle.width,
+    height: textStyle.height,
+    radius: 4
+  });
+  ctx.fillStyle = textStyle.bgcolor;
+  ctx.strokeStyle = textStyle.borderColor;
+  ctx.fill();
+  ctx.font = textStyle.font;
+  ctx.fillStyle = textStyle.color;
+  ctx.textAlign = "center";
+  ctx.fillText(
+    textStyle.data,
+    xa + textStyle.width / 2,
+    ya + textStyle.height / 2 + textStyle.fontSize / 3
+  ); // 文字
+  ctx.closePath();
+}
+
+/**
+ * 获取圆角矩形
+ */
+export function drawRadiusRect({ ctx, x = 0, y = 0, width, height, radius }) {
+  ctx.beginPath();
+  ctx.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
+  ctx.lineTo(width - radius + x, y);
+  ctx.arc(
+    width - radius + x,
+    radius + y,
+    radius,
+    (Math.PI * 3) / 2,
+    Math.PI * 2
+  );
+  ctx.lineTo(width + x, height + y - radius);
+  ctx.arc(
+    width - radius + x,
+    height - radius + y,
+    radius,
+    0,
+    (Math.PI * 1) / 2
+  );
+  ctx.lineTo(radius + x, height + y);
+  ctx.arc(radius + x, height - radius + y, radius, (Math.PI * 1) / 2, Math.PI);
+  ctx.closePath();
+}
+
+/**
+ * 解决canvas在retina高清屏(Mac)下失真问题
+ */
+export function getPixelRatio(context) {
+  let backingStore =
+    context.backingStorePixelRatio ||
+    context.webkitBackingStorePixelRatio ||
+    context.mozBackingStorePixelRatio ||
+    context.msBackingStorePixelRatio ||
+    context.oBackingStorePixelRatio ||
+    context.backingStorePixelRatio ||
+    1;
+  return (window.devicePixelRatio || 1) / backingStore;
+}
+export function canvasFromRetina(ctx, canvas) {
+  let ratio = getPixelRatio(ctx);
+  canvas.style.width = canvas.width + "px";
+  canvas.style.height = canvas.height + "px";
+  canvas.width = canvas.width * ratio;
+  canvas.height = canvas.height * ratio;
+  ctx.scale(ratio, ratio);
+  return ratio;
+}
+
+/**
+ * 消除锯齿
+ */
+export function retina(canvasEl) {
+  const canvas = canvasEl;
+  const ctx = canvas.getContext("2d");
+  const devicePixelRatio = window.devicePixelRatio || 1;
+  const backingStorePixelRatio =
+    ctx.webkitBackingStorePixelRatio ||
+    ctx.mozBackingStorePixelRatio ||
+    ctx.msBackingStorePixelRatio ||
+    ctx.oBackingStorePixelRatio ||
+    ctx.backingStorePixelRatio ||
+    1;
+
+  const ratio = devicePixelRatio / backingStorePixelRatio;
+  if (devicePixelRatio !== backingStorePixelRatio) {
+    const oldWidth = canvas.width;
+    const oldHeight = canvas.height;
+
+    canvas.width = oldWidth * ratio;
+    canvas.height = oldHeight * ratio;
+
+    canvas.style.width = `${oldWidth}px`;
+    canvas.style.height = `${oldHeight}px`;
+    ctx.scale(ratio, ratio);
+  }
+}

+ 160 - 0
src/common/utils/dom.js

@@ -0,0 +1,160 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec 操作DOM 常用方法
+ */
+
+import { trigger } from "./index.js";
+export function $(expr, con) {
+  return typeof expr === "string"
+    ? (con || document).querySelector(expr)
+    : expr || null;
+}
+
+$.createElement = (tag, attrs) => {
+  const elem = document.createElement(tag);
+  for (let attr in attrs) {
+    if (attr === "appendTo") {
+      const parent = attrs.appendTo;
+      parent.appendChild(elem);
+    } else if (attr === "innerHTML") {
+      elem.innerHTML = attrs.innerHTML;
+    } else if (attr === "width" || attr === "height") {
+      elem.style[attr] =
+        String(attrs[attr]).indexOf("%") > -1
+          ? attrs[attr]
+          : attrs[attr] + "px";
+      elem.setAttribute(attr, attrs[attr]);
+    } else if (attr === "x") {
+      elem.style.left = attrs[attr] + "px";
+      elem.setAttribute(attr, attrs[attr]);
+    } else if (attr === "y") {
+      elem.style.top = attrs[attr] + "px";
+      elem.setAttribute(attr, attrs[attr]);
+    } else if (attr === "styles") {
+      $.styles(elem, attrs[attr]);
+    } else {
+      elem.setAttribute(attr, attrs[attr]);
+    }
+  }
+  return elem;
+};
+
+$.on = (element, event, selector, callback) => {
+  if (!callback) {
+    callback = selector;
+    $.bind(element, event, callback);
+  } else {
+    $.delegate(element, event, selector, callback);
+  }
+};
+
+$.off = (element, event, handler) => {
+  element.removeEventListener(event, handler);
+};
+
+$.bind = (element, event, callback) => {
+  event.split(/\s+/).forEach(function(event) {
+    element.addEventListener(event, callback);
+  });
+};
+
+$.delegate = (element, event, selector, callback) => {
+  element.addEventListener(event, function(e) {
+    const delegatedTarget = e.target.closest(selector);
+    if (delegatedTarget) {
+      e.delegatedTarget = delegatedTarget;
+      callback.call(this, e, delegatedTarget);
+    }
+  });
+};
+
+$.closest = (selector, element) => {
+  if (!element) return null;
+
+  if (element.matches(selector)) {
+    return element;
+  }
+
+  return $.closest(selector, element.parentNode);
+};
+if (!Element.prototype.matches) {
+  Element.prototype.matches =
+    Element.prototype.msMatchesSelector ||
+    Element.prototype.webkitMatchesSelector;
+}
+if (!Element.prototype.closest) {
+  Element.prototype.closest = function(s) {
+    let el = this;
+    if (!document.documentElement.contains(el)) return null;
+    do {
+      if (el.matches(s)) return el;
+      el = el.parentElement || el.parentNode;
+    } while (el !== null && el.nodeType === 1);
+    return null;
+  };
+}
+
+$.attr = (element, attr, value) => {
+  if (!value && value !== 0 && typeof attr === "string") {
+    return element.getAttribute(attr);
+  }
+
+  if (typeof attr === "object") {
+    for (let key in attr) {
+      $.attr(element, key, attr[key]);
+    }
+    return;
+  }
+  if (attr === "width" || attr === "height") {
+    element.style[attr] =
+      String(value).indexOf("%") > -1 ? value : value + "px";
+  } else if (attr === "x") {
+    element.style.left = value + "px";
+  } else if (attr === "y") {
+    element.style.top = value + "px";
+  }
+  element.setAttribute(attr, value);
+};
+
+$.styles = (element, css, value) => {
+  if (!value && value !== 0 && typeof style === "string") {
+    return element.style[css];
+  }
+
+  if (typeof css === "object") {
+    for (let key in css) {
+      $.styles(element, key, css[key]);
+    }
+    return;
+  }
+  if (typeof value === "number") {
+    value += "px";
+  }
+  element.style[css] = value;
+};
+
+$.loadJs = (url, callback) => {
+  var script = document.createElement("script");
+  script.type = "text/javascript";
+  if (typeof callback !== "undefined") {
+    if (script.readyState) {
+      script.onreadystatechange = function() {
+        if (script.readyState == "loaded" || script.readyState == "complete") {
+          script.onreadystatechange = null;
+          callback();
+        }
+      };
+    } else {
+      script.onload = function() {
+        callback();
+      };
+    }
+  }
+  script.src = url;
+  document.body.appendChild(script);
+};
+
+$.trigger = (element, event) => {
+  trigger(element, event);
+};

+ 163 - 0
src/common/utils/image.js

@@ -0,0 +1,163 @@
+/**
+ * @author 许明浩
+ * @lasttime 2020/7/24 22:36
+ * @dec 所有图片全部load完成后执行
+ * @param {Array[String]} urlArr 图片url数组
+ * @param {Function} callBack 所有图片加载完后回掉函数
+ */
+import html2canvas from "html2canvas";
+export const loadImages = (urlArr, callBack) => {
+  if (urlArr.length <= 0) return;
+  let i = 0,
+    timer = null,
+    len = urlArr.length,
+    load = url => {
+      if (i < len) {
+        const image = new Image();
+        image.src = url;
+        timer = setInterval(() => {
+          if (image.complete) {
+            console.log("complete");
+            clearInterval(timer);
+            load(urlArr[i++]);
+          }
+        }, 80);
+      } else {
+        callBack();
+      }
+    };
+  load(urlArr[i]);
+};
+
+// 图片转blob
+export const getImage = (url, imgId) => {
+  var xhr = new XMLHttpRequest();
+  xhr.open("get", url, true);
+  xhr.responseType = "blob";
+  xhr.onload = function() {
+    if (this.status == 200) {
+      document.getElementById(imgId).src = URL.createObjectURL(this.response);
+    }
+  };
+  xhr.send();
+};
+
+// 图片转base64
+export const image2Base64 = img => {
+  var canvas = document.createElement("canvas");
+  canvas.width = img.width;
+  canvas.height = img.height;
+  var ctx = canvas.getContext("2d");
+  ctx.drawImage(img, 0, 0, img.width, img.height);
+  var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
+  var dataURL = canvas.toDataURL("image/" + ext);
+  return dataURL;
+};
+
+export const getImgBase64 = (url, callback) => {
+  return new Promise(resolve => {
+    var img = new Image();
+    img.src = url;
+    img.onload = function() {
+      if (callback) {
+        callback(img);
+      } else {
+        resolve(image2Base64(img));
+      }
+    };
+  });
+};
+
+/**
+ * @author: 许明浩
+ * @date: 2020-07-09 13:00:00
+ * @desc: 将HTML生成图片
+ * @param dom 需要生成图片的HTML
+ * @param width 图片宽度
+ * @param height 图片高度
+ * @param height 背景颜色
+ * @retrun 返回图片
+ */
+export const createImages = function({
+  dom: dom,
+  width = window.innerWidth,
+  height = window.innerHeight,
+  windowWidth = window.innerWidth,
+  windowHeight = window.innerHeight,
+  x = 0,
+  y = 0,
+  scrollX = 0,
+  scrollY = 0,
+  bgColor = "#ffffff",
+  foreignObjectRendering = false,
+  ignoreElements = () => false
+} = {}) {
+  return new Promise(resolve => {
+    html2canvas(dom, {
+      useCORS: true, // 如果截图的内容里有图片,可能会有跨域的情况,加上这个参数,解决文件跨域问题
+      allowTaint: false, //允许跨域(图片跨域相关)
+      taintTest: true, //是否在渲染前测试图片
+      logging: false,
+      width: width,
+      height: height,
+      windowWidth,
+      windowHeight,
+      x: x,
+      y: y,
+      scrollX,
+      scrollY,
+      foreignObjectRendering,
+      ignoreElements,
+      scale:
+        window.devicePixelRatio && window.devicePixelRatio > 1
+          ? window.devicePixelRatio
+          : 1,
+      backgroundColor: bgColor
+    }).then(canvas => {
+      let url = canvas.toDataURL("image/jpeg");
+      resolve(url);
+    });
+  });
+};
+
+/**
+ * 图片上传压缩  压缩至尺寸: 383*512
+ * @param {*} img 被压缩的图片对象(示例:<img src="1.png"/>)
+ * @param {*} callback
+ */
+export function compressImg(
+  img,
+  callback,
+  needCompress = true,
+  compressImgWidth = 383
+) {
+  img.onload = () => {
+    let limitWidth = compressImgWidth;
+    // let imageWidth = img.width;
+    // let imageHeight = img.height;
+    // if (imageWidth > imageHeight) {
+    //     limitWidth = arguments[2] ? arguments[2] : 512;
+    // } else {
+    //     limitWidth = arguments[2] ? arguments[2] : 383;
+    // }
+    const width = needCompress
+      ? img.width > limitWidth
+        ? limitWidth
+        : img.width
+      : img.width;
+    const height = needCompress
+      ? img.width > limitWidth
+        ? parseInt((img.height * limitWidth) / img.width)
+        : img.height
+      : img.height;
+    console.log("width:", width, "height:", height);
+    const canvas = document.createElement("canvas");
+    const ctx = canvas.getContext("2d");
+    canvas.width = width;
+    canvas.height = height;
+    ctx.fillStyle = "#fff";
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+    callback && callback(canvas.toDataURL("image/png", 1));
+  };
+}

+ 556 - 0
src/common/utils/index.js

@@ -0,0 +1,556 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec 工具包
+ * @dec throttle 节流,debounce 防抖动,请调用 throttle-debounce 插件
+ */
+
+import moment from "moment";
+/**
+ * 获取URL指定参数
+ * @param {String} name 参数名称
+ * @param {String} url 目标URL地址
+ * @return {String|null} 参数值
+ */
+export function getQueryString(name, url) {
+  let _url = url || window.location.href;
+  if (_url.includes("?") && _url.includes(name)) {
+    let arr = _url
+      .split("?")
+      .filter(x => x.includes(name))
+      .join()
+      .split("&");
+    return arr
+      .find(_ => {
+        return _.includes(name);
+      })
+      .split("=")[1];
+  } else {
+    return null;
+  }
+}
+
+/**
+ * 获取URL参数
+ * @param {String} url 目标URL地址
+ * @return {Object} 参数对象
+ */
+export function getQueryObject(url) {
+  const search = url.split("?")[1];
+  if (!search) {
+    return {};
+  }
+  return JSON.parse(
+    '{"' +
+      decodeURIComponent(search)
+        .replace(/"/g, '\\"')
+        .replace(/&/g, '","')
+        .replace(/=/g, '":"') +
+      '"}'
+  );
+}
+
+/**
+ * 非空判断
+ * @param {String} value 目标值
+ * @return {Boolean} 是否为空
+ */
+export function isEmpty(value) {
+  return typeof value === "undefined" || value === "" || value === null;
+}
+window.isEmpty = isEmpty;
+
+/**
+ * merge对象
+ * @param {Object} source 源对象
+ * @param {Object} target 要merge的对象
+ * @return {Object} merge后的source
+ */
+export function objectMerge(source, target) {
+  if (typeof source !== "object") {
+    source = {};
+  }
+  if (Array.isArray(target)) {
+    return target.slice();
+  }
+  for (const property in target) {
+    if (Object.prototype.hasOwnProperty.call(target, property)) {
+      const targetProperty = target[property];
+      if (
+        typeof targetProperty === "object" &&
+        !moment.isMoment(targetProperty)
+      ) {
+        source[property] = objectMerge(source[property], targetProperty);
+        continue;
+      }
+      source[property] = targetProperty;
+    }
+  }
+  return source;
+}
+
+/**
+ * 数据深拷
+ * @param {Array|Object} 要拷贝的对象
+ * @return {Array|Object} 返回拷贝的新对象
+ */
+export function deepClone(source) {
+  if (!source && typeof source !== "object") {
+    throw new Error("error arguments", "shallowClone");
+  }
+  const targetObj = source.constructor === Array ? [] : {};
+  for (const keys in source) {
+    if (Object.prototype.hasOwnProperty.call(source, keys)) {
+      if (
+        source[keys] &&
+        typeof source[keys] === "object" &&
+        !moment.isMoment(source[keys])
+      ) {
+        targetObj[keys] = source[keys].constructor === Array ? [] : {};
+        targetObj[keys] = deepClone(source[keys]);
+      } else {
+        targetObj[keys] = source[keys];
+      }
+    }
+  }
+  return targetObj;
+}
+
+/**
+ * 添加事件
+ * @param {Element} el 目标DOM
+ * @param {String} type 事件类型
+ * @param {Function} fn 事件方法
+ * @param {Boolean} capture true为事件捕获/false为事件冒泡 IE678不支持事件捕获,不填默认事件冒泡
+ */
+export function addEvent(el, type, fn, capture) {
+  if (window.addEventListener) {
+    if (type === "mousewheel" && document.mozHidden !== undefined) {
+      type = "DOMMouseScroll";
+    }
+    el.addEventListener(type, fn, !!capture);
+  } else if (window.attachEvent) {
+    el.attachEvent("on" + type, fn);
+  }
+}
+
+/**
+ * 移除事件
+ * @param {Element} el 目标DOM
+ * @param {String} type 事件类型
+ * @param {Function} fn 事件方法
+ * @param {Boolean} capture true为事件捕获/false为事件冒泡 IE678不支持事件捕获,不填默认事件冒泡
+ */
+export function removeEvent(el, type, fn, capture) {
+  if (window.removeEventListener) {
+    if (type === "mousewheel" && document.mozHidden !== undefined) {
+      type = "DOMMouseScroll";
+    }
+    el.removeEventListener(type, fn, !!capture);
+  } else if (window.detachEvent) {
+    el.detachEvent("on" + type, fn);
+  }
+}
+
+/**
+ * 解决浮点型计算精度问题
+ * @param {Number} f 计算表达式
+ * @param {Number} digit 保留小数点几位
+ * @param {Boolean} isNotRound false为四舍五路
+ * @return {Number} 计算后的表达式结果
+ */
+export function formatFloat(f, digit = 2, isNotRound) {
+  if (arguments.length === 2 && typeof arguments[1] === "boolean") {
+    isNotRound = arguments[1];
+    digit = 2;
+  }
+  if (isNaN(Number(f))) {
+    throw new Error("parameters cannot be non-numeric for formatFloat method");
+  } else {
+    if (typeof f === "string") {
+      f = Number(f);
+    }
+  }
+  f = f.toFixed(10);
+  let m = Math.pow(10, digit);
+  if (isNotRound) {
+    return parseInt(f * m, 10) / m;
+  }
+  let _abs = 1;
+  if (f < 0) {
+    f = Math.abs(f);
+    _abs = -1;
+  }
+  return (Math.round(f * m, 10) / m) * _abs;
+}
+
+/**
+ * 深冻结
+ * @param {Object} o 要冻结的对象
+ * @return {Object} 冻结后的源对象
+ */
+export function deepFreeze(o) {
+  var prop, propKey;
+  Object.freeze(o);
+  for (propKey in o) {
+    prop = o[propKey];
+    if (
+      !Object.prototype.hasOwnProperty.call(o, propKey) ||
+      !(typeof prop === "object") ||
+      Object.isFrozen(prop)
+    ) {
+      continue;
+    }
+    deepFreeze(prop);
+  }
+}
+
+/**
+ * 获取随机数
+ * @param {String} str 随机数开头字母
+ * @param {Number} n 随机数个数
+ * @return {String} 生成的随机数
+ */
+export function getRandomNum(str = "", n = 12) {
+  if (typeof n === "string") {
+    n = Number(n);
+  }
+  n += 2;
+  return str + String(Math.random()).slice(2, n);
+}
+
+/**
+ * trigger 扩展dispatchEvent事件语法,兼容IE
+ * @param {Element} element 目标DOM
+ * @param {String} event 事件类型
+ */
+export function trigger(element, event) {
+  if (document.createEventObject) {
+    // IE浏览器支持fireEvent方法
+    let evt = document.createEventObject();
+    evt.dispatchEvent = true;
+    return element.fireEvent("on" + event, evt);
+  } else {
+    // 其他标准浏览器使用dispatchEvent方法
+    let evt = document.createEvent("HTMLEvents");
+    evt.initEvent(event, true, true);
+    evt.dispatchEvent = true;
+    return !element.dispatchEvent(evt);
+  }
+}
+
+/**
+ * 格式化children
+ * @param {Array} data 格式化数据
+ * @param {String} clearEmptyChildren string为child的key命名 去除空数组的children对象
+ */
+export function formatChildren({ data, clearEmptyChildren }) {
+  if (clearEmptyChildren) {
+    data = data.map(item => {
+      if (item[clearEmptyChildren]) {
+        if (item[clearEmptyChildren].length < 1) {
+          delete item[clearEmptyChildren];
+        } else {
+          item[clearEmptyChildren] = formatChildren({
+            data: item[clearEmptyChildren],
+            clearEmptyChildren
+          });
+        }
+      }
+      return item;
+    });
+  }
+  return data;
+}
+
+/**
+ * 设别判断
+ * @returns string
+ */
+export function getDevice() {
+  const u = navigator.userAgent;
+  // console.log(u)
+  let device = "";
+  if (u.indexOf("Android") > -1 || u.indexOf("Linux") > -1) {
+    // console.log('android')
+    device = "android";
+  } else if (u.indexOf("iPhone") > -1 && u.indexOf("Safari") === -1) {
+    // console.log('ios', 11111)
+    device = "ios";
+  } else if (u.indexOf("MicroMessenger") > -1) {
+    // console.log('wechat')
+    device = "wechat";
+  } else {
+    // console.log('web',2222)
+    device = "web";
+  }
+  return device;
+}
+
+/**
+ * 将文件转化为base64
+ * @author wfyuan 2020-02-27
+ * @param {*} file 待转换的文件
+ * @returns base64文件流
+ */
+export function fileToBase64(file) {
+  let reader = new FileReader();
+  reader.readAsDataURL(file[0]);
+  reader.onload = function() {
+    return this.result;
+  };
+}
+
+/**
+ * 将base64转换为文件
+ * @param {*} dataurl base64数据
+ * @param {*} filename 文件名(示例:img.jpg)
+ */
+export function convertBase64UrlToFile(dataurl, filename) {
+  let arr = dataurl.split(","),
+    mime = arr[0].match(/:(.*?);/)[1],
+    bstr = atob(arr[1]),
+    n = bstr.length,
+    u8arr = new Uint8Array(n);
+  while (n--) {
+    u8arr[n] = bstr.charCodeAt(n);
+  }
+  return new File([u8arr], filename, { type: mime });
+}
+
+/**
+ * 给对象注入属性
+ * @param keys 属性key数组, 如 keys = ['config', 'path'] , 则会给对象注入 object.config.path 的属性
+ * @param value 属性值
+ * @returns {Object}
+ */
+Object.defineProperty(Object.prototype, "assignProps", {
+  writable: false,
+  enumerable: false,
+  configurable: true,
+  value: function(keys, value) {
+    let props = this;
+    for (let i = 0; i < keys.length; i++) {
+      let key = keys[i];
+      if (i == keys.length - 1) {
+        props[key] = value;
+      } else {
+        props[key] = props[key] == undefined ? {} : props[key];
+        props = props[key];
+      }
+    }
+    return this;
+  }
+});
+
+/**
+ * 非空判断
+ */
+export function isDef(v) {
+  return v !== undefined && v !== null;
+}
+/**
+ * 从数组中删除.
+ */
+export function remove(arr, item) {
+  if (arr.length) {
+    const index = arr.indexOf(item);
+    if (index > -1) {
+      return arr.splice(index, 1);
+    }
+  }
+}
+const _toString = Object.prototype.toString;
+/**
+ * 是否为正则
+ */
+export function isRegExp(v) {
+  return _toString.call(v) === "[object RegExp]";
+}
+
+/**
+ * 把对象按照 js配置文件的格式进行格式化
+ * @param obj 格式化的对象
+ * @param dep 层级,此项无需传值
+ * @returns {string}
+ */
+export function formatConfig(obj, dep) {
+  dep = dep || 1;
+  const LN = "\n",
+    TAB = "  ";
+  let indent = "";
+  for (let i = 0; i < dep; i++) {
+    indent += TAB;
+  }
+  let isArray = false,
+    arrayLastIsObj = false;
+  let str = "",
+    prefix = "{",
+    subfix = "}";
+  if (Array.isArray(obj)) {
+    isArray = true;
+    prefix = "[";
+    subfix = "]";
+    str = obj
+      .map((item, index) => {
+        let format = "";
+        if (typeof item == "function") {
+          //
+        } else if (typeof item == "object") {
+          arrayLastIsObj = true;
+          format = `${LN}${indent}${formatConfig(item, dep + 1)},`;
+        } else if (
+          (typeof item == "number" && !isNaN(item)) ||
+          typeof item == "boolean"
+        ) {
+          format = `${item},`;
+        } else if (typeof item == "string") {
+          format = `'${item}',`;
+        }
+        if (index == obj.length - 1) {
+          format = format.substring(0, format.length - 1);
+        } else {
+          arrayLastIsObj = false;
+        }
+        return format;
+      })
+      .join("");
+  } else if (typeof obj != "function" && typeof obj == "object") {
+    str = Object.keys(obj)
+      .map((key, index, keys) => {
+        const val = obj[key];
+        let format = "";
+        if (typeof val == "function") {
+          //
+        } else if (typeof val == "object") {
+          format = `${LN}${indent}${key}: ${formatConfig(val, dep + 1)},`;
+        } else if (
+          (typeof val == "number" && !isNaN(val)) ||
+          typeof val == "boolean"
+        ) {
+          format = `${LN}${indent}${key}: ${val},`;
+        } else if (typeof val == "string") {
+          format = `${LN}${indent}${key}: '${val}',`;
+        }
+        if (index == keys.length - 1) {
+          format = format.substring(0, format.length - 1);
+        }
+        return format;
+      })
+      .join("");
+  }
+  const len = TAB.length;
+  if (indent.length >= len) {
+    indent = indent.substring(0, indent.length - len);
+  }
+  if (!isArray || arrayLastIsObj) {
+    subfix = LN + indent + subfix;
+  }
+  return `${prefix}${str}${subfix}`;
+}
+
+/**
+ * 清理空值,对象
+ * @param children
+ * @returns {*[]}
+ */
+export function filterEmpty(children = []) {
+  return children.filter(c => c.tag || (c.text && c.text.trim() !== ""));
+}
+
+/**
+ * 获取字符串长度,英文字符 长度1,中文字符长度2
+ * @param {*} str
+ */
+export const getStrFullLength = (str = "") =>
+  str.split("").reduce((pre, cur) => {
+    const charCode = cur.charCodeAt(0);
+    if (charCode >= 0 && charCode <= 128) {
+      return pre + 1;
+    }
+    return pre + 2;
+  }, 0);
+
+/**
+ * 截取字符串,根据 maxLength 截取后返回
+ * @param {*} str
+ * @param {*} maxLength
+ */
+export const cutStrByFullLength = (str = "", maxLength) => {
+  let showLength = 0;
+  return str.split("").reduce((pre, cur) => {
+    const charCode = cur.charCodeAt(0);
+    if (charCode >= 0 && charCode <= 128) {
+      showLength += 1;
+    } else {
+      showLength += 2;
+    }
+    if (showLength <= maxLength) {
+      return pre + cur;
+    }
+    return pre;
+  }, "");
+};
+
+/**
+ * @author 明浩
+ * @time 2020-08-21
+ * @dec 将parentId 转 children
+ */
+export function translateDataToTree(data) {
+  let parents = data.filter(
+    value =>
+      value.parentId === undefined ||
+      value.parentId === null ||
+      value.parentId === "" ||
+      value.parentId === "0"
+  );
+  let children = data.filter(
+    value =>
+      value.parentId !== "undefined" &&
+      value.parentId !== null &&
+      value.parentId !== "" &&
+      value.parentId !== "0"
+  );
+  let translator = (parents, children) => {
+    parents.forEach(parent => {
+      children.forEach((current, index) => {
+        if (current.parentId === parent.id) {
+          let temp = JSON.parse(JSON.stringify(children));
+          temp.splice(index, 1);
+          translator([current], temp);
+          typeof parent.children !== "undefined"
+            ? parent.children.push(current)
+            : (parent.children = [current]);
+        }
+      });
+    });
+  };
+
+  translator(parents, children);
+
+  return parents;
+}
+
+/**
+ * contentd导出数据,fileName文件名
+ */
+export function exportDownload(content, fileName) {
+  const blob = new Blob([content], {
+    type: "application/vnd.ms-excel;charset=utf-8"
+  }); // 构造一个blob对象来处理数据
+  if ("download" in document.createElement("a")) {
+    // 支持a标签download的浏览器
+    const link = document.createElement("a"); // 创建a标签
+    link.download = fileName; // a标签添加属性导出文件名
+    link.style.display = "none";
+    link.href = URL.createObjectURL(blob);
+    document.body.appendChild(link);
+    link.click(); // 执行下载
+    URL.revokeObjectURL(link.href); // 释放url
+    document.body.removeChild(link); // 释放标签
+  } else {
+    // 其他浏览器
+    navigator.msSaveBlob(blob, fileName);
+  }
+}

+ 20 - 0
src/common/utils/others/device.js

@@ -0,0 +1,20 @@
+/**
+ * @author 明浩
+ * @time 2020-8-16
+ * @dec 响应式监听工具,当浏览器宽度改变时触发某方法
+ */
+import enquireJs from "enquire.js";
+
+const enquireScreen = function(call) {
+  const hanlder = {
+    match: function() {
+      call && call(true);
+    },
+    unmatch: function() {
+      call && call(false);
+    }
+  };
+  enquireJs.register("only screen and (max-width: 767.99px)", hanlder);
+};
+
+export default enquireScreen;

+ 80 - 0
src/common/utils/others/i18n.js

@@ -0,0 +1,80 @@
+import Vue from "vue";
+import VueI18n from "vue-i18n";
+import routesI18n from "@/common/router/i18n";
+import { getI18nKey } from "@/common/router/routerUtil";
+
+/**
+ * 创建 i18n 配置
+ * @param locale 本地化语言
+ * @param fallback 回退语言
+ * @returns {VueI18n}
+ */
+function initI18n(locale, fallback) {
+  Vue.use(VueI18n);
+  let i18nOptions = {
+    locale,
+    fallbackLocale: fallback,
+    silentFallbackWarn: true,
+    messages: routesI18n.messages
+  };
+  return new VueI18n(i18nOptions);
+}
+
+/**
+ * 根据 router options 配置生成 国际化语言
+ * @param lang
+ * @param routes
+ * @param valueKey
+ * @returns {*}
+ */
+function generateI18n(lang, routes, valueKey) {
+  routes.forEach(route => {
+    let keys = getI18nKey(route.fullPath).split(".");
+    let value =
+      valueKey === "path"
+        ? route[valueKey]
+            .split("/")
+            .filter(item => !item.startsWith(":") && item != "")
+            .join(".")
+        : route[valueKey];
+    lang.assignProps(keys, value);
+    if (route.children) {
+      generateI18n(lang, route.children, valueKey);
+    }
+  });
+  return lang;
+}
+
+/**
+ * 格式化 router.options.routes,生成 fullPath
+ * @param routes
+ * @param parentPath
+ */
+function formatFullPath(routes, parentPath = "") {
+  routes.forEach(route => {
+    let isFullPath = route.path.substring(0, 1) === "/";
+    route.fullPath = isFullPath
+      ? route.path
+      : parentPath === "/"
+      ? parentPath + route.path
+      : parentPath + "/" + route.path;
+    if (route.children) {
+      formatFullPath(route.children, route.fullPath);
+    }
+  });
+}
+
+/**
+ * 从路由提取国际化数据
+ * @param i18n
+ * @param routes
+ */
+function mergeI18nFromRoutes(i18n, routes) {
+  formatFullPath(routes);
+  const CN = generateI18n(new Object(), routes, "name");
+  const US = generateI18n(new Object(), routes, "path");
+  i18n.mergeLocaleMessage("CN", CN);
+  i18n.mergeLocaleMessage("US", US);
+}
+
+export { initI18n, mergeI18nFromRoutes };

+ 101 - 0
src/common/utils/weChat/qywx.js

@@ -0,0 +1,101 @@
+import store from "../store";
+
+export function getCurrentContext() {
+  const u = navigator.userAgent;
+  if (u.match(/WxWork/i) == "wxwork") {
+    return "workwechat";
+  }
+  if (
+    u.match(/MicroMessenger/i) &&
+    u.match(/MicroMessenger/i)[0] &&
+    u.match(/MicroMessenger/i)[0].toLowerCase() == "micromessenger"
+  ) {
+    return "wechat";
+  }
+  return "aclient";
+}
+// 企业微信,微信功能初始化
+export let newData = null;
+export async function wxConfig(argjsApiList, callback) {
+  let jsApiList = [
+    "hideOptionMenu",
+    "invoke",
+    "shareAppMessage",
+    "shareWechatMessage",
+    "selectExternalContact",
+    "onMenuShareAppMessage",
+  ];
+  const isEwechat = isInEwechat();
+  console.log("wxConfig", `${isEwechat}_失败的话不走企业微信`);
+  if (!isEwechat) {
+    return false;
+  }
+  // 登录时间超过1小时50分调用wxConfig会重新获取签名data,或主动清除data
+  if (newData && newData.startTime + 110 * 60 * 1000 <= new Date().getTime()) {
+    newData = null;
+  }
+  if (!newData) {
+    let { data } = await getEwechatConfig();
+    newData = { ...data, startTime: new Date().getTime() };
+  }
+  // let {appid, signature, timestamp, nonceStr} = store.getters.eWechatInfo
+  argjsApiList = argjsApiList || [];
+  wx.config({
+    beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
+    debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
+    appId: newData.appId, // 必填,企业微信的corpID
+    timestamp: newData.timestamp, // 必填,生成签名的时间戳
+    nonceStr: newData.nonceStr, // 必填,生成签名的随机串
+    signature: newData.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
+    jsApiList: [...jsApiList, ...argjsApiList], // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
+  });
+  wx.ready(function () {
+    wx.hideOptionMenu();
+    callback && callback();
+    store.dispatch("setEwechatInfo", {
+      eWxConfigStatus: true,
+      startTime: newData.startTime,
+    });
+    console.log("wx_ready");
+  });
+  wx.error(function (err) {
+    //通过error接口处理失败验证
+    // config信息验证失败会执行error
+    store.dispatch("setEwechatInfo", {
+      eWxConfigStatus: false,
+      startTime: newData.startTime,
+    });
+    console.log("执行失败", err);
+  });
+}
+
+export const listenerGoBack = {
+  add: (fn) => {
+    window.addEventListener("popstate", fn, false);
+  },
+  remove: (fn) => {
+    window.removeEventListener("popstate", fn, false);
+  },
+};
+
+export function isWindowpushState(callback) {
+  if (getCurrentContext() !== "aclient") {
+    // c端环境
+    if (getCurrentContext() === "wechat") {
+      wx.miniProgram.getEnv((res) => {
+        // 微信内
+        if (!res.miniprogram) {
+          callback(true);
+          // 小程序内
+        } else {
+          callback(false);
+        }
+      });
+    } else {
+      // 企业微信
+      callback(true);
+    }
+  } else {
+    callback(false);
+  }
+}

+ 147 - 0
src/common/utils/weChat/wechatConfig.js

@@ -0,0 +1,147 @@
+import store from "../store";
+import { sdkwxConfig } from "@/api/common";
+export const getCurrentContext = () => {
+  const u = navigator.userAgent;
+  if (
+    u.match(/MicroMessenger/i) &&
+    u.match(/MicroMessenger/i)[0] &&
+    u.match(/MicroMessenger/i)[0].toLowerCase() == "micromessenger"
+  ) {
+    console.log("运行环境 version: DEV_20200731_V1.1 ====== 微信");
+    return "wechat";
+  }
+  return "";
+};
+// 微信功能初始化
+const jsApiList = [
+  "hideOptionMenu",
+  "invoke",
+  "shareAppMessage",
+  "shareWechatMessage",
+  "selectExternalContact",
+  "getCurExternalContact",
+  "onMenuShareAppMessage",
+  "updateAppMessageShareData",
+  "hideMenuItems",
+  "showMenuItems"
+];
+export let newData = null;
+export const wxConfig = async (argjsApiList, callback) => {
+  const isWechat = getCurrentContext() === "wechat";
+  if (!isWechat) {
+    return false;
+  }
+  // 登录时间超过1小时50分调用wxConfig会重新获取签名data,或主动清除data
+  if (newData && newData.startTime + 110 * 60 * 1000 <= new Date().getTime()) {
+    newData = null;
+  }
+  if (!newData) {
+    let { data } = await sdkwxConfig();
+    newData = { ...data, startTime: new Date().getTime() };
+  }
+  // let {appid, signature, timestamp, nonceStr} = store.getters.eWechatInfo
+
+  console.log("开始初始化微信参数,入参为--------" + JSON.stringify(newData));
+  argjsApiList = argjsApiList || [];
+  window.wx.config({
+    beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
+    debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
+    appId: newData.appId, // 必填,企业微信的corpID
+    timestamp: newData.timestamp, // 必填,生成签名的时间戳
+    nonceStr: newData.nonceStr, // 必填,生成签名的随机串
+    signature: newData.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
+    jsApiList: [...jsApiList, ...argjsApiList] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
+  });
+  window.wx.ready(function(data) {
+    console.log("初始化微信config成功------" + JSON.stringify(data));
+    window.wx.hideMenuItems({
+      menuList: [
+        //分享到朋友圈:
+        "menuItem:share:timeline",
+        //分享到QQ:
+        "menuItem:share:qq",
+        //分享到Weibo:
+        "menuItem:share:weiboApp",
+        //收藏: "menuItem:favorite"
+        //分享到FB:
+        "menuItem:share:facebook",
+        //分享到 QQ 空间
+        "menuItem:share:QZone",
+
+        // 以下为保护类
+        //编辑标签:
+        "menuItem:editTag",
+        //删除:
+        "menuItem:delete",
+        //复制链接:
+        "menuItem:copyUrl",
+        //原网页:
+        "menuItem:originPage",
+        //阅读模式:
+        "menuItem:readMode",
+        //在QQ浏览器中打开:
+        "menuItem:openWithQQBrowser",
+        //在Safari中打开:
+        "menuItem:openWithSafari",
+        //邮件:
+        "menuItem:share:email",
+        //一些特殊公众号:
+        "menuItem:share:brand"
+      ] // 要隐藏的菜单项,只能隐藏“传播类”和“保护类”按钮,所有menu项见附录3
+    });
+    // 展示按钮
+    window.wx.showMenuItems({
+      menuList: ["menuItem:share:appMessage"] // 要显示的菜单项,所有menu项见附录3
+    });
+    callback && callback();
+    store.dispatch("setEwechatInfo", {
+      eWxConfigStatus: true,
+      startTime: newData.startTime
+    });
+  });
+  window.wx.error(function(err) {
+    //通过error接口处理失败验证
+    console.log("初始化微信config失败--------" + JSON.stringify(err));
+    // config信息验证失败会执行error
+    store.dispatch("setEwechatInfo", {
+      eWxConfigStatus: false,
+      startTime: newData.startTime
+    });
+  });
+};
+
+const wechatOnShare = shareData => {
+  if (!wx) return;
+  // 展示按钮
+  wx.showMenuItems({
+    menuList: ["menuItem:share:appMessage"] // 要显示的菜单项,所有menu项见附录3
+  });
+  let wxVersion = navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i)[1];
+  if (wxVersion >= "6.7.2") {
+    // 1.4以上版本sdk, 微信版本6.7.2以上
+    wx.updateAppMessageShareData({
+      title: shareData.title, // 分享标题
+      desc: shareData.desc, // 分享描述
+      link: shareData.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
+      imgUrl: shareData.imgUrl, // 分享图标
+      success: function() {
+        console.log("分享成功" + JSON.stringify(shareData));
+        // 设置成功
+      }
+    });
+    return false;
+  }
+  // 即将废弃
+  wx.onMenuShareAppMessage({
+    title: shareData.title, // 分享标题
+    desc: shareData.desc, // 分享描述
+    link: shareData.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
+    imgUrl: shareData.imgUrl, // 分享图标
+    type: "", // 分享类型,music、video或link,不填默认为link
+    dataUrl: "", // 如果type是music或video,则要提供数据链接,默认为空
+    success: function() {
+      // 用户点击了分享后执行的回调函数
+      console.log("分享成功-老得分享方法" + JSON.stringify(shareData));
+    }
+  });
+};

+ 57 - 0
src/common/utils/webViewBridge/base/WebViewBridge.js

@@ -0,0 +1,57 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+
+setupWebViewJavascriptBridge(function (bridge) {
+  window.bridgeObject = bridge;
+});
+
+export function setupWebViewJavascriptBridge(callback) {
+  var u = navigator.userAgent;
+
+  if (u.indexOf("Android") > -1 || u.indexOf("Linux") > -1) {
+    setupAndroidWebViewJavascriptBridge(function (bridge) {
+      callback(bridge);
+    });
+  } else {
+    setupiOSWebViewJavascriptBridge(function (bridge) {
+      callback(bridge);
+    });
+  }
+}
+
+//Setting ios WebViewJavascriptBridge
+function setupiOSWebViewJavascriptBridge(callback) {
+  if (window.WebViewJavascriptBridge) {
+    return callback(WebViewJavascriptBridge);
+  }
+  if (window.WVJBCallbacks) {
+    return window.WVJBCallbacks.push(callback);
+  }
+
+  window.WVJBCallbacks = [callback];
+  var WVJBIframe = document.createElement("iframe");
+  WVJBIframe.style.display = "none";
+  WVJBIframe.src = "wvjbscheme://__BRIDGE_LOADED__";
+  document.documentElement.appendChild(WVJBIframe);
+  setTimeout(function () {
+    document.documentElement.removeChild(WVJBIframe);
+  }, 0);
+}
+/**
+ * Setting Android WebViewJavascriptBridge
+ */
+function setupAndroidWebViewJavascriptBridge(callback) {
+  if (window.WebViewJavascriptBridge) {
+    callback(window.WebViewJavascriptBridge);
+  } else {
+    document.addEventListener(
+      "WebViewJavascriptBridgeReady",
+      function () {
+        callback(window.WebViewJavascriptBridge);
+      },
+      false,
+    );
+  }
+}

+ 26 - 0
src/common/utils/webViewBridge/index.js

@@ -0,0 +1,26 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+import WebViewBridge from "./base/WebViewBridge";
+import * as PDFPlugin from "./plugins/PDFPlugin";
+import * as OCRPlugin from "./plugins/OCRPlugin";
+import * as FacePlugin from "./plugins/FacePlugin";
+import * as SharePlugin from "./plugins/SharePlugin";
+import * as JpushPlugin from "./plugins/JpushPlugin";
+import * as LocationPlugin from "./plugins/LocationPlugin";
+import * as DevicePlugin from "./plugins/DevicePlugin";
+import * as ImagePickerPlugin from "./plugins/ImagePickerPlugin";
+import * as DJSettingPlugin from "./plugins/DJSettingPlugin";
+
+export {
+  PDFPlugin, //PDF插件
+  OCRPlugin, //OCR插件
+  FacePlugin, //人脸识别插件
+  SharePlugin, //分享插件
+  JpushPlugin, //推送插件
+  LocationPlugin, //定位插件
+  DevicePlugin, //设备信息插件
+  ImagePickerPlugin, //相机相册插件
+  DJSettingPlugin, //壳设置插件
+};

+ 14 - 0
src/common/utils/webViewBridge/plugins/DJSettingPlugin.js

@@ -0,0 +1,14 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+
+/**
+ * 设置头部颜色
+ * @param {*} jsonObject
+ * headColor 头部颜色 示例:d34115
+ */
+export function setHeadColor(jsonObject) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("setHeadColor", JSON.stringify(jsonObject), function (responseData) {});
+}

+ 23 - 0
src/common/utils/webViewBridge/plugins/DevicePlugin.js

@@ -0,0 +1,23 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+/**
+ * 定位
+ * @param {*} callBack 成功回调函数
+ * 返回参数:
+ * platform  DJDD 代表壳子
+ * system 系统 ios android
+ * systemVersion 系统版本
+ * appVersion app版本号
+ */
+export function getDeviceInfo(callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("getDeviceInfo", "", function (responseData) {
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}

+ 27 - 0
src/common/utils/webViewBridge/plugins/FacePlugin.js

@@ -0,0 +1,27 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+/**
+  * 活体检测
+  * @param {*} jsonObject json对象  或者 空字符串
+  * @param {*} callBack 成功回调函数 
+    返回参数:
+    code 是否成功 0 失败 1 成功
+    image_env 最清晰的一张全图
+    image_best 最清晰的一张半图
+    image_action1 第一个动作的图 本地相对路径
+    image_action2 第二个动作的图 本地相对路径
+    image_action3 第三个动作的图 本地相对路径
+    delta 加密数据
+  */
+export function startFaceDistinguish(jsonObject, callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("startFaceDistinguish", JSON.stringify(jsonObject), function (responseData) {
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}

+ 21 - 0
src/common/utils/webViewBridge/plugins/ImagePickerPlugin.js

@@ -0,0 +1,21 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+/**
+ * 根据 宽高 获取图片
+ * @param {*} callBack 成功回调函数
+ * 返回参数:
+ * width  宽度 类型 字符串
+ * height 高度  类型 字符串
+ */
+export function getCustomCropImage(jsonObject, callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("getCustomCropImage", JSON.stringify(jsonObject), function (responseData) {
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}

+ 18 - 0
src/common/utils/webViewBridge/plugins/JpushPlugin.js

@@ -0,0 +1,18 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+
+/**
+  * 推送绑定
+  * @param {*} jsonObject json对象  或者 空字符串
+    userName 用户唯一标示
+  * @param {*} callBack 成功回调函数
+  */
+
+export function bindJpush(jsonObject, callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("bindJpush", JSON.stringify(jsonObject), function (responseData) {
+    callBack(responseData);
+  });
+}

+ 27 - 0
src/common/utils/webViewBridge/plugins/LocationPlugin.js

@@ -0,0 +1,27 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+
+/**
+ * 定位   
+ * @param {*} callBack 成功回调函数
+ * "latitude"://纬度
+"longitude"://经度
+"formattedAddress"//详细地址
+"provice"//省
+"city"//市
+"district"//区
+"street"//街道
+"streetNumber"//街道号
+ */
+export function getLocation(callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("getLocation", "", function (responseData) {
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}

+ 24 - 0
src/common/utils/webViewBridge/plugins/OCRPlugin.js

@@ -0,0 +1,24 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+
+/**
+  * OCR识别
+  * @param {*} jsonObject json对象  或者 空字符串
+  * @param {*} callBack 成功回调函数
+  *返回参数:
+    code 是否成功 0 失败 1 成功
+    msg:失败原因
+    resultData 识别返回的信息
+  */
+export function getOCRData(jsonObject, callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("getOCRData", JSON.stringify(jsonObject), function (responseData) {
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}

+ 42 - 0
src/common/utils/webViewBridge/plugins/PDFPlugin.js

@@ -0,0 +1,42 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+
+/**
+ * 打开PDF
+ * @param {*} jsonObject json对象  或者 空字符串
+   pdfUrl:PDF链接
+   pdfName:PDF名字
+   pdfId:PDF id
+ * @param {*} callBack 成功回调函数
+ */
+export function openPdf(jsonObject, callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("openPDF", JSON.stringify(jsonObject), function (responseData) {
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}
+
+/**
+ * 生成PDF
+ * @param {*} jsonObject json对象  或者 空字符串
+   placeholder:PDF水印
+ * @param {*} callBack 成功回调函数
+ 返回参数
+  pdfFilePath :pdf路径
+ */
+export function generatePDF(jsonObject, callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("generatePDF", JSON.stringify(jsonObject), function (responseData) {
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}

+ 25 - 0
src/common/utils/webViewBridge/plugins/SharePlugin.js

@@ -0,0 +1,25 @@
+/*
+ * @Author: tulin
+ * @LastEditors  : tulin
+ */
+
+/**
+  * 分享到微信
+  * @param {*} jsonObject json对象  或者 空字符串
+  title:'分享标题',
+  content:'分享内容',
+  url:,//分享链接
+  image://图片url完整地址
+  * @param {*} callBack 成功回调函数
+  */
+export function shareToWechat(jsonObject, callBack) {
+  if (!window.bridgeObject) return;
+  window.bridgeObject.callHandler("shareToWechat", JSON.stringify(jsonObject), function (responseData) {
+    // callBack(responseData);
+    if (responseData) {
+      callBack(JSON.parse(responseData));
+    } else {
+      callBack("");
+    }
+  });
+}

+ 157 - 0
src/common/validate/index.js

@@ -0,0 +1,157 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec 表单验证
+ */
+
+/**
+ * 表单验证
+ */
+import {
+  // 姓名
+  validName,
+  // 手机号
+  validateMobile,
+  // 邮箱
+  validateEmail,
+  // 金额
+  validateMoney,
+  // 数字
+  validateNumber,
+  // 身份证
+  validateIDCard,
+  // 座机
+  validateLandLine
+} from "./validate";
+
+/**
+ * 非空校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkNull(rule, value, callback) {
+  if (
+    String(value).replace(/^\s+|\s+$/gm, "") === "" ||
+    (value instanceof Array && value[0] === "") ||
+    value === null ||
+    value === undefined
+  ) {
+    callback(new Error(rule.message || "内容不能为空!"));
+  } else {
+    callback();
+  }
+}
+
+/**
+ * 姓名校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ * @dec 姓名不能在2,10未
+ */
+export function checkName(rule, value, callback) {
+  if (value && !validName(value).status) {
+    callback(new Error(rule.message || validName(value).msg));
+  } else {
+    callback();
+  }
+}
+
+/**
+ * 手机号校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkMobile(rule, value, callback) {
+  if (value && !validateMobile(value).status) {
+    callback(new Error(rule.message || validateMobile(value).msg));
+  } else {
+    callback();
+  }
+}
+
+/**
+ * 邮箱校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkEmail(rule, value, callback) {
+  if (value && !validateEmail(value).status) {
+    callback(new Error(rule.message || validateEmail(value).msg));
+  } else {
+    callback();
+  }
+}
+
+/**
+ * 金额校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkMoney(rule, value, callback) {
+  if (value && !validateMoney(value).status) {
+    callback(new Error(rule.message || validateMoney(value).msg));
+  } else {
+    callback();
+  }
+}
+/**
+ * 身份证校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkIDCard(rule, value, callback) {
+  if (value && !validateIDCard(value).status) {
+    callback(new Error(rule.message || validateIDCard(value).msg));
+  } else {
+    callback();
+  }
+}
+
+/**
+ * 数字校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkNum(rule, value, callback) {
+  if (value && !validateNumber(value).status) {
+    callback(new Error(rule.message || validateNumber(value).msg));
+  } else {
+    callback();
+  }
+}
+/**
+ * 座机校验
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkLandLine(rule, value, callback) {
+  if (value && !validateLandLine(value).status) {
+    callback(new Error(rule.message || validateLandLine(value).msg));
+  } else {
+    callback();
+  }
+}
+/**
+ * 校验小数保留位数
+ * @param  {Object} rule 规则对象
+ * @param  {Number||String} value 入参的值
+ * @param  {Function} callback 回调函数返回值
+ */
+export function checkDecimal(rule, value, callback) {
+  let reg = new RegExp(
+    `^([+ -]?(([1-9]\\d*)|(0)))([.]\\d{0,${rule.precision}})?$`
+  );
+  if (!reg.test(value)) {
+    callback(new Error(rule.message || "请输入有效的数字!"));
+  } else {
+    callback();
+  }
+}

+ 344 - 0
src/common/validate/validate.js

@@ -0,0 +1,344 @@
+/**
+ * @author 明浩
+ * @time 2020-8-14
+ * @dec 表单验证常用方法
+ */
+/*
+ * 合法uri
+ */
+export function validateURL(textval) {
+  const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
+  if (urlregex.test(textval)) {
+    return { status: true };
+  } else {
+    return { status: false };
+  }
+}
+/*
+ * 小写字母
+ */
+export function validateLowerCase(str) {
+  const reg = /^[a-z]+$/;
+  if (reg.test(str)) {
+    return { status: true };
+  } else {
+    return { status: false };
+  }
+}
+/*
+ * 大写字母
+ */
+export function validateUpperCase(str) {
+  const reg = /^[A-Z]+$/;
+  if (reg.test(str)) {
+    return { status: true };
+  } else {
+    return { status: true };
+  }
+}
+/*
+ * 大小写字母
+ */
+export function validatAlphabets(str) {
+  const reg = /^[A-Za-z]+$/;
+  if (reg.test(str)) {
+    return { status: true };
+  } else {
+    return { status: false };
+  }
+}
+/*
+ * 邮箱
+ */
+export function validateEmail(email) {
+  const re = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/;
+  // if (email.length === 0) {
+  //   return { status: false, msg: "邮箱不能为空!" };
+  // }
+  if (!re.test(email)) {
+    return { status: false, msg: "请输入有效的邮箱!" };
+  }
+  return { status: true };
+}
+/*
+ * 座机
+ */
+export function validateLandLine(landLine) {
+  const re = /^(0\d{2,3}-\d{7,8})$/;
+  if (!re.test(landLine)) {
+    return { status: false, msg: "请输入有效的座机号!例:0377-66668888" };
+  }
+  return { status: true };
+}
+/*
+ * 验证手机号
+ */
+export function validateMobile(mobile) {
+  // if (mobile.length === 0) {
+  //   return { status: false, msg: "手机号码不能为空!" };
+  // }
+  if (mobile.length !== 11) {
+    return { status: false, msg: "请输入有效的手机号码,需是11位!" };
+  }
+
+  // let myreg = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8}|(19[0-9]{1}))+\d{8})$/
+  let myreg = /^(1[0-9]{10})$/;
+  if (!myreg.test(mobile)) {
+    return { status: false, msg: "请输入有效的手机号码!" };
+  }
+  return { status: true };
+}
+/*
+ * 验证身份证
+ */
+export function validateIDCard(id) {
+  // 1 "验证通过!", 0 //校验不通过
+  let format = /^(([1][1-5])|([2][1-3])|([3][1-7])|([4][1-6])|([5][0-4])|([6][1-5])|([7][1])|([8][1-2]))\d{4}(([1][9]\d{2})|([2]\d{3}))(([0][1-9])|([1][0-2]))(([0][1-9])|([1-2][0-9])|([3][0-1]))\d{3}[0-9xX]$/;
+  // 号码规则校验
+  if (!format.test(id)) {
+    return { status: false, msg: "身份证号码不合规" };
+  }
+  // 区位码校验
+  // 出生年月日校验   前正则限制起始年份为1900;
+  let year = id.substr(6, 4); // 身份证年
+  let month = id.substr(10, 2); // 身份证月
+  let date = id.substr(12, 2); // 身份证日
+  let time = Date.parse(month + "-" + date + "-" + year); // 身份证日期时间戳date
+  let nowTime = Date.parse(new Date()); // 当前时间戳
+  let dates = new Date(year, month, 0).getDate(); // 身份证当月天数
+  if (time > nowTime || date > dates) {
+    return { status: false, msg: "出生日期不合规" };
+  }
+  // 校验码判断
+  let c = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; // 系数
+  let b = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"]; // 校验码对照表
+  let idArray = id.split("");
+  let sum = 0;
+  for (let k = 0; k < 17; k++) {
+    sum += parseInt(idArray[k]) * parseInt(c[k]);
+  }
+  if (idArray[17].toUpperCase() !== b[sum % 11].toUpperCase()) {
+    return { status: false, msg: "身份证校验码不合规" };
+  }
+  return { status: true, msg: "校验通过" };
+}
+/*
+ * 是否为正整数
+ */
+export function validateInteger(val) {
+  let regNum = /^[1-9]\d*$/; // 正整数
+  if (regNum.test(val)) {
+    return { status: true };
+  } else {
+    return { status: false, msg: "必须为正整数" };
+  }
+}
+/*
+ * 是否为整数 (包括正负)
+ */
+export function validateInt(val) {
+  let regNum = /^(0|[1-9][0-9]*|-[1-9][0-9]*)$/; // 整数包括正负
+  if (regNum.test(val)) {
+    return { status: true };
+  } else {
+    return { status: false, msg: "必须为整数" };
+  }
+}
+/*
+ * 是否为数字
+ */
+export function validateNumber(val) {
+  let n = Number(val);
+  if (!isNaN(n)) {
+    return { status: true };
+  } else {
+    return { status: false, msg: "必须为数字" };
+  }
+}
+/*
+ * 匹配由数字和26个英文字母组成的字符串
+ */
+export function validateUpperCaseNumber(str) {
+  if (str.length === 0) {
+    return { status: false, msg: "不能为空!" };
+  }
+  let myreg = /^[A-Za-z0-9]+$/;
+  if (!myreg.test(str)) {
+    return { status: false, msg: "请输入正确的格式!" };
+  }
+  return { status: true };
+}
+/*
+ * 验证金额
+ */
+export function validateMoney(obj) {
+  let reg = /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/;
+  if (!reg.test(obj)) {
+    return { status: false, msg: "请输入有效的金额!" };
+  }
+  return { status: true };
+}
+/*
+ * 验证最多保留两位小数的正数
+ */
+export function validateDecimal(num) {
+  let reg = /^\d+(?:\.\d{1,2})?$/;
+  if (!reg.test(num)) {
+    return { status: false, msg: "请输入有效的数字!" };
+  }
+  return { status: true };
+}
+/*
+ * 验证最多保留两位小数的正数或负数
+ */
+export function validateDecimals(num) {
+  let reg = /^([+ -]?(([1-9]\d*)|(0)))([.]\d{0,2})?$/;
+  if (!reg.test(num)) {
+    return { status: false, msg: "请输入有效的数字!" };
+  }
+  return { status: true };
+}
+
+/**
+ * 是否正确的URL
+ * @param {string} url
+ * @returns {Boolean}
+ */
+export function validURL(url) {
+  const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
+  if (!reg.test(url)) {
+    return { status: false, msg: "请输入正确的URL!" };
+  }
+  return { status: true };
+}
+
+/**
+ * 验证姓名
+ */
+export function checkUseName(name) {
+  const reg = /^(([a-zA-Z+\s\\.?\\·?a-zA-Z+]{2,15}$)|([\u4e00-\u9fa5+\\·?\u4e00-\u9fa5+]{2,15}$))/;
+  if (!reg.test(name)) {
+    return { status: false, msg: "请输入正确的姓名!" };
+  }
+  return { status: true };
+}
+
+/**
+ * 验证用户姓名
+ * 仅是中文或者仅是英文,不能混合,英文不少于4个字符可以包含空格,中文不少于2个字符不包含空格
+ * @author wfyuan 2020-3-2
+ */
+export function validName(name) {
+  if (!window.isEmpty(name)) {
+    let num = /[1-9]+/;
+    let en = /[A-Za-z]+/;
+    let ch = /[\u4e00-\u9fa5·]+/;
+    let num_res = name.search(num);
+    let en_res = name.search(en);
+    let ch_res = name.search(ch);
+    if (en_res >= 0 || ch_res >= 0) {
+      if (en_res >= 0 && ch_res >= 0) {
+        return { status: false, msg: "不能既有中文又有英文!" };
+      } else if (
+        en_res == -1 &&
+        ch_res >= 0 &&
+        (name.length < 1 || name.length > 15 || /\s+/.test(name))
+      ) {
+        //
+        return {
+          status: false,
+          msg: "中文长度大于1且小于15且不能包含空格!"
+        };
+      } else if (num_res >= 0) {
+        return { status: false, msg: "不能包含数字!" };
+        //
+      } else if (
+        en_res >= 0 &&
+        ch_res == -1 &&
+        (name.length < 3 || name.length > 30)
+      ) {
+        return { status: false, msg: "英文长度大于3且小于30!" };
+      } else {
+        return { status: true };
+      }
+    } else {
+      return { status: false, msg: "请输入正确姓名!" };
+    }
+  } else {
+    return { status: true };
+  }
+}
+
+/**
+ * 验证是否含有特殊符号或者表情
+ */
+export function isEmojiCharacter(string) {
+  const reg = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/;
+  if (!reg.test(string)) {
+    return { status: false, msg: "输入内容不能含有特殊符号或表情!" };
+  }
+  return { status: true };
+}
+
+/**
+ * 验证是否含有特殊符号或者表情
+ */
+export function isEmojiCharacterAnother(substring) {
+  if (substring) {
+    let reg = new RegExp("[~#^$@%&!?%*]", "g");
+    if (substring.match(reg)) {
+      return true;
+    }
+    for (let i = 0; i < substring.length; i++) {
+      let hs = substring.charCodeAt(i);
+      if (0xd800 <= hs && hs <= 0xdbff) {
+        if (substring.length > 1) {
+          let ls = substring.charCodeAt(i + 1);
+          let uc = (hs - 0xd800) * 0x400 + (ls - 0xdc00) + 0x10000;
+          if (0x1d000 <= uc && uc <= 0x1f77f) {
+            return true;
+          }
+        }
+      } else if (substring.length > 1) {
+        let ls = substring.charCodeAt(i + 1);
+        if (ls == 0x20e3) {
+          return true;
+        }
+      } else {
+        if (0x2100 <= hs && hs <= 0x27ff) {
+          return true;
+        } else if (0x2b05 <= hs && hs <= 0x2b07) {
+          return true;
+        } else if (0x2934 <= hs && hs <= 0x2935) {
+          return true;
+        } else if (0x3297 <= hs && hs <= 0x3299) {
+          return true;
+        } else if (
+          hs == 0xa9 ||
+          hs == 0xae ||
+          hs == 0x303d ||
+          hs == 0x3030 ||
+          hs == 0x2b55 ||
+          hs == 0x2b1c ||
+          hs == 0x2b1b ||
+          hs == 0x2b50
+        ) {
+          return true;
+        }
+      }
+    }
+  }
+}
+
+/**
+ * 验证银行卡号
+ * @author: wangyuhong 2020-03-03
+ * @action: boolean
+ */
+export function checkBankNumber(number) {
+  const reg = /^[0-9]{15,20}$/.test(number);
+  if (!reg.test(number)) {
+    return { status: false, msg: "请输入正确的银行卡号!" };
+  }
+  return { status: true };
+}

+ 6 - 0
src/components/DrawFlow/index.js

@@ -0,0 +1,6 @@
+import DrawFlow from "./src/DrawFlow";
+/* istanbul ignore next */
+DrawFlow.install = function(Vue) {
+  Vue.component(DrawFlow.name, DrawFlow);
+};
+export default DrawFlow;

+ 354 - 0
src/components/DrawFlow/src/DrawFlow.vue

@@ -0,0 +1,354 @@
+<!--
+* @author 肖阳
+* @time 2020-9-10
+* @dec 生成流程绘制 基本节点(审批 抄送)组装逻辑
+*  组装
+-->
+<script>
+import AddNodeBtn from "@/components/DrawFlow/src/components/AddNodeBtn";
+import RowFactory from "@/components/DrawFlow/src/components/DrawRow/FactoryRow";
+// import EventBus from "@/components/ApprovalProcess/ProcessDesign/components/EventBus/EventBus";
+
+import {
+  RowNode,
+  ConditionNode,
+  CopyNode
+} from "./components/NodeConfigFactory/NodeFactory";
+import { deepClone } from "@/common/utils";
+import FlowFactory from "./components/factory";
+import { HashCode, transToTreeDat, getPidArr } from "./utils";
+import FlowNode from "@/components/DrawFlow/src/components/DrawRow/FlowNode";
+export default {
+  name: "FactoryDrawFlow",
+  components: {
+    AddNodeBtn,
+    FlowNode
+  },
+  created() {
+    this.init();
+    // this.creatBusNodeChange();  // 可用事件派发
+  },
+  props: {
+    FlowConfig: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  data() {
+    return {
+      rectTypeDic: {
+        1: "审批节点",
+        2: "规则节点",
+        3: "抄送人",
+        4: "开始节点"
+      },
+      isColList: ["3", "5"],
+      selfConfig: null,
+      currentNode: null
+    };
+  },
+  methods: {
+    // 节点数据变化事件
+    nodeChange(node) {
+      console.log(node, "node");
+      this.currentNode.title = node.title;
+      this.selfConfig.forEach(i => {
+        if (i.id === this.currentNode.id) {
+          i.data = node.data;
+          i.title = node.title;
+        }
+      });
+      this.$forceUpdate();
+    },
+    /**
+     * 添加条件框
+     */
+    addBranch(node) {
+      let newNode = new CopyNode(node[0]);
+      console.log(newNode, node, "newNode");
+      this.selfConfig = this.selfConfig.concat([newNode]);
+    },
+    creatBusNodeChange() {
+      // EventBus.$on("nodeChange", this.nodeChange);
+    },
+    /**
+     * 获取传参数据结构
+     */
+    getNodeArr() {
+      let list = JSON.parse(JSON.stringify(this.selfConfig));
+      getPidArr(list);
+      return list;
+    },
+    /**
+     * 初始化 数据私有化
+     */
+    init() {
+      this.selfConfig = deepClone(this.FlowConfig);
+    },
+    /**
+     *  @param data  源数组一维数组
+     *  @requires  tree 二维数组
+     */
+    transformTree(data) {
+      return transToTreeDat(data);
+    },
+    clickSelectBox(nextNode) {
+      let { node, selfConfig } = this.getNodeFactory(nextNode);
+      this.selfConfig = selfConfig.concat(node);
+      console.log(node, "nodenode");
+    },
+    /**
+     * 根据isRow去判断row或者rol
+     */
+    getNodeFactory(nextNode) {
+      if (!nextNode.isRow) {
+        let { node, selfConfig } = this.getColNode(nextNode);
+        return { node, selfConfig };
+      } else {
+        let { node, selfConfig } = this.getRowNode(nextNode);
+        return { node, selfConfig };
+      }
+    },
+    /**
+     * 获取row节点
+     */
+    getRowNode(nextNode) {
+      let node = [new RowNode(nextNode)];
+      let selfConfig = this.repickArr(node[0]);
+      this.clickNode(node[0]);
+      return { node, selfConfig };
+    },
+    /**
+     * 获取col节点
+     */
+    getColNode(nextNode) {
+      let groupId = HashCode();
+      let node = [
+        new ConditionNode({ groupId, ...nextNode }),
+        new ConditionNode({ groupId, ...nextNode })
+      ];
+      let repickConfig = {
+        groupId: node[0].groupPid,
+        id: node[0].id
+      };
+      let selfConfig = this.repickArr(repickConfig);
+      this.locationScroll();
+      return { node, selfConfig };
+    },
+    /**
+     * 定位滚动条
+     */
+    locationScroll() {
+      // window.location.hash = ".bottom-right-cover-line";
+      let el = document.getElementsByClassName("dingflow-design")[0];
+      setTimeout(() => {
+        el.scrollLeft = el.scrollWidth - el.clientWidth + 340;
+      }, 0);
+    },
+    /**
+     * 重定位数组
+     */
+    repickArr(repickConfig) {
+      let selfConfig = JSON.parse(JSON.stringify(this.selfConfig));
+      selfConfig.forEach(i => {
+        if (i.isRow) {
+          if (i.groupId === repickConfig.groupId) {
+            i.groupId = repickConfig.id;
+          }
+        } else {
+          if (i.groupPid === repickConfig.groupId) {
+            i.groupPid = repickConfig.id;
+          }
+        }
+      });
+      return selfConfig;
+    },
+    // 点击节点
+    clickNode(nodeConfig) {
+      this.currentNode = nodeConfig;
+      this.$emit("clickNode", nodeConfig);
+    },
+    //点击关闭节点
+    closeNode(node) {
+      let repickConfig = {};
+      if (node.isRow) {
+        repickConfig.groupId = node.groupId;
+        repickConfig.id = node.id;
+        let selfConfig = JSON.parse(JSON.stringify(this.selfConfig));
+        this.selfConfig = this.deleteNode(selfConfig, node);
+      } else {
+        repickConfig.groupId = node.groupPid;
+        repickConfig.id = node.groupId;
+        this.selfConfig = this.deleteColNode(node);
+      }
+      this.selfConfig = this.repickDeleteArr(repickConfig);
+    },
+    // 删除节点
+    deleteNode(selfConfig, node) {
+      selfConfig = selfConfig.map(i => i.id !== node.id && i).filter(Boolean);
+      return selfConfig;
+    },
+    //单独删除col下node
+    deleteColNode(node) {
+      let selfConfig = JSON.parse(JSON.stringify(this.selfConfig));
+      let nodeArr = selfConfig.filter(
+        i => i.groupId === node.groupId && !i.isRow
+      );
+      let deleteArr = [];
+      if (nodeArr.length > 2) {
+        //递归删除所有关联子节点
+        deleteArr = [node];
+        this.deleteLoop(selfConfig, node, deleteArr);
+      } else {
+        //删除整个group
+        let allCol = selfConfig
+          .map(i => i.groupId === node.groupId && !i.isRow && i)
+          .filter(Boolean);
+        deleteArr = allCol;
+        console.log(deleteArr, node, "allCol");
+        allCol.forEach(i => {
+          this.deleteLoop(selfConfig, i, deleteArr);
+        });
+      }
+      deleteArr.forEach(i => {
+        selfConfig = this.deleteNode(selfConfig, i);
+      });
+      return selfConfig;
+    },
+    // 循环遍历删除组下关联节点
+    deleteLoop(selfConfig, node, deleteArr) {
+      console.log("传入的deleteArr", deleteArr);
+      let currentDeleteArr = selfConfig.filter(i => {
+        if (i.isRow) {
+          return i.groupId === node.id;
+        } else {
+          return i.groupPid === node.id;
+        }
+      });
+      if (currentDeleteArr.length) {
+        currentDeleteArr.forEach(i => {
+          deleteArr.push(i);
+          this.deleteLoop(selfConfig, i, deleteArr);
+        });
+      } else {
+        return;
+      }
+    },
+    //判断是否是row
+    judgeNodeIsRow(node) {
+      return node.isRow;
+    },
+    //删除后重组数组
+    repickDeleteArr(repickConfig) {
+      let selfConfig = JSON.parse(JSON.stringify(this.selfConfig));
+      selfConfig.forEach(i => {
+        if (i.isRow && i.groupId === repickConfig.id) {
+          i.groupId = repickConfig.groupId;
+        } else if (i.groupPid === repickConfig.id) {
+          i.groupPid = repickConfig.groupId;
+        }
+      });
+      return selfConfig;
+    },
+    //绘制body
+    drawBody(h, node) {
+      if (node.childNode) {
+        return FlowFactory.getFactory.bind(this, h, node.childNode)();
+      } else {
+        return <div></div>;
+      }
+    }
+  },
+  destroyed() {
+    // EventBus.$off("nodeChange");
+  },
+  render(h) {
+    // this.init();
+    let FlowConfig = this.selfConfig;
+    FlowConfig = this.transformTree(FlowConfig);
+    const root = JSON.parse(JSON.stringify(this.selfConfig[0]));
+    console.log(this.selfConfig, root);
+    return (
+      <div class="design-engine">
+        <div class="dingflow-design">
+          <div class="ie-polyfill-container">
+            <div class="box-scale" id="box-scale">
+              {RowFactory.nodeWrapRender.bind(this, h, root)()}
+              {this.drawBody(h, FlowConfig[0])}
+              <div class="end-node">
+                <div class="end-node-circle"></div>
+                <div class="end-node-text">流程结束</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+};
+</script>
+
+<style scoped lang="less">
+.design-engine {
+  position: relative;
+  height: 100%;
+  .dingflow-design {
+    width: 100%;
+    // background-color: #f5f5f7;
+    overflow: auto;
+    height: 100%;
+    position: relative;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    top: 0;
+    /deep/ .ant-popover-arrow {
+      display: none !important;
+    }
+    .ie-polyfill-container {
+      display: -ms-grid;
+      -ms-grid-columns: min-content;
+      .box-scale {
+        transform: scale(1);
+        display: inline-block;
+        position: relative;
+        width: 100%;
+        padding: 54.5px 0;
+        -webkit-box-align: start;
+        -ms-flex-align: start;
+        align-items: flex-start;
+        -webkit-box-pack: center;
+        -ms-flex-pack: center;
+        justify-content: center;
+        -ms-flex-wrap: wrap;
+        flex-wrap: wrap;
+        min-width: -webkit-min-content;
+        min-width: -moz-min-content;
+        min-width: min-content;
+        // background-color: #f5f5f7;
+        -webkit-transform-origin: 0 0 0;
+        transform-origin: 50% 0px 0px;
+        .end-node {
+          border-radius: 50%;
+          font-size: 14px;
+          color: rgba(25, 31, 37, 0.4);
+          text-align: left;
+          .end-node-circle {
+            width: 10px;
+            height: 10px;
+            margin: auto;
+            border-radius: 50%;
+            background: #dbdcdc;
+          }
+          .end-node-text {
+            margin-top: 5px;
+            text-align: center;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 128 - 0
src/components/DrawFlow/src/components/AddNodeBtn.vue

@@ -0,0 +1,128 @@
+<!--
+* @author 肖阳
+* @time 2020-9-10
+* @dec 多列生成流程中 添加节点的图标
+-->
+<script>
+import AddBox from "@/components/DrawFlow/src/components/DrawAddSelectBox/DrawAddBox.vue";
+export default {
+  name: "AddNodeBtn",
+  components: {
+    AddBox
+  },
+  props: {
+    belongToNode: {
+      type: Number || String,
+      default() {
+        return "1";
+      }
+    }
+  },
+  data() {
+    return {
+      popoVisible: false
+    };
+  },
+  methods: {
+    getPopupContainer(trigger) {
+      console.log(trigger.parentElement);
+      let el = document.getElementsByClassName("dingflow-design")[0];
+      return el;
+    },
+    clickSelectBox(nextNode) {
+      this.popoVisible = false;
+      this.$emit("clickSelectBox", nextNode);
+    }
+  },
+  mounted() {},
+  render() {
+    const node = this.belongToNode;
+    return (
+      <div class="add-node-btn-box">
+        <div class="add-node-btn">
+          <a-popover
+            auto-adjust-overflow
+            arrow-point-at-center
+            placement="rightTop"
+            v-model={this.popoVisible}
+            trigger="click"
+          >
+            <template slot="content">
+              <add-box
+                {...{
+                  props: { nodeConfig: node },
+                  on: {
+                    clickSelectBox: this.clickSelectBox
+                  }
+                }}
+              ></add-box>
+            </template>
+            <button class="btn" type="button">
+              <a-icon type="plus" class="iconfont" />
+            </button>
+          </a-popover>
+        </div>
+      </div>
+    );
+  }
+};
+</script>
+
+<style scoped lang="less">
+.add-node-btn-box {
+  width: 240px;
+  display: -webkit-inline-box;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  -ms-flex-negative: 0;
+  flex-shrink: 0;
+  -webkit-box-flex: 1;
+  -ms-flex-positive: 1;
+  position: relative;
+  &::before {
+    content: "";
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 0;
+    margin: auto;
+    width: 2px;
+    height: 100%;
+    background-color: #cacaca;
+  }
+  .add-node-btn {
+    user-select: none;
+    width: 240px;
+    padding: 20px 0px 32px;
+    display: flex;
+    -webkit-box-pack: center;
+    justify-content: center;
+    flex-shrink: 0;
+    -webkit-box-flex: 1;
+    flex-grow: 1;
+    .btn {
+      outline: none;
+      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
+      width: 30px;
+      height: 30px;
+      background: #3296fa;
+      border-radius: 50%;
+      position: relative;
+      border: none;
+      line-height: 30px;
+      -webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+      &:hover {
+        transform: scale(1.3);
+        box-shadow: 0 13px 27px 0 rgba(0, 0, 0, 0.1);
+      }
+      .iconfont {
+        color: #fff;
+        font-size: 16px;
+      }
+    }
+  }
+}
+</style>

+ 96 - 0
src/components/DrawFlow/src/components/DrawAddSelectBox/DrawAddBox.vue

@@ -0,0 +1,96 @@
+<!--
+* @author 肖阳
+* @time 2020-9-10
+* @dec 添加新增节点按钮
+-->
+<script>
+/* eslint-disable no-unused-vars */
+import "./addBox.less";
+import { NextNode } from "@/components/DrawFlow/src/components/DrawAddSelectBox/NextNode";
+import { HashCode } from "../../utils";
+export default {
+  name: "AddBox",
+  props: {
+    nodeConfig: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      boxArr: [
+        {
+          type: "2",
+          value: "审批人",
+          isRow: true,
+          calssName: "approver",
+          icon: "user"
+        },
+        {
+          type: "4",
+          value: "抄送人",
+          isRow: true,
+          calssName: "notifier",
+          icon: "highlight"
+        },
+        {
+          type: "3",
+          value: "条件分支",
+          isRow: false,
+          calssName: "route",
+          icon: "sliders"
+        },
+        {
+          type: "6",
+          value: "流转至",
+          isRow: false,
+          calssName: "route",
+          icon: "sliders"
+        }
+      ]
+    };
+  },
+  methods: {
+    clickSelectBox(item) {
+      this.getNexttBox(item);
+    },
+    getNexttBox(item) {
+      const nodeConfig = this.nodeConfig;
+      if (item.value === "流转至") {
+        this.nodeConfig.isFlowTo = true;
+      }
+      let { id, prevId, type, isRow } = Object.assign(nodeConfig, item);
+      let nextNode = new NextNode({ id, prevId, type, isRow });
+      this.$emit("clickSelectBox", nextNode);
+    },
+    renderAddSBox() {
+      return (
+        <div class="add-node-popover">
+          <div class="add-node-popover-body">
+            {this.boxArr.map(item => {
+              return (
+                <a
+                  onClick={() => {
+                    this.clickSelectBox(item);
+                  }}
+                  class={["add-node-popover-item", item.calssName]}
+                >
+                  <div class="item-wrapper">
+                    <a-icon class="iconfont" type={item.icon} />
+                  </div>
+                  <span>{item.value}</span>
+                </a>
+              );
+            })}
+          </div>
+        </div>
+      );
+    }
+  },
+  render() {
+    return this.renderAddSBox();
+  }
+};
+</script>

+ 13 - 0
src/components/DrawFlow/src/components/DrawAddSelectBox/NextNode.js

@@ -0,0 +1,13 @@
+/**
+ * @author 肖阳
+ * @time 2020-9-10
+ * @dec 下一节点属性
+ */
+export class NextNode {
+  constructor({ id, prevId, type, isRow }) {
+    this.id = id;
+    this.prevId = prevId;
+    this.type = type;
+    this.isRow = isRow;
+  }
+}

+ 53 - 0
src/components/DrawFlow/src/components/DrawAddSelectBox/addBox.less

@@ -0,0 +1,53 @@
+.add-node-popover-body {
+    max-width: 336px;
+    .add-node-popover-item {
+        display: inline-flex;
+        align-items: center;
+        cursor: pointer;
+        color: #191F25 !important;
+        margin-right: 8px;
+        margin-bottom: 8px;
+        width: 160px;
+        background: rgba(17, 31, 44, 0.02);
+        padding: 8px;
+        border: 1px solid #FFFFFF;
+        border-radius: 4px;
+        .iconfont {
+            font-size: 20px;
+            line-height: 40px;
+        }
+        .item-wrapper {
+            user-select: none;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 40px;
+            height: 40px;
+            background: #FFFFFF;
+            border: 1px solid #eeeeee;
+            border-radius: 16px;
+            transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+            margin-right: 12px;
+        }
+        &:hover{
+                background: #FFFFFF;
+                border: 1px solid #ecedef;
+                box-shadow: 0 2px 8px 0 rgba(17, 31, 44, 0.08);
+        }
+    }
+    .add-node-popover-item.approver {
+        .item-wrapper {
+            color: #FF943E;
+        }
+    }
+    .add-node-popover-item.notifier {
+        .item-wrapper {
+            color: #3296FA;
+        }
+    }
+    .add-node-popover-item.route {
+        .item-wrapper {
+            color: #15BC83;
+        }
+    }
+}

+ 123 - 0
src/components/DrawFlow/src/components/DrawCol/FactoryCol.js

@@ -0,0 +1,123 @@
+/**
+ * @author 肖阳
+ * @time 2020-9-10
+ * @dec col节点工厂
+ */
+import drawFlow from "../factory";
+
+import "./layout.less";
+function branchBoxRender(h, nodeArr) {
+  let title = [];
+  if (nodeArr.isFlowTo) {
+    title = "添加流转";
+  } else {
+    title = "添加条件";
+  }
+  const colNodeArr = nodeArr.conditionNodes;
+  return (
+    <div class="branch-wrap">
+      <div class="branch-box-wrap">
+        <div class="branch-box">
+          <button onClick={() => this.addBranch(colNodeArr)} class="add-branch">
+            {title}
+          </button>
+          {colBoxRender.bind(this, h, colNodeArr)()}
+        </div>
+        <add-node-btn
+          {...{
+            props: { belongToNode: nodeArr },
+            on: { clickSelectBox: this.clickSelectBox }
+          }}
+        ></add-node-btn>
+      </div>
+    </div>
+  );
+}
+/**
+ * col-box
+ */
+function colBoxRender(h, colNodeArr) {
+  return colNodeArr.map((item, idx) => {
+    switch (idx) {
+      case 0:
+        return (
+          <div class="col-box">
+            <div class="top-left-cover-line"></div>
+            <div class="bottom-left-cover-line"></div>
+            {conditionNodeRender.bind(this, h, item)()}
+          </div>
+        );
+      case colNodeArr.length - 1:
+        return (
+          <div class="col-box">
+            {conditionNodeRender.bind(this, h, item)()}
+            <div class="top-right-cover-line"></div>
+            <div class="bottom-right-cover-line"></div>
+          </div>
+        );
+      default:
+        return (
+          <div class="col-box">{conditionNodeRender.bind(this, h, item)()}</div>
+        );
+    }
+  });
+}
+function closeNode(event, node) {
+  console.log(event, node, "colCloseNode");
+  event.stopPropagation();
+  this.closeNode(node);
+}
+function conditionNodeRender(h, node) {
+  const judegeNode = { ...node };
+  let tep = [];
+  tep.push(
+    <div class="condition-node">
+      <div class="condition-node-box">
+        <div
+          class="auto-judge node_e27d_5719"
+          onClick={() => {
+            this.clickNode(node);
+          }}
+        >
+          <div class="sort-left"></div>
+          <div class="title-wrapper">
+            <span class="editable-title">{judegeNode.title}</span>
+            <i
+              aria-label="icon: close"
+              tabindex="-1"
+              class="anticon anticon-close close"
+            >
+              <a-icon
+                type="close"
+                onClick={event => {
+                  closeNode.bind(this, event, node)();
+                }}
+              />
+            </i>
+            <span class="priority-title">
+              {judegeNode.data.priority || "123"}
+            </span>
+          </div>
+          <div class="content">{judegeNode.content}</div>
+        </div>
+        <add-node-btn
+          {...{
+            props: { belongToNode: judegeNode },
+            on: { clickSelectBox: this.clickSelectBox }
+          }}
+        ></add-node-btn>
+      </div>
+    </div>
+  );
+
+  if (node.childNode) {
+    let el = drawFlow.getFactory.bind(this, h, node.childNode)();
+    tep.push(el);
+  }
+  // let el = drawFlow.getFactory.bind(this, h, node)();
+  // tep.push(el);
+  return tep;
+}
+export default {
+  branchBoxRender
+};

+ 254 - 0
src/components/DrawFlow/src/components/DrawCol/layout.less

@@ -0,0 +1,254 @@
+
+.branch-wrap {
+    display: inline-flex;
+    width: 100%;
+    .branch-box-wrap {
+      display: flex;
+      -webkit-box-orient: vertical;
+      -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+      flex-direction: column;
+      -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+      -webkit-box-align: center;
+      -ms-flex-align: center;
+      align-items: center;
+      min-height: 270px;
+      width: 100%;
+      -ms-flex-negative: 0;
+      flex-shrink: 0;
+      .branch-box {
+        display: flex;
+        overflow: visible;
+        min-height: 180px;
+        height: auto;
+        border-bottom: 2px solid #cccccc;
+        border-top: 2px solid #cccccc;
+        position: relative;
+        margin-top: 15px;
+        .add-branch {
+          border: none;
+          outline: none;
+          user-select: none;
+          justify-content: center;
+          font-size: 12px;
+          padding: 0 10px;
+          height: 30px;
+          line-height: 30px;
+          border-radius: 15px;
+          color: #0089ff;
+          background: #fff;
+          box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
+          position: absolute;
+          top: -16px;
+          left: 50%;
+          transform: translateX(-50%);
+          transform-origin: center center;
+          cursor: pointer;
+          z-index: 1;
+          display: inline-flex;
+          align-items: center;
+          -webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+          transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+        }
+        .col-box {
+          background: #f0f2f5;
+          display: inline-flex;
+          -webkit-box-orient: vertical;
+          -webkit-box-direction: normal;
+          flex-direction: column;
+          -webkit-box-align: center;
+          align-items: center;
+          position: relative;
+          &::before {
+            content: "";
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            z-index: 0;
+            margin: auto;
+            width: 2px;
+            height: 100%;
+            background-color: #cacaca;
+          }
+  
+          .top-left-cover-line {
+            position: absolute;
+            height: 3px;
+            width: 50%;
+            background-color: #f0f2f5;
+            top: -2px;
+            left: -1px;
+          }
+          .bottom-left-cover-line {
+            position: absolute;
+            height: 3px;
+            width: 50%;
+            background-color: #f0f2f5;
+            bottom: -2px;
+            left: -1px;
+          }
+          .top-right-cover-line {
+            position: absolute;
+            height: 3px;
+            width: 50%;
+            background-color: #f0f2f5;
+            top: -2px;
+            right: -1px;
+          }
+          .bottom-right-cover-line {
+            position: absolute;
+            height: 3px;
+            width: 50%;
+            background-color: #f0f2f5;
+            bottom: -2px;
+            right: -1px;
+          }
+        }
+      }
+    }
+  }
+  
+.dingflow-design .auto-judge .sort-left,
+.dingflow-design .auto-judge .sort-right {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  display: none;
+  z-index: 1;
+}
+.condition-node {
+  min-height: 220px;
+  display: inline-flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  flex-direction: column;
+  -webkit-box-flex: 1;
+  .condition-node-box {
+    padding-top: 30px;
+    padding-right: 50px;
+    padding-left: 50px;
+    -webkit-box-pack: center;
+    justify-content: center;
+    display: inline-flex;
+    -webkit-box-align: center;
+    align-items: center;
+    -webkit-box-orient: vertical;
+    -webkit-box-direction: normal;
+    flex-direction: column;
+    -webkit-box-flex: 1;
+    flex-grow: 1;
+    position: relative;
+    &::before {
+      content: "";
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      margin: auto;
+      width: 2px;
+      height: 100%;
+      background-color: #cacaca;
+    }
+    .auto-judge {
+      position: relative;
+      width: 220px;
+      min-height: 72px;
+      background: #ffffff;
+      border-radius: 4px;
+      padding: 14px 19px;
+      cursor: pointer;
+      .close {
+        width: 14px;
+        height: 14px;
+        display: none;
+        position: absolute;
+        right: -2px;
+        top: -2px;
+        font-size: 14px;
+        text-align: center;
+        line-height: 20px;
+        z-index: 2;
+        color: rgba(25, 31, 37, 0.56);
+      }
+    .priority-title {
+      display: block;
+      margin-right: 10px;
+      float: right;
+      color: rgba(25, 31, 37, 0.56);
+    }
+      &:hover {
+        .close{
+          display: block;
+        }
+        .priority-title{
+          display: none;
+        }
+        &::after {
+          border: 1px solid #3296fa;
+          box-shadow: 0 0 6px 0 rgba(50, 150, 250, 0.3);
+        }
+      }
+      &::after{
+          pointer-events: none;
+          content: '';
+          position: absolute;
+          top: 0;
+          bottom: 0;
+          left: 0;
+          right: 0;
+          z-index: 2;
+          border-radius: 4px;
+          border: 1px solid transparent;
+          transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
+          box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
+      }
+      .title-wrapper {
+        position: relative;
+        font-size: 12px;
+        color: #15bc83;
+        text-align: left;
+        line-height: 16px;
+        .editable-title {
+          line-height: 15px;
+          overflow: hidden; 
+          border-bottom: dashed 1px transparent;
+          display: inline-block;
+          max-width: 120px;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          &:hover {
+            border-bottom: dashed 1px #ffffff;
+          }
+          &::before {
+            content: "";
+            position: absolute;
+            top: 0;
+            left: 0;
+            bottom: 0;
+            right: 40px;
+          }
+        }
+       
+      }
+      .content {
+        font-size: 14px;
+        color: #191f25;
+        text-align: left;
+        margin-top: 6px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: -webkit-box;
+        -webkit-line-clamp: 3;
+        -webkit-box-orient: vertical;
+      }
+      .sort-right {
+        right: 0;
+        border-left: 1px solid #f6f6f6;
+      }
+    }
+  }
+}

+ 46 - 0
src/components/DrawFlow/src/components/DrawRow/FactoryRow.js

@@ -0,0 +1,46 @@
+/**
+ * @author 肖阳
+ * @time 2020-9-10
+ * @dec row节点包裹框
+ */
+import "./row.less";
+import drawFlow from "../factory";
+export default {
+  /**
+   *
+   * @param {creatElement} h
+   * @param {Object} nodeConfig
+   */
+  nodeWrapRender(h, nodeConfig) {
+    let tep = [];
+    tep.push(
+      <div class="node-wrap">
+        <flow-node
+          {...{
+            props: { nodeConfig },
+            on: {
+              clickNode: this.clickNode,
+              closeNode: this.closeNode
+            }
+          }}
+        ></flow-node>
+        <add-node-btn
+          {...{
+            props: { belongToNode: nodeConfig },
+            on: {
+              clickSelectBox: this.clickSelectBox
+            }
+          }}
+        ></add-node-btn>
+      </div>
+    );
+    if (nodeConfig.isRoot) {
+      return tep;
+    }
+    if (nodeConfig.childNode) {
+      let el = drawFlow.getFactory.bind(this, h, nodeConfig.childNode)();
+      tep.push(el);
+    }
+    return tep;
+  }
+};

+ 195 - 0
src/components/DrawFlow/src/components/DrawRow/FlowNode.vue

@@ -0,0 +1,195 @@
+<!--
+* @author 肖阳
+* @time 2020-9-10
+* @dec 单列生成流程节点的基本节点(开始,抄送,审批)
+-->
+<script>
+export default {
+  name: "FlowNode",
+  props: {
+    nodeConfig: {
+      type: Object,
+      default() {
+        return {
+          pid: "root",
+          id: "1",
+          type: "root", // 1:发起人 2:审批人 3 抄送人 4条件框
+          title: "所有人12",
+          content: "内容2",
+          isRow: true,
+          data: {}
+        };
+      }
+    }
+  },
+  data() {
+    return {};
+  },
+  methods: {
+    clickNode(nodeConfig) {
+      this.$emit("clickNode", nodeConfig);
+    },
+    closeNode(e, nodeConfig) {
+      e.stopPropagation();
+      this.$emit("closeNode", nodeConfig);
+    }
+  },
+
+  render() {
+    const { title, content, type } = this.nodeConfig;
+    return (
+      <div
+        onClick={() => {
+          this.clickNode(this.nodeConfig);
+        }}
+        class="node-wrap-box node_sid-startevent start-node"
+      >
+        <div>
+          <div class={[type !== "1" ? "arrows" : "noArrows"]}>
+            <div class={[`node-type-${type}`, "title"]}>
+              <span class="edit-title">{title}</span>
+              <i
+                aria-label="icon: close"
+                tabindex="-1"
+                class="anticon anticon-close close"
+              >
+                <a-icon
+                  type="close"
+                  onClick={event => {
+                    this.closeNode(event, this.nodeConfig);
+                  }}
+                />
+              </i>
+            </div>
+          </div>
+          <div class="content">
+            <div class="text">{content}</div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+};
+</script>
+
+<style scoped lang="less">
+.arrows {
+  &::before {
+    content: "";
+    position: absolute;
+    top: -12px;
+    left: 50%;
+    -webkit-transform: translateX(-50%);
+    transform: translateX(-50%);
+    width: 0;
+    height: 4px;
+    border-style: solid;
+    border-width: 8px 6px 4px;
+    border-color: #cacaca transparent transparent;
+    background: #f5f5f7;
+  }
+}
+.node-wrap-box {
+  display: -webkit-inline-box;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  position: relative;
+  width: 220px;
+  min-height: 72px;
+  -ms-flex-negative: 0;
+  flex-shrink: 0;
+  background: #ffffff;
+  border-radius: 4px;
+  cursor: pointer;
+  .close {
+    display: none;
+    position: absolute;
+    right: 10px;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 20px;
+    height: 20px;
+    font-size: 14px;
+    color: #fff;
+    border-radius: 50%;
+    text-align: center;
+    line-height: 20px;
+  }
+  &:hover {
+    .close {
+      display: block;
+    }
+    &::after {
+      border: 1px solid #3296fa;
+      box-shadow: 0 0 6px 0 rgba(50, 150, 250, 0.3);
+    }
+  }
+  &::after {
+    pointer-events: none;
+    content: "";
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    z-index: 2;
+    border-radius: 4px;
+    border: 1px solid transparent;
+    transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
+    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
+  }
+  //审批
+  .node-type-1 {
+    background: rgb(87, 106, 149);
+  }
+  .node-type-2 {
+    background: rgb(255, 148, 62);
+  }
+  .node-type-4 {
+    background: rgb(50, 150, 250);
+  }
+  .title {
+    position: relative;
+    display: flex;
+    align-items: center;
+    padding-left: 16px;
+    padding-right: 30px;
+    width: 100%;
+    height: 24px;
+    line-height: 24px;
+    font-size: 12px;
+    color: #ffffff;
+    text-align: left;
+    border-radius: 4px 4px 0px 0px;
+    .edit-title {
+      line-height: 15px;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      border-bottom: dashed 1px transparent;
+      &::before {
+        content: "";
+        position: absolute;
+        top: 0;
+        left: 0;
+        bottom: 0;
+        right: 40px;
+      }
+    }
+  }
+  .content {
+    position: relative;
+    font-size: 14px;
+    padding: 16px;
+    padding-right: 30px;
+    .text {
+      display: block;
+      white-space: nowrap;
+    }
+  }
+}
+</style>

+ 19 - 0
src/components/DrawFlow/src/components/DrawRow/row.less

@@ -0,0 +1,19 @@
+.node-wrap,.flow-wrap {
+    display: inline-flex;
+    flex-direction: column;
+    -webkit-box-pack: start;
+    -ms-flex-pack: start;
+    justify-content: flex-start;
+    -webkit-box-align: center;
+    -ms-flex-align: center;
+    align-items: center;
+    -ms-flex-wrap: wrap;
+    flex-wrap: wrap;
+    -webkit-box-flex: 1;
+    -ms-flex-positive: 1;
+    width: 100%;
+    padding: 0 50px;
+    position: relative;
+  }
+
+  

+ 58 - 0
src/components/DrawFlow/src/components/NodeConfigFactory/NodeFactory.js

@@ -0,0 +1,58 @@
+/* eslint-disable no-unused-vars */
+/**
+ * @author 肖阳
+ * @time 2020-9-10
+ * @dec 各种节点类
+ */
+import { HashCode } from "../../utils";
+export class Node {
+  nodeId;
+  type;
+  childNode;
+  title = "title";
+  content = "content";
+  conditionNodes;
+  constructor({ id, type, isRow }) {
+    this.groupId = id;
+    this.id = HashCode();
+    this.type = type;
+    this.content += this.id;
+    this.isRow = isRow;
+  }
+}
+export class ConditionNode {
+  title = "title";
+  data = {};
+  constructor({ groupId, type, id, isRow }) {
+    this.id = HashCode();
+    this.groupId = groupId;
+    this.type = type;
+    this.content += this.id;
+    this.groupPid = id;
+    this.isRow = isRow;
+    if (type === "3") {
+      this.data = {
+        ruleInfo: {
+          conditions: [],
+          ruleLevel: 1,
+          ruleName: ""
+        }
+      };
+    }
+  }
+}
+export class RowNode extends Node {
+  data = {};
+  constructor({ id, type, isRow }) {
+    super({ id, type, isRow });
+  }
+}
+export class CopyNode {
+  title = "标题";
+  id = HashCode();
+  content = "内容";
+  data = { ruleInfo: {} };
+  constructor({ id, childNode, ...node }) {
+    return Object.assign(node, this);
+  }
+}

+ 19 - 0
src/components/DrawFlow/src/components/factory.js

@@ -0,0 +1,19 @@
+import RowFactory from "@/components/DrawFlow/src/components/DrawRow/FactoryRow";
+import ColFactory from "./DrawCol/FactoryCol";
+function getFactory(h, item) {
+  let tep = [];
+  if (item.type === "route") {
+    //多节点
+    tep.push(ColFactory.branchBoxRender.bind(this, h, item)());
+    if (item.childNode) {
+      tep.push(getFactory.bind(this, h, item.childNode)());
+    }
+  }
+  if (item.type !== "route") {
+    tep.push(RowFactory.nodeWrapRender.bind(this, h, item)());
+  }
+  return tep;
+}
+export default {
+  getFactory
+};

+ 30 - 0
src/components/DrawFlow/src/mixin/factory.vue

@@ -0,0 +1,30 @@
+<!--
+* @author 肖阳
+* @time 2020-9-10
+* @dec  预留接口 主要用在添加新功能 应用场景 多流程
+-->
+<script>
+import { transToTreeDat } from "../utils";
+
+export default {
+  name: "FlowFactoryMixin",
+  props: {},
+  methods: {
+    /**
+     *  @param data  源数组一维数组
+     *  @requires  tree 二维数组
+     */
+    transformTree(data) {
+      return transToTreeDat(data);
+    }
+  },
+  watch: {}
+};
+</script>
+
+<style>
+.branch-wrap {
+  display: inline-flex;
+  width: 100%;
+}
+</style>

+ 203 - 0
src/components/DrawFlow/src/utils/index.js

@@ -0,0 +1,203 @@
+/**
+ * @author 肖阳
+ * @time 2020-9-10
+ * @dec 公共方法
+ */
+// https://github.com/reduxjs/redux/blob/master/src/compose.js
+export function compose(...funcs) {
+  if (funcs.length === 0) {
+    return arg => arg;
+  }
+  if (funcs.length === 1) {
+    return funcs[0];
+  }
+  return funcs.reduce((a, b) => (...args) => a(b(...args)));
+}
+
+/**
+ * 转化为el-tree树形结构数据
+ */
+export function transToTreeDat(arr) {
+  let list = JSON.parse(JSON.stringify(arr));
+  let colNodes = list.filter(i => !i.isRow);
+  let colNodesGroup = getColNode(colNodes);
+  let allNodes = list.concat(colNodesGroup);
+  let tree = transTree(allNodes);
+  return tree;
+}
+export function getColNode(colNodeArrs) {
+  let colNodes = colNodeArrs;
+  let map = {};
+  colNodes.forEach(i => {
+    if (!map[i.groupId]) {
+      map[i.groupId] = [];
+    }
+    map[i.groupId].push(i);
+  });
+  let colNodesArr = [];
+  for (const groupId in map) {
+    let obj = {
+      id: groupId,
+      groupId: map[groupId][0].groupPid,
+      type: "route",
+      isRow: true,
+      isFlowTo: map[groupId][0].type === "6",
+      conditionNodes: map[groupId]
+    };
+    colNodesArr.push(obj);
+  }
+
+  return colNodesArr;
+}
+/**
+ *
+ * @param {allNodes} arr 所有的整行元素
+ * @param {*} list  所有的节点元素
+ */
+export function getPidArr(list) {
+  let colNodes = list.filter(i => !i.isRow);
+  let rowNodes = list.filter(i => i.isRow);
+  let colNodesGroup = getColNode(colNodes, list);
+  let arr = colNodesGroup.concat(rowNodes);
+
+  let map = {}; //所有整行元素的字典对象
+  for (let item of arr) {
+    map[item.id] = item;
+  }
+  //获取节点所在行
+  for (let lis of list) {
+    lis.pids = [];
+    if (!lis.isRow) {
+      let p = map[lis.groupPid];
+      if (lis.groupPid === "root") {
+        lis.pids.push(p.id);
+        continue;
+      }
+      //当上一层为rowNode
+      getColPid(p, lis);
+    } else {
+      let p = map[lis.groupId];
+      getRowPid(p, lis);
+    }
+  }
+}
+//获取row的父节点id
+function getRowPid(p, lis) {
+  if (!p) {
+    lis.pids.push(lis.groupId);
+  } else {
+    if (p.conditionNodes) {
+      p.conditionNodes.forEach(i => {
+        loopGetPid(i, lis);
+      });
+    } else {
+      lis.pids.push(p.id);
+    }
+  }
+}
+/**
+ * 获取col节点的父节点
+ */
+function getColPid(p, lis) {
+  if (!p) {
+    //当上一层为条件框元素
+    lis.pids.push(lis.groupPid);
+  } else {
+    //当上一层为整行元素
+    loopGetPidCol(p, lis);
+  }
+}
+/**
+ *
+ * @param {*} parentRow
+ * @param {*} lis
+ * 单独处理一下col节点
+ */
+export function loopGetPidCol(parentRow, lis) {
+  if (parentRow.conditionNodes) {
+    parentRow.conditionNodes.forEach(i => {
+      loopGetPid(i, lis);
+    });
+  } else {
+    lis.pids.push(parentRow.id);
+  }
+}
+/**
+ *  轮询节点获取pid
+ * @param {*} node
+ * @param {*} lis
+ */
+export function loopGetPid(node, lis) {
+  if (node.childNode) {
+    loopGetPid(node.childNode, lis);
+  } else if (node.conditionNodes) {
+    node.conditionNodes.forEach(i => {
+      loopGetPid(i, lis);
+    });
+  } else {
+    lis.pids.push(node.id);
+  }
+}
+/**
+ * 转化为el-tree树形结构数据
+ */
+export function transTree(arr) {
+  let list = arr;
+  if (!list || !list.length) return [];
+  let map = {};
+  for (let item of list) {
+    map[item.id] = item;
+  }
+  let nodes = [];
+  for (let lis of list) {
+    if (!lis.isRow) {
+      continue;
+    }
+    let p = map[lis.groupId];
+    if (!p) {
+      nodes.push(lis);
+      continue;
+    }
+    p.isParent = true;
+    p.childNode || (p.childNode = {});
+    p.childNode = lis;
+    if (
+      p.childNode.conditionNodes &&
+      p.childNode.conditionNodes[0].type === "6"
+    ) {
+      p.isFlowTo = true;
+    }
+  }
+  return nodes;
+}
+/**
+ * Hash 哈希值
+ */
+export function HashCode(hashLength) {
+  // 默认长度 24
+  return (
+    "a" +
+    Array.from(Array(Number(hashLength) || 15), () =>
+      Math.floor(Math.random() * 36).toString(36)
+    ).join("")
+  );
+}
+/**
+ * 树结构转化为扁平化结构
+ */
+export function deepTraversal(tree) {
+  let list = [];
+  tree.forEach(item => {
+    const loop = data => {
+      list.push(data);
+      let children = data.children;
+      children &&
+        children.length &&
+        children.forEach(child => {
+          loop(child);
+        });
+    };
+    loop(item);
+  });
+  return list;
+}

+ 0 - 130
src/components/HelloWorld.vue

@@ -1,130 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br />
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener"
-        >vue-cli documentation</a
-      >.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
-          target="_blank"
-          rel="noopener"
-          >babel</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
-          target="_blank"
-          rel="noopener"
-          >router</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
-          target="_blank"
-          rel="noopener"
-          >vuex</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
-          target="_blank"
-          rel="noopener"
-          >eslint</a
-        >
-      </li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li>
-        <a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
-      </li>
-      <li>
-        <a href="https://forum.vuejs.org" target="_blank" rel="noopener"
-          >Forum</a
-        >
-      </li>
-      <li>
-        <a href="https://chat.vuejs.org" target="_blank" rel="noopener"
-          >Community Chat</a
-        >
-      </li>
-      <li>
-        <a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
-          >Twitter</a
-        >
-      </li>
-      <li>
-        <a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
-      </li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li>
-        <a href="https://router.vuejs.org" target="_blank" rel="noopener"
-          >vue-router</a
-        >
-      </li>
-      <li>
-        <a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-devtools#vue-devtools"
-          target="_blank"
-          rel="noopener"
-          >vue-devtools</a
-        >
-      </li>
-      <li>
-        <a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
-          >vue-loader</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/awesome-vue"
-          target="_blank"
-          rel="noopener"
-          >awesome-vue</a
-        >
-      </li>
-    </ul>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "HelloWorld",
-  props: {
-    msg: String
-  }
-};
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped lang="less">
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 3 - 1
src/main.js

@@ -3,9 +3,11 @@ import App from "./App.vue";
 import router from "./router";
 import store from "./store";
 import ant from "ant-design-vue";
-
+import DrawFlow from "./components/DrawFlow";
+import "ant-design-vue/dist/antd.less";
 Vue.config.productionTip = false;
 Vue.use(ant);
+Vue.use(DrawFlow);
 new Vue({
   router,
   store,

+ 48 - 7
src/views/Home.vue

@@ -1,18 +1,59 @@
 <template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png" />
-    <HelloWorld msg="Welcome to Your Vue.js App" />
+  <div id="drawProcessDesign">
+    <FactoryDrawFlow :FlowConfig="FlowConfig"></FactoryDrawFlow>
   </div>
 </template>
 
 <script>
 // @ is an alias to /src
-import HelloWorld from "@/components/HelloWorld.vue";
-
+import { FlowNode } from "./getNode";
 export default {
   name: "Home",
-  components: {
-    HelloWorld
+  components: {},
+  data() {
+    return {
+      FlowConfig: [
+        {
+          id: "root",
+          groupId: null,
+          type: "1",
+          title: "所有人",
+          content: "请选择",
+          isRow: true,
+          isRoot: true,
+          data: {}
+        }
+      ]
+    };
+  },
+  created() {
+    this.init();
+  },
+  methods: {
+    /**
+     * 1、创建一个row length 1
+     * 2、创建一个col节点
+     * 3、创建一组col length 1
+     * return [{pid id type}]
+     */
+    getNodeArr() {
+      this.FlowConfig = this.$refs.flow.getNodeArr();
+      return this.FlowConfig;
+    },
+    init() {
+      if (this.processDesign.length) {
+        this.FlowConfig = this.transform(this.processDesign);
+      }
+    },
+    transform(arr) {
+      let resArr = [];
+      arr.forEach(i => {
+        let node = new FlowNode(i);
+        node.id && resArr.push(node);
+        console.log(node, "nnnode");
+      });
+      return resArr;
+    }
   }
 };
 </script>

+ 31 - 0
src/views/getNode.js

@@ -0,0 +1,31 @@
+class FlowNode {
+  constructor({ nodeType, nodeId, nodeName, groupId, parentGroupId, ...data }) {
+    if (nodeType === "1") {
+      return {
+        id: "root",
+        groupId: "root",
+        type: "root",
+        title: "所有人",
+        content: "请选择",
+        isRow: true,
+        isRoot: true,
+        data: {}
+      };
+    }
+    if (nodeType === "5") {
+      return null;
+    }
+    this.data = data;
+    if (nodeType === "3") {
+      this.data = {
+        ruleInfo: {}
+      };
+    }
+    this.id = nodeId;
+    this.title = nodeName;
+    this.type = nodeType;
+    this.groupId = groupId;
+    this.groupPid = parentGroupId;
+  }
+}
+export { FlowNode };

+ 157 - 0
vue.config.js

@@ -0,0 +1,157 @@
+// const path = require("path");
+// function resolve(dir) {
+//   return path.join(__dirname, ".", dir);
+// }
+// const webpack = require("webpack");
+// const IncludeAssetsPlugin = require("html-webpack-include-assets-plugin");
+// const CopyWebpackPlugin = require("copy-webpack-plugin");
+// const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
+// const CompressionWebpackPlugin = require("compression-webpack-plugin");
+
+// const ThemeColorReplacer = require("webpack-theme-color-replacer");
+// const { getThemeColors, modifyVars } = require("./src/common/theme/themeUtil");
+// const {
+//   resolveCss
+// } = require("./src/common/theme/theme-color-replacer-extend");
+
+// const isProduction = process.env.NODE_ENV === "production";
+// let glob = require("glob");
+// function getEntry(globPath) {
+//   let entries = {},
+//     basename,
+//     tmp,
+//     pathname;
+//   glob.sync(globPath).forEach(function(entry) {
+//     basename = path.basename(entry, path.extname(entry));
+//     tmp = entry.split("/").splice(-3);
+//     pathname = basename; // 正确输出js和html的路径
+//     var fileIs = glob.sync(entry + tmp[1] + ".html");
+//     if (tmp[1] === "common") {
+//       return;
+//     }
+//     entries[pathname] = {
+//       entry: "src/" + tmp[0] + "/" + tmp[1] + "/main.js",
+//       template: fileIs.length
+//         ? "src/" + tmp[0] + "/" + tmp[1] + "/" + tmp[1] + ".html"
+//         : "public/index.html",
+//       title: "",
+//       filename: tmp[1] + ".html",
+//       chunks: ["chunk-vendors", "chunk-common", pathname]
+//     };
+//   });
+//   return entries;
+// }
+// module.exports = {
+//   publicPath: process.env.BASE_URL,
+//   productionSourceMap: false,
+//   pluginOptions: {
+//     "style-resources-loader": {
+//       preProcessor: "less",
+//       patterns: [path.resolve(__dirname, "./src/assets/styles/theme.less")]
+//     }
+//   },
+//   chainWebpack: config => {
+//     config.module.rules.delete("svg");
+//     config.module
+//       .rule("svg-sprite-loader")
+//       .test(/\.svg$/)
+//       .include.add(resolve("src/assets/icons"))
+//       .end()
+//       .use("svg-sprite-loader")
+//       .loader("svg-sprite-loader")
+//       .options({
+//         symbolId: "icon-[name]"
+//       });
+
+//     // 生产环境下关闭css压缩的 colormin 项,因为此项优化与主题色替换功能冲突
+//     if (isProduction) {
+//       config.plugin("optimize-css").tap(args => {
+//         args[0].cssnanoOptions.preset[1].colormin = false;
+//         return args;
+//       });
+//     }
+//   },
+//   css: {
+//     // extract: true,
+//     loaderOptions: {
+//       less: {
+//         javascriptEnabled: true,
+//         modifyVars: modifyVars(),
+//         lessOptions: {
+//           modifyVars: modifyVars()
+//         }
+//       }
+//     }
+//   },
+//   pages: getEntry("./src/pages/**?/"),
+//   configureWebpack: config => {
+//     if (isProduction) {
+//       config.plugins.push(
+//         new UglifyJsPlugin({
+//           uglifyOptions: {
+//             compress: {
+//               drop_debugger: true,
+//               drop_console: true
+//             }
+//           },
+//           sourceMap: false,
+//           parallel: true
+//         })
+//       );
+//       config.plugins.push(
+//         new CompressionWebpackPlugin({
+//           algorithm: "gzip",
+//           test: new RegExp("\\.(" + ["js", "css"].join("|") + ")$"),
+//           threshold: 10240,
+//           minRatio: 0.8
+//         })
+//       );
+//       config.plugins.push(
+//         new webpack.optimize.LimitChunkCountPlugin({
+//           maxChunks: 5,
+//           minChunkSize: 100
+//         })
+//       );
+//     }
+//     config.plugins.push(
+//       new ThemeColorReplacer({
+//         fileName: "css/theme-colors-[contenthash:8].css",
+//         matchColors: getThemeColors(),
+//         injectCss: true,
+//         resolveCss
+//       })
+//     );
+//     return {
+//       devtool: "cheap-source-map",
+//       externals: {
+//         vue: "Vue",
+//         "vue-router": "VueRouter",
+//         moment: "moment",
+//         vuex: "Vuex",
+//         "ant-design-vue": "antd"
+//       },
+//       plugins: [
+//         new webpack.DllReferencePlugin({
+//           context: __dirname,
+//           manifest: path.resolve(__dirname, "dll", "manifest.json")
+//         }),
+//         new IncludeAssetsPlugin({
+//           assets: [
+//             {
+//               path: "dll",
+//               glob: "*.js",
+//               globPath: path.join(__dirname, "dll")
+//             }
+//           ],
+//           append: false
+//         }),
+//         new CopyWebpackPlugin([
+//           {
+//             from: path.join(__dirname, "dll"),
+//             to: path.join(__dirname, "dist", "dll")
+//           }
+//         ])
+//       ]
+//     };
+//   }
+// };

+ 189 - 26
yarn.lock

@@ -1609,6 +1609,14 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
   resolved "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
   integrity sha1-MfKdpatuANHC0yms97WSlhTVAU0=
 
+ajv@^4.9.1:
+  version "4.11.8"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
+  integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=
+  dependencies:
+    co "^4.6.0"
+    json-stable-stringify "^1.0.1"
+
 ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4:
   version "6.12.5"
   resolved "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
@@ -1802,6 +1810,11 @@ array-unique@^0.3.2:
   resolved "https://registry.npm.taobao.org/array-unique/download/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
   integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
 
+asap@~2.0.3:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+  integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
+
 asn1.js@^5.2.0:
   version "5.4.1"
   resolved "https://registry.npm.taobao.org/asn1.js/download/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@@ -1824,6 +1837,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
   resolved "https://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
   integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
 
+assert-plus@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
+  integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ=
+
 assert@^1.1.1:
   version "1.5.0"
   resolved "https://registry.npm.taobao.org/assert/download/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
@@ -1887,12 +1905,17 @@ autoprefixer@^9.8.6:
     postcss "^7.0.32"
     postcss-value-parser "^4.1.0"
 
+aws-sign2@~0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
+  integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8=
+
 aws-sign2@~0.7.0:
   version "0.7.0"
   resolved "https://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
   integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
 
-aws4@^1.8.0:
+aws4@^1.2.1, aws4@^1.8.0:
   version "1.10.1"
   resolved "https://registry.npm.taobao.org/aws4/download/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
   integrity sha1-4eguTz6Zniz9YbFhKA0WoRH4ZCg=
@@ -2060,6 +2083,13 @@ boolbase@^1.0.0, boolbase@~1.0.0:
   resolved "https://registry.npm.taobao.org/boolbase/download/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
   integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
 
+boom@2.x.x:
+  version "2.10.1"
+  resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
+  integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=
+  dependencies:
+    hoek "2.x.x"
+
 brace-expansion@^1.1.7:
   version "1.1.11"
   resolved "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -2560,6 +2590,11 @@ clone@^2.1.1:
   resolved "https://registry.npm.taobao.org/clone/download/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
   integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
 
+co@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+  integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
+
 coa@^2.0.2:
   version "2.0.2"
   resolved "https://registry.npm.taobao.org/coa/download/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
@@ -2622,7 +2657,7 @@ colorette@^1.2.1:
   resolved "https://registry.npm.taobao.org/colorette/download/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
   integrity sha1-TQuSEyXBT6+SYzCGpTbbbolWSxs=
 
-combined-stream@^1.0.6, combined-stream@~1.0.6:
+combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
   version "1.0.8"
   resolved "https://registry.npm.taobao.org/combined-stream/download/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
   integrity sha1-w9RaizT9cwYxoRCoolIGgrMdWn8=
@@ -2880,6 +2915,13 @@ cross-spawn@^7.0.0:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+cryptiles@2.x.x:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
+  integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=
+  dependencies:
+    boom "2.x.x"
+
 crypto-browserify@^3.11.0:
   version "3.12.0"
   resolved "https://registry.npm.taobao.org/crypto-browserify/download/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -3838,7 +3880,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
     assign-symbols "^1.0.0"
     is-extendable "^1.0.1"
 
-extend@~3.0.2:
+extend@~3.0.0, extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.npm.taobao.org/extend/download/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=
@@ -4076,6 +4118,15 @@ forever-agent@~0.6.1:
   resolved "https://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
   integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
 
+form-data@~2.1.1:
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
+  integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.5"
+    mime-types "^2.1.12"
+
 form-data@~2.3.2:
   version "2.3.3"
   resolved "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -4309,11 +4360,24 @@ handle-thing@^2.0.0:
   resolved "https://registry.npm.taobao.org/handle-thing/download/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
   integrity sha1-hX95zjWVgMNA1DCBzGSJcNC7I04=
 
+har-schema@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
+  integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=
+
 har-schema@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
   integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
 
+har-validator@~4.2.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
+  integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio=
+  dependencies:
+    ajv "^4.9.1"
+    har-schema "^1.0.5"
+
 har-validator@~5.1.3:
   version "5.1.5"
   resolved "https://registry.npm.taobao.org/har-validator/download/har-validator-5.1.5.tgz?cache=0&sync_timestamp=1596082653557&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhar-validator%2Fdownload%2Fhar-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
@@ -4409,6 +4473,16 @@ hash.js@^1.0.0, hash.js@^1.0.3:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
 
+hawk@~3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
+  integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=
+  dependencies:
+    boom "2.x.x"
+    cryptiles "2.x.x"
+    hoek "2.x.x"
+    sntp "1.x.x"
+
 he@1.2.x, he@^1.1.0:
   version "1.2.0"
   resolved "https://registry.npm.taobao.org/he/download/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -4433,6 +4507,11 @@ hmac-drbg@^1.0.0:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
+hoek@2.x.x:
+  version "2.16.3"
+  resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+  integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=
+
 hoopy@^0.1.4:
   version "0.1.4"
   resolved "https://registry.npm.taobao.org/hoopy/download/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@@ -4582,6 +4661,15 @@ http-proxy@^1.17.0:
     follow-redirects "^1.0.0"
     requires-port "^1.0.0"
 
+http-signature@~1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
+  integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=
+  dependencies:
+    assert-plus "^0.2.0"
+    jsprim "^1.2.2"
+    sshpk "^1.7.0"
+
 http-signature@~1.2.0:
   version "1.2.0"
   resolved "https://registry.npm.taobao.org/http-signature/download/http-signature-1.2.0.tgz?cache=0&sync_timestamp=1600868613104&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-signature%2Fdownload%2Fhttp-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -5171,6 +5259,13 @@ json-stable-stringify-without-jsonify@^1.0.1:
   resolved "https://registry.npm.taobao.org/json-stable-stringify-without-jsonify/download/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
 
+json-stable-stringify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+  integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=
+  dependencies:
+    jsonify "~0.0.0"
+
 json-stringify-safe@~5.0.1:
   version "5.0.1"
   resolved "https://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -5214,6 +5309,11 @@ jsonfile@^4.0.0:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+jsonify@~0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+  integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
+
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -5277,20 +5377,19 @@ less-loader@^5.0.0:
     loader-utils "^1.1.0"
     pify "^4.0.1"
 
-less@^3.0.4:
-  version "3.12.2"
-  resolved "https://registry.npm.taobao.org/less/download/less-3.12.2.tgz#157e6dd32a68869df8859314ad38e70211af3ab4"
-  integrity sha1-FX5t0ypohp34hZMUrTjnAhGvOrQ=
-  dependencies:
-    tslib "^1.10.0"
+less@^2.7.2:
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/less/-/less-2.7.3.tgz#cc1260f51c900a9ec0d91fb6998139e02507b63b"
+  integrity sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==
   optionalDependencies:
     errno "^0.1.1"
     graceful-fs "^4.1.2"
     image-size "~0.5.0"
-    make-dir "^2.1.0"
-    mime "^1.4.1"
-    native-request "^1.0.5"
-    source-map "~0.6.0"
+    mime "^1.2.11"
+    mkdirp "^0.5.0"
+    promise "^7.1.1"
+    request "2.81.0"
+    source-map "^0.5.3"
 
 leven@^3.1.0:
   version "3.1.0"
@@ -5452,7 +5551,7 @@ lru-cache@^5.1.1:
   dependencies:
     yallist "^3.0.2"
 
-make-dir@^2.0.0, make-dir@^2.1.0:
+make-dir@^2.0.0:
   version "2.1.0"
   resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
   integrity sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=
@@ -5583,14 +5682,14 @@ mime-db@1.44.0:
   resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.45.0.tgz?cache=0&sync_timestamp=1600831175828&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-db%2Fdownload%2Fmime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
   integrity sha1-zO7aIczXw6dF66LezVXUtz54eeo=
 
-mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
+mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.7:
   version "2.1.27"
   resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
   integrity sha1-R5SfmOJ56lMRn1ci4PNOUpvsAJ8=
   dependencies:
     mime-db "1.44.0"
 
-mime@1.6.0, mime@^1.4.1:
+mime@1.6.0, mime@^1.2.11:
   version "1.6.0"
   resolved "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=
@@ -5694,7 +5793,7 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
+mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
   version "0.5.5"
   resolved "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
   integrity sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8=
@@ -5792,11 +5891,6 @@ nanopop@^2.1.0:
   resolved "https://registry.yarnpkg.com/nanopop/-/nanopop-2.1.0.tgz#23476513cee2405888afd2e8a4b54066b70b9e60"
   integrity sha512-jGTwpFRexSH+fxappnGQtN9dspgE2ipa1aOjtR24igG0pv6JCxImIAmrLRHX+zUF5+1wtsFVbKyfP51kIGAVNw==
 
-native-request@^1.0.5:
-  version "1.0.7"
-  resolved "https://registry.npm.taobao.org/native-request/download/native-request-1.0.7.tgz?cache=0&sync_timestamp=1594998140876&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnative-request%2Fdownload%2Fnative-request-1.0.7.tgz#ff742dc555b4c8f2f1c14b548639ba174e573856"
-  integrity sha1-/3QtxVW0yPLxwUtUhjm6F05XOFY=
-
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.npm.taobao.org/natural-compare/download/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -5952,6 +6046,11 @@ num2fraction@^1.2.2:
   resolved "https://registry.npm.taobao.org/num2fraction/download/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
   integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
 
+oauth-sign@~0.8.1:
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+  integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
+
 oauth-sign@~0.9.0:
   version "0.9.0"
   resolved "https://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@@ -6350,6 +6449,11 @@ pbkdf2@^3.0.3:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
+performance-now@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+  integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=
+
 performance-now@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -6815,6 +6919,13 @@ promise-inflight@^1.0.1:
   resolved "https://registry.npm.taobao.org/promise-inflight/download/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
   integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
 
+promise@^7.1.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
+  integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
+  dependencies:
+    asap "~2.0.3"
+
 proxy-addr@~2.0.5:
   version "2.0.6"
   resolved "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@@ -6880,7 +6991,7 @@ punycode@1.3.2:
   resolved "https://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
   integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
 
-punycode@^1.2.4:
+punycode@^1.2.4, punycode@^1.4.1:
   version "1.4.1"
   resolved "https://registry.npm.taobao.org/punycode/download/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
@@ -6900,6 +7011,11 @@ qs@6.7.0:
   resolved "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
   integrity sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=
 
+qs@~6.4.0:
+  version "6.4.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+  integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=
+
 qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -7118,6 +7234,34 @@ repeat-string@^1.6.1:
   resolved "https://registry.npm.taobao.org/repeat-string/download/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
   integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
 
+request@2.81.0:
+  version "2.81.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
+  integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=
+  dependencies:
+    aws-sign2 "~0.6.0"
+    aws4 "^1.2.1"
+    caseless "~0.12.0"
+    combined-stream "~1.0.5"
+    extend "~3.0.0"
+    forever-agent "~0.6.1"
+    form-data "~2.1.1"
+    har-validator "~4.2.1"
+    hawk "~3.1.3"
+    http-signature "~1.1.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.7"
+    oauth-sign "~0.8.1"
+    performance-now "^0.2.0"
+    qs "~6.4.0"
+    safe-buffer "^5.0.1"
+    stringstream "~0.0.4"
+    tough-cookie "~2.3.0"
+    tunnel-agent "^0.6.0"
+    uuid "^3.0.0"
+
 request@^2.88.2:
   version "2.88.2"
   resolved "https://registry.npm.taobao.org/request/download/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
@@ -7529,6 +7673,13 @@ snapdragon@^0.8.1:
     source-map-resolve "^0.5.0"
     use "^3.1.0"
 
+sntp@1.x.x:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
+  integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=
+  dependencies:
+    hoek "2.x.x"
+
 sockjs-client@1.4.0:
   version "1.4.0"
   resolved "https://registry.npm.taobao.org/sockjs-client/download/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5"
@@ -7586,7 +7737,7 @@ source-map-url@^0.4.0:
   resolved "https://registry.npm.taobao.org/source-map-url/download/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
   integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
 
-source-map@^0.5.0, source-map@^0.5.6:
+source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6:
   version "0.5.7"
   resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
   integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -7813,6 +7964,11 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
+stringstream@~0.0.4:
+  version "0.0.6"
+  resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
+  integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
+
 strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -8096,6 +8252,13 @@ toposort@^1.0.0:
   resolved "https://registry.npm.taobao.org/toposort/download/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
   integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk=
 
+tough-cookie@~2.3.0:
+  version "2.3.4"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
+  integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==
+  dependencies:
+    punycode "^1.4.1"
+
 tough-cookie@~2.5.0:
   version "2.5.0"
   resolved "https://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@@ -8114,7 +8277,7 @@ ts-pnp@^1.1.6:
   resolved "https://registry.npm.taobao.org/ts-pnp/download/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
   integrity sha1-pQCtCEsHmPHDBxrzkeZZEshrypI=
 
-tslib@^1.10.0, tslib@^1.9.0:
+tslib@^1.9.0:
   version "1.13.0"
   resolved "https://registry.npm.taobao.org/tslib/download/tslib-1.13.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
   integrity sha1-yIHhPMcBWJTtkUhi0nZDb6mkcEM=
@@ -8358,7 +8521,7 @@ utils-merge@1.0.1:
   resolved "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
 
-uuid@^3.3.2, uuid@^3.4.0:
+uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0:
   version "3.4.0"
   resolved "https://registry.npm.taobao.org/uuid/download/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
   integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=