|
|
@@ -28,24 +28,14 @@
|
|
|
</el-image>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
- <!-- <template v-for="n in Math.max(0, 8 - contractList.length)" :key="`empty-${n}`">-->
|
|
|
- <!-- <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="4">-->
|
|
|
- <!-- <div class="contract-item empty-item">-->
|
|
|
- <!-- <el-icon class="empty-icon">-->
|
|
|
- <!-- <Picture />-->
|
|
|
- <!-- </el-icon>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </el-col>-->
|
|
|
- <!-- </template>-->
|
|
|
</el-row>
|
|
|
</div>
|
|
|
|
|
|
<!-- 更换合同弹窗 -->
|
|
|
- <el-dialog v-model="replaceDialogVisible" title="更换合同" width="800px" @close="handleReplaceDialogClose">
|
|
|
+ <el-dialog v-model="replaceDialogVisible" title="更换合同" width="860px" :before-close="handleReplaceDialogClose">
|
|
|
<el-scrollbar height="400px" class="replace-upload-scrollbar">
|
|
|
- <div class="replace-upload-area">
|
|
|
+ <div class="replace-upload-area" :class="{ 'upload-full': uploadedImageCount >= uploadMaxCount }">
|
|
|
<el-upload
|
|
|
- ref="uploadRef"
|
|
|
v-model:file-list="fileList"
|
|
|
list-type="picture-card"
|
|
|
:accept="'.jpg,.png'"
|
|
|
@@ -73,7 +63,7 @@
|
|
|
</el-scrollbar>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button @click="handleCancelReplace"> 取消 </el-button>
|
|
|
+ <el-button @click="handleCancelReplace" :disabled="hasUnuploadedImages"> 取消 </el-button>
|
|
|
<el-button type="primary" @click="handleSubmitReplace" :disabled="hasUnuploadedImages"> 去审核 </el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -87,17 +77,6 @@
|
|
|
@close="imageViewerVisible = false"
|
|
|
/>
|
|
|
|
|
|
- <!-- 取消确认弹窗 -->
|
|
|
- <el-dialog v-model="cancelConfirmVisible" title="提示" width="400px" :close-on-click-modal="false">
|
|
|
- <div class="confirm-text">确定要取消本次图片上传吗?已上传的图片将不保存</div>
|
|
|
- <template #footer>
|
|
|
- <div class="dialog-footer">
|
|
|
- <el-button @click="cancelConfirmVisible = false"> 取消 </el-button>
|
|
|
- <el-button type="primary" @click="handleConfirmCancel"> 确定 </el-button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
<!-- 变更记录弹窗 -->
|
|
|
<el-dialog v-model="changeRecordDialogVisible" title="变更记录" width="900px" :close-on-click-modal="false">
|
|
|
<div class="change-record-content">
|
|
|
@@ -136,18 +115,12 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts" name="contractManagement">
|
|
|
-import { ref, onMounted, computed, nextTick } from "vue";
|
|
|
+import { ref, onMounted, computed } from "vue";
|
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
import { Plus, Picture } from "@element-plus/icons-vue";
|
|
|
-import type { UploadProps, UploadFile, UploadInstance } from "element-plus";
|
|
|
+import type { UploadProps, UploadFile } from "element-plus";
|
|
|
import { localGet } from "@/utils";
|
|
|
-import { getContractImages } from "@/api/modules/licenseManagement";
|
|
|
-
|
|
|
-interface ContractItem {
|
|
|
- id: string;
|
|
|
- url: string;
|
|
|
- name: string;
|
|
|
-}
|
|
|
+import { getContractImages, uploadContractImage, submitContractReview } from "@/api/modules/licenseManagement";
|
|
|
|
|
|
interface ChangeRecordItem {
|
|
|
id: string;
|
|
|
@@ -165,10 +138,8 @@ const statusMap: Record<number, { name: string; class: string }> = {
|
|
|
|
|
|
const contractList = ref<any>([]);
|
|
|
const replaceDialogVisible = ref(false);
|
|
|
-const cancelConfirmVisible = ref(false);
|
|
|
const changeRecordDialogVisible = ref(false);
|
|
|
const fileList = ref<UploadFile[]>([]);
|
|
|
-const uploadRef = ref<UploadInstance>();
|
|
|
const currentRecordDate = ref("2025.08.01 10:29");
|
|
|
const changeRecordList = ref<ChangeRecordItem[]>([]);
|
|
|
const rejectionReason = ref("");
|
|
|
@@ -176,20 +147,10 @@ const rejectionReason = ref("");
|
|
|
const id = localGet("createdId");
|
|
|
|
|
|
// ==================== 图片上传相关变量 ====================
|
|
|
-// 文件上传地址
|
|
|
-const uploadUrl = ref(`${import.meta.env.VITE_API_URL_STORE}/file/uploadImg`);
|
|
|
-const imgType = ref(16);
|
|
|
const uploadMaxCount = 20;
|
|
|
const uploading = ref(false);
|
|
|
const pendingUploadFiles = ref<UploadFile[]>([]);
|
|
|
const imageUrlList = ref<string[]>([]); // 存储图片URL列表
|
|
|
-const generateImgSort = (() => {
|
|
|
- let seed = Date.now();
|
|
|
- return () => {
|
|
|
- seed += 1;
|
|
|
- return seed;
|
|
|
- };
|
|
|
-})();
|
|
|
|
|
|
// 图片预览相关
|
|
|
const imageViewerVisible = ref(false);
|
|
|
@@ -414,61 +375,20 @@ const uploadSingleFile = async (file: UploadFile) => {
|
|
|
if (!file.raw) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- const formData = new FormData();
|
|
|
- const storeId = Number(localGet("createdId"));
|
|
|
const rawFile = file.raw as File;
|
|
|
- const sortValue = generateImgSort();
|
|
|
-
|
|
|
+ const formData = new FormData();
|
|
|
formData.append("file", rawFile);
|
|
|
- formData.append(
|
|
|
- "list",
|
|
|
- JSON.stringify([
|
|
|
- {
|
|
|
- storeId,
|
|
|
- imgType: imgType.value,
|
|
|
- imgSort: sortValue
|
|
|
- }
|
|
|
- ])
|
|
|
- );
|
|
|
-
|
|
|
+ formData.append("user", "text");
|
|
|
file.status = "uploading";
|
|
|
file.percentage = 0;
|
|
|
uploading.value = true;
|
|
|
|
|
|
try {
|
|
|
- const response = await fetch(uploadUrl.value, {
|
|
|
- method: "POST",
|
|
|
- body: formData,
|
|
|
- credentials: "include"
|
|
|
- });
|
|
|
- if (!response.ok) {
|
|
|
- throw new Error("上传失败");
|
|
|
- }
|
|
|
- const result = await response.json();
|
|
|
-
|
|
|
+ // 上传过程中先保持进度为 0,避免接口异常时进度条误显示 100%
|
|
|
+ const result: any = await uploadContractImage(formData);
|
|
|
if (result?.code === 200 && result.data) {
|
|
|
// 处理单个文件的上传结果
|
|
|
- // 尝试从返回结果中获取图片URL
|
|
|
- // 可能的结构:result.data 是对象包含 url,或者 result.url,或者 result.data 是 URL 字符串
|
|
|
- let imageUrl = "";
|
|
|
- if (typeof result.data === "object" && !Array.isArray(result.data)) {
|
|
|
- // 如果返回的是对象,尝试获取 url 字段
|
|
|
- imageUrl = result.data.url || result.data.fileUrl || "";
|
|
|
- } else if (typeof result.data === "string") {
|
|
|
- // 如果返回的是字符串,可能是 URL
|
|
|
- imageUrl = result.data;
|
|
|
- } else if (Array.isArray(result.data) && result.data.length > 0) {
|
|
|
- // 如果返回的是数组,取第一个元素
|
|
|
- imageUrl = typeof result.data[0] === "string" ? result.data[0] : result.data[0]?.url || result.data[0]?.fileUrl || "";
|
|
|
- } else if (result.url) {
|
|
|
- // 如果返回结果中有 url 字段
|
|
|
- imageUrl = result.url;
|
|
|
- } else if (result.fileUrl) {
|
|
|
- // 如果返回结果中有 fileUrl 字段
|
|
|
- imageUrl = result.fileUrl;
|
|
|
- }
|
|
|
-
|
|
|
+ let imageUrl = result.data[0];
|
|
|
if (!imageUrl) {
|
|
|
throw new Error("上传成功但未获取到图片URL");
|
|
|
}
|
|
|
@@ -490,6 +410,8 @@ const uploadSingleFile = async (file: UploadFile) => {
|
|
|
throw new Error(result?.msg || "图片上传失败");
|
|
|
}
|
|
|
} catch (error: any) {
|
|
|
+ // 上传失败时保持进度条为 0
|
|
|
+ file.percentage = 0;
|
|
|
file.status = "fail";
|
|
|
if (file.url && file.url.startsWith("blob:")) {
|
|
|
URL.revokeObjectURL(file.url);
|
|
|
@@ -499,7 +421,6 @@ const uploadSingleFile = async (file: UploadFile) => {
|
|
|
if (index > -1) {
|
|
|
fileList.value.splice(index, 1);
|
|
|
}
|
|
|
- ElMessage.error(error?.message || "图片上传失败");
|
|
|
} finally {
|
|
|
uploading.value = false;
|
|
|
// 触发视图更新
|
|
|
@@ -539,26 +460,58 @@ const handlePictureCardPreview = (file: any) => {
|
|
|
imageViewerVisible.value = true;
|
|
|
};
|
|
|
|
|
|
-const handleCancelReplace = () => {
|
|
|
+const handleCancelReplace = async () => {
|
|
|
+ // 如果有图片正在上传,阻止关闭
|
|
|
+ if (hasUnuploadedImages.value) {
|
|
|
+ ElMessage.warning("请等待图片上传完成后再关闭");
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (fileList.value.length > 0) {
|
|
|
- cancelConfirmVisible.value = true;
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm("确定要取消本次图片上传吗?已上传的图片将不保存", "提示", {
|
|
|
+ confirmButtonText: "确定",
|
|
|
+ cancelButtonText: "取消",
|
|
|
+ type: "warning"
|
|
|
+ });
|
|
|
+ // 用户确认取消
|
|
|
+ fileList.value = [];
|
|
|
+ imageUrlList.value = [];
|
|
|
+ pendingUploadFiles.value = [];
|
|
|
+ uploading.value = false;
|
|
|
+ replaceDialogVisible.value = false;
|
|
|
+ } catch {
|
|
|
+ // 用户取消操作,不做任何处理
|
|
|
+ }
|
|
|
} else {
|
|
|
replaceDialogVisible.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-const handleConfirmCancel = () => {
|
|
|
- fileList.value = [];
|
|
|
- imageUrlList.value = [];
|
|
|
- pendingUploadFiles.value = [];
|
|
|
- uploading.value = false;
|
|
|
- cancelConfirmVisible.value = false;
|
|
|
- replaceDialogVisible.value = false;
|
|
|
-};
|
|
|
-
|
|
|
-const handleReplaceDialogClose = () => {
|
|
|
+const handleReplaceDialogClose = async (done: () => void) => {
|
|
|
+ // 如果有图片正在上传,阻止关闭
|
|
|
+ if (hasUnuploadedImages.value) {
|
|
|
+ ElMessage.warning("请等待图片上传完成后再关闭");
|
|
|
+ return; // 不调用 done(),阻止关闭弹窗
|
|
|
+ }
|
|
|
if (fileList.value.length > 0) {
|
|
|
- cancelConfirmVisible.value = true;
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm("确定要取消本次图片上传吗?已上传的图片将不保存", "提示", {
|
|
|
+ confirmButtonText: "确定",
|
|
|
+ cancelButtonText: "取消",
|
|
|
+ type: "warning"
|
|
|
+ });
|
|
|
+ // 用户确认取消,清空数据并关闭弹窗
|
|
|
+ fileList.value = [];
|
|
|
+ imageUrlList.value = [];
|
|
|
+ pendingUploadFiles.value = [];
|
|
|
+ uploading.value = false;
|
|
|
+ done(); // 调用 done() 允许关闭弹窗
|
|
|
+ } catch {
|
|
|
+ // 用户取消操作,不调用 done(),阻止关闭弹窗
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 没有文件,直接关闭
|
|
|
+ done();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -578,9 +531,12 @@ const handleSubmitReplace = async () => {
|
|
|
return;
|
|
|
}
|
|
|
try {
|
|
|
- // TODO: 调用API提交审核
|
|
|
- // const imageUrls = imageUrlList.value;
|
|
|
- // await submitContractReview(imageUrls);
|
|
|
+ // 根据文件列表顺序,生成带排序的图片数据(排序从0开始)
|
|
|
+ const imageDataWithSort = uploadedFiles.map((file, index) => ({
|
|
|
+ url: file.url,
|
|
|
+ sort: index
|
|
|
+ }));
|
|
|
+ await submitContractReview({ images: imageDataWithSort });
|
|
|
ElMessage.success("提交审核成功");
|
|
|
replaceDialogVisible.value = false;
|
|
|
fileList.value = [];
|
|
|
@@ -663,14 +619,6 @@ const getStatusName = (status: number) => {
|
|
|
color: var(--el-text-color-placeholder);
|
|
|
background: var(--el-fill-color-light);
|
|
|
}
|
|
|
- &.empty-item {
|
|
|
- background-color: var(--el-fill-color-lighter);
|
|
|
- border: 1px dashed var(--el-border-color);
|
|
|
- .empty-icon {
|
|
|
- font-size: 48px;
|
|
|
- color: var(--el-text-color-placeholder);
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
.replace-upload-scrollbar {
|
|
|
:deep(.el-scrollbar__wrap) {
|
|
|
@@ -680,6 +628,22 @@ const getStatusName = (status: number) => {
|
|
|
.replace-upload-area {
|
|
|
min-height: 300px;
|
|
|
padding: 20px;
|
|
|
+ :deep(.el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label) {
|
|
|
+ display: inline-flex !important;
|
|
|
+ opacity: 1 !important;
|
|
|
+ }
|
|
|
+ :deep(.el-upload-list__item.is-success:focus .el-upload-list__item-status-label) {
|
|
|
+ display: inline-flex !important;
|
|
|
+ opacity: 1 !important;
|
|
|
+ }
|
|
|
+ :deep(.el-upload-list--picture-card .el-icon--close-tip) {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+ &.upload-full {
|
|
|
+ :deep(.el-upload--picture-card) {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
.dialog-footer {
|
|
|
display: flex;
|
|
|
@@ -733,12 +697,6 @@ const getStatusName = (status: number) => {
|
|
|
color: #8c939d;
|
|
|
}
|
|
|
}
|
|
|
-.confirm-text {
|
|
|
- padding: 10px 0;
|
|
|
- font-size: 14px;
|
|
|
- line-height: 1.6;
|
|
|
- color: var(--el-text-color-primary);
|
|
|
-}
|
|
|
.change-record-content {
|
|
|
padding: 20px 0;
|
|
|
.record-date {
|