Parcourir la source

feat(mjs): sync shared modules and yida context rules

malk il y a 3 semaines
Parent
commit
9a409d9882

+ 7 - 7
doc/development.md

@@ -9,7 +9,7 @@
 ### 基础编程规范(宜搭页面与 MJS)
 
 - 页面组件 ID 统一集中配置;每个 ID 必须附中文名称注释,禁止在业务方法中散落硬编码。
-- 宜搭调用 MJS 且 MJS 需要页面能力时,必须显式传入 `$this: this`;MJS 内只使用传入的 `options.$this` 访问组件、按钮、`utils`、`router` 和 toast。MJS 方法自身的 `this` 指向 MJS 模块对象,`mjs.$this` 也不得作为当前宜搭页面实例使用
+- `mjs.init(this, config)` 后,`mjs.$this` 是当前宜搭页面实例,可访问组件、按钮、`utils`、`router` 和 toast;调用方也可通过 `options.$this` 显式覆盖。MJS 方法自身的 `this` 指向 MJS 模块对象,禁止使用 `this.$(...)`。
 - 组件取值、赋值前必须判断组件是否存在;可选组件缺失时走兼容分支,不得直接调用 `getValue` 或 `setValue`。
 - 新增业务通过独立方法或显式参数扩展;与原方法共用时增加条件判断,默认不改变原有调用行为。
 - 异步流程按“加载 → 校验 → 请求 → 赋值 → 异常提示 → 收尾”组织,loading 必须在 `finally` 中关闭。
@@ -19,7 +19,7 @@
 #### 宜搭页面上下文传递
 
 ```js
-// 宜搭页面:把当前页面实例显式交给 MJS
+// 宜搭页面:显式传入是可选写法,可用于固定当前页面实例
 export async function onClickConfirm() {
   return mjs.corp.contract.confirm({
     $this: this,
@@ -30,11 +30,11 @@ export async function onClickConfirm() {
   });
 }
 
-// MJS:只能使用传入的 $this 访问宜搭页面能力
+// MJS:显式上下文优先,否则使用 mjs.init 保存的宜搭页面实例
 async function confirm(options = {}) {
-  const $this = options.$this;
+  const $this = options.$this || mjs.$this;
   if (!$this || typeof $this.$ !== 'function') {
-    throw new Error('宜搭页面 $this 未传入');
+    throw new Error('宜搭页面上下文不存在');
   }
   const contractNo = $this.$(options.componentIds.contractNo);
   if (!contractNo || typeof contractNo.setValue !== 'function') {
@@ -45,8 +45,8 @@ async function confirm(options = {}) {
 ```
 
 - 禁止在 MJS 方法中用 `this.$(...)`:此处 `this` 是 MJS 模块对象。
-- 禁止依赖 `mjs.$this.$(...)`:设计器预览、弹层、iframe 或多页面并存时可能不是当前页面实例
-- 缺少 `$this` 或组件不存在时立即抛出明确异常,禁止继续调用 `null.getValue()` / `null.setValue()`。
+- `mjs.$this.$(...)` 是正确的宜搭页面访问方式;需要显式固定调用页面时,可传入 `options.$this`
+- 页面上下文或组件不存在时立即抛出明确异常,禁止继续调用 `null.getValue()` / `null.setValue()`。
 
 ### 前言
 

+ 137 - 0
doc/mkl-contract-example.js

@@ -0,0 +1,137 @@
+/**
+ * 曼卡龙合同页面使用示例。
+ *
+ * 使用方式:
+ * 1. 页面数据源引用打包后的 mjs.min.js。
+ * 2. 将本示例中的方法复制到页面 JS。
+ * 3. 将 FIELD_IDS 替换为当前页面实际组件 ID。
+ * 4. 将 generateContractFile 替换为当前页面已有的合同生成方法。
+ * 5. 发布生产时把 _mjsLoad("test") 改为 _mjsLoad("production")。
+ */
+
+const FIELD_IDS = {
+  // 合同唯一标识(业务唯一号)
+  uniqueId: "textField_mte3gh61",
+  // 合同号
+  contractNo: "textField_mrishceq",
+  // 合同主从类型
+  contractMode: "radioField_mryiusal",
+  // 合同类型
+  contractType: "textField_mspzgras",
+  // 主合同号(子合同使用)
+  masterNo: "textField_mrylbq28",
+  // 合同名称
+  contractName: "textField_mryko9bh",
+  // 合同附件
+  attachment: "attachmentField_msv7rz7m",
+};
+
+/**
+ * 加载 MJS 公共库。
+ * @param {"test"|"production"} environment 运行环境
+ * @returns {void}
+ */
+export function _mjsLoad (environment = "test") {
+  const script = document.createElement("script");
+  script.type = "text/javascript";
+  script.src = "https://mc.cloudpure.cn/mjs/mkl/mjs.min.js";
+  script.onload = () => this._mjsInit(environment);
+  document.head.appendChild(script);
+}
+
+/**
+ * 初始化 MJS 和曼卡龙客户模块。
+ * @param {"test"|"production"} environment 运行环境
+ * @returns {Promise<void>}
+ */
+export async function _mjsInit (environment = "test") {
+  await mjs.init(this, { vconsole: false });
+  mjs.corp.mkl.init({
+    environment,
+    testApi: "https://mc.cloudpure.cn/frphz/mankalong",
+    productionApi: "https://znht.mclon.com/api/mankalong",
+  });
+}
+
+/**
+ * 原提交前方法备份。
+ * 保留原方法内容,仅改名,后续如有原校验可继续放在这里。
+ * @param {Object} params 宜搭提交参数
+ * @returns {void}
+ */
+export function beforeSubmitOriginal ({ formDataMap }) {
+  console.log("beforeSubmit", formDataMap);
+}
+
+/**
+ * 合同提交前校验:合同号和合同附件必须同时有值。
+ * @returns {boolean} 是否允许提交
+ */
+export function beforeSubmitContractReady () {
+  return mjs.corp.mkl.beforeSubmitContract({
+    contractNo: FIELD_IDS.contractNo,
+    attachment: FIELD_IDS.attachment,
+  });
+}
+
+/**
+ * 宜搭提交前生命周期入口。
+ * 原校验先执行,新合同生成校验失败时返回 false 阻断提交。
+ * @param {Object} params 宜搭提交参数
+ * @returns {boolean} 是否允许提交
+ */
+export function beforeSubmit (params) {
+  beforeSubmitOriginal.call(this, params);
+  return beforeSubmitContractReady.call(this);
+}
+
+/**
+ * 原合同预览按钮示例。
+ * 原有预览逻辑保持不变。
+ * @returns {Promise<*>} 原预览方法结果
+ */
+export function onClickContractPreview () {
+  return generateSubFormImages.call(this, "preview", null);
+}
+
+/**
+ * 合同确认按钮示例:合同号 → 正式生成 → OSS → 附件回写。
+ * @returns {Promise<Object>} 合同确认结果
+ */
+export async function onClickContractConfirm () {
+  return mjs.corp.mkl.confirmContract({
+    // mjs.$this 是 mjs.init 保存的当前宜搭页面实例。
+    $this: mjs.$this,
+    componentIds: {
+      uniqueId: FIELD_IDS.uniqueId,
+      contractNo: FIELD_IDS.contractNo,
+      mode: FIELD_IDS.contractMode,
+      type: FIELD_IDS.contractType,
+      masterNo: FIELD_IDS.masterNo,
+      name: FIELD_IDS.contractName,
+      attachment: FIELD_IDS.attachment,
+    },
+    subDirectory: "工程类",
+    generateContract: async ({ contractNumber, mode }) => {
+      // 接入当前页面已有的正式合同生成方法。
+      // 该回调必须返回最终文件 URL,或 { downloadUrl: "..." }。
+      return generateContractFile.call(this, {
+        contractNumber,
+        mode,
+      });
+    },
+  });
+}
+
+/**
+ * 页面原合同生成方法适配层示例。
+ * 请替换为当前页面已有接口调用;不要修改原方法本身。
+ * @param {Object} params 合同生成参数
+ * @returns {Promise<string|Object>} 文件地址或包含文件地址的对象
+ */
+async function generateContractFile (params) {
+  if (typeof this.generateExistingContract === "function") {
+    return this.generateExistingContract(params);
+  }
+  throw new Error("请在页面中接入原合同生成方法并返回最终文件地址");
+}

+ 2 - 1
package.json

@@ -63,5 +63,6 @@
     "serve": "^10.0.2",
     "standard-version": "^4.3.0",
     "uglify-es": "^3.3.4"
-  }
+  },
+  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
 }

+ 1 - 1
src/auth/copyright.js

@@ -17,7 +17,7 @@ auth.requestLib = function () {
       };
 
       resolve(resp);
-      // const msg = `mjs load failure. ♨ 访问应用: ${resp.data.appType} ${resp.message} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
+      // const msg = `mjs load failure. ♨ 访问应用: ${resp.data.appType} ${resp.message} ©️ 版权请请联系: http://cloudpure.cn/`;
       // reject(msg);
     }, 750);
   });

+ 48 - 0
src/demo/xinjiyuan-wz.js

@@ -0,0 +1,48 @@
+/**
+ * 新纪元等第分数与等第控制图 [完整代码]
+ */
+const ranks = ["F", 'E', 'D', 'C-', 'C', 'C+', 'B-', 'B', 'B+', 'A-', 'A', 'A+']
+const dataArray1 = ['A+', 'B-', 'B', 'A', 'A-', 'C-', 'C', '断开', 'E', "F"]
+const dataArray2 = ['A-', 'B', 'B+', '0', 'A', 'C-', 'C+', 'D', 'A', "A"]
+const generalSeries = (values) => {
+  return values.map(v => {
+    let i = ranks.indexOf(v)
+    return i < 0 ? null : i
+  })
+}
+option = {
+  tooltip: {
+    trigger: 'axis',
+    valueFormatter: (value) => ranks[value]
+  },
+  legend: {},
+  xAxis: {
+    type: 'category',
+    data: ['12-01', '12-02', '12-03', '12-04', '12-05', '12-05', '12-07', '12-08', '12-09', '12-10']
+  },
+  yAxis: {
+    type: 'value',
+    axisLabel: {
+      margin: 30,
+      fontSize: 16,
+      formatter: function (value, index) {
+        return ranks[value];
+      }
+    },
+    interval: 1,
+    min: 0,
+    max: 11
+  },
+  series: [
+    {
+      type: 'line',
+      name: "学科1",
+      data: generalSeries(dataArray1)
+    },
+    {
+      type: 'line',
+      name: "学科2",
+      data: generalSeries(dataArray2)
+    }
+  ]
+};

+ 48 - 0
src/demo/xinjiyuan.js

@@ -0,0 +1,48 @@
+/**
+ * 新纪元等第分数与等第控制图 [js]
+ */
+const ranks = ["F", 'E', 'D', 'C-', 'C', 'C+', 'B-', 'B', 'B+', 'A-', 'A', 'A+']
+const dataArray1 = ['A+', 'B-', 'B', 'A', 'A-', 'C-', 'C', '断开', 'E', "F"]
+const dataArray2 = ['A-', 'B', 'B+', '0', 'A', 'C-', 'C+', 'D', 'A', "A"]
+const generalSeries = (values) => {
+  return values.map(v => {
+    let i = ranks.indexOf(v)
+    return i < 0 ? null : i
+  })
+}
+option = {
+  tooltip: {
+    trigger: 'axis',
+    valueFormatter: (value) => ranks[value]
+  },
+  legend: {},
+  xAxis: {
+    type: 'category',
+    data: ['12-01', '12-02', '12-03', '12-04', '12-05', '12-05', '12-07', '12-08', '12-09', '12-10']
+  },
+  yAxis: {
+    type: 'value',
+    axisLabel: {
+      margin: 30,
+      fontSize: 16,
+      formatter: function (value, index) {
+        return ranks[value];
+      }
+    },
+    interval: 1,
+    min: 0,
+    max: 11
+  },
+  series: [
+    {
+      type: 'line',
+      name: "学科1",
+      data: generalSeries(dataArray1)
+    },
+    {
+      type: 'line',
+      name: "学科2",
+      data: generalSeries(dataArray2)
+    }
+  ]
+};

+ 2 - 3
src/main.js

@@ -49,11 +49,10 @@ export async function init (_this, config = {}) {
     cp, guyuan, hangshi, rise, mkl
   }
   // 输出日志;
-  const msg = `mjs load success. ♨ 访问应用: ${pageConfig.appType} ${pageConfig.appName} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
-  console.log(msg, mjs, config);
+  // const msg = `mjs load success. ♨ 访问应用: ${pageConfig.appType} ${pageConfig.appName} ©️ 版权请请联系: http://cloudpure.cn/`;
+  // console.log(msg, mjs, config);
 }
 
 
 
 
-

+ 5 - 5
src/sample/mkl.js

@@ -99,10 +99,10 @@ function _setButtonBehavior (page, componentId, behavior) {
 }
 
 function _contractPage (options = {}) {
-  // ppExt: page 及 mjs.$this 仅兼容已发布旧页面;新增宜搭调用必须显式传 options.$this
+  // ppExt: 显式页面上下文优先,默认使用 mjs.init 保存的当前宜搭页面实例
   const page = options.$this || options.page || _page();
   if (!page || typeof page.$ !== "function") {
-    throw new Error("宜搭页面 $this 未传入");
+    throw new Error("宜搭页面上下文不存在");
   }
   return page;
 }
@@ -466,8 +466,8 @@ export default {
    * @returns {Promise<Object>} 合同号及附件
    */
   async confirmContract (options = {}) {
-    // fixme: MJS 方法内 this 指向模块对象;页面能力必须使用宜搭显式传入的 options.$this
-    // ppExt: options.page 仅兼容已发布的旧页面调用,新代码统一传 $this
+    // fixme: MJS 方法内 this 指向模块对象;页面能力通过 options.$this 或 mjs.$this 获取
+    // ppExt: 显式传入的 options.$this 优先,未传时使用 mjs.init 保存的当前宜搭页面实例
     const page = _contractPage(options);
     const ids = options.componentIds || {};
     let closeLoading = null;
@@ -490,7 +490,7 @@ export default {
         throw new Error("必须传入 generateContract 回调");
       }
 
-      // fixme: 设计器预览时 mjs.$this 可能不是当前按钮所在页面,优先使用页面直接读取后传入的合同类型
+      // fixme: 调用方显式传入合同类型时优先使用,未传时从当前宜搭页面组件读取
       const contractType = options.contractType || _textValue(ids.type, page);
       if (!contractType) throw new Error("合同类型不能为空");
       const subDirectory = contractType;

+ 19 - 5
src/sample/rise.js

@@ -5,15 +5,29 @@ import { KEY_NO_LOADING, KEY_SHOW_MESSAGE } from "../service/request";
 export default {
 
   // 公共配置
-  init () {
+  init (compId = "employeeField_lu7qx4i4", button = "button_m1aajll9") {
     mjs.conf.api = "https://mc.cloudpure.cn/proxy/ruisi";
+    // 页面环境:0提交(其它),1查看,2编辑(审批)
+    if (mjs.env) {
+      let user = mjs.$this.$(compId).getValue();
+      // 兼容单选情况下,pc端移动端返回值格式不同
+      if (!user.length) {
+        user = user.value
+      } else {
+        user = user[0].value;
+      }
+      // 控制流程申请人\且是查看页面时, 添加咨询功能
+      if (loginUser.userId == user) {
+        mjs.$this.$(button).set("behavior", "NORMAL")
+      }
+    }
     return this; // this 指向当前项目本身
   },
 
   // 获取审批节点
   approvalRecord (compId) {
 
-    const procInsId = mjs.$this.utils.router.getQuery("procInsId");
+    const procInsId = mjs.$this.utils.router.getQuery("procInsId") || mjs.$this.utils.router.getQuery("formInstId");
     // 兼容矩阵更新延迟情况
     if (procInsId && mjs.$this.$(compId).getValue().length < 3) {
       setTimeout(async () => {
@@ -27,13 +41,13 @@ export default {
   },
 
   // 流程分享权限
-  async shareRecord (userIds = [], compId, isNotice, atUserId, content) {
+  async shareRecord (userIds = [], compId, isNotice, atUserId, content, isTodo) {
 
     userIds = userIds.map(item => item.value);
     atUserId = atUserId.map(item => item.value).join(",")
-    const procInsId = mjs.$this.utils.router.getQuery("procInsId");
+    const procInsId = mjs.$this.utils.router.getQuery("procInsId") || mjs.$this.utils.router.getQuery("formInstId");
     await mjs.request.xhr.doPost(`${mjs.conf.api}/share/record`, { processInstanceId: procInsId }, {
-      userIds, compId, isNotice, atUserId, content, userId: loginUser.userId
+      userIds, compId, isNotice, atUserId, content, userId: loginUser.userId, isTodo
     }, { [KEY_SHOW_MESSAGE]: true })
     if (isNotice) {
       setTimeout(() => location.reload(), 750);