单方协议
This commit is contained in:
@@ -37,7 +37,7 @@
|
||||
<div class="section-title-row counterparty-toolbar">
|
||||
<div class="section-title">我方主体</div>
|
||||
<a-button
|
||||
v-if="!isViewMode"
|
||||
v-if="!isViewMode && !isSingleAgreementChecked"
|
||||
type="link"
|
||||
class="text-action-btn"
|
||||
@click="handleAddOurParty"
|
||||
@@ -46,7 +46,7 @@
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="party-empty-state" style="margin-bottom: 12px">
|
||||
当前登录主体必须包含在我方主体中
|
||||
{{ isSingleAgreementChecked ? "单方协议默认使用当前登录主体,且不可修改" : "当前登录主体必须包含在我方主体中" }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -59,7 +59,7 @@
|
||||
{{ party.displayName }}
|
||||
</div>
|
||||
<a-button
|
||||
v-if="!isViewMode && ourParties.length > 1"
|
||||
v-if="!isViewMode && !isSingleAgreementChecked && ourParties.length > 1"
|
||||
type="link"
|
||||
danger
|
||||
class="text-action-btn"
|
||||
@@ -73,6 +73,7 @@
|
||||
v-model:value="party.companyName"
|
||||
:options="getPartyOptions(party.roleCode)"
|
||||
show-search
|
||||
:disabled="isSingleAgreementChecked"
|
||||
:placeholder="`请选择${party.displayName}`"
|
||||
:filter-option="filterPartnerOption"
|
||||
@change="
|
||||
@@ -116,13 +117,6 @@
|
||||
<SearchOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button
|
||||
class="invoice-btn"
|
||||
@click="showInvoiceTitles(party, party.displayName)"
|
||||
>
|
||||
<PlusOutlined />
|
||||
备选开票抬头选择
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="section-title-row counterparty-toolbar">
|
||||
@@ -365,7 +359,7 @@
|
||||
</div>
|
||||
<div class="summary-line">
|
||||
<span>相对方</span
|
||||
><strong>{{ counterpartySummaryText }}</strong>
|
||||
><strong>{{ isSingleAgreementChecked ? "单方协议,无相对方" : counterpartySummaryText }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -401,7 +395,7 @@
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="upload-block">
|
||||
<div v-if="!isSingleAgreementChecked" class="upload-block">
|
||||
<div class="upload-label">签订依据附件</div>
|
||||
<a-upload-dragger
|
||||
:file-list="gistAttachmentList"
|
||||
@@ -437,7 +431,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="requiresAuthAttachment" class="upload-block">
|
||||
<div
|
||||
v-if="!isSingleAgreementChecked && requiresAuthAttachment"
|
||||
class="upload-block"
|
||||
>
|
||||
<div class="upload-label">授权委托书附件</div>
|
||||
<a-upload-dragger
|
||||
:file-list="authAttachmentUploadList"
|
||||
@@ -994,86 +991,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<a-collapse class="detail-collapse">
|
||||
<a-collapse-panel
|
||||
v-for="party in allPartyDetails"
|
||||
:key="party.localId"
|
||||
:header="`${party.displayName}详细字段维护`"
|
||||
>
|
||||
<div class="field-grid field-grid-3">
|
||||
<a-form-item class="tight-item" label="公司类型">
|
||||
<a-select
|
||||
v-model:value="party.companyType"
|
||||
:options="companyTypeOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="公司性质">
|
||||
<a-select
|
||||
v-model:value="party.companyNature"
|
||||
:options="companyNatureOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="组织性质">
|
||||
<a-select
|
||||
v-model:value="party.organizationNature"
|
||||
:options="organizationNatureOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="组织性质企业二级">
|
||||
<a-select
|
||||
v-model:value="party.enterpriseNatureLevel2"
|
||||
:options="enterpriseNatureLevel2Options"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="统一社会信用代码">
|
||||
<a-input v-model:value="party.creditCode" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="TIN号">
|
||||
<a-input v-model:value="party.tinNo" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="国家/地区">
|
||||
<a-select
|
||||
v-model:value="party.countryCode"
|
||||
:options="countryOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="法定代表人">
|
||||
<a-input v-model:value="party.legalRepresentative" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item field-span-3" label="注册地址">
|
||||
<a-input v-model:value="party.registeredAddress" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="注册资本">
|
||||
<a-input v-model:value="party.registeredCapital" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="联系电话">
|
||||
<a-input v-model:value="party.contactPhone" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="电子邮箱">
|
||||
<a-input v-model:value="party.contactEmail" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item field-span-3" label="通讯地址">
|
||||
<a-input v-model:value="party.contactAddress" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item" label="开户行">
|
||||
<a-input v-model:value="party.bankName" />
|
||||
</a-form-item>
|
||||
<a-form-item class="tight-item field-span-2" label="银行账号">
|
||||
<a-input v-model:value="party.bankAccount" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="tight-item field-span-3"
|
||||
label="备选开票抬头"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="party.invoiceTitleOptions"
|
||||
mode="tags"
|
||||
:token-separators="[',', ',']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
@@ -1131,11 +1048,6 @@ import {
|
||||
|
||||
// ========== 合同方相关字典 ==========
|
||||
|
||||
const partyCharacterOptions = [
|
||||
{ label: "企业", value: "QY" },
|
||||
{ label: "自然人", value: "ZZR" },
|
||||
];
|
||||
|
||||
const companyTypeOptions = [
|
||||
{ label: "客户", value: "客户" },
|
||||
{ label: "供应商", value: "供应商" },
|
||||
@@ -1144,38 +1056,6 @@ const companyTypeOptions = [
|
||||
{ label: "其他", value: "其他" },
|
||||
];
|
||||
|
||||
const companyNatureOptions = [
|
||||
{ label: "境内组织", value: "境内组织" },
|
||||
{ label: "境内个人", value: "境内个人" },
|
||||
{ label: "境外组织", value: "境外组织" },
|
||||
{ label: "境外个人", value: "境外个人" },
|
||||
];
|
||||
|
||||
const organizationNatureOptions = [
|
||||
{ label: "政府", value: "政府" },
|
||||
{ label: "企业", value: "企业" },
|
||||
{ label: "其他组织", value: "其他组织" },
|
||||
];
|
||||
|
||||
const enterpriseNatureLevel2Options = [
|
||||
{ label: "国有企业", value: "国有企业" },
|
||||
{ label: "民营企业", value: "民营企业" },
|
||||
{ label: "外资企业", value: "外资企业" },
|
||||
{ label: "合资企业", value: "合资企业" },
|
||||
{ label: "国有全资和控股企业", value: "国有全资和控股企业" },
|
||||
];
|
||||
|
||||
const countryOptions = [
|
||||
{ label: "中国", value: "GJ156" },
|
||||
{ label: "中国香港", value: "GJ344" },
|
||||
{ label: "中国澳门", value: "GJ446" },
|
||||
{ label: "中国台湾", value: "GJ158" },
|
||||
{ label: "新加坡", value: "GJ702" },
|
||||
{ label: "美国", value: "GJ840" },
|
||||
{ label: "英国", value: "GJ826" },
|
||||
{ label: "日本", value: "GJ392" },
|
||||
];
|
||||
|
||||
const contractCompanyOptions = ref([
|
||||
{ label: "扬州宏富特种材料有限公司", value: "扬州宏富特种材料有限公司" },
|
||||
{ label: "宁波港船务货运代理有限公司", value: "宁波港船务货运代理有限公司" },
|
||||
@@ -1382,9 +1262,6 @@ function createEmptyParty(roleCode, index = 0) {
|
||||
companyType: "",
|
||||
oppositeCharacter: "",
|
||||
oppCharacter: "",
|
||||
companyNature: "",
|
||||
organizationNature: "",
|
||||
enterpriseNatureLevel2: "",
|
||||
contactName: "",
|
||||
contactPhone: "",
|
||||
contactEmail: "",
|
||||
@@ -1398,7 +1275,6 @@ function createEmptyParty(roleCode, index = 0) {
|
||||
naturalPersonIdNumber: "",
|
||||
isGroupInternal: false,
|
||||
isHongKongMacaoTaiwan: false,
|
||||
invoiceTitleOptions: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1417,9 +1293,6 @@ function mapLaunchPartyToFormParty(item = {}) {
|
||||
roleCode,
|
||||
roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方",
|
||||
subjectCode: item.subjectCode || "",
|
||||
companyNature: getCompanyNatureByOppositeCharacter(
|
||||
item.oppositeCharacter || item.oppCharacter,
|
||||
),
|
||||
oppCharacter: item.oppositeCharacter || item.oppCharacter || "",
|
||||
oppositeCharacter: item.oppositeCharacter || item.oppCharacter || "",
|
||||
displayName: "",
|
||||
@@ -1445,16 +1318,6 @@ function buildPartyStateFromList(list = []) {
|
||||
return normalizePartyState(result);
|
||||
}
|
||||
|
||||
function getCompanyNatureByOppositeCharacter(value) {
|
||||
const map = {
|
||||
1: "境内组织",
|
||||
2: "境内个人",
|
||||
3: "境外组织",
|
||||
4: "境外个人",
|
||||
};
|
||||
return map[String(value || "")] || "";
|
||||
}
|
||||
|
||||
function isPartyFilled(party = {}) {
|
||||
const ignoreKeys = new Set([
|
||||
"localId",
|
||||
@@ -1834,11 +1697,6 @@ const ourPartySummaryText = computed(() =>
|
||||
: "未填写",
|
||||
);
|
||||
|
||||
const allPartyDetails = computed(() => [
|
||||
...ourParties.value,
|
||||
...counterpartyParties.value,
|
||||
]);
|
||||
|
||||
const signingSubjectCodeText = computed(() =>
|
||||
ourParties.value
|
||||
.map((party) => String(party.subjectCode || "").trim())
|
||||
@@ -1870,6 +1728,23 @@ const requiresAuthAttachment = computed(() =>
|
||||
["GZBSQRQZ", "HTZYZBSQRQZ"].includes(detailState.value.sealType),
|
||||
);
|
||||
|
||||
function forceSingleAgreementDefaults() {
|
||||
if (!isSingleAgreementChecked.value || !currentSelfParty.value) return;
|
||||
detailState.value.isElectron = "0";
|
||||
detailState.value.sealType = "YWZ";
|
||||
detailState.value.partyState.ourPartyList = [
|
||||
{
|
||||
...createEmptyParty(PARTY_ROLE_OUR, 0),
|
||||
...currentSelfParty.value,
|
||||
localId:
|
||||
detailState.value.partyState.ourPartyList?.[0]?.localId ||
|
||||
createLocalId("our"),
|
||||
},
|
||||
];
|
||||
detailState.value.partyState.counterpartyList = [];
|
||||
refreshPartyDisplayMeta();
|
||||
}
|
||||
|
||||
function syncUploadLists() {
|
||||
otherAttachmentList.value = (detailState.value.otherAttachmentList || []).map(
|
||||
(item, index) => ({
|
||||
@@ -1943,15 +1818,6 @@ function showSearchTip(title) {
|
||||
message.info(`${title}数据已从客户/主体库加载,可直接在下拉框搜索选择。`);
|
||||
}
|
||||
|
||||
function showInvoiceTitles(party, roleName) {
|
||||
const titles = party.invoiceTitleOptions || [];
|
||||
message.info(
|
||||
titles.length
|
||||
? `${roleName}备选开票抬头:${titles.join(";")}`
|
||||
: `${roleName}暂无备选开票抬头`,
|
||||
);
|
||||
}
|
||||
|
||||
function filterPartnerOption(input, option) {
|
||||
const keyword = String(input || "")
|
||||
.trim()
|
||||
@@ -1978,27 +1844,6 @@ function getRelativeTypeLabel(value) {
|
||||
return map[value] || value || "";
|
||||
}
|
||||
|
||||
function getOrganizationNatureLabel(value) {
|
||||
const map = {
|
||||
ZY: "政府",
|
||||
QY: "企业",
|
||||
QT: "其他组织",
|
||||
};
|
||||
return map[value] || value || "";
|
||||
}
|
||||
|
||||
function getEnterpriseNatureLevel2Label(value) {
|
||||
const map = {
|
||||
GNQY: "国有企业",
|
||||
MYQY: "民营企业",
|
||||
WZQY: "外资企业",
|
||||
HZQY: "合资企业",
|
||||
GYQZHKGQY: "国有全资和控股企业",
|
||||
QT: "其他",
|
||||
};
|
||||
return map[value] || value || "";
|
||||
}
|
||||
|
||||
function mapPartnerToParty(raw = {}, roleKey, companyName) {
|
||||
const firstContact = raw.contacts?.[0] || {};
|
||||
const roleCode =
|
||||
@@ -2013,17 +1858,9 @@ function mapPartnerToParty(raw = {}, roleKey, companyName) {
|
||||
companyType: getRelativeTypeLabel(raw.relativeType),
|
||||
oppositeCharacter: raw.oppositeCharacter || "",
|
||||
oppCharacter: raw.oppositeCharacter || "",
|
||||
companyNature: getCompanyNatureByOppositeCharacter(raw.oppositeCharacter),
|
||||
organizationNature: getOrganizationNatureLabel(raw.natureOfEnterpriseOrg),
|
||||
enterpriseNatureLevel2: getEnterpriseNatureLevel2Label(
|
||||
raw.natureOfEnterpriseOrgLev,
|
||||
),
|
||||
creditCode: raw.customerTaxno || "",
|
||||
tinNo: raw.tinCode || "",
|
||||
countryCode: raw.country || "",
|
||||
legalRepresentative: raw.legalPersonName || "",
|
||||
registeredAddress: raw.customerRegaddr || "",
|
||||
registeredCapital: raw.regCapital || raw.registeredCapital || "",
|
||||
contactName: firstContact.contactName || "",
|
||||
contactPhone: firstContact.contactPhone || raw.officephone || "",
|
||||
contactEmail: firstContact.email || raw.emailAddress || "",
|
||||
@@ -2034,7 +1871,6 @@ function mapPartnerToParty(raw = {}, roleKey, companyName) {
|
||||
naturalPersonIdNumber: raw.certificateNum || "",
|
||||
isGroupInternal: raw.groupFlag === "Y" || raw.isJtn === "1",
|
||||
isHongKongMacaoTaiwan: raw.gatFlag === "Y" || raw.isSgat === "1",
|
||||
invoiceTitleOptions: raw.customerNameC ? [raw.customerNameC] : [],
|
||||
roleCode,
|
||||
roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方",
|
||||
};
|
||||
@@ -2109,13 +1945,12 @@ function handleCompanySelect(party, companyName, option = {}) {
|
||||
legalRepresentative:
|
||||
party.legalRepresentative || profile.legalRepresentative,
|
||||
creditCode: party.creditCode || profile.creditCode,
|
||||
oppCharacter:
|
||||
party.oppCharacter ||
|
||||
(String(party.companyNature || "").includes("个人") ? "2" : "1"),
|
||||
oppCharacter: party.oppCharacter || "1",
|
||||
});
|
||||
}
|
||||
|
||||
function handleAddOurParty() {
|
||||
if (isSingleAgreementChecked.value) return;
|
||||
detailState.value.partyState.ourPartyList =
|
||||
detailState.value.partyState.ourPartyList || [];
|
||||
detailState.value.partyState.ourPartyList.push(
|
||||
@@ -2136,6 +1971,7 @@ function handleContractTypeCodeChange(value) {
|
||||
}
|
||||
|
||||
function handleAddCounterparty() {
|
||||
if (isSingleAgreementChecked.value) return;
|
||||
detailState.value.partyState.counterpartyList =
|
||||
detailState.value.partyState.counterpartyList || [];
|
||||
detailState.value.partyState.counterpartyList.push(
|
||||
@@ -2148,6 +1984,7 @@ function handleAddCounterparty() {
|
||||
}
|
||||
|
||||
function handleRemoveOurParty(localId) {
|
||||
if (isSingleAgreementChecked.value) return;
|
||||
detailState.value.partyState.ourPartyList = (
|
||||
detailState.value.partyState.ourPartyList || []
|
||||
).filter((item) => item.localId !== localId);
|
||||
@@ -2160,6 +1997,7 @@ function handleRemoveOurParty(localId) {
|
||||
}
|
||||
|
||||
function handleRemoveCounterparty(localId) {
|
||||
if (isSingleAgreementChecked.value) return;
|
||||
detailState.value.partyState.counterpartyList = (
|
||||
detailState.value.partyState.counterpartyList || []
|
||||
).filter((item) => item.localId !== localId);
|
||||
@@ -2247,7 +2085,7 @@ async function downloadFile(filePath, fileName = "附件") {
|
||||
}
|
||||
try {
|
||||
const resp = await downloadLaunchFile(filePath);
|
||||
const arrayBuffer = resp.data;
|
||||
const arrayBuffer = resp?.data ?? resp;
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
@@ -2336,6 +2174,16 @@ function validateState() {
|
||||
) {
|
||||
return "当前登录主体必须包含在我方主体中";
|
||||
}
|
||||
if (
|
||||
isSingleAgreementChecked.value &&
|
||||
currentSelfSubjectCode.value &&
|
||||
ourParties.value.some(
|
||||
(party) =>
|
||||
String(party.subjectCode || "").trim() !== currentSelfSubjectCode.value,
|
||||
)
|
||||
) {
|
||||
return "单方协议仅允许使用当前登录主体";
|
||||
}
|
||||
if (!isSingleAgreementChecked.value && !counterpartyParties.value.length)
|
||||
return "请至少添加一个相对方";
|
||||
if (
|
||||
@@ -2405,6 +2253,7 @@ function validateState() {
|
||||
if (!detailState.value.contractBodyFileName?.trim())
|
||||
return "请先配置合同正文";
|
||||
if (
|
||||
!isSingleAgreementChecked.value &&
|
||||
requiresAuthAttachment.value &&
|
||||
!(detailState.value.authAttachmentList || []).length
|
||||
)
|
||||
@@ -2473,9 +2322,7 @@ function buildLaunchRecord() {
|
||||
oppositeId: party.oppositeId,
|
||||
companyName: party.companyName,
|
||||
companyType: party.companyType,
|
||||
oppositeCharacter:
|
||||
party.oppCharacter ||
|
||||
(String(party.companyNature || "").includes("个人") ? "2" : "1"),
|
||||
oppositeCharacter: party.oppCharacter || "1",
|
||||
contactName: party.contactName,
|
||||
contactPhone: party.contactPhone,
|
||||
contactEmail: party.contactEmail,
|
||||
@@ -2501,9 +2348,7 @@ function buildLaunchRecord() {
|
||||
oppositeId: party.oppositeId,
|
||||
companyName: party.companyName,
|
||||
companyType: party.companyType,
|
||||
oppositeCharacter:
|
||||
party.oppCharacter ||
|
||||
(String(party.companyNature || "").includes("个人") ? "2" : "1"),
|
||||
oppositeCharacter: party.oppCharacter || "1",
|
||||
contactName: party.contactName,
|
||||
contactPhone: party.contactPhone,
|
||||
contactEmail: party.contactEmail,
|
||||
@@ -2675,7 +2520,6 @@ function applySelfProfile(profile = {}) {
|
||||
bankAccount: profile.bankAccount || "",
|
||||
legalRepresentative: profile.legalRepresentative || "",
|
||||
creditCode: profile.creditCode || "",
|
||||
invoiceTitleOptions: profile.invoiceTitleOptions || [],
|
||||
};
|
||||
const shouldHydrateFirstParty =
|
||||
!firstOurParty.companyName ||
|
||||
@@ -2699,12 +2543,11 @@ function applySelfProfile(profile = {}) {
|
||||
legalRepresentative:
|
||||
profile.legalRepresentative || firstOurParty.legalRepresentative,
|
||||
creditCode: profile.creditCode || firstOurParty.creditCode,
|
||||
invoiceTitleOptions:
|
||||
profile.invoiceTitleOptions || firstOurParty.invoiceTitleOptions || [],
|
||||
},
|
||||
...(detailState.value.partyState.ourPartyList || []).slice(1),
|
||||
];
|
||||
refreshPartyDisplayMeta();
|
||||
forceSingleAgreementDefaults();
|
||||
}
|
||||
|
||||
async function showModal(context = {}, mode = "create") {
|
||||
@@ -2724,6 +2567,7 @@ async function showModal(context = {}, mode = "create") {
|
||||
detailState.value = copyContractFormData(source);
|
||||
const selfProfile = await getLaunchSelfProfile();
|
||||
applySelfProfile(selfProfile || {});
|
||||
forceSingleAgreementDefaults();
|
||||
syncCounterpartyState();
|
||||
syncUploadLists();
|
||||
loadPartnerOptions();
|
||||
|
||||
@@ -1170,7 +1170,7 @@ async function fetchTemplateBuffer(templateFilePath) {
|
||||
}
|
||||
|
||||
const resp = await downloadTemplateFile(templateFilePath);
|
||||
const arrayBuffer = resp.data;
|
||||
const arrayBuffer = resp?.data ?? resp;
|
||||
const buffer =
|
||||
arrayBuffer instanceof ArrayBuffer ? arrayBuffer : arrayBuffer?.buffer;
|
||||
templateBufferCache.set(templateFilePath, buffer);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
title="上传已签署版本"
|
||||
:title="isSingleAgreement ? '上传盖章版' : '上传已签署版本'"
|
||||
width="560px"
|
||||
:mask-closable="false"
|
||||
:confirm-loading="submitting"
|
||||
@@ -22,7 +22,7 @@
|
||||
<a-divider style="margin: 16px 0" />
|
||||
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="上传已签署合同文件" required>
|
||||
<a-form-item :label="isSingleAgreement ? '上传已盖章文件' : '上传已签署合同文件'" required>
|
||||
<div
|
||||
class="upload-dragger"
|
||||
:class="{ 'is-dragover': dragOver }"
|
||||
@@ -40,7 +40,7 @@
|
||||
/>
|
||||
<template v-if="!uploadFile">
|
||||
<cloud-upload-outlined :style="{ 'fontSize': '36px', 'color': '#b0b8c5' }" />
|
||||
<p class="upload-text">点击或将已签署文件拖拽到这里</p>
|
||||
<p class="upload-text">{{ isSingleAgreement ? "点击或将已盖章文件拖拽到这里" : "点击或将已签署文件拖拽到这里" }}</p>
|
||||
<p class="upload-hint">支持 PDF、DOC、DOCX 文件,且小于 20M</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -79,11 +79,11 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="不在线签署的原因" required>
|
||||
<a-form-item :label="isSingleAgreement ? '盖章说明' : '不在线签署的原因'" required>
|
||||
<a-textarea
|
||||
v-model:value="offlineReason"
|
||||
:rows="4"
|
||||
placeholder="请填写不在线签署的原因,例如:客户无法配合在线签署、对方无数字证书等"
|
||||
:placeholder="isSingleAgreement ? '请填写盖章说明,例如:内部审批通过后线下加盖业务章' : '请填写不在线签署的原因,例如:客户无法配合在线签署、对方无数字证书等'"
|
||||
:maxlength="500"
|
||||
show-count
|
||||
/>
|
||||
@@ -111,13 +111,15 @@ const activeDate = ref("");
|
||||
const dragOver = ref(false);
|
||||
const submitting = ref(false);
|
||||
const fileInputRef = ref(null);
|
||||
const isSingleAgreement = ref(false);
|
||||
|
||||
const emit = defineEmits(["done"]);
|
||||
|
||||
function showModal(record) {
|
||||
row.value = record;
|
||||
isSingleAgreement.value = String(record?.isSingleAgreement || "0") === "1";
|
||||
uploadFile.value = null;
|
||||
offlineReason.value = "";
|
||||
offlineReason.value = isSingleAgreement.value ? "内部审批通过后线下加盖业务章" : "";
|
||||
signDate.value = sliceDateValue(record?.signDate) || todayString();
|
||||
sealDate.value = sliceDateValue(record?.sealDate) || signDate.value;
|
||||
activeDate.value = sliceDateValue(record?.activeDate) || sliceDateValue(record?.effectiveStart);
|
||||
@@ -201,12 +203,12 @@ function formatFileSize(bytes) {
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!uploadFile.value) {
|
||||
message.warning("请先上传已签署文件");
|
||||
message.warning(isSingleAgreement.value ? "请先上传已盖章文件" : "请先上传已签署文件");
|
||||
return;
|
||||
}
|
||||
const reason = (offlineReason.value || "").trim();
|
||||
if (!reason) {
|
||||
message.warning("请填写不在线签署的原因");
|
||||
message.warning(isSingleAgreement.value ? "请填写盖章说明" : "请填写不在线签署的原因");
|
||||
return;
|
||||
}
|
||||
if (!signDate.value) {
|
||||
@@ -242,7 +244,7 @@ async function handleSubmit() {
|
||||
const fileSize = uploadFile.value.size;
|
||||
|
||||
const finishRes = await finishLaunchSeal(row.value.id, {
|
||||
sealContractId: row.value.sealContractId || `OFFLINE-SEAL-${row.value.id}`,
|
||||
sealContractId: row.value.sealContractId || `${isSingleAgreement.value ? "OFFLINE-SINGLE" : "OFFLINE-SEAL"}-${row.value.id}`,
|
||||
sealContractStatus: 2000,
|
||||
signedFileName: fileName,
|
||||
signedFilePath: filePath,
|
||||
@@ -260,7 +262,7 @@ async function handleSubmit() {
|
||||
return;
|
||||
}
|
||||
|
||||
message.success("已签署版本上传成功,合同已流转到待采集");
|
||||
message.success(isSingleAgreement.value ? "盖章版上传成功,单方协议已办结" : "已签署版本上传成功,合同已流转到待采集");
|
||||
visible.value = false;
|
||||
emit("done");
|
||||
} catch (err) {
|
||||
|
||||
@@ -50,6 +50,7 @@ export const currencyOptions = [
|
||||
];
|
||||
|
||||
export const sealTypeOptions = [
|
||||
{ label: "内部业务章", value: "YWZ" },
|
||||
{ label: "公章", value: "GZ" },
|
||||
{ label: "合同专用章", value: "HTZYZ" },
|
||||
{ label: "公章+法定代表人私章", value: "GZFDDBRSZ" },
|
||||
|
||||
+410
-131
@@ -10,17 +10,29 @@
|
||||
<a-row :gutter="16" style="width: 100%">
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="合同名称">
|
||||
<a-input v-model:value="queryParam.contractName" allow-clear placeholder="请输入合同名称" />
|
||||
<a-input
|
||||
v-model:value="queryParam.contractName"
|
||||
allow-clear
|
||||
placeholder="请输入合同名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="合同编码">
|
||||
<a-input v-model:value="queryParam.contractCode" allow-clear placeholder="请输入合同编码" />
|
||||
<a-input
|
||||
v-model:value="queryParam.contractCode"
|
||||
allow-clear
|
||||
placeholder="请输入合同编码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="合同类型">
|
||||
<a-input v-model:value="queryParam.contractType" allow-clear placeholder="请输入合同类型编码" />
|
||||
<a-input
|
||||
v-model:value="queryParam.contractType"
|
||||
allow-clear
|
||||
placeholder="请输入合同类型编码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
@@ -55,10 +67,36 @@
|
||||
<vxe-toolbar ref="toolbarRef" custom>
|
||||
<template #buttons>
|
||||
<a-space wrap>
|
||||
<a-button type="primary" :icon="h(SearchOutlined)" @click="handleQuery">查询</a-button>
|
||||
<a-button :icon="h(RedoOutlined)" @click="handleReset">重置</a-button>
|
||||
<a-button type="primary" ghost :icon="h(PlusOutlined)" @click="handleCreate">发起合同</a-button>
|
||||
<a-button danger :icon="h(DeleteOutlined)" :disabled="!selectedRowKeys.length" @click="handleBatchDelete">删除</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:icon="h(SearchOutlined)"
|
||||
@click="handleQuery"
|
||||
>查询</a-button
|
||||
>
|
||||
<a-button :icon="h(RedoOutlined)" @click="handleReset"
|
||||
>重置</a-button
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
:icon="h(PlusOutlined)"
|
||||
@click="handleCreate"
|
||||
>发起合同</a-button
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
:icon="h(PlusOutlined)"
|
||||
@click="handleCreateSingleAgreement"
|
||||
>发起单方协议</a-button
|
||||
>
|
||||
<a-button
|
||||
danger
|
||||
:icon="h(DeleteOutlined)"
|
||||
:disabled="!selectedRowKeys.length"
|
||||
@click="handleBatchDelete"
|
||||
>删除</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-toolbar>
|
||||
@@ -74,33 +112,97 @@
|
||||
:checkbox-config="{ range: true }"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:loading-config="{ icon: 'vxe-icon-indicator roll', text: '正在拼命加载中...' }"
|
||||
:loading-config="{
|
||||
icon: 'vxe-icon-indicator roll',
|
||||
text: '正在拼命加载中...',
|
||||
}"
|
||||
@checkbox-range-change="selectChangeEvent"
|
||||
@checkbox-change="selectChangeEvent"
|
||||
@checkbox-all="selectChangeEvent"
|
||||
>
|
||||
<vxe-column type="checkbox" width="56" />
|
||||
<vxe-column type="seq" title="序号" width="68" />
|
||||
<vxe-column field="contractCode" title="合同编码" width="160" show-overflow="title" />
|
||||
<vxe-column field="lawContractCode" title="法务合同编号" width="160" show-overflow="title" />
|
||||
<vxe-column field="contractName" title="合同名称" width="220" show-overflow="title" />
|
||||
<vxe-column field="contractType" title="合同类型" width="160" show-overflow="title">
|
||||
<vxe-column
|
||||
field="contractCode"
|
||||
title="合同编码"
|
||||
width="160"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="lawContractCode"
|
||||
title="法务合同编号"
|
||||
width="160"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="contractName"
|
||||
title="合同名称"
|
||||
width="220"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="contractType"
|
||||
title="合同类型"
|
||||
width="160"
|
||||
show-overflow="title"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ getContractTypeSimpleLabel(row.contractTypeCode || row.contractType) }}
|
||||
{{
|
||||
getContractTypeSimpleLabel(
|
||||
row.contractTypeCode || row.contractType,
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="templateName" title="所选模板" width="180" show-overflow="title" />
|
||||
<vxe-column field="counterpartySummary" title="合同相对方" width="260" show-overflow="title" />
|
||||
<vxe-column
|
||||
field="templateName"
|
||||
title="所选模板"
|
||||
width="180"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="counterpartySummary"
|
||||
title="合同相对方"
|
||||
width="260"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column field="status" title="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="getStatusColor(row.status)">{{ getStatusLabel(row.status) }}</a-tag>
|
||||
<a-tag :color="getStatusColor(row.status)">{{
|
||||
getStatusLabelByRow(row)
|
||||
}}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="approveMessage" title="审批说明" width="220" show-overflow="title" />
|
||||
<vxe-column field="initiator" title="发起人" width="100" show-overflow="title" />
|
||||
<vxe-column field="initiatorDept" title="所属部门" width="140" show-overflow="title" />
|
||||
<vxe-column field="createTime" title="发起时间" width="170" show-overflow="title" />
|
||||
<vxe-column field="updateTime" title="最后更新时间" width="170" show-overflow="title" />
|
||||
<vxe-column
|
||||
field="approveMessage"
|
||||
title="审批说明"
|
||||
width="220"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="initiator"
|
||||
title="发起人"
|
||||
width="100"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="initiatorDept"
|
||||
title="所属部门"
|
||||
width="140"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="createTime"
|
||||
title="发起时间"
|
||||
width="170"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column
|
||||
field="updateTime"
|
||||
title="最后更新时间"
|
||||
width="170"
|
||||
show-overflow="title"
|
||||
/>
|
||||
<vxe-column title="操作" width="720" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="table-actions">
|
||||
@@ -108,17 +210,47 @@
|
||||
<a @click="handleCopy(row)">复制</a>
|
||||
<a v-if="canChange(row)" @click="handleChange(row)">变更</a>
|
||||
<a v-if="canEdit(row)" @click="handleEdit(row)">编辑</a>
|
||||
<a v-if="canSubmit(row)" @click="handleSubmit(row)">{{ isSingleAgreementRecord(row) ? "提交审批" : "提交法务" }}</a>
|
||||
<a v-if="canViewApprove(row)" @click="handleViewApprove(row)">审批记录</a>
|
||||
<a v-if="canViewLawCallback(row)" @click="handleViewLawCallback(row)">回调data</a>
|
||||
<a v-if="canMockApprove(row)" @click="handleMockApprove(row)">{{ isSingleAgreementRecord(row) ? "审批通过" : "测试通过" }}</a>
|
||||
<a v-if="canMockReject(row)" @click="handleMockReject(row)">{{ isSingleAgreementRecord(row) ? "审批驳回" : "测试驳回" }}</a>
|
||||
<a v-if="canOnlineSign(row)" @click="handleOnlineSign(row)">在线签订</a>
|
||||
<a v-if="canDownloadSigned(row)" @click="handleDownloadSigned(row)">下载签署文件</a>
|
||||
<a v-if="canUploadSigned(row)" @click="handleUploadSigned(row)">上传已签署版本</a>
|
||||
<a v-if="canMockFinishSign(row)" @click="handleMockFinishSign(row)">测试签署完成</a>
|
||||
<a v-if="canCollect(row)" @click="handleMockCollect(row)">测试采集</a>
|
||||
<a v-if="canArchive(row)" @click="handleArchive(row)">归档</a>
|
||||
<a v-if="canSubmit(row)" @click="handleSubmit(row)">{{
|
||||
isSingleAgreementRecord(row) ? "提交审批" : "提交法务"
|
||||
}}</a>
|
||||
<a v-if="canViewApprove(row)" @click="handleViewApprove(row)"
|
||||
>审批记录</a
|
||||
>
|
||||
<a
|
||||
v-if="canViewLawCallback(row)"
|
||||
@click="handleViewLawCallback(row)"
|
||||
>回调data</a
|
||||
>
|
||||
<a v-if="canMockApprove(row)" @click="handleMockApprove(row)">{{
|
||||
isSingleAgreementRecord(row) ? "审批通过" : "测试通过"
|
||||
}}</a>
|
||||
<a v-if="canMockReject(row)" @click="handleMockReject(row)">{{
|
||||
isSingleAgreementRecord(row) ? "审批驳回" : "测试驳回"
|
||||
}}</a>
|
||||
<a v-if="canOnlineSign(row)" @click="handleOnlineSign(row)">{{
|
||||
isSingleAgreementRecord(row) ? "发起盖章" : "在线签订"
|
||||
}}</a>
|
||||
<a
|
||||
v-if="canDownloadSigned(row)"
|
||||
@click="handleDownloadSigned(row)"
|
||||
>下载签署文件</a
|
||||
>
|
||||
<a v-if="canUploadSigned(row)" @click="handleUploadSigned(row)">{{
|
||||
isSingleAgreementRecord(row) ? "上传盖章版" : "上传已签署版本"
|
||||
}}</a>
|
||||
<a
|
||||
v-if="canMockFinishSign(row)"
|
||||
@click="handleMockFinishSign(row)"
|
||||
>{{
|
||||
isSingleAgreementRecord(row) ? "测试盖章完成" : "测试签署完成"
|
||||
}}</a
|
||||
>
|
||||
<a v-if="canCollect(row)" @click="handleMockCollect(row)">{{
|
||||
isSingleAgreementRecord(row) ? "测试生效" : "测试采集"
|
||||
}}</a>
|
||||
<a v-if="canArchive(row)" @click="handleArchive(row)">{{
|
||||
isSingleAgreementRecord(row) ? "办结" : "归档"
|
||||
}}</a>
|
||||
<a v-if="canTerminate(row)" @click="handleTerminate(row)">终止</a>
|
||||
<a v-if="canDelete(row)" @click="handleDelete(row)">删除</a>
|
||||
</div>
|
||||
@@ -141,10 +273,22 @@
|
||||
/>
|
||||
</a-card>
|
||||
|
||||
<ContractPartyPlaceholderModal ref="contractModalRef" @save="handleModalSave" />
|
||||
<MixedSignModal ref="mixedSignModalRef" @business-updated="handleSignBusinessUpdated" />
|
||||
<UploadSignedModal ref="uploadSignedModalRef" @done="handleSignBusinessUpdated" />
|
||||
<ContractArchiveModal ref="contractArchiveModalRef" @done="handleArchiveDone" />
|
||||
<ContractPartyPlaceholderModal
|
||||
ref="contractModalRef"
|
||||
@save="handleModalSave"
|
||||
/>
|
||||
<MixedSignModal
|
||||
ref="mixedSignModalRef"
|
||||
@business-updated="handleSignBusinessUpdated"
|
||||
/>
|
||||
<UploadSignedModal
|
||||
ref="uploadSignedModalRef"
|
||||
@done="handleSignBusinessUpdated"
|
||||
/>
|
||||
<ContractArchiveModal
|
||||
ref="contractArchiveModalRef"
|
||||
@done="handleArchiveDone"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -302,6 +446,13 @@ function getStatusColor(status) {
|
||||
return statusColorMap[status] || "default";
|
||||
}
|
||||
|
||||
function getStatusLabelByRow(row) {
|
||||
if (isSingleAgreementRecord(row) && row?.status === "pending_sign") {
|
||||
return "待盖章";
|
||||
}
|
||||
return getStatusLabel(row?.status);
|
||||
}
|
||||
|
||||
function canEdit(row) {
|
||||
return ["draft", "rejected"].includes(row.status);
|
||||
}
|
||||
@@ -316,7 +467,9 @@ function canSubmit(row) {
|
||||
|
||||
function canChange(row) {
|
||||
// 变更/补充协议:仅已完成/待采集/待归档的合同允许基于它发起变更
|
||||
return ["completed", "pending_archive", "pending_collect"].includes(row.status);
|
||||
return ["completed", "pending_archive", "pending_collect"].includes(
|
||||
row.status,
|
||||
);
|
||||
}
|
||||
|
||||
function canMockApprove(row) {
|
||||
@@ -329,11 +482,21 @@ function canMockReject(row) {
|
||||
|
||||
function canOnlineSign(row) {
|
||||
// 在线签订只对电子合同开放;纸质/线下签署走“上传已签署版本”兜底。
|
||||
return String(row.isElectron || "1") === "1" && ["pending_sign", "signing"].includes(row.status);
|
||||
return (
|
||||
!isSingleAgreementRecord(row) &&
|
||||
String(row.isElectron || "1") === "1" &&
|
||||
["pending_sign", "signing"].includes(row.status)
|
||||
);
|
||||
}
|
||||
|
||||
function canMockFinishSign(row) {
|
||||
return String(row.isElectron || "1") === "1" && ["pending_sign", "signing"].includes(row.status);
|
||||
if (isSingleAgreementRecord(row)) {
|
||||
return row.status === "pending_sign";
|
||||
}
|
||||
return (
|
||||
String(row.isElectron || "1") === "1" &&
|
||||
["pending_sign", "signing"].includes(row.status)
|
||||
);
|
||||
}
|
||||
|
||||
function canUploadSigned(row) {
|
||||
@@ -341,11 +504,11 @@ function canUploadSigned(row) {
|
||||
}
|
||||
|
||||
function canCollect(row) {
|
||||
return row.status === "pending_collect";
|
||||
return !isSingleAgreementRecord(row) && row.status === "pending_collect";
|
||||
}
|
||||
|
||||
function canArchive(row) {
|
||||
return row.status === "pending_archive";
|
||||
return !isSingleAgreementRecord(row) && row.status === "pending_archive";
|
||||
}
|
||||
|
||||
function isSingleAgreementRecord(row) {
|
||||
@@ -353,14 +516,34 @@ function isSingleAgreementRecord(row) {
|
||||
}
|
||||
|
||||
function canViewApprove(row) {
|
||||
return !isSingleAgreementRecord(row)
|
||||
&& Boolean(row.lawContractId)
|
||||
&& ["reviewing", "rejected", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status);
|
||||
return (
|
||||
!isSingleAgreementRecord(row) &&
|
||||
Boolean(row.lawContractId) &&
|
||||
[
|
||||
"reviewing",
|
||||
"rejected",
|
||||
"pending_sign",
|
||||
"signing",
|
||||
"pending_collect",
|
||||
"pending_archive",
|
||||
"completed",
|
||||
].includes(row.status)
|
||||
);
|
||||
}
|
||||
|
||||
function canViewLawCallback(row) {
|
||||
return !isSingleAgreementRecord(row)
|
||||
&& ["reviewing", "rejected", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status);
|
||||
return (
|
||||
!isSingleAgreementRecord(row) &&
|
||||
[
|
||||
"reviewing",
|
||||
"rejected",
|
||||
"pending_sign",
|
||||
"signing",
|
||||
"pending_collect",
|
||||
"pending_archive",
|
||||
"completed",
|
||||
].includes(row.status)
|
||||
);
|
||||
}
|
||||
|
||||
function canDownloadSigned(row) {
|
||||
@@ -441,6 +624,10 @@ function handleCreate() {
|
||||
contractModalRef.value?.showModal({}, "create");
|
||||
}
|
||||
|
||||
function handleCreateSingleAgreement() {
|
||||
contractModalRef.value?.showModal({ isSingleAgreement: "1" }, "create");
|
||||
}
|
||||
|
||||
function handleView(row) {
|
||||
getLaunchDetail(row.id).then((res) => {
|
||||
if (!isSuccessResponse(res) || !getDataResult(res)) {
|
||||
@@ -511,20 +698,34 @@ function handleSubmit(row) {
|
||||
Modal.confirm({
|
||||
title: singleAgreement ? "确认提交内部审批" : "确认提交法务审批",
|
||||
content: h("div", [
|
||||
h("p", singleAgreement
|
||||
? `确定将单方协议【${row.contractName}】提交内部审批吗?`
|
||||
: `确定将合同【${row.contractName}】提交法务系统审批吗?`),
|
||||
h("p", { style: { color: "#999", fontSize: "12px" } }, "提交后合同进入「审批中」状态,审批完成前不可编辑。"),
|
||||
h(
|
||||
"p",
|
||||
singleAgreement
|
||||
? `确定将单方协议【${row.contractName}】提交内部审批吗?`
|
||||
: `确定将合同【${row.contractName}】提交法务系统审批吗?`,
|
||||
),
|
||||
h(
|
||||
"p",
|
||||
{ style: { color: "#999", fontSize: "12px" } },
|
||||
"提交后合同进入「审批中」状态,审批完成前不可编辑。",
|
||||
),
|
||||
]),
|
||||
okText: "确认提交",
|
||||
cancelText: "取消",
|
||||
onOk: () =>
|
||||
submitLaunchRecord(row.id).then((res) => {
|
||||
if (!isSuccessResponse(res)) {
|
||||
message.error(getResponseMessage(res, singleAgreement ? "提交内部审批失败" : "提交法务失败"));
|
||||
message.error(
|
||||
getResponseMessage(
|
||||
res,
|
||||
singleAgreement ? "提交内部审批失败" : "提交法务失败",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
message.success(singleAgreement ? "已提交内部审批,等待审批" : "已提交法务,等待审批");
|
||||
message.success(
|
||||
singleAgreement ? "已提交内部审批,等待审批" : "已提交法务,等待审批",
|
||||
);
|
||||
getList();
|
||||
}),
|
||||
icon: null,
|
||||
@@ -555,48 +756,84 @@ function handleViewApprove(row) {
|
||||
}
|
||||
|
||||
function buildLawCallbackModalContent(sample) {
|
||||
const approvedJson = JSON.stringify({ data: sample?.approvedData || {} }, null, 2);
|
||||
const rejectedJson = JSON.stringify({ data: sample?.rejectedData || {} }, null, 2);
|
||||
return h("div", { style: { display: "flex", flexDirection: "column", gap: "12px" } }, [
|
||||
h("div", [
|
||||
h("div", { style: { marginBottom: "6px", fontWeight: 600 } }, "回调地址"),
|
||||
h("div", { style: { color: "#666", wordBreak: "break-all" } }, `${sample?.callbackMethod || "POST"} ${sample?.callbackUrl || "/contract/launch/callback/law/approve"}`),
|
||||
]),
|
||||
h("div", [
|
||||
h("div", { style: { marginBottom: "6px", fontWeight: 600 } }, "审批通过 data"),
|
||||
h("pre", {
|
||||
style: {
|
||||
margin: 0,
|
||||
maxHeight: "260px",
|
||||
overflow: "auto",
|
||||
padding: "12px",
|
||||
borderRadius: "8px",
|
||||
background: "#f6f8fa",
|
||||
fontSize: "12px",
|
||||
lineHeight: "1.6",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
},
|
||||
}, approvedJson),
|
||||
]),
|
||||
h("div", [
|
||||
h("div", { style: { marginBottom: "6px", fontWeight: 600 } }, "审批驳回 data"),
|
||||
h("pre", {
|
||||
style: {
|
||||
margin: 0,
|
||||
maxHeight: "220px",
|
||||
overflow: "auto",
|
||||
padding: "12px",
|
||||
borderRadius: "8px",
|
||||
background: "#f6f8fa",
|
||||
fontSize: "12px",
|
||||
lineHeight: "1.6",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
},
|
||||
}, rejectedJson),
|
||||
]),
|
||||
]);
|
||||
const approvedJson = JSON.stringify(
|
||||
{ data: sample?.approvedData || {} },
|
||||
null,
|
||||
2,
|
||||
);
|
||||
const rejectedJson = JSON.stringify(
|
||||
{ data: sample?.rejectedData || {} },
|
||||
null,
|
||||
2,
|
||||
);
|
||||
return h(
|
||||
"div",
|
||||
{ style: { display: "flex", flexDirection: "column", gap: "12px" } },
|
||||
[
|
||||
h("div", [
|
||||
h(
|
||||
"div",
|
||||
{ style: { marginBottom: "6px", fontWeight: 600 } },
|
||||
"回调地址",
|
||||
),
|
||||
h(
|
||||
"div",
|
||||
{ style: { color: "#666", wordBreak: "break-all" } },
|
||||
`${sample?.callbackMethod || "POST"} ${sample?.callbackUrl || "/contract/launch/callback/law/approve"}`,
|
||||
),
|
||||
]),
|
||||
h("div", [
|
||||
h(
|
||||
"div",
|
||||
{ style: { marginBottom: "6px", fontWeight: 600 } },
|
||||
"审批通过 data",
|
||||
),
|
||||
h(
|
||||
"pre",
|
||||
{
|
||||
style: {
|
||||
margin: 0,
|
||||
maxHeight: "260px",
|
||||
overflow: "auto",
|
||||
padding: "12px",
|
||||
borderRadius: "8px",
|
||||
background: "#f6f8fa",
|
||||
fontSize: "12px",
|
||||
lineHeight: "1.6",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
},
|
||||
},
|
||||
approvedJson,
|
||||
),
|
||||
]),
|
||||
h("div", [
|
||||
h(
|
||||
"div",
|
||||
{ style: { marginBottom: "6px", fontWeight: 600 } },
|
||||
"审批驳回 data",
|
||||
),
|
||||
h(
|
||||
"pre",
|
||||
{
|
||||
style: {
|
||||
margin: 0,
|
||||
maxHeight: "220px",
|
||||
overflow: "auto",
|
||||
padding: "12px",
|
||||
borderRadius: "8px",
|
||||
background: "#f6f8fa",
|
||||
fontSize: "12px",
|
||||
lineHeight: "1.6",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
},
|
||||
},
|
||||
rejectedJson,
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
function handleViewLawCallback(row) {
|
||||
@@ -618,7 +855,9 @@ function handleViewLawCallback(row) {
|
||||
function handleMockApprove(row) {
|
||||
const singleAgreement = isSingleAgreementRecord(row);
|
||||
Modal.confirm({
|
||||
title: singleAgreement ? "测试操作:模拟内部审批通过" : "测试操作:模拟审批通过",
|
||||
title: singleAgreement
|
||||
? "测试操作:模拟内部审批通过"
|
||||
: "测试操作:模拟审批通过",
|
||||
content: singleAgreement
|
||||
? `模拟单方协议【${row.contractName}】内部审批通过,状态将变为「待签署」。`
|
||||
: `模拟法务审批通过合同【${row.contractName}】,状态将变为「待签署」。`,
|
||||
@@ -640,12 +879,21 @@ function handleMockApprove(row) {
|
||||
function handleMockReject(row) {
|
||||
const singleAgreement = isSingleAgreementRecord(row);
|
||||
Modal.confirm({
|
||||
title: singleAgreement ? "测试操作:模拟内部审批驳回" : "测试操作:模拟审批驳回",
|
||||
title: singleAgreement
|
||||
? "测试操作:模拟内部审批驳回"
|
||||
: "测试操作:模拟审批驳回",
|
||||
content: h("div", [
|
||||
h("p", singleAgreement
|
||||
? `模拟单方协议【${row.contractName}】内部审批驳回,状态将回到「已驳回」。`
|
||||
: `模拟法务审批驳回合同【${row.contractName}】,状态将回到「已驳回」。`),
|
||||
h("p", { style: { color: "#999", fontSize: "12px" } }, "驳回后可重新编辑再提交。"),
|
||||
h(
|
||||
"p",
|
||||
singleAgreement
|
||||
? `模拟单方协议【${row.contractName}】内部审批驳回,状态将回到「已驳回」。`
|
||||
: `模拟法务审批驳回合同【${row.contractName}】,状态将回到「已驳回」。`,
|
||||
),
|
||||
h(
|
||||
"p",
|
||||
{ style: { color: "#999", fontSize: "12px" } },
|
||||
"驳回后可重新编辑再提交。",
|
||||
),
|
||||
]),
|
||||
okText: "确认驳回",
|
||||
cancelText: "取消",
|
||||
@@ -663,15 +911,23 @@ function handleMockReject(row) {
|
||||
}
|
||||
|
||||
function handleMockFinishSign(row) {
|
||||
const singleAgreement = isSingleAgreementRecord(row);
|
||||
Modal.confirm({
|
||||
title: "测试操作:模拟签署完成",
|
||||
content: `模拟合同【${row.contractName}】签署完成,状态将变为「待采集」。`,
|
||||
title: singleAgreement
|
||||
? "测试操作:模拟盖章完成"
|
||||
: "测试操作:模拟签署完成",
|
||||
content: singleAgreement
|
||||
? `模拟单方协议【${row.contractName}】内部业务章加盖完成,状态将变为「已完成」。`
|
||||
: `模拟合同【${row.contractName}】签署完成,状态将变为「待采集」。`,
|
||||
okText: "确认模拟",
|
||||
cancelText: "取消",
|
||||
onOk: () => {
|
||||
const signDate = sliceDateValue(row.signDate) || todayString();
|
||||
const sealDate = sliceDateValue(row.sealDate) || signDate;
|
||||
const activeDate = sliceDateValue(row.activeDate) || sliceDateValue(row.effectiveStart) || signDate;
|
||||
const activeDate =
|
||||
sliceDateValue(row.activeDate) ||
|
||||
sliceDateValue(row.effectiveStart) ||
|
||||
signDate;
|
||||
return finishLaunchSeal(row.id, {
|
||||
sealContractId: row.sealContractId || `TEST-SEAL-${row.id}`,
|
||||
sealContractStatus: 2000,
|
||||
@@ -679,16 +935,27 @@ function handleMockFinishSign(row) {
|
||||
signedFilePath: row.contractTextFilePath || `mock-signed/${row.id}.pdf`,
|
||||
signedFileType: "pdf",
|
||||
signedFileSize: row.contractTextFileSize || null,
|
||||
signedOfflineReason: "测试按钮模拟签署完成,真实签章接入后可删除此按钮",
|
||||
signedOfflineReason: singleAgreement
|
||||
? "测试按钮模拟内部业务章加盖完成"
|
||||
: "测试按钮模拟签署完成,真实签章接入后可删除此按钮",
|
||||
signDate: formatDateTimeValue(signDate),
|
||||
sealDate: formatDateTimeValue(sealDate),
|
||||
activeDate: formatDateTimeValue(activeDate),
|
||||
}).then((res) => {
|
||||
if (!isSuccessResponse(res)) {
|
||||
message.error(getResponseMessage(res, "测试签署完成失败"));
|
||||
message.error(
|
||||
getResponseMessage(
|
||||
res,
|
||||
singleAgreement ? "测试盖章完成失败" : "测试签署完成失败",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
message.success("测试签署完成,合同已流转到待采集");
|
||||
message.success(
|
||||
singleAgreement
|
||||
? "测试盖章完成,单方协议已办结"
|
||||
: "测试签署完成,合同已流转到待采集",
|
||||
);
|
||||
getList();
|
||||
});
|
||||
},
|
||||
@@ -698,24 +965,28 @@ function handleMockFinishSign(row) {
|
||||
|
||||
function handleDownloadSigned(row) {
|
||||
const filePath = row.signedFilePath;
|
||||
const fileName = row.signedFileName || `${row.contractName || "合同"}-已签署版.pdf`;
|
||||
const fileName =
|
||||
row.signedFileName || `${row.contractName || "合同"}-已签署版.pdf`;
|
||||
if (!filePath) {
|
||||
message.warning("暂无签署文件可下载");
|
||||
return;
|
||||
}
|
||||
downloadLaunchFile(filePath).then((res) => {
|
||||
const blob = new Blob([res.data]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}).catch(() => {
|
||||
message.error("签署文件下载失败");
|
||||
});
|
||||
downloadLaunchFile(filePath)
|
||||
.then((res) => {
|
||||
const arrayBuffer = res?.data ?? res;
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(() => {
|
||||
message.error("签署文件下载失败");
|
||||
});
|
||||
}
|
||||
|
||||
function handleUploadSigned(row) {
|
||||
@@ -741,7 +1012,8 @@ function handleSignBusinessUpdated() {
|
||||
}
|
||||
|
||||
function handleModalSave(payload) {
|
||||
const requestFn = payload.mode === "edit" ? updateLaunchDraft : createLaunchDraft;
|
||||
const requestFn =
|
||||
payload.mode === "edit" ? updateLaunchDraft : createLaunchDraft;
|
||||
requestFn(payload.record).then((res) => {
|
||||
if (!isSuccessResponse(res)) {
|
||||
message.error(getResponseMessage(res, "保存合同失败"));
|
||||
@@ -792,16 +1064,23 @@ function handleTerminate(row) {
|
||||
let terminateReason = "";
|
||||
Modal.confirm({
|
||||
title: `确认终止合同【${row.contractName}】吗?`,
|
||||
content: h("div", { style: { display: "flex", flexDirection: "column", gap: "12px" } }, [
|
||||
h("div", "除已完成外,任意环节都可终止。当前仅终止业务系统内流程,法务系统需分别操作。"),
|
||||
h(Input.TextArea, {
|
||||
rows: 4,
|
||||
placeholder: "可选:请输入终止原因",
|
||||
onChange: (event) => {
|
||||
terminateReason = event?.target?.value || "";
|
||||
},
|
||||
}),
|
||||
]),
|
||||
content: h(
|
||||
"div",
|
||||
{ style: { display: "flex", flexDirection: "column", gap: "12px" } },
|
||||
[
|
||||
h(
|
||||
"div",
|
||||
"除已完成外,任意环节都可终止。当前仅终止业务系统内流程,法务系统需分别操作。",
|
||||
),
|
||||
h(Input.TextArea, {
|
||||
rows: 4,
|
||||
placeholder: "可选:请输入终止原因",
|
||||
onChange: (event) => {
|
||||
terminateReason = event?.target?.value || "";
|
||||
},
|
||||
}),
|
||||
],
|
||||
),
|
||||
okText: "确认终止",
|
||||
cancelText: "取消",
|
||||
okType: "danger",
|
||||
|
||||
@@ -371,7 +371,8 @@ function handleExportExcel() {
|
||||
pageNo: 1,
|
||||
pageSize: 10000,
|
||||
}).then((data) => {
|
||||
const blob = new Blob([data], {
|
||||
const arrayBuffer = data?.data ?? data;
|
||||
const blob = new Blob([arrayBuffer], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
saveAs(blob, `合同日志_${Date.now()}.xlsx`);
|
||||
|
||||
@@ -559,8 +559,9 @@ async function handleTemplateFile(file) {
|
||||
}
|
||||
|
||||
const downloadedBuffer = await downloadTemplateFile(uploadedFilePath)
|
||||
const downloadedArrayBuffer = downloadedBuffer?.data ?? downloadedBuffer
|
||||
const previewBuffer =
|
||||
downloadedBuffer.data instanceof ArrayBuffer ? downloadedBuffer.data : downloadedBuffer.data?.buffer
|
||||
downloadedArrayBuffer instanceof ArrayBuffer ? downloadedArrayBuffer : downloadedArrayBuffer?.buffer
|
||||
|
||||
if (!previewBuffer) {
|
||||
message.error('模板文件已上传,但回读渲染失败')
|
||||
@@ -798,7 +799,8 @@ async function handleSubmit(status) {
|
||||
async function loadTemplateBufferFromRecord(record) {
|
||||
if (record.templateFilePath) {
|
||||
const response = await downloadTemplateFile(record.templateFilePath)
|
||||
templateArrayBuffer.value = response.data instanceof ArrayBuffer ? response.data.slice(0) : response.data
|
||||
const arrayBuffer = response?.data ?? response
|
||||
templateArrayBuffer.value = arrayBuffer instanceof ArrayBuffer ? arrayBuffer.slice(0) : arrayBuffer
|
||||
setUploadList(record.templateFileName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,24 +487,33 @@
|
||||
</div>
|
||||
<div class="step-card-body">
|
||||
<div class="complete-form-grid">
|
||||
<a-date-picker
|
||||
v-model:value="completeSignDate"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
placeholder="签订日期"
|
||||
/>
|
||||
<a-date-picker
|
||||
v-model:value="completeSealDate"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
placeholder="用印时间"
|
||||
/>
|
||||
<a-date-picker
|
||||
v-model:value="completeActiveDate"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
placeholder="合同生效日期"
|
||||
/>
|
||||
<div class="complete-form-item">
|
||||
<div class="complete-form-label">签订日期</div>
|
||||
<a-date-picker
|
||||
v-model:value="completeSignDate"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
placeholder="请选择签订日期"
|
||||
/>
|
||||
</div>
|
||||
<div class="complete-form-item">
|
||||
<div class="complete-form-label">用印时间</div>
|
||||
<a-date-picker
|
||||
v-model:value="completeSealDate"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
placeholder="请选择用印时间"
|
||||
/>
|
||||
</div>
|
||||
<div class="complete-form-item">
|
||||
<div class="complete-form-label">合同生效日期</div>
|
||||
<a-date-picker
|
||||
v-model:value="completeActiveDate"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
placeholder="请选择合同生效日期"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -762,6 +771,23 @@ function formatDateTimeValue(value) {
|
||||
return value ? `${value} 00:00:00` : null;
|
||||
}
|
||||
|
||||
function normalizePdfPreviewImages(list = []) {
|
||||
return (Array.isArray(list) ? list : [])
|
||||
.map((item) => {
|
||||
if (typeof item === "string") return item;
|
||||
if (!item || typeof item !== "object") return "";
|
||||
return (
|
||||
item.url ||
|
||||
item.imageUrl ||
|
||||
item.previewUrl ||
|
||||
item.fileUrl ||
|
||||
item.path ||
|
||||
""
|
||||
);
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function syncCompletionDates(record = {}) {
|
||||
completeSignDate.value = sliceDateValue(record?.signDate) || todayString();
|
||||
completeSealDate.value = sliceDateValue(record?.sealDate) || completeSignDate.value;
|
||||
@@ -895,7 +921,11 @@ async function buildBusinessBodyUploadFile(record = {}) {
|
||||
if (!bodyFile.filePath) {
|
||||
return null;
|
||||
}
|
||||
const buffer = await downloadLaunchFile(bodyFile.filePath);
|
||||
const response = await downloadLaunchFile(bodyFile.filePath);
|
||||
const buffer = response?.data ?? response;
|
||||
if (!buffer) {
|
||||
throw new Error("合同正文下载结果为空");
|
||||
}
|
||||
const type =
|
||||
String(bodyFile.fileType || "").toLowerCase() === "pdf"
|
||||
? "application/pdf"
|
||||
@@ -1108,7 +1138,7 @@ async function loadContract(cid) {
|
||||
contractName.value = detail?.contractName || contractName.value;
|
||||
contractStatus.value =
|
||||
detail?.contractStatus ?? detail?.status ?? contractStatus.value;
|
||||
pdfImages.value = preview?.pdfToImageList || [];
|
||||
pdfImages.value = normalizePdfPreviewImages(preview?.pdfToImageList);
|
||||
imageWidth.value = Number(preview?.imageWidth) || 793;
|
||||
imageHeight.value = Number(preview?.imageHeight) || 1122;
|
||||
updateBusinessStatusByContractStatus(contractStatus.value);
|
||||
@@ -1144,7 +1174,7 @@ async function handleCreateContract() {
|
||||
|
||||
const data = getResponseResult(res);
|
||||
contractId.value = data.contractId || "";
|
||||
pdfImages.value = data.pdfToImageList || [];
|
||||
pdfImages.value = normalizePdfPreviewImages(data.pdfToImageList);
|
||||
imageWidth.value = Number(data.imageWidth) || 793;
|
||||
imageHeight.value = Number(data.imageHeight) || 1122;
|
||||
contractStatus.value = 10;
|
||||
@@ -1676,7 +1706,7 @@ async function confirmTimestamp(item) {
|
||||
item.status = "signed";
|
||||
item._signTime = data.signatureTime || "";
|
||||
if (data.pdfToImageList?.length) {
|
||||
pdfImages.value = data.pdfToImageList;
|
||||
pdfImages.value = normalizePdfPreviewImages(data.pdfToImageList);
|
||||
}
|
||||
if (data.contractStatus !== undefined) {
|
||||
contractStatus.value = data.contractStatus;
|
||||
@@ -1882,9 +1912,10 @@ watch(visible, (value) => {
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
column-gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px dashed #eef2f6;
|
||||
font-size: 14px;
|
||||
@@ -1896,10 +1927,16 @@ watch(visible, (value) => {
|
||||
|
||||
.info-item span {
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-item strong {
|
||||
color: #0f172a;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.business-tag-row {
|
||||
@@ -1933,9 +1970,10 @@ watch(visible, (value) => {
|
||||
}
|
||||
|
||||
.business-meta-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
column-gap: 12px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
padding-bottom: 8px;
|
||||
@@ -1947,14 +1985,22 @@ watch(visible, (value) => {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.business-meta-item span {
|
||||
white-space: nowrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.business-meta-item strong {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
color: #0f172a;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: initial;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.upload-mode-hint {
|
||||
|
||||
Reference in New Issue
Block a user