上传已签署版本

This commit is contained in:
2026-06-03 10:08:19 +08:00
parent 74911c5ca2
commit c2d8551de0
3 changed files with 261 additions and 5 deletions
@@ -0,0 +1,248 @@
<template>
<a-modal
v-model:open="visible"
title="上传已签署版本"
width="560px"
:mask-closable="false"
:confirm-loading="submitting"
ok-text="确认提交"
cancel-text="取消"
@ok="handleSubmit"
>
<div class="upload-signed-body">
<a-descriptions :column="1" size="small" bordered class="contract-brief">
<a-descriptions-item label="合同名称">
{{ row?.contractName || "-" }}
</a-descriptions-item>
<a-descriptions-item label="合同编码">
{{ row?.contractCode || "-" }}
</a-descriptions-item>
</a-descriptions>
<a-divider style="margin: 16px 0" />
<a-form layout="vertical">
<a-form-item label="上传已签署合同文件" required>
<div
class="upload-dragger"
:class="{ 'is-dragover': dragOver }"
@click="triggerFileInput"
@dragover.prevent="dragOver = true"
@dragleave.prevent="dragOver = false"
@drop.prevent="handleFileDrop"
>
<input
ref="fileInputRef"
type="file"
accept=".pdf,.doc,.docx"
style="display: none"
@change="handleFileSelect"
/>
<template v-if="!uploadFile">
<cloud-upload-outlined :style="{ 'fontSize': '36px', 'color': '#b0b8c5' }" />
<p class="upload-text">点击或将已签署文件拖拽到这里</p>
<p class="upload-hint">支持 PDFDOCDOCX 文件且小于 20M</p>
</template>
<template v-else>
<file-text-outlined :style="{ 'fontSize': '36px', 'color': '#10b981' }" />
<p class="file-name">{{ uploadFile.name }}</p>
<p class="file-size">{{ formatFileSize(uploadFile.size) }}</p>
<a-button size="small" danger @click.stop="removeFile">移除</a-button>
</template>
</div>
</a-form-item>
<a-form-item label="不在线签署的原因" required>
<a-textarea
v-model:value="offlineReason"
:rows="4"
placeholder="请填写不在线签署的原因,例如:客户无法配合在线签署、对方无数字证书等"
:maxlength="500"
show-count
/>
</a-form-item>
</a-form>
</div>
</a-modal>
</template>
<script setup>
import { ref } from "vue";
import { message } from "ant-design-vue";
import { CloudUploadOutlined, FileTextOutlined } from "@ant-design/icons-vue";
import { finishLaunchSeal, uploadLaunchFile } from "@/api/launch.js";
const SUCCESS_CODE = "0000";
const visible = ref(false);
const row = ref(null);
const uploadFile = ref(null);
const offlineReason = ref("");
const dragOver = ref(false);
const submitting = ref(false);
const fileInputRef = ref(null);
const emit = defineEmits(["done"]);
function showModal(record) {
row.value = record;
uploadFile.value = null;
offlineReason.value = "";
dragOver.value = false;
submitting.value = false;
visible.value = true;
}
function triggerFileInput() {
fileInputRef.value?.click();
}
function handleFileSelect(event) {
const file = event.target?.files?.[0];
if (file) {
validateAndSetFile(file);
}
if (fileInputRef.value) {
fileInputRef.value.value = "";
}
}
function handleFileDrop(event) {
dragOver.value = false;
const file = event.dataTransfer?.files?.[0];
if (file) {
validateAndSetFile(file);
}
}
function validateAndSetFile(file) {
const allowedExts = [".pdf", ".doc", ".docx"];
const ext = "." + (file.name.split(".").pop()?.toLowerCase() || "");
if (!allowedExts.includes(ext)) {
message.error("只支持 PDF、DOC、DOCX 格式文件");
return;
}
const maxSize = 20 * 1024 * 1024;
if (file.size > maxSize) {
message.error("文件大小不能超过 20M");
return;
}
uploadFile.value = file;
}
function removeFile() {
uploadFile.value = null;
}
function formatFileSize(bytes) {
if (!bytes) return "-";
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
}
async function handleSubmit() {
if (!uploadFile.value) {
message.warning("请先上传已签署文件");
return;
}
const reason = (offlineReason.value || "").trim();
if (!reason) {
message.warning("请填写不在线签署的原因");
return;
}
submitting.value = true;
try {
const formData = new FormData();
formData.append("file", uploadFile.value);
const uploadRes = await uploadLaunchFile(formData);
if (String(uploadRes?.code || "") !== SUCCESS_CODE) {
message.error(uploadRes?.message || uploadRes?.msg || "文件上传失败");
submitting.value = false;
return;
}
const filePath = uploadRes?.result || "";
const fileName = uploadFile.value.name;
const fileType = fileName.split(".").pop()?.toLowerCase() || "pdf";
const fileSize = uploadFile.value.size;
const finishRes = await finishLaunchSeal(row.value.id, {
sealContractId: row.value.sealContractId || `OFFLINE-SEAL-${row.value.id}`,
sealContractStatus: 2000,
signedFileName: fileName,
signedFilePath: filePath,
signedFileType: fileType,
signedFileSize: fileSize,
signedOfflineReason: reason,
});
if (String(finishRes?.code || "") !== SUCCESS_CODE) {
message.error(finishRes?.message || finishRes?.msg || "签署完结回写失败");
submitting.value = false;
return;
}
message.success("已签署版本上传成功,合同已流转到待采集");
visible.value = false;
emit("done");
} catch (err) {
message.error("操作异常:" + (err?.message || "未知错误"));
} finally {
submitting.value = false;
}
}
defineExpose({ showModal });
</script>
<style scoped>
.upload-signed-body {
padding: 4px 0;
}
.contract-brief {
margin-bottom: 4px;
}
.upload-dragger {
border: 2px dashed #dcdfe6;
border-radius: 10px;
padding: 24px 16px;
text-align: center;
cursor: pointer;
transition: all 0.25s;
background: #fafbfc;
min-height: 120px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
}
.upload-dragger:hover,
.is-dragover {
border-color: #3b6fa0;
background: #eaf2f9;
}
.upload-text,
.file-name {
font-size: 14px;
color: #2c3e50;
margin: 4px 0 2px;
}
.file-name {
font-weight: 500;
color: #10b981;
}
.upload-hint,
.file-size {
font-size: 12px;
color: #8b95a5;
margin: 0;
}
</style>
+12
View File
@@ -115,6 +115,7 @@
<a v-if="canOnlineSign(row)" @click="handleOnlineSign(row)">
{{ row.status === "completed" ? "查看签署" : "在线签订" }}
</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>
@@ -142,6 +143,7 @@
<ContractPartyPlaceholderModal ref="contractModalRef" @save="handleModalSave" />
<MixedSignModal ref="mixedSignModalRef" @business-updated="handleSignBusinessUpdated" />
<UploadSignedModal ref="uploadSignedModalRef" @done="handleSignBusinessUpdated" />
</section>
</template>
@@ -156,6 +158,7 @@ import {
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,
@@ -194,6 +197,7 @@ 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);
@@ -307,6 +311,10 @@ 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";
}
@@ -514,6 +522,10 @@ function handleMockFinishSign(row) {
});
}
function handleUploadSigned(row) {
uploadSignedModalRef.value?.showModal(row);
}
function handleOnlineSign(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {