|
|
@@ -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;
|
|
|
+ },
|
|
|
+};
|