mankalong-contract-page-sync.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /**
  2. * 曼卡龙合同页面同步区(可复制到各合同页面 JS)。
  3. *
  4. * 使用说明:
  5. * 1. 按当前页面修改 getMankalongContractFieldIds() 中的组件 ID。
  6. * 2. didMount 中先调用 await this._mklLoad(),再调用 await this.initMankalongContractPage()。
  7. * 3. 保留页面原有“生成合同”按钮及 generateSubFormImages 请求逻辑。
  8. * 开始、结束、成功后分别调用 this.startMankalongContractGenerate()、
  9. * this.finishMankalongContractGenerate()、this.markMankalongContractGenerated()。
  10. * 4. “合同确认”按钮绑定 onClickContractConfirm。
  11. * 5. 唯一标识必须从当前页面组件读取,读取失败直接抛出异常;合同确认内部使用 toast loading/error。
  12. * 6. 已部署旧复制区的页面按函数级合并,复制区外代码和“页面自定义初始化保留区”不得覆盖。
  13. * 7. 页面没有“主从合同”组件、但业务上关联特许经营合同时,mode 设为空字符串,
  14. * contractMode 固定设为“子合同”;不得按主合同处理,也不得伪造组件 ID。
  15. *
  16. * 依赖:页面已加载 mjs.corp.mkl(https://mc.cloudpure.cn/mjs/mkl/mjs.min.js)。
  17. */
  18. // ============================================================
  19. // 曼卡龙合同初始化与按钮防抖(可复制同步区域开始)
  20. // ============================================================
  21. /**
  22. * 加载曼卡龙专用 MJS 公共库。
  23. * @returns {Promise<void>} 加载完成
  24. */
  25. export function _mklLoad() {
  26. if (window.__mankalongMjsReady) return window.__mankalongMjsReady;
  27. window.__mankalongMjsReady = new Promise((resolve, reject) => {
  28. const existing = document.querySelector('script[data-mankalong-mjs="1"]');
  29. if (existing) {
  30. if (window.mjs && mjs.corp && mjs.corp.mkl) {
  31. this._mklInit().then(resolve).catch(reject);
  32. } else {
  33. existing.addEventListener('load', () => this._mklInit().then(resolve).catch(reject));
  34. existing.addEventListener('error', reject);
  35. }
  36. return;
  37. }
  38. const script = document.createElement('script');
  39. script.type = 'text/javascript';
  40. script.src = 'https://mc.cloudpure.cn/mjs/mkl/mjs.min.js?v=20260831-3';
  41. script.dataset.mankalongMjs = '1';
  42. script.onload = () => this._mklInit().then(resolve).catch(reject);
  43. script.onerror = reject;
  44. document.head.appendChild(script);
  45. });
  46. return window.__mankalongMjsReady;
  47. }
  48. /**
  49. * 初始化 MJS 曼卡龙模块。
  50. * @returns {Promise<void>} 初始化结果
  51. */
  52. export async function _mklInit() {
  53. await mjs.init(this, { vconsole: false });
  54. mjs.corp.mkl.init({
  55. environment: 'production',
  56. testApi: 'https://mc.cloudpure.cn/frphz/mankalong',
  57. productionApi: 'https://znht.mclon.com/api/mankalong',
  58. });
  59. }
  60. /**
  61. * @returns {Object} 当前合同页面组件 ID
  62. */
  63. export function getMankalongContractFieldIds() {
  64. return {
  65. // 合同唯一标识(业务唯一号)
  66. uniqueId: 'textField_mte3gh61',
  67. // 合同编号
  68. contractNo: 'textField_mrishceq',
  69. // 主从合同
  70. mode: 'radioField_mryiusal',
  71. // 没有主从合同组件时的固定业务值;关联特许经营合同的页面设为“子合同”
  72. contractMode: '',
  73. // 合同类型
  74. type: 'textField_mspzgras',
  75. // 关联合同编号
  76. masterNo: 'textField_mrylbq28',
  77. // 合同标题
  78. name: 'textField_mryko9bh',
  79. // 合同正文
  80. attachment: 'attachmentField_msv7rz7m',
  81. // 发起人
  82. initiator: 'employeeField_mrishcex',
  83. // 生成合同按钮
  84. generateButton: 'button_msiiyhct',
  85. // 合同确认按钮
  86. confirmButton: 'button_mtfaz1tq',
  87. // 仅发起人可见区块
  88. initiatorOnlySection: 'pageSection_mte7a077',
  89. // 合同操作区块
  90. contractOperationSection: 'pageSection_mte7a078',
  91. // 合同预览区块
  92. previewSection: 'pageSection_msruj1an',
  93. // 合同预览 iframe
  94. previewFrame: 'iframe_msn38cuu',
  95. // 生成预览附件地址
  96. previewUrl: 'textField_msnynxia',
  97. };
  98. }
  99. /**
  100. * 从当前页面实例读取合同业务唯一标识。
  101. * @param {Object} page 当前宜搭页面实例
  102. * @returns {string} 合同业务唯一标识
  103. */
  104. function readMankalongContractUniqueId(page, componentId) {
  105. const component = page && typeof page.$ === 'function' ? page.$(componentId) : null;
  106. if (!component || typeof component.getValue !== 'function') {
  107. throw new Error('合同唯一标识组件不存在: ' + componentId);
  108. }
  109. let value = component.getValue();
  110. if (Array.isArray(value)) {
  111. value = value[0] && (value[0].value || value[0].label || value[0].name);
  112. } else if (value && typeof value === 'object') {
  113. value = value.value || value.label || value.name;
  114. }
  115. const normalized = String(value || '').trim();
  116. if (!normalized) throw new Error('合同唯一标识不能为空');
  117. return normalized;
  118. }
  119. /**
  120. * @param {Object} page 当前宜搭页面实例
  121. * @param {string} componentId 字段组件 ID
  122. * @param {string} fieldName 字段名称
  123. * @returns {string} 字段值
  124. */
  125. function readMankalongContractFieldValue(page, componentId, fieldName) {
  126. const component = page && typeof page.$ === 'function' ? page.$(componentId) : null;
  127. if (!component || typeof component.getValue !== 'function') {
  128. throw new Error(fieldName + '组件不存在: ' + componentId);
  129. }
  130. let value = component.getValue();
  131. if (Array.isArray(value)) {
  132. value = value[0] && (value[0].value || value[0].label || value[0].name || value[0].zh_CN || value[0].en_US);
  133. } else if (value && typeof value === 'object') {
  134. value = value.value || value.label || value.name || value.zh_CN || value.en_US;
  135. }
  136. const normalized = String(value || '').trim();
  137. if (!normalized) throw new Error(fieldName + '不能为空');
  138. return normalized;
  139. }
  140. /**
  141. * @returns {Promise<Object>} 初始化后的合同页面状态
  142. */
  143. export async function initMankalongContractPage() {
  144. const ids = this.getMankalongContractFieldIds();
  145. if (mjs.env == 0) {
  146. const initiatorOnlySection = this.$(ids.initiatorOnlySection);
  147. if (!initiatorOnlySection || typeof initiatorOnlySection.setBehavior !== 'function') {
  148. throw new Error('仅发起人可见区块不存在或不支持状态设置: ' + ids.initiatorOnlySection);
  149. }
  150. initiatorOnlySection.setBehavior('HIDDEN');
  151. const contractNo = this.$(ids.contractNo);
  152. const contractValue = contractNo && typeof contractNo.getValue === 'function'
  153. ? contractNo.getValue()
  154. : '';
  155. const contractNumber = Array.isArray(contractValue)
  156. ? (contractValue[0] && (contractValue[0].value || contractValue[0].label || contractValue[0].name)) || ''
  157. : contractValue && typeof contractValue === 'object'
  158. ? contractValue.value || contractValue.label || contractValue.name || ''
  159. : String(contractValue || '');
  160. if (contractNumber) {
  161. const unique = this.$(ids.uniqueId);
  162. if (!unique || typeof unique.setValue !== 'function') {
  163. throw new Error('合同唯一标识组件不存在或不支持赋值: ' + ids.uniqueId);
  164. }
  165. const userId = this.utils && typeof this.utils.getLoginUserId === 'function'
  166. ? this.utils.getLoginUserId()
  167. : window.loginUser && window.loginUser.userId;
  168. if (!userId) throw new Error('当前用户 ID 不能为空,无法生成新的合同唯一标识');
  169. unique.setValue(String(userId) + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10));
  170. }
  171. if (contractNo && typeof contractNo.reset === 'function') contractNo.reset();
  172. const attachment = this.$(ids.attachment);
  173. if (attachment && typeof attachment.reset === 'function') attachment.reset();
  174. // 页面自定义初始化保留区开始
  175. // 页面自定义初始化保留区结束
  176. } else {
  177. const initiatorUserId = readMankalongContractFieldValue(this, ids.initiator, '发起人');
  178. const loginUserId = this.utils && typeof this.utils.getLoginUserId === 'function'
  179. ? this.utils.getLoginUserId()
  180. : window.loginUser && window.loginUser.userId;
  181. if (!loginUserId) throw new Error('当前登录用户 ID 不能为空');
  182. if (initiatorUserId !== String(loginUserId)) {
  183. const initiatorOnlySection = this.$(ids.initiatorOnlySection);
  184. if (!initiatorOnlySection || typeof initiatorOnlySection.setBehavior !== 'function') {
  185. throw new Error('仅发起人可见区块不存在或不支持状态设置: ' + ids.initiatorOnlySection);
  186. }
  187. initiatorOnlySection.setBehavior('HIDDEN');
  188. }
  189. const contractOperationSection = this.$(ids.contractOperationSection);
  190. if (!contractOperationSection || typeof contractOperationSection.setBehavior !== 'function') {
  191. throw new Error('合同操作区块不存在或不支持状态设置: ' + ids.contractOperationSection);
  192. }
  193. contractOperationSection.setBehavior('HIDDEN');
  194. }
  195. return mjs.corp.mkl.initContractPage({
  196. $this: this,
  197. componentIds: ids,
  198. });
  199. }
  200. /**
  201. * @returns {boolean} 是否允许开始合同生成
  202. */
  203. export function startMankalongContractGenerate() {
  204. return mjs.corp.mkl.startContractGenerate({
  205. $this: this,
  206. componentIds: this.getMankalongContractFieldIds(),
  207. });
  208. }
  209. /**
  210. * @returns {Object} 当前页面合同状态
  211. */
  212. export function finishMankalongContractGenerate() {
  213. return mjs.corp.mkl.finishContractGenerate({
  214. $this: this,
  215. componentIds: this.getMankalongContractFieldIds(),
  216. });
  217. }
  218. /**
  219. * 原有合同生成请求成功后调用,仅更新合同确认按钮 UI 状态。
  220. * @returns {Object} 当前页面合同状态
  221. */
  222. export function markMankalongContractGenerated() {
  223. return mjs.corp.mkl.markContractGenerated({
  224. $this: this,
  225. componentIds: this.getMankalongContractFieldIds(),
  226. });
  227. }
  228. /**
  229. * 通过原有合同合成接口生成正式文件,供 MJS 合同确认流程上传 OSS。
  230. * @param {Object} params 合同生成参数
  231. * @returns {Promise<string>} 合成文件地址
  232. */
  233. export async function generateMankalongContractFile({ contractNumber, mode, allowMissingInstanceId }) {
  234. const ids = this.getMankalongContractFieldIds();
  235. const fieldsResponse = await mjs.request.xhr.doPost('/api/yida/getFormFields', {
  236. formUuid: getFormUuid(),
  237. }, {}, {
  238. ignoreResponse: true,
  239. noLoading: true,
  240. noErrorTip: true,
  241. });
  242. const fieldIds = fieldsResponse && Array.isArray(fieldsResponse.data)
  243. ? fieldsResponse.data
  244. : [];
  245. const formData = fieldIds
  246. .map((fieldId) => {
  247. try {
  248. const component = fieldId && this.$(fieldId);
  249. if (!component || typeof component.getValue !== 'function') {
  250. console.warn('跳过当前页面不存在的组件 ID:', fieldId);
  251. return null;
  252. }
  253. return {
  254. structKey: fieldId,
  255. structValue: String(component.getValue() || ''),
  256. };
  257. } catch (error) {
  258. console.warn('读取组件 ID 失败,已跳过:', fieldId, error);
  259. return null;
  260. }
  261. })
  262. .filter((item) => item !== null);
  263. const instanceId = this.utils.router.getQuery('procInsId')
  264. || (mjs.com && mjs.com.getFormInstIdByUrl && mjs.com.getFormInstIdByUrl());
  265. const generateMode = mode || 'generate';
  266. if (generateMode === 'generate' && !instanceId && !allowMissingInstanceId) {
  267. throw new Error('当前页面缺少流程实例 ID,无法生成正式合同');
  268. }
  269. const body = {
  270. formUuid: getFormUuid(),
  271. mappings: [],
  272. formData,
  273. type: generateMode,
  274. unique: readMankalongContractUniqueId(this, ids.uniqueId),
  275. instanceId: instanceId || '',
  276. preSubmit: allowMissingInstanceId === true,
  277. dingOrgId: '908563201',
  278. dingUid: this.state && this.state.userId || this.utils.getLoginUserId(),
  279. corpId: 'ding3735baea6bea68ac24f2f5cc6abecb85',
  280. contractNumber,
  281. };
  282. const response = await mjs.request.xhr.doPost('/api/yida/subform-to-image', {}, body, {
  283. noLoading: true,
  284. noErrorTip: true,
  285. });
  286. const fileUrl = response && (response.message || response.downloadUrl || response.fileUrl || response.url);
  287. if (!fileUrl) throw new Error('合同合成接口未返回文件地址');
  288. return fileUrl;
  289. }
  290. /**
  291. * @returns {Promise<Object|null>} 合同确认结果
  292. */
  293. export async function onClickContractConfirm() {
  294. const ids = this.getMankalongContractFieldIds();
  295. const uniqueId = readMankalongContractUniqueId(this, ids.uniqueId);
  296. return mjs.corp.mkl.runContractConfirm({
  297. $this: this,
  298. componentIds: ids,
  299. uniqueId,
  300. contractType: readMankalongContractFieldValue(this, ids.type, '合同类型'),
  301. contractMode: ids.contractMode || readMankalongContractFieldValue(this, ids.mode, '合同主从类型'),
  302. subDirectory: '工程类',
  303. generateMode: 'generate',
  304. allowMissingInstanceId: true,
  305. generateContract: (params) => this.generateMankalongContractFile(params),
  306. });
  307. }
  308. /**
  309. * @returns {boolean} 是否允许提交
  310. */
  311. export function beforeSubmitContractReady() {
  312. if (!window.mjs || !mjs.corp || !mjs.corp.mkl) {
  313. this.utils.dialog({
  314. type: 'alert',
  315. title: '提示',
  316. content: '合同还未正式生成,请操作!',
  317. });
  318. return false;
  319. }
  320. return mjs.corp.mkl.beforeSubmitContract({
  321. $this: this,
  322. componentIds: this.getMankalongContractFieldIds(),
  323. });
  324. }
  325. /**
  326. * @returns {void} 清理合同页面状态
  327. */
  328. export function disposeMankalongContractPage() {
  329. if (!window.mjs || !mjs.corp || !mjs.corp.mkl) return;
  330. mjs.corp.mkl.disposeContractPage({ $this: this });
  331. }
  332. // ============================================================
  333. // 曼卡龙合同初始化与按钮防抖(可复制同步区域结束)
  334. // ============================================================