Files
stamp-ant/src/views/contract/launch/index.vue
T

1256 lines
36 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<section class="section-container">
<a-card
:bordered="false"
class="query-card"
:body-style="queryCardBodyStyle"
>
<div ref="queryContainer" class="section-query-container">
<a-form ref="formRef" :model="queryParam" layout="inline">
<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-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-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-form-item label="合同类型">
<a-select
v-model:value="queryParam.contractType"
allow-clear
placeholder="请选择合同类型"
:options="contractTypeOptions"
/>
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-form-item label="当前状态">
<a-select
v-model:value="queryParam.status"
allow-clear
placeholder="请选择状态"
:options="contractRecordStatusOptions"
/>
</a-form-item>
</a-col>
<a-col :md="12" :sm="24">
<a-form-item label="发起日期">
<a-range-picker
v-model:value="queryParam.dateRange"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
</a-card>
<a-card
:bordered="false"
class="table-card"
:body-style="tableCardBodyStyle"
>
<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
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>
<vxe-table
ref="tableRef"
border
:height="tableHeight"
:row-config="{ isHover: true, height: 44 }"
:column-config="{ resizable: true, minWidth: 120 }"
:custom-config="{ storage: true }"
:scroll-y="{ enabled: true, gt: 0, mode: 'wheel' }"
:checkbox-config="{ range: true }"
:data="tableData"
:loading="loading"
: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"
>
<template #default="{ row }">
{{
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="status" title="状态" width="120">
<template #default="{ row }">
<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 title="操作" width="720" fixed="right">
<template #default="{ row }">
<div class="table-actions">
<a @click="handleView(row)">查看</a>
<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)">{{
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>
</template>
</vxe-column>
</vxe-table>
<a-pagination
id="pageWrapper"
ref="pageWrapperRef"
v-model:pageSize="queryParam.pageSize"
v-model:current="queryParam.pageNo"
class="table-pagination"
show-size-changer
:page-size-options="['10', '20', '50', '100']"
:total="total"
:show-total="(value) => `共 ${value} 条`"
@change="onShowSizeChange"
@show-size-change="onShowSizeChange"
/>
</a-card>
<ContractPartyPlaceholderModal
ref="contractModalRef"
@save="handleModalSave"
/>
<ContractCollectModal
ref="contractCollectModalRef"
@done="handleCollectDone"
/>
<MixedSignModal
ref="mixedSignModalRef"
@business-updated="handleSignBusinessUpdated"
/>
<UploadSignedModal
ref="uploadSignedModalRef"
@done="handleSignBusinessUpdated"
/>
<ContractArchiveModal
ref="contractArchiveModalRef"
@done="handleArchiveDone"
/>
</section>
</template>
<script setup>
import { h, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import {
DeleteOutlined,
PlusOutlined,
RedoOutlined,
SearchOutlined,
} from "@ant-design/icons-vue";
import { Input, message, Modal } from "ant-design-vue";
import ContractPartyPlaceholderModal from "./components/ContractPartyPlaceholderModal.vue";
import ContractCollectModal from "./components/ContractCollectModal.vue";
import MixedSignModal from "./components/mixedSignModal.vue";
import UploadSignedModal from "./components/UploadSignedModal.vue";
import ContractArchiveModal from "./components/ContractArchiveModal.vue";
import {
contractTypeOptions,
getContractTypeSimpleLabel,
} from "./contractLaunchOptions.js";
import {
copyLaunchDraft,
createLaunchChangeDraft,
createLaunchDraft,
deleteLaunchRecord,
getLaunchDetail,
getLawApproveCallbackSample,
mockApproveLaunch,
mockRejectLaunch,
finishLaunchSeal,
queryApproveViewUrl,
queryLaunchList,
submitLaunchRecord,
downloadLaunchFile,
terminateLaunchRecord,
updateLaunchDraft,
} from "@/api/launch.js";
const contractRecordStatusOptions = [
{ label: "草稿", value: "draft" },
{ label: "审批中", value: "reviewing" },
{ label: "审批驳回", value: "rejected" },
{ label: "待签署", value: "pending_sign" },
{ label: "签署中", value: "signing" },
{ label: "待采集", value: "pending_collect" },
{ label: "待归档", value: "pending_archive" },
{ label: "已完成", value: "completed" },
{ label: "已终止", value: "terminated" },
];
const formRef = ref(null);
const queryContainer = ref(null);
const pageWrapperRef = ref(null);
const tableRef = ref(null);
const toolbarRef = ref(null);
const contractModalRef = ref(null);
const contractCollectModalRef = ref(null);
const mixedSignModalRef = ref(null);
const uploadSignedModalRef = ref(null);
const contractArchiveModalRef = ref(null);
const total = ref(0);
const loading = ref(false);
const tableHeight = ref(420);
const tableData = ref([]);
const selectedRows = ref([]);
const selectedRowKeys = ref([]);
const queryParam = ref({
pageNo: 1,
pageSize: 10,
contractName: undefined,
contractCode: undefined,
contractType: undefined,
status: undefined,
dateRange: [],
});
const queryCardBodyStyle = {
padding: "20px 24px 6px",
};
const tableCardBodyStyle = {
display: "flex",
flexFlow: "column nowrap",
minHeight: "0",
padding: "8px 12px 6px",
};
const statusLabelMap = {
draft: "草稿",
reviewing: "审批中",
rejected: "审批驳回",
pending_sign: "待签署",
signing: "签署中",
pending_collect: "待采集",
pending_archive: "待归档",
completed: "已完成",
terminated: "已终止",
};
const statusColorMap = {
draft: "default",
reviewing: "processing",
rejected: "error",
pending_sign: "warning",
signing: "purple",
pending_collect: "cyan",
pending_archive: "orange",
completed: "success",
terminated: "default",
};
function isSuccessResponse(res) {
return String(res?.code || "") === "0000";
}
function getPageResult(res) {
return res?.result || {};
}
function getDataResult(res) {
return res?.result || null;
}
function getResponseMessage(res, fallback = "操作失败") {
return res?.message || res?.msg || fallback;
}
function sliceDateValue(value) {
return value ? String(value).slice(0, 10) : "";
}
function todayString() {
const now = new Date();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
return `${now.getFullYear()}-${month}-${day}`;
}
function formatDateTimeValue(value) {
return value ? `${value} 00:00:00` : null;
}
function connectToolbar() {
const table = tableRef.value;
const toolbar = toolbarRef.value;
if (table && toolbar && table.connect) {
table.connect(toolbar);
}
}
function getStatusLabel(status) {
return statusLabelMap[status] || status;
}
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);
}
function canDelete(row) {
return ["draft", "rejected"].includes(row.status);
}
function canSubmit(row) {
return ["draft", "rejected"].includes(row.status);
}
function canChange(row) {
// 变更/补充协议:仅已完成/待采集/待归档的合同允许基于它发起变更
return ["completed", "pending_archive", "pending_collect"].includes(
row.status,
);
}
function canMockApprove(row) {
return row.status === "reviewing";
}
function canMockReject(row) {
return row.status === "reviewing";
}
function canOnlineSign(row) {
// 在线签订只对电子合同开放;纸质/线下签署走“上传已签署版本”兜底。
return (
!isSingleAgreementRecord(row) &&
String(row.isElectron || "1") === "1" &&
["pending_sign", "signing"].includes(row.status)
);
}
function canMockFinishSign(row) {
if (isSingleAgreementRecord(row)) {
return row.status === "pending_sign";
}
return (
String(row.isElectron || "1") === "1" &&
["pending_sign", "signing"].includes(row.status)
);
}
function canUploadSigned(row) {
// 纸质合同(0)才显示上传已签署版本;电子合同走在线签订
return String(row.isElectron || "1") === "0" && ["pending_sign", "signing"].includes(row.status);
}
function canCollect(row) {
return !isSingleAgreementRecord(row) && row.status === "pending_collect";
}
function canArchive(row) {
return !isSingleAgreementRecord(row) && row.status === "pending_archive";
}
function isSingleAgreementRecord(row) {
return String(row?.isSingleAgreement || "0") === "1";
}
function canViewApprove(row) {
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)
);
}
function canDownloadSigned(row) {
return Boolean(row.signedFilePath);
}
function canTerminate(row) {
return row.status !== "completed" && row.status !== "terminated";
}
function selectChangeEvent({ records }) {
selectedRows.value = records;
selectedRowKeys.value = records.map((item) => item.id);
}
function buildQueryPayload() {
return {
pageNo: queryParam.value.pageNo,
pageSize: queryParam.value.pageSize,
contractName: queryParam.value.contractName,
contractCode: queryParam.value.contractCode,
contractType: queryParam.value.contractType,
status: queryParam.value.status,
dateStart: queryParam.value.dateRange?.[0],
dateEnd: queryParam.value.dateRange?.[1],
};
}
function getList() {
loading.value = true;
queryLaunchList(buildQueryPayload())
.then((res) => {
if (!isSuccessResponse(res)) {
tableData.value = [];
total.value = 0;
message.error(getResponseMessage(res, "合同发起列表查询失败"));
return;
}
const pageResult = getPageResult(res);
tableData.value = pageResult.data || [];
total.value = pageResult.totalSize || 0;
})
.finally(() => {
loading.value = false;
nextTick(() => {
connectToolbar();
syncTableHeight();
});
});
}
function handleQuery() {
queryParam.value.pageNo = 1;
getList();
}
function handleReset() {
formRef.value?.resetFields?.();
Object.assign(queryParam.value, {
pageNo: 1,
pageSize: 10,
contractName: undefined,
contractCode: undefined,
contractType: undefined,
status: undefined,
dateRange: [],
});
getList();
}
function onShowSizeChange(current, pageSize) {
queryParam.value.pageNo = current;
queryParam.value.pageSize = pageSize;
getList();
}
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)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
contractModalRef.value?.showModal(getDataResult(res), "view");
});
}
function handleEdit(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
contractModalRef.value?.showModal(getDataResult(res), "edit");
});
}
function handleCopy(row) {
Modal.confirm({
title: "确认复制合同",
content: `将复制合同【${row.contractName}】生成一份新草稿,复制后自动进入编辑状态。`,
okText: "确认复制",
cancelText: "取消",
onOk: () =>
copyLaunchDraft(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "复制合同失败"));
return;
}
const copiedRecord = getDataResult(res);
message.success("合同已复制为新草稿请继续完善后保存");
getList();
contractModalRef.value?.showModal(copiedRecord, "edit");
}),
icon: null,
});
}
function handleChange(row) {
Modal.confirm({
title: "确认发起变更合同",
content: h("div", [
h("p", `将基于【${row.contractName}】发起一份变更/补充协议,`),
h("p", "自动关联主合同编码生成新草稿原合同不受影响"),
]),
okText: "确认变更",
cancelText: "取消",
onOk: () =>
createLaunchChangeDraft(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "发起变更合同失败"));
return;
}
const changedRecord = getDataResult(res);
message.success("已生成变更草稿并自动关联主合同编码");
getList();
contractModalRef.value?.showModal(changedRecord, "edit");
}),
icon: null,
});
}
function handleSubmit(row) {
const singleAgreement = isSingleAgreementRecord(row);
// 非单方协议:提交法务前校验必填项完整性
if (!singleAgreement) {
return getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
const detail = getDataResult(res);
const missingFields = [];
if (!detail.contractTypeCode) missingFields.push("合同类型");
if (!detail.valuationMode) missingFields.push("计价方式");
if (!detail.currencyName) missingFields.push("币种");
if (!detail.sealType) missingFields.push("签订方式");
if (!detail.isElectron) missingFields.push("签署方式");
if (!detail.paymentDirection) missingFields.push("收支方向");
if (!detail.textSource) missingFields.push("文本来源");
if (!detail.contractSource) missingFields.push("合同来源");
if (!detail.executorAccount) missingFields.push("合同执行人");
if (detail.isMajorContract === null || detail.isMajorContract === undefined) missingFields.push("是否重大合同");
if (detail.isOpenBidding === null || detail.isOpenBidding === undefined) missingFields.push("是否公开招标");
if (detail.isSgat === null || detail.isSgat === undefined) missingFields.push("是否涉及港澳台");
if (!detail.performancePlans || !detail.performancePlans.length) missingFields.push("履行计划");
if (!detail.contractTextFileName) missingFields.push("合同正文");
if (missingFields.length) {
message.warning(`以下必填项不完整,请先在编辑草稿中补全后再提交:\n${missingFields.join("")}`);
return;
}
showSubmitConfirm(row, detail, false);
});
}
// 单方协议:直接提交内部审批
showSubmitConfirm(row, null, true);
}
function showSubmitConfirm(row, detail, singleAgreement) {
Modal.confirm({
title: singleAgreement ? "确认提交内部审批" : "确认提交法务审批",
content: h("div", [
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 ? "提交内部审批失败" : "提交法务失败",
),
);
return;
}
message.success(
singleAgreement ? "已提交内部审批等待审批" : "已提交法务等待审批",
);
getList();
}),
icon: null,
});
}
function handleViewApprove(row) {
if (!row.lawContractId) {
message.warning("当前暂无法务合同ID审批记录地址尚未生成");
return;
}
queryApproveViewUrl({
contractId: String(row.lawContractId),
ivUser: row.initiator || "admin",
}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "获取审批记录查看地址失败"));
return;
}
const approveViewUrl = res?.result?.approveViewUrl;
if (!approveViewUrl) {
message.warning("审批记录地址为空");
return;
}
window.open(approveViewUrl, "_blank");
});
}
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,
),
]),
],
);
}
function handleViewLawCallback(row) {
getLawApproveCallbackSample(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取法务回调样例失败"));
return;
}
const sample = getDataResult(res);
Modal.info({
title: `法务回调 data 结构:${row.contractName}`,
width: 900,
okText: "知道了",
content: buildLawCallbackModalContent(sample),
});
});
}
function handleMockApprove(row) {
const singleAgreement = isSingleAgreementRecord(row);
Modal.confirm({
title: singleAgreement
? "测试操作模拟内部审批通过"
: "测试操作模拟审批通过",
content: singleAgreement
? `模拟单方协议【${row.contractName}】内部审批通过,状态将变为「待签署」。`
: `模拟法务审批通过合同【${row.contractName}】,状态将变为「待签署」。`,
okText: "确认模拟",
cancelText: "取消",
onOk: () =>
mockApproveLaunch(row.id, {}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "模拟审批通过失败"));
return;
}
message.success("测试审批通过完成合同已流转到待签署");
getList();
}),
icon: null,
});
}
function handleMockReject(row) {
const singleAgreement = isSingleAgreementRecord(row);
Modal.confirm({
title: singleAgreement
? "测试操作模拟内部审批驳回"
: "测试操作模拟审批驳回",
content: h("div", [
h(
"p",
singleAgreement
? `模拟单方协议【${row.contractName}】内部审批驳回,状态将回到「已驳回」。`
: `模拟法务审批驳回合同【${row.contractName}】,状态将回到「已驳回」。`,
),
h(
"p",
{ style: { color: "#999", fontSize: "12px" } },
"驳回后可重新编辑再提交",
),
]),
okText: "确认驳回",
cancelText: "取消",
onOk: () =>
mockRejectLaunch(row.id, {}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "模拟审批驳回失败"));
return;
}
message.success("测试审批驳回完成合同已流转到审批驳回");
getList();
}),
icon: null,
});
}
function handleMockFinishSign(row) {
const singleAgreement = isSingleAgreementRecord(row);
Modal.confirm({
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;
return finishLaunchSeal(row.id, {
sealContractId: row.sealContractId || `TEST-SEAL-${row.id}`,
sealContractStatus: 2000,
signedFileName: `${row.contractName || "合同"}-测试已签署.pdf`,
signedFilePath: row.contractTextFilePath || `mock-signed/${row.id}.pdf`,
signedFileType: "pdf",
signedFileSize: row.contractTextFileSize || null,
signedOfflineReason: singleAgreement
? "测试按钮模拟内部业务章加盖完成"
: "测试按钮模拟签署完成真实签章接入后可删除此按钮",
signDate: formatDateTimeValue(signDate),
sealDate: formatDateTimeValue(sealDate),
activeDate: formatDateTimeValue(activeDate),
}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(
getResponseMessage(
res,
singleAgreement ? "测试盖章完成失败" : "测试签署完成失败",
),
);
return;
}
message.success(
singleAgreement
? "测试盖章完成单方协议已办结"
: "测试签署完成合同已流转到待采集",
);
getList();
});
},
icon: null,
});
}
function handleDownloadSigned(row) {
const filePath = row.signedFilePath;
const fileName =
row.signedFileName || `${row.contractName || "合同"}-已签署版.pdf`;
if (!filePath) {
message.warning("暂无签署文件可下载");
return;
}
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) {
uploadSignedModalRef.value?.showModal(row);
}
function handleOnlineSign(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
const detail = getDataResult(res);
mixedSignModalRef.value?.showModal({
...detail,
contractId: detail.sealContractId,
});
});
}
function handleSignBusinessUpdated() {
getList();
}
function handleModalSave(payload) {
const requestFn =
payload.mode === "edit" ? updateLaunchDraft : createLaunchDraft;
requestFn(payload.record).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "保存合同失败"));
return;
}
message.success(
payload.successMessage ||
(payload.mode === "edit" ? "合同修改成功" : "合同草稿保存成功"),
);
getList();
});
}
function handleMockCollect(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
contractCollectModalRef.value?.showModal(getDataResult(res));
});
}
function handleCollectDone() {
getList();
}
function handleArchive(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取归档详情失败"));
return;
}
contractArchiveModalRef.value?.showModal(getDataResult(res));
});
}
function handleArchiveDone() {
getList();
}
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 || "";
},
}),
],
),
okText: "确认终止",
cancelText: "取消",
okType: "danger",
onOk: () =>
terminateLaunchRecord(row.id, { terminateReason }).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "终止合同失败"));
return;
}
message.success("合同已终止");
getList();
}),
icon: null,
});
}
function handleDelete(row) {
Modal.confirm({
title: `确认删除合同【${row.contractName}】吗?`,
content: "仅草稿或审批驳回状态支持删除",
okText: "删除",
cancelText: "取消",
okType: "danger",
onOk: () => {
deleteLaunchRecord(row.id).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "删除失败"));
return;
}
message.success("删除成功");
getList();
});
},
});
}
function handleBatchDelete() {
const undeletable = selectedRows.value.filter((item) => !canDelete(item));
if (undeletable.length) {
message.warning("仅草稿或审批驳回状态支持删除请重新选择");
return;
}
Modal.confirm({
title: `确认删除选中的 ${selectedRowKeys.value.length} 条记录吗?`,
content: "仅草稿或审批驳回状态支持删除",
okText: "删除",
cancelText: "取消",
okType: "danger",
onOk: async () => {
for (const id of selectedRowKeys.value) {
await deleteLaunchRecord(id);
}
message.success("批量删除成功");
selectedRows.value = [];
selectedRowKeys.value = [];
getList();
},
});
}
function syncTableHeight() {
nextTick(() => {
const viewportHeight = window.innerHeight;
const queryHeight = queryContainer.value?.offsetHeight || 0;
const pageHeight = pageWrapperRef.value?.$el?.offsetHeight || 56;
tableHeight.value = Math.max(
320,
viewportHeight - queryHeight - pageHeight - 320,
);
});
}
onMounted(() => {
handleQuery();
nextTick(() => {
connectToolbar();
syncTableHeight();
});
window.addEventListener("resize", syncTableHeight);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", syncTableHeight);
});
</script>
<style scoped>
.section-container {
height: calc(100vh - 116px);
display: flex;
flex-direction: column;
gap: 12px;
}
.query-card {
flex: 0 0 auto;
}
.table-card {
flex: 1 1 auto;
min-height: 0;
}
.section-query-container :deep(.ant-form-item) {
display: flex;
width: 100%;
margin-bottom: 14px;
}
.section-query-container :deep(.ant-form-item .ant-form-item-control) {
flex: 1;
}
.table-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.table-pagination {
display: flex;
justify-content: flex-end;
padding: 12px 4px 2px;
}
@media (max-width: 960px) {
.section-container {
height: auto;
}
}
</style>