|
@@ -0,0 +1,424 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="card content-box">
|
|
|
|
|
+ <div class="page-header">
|
|
|
|
|
+ <h1 class="store-title">重庆老火锅</h1>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="content-section">
|
|
|
|
|
+ <div class="tip-text">
|
|
|
|
|
+ 如需续约合同或更改合同图片请在此处上传,重新上传之后需要重新进行审核,审核通过后,新的合同图片将会覆盖之前的合同
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="action-buttons">
|
|
|
|
|
+ <el-button type="primary" @click="handleReplace"> 更换 </el-button>
|
|
|
|
|
+ <el-button type="primary" @click="handleViewChangeRecord"> 查看变更记录 </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="contract-container">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col v-for="(item, index) in contractList" :key="index" :xs="12" :sm="8" :md="6" :lg="4" :xl="4">
|
|
|
|
|
+ <div class="contract-item">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ :src="item.url"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ class="contract-image"
|
|
|
|
|
+ :preview-src-list="contractList.map(img => img.url)"
|
|
|
|
|
+ :initial-index="index"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #error>
|
|
|
|
|
+ <div class="image-slot">
|
|
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </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">
|
|
|
|
|
+ <div class="replace-upload-area">
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ ref="uploadRef"
|
|
|
|
|
+ v-model:file-list="fileList"
|
|
|
|
|
+ action="#"
|
|
|
|
|
+ list-type="picture-card"
|
|
|
|
|
+ :multiple="true"
|
|
|
|
|
+ :limit="20"
|
|
|
|
|
+ :http-request="handleHttpUpload"
|
|
|
|
|
+ :before-upload="beforeUpload"
|
|
|
|
|
+ :on-exceed="handleExceed"
|
|
|
|
|
+ :on-remove="handleRemove"
|
|
|
|
|
+ :on-success="handleUploadSuccess"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
|
|
+ <template #tip>
|
|
|
|
|
+ <div class="upload-tip">({{ fileList.length }}/20)</div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button @click="handleCancelReplace"> 取消 </el-button>
|
|
|
|
|
+ <el-button type="primary" @click="handleSubmitReplace"> 去审核 </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 取消确认弹窗 -->
|
|
|
|
|
+ <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">
|
|
|
|
|
+ <div class="record-date">
|
|
|
|
|
+ {{ currentRecordDate }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="record-items">
|
|
|
|
|
+ <div v-for="(item, index) in changeRecordList" :key="index" class="record-item" :class="getStatusClass(item.status)">
|
|
|
|
|
+ <div class="record-status-text">
|
|
|
|
|
+ {{ getStatusText(item.status) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="rejectionReason" class="rejection-reason">拒绝原因: {{ rejectionReason }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button type="primary" @click="changeRecordDialogVisible = false"> 关闭 </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts" name="contractManagement">
|
|
|
|
|
+import { ref, onMounted } from "vue";
|
|
|
|
|
+import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
|
|
+import { Plus, Picture } from "@element-plus/icons-vue";
|
|
|
|
|
+import { uploadImg } from "@/api/modules/upload";
|
|
|
|
|
+import type { UploadProps, UploadFile, UploadRequestOptions } from "element-plus";
|
|
|
|
|
+
|
|
|
|
|
+interface ContractItem {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ url: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface ChangeRecordItem {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ status: "pending" | "success" | "failed";
|
|
|
|
|
+ rejectionReason?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const contractList = ref<ContractItem[]>([]);
|
|
|
|
|
+const replaceDialogVisible = ref(false);
|
|
|
|
|
+const cancelConfirmVisible = ref(false);
|
|
|
|
|
+const changeRecordDialogVisible = ref(false);
|
|
|
|
|
+const fileList = ref<UploadFile[]>([]);
|
|
|
|
|
+const uploadRef = ref();
|
|
|
|
|
+const currentRecordDate = ref("2025.08.01 10:29");
|
|
|
|
|
+const changeRecordList = ref<ChangeRecordItem[]>([]);
|
|
|
|
|
+const rejectionReason = ref("");
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ await initData();
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const initData = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // TODO: 调用API获取合同图片列表
|
|
|
|
|
+ // const response = await getContractImages();
|
|
|
|
|
+ // if (response.code === 200) {
|
|
|
|
|
+ // contractList.value = response.data;
|
|
|
|
|
+ // }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error("获取合同图片失败");
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleReplace = () => {
|
|
|
|
|
+ fileList.value = [];
|
|
|
|
|
+ replaceDialogVisible.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleViewChangeRecord = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // TODO: 调用API获取变更记录
|
|
|
|
|
+ // const response = await getChangeRecords();
|
|
|
|
|
+ // if (response.code === 200) {
|
|
|
|
|
+ // changeRecordList.value = response.data.records;
|
|
|
|
|
+ // currentRecordDate.value = response.data.date;
|
|
|
|
|
+ // rejectionReason.value = response.data.rejectionReason || "";
|
|
|
|
|
+ // }
|
|
|
|
|
+ // 模拟数据
|
|
|
|
|
+ changeRecordList.value = [
|
|
|
|
|
+ { id: "1", status: "pending" },
|
|
|
|
|
+ { id: "2", status: "pending" },
|
|
|
|
|
+ { id: "3", status: "pending" },
|
|
|
|
|
+ { id: "4", status: "pending" },
|
|
|
|
|
+ { id: "5", status: "pending" }
|
|
|
|
|
+ ];
|
|
|
|
|
+ rejectionReason.value = "";
|
|
|
|
|
+ changeRecordDialogVisible.value = true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error("获取变更记录失败");
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const beforeUpload: UploadProps["beforeUpload"] = rawFile => {
|
|
|
|
|
+ const imgSize = rawFile.size / 1024 / 1024 < 10;
|
|
|
|
|
+ const imgType = ["image/jpeg", "image/png", "image/gif"].includes(rawFile.type);
|
|
|
|
|
+ if (!imgType) {
|
|
|
|
|
+ ElMessage.warning("上传图片不符合所需的格式!");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!imgSize) {
|
|
|
|
|
+ ElMessage.warning("上传图片大小不能超过 10M!");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleHttpUpload = async (options: UploadRequestOptions) => {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ formData.append("file", options.file);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { data } = await uploadImg(formData);
|
|
|
|
|
+ const fileUrl = data.fileUrl ? data.fileUrl : data[0];
|
|
|
|
|
+ options.onSuccess({ fileUrl }, options.file as any);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ options.onError(error as any);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleExceed = () => {
|
|
|
|
|
+ ElMessage.warning("最多只能上传 20 张图片");
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleRemove = (file: UploadFile) => {
|
|
|
|
|
+ const index = fileList.value.findIndex(item => item.uid === file.uid);
|
|
|
|
|
+ if (index > -1) {
|
|
|
|
|
+ fileList.value.splice(index, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleUploadSuccess = (response: any, uploadFile: UploadFile) => {
|
|
|
|
|
+ if (response && response.fileUrl) {
|
|
|
|
|
+ uploadFile.url = response.fileUrl;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleCancelReplace = () => {
|
|
|
|
|
+ if (fileList.value.length > 0) {
|
|
|
|
|
+ cancelConfirmVisible.value = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ replaceDialogVisible.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleConfirmCancel = () => {
|
|
|
|
|
+ fileList.value = [];
|
|
|
|
|
+ cancelConfirmVisible.value = false;
|
|
|
|
|
+ replaceDialogVisible.value = false;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleReplaceDialogClose = () => {
|
|
|
|
|
+ if (fileList.value.length > 0) {
|
|
|
|
|
+ cancelConfirmVisible.value = true;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleSubmitReplace = async () => {
|
|
|
|
|
+ if (fileList.value.length === 0) {
|
|
|
|
|
+ ElMessage.warning("请至少上传一张图片");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const uploadedFiles = fileList.value.filter(file => file.url);
|
|
|
|
|
+ if (uploadedFiles.length === 0) {
|
|
|
|
|
+ ElMessage.warning("请先上传图片");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ // TODO: 调用API提交审核
|
|
|
|
|
+ // const urls = uploadedFiles.map(file => file.url);
|
|
|
|
|
+ // await submitContractReview(urls);
|
|
|
|
|
+ ElMessage.success("提交审核成功");
|
|
|
|
|
+ replaceDialogVisible.value = false;
|
|
|
|
|
+ fileList.value = [];
|
|
|
|
|
+ await initData();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error("提交审核失败");
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const getStatusClass = (status: string) => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ "status-pending": status === "pending",
|
|
|
|
|
+ "status-success": status === "success",
|
|
|
|
|
+ "status-failed": status === "failed"
|
|
|
|
|
+ };
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const getStatusText = (status: string) => {
|
|
|
|
|
+ const map: Record<string, string> = {
|
|
|
|
|
+ pending: "审核中",
|
|
|
|
|
+ success: "审核通过",
|
|
|
|
|
+ failed: "审核拒绝"
|
|
|
|
|
+ };
|
|
|
|
|
+ return map[status] || "未知";
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.page-header {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+.store-title {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+}
|
|
|
|
|
+.content-section {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+}
|
|
|
|
|
+.tip-text {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+}
|
|
|
|
|
+.action-buttons {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.contract-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ background-color: var(--el-bg-color-page);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+.contract-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ aspect-ratio: 1;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background-color: var(--el-bg-color);
|
|
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ .contract-image {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+ .image-slot {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ font-size: 30px;
|
|
|
|
|
+ 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-area {
|
|
|
|
|
+ min-height: 300px;
|
|
|
|
|
+ padding: 20px 0;
|
|
|
|
|
+ .upload-tip {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+.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 {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+ .record-items {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .record-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 150px;
|
|
|
|
|
+ height: 100px;
|
|
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ .record-status-text {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+ &.status-pending {
|
|
|
|
|
+ color: #e6a23c;
|
|
|
|
|
+ background-color: #fdf6ec;
|
|
|
|
|
+ border-color: #e6a23c;
|
|
|
|
|
+ }
|
|
|
|
|
+ &.status-success {
|
|
|
|
|
+ color: #67c23a;
|
|
|
|
|
+ background-color: #f0f9ff;
|
|
|
|
|
+ border-color: #67c23a;
|
|
|
|
|
+ }
|
|
|
|
|
+ &.status-failed {
|
|
|
|
|
+ color: #f56c6c;
|
|
|
|
|
+ background-color: #fef0f0;
|
|
|
|
|
+ border-color: #f56c6c;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .rejection-reason {
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+ background-color: var(--el-fill-color-lighter);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|