浏览代码

feat(store): 新增官方相册与菜品管理功能

- 新增官方相册的创建、删除、查询接口
- 新增菜品的创建、更新、查询接口
- 新增标签类型与标签的查询及保存接口
- 更新菜单列表,移除首页菜单项
- 优化退出登录逻辑,增加tabs和keepAlive清理
- 增强面包屑导航容错处理
- 完善标签页操作的路径检查
- 修复店铺详情数据获取与展示问题
- 重构菜单管理页面,支持菜品推荐与删除
- 调整分页组件配置,优化用户体验
spy 3 周之前
父节点
当前提交
aec22612f7

+ 47 - 0
src/api/modules/storeDecoration.ts

@@ -61,3 +61,50 @@ export const addOrEditBusinessTime = (params: any) => {
 export const getBusinessTimeList = (params: any) => {
   return httpApi.get(`/alienStorePlatform/storePlatformBusinessInfo/getByStoreId`, params);
 };
+
+//创建/更新官方相册
+export const createOrUpdateOfficialAlbum = (params: any) => {
+  return httpApi.post(`/alienStorePlatform/storePlatformOfficial/createOrUpdateOfficialAlbum`, params);
+};
+//删除官方相册
+export const deleteOfficialAlbum = (params: any) => {
+  return httpApi.post(`/alienStorePlatform/storePlatformOfficial/deleteOfficialAlbum`, params);
+};
+//获取官方相册列表
+export const getOfficialAlbumList = (params: any) => {
+  return httpApi.get(`/alienStorePlatform/storePlatformOfficial/getOfficialAlbumList`, params);
+};
+//相册内图片存储
+export const saveOfficialImg = (params: any) => {
+  return httpApi.post(`/alienStore/img/saveOrUpdate`, params);
+};
+
+//相册内图片查询
+export const getOfficialImgList = (businessId, storeId) => {
+  return httpApi.get(`/alienStore/img/getByBusinessId?businessId=${businessId}&imgType=2&storeId=${storeId}`);
+};
+//新建或修改菜品
+export const createOrUpdateDish = (params: any) => {
+  return httpApi.post(`/alienStorePlatform/menuPlatform/saveOrUpdate`, params);
+};
+//获取门店菜品列表
+export const getDishList = (params: any) => {
+  return httpApi.get(`/alienStorePlatform/menuPlatform/getMenuByStoreId`, params);
+};
+
+//获取菜品详情
+export const getDishDetail = (params: any) => {
+  return httpApi.get(`/alienStorePlatform/menuPlatform/getMenuInfo`, params);
+};
+//获取所有标签类型
+export const getAllTagType = (params?: any) => {
+  return httpApi.get(`/alienStorePlatform/storePlatformTag/getBusinessRelationTagList`, params);
+};
+//获取所有所选标签
+export const getAllTag = (params: any) => {
+  return httpApi.get(`/alienStorePlatform/storePlatformTag/getTagStoreRelationByStoreId`, params);
+};
+//保存标签
+export const saveTag = (params: any) => {
+  return httpApi.post(`/alienStorePlatform/storePlatformTag/createOrUpdateTagStoreRelation`, params);
+};

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

@@ -1,20 +1,7 @@
 {
   "code": 200,
   "data": [
-    {
-      "path": "/home/index",
-      "name": "home",
-      "component": "/home/index",
-      "meta": {
-        "icon": "List",
-        "title": "首页",
-        "isLink": "",
-        "isHide": false,
-        "isFull": false,
-        "isAffix": false,
-        "isKeepAlive": false
-      }
-    },
+
     {
       "path": "/home/notice",
       "name": "notice",

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

@@ -58,7 +58,10 @@ const globalStore = useGlobalStore();
 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 activeMenu = computed(() => {
+  if (!route || !route.path) return "";
+  return (route.meta?.activeMenu ? route.meta.activeMenu : route.path) as string;
+});
 
 const menuRef = ref<InstanceType<typeof ElMenu>>();
 const { openedMenus } = useOpenedMenus({ route, accordion });

+ 7 - 2
src/layouts/LayoutColumns/index.vue

@@ -69,7 +69,10 @@ const globalStore = useGlobalStore();
 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 activeMenu = computed(() => {
+  if (!route || !route.path) return "";
+  return (route.meta?.activeMenu ? route.meta.activeMenu : route.path) as string;
+});
 
 const subMenuList = ref<Menu.MenuOptions[]>([]);
 const splitActive = ref("");
@@ -79,11 +82,13 @@ watch(
   () => {
     // 当前菜单没有数据直接 return
     if (!menuList.value.length) return;
+    // 检查 route 和 route.path 是否存在
+    if (!route || !route.path) return;
     splitActive.value = route.path;
     const menuItem = menuList.value.filter((item: Menu.MenuOptions) => {
       return route.path === item.path || `/${route.path.split("/")[1]}` === item.path;
     });
-    if (menuItem[0].children?.length) return (subMenuList.value = menuItem[0].children);
+    if (menuItem[0]?.children?.length) return (subMenuList.value = menuItem[0].children);
     subMenuList.value = [];
   },
   {

+ 4 - 1
src/layouts/LayoutTransverse/index.vue

@@ -48,7 +48,10 @@ const route = useRoute();
 const router = useRouter();
 const authStore = useAuthStore();
 const menuList = computed(() => authStore.showMenuListGet);
-const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
+const activeMenu = computed(() => {
+  if (!route || !route.path) return "";
+  return (route.meta?.activeMenu ? route.meta.activeMenu : route.path) as string;
+});
 
 const handleClickMenu = (subItem: Menu.MenuOptions) => {
   if (subItem.meta.isLink) return window.open(subItem.meta.isLink, "_blank");

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

@@ -52,7 +52,10 @@ const globalStore = useGlobalStore();
 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 activeMenu = computed(() => {
+  if (!route || !route.path) return "";
+  return (route.meta?.activeMenu ? route.meta.activeMenu : route.path) as string;
+});
 
 const menuRef = ref<InstanceType<typeof ElMenu>>();
 const { openedMenus } = useOpenedMenus({ route, accordion });

+ 33 - 16
src/layouts/components/Header/components/Avatar.vue

@@ -30,6 +30,8 @@ import { useRouter } from "vue-router";
 import { logoutApi } from "@/api/modules/login";
 import { useUserStore } from "@/stores/modules/user";
 import { useAuthStore } from "@/stores/modules/auth";
+import { useTabsStore } from "@/stores/modules/tabs";
+import { useKeepAliveStore } from "@/stores/modules/keepAlive";
 import { ElMessageBox, ElMessage } from "element-plus";
 import InfoDialog from "./InfoDialog.vue";
 import PasswordDialog from "./PasswordDialog.vue";
@@ -42,6 +44,8 @@ import { localClear } from "@/utils";
 const router = useRouter();
 const userStore = useUserStore();
 const authStore = useAuthStore();
+const tabsStore = useTabsStore();
+const keepAliveStore = useKeepAliveStore();
 
 // 头像URL,先从本地存储读取,避免刷新后显示默认图片
 const getCachedAvatar = () => {
@@ -116,26 +120,39 @@ const logout = () => {
     cancelButtonText: "取消",
     type: "warning"
   }).then(async () => {
-    // 1.执行退出登录接口
-    // await logoutApi();
+    try {
+      // 1.执行退出登录接口
+      // await logoutApi();
+
+      // 2.清除动态路由
+      resetRouter();
 
-    // 2.清除动态路由
-    resetRouter();
+      // 3.清除菜单列表和权限信息
+      authStore.authMenuList = [];
+      authStore.authButtonList = {};
+      authStore.routeName = "";
 
-    // 3.清除菜单列表和权限信息
-    authStore.authMenuList = [];
-    authStore.authButtonList = {};
-    authStore.routeName = "";
+      // 4.清除 tabs、keepAlive 数据
+      await tabsStore.setTabs([]);
+      await keepAliveStore.setKeepAliveName([]);
 
-    // 4.清除 Token 和用户信息
-    userStore.setToken("");
-    // 3.重定向到登陆页
-    userStore.setUserInfo({ name: "" });
-    localClear();
+      // 5.清除 Token 和用户信息
+      userStore.setToken("");
+      userStore.setUserInfo({ name: "" });
+      localClear();
 
-    // 5.重定向到登陆页
-    router.replace(LOGIN_URL);
-    ElMessage.success("退出登录成功!");
+      // 6.重定向到登陆页
+      router.replace(LOGIN_URL);
+      ElMessage.success("退出登录成功!");
+    } catch (e) {
+      console.error("退出登录失败:", e);
+      // 即使出错也要清除数据并跳转
+      await tabsStore.setTabs([]);
+      await keepAliveStore.setKeepAliveName([]);
+      userStore.setToken("");
+      localClear();
+      router.replace(LOGIN_URL);
+    }
   });
 };
 

+ 13 - 2
src/layouts/components/Header/components/Breadcrumb.vue

@@ -33,9 +33,20 @@ const authStore = useAuthStore();
 const globalStore = useGlobalStore();
 
 const breadcrumbList = computed(() => {
-  let breadcrumbData = authStore.breadcrumbListGet[route.matched[route.matched.length - 1].path] ?? [];
+  // 检查 route.matched 是否存在且不为空
+  if (!route.matched || route.matched.length === 0) {
+    return [];
+  }
+
+  const lastMatched = route.matched[route.matched.length - 1];
+  // 检查最后一个匹配的路由是否有 path 属性
+  if (!lastMatched || !lastMatched.path) {
+    return [];
+  }
+
+  let breadcrumbData = authStore.breadcrumbListGet[lastMatched.path] ?? [];
   // 🙅‍♀️不需要首页面包屑可删除以下判断
-  if (breadcrumbData[0].path !== HOME_URL) {
+  if (breadcrumbData.length > 0 && breadcrumbData[0].path !== HOME_URL) {
     breadcrumbData = [{ path: HOME_URL, meta: { icon: "HomeFilled", title: "首页" } }, ...breadcrumbData];
   }
   return breadcrumbData;

+ 12 - 6
src/layouts/components/Tabs/components/MoreButton.vue

@@ -14,13 +14,13 @@
         <el-dropdown-item divided @click="closeCurrentTab">
           <el-icon><Remove /></el-icon>{{ $t("tabs.closeCurrent") }}
         </el-dropdown-item>
-        <el-dropdown-item @click="tabStore.closeTabsOnSide(route.fullPath, 'left')">
+        <el-dropdown-item @click="tabStore.closeTabsOnSide(route?.fullPath || '', 'left')">
           <el-icon><DArrowLeft /></el-icon>{{ $t("tabs.closeLeft") }}
         </el-dropdown-item>
-        <el-dropdown-item @click="tabStore.closeTabsOnSide(route.fullPath, 'right')">
+        <el-dropdown-item @click="tabStore.closeTabsOnSide(route?.fullPath || '', 'right')">
           <el-icon><DArrowRight /></el-icon>{{ $t("tabs.closeRight") }}
         </el-dropdown-item>
-        <el-dropdown-item divided @click="tabStore.closeMultipleTab(route.fullPath)">
+        <el-dropdown-item divided @click="tabStore.closeMultipleTab(route?.fullPath || '')">
           <el-icon><CircleClose /></el-icon>{{ $t("tabs.closeOther") }}
         </el-dropdown-item>
         <el-dropdown-item @click="closeAllTab">
@@ -48,11 +48,16 @@ const keepAliveStore = useKeepAliveStore();
 // refresh current page
 const refreshCurrentPage: Function = inject("refresh") as Function;
 const refresh = () => {
+  if (!route || !route.fullPath) return;
   setTimeout(() => {
-    route.meta.isKeepAlive && keepAliveStore.removeKeepAliveName(route.fullPath as string);
+    if (route.meta?.isKeepAlive && route.fullPath) {
+      keepAliveStore.removeKeepAliveName(route.fullPath as string);
+    }
     refreshCurrentPage(false);
     nextTick(() => {
-      route.meta.isKeepAlive && keepAliveStore.addKeepAliveName(route.fullPath as string);
+      if (route.meta?.isKeepAlive && route.fullPath) {
+        keepAliveStore.addKeepAliveName(route.fullPath as string);
+      }
       refreshCurrentPage(true);
     });
   }, 0);
@@ -65,7 +70,8 @@ const maximize = () => {
 
 // Close Current
 const closeCurrentTab = () => {
-  if (route.meta.isAffix) return;
+  if (!route || !route.fullPath) return;
+  if (route.meta?.isAffix) return;
   tabStore.removeTabs(route.fullPath);
 };
 

+ 5 - 3
src/layouts/components/Tabs/index.vue

@@ -33,7 +33,7 @@ const tabStore = useTabsStore();
 const authStore = useAuthStore();
 const globalStore = useGlobalStore();
 
-const tabsMenuValue = ref(route.fullPath);
+const tabsMenuValue = ref(route?.fullPath || "");
 const tabsMenuList = computed(() => tabStore.tabsMenuList);
 const tabsIcon = computed(() => globalStore.tabsIcon);
 
@@ -46,6 +46,8 @@ onMounted(() => {
 watch(
   () => route.fullPath,
   () => {
+    // 检查 route 和 route.meta 是否存在
+    if (!route || !route.meta || !route.fullPath) return;
     if (route.meta.isFull) return;
     tabsMenuValue.value = route.fullPath;
     const tabsParams = {
@@ -105,10 +107,10 @@ const tabRemove = (fullPath: TabPaneName) => {
     if (HOME_URL == fullPath) {
       return false;
     }
-    tabStore.removeTabs(fullPath as string, fullPath == route.fullPath);
+    tabStore.removeTabs(fullPath as string, route && route.fullPath ? fullPath == route.fullPath : false);
     router.push(HOME_URL);
   } else {
-    tabStore.removeTabs(fullPath as string, fullPath == route.fullPath);
+    tabStore.removeTabs(fullPath as string, route && route.fullPath ? fullPath == route.fullPath : false);
   }
 };
 </script>

+ 12 - 6
src/layouts/hooks/useOpenedMenus.ts

@@ -27,12 +27,18 @@ export const useOpenedMenus = ({ route, accordion }: UseOpenedMenusOptions) => {
 
   const buildTargetOpenedPaths = () => {
     const targetSet = new Set<string>();
-    collectAncestorPaths(route.path).forEach(path => targetSet.add(path));
-    const activeMenuPath = route.meta.activeMenu as string | undefined;
-    if (activeMenuPath) {
-      const normalizedActive = normalizePath(activeMenuPath);
-      collectAncestorPaths(normalizedActive).forEach(path => targetSet.add(path));
-      targetSet.add(normalizedActive);
+    // 检查 route.path 是否存在
+    if (route && route.path) {
+      collectAncestorPaths(route.path).forEach(path => targetSet.add(path));
+    }
+    // 检查 route.meta 是否存在
+    if (route && route.meta) {
+      const activeMenuPath = route.meta.activeMenu as string | undefined;
+      if (activeMenuPath) {
+        const normalizedActive = normalizePath(activeMenuPath);
+        collectAncestorPaths(normalizedActive).forEach(path => targetSet.add(path));
+        targetSet.add(normalizedActive);
+      }
     }
     return Array.from(targetSet);
   };

+ 8 - 2
src/routers/index.ts

@@ -46,13 +46,19 @@ router.beforeEach(async (to, from, next) => {
   // 1.NProgress 开始
   NProgress.start();
 
+  // 检查 to 对象是否存在
+  if (!to || !to.path) {
+    NProgress.done();
+    return next();
+  }
+
   // 2.动态设置标题
   const title = import.meta.env.VITE_GLOB_APP_TITLE;
-  document.title = to.meta.title ? `${to.meta.title} - ${title}` : title;
+  document.title = to.meta?.title ? `${to.meta.title} - ${title}` : title;
 
   // 3.判断是访问登陆页,有 Token 就在当前页面,没有 Token 重置路由到登陆页
   if (to.path.toLocaleLowerCase() === LOGIN_URL) {
-    if (userStore.token) return next(from.fullPath);
+    if (userStore.token && from && from.fullPath) return next(from.fullPath);
     resetRouter();
     return next();
   }

+ 16 - 1
src/routers/modules/staticRouter.ts

@@ -23,7 +23,22 @@ export const staticRouter: RouteRecordRaw[] = [
     component: () => import("@/layouts/index.vue"),
     // component: () => import("@/layouts/indexAsync.vue"),
     redirect: HOME_URL,
-    children: []
+    children: [
+      {
+        path: "/home/index",
+        name: "home",
+        component: () => import("@/views/home/index.vue"),
+        meta: {
+          icon: "List",
+          title: "首页",
+          isLink: "",
+          isHide: false,
+          isFull: false,
+          isAffix: false,
+          isKeepAlive: false
+        }
+      }
+    ]
   }
 ];
 

+ 23 - 21
src/views/storeDecoration/basicStoreInformation/index.vue

@@ -33,6 +33,7 @@
           </el-form-item>
 
           <!-- 营业状态 -->
+          {{ formData.businessStatus }}
           <el-form-item label="营业状态" prop="businessStatus">
             <el-radio-group v-model="formData.businessStatus">
               <div class="radio-item">
@@ -547,14 +548,14 @@ const handleSubmit = async () => {
         id: formData.id ? Number(formData.id) : undefined,
         isChain: formData.isChain,
         storeName: formData.storeName,
-        storeCapacity: String(formData.storeCapacity || ""),
+        storeCapacity: String(formData.storeCapacity ?? ""),
         storeTel: formData.storeTel,
         storeAddress: formData.storeAddress,
         storeArea: formData.storeArea,
         queryAddress: formData.queryAddress,
-        administrativeRegionProvinceAdcode: formData.administrativeRegionProvinceAdcode || "",
-        administrativeRegionCityAdcode: formData.administrativeRegionCityAdcode || "",
-        administrativeRegionDistrictAdcode: formData.administrativeRegionDistrictAdcode || "",
+        administrativeRegionProvinceAdcode: formData.administrativeRegionProvinceAdcode ?? "",
+        administrativeRegionCityAdcode: formData.administrativeRegionCityAdcode ?? "",
+        administrativeRegionDistrictAdcode: formData.administrativeRegionDistrictAdcode ?? "",
         storePositionLongitude: formData.storePositionLongitude,
         storePositionLatitude: formData.storePositionLatitude,
         businessStatus: formData.businessStatus
@@ -596,30 +597,31 @@ const userInfo = localGet("geeker-user")?.userInfo || {};
 const getStoreDetailData = async () => {
   isLoadingDetail.value = true; // 标记开始加载详情
   try {
-    const { data } = await getStoreDetail({ id: userInfo.id } as any);
+    const { data } = await getStoreDetail({ id: userInfo.storeId } as any);
     if (data) {
       const storeData = data as any;
-      formData.id = storeData.id || "";
-      formData.storeName = storeData.storeName || "";
-      formData.storeCapacity = Number(storeData.storeCapacity) || 0;
-      formData.storeTel = storeData.storeTel || "";
+      console.log(storeData.businessStatus ?? "", "-------参数");
+      formData.id = storeData.id ?? "";
+      formData.storeName = storeData.storeName ?? "";
+      formData.storeCapacity = Number(storeData.storeCapacity) ?? 0;
+      formData.storeTel = storeData.storeTel ?? "";
       // 将数字映射回字符串
-      formData.storeArea = storeData.storeArea || "";
+      formData.storeArea = storeData.storeArea ?? "";
       // 将数字映射回字符串
-      formData.businessStatus = storeData.businessStatus || "";
-      formData.storeAddress = storeData.storeAddress || "";
-      formData.storeBlurb = storeData.storeBlurb || "";
-      formData.expirationTime = storeData.expirationTime || "";
-      formData.foodLicenseExpirationTime = storeData.foodLicenseExpirationTime || "";
-      formData.storePositionLongitude = storeData.storePositionLongitude || "";
-      formData.storePositionLatitude = storeData.storePositionLatitude || "";
+      formData.businessStatus = storeData.businessStatus ?? "";
+      formData.storeAddress = storeData.storeAddress ?? "";
+      formData.storeBlurb = storeData.storeBlurb ?? "";
+      formData.expirationTime = storeData.expirationTime ?? "";
+      formData.foodLicenseExpirationTime = storeData.foodLicenseExpirationTime ?? "";
+      formData.storePositionLongitude = storeData.storePositionLongitude ?? "";
+      formData.storePositionLatitude = storeData.storePositionLatitude ?? "";
       formData.isChain = storeData.isChain ?? 0;
       formData.queryAddress = storeData.queryAddress;
 
       // 设置地区
-      const provinceCode = storeData.administrativeRegionProvinceAdcode || "";
-      const cityCode = storeData.administrativeRegionCityAdcode || "";
-      const districtCode = storeData.administrativeRegionDistrictAdcode || "";
+      const provinceCode = storeData.administrativeRegionProvinceAdcode ?? "";
+      const cityCode = storeData.administrativeRegionCityAdcode ?? "";
+      const districtCode = storeData.administrativeRegionDistrictAdcode ?? "";
 
       if (provinceCode) {
         formData.administrativeRegionProvinceAdcode = provinceCode;
@@ -632,7 +634,7 @@ const getStoreDetailData = async () => {
           }
         }
       }
-
+      console.log(formData, "----参数3");
       // 设置经营板块和种类
       if (storeData.businessSection) {
         const sectionId = String(storeData.businessSection);

+ 3 - 3
src/views/storeDecoration/businessHours/index.vue

@@ -503,7 +503,7 @@ const convertApiDataToDisplay = (apiData: any[]) => {
 const getBusinessTimeData = async () => {
   try {
     const userInfo: any = localGet("geeker-user")?.userInfo || {};
-    const storeId = userInfo.id || userInfo.storeId;
+    const storeId = userInfo.storeId;
     if (!storeId) {
       console.warn("未找到店铺ID");
       return;
@@ -530,7 +530,7 @@ onMounted(async () => {
 const convertDisplayDataToApi = () => {
   const apiDataList: any[] = [];
   const userInfo: any = localGet("geeker-user")?.userInfo || {};
-  const storeId = userInfo.id || userInfo.storeId;
+  const storeId = userInfo.storeId;
 
   // 转换正常营业时间
   normalHours.value.forEach(item => {
@@ -595,7 +595,7 @@ const handleSave = async () => {
     }
 
     const userInfo: any = localGet("geeker-user")?.userInfo || {};
-    const storeId = userInfo.id || userInfo.storeId;
+    const storeId = userInfo.storeId;
     if (!storeId) {
       ElMessage.error("未找到店铺ID,请先完成店铺信息填写");
       return;

+ 281 - 163
src/views/storeDecoration/menuManagement/index.vue

@@ -15,26 +15,26 @@
       <div class="dish-grid">
         <div v-for="(dish, index) in paginatedDishList" :key="dish.id" class="dish-card">
           <div class="dish-image-wrapper">
-            <img v-if="dish.image" :src="dish.image" alt="菜品图片" />
+            <img v-if="dish.imgUrl" :src="dish.imgUrl" alt="菜品图片" />
             <div v-else class="image-placeholder">
               <el-icon><Picture /></el-icon>
             </div>
-            <el-tag v-if="dish.isRecommended" type="primary" class="recommend-tag"> 推荐 </el-tag>
+            <el-tag v-if="dish.dishType === 1" type="primary" class="recommend-tag"> 推荐 </el-tag>
           </div>
           <div class="dish-info">
             <div class="dish-name">
-              {{ dish.name }}
+              {{ dish.dishName }}
             </div>
-            <div class="dish-price">¥{{ dish.price }}/{{ dish.unit }}</div>
-            <div v-if="activeTab === 'recommended'" class="dish-recommend-count">{{ dish.recommendCount || 0 }}人推荐</div>
+            <div class="dish-price">¥{{ dish.dishPrice }}/{{ dish.dishesUnit }}</div>
+            <div v-if="activeTab === 'recommended'" class="dish-recommend-count">{{ dish.likeCount || 0 }}人推荐</div>
           </div>
           <div class="dish-actions">
             <el-button type="primary" link @click="editDish(dish, index)"> 编辑 </el-button>
-            <el-button type="primary" link @click="deleteDish(dish.id, index)"> 删除 </el-button>
-            <el-button v-if="dish.isRecommended" type="primary" link @click="cancelRecommend(dish.id, index)">
+            <el-button type="primary" link @click="deleteDish(dish.id!, index)"> 删除 </el-button>
+            <el-button v-if="dish.dishType === 1" type="primary" link @click="cancelRecommend(dish.id!, index)">
               取消推荐
             </el-button>
-            <el-button v-else type="primary" link @click="setRecommend(dish.id, index)"> 设为推荐 </el-button>
+            <el-button v-else type="primary" link @click="setRecommend(dish.id!, index)"> 设为推荐 </el-button>
           </div>
         </div>
       </div>
@@ -44,7 +44,7 @@
         <el-pagination
           v-model:current-page="pageable.pageNum"
           v-model:page-size="pageable.pageSize"
-          :page-sizes="[10, 20, 50, 100]"
+          :page-sizes="[2, 20, 50, 100]"
           :total="pageable.total"
           layout="total, sizes, prev, pager, next, jumper"
           @size-change="handleSizeChange"
@@ -62,12 +62,12 @@
     <!-- 新建/编辑菜品弹窗 -->
     <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" @close="resetForm">
       <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
-        <el-form-item label="菜品名称" prop="name">
-          <el-input v-model="formData.name" placeholder="请输入" maxlength="10" show-word-limit clearable />
+        <el-form-item label="菜品名称" prop="dishName">
+          <el-input v-model="formData.dishName" placeholder="请输入" maxlength="10" show-word-limit clearable />
         </el-form-item>
-        <el-form-item label="价格 (¥)" prop="price">
+        <el-form-item label="价格 (¥)" prop="dishPrice">
           <el-input-number
-            v-model="formData.price"
+            v-model="formData.dishPrice"
             :min="0"
             :max="99999.99"
             :precision="2"
@@ -87,17 +87,18 @@
             style="width: 100%"
           />
         </el-form-item>
-        <el-form-item label="单位" prop="unit">
-          <el-select v-model="formData.unit" placeholder="请选择" style="width: 100%">
+        <el-form-item label="单位" prop="dishesUnit">
+          <el-select v-model="formData.dishesUnit" placeholder="请选择" style="width: 100%">
             <el-option v-for="unit in unitOptions" :key="unit.value" :label="unit.label" :value="unit.value" />
           </el-select>
         </el-form-item>
-        <el-form-item label="图片" prop="image">
+        <el-form-item label="图片" prop="imgUrl">
           <UploadImg
-            v-model:image-url="formData.image"
+            v-model:image-url="formData.imgUrl"
             :width="'200px'"
             :height="'200px'"
             :file-size="10"
+            :api="uploadImg"
             :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
             :border-radius="'8px'"
           />
@@ -113,14 +114,16 @@
             clearable
           />
         </el-form-item>
-        <el-form-item label="" prop="isRecommended">
+        <el-form-item label="" prop="dishType">
           <el-checkbox v-model="formData.isRecommended"> 设为推荐 </el-checkbox>
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="dialogVisible = false"> 取消 </el-button>
-          <el-button type="primary" :loading="submitLoading" @click="handleSubmit"> 新建菜品 </el-button>
+          <el-button type="primary" :loading="submitLoading" @click="handleSubmit">
+            {{ editId ? "确定" : "新建菜品" }}
+          </el-button>
         </div>
       </template>
     </el-dialog>
@@ -129,24 +132,25 @@
 
 <script setup lang="ts">
 import { ref, reactive, computed, onMounted } from "vue";
-import { ElMessage } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import { Picture } from "@element-plus/icons-vue";
 import UploadImg from "@/components/Upload/Img.vue";
-// TODO: 导入菜单相关的 API 接口
-// import { getDishList, createDish, updateDish, deleteDish, setRecommendDish, cancelRecommendDish } from "@/api/modules/storeDecoration";
+import { uploadImg } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils";
+import { createOrUpdateDish, getDishList, getDishDetail } from "@/api/modules/storeDecoration";
 
 // 菜品接口
 interface Dish {
-  id: string;
-  name: string;
-  price: number;
+  id?: string | number;
+  dishName: string;
+  dishPrice: number;
   costPrice: number;
-  unit: string;
-  image: string;
+  dishesUnit: string;
+  imgUrl: string;
   description?: string;
-  isRecommended: boolean;
-  recommendCount?: number;
+  dishType: number; // 0:未推荐, 1:推荐
+  likeCount?: number;
 }
 
 const dialogVisible = ref(false);
@@ -154,6 +158,7 @@ const formRef = ref<FormInstance>();
 const submitLoading = ref(false);
 const activeTab = ref<"menu" | "recommended">("menu");
 const editIndex = ref<number | null>(null);
+const editId = ref<string | number | null>(null);
 
 // 单位选项
 const unitOptions = [
@@ -169,11 +174,8 @@ const unitOptions = [
 // 菜品列表
 const dishList = ref<Dish[]>([]);
 
-// 显示菜品列表(根据tab过滤)
+// 显示菜品列表(直接使用从接口获取的数据,不需要过滤)
 const displayDishList = computed(() => {
-  if (activeTab.value === "recommended") {
-    return dishList.value.filter(dish => dish.isRecommended);
-  }
   return dishList.value;
 });
 
@@ -195,24 +197,24 @@ const paginatedDishList = computed(() => {
 // 弹窗标题
 const dialogTitle = computed(() => (editIndex.value !== null ? "编辑菜品" : "新建菜品"));
 
-// 表单数据
+// 表单数据(内部使用,方便表单绑定)
 const formData = reactive({
-  name: "",
-  price: undefined as number | undefined,
+  dishName: "",
+  dishPrice: undefined as number | undefined,
   costPrice: undefined as number | undefined,
-  unit: "份",
-  image: "",
+  dishesUnit: "份",
+  imgUrl: "",
   description: "",
-  isRecommended: true
+  isRecommended: true // 内部使用布尔值,提交时转换为 dishType
 });
 
 // 表单校验规则
 const rules = reactive<FormRules>({
-  name: [
+  dishName: [
     { required: true, message: "请输入菜品名称", trigger: "blur" },
     { max: 10, message: "菜品名称不能超过10个字", trigger: "blur" }
   ],
-  price: [
+  dishPrice: [
     { required: true, message: "请输入价格", trigger: "blur" },
     {
       validator: (rule, value, callback) => {
@@ -246,14 +248,15 @@ const rules = reactive<FormRules>({
       trigger: "blur"
     }
   ],
-  unit: [{ required: true, message: "请选择单位", trigger: "change" }],
-  image: [{ required: true, message: "请上传图片", trigger: "change" }],
+  dishesUnit: [{ required: true, message: "请选择单位", trigger: "change" }],
+  imgUrl: [{ required: true, message: "请上传图片", trigger: "change" }],
   description: [{ max: 300, message: "描述不能超过300个字", trigger: "blur" }]
 });
 
 // Tab切换
-const handleTabClick = () => {
+const handleTabClick = async () => {
   pageable.pageNum = 1;
+  await loadDishList();
   updatePagination();
 };
 
@@ -276,76 +279,195 @@ const handleCurrentChange = (page: number) => {
 // 打开新建弹窗
 const openCreateDialog = () => {
   editIndex.value = null;
+  editId.value = null;
   resetForm();
   dialogVisible.value = true;
 };
 
 // 编辑菜品
-const editDish = (dish: Dish, index: number) => {
-  // 在完整列表中查找索引
-  const actualIndex = dishList.value.findIndex(d => d.id === dish.id);
-  editIndex.value = actualIndex > -1 ? actualIndex : null;
-  formData.name = dish.name;
-  formData.price = dish.price;
-  formData.costPrice = dish.costPrice;
-  formData.unit = dish.unit;
-  formData.image = dish.image;
-  formData.description = dish.description || "";
-  formData.isRecommended = dish.isRecommended;
-  dialogVisible.value = true;
+const editDish = async (dish: Dish, index: number) => {
+  if (!dish.id) {
+    ElMessage.warning("菜品ID不存在");
+    return;
+  }
+
+  try {
+    // 调用获取菜品详情接口
+    const res: any = await getDishDetail({ id: dish.id });
+    if (res && (res.code === 200 || res.code === "200") && res.data) {
+      const dishDetail = res.data;
+      editId.value = dishDetail.id;
+      formData.dishName = dishDetail.dishName || "";
+      formData.dishPrice = dishDetail.dishPrice;
+      formData.costPrice = dishDetail.costPrice;
+      formData.dishesUnit = dishDetail.dishesUnit || "份";
+      formData.imgUrl = dishDetail.imgUrl || "";
+      formData.description = dishDetail.description || "";
+      formData.isRecommended = dishDetail.dishType === 1; // 将数字转换为布尔值
+      dialogVisible.value = true;
+    } else {
+      ElMessage.error(res?.msg || "获取菜品详情失败");
+    }
+  } catch (error: any) {
+    console.error("获取菜品详情失败:", error);
+    ElMessage.error(error?.msg || "获取菜品详情失败,请重试");
+  }
 };
 
 // 删除菜品
-const deleteDish = async (id: string, index: number) => {
-  // TODO: 调用删除菜品接口 deleteDish API
-  // await deleteDish({ id });
-
-  const actualIndex = dishList.value.findIndex(d => d.id === id);
-  if (actualIndex > -1) {
-    dishList.value.splice(actualIndex, 1);
-    updatePagination();
-    // 如果当前页没有数据了,回到上一页
-    if (paginatedDishList.value.length === 0 && pageable.pageNum > 1) {
-      pageable.pageNum--;
+const deleteDish = async (id: string | number, index: number) => {
+  try {
+    await ElMessageBox.confirm("确认删除该菜品吗?", "提示", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+
+    const dish = dishList.value.find(d => d.id === id);
+    if (!dish) {
+      ElMessage.error("未找到要删除的菜品");
+      return;
+    }
+
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      ElMessage.error("未找到店铺ID");
+      return;
+    }
+
+    // 通过更新接口删除(设置deleteFlag,如果有的话,或者直接调用删除接口)
+    // 如果没有删除接口,可以通过更新dishType为-1或其他标记来删除
+    // 这里假设可以通过更新接口删除,实际需要根据后端接口调整
+    const params: any = {
+      id: dish.id,
+      storeId: Number(storeId),
+      dishName: dish.dishName,
+      dishPrice: dish.dishPrice,
+      costPrice: dish.costPrice,
+      dishesUnit: dish.dishesUnit,
+      imgUrl: dish.imgUrl,
+      description: dish.description || "",
+      dishType: dish.dishType,
+      deleteFlag: 1 // 假设有删除标记字段
+    };
+
+    const res: any = await createOrUpdateDish(params);
+    if (res && (res.code === 200 || res.code === "200")) {
+      ElMessage.success("删除成功");
+      await loadDishList();
+      updatePagination();
+      // 如果当前页没有数据了,回到上一页
+      if (paginatedDishList.value.length === 0 && pageable.pageNum > 1) {
+        pageable.pageNum--;
+      }
+    } else {
+      ElMessage.error(res?.msg || "删除失败");
+    }
+  } catch (error: any) {
+    if (error !== "cancel") {
+      console.error("删除菜品失败:", error);
+      ElMessage.error(error?.msg || "删除失败,请重试");
     }
-    ElMessage.success("删除成功");
   }
 };
 
 // 设为推荐
-const setRecommend = async (id: string, index: number) => {
-  // TODO: 调用设为推荐接口 setRecommendDish API
-  // await setRecommendDish({ id });
-
-  const dish = dishList.value.find(d => d.id === id);
-  if (dish) {
-    dish.isRecommended = true;
-    dish.recommendCount = (dish.recommendCount || 0) + 1;
-    ElMessage.success("设置推荐成功");
+const setRecommend = async (id: string | number, index: number) => {
+  try {
+    const dish = dishList.value.find(d => d.id === id);
+    if (!dish) {
+      ElMessage.error("未找到菜品");
+      return;
+    }
+
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      ElMessage.error("未找到店铺ID");
+      return;
+    }
+
+    // 通过更新接口设置推荐
+    const params: any = {
+      id: dish.id,
+      storeId: Number(storeId),
+      dishName: dish.dishName,
+      dishPrice: dish.dishPrice,
+      costPrice: dish.costPrice,
+      dishesUnit: dish.dishesUnit,
+      imgUrl: dish.imgUrl,
+      description: dish.description || "",
+      dishType: 1 // 设置为推荐
+    };
+
+    const res: any = await createOrUpdateDish(params);
+    if (res && (res.code === 200 || res.code === "200")) {
+      ElMessage.success("设置推荐成功");
+      await loadDishList();
+      updatePagination();
+    } else {
+      ElMessage.error(res?.msg || "设置推荐失败");
+    }
+  } catch (error: any) {
+    console.error("设置推荐失败:", error);
+    ElMessage.error(error?.msg || "设置推荐失败,请重试");
   }
 };
 
 // 取消推荐
-const cancelRecommend = async (id: string, index: number) => {
-  // TODO: 调用取消推荐接口 cancelRecommendDish API
-  // await cancelRecommendDish({ id });
-
-  const dish = dishList.value.find(d => d.id === id);
-  if (dish) {
-    dish.isRecommended = false;
-    ElMessage.success("取消推荐成功");
+const cancelRecommend = async (id: string | number, index: number) => {
+  try {
+    const dish = dishList.value.find(d => d.id === id);
+    if (!dish) {
+      ElMessage.error("未找到菜品");
+      return;
+    }
+
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      ElMessage.error("未找到店铺ID");
+      return;
+    }
+
+    // 通过更新接口取消推荐
+    const params: any = {
+      id: dish.id,
+      storeId: Number(storeId),
+      dishName: dish.dishName,
+      dishPrice: dish.dishPrice,
+      costPrice: dish.costPrice,
+      dishesUnit: dish.dishesUnit,
+      imgUrl: dish.imgUrl,
+      description: dish.description || "",
+      dishType: 0 // 设置为未推荐
+    };
+
+    const res: any = await createOrUpdateDish(params);
+    if (res && (res.code === 200 || res.code === "200")) {
+      ElMessage.success("取消推荐成功");
+      await loadDishList();
+      updatePagination();
+    } else {
+      ElMessage.error(res?.msg || "取消推荐失败");
+    }
+  } catch (error: any) {
+    console.error("取消推荐失败:", error);
+    ElMessage.error(error?.msg || "取消推荐失败,请重试");
   }
 };
 
 // 重置表单
 const resetForm = () => {
-  formData.name = "";
-  formData.price = undefined;
+  formData.dishName = "";
+  formData.dishPrice = undefined;
   formData.costPrice = undefined;
-  formData.unit = "份";
-  formData.image = "";
+  formData.dishesUnit = "份";
+  formData.imgUrl = "";
   formData.description = "";
   formData.isRecommended = true;
+  editId.value = null;
   formRef.value?.clearValidate();
 };
 
@@ -362,91 +484,87 @@ const handleSubmit = async () => {
 
   submitLoading.value = true;
   try {
-    if (editIndex.value !== null && editIndex.value < dishList.value.length) {
-      // 编辑菜品
-      // TODO: 调用更新菜品接口 updateDish API
-      // const params = {
-      //   id: dishList.value[editIndex.value].id,
-      //   ...formData
-      // };
-      // await updateDish(params);
-
-      const dish = dishList.value[editIndex.value];
-      const wasRecommended = dish.isRecommended;
-      dish.name = formData.name;
-      dish.price = formData.price!;
-      dish.costPrice = formData.costPrice!;
-      dish.unit = formData.unit;
-      dish.image = formData.image;
-      dish.description = formData.description;
-      dish.isRecommended = formData.isRecommended;
-
-      // 如果从非推荐变为推荐,增加推荐人数
-      if (formData.isRecommended && !wasRecommended) {
-        dish.recommendCount = (dish.recommendCount || 0) + 1;
-      }
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      ElMessage.error("未找到店铺ID");
+      submitLoading.value = false;
+      return;
+    }
 
-      ElMessage.success("编辑成功");
-    } else {
-      // 新建菜品
-      // TODO: 调用创建菜品接口 createDish API
-      // const params = {
-      //   ...formData,
-      //   recommendCount: formData.isRecommended ? 1 : 0
-      // };
-      // const res = await createDish(params);
-      // dishList.value.push(res.data);
-
-      const newDish: Dish = {
-        id: `dish_${Date.now()}`,
-        name: formData.name,
-        price: formData.price!,
-        costPrice: formData.costPrice!,
-        unit: formData.unit,
-        image: formData.image,
-        description: formData.description,
-        isRecommended: formData.isRecommended,
-        recommendCount: formData.isRecommended ? 1 : 0
-      };
-
-      dishList.value.push(newDish);
-      ElMessage.success("新建成功");
+    // 构建请求参数
+    const params: any = {
+      storeId: Number(storeId),
+      dishName: formData.dishName,
+      dishPrice: formData.dishPrice!,
+      costPrice: formData.costPrice!,
+      dishesUnit: formData.dishesUnit,
+      imgUrl: formData.imgUrl,
+      description: formData.description || "",
+      dishType: formData.isRecommended ? 1 : 0
+    };
+
+    // 如果是编辑,添加id
+    if (editId.value) {
+      params.id = editId.value;
     }
 
-    dialogVisible.value = false;
-    updatePagination();
-    resetForm();
-  } catch (error) {
-    ElMessage.error("操作失败,请重试");
+    const res: any = await createOrUpdateDish(params);
+    if (res && (res.code === 200 || res.code === "200")) {
+      ElMessage.success(editId.value ? "编辑成功" : "新建成功");
+      dialogVisible.value = false;
+      resetForm();
+      // 重新加载菜品列表
+      await loadDishList();
+      updatePagination();
+    } else {
+      ElMessage.error(res?.msg || (editId.value ? "编辑失败" : "新建失败"));
+    }
+  } catch (error: any) {
     console.error("操作失败:", error);
+    ElMessage.error(error?.msg || "操作失败,请重试");
   } finally {
     submitLoading.value = false;
   }
 };
 
+// 加载菜品列表
+const loadDishList = async () => {
+  try {
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      console.warn("未找到店铺ID");
+      return;
+    }
+
+    // 根据tab切换调用不同的dishType参数
+    // dishType: 0表示菜单, 1表示推荐
+    const dishType = activeTab.value === "recommended" ? 1 : 0;
+    const res: any = await getDishList({ storeId: Number(storeId), dishType });
+
+    if (res && (res.code === 200 || res.code === "200") && res.data) {
+      const dataList = Array.isArray(res.data) ? res.data : [];
+      dishList.value = dataList.map((dish: any) => ({
+        id: dish.id,
+        dishName: dish.dishName || "",
+        dishPrice: dish.dishPrice || 0,
+        costPrice: dish.costPrice || 0,
+        dishesUnit: dish.dishesUnit || "份",
+        imgUrl: dish.imgUrl || "",
+        description: dish.description || "",
+        dishType: dish.dishType !== undefined ? dish.dishType : 0,
+        likeCount: dish.likeCount || 0
+      }));
+    }
+  } catch (error) {
+    console.error("获取菜品列表失败:", error);
+  }
+};
+
 // 页面初始化
 onMounted(async () => {
-  // TODO: 调用获取菜品列表接口 getDishList API
-  // const res = await getDishList();
-  // if (res.data) {
-  //   dishList.value = res.data;
-  //   updatePagination();
-  // }
-
-  // 示例数据
-  // dishList.value = [
-  //   {
-  //     id: "1",
-  //     name: "火锅锅底",
-  //     price: 38,
-  //     costPrice: 20,
-  //     unit: "份",
-  //     image: "",
-  //     description: "",
-  //     isRecommended: true,
-  //     recommendCount: 1
-  //   }
-  // ];
+  await loadDishList();
   updatePagination();
 });
 </script>

+ 451 - 97
src/views/storeDecoration/officialPhotoAlbum/index.vue

@@ -3,9 +3,12 @@
     <!-- 头部:Tabs和新建按钮 -->
     <div class="header-section">
       <el-tabs v-if="albumList.length > 0" v-model="activeTab" @tab-click="handleTabClick">
-        <el-tab-pane v-for="album in albumList" :key="album.id" :label="album.name" :name="album.id" />
+        <el-tab-pane v-for="album in albumList" :key="album.id" :label="album.name" :name="String(album.id)" />
       </el-tabs>
-      <el-button type="primary" @click="openCreateDialog"> 新建相册 </el-button>
+      <div class="header-actions">
+        <el-button v-if="albumList.length > 0" type="primary" link @click="handleEditAlbum"> 编辑 </el-button>
+        <el-button type="primary" @click="openCreateDialog"> 新建相册 </el-button>
+      </div>
     </div>
 
     <!-- 内容区域 -->
@@ -27,7 +30,7 @@
             :limit="60"
             :file-size="10"
             :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
-            :width="'200px'"
+            :width="'100%'"
             :height="'200px'"
             :border-radius="'8px'"
             :api="customUploadApi"
@@ -41,13 +44,12 @@
         <!-- 已上传的图片 -->
         <div v-for="(image, index) in displayImages" :key="image.uid || index" class="image-item">
           <div class="image-wrapper">
-            <img :src="image.url" alt="相册图片" />
+            <img :src="image.url || ''" alt="相册图片" />
             <div class="image-overlay">
-              <el-button type="primary" link @click="viewImage(image.url)"> 查看 </el-button>
+              <el-button type="primary" link @click="viewImage(image.url || '')"> 查看 </el-button>
               <el-button type="primary" link @click="deleteImage(index)"> 删除 </el-button>
             </div>
-            <el-tag v-if="image.isCover" type="danger" class="cover-tag"> 封面 </el-tag>
-            <el-button v-else type="primary" size="small" class="set-cover-btn" @click="setCover(index)"> 设为封面 </el-button>
+            <el-tag v-if="isFirstImage(index)" type="danger" class="cover-tag"> 封面 </el-tag>
           </div>
         </div>
       </div>
@@ -71,17 +73,22 @@
       <div class="empty-text">暂无相册,快来创建吧</div>
     </div>
 
-    <!-- 新建相册弹窗 -->
-    <el-dialog v-model="createDialogVisible" title="新建相册" width="500px" @close="resetCreateForm">
+    <!-- 新建/编辑相册弹窗 -->
+    <el-dialog v-model="createDialogVisible" :title="editId ? '修改相册名称' : '新建相册'" width="500px" @close="resetCreateForm">
       <el-form :model="createForm" :rules="createRules" ref="createFormRef" label-width="0">
         <el-form-item prop="name">
-          <el-input v-model="createForm.name" placeholder="请输入相册名称" maxlength="50" clearable />
+          <el-input v-model="createForm.name" placeholder="请输入相册名称" maxlength="6" clearable show-word-limit />
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button @click="createDialogVisible = false"> 取消 </el-button>
-          <el-button type="primary" @click="handleCreateAlbum"> 新建 </el-button>
+          <div class="footer-left">
+            <el-button v-if="editId" type="danger" @click="handleDeleteAlbum"> 删除相册 </el-button>
+          </div>
+          <div class="footer-right">
+            <el-button @click="createDialogVisible = false"> 取消 </el-button>
+            <el-button type="primary" @click="handleCreateAlbum"> 确定 </el-button>
+          </div>
         </div>
       </template>
     </el-dialog>
@@ -95,20 +102,36 @@
 
 <script setup lang="ts">
 import { ref, reactive, computed, onMounted, watch } from "vue";
-import { ElMessage } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import type { UploadUserFile } from "element-plus";
 import UploadImgs from "@/components/Upload/Imgs.vue";
-import { uploadImg } from "@/api/modules/upload";
-// TODO: 导入相册相关的 API 接口
-// import { getAlbumList, createAlbum, deleteAlbum, getAlbumImages, uploadAlbumImage, deleteAlbumImage, setCoverImage } from "@/api/modules/storeDecoration";
+import { localGet } from "@/utils";
+import {
+  getOfficialAlbumList,
+  createOrUpdateOfficialAlbum,
+  deleteOfficialAlbum,
+  saveOfficialImg,
+  getOfficialImgList
+} from "@/api/modules/storeDecoration";
+import { uploadImg } from "@/api/modules/newLoginApi";
+
+// 扩展图片类型
+interface AlbumImage extends UploadUserFile {
+  isCover?: boolean;
+  businessId?: number; // 相册ID
+  imgSort?: number; // 排序
+  imgType?: number; // 图片类型,固定为2
+  imgId?: number; // 图片ID(从服务器返回)
+}
 
 // 相册接口
 interface Album {
-  id: string;
+  id: string | number;
   name: string;
-  images: UploadUserFile[];
-  coverImageId?: string;
+  albumName?: string; // API返回的字段名
+  images: AlbumImage[];
+  coverImageId?: string | number;
 }
 
 const createDialogVisible = ref(false);
@@ -116,6 +139,7 @@ const createFormRef = ref<FormInstance>();
 const activeTab = ref("");
 const previewVisible = ref(false);
 const previewImageUrl = ref("");
+const editId = ref<string | number | null>(null);
 
 // 相册列表
 const albumList = ref<Album[]>([]);
@@ -125,7 +149,7 @@ const currentAlbum = computed(() => {
   if (albumList.value.length === 0) {
     return { id: "", name: "", images: [] };
   }
-  return albumList.value.find(album => album.id === activeTab.value) || albumList.value[0];
+  return albumList.value.find(album => String(album.id) === String(activeTab.value)) || albumList.value[0];
 });
 
 // 分页数据
@@ -155,7 +179,7 @@ const createForm = reactive({
 const createRules = reactive<FormRules>({
   name: [
     { required: true, message: "请输入相册名称", trigger: "blur" },
-    { min: 1, max: 50, message: "相册名称长度在1到50个字符", trigger: "blur" }
+    { min: 1, max: 6, message: "相册名称长度在1到6个字符", trigger: "blur" }
   ]
 });
 
@@ -204,11 +228,62 @@ const customUploadApi = async (formData: FormData): Promise<any> => {
   }
 
   // 校验通过后调用实际上传接口
-  return uploadImg(formData);
+  const result: any = await uploadImg(formData);
+
+  // 处理返回格式:可能是 { data: { fileUrl: string } } 或 { data: string[] }
+  const fileUrl = result?.data?.fileUrl || result?.data?.[0] || result?.fileUrl;
+
+  // 返回组件期望的格式
+  return {
+    data: {
+      fileUrl: fileUrl
+    }
+  };
+};
+
+// 保存图片到服务器
+const saveImageToServer = async (imgUrl: string) => {
+  try {
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId || !currentAlbum.value.id) {
+      return;
+    }
+
+    const currentImages = currentAlbum.value.images || [];
+    // 找到对应的图片(通过URL匹配)
+    const targetImage = currentImages.find((img: AlbumImage) => img.url === imgUrl && !img.imgId);
+    if (!targetImage) {
+      return; // 如果图片已经有ID或找不到,不重复保存
+    }
+
+    const imgSort = currentImages.filter((img: AlbumImage) => img.imgId).length + 1; // 排序为已保存图片数量+1
+
+    const params = [
+      {
+        businessId: Number(currentAlbum.value.id),
+        imgSort: imgSort,
+        imgType: 2,
+        imgUrl: imgUrl,
+        storeId: Number(storeId)
+      }
+    ];
+
+    const res: any = await saveOfficialImg(params);
+    if (res && (res.code === 200 || res.code === "200") && res.data && res.data[0]) {
+      // 保存成功后,更新图片的ID和其他信息
+      targetImage.imgId = res.data[0].id;
+      targetImage.businessId = res.data[0].businessId;
+      targetImage.imgSort = res.data[0].imgSort;
+    }
+  } catch (error) {
+    console.error("保存图片信息失败:", error);
+  }
 };
 
 // 打开新建相册弹窗
 const openCreateDialog = () => {
+  editId.value = null;
   createDialogVisible.value = true;
   resetCreateForm();
 };
@@ -216,10 +291,11 @@ const openCreateDialog = () => {
 // 重置新建表单
 const resetCreateForm = () => {
   createForm.name = "";
+  editId.value = null;
   createFormRef.value?.clearValidate();
 };
 
-// 创建相册
+// 创建/更新相册
 const handleCreateAlbum = async () => {
   if (!createFormRef.value) return;
 
@@ -229,35 +305,130 @@ const handleCreateAlbum = async () => {
     return;
   }
 
-  // TODO: 调用创建相册接口 createAlbum API
-  // const res = await createAlbum({ name: createForm.name });
-  // const newAlbum: Album = {
-  //   id: res.data.id,
-  //   name: createForm.name,
-  //   images: []
-  // };
-
-  // 创建新相册
-  const newAlbum: Album = {
-    id: `album_${Date.now()}`,
-    name: createForm.name,
-    images: []
-  };
+  if (!createForm.name.trim()) {
+    ElMessage.warning("请输入相册名称");
+    return;
+  }
 
-  albumList.value.push(newAlbum);
-  activeTab.value = newAlbum.id;
-  createDialogVisible.value = false;
-  ElMessage.success("创建成功");
-  resetCreateForm();
+  try {
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      ElMessage.error("未找到店铺ID");
+      return;
+    }
+
+    const params: any = {
+      albumName: createForm.name.trim(),
+      storeId: Number(storeId)
+    };
+
+    if (editId.value) {
+      params.id = editId.value;
+    }
+
+    const res: any = await createOrUpdateOfficialAlbum(params);
+    if (res && (res.code === 200 || res.code === "200")) {
+      ElMessage.success(editId.value ? "修改相册成功" : "新建相册成功");
+      createDialogVisible.value = false;
+
+      // 保存当前选中的相册ID
+      const currentAlbumId = activeTab.value;
+
+      resetCreateForm();
+      await getAlbumList();
+
+      // 如果是编辑相册,重新加载当前相册的图片
+      if (editId.value && currentAlbumId) {
+        activeTab.value = currentAlbumId;
+        await loadAlbumImages(currentAlbumId);
+      } else if (!editId.value && albumList.value.length > 0) {
+        // 如果是新建相册,切换到新创建的相册并加载图片
+        const newAlbum = albumList.value[albumList.value.length - 1];
+        activeTab.value = String(newAlbum.id);
+        await loadAlbumImages(String(newAlbum.id));
+      }
+    } else {
+      ElMessage.error(res?.msg || (editId.value ? "修改相册失败" : "新建相册失败"));
+    }
+  } catch (error: any) {
+    console.error("保存相册失败:", error);
+    ElMessage.error(error?.msg || "操作失败,请重试");
+  }
 };
 
 // Tab切换
-const handleTabClick = (tab: any) => {
-  activeTab.value = tab.props.name;
+const handleTabClick = async (tab: any) => {
+  activeTab.value = String(tab.props.name);
   pageable.pageNum = 1;
+  // 切换相册时加载图片列表
+  await loadAlbumImages(String(tab.props.name));
   updatePagination();
 };
 
+// 编辑相册
+const handleEditAlbum = () => {
+  const current = albumList.value.find(album => String(album.id) === activeTab.value);
+  if (!current) {
+    ElMessage.warning("请先选择要编辑的相册");
+    return;
+  }
+  editId.value = current.id;
+  createForm.name = current.name || current.albumName || "";
+  createDialogVisible.value = true;
+  createFormRef.value?.clearValidate();
+};
+
+// 删除相册
+const handleDeleteAlbum = async () => {
+  if (!editId.value) {
+    return;
+  }
+
+  try {
+    await ElMessageBox.confirm("确认删除该相册吗?删除后相册内的所有图片也将被删除。", "提示", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+
+    // 找到要删除的相册
+    const albumToDelete = albumList.value.find(album => album.id === editId.value);
+    if (!albumToDelete) {
+      ElMessage.error("未找到要删除的相册");
+      return;
+    }
+
+    // 调用删除接口
+    const res: any = await deleteOfficialAlbum([albumToDelete]);
+    if (res && (res.code === 200 || res.code === "200")) {
+      ElMessage.success("删除相册成功");
+      createDialogVisible.value = false;
+      resetCreateForm();
+
+      // 重新获取相册列表
+      await getAlbumList();
+
+      // 如果删除的是当前选中的相册,切换到第一个相册
+      if (String(editId.value) === activeTab.value) {
+        if (albumList.value.length > 0) {
+          activeTab.value = String(albumList.value[0].id);
+          await loadAlbumImages(String(albumList.value[0].id));
+        } else {
+          activeTab.value = "";
+        }
+      }
+    } else {
+      ElMessage.error(res?.msg || "删除相册失败");
+    }
+  } catch (error: any) {
+    if (error !== "cancel") {
+      console.error("删除相册失败:", error);
+      ElMessage.error(error?.msg || "删除失败,请重试");
+    }
+  }
+};
+
 // 查看图片
 const viewImage = (url: string) => {
   previewImageUrl.value = url;
@@ -265,35 +436,59 @@ const viewImage = (url: string) => {
 };
 
 // 删除图片
-const deleteImage = (index: number) => {
+const deleteImage = async (index: number) => {
   const actualIndex = (pageable.pageNum - 1) * pageable.pageSize + index;
-  const image = currentAlbum.value.images[actualIndex];
+  const image = currentAlbum.value.images[actualIndex] as AlbumImage;
 
-  // TODO: 调用删除图片接口 deleteAlbumImage API
-  // await deleteAlbumImage({ albumId: currentAlbum.value.id, imageId: image.id });
+  try {
+    // 如果图片有ID,说明已保存到服务器,需要调用删除接口
+    if (image.imgId) {
+      const userInfo: any = localGet("geeker-user")?.userInfo || {};
+      const storeId = userInfo.storeId;
+      if (!storeId || !currentAlbum.value.id) {
+        ElMessage.error("未找到店铺ID或相册ID");
+        return;
+      }
 
-  currentAlbum.value.images.splice(actualIndex, 1);
-  updatePagination();
-  ElMessage.success("删除成功");
+      // 通过保存接口删除(设置deleteFlag)
+      const params = [
+        {
+          id: image.imgId,
+          businessId: Number(currentAlbum.value.id),
+          imgSort: image.imgSort || 1,
+          imgType: 2,
+          imgUrl: image.url || "",
+          storeId: Number(storeId),
+          deleteFlag: 1 // 删除标记
+        }
+      ];
+
+      const res: any = await saveOfficialImg(params);
+      if (res && (res.code === 200 || res.code === "200")) {
+        currentAlbum.value.images.splice(actualIndex, 1);
+        updatePagination();
+        ElMessage.success("删除成功");
+      } else {
+        ElMessage.error(res?.msg || "删除失败");
+      }
+    } else {
+      // 如果图片还没有保存到服务器,直接删除本地数据
+      currentAlbum.value.images.splice(actualIndex, 1);
+      updatePagination();
+      ElMessage.success("删除成功");
+    }
+  } catch (error: any) {
+    console.error("删除图片失败:", error);
+    ElMessage.error(error?.msg || "删除失败,请重试");
+  }
 };
 
-// 设置封面
-const setCover = (index: number) => {
-  const actualIndex = (pageable.pageNum - 1) * pageable.pageSize + index;
-  const image = currentAlbum.value.images[actualIndex];
-
-  // TODO: 调用设置封面接口 setCoverImage API
-  // await setCoverImage({ albumId: currentAlbum.value.id, imageId: image.id });
-
-  // 清除其他图片的封面标记
-  currentAlbum.value.images.forEach(img => {
-    img.isCover = false;
-  });
-  // 设置当前图片为封面
-  image.isCover = true;
-  currentAlbum.value.coverImageId = image.uid;
-
-  ElMessage.success("设置封面成功");
+// 判断是否是第一张图片(封面)
+const isFirstImage = (displayIndex: number): boolean => {
+  // 计算在整个图片列表中的实际索引
+  const actualIndex = (pageable.pageNum - 1) * pageable.pageSize + displayIndex;
+  // 第一张图片(索引为0)是封面
+  return actualIndex === 0;
 };
 
 // 更新分页总数
@@ -316,46 +511,135 @@ const handleCurrentChange = (page: number) => {
   pageable.pageNum = page;
 };
 
-// 监听图片列表变化,更新分页
+// 监听图片列表变化,更新分页并保存新上传的图片
 watch(
   () => currentAlbum.value?.images?.length || 0,
-  () => {
+  async (newLength, oldLength) => {
     updatePagination();
+    // 如果有新图片添加(没有imgId的图片),保存到服务器
+    if (newLength > oldLength && currentAlbum.value.id) {
+      const newImages = currentAlbum.value.images.filter((img: AlbumImage) => !img.imgId && img.url);
+      for (const image of newImages) {
+        if (image.url) {
+          await saveImageToServer(image.url);
+        }
+      }
+    }
   }
 );
 
-// 监听相册切换,更新分页
+// 监听相册切换,加载图片列表
 watch(
   () => activeTab.value,
-  () => {
-    updatePagination();
-    pageable.pageNum = 1;
+  async newVal => {
+    if (newVal) {
+      await loadAlbumImages(newVal);
+      updatePagination();
+      pageable.pageNum = 1;
+    }
   }
 );
 
+// 获取相册列表
+const getAlbumList = async () => {
+  try {
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      console.warn("未找到店铺ID");
+      return;
+    }
+
+    const params = {
+      storeId: Number(storeId)
+    };
+    const res: any = await getOfficialAlbumList(params);
+    if (res && (res.code === 200 || res.code === "200") && res.data) {
+      const dataList = Array.isArray(res.data) ? res.data : [];
+      albumList.value = dataList.map((album: any) => ({
+        id: album.id,
+        name: album.albumName || album.name,
+        albumName: album.albumName,
+        images: [],
+        coverImageId: album.coverImageId
+      }));
+      // 如果没有选中的相册,默认选中第一个
+      if (albumList.value.length > 0 && !activeTab.value) {
+        activeTab.value = String(albumList.value[0].id);
+        // 加载第一个相册的图片
+        await loadAlbumImages(String(albumList.value[0].id));
+      }
+      // 如果已有选中的相册,确保该相册在列表中还存在,如果不存在则选中第一个
+      else if (albumList.value.length > 0 && activeTab.value) {
+        const exists = albumList.value.some(album => String(album.id) === activeTab.value);
+        if (!exists) {
+          activeTab.value = String(albumList.value[0].id);
+          await loadAlbumImages(String(albumList.value[0].id));
+        }
+      }
+    }
+  } catch (error) {
+    console.error("获取相册列表失败:", error);
+  }
+};
+
+// 加载相册图片列表
+const loadAlbumImages = async (albumId: string) => {
+  try {
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId || !albumId) {
+      return;
+    }
+
+    const res: any = await getOfficialImgList(Number(albumId), Number(storeId));
+    if (res && (res.code === 200 || res.code === "200") && res.data) {
+      const imageList = Array.isArray(res.data) ? res.data : [];
+      // 转换为页面显示格式
+      const images: AlbumImage[] = imageList
+        .filter((img: any) => img.deleteFlag !== 1) // 过滤已删除的图片
+        .map((img: any) => ({
+          uid: img.id || Date.now() + Math.random(),
+          name: img.imgUrl?.split("/").pop() || "image",
+          url: img.imgUrl,
+          status: "success",
+          imgId: img.id,
+          businessId: img.businessId,
+          imgSort: img.imgSort,
+          imgType: img.imgType,
+          isCover: false // 可以根据coverImageId判断
+        }))
+        .sort((a: AlbumImage, b: AlbumImage) => (a.imgSort || 0) - (b.imgSort || 0)); // 按排序字段排序
+
+      // 更新当前相册的图片列表
+      const current = albumList.value.find(album => String(album.id) === albumId);
+      if (current) {
+        current.images = images;
+        // 更新分页信息,确保视图刷新
+        updatePagination();
+      }
+    } else {
+      // 如果没有数据,也要清空当前相册的图片列表
+      const current = albumList.value.find(album => String(album.id) === albumId);
+      if (current) {
+        current.images = [];
+        updatePagination();
+      }
+    }
+  } catch (error) {
+    console.error("获取相册图片列表失败:", error);
+    // 出错时也要更新分页信息
+    const current = albumList.value.find(album => String(album.id) === albumId);
+    if (current) {
+      current.images = [];
+      updatePagination();
+    }
+  }
+};
+
 // 页面初始化
 onMounted(async () => {
-  // TODO: 调用获取相册列表接口 getAlbumList API
-  // const res = await getAlbumList();
-  // if (res.data && res.data.length > 0) {
-  //   albumList.value = res.data.map(album => ({
-  //     id: album.id,
-  //     name: album.name,
-  //     images: [],
-  //     coverImageId: album.coverImageId
-  //   }));
-  //   activeTab.value = albumList.value[0].id;
-  //   // 加载第一个相册的图片
-  //   await loadAlbumImages(albumList.value[0].id);
-  // }
-  // 初始化默认相册(示例数据)
-  // albumList.value = [
-  //   { id: "1", name: "环境氛围", images: [] },
-  //   { id: "2", name: "餐饮", images: [] }
-  // ];
-  // if (albumList.value.length > 0) {
-  //   activeTab.value = albumList.value[0].id;
-  // }
+  await getAlbumList();
 });
 </script>
 
@@ -375,6 +659,11 @@ onMounted(async () => {
         margin: 0;
       }
     }
+    .header-actions {
+      display: flex;
+      gap: 12px;
+      align-items: center;
+    }
   }
   .content-section {
     .upload-tips {
@@ -387,11 +676,67 @@ onMounted(async () => {
     }
     .image-grid {
       display: grid;
-      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+      grid-template-columns: repeat(5, 1fr);
       gap: 16px;
       margin-bottom: 24px;
       .upload-item {
+        position: relative;
+        width: 100%;
         min-height: 200px;
+
+        // 只隐藏已上传的图片列表项,保留上传按钮
+        :deep(.el-upload-list__item) {
+          display: none !important;
+        }
+
+        // 确保上传按钮容器正常
+        :deep(.upload-box) {
+          display: block !important;
+          width: 100% !important;
+        }
+
+        // 确保上传组件容器正常
+        :deep(.upload) {
+          display: block !important;
+          width: 100% !important;
+        }
+
+        // 确保上传列表容器宽度正确
+        :deep(.el-upload-list--picture-card) {
+          display: block !important;
+          width: 100% !important;
+        }
+
+        // 确保上传按钮正常显示和可点击,覆盖组件内部的固定宽度
+        :deep(.el-upload--picture-card) {
+          position: relative !important;
+          box-sizing: border-box !important;
+          display: inline-block !important;
+          width: 100% !important;
+          max-width: 100% !important;
+          height: 200px !important;
+          pointer-events: auto !important;
+          cursor: pointer !important;
+        }
+
+        // 确保上传输入框覆盖整个按钮区域
+        :deep(.el-upload--picture-card .el-upload__input) {
+          position: absolute !important;
+          top: 0 !important;
+          left: 0 !important;
+          z-index: 10 !important;
+          width: 100% !important;
+          height: 100% !important;
+          cursor: pointer !important;
+          opacity: 0 !important;
+        }
+
+        // 确保上传按钮内容不阻挡点击
+        :deep(.el-upload--picture-card .upload-empty) {
+          position: relative !important;
+          z-index: 1 !important;
+          pointer-events: none !important;
+        }
       }
       .image-item {
         position: relative;
@@ -463,7 +808,16 @@ onMounted(async () => {
   .dialog-footer {
     display: flex;
     gap: 12px;
-    justify-content: flex-end;
+    align-items: center;
+    justify-content: space-between;
+    .footer-left {
+      display: flex;
+      gap: 12px;
+    }
+    .footer-right {
+      display: flex;
+      gap: 12px;
+    }
   }
 }
 </style>

+ 2 - 2
src/views/storeDecoration/storeEntranceMap/index.vue

@@ -183,7 +183,7 @@ const previewData = reactive({
 const getEntranceImgData = async () => {
   try {
     const userInfo: any = localGet("geeker-user")?.userInfo || {};
-    const storeId = userInfo.id || userInfo.storeId;
+    const storeId = userInfo.storeId;
     if (!storeId) {
       console.warn("未找到店铺ID");
       return;
@@ -226,7 +226,7 @@ const handleSave = async () => {
 
   // 获取店铺ID
   const userInfo: any = localGet("geeker-user")?.userInfo || {};
-  const storeId = userInfo.id || userInfo.storeId;
+  const storeId = userInfo.storeId;
   if (!storeId) {
     ElMessage.error("未找到店铺ID,请先完成店铺信息填写");
     return;

+ 3 - 3
src/views/storeDecoration/storeHeadMap/index.vue

@@ -382,7 +382,7 @@ const initDragSort = () => {
 const loadHeadImgByMode = async (targetMode: "single" | "multiple") => {
   try {
     const userInfo: any = localGet("geeker-user")?.userInfo || {};
-    const storeId = userInfo.id || userInfo.storeId;
+    const storeId = userInfo.storeId;
     if (!storeId) {
       console.warn("未找到店铺ID");
       return;
@@ -442,7 +442,7 @@ const loadHeadImgByMode = async (targetMode: "single" | "multiple") => {
 const getStoreHeadImgData = async () => {
   try {
     const userInfo: any = localGet("geeker-user")?.userInfo || {};
-    const storeId = userInfo.id || userInfo.storeId;
+    const storeId = userInfo.storeId;
     if (!storeId) {
       console.warn("未找到店铺ID");
       return;
@@ -528,7 +528,7 @@ const handleSave = async () => {
 
   // 获取店铺ID
   const userInfo: any = localGet("geeker-user")?.userInfo || {};
-  const storeId = userInfo.id || userInfo.storeId;
+  const storeId = userInfo.storeId;
   if (!storeId) {
     ElMessage.error("未找到店铺ID,请先完成店铺信息填写");
     return;

+ 560 - 359
src/views/storeDecoration/storeLabel/index.vue

@@ -1,346 +1,310 @@
 <template>
   <div class="store-label-container">
     <el-form ref="formRef" :model="formData" :rules="rules" label-width="140px" class="store-label-form">
-      <div class="form-content">
-        <!-- 左侧:基础信息—顾客必看 -->
+      <!-- 头部:固定标题 -->
+      <div class="form-header">
         <div class="form-left">
           <h3 class="section-title">基础信息—顾客必看</h3>
+        </div>
+        <div class="form-right">
+          <h3 class="section-title">门店介绍-顾客想看</h3>
+        </div>
+      </div>
 
-          <!-- 卫生间 -->
-          <el-form-item label="卫生间">
-            <el-radio-group v-model="formData.restroom.has">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-            <div v-if="formData.restroom.has" class="sub-options">
-              <div class="sub-label">请选择卫生间类型</div>
-              <el-checkbox-group v-model="formData.restroom.types">
-                <el-checkbox label="商场卫生间"> 商场卫生间 </el-checkbox>
-                <el-checkbox label="室内卫生间"> 室内卫生间 </el-checkbox>
-              </el-checkbox-group>
-            </div>
-          </el-form-item>
-
-          <!-- 免费WIFI -->
-          <el-form-item label="免费WIFI">
-            <el-radio-group v-model="formData.freeWifi">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 室内空调 -->
-          <el-form-item label="室内空调">
-            <el-radio-group v-model="formData.airConditioning">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 停车场 -->
-          <el-form-item label="停车场">
-            <el-radio-group v-model="formData.parking.has">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-            <div v-if="formData.parking.has" class="sub-options">
-              <div class="sub-label">请选择停车场类型</div>
-              <el-checkbox-group v-model="formData.parking.types">
-                <el-checkbox label="免费停车场"> 免费停车场 </el-checkbox>
-                <el-checkbox label="收费停车场"> 收费停车场 </el-checkbox>
-              </el-checkbox-group>
-            </div>
-          </el-form-item>
-
-          <!-- 大桌 -->
-          <el-form-item label="大桌">
-            <el-radio-group v-model="formData.largeTable.has">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-            <div v-if="formData.largeTable.has" class="sub-options">
-              <div class="sub-label">请选择大桌类型</div>
-              <el-checkbox-group v-model="formData.largeTable.types">
-                <el-checkbox label="10人以上大桌"> 10人以上大桌 </el-checkbox>
-                <el-checkbox label="10人以下大桌"> 10人以下大桌 </el-checkbox>
-              </el-checkbox-group>
-            </div>
-          </el-form-item>
-
-          <!-- 儿童服务 -->
-          <el-form-item label="儿童服务">
-            <el-radio-group v-model="formData.childrenService">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 宠物政策 -->
-          <el-form-item label="宠物政策">
-            <el-radio-group v-model="formData.petPolicy">
-              <el-radio value="可携带宠物"> 可携带宠物 </el-radio>
-              <el-radio value="不可携带宠物"> 不可携带宠物 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 包厢 -->
-          <el-form-item label="包厢">
-            <el-radio-group v-model="formData.privateRoom.has">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-            <div v-if="formData.privateRoom.has" class="sub-options">
-              <div class="sub-label">请选择包厢类型</div>
-              <el-checkbox-group v-model="formData.privateRoom.types">
-                <el-checkbox label="10人以上包厢"> 10人以上包厢 </el-checkbox>
-                <el-checkbox label="10人以下包厢"> 10人以下包厢 </el-checkbox>
-                <el-checkbox label="双人包厢"> 双人包厢 </el-checkbox>
-              </el-checkbox-group>
-            </div>
-          </el-form-item>
-
-          <!-- 支持场景 -->
-          <el-form-item label="支持场景">
-            <div class="button-group">
-              <el-button
-                v-for="scene in sceneOptions"
-                :key="scene.value"
-                :type="formData.supportedScenes.includes(scene.value) ? 'primary' : 'default'"
-                @click="toggleScene(scene.value)"
-              >
-                {{ scene.label }}
-              </el-button>
+      <!-- 中间:可滚动的内容区域 -->
+      <div class="form-content">
+        <!-- 左侧:基础信息 -->
+        <div class="form-left">
+          <!-- 动态渲染标签卡片 -->
+          <template v-for="(tagGroup, groupIndex) in leftTagGroups" :key="groupIndex">
+            <div class="tag-card">
+              <div class="card-header">
+                <span class="header-icon" />
+                <span class="header-title">{{ tagGroup.labelName }}</span>
+              </div>
+              <div class="card-content">
+                <template v-for="(item, itemIndex) in tagGroup.list" :key="itemIndex">
+                  <!-- radioDouble类型:组合按钮(例如:卫生间、停车场、大桌、包厢) -->
+                  <div v-if="item.labelButtonType === 'radioDouble'" class="form-item-wrapper">
+                    <el-form-item :label="item.label">
+                      <div class="radio-double-wrapper">
+                        <div class="first-option">
+                          <el-radio-group v-model="formData[item.field_1]" @change="handleFormChange">
+                            <el-radio
+                              v-for="(option, optIndex) in item.labelButtonOption_1"
+                              :key="optIndex"
+                              :label="option.value"
+                              :value="option.value"
+                            >
+                              {{ option.name }}
+                            </el-radio>
+                          </el-radio-group>
+                        </div>
+                        <!-- 判断卫生间、停车场、大桌、包厢,这几项只有选择"有"的时候才显示下方的选项 -->
+                        <div
+                          v-if="
+                            (item.field_1 && formData[item.field_1] == 1) ||
+                            (item.field_5 && formData[item.field_5] == 1) ||
+                            (item.field_7 && formData[item.field_7] == 1) ||
+                            (item.field_11 && formData[item.field_11] == 1)
+                          "
+                          class="second-option"
+                        >
+                          <div class="sub-label">
+                            {{ item.tips }}
+                          </div>
+                          <el-radio-group v-if="item.field_2" v-model="formData[item.field_2]" @change="handleFormChange">
+                            <el-radio
+                              v-for="(option, optIndex) in item.labelButtonOption_2"
+                              :key="optIndex"
+                              :label="option.value"
+                              :value="option.value"
+                            >
+                              {{ option.name }}
+                            </el-radio>
+                          </el-radio-group>
+                        </div>
+                      </div>
+                    </el-form-item>
+                  </div>
+
+                  <!-- radio类型:单选有无(例如:wifi、室内空调等) -->
+                  <div v-if="item.labelButtonType === 'radio'" class="form-item-wrapper">
+                    <el-form-item :label="item.label" :class="{ 'vertical-layout': item.label === '吸烟' }">
+                      <el-radio-group v-model="formData[item.field_1]" @change="handleFormChange">
+                        <el-radio
+                          v-for="(option, optIndex) in item.labelButtonOption_1"
+                          :key="optIndex"
+                          :label="option.value"
+                          :value="option.value"
+                        >
+                          {{ option.name }}
+                        </el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                  </div>
+
+                  <!-- radios类型:单选(例如:位置类型、装修风格等) -->
+                  <div v-if="item.labelButtonType === 'radios'" class="form-item-wrapper">
+                    <el-form-item :label="item.label">
+                      <el-radio-group v-model="formData[item.field_1]" @change="handleFormChange">
+                        <el-radio
+                          v-for="(option, optIndex) in item.labelButtonOption_1"
+                          :key="optIndex"
+                          :label="option.value"
+                          :value="option.value"
+                        >
+                          {{ option.name }}
+                        </el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                  </div>
+
+                  <!-- checkbox类型:多选(多列显示) -->
+                  <div v-if="item.labelButtonType === 'checkbox'" class="form-item-wrapper">
+                    <el-form-item :label="item.label">
+                      <div class="checkbox-grid">
+                        <el-checkbox-group v-model="formData[item.field_1]" @change="handleCheckboxChange(item.field_1, $event)">
+                          <el-checkbox
+                            v-for="(option, optIndex) in item.labelButtonOption_1"
+                            :key="optIndex"
+                            :label="option.value"
+                            :value="option.value"
+                          >
+                            {{ option.name }}
+                          </el-checkbox>
+                        </el-checkbox-group>
+                      </div>
+                    </el-form-item>
+                  </div>
+
+                  <!-- 分隔线 -->
+                  <el-divider v-if="itemIndex !== tagGroup.list.length - 1 && item.label" />
+                </template>
+              </div>
             </div>
-          </el-form-item>
-
-          <!-- 等位区 -->
-          <el-form-item label="等位区">
-            <el-radio-group v-model="formData.waitingArea">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 吸烟 -->
-          <el-form-item label="吸烟">
-            <el-radio-group v-model="formData.smoking">
-              <el-radio value="可吸烟"> 可吸烟 </el-radio>
-              <el-radio value="不可吸烟"> 不可吸烟 </el-radio>
-              <el-radio value="专属吸烟区"> 专属吸烟区 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 门店服务 -->
-          <el-form-item label="门店服务">
-            <el-checkbox-group v-model="formData.storeServices">
-              <el-checkbox label="在线订座"> 在线订座 </el-checkbox>
-              <el-checkbox label="在线排队"> 在线排队 </el-checkbox>
-              <el-checkbox label="线下取号"> 线下取号 </el-checkbox>
-            </el-checkbox-group>
-          </el-form-item>
+          </template>
         </div>
 
-        <!-- 右侧:门店介绍-顾客想看 -->
+        <!-- 右侧:门店介绍 -->
         <div class="form-right">
-          <h3 class="section-title">门店介绍-顾客想看</h3>
-
-          <!-- 位置类型 -->
-          <el-form-item label="位置类型">
-            <el-radio-group v-model="formData.locationType">
-              <el-radio value="深巷小店"> 深巷小店 </el-radio>
-              <el-radio value="沿街"> 沿街 </el-radio>
-              <el-radio value="商场餐厅"> 商场餐厅 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 交通 -->
-          <el-form-item label="交通">
-            <el-checkbox-group v-model="formData.transportation">
-              <el-checkbox label="近地铁/地铁直达"> 近地铁/地铁直达 </el-checkbox>
-              <el-checkbox label="近机场"> 近机场 </el-checkbox>
-              <el-checkbox label="近商场"> 近商场 </el-checkbox>
-              <el-checkbox label="近公交"> 近公交 </el-checkbox>
-            </el-checkbox-group>
-          </el-form-item>
-
-          <!-- 景观特色 -->
-          <el-form-item label="景观特色">
-            <el-checkbox-group v-model="formData.scenicFeatures">
-              <el-checkbox label="江景"> 江景 </el-checkbox>
-              <el-checkbox label="湖景"> 湖景 </el-checkbox>
-              <el-checkbox label="海景"> 海景 </el-checkbox>
-              <el-checkbox label="山景"> 山景 </el-checkbox>
-              <el-checkbox label="高空城景"> 高空城景 </el-checkbox>
-            </el-checkbox-group>
-          </el-form-item>
-
-          <!-- 室内景观 -->
-          <el-form-item label="室内景观">
-            <el-radio-group v-model="formData.indoorScenery">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 露台 -->
-          <el-form-item label="露台">
-            <el-radio-group v-model="formData.terrace">
-              <el-radio :value="true"> 有 </el-radio>
-              <el-radio :value="false"> 无 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 装修风格 -->
-          <el-form-item label="装修风格">
-            <el-radio-group v-model="formData.decorationStyle">
-              <el-radio value="禅意餐厅"> 禅意餐厅 </el-radio>
-              <el-radio value="异国风情"> 异国风情 </el-radio>
-              <el-radio value="复古风格"> 复古风格 </el-radio>
-              <el-radio value="现代"> 现代 </el-radio>
-              <el-radio value="小清新风格"> 小清新风格 </el-radio>
-              <el-radio value="二次元风"> 二次元风 </el-radio>
-              <el-radio value="工业风"> 工业风 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 特色 -->
-          <el-form-item label="特色">
-            <div class="button-group">
-              <el-button
-                v-for="feature in featureOptions"
-                :key="feature.value"
-                :type="formData.features.includes(feature.value) ? 'primary' : 'default'"
-                @click="toggleFeature(feature.value)"
-              >
-                {{ feature.label }}
-              </el-button>
+          <!-- 动态渲染标签卡片 -->
+          <template v-for="(tagGroup, groupIndex) in rightTagGroups" :key="groupIndex">
+            <div class="tag-card">
+              <div class="card-header">
+                <span class="header-icon" />
+                <span class="header-title">{{ tagGroup.labelName }}</span>
+              </div>
+              <div class="card-content">
+                <template v-for="(item, itemIndex) in tagGroup.list" :key="itemIndex">
+                  <!-- radioDouble类型:组合按钮 -->
+                  <div v-if="item.labelButtonType === 'radioDouble'" class="form-item-wrapper">
+                    <el-form-item :label="item.label">
+                      <div class="radio-double-wrapper">
+                        <div class="first-option">
+                          <el-radio-group v-model="formData[item.field_1]" @change="handleFormChange">
+                            <el-radio
+                              v-for="(option, optIndex) in item.labelButtonOption_1"
+                              :key="optIndex"
+                              :label="option.value"
+                              :value="option.value"
+                            >
+                              {{ option.name }}
+                            </el-radio>
+                          </el-radio-group>
+                        </div>
+                        <div
+                          v-if="
+                            (item.field_1 && formData[item.field_1] == 1) ||
+                            (item.field_5 && formData[item.field_5] == 1) ||
+                            (item.field_7 && formData[item.field_7] == 1) ||
+                            (item.field_11 && formData[item.field_11] == 1)
+                          "
+                          class="second-option"
+                        >
+                          <div class="sub-label">
+                            {{ item.tips }}
+                          </div>
+                          <el-radio-group v-if="item.field_2" v-model="formData[item.field_2]" @change="handleFormChange">
+                            <el-radio
+                              v-for="(option, optIndex) in item.labelButtonOption_2"
+                              :key="optIndex"
+                              :label="option.value"
+                              :value="option.value"
+                            >
+                              {{ option.name }}
+                            </el-radio>
+                          </el-radio-group>
+                        </div>
+                      </div>
+                    </el-form-item>
+                  </div>
+
+                  <!-- radio类型:单选有无 -->
+                  <div v-if="item.labelButtonType === 'radio'" class="form-item-wrapper">
+                    <el-form-item :label="item.label" :class="{ 'vertical-layout': item.label === '吸烟' }">
+                      <el-radio-group v-model="formData[item.field_1]" @change="handleFormChange">
+                        <el-radio
+                          v-for="(option, optIndex) in item.labelButtonOption_1"
+                          :key="optIndex"
+                          :label="option.value"
+                          :value="option.value"
+                        >
+                          {{ option.name }}
+                        </el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                  </div>
+
+                  <!-- radios类型:单选 -->
+                  <div v-if="item.labelButtonType === 'radios'" class="form-item-wrapper">
+                    <el-form-item :label="item.label">
+                      <el-radio-group v-model="formData[item.field_1]" @change="handleFormChange">
+                        <el-radio
+                          v-for="(option, optIndex) in item.labelButtonOption_1"
+                          :key="optIndex"
+                          :label="option.value"
+                          :value="option.value"
+                        >
+                          {{ option.name }}
+                        </el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                  </div>
+
+                  <!-- checkbox类型:多选(多列显示) -->
+                  <div v-if="item.labelButtonType === 'checkbox'" class="form-item-wrapper">
+                    <el-form-item :label="item.label">
+                      <div class="checkbox-grid">
+                        <el-checkbox-group v-model="formData[item.field_1]" @change="handleCheckboxChange(item.field_1, $event)">
+                          <el-checkbox
+                            v-for="(option, optIndex) in item.labelButtonOption_1"
+                            :key="optIndex"
+                            :label="option.value"
+                            :value="option.value"
+                          >
+                            {{ option.name }}
+                          </el-checkbox>
+                        </el-checkbox-group>
+                      </div>
+                    </el-form-item>
+                  </div>
+
+                  <!-- 分隔线 -->
+                  <el-divider v-if="itemIndex !== tagGroup.list.length - 1 && item.label" />
+                </template>
+              </div>
             </div>
-          </el-form-item>
+          </template>
         </div>
       </div>
 
       <!-- 保存按钮 -->
       <div class="form-footer">
-        <el-button type="primary" size="large" :loading="loading" @click="handleSubmit"> 保存 </el-button>
+        <el-button type="primary" size="large" :loading="loading" @click="handleSave"> 保存 </el-button>
       </div>
     </el-form>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from "vue";
+import { ref, reactive, computed, onMounted } from "vue";
 import { ElMessage } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
-// TODO: 导入门店标签相关的 API 接口
-// import { getStoreLabel, saveStoreLabel } from "@/api/modules/storeDecoration";
+import { localGet } from "@/utils";
+import { getAllTagType, getAllTag, saveTag } from "@/api/modules/storeDecoration";
 
 const loading = ref(false);
 const formRef = ref<FormInstance>();
 
-// 支持场景选项
-const sceneOptions = [
-  { label: "多人聚餐", value: "多人聚餐" },
-  { label: "生日聚会", value: "生日聚会" },
-  { label: "商务宴请", value: "商务宴请" },
-  { label: "情侣约会", value: "情侣约会" },
-  { label: "带娃吃饭", value: "带娃吃饭" },
-  { label: "闺蜜小聚", value: "闺蜜小聚" }
-];
-
-// 特色选项
-const featureOptions = [
-  { label: "充电宝", value: "充电宝" },
-  { label: "充电线", value: "充电线" }
-];
-
-// 表单数据
-const formData = reactive({
-  // 卫生间
-  restroom: {
-    has: true,
-    types: ["商场卫生间"] as string[]
-  },
-  // 免费WIFI
-  freeWifi: true,
-  // 室内空调
-  airConditioning: true,
-  // 停车场
-  parking: {
-    has: true,
-    types: ["免费停车场"] as string[]
-  },
-  // 大桌
-  largeTable: {
-    has: true,
-    types: ["10人以上大桌"] as string[]
-  },
-  // 儿童服务
-  childrenService: true,
-  // 宠物政策
-  petPolicy: "可携带宠物",
-  // 包厢
-  privateRoom: {
-    has: true,
-    types: ["10人以上包厢"] as string[]
-  },
-  // 支持场景
-  supportedScenes: ["多人聚餐", "生日聚会", "商务宴请", "情侣约会", "带娃吃饭", "闺蜜小聚"] as string[],
-  // 等位区
-  waitingArea: true,
-  // 吸烟
-  smoking: "可吸烟",
-  // 门店服务
-  storeServices: ["在线订座"] as string[],
-  // 位置类型
-  locationType: "深巷小店",
-  // 交通
-  transportation: ["近地铁/地铁直达"] as string[],
-  // 景观特色
-  scenicFeatures: ["江景"] as string[],
-  // 室内景观
-  indoorScenery: true,
-  // 露台
-  terrace: true,
-  // 装修风格
-  decorationStyle: "禅意餐厅",
-  // 特色
-  features: ["充电宝", "充电线"] as string[]
+// 标签分组数据
+const tagGroups = ref<
+  Array<{
+    labelName: string;
+    list: Array<{
+      label: string;
+      labelButtonType: "radioDouble" | "radio" | "radios" | "checkbox";
+      field_1: string;
+      field_2?: string;
+      field_5?: string;
+      field_7?: string;
+      field_11?: string;
+      tips?: string;
+      labelButtonOption_1: Array<{ name: string; value: string | number }>;
+      labelButtonOption_2?: Array<{ name: string; value: string | number }>;
+    }>;
+  }>
+>([]);
+
+// 左侧标签组(基础信息)
+const leftTagGroups = computed(() => {
+  return tagGroups.value.filter(group => group.labelName === "基础设施");
 });
 
-// 表单校验规则(根据实际需求添加)
+// 右侧标签组(门店介绍)
+const rightTagGroups = computed(() => {
+  return tagGroups.value.filter(group => group.labelName === "门店介绍");
+});
+
+// 表单数据(动态字段)
+const formData = reactive<Record<string, any>>({});
+
+// 表单校验规则
 const rules = reactive<FormRules>({});
 
-// 切换支持场景
-const toggleScene = (scene: string) => {
-  const index = formData.supportedScenes.indexOf(scene);
-  if (index > -1) {
-    formData.supportedScenes.splice(index, 1);
-  } else {
-    formData.supportedScenes.push(scene);
-  }
+// 处理表单变化
+const handleFormChange = () => {
+  // 表单数据变化时的处理逻辑
 };
 
-// 切换特色
-const toggleFeature = (feature: string) => {
-  const index = formData.features.indexOf(feature);
-  if (index > -1) {
-    formData.features.splice(index, 1);
-  } else {
-    formData.features.push(feature);
-  }
+// 处理多选框变化(保持为数组)
+const handleCheckboxChange = (fieldName: string, value: any[]) => {
+  formData[fieldName] = value;
+  handleFormChange();
 };
 
-// 页面初始化
-onMounted(async () => {
-  // TODO: 调用获取门店标签接口 getStoreLabel API
-  // const res = await getStoreLabel();
-  // if (res.data) {
-  //   Object.assign(formData, res.data);
-  // }
-});
-
-// 保存
-const handleSubmit = async () => {
+// 保存数据
+const handleSave = async () => {
   if (!formRef.value) return;
 
   try {
@@ -352,90 +316,327 @@ const handleSubmit = async () => {
 
   loading.value = true;
   try {
-    // TODO: 调用保存门店标签接口 saveStoreLabel API
-    // const params = {
-    //   ...formData
-    // };
-    // await saveStoreLabel(params);
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      ElMessage.error("未找到店铺ID");
+      loading.value = false;
+      return;
+    }
 
-    ElMessage.success("保存成功");
-  } catch (error) {
-    ElMessage.error("保存失败,请重试");
+    // 调用保存门店标签接口 saveTag API
+    // 在保存前,将checkbox类型的数组字段转换为字符串
+    const params: any = {
+      ...formData,
+      storeId: Number(storeId)
+    };
+
+    // 遍历所有checkbox字段,将数组转换为逗号分隔的字符串
+    tagGroups.value.forEach(group => {
+      group.list.forEach(item => {
+        if (item.labelButtonType === "checkbox" && item.field_1) {
+          const fieldName = item.field_1;
+          if (Array.isArray(params[fieldName])) {
+            params[fieldName] = params[fieldName].join(",");
+          } else if (!params[fieldName]) {
+            params[fieldName] = "";
+          }
+        }
+      });
+    });
+
+    const res: any = await saveTag(params);
+    if (res && (res.code === 200 || res.code === "200")) {
+      ElMessage.success("保存成功");
+    } else {
+      ElMessage.error(res?.msg || "保存失败");
+    }
+  } catch (error: any) {
     console.error("保存失败:", error);
+    ElMessage.error(error?.msg || "保存失败,请重试");
   } finally {
     loading.value = false;
   }
 };
+
+// 初始化表单数据(从接口获取已保存的数据)
+const initFormData = async () => {
+  try {
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const storeId = userInfo.storeId;
+    if (!storeId) {
+      console.warn("未找到店铺ID");
+      return;
+    }
+
+    // 调用获取门店标签数据接口 getAllTag API
+    const res: any = await getAllTag({ storeId: Number(storeId) });
+    if (res && (res.code === 200 || res.code === "200") && res.data) {
+      // 将接口返回的数据合并到formData中
+      Object.assign(formData, res.data);
+      // 处理checkbox类型的数据(如果是字符串,需要转换为数组)
+      tagGroups.value.forEach(group => {
+        group.list.forEach(item => {
+          if (item.labelButtonType === "checkbox" && item.field_1) {
+            const fieldName = item.field_1;
+            const value = formData[fieldName];
+            if (value && typeof value === "string") {
+              formData[fieldName] = value.split(",").filter(Boolean);
+            } else if (!value) {
+              // 如果字段不存在,初始化为空数组
+              formData[fieldName] = [];
+            }
+          } else if (item.field_1 && formData[item.field_1] === undefined) {
+            // 其他类型字段如果不存在,初始化为空值
+            formData[item.field_1] = "";
+          }
+        });
+      });
+    }
+  } catch (error) {
+    console.error("获取门店标签数据失败:", error);
+  }
+};
+
+// 初始化标签选项(从接口获取标签列表)
+const initTagOptions = async () => {
+  try {
+    // 从userInfo中获取businessSection
+    const userInfo: any = localGet("geeker-user")?.userInfo || {};
+    const businessSection = userInfo.businessSection || localGet("businessSection") || "";
+
+    if (!businessSection) {
+      console.warn("未找到businessSection参数");
+      return;
+    }
+
+    // 调用获取标签列表接口 getAllTagType API
+    const res: any = await getAllTagType({ businessSection });
+    if (res && (res.code === 200 || res.code === "200") && res.data) {
+      const dataList = Array.isArray(res.data) ? res.data : [];
+
+      // 处理checkbox类型的已选状态
+      dataList.forEach((item: any) => {
+        if (item.labelButtonType === "checkbox") {
+          item.labelButtonOption_1.forEach((option: any) => {
+            if (formData[item.field_1] && formData[item.field_1].indexOf(option.value) !== -1) {
+              option.isChecked = true;
+            }
+          });
+        }
+      });
+
+      // 按labelName分组
+      const grouped = dataList.reduce((acc: any[], current: any) => {
+        const existing = acc.find(item => item.labelName === current.labelName);
+        if (existing) {
+          existing.list.push(current);
+        } else {
+          acc.push({
+            labelName: current.labelName,
+            list: [current]
+          });
+        }
+        return acc;
+      }, []);
+
+      tagGroups.value = grouped;
+    }
+  } catch (error) {
+    console.error("获取标签列表失败:", error);
+  }
+};
+
+// 页面初始化
+onMounted(async () => {
+  // 先初始化标签选项
+  await initTagOptions();
+  // 再初始化表单数据(需要先有标签选项,才能正确处理checkbox数据)
+  await initFormData();
+});
 </script>
 
 <style scoped lang="scss">
 .store-label-container {
-  min-height: 100%;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  height: 85vh;
   padding: 20px;
+  overflow: hidden;
   background-color: white;
   .store-label-form {
-    .form-content {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    min-height: 0;
+    overflow: hidden;
+
+    // 头部:固定标题
+    .form-header {
+      z-index: 10;
       display: flex;
+      flex-shrink: 0;
       gap: 40px;
+      padding-bottom: 20px;
+      background-color: white;
+      border-bottom: 1px solid #f0f0f0;
       .form-left,
       .form-right {
         flex: 1;
+        .section-title {
+          padding-bottom: 12px;
+          margin-bottom: 0;
+          font-size: 16px;
+          font-weight: bold;
+          color: #000000;
+          border-bottom: none;
+        }
       }
-      .section-title {
-        padding-bottom: 12px;
-        margin-bottom: 24px;
-        font-size: 16px;
-        font-weight: bold;
-        color: #000000;
-        border-bottom: 1px solid #f0f0f0;
+    }
+
+    // 中间:可滚动的内容区域
+    .form-content {
+      display: flex;
+      flex: 1;
+      gap: 40px;
+      min-height: 0;
+      padding-top: 20px;
+      padding-right: 10px;
+      margin-right: -10px;
+      overflow: hidden auto;
+
+      // 自定义滚动条样式
+      &::-webkit-scrollbar {
+        width: 8px;
       }
-      :deep(.el-form-item) {
-        margin-bottom: 24px;
-        .el-form-item__label {
-          font-weight: 500;
-          color: #606266;
+      &::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 4px;
+      }
+      &::-webkit-scrollbar-thumb {
+        background: #c1c1c1;
+        border-radius: 4px;
+        &:hover {
+          background: #a8a8a8;
         }
-        .sub-options {
-          padding-left: 20px;
-          margin-top: 12px;
-          .sub-label {
-            margin-bottom: 8px;
-            font-size: 14px;
-            color: #909399;
-          }
-          :deep(.el-checkbox-group) {
+      }
+      .form-left,
+      .form-right {
+        flex: 1;
+        .tag-card {
+          padding: 20px;
+          margin-bottom: 24px;
+          background: #ffffff;
+          border-radius: 8px;
+          box-shadow: 0 2px 12px 0 rgb(0 0 0 / 6%);
+          .card-header {
             display: flex;
-            flex-direction: column;
-            gap: 8px;
+            align-items: center;
+            margin-bottom: 20px;
+            font-size: 16px;
+            font-weight: bold;
+            color: #334154;
+            .header-icon {
+              display: inline-block;
+              width: 4px;
+              height: 18px;
+              margin-right: 12px;
+              background-color: #6c8ff8;
+              border-radius: 2px;
+            }
+            .header-title {
+              font-size: 16px;
+              font-weight: bold;
+              color: #334154;
+            }
           }
-        }
-        .button-group {
-          display: flex;
-          flex-wrap: wrap;
-          gap: 8px;
-          .el-button {
-            margin: 0;
+          .card-content {
+            padding: 20px 10px;
+            background: #ffffff;
+            border-radius: 8px;
+            box-shadow: 0 -2px 12px 0 rgb(86 125 244 / 5%);
+            .form-item-wrapper {
+              margin-bottom: 20px;
+              &:last-child {
+                margin-bottom: 0;
+              }
+            }
           }
         }
-        :deep(.el-radio-group) {
-          display: flex;
-          flex-direction: column;
-          gap: 12px;
-        }
-        :deep(.el-checkbox-group) {
-          display: flex;
-          flex-direction: column;
-          gap: 8px;
-        }
       }
     }
     .form-footer {
       display: flex;
+      flex-shrink: 0;
       justify-content: center;
-      padding-top: 24px;
-      margin-top: 40px;
+      padding: 24px 0;
+      margin-top: 20px;
+      background-color: white;
       border-top: 1px solid #f0f0f0;
     }
   }
 }
+
+// 表单样式
+:deep(.el-form-item) {
+  margin-bottom: 24px;
+  .el-form-item__label {
+    font-weight: 500;
+    color: #606266;
+  }
+  &.vertical-layout {
+    .el-form-item__content {
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+    }
+  }
+}
+.radio-double-wrapper {
+  .first-option {
+    margin-bottom: 12px;
+  }
+  .second-option {
+    padding: 20px;
+    margin-top: 12px;
+    background: #f8f9ff;
+    border-radius: 8px;
+    .sub-label {
+      margin-bottom: 12px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #666666;
+    }
+  }
+}
+:deep(.el-radio-group) {
+  display: flex;
+  flex-flow: row wrap;
+  gap: 12px;
+  width: 100%;
+  .el-radio {
+    margin-right: 0;
+    margin-bottom: 0;
+    white-space: nowrap;
+    .el-radio__label {
+      padding-left: 8px;
+    }
+  }
+}
+:deep(.el-checkbox-group) {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+.checkbox-grid {
+  :deep(.el-checkbox-group) {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 12px 20px;
+  }
+}
+:deep(.el-divider) {
+  margin: 20px 0;
+}
 </style>