// ==UserScript== // @name 数字供应链框架协议助手 // @namespace http://tampermonkey.net/ // @version 2026年7月10日 // @description 批量编制框架协议并提交审批 (嵌入修正+上传重试版) // @author Assistant // @match https://dsc.cscec8b.com.cn:20025/* // @grant GM_addStyle // @require https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js // @license MIT // ==/UserScript== (function () { 'use strict'; const CONFIG = { apiBase: 'https://dsc.cscec8b.com.cn:20025', endpoints: { searchWinBid: '/api-gateway/dsc-contract-manager/yunZhuProvider/findYunZhuWinBidResults', searchTemplate: '/api-gateway/dsc-contract/template/searchByPage', getTemplateElements: '/api-gateway/dsc-contract/templateElement/findByTemplateId', searchContract: '/api-gateway/dsc-contract-manager/contract/searchByPage', createDraft: '/api-gateway/dsc-contract-manager/contract/createdYunZhuNetWorkContract', getContractDetail: '/api-gateway/dsc-contract-manager/contract/findOne', updateContract: '/api-gateway/dsc-contract-manager/contract/updateContract', savePaymentPlan: '/api-gateway/dsc-contract-manager/paymentPlan/savePaymentPlans', uploadFile: '/api-gateway/dsc-contract-manager/contract/uploadWithWaterMark', saveArchive: '/api-gateway/dsc-contract-manager/contractArchive/saveApproveReferenceArchives', saveArchiveBatch: '/api-gateway/dsc-contract-manager/contractArchive/saveArchiveBatch', getTaxRates: '/api-gateway/bidb-dms/taxRate/findByPage', getDict: '/api-gateway/bidb-dms/dataDict/getCanUseDataDictValues', createRequest: '/api-gateway/dsc-contract-manager/draftRequest/createRequestAndGetStartParam', startFlow: '/api-gateway/dsc-contract-manager/draftRequest/startFlow', startFlowNew: '/api-gateway/flow-service/defaultFlowBase/startFlowNew', getEntity: '/api-gateway/flow-service/design/getEntity', saveSolidify: '/api-gateway/flow-service/flowSolidifyExecutor/saveSolidifyInfoByExecutorVos', getAreas: '/api-gateway/bidb-dms/area/findListWithRegionTree?filterFrozen=true', getCalculatedUsers: '/api-gateway/flow-service/flowTask/getExecutorsByExecutorsVos', getOrgs: '/api-gateway/bidb-basic/corporation/getUserAuthorizedEntitiesWithoutFullColumns', getAllCorporations: '/api-gateway/bidb-basic/corporation/findAll', getSignInfo: '/api-gateway/dsc-contract-manager/contractSign/findSignInfoDetailByContractId', offlineSign: '/api-gateway/dsc-contract-manager/contractSign/offlineSign' }, implicitFields: [ { name: "税率", match: (code, name) => name.includes("税率"), domId: 'yz-dynamic-tax', api: { dataSourceUrl: '/api-gateway/bidb-dms/taxRate/findByPage', requestMethod: 'POST', params: JSON.stringify({ "quickSearchValue": "", "quickSearchProperties": ["code", "name"], "pageInfo": { "page": 1, "rows": 1000 } }) } }, { name: "国际贸易条款", match: (code, name) => (code === 'internationalTradeTerms' || name === "国际贸易条款") && !code.includes("Additional") && !name.includes("补充"), api: { dataSourceUrl: '/api-gateway/bidb-dms/dataDict/getCanUseDataDictValues', requestMethod: 'GET', params: JSON.stringify({ dictCode: 'priceClause' }) } } ], uiInjections: [ { triggerCode: 'internationalTradeTerms', targetField: { elementName: "国际贸易条款补充内容", elementCode: "internationalTradeTermsAdditional", showType: "text", required: false, } } ], }; let state = { xsid: '', isMinimized: false, templates: [], searchResults: [], loadingDicts: {}, paymentRowCounter: 0, exportData: [], areaTreeData: [], orgTreeData: [], userScopes: [], templateFieldMaps: {}, templateElementsCache: {}, currentTemplateFields: [], defaultPartyA: null }; const STYLES = ` .yz-launcher-tab { width: 120px; cursor: pointer; display: inline-block; height: 100%; vertical-align: top; position: relative; color: rgba(0,0,0,0.65); transition: color 0.3s; float: left; } .yz-launcher-tab:hover { color: #1890ff; } .yz-launcher-tab .tab-top-line { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background-color: transparent; } .yz-launcher-tab:hover .tab-top-line { background-color: #1890ff; } .yz-launcher-tab .tab-title { height: 40px; margin-top: 4px; display: flex; justify-content: center; align-items: center; font-size: 14px; font-weight: 400; } .yz-launcher-tab a { color: inherit; } #yz-helper-panel { position: fixed; top: 90px; right: 30px; width: 840px; background: #fff; box-shadow: 0 8px 30px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.08); border-radius: 8px; z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; border: 1px solid #e8e8e8; display: none; flex-direction: column; max-height: 85vh; transition: all 0.3s ease; } .yz-header { background: linear-gradient(135deg, #fafafa 0%, #fff 100%); color: #1a1a1a; padding: 18px 24px; display: flex; justify-content: space-between; align-items: center; cursor: move; border-bottom: 1px solid #e8e8e8; border-radius: 8px 8px 0 0; font-size: 16px; font-weight: 600; user-select: none; } .yz-body { padding: 20px; overflow-y: auto; background: #f5f6f8; flex: 1; display: flex; flex-direction: column; gap: 14px; } .yz-body::-webkit-scrollbar { width: 6px; } .yz-body::-webkit-scrollbar-track { background: transparent; } .yz-body::-webkit-scrollbar-thumb { background: #d9d9d9; border-radius: 3px; } .yz-body::-webkit-scrollbar-thumb:hover { background: #b3b3b3; } .yz-section { background: #fff; padding: 18px; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0; transition: box-shadow 0.2s; } .yz-section:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.06); } .yz-section-title { font-weight: 600; font-size: 15px; color: #1a1a1a; margin-bottom: 14px; display: flex; align-items: center; letter-spacing: 0.3px; } .yz-section-title::before { content: ''; display: inline-block; width: 3px; height: 18px; background: linear-gradient(180deg, #1890ff, #69c0ff); margin-right: 10px; border-radius: 3px; } .yz-input-group { display: flex; gap: 8px; margin-bottom: 8px; } .yz-input { flex: 1; padding: 8px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; outline: none; transition: all 0.2s ease; color: rgba(0,0,0,0.65); background: #fafafa; } .yz-input:hover { border-color: #91d5ff; background: #fff; } .yz-input:focus { border-color: #1890ff; background: #fff; box-shadow: 0 0 0 3px rgba(24,144,255,0.12); } .yz-input:disabled { background: #f5f5f5; cursor: not-allowed; color: rgba(0,0,0,0.25); } .yz-btn { padding: 8px 20px; background: #1890ff; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; white-space: nowrap; transition: all 0.2s ease; box-shadow: 0 2px 4px rgba(24,144,255,0.2); } .yz-btn:hover { background: #40a9ff; box-shadow: 0 4px 8px rgba(24,144,255,0.3); transform: translateY(-1px); } .yz-btn:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(24,144,255,0.2); } .yz-btn-green { background: #52c41a; box-shadow: 0 2px 4px rgba(82,196,26,0.2); } .yz-btn-green:hover { background: #73d13d; box-shadow: 0 4px 8px rgba(82,196,26,0.3); } .yz-btn-orange { background: #faad14; color: #fff; box-shadow: 0 2px 4px rgba(250,173,20,0.2); } .yz-btn-orange:hover { background: #ffc53d; box-shadow: 0 4px 8px rgba(250,173,20,0.3); } .yz-btn-purple { background: #722ed1; box-shadow: 0 2px 4px rgba(114,46,209,0.2); } .yz-btn-purple:hover { background: #9254de; box-shadow: 0 4px 8px rgba(114,46,209,0.3); } .yz-btn-danger { background: #ff4d4f; padding: 4px 10px; font-size: 12px; box-shadow: 0 2px 4px rgba(255,77,79,0.2); } .yz-btn-danger:hover { background: #ff7875; box-shadow: 0 4px 8px rgba(255,77,79,0.3); } .yz-btn-add { width: 100%; background: #fff; margin-top: 5px; border: 1px dashed #d9d9d9; color: rgba(0,0,0,0.65); box-shadow: none; } .yz-btn-add:hover { border-color: #1890ff; color: #1890ff; transform: none; } .yz-form-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 16px; } .yz-form-item { display: flex; flex-direction: column; } .yz-form-item.dependent-field { margin-left: 15px; border-left: 4px solid #faad14; padding: 8px 12px; background: #fffbe6; border-radius: 0 6px 6px 0; grid-column: 1/-1 !important; margin-bottom: 8px; } .yz-form-label { font-size: 14px; color: rgba(0,0,0,0.85); margin-bottom: 6px; } .yz-payment-row { background: #fafafa; border: 1px solid #e8e8e8; padding: 12px; margin-bottom: 12px; border-radius: 6px; } .yz-payment-header { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px; font-weight: 500; color: #1890ff; } .yz-table-wrapper { overflow-x: auto; border: 1px solid #f0f0f0; border-radius: 6px; height: 150px; overflow-y: auto; } .yz-table { width: 100%; border-collapse: collapse; font-size: 13px; } .yz-table th { background: #fafafa; position: sticky; top: 0; padding: 10px 8px; text-align: left; border-bottom: 1px solid #f0f0f0; font-weight: 500; color: rgba(0,0,0,0.85); } .yz-table td { padding: 10px 8px; border-bottom: 1px solid #f0f0f0; color: rgba(0,0,0,0.65); } .yz-status { font-size: 13px; color: #52c41a; display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: #f6ffed; border: 1px solid #b7eb8f; border-radius: 6px; margin-top: 0; } .yz-status.error { color: #ff4d4f; background: #fff2f0; border-color: #ffccc7; } .yz-status.processing { color: #1890ff; background: #e6f7ff; border-color: #91d5ff; } .required::after { content: '*'; color: #ff4d4f; margin-left: 2px; } .yz-file-input { border: 1px dashed #d9d9d9; padding: 8px; width: 100%; border-radius: 6px; background: #fafafa; transition: border-color 0.3s; } .yz-file-input:hover { border-color: #1890ff; } .yz-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.45); z-index: 10000; display: flex; justify-content: center; align-items: center; } .yz-modal { background: #fff; width: 650px; border-radius: 8px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); display: flex; flex-direction: column; max-height: 85vh; } .yz-modal-header { padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-weight: 600; font-size: 16px; color: #1a1a1a; } .yz-modal-body { padding: 24px; overflow-y: auto; flex: 1; } .yz-modal-footer { padding: 10px 16px; border-top: 1px solid #f0f0f0; text-align: right; display: flex; justify-content: flex-end; gap: 8px; } .yz-node-group { margin-bottom: 15px; border: 1px solid #e8e8e8; border-radius: 6px; padding: 12px; background: #fff; } .yz-node-title { font-weight: bold; color: #1890ff; margin-bottom: 8px; font-size: 14px; display: flex; justify-content: space-between; } .yz-node-title.error-node { color: #ff4d4f; } .yz-user-item { display: flex; align-items: center; margin-bottom: 5px; font-size: 14px; cursor: pointer; padding: 4px 0; border-bottom: 1px dashed #f0f0f0; } .yz-user-meta { color: rgba(0,0,0,0.45); font-size: 12px; margin-left: 5px; } .yz-user-pos { color: rgba(0,0,0,0.45); font-size: 12px; margin-left: 5px; } .yz-tree-search { margin-bottom: 10px; width: 100%; box-sizing: border-box; } .yz-tree-ul { list-style: none; padding-left: 20px; margin: 0; } .yz-tree-ul.root { padding-left: 0; } .yz-tree-node { margin: 2px 0; } .yz-tree-content { display: flex; align-items: center; cursor: pointer; padding: 4px 6px; border-radius: 6px; } .yz-tree-content:hover { background-color: #e6f7ff; } .yz-tree-toggle { display: inline-block; width: 16px; height: 16px; line-height: 14px; text-align: center; margin-right: 4px; color: rgba(0,0,0,0.45); font-family: monospace; border: 1px solid #d9d9d9; border-radius: 4px; background: #fff; } .yz-tree-toggle:hover { border-color: #1890ff; color: #1890ff; } .yz-tree-label { font-size: 14px; color: rgba(0,0,0,0.85); } .yz-tree-children { display: none; } .yz-tree-children.expanded { display: block; } .yz-scope-list { margin-top: 10px; border: 1px solid #f0f0f0; border-radius: 6px; max-height: 120px; overflow-y: auto; } .yz-scope-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border-bottom: 1px solid #f0f0f0; background: #fff; font-size: 13px; } .yz-scope-text { flex: 1; } .yz-del-btn { color: #ff4d4f; cursor: pointer; margin-left: 10px; font-weight: 500; } .yz-checkbox-wrapper { display: flex; align-items: center; font-size: 14px; color: rgba(0,0,0,0.85); margin-top: 5px; } .yz-checkbox-wrapper input { margin-right: 6px; } .yz-flow-radio-item { padding: 8px; border-bottom: 1px solid #eee; display: flex; align-items: center; cursor: pointer; } .yz-flow-radio-item:hover { background: #e6f7ff; } .yz-flow-radio-item input { margin-right: 10px; } .yz-error-tip { font-size: 12px; color: #ff4d4f; margin-bottom: 4px; background: #fff2f0; padding: 6px; border-radius: 6px; border: 1px solid #ffccc7; } .yz-sub-title { font-size: 12px; font-weight: 600; color: #8c8c8c; margin-bottom: 10px; padding-left: 10px; border-left: 3px solid #1890ff; text-transform: uppercase; letter-spacing: 0.5px; } .yz-batch-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } .yz-batch-card { background: #fff; border: 1px solid #e8e8e8; border-radius: 8px; padding: 16px; display: flex; flex-direction: column; transition: box-shadow 0.2s, border-color 0.2s; } .yz-batch-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); border-color: #d0d0d0; } .yz-batch-card-title { font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 2px solid #1890ff; } .yz-batch-card-title.accent-green { border-bottom-color: #52c41a; } .yz-batch-card-title.accent-orange { border-bottom-color: #faad14; } .yz-batch-card-title.accent-purple { border-bottom-color: #722ed1; } `; const utils = { decodeSidCandidate: (encodedStr) => { try { const decodedBase64 = atob(encodedStr); let decodedUrl = decodedBase64; try { decodedUrl = decodeURIComponent(decodedBase64); } catch (e) { } return decodedUrl.replace(/^["']|["']$/g, ''); } catch (e) { return null; } }, autoGetXsid: () => { const guidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; for (let i = 0; i < sessionStorage.length; i++) { const key = sessionStorage.key(i); const val = sessionStorage.getItem(key); const candidates = [val, key]; for (const candidate of candidates) { if (!candidate || candidate.length < 20) continue; const result = utils.decodeSidCandidate(candidate); if (result && guidRegex.test(result)) return result; } } return ''; }, generateUUID: () => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }).toUpperCase(); }, parseJSONSafe: (str) => { try { return JSON.parse(str); } catch (e) { return null; } }, request: async (method, url, data, callback) => { if (!state.xsid) state.xsid = utils.autoGetXsid(); let finalUrl = CONFIG.apiBase + (url.startsWith('/') ? '' : '/') + url; const headers = { 'x-sid': state.xsid || '', 'neverCancel': 'true' }; const options = { method: method, headers: headers }; if (method === 'GET' && data) { const params = Object.keys(data).map(key => `${key}=${encodeURIComponent(data[key])}`).join('&'); if (params) finalUrl += (finalUrl.includes('?') ? '&' : '?') + params; headers['Content-Type'] = 'application/json;charset=UTF-8'; } else if (data instanceof FormData) { options.body = data; } else if (data) { headers['Content-Type'] = 'application/json;charset=UTF-8'; options.body = JSON.stringify(data); } else { headers['Content-Type'] = 'application/json;charset=UTF-8'; } console.log(`[YzHelper] Fetch: ${method} ${finalUrl}`, data); try { const response = await fetch(finalUrl, options); const text = await response.text(); let json = null; try { json = JSON.parse(text); } catch (e) { console.warn("JSON Parse Failed", text); } if (!response.ok) { callback(new Error(`HTTP Error ${response.status}: ${response.statusText}`), json); } else if (json) { callback(null, json); } else { callback(new Error("Empty response"), null); } } catch (err) { console.error("Fetch Network Error", err); callback(err, null); } }, requestPromise: (method, url, data, desc = "API请求") => { return new Promise((resolve, reject) => { utils.request(method, url, data, (err, res) => { if (err) return reject(new Error(`${desc}失败: ${err.message || err}`)); if (!res) return reject(new Error(`${desc}无响应`)); if (!res.success && res.status !== 200) { const errorMsg = res.message || (res.data ? JSON.stringify(res.data) : "未知错误"); return reject(new Error(`${desc}失败: ${errorMsg}`)); } resolve(res); }); }); }, sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms)), getNowStr: () => { const d = new Date(); const p = n => n < 10 ? '0' + n : n; return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; }, parseCodeList: (text) => text.split(/[\n,,]/).map(k => k.trim()).filter(k => k.length > 0), buildFileFormData: (file) => { const fd = new FormData(); fd.append('file', file); return fd; }, searchContractByCode: async (code) => { const res = await utils.requestPromise('POST', CONFIG.endpoints.searchContract, { "quickSearchValue": code, "quickSearchProperties": ["code"], "pageInfo": { "page": 1, "rows": 100 } }); return res.data?.rows?.find(r => r.code === code) || null; }, showStatus: (msg, type = 'success') => { const el = document.getElementById('yz-status-msg'); if (el) { el.innerHTML = msg; el.className = `yz-status ${type}`; } }, readExcelFile: (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { try { const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, { type: 'array' }); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const json = XLSX.utils.sheet_to_json(worksheet); resolve(json); } catch (error) { reject(error); } }; reader.onerror = (error) => reject(error); reader.readAsArrayBuffer(file); }); }, getFormData: () => { const getVal = (id) => { const el = document.getElementById(id); return el ? el.value.trim() : ''; }; const fileInput = document.getElementById('yz-in-file'); const files = fileInput && fileInput.files.length > 0 ? fileInput.files : []; const excelInput = document.getElementById('yz-in-data-excel'); const excelFile = excelInput && excelInput.files.length > 0 ? excelInput.files[0] : null; const overwriteScope = document.getElementById('yz-overwrite-scope') ? document.getElementById('yz-overwrite-scope').checked : false; const paymentRows = document.querySelectorAll('.yz-payment-row'); const payments = Array.from(paymentRows).map(row => { const getOptData = (sel) => { if (sel && sel.selectedIndex >= 0) { const opt = sel.options[sel.selectedIndex]; return { value: opt.value, name: opt.getAttribute('data-name') || opt.text }; } return { value: '', name: '' }; }; return { windUp: getOptData(row.querySelector('[data-type="windUp"]')), stage: getOptData(row.querySelector('[data-type="stage"]')), method: getOptData(row.querySelector('[data-type="method"]')), premise: row.querySelector('[id^="yz-in-pay-premise-"]').value.trim(), period: row.querySelector('[id^="yz-in-pay-period-"]').value.trim(), percent: row.querySelector('[id^="yz-in-pay-percent-"]').value.trim(), remark: row.querySelector('[id^="yz-in-pay-remark-"]').value.trim() }; }); const dynamicFields = {}; const dynContainers = ['yz-partya-form-area', 'yz-dynamic-form-area']; dynContainers.forEach(containerId => { const dynContainer = document.getElementById(containerId); if (dynContainer) { const inputs = dynContainer.querySelectorAll('input, select'); inputs.forEach(input => { if (input.tagName === 'SELECT' && input.dataset.label) { dynamicFields[input.dataset.label] = input.value; if (input.selectedIndex >= 0) { const opt = input.options[input.selectedIndex]; if (opt.dataset.rate) dynamicFields[input.dataset.label + "_rate"] = opt.dataset.rate; if (opt.dataset.code) dynamicFields[input.dataset.label + "_code"] = opt.dataset.code; if (opt.dataset.name) dynamicFields[input.dataset.label + "_name"] = opt.dataset.name; } } else if (input.dataset.label) { dynamicFields[input.dataset.label] = input.value; } }); } }); const flowDefKey = getVal('yz-in-flowKey'); const autoSubmit = document.getElementById('yz-in-auto-submit').checked; return { dynamicFields: dynamicFields, payments: payments, files: files, excelFile: excelFile, flow: { key: flowDefKey, autoSubmit: autoSubmit }, userScopes: state.userScopes, overwriteScope: overwriteScope, tax: { code: null } }; }, transformExcelDataToStructure: (res, formData, excelRow, fieldMap) => { if (!res || !res.data) return null; if (Array.isArray(res.data)) throw new Error("获取的合同详情数据异常(返回了数组)"); const data = res.data; const additional = data.additional || {}; const parties = data.contractParties || []; const partyA = parties.find(p => p.partyType === 'A') || {}; const partyB = parties.find(p => p.partyType === 'B') || {}; const rowName = excelRow['合同名称'] || data.name; const rowSignAddr = excelRow['合同签订地点'] || additional.signAddress || ''; const rowLegal = excelRow['乙方法人代表'] || ''; const rowAgent = excelRow['乙方代理人姓名'] || ''; const rowTaxType = excelRow['乙方纳税人身份'] || ''; const rowTaxCode = excelRow['乙方纳税人识别号'] || ''; const rowBank = excelRow['乙方开户行'] || ''; const rowAccount = excelRow['乙方开户行账号'] || ''; const rowPhone = excelRow['乙方联系电话'] || ''; const rowEmail = excelRow['乙方联系邮箱'] || ''; const rowAddr = excelRow['乙方地址'] || ''; let finalScopes = additional.contractScopes || []; if (formData.userScopes && formData.userScopes.length > 0) { const newScopes = formData.userScopes.map(scope => ({ contractCode: data.code, useAreaCode: scope.area.code, useAreaName: scope.area.name, useOriginalCode: scope.org.code, useOriginalName: scope.org.name, id: null, contractId: data.id })); finalScopes = formData.overwriteScope ? newScopes : [...finalScopes, ...newScopes]; } const dynamicElm = {}; const dynamicValueMap = {}; const dynamicTextMap = {}; let taxCodeVal = null, taxNameVal = null, taxRateVal = null; let tradeCodeVal = null, tradeNameVal = null, tradeAddVal = null; let transCodeVal = null; const getExcelVal = (keywords) => { const key = Object.keys(excelRow).find(k => keywords.some(kw => k.includes(kw))); return key ? excelRow[key] : undefined; }; let tradeAddContent = getExcelVal(['国际贸易条款补充', '贸易条款补充', '补充内容']); if (tradeAddContent) { tradeAddVal = String(tradeAddContent); } for (const [label, code] of Object.entries(fieldMap)) { let val = undefined; if (excelRow.hasOwnProperty(label)) { val = excelRow[label]; } if (val === undefined || val === null || val === '') { val = formData.dynamicFields[label]; } if (label.includes("税率")) { taxCodeVal = formData.dynamicFields[label + "_code"] || val; taxNameVal = formData.dynamicFields[label + "_name"]; taxRateVal = formData.dynamicFields[label + "_rate"]; } if (label.includes("贸易条款") && !label.includes("补充")) { tradeCodeVal = val; tradeNameVal = formData.dynamicFields[label + "_name"]; } if (label.includes("补充条款") || (label.includes("贸易") && label.includes("补充"))) { tradeAddVal = val; } if (label.includes("运输条款")) { transCodeVal = val; } if (val !== undefined && val !== null) { val = String(val); dynamicElm[code] = val; dynamicValueMap[code] = val; dynamicTextMap[code] = val; } } return { contract: { ...data, name: rowName, contractId: data.id, additional: null, partyAiCode: partyA.partyCode, partyBiCode: partyB.partyCode, elm: { ...dynamicElm, signAddress: rowSignAddr, partyBi: partyB.partyName, mysideName: partyA.partyName, partyBiCode: partyB.partyCode, mysideCode: partyA.partyCode, mysideId: partyA.partyId, partyBiName: partyB.partyName, partyBiId: partyB.partyId, myside: partyA.partyName }, contractParties: null, contractScopes: null, contractProjects: null }, elementValueMap: dynamicValueMap, elementTextMap: dynamicTextMap, contractParties: parties.map(p => p.partyType === 'B' ? { ...p, legalPerson: rowLegal, agentName: rowAgent, taxpayerType: rowTaxType, taxpayerCode: rowTaxCode, openedBankName: rowBank, openedBankAccount: rowAccount, telPhone: rowPhone, email: rowEmail, partyAddress: rowAddr } : p), contractAdditional: { ...additional, signAddress: rowSignAddr, agreementSignAddress: rowSignAddr, transportationType: transCodeVal || additional.transportationType, taxCode: taxCodeVal || additional.taxCode, taxName: taxNameVal || additional.taxName, taxRate: taxRateVal ? parseFloat(taxRateVal) : additional.taxRate, internationalTradeTermsCode: tradeCodeVal || additional.internationalTradeTermsCode, internationalTradeTermsName: tradeNameVal || additional.internationalTradeTermsName, internationalTradeTermsAdditional: tradeAddVal || additional.internationalTradeTermsAdditional, contractScopes: finalScopes }, temporary: true }; }, toggleInputs: (disabled) => { const ids = ['yz-template-select', 'yz-search-input', 'yz-btn-search', 'yz-btn-update-excel', 'yz-btn-add-payment', 'yz-in-auto-submit', 'yz-in-flowKey', 'yz-manual-contract-code', 'yz-btn-manual-submit']; ids.forEach(id => { const el = document.getElementById(id); if (el) el.disabled = disabled; }); ['yz-dynamic-form-area', 'yz-partya-form-area'].forEach(areaId => { const dynArea = document.getElementById(areaId); if (dynArea) { dynArea.querySelectorAll('input, select').forEach(el => el.disabled = disabled); } }); } }; const actions = { init: () => { state.xsid = utils.autoGetXsid(); console.log("YunZhuHelper: Init V9.60 (Fix Upload Retry)..."); actions.fetchTreeData(); actions.fetchDefaultPartyA(); actions.injectLauncher(); }, injectLauncher: () => { let tryCount = 0; const checkExist = setInterval(() => { tryCount++; const headerRight = document.querySelector('.header-layout-right'); if (headerRight) { clearInterval(checkExist); const launcher = document.createElement('div'); launcher.className = 'custom-tabs-item___hCi_u ant-dropdown-trigger yz-launcher-tab'; launcher.style.marginRight = '15px'; launcher.innerHTML = `
`; launcher.onclick = () => { const panel = document.getElementById('yz-helper-panel'); if (panel) { panel.style.display = 'flex'; } }; headerRight.insertBefore(launcher, headerRight.firstChild); console.log("[YzHelper] 助手按钮已成功注入到右上角导航栏"); } else if (tryCount >= 10) { clearInterval(checkExist); console.warn("[YzHelper] 找不到顶部菜单定位点,启用全局悬浮按钮兜底"); const floatBtn = document.createElement('div'); floatBtn.innerHTML = '助手'; floatBtn.style.cssText = ` position: fixed; top: 50%; right: 0; transform: translateY(-50%); background: #1890ff; color: white; padding: 15px 8px; border-radius: 8px 0 0 8px; cursor: pointer; z-index: 99999; box-shadow: -2px 0 12px rgba(0,0,0,0.15); font-size: 14px; writing-mode: vertical-rl; text-orientation: upright; font-weight: bold; transition: background-color 0.3s; `; floatBtn.onmouseover = () => floatBtn.style.backgroundColor = '#40a9ff'; floatBtn.onmouseout = () => floatBtn.style.backgroundColor = '#1890ff'; floatBtn.onclick = () => { const panel = document.getElementById('yz-helper-panel'); if (panel) { panel.style.display = 'flex'; } }; document.body.appendChild(floatBtn); } }, 1000); }, fetchDefaultPartyA: () => { utils.request('GET', CONFIG.endpoints.getAllCorporations, null, (err, data) => { if (err || !data || !data.success) { console.error("Fetch PartyA Error", err, data); return; } if (Array.isArray(data.data)) { const target = data.data.find(c => c.code === "100"); if (target) { state.defaultPartyA = target; console.log("Default PartyA Loaded:", target.name); } } }); }, fetchTreeData: () => { utils.request('GET', CONFIG.endpoints.getAreas, null, (err, data) => { if (!err && data.success) state.areaTreeData = data.data || []; }); utils.request('GET', CONFIG.endpoints.getOrgs, null, (err, data) => { if (!err && data.success) state.orgTreeData = data.data || []; }); }, getAvailableFlows: async (contractId, contractName) => { utils.showStatus("正在获取可用审批流...", "processing"); try { const reqRes = await utils.requestPromise('POST', CONFIG.endpoints.createRequest, { contractId: contractId, name: contractName || "FlowQuery", flowTypeId: null, requestId: null }, "创建流程请求(用于获取列表)"); if (reqRes.success && reqRes.data && reqRes.data.requestId) { const requestId = reqRes.data.requestId; const dynModelCode = reqRes.data.businessModelCode; const flowRes = await utils.requestPromise('POST', CONFIG.endpoints.startFlow, { businessKey: requestId, businessModelCode: dynModelCode, typeId: null, flowDefKey: null }, "获取审批流列表"); if (flowRes.success && flowRes.data) { const raw = flowRes.data; let list = []; if (raw.flowTypeList && Array.isArray(raw.flowTypeList)) list = raw.flowTypeList; else if (Array.isArray(raw)) list = raw; return list; } } } catch (e) { console.error("Get Flows Error", e); utils.showStatus("获取审批流列表失败", "error"); } return []; }, promptFlowSelection: (contractName, flowList) => { return new Promise((resolve) => { if (!flowList || flowList.length === 0) { alert("未找到可用的审批流,无法自动发起审批。"); resolve(null); return; } const overlay = document.createElement('div'); overlay.className = 'yz-modal-overlay'; const modal = document.createElement('div'); modal.className = 'yz-modal'; const header = document.createElement('div'); header.className = 'yz-modal-header'; header.innerText = `🔄 选择审批流程 - ${contractName}`; const body = document.createElement('div'); body.className = 'yz-modal-body'; const tip = document.createElement('div'); tip.style.marginBottom = '10px'; tip.style.color = '#666'; tip.style.fontSize = '12px'; tip.innerText = '请确认本次发起审批使用的流程定义。系统默认选中 UI 上选择的或第一个可用流程。'; body.appendChild(tip); const uiSelect = document.getElementById('yz-in-flowKey'); const userPrefKey = uiSelect ? uiSelect.value : null; let selectedKey = null; flowList.forEach((f, idx) => { const key = f.flowDefKey || f.key; const name = f.flowDefName || f.name; const row = document.createElement('label'); row.className = 'yz-flow-radio-item'; const radio = document.createElement('input'); radio.type = 'radio'; radio.name = 'yz_flow_choice'; radio.value = key; if (userPrefKey && key === userPrefKey) { radio.checked = true; selectedKey = key; } else if (!selectedKey && idx === 0) { radio.checked = true; selectedKey = key; } radio.onclick = () => { selectedKey = key; }; const text = document.createElement('span'); text.innerText = `${name} (${key})`; row.appendChild(radio); row.appendChild(text); body.appendChild(row); }); const footer = document.createElement('div'); footer.className = 'yz-modal-footer'; const cancelBtn = document.createElement('button'); cancelBtn.className = 'yz-btn'; cancelBtn.innerText = '跳过审批'; cancelBtn.onclick = () => { document.body.removeChild(overlay); resolve(null); }; const confirmBtn = document.createElement('button'); confirmBtn.className = 'yz-btn yz-btn-green'; confirmBtn.innerText = '确认流程并继续'; confirmBtn.onclick = () => { if (!selectedKey) return alert("请选择一个流程"); document.body.removeChild(overlay); resolve(selectedKey); }; footer.appendChild(cancelBtn); footer.appendChild(confirmBtn); modal.appendChild(header); modal.appendChild(body); modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay); }); }, generateMappingTable: async () => { try { console.log("[YzHelper] === 开始执行智能匹配 ==="); const fileInput = document.getElementById('yz-in-sign-file'); if (!fileInput) return alert("❌ 找不到 PDF 上传框,请检查 UI 代码是否替换完整!"); const files = fileInput.files && fileInput.files.length > 0 ? Array.from(fileInput.files) : []; if (files.length === 0) return alert("请先在上方 ① 处选择已盖章的 PDF 文件集合!"); console.log("[YzHelper] 已选择的 PDF 文件名:", files.map(f => f.name)); let rowData = []; const excelInput = document.getElementById('yz-in-sign-excel'); const textInput = document.getElementById('yz-manual-sign-code'); const rawText = textInput ? textInput.value.trim() : ""; if (excelInput && excelInput.files && excelInput.files.length > 0) { const btn = document.getElementById('yz-btn-generate-map'); const originalText = btn.innerText; btn.innerText = "解析 Excel 中..."; try { console.log("[YzHelper] 正在解析 Excel 文件..."); const xlsData = await utils.readExcelFile(excelInput.files[0]); console.log("[YzHelper] Excel 解析成功,共读取到 " + xlsData.length + " 行数据"); if (!xlsData || xlsData.length === 0) throw new Error("Excel 数据为空"); xlsData.forEach(row => { const code = (row['合同编号'] || row['合同编号/名称'] || row['编号'] || ''); const attachName = (row['合同附件名称'] || ''); if (code && String(code).trim()) { rowData.push({ code: String(code).trim(), attachName: String(attachName).trim() }); } }); console.log("[YzHelper] Excel 提取的列:", Object.keys(xlsData[0] || {})); } catch (e) { btn.innerText = originalText; console.error("[YzHelper] Excel 解析失败:", e); return alert("解析 Excel 失败:" + e.message + "\n(请确保文件没有损坏,且含有表头数据)"); } btn.innerText = originalText; } else if (rawText) { console.log("[YzHelper] 没有上传 Excel,正在读取文本框内容..."); const codes = utils.parseCodeList(rawText); rowData = codes.map(c => ({ code: c, attachName: '' })); } if (rowData.length === 0) { return alert("未获取到有效的合同编号!\n请确保上传的 Excel 中包含【合同编号】这一列,或者展开下方折叠面板手动粘贴编号。"); } console.log("[YzHelper] 最终提取到的合同数据:", rowData.map(r => ({ code: r.code, attachName: r.attachName }))); const tbody = document.getElementById('yz-mapping-tbody'); if (!tbody) return alert("❌ 找不到表格容器 (yz-mapping-tbody),请检查 HTML!"); tbody.innerHTML = ''; let optionsHtml = ``; files.forEach((f, idx) => { optionsHtml += ``; }); let autoMatchCount = 0; rowData.forEach((row, rowIndex) => { const tr = document.createElement('tr'); tr.dataset.code = row.code; tr.id = `yz-map-row-${rowIndex}`; let matchedIndex = -1; const matchKey = row.attachName || row.code; if (matchKey) { for (let i = 0; i < files.length; i++) { if (files[i].name.includes(matchKey)) { matchedIndex = i; break; } } } if (matchedIndex === -1) { const numericMatch = row.code.match(/\d{10,}/); if (numericMatch) { for (let i = 0; i < files.length; i++) { if (files[i].name.includes(numericMatch[0])) { matchedIndex = i; break; } } } } const selectEl = document.createElement('select'); selectEl.className = 'yz-input'; selectEl.style.width = '100%'; selectEl.innerHTML = optionsHtml; if (matchedIndex !== -1) { selectEl.value = matchedIndex; selectEl.style.backgroundColor = '#f6ffed'; autoMatchCount++; console.log(`[YzHelper] ✓ 已匹配: [${row.code}] 附件名[${row.attachName}] → [${files[matchedIndex].name}]`); } else { console.log(`[YzHelper] ✗ 未匹配: [${row.code}] 附件名[${row.attachName}], 文件名: [${files.map(f => f.name).join(', ')}]`); } const tdCode = document.createElement('td'); tdCode.innerText = row.code; const tdSelect = document.createElement('td'); tdSelect.appendChild(selectEl); const tdStatus = document.createElement('td'); tdStatus.className = 'res-cell'; tdStatus.style.textAlign = 'center'; tdStatus.innerText = '待确认'; tr.appendChild(tdCode); tr.appendChild(tdSelect); tr.appendChild(tdStatus); tbody.appendChild(tr); }); const container = document.getElementById('yz-mapping-container'); if (container) container.style.display = 'block'; console.log(`[YzHelper] === 智能匹配清单渲染完成 (自动匹配: ${autoMatchCount}/${rowData.length}) ===`); } catch (globalError) { console.error("[YzHelper] 生成清单时发生未知错误:", globalError); alert("发生系统错误:" + globalError.message + "\n请按 F12 查看控制台红字报错信息。"); } }, executeMappedUpload: async () => { const tbody = document.getElementById('yz-mapping-tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); if (rows.length === 0) return; const fileInput = document.getElementById('yz-in-sign-file'); const files = Array.from(fileInput.files); const tasks = []; for (let tr of rows) { const code = tr.dataset.code; const select = tr.querySelector('select'); const fileIndex = select.value; if (!fileIndex) { return alert(`合同 [${code}] 未选择对应的文件,请检查!`); } tasks.push({ rowId: tr.id, code: code, file: files[fileIndex] }); } if (!confirm(`即将开始回传 ${tasks.length} 个合同,请确认映射关系无误。`)) return; const btn = document.getElementById('yz-btn-execute-map'); const originalText = btn.innerText; btn.disabled = true; let successCount = 0, failCount = 0; const updateRowStatus = (rowId, st, msg) => { const td = document.getElementById(rowId).querySelector('.res-cell'); td.innerText = st === 'success' ? '✅ 成功' : `❌ ${msg}`; td.style.color = st === 'success' ? '#52c41a' : '#ff4d4f'; }; try { for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; btn.innerText = `处理中 (${i + 1}/${tasks.length})...`; document.getElementById(task.rowId).querySelector('.res-cell').innerText = '⏳ 上传中'; try { const c = await utils.searchContractByCode(task.code); if (!c) throw new Error(`查无此合同 [${task.code}]`); const signInfoRes = await utils.requestPromise('GET', `${CONFIG.endpoints.getSignInfo}?contractId=${c.id}`); const signInfo = signInfoRes.data?.signInfo; if (!signInfo || !signInfo.id) throw new Error("节点未达"); const f1 = utils.buildFileFormData(task.file); const uRes = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=false&toPdf=true`, f1); const docId = uRes.data?.docId; if (!docId) throw new Error("上传失败"); const payload = { ...signInfo, signType: "OFFLINE_SIGN", signDate: utils.getNowStr(), officialAnnex: true, signFileIdList: [docId], signFileList: [{ uid: "rc-upload-" + Date.now(), name: task.file.name, size: task.file.size, type: task.file.type || "application/pdf", status: "done", docId: docId, response: { data: { docId: docId, fileName: task.file.name, documentType: "Pdf" }, success: true } }] }; await utils.requestPromise('POST', CONFIG.endpoints.offlineSign, payload); updateRowStatus(task.rowId, 'success', ''); successCount++; } catch (e) { console.error(`[Map Upload] Failed: ${task.code}`, e); updateRowStatus(task.rowId, 'error', e.message); failCount++; } await utils.sleep(800); } alert(`回传完毕!\n成功: ${successCount} \n失败: ${failCount}`); } finally { btn.innerText = originalText; btn.disabled = false; } }, fetchTemplateFields: async (templateId) => { if (state.templateElementsCache && state.templateElementsCache[templateId]) { state.currentTemplateFields = state.templateElementsCache[templateId]; return state.templateFieldMaps[templateId]; } utils.showStatus(`加载模板定义...`, 'processing'); try { let res = await utils.requestPromise('GET', CONFIG.endpoints.getTemplateElements, { templateId: templateId }, "获取模板字段"); let elements = null; if (res.success) { if (Array.isArray(res.data)) elements = res.data; else if (res.data && Array.isArray(res.data.rows)) elements = res.data.rows; else if (res.data && Array.isArray(res.data.elements)) elements = res.data.elements; } if (elements && elements.length > 0) { const filteredElements = elements.filter(el => { const n = el.elementName ? el.elementName.trim() : ''; const cls = el.elementClassifyCode; const type = el.elementType; if (cls === 'YFXX') return false; if (type === 'ContractPartyA' || type === 'ContractPartyB') return false; if (type === 'ContractBaseElement') { const excludedNames = [ '合同名称', '合同编号', '甲方经办人', '合同签订地点', '合同签订日期', '合同使用主体', '合同使用范围', '厂家直采', '合同经销商', '所属机构' ]; if (excludedNames.some(ex => n.includes(ex))) return false; } return true; }); filteredElements.sort((a, b) => (a.sort || 0) - (b.sort || 0)); const map = {}; state.currentTemplateFields = filteredElements; state.templateElementsCache[templateId] = filteredElements; elements.forEach(el => { if (el.elementName) { map[el.elementName.trim()] = el.elementCode; } }); state.templateFieldMaps[templateId] = map; return map; } else { throw new Error("无字段定义"); } } catch (e) { console.error("Fetch Error", e); return {}; } }, renderDynamicForm: async () => { const tId = document.getElementById('yz-template-select').value; const container = document.getElementById('yz-dynamic-form-area'); const partyAContainer = document.getElementById('yz-partya-form-area'); if (!container || !partyAContainer) return; container.innerHTML = ''; partyAContainer.innerHTML = ''; if (!tId) { container.innerHTML = '
请先选择模板
'; state.currentTemplateFields = []; return; } await actions.fetchTemplateFields(tId); const elements = state.currentTemplateFields || []; if (CONFIG.uiInjections && CONFIG.uiInjections.length > 0) { CONFIG.uiInjections.forEach(rule => { const triggerElement = elements.find(e => e.elementCode === rule.triggerCode); const alreadyExists = elements.some(e => e.elementCode === rule.targetField.elementCode); if (triggerElement && !alreadyExists) { const newField = { ...rule.targetField }; newField.sort = (triggerElement.sort || 0) + 0.1; elements.push(newField); state.templateFieldMaps[tId][rule.targetField.elementName] = rule.targetField.elementCode; } }); elements.sort((a, b) => (a.sort || 0) - (b.sort || 0)); } if (elements.length === 0) { container.innerHTML = '
无需要手动填写的字段
'; return; } const linkageMap = {}; const dependentSet = new Set(); elements.forEach(el => { if (el.customData) { const opts = utils.parseJSONSafe(el.customData); if (opts && Array.isArray(opts)) { opts.forEach(opt => { const regex = /\$\{([\w\-]+)\}/g; let match; while ((match = regex.exec(opt.name)) !== null) { const depCode = match[1]; if (!linkageMap[el.elementCode]) linkageMap[el.elementCode] = {}; if (!linkageMap[el.elementCode][opt.value]) linkageMap[el.elementCode][opt.value] = []; linkageMap[el.elementCode][opt.value].push(depCode); dependentSet.add(depCode); } }); } } }); const renderField = (el, parentContainer) => { const name = el.elementName.trim(); const code = el.elementCode; const wrapper = document.createElement('div'); wrapper.className = 'yz-form-item'; if (dependentSet.has(code)) { wrapper.classList.add('dependent-field'); wrapper.style.display = 'none'; wrapper.dataset.dependent = code; } else if (name.length > 8) { wrapper.style.gridColumn = "span 2"; } const isReq = el.required === true || el.required === 'true'; const label = document.createElement('span'); label.className = `yz-form-label ${isReq ? 'required' : ''}`; label.innerText = name; wrapper.appendChild(label); let input; const createSelect = () => { const sel = document.createElement('select'); sel.className = 'yz-input'; sel.innerHTML = ''; return sel; }; let implicitMatch = CONFIG.implicitFields.find(f => f.match(code, name)); if (implicitMatch) { input = createSelect(); if (implicitMatch.domId) input.id = implicitMatch.domId; input.addEventListener('focus', () => actions.loadFromInterface(name, { dataSourceUrl: implicitMatch.api.dataSourceUrl, requestMethod: implicitMatch.api.requestMethod, params: implicitMatch.api.params }, input)); } else if (el.customData && (el.sourceType === 'CUSTOM' || el.showType === 'selectAndInput' || el.showType === 'select' || el.showType === 'radio')) { input = createSelect(); const opts = utils.parseJSONSafe(el.customData); if (opts && Array.isArray(opts)) { opts.forEach(o => { const option = document.createElement('option'); option.value = o.value; option.text = o.name.replace(/\$\{[^}]+\}/g, '').replace(/""/g, '').trim(); input.appendChild(option); }); const def = opts.find(o => o.isDefault); if (def) input.value = def.value; } } else if (el.sourceType === 'INTERFACE') { input = createSelect(); input.addEventListener('focus', () => actions.loadFromInterface(name, el, input)); } else if (name.includes("运输条款") && !el.customData) { input = createSelect(); input.innerHTML = ''; } else if (el.dictionaryCode) { input = createSelect(); input.addEventListener('focus', () => actions.loadFromInterface(name, { params: JSON.stringify({ dictCode: el.dictionaryCode }) }, input)); } else if (el.showType === 'select' || el.showType === 'radio') { input = createSelect(); } else { input = document.createElement('input'); input.className = 'yz-input'; input.type = 'text'; } input.dataset.label = name; input.dataset.code = code; if (state.defaultPartyA) { const pa = state.defaultPartyA; if (code === 'partyAiName') { /* 通常是选择框,暂不处理文本值以免冲突 */ } else if (code === 'partyAiLegalPerson') input.value = pa.legalRepresentative || ''; else if (code === 'partyAiTelPhone') input.value = pa.telephone || ''; else if (code === 'partyAiTaxpayerCode') input.value = pa.businessLicence || ''; else if (code === 'partyAiOpenedBankName') input.value = pa.bankName || ''; else if (code === 'partyAiOpenedBankAccount') input.value = pa.ownerAccount || ''; else if (code === 'partyAiPartyAddress') input.value = pa.registrationAddress || ''; } if (el.showValue && !input.value) { if (input.tagName === 'SELECT') { input.value = el.showValue; if (!input.value && el.showValue) { for (let i = 0; i < input.options.length; i++) { if (input.options[i].text === el.showValue || input.options[i].text.includes(el.showValue)) { input.selectedIndex = i; break; } } } if (!input.value) input.dataset.pendingDefault = el.showValue; } else { input.value = el.showValue; } } wrapper.appendChild(input); parentContainer.appendChild(wrapper); if (linkageMap[code]) { input.addEventListener('change', (e) => { const val = e.target.value; const targetCodes = linkageMap[code][val] || []; let allPotential = []; Object.values(linkageMap[code]).forEach(arr => allPotential = allPotential.concat(arr)); allPotential.forEach(dep => { const depWrapper = document.querySelector(`.yz-form-item[data-dependent="${dep}"]`); if (depWrapper) { depWrapper.style.display = targetCodes.includes(dep) ? 'flex' : 'none'; } }); }); setTimeout(() => input.dispatchEvent(new Event('change')), 100); } if (el.mapRelation) { try { const mapRel = JSON.parse(el.mapRelation); if (mapRel && mapRel.map && mapRel.map.length > 0) { input.addEventListener('change', function () { if (this.tagName === 'SELECT' && this.selectedIndex >= 0) { const opt = this.options[this.selectedIndex]; if (opt && opt.dataset.fullData) { const data = JSON.parse(opt.dataset.fullData); mapRel.map.forEach(mapping => { const sourceKey = mapping.apiCode; const targetCode = mapping.businessCode; const targetInput = document.getElementById('yz-dynamic-form-area').querySelector(`[data-code="${targetCode}"]`); if (targetInput) { const val = data[sourceKey]; targetInput.value = val || ''; targetInput.dispatchEvent(new Event('change')); targetInput.style.transition = 'background-color 0.3s'; targetInput.style.backgroundColor = '#d9f7be'; setTimeout(() => targetInput.style.backgroundColor = '', 800); } }); } } }); } } catch (e) { console.warn("MapRelation parse error", e); } } }; const elementMap = {}; elements.forEach(e => elementMap[e.elementCode] = e); elements.forEach(el => { if (dependentSet.has(el.elementCode)) return; const isPartyA = el.elementClassifyCode === 'JFXX' || el.elementCode.startsWith('partyAi'); const targetContainer = isPartyA ? partyAContainer : container; renderField(el, targetContainer); if (linkageMap[el.elementCode]) { const myDependents = new Set(); Object.values(linkageMap[el.elementCode]).forEach(arr => arr.forEach(c => myDependents.add(c))); myDependents.forEach(depCode => { const depEl = elementMap[depCode]; if (depEl) { const isDepPartyA = depEl.elementClassifyCode === 'JFXX' || depEl.elementCode.startsWith('partyAi'); renderField(depEl, isDepPartyA ? partyAContainer : container); } }); } }); utils.showStatus(`动态表单就绪`); }, loadFromInterface: (key, elDef, inputEl) => { if (inputEl.options.length > 1) return; if (state.loadingDicts[key]) return; state.loadingDicts[key] = true; inputEl.innerHTML = ''; const method = elDef.requestMethod || 'GET'; let url = elDef.dataSourceUrl; let params = typeof elDef.params === 'string' ? (utils.parseJSONSafe(elDef.params) || {}) : (elDef.params || {}); if (url) { if (!url.startsWith('/')) url = '/' + url; if (!url.startsWith('/api-gateway') && !url.startsWith('http')) { url = '/api-gateway' + url; } utils.request(method, url, params, (err, data) => { actions.handleInterfaceResponse(key, inputEl, err, data); }); } else if (params.dictCode) { utils.request('GET', CONFIG.endpoints.getDict, { dictCode: params.dictCode }, (err, data) => { actions.handleInterfaceResponse(key, inputEl, err, data); }); } else { state.loadingDicts[key] = false; inputEl.innerHTML = ''; } }, handleInterfaceResponse: (key, inputEl, err, data) => { state.loadingDicts[key] = false; if (!err && data.success) { inputEl.innerHTML = ''; let list = []; if (Array.isArray(data.data)) list = data.data; else if (data.data && Array.isArray(data.data.rows)) list = data.data.rows; else if (data.data && Array.isArray(data.data.list)) list = data.data.list; list.forEach(i => { const opt = document.createElement('option'); const val = i.id || i.code || i.value || i.dataValue; const txt = i.name || i.text || i.dataName || val; if (val !== undefined && val !== null) { opt.value = val; opt.text = txt; opt.dataset.fullData = JSON.stringify(i); opt.dataset.name = txt; opt.dataset.code = val; inputEl.appendChild(opt); } }); const pendingVal = inputEl.dataset.pendingDefault; if (pendingVal) { inputEl.value = pendingVal; if (!inputEl.value) { for (let i = 0; i < inputEl.options.length; i++) { if (inputEl.options[i].text === pendingVal || inputEl.options[i].text.includes(pendingVal)) { inputEl.selectedIndex = i; break; } } } } } else { inputEl.innerHTML = ''; console.error("Interface Load Error", err || data); } }, openTreeSelector: (title, treeData, onSelect) => { if (!treeData || treeData.length === 0) { actions.fetchTreeData(); return alert("数据正在加载中或为空,请等待 1-2 秒后重试..."); } const overlay = document.createElement('div'); overlay.className = 'yz-modal-overlay'; const modal = document.createElement('div'); modal.className = 'yz-modal'; modal.style.height = '600px'; const header = document.createElement('div'); header.className = 'yz-modal-header'; header.innerText = title; const body = document.createElement('div'); body.className = 'yz-modal-body'; const searchInput = document.createElement('input'); searchInput.className = 'yz-input yz-tree-search'; searchInput.placeholder = '输入关键词搜索...'; body.appendChild(searchInput); const treeContainer = document.createElement('div'); body.appendChild(treeContainer); const renderTree = (nodes, parentUl) => { nodes.forEach(node => { const li = document.createElement('li'); li.className = 'yz-tree-node'; li.dataset.name = node.name; const content = document.createElement('div'); content.className = 'yz-tree-content'; const hasChildren = node.children && node.children.length > 0; const toggle = document.createElement('span'); toggle.className = 'yz-tree-toggle'; toggle.innerText = hasChildren ? '+' : '•'; if (!hasChildren) toggle.style.visibility = 'hidden'; content.appendChild(toggle); const label = document.createElement('span'); label.className = 'yz-tree-label'; label.innerText = node.name; content.appendChild(label); li.appendChild(content); parentUl.appendChild(li); if (hasChildren) { const childUl = document.createElement('ul'); childUl.className = 'yz-tree-ul yz-tree-children'; renderTree(node.children, childUl); li.appendChild(childUl); toggle.onclick = (e) => { e.stopPropagation(); const isHidden = childUl.style.display !== 'block'; childUl.style.display = isHidden ? 'block' : 'none'; toggle.innerText = isHidden ? '-' : '+'; }; } label.onclick = () => { onSelect({ code: node.code || node.id, name: node.name }); document.body.removeChild(overlay); }; }); }; const rootUl = document.createElement('ul'); rootUl.className = 'yz-tree-ul root'; renderTree(treeData, rootUl); treeContainer.appendChild(rootUl); searchInput.addEventListener('input', (e) => { const k = e.target.value.trim().toLowerCase(); treeContainer.querySelectorAll('.yz-tree-node').forEach(n => { n.style.display = (!k || n.dataset.name.toLowerCase().includes(k)) ? 'block' : 'none'; if (k && n.style.display === 'block') { let p = n.parentElement; while (p && p.classList.contains('yz-tree-children')) { p.style.display = 'block'; p = p.parentElement.parentElement; } } }); }); const footer = document.createElement('div'); footer.className = 'yz-modal-footer'; const closeBtn = document.createElement('button'); closeBtn.className = 'yz-btn'; closeBtn.innerText = '关闭'; closeBtn.onclick = () => document.body.removeChild(overlay); footer.appendChild(closeBtn); modal.appendChild(header); modal.appendChild(body); modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay); }, addScope: () => { const areaInput = document.getElementById('yz-in-area'); const orgInput = document.getElementById('yz-in-org'); if (!areaInput.dataset.code || !orgInput.dataset.code) return alert("请先选择区域和组织"); state.userScopes.push({ area: { code: areaInput.dataset.code, name: areaInput.value }, org: { code: orgInput.dataset.code, name: orgInput.value } }); actions.renderScopeList(); areaInput.value = ''; areaInput.dataset.code = ''; orgInput.value = ''; orgInput.dataset.code = ''; }, removeScope: (index) => { state.userScopes.splice(index, 1); actions.renderScopeList(); }, renderScopeList: () => { const container = document.getElementById('yz-scope-list'); container.innerHTML = ''; state.userScopes.forEach((s, idx) => { const div = document.createElement('div'); div.className = 'yz-scope-item'; div.innerHTML = `
${s.area.name} - ${s.org.name}
删除
`; container.appendChild(div); }); }, promptUserForApprovers: (contractName, candidatesData) => { return new Promise((resolve) => { if (!candidatesData || candidatesData.length === 0) { resolve([]); return; } const overlay = document.createElement('div'); overlay.className = 'yz-modal-overlay'; const modal = document.createElement('div'); modal.className = 'yz-modal'; const header = document.createElement('div'); header.className = 'yz-modal-header'; header.innerText = `📋 确认审批人 - ${contractName}`; const body = document.createElement('div'); body.className = 'yz-modal-body'; candidatesData.forEach(node => { const group = document.createElement('div'); group.className = 'yz-node-group'; const title = document.createElement('div'); title.className = 'yz-node-title'; if (node.isError) title.classList.add('error-node'); title.innerHTML = `${node.nodeName} (${node.nodeId})`; group.appendChild(title); if (node.isError) { const errorTip = document.createElement('div'); errorTip.className = 'yz-error-tip'; errorTip.innerText = '⚠️ 系统计算失败,请检查流程配置。'; group.appendChild(errorTip); } const uniqueUsers = []; const seenIds = new Set(); if (node.users && node.users.length > 0) { node.users.forEach(u => { if (!seenIds.has(u.id)) { seenIds.add(u.id); uniqueUsers.push(u); } }); } if (uniqueUsers.length > 0) { uniqueUsers.forEach(user => { const label = document.createElement('label'); label.className = 'yz-user-item'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.value = user.id; checkbox.dataset.name = user.name; checkbox.checked = true; if (!user.id || user.id === 'Auto' || user.id.length < 5) { checkbox.disabled = true; checkbox.checked = false; } label.appendChild(checkbox); const infoDiv = document.createElement('div'); let infoHtml = `
${user.name}`; if (user.positionName) { infoHtml += ` ${user.positionName}`; } infoHtml += `
`; if (user.organizationName) { infoHtml += `
${user.organizationName}
`; } infoDiv.innerHTML = infoHtml; label.appendChild(infoDiv); group.appendChild(label); }); } else { const empty = document.createElement('div'); empty.style.color = 'red'; empty.innerText = '❌ 无候选人'; group.appendChild(empty); } group.dataset.nodeId = node.nodeId; group.dataset.userVarName = node.userVarName || ""; group.dataset.flowTaskType = node.flowTaskType; body.appendChild(group); }); const footer = document.createElement('div'); footer.className = 'yz-modal-footer'; const cancelBtn = document.createElement('button'); cancelBtn.className = 'yz-btn'; cancelBtn.innerText = '跳过'; cancelBtn.onclick = () => { document.body.removeChild(overlay); resolve(null); }; const confirmBtn = document.createElement('button'); confirmBtn.className = 'yz-btn'; confirmBtn.innerText = '提交'; confirmBtn.onclick = () => { const res = []; body.querySelectorAll('.yz-node-group').forEach(g => { const nodeId = g.dataset.nodeId; const userVarName = g.dataset.userVarName; const flowTaskType = g.dataset.flowTaskType; const checked = Array.from(g.querySelectorAll('input[type="checkbox"]:checked')); if (checked.length) { res.push({ actTaskDefKey: nodeId, executorIds: checked.map(c => c.value).join(','), executorNames: checked.map(c => c.dataset.name).join(','), instancyStatus: false, nodeType: flowTaskType === 'countersign' ? 'CounterSign' : 'Approve', nodeId: nodeId, userVarName: userVarName, flowTaskType: flowTaskType, userIds: checked.map(c => c.value).join(','), solidifyFlow: true }); } else { res.push({ actTaskDefKey: nodeId, executorIds: "", executorNames: "", instancyStatus: false, nodeType: flowTaskType === 'countersign' ? 'CounterSign' : 'Approve', nodeId: nodeId, userVarName: userVarName, flowTaskType: flowTaskType, userIds: "", solidifyFlow: true }); } }); document.body.removeChild(overlay); resolve(res); }; footer.appendChild(cancelBtn); footer.appendChild(confirmBtn); modal.appendChild(header); modal.appendChild(body); modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay); }); }, exportToExcel: () => { if (!state.exportData.length) return alert("无数据导出"); const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet(state.exportData); XLSX.utils.book_append_sheet(wb, ws, "结果"); XLSX.writeFile(wb, `合同导出_${new Date().toISOString().slice(0, 10)}.xlsx`); }, updatePaymentRowIndices: () => document.querySelectorAll('.yz-payment-row').forEach((r, i) => r.querySelector('.yz-payment-title-text').textContent = `付款节点 #${i + 1}`), updateSelect: (id, items, k, v) => { const sel = document.getElementById(id); if (!sel) return; sel.innerHTML = ''; items.forEach(i => { const opt = document.createElement('option'); opt.value = i[k]; opt.text = i[v]; sel.appendChild(opt); }); }, handlePaymentLogic: (targetInput) => { const allPercents = Array.from(document.querySelectorAll('.yz-pay-percent')); const idx = allPercents.indexOf(targetInput); if (idx === -1) return; let val = parseFloat(targetInput.value); if (val > 100) { targetInput.value = 100; val = 100; } for (let i = idx; i < allPercents.length - 1; i++) { const curr = parseFloat(allPercents[i].value); const nextInput = allPercents[i + 1]; const next = parseFloat(nextInput.value) || 0; if (!isNaN(curr) && (isNaN(next) || next < curr)) { nextInput.value = curr; nextInput.style.backgroundColor = '#fff1f0'; setTimeout(() => nextInput.style.backgroundColor = '', 300); } } }, addPaymentRow: () => { state.paymentRowCounter++; const uid = state.paymentRowCounter; const c = document.getElementById('yz-payment-container'); const d = document.createElement('div'); d.className = 'yz-payment-row'; d.id = `yz-pay-row-${uid}`; d.innerHTML = `
付款节点 #?
计算方式
付款阶段
付款方式
付款前提
账期 (天)
付款比例至 (%)
备注
`; c.appendChild(d); actions.updatePaymentRowIndices(); d.querySelector('.yz-btn-danger').onclick = () => { d.remove(); actions.updatePaymentRowIndices(); const remaining = document.querySelectorAll('.yz-pay-percent'); if (remaining.length > 0) actions.handlePaymentLogic(remaining[0]); }; const bf = (id, k, s, sk) => document.getElementById(id).addEventListener('focus', () => { if (state[sk] && state[sk].length) return actions.updateSelect(id, state[sk], 'dataValue', 'dataName'); if (state.loadingDicts[k]) return; state.loadingDicts[k] = true; utils.request('GET', CONFIG.endpoints.getDict, { dictCode: s }, (err, data) => { state.loadingDicts[k] = false; if (!err && data.success) { state[sk] = data.data; actions.updateSelect(id, state[sk], 'dataValue', 'dataName'); } }); }); bf(`yz-in-pay-windUp-${uid}`, `windUp-${uid}`, 'windUpMethod', 'windUpMethods'); bf(`yz-in-pay-stage-${uid}`, `stage-${uid}`, 'paymentStage', 'paymentStages'); bf(`yz-in-pay-method-${uid}`, `method-${uid}`, 'paymentMethod', 'paymentMethods'); const percentInput = d.querySelector('.yz-pay-percent'); percentInput.addEventListener('input', function () { actions.handlePaymentLogic(this); }); percentInput.addEventListener('change', function () { const allPercents = Array.from(document.querySelectorAll('.yz-pay-percent')); const idx = allPercents.indexOf(this); if (idx > 0) { const prev = parseFloat(allPercents[idx - 1].value) || 0; const curr = parseFloat(this.value) || 0; if (curr < prev) { this.value = prev; actions.handlePaymentLogic(this); } } }); }, manualSubmitApproval: async () => { const rawText = document.getElementById('yz-manual-contract-code').value.trim(); if (!rawText) return alert("请输入合同编号或名称"); const keys = utils.parseCodeList(rawText); if (keys.length === 0) return alert("请输入有效的合同编号"); if (!confirm(`识别到 ${keys.length} 个合同,将依次发起审批。\n\n提示:系统会自动复用第一份合同选择的审批流程和人员,请确保这些合同的审批逻辑一致。`)) return; const btn = document.getElementById('yz-btn-manual-submit'); const originalText = btn.innerText; btn.disabled = true; let successCount = 0; let failCount = 0; let cachedFlowKey = null; let cachedExecutors = null; try { for (let i = 0; i < keys.length; i++) { const codeOrName = keys[i]; btn.innerText = `处理中 (${i + 1}/${keys.length})...`; console.log(`[Batch Manual] Processing: ${codeOrName}`); try { const searchRes = await utils.requestPromise('POST', CONFIG.endpoints.searchContract, { "quickSearchValue": codeOrName, "pageInfo": { "page": 1, "rows": 10 } }, `查询合同[${codeOrName}]`); const contract = searchRes.data?.rows?.[0]; if (!contract) throw new Error(`未找到合同: ${codeOrName}`); if (!cachedFlowKey) { const flowList = await actions.getAvailableFlows(contract.id, contract.name); const selected = await actions.promptFlowSelection(contract.name, flowList); if (!selected) throw new Error("取消审批"); cachedFlowKey = selected; } const req = await utils.requestPromise('POST', CONFIG.endpoints.createRequest, { contractId: contract.id, name: contract.name }); if (!req.success || !req.data?.requestId) throw new Error("创建流程请求失败"); const requestId = req.data.requestId; const dynModelCode = req.data.businessModelCode; if (cachedExecutors) { await utils.requestPromise('POST', CONFIG.endpoints.saveSolidify, { businessModelCode: dynModelCode, businessId: requestId, executorsVos: JSON.stringify(cachedExecutors) }); } else { const pre = await utils.requestPromise('POST', CONFIG.endpoints.startFlowNew, { businessKey: requestId, businessModelCode: dynModelCode, typeId: null, flowDefKey: cachedFlowKey }); if (!pre.success || !pre.data?.flowDefinationId) throw new Error("预发起失败"); const ent = await utils.requestPromise('POST', `${CONFIG.endpoints.getEntity}?id=${pre.data.flowDefinationId}&versionCode=-1&businessId=${requestId}&businessModelCode=${dynModelCode}`, {}); let nodesMap = {}; try { nodesMap = JSON.parse(ent.data.defJson).process.nodes; } catch (e) { } const solidEx = ent.data?.solidifyExecutorOfOnly || {}; const flowNodeInfos = pre.data.nodeInfoList || []; const cands = []; for (let nid in nodesMap) { const node = nodesMap[nid]; if (node.type === 'UserTask' && !(node.nodeType === 'Normal' && node.name.includes("修改"))) { let u = []; let calcError = false; if (node.nodeConfig && node.nodeConfig.executor && Array.isArray(node.nodeConfig.executor)) { try { const rawExecutors = node.nodeConfig.executor.map(ex => { const newEx = { ...ex }; delete newEx.rowdata; if (ex.userType === "SelfDefinition") { newEx.ids = ex.selfDefId || ex.selfDefOfOrgAndSelId || null; newEx.customParam = ex.customParam || null; newEx.allowDefaultStarter = node.nodeConfig.normal?.allowDefaultStarter || null; newEx.repeatExecutorPass = node.nodeConfig.normal?.repeatExecutorPass || null; } else { if (newEx.ids === "") newEx.ids = null; if (newEx.customParam === "") newEx.customParam = null; } return newEx; }); if (rawExecutors.length > 0) { const calcPayload = { requestExecutorsVos: JSON.stringify(rawExecutors), businessModelCode: dynModelCode, businessId: requestId }; const calcRes = await utils.requestPromise('POST', CONFIG.endpoints.getCalculatedUsers, calcPayload, `计算${node.name}`); if (calcRes.success && calcRes.data && Array.isArray(calcRes.data)) { calcRes.data.forEach(user => { u.push({ id: user.userId || user.id, name: user.userName || user.name, positionName: user.positionName || '', organizationName: user.organizationName || '' }); }); } else { calcError = true; } } } catch (err) { calcError = true; } } if (u.length === 0 && solidEx[nid] && solidEx[nid].executorIds) { const ids = solidEx[nid].executorIds.split(','); const nms = solidEx[nid].executorNames.split(','); u = ids.map((id, x) => ({ id: id, name: nms[x] || '', positionName: '', organizationName: '' })); } let matchedVarName = null; if (flowNodeInfos) { const infoMatch = flowNodeInfos.find(n => n.id === nid); if (infoMatch) matchedVarName = infoMatch.userVarName; } if (u.length > 0 || calcError || (node.nodeConfig && node.nodeConfig.executor && node.nodeConfig.executor.length > 0)) { cands.push({ nodeId: nid, nodeName: node.name, users: u, flowTaskType: node.nodeType === 'CounterSign' ? 'countersign' : 'approve', userVarName: matchedVarName, isError: calcError }); } } } if (cands.length === 0) { const fallbackNodes = Object.keys(solidEx); fallbackNodes.forEach(nid => { let u = []; if (solidEx[nid] && solidEx[nid].executorIds) { const ids = solidEx[nid].executorIds.split(','); const nms = solidEx[nid].executorNames.split(','); u = ids.map((id, x) => ({ id: id, name: nms[x] || '', positionName: '', organizationName: '' })); } if (u.length) cands.push({ nodeId: nid, nodeName: solidEx[nid]?.actTaskDefKey || nid, users: u, flowTaskType: solidEx[nid]?.nodeType === 'CounterSign' ? 'countersign' : 'approve' }); }); } const sel = await actions.promptUserForApprovers(contract.code, cands); if (!sel || !sel.length) throw new Error("未选择审批人或取消"); cachedExecutors = sel; await utils.requestPromise('POST', CONFIG.endpoints.saveSolidify, { businessModelCode: dynModelCode, businessId: requestId, executorsVos: JSON.stringify(sel) }); } const startNodes = cachedExecutors.map(s => ({ nodeId: s.nodeId, userVarName: s.userVarName, flowTaskType: s.flowTaskType, userIds: s.userIds, solidifyFlow: true })); await utils.requestPromise('POST', CONFIG.endpoints.startFlowNew, { businessKey: requestId, businessModelCode: dynModelCode, typeId: null, flowDefKey: cachedFlowKey, taskList: JSON.stringify(startNodes) }); console.log(`[Batch Manual] Success: ${codeOrName}`); successCount++; } catch (e) { console.error(`[Batch Manual] Failed: ${codeOrName}`, e); if (i === 0 && e.message === "取消审批") { throw e; } failCount++; utils.showStatus(`合同 ${codeOrName} 失败: ${e.message}`, 'error'); await new Promise(r => setTimeout(r, 1000)); } } alert(`批量发起完成!\n成功: ${successCount}\n失败: ${failCount}`); } catch (e) { alert("操作终止: " + e.message); } finally { btn.innerText = originalText; btn.disabled = false; } }, manualUploadAttachments: async () => { const fileInput = document.getElementById('yz-in-file'); const files = fileInput && fileInput.files.length > 0 ? fileInput.files : []; if (files.length === 0) return alert("请先在上方选择要上传的附件文件!"); const rawText = document.getElementById('yz-manual-upload-code').value.trim(); if (!rawText) return alert("请输入需要上传附件的合同编号或名称"); const keys = utils.parseCodeList(rawText); if (keys.length === 0) return alert("请输入有效的合同编号"); if (!confirm(`准备为 ${keys.length} 个合同独立匹配并上传附件。`)) return; const btn = document.getElementById('yz-btn-manual-upload'); const originalText = btn.innerText; btn.disabled = true; let successCount = 0, failCount = 0, skipCount = 0; try { for (let i = 0; i < keys.length; i++) { btn.innerText = `上传中 (${i + 1}/${keys.length})...`; try { const sRes = await utils.requestPromise('POST', CONFIG.endpoints.searchContract, { "quickSearchValue": keys[i], "pageInfo": { "page": 1, "rows": 10 } }); const c = sRes.data?.rows?.[0]; if (!c) throw new Error(`未在系统中查到合同`); const matchedFiles = Array.from(files).filter(f => f.name.includes(c.code) || f.name.includes(c.contractCode) || (c.name && f.name.includes(c.name))); if (matchedFiles.length === 0) { skipCount++; continue; } for (let j = 0; j < matchedFiles.length; j++) { const file = matchedFiles[j]; const u1 = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=true&toPdf=false`, utils.buildFileFormData(file)); if (u1.data?.docId) { await utils.requestPromise('POST', CONFIG.endpoints.saveArchive, { contractId: c.id, contractOtherDocIds: [u1.data.docId], approveReferenceDocIds: [] }); await utils.sleep(1500); let u2 = null, uploadSuccess = false; for (let attempt = 1; attempt <= 3; attempt++) { try { if (attempt > 1) { btn.innerText = `PDF重试中 (${attempt}/3)...`; await utils.sleep(3000); } u2 = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=true&toPdf=true`, utils.buildFileFormData(file)); uploadSuccess = true; break; } catch (e) { if (attempt === 3) throw e; } } if (uploadSuccess && u2.data?.docId) await utils.requestPromise('POST', `${CONFIG.endpoints.saveArchiveBatch}?fileIds=${u2.data.docId}&relationType=1&entityId=${c.id}`, {}); } } successCount++; } catch (e) { failCount++; } await utils.sleep(500); } alert(`独立附件批量上传完成!\n\n成功: ${successCount}\n跳过(未匹配): ${skipCount}\n失败: ${failCount}`); } catch (e) { alert("操作终止: " + e.message); } finally { btn.innerText = originalText; btn.disabled = false; } }, generateAttachMappingTable: async () => { try { const fileInput = document.getElementById('yz-in-file'); if (!fileInput) return alert("找不到附件上传框!"); const files = fileInput.files && fileInput.files.length > 0 ? Array.from(fileInput.files) : []; if (files.length === 0) return alert("请先在上方选择附件文件!"); let rowData = []; const excelInput = document.getElementById('yz-in-attach-excel'); const textInput = document.getElementById('yz-manual-upload-code'); const rawText = textInput ? textInput.value.trim() : ""; if (excelInput && excelInput.files && excelInput.files.length > 0) { const btn = document.getElementById('yz-btn-generate-attach-map'); const originalText = btn.innerText; btn.innerText = "解析 Excel 中..."; try { const xlsData = await utils.readExcelFile(excelInput.files[0]); if (!xlsData || xlsData.length === 0) throw new Error("Excel 数据为空"); xlsData.forEach(row => { const code = (row['合同编号'] || ''); const attachName = (row['合同附件名称'] || ''); if (code && String(code).trim()) rowData.push({ code: String(code).trim(), attachName: String(attachName).trim() }); }); } catch (e) { btn.innerText = originalText; return alert("解析 Excel 失败:" + e.message); } btn.innerText = originalText; } else if (rawText) { rowData = utils.parseCodeList(rawText).map(c => ({ code: c, attachName: '' })); } if (rowData.length === 0) return alert("未获取到有效的合同编号!"); const tbody = document.getElementById('yz-attach-mapping-tbody'); if (!tbody) return alert("找不到表格容器!"); tbody.innerHTML = ''; let optionsHtml = ''; files.forEach((f, idx) => { optionsHtml += ``; }); let autoMatchCount = 0; rowData.forEach((row, rowIndex) => { const tr = document.createElement('tr'); tr.dataset.code = row.code; tr.id = `yz-attach-map-row-${rowIndex}`; let matchedIndex = -1; const matchKey = row.attachName || row.code; if (matchKey) { for (let i = 0; i < files.length; i++) { if (files[i].name.includes(matchKey)) { matchedIndex = i; break; } } } if (matchedIndex === -1) { const nm = row.code.match(/\d{10,}/); if (nm) { for (let i = 0; i < files.length; i++) { if (files[i].name.includes(nm[0])) { matchedIndex = i; break; } } } } const selectEl = document.createElement('select'); selectEl.className = 'yz-input'; selectEl.style.width = '100%'; selectEl.innerHTML = optionsHtml; if (matchedIndex !== -1) { selectEl.value = matchedIndex; selectEl.style.backgroundColor = '#f6ffed'; autoMatchCount++; } const tdCode = document.createElement('td'); tdCode.innerText = row.code; const tdSelect = document.createElement('td'); tdSelect.appendChild(selectEl); const tdStatus = document.createElement('td'); tdStatus.className = 'res-cell'; tdStatus.style.textAlign = 'center'; tdStatus.innerText = '待确认'; tr.appendChild(tdCode); tr.appendChild(tdSelect); tr.appendChild(tdStatus); tbody.appendChild(tr); }); const container = document.getElementById('yz-attach-mapping-container'); if (container) container.style.display = 'block'; console.log(`[AttachMap] 清单生成完成 (${autoMatchCount}/${rowData.length})`); } catch (e) { alert("发生错误:" + e.message); } }, executeAttachMappedUpload: async () => { const tbody = document.getElementById('yz-attach-mapping-tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); if (rows.length === 0) return; const fileInput = document.getElementById('yz-in-file'); const files = Array.from(fileInput.files); const tasks = []; for (let tr of rows) { const code = tr.dataset.code; const select = tr.querySelector('select'); const fileIndex = select.value; if (!fileIndex) return alert(`合同 [${code}] 未选择对应的文件!`); tasks.push({ rowId: tr.id, code, file: files[fileIndex] }); } if (!confirm(`即将上传 ${tasks.length} 个合同附件,确认?`)) return; const btn = document.getElementById('yz-btn-execute-attach-map'); const originalText = btn.innerText; btn.disabled = true; let successCount = 0, failCount = 0; const updateRowStatus = (rowId, st, msg) => { const td = document.getElementById(rowId).querySelector('.res-cell'); td.innerText = st === 'success' ? '成功' : `失败: ${msg}`; td.style.color = st === 'success' ? '#52c41a' : '#ff4d4f'; }; try { for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; btn.innerText = `上传中 (${i + 1}/${tasks.length})...`; document.getElementById(task.rowId).querySelector('.res-cell').innerText = '上传中'; try { const c = await utils.searchContractByCode(task.code); if (!c) throw new Error('查无此合同'); const u1 = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=true&toPdf=false`, utils.buildFileFormData(task.file)); if (!u1.data?.docId) throw new Error('上传失败'); await utils.requestPromise('POST', CONFIG.endpoints.saveArchive, { contractId: c.id, contractOtherDocIds: [u1.data.docId], approveReferenceDocIds: [] }); await utils.sleep(1500); let u2 = null, uploadSuccess = false; for (let attempt = 1; attempt <= 3; attempt++) { try { if (attempt > 1) { btn.innerText = `PDF重试 (${attempt}/3)...`; await utils.sleep(3000); } u2 = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=true&toPdf=true`, utils.buildFileFormData(task.file)); uploadSuccess = true; break; } catch (e) { if (attempt === 3) throw e; } } if (uploadSuccess && u2.data?.docId) { await utils.requestPromise('POST', `${CONFIG.endpoints.saveArchiveBatch}?fileIds=${u2.data.docId}&relationType=1&entityId=${c.id}`, {}); } updateRowStatus(task.rowId, 'success'); successCount++; } catch (e) { updateRowStatus(task.rowId, 'fail', e.message); failCount++; } } alert(`附件上传完成!\n\n成功: ${successCount}\n失败: ${failCount}`); } catch (e) { alert("操作终止: " + e.message); } finally { btn.innerText = originalText; btn.disabled = false; } }, manualOfflineSign: async () => { const fileInput = document.getElementById('yz-in-sign-file'); const files = fileInput && fileInput.files.length > 0 ? fileInput.files : []; if (files.length === 0) return alert("请先选择已盖章的 PDF 文件!"); const rawText = document.getElementById('yz-manual-sign-code').value.trim(); if (!rawText) return alert("请输入需要回传盖章文件的合同编号或名称"); const keys = utils.parseCodeList(rawText); if (keys.length === 0) return alert("请输入有效的合同编号"); if (!confirm(`准备为 ${keys.length} 个合同回传线下盖章文件。\n注意:请确保上方已选中所有对应的 PDF 文件,系统将根据文件名自动匹配。`)) return; const btn = document.getElementById('yz-btn-manual-sign'); const originalText = btn.innerText; btn.disabled = true; let successCount = 0, failCount = 0, skipCount = 0; try { for (let i = 0; i < keys.length; i++) { const codeOrName = keys[i]; btn.innerText = `回传中 (${i + 1}/${keys.length})...`; console.log(`[Offline Sign] Processing: ${codeOrName}`); try { const sRes = await utils.requestPromise('POST', CONFIG.endpoints.searchContract, { "quickSearchValue": codeOrName, "pageInfo": { "page": 1, "rows": 10 } }); const c = sRes.data?.rows?.[0]; if (!c) throw new Error(`未在系统中查到合同`); const matchedFiles = Array.from(files).filter(f => f.name.includes(c.code) || f.name.includes(c.contractCode) || (c.name && f.name.includes(c.name)) ); if (matchedFiles.length === 0) { utils.showStatus(`合同 ${codeOrName} 无匹配签章文件,跳过`, 'error'); skipCount++; continue; } const file = matchedFiles[0]; const signInfoRes = await utils.requestPromise('GET', `${CONFIG.endpoints.getSignInfo}?contractId=${c.id}`); const signInfo = signInfoRes.data; if (!signInfo || !signInfo.id) throw new Error("获取签章节点信息失败(可能尚未流转至签章节点)"); const f1 = utils.buildFileFormData(file); const uRes = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=false&toPdf=true`, f1, "上传签章PDF"); const docId = uRes.data?.docId; if (!docId) throw new Error("文件上传失败,未返回 docId"); const payload = { ...signInfo, signType: "OFFLINE_SIGN", signDate: utils.getNowStr(), officialAnnex: true, signFileIdList: [docId], signFileList: [{ uid: "rc-upload-" + Date.now(), name: file.name, size: file.size, type: file.type || "application/pdf", status: "done", docId: docId, response: { data: { docId: docId, fileName: file.name, documentType: "Pdf" }, success: true, error: false, message: "处理成功!" } }] }; await utils.requestPromise('POST', CONFIG.endpoints.offlineSign, payload, "提交签章回传"); console.log(`[Offline Sign] Success: ${codeOrName}`); utils.showStatus(`合同 ${codeOrName} 签章回传成功`); successCount++; } catch (e) { console.error(`[Offline Sign] Failed: ${codeOrName}`, e); failCount++; utils.showStatus(`合同 ${codeOrName} 失败: ${e.message}`, 'error'); } await utils.sleep(800); } alert(`线下签章批量回传完成!\n\n✅ 成功: ${successCount}\n⏭️ 跳过(未匹配到文件): ${skipCount}\n❌ 失败: ${failCount}`); } catch (e) { alert("操作终止: " + e.message); } finally { btn.innerText = originalText; btn.disabled = false; } }, searchAndExport: () => { const v = document.getElementById('yz-search-input').value.trim(); if (!v) return alert("请输入编号"); let tId = document.getElementById('yz-template-select').value; utils.toggleInputs(true); const btn = document.getElementById('yz-btn-search'); btn.textContent = '处理中...'; utils.request('POST', CONFIG.endpoints.searchWinBid, { "quickSearchValue": v, "quickSearchProperties": ["tenderProjectName", "tenderProjectCode", "signCorporationName", "signCorporationCode"], "pageInfo": { "page": 1, "rows": 100 }, "sourceSystem": "SECOND_VERSION" }, (err, data) => { if (err || !data.success) { btn.textContent = '查询并自动导出'; utils.toggleInputs(false); return utils.showStatus(data?.message || '查询失败', 'error'); } state.searchResults = (data.data.rows || []).map((r, i) => ({ ...r, _uiId: `row-${i}`, _status: 'pending' })); actions.renderTable(); if (state.searchResults.length > 0) { const sel = document.getElementById('yz-template-select'); if (sel && !sel.value && state.searchResults[0].contractTemplateId) { sel.value = state.searchResults[0].contractTemplateId; tId = state.searchResults[0].contractTemplateId; actions.renderDynamicForm(); } if (!tId && sel.options.length > 1) { sel.selectedIndex = 1; tId = sel.value; actions.renderDynamicForm(); } if (!tId) { btn.textContent = '查询并自动导出'; utils.toggleInputs(false); return alert("未选择合同模板"); } actions.createDraftOnly(tId, btn); } else { btn.textContent = '查询并自动导出'; utils.toggleInputs(false); utils.showStatus('无数据', 'error'); } }); }, createDraftOnly: async (templateId, searchBtn) => { const list = state.searchResults; state.exportData = []; let successCount = 0; for (let i = 0; i < list.length; i++) { const item = list[i]; actions.updateRowStatus(item._uiId, 'processing', '创建中...'); try { const cRes = await utils.requestPromise('POST', CONFIG.endpoints.createDraft, { "fixResultSubId": item.fixResultSubId, "templateId": templateId, "confide": false, "sourceSystem": "SECOND_VERSION" }, "创建草稿"); const cId = (typeof cRes.data === 'string') ? cRes.data : cRes.data?.id; const dRes = await utils.requestPromise('GET', CONFIG.endpoints.getContractDetail, { id: cId }, "详情"); const d = dRes.data; const pB = (d.contractParties || []).find(p => p.partyType === 'B') || {}; state.exportData.push({ "合同编号": d.contractCode || d.code, "合同名称": d.name || d.contractName, "不含税金额": d.additional?.amount || "", "合同签订地点": d.additional?.signAddress || "", "合同附件名称": "", "乙方名称": pB.partyName, "乙方法人代表": pB.legalPerson, "乙方代理人姓名": pB.agentName, "乙方纳税人身份": pB.taxpayerType, "乙方纳税人识别号": pB.taxpayerCode, "乙方开户行": pB.openedBankName, "乙方开户行账号": pB.openedBankAccount, "乙方联系电话": pB.telPhone, "乙方联系邮箱": pB.email, "乙方地址": pB.partyAddress }); actions.updateRowStatus(item._uiId, 'success', null, { code: d.contractCode || d.code, taxAmount: d.additional?.taxAmount || 0, stateRemark: "成功" }); successCount++; } catch (e) { actions.updateRowStatus(item._uiId, 'error', e.message); } await new Promise(r => setTimeout(r, 300)); } if (searchBtn) { searchBtn.textContent = "查询并自动导出"; } utils.toggleInputs(false); const tplSelect = document.getElementById('yz-template-select'); if (tplSelect) tplSelect.disabled = true; if (state.exportData.length) actions.exportToExcel(); utils.showStatus(`完成: ${successCount} / ${list.length}`); }, batchUpdateFromExcel: async () => { const fd = utils.getFormData(); if (!fd.excelFile) return alert("请上传Excel"); const taxInput = document.getElementById('yz-dynamic-tax'); if (taxInput && !taxInput.value) return alert("请选择税率"); if (!confirm(`确认更新?`)) return; utils.toggleInputs(true); const btn = document.getElementById('yz-btn-update-excel'); btn.textContent = "Processing..."; try { const xlsData = await utils.readExcelFile(fd.excelFile); if (!xlsData.length) throw new Error("Excel为空"); if (fd.files.length > 100) throw new Error("附件超过100个"); let successCount = 0, failCount = 0; let batchFlowKey = null; let batchExecutors = null; for (let i = 0; i < xlsData.length; i++) { const row = xlsData[i]; const code = String(row['合同编号'] || '').trim(); if (!code || code === '-' || code === 'undefined') continue; btn.textContent = `Processing (${i + 1}/${xlsData.length})...`; try { const sRes = await utils.requestPromise('POST', CONFIG.endpoints.searchContract, { "quickSearchValue": code, "pageInfo": { "page": 1, "rows": 100 } }); const c = sRes.data?.rows?.find(r => r.contractCode === code || r.code === code); if (!c) throw new Error(`未找到 ${code}`); const dRes = await utils.requestPromise('GET', CONFIG.endpoints.getContractDetail, { id: c.id }); let templateId = dRes.data.contractTemplateId; if (!templateId) { const uiTpl = document.getElementById('yz-template-select').value; if (uiTpl) { templateId = uiTpl; console.warn("合同未关联模板,使用UI选择的模板ID:", templateId); } else { throw new Error("合同未关联模板ID,且UI未选择模板"); } } const fieldMap = await actions.fetchTemplateFields(templateId); const upPayload = utils.transformExcelDataToStructure(dRes, fd, row, fieldMap); await utils.requestPromise('POST', CONFIG.endpoints.updateContract, upPayload, "更新信息"); const payPl = fd.payments.map(p => ({ "key": utils.generateUUID(), "contractId": c.id, "windUpMethodId": p.windUp.value, "windUpMethodCode": p.windUp.value, "windUpMethodName": p.windUp.name, "stageId": p.stage.value, "stageCode": p.stage.value, "stageName": p.stage.name, "paymentMethodId": p.method.value, "paymentMethodCode": p.method.value, "paymentMethodName": p.method.name, "paymentPremise": p.premise, "payPeriod": p.period ? parseInt(p.period) : null, "paymentPercentage": parseFloat(p.percent), "remark": p.remark })); await utils.requestPromise('POST', CONFIG.endpoints.savePaymentPlan, payPl, "付款计划"); const tName = row['合同附件名称']; let hasUploadedFiles = false; if (tName && fd.files.length) { const mFiles = []; for (let j = 0; j < fd.files.length; j++) if (fd.files[j].name.includes(tName)) mFiles.push(fd.files[j]); if (mFiles.length > 0) { for (let j = 0; j < mFiles.length; j++) { const f1 = utils.buildFileFormData(mFiles[j]); const u1 = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=true&toPdf=false`, f1); if (u1.data?.docId) { await utils.requestPromise('POST', CONFIG.endpoints.saveArchive, { contractId: c.id, contractOtherDocIds: [u1.data.docId], approveReferenceDocIds: [] }); await utils.sleep(1500); const f2 = utils.buildFileFormData(mFiles[j]); let u2 = null; let uploadSuccess = false; for (let attempt = 1; attempt <= 3; attempt++) { try { if (attempt > 1) { btn.textContent = `Retry PDF (${attempt}/3)...`; console.warn(`[V9.60] Retrying PDF upload for ${tName} (Attempt ${attempt})`); await utils.sleep(3000); } u2 = await utils.requestPromise('POST', `${CONFIG.endpoints.uploadFile}?companyCode=100&waterMark=true&toPdf=true`, f2); uploadSuccess = true; break; } catch (e) { console.error(`PDF Upload Failed Attempt ${attempt}`, e); if (attempt === 3) throw e; } } if (uploadSuccess && u2.data?.docId) { await utils.requestPromise('POST', `${CONFIG.endpoints.saveArchiveBatch}?fileIds=${u2.data.docId}&relationType=1&entityId=${c.id}`, {}); hasUploadedFiles = true; } } } } else { console.warn(`[V9.60] 未找到匹配附件: ${tName}`); } } if (fd.flow.autoSubmit) { if (hasUploadedFiles) { let selectedFlowKey = batchFlowKey; if (!selectedFlowKey) { const flowList = await actions.getAvailableFlows(c.id, row['合同名称'] || dRes.data.name); selectedFlowKey = await actions.promptFlowSelection(row['合同名称'] || dRes.data.name, flowList); if (selectedFlowKey) { batchFlowKey = selectedFlowKey; } } if (selectedFlowKey) { const req = await utils.requestPromise('POST', CONFIG.endpoints.createRequest, { contractId: c.id, name: row['合同名称'] || dRes.data.name }); if (req.success && req.data?.requestId) { const dynModelCode = req.data.businessModelCode; if (batchExecutors) { console.log("[V9.41] 复用已有审批人配置,跳过计算...", batchExecutors); await utils.requestPromise('POST', CONFIG.endpoints.saveSolidify, { businessModelCode: dynModelCode, businessId: req.data.requestId, executorsVos: JSON.stringify(batchExecutors) }); const startNodes = batchExecutors.map(s => ({ nodeId: s.nodeId, userVarName: s.userVarName, flowTaskType: s.flowTaskType, userIds: s.userIds, solidifyFlow: true })); await utils.requestPromise('POST', CONFIG.endpoints.startFlowNew, { businessKey: req.data.requestId, businessModelCode: dynModelCode, typeId: null, flowDefKey: selectedFlowKey, taskList: JSON.stringify(startNodes) }); } else { const pre = await utils.requestPromise('POST', CONFIG.endpoints.startFlowNew, { businessKey: req.data.requestId, businessModelCode: dynModelCode, typeId: null, flowDefKey: selectedFlowKey }); if (pre.success && pre.data?.flowDefinationId) { const ent = await utils.requestPromise('POST', `${CONFIG.endpoints.getEntity}?id=${pre.data.flowDefinationId}&versionCode=-1&businessId=${req.data.requestId}&businessModelCode=${dynModelCode}`, {}); let nodesMap = {}; try { const def = JSON.parse(ent.data.defJson); if (def && def.process && def.process.nodes) { nodesMap = def.process.nodes; } } catch (e) { console.warn("Parse defJson failed", e); } const solidEx = ent.data?.solidifyExecutorOfOnly || {}; const cands = []; const flowNodeInfos = pre.data.nodeInfoList || []; for (let nid in nodesMap) { const node = nodesMap[nid]; if (node.type === 'UserTask' && !(node.nodeType === 'Normal' && node.name.includes("修改"))) { let u = []; let isCalculated = false; let calcError = false; if (node.nodeConfig && node.nodeConfig.executor && Array.isArray(node.nodeConfig.executor)) { try { const rawExecutors = node.nodeConfig.executor.map(ex => { const newEx = { ...ex }; delete newEx.rowdata; if (ex.userType === "SelfDefinition") { newEx.ids = ex.selfDefId || ex.selfDefOfOrgAndSelId || null; newEx.customParam = ex.customParam || null; newEx.allowDefaultStarter = node.nodeConfig.normal?.allowDefaultStarter || null; newEx.repeatExecutorPass = node.nodeConfig.normal?.repeatExecutorPass || null; } else { if (newEx.ids === "") newEx.ids = null; if (newEx.customParam === "") newEx.customParam = null; } return newEx; }); if (rawExecutors.length > 0) { const calcPayload = { requestExecutorsVos: JSON.stringify(rawExecutors), businessModelCode: dynModelCode, businessId: req.data.requestId }; console.log(`%c[YzHelper Debug] 计算节点: ${node.name} (${nid})`, "color: #722ed1; font-weight: bold;"); utils.showStatus(`正在计算节点 [${node.name}]...`, 'processing'); const calcRes = await utils.requestPromise('POST', CONFIG.endpoints.getCalculatedUsers, calcPayload, `计算${node.name}`); if (calcRes.success && calcRes.data && Array.isArray(calcRes.data)) { calcRes.data.forEach(user => { u.push({ id: user.userId || user.id, name: user.userName || user.name, positionName: user.positionName || '', organizationName: user.organizationName || '' }); }); if (u.length > 0) isCalculated = true; } else { calcError = true; } } } catch (err) { console.warn(`节点 ${node.name} 计算失败`, err); calcError = true; } } if (u.length === 0 && solidEx[nid] && solidEx[nid].executorIds) { const ids = solidEx[nid].executorIds.split(','); const nms = solidEx[nid].executorNames.split(','); u = ids.map((id, x) => ({ id: id, name: nms[x] || '', positionName: '', organizationName: '' })); } let matchedVarName = null; if (flowNodeInfos) { const infoMatch = flowNodeInfos.find(n => n.id === nid); if (infoMatch) matchedVarName = infoMatch.userVarName; } if (u.length > 0 || calcError || (node.nodeConfig && node.nodeConfig.executor && node.nodeConfig.executor.length > 0)) { cands.push({ nodeId: nid, nodeName: node.name, users: u, flowTaskType: node.nodeType === 'CounterSign' ? 'countersign' : 'approve', userVarName: matchedVarName, isError: calcError }); } } } if (cands.length === 0) { const fallbackNodes = Object.keys(solidEx); fallbackNodes.forEach(nid => { let u = []; if (solidEx[nid] && solidEx[nid].executorIds) { const ids = solidEx[nid].executorIds.split(','); const nms = solidEx[nid].executorNames.split(','); u = ids.map((id, x) => ({ id: id, name: nms[x] || '', positionName: '', organizationName: '' })); } if (u.length) cands.push({ nodeId: nid, nodeName: solidEx[nid]?.actTaskDefKey || nid, users: u, flowTaskType: solidEx[nid]?.nodeType === 'CounterSign' ? 'countersign' : 'approve' }); }); } if (pre.data.nodeInfoList) { cands.forEach(c => { const match = pre.data.nodeInfoList.find(n => n.id === c.nodeId); if (match) c.userVarName = match.userVarName; }); } const sel = await actions.promptUserForApprovers(code, cands); if (sel && sel.length) { batchExecutors = sel; await utils.requestPromise('POST', CONFIG.endpoints.saveSolidify, { businessModelCode: dynModelCode, businessId: req.data.requestId, executorsVos: JSON.stringify(sel) }); const startNodes = sel.map(s => ({ nodeId: s.nodeId, userVarName: s.userVarName, flowTaskType: s.flowTaskType, userIds: s.userIds, solidifyFlow: true })); await utils.requestPromise('POST', CONFIG.endpoints.startFlowNew, { businessKey: req.data.requestId, businessModelCode: dynModelCode, typeId: null, flowDefKey: selectedFlowKey, taskList: JSON.stringify(startNodes) }); } } } } } else { utils.showStatus("用户取消或跳过审批流程", "error"); } } else { utils.showStatus("无附件或上传失败,跳过审批", "error"); throw new Error("附件上传失败,无法发起审批"); } } successCount++; } catch (e) { console.error(e); failCount++; } } alert(`完成! 成功: ${successCount}, 失败: ${failCount}`); } catch (e) { alert("Error: " + e.message); } finally { utils.toggleInputs(false); const tplSelect = document.getElementById('yz-template-select'); if (tplSelect) tplSelect.disabled = true; btn.textContent = "根据 Excel 批量更新合同信息 (完成)"; } }, toggleMinimize: () => { const panel = document.getElementById('yz-helper-panel'); if (panel) panel.style.display = 'none'; }, loadTemplates: () => { utils.request('POST', CONFIG.endpoints.searchTemplate, { "enable": true, "page": true, "contractType": "FRAME_AGREEMENT", "companyCode": "100", "pageInfo": { "page": 1, "rows": 1000 } }, (err, data) => { if (!err && data.success) { state.templates = data.data.rows; actions.renderTemplateOptions(); } }); }, renderTemplateOptions: () => { const s = document.getElementById('yz-template-select'); if (!s) return; s.innerHTML = ''; state.templates.forEach(t => { const o = document.createElement('option'); o.value = t.id; o.text = t.templateName; s.appendChild(o); }); }, renderTable: () => { const b = document.getElementById('yz-table-body'); b.innerHTML = ''; if (!state.searchResults.length) return b.innerHTML = '无数据'; state.searchResults.forEach(r => { const tr = document.createElement('tr'); tr.id = r._uiId; tr.innerHTML = `${r.tenderProjectName}${r.supplierName}${r.contractTemplateName || '-'}${r._status === 'success' ? '✅' : '⏳'}`; b.appendChild(tr); }); }, updateRowStatus: (uid, st, msg, det) => { const el = document.getElementById(uid)?.querySelector('.res-cell'); if (el) el.innerHTML = st === 'success' ? `✅ ${det.code}` : `${st === 'error' ? '❌' : '⏳'} ${msg || ''}`; } }; function initUI() { GM_addStyle(STYLES); const div = document.createElement('div'); div.id = 'yz-helper-panel'; div.innerHTML = `
数字供应链框架协议助手
招标查询 & 模板选择
编制合同
Excel 数据源
区域与组织
甲方信息
等待模板加载...
其他信息
请先选择模板以加载字段...
付款信息
批量操作
待审批附件上传
附件文件
Excel 台账
手动粘贴编号
提交审批
签章附件上传
已盖章 PDF
Excel 台账
手动粘贴编号
输出日志
请先查询
Ready
`; document.body.appendChild(div); document.getElementById('yz-btn-search').onclick = actions.searchAndExport; document.getElementById('yz-btn-update-excel').onclick = actions.batchUpdateFromExcel; document.getElementById('yz-btn-manual-submit').onclick = actions.manualSubmitApproval; document.getElementById('yz-close-icon').onclick = actions.toggleMinimize; document.getElementById('yz-header').ondblclick = actions.toggleMinimize; document.getElementById('yz-template-select').onchange = actions.renderDynamicForm; document.getElementById('yz-btn-add-payment').onclick = actions.addPaymentRow; document.getElementById('yz-in-area').onclick = () => actions.openTreeSelector('区域', state.areaTreeData, n => { document.getElementById('yz-in-area').value = n.name; document.getElementById('yz-in-area').dataset.code = n.code || n.id; }); document.getElementById('yz-in-org').onclick = () => actions.openTreeSelector('组织', state.orgTreeData, n => { document.getElementById('yz-in-org').value = n.name; document.getElementById('yz-in-org').dataset.code = n.code || n.id; }); document.getElementById('yz-btn-add-scope').onclick = actions.addScope; document.getElementById('yz-helper-panel').addEventListener('delScope', e => actions.removeScope(e.detail)); const btnManualUpload = document.getElementById('yz-btn-manual-upload'); if (btnManualUpload) btnManualUpload.onclick = actions.manualUploadAttachments; const btnGenAttachMap = document.getElementById('yz-btn-generate-attach-map'); if (btnGenAttachMap) btnGenAttachMap.onclick = actions.generateAttachMappingTable; const btnExecAttachMap = document.getElementById('yz-btn-execute-attach-map'); if (btnExecAttachMap) btnExecAttachMap.onclick = actions.executeAttachMappedUpload; const btnGenMap = document.getElementById('yz-btn-generate-map'); if (btnGenMap) btnGenMap.onclick = actions.generateMappingTable; const btnExecMap = document.getElementById('yz-btn-execute-map'); if (btnExecMap) btnExecMap.onclick = actions.executeMappedUpload; actions.addPaymentRow(); actions.loadTemplates(); } window.addEventListener('load', () => { actions.init(); setTimeout(initUI, 1000); }); })();