Przeglądaj źródła

Merge remote-tracking branch 'origin/development' into development

# Conflicts:
#	src/views/home/index.vue
spy 1 miesiąc temu
rodzic
commit
ba24d1a9f0

+ 2 - 2
src/api/modules/groupPackageManagement.ts

@@ -19,7 +19,7 @@ export const getDetail = params => {
   return http.get(PORT_NONE + `/store/info/getDetail`, params);
 };
 export const getMenuByStoreId = params => {
-  return http.get(PORT_NONE + `/menu/getMenuByStoreId`, params);
+  return http.get(PORT_NONE + `/menuPlatform/getMenuByStoreId`, params);
 };
 
 // 新增商家端用户
@@ -154,7 +154,7 @@ export const updateStoreCommissionRate = (params: { id: string; commissionRate:
 
 // 节假日列表
 export const getHolidayList = (params: any) => {
-  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/coupon/getHolidayList`, params);
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/couponPlatform/getHolidayList`, params);
 };
 
 // 节假日列表

BIN
src/assets/images/coupon.png


BIN
src/assets/images/groupPackageManagement/aboutUsLogo.png


BIN
src/assets/images/home-bg.png


BIN
src/assets/images/home-icon.png


BIN
src/assets/images/home-income.png


BIN
src/assets/images/home-order.png


BIN
src/assets/images/home-wallet.png


BIN
src/assets/images/welcome.png


+ 27 - 14
src/assets/json/authMenuList.json

@@ -168,7 +168,7 @@
       },
       "children": [
         {
-          "path": "/orderManagementDetail",
+          "path": "/orderManagement/detail",
           "name": "orderManagementDetail",
           "component": "/orderManagement/detail",
           "meta": {
@@ -301,7 +301,7 @@
     {
       "path": "/licenseManagement",
       "name": "licenseManagement",
-      "redirect": "/licenseManagement/index",
+      "redirect": "/licenseManagement/businessLicense",
       "meta": {
         "icon": "Files",
         "title": "证照管理",
@@ -313,12 +313,12 @@
       },
       "children": [
         {
-          "path": "/licenseManagement/index",
-          "name": "licenseManagementIndex",
-          "component": "/licenseManagement/index",
+          "path": "/licenseManagement/businessLicense",
+          "name": "businessLicense",
+          "component": "/licenseManagement/businessLicense",
           "meta": {
-            "icon": "Files",
-            "title": "证照管理",
+            "icon": "Document",
+            "title": "营业执照",
             "isLink": "",
             "isHide": false,
             "isFull": false,
@@ -327,15 +327,28 @@
           }
         },
         {
-          "path": "/licenseManagementDetail",
-          "name": "licenseManagementDetail",
-          "component": "/licenseManagement/detail",
+          "path": "/licenseManagement/contractManagement",
+          "name": "contractManagement",
+          "component": "/licenseManagement/contractManagement",
           "meta": {
-            "icon": "Menu",
-            "title": "证照管理详情",
-            "activeMenu": "/licenseManagement",
+            "icon": "Picture",
+            "title": "合同管理",
             "isLink": "",
-            "isHide": true,
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/licenseManagement/foodBusinessLicense",
+          "name": "foodBusinessLicense",
+          "component": "/licenseManagement/foodBusinessLicense",
+          "meta": {
+            "icon": "Document",
+            "title": "食品经营许可证",
+            "isLink": "",
+            "isHide": false,
             "isFull": false,
             "isAffix": false,
             "isKeepAlive": false

+ 54 - 1
src/layouts/LayoutClassic/index.vue

@@ -18,8 +18,10 @@
         <div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
           <el-scrollbar>
             <el-menu
+              ref="menuRef"
               :router="false"
               :default-active="activeMenu"
+              :default-openeds="openedMenus"
               :collapse="isCollapse"
               :unique-opened="accordion"
               :collapse-transition="false"
@@ -37,7 +39,7 @@
 </template>
 
 <script setup lang="ts" name="layoutClassic">
-import { computed } from "vue";
+import { computed, ref, watch, nextTick } from "vue";
 import { useRoute } from "vue-router";
 import { useAuthStore } from "@/stores/modules/auth";
 import { useGlobalStore } from "@/stores/modules/global";
@@ -45,6 +47,7 @@ import Main from "@/layouts/components/Main/index.vue";
 import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
 import ToolBarLeft from "@/layouts/components/Header/ToolBarLeft.vue";
 import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
+import type { ElMenu } from "element-plus";
 
 const title = import.meta.env.VITE_GLOB_APP_TITLE;
 
@@ -55,6 +58,56 @@ const accordion = computed(() => globalStore.accordion);
 const isCollapse = computed(() => globalStore.isCollapse);
 const menuList = computed(() => authStore.showMenuListGet);
 const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
+
+const menuRef = ref<InstanceType<typeof ElMenu>>();
+const openedMenus = ref<string[]>([]);
+
+// 计算应该展开的父菜单路径
+const getParentMenuPath = (path: string) => {
+  const pathSegments = path.split("/").filter(Boolean);
+  // 只有当路径有两段或以上时才返回父菜单路径
+  // 例如: /licenseManagement/businessLicense -> /licenseManagement
+  if (pathSegments.length > 1) {
+    return `/${pathSegments[0]}`;
+  }
+  return "";
+};
+
+// 根据当前路由计算应该展开的父菜单
+const calculateOpenedMenus = () => {
+  const currentPath = (route.meta.activeMenu as string) || route.path;
+  const parentPath = getParentMenuPath(currentPath);
+
+  if (parentPath) {
+    // 如果手风琴模式开启,只保留当前父菜单
+    if (accordion.value) {
+      return [parentPath];
+    } else {
+      // 如果手风琴模式关闭,保留已有的展开菜单,并添加当前父菜单
+      const newOpenedMenus = [...openedMenus.value];
+      if (!newOpenedMenus.includes(parentPath)) {
+        newOpenedMenus.push(parentPath);
+      }
+      return newOpenedMenus;
+    }
+  }
+  return openedMenus.value;
+};
+
+// 监听路由变化,保持父菜单展开
+watch(
+  () => [route.path, route.meta.activeMenu],
+  () => {
+    nextTick(() => {
+      const newOpenedMenus = calculateOpenedMenus();
+      // 只有当展开菜单发生变化时才更新
+      if (JSON.stringify(newOpenedMenus.sort()) !== JSON.stringify(openedMenus.value.sort())) {
+        openedMenus.value = newOpenedMenus;
+      }
+    });
+  },
+  { immediate: true }
+);
 </script>
 
 <style scoped lang="scss">

+ 54 - 1
src/layouts/LayoutVertical/index.vue

@@ -9,8 +9,10 @@
         </div>
         <el-scrollbar>
           <el-menu
+            ref="menuRef"
             :router="false"
             :default-active="activeMenu"
+            :default-openeds="openedMenus"
             :collapse="isCollapse"
             :unique-opened="accordion"
             :collapse-transition="false"
@@ -31,7 +33,7 @@
 </template>
 
 <script setup lang="ts" name="layoutVertical">
-import { computed } from "vue";
+import { computed, ref, watch, nextTick } from "vue";
 import { useRoute } from "vue-router";
 import { useAuthStore } from "@/stores/modules/auth";
 import { useGlobalStore } from "@/stores/modules/global";
@@ -39,6 +41,7 @@ import Main from "@/layouts/components/Main/index.vue";
 import ToolBarLeft from "@/layouts/components/Header/ToolBarLeft.vue";
 import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
 import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
+import type { ElMenu } from "element-plus";
 
 const title = import.meta.env.VITE_GLOB_APP_TITLE;
 
@@ -49,6 +52,56 @@ const accordion = computed(() => globalStore.accordion);
 const isCollapse = computed(() => globalStore.isCollapse);
 const menuList = computed(() => authStore.showMenuListGet);
 const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
+
+const menuRef = ref<InstanceType<typeof ElMenu>>();
+const openedMenus = ref<string[]>([]);
+
+// 计算应该展开的父菜单路径
+const getParentMenuPath = (path: string) => {
+  const pathSegments = path.split("/").filter(Boolean);
+  // 只有当路径有两段或以上时才返回父菜单路径
+  // 例如: /licenseManagement/businessLicense -> /licenseManagement
+  if (pathSegments.length > 1) {
+    return `/${pathSegments[0]}`;
+  }
+  return "";
+};
+
+// 根据当前路由计算应该展开的父菜单
+const calculateOpenedMenus = () => {
+  const currentPath = (route.meta.activeMenu as string) || route.path;
+  const parentPath = getParentMenuPath(currentPath);
+
+  if (parentPath) {
+    // 如果手风琴模式开启,只保留当前父菜单
+    if (accordion.value) {
+      return [parentPath];
+    } else {
+      // 如果手风琴模式关闭,保留已有的展开菜单,并添加当前父菜单
+      const newOpenedMenus = [...openedMenus.value];
+      if (!newOpenedMenus.includes(parentPath)) {
+        newOpenedMenus.push(parentPath);
+      }
+      return newOpenedMenus;
+    }
+  }
+  return openedMenus.value;
+};
+
+// 监听路由变化,保持父菜单展开
+watch(
+  () => [route.path, route.meta.activeMenu],
+  () => {
+    nextTick(() => {
+      const newOpenedMenus = calculateOpenedMenus();
+      // 只有当展开菜单发生变化时才更新
+      if (JSON.stringify(newOpenedMenus.sort()) !== JSON.stringify(openedMenus.value.sort())) {
+        openedMenus.value = newOpenedMenus;
+      }
+    });
+  },
+  { immediate: true }
+);
 </script>
 
 <style scoped lang="scss">

+ 88 - 24
src/views/couponManagement/index.vue

@@ -12,15 +12,54 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
-        <template v-if="scope.row.status === '0'">
-          <el-button link type="primary" @click="changeTypes(scope.row, 'on')"> 上架 </el-button>
-          <el-button link type="primary" @click="changeTypes(scope.row, 'off')"> 下架 </el-button>
-          <el-button link type="primary" @click="changeInventory(scope.row)"> 修改库存 </el-button>
-        </template>
-        <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
-        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
-        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeTypes(scope.row, 'on')"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.上架)"
+        >
+          上架
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeTypes(scope.row, 'off')"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.下架)"
+        >
+          下架
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeInventory(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.修改库存)"
+        >
+          修改库存
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          @click="toDetail(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看详情)"
+        >
+          查看详情
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="editRow(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.编辑)"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="deleteRow(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.删除)"
+        >
+          删除
+        </el-button>
       </template>
     </ProTable>
     <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
@@ -78,12 +117,48 @@ const rules = reactive<FormRules<RuleForm>>({
     }
   ]
 });
-const statusEnum = [
-  { value: "-1", label: "待审核" },
-  { value: "-2", label: "审核通过" },
-  { value: "0", label: "审核驳回" }
+const allTabOptions = [
+  { label: "全部", name: "" },
+  { label: "草稿", name: "0" },
+  { label: "进行中", name: "5" },
+  { label: "未开始", name: "2" },
+  { label: "已下架", name: "6" },
+  { label: "已售罄", name: "4" },
+  { label: "已结束", name: "7" }
 ];
 
+// 状态枚举:0草稿 1待审核 2未开始 3审核拒绝 4已售罄 5进行中 6已下架 7已结束
+const STATUS = {
+  草稿: 0,
+  待审核: 1,
+  未开始: 2,
+  审核拒绝: 3,
+  已售罄: 4,
+  进行中: 5,
+  已下架: 6,
+  已结束: 7
+} as const;
+
+// 操作按钮权限配置:定义每个操作按钮在哪些状态下显示
+const OPERATION_PERMISSIONS = {
+  // 查看详情:待审核、未开始、审核拒绝、进行中、已售罄、已下架
+  查看详情: [STATUS.待审核, STATUS.未开始, STATUS.审核拒绝, STATUS.进行中, STATUS.已售罄, STATUS.已下架],
+  // 上架:未开始、已下架
+  上架: [STATUS.未开始, STATUS.已下架],
+  // 下架:进行中
+  下架: [STATUS.进行中],
+  // 修改库存:未开始、进行中、已售罄
+  修改库存: [STATUS.未开始, STATUS.进行中, STATUS.已售罄],
+  // 编辑:草稿、审核拒绝、已售罄、已下架、已结束
+  编辑: [STATUS.草稿, STATUS.审核拒绝, STATUS.已售罄, STATUS.已下架, STATUS.已结束],
+  // 删除:草稿、未开始、审核拒绝、已售罄、已结束
+  删除: [STATUS.草稿, STATUS.未开始, STATUS.审核拒绝, STATUS.已售罄, STATUS.已结束]
+} as const;
+
+// 判断按钮是否显示的工具函数
+const canShowButton = (status: number, allowedStatuses: readonly number[]) => {
+  return allowedStatuses.includes(status);
+};
 // ProTable 实例(需要在使用它的地方之前定义)
 const proTable = ref<ProTableInstance>();
 
@@ -123,17 +198,6 @@ const columns = reactive<ColumnProps<any>[]>([
   { prop: "operation", label: "操作", fixed: "right", width: 330 }
 ]);
 
-// 在 script setup 中添加
-const allTabOptions = [
-  { label: "全部", name: "" },
-  { label: "草稿", name: "0" },
-  { label: "进行中", name: "5" },
-  { label: "待审核", name: "1" },
-  { label: "审核拒绝", name: "3" },
-  { label: "已售罄", name: "4" },
-  { label: "已结束", name: "7" }
-];
-
 // 获取当前审核状态
 const currentAuditStatus = computed(() => {
   if (!proTable.value?.searchParam) return "";

+ 120 - 35
src/views/groupPackageManagement/index.vue

@@ -12,7 +12,13 @@
       </template>
       <template #Information="scope">
         <div class="information">
-          <el-image :src="scope.row.imageValueStr[0]" :preview-src-list="scope.row.imageValueStr" preview-teleported />
+          <el-image
+            v-if="scope.row.imageValueStr"
+            :src="scope.row.imageValueStr[0]"
+            :preview-src-list="scope.row.imageValueStr"
+            preview-teleported
+          />
+          <el-image v-else src="/src/assets/images/groupPackageManagement/aboutUsLogo.png" />
           <div class="information-right">
             <p>团购名称:{{ scope.row.groupName }}</p>
             <p>团购编号:{{ scope.row.groupNo }}</p>
@@ -21,20 +27,59 @@
         </div>
       </template>
       <template #status="scope">
-        <p>团购状态:{{ allTabOptions.find(item => item.name === scope.row.groupType)?.label ?? "--" }}</p>
-        <p>审核状态:{{ statusEnum.find(item => item.value === scope.row.reviewType)?.label ?? "--" }}</p> </template
-      >:
+        <p>团购状态:{{ scope.row.statusName ?? "--" }}</p>
+        <p>审核状态:{{ scope.row.reviewType ?? "--" }}</p>
+      </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
-        <template v-if="scope.row.status === '0'">
-          <el-button link type="primary" @click="changeTypes(scope.row, 'on')"> 上架 </el-button>
-          <el-button link type="primary" @click="changeTypes(scope.row, 'off')"> 下架 </el-button>
-          <el-button link type="primary" @click="changeInventory(scope.row)"> 修改库存 </el-button>
-        </template>
-        <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
-        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
-        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeTypes(scope.row, 'on')"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.上架)"
+        >
+          上架
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeTypes(scope.row, 'off')"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.下架)"
+        >
+          下架
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeInventory(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.修改库存)"
+        >
+          修改库存
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          @click="toDetail(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看详情)"
+        >
+          查看详情
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="editRow(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.编辑)"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="deleteRow(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.删除)"
+        >
+          删除
+        </el-button>
       </template>
     </ProTable>
     <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
@@ -81,23 +126,71 @@ const formInventory: any = ref({
 const rowData = ref<any>();
 const activeName = ref("");
 
+// 定义表单类型
+interface RuleForm {
+  newInventory: string;
+}
+
 const ruleFormRef = ref<FormInstance>();
 const rules = reactive<FormRules<RuleForm>>({
   newInventory: [
     { required: true, message: "请输入库存数量", trigger: "blur" },
     {
       pattern: /^(0|[1-9][0-9]*)$/,
-      message: "请输入整数,不允许输入小数,负数",
+      message: "请输入整数",
       trigger: "blur"
     }
   ]
 });
 const statusEnum = [
-  { value: "-1", label: "待审核" },
-  { value: "-2", label: "审核通过" },
-  { value: "0", label: "审核驳回" }
+  { value: "0", label: "待审核" },
+  { value: "1", label: "审核通过" },
+  { value: "2", label: "审核驳回" }
+];
+
+const allTabOptions = [
+  { label: "全部", name: "" },
+  { label: "草稿", name: "0" },
+  { label: "进行中", name: "5" },
+  { label: "未开始", name: "2" },
+  { label: "已下架", name: "6" },
+  { label: "已售罄", name: "4" },
+  { label: "已结束", name: "7" }
 ];
 
+// 状态枚举:0草稿 1待审核 2未开始 3审核拒绝 4已售罄 5进行中 6已下架 7已结束
+const STATUS = {
+  草稿: 0,
+  待审核: 1,
+  未开始: 2,
+  审核拒绝: 3,
+  已售罄: 4,
+  进行中: 5,
+  已下架: 6,
+  已结束: 7
+} as const;
+
+// 操作按钮权限配置:定义每个操作按钮在哪些状态下显示
+const OPERATION_PERMISSIONS = {
+  // 查看详情:待审核、未开始、审核拒绝、进行中、已售罄、已下架
+  查看详情: [STATUS.待审核, STATUS.未开始, STATUS.审核拒绝, STATUS.进行中, STATUS.已售罄, STATUS.已下架],
+  // 上架:未开始、已下架
+  上架: [STATUS.未开始, STATUS.已下架],
+  // 下架:进行中
+  下架: [STATUS.进行中],
+  // 修改库存:未开始、进行中、已售罄
+  修改库存: [STATUS.未开始, STATUS.进行中, STATUS.已售罄],
+  // 编辑:草稿、审核拒绝、已售罄、已下架、已结束
+  编辑: [STATUS.草稿, STATUS.审核拒绝, STATUS.已售罄, STATUS.已下架, STATUS.已结束],
+  // 删除:草稿、未开始、审核拒绝、已售罄、已结束
+  删除: [STATUS.草稿, STATUS.未开始, STATUS.审核拒绝, STATUS.已售罄, STATUS.已结束]
+} as const;
+
+// 判断按钮是否显示的工具函数
+const canShowButton = (status: number, allowedStatuses: readonly number[]) => {
+  return allowedStatuses.includes(status);
+};
+
 // ProTable 实例(需要在使用它的地方之前定义)
 const proTable = ref<ProTableInstance>();
 
@@ -133,7 +226,7 @@ const columns = reactive<ColumnProps<any>[]>([
     width: 400
   },
   {
-    prop: "goodsId",
+    prop: "saleNum",
     label: "已售"
   },
   {
@@ -148,15 +241,18 @@ const columns = reactive<ColumnProps<any>[]>([
     }
   },
   {
-    prop: "goodsId",
+    prop: "costPrice",
     label: "成本价",
     render: (scope: any) => {
-      return `¥${scope.row.originalPrice}`;
+      return `¥${scope.row.costPrice}`;
     }
   },
   {
-    prop: "goodsId",
-    label: "利润"
+    prop: "profit",
+    label: "利润",
+    render: (scope: any) => {
+      return `¥${scope.row.profit}`;
+    }
   },
   {
     prop: "reviewType",
@@ -179,17 +275,6 @@ const columns = reactive<ColumnProps<any>[]>([
   { prop: "operation", label: "操作", fixed: "right", width: 330 }
 ]);
 
-// 在 script setup 中添加
-const allTabOptions = [
-  { label: "全部", name: "" },
-  { label: "草稿", name: "0" },
-  { label: "进行中", name: "5" },
-  { label: "待审核", name: "1" },
-  { label: "审核拒绝", name: "3" },
-  { label: "已售罄", name: "4" },
-  { label: "已结束", name: "7" }
-];
-
 // 获取当前审核状态
 const currentAuditStatus = computed(() => {
   if (!proTable.value?.searchParam) return "";
@@ -286,8 +371,8 @@ const changeInventory = (row: any) => {
 };
 const handleChangeStatus = async (row: any, status: string) => {
   try {
-    let res = await audit({ id: row.id, status: status, rejectionReason: form.comment });
-    if (res.code == 200) {
+    let res = await audit({ id: row.id, status: status, rejectionReason: "" });
+    if ((res.code as any) == 200) {
       proTable.value?.getTableList();
       if (status === "2") closeDialog();
       ElMessage.success("审核成功");

+ 301 - 296
src/views/groupPackageManagement/newGroup.vue

@@ -1,6 +1,10 @@
 <template>
   <!-- 团购包管理 - 新增/编辑页面 -->
   <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
+    <div class="header">
+      <el-button @click="goBack"> 返回 </el-button>
+      <h2 class="title">新建团购</h2>
+    </div>
     <el-form :model="storeInfoModel" ref="ruleFormRef" :rules="rules" label-width="120px" class="formBox">
       <div class="content">
         <!-- 左侧内容区域 -->
@@ -235,7 +239,7 @@
             <h3 style="font-weight: bold">购买须知:</h3>
             <el-form-item label="有效期" prop="effectiveDateType">
               <el-radio-group v-model="storeInfoModel.effectiveDateType" class="ml-4">
-                <el-radio v-for="status in expirationDateList" :value="status.value" :key="status.value">
+                <el-radio v-for="status in expirationDateStatusList" :value="status.value" :key="status.value">
                   {{ status.label }}
                 </el-radio>
               </el-radio-group>
@@ -247,9 +251,9 @@
                 <span class="expiration-label">天内有效</span>
               </div>
             </el-form-item>
-            <el-form-item label="" prop="effectiveDateValue" v-else>
+            <el-form-item label="" prop="expirationDateList" v-else>
               <el-date-picker
-                v-model="storeInfoModel.effectiveDateValue"
+                v-model="storeInfoModel.expirationDateList"
                 type="daterange"
                 value-format="YYYY-MM-DD"
                 range-separator="-"
@@ -308,9 +312,9 @@
             <el-form-item label="" prop="customUnavailableDates" v-else-if="storeInfoModel.disableDateType == 2">
               <div class="date-picker-container">
                 <el-button :icon="Plus" class="add-date-btn" type="primary" @click="addDate"> 添加日期 </el-button>
-                <div v-for="(item, index) in dates" :key="index" class="date-item">
+                <div v-for="(item, index) in storeInfoModel.disableDateList" :key="index" class="date-item">
                   <el-date-picker
-                    v-model="dates[index]"
+                    v-model="storeInfoModel.disableDateList[index]"
                     type="daterange"
                     value-format="YYYY-MM-DD"
                     range-separator="-"
@@ -326,7 +330,7 @@
                     size="small"
                     class="delete-btn"
                     @click="removeDate(index)"
-                    v-show="dates.length > 1"
+                    v-show="storeInfoModel.disableDateList.length > 1"
                   />
                 </div>
               </div>
@@ -383,7 +387,6 @@
     </el-form>
     <!-- 底部按钮区域 -->
     <div class="button-container">
-      <el-button @click="goBack"> 返回 </el-button>
       <el-button @click="handleSubmit('cg')"> 存草稿 </el-button>
       <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
     </div>
@@ -448,7 +451,7 @@
  */
 import { ref, reactive, onMounted, watch, nextTick, computed } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
-import { Plus, Delete, ArrowDown, ArrowUp, Picture, Check } from "@element-plus/icons-vue";
+import { Plus, Delete, ArrowDown, ArrowUp, Picture } from "@element-plus/icons-vue";
 import {
   saveStoreInfo,
   editStoreInfo,
@@ -666,7 +669,7 @@ const rules = reactive({
       trigger: "blur"
     }
   ],
-  effectiveDateValue: [
+  expirationDateList: [
     {
       required: true,
       validator: (rule: any, value: any, callback: any) => {
@@ -704,7 +707,7 @@ const rules = reactive({
     {
       required: true,
       validator: (rule: any, value: any, callback: any) => {
-        if (storeInfoModel.value.unavailableDates === 1) {
+        if (storeInfoModel.value.disableDateType === 1) {
           if (!value || value.length === 0) {
             callback(new Error("至少需要选择一个星期"));
             return;
@@ -719,7 +722,7 @@ const rules = reactive({
     {
       required: true,
       validator: (rule: any, value: any, callback: any) => {
-        if (storeInfoModel.value.unavailableDates === 1) {
+        if (storeInfoModel.value.disableDateType === 1) {
           if (!value || value.length === 0) {
             callback(new Error("至少需要选择一个节日"));
             return;
@@ -735,21 +738,21 @@ const rules = reactive({
       required: true,
       validator: (rule: any, value: any, callback: any) => {
         if (storeInfoModel.value.disableDateType === 2) {
-          if (!dates.value || dates.value.length === 0) {
+          if (!storeInfoModel.value.disableDateList || storeInfoModel.value.disableDateList.length === 0) {
             callback(new Error("至少需要添加一个自定义不可用日期"));
             return;
           }
           const today = new Date();
           today.setHours(0, 0, 0, 0);
           // 验证每个日期项是否已填写
-          for (let i = 0; i < dates.value.length; i++) {
-            if (!dates.value[i] || dates.value[i].length !== 2) {
+          for (let i = 0; i < storeInfoModel.value.disableDateList.length; i++) {
+            if (!storeInfoModel.value.disableDateList[i] || storeInfoModel.value.disableDateList[i].length !== 2) {
               callback(new Error(`第${i + 1}个日期项未完整填写`));
               return;
             }
             // 验证开始时间和结束时间不能早于当前时间
-            const startDate = new Date(dates.value[i][0]);
-            const endDate = new Date(dates.value[i][1]);
+            const startDate = new Date(storeInfoModel.value.disableDateList[i][0]);
+            const endDate = new Date(storeInfoModel.value.disableDateList[i][1]);
             if (startDate < today) {
               callback(new Error(`第${i + 1}个日期项的开始时间不能早于当前时间`));
               return;
@@ -814,7 +817,7 @@ const storeInfoModel = ref<any>({
   // 库存数量
   inventoryNum: "",
   // 每人限购设置:0-不限量,1-自定义限购数量
-  quotaValueStr: 0,
+  quotaType: 0,
   // 自定义限购数量(当quotaType为1时必填)
   quotaValue: 0,
   // 套餐内容(虚拟字段,用于表单验证,实际数据在 lifeGroupBuyThalis 变量中)
@@ -826,13 +829,15 @@ const storeInfoModel = ref<any>({
   // 有效期设置:0-指定天数,1-指定时间段内可用
   effectiveDateType: 0,
   expirationDate: 0,
-  effectiveDateValue: [],
+  expirationDateList: [],
   // 不可用日期设置:0-全部日期可用,1-限制日期,2-自定义不可用日期
   disableDateType: 0,
   // 限制日期 - 星期选择(数组,存储选中的星期值)
   unavailableWeekdays: [],
   // 限制日期 - 节日选择(数组,存储选中的节日值)
   unavailableHolidays: [],
+  // 自定义不可用日期列表
+  disableDateList: [],
   // 预约规则
   reservationRules: "",
   // 使用规则
@@ -861,26 +866,22 @@ const storeStatusList = ref([
   { value: 1, label: "设置售卖时间" }
 ]);
 
-// 每人限购设置列表
+// 每人限购设置
 const perList = ref([
   { value: 0, label: "不限量" },
   { value: 1, label: "自定义限购数量" }
 ]);
-// 每人限购设置列表
-const expirationDateList = ref([
+// 有效期设置
+const expirationDateStatusList = ref([
   { value: 0, label: "指定天数" },
   { value: 1, label: "指定时间段内可用" }
 ]);
-// 每人限购设置列表
+// 有效期设置
 const unavailableDatesList = ref([
   { value: 0, label: "全部日期可用" },
   { value: 1, label: "限制日期" },
   { value: 2, label: "自定义不可用日期" }
 ]);
-//图片集合
-const videoUrlList = ref<string[]>([]);
-// 自定义不可用日期列表
-const dates = ref([]);
 
 // 星期选项列表
 const weekdayList = ref([
@@ -896,8 +897,8 @@ const weekdayList = ref([
 // 节日选项列表
 const holidayList: any = ref([]);
 
-// 菜品选项列表(这里需要根据实际API获取,暂时使用示例数据)
-const dishOptions = ref([]);
+// 菜品选项列表
+const dishOptions: any = ref([]);
 
 // 套餐内容(数组,每个元素是一个分组)
 const lifeGroupBuyThalis = ref([
@@ -938,9 +939,6 @@ const visibleGroups = computed(() => {
   return lifeGroupBuyThalis.value.map((group, index) => ({ group, originalIndex: index }));
 });
 
-// 标记是否跳过最后一个分组的验证(用于添加新分组时)
-let skipLastGroupValidation = false;
-
 // ==================== 监听器 ====================
 
 /**
@@ -948,12 +946,12 @@ let skipLastGroupValidation = false;
  * 当切换到自定义不可用日期时,确保至少有一个日期项
  */
 watch(
-  () => storeInfoModel.value.unavailableDates,
+  () => storeInfoModel.value.disableDateType,
   newVal => {
     if (newVal === 2) {
-      // 切换到自定义不可用日期时,如果dates为空,则添加一个默认项
-      if (!dates.value || dates.value.length === 0) {
-        dates.value = [null];
+      // 切换到自定义不可用日期时,如果disableDateList为空,则添加一个默认项
+      if (!storeInfoModel.value.disableDateList || storeInfoModel.value.disableDateList.length === 0) {
+        storeInfoModel.value.disableDateList = [null];
       }
     }
   },
@@ -990,6 +988,26 @@ watch(
   }
 );
 
+/**
+ * 监听所有分组的类别值变化
+ * 当任何一个分组的类别值改变时,重新验证所有分组的类别字段,确保重复性校验实时生效
+ */
+watch(
+  () => lifeGroupBuyThalis.value.map(group => group.groupName),
+  () => {
+    // 当类别值改变时,重新验证所有分组的类别字段
+    nextTick(() => {
+      if (packageFormRef.value) {
+        lifeGroupBuyThalis.value.forEach((_, groupIndex) => {
+          const prop = `groups.${groupIndex}.groupName`;
+          packageFormRef.value?.validateField(prop, () => {});
+        });
+      }
+    });
+  },
+  { deep: true }
+);
+
 // ==================== 生命周期钩子 ====================
 
 /**
@@ -999,25 +1017,31 @@ watch(
 onMounted(async () => {
   id.value = (route.query.id as string) || "";
   type.value = (route.query.type as string) || "";
-  let param = {
-    // phone: localGet("iphone")
-    phone: "18641153170"
-  };
-  const resP: any = await getUserByPhone(param);
-  if (resP.data && resP.data.storeId) {
-    localSet("createdId", resP.data.storeId);
-    const resD: any = await getDetail({
-      id: localGet("createdId")
-    });
-    if (resD.data && resD.data.commissionRate) {
-      localSet("commissionRate", resD.data.commissionRate);
-    }
-    if (resD.data && resD.data.businessSection) {
-      localSet("businessSection", resD.data.businessSection);
-    }
-  } else {
-    ElMessage.warning("请完成商家入驻后再进行新建团购");
-  }
+  // 不要删除-开始
+  // let param = {
+  //   // phone: localGet("iphone")
+  //   phone: "18641153170"
+  // };
+  // const resP: any = await getUserByPhone(param);
+  // if (resP.data && resP.data.storeId) {
+  //   localSet("createdId", resP.data.storeId);
+  //   const resD: any = await getDetail({
+  //     id: localGet("createdId")
+  //   });
+  //   if (resD.data && resD.data.commissionRate) {
+  //     localSet("commissionRate", resD.data.commissionRate);
+  //   }
+  //   if (resD.data && resD.data.businessSection) {
+  //     localSet("businessSection", resD.data.businessSection);
+  //   }
+  // } else {
+  //   ElMessage.warning("请完成商家入驻后再进行新建团购");
+  // }
+  // if (!getGroupCombination()) {
+  //   ElMessage.warning("请完成商家入驻后重新登录再进行新建团购");
+  //   return;
+  // }
+  // 不要删除-结束
   let params = {
     year: new Date().getFullYear(),
     page: 1,
@@ -1025,25 +1049,22 @@ onMounted(async () => {
     openFlag: 1,
     holidayName: ""
   };
-  let res = await getHolidayList(params);
+  let res: any = await getHolidayList(params);
   if (res && res.code == 200) {
     holidayList.value = res.data.records;
   }
   if (type.value != "add") {
     let res: any = await getStoreDetail({ id: id.value } as any);
-    storeInfoModel.value = res.data as any;
-    videoUrlList.value = (res.data as any).businessLicenseAddress || [];
+    storeInfoModel.value = { ...storeInfoModel.value, ...res.data.lifeGroupBuyMain };
     let imageList: any[] = [];
     handleImageParam((res.data as any).businessLicenseAddress || [], imageList);
     storeInfoModel.value.imageList = imageList;
+    storeInfoModel.value.expirationDateList = storeInfoModel.value.expirationDateList.split(";");
+    storeInfoModel.value.invoiceInformation = storeInfoModel.value.invoiceType.split(";");
     // 确保星期和节日字段存在
-    if (!storeInfoModel.value.unavailableWeekdays) {
-      storeInfoModel.value.unavailableWeekdays = [];
-    }
-    if (!storeInfoModel.value.unavailableHolidays) {
-      storeInfoModel.value.unavailableHolidays = [];
-    }
-    // 确保套餐内容字段存在
+    const listVal = storeInfoModel.value.disableDateValue.split(";");
+    storeInfoModel.value.unavailableWeekdays = listVal[0].split(",");
+    storeInfoModel.value.unavailableHolidays = listVal[1].split(",");
     if (res.data.lifeGroupBuyThalis && res.data.lifeGroupBuyThalis.length > 0) {
       lifeGroupBuyThalis.value = res.data.lifeGroupBuyThalis;
       // 确保每个分组都有必要的字段
@@ -1080,22 +1101,15 @@ onMounted(async () => {
       groupCollapsedStates.value = [false];
     }
     // 确保自定义不可用日期字段存在
-    if (storeInfoModel.value.unavailableDates === 2) {
-      if (!dates.value || dates.value.length === 0) {
-        dates.value = [null];
-      }
-    }
-  } else {
-    // 新增模式下,如果默认选择自定义不可用日期,确保dates至少有一个元素
-    if (storeInfoModel.value.unavailableDates === 2) {
-      if (!dates.value || dates.value.length === 0) {
-        dates.value = [null];
+    if (storeInfoModel.value.disableDateType === 2) {
+      // 后端直接返回 disableDateList 字段,如果为空则初始化为默认值
+      if (!storeInfoModel.value.disableDateList || storeInfoModel.value.disableDateList.length === 0) {
+        storeInfoModel.value.disableDateList = [null];
       }
     }
   }
 });
 // ==================== 事件处理函数 ====================
-
 /**
  * 返回上一页
  */
@@ -1161,7 +1175,7 @@ const handlePictureCardPreview = (file: any) => {
  * 添加自定义不可用日期
  */
 const addDate = () => {
-  dates.value.push(null);
+  storeInfoModel.value.disableDateList.push(null);
 };
 
 /**
@@ -1169,11 +1183,15 @@ const addDate = () => {
  * @param index 要删除的日期索引
  */
 const removeDate = (index: number) => {
-  if (dates.value.length <= 1) {
+  if (storeInfoModel.value.disableDateList.length <= 1) {
     ElMessage.warning("至少需要保留一个日期项");
     return;
   }
-  dates.value.splice(index, 1);
+  storeInfoModel.value.disableDateList.splice(index, 1);
+  // 删除日期项后,重新验证表单以清除旧的验证错误
+  nextTick(() => {
+    ruleFormRef.value?.validateField("customUnavailableDates");
+  });
 };
 
 /**
@@ -1232,120 +1250,71 @@ const addGroup = () => {
     return;
   }
 
-  // 验证所有现有分组是否填写完整
-  for (let i = 0; i < lifeGroupBuyThalis.value.length; i++) {
-    const group = lifeGroupBuyThalis.value[i];
-
-    // 验证类别
-    if (!group.groupName || group.groupName.trim() === "") {
-      ElMessage.warning("请先完成现有分组的填写");
-      // 触发表单验证,显示验证错误
-      nextTick(() => {
-        if (packageFormRef.value) {
-          packageFormRef.value.validate(() => {});
-        }
-      });
-      return;
-    }
-
-    // 验证是否有菜品
-    if (!group.dishes || group.dishes.length === 0) {
-      ElMessage.warning("请先完成现有分组的填写");
-      // 触发表单验证,显示验证错误
-      nextTick(() => {
-        if (packageFormRef.value) {
-          packageFormRef.value.validate(() => {});
-        }
-      });
-      return;
-    }
-
-    // 验证当前分组中每个菜品是否填写完整
-    for (let j = 0; j < group.dishes.length; j++) {
-      const dish = group.dishes[j];
-
-      // 验证菜品是否已选择
-      if (!dish.detailId) {
+  // 使用表单验证来验证所有现有分组
+  if (packageFormRef.value) {
+    packageFormRef.value.validate((valid: boolean) => {
+      if (!valid) {
         ElMessage.warning("请先完成现有分组的填写");
-        // 触发表单验证,显示验证错误
-        nextTick(() => {
-          if (packageFormRef.value) {
-            packageFormRef.value.validate(() => {});
-          }
-        });
         return;
       }
 
-      // 验证数量是否已填写
-      if (!dish.qty || dish.qty.toString().trim() === "") {
-        ElMessage.warning("请先完成现有分组的填写");
-        // 触发表单验证,显示验证错误
-        nextTick(() => {
-          if (packageFormRef.value) {
-            packageFormRef.value.validate(() => {});
-          }
-        });
-        return;
-      }
-
-      // 验证数量格式
-      const quantityNum = Number(dish.qty);
-      if (isNaN(quantityNum) || quantityNum <= 0 || !Number.isInteger(quantityNum)) {
-        ElMessage.warning("请先完成现有分组的填写");
-        // 触发表单验证,显示验证错误
-        nextTick(() => {
-          if (packageFormRef.value) {
-            packageFormRef.value.validate(() => {});
+      // 验证通过,添加新分组
+      lifeGroupBuyThalis.value.push({
+        groupName: "",
+        dishes: [
+          {
+            detailId: "",
+            dishName: "",
+            dishPrice: "",
+            dishImage: "",
+            qty: "",
+            dishesUnit: ""
           }
-        });
-        return;
-      }
-    }
-  }
-
-  // 记录旧分组的数量,用于后续只验证旧分组
-  const oldGroupsCount = lifeGroupBuyThalis.value.length;
-
-  // 添加新分组
-  lifeGroupBuyThalis.value.push({
-    groupName: "",
-    dishes: [
-      {
-        detailId: "",
-        dishName: "",
-        dishPrice: "",
-        dishImage: "",
-        qty: "",
-        dishesUnit: ""
-      }
-    ]
-  });
+        ]
+      });
 
-  // 初始化新分组的收起状态为展开
-  groupCollapsedStates.value.push(false);
+      // 初始化新分组的收起状态为展开
+      groupCollapsedStates.value.push(false);
 
-  // 清除表单验证状态,避免新分组显示验证错误
-  // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
-  nextTick(() => {
-    requestAnimationFrame(() => {
-      setTimeout(() => {
-        if (packageFormRef.value) {
-          // 只清除新添加分组的验证状态
-          const newGroupIndex = lifeGroupBuyThalis.value.length - 1;
-          const propsToClear = [
-            `groups.${newGroupIndex}.groupName`,
-            `groups.${newGroupIndex}.dishes.0.detailId`,
-            `groups.${newGroupIndex}.dishes.0.qty`
-          ];
-          // Element Plus 的 clearValidate 可以接受字符串数组
-          propsToClear.forEach(prop => {
-            packageFormRef.value?.clearValidate(prop);
-          });
+      // 清除表单验证状态,避免新分组显示验证错误
+      // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
+      nextTick(() => {
+        requestAnimationFrame(() => {
+          setTimeout(() => {
+            if (packageFormRef.value) {
+              // 只清除新添加分组的验证状态
+              const newGroupIndex = lifeGroupBuyThalis.value.length - 1;
+              const propsToClear = [
+                `groups.${newGroupIndex}.groupName`,
+                `groups.${newGroupIndex}.dishes.0.detailId`,
+                `groups.${newGroupIndex}.dishes.0.qty`
+              ];
+              // Element Plus 的 clearValidate 可以接受字符串数组
+              propsToClear.forEach(prop => {
+                packageFormRef.value?.clearValidate(prop);
+              });
+            }
+          }, 150);
+        });
+      });
+    });
+  } else {
+    // 如果表单引用不存在,直接添加(兜底逻辑)
+    lifeGroupBuyThalis.value.push({
+      groupName: "",
+      dishes: [
+        {
+          detailId: "",
+          dishName: "",
+          dishPrice: "",
+          dishImage: "",
+          qty: "",
+          dishesUnit: ""
         }
-        skipLastGroupValidation = false;
-      }, 150);
+      ]
     });
-  });
+    groupCollapsedStates.value.push(false);
+  }
 };
 
 /**
@@ -1443,106 +1412,103 @@ const addDish = (groupIndex: number) => {
     return;
   }
 
-  // 验证当前分组是否填写完整
-  // 验证类别
-  if (!group.groupName || group.groupName.trim() === "") {
-    ElMessage.warning("请先完成现有菜品的填写");
-    // 触发表单验证,显示验证错误
-    nextTick(() => {
-      if (packageFormRef.value) {
-        packageFormRef.value.validate(() => {});
-      }
-    });
-    return;
-  }
+  // 使用表单验证来验证当前分组的所有字段
+  if (packageFormRef.value) {
+    // 收集当前分组的所有字段路径
+    const fieldsToValidate: string[] = [];
 
-  // 验证是否有菜品
-  if (!group.dishes || group.dishes.length === 0) {
-    ElMessage.warning("请先完成现有菜品的填写");
-    // 触发表单验证,显示验证错误
-    nextTick(() => {
-      if (packageFormRef.value) {
-        packageFormRef.value.validate(() => {});
-      }
-    });
-    return;
-  }
+    // 添加类别字段
+    fieldsToValidate.push(`groups.${groupIndex}.groupName`);
 
-  // 验证当前分组中每个菜品是否填写完整
-  for (let j = 0; j < group.dishes.length; j++) {
-    const dish = group.dishes[j];
-
-    // 验证菜品是否已选择
-    if (!dish.detailId) {
-      ElMessage.warning("请先完成现有菜品的填写");
-      // 触发表单验证,显示验证错误
-      nextTick(() => {
-        if (packageFormRef.value) {
-          packageFormRef.value.validate(() => {});
-        }
+    // 添加当前分组所有菜品的字段
+    if (group.dishes && group.dishes.length > 0) {
+      group.dishes.forEach((_, dishIndex) => {
+        fieldsToValidate.push(`groups.${groupIndex}.dishes.${dishIndex}.detailId`);
+        fieldsToValidate.push(`groups.${groupIndex}.dishes.${dishIndex}.qty`);
       });
-      return;
     }
 
-    // 验证数量是否已填写
-    if (!dish.qty || dish.qty.toString().trim() === "") {
-      ElMessage.warning("请先完成现有菜品的填写");
-      // 触发表单验证,显示验证错误
-      nextTick(() => {
-        if (packageFormRef.value) {
-          packageFormRef.value.validate(() => {});
-        }
+    if (fieldsToValidate.length === 0) {
+      // 如果没有字段需要验证,直接添加
+      if (!group.dishes) {
+        group.dishes = [];
+      }
+      group.dishes.push({
+        detailId: "",
+        dishName: "",
+        dishPrice: "",
+        dishImage: "",
+        qty: "",
+        dishesUnit: ""
       });
       return;
     }
 
-    // 验证数量格式
-    const quantityNum = Number(dish.qty);
-    if (isNaN(quantityNum) || quantityNum <= 0 || !Number.isInteger(quantityNum)) {
-      ElMessage.warning("请先完成现有菜品的填写");
-      // 触发表单验证,显示验证错误
-      nextTick(() => {
-        if (packageFormRef.value) {
-          packageFormRef.value.validate(() => {});
-        }
+    // 使用 Promise 等待所有字段验证完成
+    const validatePromises = fieldsToValidate.map(field => {
+      return new Promise<boolean>(resolve => {
+        packageFormRef.value?.validateField(field, (isValid: boolean) => {
+          resolve(isValid);
+        });
       });
-      return;
-    }
-  }
+    });
 
-  // 所有验证通过,添加新菜品
-  if (!group.dishes) {
-    group.dishes = [];
-  }
-  group.dishes.push({
-    detailId: "",
-    dishName: "",
-    dishPrice: "",
-    dishImage: "",
-    qty: "",
-    dishesUnit: ""
-  });
+    Promise.all(validatePromises).then(results => {
+      const allValid = results.every(result => result === true);
 
-  // 清除新添加菜品的验证状态,避免立即显示验证错误
-  // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
-  nextTick(() => {
-    requestAnimationFrame(() => {
-      setTimeout(() => {
-        if (packageFormRef.value) {
-          // 只清除新添加菜品的验证状态
-          const newDishIndex = group.dishes.length - 1;
-          const propsToClear = [
-            `groups.${groupIndex}.dishes.${newDishIndex}.detailId`,
-            `groups.${groupIndex}.dishes.${newDishIndex}.qty`
-          ];
-          // Element Plus 的 clearValidate 可以接受字符串
-          propsToClear.forEach(prop => {
-            packageFormRef.value?.clearValidate(prop);
-          });
-        }
-      }, 150);
+      if (!allValid) {
+        ElMessage.warning("请先完成现有菜品的填写");
+        return;
+      }
+
+      // 验证通过,添加新菜品
+      if (!group.dishes) {
+        group.dishes = [];
+      }
+      group.dishes.push({
+        detailId: "",
+        dishName: "",
+        dishPrice: "",
+        dishImage: "",
+        qty: "",
+        dishesUnit: ""
+      });
+
+      // 清除新添加菜品的验证状态,避免立即显示验证错误
+      // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
+      nextTick(() => {
+        requestAnimationFrame(() => {
+          setTimeout(() => {
+            if (packageFormRef.value) {
+              // 只清除新添加菜品的验证状态
+              const newDishIndex = group.dishes.length - 1;
+              const propsToClear = [
+                `groups.${groupIndex}.dishes.${newDishIndex}.detailId`,
+                `groups.${groupIndex}.dishes.${newDishIndex}.qty`
+              ];
+              // Element Plus 的 clearValidate 可以接受字符串
+              propsToClear.forEach(prop => {
+                packageFormRef.value?.clearValidate(prop);
+              });
+            }
+          }, 150);
+        });
+      });
     });
-  });
+  } else {
+    // 如果表单引用不存在,直接添加(兜底逻辑)
+    if (!group.dishes) {
+      group.dishes = [];
+    }
+    group.dishes.push({
+      detailId: "",
+      dishName: "",
+      dishPrice: "",
+      dishImage: "",
+      qty: "",
+      dishesUnit: ""
+    });
+  }
 };
 
 /**
@@ -1569,7 +1535,7 @@ const openDishDialog = async (groupIndex: number, dishIndex: number) => {
   currentDishIndex.value = dishIndex;
   dishSearchKeyword.value = "";
   const params = {
-    storeId: 361,
+    storeId: 104,
     phoneId: 18641153170,
     dishType: 0
   };
@@ -1679,6 +1645,31 @@ const packageFormRules = computed(() => {
         required: true,
         message: `第${groupIndex + 1}个分组的类别不能为空`,
         trigger: "blur"
+      },
+      {
+        validator: (rule: any, value: any, callback: any) => {
+          // 如果值为空,由 required 规则处理,这里直接通过
+          if (!value || value.toString().trim() === "") {
+            callback();
+            return;
+          }
+          const trimmedValue = value.toString().trim();
+          // 检查是否与其他分组的类别重复
+          for (let i = 0; i < lifeGroupBuyThalis.value.length; i++) {
+            // 跳过当前分组
+            if (i === groupIndex) {
+              continue;
+            }
+            const otherGroupName = lifeGroupBuyThalis.value[i].groupName;
+            // 如果其他分组的类别值与当前值相同(忽略前后空格),则重复
+            if (otherGroupName && otherGroupName.toString().trim() === trimmedValue) {
+              callback(new Error(`第${groupIndex + 1}个分组的类别与第${i + 1}个分组的类别重复`));
+              return;
+            }
+          }
+          callback();
+        },
+        trigger: "blur"
       }
     ];
 
@@ -1734,29 +1725,30 @@ const packageFormRules = computed(() => {
 const handleSubmit = (type?) => {
   let params: any = { ...storeInfoModel.value };
   if (params.effectiveDateType == 0) {
-    params.couponCompDate = `用户购买${params.expirationDate}天内有效`;
+    // params.couponCompDate = `用户购买${params.expirationDate}天内有效`;
   } else {
-    params.effectiveDateValue = params.effectiveDateValue.join(",");
+    params.expirationDateList = params.expirationDateList.join(",");
   }
   if (params.disableDateType == 1) {
     params.disableDateValue = params.unavailableWeekdays.join(",") + ";" + params.unavailableHolidays.join(",");
+    // params.disableDateValue = [params.unavailableWeekdays, params.unavailableHolidays];
     delete params.unavailableWeekdays;
     delete params.unavailableHolidays;
   } else if (params.disableDateType == 2) {
-    params.disableDateValue = dates.value.map(subArray => subArray.join(",")).join(";");
+    // params.disableDateValue = params.disableDateList.map(subArray => subArray.join(",")).join(";");
   }
   params.invoiceType = params.invoiceInformation.join(",");
-  const output = lifeGroupBuyThalis.value.flatMap(group =>
-    group.dishes.map(dish => ({
-      groupName: group.groupName,
-      detailId: dish.detailId,
-      qty: dish.qty,
-      dishPrice: dish.dishPrice
-    }))
-  );
+  // const output = lifeGroupBuyThalis.value.flatMap(group =>
+  //   group.dishes.map(dish => ({
+  //     groupName: group.groupName,
+  //     detailId: dish.detailId,
+  //     qty: dish.qty,
+  //     dishPrice: dish.dishPrice
+  //   }))
+  // );
   const paramsObj = {
     lifeGroupBuyMain: params,
-    lifeGroupBuyThalis: output
+    lifeGroupBuyThalis: lifeGroupBuyThalis.value
   };
   console.log(paramsObj);
   if (type) {
@@ -1892,6 +1884,21 @@ const handleImageParam = (list: any[], result: any[]) => {
   min-height: 100%;
 }
 
+/* 头部区域 */
+.header {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+  border-bottom: 1px solid #e4e7ed;
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+  text-align: center;
+}
+
 /* 内容区域布局 */
 .content {
   display: flex;
@@ -1957,7 +1964,7 @@ const handleImageParam = (list: any[], result: any[]) => {
 
   // background-color: #f5f7fa;
   border-radius: 4px;
-  transition: background-color 0.3s;
+  transition: background-color 0.1s;
   &:hover {
     background-color: #ecf5ff;
   }
@@ -2035,7 +2042,7 @@ const handleImageParam = (list: any[], result: any[]) => {
   margin: 0;
   font-size: 14px;
   border-radius: 4px;
-  transition: all 0.3s;
+  transition: all 0.1s;
 }
 
 /* 套餐内容相关样式 */
@@ -2170,11 +2177,9 @@ const handleImageParam = (list: any[], result: any[]) => {
   margin-top: 0;
 }
 .extra-dishes-container .dish-row {
-  padding-bottom: 22px;
   margin-bottom: 4px;
 }
 .extra-dishes-container .dish-row:last-child {
-  padding-bottom: 0;
   margin-bottom: 0;
 }
 .package-group-collapsed {
@@ -2378,7 +2383,7 @@ const handleImageParam = (list: any[], result: any[]) => {
   cursor: pointer;
   border: 1px solid #e4e7ed;
   border-radius: 4px;
-  transition: all 0.3s;
+  transition: all 0.1s;
 }
 .dish-item:hover {
   background-color: #f5f7fa;
@@ -2387,7 +2392,7 @@ const handleImageParam = (list: any[], result: any[]) => {
 .dish-item-selected {
   background-color: #ecf5ff;
   border-color: #409eff;
-  border-width: 2px;
+  border-width: 1px;
 }
 .dish-item-selected:hover {
   background-color: #d9ecff;
@@ -2449,14 +2454,14 @@ const handleImageParam = (list: any[], result: any[]) => {
 .slide-fade-enter-active {
   overflow: hidden;
   transition:
-    max-height 0.3s ease-out,
-    opacity 0.3s ease-out;
+    max-height 0.1s ease-out,
+    opacity 0.1s ease-out;
 }
 .slide-fade-leave-active {
   overflow: hidden;
   transition:
-    max-height 0.3s ease-in,
-    opacity 0.3s ease-in;
+    max-height 0.1s ease-in,
+    opacity 0.1s ease-in;
 }
 .slide-fade-enter-from {
   max-height: 0;
@@ -2474,10 +2479,10 @@ const handleImageParam = (list: any[], result: any[]) => {
 
 /* 分组收起全部动画 */
 .group-fade-enter-active {
-  transition: all 0.3s ease-out;
+  transition: all 0.1s ease-out;
 }
 .group-fade-leave-active {
-  transition: all 0.3s ease-in;
+  transition: all 0.1s ease-in;
 }
 .group-fade-enter-from {
   opacity: 0;
@@ -2488,6 +2493,6 @@ const handleImageParam = (list: any[], result: any[]) => {
   transform: translateY(-10px);
 }
 .group-fade-move {
-  transition: transform 0.3s ease;
+  transition: transform 0.1s linear;
 }
 </style>

+ 24 - 10
src/views/home/components/go-enter.vue

@@ -1,7 +1,7 @@
 <template>
   <!-- 首页:流程概览 -->
   <div v-if="currentStep === 0" class="home-entry">
-    <h3 class="title">免费入驻店铺</h3>
+    <h3 class="title"><el-image :src="homeIcon" class="homeIcon" />免费入驻店铺</h3>
     <div class="steps-container">
       <el-steps align-center>
         <el-step>
@@ -55,6 +55,7 @@
   </div>
 </template>
 <script setup lang="ts">
+import homeIcon from "../../../assets/images/home-icon.png";
 const props = defineProps({
   currentStep: {
     type: Number,
@@ -73,20 +74,31 @@ const handleRegister = () => {
 <style scoped lang="scss">
 // 首页样式
 .home-entry {
-  padding: 30px 40px;
-  background: #ffffff;
-  border: 1px solid #e4e7ed;
-  border-radius: 8px;
-  box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
+  box-sizing: border-box;
+  width: 100%;
+  height: calc(100vh - 105px);
+  overflow: hidden;
+  background: url("../../../assets/images/home-bg.png") center center no-repeat;
+  background-size: cover;
   .title {
-    margin: 0 0 30px;
+    display: flex;
+    align-items: center;
+    height: 105px;
     font-size: 20px;
     font-weight: 600;
-    color: #333333;
-    text-align: center;
+    color: #ffffff;
+    background: linear-gradient(90deg, #6c8ff8 0%, rgb(255 255 255 / 0%) 100%);
+    border-radius: 10px;
+    .homeIcon {
+      width: 76px;
+      height: 76px;
+      margin-right: 8px;
+      margin-left: 25px;
+    }
   }
   .steps-container {
-    margin-bottom: 40px;
+    margin-top: 60px;
+    margin-bottom: 60px;
     :deep(.el-steps) {
       .el-step__head {
         .el-step__icon {
@@ -138,7 +150,9 @@ const handleRegister = () => {
       height: 44px;
       font-size: 16px;
       font-weight: 500;
+      background: #6c8ff8;
       border-radius: 4px;
+      outline: none;
     }
   }
 }

+ 102 - 53
src/views/home/components/go-examine.vue

@@ -1,36 +1,45 @@
 <template>
   <div class="go-examine">
     <div class="panel">
-      <div class="panel-header">
-        <div class="store-name">
-          {{ storeName }}
-        </div>
-        <div class="expire">店铺到期时间:{{ expireDate }}</div>
-      </div>
-      <div class="verify-row">
-        <el-input v-model="voucherCode" placeholder="请输入劵码" class="verify-input" clearable />
-        <el-button type="primary" class="verify-btn" @click="handleVerify"> 验券 </el-button>
+      <div class="store-name">
+        <el-image :src="homeIcon" class="homeIcon" />
+        <div>{{ storeName }}</div>
       </div>
+      <div class="expire">店铺到期时间:{{ expireDate }}</div>
+    </div>
+
+    <div class="verify-row">
+      <el-input outline="none" v-model="voucherCode" placeholder="请输入劵码" class="verify-input" clearable />
+      <el-button type="primary" class="verify-btn" @click="handleVerify"> 验 券 </el-button>
     </div>
 
     <div class="stats">
-      <div class="stat-card">
-        <div class="stat-title">店铺钱包(元)</div>
-        <div class="stat-value">
-          {{ walletAmount.toFixed(2) }}
+      <div class="wallet">
+        <div class="stat-card">
+          <div class="stat-title">店铺钱包(元)</div>
+          <div class="stat-value">
+            {{ walletAmount.toFixed(2) }}
+          </div>
         </div>
+        <el-image :src="homeWallet" class="walletImg" />
       </div>
-      <div class="stat-card">
-        <div class="stat-title">今日订单量(单)</div>
-        <div class="stat-value">
-          {{ todayOrders }}
+      <div class="wallet">
+        <div class="stat-card">
+          <div class="stat-title">今日订单量(单)</div>
+          <div class="stat-value">
+            {{ todayOrders }}
+          </div>
         </div>
+        <el-image :src="homeOrder" class="walletImg" />
       </div>
-      <div class="stat-card">
-        <div class="stat-title">今日收益(元)</div>
-        <div class="stat-value">
-          {{ todayRevenue }}
+      <div class="wallet">
+        <div class="stat-card">
+          <div class="stat-title">今日收益(元)</div>
+          <div class="stat-value">
+            {{ todayRevenue.toFixed(2) }}
+          </div>
         </div>
+        <el-image :src="homeIncome" class="walletImg" />
       </div>
     </div>
   </div>
@@ -38,6 +47,10 @@
 
 <script setup lang="ts">
 import { ref } from "vue";
+import homeIcon from "../../../assets/images/home-icon.png";
+import homeWallet from "../../../assets/images/home-wallet.png";
+import homeOrder from "../../../assets/images/home-order.png";
+import homeIncome from "../../../assets/images/home-income.png";
 
 // 数据(可根据实际接口替换)
 const storeName = ref("时间图书馆");
@@ -58,58 +71,94 @@ const handleVerify = () => {
 
 <style scoped lang="scss">
 .go-examine {
+  box-sizing: border-box;
+  width: 100%;
+  height: calc(100vh - 105px);
+  overflow: hidden;
+  background: url("../../../assets/images/home-bg.png") center center no-repeat;
+  background-size: cover;
   .panel {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 105px;
     padding: 16px;
     margin-bottom: 16px;
     background: #ffffff;
+    background: linear-gradient(90deg, #6c8ff8 0%, rgb(255 255 255 / 0%) 100%);
     border: 1px solid #e4e7ed;
-    border-radius: 6px;
-    .panel-header {
+    border-radius: 10px;
+    .store-name {
       display: flex;
       align-items: center;
-      justify-content: space-between;
-      margin-bottom: 14px;
-      .store-name {
-        font-size: 16px;
-        font-weight: 600;
-        color: #303133;
-      }
-      .expire {
-        font-size: 13px;
-        color: #606266;
+      font-size: 24px;
+      font-weight: 600;
+      color: #ffffff;
+      .homeIcon {
+        width: 76px;
+        height: 76px;
+        margin-right: 8px;
+        margin-left: 25px;
       }
     }
-    .verify-row {
-      display: flex;
-      gap: 12px;
-      align-items: center;
-      .verify-input {
-        flex: 1;
-      }
-      .verify-btn {
-        width: 96px;
+    .expire {
+      font-size: 13px;
+      color: #606266;
+    }
+  }
+  .verify-row {
+    display: flex;
+    align-items: center;
+    height: 228px;
+    padding: 0 50px;
+    background: linear-gradient(90deg, #6c8ff8 0%, rgb(255 255 255 / 0%) 100%);
+    border-radius: 10px;
+    .verify-input {
+      height: 132px;
+      font-size: 30px;
+
+      // 去除输入框获得焦点时的默认蓝框
+      :deep(.el-input__wrapper) {
+        outline: none;
+        box-shadow: none;
       }
     }
+    .verify-btn {
+      width: 356px;
+      height: 132px;
+      font-size: 30px;
+      background: #6c8ff8;
+    }
   }
   .stats {
     display: grid;
     grid-template-columns: repeat(3, 1fr);
     gap: 20px;
-    .stat-card {
-      padding: 26px 12px;
-      text-align: center;
+    margin-top: 30px;
+    .wallet {
+      display: flex;
+      align-items: center;
+      justify-content: center;
       background: #ffffff;
       border: 1px solid #e4e7ed;
       border-radius: 6px;
-      .stat-title {
-        margin-bottom: 14px;
-        font-size: 14px;
-        color: #606266;
+      .stat-card {
+        padding: 26px 12px;
+        text-align: center;
+        .stat-title {
+          margin-bottom: 14px;
+          font-size: 14px;
+          color: #606266;
+        }
+        .stat-value {
+          font-size: 22px;
+          font-weight: 600;
+          color: #303133;
+        }
       }
-      .stat-value {
-        font-size: 22px;
-        font-weight: 600;
-        color: #303133;
+      .walletImg {
+        width: 211px;
+        height: 211px;
       }
     }
   }

+ 3 - 10
src/views/home/index.vue

@@ -17,22 +17,15 @@ import { ref } from "vue";
 import goEnter from "./components/go-enter.vue";
 import goFlow from "./components/go-flow.vue";
 import goExamine from "./components/go-examine.vue";
-import { useRouter } from "vue-router";
-const userInfo = ref(1);
+const userInfo = ref(0);
 
 // 当前步骤:0-首页,1-第一步,2-第二步
 const currentStep = ref(0);
-const router = useRouter();
-console.log(router.getRoutes());
+
 // 处理更新currentStep事件
 const handleUpdateCurrentStep = (step: number) => {
   currentStep.value = step;
 };
 </script>
 
-<style scoped lang="scss">
-#home {
-  min-height: calc(100vh - 100px);
-  padding: 20px;
-}
-</style>
+<style scoped lang="scss"></style>

+ 105 - 0
src/views/licenseManagement/businessLicense.vue

@@ -0,0 +1,105 @@
+<template>
+  <div class="card content-box">
+    <div class="page-header">
+      <h1 class="store-title">重庆老火锅</h1>
+    </div>
+    <div class="divider" />
+    <div class="tip-text">如需更换请联系客服</div>
+    <div class="license-container">
+      <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>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="businessLicense">
+import { ref, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+import { Picture } from "@element-plus/icons-vue";
+
+const licenseImage = ref<string>("");
+
+onMounted(async () => {
+  await initData();
+});
+
+const initData = async () => {
+  try {
+    // TODO: 调用API获取营业执照图片
+    // const response = await getBusinessLicense();
+    // if (response.code === 200) {
+    //   licenseImage.value = response.data.imageUrl;
+    // }
+  } catch (error) {
+    ElMessage.error("获取营业执照失败");
+  }
+};
+</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);
+}
+.divider {
+  width: 100%;
+  height: 1px;
+  margin-bottom: 20px;
+  background-color: var(--el-border-color-lighter);
+}
+.tip-text {
+  margin-bottom: 30px;
+  font-size: 14px;
+  color: var(--el-text-color-regular);
+}
+.license-container {
+  width: 100%;
+  min-height: 500px;
+  padding: 20px;
+  background-color: var(--el-bg-color-page);
+  border-radius: 8px;
+}
+.license-display {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  min-height: 500px;
+}
+.license-image {
+  max-width: 100%;
+  max-height: 600px;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+}
+.empty-image-box {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 500px;
+  background-color: var(--el-fill-color-lighter);
+  border-radius: 8px;
+  .empty-icon {
+    font-size: 64px;
+    color: var(--el-text-color-placeholder);
+  }
+}
+</style>

+ 424 - 0
src/views/licenseManagement/contractManagement.vue

@@ -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>

+ 0 - 93
src/views/licenseManagement/detail.vue

@@ -1,93 +0,0 @@
-<template>
-  <div class="card content-box">
-    <el-form :model="formData" label-width="140px">
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="店铺名称 :">
-            <span>{{ formData.storeName }}</span>
-          </el-form-item>
-          <el-form-item label="名称 :">
-            <span>{{ formData.name }}</span>
-          </el-form-item>
-          <el-form-item label="描述 :">
-            <span>{{ formData.description }}</span>
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="状态:">
-            <span>{{ getStatusName(formData.status) }}</span>
-          </el-form-item>
-          <el-form-item label="拒绝原因:" v-if="formData.status === '2'">
-            <span>{{ formData.rejectionReason }}</span>
-          </el-form-item>
-        </el-col>
-      </el-row>
-      <el-row class="text-center" style="margin-top: 20px">
-        <el-col :span="24">
-          <el-button type="primary" @click="goBack"> 确定 </el-button>
-        </el-col>
-      </el-row>
-    </el-form>
-  </div>
-</template>
-
-<script setup lang="tsx" name="licenseManagementDetail">
-import { ref, onMounted } from "vue";
-import { useRoute, useRouter } from "vue-router";
-import { ElMessage } from "element-plus";
-import { getStaffConfigDeatail } from "@/api/modules/staffConfig";
-
-const route = useRoute();
-const router = useRouter();
-
-const formData = ref({});
-
-const id = ref((route.query.id as string) || "");
-
-const getStatusName = (status: string) => {
-  switch (status) {
-    case "0":
-      return "待审核";
-    case "1":
-      return "审核通过";
-    case "2":
-      return "审核拒绝";
-    default:
-      return "未知状态";
-  }
-};
-
-onMounted(async () => {
-  await initData();
-});
-
-const initData = async () => {
-  if (id.value) {
-    try {
-      const response = await getStaffConfigDeatail({ id: id.value });
-      if (response.code === 200) {
-        formData.value = response.data;
-      }
-    } catch (error) {
-      ElMessage.error("获取详情失败");
-    }
-  }
-};
-
-const goBack = () => {
-  router.go(-1);
-};
-</script>
-
-<style lang="scss" scoped>
-.el-form {
-  width: 100%;
-  .text-center {
-    text-align: center;
-  }
-}
-.el-col {
-  box-sizing: border-box;
-  padding-right: 10px;
-}
-</style>

+ 393 - 0
src/views/licenseManagement/foodBusinessLicense.vue

@@ -0,0 +1,393 @@
+<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="license-container">
+      <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>
+      </div>
+    </div>
+
+    <!-- 更换食品经营许可证弹窗 -->
+    <el-dialog v-model="replaceDialogVisible" title="更换食品经营许可证" width="600px" @close="handleReplaceDialogClose">
+      <div class="replace-upload-area">
+        <el-upload
+          ref="uploadRef"
+          action="#"
+          :show-file-list="false"
+          :http-request="handleHttpUpload"
+          :before-upload="beforeUpload"
+          drag
+          class="upload-box"
+        >
+          <el-icon class="el-icon--upload">
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+          <template #tip>
+            <div class="upload-tip">({{ uploadedCount }}/1)</div>
+          </template>
+        </el-upload>
+        <div v-if="newLicenseUrl" class="preview-section">
+          <div class="preview-label">预览</div>
+          <el-image :src="newLicenseUrl" fit="contain" class="preview-image" />
+        </div>
+      </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="foodBusinessLicense">
+import { ref, computed, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+import { Picture, UploadFilled } from "@element-plus/icons-vue";
+import { uploadImg } from "@/api/modules/upload";
+import type { UploadProps, UploadRequestOptions } from "element-plus";
+
+interface ChangeRecordItem {
+  id: string;
+  status: "pending" | "success" | "failed";
+  rejectionReason?: string;
+}
+
+const licenseImage = ref<string>("");
+const replaceDialogVisible = ref(false);
+const cancelConfirmVisible = ref(false);
+const changeRecordDialogVisible = ref(false);
+const uploadRef = ref();
+const newLicenseUrl = ref("");
+const currentRecordDate = ref("2025.08.01 10:29");
+const changeRecordList = ref<ChangeRecordItem[]>([]);
+const rejectionReason = ref("");
+
+const uploadedCount = computed(() => (newLicenseUrl.value ? 1 : 0));
+
+onMounted(async () => {
+  await initData();
+});
+
+const initData = async () => {
+  try {
+    // TODO: 调用API获取食品经营许可证图片
+    // const response = await getFoodBusinessLicense();
+    // if (response.code === 200) {
+    //   licenseImage.value = response.data.imageUrl;
+    // }
+  } catch (error) {
+    ElMessage.error("获取食品经营许可证失败");
+  }
+};
+
+const handleReplace = () => {
+  newLicenseUrl.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" }];
+    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);
+    newLicenseUrl.value = data.fileUrl ? data.fileUrl : data[0];
+    ElMessage.success("上传成功");
+  } catch (error) {
+    ElMessage.error("上传失败");
+  }
+};
+
+const handleCancelReplace = () => {
+  if (newLicenseUrl.value) {
+    cancelConfirmVisible.value = true;
+  } else {
+    replaceDialogVisible.value = false;
+  }
+};
+
+const handleConfirmCancel = () => {
+  newLicenseUrl.value = "";
+  cancelConfirmVisible.value = false;
+  replaceDialogVisible.value = false;
+};
+
+const handleReplaceDialogClose = () => {
+  if (newLicenseUrl.value) {
+    cancelConfirmVisible.value = true;
+  }
+};
+
+const handleSubmitReplace = async () => {
+  if (!newLicenseUrl.value) {
+    ElMessage.warning("请先上传图片");
+    return;
+  }
+  try {
+    // TODO: 调用API提交审核
+    // await submitFoodLicenseReview(newLicenseUrl.value);
+    ElMessage.success("提交审核成功");
+    replaceDialogVisible.value = false;
+    newLicenseUrl.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;
+}
+.license-container {
+  width: 100%;
+  min-height: 500px;
+  padding: 20px;
+  background-color: var(--el-bg-color-page);
+  border-radius: 8px;
+}
+.license-display {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  min-height: 500px;
+}
+.license-image {
+  max-width: 100%;
+  max-height: 600px;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+}
+.empty-image-box {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 500px;
+  background-color: var(--el-fill-color-lighter);
+  border-radius: 8px;
+  .empty-icon {
+    font-size: 64px;
+    color: var(--el-text-color-placeholder);
+  }
+}
+.replace-upload-area {
+  min-height: 300px;
+  padding: 20px 0;
+  .upload-box {
+    width: 100%;
+    min-height: 200px;
+  }
+  .upload-tip {
+    margin-top: 10px;
+    font-size: 14px;
+    color: var(--el-text-color-secondary);
+    text-align: center;
+  }
+  .preview-section {
+    padding: 20px;
+    margin-top: 30px;
+    background-color: var(--el-fill-color-lighter);
+    border-radius: 8px;
+    .preview-label {
+      margin-bottom: 15px;
+      font-size: 14px;
+      color: var(--el-text-color-secondary);
+    }
+    .preview-image {
+      width: 100%;
+      max-height: 300px;
+    }
+  }
+}
+.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;
+    background-color: var(--el-fill-color-lighter);
+    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>

+ 0 - 203
src/views/licenseManagement/index.vue

@@ -1,203 +0,0 @@
-<template>
-  <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
-      <!-- 表格 header 按钮 -->
-      <template #tableHeader="scope">
-        <el-button type="primary" :icon="Download" @click="exportInfoExcel(scope)"> 导出 </el-button>
-      </template>
-      <template #expand="scope">
-        {{ scope.row }}
-      </template>
-      <!-- 表格操作 -->
-      <template #operation="scope">
-        <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
-        <template v-if="scope.row.status === '0'">
-          <el-button type="primary" link @click="changeTypes(scope.row, 'pass')"> 审核通过 </el-button>
-          <el-button type="primary" link @click="changeTypes(scope.row, '')"> 审核拒绝 </el-button>
-        </template>
-        <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
-      </template>
-    </ProTable>
-
-    <el-dialog v-model="dialogFormVisible" title="审核拒绝" width="500">
-      <el-form :model="form">
-        <el-form-item label="" label-width="0">
-          <el-input v-model="form.comment" autocomplete="off" type="textarea" maxlength="200" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="closeDialog"> 取消 </el-button>
-          <el-button type="primary" @click="handleSubmit"> 驳回 </el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup lang="tsx" name="licenseManagement">
-import { ref, reactive, onMounted, onActivated } from "vue";
-import { useRouter } from "vue-router";
-import { ElMessage } from "element-plus";
-import ProTable from "@/components/ProTable/index.vue";
-import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { Download } from "@element-plus/icons-vue";
-import { audit, exportExcelStaffConfig, getStaffConfigList } from "@/api/modules/staffConfig";
-
-const router = useRouter();
-const dialogFormVisible = ref(false);
-const form = reactive({
-  comment: ""
-});
-
-const rowData = ref<any>();
-
-const statusEnum = [
-  { value: "0", label: "待审核" },
-  { value: "1", label: "审核通过" },
-  { value: "2", label: "审核拒绝" }
-];
-// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
-const initParam = reactive({});
-
-// 定义 filterValues
-const filterValues = reactive({});
-
-const getStatusObj = (statusValue: string) => {
-  const statusObj = statusEnum.find(item => item.value === statusValue);
-  if (statusObj) {
-    filterValues.status = statusObj;
-  } else {
-    filterValues.status = "";
-  }
-  return statusObj;
-};
-
-// ProTable 实例
-const proTable = ref<ProTableInstance>();
-
-// 页面加载时触发查询
-onMounted(() => {
-  proTable.value?.getTableList();
-});
-
-// 从其他页面返回时触发查询
-onActivated(() => {
-  proTable.value?.getTableList();
-});
-
-// dataCallback 是对于返回的表格数据做处理,如果你后台返回的数据不是 list && total 这些字段,可以在这里进行处理成这些字段
-// 或者直接去 hooks/useTable.ts 文件中把字段改为你后端对应的就行
-const dataCallback = (data: any) => {
-  return {
-    list: data.records,
-    total: data.total
-  };
-};
-
-// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
-// 默认不做操作就直接在 ProTable 组件上绑定	:requestApi="getUserList"
-const getTableList = (params: any) => {
-  let newParams = JSON.parse(JSON.stringify(params));
-  return getStaffConfigList(newParams);
-};
-
-// 跳转详情页
-const toDetail = row => {
-  router.push(`/store/licenseManagementDetail?id=${row.id}`);
-};
-
-// 表格配置项
-const columns = reactive<ColumnProps<any>[]>([
-  { type: "index", fixed: "left", label: "序号", width: 130 },
-  { prop: "storeName", label: "所属店铺" },
-  { prop: "name", label: "名称" },
-  { prop: "description", label: "描述" },
-  {
-    prop: "status",
-    label: "状态",
-    render: scope => {
-      const statusObj = getStatusObj(scope.row.status);
-      return statusObj ? statusObj.label : "未知状态";
-    },
-    search: {
-      el: "select"
-    },
-    enum: statusEnum,
-    fieldNames: { label: "label", value: "value" }
-  },
-  { prop: "operation", label: "操作", fixed: "right", width: 330 }
-]);
-
-const changeTypes = (row: any, status: string) => {
-  rowData.value = row;
-  if (status === "pass") {
-    handleChangeStatus(row, "1");
-  } else {
-    form.comment = "";
-    dialogFormVisible.value = true;
-  }
-};
-
-const handleChangeStatus = async (row: any, status: string) => {
-  try {
-    let res = await audit({ id: row.id, status: status, rejectionReason: form.comment });
-    if (res.code === 200) {
-      proTable.value?.getTableList();
-      if (status === "2") closeDialog();
-      ElMessage.success("审核成功");
-    }
-  } catch (error) {
-    ElMessage.error("操作失败");
-  }
-};
-
-// 导出信息
-const exportInfoExcel = async scope => {
-  let res;
-  // 获取原始状态值(可能为数字、字符串或 undefined)
-  const rawStatus = proTable.value.searchParam.status;
-  // 转换为字符串(处理 undefined/null 为 "" 或保留原始字符串)
-  const statusParam = rawStatus !== undefined && rawStatus !== null ? String(rawStatus) : undefined;
-  // 将筛选条件作为参数传递给后台
-  res = await exportExcelStaffConfig({ status: statusParam });
-  if (res.code === 200) {
-    if (!res.data) {
-      ElMessage.error("暂无可下载数据");
-      return;
-    }
-    const exportFile = document.createElement("a");
-    exportFile.style.display = "none";
-    exportFile.download = `证照管理.xlsx`;
-    exportFile.href = `${res.data}?timestamp=${new Date().getTime()}`; // 添加时间戳防止缓存
-    document.body.appendChild(exportFile);
-    exportFile.click();
-    document.body.removeChild(exportFile);
-    ElMessage.success("下载成功");
-  }
-};
-
-// 弹窗提交
-const handleSubmit = () => {
-  if (!form.comment) {
-    ElMessage.error("请输入审批意见");
-    return;
-  }
-  handleChangeStatus(rowData.value, "2");
-};
-// 关闭弹窗;
-const closeDialog = () => {
-  dialogFormVisible.value = false;
-  form.comment = "";
-};
-</script>
-
-<style lang="scss" scoped>
-// 在组件样式中添加
-.date-range {
-  display: block; // 确保换行生效
-  padding: 0 8px; // 可选:增加内边距
-  word-wrap: break-word; // 长单词内换行
-  white-space: normal; // 允许自然换行
-}
-</style>

+ 157 - 47
src/views/orderManagement/detail.vue

@@ -1,33 +1,72 @@
 <template>
-  <div class="card content-box">
-    <el-form :model="formData" label-width="140px">
-      <el-row>
+  <div class="order-detail-box">
+    <div class="detail-header">
+      <el-button @click="goBack"> 返回 </el-button>
+      <h2 class="detail-title">订单详情</h2>
+    </div>
+    <div class="detail-content">
+      <div class="product-title">
+        {{ formData.productName || "--" }}
+      </div>
+      <el-row :gutter="40" class="detail-row">
         <el-col :span="12">
-          <el-form-item label="店铺名称 :">
-            <span>{{ formData.storeName }}</span>
-          </el-form-item>
-          <el-form-item label="名称 :">
-            <span>{{ formData.name }}</span>
-          </el-form-item>
-          <el-form-item label="描述 :">
-            <span>{{ formData.description }}</span>
-          </el-form-item>
+          <div class="detail-section">
+            <el-form :model="formData" label-width="120px">
+              <el-form-item label="订单编号:">
+                <span>{{ formData.orderNo || "--" }}</span>
+              </el-form-item>
+              <el-form-item label="下单时间:">
+                <span>{{ formData.orderTime || "--" }}</span>
+              </el-form-item>
+              <el-form-item label="原价:">
+                <span>¥{{ formData.originalPrice || 0 }}</span>
+              </el-form-item>
+              <el-form-item label="优惠价:">
+                <span>¥{{ formData.discountedPrice || 0 }}</span>
+              </el-form-item>
+              <el-form-item label="数量:">
+                <span>{{ formData.quantity || 0 }}</span>
+              </el-form-item>
+              <el-form-item label="优惠券减免:">
+                <span>¥{{ formData.couponDiscount || 0 }}</span>
+              </el-form-item>
+              <el-form-item label="优惠券类型:">
+                <span>{{ formData.couponType || "--" }}</span>
+              </el-form-item>
+              <el-form-item label="预留手机号:">
+                <span>{{ formData.phone || "--" }}</span>
+              </el-form-item>
+              <el-form-item label="预计收入:">
+                <span>¥{{ formData.estimatedIncome || 0 }}</span>
+              </el-form-item>
+            </el-form>
+          </div>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="状态:">
-            <span>{{ getStatusName(formData.status) }}</span>
-          </el-form-item>
-          <el-form-item label="拒绝原因:" v-if="formData.status === '2'">
-            <span>{{ formData.rejectionReason }}</span>
-          </el-form-item>
+          <div class="detail-section">
+            <div class="coupon-list">
+              <div v-for="(coupon, index) in couponList" :key="index" class="coupon-item">
+                <el-form :model="coupon" label-width="100px">
+                  <el-form-item label="状态:">
+                    <span>{{ getCouponStatusName(coupon.status) }}</span>
+                  </el-form-item>
+                  <el-form-item label="券码:">
+                    <span>{{ coupon.code || "--" }}</span>
+                  </el-form-item>
+                  <el-form-item v-if="coupon.status === '2'" label="核销时间:">
+                    <span>{{ coupon.verifyTime || "--" }}</span>
+                  </el-form-item>
+                  <el-form-item v-if="coupon.status === '3'" label="退款时间:">
+                    <span>{{ coupon.refundTime || "--" }}</span>
+                  </el-form-item>
+                </el-form>
+                <el-divider v-if="index < couponList.length - 1" />
+              </div>
+            </div>
+          </div>
         </el-col>
       </el-row>
-      <el-row class="text-center" style="margin-top: 20px">
-        <el-col :span="24">
-          <el-button type="primary" @click="goBack"> 确定 </el-button>
-        </el-col>
-      </el-row>
-    </el-form>
+    </div>
   </div>
 </template>
 
@@ -40,21 +79,19 @@ import { getStaffConfigDeatail } from "@/api/modules/staffConfig";
 const route = useRoute();
 const router = useRouter();
 
-const formData = ref({});
+const formData = ref<any>({});
+const couponList = ref<any[]>([]);
 
 const id = ref((route.query.id as string) || "");
 
-const getStatusName = (status: string) => {
-  switch (status) {
-    case "0":
-      return "待审核";
-    case "1":
-      return "审核通过";
-    case "2":
-      return "审核拒绝";
-    default:
-      return "未知状态";
-  }
+// 券码状态映射
+const getCouponStatusName = (status: string) => {
+  const statusMap: Record<string, string> = {
+    "1": "未核销",
+    "2": "已核销",
+    "3": "已退款"
+  };
+  return statusMap[status] || "--";
 };
 
 onMounted(async () => {
@@ -64,10 +101,43 @@ onMounted(async () => {
 const initData = async () => {
   if (id.value) {
     try {
-      const response = await getStaffConfigDeatail({ id: id.value });
-      if (response.code === 200) {
-        formData.value = response.data;
-      }
+      // TODO: 根据实际API获取订单详情
+      // const response = await getOrderDetail({ id: id.value });
+      // if (response.code === 200) {
+      //   formData.value = response.data;
+      //   couponList.value = response.data.couponList || [];
+      // }
+
+      // 临时模拟数据
+      formData.value = {
+        productName: "4拼冰淇淋蛋糕套餐",
+        orderNo: "17554469202966300062",
+        orderTime: "2025/01/01 12:00:00",
+        originalPrice: 400,
+        discountedPrice: 380,
+        quantity: 3,
+        couponDiscount: 20,
+        couponType: "商家优惠券/平台优惠券",
+        phone: "18900000000",
+        estimatedIncome: 380
+      };
+
+      couponList.value = [
+        {
+          status: "1",
+          code: "B844556562220"
+        },
+        {
+          status: "2",
+          code: "B844556562220",
+          verifyTime: "2025/01/01 12:00:00"
+        },
+        {
+          status: "3",
+          code: "B844556562220",
+          refundTime: "2025/01/01 12:00:00"
+        }
+      ];
     } catch (error) {
       ElMessage.error("获取详情失败");
     }
@@ -80,14 +150,54 @@ const goBack = () => {
 </script>
 
 <style lang="scss" scoped>
-.el-form {
-  width: 100%;
-  .text-center {
-    text-align: center;
+.order-detail-box {
+  padding: 20px;
+  background: #ffffff;
+}
+.detail-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+  .detail-title {
+    margin: 0 auto;
+    font-size: 20px;
+    font-weight: 500;
+  }
+}
+.detail-content {
+  .product-title {
+    padding-bottom: 15px;
+    margin-bottom: 30px;
+    font-size: 18px;
+    font-weight: 500;
+    border-bottom: 1px solid #e4e7ed;
+  }
+  .detail-row {
+    margin-top: 20px;
+  }
+  .detail-section {
+    min-height: 400px;
+    padding: 20px;
+    background: #fafafa;
+    border-radius: 4px;
+  }
+  .coupon-list {
+    .coupon-item {
+      margin-bottom: 20px;
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
   }
 }
-.el-col {
-  box-sizing: border-box;
-  padding-right: 10px;
+:deep(.el-form-item) {
+  margin-bottom: 20px;
+  .el-form-item__label {
+    font-weight: normal;
+    color: #606266;
+  }
+  span {
+    color: #303133;
+  }
 }
 </style>

+ 115 - 208
src/views/orderManagement/index.vue

@@ -4,201 +4,152 @@
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
         <div class="table-header-btn">
-          <el-button :icon="Plus" class="button" type="primary" @click="newGroupBuying"> 新建代金券 </el-button>
-          <el-tabs v-if="showTabs" v-model="activeName" class="tabs" @tab-click="handleClick">
-            <el-tab-pane v-for="tab in filteredTabOptions" :key="tab.name" :label="tab.label" :name="tab.name" />
+          <el-tabs v-model="activeName" class="tabs" @tab-click="handleClick">
+            <el-tab-pane v-for="tab in allTabOptions" :key="tab.name" :label="tab.label" :name="tab.name" />
           </el-tabs>
         </div>
       </template>
-      <template #status="scope">
-        <p>团购状态:{{ allTabOptions.find(item => item.name === scope.row.groupType)?.label ?? "--" }}</p>
-        <p>审核状态:{{ statusEnum.find(item => item.value === scope.row.reviewType)?.label ?? "--" }}</p> </template
-      >:
       <!-- 表格操作 -->
       <template #operation="scope">
-        <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
-        <template v-if="scope.row.status === '0'">
-          <el-button link type="primary" @click="changeTypes(scope.row, 'on')"> 上架 </el-button>
-          <el-button link type="primary" @click="changeTypes(scope.row, 'off')"> 下架 </el-button>
-          <el-button link type="primary" @click="changeInventory(scope.row)"> 修改库存 </el-button>
-        </template>
-        <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
-        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
-        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+        <el-button type="primary" link @click="showDishDialog(scope.row)"> 查看菜品 </el-button>
+        <el-button link type="primary" @click="toDetail(scope.row)"> 订单详情 </el-button>
       </template>
     </ProTable>
-    <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
-      <el-form ref="ruleFormRef" :model="formInventory" :rules="rules" @submit.prevent>
-        <el-form-item label="套餐名">
-          {{ formInventory.packageName }}
-        </el-form-item>
-        <el-form-item label="剩余库存">
-          {{ formInventory.remainingInventory }}
-        </el-form-item>
-        <el-form-item label="修改库存" prop="newInventory">
-          <el-input v-model="formInventory.newInventory" placeholder="请输入" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="closeDialog"> 取消 </el-button>
-          <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
-        </div>
-      </template>
+    <!-- 菜品详情弹窗 -->
+    <el-dialog v-model="dishDialogVisible" title="菜品详情" width="600">
+      <el-table :data="dishList" border>
+        <el-table-column prop="category" label="菜品类别" width="150" />
+        <el-table-column prop="name" label="菜品名称" />
+        <el-table-column prop="price" label="价格" width="120">
+          <template #default="scope"> ¥{{ scope.row.price }} </template>
+        </el-table-column>
+        <el-table-column prop="quantity" label="数量" width="120">
+          <template #default="scope"> {{ scope.row.quantity }}份 </template>
+        </el-table-column>
+      </el-table>
     </el-dialog>
   </div>
 </template>
 
-<script setup lang="tsx" name="groupPackageManagement">
-import { computed, onActivated, onMounted, reactive, ref, watch } from "vue";
+<script setup lang="tsx" name="orderManagement">
+import { onActivated, onMounted, reactive, ref } from "vue";
 import { useRouter } from "vue-router";
-import type { FormInstance, FormRules } from "element-plus";
-import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
-import { Plus } from "@element-plus/icons-vue";
-import { audit, getStaffConfigList } from "@/api/modules/staffConfig";
 import { getThaliList } from "@/api/modules/groupPackageManagement";
 
 const router = useRouter();
-const dialogFormVisible = ref(false);
-const formInventory: any = ref({
-  id: "",
-  packageName: "",
-  remainingInventory: "",
-  newInventory: ""
-});
-const rowData = ref<any>();
+const proTable = ref<ProTableInstance>();
 const activeName = ref("");
+const dishDialogVisible = ref(false);
+const dishList = ref<any[]>([]);
 
-const ruleFormRef = ref<FormInstance>();
-const rules = reactive<FormRules<RuleForm>>({
-  newInventory: [
-    { required: true, message: "请输入库存数量", trigger: "blur" },
-    {
-      pattern: /^(0|[1-9][0-9]*)$/,
-      message: "请输入整数,不允许输入小数,负数",
-      trigger: "blur"
-    }
-  ]
-});
+// 状态标签选项
+const allTabOptions = [
+  { label: "全部", name: "" },
+  { label: "待使用", name: "1" },
+  { label: "已完成", name: "2" },
+  { label: "已退款", name: "3" }
+];
+
+// 商品类型选项
 const statusEnum = [
-  { value: "-1", label: "待审核" },
-  { value: "-2", label: "审核通过" },
-  { value: "0", label: "审核驳回" }
+  { label: "团购", value: "1" },
+  { label: "代金券", value: "2" },
+  { label: "优惠券", value: "3" }
 ];
 
-// ProTable 实例(需要在使用它的地方之前定义)
-const proTable = ref<ProTableInstance>();
+// 状态映射
+const statusMap: Record<string, string> = {
+  "1": "待使用",
+  "2": "已完成",
+  "3": "已退款"
+};
 
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
   {
     prop: "index",
     label: "序号",
-    width: 100,
+    width: 80,
     render: (scope: any) => {
       return scope.$index + (proTable.value!.pageable.pageNum - 1) * proTable.value!.pageable.pageSize + 1;
     }
   },
   {
-    prop: "groupName",
-    label: "代金券名称",
+    prop: "orderNo",
+    label: "订单编号",
     search: {
-      el: "input"
+      el: "input",
+      props: { placeholder: "订单编号" }
     }
   },
   {
-    prop: "groupName",
-    label: "价格",
-    render: (scope: any) => {
-      return `¥${scope.row.preferentialPrice}`;
+    prop: "productName",
+    label: "商品名称",
+    search: {
+      el: "input",
+      props: { placeholder: "商品名称" }
     }
   },
   {
-    prop: "goodsId",
-    label: "已售"
+    prop: "productType",
+    label: "商品类型",
+    render: (scope: any) => {
+      const type = statusEnum.find(item => item.value === scope.row.productType);
+      return type ? type.label : "--";
+    },
+    search: {
+      el: "select",
+      props: { placeholder: "请选择" }
+    },
+    enum: statusEnum,
+    fieldNames: { label: "label", value: "value" }
   },
   {
-    prop: "inventoryNum",
-    label: "剩余库存"
+    prop: "quantity",
+    label: "数量"
   },
   {
-    prop: "goodsId",
-    label: "结束时间"
+    prop: "paidAmount",
+    label: "实付款",
+    render: (scope: any) => {
+      return `¥${scope.row.paidAmount || 0}`;
+    }
   },
   {
-    prop: "reviewType",
-    label: "审核状态",
-    isShow: false,
-    render: scope => {
-      const statusObj = statusEnum.find(item => item.value === scope.row.reviewType);
-      return statusObj ? statusObj.label : "--";
-    },
+    prop: "estimatedIncome",
+    label: "本单预计收入",
+    render: (scope: any) => {
+      return `¥${scope.row.estimatedIncome || 0}`;
+    }
+  },
+  {
+    prop: "orderTime",
+    label: "下单时间",
     search: {
-      el: "select"
-    },
-    enum: statusEnum,
-    fieldNames: { label: "label", value: "value" }
+      el: "date-picker",
+      props: {
+        type: "daterange",
+        "range-separator": " - ",
+        "start-placeholder": "开始日期",
+        "end-placeholder": "结束日期"
+      }
+    }
   },
   {
     prop: "status",
-    label: "状态"
+    label: "状态",
+    render: (scope: any) => {
+      return statusMap[scope.row.status] || "--";
+    }
   },
-  { prop: "operation", label: "操作", fixed: "right", width: 330 }
+  { prop: "operation", label: "操作", fixed: "right", width: 200 }
 ]);
 
-// 在 script setup 中添加
-const allTabOptions = [
-  { label: "全部", name: "" },
-  { label: "草稿", name: "0" },
-  { label: "进行中", name: "5" },
-  { label: "待审核", name: "1" },
-  { label: "审核拒绝", name: "3" },
-  { label: "已售罄", name: "4" },
-  { label: "已结束", name: "7" }
-];
-
-// 获取当前审核状态
-const currentAuditStatus = computed(() => {
-  if (!proTable.value?.searchParam) return "";
-  return proTable.value.searchParam.reviewType || "";
-});
-
-// 控制 el-tabs 是否显示:当审核状态为空或审核通过时显示
-const showTabs = computed(() => {
-  const status = currentAuditStatus.value;
-  return !status || status === "-2";
-});
-
-// 根据审核状态过滤 tabOptions:如果审核状态为空,只显示草稿;审核通过时,显示除草稿外的所有标签页
-const filteredTabOptions = computed(() => {
-  const status = currentAuditStatus.value;
-  if (!status) {
-    // 审核状态为空时,只显示草稿
-    return allTabOptions;
-  } else if (status === "-2") {
-    // 审核通过时,显示除草稿外的所有标签页
-    return allTabOptions.filter(tab => tab.name !== "1");
-  }
-  return [];
-});
-
-// 监听审核状态变化
-watch(
-  currentAuditStatus,
-  newStatus => {
-    if (!newStatus || newStatus === "-2") {
-      // 审核状态为空时,确保 activeName 为草稿
-      activeName.value = "";
-    }
-  },
-  { immediate: true }
-);
-// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
+// 初始化请求参数
 const initParam = reactive({
   storeId: "104",
-  groupType: "1",
   status: activeName
 });
 
@@ -212,93 +163,49 @@ onActivated(() => {
   proTable.value?.getTableList();
 });
 
-// dataCallback 是对于返回的表格数据做处理,如果你后台返回的数据不是 list && total 这些字段,可以在这里进行处理成这些字段
-// 或者直接去 hooks/useTable.ts 文件中把字段改为你后端对应的就行
+// 数据处理回调
 const dataCallback = (data: any) => {
   return {
-    list: data.records,
-    total: data.total
+    list: data.records || data.list || [],
+    total: data.total || 0
   };
 };
 
-// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
-// 默认不做操作就直接在 ProTable 组件上绑定	:requestApi="getUserList"
+// 获取表格列表
 const getTableList = (params: any) => {
   let newParams = JSON.parse(JSON.stringify(params));
+  // 将状态标签的值添加到请求参数中
+  if (activeName.value) {
+    newParams.status = activeName.value;
+  }
   return getThaliList(newParams);
 };
 
-// 跳转详情页
-const toDetail = row => {
-  router.push(`/voucherManagement/detail?id=${row.id}&type=edit`);
-};
-const editRow = row => {};
-const deleteRow = row => {};
-const newGroupBuying = () => {
-  router.push(`/voucherManagement/newVoucher?id=104&type=add`);
-};
-const handleClick = () => {};
-const changeTypes = (row: any, status: string) => {
-  rowData.value = row;
-  if (status === "on") {
-    handleChangeStatus(row, "1");
-  } else {
-    handleChangeStatus(row, "2");
-  }
-};
-const changeInventory = (row: any) => {
-  formInventory.value.id = 1;
-  formInventory.value.packageName = 1;
-  formInventory.value.remainingInventory = 1;
-  formInventory.value.newInventory = "";
-  dialogFormVisible.value = true;
-};
-const handleChangeStatus = async (row: any, status: string) => {
-  try {
-    let res = await audit({ id: row.id, status: status, rejectionReason: form.comment });
-    if (res.code == 200) {
-      proTable.value?.getTableList();
-      if (status === "2") closeDialog();
-      ElMessage.success("审核成功");
-    }
-  } catch (error) {
-    ElMessage.error("操作失败");
-  }
+// 状态标签切换
+const handleClick = () => {
+  proTable.value?.getTableList();
 };
 
-// 弹窗提交
-const handleSubmit = async () => {
-  if (!ruleFormRef.value) return;
-  await ruleFormRef.value.validate((valid, fields) => {
-    if (valid) {
-      ElMessage.success("修改成功");
-      dialogFormVisible.value = false;
-    }
-  });
+// 显示菜品弹窗
+const showDishDialog = (row: any) => {
+  // TODO: 根据实际API获取菜品数据
+  // 这里先使用模拟数据
+  dishList.value = [
+    { category: "蔬菜", name: "黄豆芽", price: 5, quantity: 1 },
+    { category: "荤菜", name: "虾滑", price: 12, quantity: 1 }
+  ];
+  dishDialogVisible.value = true;
 };
-// 关闭弹窗;
-const closeDialog = () => {
-  dialogFormVisible.value = false;
-  formInventory.value = {
-    id: "",
-    packageName: "",
-    remainingInventory: "",
-    newInventory: ""
-  };
+
+// 跳转详情页
+const toDetail = (row: any) => {
+  router.push(`/orderManagementDetail?id=${row.id}`);
 };
 </script>
 
 <style lang="scss" scoped>
-// 在组件样式中添加
-.date-range {
-  display: block; // 确保换行生效
-  padding: 0 8px; // 可选:增加内边距
-  word-wrap: break-word; // 长单词内换行
-  white-space: normal; // 允许自然换行
-}
 .table-header-btn {
   .tabs {
-    margin-top: 10px;
     :deep(.el-tabs__nav-wrap::after) {
       height: 0;
     }

+ 83 - 12
src/views/voucherManagement/index.vue

@@ -16,15 +16,54 @@
       >:
       <!-- 表格操作 -->
       <template #operation="scope">
-        <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
-        <template v-if="scope.row.status === '0'">
-          <el-button link type="primary" @click="changeTypes(scope.row, 'on')"> 上架 </el-button>
-          <el-button link type="primary" @click="changeTypes(scope.row, 'off')"> 下架 </el-button>
-          <el-button link type="primary" @click="changeInventory(scope.row)"> 修改库存 </el-button>
-        </template>
-        <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
-        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
-        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeTypes(scope.row, 'on')"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.上架)"
+        >
+          上架
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeTypes(scope.row, 'off')"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.下架)"
+        >
+          下架
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="changeInventory(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.修改库存)"
+        >
+          修改库存
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          @click="toDetail(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看详情)"
+        >
+          查看详情
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="editRow(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.编辑)"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="deleteRow(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.删除)"
+        >
+          删除
+        </el-button>
       </template>
     </ProTable>
     <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
@@ -148,17 +187,49 @@ const columns = reactive<ColumnProps<any>[]>([
   { prop: "operation", label: "操作", fixed: "right", width: 330 }
 ]);
 
-// 在 script setup 中添加
 const allTabOptions = [
   { label: "全部", name: "" },
   { label: "草稿", name: "0" },
   { label: "进行中", name: "5" },
-  { label: "待审核", name: "1" },
-  { label: "审核拒绝", name: "3" },
+  { label: "未开始", name: "2" },
+  { label: "已下架", name: "6" },
   { label: "已售罄", name: "4" },
   { label: "已结束", name: "7" }
 ];
 
+// 状态枚举:0草稿 1待审核 2未开始 3审核拒绝 4已售罄 5进行中 6已下架 7已结束
+const STATUS = {
+  草稿: 0,
+  待审核: 1,
+  未开始: 2,
+  审核拒绝: 3,
+  已售罄: 4,
+  进行中: 5,
+  已下架: 6,
+  已结束: 7
+} as const;
+
+// 操作按钮权限配置:定义每个操作按钮在哪些状态下显示
+const OPERATION_PERMISSIONS = {
+  // 查看详情:待审核、未开始、审核拒绝、进行中、已售罄、已下架
+  查看详情: [STATUS.待审核, STATUS.未开始, STATUS.审核拒绝, STATUS.进行中, STATUS.已售罄, STATUS.已下架],
+  // 上架:未开始、已下架
+  上架: [STATUS.未开始, STATUS.已下架],
+  // 下架:进行中
+  下架: [STATUS.进行中],
+  // 修改库存:未开始、进行中、已售罄
+  修改库存: [STATUS.未开始, STATUS.进行中, STATUS.已售罄],
+  // 编辑:草稿、审核拒绝、已售罄、已下架、已结束
+  编辑: [STATUS.草稿, STATUS.审核拒绝, STATUS.已售罄, STATUS.已下架, STATUS.已结束],
+  // 删除:草稿、未开始、审核拒绝、已售罄、已结束
+  删除: [STATUS.草稿, STATUS.未开始, STATUS.审核拒绝, STATUS.已售罄, STATUS.已结束]
+} as const;
+
+// 判断按钮是否显示的工具函数
+const canShowButton = (status: number, allowedStatuses: readonly number[]) => {
+  return allowedStatuses.includes(status);
+};
+
 // 获取当前审核状态
 const currentAuditStatus = computed(() => {
   if (!proTable.value?.searchParam) return "";