Quellcode durchsuchen

feat(mkl): add contract confirmation module

malk vor 3 Wochen
Ursprung
Commit
837d619f18
2 geänderte Dateien mit 411 neuen und 1 gelöschten Zeilen
  1. 2 1
      src/main.js
  2. 409 0
      src/sample/mkl.js

+ 2 - 1
src/main.js

@@ -27,6 +27,7 @@ import cp from "./sample/cloudpure"
 import guyuan from "./sample/guyuan"
 import hangshi from "./sample/hangshi"
 import rise from "./sample/rise"
+import mkl from "./sample/mkl"
 
 export async function init (_this, config = {}) {
   console.log(this)
@@ -45,7 +46,7 @@ export async function init (_this, config = {}) {
   this.request = { dp, xhr, net: { crossDomainByScript } }; // 请求
   this.ding = ding;
   this.corp = {
-    cp, guyuan, hangshi, rise
+    cp, guyuan, hangshi, rise, mkl
   }
   // 输出日志;
   const msg = `mjs load success. ♨ 访问应用: ${pageConfig.appType} ${pageConfig.appName} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;

+ 409 - 0
src/sample/mkl.js

@@ -0,0 +1,409 @@
+/*** mjs 之曼卡龙合同确认 ***/
+
+import {
+  KEY_NO_ERROR_TIP,
+  KEY_NO_LOADING,
+} from "../service/request";
+
+const TEST_API = "https://mc.cloudpure.cn/frphz/mankalong";
+const PRODUCTION_API = "https://znht.mclon.com/api/mankalong";
+
+function _page () {
+  return mjs.$this;
+}
+
+function _textValue (compId) {
+  if (!compId) return "";
+  const comp = _page().$(compId);
+  if (!comp || typeof comp.getValue !== "function") return "";
+  const value = comp.getValue();
+  if (Array.isArray(value)) {
+    const first = value[0];
+    return first && (first.value || first.label || first.name) || "";
+  }
+  if (value && typeof value === "object") {
+    return value.value || value.label || value.name || "";
+  }
+  return String(value || "");
+}
+
+function _hasValue (value) {
+  if (Array.isArray(value)) return value.length > 0;
+  return value !== null && value !== undefined && String(value).trim() !== "";
+}
+
+function _requestConfig () {
+  return {
+    [KEY_NO_LOADING]: true,
+    [KEY_NO_ERROR_TIP]: true,
+  };
+}
+
+async function _post (url, body) {
+  let response;
+  try {
+    response = await mjs.request.xhr.doPost(url, {}, body, _requestConfig());
+  } catch (error) {
+    // 请求拦截器可能直接 reject 字符串消息;统一转换为 Error,确保页面 catch 能拿到明确错误。
+    if (error instanceof Error) throw error;
+    const message = error && (error.message || error.msg)
+      || error && error.response && error.response.data
+      && (error.response.data.message || error.response.data.msg)
+      || String(error || "合同接口调用失败");
+    throw new Error(message);
+  }
+  if (!response || Number(response.code) !== 200) {
+    throw new Error(response && (response.message || response.msg) || "合同接口调用失败");
+  }
+  return response.data;
+}
+
+async function _postRaw (url, params = {}, body = {}) {
+  const response = await mjs.request.xhr.doPost(url, params, body, {
+    ..._requestConfig(),
+    ignoreResponse: true,
+  });
+  return response && response.data;
+}
+
+function _componentValue (componentId) {
+  if (!componentId) return "";
+  const component = _page().$(componentId);
+  return component && typeof component.getValue === "function" ? component.getValue() : "";
+}
+
+function _safeSetValue (componentId, value) {
+  if (!componentId) return;
+  const component = _page().$(componentId);
+  if (component && typeof component.setValue === "function") component.setValue(value);
+}
+
+function _formDataFromFieldIds (fieldIds) {
+  return (Array.isArray(fieldIds) ? fieldIds : []).map((fieldId) => ({
+    structKey: fieldId,
+    structValue: String(_componentValue(fieldId) || ""),
+  }));
+}
+
+export default {
+
+  /**
+   * 初始化曼卡龙后端地址。
+   * @param {Object} options 配置
+   * @param {string} options.environment 环境:test 或 production
+   * @param {string} options.api 显式指定后端基础地址,优先级最高
+   * @param {string} options.testApi 测试环境地址,默认走 180 的 FRP 转发
+   * @param {string} options.productionApi 生产环境地址
+   * @returns {Object} 当前客户模块
+   */
+  init (options = {}) {
+    const environment = options.environment || options.env || "production";
+    const defaultApi = environment === "test"
+      ? options.testApi || TEST_API
+      : options.productionApi || PRODUCTION_API;
+    mjs.conf.api = (options.api || defaultApi).replace(/\/$/, "");
+    this.environment = environment;
+    return this;
+  },
+
+  /**
+   * 分配主合同或子合同号。
+   * @param {Object} params 合同号参数
+   * @returns {Promise<Object>} 合同号分配结果
+   */
+  async allocateContractNumber (params = {}) {
+    return _post("/contract/number", params);
+  },
+
+  /**
+   * 把已生成文件同步到 OSS。
+   * @param {string} fileUrl 文件地址
+   * @param {string} subDirectory contract 下的二级目录
+   * @returns {Promise<Array>} 宜搭附件数组
+   */
+  async syncContractToOss (fileUrl, subDirectory) {
+    return _post("/contract/confirm", { fileUrl, subDirectory });
+  },
+
+  /**
+   * 获取表单字段 ID 列表。
+   * @param {string} formUuid 宜搭表单 ID
+   * @returns {Promise<Array>} 字段 ID 列表
+   */
+  async getFormFieldIds (formUuid) {
+    return _postRaw("/api/yida/getFormFields", { formUuid }, {});
+  },
+
+  /**
+   * 获取当前表单字段数据。
+   * @param {string} formUuid 宜搭表单 ID
+   * @returns {Promise<Array>} 宜搭结构化字段数据
+   */
+  async getFormData (formUuid) {
+    const fieldIds = await this.getFormFieldIds(formUuid);
+    return _formDataFromFieldIds(fieldIds);
+  },
+
+  /**
+   * 迁移原页面的子表转图片/合同预览逻辑。
+   * @param {Object} options 参数
+   * @param {string} options.formUuid 合同模板表单 ID
+   * @param {string} options.type preview 或 generate
+   * @param {string} options.instanceId 流程实例 ID
+   * @param {Object} options.componentIds 页面组件 ID
+   * @param {Array} options.mappings 子表图片映射
+   * @returns {Promise<string|null>} 预览地址或空值
+   */
+  async generateSubFormImages (options = {}) {
+    const page = _page();
+    const ids = options.componentIds || {};
+    const formUuid = options.formUuid || "";
+    const type = options.type || "preview";
+    const formData = await this.getFormData(formUuid);
+    const body = {
+      formUuid,
+      mappings: Array.isArray(options.mappings) ? options.mappings : [],
+      formData,
+      type,
+      unique: _componentValue(ids.uniqueId),
+      instanceId: options.instanceId || "",
+      dingOrgId: options.dingOrgId || "",
+      dingUid: options.dingUid || page.utils.getLoginUserId(),
+      corpId: options.corpId || "",
+    };
+    const result = await _post("/api/yida/subform-to-image", body);
+    const fileUrl = result && (result.message || result.downloadUrl || result.fileUrl || result.url);
+    if (fileUrl && ids.previewFrame) _page().$(ids.previewFrame).set("src", fileUrl);
+    if (fileUrl && ids.previewSection) _page().$(ids.previewSection).setBehavior("NORMAL");
+    return fileUrl || null;
+  },
+
+  /**
+   * 合同确认完整流程的默认适配器。
+   * @param {Object} options 合同确认参数
+   * @returns {Promise<Object>} 合同号及附件结果
+   */
+  async confirmPageContract (options = {}) {
+    const formUuid = options.formUuid || "";
+    const componentIds = options.componentIds || {};
+    return this.confirmContract({
+      ...options,
+      componentIds,
+      generateContract: ({ contractNumber, mode }) => this.generateSubFormImages({
+        formUuid,
+        type: mode,
+        instanceId: options.instanceId || _page().utils.router.getQuery("procInsId") || "",
+        componentIds: {
+          ...componentIds,
+          uniqueId: componentIds.uniqueId,
+        },
+        mappings: options.mappings,
+        dingOrgId: options.dingOrgId,
+        dingUid: options.dingUid,
+        corpId: options.corpId,
+        contractNumber,
+      }),
+    });
+  },
+
+  /**
+   * 迁移原页面的提交后生成逻辑。
+   * @param {Object} options 参数
+   * @returns {Promise<string|null>} 生成地址
+   */
+  async afterSubmit (options = {}) {
+    return this.generateSubFormImages({
+      ...options,
+      type: "generate",
+      instanceId: options.instanceId || _page().utils.router.getQuery("procInsId") || "",
+    });
+  },
+
+  /**
+   * 下载子表图片文件。
+   * @param {string} fileName 文件名
+   * @returns {void}
+   */
+  downloadImage (fileName) {
+    const url = `${mjs.conf.api}/api/yida/images/download/${encodeURIComponent(fileName || "")}`;
+    window.open(url);
+  },
+
+  /**
+   * 迁移原页面的分享弹窗逻辑。
+   * @param {Object} componentIds 分享相关组件 ID
+   * @returns {void}
+   */
+  openShareDialog (componentIds = {}) {
+    const dialog = componentIds.dialog && _page().$(componentIds.dialog);
+    if (dialog && typeof dialog.show === "function") dialog.show();
+    setTimeout(() => {
+      [componentIds.receiver, componentIds.extraReceiver, componentIds.noticeType, componentIds.noticeContent]
+        .filter(Boolean)
+        .forEach((id) => {
+          const component = _page().$(id);
+          if (component && typeof component.setBehavior === "function") component.setBehavior("NORMAL");
+        });
+    }, 400);
+  },
+
+  /**
+   * 迁移原页面的分享记录请求。
+   * @param {Object} options 分享参数及组件 ID
+   * @returns {Promise<Object>} 后端响应
+   */
+  async shareRecord (options = {}) {
+    const ids = options.componentIds || {};
+    const page = _page();
+    const receivers = _componentValue(ids.receiver) || [];
+    if (!Array.isArray(receivers) || receivers.length === 0) return null;
+    const extraReceivers = _componentValue(ids.extraReceiver) || [];
+    const userIds = [...(Array.isArray(extraReceivers) ? extraReceivers : []), ...receivers];
+    const atUserIds = receivers.map((item) => item && item.workId).filter(Boolean);
+    return _post("/shareRecord", {
+      compId: ids.extraReceiver,
+      userIds,
+      userId: page.utils.getLoginUserId(),
+      atUserId: atUserIds,
+      content: _textValue(ids.title),
+      processInstanceId: options.processInstanceId || "",
+      isNotice: _textValue(ids.notice) === "是",
+      appUrl: options.appUrl || "",
+      pcUrl: options.pcUrl || "",
+      formId: options.formId || "",
+      processKey: options.processKey || "",
+      empKey: options.empKey || "",
+      titleKey: options.titleKey || "",
+    });
+  },
+
+  /**
+   * 迁移原页面的数值校验逻辑。
+   * @param {Object} options 参数
+   * @returns {void}
+   */
+  handleNumberChange (options = {}) {
+    const value = Number(options.value || 0);
+    const component = options.validationComponentId && _page().$(options.validationComponentId);
+    if (component && typeof component.setValidation === "function") {
+      component.setValidation([{ type: value > Number(options.threshold || 10000) ? "required" : "" }]);
+    }
+  },
+
+  /**
+   * 迁移原页面 didMount 的界面初始化逻辑。
+   * @param {Object} options 参数
+   * @param {Object} options.componentIds 页面组件 ID
+   * @returns {void}
+   */
+  syncPageDidMount (options = {}) {
+    const ids = options.componentIds || {};
+    const page = _page();
+    const section = ids.previewSection && page.$(ids.previewSection);
+    if (section && typeof section.setBehavior === "function") section.setBehavior("HIDDEN");
+    if (typeof page.setState === "function" && typeof window !== "undefined" && window.loginUser) {
+      page.setState({ deptId: window.loginUser.deptId, userId: window.loginUser.userId });
+    }
+    const iframeUrl = _textValue(ids.previewUrl);
+    if (iframeUrl && ids.previewFrame) page.$(ids.previewFrame).set("src", iframeUrl);
+  },
+
+  /**
+   * 合同确认完整流程:合同号 → 原合同生成回调 → OSS → 附件回写。
+   * @param {Object} options 流程参数
+   * @param {Object} options.componentIds 页面组件 ID
+   * @param {string} options.subDirectory OSS 二级目录
+   * @param {boolean} options.allowMissingInstanceId 预提交正式合成时允许暂缺流程实例 ID
+   * @param {Function} options.generateContract 原合同生成回调,必须返回文件 URL 或包含 downloadUrl 的对象
+   * @returns {Promise<Object>} 合同号及附件
+   */
+  async confirmContract (options = {}) {
+    const page = _page();
+    const ids = options.componentIds || {};
+    const closeLoading = page.utils.toast({
+      type: "loading",
+      title: "合同生成中",
+      hasMask: true,
+    });
+    try {
+      if (!ids.uniqueId || !ids.contractNo || !ids.mode || !ids.type || !ids.attachment) {
+        throw new Error("合同确认组件 ID 配置不完整");
+      }
+      if (typeof options.generateContract !== "function") {
+        throw new Error("必须传入 generateContract 回调");
+      }
+
+      let contractNumber = _textValue(ids.contractNo);
+      if (!contractNumber) {
+        const numberResult = await this.allocateContractNumber({
+          targetUniqueId: _textValue(ids.uniqueId),
+          contractType: _textValue(ids.type),
+          contractMode: _textValue(ids.mode),
+          masterContractNumber: _textValue(ids.masterNo),
+          contractName: _textValue(ids.name),
+          yearMonth: options.yearMonth || "",
+        });
+        contractNumber = numberResult && numberResult.contractNumber;
+        if (!contractNumber) throw new Error("合同号接口未返回合同号");
+        page.$(ids.contractNo).setValue(contractNumber);
+      }
+
+      const generated = await options.generateContract.call(page, {
+        contractNumber,
+        mode: options.generateMode || options.mode || "generate",
+        allowMissingInstanceId: options.allowMissingInstanceId === true,
+      });
+      const fileUrl = typeof generated === "string"
+        ? generated
+        : generated && (generated.downloadUrl || generated.fileUrl || generated.url);
+      if (!fileUrl) throw new Error("原合同生成接口未返回文件地址");
+
+      const ossResult = await this.syncContractToOss(fileUrl, options.subDirectory);
+      const attachments = Array.isArray(ossResult)
+        ? ossResult
+        : ossResult && Array.isArray(ossResult.data) ? ossResult.data : [];
+      if (attachments.length === 0) throw new Error("OSS 接口返回的附件格式不正确");
+      const attachmentComp = page.$(ids.attachment);
+      if (!attachmentComp || typeof attachmentComp.setValue !== "function") {
+        throw new Error("合同附件组件不存在或不支持赋值");
+      }
+      attachmentComp.setValue(attachments);
+      page.utils.toast({
+        type: "success",
+        title: "正式合同生成成功",
+      });
+      return { contractNumber, attachments };
+    } catch (error) {
+      const normalizedError = error instanceof Error
+        ? error
+        : new Error(error && (error.message || error.msg) || String(error || "合同确认失败"));
+      page.utils.toast({
+        type: "error",
+        title: normalizedError.message,
+      });
+      throw normalizedError;
+    } finally {
+      if (typeof closeLoading === "function") closeLoading();
+    }
+  },
+
+  /**
+   * 提交前校验合同号和合同附件是否已经生成。
+   * @param {Object} componentIds 页面组件 ID
+   * @returns {boolean} 是否允许提交
+   */
+  beforeSubmitContract (componentIds = {}) {
+    const number = _textValue(componentIds.contractNo);
+    const attachmentComp = componentIds.attachment && _page().$(componentIds.attachment);
+    const attachment = attachmentComp && typeof attachmentComp.getValue === "function"
+      ? attachmentComp.getValue()
+      : null;
+    if (_hasValue(number) && _hasValue(attachment)) return true;
+    _page().utils.dialog({
+      type: "alert",
+      title: "提示",
+      content: "合同还未正式生成,请操作!",
+    });
+    return false;
+  },
+};