浏览代码

feat(licenseManagement): 优化合同与许可证管理界面及逻辑

- 合同列表增加空状态展示,无数据时显示占位图
- 变更记录支持按日期分组展示,并优化样式结构
- 食品经营许可证页面新增空状态提示
- 更换许可证弹窗禁用点击遮罩关闭,防止误操作
- 提交审核时校验许可证状态,若审核中则提示用户
- 接口调用统一使用真实API,替换原有模拟数据逻辑
- 样式调整:优化空白页展示区域,增强视觉一致性
- 数据字段映射标准化,提升代码可维护性
congxuesong 2 周之前
父节点
当前提交
6bf630a437
共有 2 个文件被更改,包括 136 次插入112 次删除
  1. 16 14
      src/views/licenseManagement/contractManagement.vue
  2. 120 98
      src/views/licenseManagement/foodBusinessLicense.vue

+ 16 - 14
src/views/licenseManagement/contractManagement.vue

@@ -9,7 +9,7 @@
         <el-button type="primary" @click="handleViewChangeRecord"> 查看变更记录 </el-button>
       </div>
     </div>
-    <div class="contract-container">
+    <div class="contract-container" v-if="contractList && contractList.length > 0">
       <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">
@@ -30,6 +30,9 @@
         </el-col>
       </el-row>
     </div>
+    <div v-else class="empty-contract">
+      <el-empty description="暂无合同图片" :image-size="100" />
+    </div>
 
     <!-- 更换合同弹窗 -->
     <el-dialog
@@ -89,20 +92,17 @@
       <el-scrollbar height="400px" class="change-record-scrollbar">
         <div v-if="changeRecordList && changeRecordList.length > 0" class="change-record-content">
           <div v-for="(item, index) in changeRecordList" :key="index" class="record-group">
-            <div class="record-date" v-if="item.date">
-              {{ item.date }}
+            <div class="record-date">
+              {{ item.createdDateFormat }}
             </div>
             <div class="record-items">
-              <div class="record-item">
-                <div class="record-status-badge" :class="getStatusClass(item.licenseExecuteStatus)">
-                  {{ item.licenseExecuteName }}
-                </div>
+              <div class="record-item" v-for="(citem, cindex) in item.licenseList" :key="cindex">
                 <el-image
-                  :src="item.imgUrl"
+                  :src="citem.imgUrl"
                   fit="cover"
                   class="record-image"
-                  :preview-src-list="changeRecordList.map(record => record.imgUrl)"
-                  :initial-index="index"
+                  :preview-src-list="item.licenseList.map(v => v.imgUrl)"
+                  :initial-index="cindex"
                 >
                   <template #error>
                     <div class="image-slot">
@@ -110,9 +110,12 @@
                     </div>
                   </template>
                 </el-image>
+                <div class="record-status-badge" :class="getStatusClass(item.licenseExecuteStatus)">
+                  {{ item.licenseExecuteName }}
+                </div>
               </div>
             </div>
-            <div v-if="item.rejectionReason" class="rejection-reason">拒绝原因: {{ item.rejectionReason }}</div>
+            <div v-if="item.reasonRefusal" class="rejection-reason">拒绝原因: {{ item.reasonRefusal }}</div>
           </div>
         </div>
         <div v-else class="empty-record">
@@ -139,8 +142,7 @@ import {
   uploadContractImage,
   submitContractReview,
   getStoreContractStatus,
-  queryContractByStatusList,
-  getChangeRecords
+  queryContractByStatusList
 } from "@/api/modules/licenseManagement";
 
 // 状态映射对象
@@ -224,7 +226,7 @@ const handleViewChangeRecord = async () => {
     const params = {
       storeId: localGet("createdId")
     };
-    const res: any = await getChangeRecords(params);
+    const res: any = await queryContractByStatusList(params);
     if (res.code === 200) {
       changeRecordList.value = res.data;
     } else {

+ 120 - 98
src/views/licenseManagement/foodBusinessLicense.vue

@@ -9,25 +9,23 @@
         <el-button type="primary" @click="handleViewChangeRecord"> 查看变更记录 </el-button>
       </div>
     </div>
-    <div class="license-container">
+    <div class="license-container" v-if="licenseImage">
       <div class="license-display">
-        <el-image
-          v-if="licenseImage"
-          :src="licenseImage"
-          fit="contain"
-          class="license-image"
-          :preview-src-list="[licenseImage]"
-        />
-        <div v-else class="empty-image-box">
-          <el-icon class="empty-icon">
-            <Picture />
-          </el-icon>
-        </div>
+        <el-image :src="licenseImage" fit="contain" class="license-image" :preview-src-list="[licenseImage]" />
       </div>
     </div>
-
+    <div v-else class="empty-license">
+      <el-empty description="暂无食品经营许可证" :image-size="100" />
+    </div>
     <!-- 更换食品经营许可证弹窗 -->
-    <el-dialog v-model="replaceDialogVisible" title="更换食品经营许可证" width="600px" :before-close="handleReplaceDialogClose">
+    <el-dialog
+      v-model="replaceDialogVisible"
+      title="更换食品经营许可证"
+      width="600px"
+      :before-close="handleReplaceDialogClose"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+    >
       <div class="replace-upload-area" :class="{ 'upload-full': uploadedImageCount >= 1 }">
         <el-upload
           v-model:file-list="fileList"
@@ -111,24 +109,29 @@ import { ref, computed, onMounted } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { Picture, Plus } from "@element-plus/icons-vue";
 import type { UploadProps, UploadFile } from "element-plus";
-import { getFoodBusinessLicense, uploadContractImage, submitFoodLicenseReview } from "@/api/modules/licenseManagement";
+import {
+  getFoodBusinessLicense,
+  uploadContractImage,
+  submitFoodLicenseReview,
+  getChangeRecords,
+  getStoreFoodLicenceStatus
+} from "@/api/modules/licenseManagement";
 import { localGet } from "@/utils";
 
-interface ChangeRecordItem {
-  id: string;
-  status: "pending" | "success" | "failed";
-  imgUrl: string; // 图片URL
-  rejectionReason?: string;
-}
+// 状态映射对象
+const statusMap: Record<number, { name: string; class: string }> = {
+  1: { name: "审核通过", class: "status-success" },
+  2: { name: "审核中", class: "status-pending" },
+  3: { name: "审核拒绝", class: "status-failed" }
+};
+
 const id = localGet("createdId");
 
 const licenseImage = ref<string>("");
 const replaceDialogVisible = ref(false);
 const changeRecordDialogVisible = ref(false);
 const fileList = ref<UploadFile[]>([]);
-const currentRecordDate = ref("2025.08.01 10:29");
-const changeRecordList = ref<ChangeRecordItem[]>([]);
-const rejectionReason = ref("");
+const changeRecordList = ref<any>([]);
 
 // ==================== 图片上传相关变量 ====================
 const uploading = ref(false);
@@ -174,12 +177,20 @@ const initData = async () => {
   }
 };
 
-const handleReplace = () => {
+const handleReplace = async () => {
   fileList.value = [];
   imageUrlList.value = [];
   pendingUploadFiles.value = [];
   uploading.value = false;
-  replaceDialogVisible.value = true;
+  const params = {
+    id: localGet("createdId")
+  };
+  const res: any = await getStoreFoodLicenceStatus(params);
+  if (res.data.foodLicenceStatus === 2) {
+    ElMessage.warning("食品经营许可证审核中,请耐心等待");
+  } else {
+    replaceDialogVisible.value = true;
+  }
 };
 
 const handleViewChangeRecord = async () => {
@@ -598,18 +609,12 @@ const getStatusText = (status: string) => {
   border-radius: 8px;
   box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
 }
-.empty-image-box {
+.empty-license {
   display: flex;
   align-items: center;
   justify-content: center;
-  width: 100%;
-  height: 100%;
-  background-color: var(--el-fill-color-lighter);
-  border-radius: 8px;
-  .empty-icon {
-    font-size: 64px;
-    color: var(--el-text-color-placeholder);
-  }
+  min-height: 570px;
+  padding: 40px 20px;
 }
 .replace-upload-area {
   min-height: 300px;
@@ -683,75 +688,92 @@ const getStatusText = (status: string) => {
     color: #8c939d;
   }
 }
+.change-record-scrollbar {
+  :deep(.el-scrollbar__wrap) {
+    overflow-x: hidden;
+  }
+}
 .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 {
-    position: relative;
-    width: 150px;
-    height: 150px;
-    overflow: hidden;
-    border-radius: 8px;
-    .record-status-badge {
-      position: absolute;
-      right: 0;
-      bottom: 0;
-      left: 0;
-      z-index: 1;
-      padding: 4px 8px;
-      font-size: 12px;
+  .record-group {
+    padding: 20px;
+    margin-bottom: 30px;
+    background-color: var(--el-fill-color-lighter);
+    &:last-child {
+      margin-bottom: 0;
+    }
+    .record-date {
+      margin-bottom: 15px;
+      font-size: 16px;
       font-weight: 500;
-      text-align: center;
-      border-radius: 0 0 8px 8px;
-      &.status-pending {
-        color: #e6a23c;
-        background-color: rgb(253 246 236 / 95%);
-        border-top: 1px solid #e6a23c;
-      }
-      &.status-success {
-        color: #67c23a;
-        background-color: rgb(240 249 255 / 95%);
-        border-top: 1px solid #67c23a;
-      }
-      &.status-failed {
-        color: #f56c6c;
-        background-color: rgb(254 240 240 / 95%);
-        border-top: 1px solid #f56c6c;
-      }
+      color: var(--el-text-color-primary);
     }
-    .record-image {
-      width: 100%;
-      height: 100%;
-      .image-slot {
-        display: flex;
-        align-items: center;
-        justify-content: center;
+    .record-items {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 15px;
+    }
+    .record-item {
+      position: relative;
+      width: 150px;
+      height: 150px;
+      overflow: hidden;
+      border-radius: 8px;
+      .record-status-badge {
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        left: 0;
+        z-index: 1;
+        padding: 4px 8px;
+        font-size: 12px;
+        font-weight: 500;
+        text-align: center;
+        border-radius: 0 0 8px 8px;
+        &.status-pending {
+          color: #e6a23c;
+          background-color: rgb(253 246 236 / 90%);
+          border-top: 1px solid #e6a23c;
+        }
+        &.status-success {
+          color: #67c23a;
+          background-color: rgb(240 249 255 / 90%);
+          border-top: 1px solid #67c23a;
+        }
+        &.status-failed {
+          color: #f56c6c;
+          background-color: rgb(254 240 240 / 90%);
+          border-top: 1px solid #f56c6c;
+        }
+      }
+      .record-image {
         width: 100%;
         height: 100%;
-        font-size: 30px;
-        color: var(--el-text-color-placeholder);
-        background: var(--el-fill-color-light);
+        .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);
+        }
       }
     }
+    .rejection-reason {
+      margin-top: 15px;
+      font-size: 14px;
+      font-weight: 500;
+      color: var(--el-text-color-regular);
+      border-radius: 8px;
+    }
   }
-  .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;
-  }
+}
+.empty-record {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 300px;
+  padding: 40px 20px;
 }
 </style>