pruple_boy 2 lat temu
commit
b6b1592f74
65 zmienionych plików z 20714 dodań i 0 usunięć
  1. 20 0
      .babelrc
  2. 1 0
      .browserslistrc
  3. 9 0
      .editorconfig
  4. 51 0
      .gitignore
  5. 3 0
      .travis.yml
  6. 28 0
      .vscode/settings.json
  7. 39 0
      README.md
  8. 286 0
      RRMARK.md
  9. 345 0
      doc/deploy.md
  10. 1198 0
      doc/development.md
  11. 77 0
      doc/sample.md
  12. 67 0
      package.json
  13. BIN
      public/favicon.ico
  14. 19 0
      public/index.css
  15. 22 0
      public/index.html
  16. 46 0
      public/index.js
  17. 55 0
      scripts/rollup.config.base.js
  18. 29 0
      scripts/rollup.config.dev.js
  19. 68 0
      scripts/rollup.config.prod.js
  20. 38 0
      src/aliwork/bom.js
  21. 10 0
      src/aliwork/bus.js
  22. 117 0
      src/aliwork/com.js
  23. 18 0
      src/aliwork/cvt.js
  24. 224 0
      src/aliwork/dom.js
  25. 107 0
      src/aliwork/dp.js
  26. 62 0
      src/aliwork/private/assemble.js
  27. 89 0
      src/aliwork/private/redundancy.js
  28. 33 0
      src/aliwork/scene.js
  29. 61 0
      src/auth/copyright.js
  30. 29 0
      src/config/conf.js
  31. 8 0
      src/config/vConsole.js
  32. 62 0
      src/deploy/aliwork-dp.js
  33. 495 0
      src/deploy/aliwork-v2.js
  34. 640 0
      src/deploy/aliwork-v3.js
  35. 54 0
      src/main.js
  36. 36 0
      src/sample/cloudpure.js
  37. 126 0
      src/sample/crmServer.js
  38. 695 0
      src/sample/fegroup.js
  39. 67 0
      src/sample/fushi.js
  40. 136 0
      src/sample/guyuan.js
  41. 56 0
      src/sample/hisJianGao.js
  42. 29 0
      src/sample/nongzhan.js
  43. 14 0
      src/sample/ruyi.js
  44. 84 0
      src/sample/yde.js
  45. 34 0
      src/sample/zitooCrm.js
  46. 78 0
      src/service/aliwork.js
  47. 4 0
      src/service/network.js
  48. 304 0
      src/service/request.js
  49. 16 0
      src/service/vendor.js
  50. 35 0
      src/style/index.css
  51. 16 0
      src/utils/browser.js
  52. 45 0
      src/utils/math.js
  53. 78 0
      src/utils/optimize.js
  54. 112 0
      src/utils/storage.js
  55. 82 0
      src/utils/util.js
  56. 87 0
      src/vendor/calc.js
  57. 31 0
      src/vendor/capture.js
  58. 67 0
      src/vendor/date.js
  59. 38 0
      src/vendor/diff.js
  60. 157 0
      src/vendor/dingApi.js
  61. 179 0
      src/vendor/lib/Blob.js
  62. 153 0
      src/vendor/lib/Export2Excel.js
  63. 7308 0
      src/vendor/lib/pinyin.js
  64. 212 0
      src/vendor/xlsx.js
  65. 6025 0
      yarn.lock

+ 20 - 0
.babelrc

@@ -0,0 +1,20 @@
+{
+  "presets": [
+    [
+      "env",
+      {
+        "modules": false
+      }
+    ],
+    "stage-2",
+    "flow"
+  ],
+  "plugins": ["transform-runtime"],
+  "env": {
+    "test": {
+      "presets": [
+        ["env"], "stage-2", "flow"
+      ]
+    }
+  }
+}

+ 1 - 0
.browserslistrc

@@ -0,0 +1 @@
+ie >= 9

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true

+ 51 - 0
.gitignore

@@ -0,0 +1,51 @@
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+dist
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Editor directories and files
+.idea
+
+# Dependency directories
+node_modules
+
+# Optional
+.npm
+.node_repl_history
+
+# assets publish
+# you can ignore dist Folder if you publish your assets with CI to CDN
+
+# Project specific stuff
+temp
+typings

+ 3 - 0
.travis.yml

@@ -0,0 +1,3 @@
+anguage: node_js
+node_js:
+- 8.9.3

+ 28 - 0
.vscode/settings.json

@@ -0,0 +1,28 @@
+{
+  "cSpell.words": [
+    "Aliwork",
+    "Parens",
+    "XLSXAPI",
+    "Zqaq",
+    "ajelv",
+    "ajelw",
+    "ajelx",
+    "anguage",
+    "flowtype",
+    "isnan",
+    "kaoqmj",
+    "kaorkyrr",
+    "kaotctsb",
+    "kaqd",
+    "kaqe",
+    "kaqfkxm",
+    "nonwords",
+    "plusplus",
+    "reqs",
+    "rsqfip",
+    "upchar",
+    "uploadbtn",
+    "xury",
+    "zopg"
+  ]
+}

+ 39 - 0
README.md

@@ -0,0 +1,39 @@
+## Tips
+
+- package.json 中 配置 main, module, unpkg 指向 cjs, esm, umd 三个版本
+- sourcemap, 应该在 development 版本中输出, 生产环境不需要, library 会被应用层再次打包, 由应用控制 sourcemap 的输出
+- 生产环境
+  - umd: 一份压缩版本(.min)和未压缩版本(带有 sourcemap 吧?)
+  - 一份 commonjs 版本, 不需要压缩
+  - 一份 esm 版本, 不需要压缩
+  - cjs, esm 版本 如需有调试信息, 通过 process.env.NODE_ENV 区分
+- jest: 设置 babelrc, 新增 env 配置, 因为 jest 无法识别 ES Module.
+
+## run
+
+- 依赖下载 `yarn install`
+- 本地运行 `yarn run start`
+- 项目打包 `yarn run build`
+
+## tech
+
+- 通用网络请求: axios
+- 前端调试工具: VConsole
+- 时间格式化: fecha
+- excel 导入导出: xlsx
+- 钉钉 jsapi: dd
+
+## todolist
+
+- css
+
+```
+/**引入css
+
+  const new_element = document.createElement("link");
+  new_element.setAttribute("rel", "stylesheet");
+  new_element.setAttribute("type", "text/css");
+  new_element.setAttribute("href", "https://aliwork.zitoo.com.cn/fegroup/index.css");
+  document.body.appendChild(new_element);
+  */
+```

+ 286 - 0
RRMARK.md

@@ -0,0 +1,286 @@
+## 备注
+
+```
+/*** mc 系列之 mjs
+ * 对接宜搭公共JavaScript库
+ * 公共库地址:https://mc.cloudpure.cn/mjs/mjs.min.js
+ ***/
+
+//---------------------- common ----------------------//
+
+// 加载 mjs
+export function _mjsLoad(callback = Function.prototype) {
+  // const src = "https://mc.cloudpure.cn/mjs/mjs.min.js"
+  const src = "http://127.0.0.1:7001/dist/mjs.js"
+  const script = document.createElement('script');
+  script.type = 'text/javascript';
+  script.src = src;
+  document.getElementsByTagName('head')[0].appendChild(script);
+  script.onload = () => this._mjsInit();;
+};
+
+//---------------------- private ----------------------//
+
+// 加载即调用方法请在此处进行调用
+export async function _mjsInit() {
+  await mjs.init(this, { vconsole: false })
+  // 页面环境:0提交(其它),1查看,2编辑(审批)
+  if (!mjs.env) { }
+
+  this.request.xhr.doPost("xxxx")
+}
+
+//---------------------- event ----------------------//
+
+// 页面节点加载渲染完毕
+export function didMount() {
+  // 工具库: mjs & 初始化
+  this._mjsLoad();
+}
+
+// 查询事件
+export function onSubmit(values) {
+  this.queryAllData(values)
+  console.log('onFilterSubmit', values);
+}
+```
+
+#### 已使用客户
+
+- 新天龙案例
+
+```
+/*** mc 系列之 mjs
+ * 对接宜搭公共JavaScript库
+ * 公共库地址:https://mc.cloudpure.cn/mjs/mjs.min.js
+ ***/
+
+//---------------------- common ----------------------//
+
+// 加载 mjs
+export function _mjsLoad(callback = Function.prototype) {
+  const src = "https://mc.cloudpure.cn/mjs/mjs.min.js"
+  // const src = "http://127.0.0.1:7001/dist/mjs.js"
+  const script = document.createElement('script');
+  script.type = 'text/javascript';
+  script.src = src;
+  document.getElementsByTagName('head')[0].appendChild(script);
+  script.onload = () => this._mjsInit();;
+};
+
+//---------------------- private ----------------------//
+
+// 加载即调用方法请在此处进行调用
+export async function _mjsInit() {
+  await mjs.init(this, { vconsole: false })
+  // 页面环境:0提交(其它),1查看,2编辑(审批)
+  if (!mjs.env) { }
+  mjs._queryList = mjs.optimize.debounce(param => this.queryList(param), 750)
+  console.log(this.$('tablePc_lgp1b94w'))
+
+  this.$("textField_lgp1b94r").set("value", mjs.storage.SS.GET("s_code"))
+}
+
+
+// 查询列表
+export async function queryList(values) {
+  const date = values.cascadeDateField_lgq1sjrd;
+  const rsp = await mjs.request.xhr.doPost("https://mc.cloudpure.cn/api/xintianlong/yd/order-list", {}, {
+    order: {
+      "formUuid": "FORM-3C866TC1MH19IOBZEXJJ58ZQ36IE3FDACV7FL0",
+      "compId_code": "textField_lf7vdf33",
+      "condition_code": values.textField_lgp1b94r,
+      "compId_date": "dateField_lf80vr20",
+      "condition_date": date.start && date.end ? [date.start, date.end] : ""
+    },
+    supplier: {
+      "formUuid": "FORM-AC6660814FW8UIE496KJT4YYKNA022Y1VU7FLC",
+      "compId": "textField_lf7v3zj0",
+      "condition": values.textField_lgp1b94r
+    }
+  })
+  this.$('tablePc_lgp1b94w').set("data", rsp.data.data)
+  mjs.storage.SS.SET("s_code", values.textField_lgp1b94r)
+}
+
+
+//---------------------- event ----------------------//
+
+// 页面节点加载渲染完毕
+export function didMount() {
+  // 工具库: mjs & 初始化
+  this._mjsLoad();
+}
+
+// 查询事件
+export function onSubmit(values) {
+  if (!values.textField_lgp1b94r) {
+    this.utils.toast({
+      title: '客户代码不能为空!', // 'success', 'warning', 'error', 'notice', 'help', 'loading'
+      type: 'error',
+      size: 'large',
+      duration: 2000, // 毫秒, type 为 loding 时无效
+    });
+    return
+  }
+  mjs._queryList(values)
+}
+```
+
+- 谷元
+
+```
+
+// ocr 识别
+export async function ocr(file, dp, reflect) {
+  const rsp = await mjs.request.xhr.doPost("https://mc.cloudpure.cn/api/chuoqi/yd/openUrl", {}, {
+    "url": file.url
+  })
+  const ocr = await this.dataSourceMap[dp].load({
+    inputs: JSON.stringify({
+      image_url: rsp.data.data
+    })
+  }).catch(err => {
+    this.utils.toast({
+      title: "图片未识别成功", // err.message, // 'success', 'warning', 'error', 'notice', 'help', 'loading'
+      type: 'error',
+      size: 'large',
+      duration: 2000, // 毫秒, type 为 loding 时无效
+    });
+  })
+  const data = JSON.parse(ocr)
+  console.log(data.column18, data)
+  this.verify(data)
+}
+
+```
+
+- 福氏
+
+```
+
+/*** mc 系列之 mjs
+ * 对接宜搭公共JavaScript库
+ * 版权请联系:https://www.aliwork.com/o/mjs
+ * 公共库地址:https://aliwork.zitoo.com.cn/cdn/mjs.min.js
+ ***/
+
+//---------------------- common ----------------------//
+
+function loadAliworkSDK(that) {
+  const src = "https://aliwork.zitoo.com.cn/mc/js/mjs.min.js"
+  // const src = "http://127.0.0.1:7001/dist/mjs.js"
+  const script = document.createElement('script');
+  script.type = 'text/javascript';
+  script.src = src;
+  script.addEventListener('load', () => _mjsLoad(that), false);
+  document.head.appendChild(script);
+};
+
+//---------------------- private ----------------------//
+
+// 加载即调用方法请在此处进行调用
+async function _mjsLoad(that) {
+  await mjs.init(that, { vconsole: false })
+  // 提交页面自动查询位置:0提交,1查看,2编辑
+  if (!mjs.env) { }
+}
+
+//---------------------- event ----------------------//
+
+// 页面节点加载渲染完毕
+export function didMount() {
+  // 工具库: mjs & 初始化
+  loadAliworkSDK(this)
+}
+
+
+```
+
+# 前端通过连接器调用发票识别
+
+```
+/*** mc 系列之 mjs
+ * 对接宜搭公共JavaScript库
+ * 版权请联系:https://www.aliwork.com/o/mjs
+ * 公共库地址:https://aliwork.zitoo.com.cn/mc/js/mjs.min.js
+ ***/
+
+//---------------------- common ----------------------//
+
+// 加载 mjs
+export function _mjsLoad(callback = Function.prototype) {
+  const src = "https://aliwork.zitoo.com.cn/mc/js/mjs.min.js"
+  const script = document.createElement('script');
+  script.type = 'text/javascript';
+  script.src = src;
+  document.getElementsByTagName('head')[0].appendChild(script);
+  if (!script.readyState) {
+    script.onload = () => this._mjsInit();;
+    return;
+  }
+  // IE
+  script.onreadystatechange = () => {
+    if (script.readyState == 'loaded' || script.readyState == 'complete') {
+      script.onreadystatechange = null;
+      this.mounted();
+    }
+  }
+};
+
+//---------------------- private ----------------------//
+
+// 加载即调用方法请在此处进行调用
+export async function _mjsInit() {
+  await mjs.init(this, { vconsole: false })
+  // 页面环境:0提交(其它),1查看,2编辑(审批)
+  if (!mjs.env) { }
+}
+
+//---------------------- event ----------------------//
+
+// 页面节点加载渲染完毕
+export function didMount() {
+  // 工具库: mjs & 初始化
+  this._mjsLoad();
+}
+
+// ocr识别
+export function onSuccess(file, value) {
+  this.ocr(file)
+}
+
+export async function ocr(file) {
+  const rsp = await mjs.request.xhr.doPost("https://aliwork.zitoo.com.cn/api/uc/yd/getMdUrl", {}, {
+    "projectId": "100091",
+    "imageUrl": file.url
+  })
+  const ocr = await this.dataSourceMap.ocr.load({
+    inputs: JSON.stringify({
+      image_url: rsp.data[0]
+    })
+  }).catch(err => {
+    this.utils.toast({
+      title: err.message, // 'success', 'warning', 'error', 'notice', 'help', 'loading'
+      type: 'error',
+      size: 'large',
+      duration: 2000, // 毫秒, type 为 loding 时无效
+    });
+  })
+  const data = JSON.parse(ocr)
+  const reflect = {
+    textField_l5gfoa0p: "column1", // 发票代码
+    textField_l5gfoa0q: "column2", // 发票号码
+    textField_l61k48y4: "column3", // 开票时间
+    textField_l5gfoa0r: "column4", // 校验码
+    numberField_l5gfoa0v: "column5", // 开票金额
+    numberField_l5gfoa0w: "column7", // 税额
+    numberField_l5gfoa0u: "column8", // 不含税金额
+    textField_l5gfoa0y: "column9", // 开票公司名称
+  }
+  console.log(data)
+  for (let compId of Object.keys(reflect)) {
+    this.$(compId).setValue(data[reflect[compId]])
+  }
+}
+```

+ 345 - 0
doc/deploy.md

@@ -0,0 +1,345 @@
+> 工具库功能和使用手册   [牧语  开发文档](https://aliwork.zitoo.com.cn/mjs.html)
+
+<p align="center"><font face="微软雅黑" size="6">mjs 配置型文档</font></p>
+
+---
+
+## 宜搭公式
+
+### 常用公式
+
+1. 日期格式为数值, 如 7 - 12 点不允许点餐:
+
+   ```
+   OR(GT(VALUE(TEXT(DATE(TIMESTAMP(SYSTIME())),"HHmm")),1600),GT(700,VALUE(TEXT(DATE(TIMESTAMP(SYSTIME())),"HHmm"))))
+   ```
+
+2. 获取天数差值:`DYAS(DATE(结束日期组件),DATE(开始日期组件))`
+
+3. 由时间戳生成编号 `CONCATENATE("PRO",LEFT(TIMESTAMP(SYSTIME()),10))`, `CONCATENATE("COC",TEXT(TODAY(),"yyyyMMddHHmmSS"))`
+
+4. 日期组件默认值:`TIMESTAMP(SYSTIME())`
+
+5. 数据关联两个字段, 数据联动处理, 获取拼接字段的其中一个值:`ARRAYGET(SPLIT(​ 合同编号 ​,"-"), 1)`
+
+6. 明细数据某一列不能重复验证: 自定义验证函数 - 不能使用 scope, 移到 change 事件,通过 forceValid 实现
+
+```
+function validateRule(value, state, ctx) {
+  if (value == "自定义回款") return true
+  const details = ctx.store.getData("tableField_kexuivc3").fieldData.value
+  const arr = details.filter(detail => detail["selectField_kexuivc5"].fieldData.value == value)
+  return arr.length < 2
+}
+```
+
+7. 映射人员搜索框人员信息: `USERFIELD(人员搜索框,"userId")` -- `name`; 获取部门: `DEPTNAME(人员搜索框)`
+
+### 高级公式
+
+- 使用高级公式,条件如涉及到当期表,则条件的首参必须是目标表字段:否则报:主条件参数错误
+- 高级公式 crud:可执行明细插入明细,在关联审批流时。不能同时操作多个明细,或明细和明细外组件
+
+---
+
+## 2.表间数据关联
+
+从一个表单或者流程页面,自动加载其它表单或者流程的明细数据,自动填充到当前页面指定组件内
+
+### 前言
+
+两个准备工作,整个应用仅仅配置一次。**上线前请切换生产地址,通过数据源配置方案仅修改一次即可**
+
+- 提供应用的 code 和 secret:【应用设置】➜【应用数据】➜【应用编码】|【应用秘钥】
+- 在数据源添加库地址,`mjs_LIB` 公共库地址:<https://aliwork.zitoo.com.cn/mjs/mjs.min.js>
+
+### 效果
+
+> [demo](https://www.aliwork.com/alibaba/web/APP_XZ180SDQ2TQ3UUB7KR2A/inst/formSubmit.html?formUuid=FORM-RM966M91I9MJ7RMQ0RKX6B6EN1I23L6ZOJNFKBA&corpid=dingebe19bcf228f85ec35c2f4657eb6378f&dd_addcookie=true) 页面
+
+1. 场景:通过下拉条件关联目标单据值
+2. 方案:支持多条件查询,支持查询后数据过滤,支持查询和明细赋值默认值:将 src 设置为空
+3. 进阶:可实现一表查询更新多明细;多个明细来自多个数据源;主表更新主表:兼容一些当前页面需要公式或者数据联动情况【若需要作为联动条件,需要修复】;明细加载主表数据
+4. 备注:取消所有组件的数据联动,若无异常可不取消 _【新的宜搭版本下拉修改优先级偶尔会高于 js 赋值,因此不取消可能会出现查询结果异常】_
+
+### 参数
+
+- 全局编码命名规则说明:
+
+  - cur 即 current,通常指当前页面内组件 ID;
+  - src 即 source,通常值源表页面内组件 ID;
+  - def 即 default,通常指默认值,默认值优先级低于来源表数据
+
+- 一些必填字段情况说明:
+
+  - formId:来源数据的表单 Id,格式为 `FORM-xxxx`,可在应用设置或者直接打开目标页面,在地址栏粘贴
+  - conditions: 查询条件,格式为 ` [{ src: "", cur: "", def: "" }];`,def 优于 src, src 和 cur 均需要传入组件 ID,支持多条件查询
+  - funcFilter:明细集合数据过滤方法,若不需要过滤则置空。格式为 `detail => detail["compId"] == ""`
+
+- 一些非必填选配字段说明:
+
+  - compId:报错的提示到输入框,若不传入则报错会以 `toast` 方式进行提示。报错提示在输入框,可阻断提交【建议配置】
+  - isForm:数据查询是否来自表单,流程数据通过表单方式亦能查询【通常不需要配置】
+  - errMsg:查询失败报错提示优先使用接口报错,若查询为空,默认提示为 `未查询到匹配数据`【通常不需要配置】
+
+### 案例
+
+> 打开页面的 `js` 功能,复制如下代码,**按需选择**
+
+```
+//---------------------- common ----------------------//
+
+// 工具库: 库地址存储于 dp 在数据页面全局共享, dp 在页面加载后才有值
+function loadAliworkSDK(ctx = window.LeGao.getContext()) {
+  const src = ctx.dp.getValue("mjs_LIB");
+  const script = document.createElement('script');
+  script.setAttribute('type', 'text/javascript');
+  script.setAttribute("src", src);
+  document.body.appendChild(script);
+  script.onload = () => _mjsOnload(ctx);
+}
+
+//---------------------- private ----------------------//
+
+// 加载即调用方法请在此处进行调用
+async function _mjsOnload(ctx) {
+  mjs.aliworkInitParams()
+  // 效验操作组权限:提交态
+  if (mjs._checkSubmitEnv()) { }
+}
+
+//【3.红色都是需要匹配的】明细查询和赋值
+async function _queryAndUpdateCompData(ctx) {
+  //【3.1.多条件查询】表单查询条件:src 来源表条件组件Id,cur 当前表条件组件ID,def 默认值优先级低于来源表数据
+  const conditions = [{
+    src: "textField_kfnjmfq2",
+    cur: "selectField_kfnjp1cu",
+    def: ""
+  }];
+  const resList = await mjs._queryFormOrProcess(ctx, { formId: "FORM-JHYJNJSVPXGLZXSE43HQV9M8XE7U2UHXM00JKB3", /** 数据来源表Id */ conditions, compId_tip: "selectField_kfnjp1cu", /** 非必填:报错的提示到输入框,可阻断提交 */ })
+  //【3.2.1.多明细更新】明细组件Id映射表: src 来源明细表内组件ID,cur 当前明细表内组件ID,def 默认值优先级低于来源表数据
+  const props1 = [{
+    // 名称
+    src: "textField_kfnjmfq4",
+    cur: "selectField_kkny9ed5",
+  },];
+  mjs._updateCompDetailFromDetail(ctx, { resList, props: props1, compId_detail_src: "tableField_kfnjmfq3", /** 来源表明细组件Id */ compId_detail_cur: "tableField_kfnjmfq3" /** 当前明细的compId */ }, { funcFilter: null, /** 过滤方法条件:detail => detail["textField_kfnjmfq4"] == "条件" */ })
+  //【3.2.2.多明细更新】明细组件Id映射表: src 来源明细表内组件ID,cur 当前明细表内组件ID,def 默认值优先级低于来源表数据
+  const props2 = [{
+    // 名称
+    src: "textField_kfnjmfq4",
+    cur: "textField_kknt6uqk",
+  },];
+  mjs._updateCompDetailFromDetail(ctx, { resList, props: props2, compId_detail_src: "tableField_kfnjmfq3", /** 来源表明细组件Id */ compId_detail_cur: "tableField_kknt6uqo" /** 当前明细的compId */ }, { funcFilter: null, /** 过滤方法条件:detail => detail["compId"] == "条件" */ })
+  //【3.3.主表更新】组件Id映射表: src 来源表内组件ID,cur 当前表内组件ID,def 默认值优先级低于来源表数据
+  const mains1 = [{
+    // 主表赋值1
+    src: "textField_kfnjmfq2",
+    cur: "textField_kkoslg5n",
+  },];
+  mjs._updateCompMainFromMain(ctx, { resList, mains: mains1 })
+
+  //【3.4.明细加载主表】组件Id映射表: src 来源表内组件ID,cur 当前表内明细组件ID,def 默认值优先级低于来源表数据
+  const mains2 = [{
+    // 主表赋值1
+    src: "textField_kfnjmfq2",
+    cur: "textField_kkou7cb4",
+  },];
+  mjs._updateCompDetailFromMain(ctx, { resList, props: mains2, compId_detail_cur: "tableField_kkou7cb3" /** 当前明细的compId */ }, { funcFilter: null, /** 过滤方法条件:main => main["compId"] == "条件" */ })
+}
+
+//---------------------- event ----------------------//
+
+// 【1.将事件绑定到页面】页面节点加载渲染完毕
+export function didMount(ctx) {
+  // 加载工具库: dp & 初始化
+  loadAliworkSDK(ctx)
+}
+
+//---------------------- event ----------------------//
+
+// 【2.将事件绑定到查询条件】当查询条件发生变更
+export function onConditionChange(ctx) {
+  // 匹配查询和赋值
+  _queryAndUpdateCompData(ctx)
+}
+```
+
+---
+
+## 3.服务变更流程
+
+服务可执行宜搭公有云接口文档所有服务,也可调用后台开发提供的接口服务
+
+### 前言
+
+若是单据页面,可直接提供高级公式执行更新操作。流程单据不支持高级公式,可通过服务来完成。变更发起者可以是流程也可以是单据
+
+[demo](https://www.aliwork.com/alibaba/web/APP_XZ180SDQ2TQ3UUB7KR2A/admin/pageAppEdit.html?spm=a1z4e7.13467989.0.0.41821791ZVa3Wh#/form/process/FORM-JFYJZQEVMOVLZUIUVZE7VFJZON5P1B4L94QIK08/page?code=TPROC--JFYJZQEVMOVLZUIUVZE7VFJZON5P1B4L94QIK18&navUuid=FORM-JFYJZQEVMOVLZUIUVZE7VFJZON5P1B4L94QIK08&_k=hoakq3) 页面,**目前以流程实例更新为案例**
+
+### 配置
+
+- [接口地址:更新流程实例](https://www.yuque.com/yida-old/help/ihxi9r#r78nho)
+
+- 将接口地址参数配置到宜搭服务,配置路径:【平台管理】➜【服务注册】➜【新增服务】
+
+  1.  类型选择:`GETWAY`
+  2.  url 相对地址:填写对应服务的地址,如流程更新文档为 `/yida_vpc/process/updateInstance.json`
+  3.  参数列表:点击 _增加参数_ 将文档上参数一一对应拷贝过来
+
+      1. 注意每个参数填写完成点击右侧保存,点击确认保护不会自动保存
+      2. 第一列 _参数名_ 必须为文档参数名称,标签名(中文)可填写文档描述,标签名(英文)可填写参数名称
+
+  4.  其余默认即可:名称自行取,建议不要使用特殊符号,在使用的时候通过名称下拉选择
+
+> 从接口参数可知,我们更新实例是需要 `实例 Id:processInstanceId 的`,因此需要一个中间表将 ID 存储起来
+
+- 中间表:记录关键字段,如编号,审批状态,关联名称,实例 ID,余下主表数据可通过编号从原流程单据进行数据联动
+
+- 审批过滤:此处刚好实现了审批过滤功能,一般将审批状态记录为三种状态:0-可提交;1-审批中;2-已通过;3-已关闭
+
+> 数据引用,审批过滤:通过数据联动过滤审批状态,下拉框数据过滤审批状态;将实例 ID 字段通过中间表编号关联到出实例 ID 进行储存,配置服务都需要通过实例 ID 进行操作
+
+- 原始表:添加公式在审批通过完成执行公式,将中间表关键信息进行写入
+
+  1.  **注意:页面配置公式只对提交和编辑有效,执行公式不会触发页面内的组件公式,也不会自动带入默认值。**
+  2.  **所有值的修改都需要写入,所有值的变动都需要写入**,因此此处要写入审批状态为 0【可提交】
+
+- 中间表:将所有字段设置为可写入,将页面隐藏,方便管理员补录\调整数据。
+
+  1.  过滤逻辑为写入数据审批状态为 0,在被引用时过滤为 0 可选取
+  2.  当被下游单据引用时通过单号:在开始节点将审批状态更新为 1【审批中】。在完成节点,通过时将审批状态更新为 2 【已通过】,完成节点拒绝、撤销\终止,将审批状态回退为 0【可提交】。当单子行程闭环,可在适当位置将审批状态更新为 3 【已关闭】
+
+- 变更表:建议将原始表 `Schema` 直接导入过来,删除不能变更的字段,或者保留,将变更字段设置为可编辑,其余为只读
+
+  1.  所有主表字段,通过数据联动完成,单号关联可来自中间表,可自行添加变更状态管理
+  2.  明细字段建议通过接口自动读取,详见**【1.表间数据关联】**;若无此条件,自行添加中间表处理,让用户执行下拉勾线联动
+  3.  **明细字段**:服务对明细的操作是全量替换,为了简化操作,明细内的组件 ID _必须_ 要和原始表一致,方法可以是直接将原表通过 `Schema` 导入过来,也可以是直接复制原表明细组件,粘贴到变更表上
+  4.  **明细变更**:添加一个多行输入框,将需要被修改的明细内组件设置为可操作,余下不可修改都为只读。将如下代码复制到 `js` 面板中,将需要变更的明细内组件设置添加 `onDetailChange `事件,传入 ctx,"当前明细组件 ID", "多行输入框 ID", 若传入 `details` 会优先使用不再去取明细组件值
+
+```
+// 用于明细更新后返回:兼容用户不修改情况下操作
+mjs._assembleDetailForKeyValue(ctx, "", "textareaField_kis8c0n5", details)
+
+// 用于明细内组件变更:兼容用户修改同步明细数据
+mjs._assembleDetailForKeyValue(ctx, "tableField_kfnjmfq3", "textareaField_kis8c0n5")
+```
+
+5.  在流程完成节点配置服务:在同意节点执行服务操作,选择关联操作,选择第三方服务,此处会下拉出现在宜搭平配置的服务的名称列表,选择对应的服务,会出现配置好的参数列表,左侧显示名称为服务配置中文名称
+
+    1. userId:#{LOGINUSER} &nbsp;&nbsp; ➜ 取当前登录人;
+    2. 应用秘钥:在应用设置 ➜ 应用数据 ➜ 应用秘钥
+    3. 应用编码:在应用设置 ➜ 应用数据 ➜ 应用编码
+    4. 实例 ID:输入 # 号键后,会弹出当前页面所有字段选项,选择实例 ID 组件即可
+    5. 更新表单数据:`{"原始表组件ID":"#{当前表组件ID}","原始表组件ID":"#{当前表组件ID}"}`,一一对应,多个按照格式添加即可
+    6. **更新表单数据【明细处理】:**格式和 **5** 一致,_区别是当前明细组件 ID 不使用,使用格式化后的多行文本框的值,**尤其要注意:多行文本框不要添加不要引号,被转义后服务会失败**_,正确格式:`"原始明细组件ID": #{多行文本框ID}`
+
+6.  服务可查询执行日志:
+
+    1. 日志地址:`https://aliwork.com/alibaba/web/APP_TYPE/query/invoke/queryRecord.json%3FpageNo=1&pageSize=10` ,将 `APP_TYPE` 替换为需要查询的应用 Id 即可
+    2. 查询方式:查询的应用是当前页面登录的企业宜搭账号,若不匹配会报无此应用
+
+7.  补充:
+
+    1. 若需要更新的数据大,可使用 [json 格式化](https://www.bejson.com/),完成后复制到 `更新表单数据`
+    2. 操作建议:先配置更新一个主表字段,跑通流程,再测试多个字段,最后再单独测试明细数据
+
+8.  三方服务
+
+    1.  宜搭服务回调是单向的, 没有重试机制, 所以流程审批结束同意操作下需要记录状态
+    2.  在页面内添加记录成功与失败状态的组件, 失败原因. 调用后端服务接口传入 #{formInstId}, 后台执行完成后调用宜搭更新接口会写执行状态
+
+---
+
+### 多选合并查询明细
+
+[demo](https://www.aliwork.com/alibaba/web/APP_LJHV920D3CF3KNPRYN40/admin/pageAppEdit.html?spm=a1z4e7.13467989.0.0.bdcb6ddbakMjxp#/form/receipt/FORM-PFYJO8XU7G2OP1V63M57W66LM24D3H6L4AULKN6/page?navUuid=FORM-PFYJO8XU7G2OP1V63M57W66LM24D3H6L4AULKN6&_k=lxq5eu) 值多选查询表单或者流程:
+
+- 多选条件仅仅支持一个字段;
+- 以第一条查询为主数据, 合并明细;
+- 数据联动可能会异常.
+
+> 明细关联详细使用方式详见 [2.表间数据关联](https://www.yuque.com/gxg6w6/xg2yg3/txoqrm)
+
+```
+
+//---------------------- common ----------------------//
+
+// 工具库: 库地址存储于 dp 在数据页面全局共享, dp 在页面加载后才有值
+function loadAliworkSDK(ctx = window.LeGao.getContext()) {
+  const src = ctx.dp.getValue("mjs_LIB");
+  const script = document.createElement('script');
+  script.setAttribute('type', 'text/javascript');
+  script.setAttribute("src", src);
+  document.body.appendChild(script);
+  script.onload = () => _mjsOnload(ctx);
+}
+
+//---------------------- private ----------------------//
+
+// 加载即调用方法请在此处进行调用
+async function _mjsOnload(ctx) {
+  mjs.aliworkInitParams()
+  // 效验操作组权限:提交态
+  if (mjs._checkSubmitEnv()) { }
+}
+
+//【3.红色都是需要匹配的】明细查询和赋值
+async function _queryAndUpdateCompData(ctx) {
+ //【3.1.多条件查询】表单查询条件:src 来源表条件组件Id,cur 当前表条件组件ID,def 默认值优先级低于来源表数据 - 多选条件仅仅支持一个字段; 以第一条查询为主数据, 合并明细; 数据联动可能会异常
+  const conditions = [{
+    src: "textField_kfnjmfq2",
+    cur: "multiSelectField_klu9cmvv",
+    def: ""
+  }];
+  const resList = await mjs._queryMultipleFormOrProcess(ctx, { formId: "FORM-JHYJNJSVPXGLZXSE43HQV9M8XE7U2UHXM00JKB3", /** 数据来源表Id */ conditions, compId_tip: "multiSelectField_klu9cmvv", /** 非必填:报错的提示到输入框,可阻断提交 */ compId_detail_src: "tableField_kfnjmfq3", /** 来源表明细组件Id */ })
+  //【3.2.1.多明细更新】明细组件Id映射表: src 来源明细表内组件ID,cur 当前明细表内组件ID,def 默认值优先级低于来源表数据
+  const props1 = [{
+    // 名称
+    src: "textField_kfnjmfq4",
+    cur: "selectField_kkny9ed5",
+  },
+  {
+    // 名称
+    src: "textField_kfnjmfq4",
+    cur: "textField_kfnjmfq4",
+  },
+  {
+    // 数量
+    src: "numberField_kfnjmfq5",
+    cur: "numberField_kfnjmfq5",
+    def: 0
+  },
+  {
+    // 价格
+    src: "numberField_kfnjmfq6",
+    cur: "numberField_kfnjmfq6"
+  },
+  {
+    // 小计
+    src: "numberField_kfnjmfq7",
+    cur: "numberField_kfnjmfq7"
+  }];
+  mjs._updateCompDetailFromDetail(ctx, { resList, props: props1, compId_detail_src: "tableField_kfnjmfq3", /** 来源表明细组件Id */ compId_detail_cur: "tableField_kfnjmfq3" /** 当前明细的compId */ }, { funcFilter: null, /** 过滤方法条件:detail => detail["textField_kfnjmfq4"] == "条件" */ })
+
+}
+
+//---------------------- event ----------------------//
+
+// 【1.将事件绑定到页面】页面节点加载渲染完毕
+export function didMount(ctx) {
+  // 加载工具库: dp & 初始化
+  loadAliworkSDK(ctx)
+}
+
+//---------------------- event ----------------------//
+
+// 【2. 将事件绑定到查询条件】当查询条件发生变更
+export function onMultipleConditionChange(ctx) {
+  // 匹配查询和赋值
+  _queryAndUpdateCompData(ctx)
+}
+
+```
+
+> update 2021-02-03 w3 ➜ update 2021-03-02 w2

Plik diff jest za duży
+ 1198 - 0
doc/development.md


+ 77 - 0
doc/sample.md

@@ -0,0 +1,77 @@
+## 新天龙案例
+
+```
+/*** mc 系列之 mjs
+ * 对接宜搭公共JavaScript库
+ * 公共库地址:https://mc.cloudpure.cn/mjs/mjs.min.js
+ ***/
+
+//---------------------- common ----------------------//
+
+// 加载 mjs
+export function _mjsLoad(callback = Function.prototype) {
+  const src = "https://mc.cloudpure.cn/mjs/mjs.min.js"
+  // const src = "http://127.0.0.1:7001/dist/mjs.js"
+  const script = document.createElement('script');
+  script.type = 'text/javascript';
+  script.src = src;
+  document.getElementsByTagName('head')[0].appendChild(script);
+  script.onload = () => this._mjsInit();;
+};
+
+//---------------------- private ----------------------//
+
+// 加载即调用方法请在此处进行调用
+export async function _mjsInit() {
+  await mjs.init(this, { vconsole: false })
+  // 页面环境:0提交(其它),1查看,2编辑(审批)
+  if (!mjs.env) { }
+  mjs._queryList = mjs.optimize.debounce(param => this.queryList(param), 750)
+  this.$("textField_lgp1b94r").set("value", mjs.storage.SS.GET("s_code"))
+}
+
+
+// 查询列表
+export async function queryList(values) {
+  const date = values.cascadeDateField_lgq1sjrd;
+  const rsp = await mjs.request.xhr.doPost("https://mc.cloudpure.cn/api/xintianlong/yd/order-list", {}, {
+    order: {
+      "formUuid": "FORM-3C866TC1MH19IOBZEXJJ58ZQ36IE3FDACV7FL0",
+      "compId_code": "textField_lf7vdf33",
+      "condition_code": values.textField_lgp1b94r,
+      "compId_date": "dateField_lf80vr20",
+      "condition_date": date.start && date.end ? [date.start, date.end] : ""
+    },
+    supplier: {
+      "formUuid": "FORM-AC6660814FW8UIE496KJT4YYKNA022Y1VU7FLC",
+      "compId": "textField_lf7v3zj0",
+      "condition": values.textField_lgp1b94r
+    }
+  })
+  this.$('tablePc_lgp1b94w').set("data", rsp.data.data)
+  mjs.storage.SS.SET("s_code", values.textField_lgp1b94r)
+}
+
+
+//---------------------- event ----------------------//
+
+// 页面节点加载渲染完毕
+export function didMount() {
+  // 工具库: mjs & 初始化
+  this._mjsLoad();
+}
+
+// 查询事件
+export function onSubmit(values) {
+  if (!values.textField_lgp1b94r) {
+    this.utils.toast({
+      title: '客户代码不能为空!', // 'success', 'warning', 'error', 'notice', 'help', 'loading'
+      type: 'error',
+      size: 'large',
+      duration: 2000, // 毫秒, type 为 loding 时无效
+    });
+    return
+  }
+  mjs._queryList(values)
+}
+```

+ 67 - 0
package.json

@@ -0,0 +1,67 @@
+{
+  "name": "mjs",
+  "version": "0.2.0",
+  "main": "dist/mjs.cjs.js",
+  "module": "dist/mjs.esm.js",
+  "unpkg": "dist/mjs.min.js",
+  "keywords": [
+    "rollup",
+    "babel",
+    "starter"
+  ],
+  "author": "malk",
+  "license": "MIT",
+  "scripts": {
+    "cz": "git-cz",
+    "clean": "rimraf dist",
+    "start": "yarn run clean && cross-env NODE_ENV=development rollup -w -c scripts/rollup.config.dev.js",
+    "build": "yarn run clean && cross-env NODE_ENV=production rollup -c scripts/rollup.config.prod.js",
+    "test": "yarn run flow && jest",
+    "flow": "flow check",
+    "serve": "serve -p 8080",
+    "release": "standard-version"
+  },
+  "files": [
+    "src",
+    "dist/*.js"
+  ],
+  "dependencies": {
+    "axios": "^0.19.2",
+    "babel-runtime": "^6.26.0",
+    "diff": "^5.0.0",
+    "dingtalk-jsapi": "^2.10.3",
+    "fecha": "^4.2.0",
+    "file-saver": "^2.0.5",
+    "js-pinyin": "^0.1.9",
+    "number-precision": "^1.4.0",
+    "qs": "^6.9.4",
+    "vconsole": "^3.3.4",
+    "xlsx": "^0.16.1"
+  },
+  "devDependencies": {
+    "babel-core": "^6.26.0",
+    "babel-plugin-transform-runtime": "^6.23.0",
+    "babel-preset-env": "^1.6.1",
+    "babel-preset-flow": "^6.23.0",
+    "babel-preset-stage-2": "^6.24.1",
+    "cross-env": "^5.1.3",
+    "flow-bin": "^0.62.0",
+    "jest": "^22.0.4",
+    "postcss": "^8.4.14",
+    "rimraf": "^2.6.2",
+    "rollup": "^0.53.3",
+    "rollup-plugin-alias": "^1.4.0",
+    "rollup-plugin-babel": "^3.0.3",
+    "rollup-plugin-commonjs": "^8.2.6",
+    "rollup-plugin-filesize": "^1.5.0",
+    "rollup-plugin-json": "^4.0.0",
+    "rollup-plugin-node-resolve": "^3.0.0",
+    "rollup-plugin-postcss": "^4.0.2",
+    "rollup-plugin-replace": "^2.0.0",
+    "rollup-plugin-serve": "^0.4.2",
+    "rollup-plugin-uglify": "^2.0.1",
+    "serve": "^10.0.2",
+    "standard-version": "^4.3.0",
+    "uglify-es": "^3.3.4"
+  }
+}

BIN
public/favicon.ico


+ 19 - 0
public/index.css

@@ -0,0 +1,19 @@
+.upload-btn {
+  display: inline-block;
+  padding: 5px 15px;
+  font-size: 14px;
+  text-align: center;
+  border-radius: 5px;
+  overflow: hidden;
+  cursor: pointer;
+  color: #fff;
+  border: 1px solid #169bd5;
+  background-color: #169bd5;
+}
+
+.next-shell,
+.next-shell *,
+.next-shell :after,
+.next-shell :before {
+  font-size: 20px;
+}

+ 22 - 0
public/index.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+  <link rel="icon" href="./favicon.ico" />
+  <link rel="stylesheet" href="./index.css" />
+  <title>Rollup UMD</title>
+</head>
+
+<body>
+  <h1 id="label">rollup</h1>
+  <div class="upload-btn" onclick="fileInput()">批量导入</div>
+  <p>设置一个隐藏域,用来导入文件</p>
+  <input type="file" name="file" accept=".xlsx, .xls" id="fileReader" style="display: none" onchange="fileChange();" />
+  <script src="../dist/mjs.js"></script>
+  <script src="./index.js"></script>
+</body>
+
+</html>

+ 46 - 0
public/index.js

@@ -0,0 +1,46 @@
+window.onload = async function () {
+  const element = document.getElementById("label");
+  element.innerHTML = await mjs.getTask();
+
+  console.log("cookie", ck("Hm_lvt_34b4e2ce36a1bc364012b1aa8161c8b8"));
+
+  console.log("pinyin", mjs.getCaptureUpper("管理员"));
+
+  const res = await mjs.ddingAddressBook("342523198812232527");
+  const obj = JSON.parse(res.data);
+  const arr = [];
+  obj.parentDpData[obj.userInfo.department[0]].forEach((item) => {
+    if (!item.errcode) arr.push(item.userid);
+  });
+  console.log("主管列表: ", arr);
+
+  console.log("calcStripDay", mjs.calcCeilDay(1594177368483, 1594363480000));
+};
+
+function fileInput() {
+  document.getElementById("fileReader").click();
+}
+
+async function fileChange() {
+  // 判断文件是否为空
+  const file = document.getElementById("fileReader").files[0];
+  console.log("file", file);
+  if (!file) return;
+}
+
+function ck(name) {
+  var nameEQ = name + "=";
+  var ca = document.cookie.split(";"); // 把cookie分割成组
+  for (let i = 0; i < ca.length; i++) {
+    var c = ca[i]; // 取得字符串
+    while (c.charAt(0) == " ") {
+      // 判断一下字符串有没有前导空格
+      c = c.substring(1, c.length); // 有的话,从第二位开始取
+    }
+    if (c.indexOf(nameEQ) == 0) {
+      // 如果含有需要的name
+      return unescape(c.substring(nameEQ.length, c.length)); // 解码并截取我们要值
+    }
+  }
+  return false;
+}

+ 55 - 0
scripts/rollup.config.base.js

@@ -0,0 +1,55 @@
+import alias from "rollup-plugin-alias";
+import resolve from "rollup-plugin-node-resolve";
+import commonjs from "rollup-plugin-commonjs";
+import babel from "rollup-plugin-babel";
+import replace from "rollup-plugin-replace";
+import json from "rollup-plugin-json";
+import postcss from "rollup-plugin-postcss";
+
+export default {
+  input: "src/main.js",
+  plugins: [
+    alias({
+      resolve: [".js"],
+    }),
+    replace({
+      "process.env.NODE_ENV": JSON.stringify(
+        process.env.NODE_ENV || "development"
+      ),
+    }),
+    postcss({
+      extensions: [".css"],
+    }),
+    //  NOTE: ****** 2020.5.7 兼容 axios, 否则报错 process 未定义 ******
+    resolve({
+      jsnext: true,
+      preferBuiltins: true,
+      browser: true,
+    }),
+    commonjs({
+      // non-CommonJS modules will be ignored, but you can also
+      // specifically include/exclude files
+      include: "node_modules/**",
+    }),
+    babel({
+      runtimeHelpers: true,
+      exclude: "node_modules/**", // only transpile our source code
+    }),
+    json({
+      // All JSON files will be parsed by default,
+      // but you can also specifically include/exclude files
+      include: "node_modules/**",
+      exclude: ["node_modules/foo/**", "node_modules/bar/**"],
+      // for tree-shaking, properties will be declared as
+      // variables, using either `var` or `const`
+      preferConst: true, // Default: false
+      // specify indentation for the generated default export —
+      // defaults to '\t'
+      indent: "  ",
+      // ignores indent and generates the smallest code
+      compact: true, // Default: false
+      // generate a named export for every property of the JSON object
+      namedExports: true, // Default: true
+    }),
+  ],
+};

+ 29 - 0
scripts/rollup.config.dev.js

@@ -0,0 +1,29 @@
+import baseConfig from "./rollup.config.base";
+import serve from "rollup-plugin-serve";
+
+import { name } from "../package.json";
+
+export default {
+  ...baseConfig,
+  output: [
+    {
+      file: `dist/${name}.js`,
+      format: "umd",
+      name,
+      sourcemap: true,
+    },
+    {
+      file: `dist/${name}.cjs.js`,
+      format: "cjs",
+      name,
+      sourcemap: "inline",
+    },
+  ],
+  plugins: [
+    ...baseConfig.plugins,
+    serve({
+      port: 7001,
+      contentBase: [""],
+    }),
+  ],
+};

+ 68 - 0
scripts/rollup.config.prod.js

@@ -0,0 +1,68 @@
+import filesize from "rollup-plugin-filesize";
+import uglify from "rollup-plugin-uglify";
+import { minify } from "uglify-es";
+
+import baseConfig from "./rollup.config.base";
+import { name, version, author } from "../package.json";
+
+// banner
+const banner =
+  `${"/*!\n" + " * "}${name}.js v${version}\n` +
+  ` * (c) 2018-${new Date().getFullYear()} ${author}\n` +
+  ` */`;
+
+// 支持输出 []
+export default [
+  // .js, .cjs.js, .esm.js
+  {
+    ...baseConfig,
+    output: [
+      // umd development version with sourcemap
+      {
+        file: `dist/${name}.js`,
+        format: "umd",
+        name,
+        banner,
+        sourcemap: true,
+      },
+      // cjs and esm version
+      {
+        file: `dist/${name}.cjs.js`,
+        format: "cjs",
+        banner,
+      },
+      // cjs and esm version
+      {
+        file: `dist/${name}.esm.js`,
+        format: "es",
+        banner,
+      },
+    ],
+    plugins: [...baseConfig.plugins, filesize()],
+  },
+  // .min.js
+  {
+    ...baseConfig,
+    output: [
+      // umd with compress version
+      {
+        file: `dist/${name}.min.js`,
+        format: "umd",
+        name,
+        banner,
+      },
+    ],
+    plugins: [
+      ...baseConfig.plugins,
+      uglify(
+        {
+          compress: {
+            drop_console: false,
+          },
+        },
+        minify
+      ),
+      filesize(),
+    ],
+  },
+];

+ 38 - 0
src/aliwork/bom.js

@@ -0,0 +1,38 @@
+/// 自定义页面bom展示效果 ///
+export default {
+
+  compId: {
+    parent: "",
+    child: "",
+    top: "",  // 第一层
+    name: ""
+  },
+
+  collectChildren (dto, baseList) {
+    const tempList = baseList.filter(d => dto[this.compId.child] == d[this.compId.parent]);
+    if (tempList.length > 0) dto.children = tempList;
+    for (const child of tempList) {
+      this.collectChildren(child, baseList);
+    }
+  },
+
+  // 查询bom
+  async queryHierarchy ({ dpRemote, formUuid, conditions, compId_tree }) {
+    const rsp = await mjs.request.dp.queryForm({ formUuid, conditions, dpRemote }, { queryAll: true })
+    const dataList = rsp.data.map(item => {
+      return {
+        ...item.formData,
+        label: item.formData[this.compId.name],
+        key: item.formData[this.compId.child],
+        formInstId: item.formInstId
+      }
+    });
+    const topList = dataList.filter(d => this.compId.top == d[this.compId.parent]);
+    for (const dto of topList) {
+      this.collectChildren(dto, dataList);
+    }
+    if (compId_tree) mjs.$this.$(compId_tree).set("dataSource", topList)
+    return dataList;
+  }
+
+}

+ 10 - 0
src/aliwork/bus.js

@@ -0,0 +1,10 @@
+// 回调事件
+export default {
+  // dom监听按钮点击: 提交事件回调, 添加成功执行 [页面存在beforeSubmit下需要手动调用]
+  DOM_CALLBACK_SUBMIT: null,
+  // dom监听按钮点击: 审批事件回调, 入参有按钮的文本
+  DOM_CALLBACK_APPROVE: null,
+};
+
+
+// todolist  作为判断条件; 配置config, 方法名称改为register; dom操作方法名称更新

+ 117 - 0
src/aliwork/com.js

@@ -0,0 +1,117 @@
+const com = {};
+
+// 页面环境: 0提交(其它),1查看,2编辑(审批)
+com.checkEnv = function () {
+  const instanceData = mjs.$this.utils.getFormInstanceData();
+  const { flowData = {} } = instanceData;
+  const { editMode, viewMode } = flowData;
+  if (editMode) return 2; // 审批页面为2, 编辑状态
+  if (viewMode) return 1;
+  return 0;
+};
+
+// 冗余Toast提示方法
+com.showMessage = function (title, type = "success", size = "medium", duration = "750") {
+  if (!title) return;
+  mjs.$this.utils.toast({ type, title, size, duration });
+};
+
+// type: 'success', 'warning', 'error', 'notice', 'help', 'loading'
+com.showErrorMessage = function (title) {
+  this.showMessage(title, "error");
+}
+
+// size: large, medium
+com.showSuccessMessage = function (title) {
+  this.showMessage(title);
+}
+
+// 冗余显示全屏loading: 全局对象
+let G_DIALOG;
+com.showLoading = function (title = "拼命加载中...") {
+  if (G_DIALOG) return; // 避免多次闪屏
+  G_DIALOG = mjs.$this.utils.toast({ type: "loading", title, closeable: false, footer: false, messageProps: { type: "loading", }, });
+};
+
+// 冗余隐藏全屏loading: 全局对象
+com.hideLoading = function () {
+  G_DIALOG && G_DIALOG();
+  G_DIALOG = null;
+};
+
+// 弹出确认框
+com.showConfirm = function (title, content, type = "confirm") {
+  if (!title && !content) {
+    throw new Error(`${type} => The title and content are empty.`);
+  }
+  return new Promise((resolve, reject) => {
+    mjs.$this.utils.dialog({
+      type, title, content, /* 如需换行可传入 HTML/JSX 来实现 */
+      onOk: () => resolve(),
+      onCancel: () => reject("用户取消了"),
+    });
+  });
+};
+
+// 提交校验toast
+com.toastAccess = function (isShow, title, resolve, reject) {
+  console.log(isShow, title)
+  if (isShow) {
+    mjs.$this.utils.toast({
+      title: title, // 'success', 'warning', 'error', 'notice', 'help', 'loading'
+      type: 'error',
+      size: 'large',
+      duration: 2000, // 毫秒, type 为 loding 时无效
+    });
+    reject && reject(title)
+    return false;
+  }
+  resolve && resolve()
+  return true;
+}
+
+/**
+ * ppExt: 提交校验 [存在请求若报错无需catch]
+ * 1. 返回Promise时,不能在Promise外直接return,会提示undefined
+ * 2. 失败需要返回reject,否则不会重置按钮loading状态,无法重新发起提交
+ */
+
+// 提交错误toast
+com.toastError = function (title, resolve, reject) {
+  return this.toastAccess(!!title, title, resolve, reject)
+}
+
+// 提交弱校验【后置: 置于所有判断的最后, 避免命中若校验直接提交了】
+com.dialogTips = function (title, message, resolve, reject) {
+  if (title || message) {
+    mjs.$this.utils.dialog({
+      method: 'alert', // 'alert', 'confirm', 'show'
+      title: title,
+      content: message, // 如需换行可传入 HTML/JSX 来实现
+      onOk: () => {
+        resolve && resolve()
+      },
+      onCancel: () => {
+        reject && reject()
+      }
+    });
+    return false
+  }
+  return true
+}
+
+// 获取当前地址的表单实例ID
+com.getFormInstIdByUrl = function () {
+  if (mjs.$this) {
+    // 智障的设计:流程从管理页面进入是 formInstId ,流程提交完成是 procInsId
+    const query = mjs.$this.utils.router.getQuery();
+    return query.procInsId || query.formInstId;
+  }
+  const url = window.location.href;
+  if (url.includes("procInsId=")) {
+    return url.split("procInsId=")[1].split("&")[0];
+  }
+  return url.split("formInstId=")[1].split("&")[0];
+}
+
+export default com;

+ 18 - 0
src/aliwork/cvt.js

@@ -0,0 +1,18 @@
+import { previewImageScale } from "../vendor/dingApi";
+import { aliworkConvertOpenUrl } from "../service/network";
+
+const cvt = {}; // convert
+
+// 宜搭附件转免登地址, 需要在 u8_connecter 项目内添加应用信息
+cvt.previewImage = async function (that, compId_image, projectId) {
+  const images = that.$(compId_image).getValue();
+  if (!images.length) return;
+  const urls = images.map(
+    (item) => `https://www.aliwork.com${item.downloadURL}`
+  );
+  const resp = await aliworkConvertOpenUrl({ projectId, imageUrl: urls.join(","), });
+  if (!resp.success) return;
+  previewImageScale(resp.data);
+};
+
+export default cvt;

+ 224 - 0
src/aliwork/dom.js

@@ -0,0 +1,224 @@
+const dom = {};
+
+// 异常打印和评论按钮: 兼容BUG, 平台设置不生效
+dom.removeMobInvalid = function (that) {
+  const doms = document.querySelectorAll(".flow-operation-button");
+  doms.forEach((ele) => {
+    if (["评论", "打印"].includes(ele.textContent)) ele.remove();
+  });
+};
+
+// 设置iframe组件, 并且加载;链接
+dom.iframeLoadFullScreen = function (that, { compId, link, bottom = 44 }) {
+  document.getElementById(`frame_${compId}`).style.height =
+    window.screen.height - bottom + "px";
+  that.$(compId).set("src", link);
+};
+
+// 去除tab间距样式调整
+dom.removeLayoutSpace = function (that) {
+  const doms = document.querySelectorAll(".next-tabs-content");
+  doms.forEach((elem) => elem.remove());
+};
+
+import ding from "../vendor/dingApi";
+import bus from "./bus";
+
+// 审批页面: button事件的3种绑定方式以及触发逻辑:https://www.cnblogs.com/ooo0/p/7742214.html
+dom.registerFlowEvent = function (that) {
+  if (mjs.env != 2) return;
+  setTimeout(() => {
+    const approveEvent = (title) => {
+      // 回调审批事件
+      if (bus.DOM_CALLBACK_APPROVE) {
+        bus.DOM_CALLBACK_APPROVE(that, title);
+        return;
+      }
+    }
+    // PC端
+    const doms = document.querySelectorAll(".flow-button"); // 使用getElementsByClassName拿不到dom实体
+    if (doms.length) {
+      // 读取弹出框按钮: 延迟体验
+      const listenerDialog = function (title) {
+        setTimeout(() => {
+          // 多个使用不需要添加空格
+          document.querySelectorAll(".next-btn.next-small").forEach((ele) => {
+            // 取消刷新页面: dom二次会失效
+            if ("取消" == ele.innerText) {
+              ele.onclick = function () {
+                window.location.reload()
+              };
+            }
+            if ("确认" == ele.innerText) {
+              ele.onclick = function () {
+                approveEvent(title);
+              };
+            }
+          });
+        }, 200);
+      };
+      doms.forEach((ele) => {
+        // 目前退回为加签逻辑 ==> 退回页面环境为编辑态值2
+        if (["同意", "拒绝", "退回", "转交", "提交"].includes(ele.innerText)) {
+          ele.onclick = function () {
+            listenerDialog(ele.innerText);
+          };
+        }
+      });
+      return;
+    }
+    // 移动端
+    const listenerDialog = function (title) {
+      // 读取弹出框按钮: 延迟体验
+      setTimeout(() => {
+        let doms = document.querySelectorAll(".t-button"); // 其它
+        if (!doms.length) doms = document.querySelectorAll(".mt-button"); // 退回
+        // 取消刷新页面: dom二次会失效
+        document.querySelectorAll(".flow-layer-header-left").forEach(ele => {
+          ele.onclick = function () {
+            window.location.reload()
+          };
+        })
+        doms.forEach((ele) => {
+          if (["提交", "确认退回"].includes(ele.innerText)) {
+            ele.onclick = function () {
+              approveEvent(title);
+            };
+          }
+        });
+      }, 200);
+    };
+    document.querySelectorAll(".icon-box").forEach((ele) => {
+      // 目前退回为加签逻辑 ==> 退回页面环境为编辑态值2
+      if (["退回", "转交", "提交"].includes(ele.innerText)) {
+        ele.onclick = function () {
+          listenerDialog(ele.innerText);
+        };
+      }
+    });
+    document.querySelectorAll(".mt-button").forEach((ele) => {
+      // 目前退回为加签逻辑 ==> 退回页面环境为编辑态值2
+      if (["同意", "拒绝"].includes(ele.innerText)) {
+        ele.onclick = function () {
+          listenerDialog(ele.innerText);
+        };
+      }
+    });
+  }, 200);
+};
+
+// 提交页面: button事件的3种绑定方式以及触发逻辑:https://www.cnblogs.com/ooo0/p/7742214.html
+dom.closeCurrentTabForSubmit = function (that) {
+  // 退回再提交走审批事件, 目前退回为加签逻辑 ==> 退回页面环境为编辑态值2
+  if (mjs.env) return;
+  function closeTab () {
+    // 执行顺序: onclick > addEventListener > beforeSubmit
+    // 特别说明:在提交情况下,页面存在beforeSubmit,相关的逻辑在beforeSubmit下要手动调用,因为这里是直接触发若是有判断情况下决定权交给beforeSubmit处理
+    if (that.beforeSubmit) return;
+    // 异步不会等待
+    dom.closeTabCompatibilityBeforeSubmit(that);
+  }
+  // PC端 & 移动端
+  let doms = document.querySelectorAll(".deep-form-submit");
+  if (!doms.length) return
+  doms[0].addEventListener("click", closeTab, false);
+};
+
+// 兼容 beforeSubmit 情况下, 异步且在按钮事件之后的问题 执行顺序: onclick > addEventListener > beforeSubmit
+dom.closeTabCompatibilityBeforeSubmit = function (that) {
+  bus.DOM_CALLBACK_SUBMIT(that);
+  // 关闭页面: 延迟体验
+  ding.closeNavigationForDomEvent(that);
+};
+
+// 隐藏tab空白
+dom.removeTabLayoutContainer = function () {
+  const elemP = document.querySelectorAll(".next-tabs-content")[0];
+  if (elemP) elemP.remove();
+}
+
+// 自定义页面全屏: pc
+dom.removeSpaceForCustomPage = function () {
+  const page = document.getElementsByClassName("vc-rootcontent");
+  if (page.length) {
+    page[0].style.margin = "0px";
+    page[0].className = "";
+  }
+}
+
+// 隐藏默认打印模板
+dom.removePrinterForDefault = function () {
+  if (!mjs.env) return;
+  // pc - 全屏
+  let doms = document.querySelectorAll(".next-icon-printer");
+  if (doms.length) {
+    doms[0].onclick = function () {
+      setTimeout(() => {
+        document.querySelectorAll(".next-menu-item").forEach(elem => {
+          if (elem.innerText == "默认模板") {
+            elem.remove()
+          }
+        })
+      }, 100);
+    };
+  }
+  // pc - 侧边栏
+  doms = document.querySelectorAll(".next-yida-button-iconOnly");
+  if (doms.length) {
+    doms[2].onclick = function () {
+      setTimeout(() => {
+        document.querySelectorAll(".next-menu-item").forEach(elem => {
+          if (elem.innerText == "打印") {
+            elem.onclick = function () {
+              setTimeout(() => {
+                document.querySelectorAll(".next-menu-item").forEach(elem => {
+                  if (elem.innerText == "默认模板") {
+                    elem.remove()
+                  }
+                })
+              }, 100);
+            }
+          }
+        })
+      }, 100)
+    }
+  }
+  // mobile
+  setTimeout(() => {
+    doms = document.querySelectorAll(".icon-box.dayin");
+    if (doms.length) {
+      doms[0].onclick = function () {
+        setTimeout(() => {
+          document.querySelectorAll(".mt-actionsheet-option").forEach(elem => {
+            if (elem.innerText == "默认模板") {
+              elem.remove()
+            }
+          })
+        }, 100);
+      };
+    }
+  }, 400);
+
+}
+
+// 隐藏撤销按钮
+dom.removeTerminationButton = function () {
+  if (!mjs.env) return;
+  // pc
+  setTimeout(() => {
+    const doms = document.querySelectorAll(".deep-button-group-item");
+    if (doms.length) {
+      doms[0].remove()
+      return
+    }
+  }, 200)
+  // mobile
+  setTimeout(() => {
+    const doms = document.querySelectorAll(".icon-box.chexiao");
+    if (doms.length) {
+      doms[0].remove()
+    }
+  }, 400);
+}
+
+export default dom;

+ 107 - 0
src/aliwork/dp.js

@@ -0,0 +1,107 @@
+import { __redundancy_query__ } from "./private/redundancy";
+import config from "../config/conf"
+
+// 前端接口
+const dp = {};
+
+// 更新数据逻辑: 相对路径 `/${window.pageConfig.appType}/v1/form/searchFormDatas.json`
+dp.updateForm = async function ({ formInstId, updateData, dpRemote = "updateForm", isLoading = true, hideToast, message = "操作成功", } = {}, { title, content } = {}) {
+  if (!formInstId) {
+    throw new Error(`The params of formInstId is empty.`);
+  }
+  // 确认弹出框
+  if (title || content) await mjs.com.showConfirm(title, content);
+  if (isLoading) mjs.com.showLoading();
+  const rsp = await mjs.$this.dataSourceMap[dpRemote].load({ formInstId, updateFormDataJson: JSON.stringify(updateData) }).catch((error) => {
+    if (!hideToast) mjs.com.showErrorMessage(error.message);
+  });
+  if (isLoading) mjs.com.hideLoading();
+  if (!rsp) return;
+  if (rsp && !hideToast) mjs.com.showMessage(message);
+};
+
+// 查询实例列表逻辑: 相对路径 `/${window.pageConfig.appType}/v1/form/searchFormDatas.json`
+dp.queryForm = async function ({ formUuid, conditions = [], dpRemote = "queryForm", isLoading = true, hideToast = true, matchAllCondition = false, message = "查询成功" } = {}, { currentPage = 1, pageSize = config.pageSize, queryAll } = {}) {
+  // 查询条件格式化
+  const searchCondition = conditions.reduce((acc, cur) => {
+    const value = __redundancy_query__(mjs.$this, cur);
+    if (value) {
+      acc[cur.src] = value;
+    }
+    return acc;
+  }, {});
+  const searchLength = Object.keys(searchCondition).length;
+  if (conditions.length && !searchLength) return [];
+  // 是否强制匹配多参必须都有值
+  if (matchAllCondition && searchLength != conditions.length) return [];
+  const queryFunc = async (currentPage) => {
+    const params = {
+      searchFieldJson: JSON.stringify(searchCondition),
+      currentPage,
+      pageSize,
+    };
+    // 兼容默认参数已设置FormUuid, 为空会被覆盖
+    if (formUuid) params.formUuid = formUuid;
+    return await mjs.$this.dataSourceMap[dpRemote].load(params).catch((error) => {
+      if (!hideToast) mjs.com.showErrorMessage(error.message);
+    });
+  }
+  if (isLoading) mjs.com.showLoading();
+  const resp = await queryFunc(currentPage);
+  const list = resp.data;
+  const pages = Math.ceil(resp.totalCount / pageSize);
+  if (queryAll && pages > currentPage) {
+    const promiseReq = [];
+    const pageCount = Math.ceil(config.upperLimit / pageSize);
+    for (let index = currentPage + 1; index < pageCount + currentPage && index <= pages; index++) {
+      promiseReq.push(queryFunc(index))
+    }
+    const respArr = await Promise.all(promiseReq)
+    respArr.forEach(rsp => {
+      list.push(...rsp.data)
+    })
+  }
+  if (isLoading) mjs.com.hideLoading();
+  if (resp && !hideToast) mjs.com.showMessage(message);
+  return { totalCount: resp.totalCount, data: list }
+};
+
+// 明细全量查询: Promise并发
+dp.queryDetail = async function ({ formUuid, conditions = {}, dpRemote = "queryForm" } = {}, { currentPage = 1, pageSize = config.pageSize, detailCount = config.detailCount } = {}) {
+  const searchCondition = conditions.reduce((acc, cur) => {
+    const value = __redundancy_query__(mjs.$this, cur);
+    if (value) {
+      acc[cur.src] = value;
+    }
+    return acc;
+  }, {});
+  const queryFunc = async (currentPage) => {
+    const params = {
+      searchFieldJson: JSON.stringify(searchCondition),
+      currentPage,
+      pageSize,
+    };
+    // 兼容默认参数已设置FormUuid, 为空会被覆盖
+    if (formUuid) params.formUuid = formUuid;
+    return await mjs.$this.dataSourceMap[dpRemote].load(params);
+  }
+  mjs.com.showLoading();
+  const resp = await queryFunc(currentPage)
+  const list = resp.data;
+  const pages = Math.ceil(resp.totalCount / pageSize);
+  if (pages > currentPage) {
+    const promiseReq = [];
+    const pageCount = Math.ceil(detailCount / pageSize);
+    for (let index = currentPage + 1; index < pageCount + currentPage && index <= pages; index++) {
+      promiseReq.push(queryFunc(index))
+    }
+    const respArr = await Promise.all(promiseReq)
+    respArr.forEach(res => {
+      list.push(...res.data)
+    })
+  }
+  mjs.com.hideLoading();
+  return { totalCount: resp.totalCount, data: list }
+}
+
+export default dp;

+ 62 - 0
src/aliwork/private/assemble.js

@@ -0,0 +1,62 @@
+// 数据逻辑组装
+import {
+  _resetDetailData,
+  __redundancy_value__,
+} from "../../deploy/aliwork-v3";
+
+const logic = {}
+
+// 明细更新明细数据
+logic.updateCompDetailFromDetail = function (that,
+  { resList, props, compId_detail_src, compId_detail_cur } = {},
+  { funcFilter, isForm = true, isUpdate = true } = {}) {
+  _resetDetailData(that, compId_detail_cur, props); // 重置明细数据
+  if (!resList.length) return;
+  const form = resList[0][isForm ? "formData" : "data"];
+  if (!form) return;
+  let srcDetails = form[compId_detail_src];
+  if (funcFilter) {
+    // 过滤方法条件:detail => detail["compId"] == "条件"
+    srcDetails = srcDetails.filter((detail) => funcFilter(detail));
+  }
+  const details = srcDetails.map((detail) => {
+    return props.reduce((row, prop) => {
+      row[prop.cur] = __redundancy_value__(detail, prop.src, prop.def);
+      return row;
+    }, {});
+  });
+  /** 连续更新会异常: 需要调用调用forceUpdate才会生效, 若后续即将有更新, 这里置为false, 匹配明细: queryArrayFormOrProcessAndUpdate */
+  if (isUpdate) that.$(compId_detail_cur).setValue(destails);
+  return details;
+}
+
+// 明细加载主表数据
+logic.updateCompDetailFromMain = async function (
+  that,
+  { resList, props, compId_detail_cur } = {},
+  { funcFilter, isForm = true } = {}
+) {
+  _resetDetailData(that, compId_detail_cur, props); // 重置明细数据
+  if (!resList.length) return;
+  let srcList = resList;
+  if (funcFilter) {
+    // 过滤方法条件:main => main["compId"] == "条件"
+    srcList = srcList.filter((item) =>
+      funcFilter(item[isForm ? "formData" : "data"])
+    );
+  }
+  const details = srcList.map((item) => {
+    const detail = item[isForm ? "formData" : "data"];
+    return props.reduce((row, prop) => {
+      let value = __redundancy_value__(detail, prop.src, prop.def);
+      // 兼容主表填充明细获取实例ID
+      if (prop.def == "formInstId") value = item[prop.def];
+      row[prop.cur] = value;
+      return row;
+    }, {});
+  });
+  that.$(compId_detail_cur).setValue(details);
+  return details;
+}
+
+export default logic;

+ 89 - 0
src/aliwork/private/redundancy.js

@@ -0,0 +1,89 @@
+// 组件数据处理
+
+// 接口返回取值到组件, 人员搜索框需要对象类型
+export function __redundancy_value__ (row, prop, def) {
+  // 为空返回默认值
+  if (!prop) return def;
+  // 取值判空, 默认值规则
+  let value = prop ? row[prop] : def;
+  if (!value) return value;
+  // 人员搜索框: 取值组装为集合
+  if (prop.includes("employeeField_")) {
+    value = row[prop].reduce((acc, cur, idx) => {
+      acc.push({ key: row[`${prop}_id`][idx], label: cur });
+      return acc;
+    }, []);
+  }
+  // 附件赋值, jsonString 转镀锡即可
+  if (prop.includes("attachmentField_")) {
+    value = JSON.parse(row[prop]);
+  }
+  return value;
+}
+
+// 明细组件数据格式化, 接口服务人员搜索框需要集合的 json string
+export function __redundancy_server__ (row, prop, fromComp) {
+  let value;
+  if (fromComp) {
+    value = row[prop]; // 3.0 无filedData包装
+    value = value && value != null ? value : undefined;
+    if (prop.includes("employeeField_")) {
+      const arr = value.reduce((acc, cur) => {
+        acc.push(cur.key);
+        return acc;
+      }, []);
+      value = JSON.stringify(arr);
+    }
+    return value;
+  }
+  // 接口返回数据, 无 fieldData.value
+  if (prop.includes("employeeField_")) {
+    value = row[`${prop}_id`];
+  } else {
+    value = row[prop];
+  }
+  return value;
+}
+
+// 查询条件兼容各种组件形式
+export function __redundancy_query__ (that, cur) {
+  let value = "cur" in cur ? that.$(cur.cur).getValue() || cur.def : cur.def;
+  // 兼容人员搜索框, 单选 + 多选
+  if (cur.cur && cur.cur.includes("employeeField_")) {
+    const arrEmp = [];
+    const state = that.$(cur.cur).getValue() || [];
+    // 单选
+    if (state.length === undefined) {
+      arrEmp.push(state.value);
+    }
+    // 多选且有值
+    if (state.length) {
+      arrEmp.push(...state.map((emp) => emp.value));
+    }
+    value = arrEmp;
+    // 兼容匹配isAll, 避免被忽略
+    if (!value.length) value = undefined;
+  }
+  // 兼容日期查询: 时间戳, cur为开始, end为默认, 若无则传def
+  if (cur.src.includes("dateField_")) {
+    const end = "end" in cur ? that.$(cur.end).getValue() || cur.def : cur.def;
+    value = [value, end];
+    // 兼容匹配isAll, 避免被忽略
+    if (!value || !end) value = undefined;
+  }
+  // 兼容部门, 精确匹配, 兼容多选
+  if (cur.cur && cur.cur.includes("departmentSelectField_")) {
+    const depart = that.$(cur.cur).getValue();
+    // 兼容匹配isAll, 避免被忽略
+    if (!depart.length) {
+      value = undefined;
+    } else {
+      value = depart.map((item) => item.value).join(",");
+    }
+  }
+  // 20.8.20 关联表单
+  if (cur.cur && cur.cur.includes("associationFormField_") && value.length) {
+    value = value.shift().title;
+  }
+  return value;
+}

+ 33 - 0
src/aliwork/scene.js

@@ -0,0 +1,33 @@
+// 使用场景
+const scene = {};
+
+scene.verifyCrossQueryList = function (that) {
+  return new Promise((resolve) => {
+    const conditions = [
+      {
+        // 客户池:2或4或8
+        src: "radioField_ku3xv3p1",
+        def: "是",
+      },
+      {
+        // 客户负责人
+        src: "employeeField_ktjokwzq",
+        cur: "employeeField_ku2hu5pb",
+      },
+    ];
+    mjs.request.dp.queryFormList(that, {
+      formUuid: "FORM-1K766GD1T9FT3SB625AX56TJ776T3QVPA2BTK2A",
+      conditions,
+      isLoading: false,
+    });
+  });
+};
+
+
+previewImage = function (e) {
+  var t = (e || {}).current;
+  t && a.default.show({
+    isMobileEnv: (0, o.isMobile)(),
+    photos: { data: [{ previewSrc: t, src: t }] }
+  })
+}

+ 61 - 0
src/auth/copyright.js

@@ -0,0 +1,61 @@
+const auth = {};
+
+/** @授权成功后挂载API */
+auth.requestLib = function () {
+  return new Promise((resolve, reject) => {
+    const appType = pageConfig.appType || pageConfig.appKey;
+    setTimeout(() => {
+      const resp = {
+        success: true,
+        code: 200,
+        message: "请求成功",
+        msg: "success",
+        data: {
+          appType,
+          desc: "产品授权",
+        },
+      };
+
+      resolve(resp);
+      // const msg = `mjs load failure. ♨ 访问应用: ${resp.data.appType} ${resp.message} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
+      // reject(msg);
+    }, 750);
+  });
+};
+
+
+/** @打开钉钉名片 */
+auth.contactNoDing = function (noDing) {
+  window.open(
+    `dingtalk://dingtalkclient/action/sendmsg?dingtalk_id=${noDing}`, // 燕江钉钉号
+    "_self"
+  );
+};
+
+/** @钉钉名片牧语 */
+auth.contactUs = function () {
+  window.open(
+    "dingtalk://dingtalkclient/action/sendmsg?dingtalk_id=yanjiangboy", // 燕江钉钉号
+    "_self"
+  );
+};
+
+////// 阿竹:invitedDeptId=58279212,invitedCode=P127003E238004,dingtalk_id=k9b-l2lrvwk1v //////
+
+/** @钉钉商务名片 */
+auth.contactBusiness = function () {
+  window.open(
+    "dingtalk://dingtalkclient/action/sendmsg?dingtalk_id=k9b-l2lrvwk1v",
+    "_self"
+  );
+};
+
+/** @创建场景群 */
+auth.contactLeads = function (channel = "上海云璞宜搭模板") {
+  window.open(
+    `https://partner.dingtalk.com/opportunity_web.html?templateId=75fe8503808347c6ab6269b431a1e254&invitedCode=P127003E238004&invitedDeptId=58279212&channel=${channel}#/consultingService`,
+    "_blank"
+  );
+};
+
+export default auth;

+ 29 - 0
src/config/conf.js

@@ -0,0 +1,29 @@
+
+// 公共配置 //
+
+/** mjava path: 开发dev, 生产prod, 测试test */
+const api = ""
+
+/** 设置为不超时 */
+const timeout = 0;
+
+/** 授权 */
+const token = "";
+
+/** 宜搭分页上限*/
+const pageSize = 100;
+
+/** 宜搭明细数据上限 */
+const detailCount = 500;
+
+/** 宜搭数据查询上限 */
+const upperLimit = 30000;
+
+/** 授权企业ID */
+const corpId = "";
+
+/** 授权登录加密字符 */
+const nonceStr = "";
+
+/** 导出配置信息 */
+export default { api, timeout, token, pageSize, detailCount, upperLimit, corpId, nonceStr }

+ 8 - 0
src/config/vConsole.js

@@ -0,0 +1,8 @@
+
+import vConsole from "vconsole";
+
+
+// todo 动态加载vconsole
+export function loadVConsole (isLoad) {
+  if (isLoad) new vConsole()
+}

+ 62 - 0
src/deploy/aliwork-dp.js

@@ -0,0 +1,62 @@
+import {
+  showMessage,
+  showLoading,
+  hideLoading,
+  _resetDetailData,
+  __redundancy_value__,
+} from "./aliwork-v3";
+
+////////////////////////////////////////////////  逻辑组装  ////////////////////////////////////////////////
+
+/** @exports 明细数据子表全量赋值 */
+export function updateDetailTable (that, { details, props, compId_detail_cur, funcFilter } = {}) {
+  _resetDetailData(that, compId_detail_cur, props); // 重置明细数据
+  if (!details.length) return;
+  if (funcFilter) {
+    // 过滤方法条件:detail => detail["compId"] == "条件"
+    details = details.filter((detail) => funcFilter(detail));
+  }
+  details = details.map((detail) => {
+    return props.reduce((row, prop) => {
+      row[prop.cur] = __redundancy_value__(detail, prop.src, prop.def);
+      return row;
+    }, {});
+  });
+  that.$(compId_detail_cur).setValue(details);
+  return details;
+}
+
+////////////////////////////////////////////////  接口服务  ////////////////////////////////////////////////
+
+/** @exports 明细数据全量读取:前端接口 */
+// 新建远程请求: 填写地址: https://www.aliwork.com/alibaba/web/APP_CVW86QLT3Q201DLTUIEY/v1/form/listTableDataByFormInstIdAndTableId.json, 更新appType, 关闭自动加载
+export async function queryDetailTable (that,
+  { dpRemote = "getDetailList", currentPage = 1, pageSize = 50, formUuid, formInstId, compId_detail_src, } = {},
+  { details = [], isLoading = true, hideTip, limit = 500 } = {}) {
+  if (isLoading) showLoading(that);
+  // 实现: 数据缓存
+  const res = await that.dataSourceMap[dpRemote]
+    .load({ formUuid, formInstanceId: formInstId, tableFieldId: compId_detail_src, currentPage, pageSize, })
+    .catch((error) => {
+      if (!hideTip) showMessage(that, error.message, "error");
+    });
+  if (!res) return details;
+  details.push(...res.data);
+  // 递归: 等待异步
+  if (details.length < limit && details.length < res.totalCount) {
+    await queryDetailTable(that, { dpRemote, currentPage: res.currentPage + 1, pageSize, formUuid, formInstId, compId_detail_src, },
+      { hideTip, details, isLoading, limit, });
+  }
+  if (isLoading) hideLoading();
+  return details;
+}
+
+/** @exports 明细数据全量读取与更新:前端实现 */
+export async function queryDetailTableAndUpdate (that, { dpRemote, currentPage, pageSize, formUuid, resList, compId_detail_src, props, compId_detail_cur, funcFilter, } = {},
+  { limit, hideTip, isLoading, details } = {}) {
+  const form = resList[0];
+  if (!form) return;
+  const table = await queryDetailTable(that, { dpRemote, currentPage, pageSize, hideTip, formUuid, formInstId: form.formInstId, compId_detail_src, },
+    { hideTip, details, isLoading, limit, });
+  return updateDetailTable(that, { details: table, props, compId_detail_cur, funcFilter, });
+}

+ 495 - 0
src/deploy/aliwork-v2.js

@@ -0,0 +1,495 @@
+/** @内部API */
+import {
+  aliworkTheDynamicSubsidiary,
+  aliworkTheDynamicInsert,
+  aliworkTheDynamicDelete,
+  aliworkTheDynamicUpdate,
+  aliworkTheDynamicDetail,
+} from "../service/network";
+
+//////////////////////////////////////////////// 冗余封装 ////////////////////////////////////////////////
+
+// 冗余Toast提示方法
+export function _showMessage (msg, type = "success") {
+  if (!msg) return;
+  const ctx = window.LeGao.getContext();
+  ctx.fn.toast({
+    title: msg,
+    type,
+  });
+}
+
+// 冗余显示全屏loading: 全局对象
+let G_TOAST;
+export function _showLoading (msg) {
+  const ctx = window.LeGao.getContext();
+  G_TOAST = ctx.fn.toast({
+    type: "mask_loading", // 支持 loading/info/success/error/nw_loading(3.3.0 支持)/mask_loading(3.4.1 支持)
+    title: msg || "加载中...",
+  });
+}
+
+// 冗余隐藏全屏loading: 全局对象
+export function _hideLoading () {
+  G_TOAST && G_TOAST();
+  G_TOAST = null;
+}
+
+// 检测页面状态 : 非提交态, 详情态(单据详情页编辑态 & 流程审批态)
+export function _checkSubmitEnv (ctx = window.LeGao.getContext()) {
+  const modeData = ctx.getInstanceData().flowData || {};
+  return !(modeData.viewMode || modeData.editMode);
+}
+
+// 弹出确认框
+export async function _showConfirm (title, message, method = "confirm") {
+  const ctx = window.LeGao.getContext();
+  return new Promise((resolve) => {
+    ctx.fn.dialog({
+      method,
+      title,
+      content: message,
+      callback: (flag) => {
+        if (flag) resolve();
+      },
+    });
+  });
+}
+
+// 冗余时间格式化
+export function _formatDate (date = new Date(), fmt = "yyyy-MM-dd HH:mm:ss") {
+  ctx.fn.formatter("date", date, fmt);
+}
+
+// 格式化实例ID跳转详情地址
+export function _formatInstanceLink ({
+  appType = pageConfig.appType,
+  formInstId,
+  formUuid,
+}) {
+  return `https://www.aliwork.com/alibaba/web/${appType}/inst/formEdit.html?formInstId=${formInstId}&formUuid=${formUuid}`;
+}
+
+// 获取当前地址的实例ID
+export function _valueInstanceIdHref () {
+  return window.location.href.split("formInstId=")[1].split("&")[0];
+}
+
+// 触发免登后跳转
+export function _redirectTask () {
+  // 格式为当前页面免登地址 + &formList= + 目标表的FormId:FORM-xxxx
+  const formList = window.location.href.split("&formList=");
+  if (formList.length != 2) return;
+  const appType = pageConfig.appType;
+  const url = formList[1].replace("#/", "").replace("&", "");
+  window.location.href = `https://www.aliwork.com/alibaba/web/${appType}/inst/homepage/#/${url}`; // 手机端会自动添加 &
+}
+
+// NOTE: 联动绑定逻辑: 目前手动通过 JS 赋值的方式,再触发联动还需要重新触发联动
+export function _triggerFieldLinkage (formFieldLinkageList = []) {
+  const ctx = window.LeGao.getContext(); // 读取全局才有效
+  // 通过远程接口获取数据,并赋值给得分组件
+  formFieldLinkageList.forEach((fieldId) => triggerFieldLinkage(ctx, fieldId));
+  const globalEvents = ctx.getGlobalEvents();
+  const linkageMap = globalEvents[fieldId] || {};
+  const linkageEvents = linkageMap["onChange"] || [];
+  linkageEvents.forEach((event) => {
+    ctx.params = {
+      ...event.params,
+    };
+    typeof event.func === "function" && event.func(ctx);
+  });
+}
+
+// NOTE: [非定时器方案]通过 emitter.on 等触发的回调中,如果有操作明细组件,需要补充明细禁用和启用限制。否则会出现联动后,输入框值无效,无法录入情况 - https://www.yuque.com/yida/help/aagb7a#700a39bc
+export function _emitterCompCallBack (compId, changeCallBack) {
+  const _context = window.LeGao && window.LeGao.getContext();
+  _context &&
+    _context.emitter.on(compId, (params = {}) => {
+      changeCallBack && changeCallBack(_context, params);
+    });
+}
+
+////////////////////////////////////////////////  私有方法  ////////////////////////////////////////////////
+
+// 接口返回取值到组件, 人员搜索框需要对象类型
+function __redundancy_value__ (row, prop, def) {
+  if (!prop) return def;
+  // 取值判空, 默认值规则
+  let value = prop ? row[prop] : def;
+  if (!value) return value;
+  // 人员搜索框: 取值组装为集合
+  if (prop.includes("employeeField")) {
+    value = row[prop].reduce((acc, cur, idx) => {
+      acc.push({ key: row[`${prop}_id`][idx], label: cur });
+      return acc;
+    }, []);
+  }
+  // 附件赋值, jsonString 转镀锡即可
+  if (prop.includes("attachmentField")) {
+    value = JSON.parse(row[prop]);
+  }
+  return value;
+}
+
+// 明细组件数据格式化, 接口服务人员搜索框需要集合的 json string
+function __redundancy_server__ (row, prop, fromComp) {
+  let value;
+  if (fromComp) {
+    value = ((row[prop] || {}).fieldData || {}).value;
+    value = value && value != null ? value : undefined;
+    if (prop.includes("employeeField")) {
+      const arr = value.reduce((acc, cur) => {
+        acc.push(cur.key);
+        return acc;
+      }, []);
+      value = JSON.stringify(arr);
+    }
+    return value;
+  }
+  // 接口返回数据, 无 fieldData.value
+  if (prop.includes("employeeField")) {
+    value = row[`${prop}_id`];
+  } else {
+    value = row[prop];
+  }
+  return value;
+}
+
+////////////////////////////////////////////////  逻辑组装  ////////////////////////////////////////////////
+
+// 赋值明细组件: 通过 emitter.on 等触发的回调中,处理详见 _emitterCompCallBack
+export function _updateCompDetail (ctx, compId_detail, details) {
+  const compDetail = ctx.store.get(compId_detail);
+  // NOTE: 宜搭2.0明细赋值,禁用其状态,避免赋值后触发了组件的数据联动,导致手动修改数据无效
+  if (details.length) compDetail.setCurrentTargetDisable(true);
+  setTimeout(() => {
+    // 赋值不能及时更新: 集合为空执行报错
+    compDetail.mergeVal(details);
+    compDetail.setCurrentTargetDisable(false);
+  });
+  return details;
+}
+
+// 重置明细数据
+export function _resetDetailData (ctx, compId_detail, props, retainOne = false) {
+  // 兼容保留一条清空, 因为 rest() 有 BUG, 在移动端数据关联会失效
+  const compDetail = ctx.store.get(compId_detail);
+  let details = compDetail.getVal();
+  if (retainOne) details = [details.shift];
+  // 兼容不传入props, 自动组装
+  if (!props) {
+    details.forEach((detail) =>
+      Object.keys(detail).forEach((key) => {
+        detail[key] = { fieldData: { value: "", text: "" } }; // 明细空白解决办法
+      })
+    );
+  } else {
+    details.forEach((detail) =>
+      props.forEach((prop) => {
+        detail[prop.cur] = { fieldData: { value: prop.def, text: prop.def } }; // 明细空白解决办法
+      })
+    );
+  }
+  return _updateCompDetail(ctx, compId_detail, details);
+}
+
+// 明细更新明细数据
+export function _updateCompDetailFromDetail (
+  ctx,
+  { resList, props, compId_detail_src, compId_detail_cur },
+  { funcFilter, isForm = true }
+) {
+  _resetDetailData(ctx, compId_detail_cur, props); // 重置明细数据
+  if (!resList.length) return;
+  const form = resList[0][isForm ? "formData" : "data"];
+  if (!form) return;
+  let srcDetails = form[compId_detail_src];
+  if (funcFilter) {
+    // 过滤方法条件:detail => detail["compId"] == "条件"
+    srcDetails = srcDetails.filter((detail) => funcFilter(detail));
+  }
+  const details = srcDetails.map((detail) => {
+    return props.reduce((row, prop) => {
+      const value = __redundancy_value__(detail, prop.src, prop.def);
+      row[prop.cur] = { fieldData: { value, text: value } }; // 明细空白解决办法
+      return row;
+    }, {});
+  });
+  return _updateCompDetail(ctx, compId_detail_cur, details);
+}
+
+// 主表更新主表数据: 兼容一些当前页面需要公式或者数据联动情况 ➜ 若需要作为联动条件, 详见: _triggerFieldLinkage
+export function _updateCompMainFromMain (
+  ctx,
+  { resList, mains },
+  { isForm = true } = {}
+) {
+  const isReset = !resList.length;
+  const form = isReset ? {} : resList[0][isForm ? "formData" : "data"];
+  mains.forEach((prop) => {
+    const value = __redundancy_value__(form, prop.src, prop.def);
+    ctx.store.get(prop.cur).setVal(value);
+  });
+}
+
+// 明细加载主表数据
+export async function _updateCompDetailFromMain (
+  ctx,
+  { resList, props, compId_detail_cur },
+  { funcFilter, isForm = true }
+) {
+  _resetDetailData(ctx, compId_detail_cur, props); // 重置明细数据
+  if (!resList.length) return;
+  let srcList = resList;
+  if (funcFilter) {
+    // 过滤方法条件:main => main["compId"] == "条件"
+    srcList = srcList.filter((item) =>
+      funcFilter(item[isForm ? "formData" : "data"])
+    );
+  }
+  const details = srcList.map((item) => {
+    const detail = item[isForm ? "formData" : "data"];
+    return props.reduce((row, prop) => {
+      let value = __redundancy_value__(detail, prop.src, prop.def);
+      // 兼容主表填充明细获取实例ID
+      if (prop.def == "formInstId") value = item[prop.def];
+      row[prop.cur] = { fieldData: { value, text: value } }; // 明细空白解决办法
+      return row;
+    }, {});
+  });
+  return _updateCompDetail(ctx, compId_detail_cur, details);
+}
+
+// 人员搜索框格式为 json string: 变更记录, 支持多选, 支持返回值
+// mjs._assembleCompForKeyValue(ctx, { compId_get: "需要被格式化的组件id", compId_set: "格式化后储存的组件id" })
+export function _assembleCompForKeyValue (ctx, { compId_get, compId_set }) {
+  if (!compId_get) return;
+  let value = ctx.store.get(compId_get).getVal();
+  if (compId_get.includes("employee")) {
+    // 人员搜索框: 转为 json string, 宜搭服务会转义 [] 字符, 需要提前拼接存放于组件
+    value = value.reduce((acc, cur) => {
+      acc.push(cur.key);
+      return acc;
+    }, []);
+    const arrJson = JSON.stringify(value);
+    if (compId_set) ctx.store.get(compId_set).setVal(arrJson);
+    return arrJson;
+  }
+  return value;
+}
+
+// 明细组件转数据源
+// [{ 组件Id: value }] 格式 - 服务明细字段需要提前拼接,全量覆盖  -- 明细转 jsonStr 后, 服务内的组件取值不要加双引号
+export function _assembleDetailForKeyValue (
+  ctx,
+  { compId_detail, compId_textArea },
+  details
+) {
+  // 服务: 明细覆盖, 无需更新明细内的组件 id, 需要确保变更的明细组件和原始明细组件是一致的  -- 明细内有需要同步数据点组件都要绑定这个方法
+  if (!details) details = ctx.store.get(compId_detail).getVal() || [];
+  const arrData = details.reduce((acc, cur) => {
+    const sub = Object.keys(cur).reduce((obj, prop) => {
+      const value = __redundancy_server__(cur, prop, true);
+      obj[prop] = value;
+      return obj;
+    }, {});
+    delete sub["__mergeAfterBind"];
+    acc.push(sub);
+    return acc;
+  }, []);
+  const jsonStr = JSON.stringify(arrData);
+  if (compId_textArea) ctx.store.get(compId_textArea).setVal(jsonStr);
+  return jsonStr;
+}
+
+// 按需取明细组件
+// props = [{compId: "", field: ""}] -- 组件ID和接口需要字段名
+export function _formatDetailToJsonString (ctx, { compId_detail, compId_textArea }, props) {
+  if (!compId_detail) {
+    ctx.store.get(compId_textArea).setVal(JSON.stringify(props));
+    return props;
+  }
+  const details = ctx.store.get(compId_detail).getVal();
+  const data = details.reduce((acc, cur) => {
+    acc.push(
+      props.reduce((obj, item) => {
+        const value = __redundancy_server__(cur, item.compId, true);
+        obj[item.field] = value;
+        return obj;
+      }, {})
+    );
+    return acc;
+  }, []);
+  if (compId_textArea) ctx.store.get(compId_textArea).setVal(JSON.stringify(data));
+  return data;
+}
+
+////////////////////////////////////////////////  接口服务  ////////////////////////////////////////////////
+
+// 查询表单或者流程封装
+// const conditions = [{ src: "", cur: "", def: "" }];  // def 优于 src, src 和 cur 均需要传入组件ID
+export async function _queryFormOrProcess (
+  ctx,
+  { formId, conditions = [], compId_tip } = {},
+  { isForm = true, errMsg = "未查询到匹配数据", isAll = true, isHide } = {},
+  { pageIndex, pageSize, queryAll } = {}
+) {
+  // 条件格式化: val 兼容多选
+  const conditionList = conditions.reduce((acc, cur) => {
+    const value =
+      "cur" in cur ? ctx.store.get(cur.cur).getVal() || cur.def : cur.def;
+    if (value) {
+      // def 优于 src, src 和 cur 均需要传入组件ID
+      acc.push({
+        fieldName: cur.src,
+        value,
+      });
+    }
+    return acc;
+  }, []);
+  if (conditions.length && !conditionList.length) return [];
+  // 是否强制匹配多参必须都有值
+  if (isAll && conditionList.length != conditions.length) return [];
+  // 处理接口全局公共参数
+  const queryUrl = isForm ? "/form/select" : "/process/select";
+  const res = await aliworkTheDynamicSubsidiary(queryUrl, {
+    ...window.zParams,
+    formId,
+    conditionList,
+    pageIndex,
+    pageSize,
+  }).catch((err) => err);
+  const isErr = res.list && res.list.length;
+  // 容错 && 提示
+  if (res.message) errMsg = res.message;
+  if (compId_tip) {
+    const compState = ctx.store.get(compId_tip);
+    compState.invokeValidate(isErr, errMsg);
+    compState.forceValid();
+  } else {
+    if (!isHide && !isErr) showMessage(that, errMsg, "error");
+  }
+  // TODO: queryAll: 递归查询主表宜搭限制为100每次分页上限, 兼容累计查询结束
+  return res.list || [];
+}
+_updateCompDetail;
+// 值多选查询表单或者流程: 多选条件仅仅支持一个字段; 以第一条查询为主数据, 合并明细; 数据联动可能会异常
+export async function _queryMultipleFormOrProcess (
+  ctx,
+  { formId, conditions = [], compId_tip, compId_detail_src } = {},
+  { isForm, errMsg, isHide, isAll } = {}
+) {
+  const multiple = conditions.find((item) =>
+    item.cur.includes("multiSelectField")
+  );
+  const condition = ctx.store.get(multiple.cur).getVal();
+  if (!condition.length) {
+    multiple.val = "";
+    return await _queryFormOrProcess(
+      ctx,
+      { formId, conditions, compId_tip },
+      { isForm, errMsg, isHide, isAll }
+    );
+  } else {
+    // NOTE: 当 async/await 遇上 forEach: https://www.jianshu.com/p/4ec6a55c26d7
+    const result = [];
+    for (let val of condition) {
+      multiple.val = val;
+      const resList = await _queryFormOrProcess(
+        ctx,
+        { formId, conditions, compId_tip },
+        { isForm, errMsg, isHide, isAll }
+      );
+      // FIXME: 将主表数据合并到子表, 合并后删除掉主表的子表数据
+      resList.forEach((item) => {
+        const formData = item.formData;
+        const details = formData[compId_detail_src].map((detail) => {
+          delete formData[compId_detail_src];
+          return {
+            ...detail,
+            ...formData,
+          };
+        });
+        result.push(...details);
+      });
+    }
+    return result;
+  }
+}
+
+// 更新当前表单的字段
+// const updateList = [{ fieldName: compId, value: changeValue }]; // 更新内容对照表
+export async function _updateFormList (
+  { updateList, message = "更新", formInstId },
+  { isReload = false }
+) {
+  if (!formInstId) formInstId = _valueInstanceIdHref();
+  // 处理接口全局公共参数: userId 接口有权限管理
+  const res = await aliworkTheDynamicUpdate("/form/update", {
+    ...window.zParams,
+    formInstId,
+    updateList,
+  }).catch(() => []);
+  const isSuccess = res.success;
+  _showMessage(
+    message + (isSuccess ? "成功" : "失败"),
+    isSuccess ? "success" : "error"
+  );
+  if (isReload && isSuccess) window.location.reload();
+}
+
+// 新增表单记录: 若通过拷贝形式新增, 两表组件id必须一一对应(formInstId) ➜ 多字段不会写入
+export async function _insertFromInstance (
+  ctx,
+  { formUuid, props, formInstId },
+  formatParams = (params) => params
+) {
+  let formDataJson = {};
+  if (formInstId) {
+    const resp = await aliworkTheDynamicDetail("/form/getFormDataById", {
+      ...zParams,
+      formInstId,
+    }).catch(() => { });
+    formDataJson = Object.keys(resp.obj).reduce((acc, prop) => {
+      const value = __redundancy_server__(resp.obj, prop);
+      acc[prop] = value;
+      return acc;
+    }, {});
+  } else {
+    // 不支持明细数据
+    formDataJson = props.reduce((acc, cur) => {
+      const value = _assembleCompForKeyValue(ctx, { compId_get: cur.cur });
+      acc[cur.src] = value || cur.def;
+      return acc;
+    }, {});
+  }
+  // 参数回调修改机制
+  formDataJson = (formatParams && formatParams(formDataJson)) || formDataJson;
+  const res = await aliworkTheDynamicInsert("/form/insert", {
+    ...window.zParams,
+    formUuid,
+    formDataJson: JSON.stringify(formDataJson),
+  }).catch((err) => err);
+  const isSuccess = res.success;
+  _showMessage(
+    isSuccess ? "强开成功" : res.message,
+    isSuccess ? "success" : "error"
+  );
+  return res;
+}
+
+// 删除表单实例
+export async function _deleteFormInstance ({ formInstId }, callBack) {
+  if (!formInstId) formInstId = _valueInstanceIdHref();
+  const res = await aliworkTheDynamicDelete("/form/delete", {
+    ...window.zParams,
+    formInstId,
+  }).catch((err) => err);
+  const isSuccess = res.success;
+  _showMessage(
+    isSuccess ? "抢单成功" : res.message,
+    isSuccess ? "success" : "error"
+  );
+  callBack && callBack(isSuccess);
+}

+ 640 - 0
src/deploy/aliwork-v3.js

@@ -0,0 +1,640 @@
+/** @网络请求API */
+import {
+  aliworkTheDynamicSubsidiary,
+  aliworkTheDynamicInsert,
+  aliworkTheDynamicDelete,
+  aliworkTheDynamicUpdate,
+  aliworkTheDynamicDetail,
+} from "../service/network";
+
+/** @工具库API */
+import { debounce } from "../utils/optimize";
+
+//////////////////////////////////////////////// 冗余封装 ////////////////////////////////////////////////
+
+// 冗余Toast提示方法
+export function showMessage (that, title, type = "success") {
+  if (!title) return;
+  that.utils.toast({ type, title });
+}
+
+// 冗余显示全屏loading: 全局对象
+let G_DIALOG;
+export function showLoading (that, title = "拼命加载中...") {
+  if (G_DIALOG) return;
+  G_DIALOG = that.utils.toast({
+    type: "loading",
+    title,
+    closeable: false,
+    footer: false,
+    messageProps: {
+      type: "loading",
+    },
+  });
+}
+
+// 冗余隐藏全屏loading: 全局对象
+export function hideLoading () {
+  G_DIALOG && G_DIALOG();
+  G_DIALOG = null;
+}
+
+// 检测页面状态: 提交态都为undefined; 详情页viewMode为true, editMode为false; 编辑和流程节点editMode为true, viewMode为false
+export function checkSubmitEnv (that) {
+  // 枚举: 0提交,1查看,2编辑,-1自定义页面
+  const instanceData = that.utils.getFormInstanceData();
+  if (!instanceData) return -1; // 自定义页面
+  const { flowData = {} } = instanceData;
+  const { editMode, viewMode } = flowData;
+  if (editMode) return 2; // 审批页面为2, 编辑状态
+  if (viewMode) return 1;
+  return 0;
+}
+
+// 弹出确认框
+export async function showConfirm (that, title, message, method = "confirm") {
+  return new Promise((resolve) => {
+    that.fn.dialog({
+      method,
+      title,
+      content: message,
+      callback: (flag) => {
+        if (flag) resolve();
+      },
+    });
+  });
+}
+
+// 冗余时间格式化
+export function formatDate (
+  that,
+  date = new Date(),
+  fmt = "yyyy-MM-dd HH:mm:ss"
+) {
+  that.utils.formatter(type, date, fmt);
+}
+
+// 格式化实例ID跳转详情地址
+export function formatInstanceLink ({
+  appType = pageConfig.appType,
+  formInstId,
+  formUuid,
+}) {
+  return `https://www.aliwork.com/alibaba/web/${appType}/inst/formEdit.html?formInstId=${formInstId}&formUuid=${formUuid}`;
+}
+
+// 获取当前地址的实例ID
+export function valueInstanceId (that) {
+  if (that) return that.utils.router.getQuery().formInstId;
+  return window.location.href.split("formInstId=")[1].split("&")[0];
+}
+
+// 触发免登后跳转
+export function redirectTaskLink () {
+  // 格式为当前页面免登地址 + &formList= + 目标表的FormId:FORM-xxxx
+  const formList = window.location.href.split("&formList=");
+  if (formList.length != 2) return;
+  const appType = pageConfig.appType;
+  const url = formList[1].replace("#/", "").replace("&", "");
+  window.location.href = `https://www.aliwork.com/alibaba/web/${appType}/inst/homepage/#/${url}`; // 手机端会自动添加 &
+}
+
+////////////////////////////////////////////////  私有方法  ////////////////////////////////////////////////
+
+// 接口返回取值到组件, 人员搜索框需要对象类型
+export function __redundancy_value__ (row, prop, def) {
+  // 为空返回默认值
+  if (!prop) return def;
+  // 取值判空, 默认值规则
+  let value = prop ? row[prop] : def;
+  if (!value) return value;
+  // 人员搜索框: 取值组装为集合
+  if (prop.includes("employeeField")) {
+    value = row[prop].reduce((acc, cur, idx) => {
+      acc.push({ key: row[`${prop}_id`][idx], label: cur });
+      return acc;
+    }, []);
+  }
+  // 附件赋值, jsonString 转镀锡即可
+  if (prop.includes("attachmentField")) {
+    value = JSON.parse(row[prop]);
+  }
+  return value;
+}
+
+// 明细组件数据格式化, 接口服务人员搜索框需要集合的 json string
+export function __redundancy_server__ (row, prop, fromComp) {
+  let value;
+  if (fromComp) {
+    value = row[prop]; // 3.0 无filedData包装
+    value = value && value != null ? value : undefined;
+    if (prop.includes("employeeField")) {
+      const arr = value.reduce((acc, cur) => {
+        acc.push(cur.key);
+        return acc;
+      }, []);
+      value = JSON.stringify(arr);
+    }
+    return value;
+  }
+  // 接口返回数据, 无 fieldData.value
+  if (prop.includes("employeeField")) {
+    value = row[`${prop}_id`];
+  } else {
+    value = row[prop];
+  }
+  return value;
+}
+
+// 查询条件兼容各种组件形式
+export function __redundancy_query__ (that, cur) {
+  let value = "cur" in cur ? that.$(cur.cur).getValue() || cur.def : cur.def;
+  // 兼容人员搜索框, 单选 + 多选
+  if (cur.cur && cur.cur.includes("employeeField")) {
+    const arrEmp = [];
+    const state = that.$(cur.cur).getValue() || [];
+    // 单选
+    if (state.length === undefined) {
+      arrEmp.push(state.value);
+    }
+    // 多选且有值
+    if (state.length) {
+      arrEmp.push(...state.map((emp) => emp.value));
+    }
+    value = arrEmp;
+    // 兼容匹配isAll, 避免被忽略
+    if (!value.length) value = undefined;
+  }
+  // 兼容日期查询: 时间戳, cur为开始, end为默认, 若无则传def
+  if (cur.src.includes("dateField")) {
+    const end = "end" in cur ? that.$(cur.end).getValue() || cur.def : cur.def;
+    value = [value, end];
+    // 兼容匹配isAll, 避免被忽略
+    if (!value || !end) value = undefined;
+  }
+  // 兼容部门, 精确匹配, 兼容多选
+  if (cur.cur && cur.cur.includes("departmentSelectField")) {
+    const depart = that.$(cur.cur).getValue();
+    // 兼容匹配isAll, 避免被忽略
+    if (!depart.length) {
+      value = undefined;
+    } else {
+      value = depart.map((item) => item.value).join(",");
+    }
+  }
+  // 20.8.20 关联表单
+  if (cur.cur && cur.cur.includes("associationFormField") && value.length) {
+    value = value.shift().title;
+  }
+  return value;
+}
+
+// 重置明细数据
+export function _resetDetailData (
+  that,
+  compId_detail,
+  props,
+  retainOne = false
+) {
+  // 兼容保留一条清空, 因为 rest() 有 BUG, 在移动端数据关联会失效
+  const compDetail = that.$(compId_detail);
+  let details = compDetail.getValue();
+  if (retainOne) details = [details.shift];
+  // 兼容不传入props, 自动组装
+  if (!props) {
+    details.forEach((detail) =>
+      Object.keys(detail).forEach((key) => {
+        detail[key] = "";
+      })
+    );
+  } else {
+    details.forEach((detail) =>
+      props.forEach((prop) => {
+        detail[prop.cur] = prop.def;
+      })
+    );
+  }
+  compDetail.setValue(details);
+}
+
+////////////////////////////////////////////////  逻辑组装  ////////////////////////////////////////////////
+
+// 明细更新明细数据
+export function updateCompDetailFromDetail (
+  that,
+  { resList, props, compId_detail_src, compId_detail_cur },
+  { funcFilter, isForm = true, isUpdate = true }
+) {
+  _resetDetailData(that, compId_detail_cur, props); // 重置明细数据
+  if (!resList.length) return;
+  const form = resList[0][isForm ? "formData" : "data"];
+  if (!form) return;
+  let srcDetails = form[compId_detail_src];
+  if (funcFilter) {
+    // 过滤方法条件:detail => detail["compId"] == "条件"
+    srcDetails = srcDetails.filter((detail) => funcFilter(detail));
+  }
+  const details = srcDetails.map((detail) => {
+    return props.reduce((row, prop) => {
+      row[prop.cur] = __redundancy_value__(detail, prop.src, prop.def);
+      return row;
+    }, {});
+  });
+  /** 连续更新会异常: 需要调用调用forceUpdate才会生效, 若后续即将有更新, 这里置为false, 匹配明细: queryArrayFormOrProcessAndUpdate */
+  if (isUpdate) that.$(compId_detail_cur).setValue(details);
+  return details;
+}
+
+// 主表更新主表数据: 兼容一些当前页面需要公式或者数据联动情况
+export function updateCompMainFromMain (
+  that,
+  { resList, mains },
+  { isForm = true } = {}
+) {
+  const isReset = !resList.length;
+  const form = isReset ? {} : resList[0][isForm ? "formData" : "data"];
+  mains.forEach((prop) => {
+    const value = __redundancy_value__(form, prop.src, prop.def);
+    that.$(prop.cur).setVal(value);
+  });
+}
+
+// 明细加载主表数据
+export async function updateCompDetailFromMain (
+  that,
+  { resList, props, compId_detail_cur },
+  { funcFilter, isForm = true }
+) {
+  _resetDetailData(that, compId_detail_cur, props); // 重置明细数据
+  if (!resList.length) return;
+  let srcList = resList;
+  if (funcFilter) {
+    // 过滤方法条件:main => main["compId"] == "条件"
+    srcList = srcList.filter((item) =>
+      funcFilter(item[isForm ? "formData" : "data"])
+    );
+  }
+  const details = srcList.map((item) => {
+    const detail = item[isForm ? "formData" : "data"];
+    return props.reduce((row, prop) => {
+      let value = __redundancy_value__(detail, prop.src, prop.def);
+      // 兼容主表填充明细获取实例ID
+      if (prop.def == "formInstId") value = item[prop.def];
+      row[prop.cur] = value;
+      return row;
+    }, {});
+  });
+  that.$(compId_detail_cur).setValue(details);
+  return details;
+}
+
+// 人员搜索框格式为 json string: 变更记录, 支持多选, 支持返回值
+// mjs._assembleCompForKeyValue(that, { compId_get: "需要被格式化的组件id", compId_set: "格式化后储存的组件id" })
+export function assembleCompForKeyValue (that, { compId_get, compId_set }) {
+  if (!compId_get) return;
+  let value = that.$(compId_get).getValue();
+  if (!!prop && compId_get.includes("employeeField")) {
+    // 人员搜索框: 转为 json string, 宜搭服务会转义 [] 字符, 需要提前拼接存放于组件
+    value = value.reduce((acc, cur) => {
+      acc.push(cur.key);
+      return acc;
+    }, []);
+    const arrJson = JSON.stringify(value);
+    if (compId_set) that.$(compId_set).setValue(arrJson);
+    return arrJson;
+  }
+  return value;
+}
+
+// 明细组件转数据源
+// [{ 组件Id: value }] 格式 - 服务明细字段需要提前拼接,全量覆盖  -- 明细转 jsonStr 后, 服务内的组件取值不要加双引号
+export function assembleDetailForKeyValue (
+  that,
+  { compId_detail, compId_textArea },
+  details
+) {
+  // 服务: 明细覆盖, 无需更新明细内的组件 id, 需要确保变更的明细组件和原始明细组件是一致的  -- 明细内有需要同步数据点组件都要绑定这个方法
+  if (!details) details = that.$(compId_detail).getValue() || [];
+  const arrData = details.reduce((acc, cur) => {
+    const sub = Object.keys(cur).reduce((obj, prop) => {
+      const value = __redundancy_server__(cur, prop, true);
+      obj[prop] = value;
+      return obj;
+    }, {});
+    acc.push(sub);
+    return acc;
+  }, []);
+  const jsonStr = JSON.stringify(arrData);
+  if (compId_textArea) {
+    that.$(compId_textArea).setValue(jsonStr);
+    return jsonStr;
+  }
+  return arrData;
+}
+
+// 按需取明细组件 - 建议页面非提交态获取,实时获取极不稳定:审批流程页面到到查看状态都是2,编辑。js赋值后同意后数据会保存的,不需要单独调用更新
+// props = [{compId: "", field: ""}] -- 组件ID和接口需要字段名
+export function formatDetailToJsonString (
+  that,
+  { compId_detail, compId_textArea },
+  props
+) {
+  if (!compId_detail) {
+    that.$(compId_textArea).setValue(JSON.stringify(props));
+    return props;
+  }
+  const details = that.$(compId_detail).getValue();
+  const data = details.reduce((acc, cur) => {
+    acc.push(
+      props.reduce((obj, item) => {
+        const value = __redundancy_server__(cur, item.compId, true);
+        obj[item.field] = value;
+        return obj;
+      }, {})
+    );
+    return acc;
+  }, []);
+  if (compId_textArea) that.$(compId_textArea).setValue(JSON.stringify(data));
+  return data;
+}
+
+// 明细序号自增
+export function incrementCompDetailID (that, compID_detailed, compID_number) {
+  const details = that.$(compID_detailed).getValue();
+  details.forEach((item, index) => {
+    item[compID_number] = index + 1;
+  });
+  that.$(compID_detailed).setValue(details);
+}
+
+// 更新明细内数据方法回调  -- 若是匹配可以先filter, 然后遍历就行了
+export function updateCompDetailFromConfig (
+  that,
+  { compId_detail },
+  funcUpdate
+) {
+  const compDetail = that.$(compId_detail);
+  const details = compDetail.getValue().map((item, idx) => {
+    funcUpdate && funcUpdate(item, idx, details);
+    return item;
+  });
+  compDetail.setValue(details);
+  return details;
+}
+
+// 分类求和: 宜搭明细分类求和,当明细内相同数据选择多次情况,回写中间表使用,总计数更新 [宜搭明细相同条件只会更新最后一条]
+export function debounceCalcCategory (
+  that,
+  { compId_table, funcCondition, compId_cur, compId_sum } = {},
+  { funcCondition2, compId_cur2, compId_sum2 } = {}
+) {
+  if (!that[compId_table]) {
+    that[compId_table] = debounce((that) => {
+      const compTable = that.$(compId_table);
+      const details = compTable.getValue();
+      details.forEach((detail) => {
+        const value = details.reduce((acc, cur) => {
+          if (funcCondition({ cur, detail })) acc += Number(cur[compId_cur]);
+          return acc;
+        }, 0);
+        detail[compId_sum] = value;
+        // 多个合并情况
+        if (funcCondition2) {
+          const value = details.reduce((acc, cur) => {
+            if (funcCondition2({ cur, detail }))
+              acc += Number(cur[compId_cur2]);
+            return acc;
+          }, 0);
+          detail[compId_sum2] = value;
+        }
+      });
+      compTable.setValue(details);
+      compTable.forceUpdate();
+    }, 400);
+  }
+  that[compId_table](that);
+}
+
+// 计算流水号, 以 count 公式获取流水号, 设定位数不足部分补 0
+export function countSerialNumber (that, compId, length = 5, prefix = "") {
+  const num = that.$(compId).getValue();
+  const ser = (Array(length).join("0") + num).slice(-length);
+  if (prefix) that.$(compId).setValue(`${prefix}${ser}`);
+  return ser;
+}
+
+////////////////////////////////////////////////  接口服务  ////////////////////////////////////////////////
+
+// 查询表单或者流程封装
+// const conditions = [{ src: "", cur: "", def: "" }];  // src和cur需要传入组件ID; cur可不传, 取值def;
+export async function queryFormOrProcess (
+  that,
+  { formId, conditions = [], compId_tip, conditionList = [] } = {},
+  { isForm = true, errMsg = "未查询到匹配数据", isHide, isAll } = {},
+  { pageIndex, pageSize, queryAll } = {}
+) {
+  // 查询条件格式化
+  if (!conditionList.length) {
+    conditionList = conditions.reduce((acc, cur) => {
+      const value = __redundancy_query__(that, cur);
+      if (value) {
+        acc.push({
+          fieldName: cur.src,
+          value,
+        });
+      }
+      return acc;
+    }, []);
+    if (conditions.length && !conditionList.length) return [];
+    // 是否强制匹配多参必须都有值
+    if (isAll && conditionList.length != conditions.length) return [];
+  }
+  // 处理接口全局公共参数queryFormOrProcess
+  const queryUrl = isForm ? "/form/select" : "/process/select";
+  const res = await aliworkTheDynamicSubsidiary(queryUrl, {
+    ...window.zParams,
+    formId,
+    conditionList,
+    pageIndex,
+    pageSize,
+  }).catch((err) => err);
+  // 容错 && 提示
+  const isErr = res.list && res.list.length;
+  if (res.message) errMsg = res.message;
+  if (compId_tip) {
+    const compState = that.$(compId_tip);
+    compState.setValidation(
+      [
+        { type: "required" },
+        {
+          type: "customValidate",
+          message: errMsg,
+          param: () => !!isErr,
+        },
+      ],
+      true
+    );
+  } else {
+    if (!isHide && !isErr) showMessage(that, errMsg, "error");
+  }
+  // TODO: queryAll: 递归查询主表宜搭限制为100每次分页上限, 兼容累计查询结束
+  return res.list || [];
+}
+
+// 明细内触发查询, 如bom查库存, 当前值传入def: const conditions = [{ src: "", def: "" }];
+export async function queryArrayFormOrProcessAndUpdate (
+  that,
+  { formId, conditions = [], array = [], props = [], compId_detail_cur } = {},
+  { isForm = true, isHide = true } = {}
+) {
+  // NOTE: 当 async/await 遇上 forEach 使用 for ... of 代替: https://www.jianshu.com/p/4ec6a55c26d7
+  for (let detail of array) {
+    // 入参数格式化
+    const conditionList = conditions.reduce((acc, cur) => {
+      const value = "cur" in cur ? detail[cur.cur] || cur.def : cur.def;
+      acc.push({ fieldName: cur.src, value });
+      return acc;
+    }, []);
+    const resList = await queryFormOrProcess(
+      that,
+      { formId, conditionList },
+      { isForm, isHide }
+    );
+    if (resList.length) {
+      const form = resList[0][isForm ? "formData" : "data"];
+      props.forEach((prop) => {
+        const value = __redundancy_value__(form, prop.src, prop.def);
+        detail[prop.cur] = value;
+      });
+    }
+  }
+  /** 连续更新会异常: 需要调用调用forceUpdate才会生效, 若后续即将有更新, 这里置为false, 匹配明细: updateCompDetailFromDetail */
+  that.$(compId_detail_cur).setValue(array);
+}
+
+// 值多选查询表单或者流程: 多选条件仅仅支持一个字段; 以第一条查询为主数据, 合并明细; 数据联动可能会异常
+export async function queryMultipleFormOrProcess (
+  that,
+  { formId, conditions = [], compId_tip, compId_detail_src } = {},
+  { isForm, errMsg, isHide, isAll } = {}
+) {
+  const multiple = conditions.find((item) =>
+    item.cur.includes("multiSelectField")
+  );
+  const condition = that.$(multiple.cur).getVal();
+  if (!condition.length) {
+    multiple.val = "";
+    return await queryFormOrProcess(
+      that,
+      { formId, conditions, compId_tip },
+      { isForm, errMsg, isHide, isAll }
+    );
+  } else {
+    // NOTE: 当 async/await 遇上 forEach 使用 for ... of 代替: https://www.jianshu.com/p/4ec6a55c26d7
+    const result = [];
+    for (let val of condition) {
+      multiple.val = val;
+      const resList = await queryFormOrProcess(
+        that,
+        { formId, conditions, compId_tip },
+        { isForm, errMsg, isHide, isAll }
+      );
+      // FIXME: 将主表数据合并到子表, 合并后删除掉主表的子表数据
+      resList.forEach((item) => {
+        const formData = item.formData;
+        const details = formData[compId_detail_src].map((detail) => {
+          delete formData[compId_detail_src];
+          return {
+            ...detail,
+            ...formData,
+          };
+        });
+        result.push(...details);
+      });
+    }
+    return result;
+  }
+}
+
+// 更新当前表单的字段
+// const updateList = [{ fieldName: compId, value: changeValue }]; // 更新内容对照表
+export async function updateFormList (
+  that,
+  { updateList, message = "更新", formInstId },
+  { isReload = false }
+) {
+  if (!formInstId) formInstId = valueInstanceId(that);
+  // 处理接口全局公共参数: userId 接口有权限管理
+  const res = await aliworkTheDynamicUpdate("/form/update", {
+    ...window.zParams,
+    formInstId,
+    updateList,
+  }).catch(() => []);
+  const isSuccess = res.success;
+  showMessage(
+    that,
+    message + (isSuccess ? "成功" : "失败"),
+    isSuccess ? "success" : "error"
+  );
+  if (isReload && isSuccess) window.location.reload();
+}
+
+// 新增表单记录: 若通过拷贝形式新增, 两表组件id必须一一对应(formInstId) ➜ 多字段不会写入
+export async function insertFromInstance (
+  that,
+  { formUuid, props, formInstId },
+  formatParams = (params) => params
+) {
+  let formDataJson = {};
+  if (formInstId) {
+    const resp = await aliworkTheDynamicDetail("/form/getFormDataById", {
+      ...zParams,
+      formInstId,
+    }).catch(() => { });
+    formDataJson = Object.keys(resp.obj).reduce((acc, prop) => {
+      const value = __redundancy_server__(resp.obj, prop);
+      acc[prop] = value;
+      return acc;
+    }, {});
+  } else {
+    // 不支持明细数据
+    formDataJson = props.reduce((acc, cur) => {
+      const value = _assembleCompForKeyValue(that, { compId_get: cur.cur });
+      acc[cur.src] = value || cur.def;
+      return acc;
+    }, {});
+  }
+  // 参数回调修改机制
+  formDataJson = (formatParams && formatParams(formDataJson)) || formDataJson;
+  const res = await aliworkTheDynamicInsert("/form/insert", {
+    ...window.zParams,
+    formUuid,
+    formDataJson: JSON.stringify(formDataJson),
+  }).catch((err) => err);
+  const isSuccess = res.success;
+  showMessage(
+    that,
+    isSuccess ? "强开成功" : res.message,
+    isSuccess ? "success" : "error"
+  );
+  return res;
+}
+
+// 删除表单实例
+export async function deleteFormInstance (that, { formInstId }, callBack) {
+  if (!formInstId) formInstId = valueInstanceId();
+  const res = await aliworkTheDynamicDelete("/form/delete", {
+    ...window.zParams,
+    formInstId,
+  }).catch((err) => err);
+  const isSuccess = res.success;
+  showMessage(
+    that,
+    isSuccess ? "抢单成功" : res.message,
+    isSuccess ? "success" : "error"
+  );
+  callBack && callBack(isSuccess);
+}
+
+
+

+ 54 - 0
src/main.js

@@ -0,0 +1,54 @@
+
+/*** mc 系列之 mjs
+ * 对接宜搭公共JavaScript库
+ * 公共库地址:https://mc.cloudpure.cn/mjs/mjs.min.js
+ * 本地库地址: http://127.0.0.1:7001/dist/mjs.js
+ ***/
+
+import { loadVConsole } from "./config/vConsole" /** @远程调试工具 */
+import auth from "./auth/copyright";
+
+import com from "./aliwork/com";
+import dom from "./aliwork/dom";
+import bus from "./aliwork/bus";
+
+import optimize from "./utils/optimize";
+import storage from "./utils/storage";
+import date from "./vendor/date";
+
+import conf from "./config/conf"; /** @全局公共配置 */
+import xhr from "./service/request"; /** @前端网络对象 */
+import dp from "./aliwork/dp"; /** @宜搭前端查询 */
+import { crossDomainByScript } from "./service/vendor"; /** @前端网络三方 */
+import ding from "./vendor/dingApi"; /** @钉钉API */
+
+import cp from "./sample/cloudpure"
+import guyuan from "./sample/guyuan"
+
+export async function init (_this, config = {}) {
+  console.log(this)
+  this.$this = _this // this全局化
+  this.env = com.checkEnv(); // 环境: 0提交(其它),1查看,2编辑(审批)
+  loadVConsole(config.vconsole)
+  this.auth = auth; // 授权
+  this.dom = dom; // 样式
+  this.com = com; // 通用
+  this.bus = bus; // 事件
+  this.conf = conf; // 配置
+  this.optimize = optimize; // 优化
+  this.storage = storage; // 存储
+  this.date = date; // 日期
+  this.request = { dp, xhr, net: { crossDomainByScript } }; // 请求
+  this.ding = ding;
+  this.corp = {
+    cp, guyuan
+  }
+  // 输出日志;
+  const msg = `mjs load success. ♨ 访问应用: ${pageConfig.appType} ${pageConfig.appName} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
+  console.log(msg, mjs, config);
+}
+
+
+
+
+

+ 36 - 0
src/sample/cloudpure.js

@@ -0,0 +1,36 @@
+
+/*** mjs 之 云璞tb与宜搭对接 ***/
+
+import { KEY_NO_LOADING, KEY_SHOW_MESSAGE } from "../service/request";
+
+export default {
+
+  // 公共配置
+  init () {
+    mjs.conf.api = "https://mc.cloudpure.cn/tb-yd/";
+    return this; // this 指向当前项目本身
+  },
+
+  // 获取企业项目模板
+  async getTemplate (compId) {
+    const rsp = await mjs.request.xhr.doPost("tb/template", {}, {}, {
+      [KEY_NO_LOADING]: true
+    });
+    mjs.$this.$(compId).set("dataSource", rsp.data.map(item => ({ label: item.name, value: item.id })))
+  },
+
+  // 获取企业项目模板
+  async createProject () {
+    const body = {
+      userName: loginUser.userName,
+      projectName: mjs.$this.$("textField_l9m4krcc").getValue()
+    }
+    const templateId = mjs.$this.$("selectField_laqbuhr7").getValue();
+    if (templateId) {
+      body.templateId = templateId;
+    }
+    const rsp = await mjs.request.xhr.doPost("tb/project", {}, body);
+  },
+
+
+};

+ 126 - 0
src/sample/crmServer.js

@@ -0,0 +1,126 @@
+import request from "../service/request";
+
+// -------------------- 服务商crm -------------------- //
+
+
+// 跑批地址
+function __batch_url__ (url) {
+  return `http://139.196.10.121:38805/fws${url}`;
+}
+
+// 跑批服务
+async function __batch_server__ (url) {
+  mjs._showLoading();
+  const res = await request.post(__batch_url__(url));
+  mjs._hideLoading();
+  mjs._showMessage("强开 ➜ 抢单, 跑批完成");
+  return res;
+}
+/** @exports 跑批: 强开 ➜ 抢单 */
+export async function _batchAbandonToGrab () {
+  return __batch_server__("/data/openToGrabbing");
+}
+
+/** @exports 跑批: 抢单 ➜ 公海 */
+export async function _batchGrabToPublic () {
+  return __batch_server__("/data/openToGrabbing");
+}
+
+///////////////////////////////////////////////////// 私有方法 /////////////////////////////////////////////////////
+
+// 效验用户的库容是否符合可操作空间
+async function __verify_user_capacity__ (userId) {
+  const usr = JSON.stringify([userId || loginUser.userId]);
+  // 查询当前登录人库容
+  mjs._showLoading();
+  const res = await mjs.aliworkTheDynamicSubsidiary("/form/select", {
+    ...window.zParams,
+    formId: "FORM-YSA66KC1PZALF9TC3IQYNC6VW9LU3JGACCXHKOE",
+    conditionList: [{ fieldName: "employeeField_ki45o38a", value: usr }],
+  });
+  if (!res.list.length) {
+    mjs._hideLoading();
+    mjs._showMessage("您没有库容信息,请联系管理员维护!", "error");
+    return {};
+  }
+  const capacity = res.list[0].formData["numberField_khxccepl"];
+  // 查询当前登录人客户数量
+  const resp = await mjs.aliworkTheDynamicSubsidiary("/form/select", {
+    ...window.zParams,
+    formId: "FORM-2O5667C141SKMDAVWESOGVMV8NUU1X32PSLHKNF3",
+    conditionList: [
+      { fieldName: "employeeField_khlsp71r", value: usr },
+      { fieldName: "selectField_khxcivp1", value: "新客户" },
+    ],
+  });
+  mjs._hideLoading();
+  const customCount = resp.list.length;
+  const isOperation = capacity >= customCount;
+  if (!isOperation)
+    mjs._showMessage("您的库容已满, 请释放客户再捞取", "error");
+  return { isOperation, capacity, customCount };
+}
+
+//////////////////////////////////////////////// 服务商 CRM 业务场景 ////////////////////////////////////////////////
+
+/** @exports 私海客户:今日强开逻辑\放弃按钮操作 */
+export async function _copyInsertToAbandonAndUpdatePrivate (ctx) {
+  await mjs._showConfirm("操作提示", "将执行客户强开操作");
+  const formInstId = mjs._valueInstanceIdHref();
+  // 今日强开客户数据:以复制方式新增
+  const res = await mjs._insertFromInstance(
+    ctx,
+    { formUuid: "FORM-JFYJPKDVQIILRIE00TBVO56FRUJW2HGMW27IKQ3", formInstId },
+    (params) => {
+      // 新增强开数据需要变更字段
+      params["selectField_khxcivp8"] = "今日强开"; // 客户类型
+      params["selectField_kjv4jdnj"] = "自主放弃"; // 开放类型
+      params["employeeField_kicwjyay"] = JSON.stringify([loginUser.userId]); // 私海释放人
+      params["employeeField_khlsp71r"] = JSON.stringify([]); // 客户负责人
+      params["textField_kiij9tqe"] = formInstId; // 记录实例ID
+      return params;
+    }
+  );
+  if (!res.success) return;
+  // 更新内容对照表:客户类型变更为今日强开,客户归属人置空, 记录最新操作时间
+  const updateList = [
+    { fieldName: "selectField_khxcivp8", value: "今日强开" },
+    { fieldName: "dateField_khxcivp2", value: new Date().getTime() },
+    { fieldName: "employeeField_khlsp71r", value: JSON.stringify([]) },
+  ];
+  mjs._updateFormList({ updateList, formInstId }, { isReload: false });
+}
+
+/** @exports 今日抢单\公海捞取:确保页面组件ID一致性 */
+export async function _updatePrivateAndDeleteCurrentRedirect (
+  ctx,
+  { msg = "将执行客户操作" }
+) {
+  await mjs._showConfirm("操作提示", msg);
+  if (
+    ctx.store.get("employeeField_kicwjyay").getVal()[0].key == loginUser.userId
+  ) {
+    mjs._showMessage("客户释放人不能抢单", "error");
+    return;
+  }
+  const { isOperation } = await __verify_user_capacity__();
+  if (!isOperation) return;
+  // 更新内容对照表: 客户类型变更为私海客户,客户归属人设置登录人
+  const formInstId = ctx.store.get("textField_kiij9tqe").getVal();
+  const updateList = [
+    { fieldName: "selectField_khxcivp8", value: "私海客户" },
+    {
+      fieldName: "employeeField_khlsp71r",
+      value: JSON.stringify([loginUser.userId]),
+    },
+  ];
+  mjs._updateFormList({ updateList, formInstId });
+  // 不传入formInstI, 会执行删除当前记录;
+  const redirect = mjs._formatInstanceLink({
+    formInstId,
+    formUuid: "FORM-2O5667C141SKMDAVWESOGVMV8NUU1X32PSLHKNF3",
+  });
+  mjs._deleteFormInstance({}, (isSuccess) => {
+    if (isSuccess) window.location.href = redirect;
+  });
+}

+ 695 - 0
src/sample/fegroup.js

@@ -0,0 +1,695 @@
+import * as ddAPi from "dingtalk-jsapi";
+const dd = ddAPi.default;
+
+// -------------------- 远东本地函数 -------------------- //
+
+const fegroup = {};
+
+// 远东库全局设置
+fegroup.globalSetting = function (that, config) {
+  // 钉钉接口
+  if (dd.env.platform !== "notInDingTalk") {
+    dd.ui.webViewBounce.disable();
+    dd.ui.pullToRefresh.disable();
+  }
+  if (!mjs.env) {
+    // 同步登录人信息
+    that.setState({
+      loginUser: [
+        {
+          value: loginUser.userId,
+          label: loginUser.userName,
+          avatar: loginUser.avatar,
+        },
+      ],
+    });
+  }
+
+  // 移除移动端无效按钮
+  mjs.dom.removeMobInvalid(that);
+  // 配置事件 : 取名改为 register
+  if (config.closed) {
+    // FIXME: 存在 beforeSubmit 情况下, 不会执行关闭和回调, 需要在方法内调用 `mjs.dom.closeTabCompatibilityBeforeSubmit(this)`
+    mjs.dom.closeCurrentTabForSubmit(that);
+    mjs.dom.closeCurrentTabForApprove(that);
+  }
+};
+
+//////////////////////////////////////////////////////////////////////// PC端首页 ////////////////////////////////////////////////////////////////////////
+
+const home = {};
+
+// 查询股票信息
+home.queryStock = async function (that) {
+  const resp = await mjs.request.ven.crossDomainByScript(
+    "https://hq.sinajs.cn/list=sh600869",
+    "hq_str_sh600869"
+  );
+  // split(",")之后,[1]: 开盘价, [2]: 昨收价,[3]: 当前价, [4]: 最高价, [5]: 最低价, [8]: 成交量, [9]: 成交额
+  const stock = resp.split(",");
+  [3, 2, 8, 4, 5, 9].forEach((subscript) => {
+    let val = stock[subscript];
+    if (subscript == 9) val = Math.round((val /= 10000)) + "万";
+    that.$(`stock_${subscript}`).set("content", val);
+  });
+};
+
+// 查询股票信息
+home.queryStock_New = async function (that) {
+  const resp = await mjs.request.xhr.getParams(`${mjs.conf.domain}/api/index/stock`)
+  const keys = [{ prop: "yesterday", name: "昨收价" }, { prop: "price", name: "价格" }, { name: "均价", prop: "average" }, { name: "成交量", prop: "turnover" }];
+  keys.forEach(({ prop, name }) => {
+    that.$(`text_${prop}`).set("content", `${name}: ${resp.data[prop]}`);
+  });
+};
+
+// 查询新闻信息
+home.queryNews = async function (that) {
+  const updateCompTable = (prop, value1, value2) => {
+    that.$("tablePc_kwbflkep").set(prop, value1);
+    that.$("tablePc_kwbflpcy").set(prop, value2);
+    if (prop == "data") updateCompTable("loading", false, false);
+  };
+  updateCompTable("loading", true, true);
+  const resp = await mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/index/getNews`
+  );
+  const tableList1 = [],
+    tableList2 = [];
+  resp.data.forEach((item, index) => {
+    item.date = item.publishDate;
+    if (index < 5) {
+      tableList1.push(item);
+    } else {
+      tableList2.push(item);
+    }
+  });
+  updateCompTable("data", tableList1, tableList2);
+};
+
+// 查询今日指数
+home.queryStandard = async function (that, activeKey) {
+  const updateCompTable = (prop, value) => {
+    that.$("tablePc_kwbflsdj").set(prop, value);
+    if (prop == "data") updateCompTable("loading", false);
+  };
+  updateCompTable("loading", true);
+  const category = {
+    tab_kwbfliim: "GetZGDLJYSPriceIndex", // 远东材料交易中心
+    tab_kwbfliin: "GetDLSPJYSPriceIndex", // 大连商品交易所
+    tab_kwbfliio: "GetLMEPriceIndex", // LME
+    tab_kwbfliip: "GetSQSPriceIndex", // 上海期货交易所
+    tab_kwbfliiq: "GetCJXHPriceIndex", // 长江现货
+  };
+  if (!activeKey) activeKey = that.$("tabsLayout_kwbflran").state.activeKey;
+  const resp = await mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/index/getNowData`,
+    {
+      type: category[activeKey],
+    }
+  );
+  // 动态表头处理 - 今日指数表切换:长江现货和远东材料
+  const type = ["tab_kwbfliim", "tab_kwbfliiq"].includes(activeKey);
+  resp.data.list.forEach((item) => {
+    const dateStr = mjs.utils.date.format(
+      mjs.utils.date.parse(
+        item.Ffd_Datadate || item.Fsd_Datadate,
+        "YYYYMMDD"
+      ),
+      "YYYY-MM-DD"
+    );
+    item[type ? "Fsd_Datadate" : "Ffd_Datadate"] = dateStr;
+  });
+  updateCompTable(
+    "columns",
+    that.state[type ? "standardType1" : "standardType2"]
+  );
+  updateCompTable("data", resp.data.list);
+};
+
+// 查询天气
+home.queryWeather = async function (that) {
+  const resp = await mjs.request.ven.crossDomainByScript(
+    "https://pv.sohu.com/cityjson?ie=utf-8",
+    "returnCitySN"
+  );
+  const address = resp.cname.split("省");
+  if (address.length == 2) {
+    const result = await mjs.request.xhr.getParams(
+      `${mjs.conf.domain}/api/index/weather?cName=${address[1]}`
+    );
+    const today = result.data.data.forecast[1];
+    const weather = {
+      city: result.data.data.city,
+      type: today.type,
+      area: `${today.low.split(" ")[1]} ~ ${today.high.split(" ")[1]}`,
+    };
+    that.setState({ weather });
+    that.$("JSX_kwdfgbwj").forceUpdate();
+  }
+};
+
+// 查询钉钉通讯录扩展字段【自定义字段】& 阅读查看范围
+home.queryDingContact = async function (that, extension = true) {
+  const params = {
+    userId: loginUser.userId,
+  };
+  if (extension) params.extension = true; // 后端是字段传入判空, 不是判值
+  const resp = await mjs.request.xhr.postDataForm(
+    `${mjs.conf.domain}/api/ding/getDingContact`,
+    params
+  );
+  // 兼容查询钉钉扩展字段
+  const compState = that.$("text_kwgf2sdc");
+  if (compState) {
+    const data = extension ? resp.data : JSON.parse(resp.data.extension);
+    compState.set("content", data["外派公司"] || data["公司"]);
+  }
+  return resp.data;
+};
+
+// 查询邮箱未读数量
+home.queryEmailUnreadCount = async function (that, email) {
+  // respData
+  const resp = await mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/index/getEmailUnread`,
+    {
+      accessTarget: email,
+    }
+  );
+  that.$("text_kw60qo7h").set("content", resp.data.totalCount);
+};
+
+// 根据所属公司名称查询阅读查看范围
+home.queryTabSelect = async function (that, isNew) {
+  const userCompany = that.$("text_kwgf2sdc").get("content");
+  if (!userCompany) return;
+  const resSelect = await that.dataSourceMap.queryFromTab.load({
+    formUuid: "FORM-OM566O71F7HVX7ZAX1J8N3ECSLRX2YWJKAAWKJ",
+    pageSize: 1,
+    searchFieldJson: JSON.stringify({
+      selectField_kwab6g3s: userCompany,
+    }),
+  });
+  // 设置下拉框默认值和下拉选项
+  if (resSelect.data.length) {
+    const data = resSelect.data[0].data;
+    const options = data.tableField_kwabgynl.map((item) => ({
+      text: item.selectField_kwabgyno,
+      value: item.numberField_kwgfn6dr.toString(),
+      order: item.numberField_kwhfutnv,
+    }));
+    options.sort((a, b) => a.order - b.order);
+    // 设置value会触发tab查询
+    that.$("selectField_kw7762kc").set("dataSource", options);
+    that
+      .$("selectField_kw7762kc")
+      .setValue(data.numberField_kwgfn6ds.toString());
+  }
+};
+
+// 根据所属公司名称查询阅读查看范围
+home.queryTabSelect_New = async function (that) {
+  const userCompany = that.$("text_kwgf2sdc").get("content");
+  if (!userCompany) return;
+  const resSelect = await that.dataSourceMap.queryFromTab.load({
+    formUuid: "FORM-OM566O71F7HVX7ZAX1J8N3ECSLRX2YWJKAAWKJ",
+    pageSize: 1,
+    searchFieldJson: JSON.stringify({
+      selectField_kwab6g3s: userCompany,
+    }),
+  });
+  // 设置下拉框默认值和下拉选项
+  if (resSelect.data.length) {
+    const data = resSelect.data[0].data;
+    const options = data.tableField_kwabgynl.map((item) => ({
+      text: item.selectField_kwabgyno,
+      value: item.numberField_kwgfn6dr.toString(),
+      order: item.numberField_kwhfutnv,
+    }));
+    options.sort((a, b) => a.order - b.order);
+    // 设置value会触发tab查询
+    that.$("multiSelectField_kybax1xz").set("dataSource", options);
+    that
+      .$("multiSelectField_kybax1xz")
+      .setValue([data.numberField_kwgfn6ds.toString()]);
+  }
+};
+
+// 查询信息发布Tab数据
+home.queryTabList = async function (that, activeKey) {
+  const messageTypeId = that.$("selectField_kw7762kc").getValue();
+  if (!messageTypeId) return;
+  const updateCompTable = (prop, value1, value2) => {
+    that.$("tablePc_kwbflqe8").set(prop, value1);
+    that.$("tablePc_kwbflqea").set(prop, value2);
+    if (prop == "data") updateCompTable("loading", false, false);
+  };
+  updateCompTable("loading", true, true);
+  const category = {
+    tab_kw60qnkn: "FORM-4A9667B1RLMU2F5WWJI1E0MX4GLZ1KIEBP1VKFA", // 通知公告
+    tab_kw60qnko: "FORM-2G766HA1UWUUAFU10O9TPCXLHYVC38IYRWBVK49", // 公司文件
+    tab_kw60qnkp: "FORM-RH766AC15VUUX7TMY4J2K1AWM0YF3B0VCXBVKI5", // 制度流程
+    tab_kw60qnkq: "FORM-9X766NA15XUUXO4DWI30P08B9DBF3MAB0XBVK95", // 奖惩公示
+    tab_kw60qnkr: "FORM-3J966U61NXUU7UFW3G4S5B7A0XA03QQBCXBVK83", // 意见征集
+  };
+  if (!activeKey) activeKey = that.$("tabsLayout_kw60qyew").state.activeKey;
+  const resp = await that.dataSourceMap.queryFromTab.load({
+    formUuid: category[activeKey],
+    pageSize: 10,
+    searchFieldJson: JSON.stringify({
+      textField_kwemnfh1: messageTypeId,
+    }),
+    approvedResult: "agree",
+  });
+  // 为空当前table数据重置
+  if (!resp.data.length) return updateCompTable("data", [], []);
+  // 匹配数据:1.按照发布日期排序【管理页面设置】,2.区分未读/已读顺序排序,3.抽取当前发布未读置顶
+  const resRead = await mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/msg/getClickRecord`,
+    {
+      userId: loginUser.userId,
+      list: resp.data.map((item) => item.processInstanceId),
+    }
+  );
+  const tableList_read = [];
+  const tableList_top = []; // 本月未读 & 1号显示上个月
+  const tableList_unread = resp.data.reduce((acc, cur) => {
+    if (resRead.data.includes(cur.processInstanceId)) {
+      cur.status = "read";
+      tableList_read.push(cur);
+      return acc;
+    }
+    cur.status = "unread";
+    const yesterday = new Date(Date.now() - 86400000).getMonth();
+    if (new Date(cur.gmtModified).getMonth() == yesterday) {
+      tableList_top.push(cur);
+      return acc;
+    }
+    acc.push(cur);
+    return acc;
+  }, []);
+  // 数据分布和更新
+  const tableList1 = [],
+    tableList2 = [];
+  tableList_top
+    .concat(tableList_unread)
+    .concat(tableList_read)
+    .forEach((item, index) => {
+      item.date = mjs.utils.date.format(
+        new Date(item.data.dateField_kvbwsduj),
+        "YYYY-MM-DD HH:mm:ss"
+      );
+      item.title = item.data.textField_kvbwsduf;
+      if (index < 5) {
+        tableList1.push(item);
+      } else {
+        tableList2.push(item);
+      }
+    });
+  updateCompTable("data", tableList1, tableList2);
+};
+
+// 查询信息发布Tab数据: 宜搭不能实现分页和权限
+home.queryTabList_New = async function (that, activeKey) {
+  const updateCompTable = (prop, value1, value2) => {
+    that.$("tablePc_kwbflqe8").set(prop, value1);
+    that.$("tablePc_kwbflqea").set(prop, value2);
+    if (prop == "data") updateCompTable("loading", false, false);
+  };
+  updateCompTable("loading", true, true);
+  const category = {
+    tab_kw60qnkn: "1", // 通知公告
+    tab_kw60qnko: "0", // 公司文件
+    tab_kw60qnkp: "4", // 制度流程
+    tab_kw60qnkq: "2", // 奖惩公示
+    tab_kw60qnkr: "3", // 意见征集
+  };
+  // 需要做部门屏蔽, 若未传值则取值可见全部范围
+  const msgTypes = that.$("multiSelectField_kybax1xz").getValue();
+  if (!msgTypes.length) {
+    msgTypes.push(...that.$("multiSelectField_kybax1xz").get("dataSource").map(item => item.value));
+  }
+  if (!activeKey) activeKey = that.$("tabsLayout_kw60qyew").state.activeKey;
+  const resp = await mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/msg/msgPublishSelectIndex`,
+    {
+      canSeeUserId: window.loginUser.userId,
+      msgType: msgTypes,
+      formType: category[activeKey],
+      pageIndex: 1,
+      pageSize: 10,
+    }
+  );
+  // 为空当前table数据重置
+  const resList = resp.data.list;
+  if (resList.length > 10) resList.length = 10;
+  // 数据分布和更新
+  const tableList1 = [];
+  const tableList2 = [];
+  resList.forEach((item, index) => {
+    item.status = item.userId ? "read" : "unread";
+    if (index < 5) {
+      tableList1.push(item);
+    } else {
+      tableList2.push(item);
+    }
+  });
+  updateCompTable("data", tableList1, tableList2);
+};
+
+// 上报点击数据
+home.reportEffectiveness = async function (that, record) {
+  const category = {
+    tab_kw60qnkn: 2, // 通知公告
+    tab_kw60qnko: 1, // 公司文件
+    tab_kw60qnkp: 5, // 制度流程
+    tab_kw60qnkq: 3, // 奖惩公示
+    tab_kw60qnkr: 4, //意见征集
+  };
+  let date = record.sendDate || record.date;
+  if (!date.includes(" ")) {
+    date += " 00:00:00";
+  }
+  return mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/msg/saveClickRecord`,
+    {
+      deptId: loginUser.deptId,
+      userId: loginUser.userId,
+      userName: loginUser.userName,
+      formInstId: record.formInstId || record.processInstanceId, // 兼容宜搭权限版本
+      msgCreateTime: date, // 兼容宜搭权限版本
+      msgType: record.msgType || record.data.textField_kwemnfh1, // 兼容宜搭权限版本
+      msgClass: category[that.$("tabsLayout_kw60qyew").state.activeKey],
+      userCompany: that.$("text_kwgf2sdc").get("content"),
+    }
+  );
+};
+
+// 智障:record不是引用类型,forceUpdate无效,故拆分为两个事件
+home.noticeRowClick = async function (that, compId_table, index, record) {
+  await home.reportEffectiveness(that, record); // 阻断
+  const table = that.$(compId_table).get("data");
+  // 兼容信息发布大页面: table为对象
+  const row = table.constructor == Array ? table[index] : table.data[index];
+  row.status = "read";
+  row.userId = loginUser.userId;
+  that.$(compId_table).forceUpdate();
+  // 以打开表单的方式打开流程,显示过滤不显示掉审批记录
+  const formUuid = {
+    tab_kw60qnkn: "FORM-4A9667B1RLMU2F5WWJI1E0MX4GLZ1KIEBP1VKFA", // 通知公告
+    tab_kw60qnko: "FORM-2G766HA1UWUUAFU10O9TPCXLHYVC38IYRWBVK49", // 公司文件
+    tab_kw60qnkp: "FORM-RH766AC15VUUX7TMY4J2K1AWM0YF3B0VCXBVKI5", // 制度流程
+    tab_kw60qnkq: "FORM-9X766NA15XUUXO4DWI30P08B9DBF3MAB0XBVK95", // 奖惩公示
+    tab_kw60qnkr: "FORM-3J966U61NXUU7UFW3G4S5B7A0XA03QQBCXBVK83", // 意见征集
+  }[that.$("tabsLayout_kw60qyew").state.activeKey];
+  // 宜搭可以以表单的形式打开流程,会隐藏审批预览,但权限会失效, 需要兼容: 宜搭智障组件状态:权限管理 3.0 权限设置 > js代码  > 组件配置
+  const dataId = record.processInstanceId || record.formInstId; // 兼容宜搭权限版本
+  const linkUrl = `https://fegroup.aliwork.com/APP_GXUUGZJ1ZPPBIJKLE9BH/formDetail/${formUuid}?formInstId=${dataId}&convertForm=true`;
+  // 钉钉接口
+  if (dd.env.platform === "ios" || dd.env.platform === "android") {
+    dd.biz.util.openLink({ url: linkUrl });
+  } else {
+    window.open(linkUrl);
+  }
+};
+
+// 智障:record不是引用类型,forceUpdate无效,故拆分为两个事件
+home.noticeRowClick_New = async function (that, compId_table, index, record) {
+  await home.reportEffectiveness(that, record); // 阻断
+  const table = that.$(compId_table).get("data");
+  // 兼容信息发布大页面: table为对象
+  const row = table.constructor == Array ? table[index] : table.data[index];
+  row.status = "read";
+  row.userId = loginUser.userId;
+  that.$(compId_table).forceUpdate();
+  // 以打开表单的方式打开流程,显示过滤不显示掉审批记录
+  const formUuid = {
+    tab_kw60qnkn: "FORM-4A9667B1RLMU2F5WWJI1E0MX4GLZ1KIEBP1VKFA", // 通知公告
+    tab_kw60qnko: "FORM-2G766HA1UWUUAFU10O9TPCXLHYVC38IYRWBVK49", // 公司文件
+    tab_kw60qnkp: "FORM-RH766AC15VUUX7TMY4J2K1AWM0YF3B0VCXBVKI5", // 制度流程
+    tab_kw60qnkq: "FORM-9X766NA15XUUXO4DWI30P08B9DBF3MAB0XBVK95", // 奖惩公示
+    tab_kw60qnkr: "FORM-3J966U61NXUU7UFW3G4S5B7A0XA03QQBCXBVK83", // 意见征集
+  }[that.$("tabsLayout_kw60qyew").state.activeKey];
+  // 宜搭可以以表单的形式打开流程,会隐藏审批预览,但权限会失效, 需要兼容: 宜搭智障组件状态:权限管理 3.0 权限设置 > js代码  > 组件配置
+  const dataId = record.processInstanceId || record.formInstId; // 兼容宜搭权限版本
+  const linkUrl = `https://fegroup.aliwork.com/APP_GXUUGZJ1ZPPBIJKLE9BH/formDetail/${formUuid}?formInstId=${dataId}&convertForm=true`;
+  // 钉钉接口
+  if (dd.env.platform === "ios" || dd.env.platform === "android") {
+    if (formUuid == "FORM-9X766NA15XUUXO4DWI30P08B9DBF3MAB0XBVK95") {
+      dd.biz.util.openLink({ url: linkUrl });
+    } else {
+      mjs.utils.storage.LS.SET("msg_info_attachment", record.attachment);
+      // 后台返回数据域名替换
+      const url = record.msgUrl.replace("https://www.aliwork.com", "").replace("https://fegroup.aliwork.com", "");
+      mjs.ding.openNavigation(
+        `https://fegroup.aliwork.com/APP_GXUUGZJ1ZPPBIJKLE9BH/custom/FORM-W46663A1V0NWK3TA3F3QKA0PHX3832VQOH5YKR2?link=${url}`
+      );
+    }
+  } else {
+    window.open(linkUrl);
+  }
+};
+
+////////////////////////////////////////////////////////////// PC端首页-信息发布二级页面 //////////////////////////////////////////////////////////////
+
+// 查询信息类型数据
+home.queryTabSelectAll = async function (that, isMob) {
+  const resSelect = await that.dataSourceMap.querytableList.load({
+    pageSize: 100,
+  });
+  const options = resSelect.data.map((item) => ({
+    text: item.formData.textField_kw7ifc88,
+    value: item.formData.numberField_kwabuszy.toString(),
+    order: item.formData.numberField_kwgek6mh,
+  }));
+  options.sort((a, b) => a.order - b.order);
+  // 搜索条件 || 兼容移动端drawer取数state逻辑
+  if (!isMob) {
+    that.$("selectField_kw7762kc").set("dataSource", options);
+  } else {
+    that.setState({ options });
+  }
+  return options;
+};
+
+// 查询信息类型数据
+home.queryTabSelectAll_New = async function (that, isMob) {
+  const resSelect = await that.dataSourceMap.querytableList.load({
+    pageSize: 100,
+  });
+  let options = resSelect.data.map((item) => ({
+    text: item.formData.textField_kw7ifc88,
+    value: item.formData.numberField_kwabuszy.toString(),
+    order: item.formData.numberField_kwgek6mh,
+    energy: item.formData.textField_l0rgr3ji
+  }));
+  // 远东能源系和其它公司互不可见
+  const company = that.$("text_kwgf2sdc").get("content");
+  const isEnergy = options.some(item => item.energy == company);
+  options = options.reduce((acc, item) => {
+    if (isEnergy && item.energy) {
+      acc.push(item)
+    } else {
+      if (!isEnergy && !item.energy) acc.push(item)
+    }
+    return acc;
+  }, [])
+  options.sort((a, b) => a.order - b.order);
+  // 搜索条件 || 兼容移动端drawer取数state逻辑
+  if (!isMob) {
+    that.$("multiSelectField_kybax1xz").set("dataSource", options);
+  } else {
+    that.setState({ options });
+  }
+  return options;
+};
+
+// 查询信息发布Tab数据
+home.queryTabListForPagination = async function (that, activeKey, isMob) {
+  const updateCompTable = (prop, value) => {
+    that.$("tablePc_kwbflqe8").set(prop, value);
+    if (prop == "data") updateCompTable("loading", false);
+  };
+  updateCompTable("loading", true);
+  const category = {
+    tab_kw60qnkn: "FORM-4A9667B1RLMU2F5WWJI1E0MX4GLZ1KIEBP1VKFA", // 通知公告
+    tab_kw60qnko: "FORM-2G766HA1UWUUAFU10O9TPCXLHYVC38IYRWBVK49", // 公司文件
+    tab_kw60qnkp: "FORM-RH766AC15VUUX7TMY4J2K1AWM0YF3B0VCXBVKI5", // 制度流程
+    tab_kw60qnkq: "FORM-9X766NA15XUUXO4DWI30P08B9DBF3MAB0XBVK95", // 奖惩公示
+    tab_kw60qnkr: "FORM-3J966U61NXUU7UFW3G4S5B7A0XA03QQBCXBVK83", // 意见征集
+  };
+  if (!activeKey) activeKey = that.$("tabsLayout_kw60qyew").state.activeKey;
+  const { currentPage, pageSize } = that.state.paging;
+  // 搜索条件 || 兼容移动端drawer取数state逻辑
+  const title = isMob
+    ? that.state.title
+    : that.$("textField_kwj51qu7").getValue();
+  const typeId = isMob
+    ? that.state.typeId
+    : that.$("selectField_kw7762kc").getValue();
+  const deptId = isMob
+    ? that.state.deptId
+    : that.$("departmentSelectField_kwj51qu8").getValue();
+  const userId = isMob
+    ? that.state.userId
+    : that.$("employeeField_kwj51qu9").getValue();
+  const dateArea = isMob
+    ? that.state.dateArea
+    : that.$("cascadeDateField_kwj51qua").getValue();
+  const resp = await that.dataSourceMap.queryFromTab.load({
+    formUuid: category[activeKey],
+    currentPage,
+    pageSize,
+    searchFieldJson: JSON.stringify({
+      textField_kvbwsduf: title, // 信息标题
+      textField_kwemnfh1: typeId, // 信息类型ID
+      textField_kwj570be: deptId.length ? deptId[0].value : "", // 发布部门ID
+      textField_kwj7bwen: userId.value, // 发布人ID
+      dateField_kvbwsduj: dateArea.start ? [dateArea.start, dateArea.end] : "", // 发布时间区间
+    }),
+    approvedResult: "agree",
+  });
+  // 为空当前table数据重置
+  if (!resp.data.length) return updateCompTable("data", []);
+  // 匹配数据:1.按照发布日期排序【管理页面设置】,2.区分未读/已读顺序排序,3.抽取当前发布未读置顶
+  const resRead = await mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/msg/getClickRecord`,
+    {
+      userId: loginUser.userId,
+      list: resp.data.map((item) => item.processInstanceId),
+    }
+  );
+  const tableList_read = [];
+  const tableList_top = []; // 本月未读 & 1号显示上个月
+  const tableList_unread = resp.data.reduce((acc, cur) => {
+    // 数据更新
+    cur.date = mjs.utils.date.format(
+      new Date(cur.data.dateField_kvbwsduj),
+      "YYYY-MM-DD HH:mm:ss"
+    );
+    cur.title = cur.data.textField_kvbwsduf;
+    if (resRead.data.includes(cur.processInstanceId)) {
+      cur.status = "read";
+      tableList_read.push(cur);
+      return acc;
+    }
+    cur.status = "unread";
+    const yesterday = new Date(Date.now() - 86400000).getMonth();
+    if (new Date(cur.gmtModified).getMonth() == yesterday) {
+      tableList_top.push(cur);
+      return acc;
+    }
+    acc.push(cur);
+    return acc;
+  }, []);
+  // 数据更新:保留分页
+  resp.data = tableList_top.concat(tableList_unread).concat(tableList_read);
+  updateCompTable("data", resp);
+};
+
+// 查询信息发布Tab数据: 宜搭不能实现分页和权限
+home.queryTabListForPagination_New = async function (that, activeKey, isMob) {
+  const updateCompTable = (prop, value) => {
+    that.$("tablePc_kwbflqe8").set(prop, value);
+    if (prop == "data") updateCompTable("loading", false);
+  };
+  updateCompTable("loading", true);
+  const category = {
+    tab_kw60qnkn: "1", // 通知公告
+    tab_kw60qnko: "0", // 公司文件
+    tab_kw60qnkp: "4", // 制度流程
+    tab_kw60qnkq: "2", // 奖惩公示
+    tab_kw60qnkr: "3", // 意见征集
+  };
+  if (!activeKey) activeKey = that.$("tabsLayout_kw60qyew").state.activeKey;
+  const { currentPage, pageSize } = that.state.paging;
+  // 搜索条件 || 兼容移动端drawer取数state逻辑
+  const title = isMob
+    ? that.state.title
+    : that.$("textField_kwj51qu7").getValue();
+  const typeId = isMob
+    ? that.state.typeId
+    : that.$("multiSelectField_kybax1xz").getValue();
+  // 需要做部门屏蔽, 若未传值则取值可见全部范围
+  if (!typeId.length) {
+    const options = isMob ? that.state.options : that.$("multiSelectField_kybax1xz").get("dataSource")
+    typeId.push(...options.map(item => item.value));
+  }
+  const deptId = isMob
+    ? that.state.deptId
+    : that.$("departmentSelectField_kwj51qu8").getValue();
+  const userId = isMob
+    ? that.state.userId
+    : that.$("employeeField_kwj51qu9").getValue();
+  const dateArea = isMob
+    ? that.state.dateArea
+    : that.$("cascadeDateField_kwj51qua").getValue();
+  // 发布时间
+  const params = {};
+  if (dateArea.start) {
+    params.startTime = dateArea.start; // 发布时间区间
+    params.endTime = dateArea.end; // 发布时间区间
+  }
+
+  const resp = await mjs.request.xhr.postData(
+    `${mjs.conf.domain}/api/msg/msgPublishSelectIndex`,
+    {
+      canSeeUserId: window.loginUser.userId, // 可见人员
+      msgType: typeId, // 信息类型
+      formType: category[activeKey], // 信息表单
+      msgTitle: title, // 信息标题
+      createDeptId: deptId.length ? deptId[0].value : "", // 发布部门ID
+      createUserId: userId.value, // 发布人ID
+      pageIndex: currentPage,
+      pageSize,
+      ...params,
+    }
+  );
+  // 数据更新:保留分页
+  updateCompTable("data", {
+    data: resp.data.list || [], // 为空当前table数据重置
+    totalCount: resp.data.total,
+    currentPage: that.state.paging.currentPage,
+  });
+};
+
+// 根据所属公司名称查询阅读查看范围: 仅需要默认值
+home.queryTabSelectMob = async function (that) {
+  const userCompany = that.$("text_kwgf2sdc").get("content");
+  if (!userCompany) return;
+  const resSelect = await that.dataSourceMap.querySelect.load({
+    formUuid: "FORM-OM566O71F7HVX7ZAX1J8N3ECSLRX2YWJKAAWKJ",
+    pageSize: 1,
+    searchFieldJson: JSON.stringify({
+      selectField_kwab6g3s: userCompany,
+    }),
+  });
+  // 设置下拉框默认值
+  that.setState({
+    typeId: [resSelect.data[0].formData.numberField_kwgfn6ds.toString()],
+  });
+};
+
+// 根据所属公司名称查询阅读查看范围: 仅需要默认值
+home.queryTabSelectMob_New = async function (that) {
+  const userCompany = that.$("text_kwgf2sdc").get("content");
+  if (!userCompany) return;
+  const resSelect = await that.dataSourceMap.querySelect.load({
+    formUuid: "FORM-OM566O71F7HVX7ZAX1J8N3ECSLRX2YWJKAAWKJ",
+    pageSize: 1,
+    searchFieldJson: JSON.stringify({
+      selectField_kwab6g3s: userCompany,
+    }),
+  });
+  // 设置下拉框默认值
+  that.setState({
+    typeId: [resSelect.data[0].formData.numberField_kwgfn6ds.toString()],
+  });
+};
+
+fegroup.home = home;
+
+//////////////////////////////////////////////////////////////////////// 导出对象 ////////////////////////////////////////////////////////////////////////
+
+export default fegroup;

+ 67 - 0
src/sample/fushi.js

@@ -0,0 +1,67 @@
+
+/*** mjs 之 福氏钉钉SAP接口对接
+ * 对接宜搭公共JavaScript库
+ * 版权请联系:https://www.aliwork.com/o/mjs
+ * 项目库地址: https://ding.practek.cn:38080/api/mjs/mjs.min.js
+ ***/
+
+import { KEY_NO_LOADING, KEY_SHOW_MESSAGE } from "../service/request";
+
+export default {
+
+  // 公共配置
+  init () {
+    mjs.conf.api = "https://ding.practek.cn:38080/api/fushi";
+    return this; // this 指向当前项目本身
+  },
+
+  // 定制配置
+  SYNC_SUC_ALL: "全部成功",
+
+  // 获取申请人部门层级
+  async getDepartmentCascade (userId, compId) {
+    const rsp = await mjs.request.xhr.doPost("/user/department", { userId }, null, {
+      [KEY_NO_LOADING]: true
+    });
+    mjs.$this.$(compId).setValue(rsp.data.map(item => item.name).join(" / "))
+  },
+
+  // mjs加载完成: 加载推送按钮
+  purchaseLoad () {
+    const status = mjs.$this.$("textField_l2uot27h").getValue() == "同意" && mjs.$this.$('textareaField_l2lgyawc').getValue() != this.SYNC_SUC_ALL;
+    mjs.$this.$('pageSection_l2uoi3fr').setBehavior(status ? "NORMAL" : "HIDDEN")
+  },
+
+  // mjs加载完成: 加载推送按钮
+  paymentLoad () {
+    const status = mjs.$this.$('radioField_l2og167t').getValue() == "是" && mjs.$this.$("textField_l2uot27h").getValue() == "同意"
+      && mjs.$this.$('textareaField_l2lgyawc').getValue() != this.SYNC_SUC_ALL;
+    mjs.$this.$('pageSection_l2uoi3fr').setBehavior(status ? "NORMAL" : "HIDDEN")
+  },
+
+  // 推送采购订单
+  async sapPurchase (compId) {
+    this.doSap("/sap/purchase", compId)
+  },
+
+  // 推送付款申请
+  sapPayment (compId) {
+    this.doSap("/sap/payment", compId)
+  },
+
+  // 推送SAP
+  async doSap (path, compId) {
+    const button = mjs.$this.$(compId);
+    button.set("loading", true)
+    const rsp = await mjs.request.xhr.doPost(path, { processInstanceId: mjs.com.getFormInstIdByUrl() }, null, {
+      [KEY_SHOW_MESSAGE]: true
+    })
+    if (rsp.message == this.SYNC_SUC_ALL) {
+      button.set("behavior", "HIDDEN")
+      return
+    }
+    setTimeout(() => {
+      location.reload();
+    }, 750);
+  }
+};

+ 136 - 0
src/sample/guyuan.js

@@ -0,0 +1,136 @@
+
+/*** mjs 之 谷元发票识别 ***/
+
+export default {
+
+  // 修改公共配置
+  init () {
+    if (process.env.NODE_ENV == "development") {
+      mjs.conf.api = "https://mc.cloudpure.cn/frp/guyuan/";
+    } else {
+      mjs.conf.api = "https://mc.cloudpure.cn/api/guyuan/";
+    }
+    return this; // this 指向当前项目本身
+  },
+
+  // 发票识别, 混票
+  async mixedInvoice (file) {
+    const rsp = await mjs.request.xhr.doPost(`${mjs.conf.api}/invoice-iv`, {}, {
+      url: file.url,
+      isPdf: file.type.includes("pdf"),
+      size: file.size / 1024 / 1024
+    })
+    // 明细数据匹配表头
+    const headers = mjs.$this.$("tableField_liv5f4d2").props.children.props.children.map(({ props: { label, fieldId } }) => ({ label, compId: fieldId }))
+    const prop = rsp.data.dto; // 通用字段定义
+    const invoices = rsp.data.result.map(item => {
+      const rowData = Object.keys(prop).reduce((acc, cur) => {
+        const comp = headers.find(c => c.label == cur)
+        if (comp) {
+          acc[comp.compId] = item[prop[cur]]
+        }
+        // 非标准表头,格式化
+        if (cur == "价税合计") {
+          acc.numberField_liihyrt7 = item.amount;
+        }
+        if (cur == "开票日期") {
+          acc.textField_livimrja = item[prop[cur]];
+          acc.dateField_liihyrt9 = new Date(acc.textField_livimrja).getTime()
+        }
+        return acc;
+      }, {})
+      // 发票数据标题: 销售方 + 发票类型 + 价税合计
+      rowData.textField_ljmgqvbz = item.sellerName + "-" + item.kindName + "-" + item.amount
+      return rowData;
+    });
+    mjs.$this.$('tableField_liv5f4d2').setValue(invoices);
+  },
+
+  // 批量验证
+  batchCheck () {
+    return new Promise((resolve, reject) => {
+      const details = mjs.$this.$('tableField_liv5f4d2').getValue()
+      const param = details.map(item => {
+        return {
+          kindName: item.selectField_liihyrta,
+          serial: item.textField_liihyrt8,
+          code: item.textField_lil34mnc,
+          date: item.textField_livimrja,
+          amount: item.numberField_liihyrt7,
+          excludingTax: item.numberField_livimrj8,
+          checkCode: item.textField_lil34mne,
+          buyerName: item.selectField_lix0cyqx,
+          buyerTaxId: item.textField_lil34mng,
+          sellerName: item.textField_liihyrt3,
+          sellerTaxId: item.textField_lil34mnf,
+        }
+      })
+      mjs.request.xhr.doPost(`${mjs.conf.api}/invoice-va`, {}, {
+        param
+      }).then(() => resolve()).catch(() => reject()) // ppExt: 需要拦截错误, 否则一次失败后程序阻断
+    })
+  },
+
+  // 发票查重, 验真
+  validate () {
+    return new Promise((resolve, reject) => {
+      if (mjs.env == 2) {
+        if (mjs.$this.$('selectField_liihyrt6').getValue() == "已使用") {
+          return mjs.com.toastError("发票已被使用,不允许修改!")
+        }
+        // 校验通过: 发票未使用状态且不在流程中 || 否改为否,标识状态不做校验
+        if (mjs.$this.$('radioField_liihyrtb').getValue() == "是" || mjs.corp.guyuan.status == "否") {
+          return mjs.com.toastError(null, resolve)
+        }
+      }
+      // 非作废校验
+      const param = {
+        kindName: mjs.$this.$("selectField_liihyrta").getValue(),
+        serial: mjs.$this.$("textField_liihyrt8").getValue(),
+        code: mjs.$this.$("textField_lil34mnc").getValue(),
+        date: mjs.$this.$("textField_liwadykg").getValue(),
+        amount: mjs.$this.$("numberField_liihyrt7").getValue(),
+        excludingTax: mjs.$this.$("numberField_liwauxaq").getValue(),
+        checkCode: mjs.$this.$("textField_lil34mne").getValue(),
+        buyerName: mjs.$this.$('selectField_lix0cyqx').getValue(),
+        buyerTaxId: mjs.$this.$('textField_lil34mng').getValue(),
+        sellerName: mjs.$this.$('textField_liihyrt3').getValue(),
+        sellerTaxId: mjs.$this.$('textField_lil34mnf').getValue(),
+      };
+      mjs.request.xhr.doPost(`${mjs.conf.api}/invoice-va`, {}, {
+        param: [param]
+      }).then(() => resolve()).catch(err => reject(err)) // ppExt: 需要拦截错误, 否则一次失败后程序阻断
+    })
+  },
+
+  // 提交校验 [日常报销/项目付款]
+  submit () {
+    const associationIds = mjs.$this.$('tableField_krmybpq6').getValue().reduce((acc, cur) => {
+      acc.push(...(cur.associationFormField_liya90jt || cur.associationFormField_ljnoa248).map(item => item.instanceId))
+      return acc;
+    }, [])
+    // […new Set(arr)] 返回的是数组内set,取length异常,为1个Set对象,但循环处理数据正常。因此可通过:new Set(ids).size != ids.length
+    if (new Set(associationIds).size != associationIds.length) {
+      return mjs.com.toastError("请勿重复选择发票!")
+    }
+    // prd 费用明细中的报销金额修改金额, 不能大于发票金额
+    let idx = -1;
+    mjs.$this.$('tableField_krmybpq6').getValue().forEach((item, index) => {
+      if (item.numberField_krn4ig4x > (item.numberField_ljau8ps7 || item.numberField_ljnoa249)) {
+        idx = index;
+        return
+      }
+    })
+    if (idx >= 0) {
+      return mjs.com.toastError(`第【${idx + 1}】记录, 报销金额已大于发票金额!`)
+    }
+    // 兼容: 退回为监听宜搭dom事件, 先执行接口调用, 才会校验宜搭必填, 过滤无效调用 || 先匹配校验是否可调用
+    const bx = (mjs.$this.$("numberField_krn54uoe") || mjs.$this.$("numberField_kroa4wk1")).getValue();
+    const fk = (mjs.$this.$("numberField_krn7ufyt") || mjs.$this.$("numberField_krf2spcw")).getValue();
+    if (bx != fk) {
+      return mjs.com.toastError("费用明细合计与支付金额不一致!")
+    }
+    return true;
+  }
+}
+

+ 56 - 0
src/sample/hisJianGao.js

@@ -0,0 +1,56 @@
+import request from "./request";
+import { u8cGetAllCorp, u8cPushPayment } from "../service/network";
+
+// 报销 v2.0
+
+// /** @exports 健高导出-v2 */
+// export { aliworkInitParams } from "./service/network";
+// export { _checkSubmitEnv, _formatDetailToJsonString } from "./deploy/aliwork";
+
+// -------------------- 健高 -------------------- //
+
+/**
+ * @function 使用是接口
+ * 1.aliworkInitParams
+ * 2._checkSubmitEnv
+ * 3._formatDetailToJsonStringw
+ */
+
+// 健高宜搭\U8服务地址
+function _aliworkHisJianGaoUrl (uri) {
+  return "https://yd.jiangaoerke-info.com/" + uri;
+}
+
+/**
+ * @exports 健高:获取钉钉指定人员部门列表
+ * @param userId
+ **/
+export function ddingAddressBookForDepart (userId) {
+  return request.post(_aliworkHisJianGaoUrl("dingUser/getDIByUId"), {
+    userId,
+  });
+}
+
+/** @exports 获取公司信息 */
+export async function _u8cGetCompanyList () {
+  const res = await u8cGetAllCorp();
+  return res.data.list.map((item) => {
+    return {
+      text: item.unitname,
+      value: item.unitcode,
+    };
+  });
+}
+
+// 临时接口: 替代注册服务测试
+export async function _u8cPushProofOfPayment (orderType, { params, details }) {
+  await u8cPushPayment({
+    orderType,
+    ...params,
+    djlxbm: "D5",
+    traderype: 2,
+    pj_jsfs: 104,
+    hbbm: "",
+    subList: details,
+  });
+}

+ 29 - 0
src/sample/nongzhan.js

@@ -0,0 +1,29 @@
+import * as ddAPi from "dingtalk-jsapi";
+// // NOTE: 未默认解构 default - 不能直接解构, 不识别. 不能有效减少包体积
+const dd = ddAPi.default;
+
+import request from "../service/request";
+
+// -------------------- 农展本地函数 -------------------- //
+
+const nz = {};
+
+// 免登获取用户信息: 移动端
+nz.authUserInfo = function () {
+  return new Promise((resolve) => {
+    dd.ready(() => {
+      dd.runtime.permission.requestAuthCode({
+        corpId: "dingb8fa8a8bd555723bee0f45d8e4f7c288",
+        onSuccess: async (result) => {
+          const resp = await request.postParams(
+            `https://yida.zitoo.com.cn/transmit/nz`,
+            { authCode: result.code }
+          );
+          resolve(resp.data);
+        },
+      });
+    });
+  });
+};
+
+export default nz;

+ 14 - 0
src/sample/ruyi.js

@@ -0,0 +1,14 @@
+import request from "./request";
+
+// -------------------- 如意 -------------------- //
+
+function _dingRuyiUrl (uri) {
+  return "https://ding.ruyi-ht.com:38800/ryApi" + uri;
+}
+
+/* @exports 手动同步钉钉组织架构
+ * @param userId
+ */
+export function ddingSyncSysInfoAll () {
+  return request.post(_dingRuyiUrl("/sysInfoAll"));
+}

+ 84 - 0
src/sample/yde.js

@@ -0,0 +1,84 @@
+import request from "../service/request";
+import { KEY_M_CONTENT_TYPE } from "../service/request";
+
+// -------------------- 意迪尔 -------------------- //
+
+// 意迪尔服务地址
+function _aliworkYdrUrl (uri) {
+  return "https://aliwork.zitoo.com.cn/yde" + uri;
+}
+
+/**
+ * @exports 查询U8销售列表
+ **/
+export async function queryOrderList ({ prop, ctx, compId }) {
+  const res = await request.postFormDataAppend(
+    _aliworkYdrUrl("/order/batchGet"),
+    {
+      page_index: 1,
+      rows_per_page: 100,
+    },
+    {
+      [KEY_M_CONTENT_TYPE]: true,
+    }
+  );
+  if (!prop) return res.data.list;
+  const options = res.data.list.map((item) => {
+    const value = item[prop];
+    return {
+      text: value,
+      value,
+    };
+  });
+  ctx.store.get(compId).set("options", options);
+}
+
+/**
+ * @exports 查询U8销售明细列表
+ **/
+export async function queryOrderDetailList (
+  { conditions, props, ctx, compId_detail_cur } = {},
+  { funcFilter } = {}
+) {
+  if (!conditions.length) return;
+  const ids = conditions.join(",");
+  const res = await request.postFormDataAppend(
+    _aliworkYdrUrl("/order/getDetail"),
+    {
+      ids,
+    },
+    {
+      [KEY_M_CONTENT_TYPE]: true,
+    }
+  );
+  const resList = res.data.list;
+  return _updateCompDetailFromDetail(
+    { ctx, resList, compId_detail_cur, props },
+    { funcFilter }
+  );
+}
+
+//////////////////////////////////////////////// 数据处理 ////////////////////////////////////////////////
+
+import { _resetDetailData, _updateCompDetail } from "./aliwork";
+
+// list数据更新明细组件
+export function _updateCompDetailFromDetail (
+  { ctx, resList, props, compId_detail_cur } = {},
+  { funcFilter } = {}
+) {
+  _resetDetailData(ctx, compId_detail_cur, props); // 重置明细数据
+  if (!resList.length) return;
+  if (funcFilter) {
+    // 过滤方法条件:detail => detail["compId"] == "条件"
+    resList = resList.filter((detail) => funcFilter(detail));
+  }
+  const details = resList.map((detail) => {
+    return props.reduce((row, prop) => {
+      const value = detail[prop.src] || prop.def;
+      row[prop.cur] = { fieldData: { value, text: value } }; // 明细空白解决办法
+      return row;
+    }, {});
+  });
+  return _updateCompDetail(ctx, compId_detail_cur, details);
+}

+ 34 - 0
src/sample/zitooCrm.js

@@ -0,0 +1,34 @@
+import request from "./request";
+import { nonceStr } from "../config/env";
+
+// -------------------- 致拓crm -------------------- //
+
+// 钉钉服务地址
+function _dingZitooUrl (url) {
+  return `https://aliwork.zitoo.com.cn.com/zitooCrm${url}`;
+}
+
+/**
+ * @exports :创建钉钉群聊会话
+ * @param name 群名
+ * @param owner 群主
+ * @param useridlist 群成员
+ * */
+export function ddingTalkChatCreate (name, owner, useridlist) {
+  return request.post(_dingZitooUrl("/chatCreate"), {
+    name,
+    owner,
+    useridlist,
+  });
+}
+
+/**
+ * @exports :钉钉注册鉴权接口
+ */
+export function ddingRegisterAuth (timeStamp, url) {
+  return request.post(_dingZitooUrl("/chatAuth"), {
+    timeStamp,
+    url,
+    nonceStr,
+  });
+}

+ 78 - 0
src/service/aliwork.js

@@ -0,0 +1,78 @@
+
+import request from "./request";
+
+//////////////////////////////////////////// 宜搭服务 ////////////////////////////////////////////
+
+/**
+ * @exports 公共:查询单据数据
+ * @param url: 单据 /form/select ; 流程 /process/select
+ * @param conditionList: 原关联表单的字段, 如 [{"fieldName":"selectField_k9tw8gaq","value":"天城水榭一期"}] - 只能查询当前formId下的表单字段
+ **/
+export function aliworkTheDynamicSubsidiary (
+  url,
+  { userId, appType, formId, conditionList, pageIndex = 1, pageSize = 10 }
+) {
+  // 宜搭限制上限为100
+  if (pageSize > 100) pageSize = 100;
+  const params = {
+    pageIndex,
+    pageSize,
+    userId,
+    appType,
+    formId,
+  };
+  if (conditionList) params.conditionList = conditionList;
+  return request.postData(_aliworkZitooUrl(url), params);
+}
+
+/**
+ * @exports 公共:修改组件数据,明细是全量覆盖
+ * @param url: 单据 /form/update ; 流程 /process/update
+ * @param updateList: 更新表单内的字段, 如 [{"fieldName":"compId","value":"xxxx"}] - 通过实例Id进行更新
+ **/
+export function aliworkTheDynamicUpdate (
+  url,
+  { userId, appType, formInstId, updateList }
+) {
+  const params = {
+    userId,
+    appType,
+    formInstId,
+  };
+  if (updateList) params.conditionList = updateList;
+  return request.postData(_aliworkZitooUrl(url), params);
+}
+
+/**
+ * @exports 公共:新增实例,表单\流程
+ * @param url: 单据 /form/insert ; 流程 /process/insert
+ * @param params: 数据和语雀文档一直, systemToken 不传, 后端通过 appType 匹配
+ **/
+export function aliworkTheDynamicInsert (url, params) {
+  return request.postData(_aliworkZitooUrl(url), params);
+}
+
+/**
+ * @exports 公共:删除实例,表单\流程
+ * @param url: 单据 /form/delete ; 流程 /process/delete
+ * @param params: userId, appType, formInstId\processInstanceId
+ *
+ **/
+export function aliworkTheDynamicDelete (url, params) {
+  return request.postData(_aliworkZitooUrl(url), params);
+}
+
+/**
+ * @exports 公共:实例详情,表单\流程(流程支持批量)
+ * @param url: 单据 /form/getFormDataById ; 流程 process/getInstancesByIds
+ * @param params: userId, appType, formInstId\processInstanceIds
+ *
+ **/
+export function aliworkTheDynamicDetail (url, params) {
+  return request.postData(_aliworkZitooUrl(url), params);
+}
+
+/** @exports 公共:附件转免登访问地址 */
+export function aliworkConvertOpenUrl (params) {
+  return request.postData(_aliworkConnecterUrl("getMdUrl"), params);
+}

+ 4 - 0
src/service/network.js

@@ -0,0 +1,4 @@
+import request from "./request";
+
+// 请求地址
+

+ 304 - 0
src/service/request.js

@@ -0,0 +1,304 @@
+import axios from "axios";
+import qs from "qs";
+
+import progress from "../aliwork/com";
+import config from "../config/conf"
+
+export const CODE_SUCCESS = 200;           // 状态正常
+export const CODE_TOKEN_INVALID = 4002;    // 登录失效
+export const CODE_NOT_AUTHORIZED = 4003;   // 没有权限
+
+/////////////////////////////////////////////////  基础参数   /////////////////////////////////////////////////
+
+const request = {};
+
+// 默认配置: 内部变量, 兼容reject不能获取到请求conf配置, 设置全局变量记录
+const _conf = {
+  mContentType: false, // 记录默认请求头格式
+  countLoading: 0 // 使用计数 [拦截器失败重置]
+};
+
+// 网络请求
+const ContentType = [
+  "application/json;charset=UTF-8",
+  "application/x-www-form-urlencoded"
+];
+
+const http = axios.create({
+  timeout: config.timeout,
+  baseURL: ""
+});
+http.defaults.headers["Content-Type"] = ContentType[Number(_conf.mContentType)]; // 默认 Content-Type
+
+// 拦截器专用: 处理loading状态, 避免出现闪屏; 处理是否显示错误提示
+function _interceptorsError (error) {
+  _conf.countLoading = 0;
+  progress.hideLoading();
+  const errMsg = error ? error.message || "系统错误" : null;
+  progress.showErrorMessage(errMsg)
+  return error;
+}
+
+// 请求拦截器
+http.interceptors.request.use(
+  config => {
+    if (!config.url.includes("http")) {
+      config.url = mjs.conf.api + config.url
+    }
+    // 无需删除conf属性: 不会提交到请求
+    const { conf } = config;
+    if (!conf[KEY_NO_LOADING]) {
+      _conf.countLoading += 1;
+      progress.showLoading();
+    }
+    if (conf[KEY_M_CONTENT_TYPE]) {
+      config.headers["Content-Type"] = ContentType[Number(!_conf.mContentType)]; // // 修改 Content-Type: 取反
+    }
+    config.headers["Authorization"] = mjs.conf.token;
+    console.log('请求入参', config);
+    return config;
+  },
+  error => {
+    return Promise.reject(_interceptorsError(error));
+  }
+);
+
+// 将blob对象转化为json(文件类型调用ajax 取后端的返回值做特殊处理)
+function _fileToJson (file) {
+  let data = {},
+    message = "";
+  function formatReturn () {
+    return { data, message };
+  }
+  return new Promise(resolve => {
+    const reader = new FileReader();
+    reader.onload = res => {
+      const { result } = res.target; // 得到字符串
+      try {
+        // 解析成json对象
+        data = JSON.parse(result);
+      } catch (err) {
+        message = err.message || err;
+      }
+      resolve(formatReturn());
+    }; // 成功回调
+    reader.onerror = err => {
+      message = err.message || err;
+      resolve(formatReturn());
+    }; // 失败回调
+    reader.readAsText(new Blob([file]), "utf-8"); // 按照utf-8编码解析
+  });
+}
+
+// 响应拦截器
+http.interceptors.response.use(
+  async response => {
+    console.log('请求响应', response);
+    const { conf } = response.config;
+    // loading处理
+    if (!conf[KEY_NO_LOADING]) {
+      _conf.countLoading -= 1;
+    }
+    if (!_conf.countLoading) {
+      progress.hideLoading()
+    }
+    if (conf[KEY_IGNORE_RESPONSE]) {
+      return response;
+    }
+    // 文件Excel导出: 处理请求声明的blob是否为json
+    if (response.config.conf[KEY_EXPORT_TYPE]) {
+      const rsp = await _fileToJson(response.data);
+      // 若响应数据为流
+      if (!rsp.data.code) {
+        // 文件名称: 需要后端设置为可见, 否则 header 有, 但 js 取不到值
+        let fileName;
+        let disposition = response.headers["content-disposition"];
+        if (disposition) {
+          disposition = disposition.toLowerCase()
+          fileName = decodeURIComponent(disposition.replace("attachment;filename=", "").replace("attachment;filename*=utf-8", ""));
+        }
+        return { data: response.data, fileName }; // 格式化输出
+      }
+      // 赋值, 触发失败弹出框
+      response.data = rsp.data;
+    }
+    const msg = response.data.msg || response.data.message;
+    const code = Number(response.data.code);
+    if (code === CODE_SUCCESS || response.data.success) {
+      const tip = conf[KEY_SHOW_MESSAGE];
+      if (tip) {
+        // 需要show成功Message信息
+        progress.showSuccessMessage(typeof tip == "boolean" ? msg || "请求成功" : tip);
+      }
+      return response.data
+    }
+    // 登录失效跳转登录页面. NOTE: 注意可能导致循环调用
+    if (code === CODE_TOKEN_INVALID) {
+      progress.showErrorMessage(msg)
+      setTimeout(() => {
+        progress.invalidToken(msg);
+      }, 350);
+    }
+    // 请求结束忽略error
+    if (!conf[KEY_NO_ERROR_TIP]) {
+      progress.showErrorMessage(msg || '未知异常');
+    }
+    return Promise.reject(msg);
+  },
+  error => {
+    return Promise.reject(_interceptorsError(error));
+  }
+);
+
+/////////////////////////////////////////////////  请求配置   /////////////////////////////////////////////////
+
+/** @param Boolean: noLoading => 是否显示loading */
+export const KEY_NO_LOADING = "noLoading";
+/** @param Boolean: noErrorTip => 是否显示错误提示框; */
+export const KEY_NO_ERROR_TIP = "noErrorTip";
+/** @param Boolean: mContentType => 是否修改请求头类型 */
+export const KEY_M_CONTENT_TYPE = "mContentType";
+/** @param Boolean: showMessage => 是否显示提示 */
+export const KEY_SHOW_MESSAGE = "showMessage";
+/** @param Boolean: responseType => 导出文件流类型 */
+export const KEY_EXPORT_TYPE = "responseType";
+/** @param Boolean: ignoreResponse => 返回数据不校验 */
+export const KEY_IGNORE_RESPONSE = "ignoreResponse";
+/** @param Boolean: noLoadToken => 不传递token */
+export const KEY_NO_LOAD_TOKEN = "noLoadToken";
+
+/** 0.通用请求 */
+request.do = function (url, param = {}, data = {}, conf = {}, method) {
+  const params = qs.stringify(param);
+  if (params) {
+    const joint = url.includes("?") ? "&" : "?";
+    url += (joint + params)
+  }
+  return http({
+    url,
+    method,
+    data,
+    conf,
+    responseType: conf[KEY_EXPORT_TYPE]
+  });
+}
+
+/** 1.get: url上param, 后端取值@requestParam,也可用request.getParameterMap().get(“key”), 参数会被放入一个集合 */
+request.doGet = function (url, param = {}, conf = {}) {
+  return this.do(url, param, null, conf, "GET");
+};
+
+/** 2.post: body内json, 后端取值@requestBody, Map 或转为实体 */
+request.doPost = function (url, param = {}, data = {}, conf = {}) {
+  return this.do(url, param, data, conf, "POST");
+};
+
+/** 3.form: body内格式为form, 和content-type有关系, 需要为form格式后端才能读取: 不能使用@RequestBody,参数会自动解析到实体; 若不是实体通过方法转Map */
+request.doForm = function (url, param, data = {}, conf = {}) {
+  // 变更请求头类型, 传递form
+  conf[KEY_M_CONTENT_TYPE] = true;
+  return this.doPost(url, param, data, conf);
+};
+
+
+/** 4.upload: body-formData, 一般用于文件上传, 追加数据流 */
+request.doUpload = function (url, param, data = {}, conf = {}) {
+  const formData = new FormData();
+  Object.keys(data).forEach(prop => {
+    formData.append(prop, data[prop]);
+  });
+  return this.doPost(url, param, formData, conf);
+};
+
+/** 5.并发多个请求优先(类方法) */
+request.doSpread = function (arrReq) {
+  return axios.spread(arrReq);
+};
+
+/** 6.并发多个请求全部(类方法) */
+request.doAll = function (arrReq) {
+  return axios.all(arrReq);
+};
+
+
+/////////////////////////////////////////////////  服务配置   /////////////////////////////////////////////////
+
+/** 1.excel: 下载blob数据 */
+request.doDownload = function (blobData, fileName, type) {
+  fileName = fileName || "导出文件_" + Date.now();
+  const blob = new Blob([blobData], {
+    type: type || "application/vnd.ms-pdf;charset=UTF-8"
+  });
+  const link = document.createElement("a");
+  link.style.display = "none";
+  link.href = URL.createObjectURL(blob);
+  link.download = fileName;
+  document.body.appendChild(link);
+  link.click();
+  URL.revokeObjectURL(link.href);
+  document.body.removeChild(link);
+};
+
+/** 2.Excel: 导出并下载blob数据 */
+request.doExport = async function (url, param, data = {}, method = "POST", type) {
+  const rsp = await request.do(url, param, data, {
+    [KEY_EXPORT_TYPE]: "blob"
+  }, method);
+  // 若响应数据为流
+  if (!rsp.code) {
+    this.doDownload(rsp.data, rsp.fileName, type);
+  }
+  return rsp;
+};
+
+/** 2.1.post方式下载blob数据 */
+request.doExportByPOST = async function (url, param, data = {}, type) {
+  return this.doExport(url, param, data, "POST", type)
+};
+
+/** 2.2.get方式下载blob数据 */
+request.doExportByGET = async function (url, param, type) {
+  return this.doExport(url, param, null, "GET", type)
+};
+
+/** 3.Excel: 自定义导入, 自动下载失败记录 */
+request.doImport = async function (url, file, param, data) {
+  const rsp = await request.doPostFormData(url, param, { file, ...data }, {
+    [KEY_EXPORT_TYPE]: "blob"
+  });
+  // 若响应数据为流
+  if (!rsp.code) {
+    progress.showErrorMessage("部分导入失败");
+    this.doDownload(rsp.data, rsp.fileName);
+    return
+  }
+  progress.showSuccessMessage("全部导入成功");
+}
+
+/** 4.Excel:导入数据el-upload组件 */
+request.doElementUI = function (url, data = {}, successHandle) {
+  return {
+    action: url,
+    fileList: [],
+    data,
+    onReady () {
+      progress.showLoading();
+    },
+    success: function (rsp) {
+      progress.hideLoading();
+      if (rsp.code == CODE_SUCCESS) {
+        progress.showSuccessMessage("导入成功");
+        successHandle && successHandle(rsp)
+      } else {
+        progress.showErrorMessage(rsp.msg);
+      }
+    },
+    failure: function () {
+      progress.hideLoading();
+      progress.showErrorMessage("导入失败");
+    }
+  };
+};
+
+/** @interface 导出请求方法:thunk包装调用 */
+export default request;

+ 16 - 0
src/service/vendor.js

@@ -0,0 +1,16 @@
+/**
+ * @exports 返回变量型跨域
+ * @returns 如获取ip: https://pv.sohu.com/cityjson?ie=utf-8, 返回值:var returnCitySN = {"cip": "124.79.25.204", "cid": "310101", "cname": "上海市黄浦区"};
+ **/
+export function crossDomainByScript(src, prop) {
+  if (!src) Promise.reject("地址不能为空");
+  return new Promise((resolve) => {
+    const script = document.createElement("script");
+    script.setAttribute("type", "text/javascript");
+    script.setAttribute("src", src);
+    document.body.appendChild(script);
+    script.onload = function () {
+      resolve(window[prop]);
+    };
+  });
+}

+ 35 - 0
src/style/index.css

@@ -0,0 +1,35 @@
+/* 去掉审批流程顶部信息 */
+.detail-head-field {
+  display: none !important;
+}
+
+/* 输入框标题字号 */
+.next-form-item-label,
+.mt-form-item-label,
+.mt-form-item-label--inset--medium {
+  font-size: 16px !important;
+  font-weight: 500;
+}
+
+/* 输入框内容字号 */
+.mt-input-textinput,
+.mt-form-item-input--preview-medium,
+.mt-form-item-textinput--preview-medium {
+  font-size: 16px !important;
+}
+
+/* 成员组件去掉头像 */
+.deep-employee-field-list-item-avatar {
+  display: none !important;
+}
+/* 成员组件样式 */
+.deep-employee-field-list {
+  padding-top: 0;
+  margin-right: 0;
+  text-align: left;
+}
+/* 成员组件文本处理 */
+.deep-employee-field-list-item-name {
+  font-size: 16px !important;
+  text-align: left;
+}

+ 16 - 0
src/utils/browser.js

@@ -0,0 +1,16 @@
+const browser = {};
+
+// 关闭页面
+browser.closeCurrentTab = function () {
+  try {
+    window.opener = window;
+    var win = window.open("", "_self");
+    win.close();
+    // frame的时候
+    if (window.top) window.top.close();
+  } catch (e) {
+    console.log('关闭页面', e);
+  }
+};
+
+export default browser;

+ 45 - 0
src/utils/math.js

@@ -0,0 +1,45 @@
+export default {
+
+  // 四舍五入位数
+  round (number, precision) {
+    return Math.round(+number + 'e' + precision) / Math.pow(10, precision);
+    //same as:
+    //return Number(Math.round(+number + 'e' + precision) + 'e-' + precision);
+  },
+
+  // 获取最大值
+  max (list, prop) {
+    const arr = list.map(item => item[prop])
+    // apply, 修改this指向: Math.max.apply(null, arr); 或 Math.max.apply(Math, arr);
+    return Math.max(arr)
+  },
+
+  // 获取最小值
+  min (list, prop) {
+    const arr = list.map(item => item[prop])
+    return Math.min(arr)
+  },
+
+  // 四舍五入保留两位小数 (不够位数, 则用0替补)
+  keepTwoDecimalFull (num) {
+    let result = parseFloat(num);
+    if (isNaN(result)) {
+      return "0.00";
+    }
+    result = Math.round(num * 100) / 100;
+    let s_x = result.toString();
+    const pos_decimal = s_x.indexOf(".");
+    // 当整数时, pos_decimal=-1 自动补0
+    if (pos_decimal < 0) {
+      pos_decimal = s_x.length;
+      s_x += ".";
+    }
+    // 当数字的长度 < 小数点索引 +2时, 补0
+    while (s_x.length <= pos_decimal + 2) {
+      s_x += "0"
+    }
+    return s_x;
+  }
+}
+
+

+ 78 - 0
src/utils/optimize.js

@@ -0,0 +1,78 @@
+/**
+ * @export 函数节流:设置一个可执行状态,interval时间内不再调用就触发fn
+ */
+export function throttle (fn, interval = 300, scope) {
+  let status = false;
+  return function () {
+    if (status) return;
+    status = true;
+    fn.apply(scope || this, arguments);
+    setTimeout(() => {
+      status = false;
+    }, interval);
+  };
+}
+
+/**
+ * @export 函数防抖:每次调用重置计时器,delay时间内不再调用就触发fn
+ * @function 页面内防抖 clearTimeout(this.timer); this.timer = null; this.timer = setTimeout(() => { if (_this.timer) _query(); }, 300);
+ * @function 页面内防抖, 在methods内直接引用debounce, querySearch: debounce(async function(done) { ... })
+ */
+export function debounce (fn, delay = 300) {
+  var timer;
+  return function () {
+    var args = arguments;
+    if (timer) {
+      clearTimeout(timer);
+      timer = null;
+    }
+    timer = setTimeout(() => {
+      fn.apply(this, args);
+    }, delay);
+  };
+}
+
+/**
+ * @export 对象深度拷贝
+ */
+export function deepClone (source) {
+  // 判断复制的目标是数组还是对象
+  const targetObj = source.constructor === Array ? [] : {};
+  for (const keys in source) {
+    if (source.hasOwnProperty(keys)) {
+      if (source[keys] && typeof source[keys] === "object") {
+        // 如果值是对象,就递归一下
+        targetObj[keys] = source[keys].constructor === Array ? [] : {};
+        targetObj[keys] = deepClone(source[keys]);
+      } else {
+        // 如果不是,就直接赋值
+        targetObj[keys] = source[keys];
+      }
+    }
+  }
+  return targetObj;
+}
+
+/**
+ * @export 图片地址转base64:异步
+ */
+export function imageUrlToBase64Image (url) {
+  return new Promise((resolve) => {
+    var image = new Image();
+    image.src = url + "?v=" + Math.random(); // 处理缓存
+    image.crossOrigin = "*"; // 支持跨域图片
+    image.onload = () => {
+      var canvas = document.createElement("canvas");
+      canvas.width = image.width;
+      canvas.height = image.height;
+      var context = canvas.getContext("2d");
+      context.drawImage(image, 0, 0, image.width, image.height);
+      var quality = 0.8;
+      resolve(canvas.toDataURL("image/jpeg", quality));
+    };
+  });
+}
+
+export default {
+  throttle, debounce
+}

+ 112 - 0
src/utils/storage.js

@@ -0,0 +1,112 @@
+const SS = {};
+const LS = {};
+const CK = {};
+
+// -------------------------------- //
+//    sessionStorage - str\obj      //
+// -------------------------------- //
+
+SS.SET = function (key, data) {
+  if (typeof data == "object") data = JSON.stringify(data);
+  sessionStorage.setItem(key, data);
+};
+
+SS.GET = function (key) {
+  let data = sessionStorage.getItem(key);
+  if (/"/.test(data)) data = JSON.parse(data);
+  return data;
+};
+
+SS.REMOVE = function (key) {
+  sessionStorage.removeItem(key);
+};
+
+SS.CLEAR = function () {
+  sessionStorage.clear();
+};
+
+// -------------------------------- //
+//     localStorage - str\obj       //
+// -------------------------------- //
+
+LS.SET = function (key, data) {
+  if (typeof data == "object") data = JSON.stringify(data);
+  localStorage.setItem(key, data);
+};
+
+LS.GET = function (key) {
+  let data = localStorage.getItem(key);
+  if (/"/.test(data)) data = JSON.parse(data);
+  return data;
+};
+
+LS.REMOVE = function (key) {
+  localStorage.removeItem(key);
+};
+
+LS.CLEAR = function () {
+  localStorage.clear();
+};
+
+// cookie 设置过多可能会导致浏览器异常
+LS.SET_EXPIRE = function (key, data, expire) {
+  data = {
+    expire: Date.now() + expire * 1000,
+    data
+  }
+  this.SET(key, data)
+}
+
+LS.GET_EXPIRE = function (key) {
+  let data = this.GET(key);
+  if (data && data.expire >= Date.now()) {
+    data = data.data;
+  } else {
+    this.REMOVE(key)
+    data = null;
+  }
+  return data;
+}
+
+// -------------------------------- //
+//         cookies - expires        //
+// -------------------------------- //
+
+CK.SET = function (name, value, seconds) {
+  if (typeof value == "object") value = JSON.stringify(value);
+  let expires = "";
+  if (seconds != 0) {
+    //设置cookie生存时间
+    const date = new Date();
+    date.setTime(date.getTime() + seconds * 1000);
+    expires = "; expires=" + date.toGMTString();
+  }
+  document.cookie = name + "=" + escape(value) + expires + "; path=/"; //转码并赋值
+};
+
+CK.GET = function (name) {
+  const nameEQ = name + "=";
+  const ca = document.cookie.split(";"); // 把cookie分割成组
+  for (let i = 0; i < ca.length; i++) {
+    let c = ca[i]; // 取得字符串
+    while (c.charAt(0) == " ") {
+      // 判断一下字符串有没有前导空格
+      c = c.substring(1, c.length); // 有的话,从第二位开始取
+    }
+    if (c.indexOf(nameEQ) == 0) {
+      // 如果含有需要的name
+      let value = unescape(c.substring(nameEQ.length, c.length));
+      if (/"/.test(value)) value = JSON.parse(value);
+      return value; // 解码并截取我们要值
+    }
+  }
+  return false;
+};
+
+CK.REMOVE = function (name) {
+  this.SET(name, "", -1);
+};
+
+/* export default 导出会加一层 default 包裹:  import 导入时默认已解构
+import { SS as storage } from "storage"; // import { SS } from "storage"; */
+export default { SS, LS, CK };

+ 82 - 0
src/utils/util.js

@@ -0,0 +1,82 @@
+/**
+ * @export 获取随机数
+ */
+export function getRandomNumber (count = 1, max = 999, min = 1) {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      const arr = [];
+      for (let i = 0; i < count; i++) {
+        let num = (
+          Math.floor(Math.random() * (max - min + 1)) + min
+        ).toString();
+        num = (Array(max.toString().length).join("0") + num).slice(
+          -max.toString().length
+        );
+        arr.push(num);
+      }
+      resolve(arr);
+    }, 750);
+  });
+}
+
+/**
+ * @export 表单关联有数据延迟:数字金额大写转换(可以处理整数,小数,负数)
+ */
+export function rmbFormat (n) {
+  var fraction = ["角", "分"];
+  var digit = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
+  var unit = [
+    ["元", "万", "亿"],
+    ["", "拾", "佰", "仟"],
+  ];
+  var head = n < 0 ? "欠" : "";
+  n = Math.abs(n);
+  var s = "";
+  for (var i = 0; i < fraction.length; i++) {
+    s += (
+      digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]
+    ).replace(/零./, "");
+  }
+  s = s || "整";
+  n = Math.floor(n);
+  for (var i = 0; i < unit[0].length && n > 0; i++) {
+    var p = "";
+    for (var j = 0; j < unit[1].length && n > 0; j++) {
+      p = digit[n % 10] + unit[1][j] + p;
+      n = Math.floor(n / 10);
+    }
+    s = p.replace(/(零.)*零$/, "").replace(/^$/, "零") + unit[0][i] + s;
+  }
+  return (
+    head +
+    s
+      .replace(/(零.)*零元/, "元")
+      .replace(/(零.)+/g, "零")
+      .replace(/^整$/, "零元整")
+  );
+}
+
+/**
+ * @export 日期格式化
+ */
+function formatDate (time = new Date().getTime(), format = 'YYYY-MM-DD HH:mm:ss') {
+  var date = new Date(time);
+
+  var year = date.getFullYear(),
+    month = date.getMonth() + 1,//月份是从0开始的
+    day = date.getDate(),
+    hour = date.getHours(),
+    min = date.getMinutes(),
+    sec = date.getSeconds();
+  var preArr = Array.apply(null, Array(10)).map(function (elem, index) {
+    return '0' + index;
+  });
+  var newTime = format.replace(/YYYY/g, year)
+    .replace(/MM/g, preArr[month] || month)
+    .replace(/DD/g, preArr[day] || day)
+    .replace(/HH/g, preArr[hour] || hour)
+    .replace(/mm/g, preArr[min] || min)
+    .replace(/ss/g, preArr[sec] || sec);
+
+  return newTime;
+}

+ 87 - 0
src/vendor/calc.js

@@ -0,0 +1,87 @@
+// ------------------- 工具库  ------------------- //
+
+import calc from "number-precision";
+export function calcLib() {
+  return calc;
+}
+
+// ------------------- 自定义  ------------------- //
+
+//浮点数加法运算
+export function plus(arg1, arg2) {
+  let r1, r2, m;
+  try {
+    r1 = arg1.toString().split(".")[1].length;
+  } catch (e) {
+    r1 = 0;
+  }
+  try {
+    r2 = arg2.toString().split(".")[1].length;
+  } catch (e) {
+    r2 = 0;
+  }
+  m = Math.pow(10, Math.max(r1, r2));
+  return (arg1 * m + arg2 * m) / m;
+}
+
+//浮点数减法运算
+export function minus(arg1, arg2) {
+  let r1, r2, m, n;
+  try {
+    r1 = arg1.toString().split(".")[1].length;
+  } catch (e) {
+    r1 = 0;
+  }
+  try {
+    r2 = arg2.toString().split(".")[1].length;
+  } catch (e) {
+    r2 = 0;
+  }
+  m = Math.pow(10, Math.max(r1, r2));
+  //动态控制精度长度
+  n = r1 == r2 ? r1 : r2;
+  return ((arg1 * m - arg2 * m) / m).toFixed(n);
+}
+
+//浮点数乘法运算
+export function multiply(arg1, arg2) {
+  let m = 0,
+    s1 = arg1.toString(),
+    s2 = arg2.toString();
+  try {
+    m += s1.split(".")[1].length;
+  } catch (e) {
+    throw new Error(e);
+  }
+  try {
+    m += s2.split(".")[1].length;
+  } catch (e) {
+    throw new Error(e);
+  }
+  return (
+    (Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) /
+    Math.pow(10, m)
+  );
+}
+
+//浮点数除法运算
+export function divide(arg1, arg2) {
+  let t1 = 0,
+    t2 = 0,
+    r1,
+    r2;
+  try {
+    t1 = arg1.toString().split(".")[1].length;
+  } catch (e) {
+    throw new Error(e);
+  }
+  try {
+    t2 = arg2.toString().split(".")[1].length;
+  } catch (e) {
+    throw new Error(e);
+  }
+  // 严格模式下不能使用with关键字
+  r1 = Number(arg1.toString().replace(".", ""));
+  r2 = Number(arg2.toString().replace(".", ""));
+  return (r1 / r2) * Math.pow(10, t2 - t1);
+}

+ 31 - 0
src/vendor/capture.js

@@ -0,0 +1,31 @@
+import Pinyin from "./lib/pinyin";
+
+/** @export 获取中文首字母 */
+export function getCaptureUpper(txt) {
+  return Pinyin.GetJP(txt);
+}
+
+/** @export 宜搭生成明细的Id:部门首字母+年月日+递增编号 */
+export function getDetailCompId(depart, details, compId, space = "") {
+  if (!depart || !details) return;
+  const date = new Date();
+  const month = date.getMonth() + 1; // 日期格式月是从0开始的
+  const unique =
+    getCaptureUpper(depart) +
+    space +
+    date.getFullYear() +
+    (month < 10 ? "0" + month : month);
+  return details.map((item, index) => {
+    item[compId] = {
+      fieldData: {
+        value: unique + (Array(3).join("0") + (index + 1)).slice(-3),
+      },
+    };
+    return item;
+  });
+}
+
+/** @export 宜搭生成明细的Id:部门首字母+GZ+年月(健高固定资产) */
+export function getDetailCompIdWithGZ(depart, details, compId) {
+  return getDetailCompId(depart, details, compId, "GZ");
+}

+ 67 - 0
src/vendor/date.js

@@ -0,0 +1,67 @@
+import { format, parse } from "fecha";
+
+export function dateFmt () {
+  return format;
+}
+
+export function dateParse () {
+  return parse;
+}
+
+/** 计算时间差: day */
+export function calcCeilDay (timestampStart, timestampEnd) {
+  let start = new Date(timestampStart);
+  let end = new Date(timestampEnd);
+  start = format(start, "YYYY-MM-DD");
+  end = format(end, "YYYY-MM-DD");
+  start = parse(`${start} 00:00:00`, "YY-MM-DD HH:mm:ss");
+  end = parse(`${end} 23:59:59`, "YY-MM-DD HH:mm:ss");
+  const day = (end.getTime() - start.getTime()) / 1000 / 60 / 60 / 24;
+  return Math.ceil(day);
+}
+
+/** @exports 获取指定年月的最后一天 */
+export function getLastDayFromYM (year, month) {
+  if (!year) year = new Date().getFullYear();
+  if (!month) month = new Date().getMonth() + 1;
+  // 使用new Date(year,month,0)的方式,可以获取该月的最后一天
+  return new Date(year, month, 0).getDate();
+}
+
+/** @exports 获取自指定日期或者今天是周几 */
+export function getWeekFromDate (date = new Date(), fmt = "天一二三四五六") {
+  // 使用new Date(year,month,0)的方式,可以获取该月的最后一天
+  return fmt.charAt(date.getDay());
+}
+
+/** @exports 获取当前日期本周的周日-周六,返回-Date */
+export function getCurrentWeekFromDate (date = new Date()) {
+  let start = new Date(date.getTime() - date.getDay() * 86400000);
+  let end = new Date(date.getTime() + (6 - date.getDay()) * 86400000);
+  start = format(start, "YYYY-MM-DD");
+  start = parse(`${start} 00:00:00`, "YY-MM-DD HH:mm:ss");
+  end = format(end, "YYYY-MM-DD");
+  end = parse(`${end} 23:59:59`, "YY-MM-DD HH:mm:ss");
+  return [start, end];
+}
+
+export default {
+  dateFmt, dateParse
+}
+
+
+
+/**
+ *  今天最早于最晚
+ const sToday = mjs.date.dateParse()(mjs.date.dateFmt()(new Date(), "YYYY-MM-DD") + " 00:00:00", "YYYY-MM-DD HH:mm:ss").getTime();
+    const eToday = mjs.date.dateParse()(mjs.date.dateFmt()(new Date(), "YYYY-MM-DD") + " 23:59:59", "YYYY-MM-DD HH:mm:ss").getTime(); */
+
+
+/**
+ *  延迟赋值
+ * const sToday = mjs.date.dateParse()(mjs.date.dateFmt()(new Date(), "YYYY-MM-DD") + " 00:00:00", "YYYY-MM-DD HH:mm:ss").getTime();
+const eToday = mjs.date.dateParse()(mjs.date.dateFmt()(new Date(), "YYYY-MM-DD") + " 23:59:59", "YYYY-MM-DD HH:mm:ss").getTime();
+setTimeout(() => {
+  this.$('dateField_likcqjcr').setValue(sToday)
+  this.$('dateField_likcqjcs').setValue(eToday)
+}, 400) */

+ 38 - 0
src/vendor/diff.js

@@ -0,0 +1,38 @@
+import diff from "diff";
+
+export function diffTextAreaToRichText(
+  that,
+  { compId_pre, compId_cur, compId_rich, editMode = true }
+) {
+  if (editMode) {
+    that.$(compId_cur).setBehavior("HIDDEN");
+    that.$(compId_rich).setBehavior("NORMAL");
+  }
+  const preText = that.$(compId_pre).getValue().split("\n");
+  const curText = that.$(compId_cur).getValue().split("\n");
+  const arrDiff = diff.diffArrays(preText, curText, {
+    newlineIsToken: true,
+    ignoreWhitespace: true,
+  });
+  const arr = arrDiff.map((item) => {
+    const fmtStyle = () => {
+      const arrT = item.value.map((sub) => {
+        if (item.added) {
+          return `<div style="color: #c10015;">${sub}</div>`;
+        }
+        if (item.removed) {
+          return `<div style="text-decoration: line-through;">${sub}</div>`;
+        }
+        return `<div style="color: #333333;">${sub}</div>`;
+      });
+      return arrT.join("");
+    };
+    return fmtStyle();
+  });
+  const content = arr.join("");
+  const html = `
+  <div style="font-size: 12px; color: #66666; line-height: 28px;">对比差异</div>
+  <div style="text-align: justify; font-size: 14px; line-height: 28px; font-weight: 400; ">${content}</div>`;
+  if (compId_rich) that.$(compId_rich).setValue(html);
+  return arrDiff;
+}

+ 157 - 0
src/vendor/dingApi.js

@@ -0,0 +1,157 @@
+import * as ddAPi from "dingtalk-jsapi";
+// NOTE: 未默认解构 default - 不能直接解构, 不识别. 不能有效减少包体积
+const dd = ddAPi.default;
+
+import conf from "../config/conf";
+
+const ding = {}; // 钉钉jsapi对象
+let $loading = false; // 避免多个请求闪屏
+
+// loading 显示
+ding.showLoading = function (message = "拼命加载中...") {
+  if ($loading) return;
+  $loading = true;
+  dd.device.notification.showPreloader({
+    showIcon: true,
+    text: message,
+  });
+};
+
+// loading 关闭
+ding.hideLoading = function () {
+  if (!$loading) return;
+  dd.device.notification.hidePreloader();
+  $loading = false;
+};
+
+// toast 提示: info 详见 https://ding-doc.dingtalk.com/doc#/dev/oo98ye/6pwsuy
+ding.showMessageToast = function (info = {}) {
+  if (!info.message) return;
+  dd.device.notification.toast(info);
+};
+
+// 获取定位
+ding.getLocation = function () {
+  return new Promise((resolve, reject) => {
+    dd.device.geolocation.get({
+      targetAccuracy: 200,
+      coordinate: 1,
+      withReGeocode: true,
+      useCache: true,
+      onSuccess (result) {
+        resolve(result);
+      },
+      onFail (err) {
+        reject(err);
+      },
+    });
+  });
+};
+
+// 根据chatId跳转到对应会话
+ding.toConversation = function (chatId) {
+  return new Promise((resolve, reject) => {
+    dd.biz.chat.toConversation({
+      corpId: conf.corpId,
+      chatId,
+      onSuccess: () => resolve(),
+      onFail: (err) => reject(err),
+    });
+  });
+};
+
+// 图片预览: 支持缩放和左右切换
+ding.previewImageScale = function (urls, index = 0) {
+  if (!urls) return;
+  dd.biz.previewImage({
+    urls,
+    current: urls[index],
+  });
+};
+
+// 获取UUID
+ding.getUUID = function () {
+  return new Promise((resolve) => {
+    dd.device.base.getUUID({
+      onSuccess (data) {
+        resolve(data);
+      },
+    });
+  });
+};
+
+/** @exports 钉钉新开页面方法 */
+ding.openNavigation = function (url, isReplace) {
+  if (!url) return;
+  // 区分环境, 兼容分享
+  if (dd.env.platform === "notInDingTalk") {
+    window.open(url, isReplace ? "_self" : "");
+  } else {
+    if (isReplace) {
+      dd.biz.navigation.replace({ url });
+    } else {
+      dd.biz.util.openLink({ url });
+    }
+  }
+};
+
+import browser from "../utils/browser";
+
+/** @exports 退出页面方法:兼容浏览器 */
+ding.closeNavigationForDomEvent = function (that) {
+  // 延迟: 体检 + 弹出离开确认框
+  if (dd.env.platform === "notInDingTalk") {
+    setTimeout(() => browser.closeCurrentTab(), 1000);
+    return false;
+  }
+  if (dd.env.platform === "ios" || dd.env.platform === "android") {
+    setTimeout(() => mjs.com.showMessage(that, "提交成功"), 350);
+    setTimeout(() => dd.biz.navigation.close({}), 750);
+    return true;
+  }
+  if (dd.env.platform === "pc") {
+    // 钉钉BUG: 只在SlidePanel和Modal里起作用
+    setTimeout(() => biz.navigation.quit({}), 750);
+    return true;
+  }
+};
+
+/** @exports 设置右侧导航栏: [{ id: "1", text: "附 件", url: "http://alading-20210318.oss-cn-shanghai.aliyuncs.com/assets/nav-link.png",},] */
+ding.settingNavigationMenu = function (menus = [], isClear, callback) {
+  if (isClear) {
+    menus = [
+      {
+        id: "999",
+        text: " ",
+      },
+    ];
+  }
+  dd.biz.navigation.setMenu({
+    items: menus,
+    onSuccess (result) {
+      callback && callback(result);
+    },
+  });
+};
+
+// 注册钉钉js接口鉴权
+ding.registerConfig = function (agentId, timeStamp, signature) {
+  dd.config({
+    agentId,
+    corpId,
+    timeStamp,
+    nonceStr,
+    signature,
+    type: 0,
+    jsApiList: ["biz.chat.toConversation"], // 注意:不要带dd。
+  });
+};
+
+// 注册配置异常抛出
+dd.error(function (error) {
+  window.alert(JSON.stringify(error));
+});
+
+ding.dd = dd;
+
+export default ding;

+ 179 - 0
src/vendor/lib/Blob.js

@@ -0,0 +1,179 @@
+/* eslint-disable */
+/* Blob.js
+ * A Blob implementation.
+ * 2014-05-27
+ *
+ * By Eli Grey, http://eligrey.com
+ * By Devin Samarin, https://github.com/eboyjr
+ * License: X11/MIT
+ *   See LICENSE.md
+ */
+
+/*global self, unescape */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+ plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
+
+(function (view) {
+    "use strict";
+
+    view.URL = view.URL || view.webkitURL;
+
+    if (view.Blob && view.URL) {
+        try {
+            new Blob;
+            return;
+        } catch (e) {}
+    }
+
+    // Internally we use a BlobBuilder implementation to base Blob off of
+    // in order to support older browsers that only have BlobBuilder
+    var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
+            var
+                get_class = function(object) {
+                    return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
+                }
+                , FakeBlobBuilder = function BlobBuilder() {
+                    this.data = [];
+                }
+                , FakeBlob = function Blob(data, type, encoding) {
+                    this.data = data;
+                    this.size = data.length;
+                    this.type = type;
+                    this.encoding = encoding;
+                }
+                , FBB_proto = FakeBlobBuilder.prototype
+                , FB_proto = FakeBlob.prototype
+                , FileReaderSync = view.FileReaderSync
+                , FileException = function(type) {
+                    this.code = this[this.name = type];
+                }
+                , file_ex_codes = (
+                    "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+                    + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
+                ).split(" ")
+                , file_ex_code = file_ex_codes.length
+                , real_URL = view.URL || view.webkitURL || view
+                , real_create_object_URL = real_URL.createObjectURL
+                , real_revoke_object_URL = real_URL.revokeObjectURL
+                , URL = real_URL
+                , btoa = view.btoa
+                , atob = view.atob
+
+                , ArrayBuffer = view.ArrayBuffer
+                , Uint8Array = view.Uint8Array
+                ;
+            FakeBlob.fake = FB_proto.fake = true;
+            while (file_ex_code--) {
+                FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
+            }
+            if (!real_URL.createObjectURL) {
+                URL = view.URL = {};
+            }
+            URL.createObjectURL = function(blob) {
+                var
+                    type = blob.type
+                    , data_URI_header
+                    ;
+                if (type === null) {
+                    type = "application/octet-stream";
+                }
+                if (blob instanceof FakeBlob) {
+                    data_URI_header = "data:" + type;
+                    if (blob.encoding === "base64") {
+                        return data_URI_header + ";base64," + blob.data;
+                    } else if (blob.encoding === "URI") {
+                        return data_URI_header + "," + decodeURIComponent(blob.data);
+                    } if (btoa) {
+                        return data_URI_header + ";base64," + btoa(blob.data);
+                    } else {
+                        return data_URI_header + "," + encodeURIComponent(blob.data);
+                    }
+                } else if (real_create_object_URL) {
+                    return real_create_object_URL.call(real_URL, blob);
+                }
+            };
+            URL.revokeObjectURL = function(object_URL) {
+                if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
+                    real_revoke_object_URL.call(real_URL, object_URL);
+                }
+            };
+            FBB_proto.append = function(data/*, endings*/) {
+                var bb = this.data;
+                // decode data to a binary string
+                if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
+                    var
+                        str = ""
+                        , buf = new Uint8Array(data)
+                        , i = 0
+                        , buf_len = buf.length
+                        ;
+                    for (; i < buf_len; i++) {
+                        str += String.fromCharCode(buf[i]);
+                    }
+                    bb.push(str);
+                } else if (get_class(data) === "Blob" || get_class(data) === "File") {
+                    if (FileReaderSync) {
+                        var fr = new FileReaderSync;
+                        bb.push(fr.readAsBinaryString(data));
+                    } else {
+                        // async FileReader won't work as BlobBuilder is sync
+                        throw new FileException("NOT_READABLE_ERR");
+                    }
+                } else if (data instanceof FakeBlob) {
+                    if (data.encoding === "base64" && atob) {
+                        bb.push(atob(data.data));
+                    } else if (data.encoding === "URI") {
+                        bb.push(decodeURIComponent(data.data));
+                    } else if (data.encoding === "raw") {
+                        bb.push(data.data);
+                    }
+                } else {
+                    if (typeof data !== "string") {
+                        data += ""; // convert unsupported types to strings
+                    }
+                    // decode UTF-16 to binary string
+                    bb.push(unescape(encodeURIComponent(data)));
+                }
+            };
+            FBB_proto.getBlob = function(type) {
+                if (!arguments.length) {
+                    type = null;
+                }
+                return new FakeBlob(this.data.join(""), type, "raw");
+            };
+            FBB_proto.toString = function() {
+                return "[object BlobBuilder]";
+            };
+            FB_proto.slice = function(start, end, type) {
+                var args = arguments.length;
+                if (args < 3) {
+                    type = null;
+                }
+                return new FakeBlob(
+                    this.data.slice(start, args > 1 ? end : this.data.length)
+                    , type
+                    , this.encoding
+                );
+            };
+            FB_proto.toString = function() {
+                return "[object Blob]";
+            };
+            FB_proto.close = function() {
+                this.size = this.data.length = 0;
+            };
+            return FakeBlobBuilder;
+        }(view));
+
+    view.Blob = function Blob(blobParts, options) {
+        var type = options ? (options.type || "") : "";
+        var builder = new BlobBuilder();
+        if (blobParts) {
+            for (var i = 0, len = blobParts.length; i < len; i++) {
+                builder.append(blobParts[i]);
+            }
+        }
+        return builder.getBlob(type);
+    };
+}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));

+ 153 - 0
src/vendor/lib/Export2Excel.js

@@ -0,0 +1,153 @@
+/* eslint-disable */
+import saveAs from "file-saver";
+import * as XLSXDU from "xlsx";
+var XLSX = XLSXDU.default;
+
+function generateArray(table) {
+  var out = [];
+  var rows = table.querySelectorAll("tr");
+  var ranges = [];
+  for (var R = 0; R < rows.length; ++R) {
+    var outRow = [];
+    var row = rows[R];
+    var columns = row.querySelectorAll("td");
+    for (var C = 0; C < columns.length; ++C) {
+      var cell = columns[C];
+      var colspan = cell.getAttribute("colspan");
+      var rowspan = cell.getAttribute("rowspan");
+      var cellValue = cell.innerText;
+      if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
+
+      //Skip ranges
+      ranges.forEach(function (range) {
+        if (
+          R >= range.s.r &&
+          R <= range.e.r &&
+          outRow.length >= range.s.c &&
+          outRow.length <= range.e.c
+        ) {
+          for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
+        }
+      });
+
+      //Handle Row Span
+      if (rowspan || colspan) {
+        rowspan = rowspan || 1;
+        colspan = colspan || 1;
+        ranges.push({
+          s: { r: R, c: outRow.length },
+          e: { r: R + rowspan - 1, c: outRow.length + colspan - 1 },
+        });
+      }
+      //Handle Value
+      outRow.push(cellValue !== "" ? cellValue : null);
+
+      //Handle Colspan
+      if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
+    }
+    out.push(outRow);
+  }
+  return [out, ranges];
+}
+
+function datenum(v, date1904) {
+  if (date1904) v += 1462;
+  var epoch = Date.parse(v);
+  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+
+function sheet_from_array_of_arrays(data, opts) {
+  var ws = {};
+  var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
+
+  for (var R = 0; R != data.length; ++R) {
+    for (var C = 0; C != data[R].length; ++C) {
+      if (range.s.r > R) range.s.r = R;
+      if (range.s.c > C) range.s.c = C;
+      if (range.e.r < R) range.e.r = R;
+      if (range.e.c < C) range.e.c = C;
+      var cell = { v: data[R][C] };
+      if (cell.v == null) continue;
+      var cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
+      if (typeof cell.v === "number") cell.t = "n";
+      else if (typeof cell.v === "boolean") cell.t = "b";
+      else if (cell.v instanceof Date) {
+        cell.t = "n";
+        cell.z = XLSX.SSF._table[14];
+        cell.v = datenum(cell.v);
+      } else cell.t = "s";
+
+      ws[cell_ref] = cell;
+    }
+  }
+  if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
+  return ws;
+}
+
+function Workbook() {
+  if (!(this instanceof Workbook)) return new Workbook();
+  this.SheetNames = [];
+  this.Sheets = {};
+}
+
+function s2ab(s) {
+  var buf = new ArrayBuffer(s.length);
+  var view = new Uint8Array(buf);
+  for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
+  return buf;
+}
+
+export function export_table_to_excel(id) {
+  var theTable = document.getElementById(id);
+  var oo = generateArray(theTable);
+  var ranges = oo[1];
+  /* original data */
+  var data = oo[0];
+  var ws_name = "Sheet1";
+  var wb = new Workbook(),
+    ws = sheet_from_array_of_arrays(data);
+
+  /* add ranges to worksheet */
+  // ws['!cols'] = ['apple', 'banan'];
+  ws["!merges"] = ranges;
+
+  /* add worksheet to workbook */
+  wb.SheetNames.push(ws_name);
+  wb.Sheets[ws_name] = ws;
+
+  var wbout = XLSX.write(wb, {
+    bookType: "xlsx",
+    bookSST: false,
+    type: "binary",
+  });
+
+  saveAs(
+    new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
+    "test.xlsx"
+  );
+}
+
+export function export_json_to_excel(th, jsonData, defaultTitle) {
+  /* original data */
+
+  var data = jsonData;
+  data.unshift(th);
+  var ws_name = "Sheet1";
+  var wb = new Workbook(),
+    ws = sheet_from_array_of_arrays(data);
+
+  /* add worksheet to workbook */
+  wb.SheetNames.push(ws_name);
+  wb.Sheets[ws_name] = ws;
+
+  var wbout = XLSX.write(wb, {
+    bookType: "xlsx",
+    bookSST: false,
+    type: "binary",
+  });
+  var title = defaultTitle || "列表";
+  saveAs(
+    new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
+    title + ".xlsx"
+  );
+}

Plik diff jest za duży
+ 7308 - 0
src/vendor/lib/pinyin.js


+ 212 - 0
src/vendor/xlsx.js

@@ -0,0 +1,212 @@
+import * as XLSXAPI from "xlsx";
+import { export_json_to_excel } from "./lib/Export2Excel";
+const XLSX = XLSXAPI.default;
+import { format, parse } from "fecha";
+
+///////////////////////////////////////////////// excel 导入兼容 2.0 + 3.0, 3.0 有标准导入功能 /////////////////////////////////////////////////
+
+// 读取excel文件
+function _readWorkbookFromLocalFile (file, sheetName) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    try {
+      reader.onload = function (e) {
+        const data = e.target.result;
+        const workbook = XLSX.read(data, { type: "binary" });
+        resolve(XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]));
+      };
+    } catch (e) {
+      reject(e.message || "文件类型不正确");
+    }
+    reader.readAsBinaryString(file);
+  });
+}
+
+/** 宜搭明细数据处理 */
+export async function _importExcelForAliworkDetail (file, sheetName, version) {
+  const list = await _readWorkbookFromLocalFile(file, sheetName);
+  const compId = list.shift(); // 过滤到第二行标题: 首行默认为成为数据的key
+  const keys = Object.keys(compId); // 获取模板首行compId
+  // 格式化数据为宜搭明细组件: fieldData: { value: "" }
+  return list.map((item) => {
+    keys.forEach((key) => {
+      let value = item[key];
+      // 时间处理: 传入内容为文本, 格式体现在表头内
+      if (key.includes("dateField_")) {
+        let fmt = "YYYY-MM-DD";
+        const arr = compId[key].split("__");
+        if (arr.length == 2) fmt = arr[1];
+        value = parse(value, fmt).getTime();
+      }
+      // 人员处理: 格式保留和宜搭导入一致
+      if (value && key.includes("employeeField_")) {
+        value = value.split(",");
+        value = value.reduce((acc, cur) => {
+          const emp = cur.split("[");
+          acc.push({ name: emp[0], value: emp[1].replace("]", "") });
+          return acc;
+        }, []);
+        console.log("value", value);
+      }
+      if (version == "v2") {
+        value = {
+          fieldData: {
+            value: value,
+          },
+        };
+      }
+      item[key] = value;
+    });
+    return item;
+  });
+}
+
+/** @exports 宜搭明细组件和导入  */
+export function createElementUpload (
+  { domId = "fileReader", sheet = "Sheet1", version = "v3" } = {},
+  callback,
+  changeCallback
+) {
+  const upload = document.createElement("input");
+  upload.type = "file";
+  upload.name = "file";
+  upload.accept = ".xlsx, .xls";
+  upload.id = domId;
+  upload.style = "display: none";
+  upload.onchange = async function () {
+    changeCallback && changeCallback();
+    // 判断文件是否为空
+    const file = document.getElementById("fileReader").files[0];
+    if (file) {
+      const details = await _importExcelForAliworkDetail(file, sheet, version);
+      if (details) callback(details);
+      else callback("数据格式异常");
+    } else {
+      callback("上传文件格式异常");
+    }
+  };
+  document.body.append(upload);
+}
+
+///////////////////////////////////////////////// excel 2.0 导出功能 /////////////////////////////////////////////////
+
+function formatJson (filterVal, jsonData) {
+  return jsonData.map((v) => filterVal.map((j) => v[j]));
+}
+
+export function export2Excel (tHeader, filterVal, dataList, fileName) {
+  const data = formatJson(filterVal, dataList);
+  export_json_to_excel(tHeader, data, fileName);
+}
+
+///////////////////////////////////////////////// excel 3.0 导出功能, 标准功能在提交页面不能导入且目前还未有钩子提供, 如清除 /////////////////////////////////////////////////
+
+//  cols承载对象,表头名称+取值字段名称, 默认取值明细所有ID
+export function detailsExportExcel (
+  that,
+  { compId_detail, cols, fileName, sheetName, bookType } = {},
+  { isClear = true } = {}
+) {
+  const comp = that.$(compId_detail);
+  if (!cols || !cols.length) {
+    cols = comp.baseRef.columns.reduce((acc, cur) => {
+      acc[cur.name] = cur.label;
+      return acc;
+    }, {});
+  }
+  // TODO: 人员搜索框. 时间, 多选未兼容
+  const list = comp.getValue();
+  exportExcel({ cols, list, fileName, sheetName, bookType });
+  // 清空明细内数据
+  if (isClear) comp.setValue([]);
+}
+
+export function tabledataExportExcel (
+  that,
+  { compId_detail, cols, fileName, sheetName, bookType } = {}
+) {
+  const comp = that.$(compId_detail);
+  let datakey;
+  if (!cols || !cols.length) {
+    cols = comp.get("columns").reduce((acc, cur) => {
+      datakey = cur.dataKey;
+      acc[datakey.substring(9, datakey.length)] = cur.title;
+      return acc;
+    }, {});
+  }
+  // TODO: 人员搜索框. 时间, 多选未兼容
+  const data = comp.get("data").data;
+  let list = new Array();
+  data.forEach((item) => {
+    let data_keys = Object.keys(item.formData);
+    data_keys.forEach((key) => {
+      if (key.includes("dateField_")) {
+        item.formData[key] = format(new Date(item.formData[key]), "YYYY-MM-DD");
+      }
+    });
+    list.push(item.formData);
+  });
+  exportExcel({ cols, list, fileName, sheetName, bookType });
+}
+
+import fs from "file-saver";
+
+// 写入数据 blob 格式化
+const _s2ab = (s) => {
+  let buf;
+  if (typeof ArrayBuffer !== "undefined") {
+    buf = new ArrayBuffer(s.length);
+    var view = new Uint8Array(buf);
+    for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
+    return buf;
+  } else {
+    buf = new Array(s.length);
+    for (let i = 0; i != s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff;
+    return buf;
+  }
+};
+
+// 前端导出 xlsx { cols: { prop: header }, list } - cols承载对象,表头名称+取值字段名称
+export function exportExcel ({
+  cols,
+  list,
+  fileName = "导出文件",
+  sheetName,
+  bookType = "xlsx",
+} = {}) {
+  // 过滤无效字段, 且导出格式为 header中文名称: 数据源值
+  const props = Object.keys(cols);
+  const data = list.map((item) => {
+    return props.reduce((row, prop) => {
+      row[cols[prop]] = item[prop];
+      return row;
+    }, {});
+  });
+  console.log("data, ", data)
+  // 名称处理
+  fileName = fileName;
+  sheetName = sheetName || fileName;
+  // 工作簿对象包含一SheetNames数组,以及一个表对象映射表名称到表对象。XLSX.utils.book_new实用函数创建一个新的工作簿对象。
+  const wb = XLSX.utils.book_new();
+  // 将JS对象数组转换为工作表。
+  const ws = XLSX.utils.json_to_sheet(data, { header: Object.values(cols) });
+  wb.SheetNames.push(sheetName);
+  wb.Sheets[sheetName] = ws;
+  const defaultCellStyle = {
+    font: { name: "Verdana", sz: 13, color: "FF00FF88" },
+    fill: { fgColor: { rgb: "FFFFAA00" } },
+  };
+  //设置表格的样式
+  const wopts = {
+    bookType: bookType,
+    bookSST: false,
+    type: "binary",
+    cellStyles: true,
+    defaultCellStyle: defaultCellStyle,
+    showGridLines: false,
+  };
+  // 写入的样式
+  const wbout = XLSX.write(wb, wopts);
+  const blob = new Blob([_s2ab(wbout)], { type: "application/octet-stream" });
+  fs.saveAs(blob, fileName + "." + bookType);
+}

Plik diff jest za duży
+ 6025 - 0
yarn.lock