855 lines
26 KiB
Vue
855 lines
26 KiB
Vue
<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-input v-model:value="queryParam.contractType" allow-clear placeholder="请输入合同类型编码" />
|
||
</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 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)">{{ getStatusLabel(row.status) }}</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)">提交法务</a>
|
||
<a v-if="canViewApprove(row)" @click="handleViewApprove(row)">审批记录</a>
|
||
<a v-if="canMockApprove(row)" @click="handleMockApprove(row)">测试通过</a>
|
||
<a v-if="canOnlineSign(row)" @click="handleOnlineSign(row)">在线签订</a>
|
||
<a v-if="row.status === 'completed' && row.signedFilePath" @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="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" />
|
||
<MixedSignModal ref="mixedSignModalRef" @business-updated="handleSignBusinessUpdated" />
|
||
<UploadSignedModal ref="uploadSignedModalRef" @done="handleSignBusinessUpdated" />
|
||
</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 MixedSignModal from "./components/mixedSignModal.vue";
|
||
import UploadSignedModal from "./components/UploadSignedModal.vue";
|
||
import { getContractTypeSimpleLabel } from "./contractLaunchOptions.js";
|
||
import {
|
||
archiveLaunch,
|
||
copyLaunchDraft,
|
||
createLaunchChangeDraft,
|
||
createLaunchDraft,
|
||
deleteLaunchRecord,
|
||
getLaunchDetail,
|
||
mockApproveLaunch,
|
||
mockCollectLaunch,
|
||
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 mixedSignModalRef = ref(null);
|
||
const uploadSignedModalRef = 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 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 ["pending_sign", "signing"].includes(row.status);
|
||
}
|
||
|
||
function canMockFinishSign(row) {
|
||
return ["pending_sign", "signing"].includes(row.status);
|
||
}
|
||
|
||
function canUploadSigned(row) {
|
||
return ["pending_sign", "signing"].includes(row.status);
|
||
}
|
||
|
||
function canCollect(row) {
|
||
return row.status === "pending_collect";
|
||
}
|
||
|
||
function canArchive(row) {
|
||
return row.status === "pending_archive";
|
||
}
|
||
|
||
function canViewApprove(row) {
|
||
return ["reviewing", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status);
|
||
}
|
||
|
||
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 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) {
|
||
Modal.confirm({
|
||
title: "确认提交法务审批",
|
||
content: h("div", [
|
||
h("p", `确定将合同【${row.contractName}】提交法务系统审批吗?`),
|
||
h("p", { style: { color: "#999", fontSize: "12px" } }, "提交后合同进入「审批中」状态,审批完成前不可编辑。"),
|
||
]),
|
||
okText: "确认提交",
|
||
cancelText: "取消",
|
||
onOk: () =>
|
||
submitLaunchRecord(row.id).then((res) => {
|
||
if (!isSuccessResponse(res)) {
|
||
message.error(getResponseMessage(res, "提交法务失败"));
|
||
return;
|
||
}
|
||
message.success("已提交法务,等待审批");
|
||
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 handleMockApprove(row) {
|
||
Modal.confirm({
|
||
title: "测试操作:模拟审批通过",
|
||
content: `模拟法务审批通过合同【${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) {
|
||
Modal.confirm({
|
||
title: "测试操作:模拟审批驳回",
|
||
content: h("div", [
|
||
h("p", `模拟法务审批驳回合同【${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) {
|
||
Modal.confirm({
|
||
title: "测试操作:模拟签署完成",
|
||
content: `模拟合同【${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: "测试按钮模拟签署完成,真实签章接入后可删除此按钮",
|
||
signDate: formatDateTimeValue(signDate),
|
||
sealDate: formatDateTimeValue(sealDate),
|
||
activeDate: formatDateTimeValue(activeDate),
|
||
}).then((res) => {
|
||
if (!isSuccessResponse(res)) {
|
||
message.error(getResponseMessage(res, "测试签署完成失败"));
|
||
return;
|
||
}
|
||
message.success("测试签署完成,合同已流转到待采集");
|
||
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 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("签署文件下载失败");
|
||
});
|
||
}
|
||
|
||
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) {
|
||
Modal.confirm({
|
||
title: "测试操作:模拟合同采集",
|
||
content: `模拟将合同【${row.contractName}】信息采集同步至法务系统,状态将变为「待归档」。`,
|
||
okText: "确认模拟",
|
||
cancelText: "取消",
|
||
onOk: () =>
|
||
mockCollectLaunch(row.id).then((res) => {
|
||
if (!isSuccessResponse(res)) {
|
||
message.error(getResponseMessage(res, "模拟合同采集失败"));
|
||
return;
|
||
}
|
||
message.success("测试合同采集完成,合同已流转到待归档");
|
||
getList();
|
||
}),
|
||
icon: null,
|
||
});
|
||
}
|
||
|
||
function handleArchive(row) {
|
||
Modal.confirm({
|
||
title: "确认归档合同",
|
||
content: h("div", [
|
||
h("p", `确认将合同【${row.contractName}】归档?`),
|
||
h("p", { style: { color: "#999", fontSize: "12px" } }, "归档后合同进入「已完成」状态,全生命周期结束。"),
|
||
]),
|
||
okText: "确认归档",
|
||
cancelText: "取消",
|
||
onOk: () =>
|
||
archiveLaunch(row.id, {}).then((res) => {
|
||
if (!isSuccessResponse(res)) {
|
||
message.error(getResponseMessage(res, "测试归档失败"));
|
||
return;
|
||
}
|
||
message.success("测试归档完成,合同已流转到已完成");
|
||
getList();
|
||
}),
|
||
icon: null,
|
||
});
|
||
}
|
||
|
||
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>
|