Bladeren bron

Merge branch 'development' of http://8.152.195.41:3000/alien/group_web_merchant into development

zhuli 3 weken geleden
bovenliggende
commit
65ec87d308

+ 5 - 0
src/api/modules/homeEntry.ts

@@ -101,3 +101,8 @@ export const cashOut = params => {
 export const getCheckPayPasswordTwo = params => {
   return http.get(PORT_NONE + `/merchantUser/checkPayPassword`, params);
 };
+
+// 检查菜单访问权限(返回合同管理和食品经营许可证两个字段)
+export const checkMenuPermissions = params => {
+  return http.get(PORT_NONE + `/storePlatformLogin/getExpirationTime`, params);
+};

+ 4 - 1
src/api/modules/storeDecoration.ts

@@ -78,7 +78,10 @@ export const getOfficialAlbumList = (params: any) => {
 export const saveOfficialImg = (params: any) => {
   return httpApi.post(`/alienStore/img/saveOrUpdate`, params);
 };
-
+//相册内图片删除
+export const deleteOfficialImg = (params: any) => {
+  return httpApi.post(`alienStore/img/delete`, params);
+};
 //相册内图片查询
 export const getOfficialImgList = (businessId, storeId) => {
   return httpApi.get(`/alienStore/img/getByBusinessId?businessId=${businessId}&imgType=2&storeId=${storeId}`);

+ 37 - 2
src/layouts/LayoutTransverse/index.vue

@@ -36,8 +36,10 @@
 
 <script setup lang="ts" name="layoutTransverse">
 import { computed } from "vue";
+import { ElMessage } from "element-plus";
 import { useAuthStore } from "@/stores/modules/auth";
 import { useRoute, useRouter } from "vue-router";
+import { checkMenuClickPermission } from "@/utils/permission";
 import Main from "@/layouts/components/Main/index.vue";
 import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
 import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
@@ -53,9 +55,42 @@ const activeMenu = computed(() => {
   return (route.meta?.activeMenu ? route.meta.activeMenu : route.path) as string;
 });
 
-const handleClickMenu = (subItem: Menu.MenuOptions) => {
+const handleClickMenu = async (subItem: Menu.MenuOptions) => {
   if (subItem.meta.isLink) return window.open(subItem.meta.isLink, "_blank");
-  router.push(subItem.path);
+
+  if (!subItem.path) {
+    ElMessage.warning("菜单路径不存在");
+    return;
+  }
+
+  // 调用权限检查方法,判断是否可以点击(方法内部会显示相应的提示信息)
+  const { canClick } = await checkMenuClickPermission(subItem.path);
+
+  if (!canClick) {
+    return;
+  }
+
+  // 允许访问,执行路由跳转
+  try {
+    await router.push(subItem.path);
+  } catch (error: any) {
+    handleRouteError(error);
+  }
+};
+
+// 处理路由错误
+const handleRouteError = (error: any) => {
+  // 如果是导航重复的错误,忽略它
+  if (error?.name === "NavigationDuplicated") {
+    return;
+  }
+  // 如果是路由不存在的错误
+  if (error?.message?.includes("No match") || error?.message?.includes("not found")) {
+    ElMessage.warning("该页面不存在或尚未配置");
+  } else {
+    console.error("路由跳转失败:", error);
+    ElMessage.error("页面跳转失败,请稍后重试");
+  }
 };
 </script>
 

+ 38 - 2
src/layouts/components/Menu/SubMenu.vue

@@ -22,13 +22,49 @@
 
 <script setup lang="ts">
 import { useRouter } from "vue-router";
+import { ElMessage } from "element-plus";
+import { checkMenuClickPermission } from "@/utils/permission";
 
 defineProps<{ menuList: Menu.MenuOptions[] }>();
 
 const router = useRouter();
-const handleClickMenu = (subItem: Menu.MenuOptions) => {
+
+const handleClickMenu = async (subItem: Menu.MenuOptions) => {
   if (subItem.meta.isLink) return window.open(subItem.meta.isLink, "_blank");
-  router.push(subItem.path);
+
+  if (!subItem.path) {
+    ElMessage.warning("菜单路径不存在");
+    return;
+  }
+
+  // 调用权限检查方法,判断是否可以点击(方法内部会显示相应的提示信息)
+  const { canClick } = await checkMenuClickPermission(subItem.path);
+
+  if (!canClick) {
+    return;
+  }
+
+  // 允许访问,执行路由跳转
+  try {
+    await router.push(subItem.path);
+  } catch (error: any) {
+    handleRouteError(error);
+  }
+};
+
+// 处理路由错误
+const handleRouteError = (error: any) => {
+  // 如果是导航重复的错误,忽略它
+  if (error?.name === "NavigationDuplicated") {
+    return;
+  }
+  // 如果是路由不存在的错误
+  if (error?.message?.includes("No match") || error?.message?.includes("not found")) {
+    ElMessage.warning("该页面不存在或尚未配置");
+  } else {
+    console.error("路由跳转失败:", error);
+    ElMessage.error("页面跳转失败,请稍后重试");
+  }
 };
 </script>
 

+ 119 - 1
src/utils/permission.ts

@@ -1,6 +1,6 @@
 import { localGet, localSet } from "@/utils/index";
 import { ElMessage } from "element-plus";
-import { getUserByPhone, getDetail } from "@/api/modules/homeEntry";
+import { getUserByPhone, getDetail, checkMenuPermissions } from "@/api/modules/homeEntry";
 
 /**
  * @description 判断是否有操作权限
@@ -45,3 +45,121 @@ export async function usePermission(tip?: string) {
   }
   return type;
 }
+
+/**
+ * @description 检查菜单访问权限(新方法)
+ * @returns {Object} 返回合同管理和食品经营许可证的权限状态
+ */
+export async function checkMenuAccessPermission(): Promise<{
+  contractManagement: boolean;
+  foodBusinessLicense: boolean;
+}> {
+  try {
+    // 调用单个API,返回两个字段
+    const res: any = await checkMenuPermissions({
+      storeId: localGet("createdId")
+    });
+
+    if (res) {
+      const data = res || {};
+      // 解析合同管理权限
+      let contractPermission = false;
+      if (data.expirationTime !== undefined) {
+        const value = data.expirationTime;
+        contractPermission = value == "0";
+      }
+
+      // 解析食品经营许可证权限
+      let foodPermission = false;
+      if (data.foodLicenceExpirationTime !== undefined) {
+        const value = data.foodLicenceExpirationTime;
+        foodPermission = value == "0";
+      }
+
+      return {
+        contractManagement: contractPermission,
+        foodBusinessLicense: foodPermission
+      };
+    }
+
+    // 如果API调用失败或返回格式不正确,默认返回两个都为false(不做限制)
+    return {
+      contractManagement: false,
+      foodBusinessLicense: false
+    };
+  } catch (error) {
+    console.error("检查菜单权限失败:", error);
+    // 如果API调用失败,默认返回两个都为false(不做限制)
+    return {
+      contractManagement: false,
+      foodBusinessLicense: false
+    };
+  }
+}
+
+/**
+ * @description 检查菜单项是否可以点击
+ * @param {string} path 菜单路径
+ * @returns {Promise<Object>} 返回包含canClick、contractManagement、foodBusinessLicense的对象
+ */
+export async function checkMenuClickPermission(path: string): Promise<{
+  canClick: boolean;
+  contractManagement: boolean;
+  foodBusinessLicense: boolean;
+}> {
+  // 页面路径常量
+  const CONTRACT_MANAGEMENT_PATH = "/licenseManagement/contractManagement"; // 合同管理
+  const FOOD_BUSINESS_LICENSE_PATH = "/licenseManagement/foodBusinessLicense"; // 食品经营许可证
+
+  // 调用权限检查方法,获取两个权限状态
+  const permissions = await checkMenuAccessPermission();
+  const { contractManagement, foodBusinessLicense } = permissions;
+
+  // 如果两者都为false,不做限制,所有页面都可以点击
+  if (!contractManagement && !foodBusinessLicense) {
+    return {
+      canClick: true,
+      contractManagement: false,
+      foodBusinessLicense: false
+    };
+  }
+
+  // 如果至少有一个为true,需要检查权限
+  // 构建允许访问的路径列表
+  const allowedPaths: string[] = [];
+  if (contractManagement) {
+    allowedPaths.push(CONTRACT_MANAGEMENT_PATH);
+  }
+  if (foodBusinessLicense) {
+    allowedPaths.push(FOOD_BUSINESS_LICENSE_PATH);
+  }
+
+  // 检查当前路径是否在允许访问的列表中
+  const canClick = allowedPaths.includes(path);
+
+  // 如果不可点击,根据权限状态显示相应的提示信息
+  if (!canClick) {
+    let message = "";
+
+    // 如果两者都为true,显示两行提示
+    if (contractManagement && foodBusinessLicense) {
+      message = "合同已到期,请上传最新合同。\n经营许可证已到期,请上传最新许可证。";
+    } else if (contractManagement) {
+      // 只有合同管理为true
+      message = "合同已到期,请上传最新合同。";
+    } else if (foodBusinessLicense) {
+      // 只有食品经营许可证为true
+      message = "经营许可证已到期,请上传最新许可证。";
+    }
+
+    if (message) {
+      ElMessage.warning(message);
+    }
+  }
+
+  return {
+    canClick,
+    contractManagement,
+    foodBusinessLicense
+  };
+}

+ 70 - 36
src/views/login/index.vue

@@ -64,8 +64,8 @@
             <el-form-item prop="code" label="验证码" label-position="top">
               <div class="code-wrapper">
                 <el-input v-model="codeForm.code" placeholder="请输入验证码" maxlength="6" clearable class="code-input" />
-                <el-button :disabled="codeCountdown > 0" @click="sendLoginCode" class="code-button">
-                  {{ codeCountdown > 0 ? `${codeCountdown}秒后重试` : "获取验证码" }}
+                <el-button :disabled="loginCodeCountdown > 0" @click="sendLoginCode" class="code-button">
+                  {{ loginCodeCountdown > 0 ? `${loginCodeCountdown}秒后重试` : "获取验证码" }}
                 </el-button>
               </div>
             </el-form-item>
@@ -478,8 +478,8 @@
         <el-form-item prop="code" label="验证码" label-position="top">
           <div class="code-wrapper">
             <el-input v-model="forgetForm.code" placeholder="请输入验证码" maxlength="6" clearable class="code-input" />
-            <el-button :disabled="codeCountdown > 0" @click="sendForgotCode" class="code-button">
-              {{ codeCountdown > 0 ? `${codeCountdown}秒后重试` : "获取验证码" }}
+            <el-button :disabled="forgotCodeCountdown > 0" @click="sendForgotCode" class="code-button">
+              {{ forgotCodeCountdown > 0 ? `${forgotCodeCountdown}秒后重试` : "获取验证码" }}
             </el-button>
           </div>
         </el-form-item>
@@ -528,8 +528,8 @@
         <el-form-item prop="code" label="验证码" label-position="top">
           <div class="code-wrapper">
             <el-input v-model="registerForm.code" placeholder="请输入验证码" maxlength="6" clearable class="code-input" />
-            <el-button :disabled="codeCountdown > 0" @click="sendRegisterCode" class="code-button">
-              {{ codeCountdown > 0 ? `${codeCountdown}秒后重试` : "获取验证码" }}
+            <el-button :disabled="registerCodeCountdown > 0" @click="sendRegisterCode" class="code-button">
+              {{ registerCodeCountdown > 0 ? `${registerCodeCountdown}秒后重试` : "获取验证码" }}
             </el-button>
           </div>
         </el-form-item>
@@ -656,9 +656,17 @@ const codeForm = reactive({
 // 图片验证码
 const captchaImage = ref("");
 
-// 验证码倒计时
-const codeCountdown = ref(0);
-let countdownTimer: NodeJS.Timeout | null = null;
+// 登录验证码倒计时
+const loginCodeCountdown = ref(0);
+let loginCountdownTimer: NodeJS.Timeout | null = null;
+
+// 忘记密码验证码倒计时
+const forgotCodeCountdown = ref(0);
+let forgotCountdownTimer: NodeJS.Timeout | null = null;
+
+// 注册验证码倒计时
+const registerCodeCountdown = ref(0);
+let registerCountdownTimer: NodeJS.Timeout | null = null;
 
 // 表单验证规则
 const passwordRules = reactive({
@@ -734,14 +742,19 @@ const sendForgotCode = async () => {
   let phoneCode: any = await getPhoneCode({ phone: forgetForm.value.phone, appType: "2", businessType: "6" });
   if (phoneCode.code === 200) {
     ElMessage.success("验证码已发送");
+    // 清除之前的定时器
+    if (forgotCountdownTimer) {
+      clearInterval(forgotCountdownTimer);
+      forgotCountdownTimer = null;
+    }
     // 开始倒计时
-    codeCountdown.value = 60;
-    countdownTimer = setInterval(() => {
-      codeCountdown.value--;
-      if (codeCountdown.value <= 0) {
-        if (countdownTimer) {
-          clearInterval(countdownTimer);
-          countdownTimer = null;
+    forgotCodeCountdown.value = 60;
+    forgotCountdownTimer = setInterval(() => {
+      forgotCodeCountdown.value--;
+      if (forgotCodeCountdown.value <= 0) {
+        if (forgotCountdownTimer) {
+          clearInterval(forgotCountdownTimer);
+          forgotCountdownTimer = null;
         }
       }
     }, 1000);
@@ -762,14 +775,19 @@ const sendLoginCode = async () => {
   let phoneCode: any = await getPhoneCode({ phone: codeForm.phone, appType: "2", businessType: "0" });
   if (phoneCode.code === 200) {
     ElMessage.success("验证码已发送");
+    // 清除之前的定时器
+    if (loginCountdownTimer) {
+      clearInterval(loginCountdownTimer);
+      loginCountdownTimer = null;
+    }
     // 开始倒计时
-    codeCountdown.value = 60;
-    countdownTimer = setInterval(() => {
-      codeCountdown.value--;
-      if (codeCountdown.value <= 0) {
-        if (countdownTimer) {
-          clearInterval(countdownTimer);
-          countdownTimer = null;
+    loginCodeCountdown.value = 60;
+    loginCountdownTimer = setInterval(() => {
+      loginCodeCountdown.value--;
+      if (loginCodeCountdown.value <= 0) {
+        if (loginCountdownTimer) {
+          clearInterval(loginCountdownTimer);
+          loginCountdownTimer = null;
         }
       }
     }, 1000);
@@ -796,14 +814,19 @@ const sendRegisterCode = async () => {
   if (phoneCode.code === 200) {
     console.log(phoneCode, "phoneCode");
     ElMessage.success("验证码已发送");
+    // 清除之前的定时器
+    if (registerCountdownTimer) {
+      clearInterval(registerCountdownTimer);
+      registerCountdownTimer = null;
+    }
     // 开始倒计时
-    codeCountdown.value = 60;
-    countdownTimer = setInterval(() => {
-      codeCountdown.value--;
-      if (codeCountdown.value <= 0) {
-        if (countdownTimer) {
-          clearInterval(countdownTimer);
-          countdownTimer = null;
+    registerCodeCountdown.value = 60;
+    registerCountdownTimer = setInterval(() => {
+      registerCodeCountdown.value--;
+      if (registerCodeCountdown.value <= 0) {
+        if (registerCountdownTimer) {
+          clearInterval(registerCountdownTimer);
+          registerCountdownTimer = null;
         }
       }
     }, 1000);
@@ -1036,10 +1059,11 @@ const resetForms = () => {
     codeFormRef.value.resetFields();
   }
   captchaImage.value = "";
-  codeCountdown.value = 0;
-  if (countdownTimer) {
-    clearInterval(countdownTimer);
-    countdownTimer = null;
+  // 重置登录验证码倒计时
+  loginCodeCountdown.value = 0;
+  if (loginCountdownTimer) {
+    clearInterval(loginCountdownTimer);
+    loginCountdownTimer = null;
   }
 };
 
@@ -1076,8 +1100,18 @@ onMounted(() => {
 
 onBeforeUnmount(() => {
   document.onkeydown = null;
-  if (countdownTimer) {
-    clearInterval(countdownTimer);
+  // 清理所有定时器
+  if (loginCountdownTimer) {
+    clearInterval(loginCountdownTimer);
+    loginCountdownTimer = null;
+  }
+  if (forgotCountdownTimer) {
+    clearInterval(forgotCountdownTimer);
+    forgotCountdownTimer = null;
+  }
+  if (registerCountdownTimer) {
+    clearInterval(registerCountdownTimer);
+    registerCountdownTimer = null;
   }
 });
 </script>

+ 98 - 52
src/views/storeDecoration/basicStoreInformation/index.vue

@@ -132,16 +132,23 @@
               show-word-limit
             />
           </el-form-item>
-
           <!-- 经纬度查询 -->
-          <el-form-item label="经纬度查询" prop="queryAddress">
-            <el-input v-model="formData.queryAddress" placeholder="请输入地址进行查询" clearable @blur="handleLocationQuery">
-              <template #append>
-                <el-button :icon="Search" @click="handleLocationQuery"> 查询 </el-button>
-              </template>
-            </el-input>
+          <el-form-item label="经纬度查询" prop="address">
+            <el-select
+              v-model="formData.storePosition"
+              filterable
+              placeholder="请输入地址进行查询"
+              remote
+              reserve-keyword
+              :remote-method="getLonAndLat"
+              @change="selectAddress"
+            >
+              <el-option v-for="item in addressList" :key="item.id" :label="item.name" :value="item.location">
+                <span style="float: left">{{ item.name }}</span>
+                <span style="float: right; font-size: 13px; color: var(--el-text-color-secondary)">{{ item.district }}</span>
+              </el-option>
+            </el-select>
           </el-form-item>
-
           <!-- 经营板块 -->
           <el-form-item label="经营板块" prop="">
             <el-radio-group v-model="formData.businessSection" disabled>
@@ -177,7 +184,7 @@
 
           <!-- 食品经营许可证到期时间(只读) -->
           <el-form-item label="食品经营许可证到期时间">
-            <el-input v-model="formData.foodLicenseExpirationTime" disabled class="readonly-input">
+            <el-input v-model="formData.foodLicenceExpirationTime" disabled class="readonly-input">
               <template #prefix>
                 <el-icon>
                   <Lock />
@@ -212,6 +219,10 @@ import { getInputPrompt } from "@/api/modules/newLoginApi";
 import { useRoute } from "vue-router";
 import { localGet } from "@/utils";
 
+//地址集合
+const addressList = ref<any[]>([]);
+const addressMap = new Map();
+
 const route = useRoute();
 const formRef = ref<FormInstance>();
 const loading = ref(false);
@@ -235,12 +246,69 @@ const formData = reactive({
   businessSection: "",
   businessTypes: [] as string[],
   expirationTime: "",
-  foodLicenseExpirationTime: "",
+  foodLicenceExpirationTime: "",
+  storePosition: "",
   storePositionLongitude: "",
   storePositionLatitude: "",
   isChain: 0
 });
 
+// 经纬度查询
+const getLonAndLat = async (keyword: string) => {
+  if (keyword) {
+    console.log("地址查询", keyword);
+    let param = {
+      addressName: keyword
+    };
+    let res: any = await getInputPrompt(param as any);
+    if (res.code == "200") {
+      res.data.tips.forEach((item: any) => {
+        addressMap.set(item.location, item.name);
+      });
+      addressList.value = res?.data?.tips || [];
+      console.log("res", res);
+    } else {
+      ElMessage.error("新增失败!");
+    }
+  } else {
+    addressList.value = [];
+  }
+};
+const selectAddress = async (param: any) => {
+  // 安全检查:确保 address 存在且是字符串类型
+  if (!formData.storePosition || typeof formData.storePosition !== "string") {
+    ElMessage.warning("地址格式不正确,请重新选择");
+    return;
+  }
+
+  // 检查是否包含逗号(经纬度格式应该是 "经度,纬度")
+  if (!formData.storePosition.includes(",")) {
+    ElMessage.warning("地址格式不正确,缺少经纬度信息");
+    return;
+  }
+
+  // 安全地分割地址字符串
+  let locationList = formData.storePosition.split(",");
+
+  // 检查分割后的数组长度
+  // if (locationList.length < 2) {
+  //   ElMessage.warning("地址格式不正确,无法获取经纬度");
+  //   return;
+  // }
+
+  // 查找对应的地址名称
+  addressList.value.forEach((item: any) => {
+    if (item.location == formData.storePosition) {
+      formData.queryAddress = item.name;
+      // queryAddress.value = item.name;
+    }
+  });
+  formData.storePositionLongitude = locationList[0]?.trim();
+  formData.storePositionLatitude = locationList[1]?.trim();
+  // 设置经纬度,并去除可能的空格
+  // formData.storePosition = locationList[0]?.trim() +","+locationList[1]?.trim()
+};
+
 // 表单验证规则
 const rules = reactive<FormRules>({
   storeName: [{ required: true, message: "请输入店铺名称", trigger: "blur" }],
@@ -480,43 +548,19 @@ const handleBusinessSectionChange = async (sectionId: string) => {
   }
 };
 
-// 经纬度查询(返回是否成功)
-const queryLocation = async (address?: string): Promise<boolean> => {
-  const queryAddr = address || formData.queryAddress;
-  if (!queryAddr) {
-    return false;
-  }
-  try {
-    const { data }: any = await getInputPrompt({ addressName: queryAddr } as any);
-    if (data.tips && data.tips.length > 0) {
-      if (!data.tips[0].location) {
-        return false;
-      }
-      let latlng = data.tips[0].location.split(",");
-      formData.storePositionLongitude = latlng[0];
-      formData.storePositionLatitude = latlng[1];
-      return true;
-    }
-    return false;
-  } catch (error) {
-    console.error("经纬度查询失败:", error);
-    return false;
-  }
-};
-
 // 经纬度查询(用户手动触发)
-const handleLocationQuery = async () => {
-  if (!formData.queryAddress) {
-    ElMessage.warning("请输入地址进行查询");
-    return;
-  }
-  const success = await queryLocation();
-  if (success) {
-    ElMessage.success("查询成功");
-  } else {
-    ElMessage.warning("未找到相关地址信息,请检查地址是否正确");
-  }
-};
+// const handleLocationQuery = async () => {
+//   if (!formData.queryAddress) {
+//     ElMessage.warning("请输入地址进行查询");
+//     return;
+//   }
+//   const success = await queryLocation();
+//   if (success) {
+//     ElMessage.success("查询成功");
+//   } else {
+//     ElMessage.warning("未找到相关地址信息,请检查地址是否正确");
+//   }
+// };
 
 // 表单提交
 const handleSubmit = async () => {
@@ -532,10 +576,10 @@ const handleSubmit = async () => {
     try {
       // 保存前先调用经纬度查询接口
       if (formData.queryAddress) {
-        const locationQuerySuccess = await queryLocation();
-        if (!locationQuerySuccess) {
-          ElMessage.warning("经纬度查询失败,请检查地址是否正确,将继续保存其他信息");
-        }
+        // const locationQuerySuccess = await queryLocation();
+        // if (!locationQuerySuccess) {
+        //   ElMessage.warning("经纬度查询失败,请检查地址是否正确,将继续保存其他信息");
+        // }
       }
 
       // 映射门店面积为数字
@@ -555,6 +599,7 @@ const handleSubmit = async () => {
         administrativeRegionProvinceAdcode: formData.administrativeRegionProvinceAdcode ?? "",
         administrativeRegionCityAdcode: formData.administrativeRegionCityAdcode ?? "",
         administrativeRegionDistrictAdcode: formData.administrativeRegionDistrictAdcode ?? "",
+        storePosition: formData.storePosition,
         storePositionLongitude: formData.storePositionLongitude,
         storePositionLatitude: formData.storePositionLatitude,
         businessStatus: formData.businessStatus
@@ -611,12 +656,13 @@ const getStoreDetailData = async () => {
       formData.storeAddress = storeData.storeAddress ?? "";
       formData.storeBlurb = storeData.storeBlurb ?? "";
       formData.expirationTime = storeData.expirationTime ?? "";
-      formData.foodLicenseExpirationTime = storeData.foodLicenseExpirationTime ?? "";
+      formData.foodLicenceExpirationTime = storeData.foodLicenceExpirationTime ?? "";
+      formData.storePosition = storeData.storePosition ?? "";
       formData.storePositionLongitude = storeData.storePositionLongitude ?? "";
       formData.storePositionLatitude = storeData.storePositionLatitude ?? "";
       formData.isChain = storeData.isChain ?? 0;
       formData.queryAddress = storeData.queryAddress;
-
+      await getLonAndLat(storeData.queryAddress);
       // 设置地区
       const provinceCode = storeData.administrativeRegionProvinceAdcode ?? "";
       const cityCode = storeData.administrativeRegionCityAdcode ?? "";

+ 1 - 1
src/views/storeDecoration/menuManagement/index.vue

@@ -97,7 +97,7 @@
             v-model:image-url="formData.imgUrl"
             :width="'200px'"
             :height="'200px'"
-            :file-size="10"
+            :file-size="9999"
             :api="uploadImg"
             :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
             :border-radius="'8px'"

+ 54 - 32
src/views/storeDecoration/officialPhotoAlbum/index.vue

@@ -28,7 +28,7 @@
           <UploadImgs
             v-model:file-list="currentAlbum.images"
             :limit="60"
-            :file-size="10"
+            :file-size="9999"
             :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
             :width="'100%'"
             :height="'200px'"
@@ -112,7 +112,8 @@ import {
   createOrUpdateOfficialAlbum,
   deleteOfficialAlbum,
   saveOfficialImg,
-  getOfficialImgList
+  getOfficialImgList,
+  deleteOfficialImg
 } from "@/api/modules/storeDecoration";
 import { uploadImg } from "@/api/modules/newLoginApi";
 
@@ -141,6 +142,11 @@ const previewVisible = ref(false);
 const previewImageUrl = ref("");
 const editId = ref<string | number | null>(null);
 
+// 正在保存的图片URL集合,防止重复保存
+const savingImages = ref<Set<string>>(new Set());
+// 已处理过的图片URL集合,防止重复处理
+const processedImages = ref<Set<string>>(new Set());
+
 // 相册列表
 const albumList = ref<Album[]>([]);
 
@@ -243,6 +249,11 @@ const customUploadApi = async (formData: FormData): Promise<any> => {
 
 // 保存图片到服务器
 const saveImageToServer = async (imgUrl: string) => {
+  // 如果正在保存,直接返回,防止重复保存
+  if (savingImages.value.has(imgUrl)) {
+    return;
+  }
+
   try {
     const userInfo: any = localGet("geeker-user")?.userInfo || {};
     const storeId = userInfo.storeId;
@@ -257,6 +268,9 @@ const saveImageToServer = async (imgUrl: string) => {
       return; // 如果图片已经有ID或找不到,不重复保存
     }
 
+    // 标记为正在保存
+    savingImages.value.add(imgUrl);
+
     const imgSort = currentImages.filter((img: AlbumImage) => img.imgId).length + 1; // 排序为已保存图片数量+1
 
     const params = [
@@ -275,9 +289,16 @@ const saveImageToServer = async (imgUrl: string) => {
       targetImage.imgId = res.data[0].id;
       targetImage.businessId = res.data[0].businessId;
       targetImage.imgSort = res.data[0].imgSort;
+      // 保存成功后,从已处理集合中移除,允许后续更新
+      processedImages.value.delete(imgUrl);
     }
   } catch (error) {
     console.error("保存图片信息失败:", error);
+    // 保存失败时,也从已处理集合中移除,允许重试
+    processedImages.value.delete(imgUrl);
+  } finally {
+    // 无论成功或失败,都移除保存标记
+    savingImages.value.delete(imgUrl);
   }
 };
 
@@ -443,27 +464,10 @@ const deleteImage = async (index: number) => {
   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;
-      }
-
-      // 通过保存接口删除(设置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 // 删除标记
-        }
-      ];
+      // 调用删除接口,参数是图片ID数组
+      const params = [image.imgId];
 
-      const res: any = await saveOfficialImg(params);
+      const res: any = await deleteOfficialImg(params);
       if (res && (res.code === 200 || res.code === "200")) {
         currentAlbum.value.images.splice(actualIndex, 1);
         updatePagination();
@@ -513,19 +517,33 @@ const handleCurrentChange = (page: number) => {
 
 // 监听图片列表变化,更新分页并保存新上传的图片
 watch(
-  () => currentAlbum.value?.images?.length || 0,
-  async (newLength, oldLength) => {
+  () => currentAlbum.value?.images || [],
+  async (newImages, oldImages) => {
     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);
-        }
+
+    // 如果没有相册ID,不处理
+    if (!currentAlbum.value.id) {
+      return;
+    }
+
+    // 找出真正新增的图片(通过对比URL)
+    const oldUrls = new Set((oldImages || []).map((img: AlbumImage) => img.url).filter((url): url is string => Boolean(url)));
+    const newUrls = new Set((newImages || []).map((img: AlbumImage) => img.url).filter((url): url is string => Boolean(url)));
+
+    // 找出新增的URL(在新列表中存在,但在旧列表中不存在)
+    const addedUrls = Array.from(newUrls).filter(url => !oldUrls.has(url));
+
+    // 处理新增的图片:没有imgId、有url、且未处理过、且不在保存中的图片
+    for (const url of addedUrls) {
+      const image = newImages.find((img: AlbumImage) => img.url === url);
+      if (image && !image.imgId && url && !savingImages.value.has(url) && !processedImages.value.has(url)) {
+        // 标记为已处理,防止重复处理
+        processedImages.value.add(url);
+        await saveImageToServer(url);
       }
     }
-  }
+  },
+  { deep: true }
 );
 
 // 监听相册切换,加载图片列表
@@ -614,6 +632,8 @@ const loadAlbumImages = async (albumId: string) => {
       // 更新当前相册的图片列表
       const current = albumList.value.find(album => String(album.id) === albumId);
       if (current) {
+        // 清空已处理图片集合,因为加载的是服务器数据,不需要再保存
+        processedImages.value.clear();
         current.images = images;
         // 更新分页信息,确保视图刷新
         updatePagination();
@@ -622,6 +642,7 @@ const loadAlbumImages = async (albumId: string) => {
       // 如果没有数据,也要清空当前相册的图片列表
       const current = albumList.value.find(album => String(album.id) === albumId);
       if (current) {
+        processedImages.value.clear();
         current.images = [];
         updatePagination();
       }
@@ -631,6 +652,7 @@ const loadAlbumImages = async (albumId: string) => {
     // 出错时也要更新分页信息
     const current = albumList.value.find(album => String(album.id) === albumId);
     if (current) {
+      processedImages.value.clear();
       current.images = [];
       updatePagination();
     }

+ 6 - 46
src/views/storeDecoration/storeEntranceMap/index.vue

@@ -21,7 +21,7 @@
               v-model:image-url="formData.entranceImage"
               :width="'400px'"
               :height="'400px'"
-              :file-size="10"
+              :file-size="9999"
               :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
               :border-radius="'8px'"
               :api="customUploadApi"
@@ -86,57 +86,17 @@ const formData = reactive({
   entranceImage: "" // 入口图URL
 });
 
-// 图片尺寸和比例校验函数
+// 图片校验函数(已移除尺寸限制)
 const validateImageDimensions = (file: File): Promise<boolean> => {
   return new Promise((resolve, reject) => {
-    const reader = new FileReader();
-    reader.onload = (e: ProgressEvent<FileReader>) => {
-      const img = new Image();
-      img.onload = () => {
-        const width = img.width;
-        const height = img.height;
-        const aspectRatio = width / height;
-
-        // 检查尺寸:900*900像素以内
-        if (width > 900 || height > 900) {
-          ElMessage.warning("图片尺寸不能超过900*900像素");
-          reject(new Error("图片尺寸超过限制"));
-          return;
-        }
-
-        // 检查比例:1:1(允许一定误差,比如0.95-1.05之间)
-        if (aspectRatio < 0.95 || aspectRatio > 1.05) {
-          ElMessage.warning("建议上传1:1尺寸的图片,避免图片出现显示不全问题");
-          reject(new Error("图片比例不符合要求"));
-          return;
-        }
-
-        resolve(true);
-      };
-      img.onerror = () => {
-        reject(new Error("图片加载失败"));
-      };
-      if (e.target?.result) {
-        img.src = e.target.result as string;
-      }
-    };
-    reader.onerror = () => {
-      reject(new Error("文件读取失败"));
-    };
-    reader.readAsDataURL(file);
+    // 直接通过,不进行任何校验
+    resolve(true);
   });
 };
 
-// 自定义上传API,包含尺寸和比例校验
+// 自定义上传API(已移除尺寸校验)
 const customUploadApi = async (formData: FormData): Promise<any> => {
-  const file = formData.get("file") as File;
-
-  // 先进行尺寸和比例校验
-  try {
-    await validateImageDimensions(file);
-  } catch (error) {
-    throw error;
-  }
+  // 已移除尺寸校验,直接上传
 
   // 校验通过后调用实际上传接口
   const response: any = await uploadImg(formData);

+ 10 - 84
src/views/storeDecoration/storeHeadMap/index.vue

@@ -88,7 +88,7 @@
               v-model:image-url="formData.singleImage"
               :width="'400px'"
               :height="'400px'"
-              :file-size="10"
+              :file-size="9999"
               :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
               :border-radius="'8px'"
               :api="singleImageUploadApi"
@@ -107,7 +107,7 @@
               <UploadImgs
                 v-model:file-list="formData.multipleImages"
                 :limit="6"
-                :file-size="20"
+                :file-size="9999"
                 :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
                 :width="'200px'"
                 :height="'112px'"
@@ -169,92 +169,25 @@ const displayImages = computed(() => {
   return formData.multipleImages.slice(0, 3).map(item => item.url || "");
 });
 
-// 单图模式:图片尺寸和比例校验函数(1:1,900*900
+// 单图模式:图片校验函数(已移除尺寸限制
 const validateSingleImageDimensions = (file: File): Promise<boolean> => {
   return new Promise((resolve, reject) => {
-    const reader = new FileReader();
-    reader.onload = (e: ProgressEvent<FileReader>) => {
-      const img = new Image();
-      img.onload = () => {
-        const width = img.width;
-        const height = img.height;
-        const aspectRatio = width / height;
-
-        // 检查尺寸:900*900像素以内
-        if (width > 900 || height > 900) {
-          ElMessage.warning("图片尺寸不能超过900*900像素");
-          reject(new Error("图片尺寸超过限制"));
-          return;
-        }
-
-        // 检查比例:1:1(允许一定误差,比如0.95-1.05之间)
-        if (aspectRatio < 0.95 || aspectRatio > 1.05) {
-          ElMessage.warning("建议上传1:1尺寸的图片,避免图片出现显示不全问题");
-          reject(new Error("图片比例不符合要求"));
-          return;
-        }
-
-        resolve(true);
-      };
-      img.onerror = () => {
-        reject(new Error("图片加载失败"));
-      };
-      if (e.target?.result) {
-        img.src = e.target.result as string;
-      }
-    };
-    reader.onerror = () => {
-      reject(new Error("文件读取失败"));
-    };
-    reader.readAsDataURL(file);
+    // 直接通过,不进行任何校验
+    resolve(true);
   });
 };
 
-// 多图模式:图片尺寸和比例校验函数(16:9,20MB
+// 多图模式:图片校验函数(已移除比例限制)
 const validateMultipleImageDimensions = (file: File): Promise<boolean> => {
   return new Promise((resolve, reject) => {
-    const reader = new FileReader();
-    reader.onload = (e: ProgressEvent<FileReader>) => {
-      const img = new Image();
-      img.onload = () => {
-        const width = img.width;
-        const height = img.height;
-        const aspectRatio = width / height;
-        const targetRatio = 16 / 9; // 16:9比例
-
-        // 检查比例:16:9(允许一定误差,比如±0.1)
-        if (Math.abs(aspectRatio - targetRatio) > 0.1) {
-          ElMessage.warning("建议上传16:9尺寸的图片,避免图片出现显示不全问题");
-          reject(new Error("图片比例不符合要求"));
-          return;
-        }
-
-        resolve(true);
-      };
-      img.onerror = () => {
-        reject(new Error("图片加载失败"));
-      };
-      if (e.target?.result) {
-        img.src = e.target.result as string;
-      }
-    };
-    reader.onerror = () => {
-      reject(new Error("文件读取失败"));
-    };
-    reader.readAsDataURL(file);
+    // 直接通过,不进行任何校验
+    resolve(true);
   });
 };
 
 // 单图模式:自定义上传API
 const singleImageUploadApi = async (formData: FormData): Promise<any> => {
-  const file = formData.get("file") as File;
-
-  // 先进行尺寸和比例校验
-  try {
-    await validateSingleImageDimensions(file);
-  } catch (error) {
-    throw error;
-  }
+  // 已移除尺寸校验,直接上传
 
   // 校验通过后调用实际上传接口
   const response: any = await uploadImg(formData);
@@ -271,14 +204,7 @@ const singleImageUploadApi = async (formData: FormData): Promise<any> => {
 
 // 多图模式:自定义上传API
 const multipleImageUploadApi = async (formData: FormData): Promise<any> => {
-  const file = formData.get("file") as File;
-
-  // 先进行比例校验
-  try {
-    await validateMultipleImageDimensions(file);
-  } catch (error) {
-    throw error;
-  }
+  // 已移除比例校验,直接上传
 
   // 校验通过后调用实际上传接口
   const response: any = await uploadImg(formData);