Просмотр исходного кода

Merge branch 'development' into uat

LuTong 1 месяц назад
Родитель
Сommit
cbd14cc4b8

+ 1 - 1
.env.development

@@ -27,7 +27,7 @@ VITE_API_URL_PLATFORM = /api/alienStorePlatform
 VITE_PROXY = [["/api","http://120.26.186.130:8000"]] # 邹建宇
 
 # WebSocket 基础地址(分享等能力,与商家端一致)
-VITE_WS_BASE = ws://120.26.186.130:8000/alienStore/socket/
+VITE_WS_BASE = http://120.26.186.130:8000/alienStore/socket/
 
 
 # 接口加密配置

+ 2 - 1
src/api/modules/licenseManagement.ts

@@ -2,6 +2,7 @@ import { ResPage, StoreUser } from "@/api/interface/index";
 import { PORT_NONE } from "@/api/config/servicePort";
 import http from "@/api";
 import http_store from "@/api/indexStore";
+import httpApi from "@/api/indexApi";
 
 // 获取营业执照
 export const getBusinessLicense = params => {
@@ -89,5 +90,5 @@ export const uploadContractImage = (formData: FormData, onProgress?: (progress:
 
 // OCR 二次校验接口
 export const ocrRequestUrl = params => {
-  return http_store.post(PORT_NONE + `/ali/ocrRequestUrl`, params);
+  return httpApi.post(PORT_NONE + `/alienStore/ali/ocrRequestUrl`, params);
 };

+ 49 - 0
src/api/modules/newLoginApi.ts

@@ -340,6 +340,55 @@ export const addTransferCount = (params: any) => {
 export const deleteDynamicsById = (params: any) => {
   return httpLogin.get(`/alienStore/userDynamics/deleteDynamicsById`, params);
 };
+
+//入驻   根据店铺用户ID查询支付配置(设置收款账号)
+export const getPaymentStoreUserId = (params: any) => {
+  return httpLogin.get(`/alienStore/store/payment/config/getByStoreUserId`, params);
+};
+
+/**
+ * 以 multipart/form-data 方式保存微信支付配置(公钥、私钥以文件上传,Web 端传 File)
+ * @param formFields - 除证书外的表单字段(storeUserId、apiV3Key、wechatAppId 等)
+ * @param privateKeyFile - 私钥证书文件(input type="file" 的 file)
+ * @param publicKeyFile - 公钥证书文件
+ */
+export const saveWechatWithFiles = (
+  formFields: Record<string, string>,
+  privateKeyFile?: File | null,
+  publicKeyFile?: File | null
+) => {
+  const fd = new FormData();
+  Object.entries(formFields).forEach(([key, value]) => {
+    if (value != null && value !== "") fd.append(key, String(value));
+  });
+  if (privateKeyFile) fd.append("wechatPrivateKeyFile", privateKeyFile);
+  if (publicKeyFile) fd.append("wechatPayPublicKeyFile", publicKeyFile);
+  return httpLogin.post(`/alienStore/store/payment/config/saveWechatByStoreUserId`, fd);
+};
+
+/**
+ * 以 multipart/form-data 方式保存支付宝支付配置(应用公钥、支付宝公钥、根证书以文件上传,Web 端传 File)
+ * @param formFields - 除证书外的表单字段(storeUserId、appId、appSecretCert 等)
+ * @param appPublicCertFile - 应用公钥证书文件
+ * @param alipayPublicCertFile - 支付宝公钥证书文件
+ * @param alipayRootCertFile - 支付宝根证书文件
+ */
+export const saveAlipayWithFiles = (
+  formFields: Record<string, string>,
+  appPublicCertFile?: File | null,
+  alipayPublicCertFile?: File | null,
+  alipayRootCertFile?: File | null
+) => {
+  const fd = new FormData();
+  Object.entries(formFields).forEach(([key, value]) => {
+    if (value != null && value !== "") fd.append(key, String(value));
+  });
+  if (appPublicCertFile) fd.append("appPublicCert", appPublicCertFile);
+  if (alipayPublicCertFile) fd.append("alipayPublicCert", alipayPublicCertFile);
+  if (alipayRootCertFile) fd.append("alipayRootCert", alipayRootCertFile);
+  return httpLogin.post(`/alienStore/store/payment/config/saveAlipayByStoreUserId`, fd);
+};
+
 // 根据手机号获取用户ID
 export const getUserByPhone = (params: {
   phone: string; // 手机号

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

@@ -105,7 +105,16 @@ export const saveStoreHeadImg = (params: any) => {
 
 //批量新增或编辑营业时间
 export const addOrEditBusinessTime = (params: any) => {
-  return httpApi.post(`/alienStorePlatform//storePlatformBusinessInfo/saveOrUpdateList`, params);
+  return httpApi.post(`/alienStore/businessInfo/saveOrUpdateList`, params);
+};
+
+//特殊时间
+export const getHolidayList = (params: any) => {
+  return httpApi.get(`alienStore/coupon/getHolidayList`, params);
+};
+
+export const getStoreInfoBusinessHours = (params: any) => {
+  return httpApi.get(`alienStore/store/info/getStoreInfoBusinessHours`, params);
 };
 
 //获取营业时间列表

+ 25 - 15
src/components/Upload/Imgs.vue

@@ -194,26 +194,34 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
   formData.append("file", options.file);
   try {
     const api = props.api ?? uploadImg;
-    const { data } = await api(formData);
-    // 无论是否显示成功提示,都先把当前文件的 url 设为服务器地址,否则父组件校验会认为仍是 blob 而报「请上传」
-    const fileUrl =
-      typeof data === "string"
-        ? data
-        : Array.isArray(data) && data.length > 0
-          ? data[0]
-          : (data?.fileUrl ?? (data && (Object.values(data)[0] as string)) ?? "");
+    const response = await api(formData);
+    // 从 response.fileUrl 取值
+    const fileUrl = response?.fileUrl || "";
+
     if (fileUrl) {
-      (options.file as UploadFile).url = fileUrl;
-      (options.file as UploadFile).status = "success";
+      // 更新 options.file(Element Plus 传入的文件对象)
+      (options.file as unknown as UploadFile).url = fileUrl;
+      (options.file as unknown as UploadFile).status = "success";
+      (options.file as unknown as UploadFile).response = response;
+
+      // 同步更新 _fileList 中对应的文件
+      const fileIndex = _fileList.value.findIndex(item => item.uid === fileUid);
+      if (fileIndex !== -1) {
+        _fileList.value[fileIndex].url = fileUrl;
+        _fileList.value[fileIndex].status = "success";
+        (_fileList.value[fileIndex] as any).response = response;
+      }
     }
-    options.onSuccess(data);
+
+    options.onSuccess(response);
+    // 传递更新后的 _fileList 给父组件
     emit("update:fileList", _fileList.value);
     if (props.onSuccess) {
       try {
-        const result = props.onSuccess(data?.fileUrl ? data.fileUrl : (data?.[0] ?? fileUrl));
+        const result = props.onSuccess(fileUrl);
         // 如果回调返回 Promise,等待它完成(但不影响上传成功状态)
-        if (result && typeof result.then === "function") {
-          result.catch((callbackError: any) => {
+        if (result !== undefined && result !== null && typeof result === "object" && typeof (result as any).then === "function") {
+          (result as Promise<any>).catch((callbackError: any) => {
             // 回调失败不影响上传成功状态,只记录错误
             console.error("onSuccess callback error:", callbackError);
           });
@@ -263,9 +271,11 @@ const uploadSuccess = (response: { fileUrl: string } | string | string[] | undef
   // 当所有文件上传完成时,显示成功提示(只显示一次)
   if (uploadingFiles.size === 0 && !hasShownSuccessNotification) {
     hasShownSuccessNotification = true;
+    // 判断是否为视频文件,显示相应的提示语
+    const isVideo = isVideoFile(uploadFile);
     ElNotification({
       title: "温馨提示",
-      message: "图片上传成功!",
+      message: isVideo ? "视频上传成功!" : "图片上传成功!",
       type: "success"
     });
   }

+ 13 - 0
src/layouts/components/Tabs/index.vue

@@ -22,14 +22,19 @@ import { ref, computed, watch, onMounted } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { useGlobalStore } from "@/stores/modules/global";
 import { useTabsStore } from "@/stores/modules/tabs";
+import { useKeepAliveStore } from "@/stores/modules/keepAlive";
 import { useAuthStore } from "@/stores/modules/auth";
 import { TabsPaneContext, TabPaneName } from "element-plus";
 import MoreButton from "./components/MoreButton.vue";
 import { HOME_URL } from "@/config";
+import { localGet } from "@/utils";
+
+const TABS_OWNER_KEY = "geeker-tabs-owner";
 
 const route = useRoute();
 const router = useRouter();
 const tabStore = useTabsStore();
+const keepAliveStore = useKeepAliveStore();
 const authStore = useAuthStore();
 const globalStore = useGlobalStore();
 
@@ -38,6 +43,14 @@ const tabsMenuList = computed(() => tabStore.tabsMenuList);
 const tabsIcon = computed(() => globalStore.tabsIcon);
 
 onMounted(() => {
+  // 若当前用户与持久化标签所属用户不一致,清空标签,避免展示其它系统的标签
+  const currentUserId = String(localGet("geeker-user")?.userInfo?.id ?? "");
+  const storedOwner = window.localStorage.getItem(TABS_OWNER_KEY) ?? "";
+  if (currentUserId && currentUserId !== storedOwner) {
+    tabStore.setTabs([]);
+    keepAliveStore.setKeepAliveName([]);
+    window.localStorage.setItem(TABS_OWNER_KEY, currentUserId);
+  }
   tabsDrop();
   initTabs();
 });

+ 2 - 1
src/utils/permission.ts

@@ -2,7 +2,8 @@ import { localGet, localSet } from "@/utils/index";
 import { ElMessage, ElMessageBox } from "element-plus";
 import router from "@/routers";
 import { HOME_URL, STORE_HEAD_IMAGE_PATH } from "@/config";
-import { getUserByPhone, getDetail, checkMenuPermissions } from "@/api/modules/homeEntry";
+import { getDetail, checkMenuPermissions } from "@/api/modules/homeEntry";
+import { getUserByPhone } from "@/api/modules/newLoginApi";
 
 /**
  * @description 判断是否有操作权限

+ 2 - 2
src/views/businessData/overview.vue

@@ -39,9 +39,9 @@
         <el-tab-pane label="优惠券" name="coupon">
           <StatCardList :items="couponStats" />
         </el-tab-pane>
-        <el-tab-pane label="代金券" name="voucher">
+        <!-- <el-tab-pane label="代金券" name="voucher">
           <StatCardList :items="voucherStats" />
-        </el-tab-pane>
+        </el-tab-pane> -->
         <el-tab-pane label="服务质量" name="service">
           <StatCardList :items="serviceStats" />
         </el-tab-pane>

+ 7 - 2
src/views/dynamicManagement/index.vue

@@ -156,9 +156,14 @@
                 </div>
               </div>
             </div>
-
+            <div style=" padding-bottom: 10px;color: #ffffff">
+              {{ currentDetail.title }}
+            </div>
             <div class="detail-description">
-              <p :class="{ 'text-ellipsis': !isDescriptionExpanded }">
+              <p
+                v-if="isDescriptionExpanded || (currentDetail.context && currentDetail.context.length >= 20)"
+                :class="{ 'text-ellipsis': !isDescriptionExpanded }"
+              >
                 {{ currentDetail.context }}
               </p>
               <span v-if="currentDetail.context" class="expand-btn" @click="toggleDescription">

+ 11 - 5
src/views/dynamicManagement/myDynamic.vue

@@ -222,11 +222,17 @@
               </div>
             </div>
 
+            <div style="padding-bottom: 10px; color: #ffffff">
+              {{ currentDetail.title }}
+            </div>
             <div class="detail-description">
-              <p :class="{ 'text-ellipsis': !isDescriptionExpanded }">
-                {{ currentDetail.description }}
+              <p
+                v-if="isDescriptionExpanded || (currentDetail.context && currentDetail.context.length >= 20)"
+                :class="{ 'text-ellipsis': !isDescriptionExpanded }"
+              >
+                {{ currentDetail.context }}
               </p>
-              <span v-if="currentDetail.description" class="expand-btn" @click="toggleDescription">
+              <span v-if="currentDetail.context" class="expand-btn" @click="toggleDescription">
                 {{ isDescriptionExpanded ? "收起" : "展开" }}
               </span>
             </div>
@@ -1777,8 +1783,8 @@ const handleRelationTabClick = (tab: any) => {
 const mapRecordToRelationUser = (item: any, defaultStatus: "following" | "mutual" | "none" = "none"): RelationUser => ({
   id: item.id || item.userId,
   name: item.userName || item.nickname || item.name || "用户",
-  avatar: item.userImage || item.avatar || item.headImg || "",
-  description: item.description || item.bio || item.signature || "",
+  avatar: item.image || item.userImage || item.avatar || item.headImg || "",
+  description: item.blurb || item.accountBlurb || item.description || item.bio || item.signature || "",
   relationStatus: item.isFollowThis === 1 ? ("mutual" as const) : defaultStatus,
   phoneId: item.phoneId || item.fansId || ""
 });

+ 22 - 1
src/views/dynamicManagement/publishDynamic.vue

@@ -73,11 +73,13 @@
           <el-select
             v-model="formData.storePosition"
             filterable
+            clearable
             placeholder="请输入地址进行查询"
             remote
             reserve-keyword
             :remote-method="getLonAndLat"
             @change="selectAddress"
+            @clear="handleClearLocation"
           >
             <el-option v-for="item in addressList" :key="item.id" :label="item.name" :value="item.location">
               <span style="float: left">{{ item.name }}</span>
@@ -214,7 +216,13 @@ const getLonAndLat = async (keyword: string) => {
   }
 };
 const selectAddress = async (param: any) => {
-  if (!formData.storePosition || typeof formData.storePosition !== "string") {
+  // 如果清空了位置,清空相关字段
+  if (!formData.storePosition || formData.storePosition === "") {
+    handleClearLocation();
+    return;
+  }
+
+  if (typeof formData.storePosition !== "string") {
     ElMessage.warning("地址格式不正确,请重新选择");
     return;
   }
@@ -236,6 +244,19 @@ const selectAddress = async (param: any) => {
   formData.storePositionLatitude = locationList[1]?.trim();
 };
 
+// 清空位置
+const handleClearLocation = () => {
+  formData.storePosition = "";
+  formData.queryAddress = "";
+  formData.storePositionLongitude = "";
+  formData.storePositionLatitude = "";
+  formData.addressProvince = "";
+  formData.location = "";
+  formData.locationId = "";
+  formData.address = "";
+  addressList.value = [];
+};
+
 // 表单验证规则
 const rules = reactive<FormRules<FormData>>({
   title: [{ required: true, message: "请输入标题", trigger: "blur" }],

+ 8 - 9
src/views/dynamicManagement/userDynamic.vue

@@ -587,13 +587,7 @@ import {
   ChatDotRound,
   CircleCheck
 } from "@element-plus/icons-vue";
-import {
-  reportUserViolation,
-  blockUser,
-  getUserByPhone,
-  likeDynamicNew,
-  unlikeDynamicNew
-} from "@/api/modules/dynamicManagement";
+import {} from "@/api/modules/dynamicManagement";
 import {
   getUserDynamicsList,
   cancelFollewed,
@@ -603,9 +597,14 @@ import {
   getCouponList,
   setFriendCoupon,
   saveComment,
-  commentList
+  commentList,
+  getUserByPhone,
+  reportUserViolation,
+  uploadImg,
+  likeDynamicNew,
+  unlikeDynamicNew,
+  blockUser
 } from "@/api/modules/newLoginApi";
-import { uploadImg } from "@/api/modules/upload";
 import { useUserStore } from "@/stores/modules/user";
 import { localGet } from "@/utils";
 

+ 784 - 0
src/views/home/components/go-businessHours.vue

@@ -0,0 +1,784 @@
+<template>
+  <el-dialog
+    :model-value="modelValue"
+    title="营业时间"
+    width="560px"
+    destroy-on-close
+    append-to-body
+    @update:model-value="emit('update:modelValue', $event)"
+    @open="onOpen"
+  >
+    <p class="business-hours-tip">
+      特殊营业时间适用于特殊日期(如:春节/国庆/其他指定日期),设置成功后会在指定日期调整前台营业时间时显示
+    </p>
+    <div v-if="displayList.length > 0" class="business-hours-list">
+      <div v-for="(item, index) in displayList" :key="item.key" class="business-hours-item">
+        <div class="business-hours-item-header">
+          <span class="item-title">{{ item.title }}</span>
+          <el-tag :type="item.businessType === 1 ? 'success' : 'danger'" size="small">
+            {{ item.businessType === 1 ? "正常" : "特殊" }}
+          </el-tag>
+        </div>
+        <div class="business-hours-item-time">
+          {{ item.timeText }}
+        </div>
+        <div class="business-hours-item-actions">
+          <el-button type="primary" link @click="handleEdit(item)"> 编辑 </el-button>
+          <el-button type="primary" link @click="handleDelete(item)"> 删除 </el-button>
+        </div>
+      </div>
+    </div>
+    <el-empty v-else description="暂未添加营业时间" :image-size="80" />
+    <template #footer>
+      <div class="business-hours-dialog-footer">
+        <el-button @click="openAddNormal"> 添加正常营业时间 </el-button>
+        <el-button @click="openAddSpecial"> 添加特殊营业时间 </el-button>
+        <el-button v-if="cacheOnly" type="primary" @click="handleCloseAndCache"> 关闭 </el-button>
+        <el-button v-else type="primary" :loading="saving" @click="handleSave"> 保存 </el-button>
+      </div>
+    </template>
+  </el-dialog>
+  <!-- 添加/编辑 正常营业时间 -->
+  <el-dialog v-model="normalDialogVisible" title="正常营业时间" width="500px" append-to-body @close="resetNormalForm">
+    <el-form label-width="120px">
+      <el-form-item label="请选择营业日">
+        <div class="day-buttons">
+          <el-button
+            v-for="day in weekDays"
+            :key="day.value"
+            :type="normalForm.selectedDays.includes(day.value) ? 'primary' : 'default'"
+            @click="toggleNormalDay(day.value)"
+          >
+            {{ day.label }}
+          </el-button>
+        </div>
+      </el-form-item>
+      <el-form-item label="营业时间段">
+        <el-radio-group v-model="normalForm.timeType">
+          <el-radio :value="'custom'"> 自定义 </el-radio>
+          <el-radio :value="'24hours'"> 24小时 </el-radio>
+        </el-radio-group>
+        <div v-if="normalForm.timeType === 'custom'" class="time-picker-row">
+          <el-time-picker
+            v-model="normalForm.startTime"
+            format="HH:mm"
+            value-format="HH:mm"
+            placeholder="开始时间"
+            style="width: 140px"
+          />
+          <span class="time-sep">至</span>
+          <el-time-picker
+            v-model="normalForm.endTime"
+            format="HH:mm"
+            value-format="HH:mm"
+            placeholder="结束时间"
+            style="width: 140px"
+          />
+        </div>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="normalDialogVisible = false"> 取消 </el-button>
+      <el-button type="primary" @click="confirmNormal"> 确定 </el-button>
+    </template>
+  </el-dialog>
+  <!-- 添加/编辑 特殊营业时间 -->
+  <el-dialog v-model="specialDialogVisible" title="特殊营业时间" width="500px" append-to-body @close="resetSpecialForm">
+    <el-form label-width="120px">
+      <el-form-item label="请选择营业日">
+        <div class="day-buttons holiday-buttons">
+          <el-button
+            v-for="h in holidays"
+            :key="h.value"
+            :type="specialForm.selectedHolidays.includes(h.value) ? 'primary' : 'default'"
+            @click="toggleSpecialHoliday(h.value)"
+          >
+            {{ h.label }}
+          </el-button>
+        </div>
+      </el-form-item>
+      <el-form-item label="营业时间段">
+        <el-radio-group v-model="specialForm.timeType">
+          <el-radio :value="'custom'"> 自定义 </el-radio>
+          <el-radio :value="'24hours'"> 24小时 </el-radio>
+        </el-radio-group>
+        <div v-if="specialForm.timeType === 'custom'" class="time-picker-row">
+          <el-time-picker
+            v-model="specialForm.startTime"
+            format="HH:mm"
+            value-format="HH:mm"
+            placeholder="开始时间"
+            style="width: 140px"
+          />
+          <span class="time-sep">至</span>
+          <el-time-picker
+            v-model="specialForm.endTime"
+            format="HH:mm"
+            value-format="HH:mm"
+            placeholder="结束时间"
+            style="width: 140px"
+          />
+        </div>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="specialDialogVisible = false"> 取消 </el-button>
+      <el-button type="primary" @click="confirmSpecial"> 确定 </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, watch } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { getStoreInfoBusinessHours, addOrEditBusinessTime, getHolidayList } from "@/api/modules/storeDecoration";
+import { localGet, localSet } from "@/utils/index";
+
+const CACHE_KEY = "geeker-entry-businessHours";
+
+const props = withDefaults(defineProps<{ modelValue: boolean; cacheOnly?: boolean }>(), { cacheOnly: false });
+const emit = defineEmits<{
+  (e: "update:modelValue", v: boolean): void;
+  (e: "success"): void;
+  (e: "hasConfig", v: boolean): void;
+}>();
+
+const weekDays = [
+  { label: "周一", value: 1 },
+  { label: "周二", value: 2 },
+  { label: "周三", value: 3 },
+  { label: "周四", value: 4 },
+  { label: "周五", value: 5 },
+  { label: "周六", value: 6 },
+  { label: "周日", value: 7 }
+];
+
+interface NormalHoursItem {
+  id?: number | null;
+  days: number[];
+  timeType: "custom" | "24hours";
+  startTime?: string;
+  endTime?: string;
+}
+interface SpecialHoursItem {
+  id?: number | null;
+  holidays: string[];
+  timeType: "custom" | "24hours";
+  startTime?: string;
+  endTime?: string;
+  festivalName?: string;
+  holidayId?: number;
+  festivalDate?: string;
+  specialEndTime?: string;
+}
+
+/** 缓存/提交用结构:与后端一致 */
+interface CacheBusinessHoursItem {
+  timeType: string;
+  startTime: string;
+  endTime: string;
+  businessType: 1 | 2;
+  businessTypeStr: string;
+  businessDate: string;
+  holidayId?: number;
+  festivalDate?: string;
+  specialEndTime?: string;
+}
+
+/** 节日选项:value 为名称用于展示与去重,id 为 getHolidayList 接口返回的 id,提交时作为 essentialId */
+const holidays = ref<{ label: string; value: string; id: number }[]>([]);
+const normalHours = ref<NormalHoursItem[]>([]);
+const specialHours = ref<SpecialHoursItem[]>([]);
+const saving = ref(false);
+const normalDialogVisible = ref(false);
+const specialDialogVisible = ref(false);
+const normalEditIndex = ref<number | null>(null);
+const specialEditIndex = ref<number | null>(null);
+
+const normalForm = reactive({
+  selectedDays: [] as number[],
+  timeType: "custom" as "custom" | "24hours",
+  startTime: "",
+  endTime: ""
+});
+const specialForm = reactive({
+  selectedHolidays: [] as string[],
+  timeType: "custom" as "custom" | "24hours",
+  startTime: "",
+  endTime: ""
+});
+
+const formatTimeRange = (start?: string, end?: string) => {
+  if (!start || !end) return "";
+  if (end <= start) return `${start}-次日${end}`;
+  return `${start}-${end}`;
+};
+
+const getTitle = (item: NormalHoursItem | SpecialHoursItem, type: 1 | 2) => {
+  if (type === 1) {
+    const n = item as NormalHoursItem;
+    const sorted = [...n.days].sort((a, b) => a - b);
+    if (sorted.length === 7) return "全年";
+    return sorted
+      .map(d => weekDays.find(w => w.value === d)?.label)
+      .filter(Boolean)
+      .join("、");
+  }
+  const s = item as SpecialHoursItem;
+  return s.festivalName || s.holidays.join("、");
+};
+
+const getTimeText = (item: NormalHoursItem | SpecialHoursItem) => {
+  if (item.timeType === "24hours") return "24小时";
+  return formatTimeRange(item.startTime || "00:00", item.endTime || "00:00");
+};
+
+const displayList = computed(() => {
+  const list: {
+    key: string;
+    title: string;
+    businessType: 1 | 2;
+    timeText: string;
+    source: "normal" | "special";
+    index: number;
+  }[] = [];
+  normalHours.value.forEach((item, i) => {
+    list.push({
+      key: `n-${i}`,
+      title: getTitle(item, 1),
+      businessType: 1,
+      timeText: getTimeText(item),
+      source: "normal",
+      index: i
+    });
+  });
+  specialHours.value.forEach((item, i) => {
+    list.push({
+      key: `s-${i}`,
+      title: getTitle(item, 2),
+      businessType: 2,
+      timeText: getTimeText(item),
+      source: "special",
+      index: i
+    });
+  });
+  return list;
+});
+
+const convertApiToDisplay = (apiData: any[]) => {
+  const norm: NormalHoursItem[] = [];
+  const spec: SpecialHoursItem[] = [];
+  apiData.forEach((item: any) => {
+    if (item.businessType === 1) {
+      const businessDate = item.businessDate || "";
+      const days: number[] = [];
+      businessDate.split("、").forEach((label: string) => {
+        const day = weekDays.find(d => d.label === label.trim());
+        if (day) days.push(day.value);
+      });
+      const is24 = item.startTime === "00:00" && (item.endTime === "23:59" || item.endTime === "02:00");
+      norm.push({
+        id: item.id,
+        days,
+        timeType: is24 ? "24hours" : "custom",
+        startTime: is24 ? undefined : item.startTime,
+        endTime: is24 ? undefined : item.endTime
+      });
+    } else if (item.businessType === 2) {
+      const businessDate = item.businessDate || "";
+      const holidayLabels = businessDate.split("、").map((h: string) => h.trim());
+      const is24 = item.startTime === "00:00" && (item.endTime === "23:59" || item.endTime === "02:00");
+      const festivalName = item.festivalName ?? item.holidayInfo?.festivalName ?? "";
+      spec.push({
+        id: item.id,
+        holidays: holidayLabels,
+        timeType: is24 ? "24hours" : "custom",
+        startTime: is24 ? undefined : item.startTime,
+        endTime: is24 ? undefined : item.endTime,
+        festivalName: festivalName || undefined
+      });
+    }
+  });
+  return { norm, spec };
+};
+
+const displayToApi = () => {
+  const list: any[] = [];
+  const storeId = localGet("geeker-user")?.userInfo?.storeId || localGet("createdId");
+  if (!storeId) return list;
+  normalHours.value.forEach(item => {
+    const dayLabels = [...item.days]
+      .sort((a, b) => a - b)
+      .map(d => weekDays.find(w => w.value === d)?.label)
+      .filter(Boolean);
+    list.push({
+      storeId: Number(storeId),
+      businessType: 1,
+      businessDate: dayLabels.join("、"),
+      startTime: item.timeType === "24hours" ? "00:00" : item.startTime || "00:00",
+      endTime: item.timeType === "24hours" ? "23:59" : item.endTime || "23:59",
+      deleteFlag: 0,
+      createdTime: "",
+      createdUserId: 0,
+      updatedTime: "",
+      updatedUserId: 0
+    });
+  });
+  specialHours.value.forEach(item => {
+    list.push({
+      storeId: Number(storeId),
+      businessType: 2,
+      businessDate: item.holidays.join("、"),
+      startTime: item.timeType === "24hours" ? "00:00" : item.startTime || "00:00",
+      endTime: item.timeType === "24hours" ? "23:59" : item.endTime || "23:59",
+      deleteFlag: 0,
+      createdTime: "",
+      createdUserId: 0,
+      updatedTime: "",
+      updatedUserId: 0
+    });
+  });
+  return list;
+};
+
+const fetchHolidays = async () => {
+  try {
+    const res: any = await getHolidayList({
+      year: new Date().getFullYear(),
+      page: 1,
+      size: 500,
+      openFlag: 1,
+      holidayName: ""
+    });
+    const data = res?.data?.records ?? res?.data ?? [];
+    holidays.value = (Array.isArray(data) ? data : []).map((item: any) => ({
+      label: item.festivalName ?? item.name ?? String(item.id ?? ""),
+      value: item.festivalName ?? item.name ?? String(item.id ?? ""),
+      id: item.id != null ? Number(item.id) : 0
+    }));
+  } catch {
+    holidays.value = [];
+  }
+};
+
+const fetchList = async () => {
+  const storeId = localGet("geeker-user")?.userInfo?.storeId || localGet("createdId");
+  if (!storeId) {
+    emit("hasConfig", false);
+    return;
+  }
+  try {
+    const res: any = await getStoreInfoBusinessHours({ id: storeId });
+    const ok = res && (res.code === 200 || res.code === "200" || res.success) && res.data != null;
+    if (ok) {
+      const dataList = Array.isArray(res.data) ? res.data : res.data.list || [];
+      const { norm, spec } = convertApiToDisplay(dataList);
+      normalHours.value = norm;
+      specialHours.value = spec;
+      emit("hasConfig", norm.length + spec.length > 0);
+    } else {
+      normalHours.value = [];
+      specialHours.value = [];
+      emit("hasConfig", false);
+    }
+  } catch {
+    normalHours.value = [];
+    specialHours.value = [];
+    emit("hasConfig", false);
+  }
+};
+
+watch(
+  () => props.modelValue,
+  visible => {
+    if (!visible) {
+      normalDialogVisible.value = false;
+      specialDialogVisible.value = false;
+    }
+  }
+);
+
+/** 内部数据 → 缓存格式(含 timeType/businessDate 等字段) */
+const toCacheList = (): CacheBusinessHoursItem[] => {
+  const list: CacheBusinessHoursItem[] = [];
+  normalHours.value.forEach(item => {
+    const dayLabels = [...item.days]
+      .sort((a, b) => a - b)
+      .map(d => weekDays.find(w => w.value === d)?.label)
+      .filter(Boolean);
+    list.push({
+      timeType: item.timeType === "24hours" ? "2" : "1",
+      startTime: item.timeType === "24hours" ? "00:00" : item.startTime || "00:00",
+      endTime: item.timeType === "24hours" ? "23:59" : item.endTime || "23:59",
+      businessType: 1,
+      businessTypeStr: "正常",
+      businessDate: dayLabels.join("、")
+    });
+  });
+  specialHours.value.forEach(item => {
+    const row: CacheBusinessHoursItem = {
+      timeType: item.timeType === "24hours" ? "2" : "1",
+      startTime: item.timeType === "24hours" ? "00:00" : item.startTime || "00:00",
+      endTime: item.timeType === "24hours" ? "23:59" : item.endTime || "23:59",
+      businessType: 2,
+      businessTypeStr: "特殊",
+      businessDate: item.festivalName || item.holidays.join("、")
+    };
+    // 确保 getHolidayList 返回的 id 写入缓存,供入驻提交时作为 essentialId
+    if (item.holidayId != null && item.holidayId !== undefined) {
+      row.holidayId = item.holidayId;
+    } else if (item.holidays?.length > 0 && holidays.value.length > 0) {
+      const byName = holidays.value.find(h => h.value === item.holidays[0] || h.label === item.holidays[0]);
+      if (byName?.id != null) row.holidayId = byName.id;
+    }
+    if (item.festivalDate) row.festivalDate = item.festivalDate;
+    if (item.specialEndTime) row.specialEndTime = item.specialEndTime;
+    list.push(row);
+  });
+  return list;
+};
+
+/** 缓存格式 → 内部数据 */
+const fromCacheList = (list: CacheBusinessHoursItem[]): void => {
+  const norm: NormalHoursItem[] = [];
+  const spec: SpecialHoursItem[] = [];
+  (list || []).forEach((item: CacheBusinessHoursItem) => {
+    if (item.businessType === 1) {
+      const businessDate = item.businessDate || "";
+      const days: number[] = [];
+      businessDate.split("、").forEach((label: string) => {
+        const day = weekDays.find(d => d.label === label.trim());
+        if (day) days.push(day.value);
+      });
+      const is24 =
+        item.timeType === "2" || (item.startTime === "00:00" && (item.endTime === "23:59" || item.endTime === "02:00"));
+      norm.push({
+        days,
+        timeType: is24 ? "24hours" : "custom",
+        startTime: is24 ? undefined : item.startTime,
+        endTime: is24 ? undefined : item.endTime
+      });
+    } else if (item.businessType === 2) {
+      const businessDate = (item.businessDate || "").trim();
+      const holidayLabels = businessDate ? businessDate.split("、").map((h: string) => h.trim()) : [];
+      const is24 =
+        item.timeType === "2" || (item.startTime === "00:00" && (item.endTime === "23:59" || item.endTime === "02:00"));
+      const specItem: SpecialHoursItem = {
+        holidays: holidayLabels,
+        timeType: is24 ? "24hours" : "custom",
+        startTime: is24 ? undefined : item.startTime,
+        endTime: is24 ? undefined : item.endTime,
+        festivalName: businessDate || undefined
+      };
+      if (item.holidayId != null) specItem.holidayId = item.holidayId;
+      if (item.festivalDate) specItem.festivalDate = item.festivalDate;
+      if (item.specialEndTime) specItem.specialEndTime = item.specialEndTime;
+      spec.push(specItem);
+    }
+  });
+  normalHours.value = norm;
+  specialHours.value = spec;
+};
+
+const loadFromCache = () => {
+  try {
+    const raw = localGet(CACHE_KEY);
+    if (raw && Array.isArray(raw)) {
+      fromCacheList(raw as CacheBusinessHoursItem[]);
+      emit("hasConfig", normalHours.value.length + specialHours.value.length > 0);
+      return true;
+    }
+    if (raw && typeof raw === "object" && Array.isArray((raw as any).list)) {
+      fromCacheList((raw as any).list);
+      emit("hasConfig", normalHours.value.length + specialHours.value.length > 0);
+      return true;
+    }
+  } catch {
+    // ignore
+  }
+  normalHours.value = [];
+  specialHours.value = [];
+  emit("hasConfig", false);
+  return false;
+};
+
+const saveToCache = () => {
+  try {
+    const list = toCacheList();
+    localSet(CACHE_KEY, list);
+  } catch {
+    // ignore
+  }
+};
+
+const handleCloseAndCache = () => {
+  saveToCache();
+  emit("hasConfig", normalHours.value.length + specialHours.value.length > 0);
+  emit("update:modelValue", false);
+};
+
+const onOpen = () => {
+  fetchHolidays();
+  if (props.cacheOnly) {
+    loadFromCache();
+  } else {
+    fetchList();
+  }
+};
+
+const resetNormalForm = () => {
+  normalForm.selectedDays = [];
+  normalForm.timeType = "custom";
+  normalForm.startTime = "";
+  normalForm.endTime = "";
+  normalEditIndex.value = null;
+};
+
+const resetSpecialForm = () => {
+  specialForm.selectedHolidays = [];
+  specialForm.timeType = "custom";
+  specialForm.startTime = "";
+  specialForm.endTime = "";
+  specialEditIndex.value = null;
+};
+
+const toggleNormalDay = (day: number) => {
+  const i = normalForm.selectedDays.indexOf(day);
+  if (i > -1) normalForm.selectedDays.splice(i, 1);
+  else normalForm.selectedDays.push(day);
+};
+
+const toggleSpecialHoliday = (h: string) => {
+  const i = specialForm.selectedHolidays.indexOf(h);
+  if (i > -1) specialForm.selectedHolidays.splice(i, 1);
+  else specialForm.selectedHolidays.push(h);
+};
+
+const openAddNormal = () => {
+  if (normalHours.value.length > 0) {
+    ElMessage.warning("已有正常营业时间");
+    return;
+  }
+  normalEditIndex.value = null;
+  resetNormalForm();
+  normalDialogVisible.value = true;
+};
+
+const openAddSpecial = () => {
+  if (specialHours.value.length >= 5) {
+    ElMessage.warning("最多添加5个特殊营业时间");
+    return;
+  }
+  specialEditIndex.value = null;
+  resetSpecialForm();
+  specialDialogVisible.value = true;
+};
+
+const handleEdit = (row: { source: "normal" | "special"; index: number }) => {
+  if (row.source === "normal") {
+    const item = normalHours.value[row.index];
+    normalEditIndex.value = row.index;
+    normalForm.selectedDays = [...item.days];
+    normalForm.timeType = item.timeType;
+    normalForm.startTime = item.startTime || "";
+    normalForm.endTime = item.endTime || "";
+    normalDialogVisible.value = true;
+  } else {
+    const item = specialHours.value[row.index];
+    specialEditIndex.value = row.index;
+    specialForm.selectedHolidays = [...item.holidays];
+    specialForm.timeType = item.timeType;
+    specialForm.startTime = item.startTime || "";
+    specialForm.endTime = item.endTime || "";
+    specialDialogVisible.value = true;
+  }
+};
+
+const confirmNormal = () => {
+  if (normalForm.selectedDays.length === 0) {
+    ElMessage.warning("请选择营业日");
+    return;
+  }
+  if (normalForm.timeType === "custom" && (!normalForm.startTime || !normalForm.endTime)) {
+    ElMessage.warning("请选择营业时间段");
+    return;
+  }
+  const newItem: NormalHoursItem = {
+    days: [...normalForm.selectedDays],
+    timeType: normalForm.timeType,
+    startTime: normalForm.timeType === "custom" ? normalForm.startTime : "00:00",
+    endTime: normalForm.timeType === "custom" ? normalForm.endTime : "23:59"
+  };
+  if (normalEditIndex.value !== null) {
+    newItem.id = normalHours.value[normalEditIndex.value].id;
+    normalHours.value[normalEditIndex.value] = newItem;
+    ElMessage.success("编辑成功");
+  } else {
+    normalHours.value.push(newItem);
+    ElMessage.success("添加成功");
+  }
+  normalDialogVisible.value = false;
+  resetNormalForm();
+};
+
+const checkSpecialDuplicate = (arr: string[]) => {
+  const other = specialHours.value.filter((_, i) => i !== specialEditIndex.value);
+  return other.some(item => arr.some(h => item.holidays.includes(h)));
+};
+
+const confirmSpecial = () => {
+  if (specialForm.selectedHolidays.length === 0) {
+    ElMessage.warning("请选择营业日");
+    return;
+  }
+  if (specialForm.timeType === "custom" && (!specialForm.startTime || !specialForm.endTime)) {
+    ElMessage.warning("请选择营业时间段");
+    return;
+  }
+  if (checkSpecialDuplicate(specialForm.selectedHolidays)) {
+    ElMessage.warning("特殊营业时间的日期不能重复");
+    return;
+  }
+  const firstVal = specialForm.selectedHolidays[0];
+  const holidayOpt = holidays.value.find(h => h.value === firstVal);
+  const holidayIdFromList = holidayOpt?.id != null ? holidayOpt.id : undefined;
+  const newItem: SpecialHoursItem = {
+    holidays: [...specialForm.selectedHolidays],
+    timeType: specialForm.timeType,
+    startTime: specialForm.timeType === "custom" ? specialForm.startTime : "00:00",
+    endTime: specialForm.timeType === "custom" ? specialForm.endTime : "23:59"
+  };
+  newItem.holidayId =
+    holidayIdFromList ?? (specialEditIndex.value !== null ? specialHours.value[specialEditIndex.value].holidayId : undefined);
+  if (specialEditIndex.value !== null) {
+    newItem.id = specialHours.value[specialEditIndex.value].id;
+    specialHours.value[specialEditIndex.value] = newItem;
+    ElMessage.success("编辑成功");
+  } else {
+    specialHours.value.push(newItem);
+    ElMessage.success("添加成功");
+  }
+  specialDialogVisible.value = false;
+  resetSpecialForm();
+};
+
+const handleDelete = (row: { source: "normal" | "special"; index: number }) => {
+  const doDel = () => {
+    if (row.source === "normal") {
+      if (normalHours.value.length <= 1) {
+        ElMessage.warning("至少存在一条营业时间");
+        return;
+      }
+      normalHours.value.splice(row.index, 1);
+    } else {
+      specialHours.value.splice(row.index, 1);
+    }
+    ElMessage.success("删除成功");
+  };
+  ElMessageBox.confirm("确认要删除当前营业时间吗?", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(doDel)
+    .catch(() => {});
+};
+
+const handleSave = async () => {
+  if (normalHours.value.length === 0) {
+    ElMessage.warning("至少添加一条营业时间");
+    return;
+  }
+  const storeId = localGet("geeker-user")?.userInfo?.storeId || localGet("createdId");
+  if (!storeId) {
+    ElMessage.warning("未找到店铺信息");
+    return;
+  }
+  saving.value = true;
+  try {
+    const list = displayToApi();
+    const result: any = await addOrEditBusinessTime(list);
+    if (result && (result.code === 200 || result.code === "200")) {
+      ElMessage.success("保存成功");
+      emit("success");
+      await fetchList();
+      emit("update:modelValue", false);
+    } else {
+      ElMessage.error(result?.msg || "保存失败");
+    }
+  } catch {
+    ElMessage.error("保存失败");
+  } finally {
+    saving.value = false;
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.business-hours-tip {
+  margin: 0 0 16px;
+  font-size: 13px;
+  line-height: 1.5;
+  color: #334154;
+}
+.business-hours-list {
+  max-height: 400px;
+  overflow-y: auto;
+}
+.business-hours-item {
+  padding: 14px 16px;
+  margin-bottom: 12px;
+  background: #fafafa;
+  border: 1px solid #f2f2f2;
+  border-radius: 8px;
+  .business-hours-item-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .item-title {
+      max-width: 75%;
+      overflow: hidden;
+      font-size: 14px;
+      font-weight: 600;
+      color: #334154;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+  .business-hours-item-time {
+    margin-top: 8px;
+    font-size: 13px;
+    color: #334154;
+  }
+  .business-hours-item-actions {
+    padding-top: 8px;
+    margin-top: 8px;
+    border-top: 1px solid #f2f2f2;
+  }
+}
+.business-hours-dialog-footer {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  justify-content: flex-end;
+}
+.day-buttons,
+.holiday-buttons {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  .el-button {
+    margin: 0;
+  }
+}
+.time-picker-row {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  margin-top: 8px;
+}
+.time-sep {
+  font-size: 14px;
+  color: #606266;
+}
+</style>

+ 134 - 30
src/views/home/components/go-flow.vue

@@ -219,6 +219,22 @@
                   <el-input v-model="step2Form.storePj[2]" placeholder="请输入" maxlength="2" clearable class="store-pj-input" />
                 </div>
               </el-form-item>
+              <el-form-item label="设置收款账号" required>
+                <div style="color: #6c8ff8; cursor: pointer" @click="addPayAccount">
+                  {{ hasPayAccountConfig ? "已添加" : "添加" }}
+                </div>
+              </el-form-item>
+              <el-form-item label="预约服务">
+                <el-radio-group v-model="step2Form.appointment">
+                  <el-radio label="提供"> 提供 </el-radio>
+                  <el-radio label="不提供"> 不提供 </el-radio>
+                </el-radio-group>
+              </el-form-item>
+              <el-form-item label="营业时间">
+                <div style="color: #6c8ff8; cursor: pointer" @click="openBusinessHours">
+                  {{ hasBusinessHoursConfig ? "已添加" : "添加" }}
+                </div>
+              </el-form-item>
 
               <el-form-item label="营业执照" prop="businessLicenseAddress">
                 <el-upload
@@ -264,7 +280,19 @@
       </div>
     </div>
   </div>
-
+  <!-- 设置收款账号弹窗 -->
+  <GoReceivingAccount
+    v-model="setPayAccountDialogVisible"
+    @success="hasPayAccountConfig = true"
+    @has-config="(v: boolean) => (hasPayAccountConfig = v)"
+  />
+  <!-- 营业时间弹窗(入驻:仅缓存,关闭按钮) -->
+  <GoBusinessHours
+    v-model="businessHoursDialogVisible"
+    :cache-only="true"
+    @success="hasBusinessHoursConfig = true"
+    @has-config="(v: boolean) => (hasBusinessHoursConfig = v)"
+  />
   <!-- 图片预览 -->
   <el-image-viewer
     v-if="imageViewerVisible"
@@ -275,19 +303,14 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch, onMounted, computed } from "vue";
+import { ref, reactive, watch, onMounted, computed, toRaw } from "vue";
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules, UploadUserFile, UploadRequestOptions } from "element-plus";
 import { Plus } from "@element-plus/icons-vue";
 
-import {
-  applyStore,
-  getMerchantByPhone,
-  getFirstLevelList,
-  getSecondLevelList,
-  getThirdLevelList,
-  verifyIdInfo
-} from "@/api/modules/homeEntry";
+import { applyStore, getMerchantByPhone, getThirdLevelList, verifyIdInfo } from "@/api/modules/homeEntry";
 import { getInputPrompt, getDistrict, uploadImg, ocrRequestUrl, getAiapprovestoreInfo } from "@/api/modules/newLoginApi";
+import GoReceivingAccount from "./go-receivingAccount.vue";
+import GoBusinessHours from "./go-businessHours.vue";
 import { localGet, localSet } from "@/utils/index";
 import { useAuthStore } from "@/stores/modules/auth";
 
@@ -299,7 +322,15 @@ const showDisportLicence = ref(false);
 const imageViewerVisible = ref(false);
 const imageViewerUrlList = ref<string[]>([]);
 const imageViewerInitialIndex = ref(0);
-
+// 设置收款账号
+const setPayAccountDialogVisible = ref(false);
+const hasPayAccountConfig = ref(false);
+// 营业时间
+const businessHoursDialogVisible = ref(false);
+const hasBusinessHoursConfig = ref(false);
+const openBusinessHours = () => {
+  businessHoursDialogVisible.value = true;
+};
 const entryList = ref([
   {
     title: "个人实名"
@@ -400,22 +431,24 @@ const isIdCardUploadComplete = computed(() => {
 // 下一步 - 验证身份证正反面是否已上传
 const handleNextStep = async () => {
   // 识别成功,进入下一步
-  try {
-    const res: any = await verifyIdInfo({
-      idCard: ocrResult.value.idCard,
-      name: ocrResult.value.name,
-      appType: 1
-    });
-    if (res.code === 200) {
-      ElMessage.success("身份证识别成功");
-      setStep(2);
-    } else {
-      ElMessage.error(res?.msg || "身份证识别失败");
-      idCardFrontList.value = [];
-    }
-  } catch (error: any) {
-    console.log(error);
-  }
+  // try {
+  //   const res: any = await verifyIdInfo({
+  //     idCard: ocrResult.value.idCard,
+  //     name: ocrResult.value.name,
+  //     appType: 1
+  //   });
+  //   if (res.code === 200) {
+  //     ElMessage.success("身份证识别成功");
+  //     setStep(2);
+  //   } else {
+  //     ElMessage.error(res?.msg || "身份证识别失败");
+  //     idCardFrontList.value = [];
+  //   }
+  // } catch (error: any) {
+  //   console.log(error);
+  // }
+  ElMessage.success("身份证识别成功");
+  setStep(2);
 };
 const step2Rules: FormRules = {
   storeName: [{ required: true, message: "请输入店铺名称", trigger: "blur" }],
@@ -597,6 +630,9 @@ watch(
   }
 );
 
+const addPayAccount = () => {
+  setPayAccountDialogVisible.value = true;
+};
 onMounted(() => {
   callGetUserInfo();
   if (currentStep.value === 3 && (storeApplicationStatus.value === 0 || storeApplicationStatus.value === 2)) {
@@ -617,6 +653,7 @@ const setStep = (val: number) => {
 // 第二步表单
 const step2FormRef = ref<FormInstance>();
 const step2Form = reactive({
+  appointment: "",
   businessSecondMeal: 1,
   storeName: "",
   storeCapacity: 1,
@@ -1253,16 +1290,39 @@ const handleSubmit = async () => {
         administrativeRegionDistrictName: whereAddress[2]?.name || "",
         businessStatus: step2Form.businessStatus,
         storeContact: userInfo.name || (localGet("smName") as string) || "",
-        idCard: localGet("idCard") || ""
+        idCard: localGet("idCard") || "",
+        bookingService: step2Form.appointment === "提供" ? 1 : 0
       };
 
       storeInfoDto.storeTickets = step2Form.storeTickets;
 
+      // 营业时间:从缓存读取并转为提交格式
+      const businessHoursRaw = localGet("geeker-entry-businessHours");
+      const businessHoursCopy = Array.isArray(businessHoursRaw)
+        ? businessHoursRaw
+        : businessHoursRaw && typeof businessHoursRaw === "object" && Array.isArray((businessHoursRaw as any).list)
+          ? (businessHoursRaw as any).list
+          : [];
+      const storeBusinessTime = businessHoursCopy.map((item: any) => {
+        const obj: any = {
+          businessType: item.businessType,
+          startTime: item.startTime,
+          endTime: item.endTime,
+          storeId: userInfo.storeId ?? null
+        };
+        if (item.businessType === 2) {
+          obj.essentialId = item.holidayId; // geeker-entry-businessHours 缓存里的 holidayId(getHolidayList 返回的 id)
+        }
+        if (item.businessType === 1) obj.businessDate = item.businessDate;
+        return obj;
+      });
+
       const saveStoreInfoParams = {
         ...storeInfoDto,
         foodLicenceUrl: foodLicenceUrls.join(","),
         mealsFlag: step2Form.businessSecondMeal === 1 ? 1 : 0,
-        createdUserId: userInfo.id || ""
+        createdUserId: userInfo.id || "",
+        storeBusinessTime: storeBusinessTime.length > 0 ? storeBusinessTime : undefined
       };
 
       ElMessageBox.confirm("确认提交入驻申请吗?", "提示", {
@@ -1301,7 +1361,15 @@ const handleExceed = () => {
   ElMessage.warning("文件数量超出限制");
 };
 </script>
-
+<style>
+.el-dialog__body {
+  height: 600px;
+  overflow: scroll;
+}
+.set-pay-key-upload {
+  width: 100%;
+}
+</style>
 <style scoped lang="scss">
 // 店铺评价三个输入框纵向排列
 .store-pj-inputs {
@@ -1507,4 +1575,40 @@ const handleExceed = () => {
     }
   }
 }
+.set-pay-upload-box {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  align-items: center;
+  justify-content: center;
+  padding: 24px;
+  cursor: pointer;
+  background: #fafafa;
+  border: 1px dashed #dcdfe6;
+  border-radius: 8px;
+  transition: border-color 0.2s;
+  &:hover {
+    border-color: #6c8ff8;
+  }
+  p {
+    margin: 0;
+    font-size: 14px;
+    color: #606266;
+  }
+  span {
+    font-size: 12px;
+    color: #909399;
+  }
+}
+.set-pay-key-upload {
+  width: 100%;
+  border: 1px solid red !important;
+  :deep(.el-upload) {
+    width: 100%;
+  }
+  :deep(.el-upload-dragger) {
+    width: 100%;
+    padding: 32px 16px;
+  }
+}
 </style>

+ 369 - 0
src/views/home/components/go-receivingAccount.vue

@@ -0,0 +1,369 @@
+<template>
+  <el-dialog
+    :model-value="modelValue"
+    title="设置收款账号"
+    width="520px"
+    destroy-on-close
+    append-to-body
+    @update:model-value="emit('update:modelValue', $event)"
+  >
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="140px" label-position="top">
+      <el-form-item label="账号类型" prop="accountType">
+        <el-radio-group v-model="form.accountType">
+          <el-radio value="wechat"> 微信 </el-radio>
+          <el-radio value="alipay"> 支付宝 </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="appid" prop="wechatAppid" v-if="form.accountType === 'wechat'">
+        <el-input v-model="form.wechatAppid" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="mchld" prop="wechatMchld" v-if="form.accountType === 'wechat'">
+        <el-input v-model="form.wechatMchld" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="小程序appid" prop="wechatMiniAppId" v-if="form.accountType === 'wechat'">
+        <el-input v-model="form.wechatMiniAppId" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="API证书序列号" prop="wechatApiCertSerialNo" v-if="form.accountType === 'wechat'">
+        <el-input v-model="form.wechatApiCertSerialNo" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="APIv3 Key" prop="wechatApiV3Key" v-if="form.accountType === 'wechat'">
+        <el-input v-model="form.wechatApiV3Key" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="支付公钥ID" prop="wechatPayPublicKeyId" v-if="form.accountType === 'wechat'">
+        <el-input v-model="form.wechatPayPublicKeyId" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="私钥证书" prop="wechatPrivateKeyFile" v-if="form.accountType === 'wechat'">
+        <el-upload
+          v-model:file-list="form.wechatPrivateKeyFile"
+          class="set-pay-key-upload"
+          drag
+          :auto-upload="false"
+          :limit="1"
+          accept=".pem"
+          :on-exceed="() => ElMessage.warning('最多上传1个文件')"
+        >
+          <div class="set-pay-upload-box">
+            <el-icon :size="32" color="#6c8ff8">
+              <UploadFilled />
+            </el-icon>
+            <p>点击或拖拽文件至此处上传</p>
+            <span>支持.pem 格式文件,大小不超过2MB</span>
+          </div>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="公钥证书" prop="wechatPayPublicKeyFile" v-if="form.accountType === 'wechat'">
+        <el-upload
+          v-model:file-list="form.wechatPayPublicKeyFile"
+          class="set-pay-key-upload"
+          drag
+          :auto-upload="false"
+          :limit="1"
+          accept=".pem"
+          :on-exceed="() => ElMessage.warning('最多上传1个文件')"
+        >
+          <div class="set-pay-upload-box">
+            <el-icon :size="32" color="#6c8ff8">
+              <UploadFilled />
+            </el-icon>
+            <p>点击或拖拽文件至此处上传</p>
+            <span>支持.pem 格式文件,大小不超过2MB</span>
+          </div>
+        </el-upload>
+      </el-form-item>
+
+      <!-- 支付宝 -->
+      <el-form-item label="应用ID" prop="aliAppid" v-if="form.accountType === 'alipay'">
+        <el-input v-model="form.aliAppid" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="应用私钥" prop="aliPrivateId" v-if="form.accountType === 'alipay'">
+        <el-input v-model="form.aliPrivateId" placeholder="请输入" clearable />
+      </el-form-item>
+      <el-form-item label="应用公钥证书" prop="appPublicCertFile" v-if="form.accountType === 'alipay'">
+        <el-upload
+          v-model:file-list="form.appPublicCertFile"
+          class="set-pay-key-upload"
+          drag
+          :auto-upload="false"
+          :limit="1"
+          accept=".pem"
+          :on-exceed="() => ElMessage.warning('最多上传1个文件')"
+        >
+          <div class="set-pay-upload-box">
+            <el-icon :size="32" color="#6c8ff8">
+              <UploadFilled />
+            </el-icon>
+            <p>点击或拖拽文件至此处上传</p>
+            <span>支持.pem 格式文件,大小不超过2MB</span>
+          </div>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="支付宝公钥证书" prop="alipayPublicCertFile" v-if="form.accountType === 'alipay'">
+        <el-upload
+          v-model:file-list="form.alipayPublicCertFile"
+          class="set-pay-key-upload"
+          drag
+          :auto-upload="false"
+          :limit="1"
+          accept=".pem"
+          :on-exceed="() => ElMessage.warning('最多上传1个文件')"
+        >
+          <div class="set-pay-upload-box">
+            <el-icon :size="32" color="#6c8ff8">
+              <UploadFilled />
+            </el-icon>
+            <p>点击或拖拽文件至此处上传</p>
+            <span>支持.pem 格式文件,大小不超过2MB</span>
+          </div>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="支付宝根证书" prop="alipayRootCertFile" v-if="form.accountType === 'alipay'">
+        <el-upload
+          v-model:file-list="form.alipayRootCertFile"
+          class="set-pay-key-upload"
+          drag
+          :auto-upload="false"
+          :limit="1"
+          accept=".pem"
+          :on-exceed="() => ElMessage.warning('最多上传1个文件')"
+        >
+          <div class="set-pay-upload-box">
+            <el-icon :size="32" color="#6c8ff8">
+              <UploadFilled />
+            </el-icon>
+            <p>点击或拖拽文件至此处上传</p>
+            <span>支持.pem 格式文件,大小不超过2MB</span>
+          </div>
+        </el-upload>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="emit('update:modelValue', false)"> 取消 </el-button>
+      <el-button type="primary" @click="submit"> 确定 </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, watch } from "vue";
+import { ElMessage, type FormInstance, type UploadUserFile } from "element-plus";
+import { UploadFilled } from "@element-plus/icons-vue";
+import { getPaymentStoreUserId, saveWechatWithFiles, saveAlipayWithFiles } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils/index";
+
+const props = defineProps<{
+  modelValue: boolean;
+}>();
+
+const emit = defineEmits<{
+  (e: "update:modelValue", value: boolean): void;
+  (e: "success"): void;
+  (e: "hasConfig", value: boolean): void;
+}>();
+
+const formRef = ref<FormInstance>();
+const form = reactive({
+  accountType: "wechat",
+  wechatAppid: "",
+  wechatMchld: "",
+  wechatMiniAppId: "",
+  wechatApiCertSerialNo: "",
+  wechatApiV3Key: "",
+  wechatPayPublicKeyId: null as string | null,
+  wechatPrivateKeyFile: [] as UploadUserFile[],
+  wechatPayPublicKeyFile: [] as UploadUserFile[],
+  aliAppid: "",
+  aliPrivateId: "",
+  alipayRootCertFile: [] as UploadUserFile[],
+  alipayPublicCertFile: [] as UploadUserFile[],
+  appPublicCertFile: [] as UploadUserFile[]
+});
+
+const rules = ref({
+  accountType: [{ required: true, message: "请选择账号类型", trigger: "change" }],
+  wechatAppid: [{ required: true, message: "请输入appid", trigger: "blur" }],
+  wechatMchld: [{ required: true, message: "请输入mchId", trigger: "blur" }],
+  wechatApiCertSerialNo: [{ required: true, message: "请输入API证书序列号", trigger: "blur" }],
+  wechatApiV3Key: [{ required: true, message: "请输入APIv3 Key", trigger: "blur" }],
+  wechatPayPublicKeyId: [{ required: true, message: "请输入支付公钥ID", trigger: "blur" }],
+  wechatMiniAppId: [{ required: true, message: "请输入小程序AppId", trigger: "blur" }],
+  wechatPrivateKeyFile: [{ required: true, type: "array" as const, min: 1, message: "请上传私钥证书", trigger: "change" }],
+  wechatPayPublicKeyFile: [{ required: true, type: "array" as const, min: 1, message: "请上传公钥证书", trigger: "change" }]
+});
+
+const base64ToFile = (base64Str: string, fileName: string): File => {
+  const bstr = atob(base64Str);
+  const u8arr = new Uint8Array(bstr.length);
+  for (let i = 0; i < bstr.length; i++) u8arr[i] = bstr.charCodeAt(i);
+  return new File([u8arr], fileName, { type: "application/x-pem-file" });
+};
+
+const fetchPayAccountConfig = async () => {
+  const storeUserId = localGet("geeker-user")?.userInfo?.id;
+  if (!storeUserId) return;
+  const res: any = await getPaymentStoreUserId({ storeUserId });
+  if (res?.code !== 200 || !res.data) {
+    emit("hasConfig", false);
+    return;
+  }
+  const data = res.data;
+  emit("hasConfig", !!(data.wechatAppId || data.appId));
+
+  if (data.wechatAppId) {
+    form.accountType = "wechat";
+    form.wechatAppid = data.wechatAppId ?? "";
+    form.wechatMchld = data.wechatMchId ?? "";
+    form.wechatMiniAppId = data.wechatMiniAppId ?? "";
+    form.wechatApiCertSerialNo = data.merchantSerialNumber ?? "";
+    form.wechatApiV3Key = data.apiV3Key ?? "";
+    form.wechatPayPublicKeyId = data.wechatPayPublicKeyId ?? null;
+    form.wechatPrivateKeyFile = [];
+    form.wechatPayPublicKeyFile = [];
+    if (data.wechatPrivateKeyFile && data.wechatPrivateKeyName) {
+      const file = base64ToFile(data.wechatPrivateKeyFile, data.wechatPrivateKeyName);
+      form.wechatPrivateKeyFile = [{ name: file.name, raw: file, status: "success", uid: Date.now() } as UploadUserFile];
+    }
+    if (data.wechatPayPublicKeyFile && data.wechatPayPublicKeyFileName) {
+      const file = base64ToFile(data.wechatPayPublicKeyFile, data.wechatPayPublicKeyFileName);
+      form.wechatPayPublicKeyFile = [{ name: file.name, raw: file, status: "success", uid: Date.now() + 1 } as UploadUserFile];
+    }
+  }
+
+  if (data.appId) {
+    form.aliAppid = data.appId ?? "";
+    form.aliPrivateId = data.appSecretCert ?? "";
+    form.appPublicCertFile = [];
+    form.alipayPublicCertFile = [];
+    form.alipayRootCertFile = [];
+    if (data.appPublicCert && data.appPublicCertName) {
+      const file = base64ToFile(data.appPublicCert, data.appPublicCertName);
+      form.appPublicCertFile = [{ name: file.name, raw: file, status: "success", uid: Date.now() } as UploadUserFile];
+    }
+    if (data.alipayPublicCert && data.alipayPublicCertName) {
+      const file = base64ToFile(data.alipayPublicCert, data.alipayPublicCertName);
+      form.alipayPublicCertFile = [{ name: file.name, raw: file, status: "success", uid: Date.now() + 1 } as UploadUserFile];
+    }
+    if (data.alipayRootCert && data.alipayRootCertName) {
+      const file = base64ToFile(data.alipayRootCert, data.alipayRootCertName);
+      form.alipayRootCertFile = [{ name: file.name, raw: file, status: "success", uid: Date.now() + 2 } as UploadUserFile];
+    }
+    if (!data.wechatAppId) form.accountType = "alipay";
+  }
+};
+
+const submit = async () => {
+  if (!formRef.value) return;
+  await formRef.value.validate(async valid => {
+    if (!valid) return;
+
+    const storeUserId = localGet("geeker-user")?.userInfo?.id;
+    if (!storeUserId) {
+      ElMessage.warning("请先登录");
+      return;
+    }
+
+    if (form.accountType === "alipay") {
+      const appPublicCertFile = (form.appPublicCertFile as UploadUserFile[])[0]?.raw as File | undefined;
+      const alipayPublicCertFile = (form.alipayPublicCertFile as UploadUserFile[])[0]?.raw as File | undefined;
+      const alipayRootCertFile = (form.alipayRootCertFile as UploadUserFile[])[0]?.raw as File | undefined;
+
+      if (!form.aliAppid?.trim()) {
+        ElMessage.warning("请输入应用ID");
+        return;
+      }
+      if (!form.aliPrivateId?.trim()) {
+        ElMessage.warning("请输入应用私钥");
+        return;
+      }
+      if (!appPublicCertFile || !alipayPublicCertFile || !alipayRootCertFile) {
+        ElMessage.warning("请上传应用公钥证书、支付宝公钥证书和支付宝根证书");
+        return;
+      }
+
+      try {
+        const res: any = await saveAlipayWithFiles(
+          {
+            storeUserId: String(storeUserId),
+            appId: form.aliAppid.trim(),
+            appSecretCert: form.aliPrivateId.trim()
+          },
+          appPublicCertFile,
+          alipayPublicCertFile,
+          alipayRootCertFile
+        );
+        if (res?.code === 200) {
+          ElMessage.success("保存成功");
+          emit("success");
+          emit("update:modelValue", false);
+        } else {
+          ElMessage.error(res?.msg || "保存失败");
+        }
+      } catch (e: any) {
+        ElMessage.error(e?.message || "保存失败");
+      }
+      return;
+    }
+
+    if (form.accountType === "wechat") {
+      const privateKeyUpload = (form.wechatPrivateKeyFile as UploadUserFile[])[0];
+      const publicKeyUpload = (form.wechatPayPublicKeyFile as UploadUserFile[])[0];
+      const privateKeyFile = privateKeyUpload?.raw as File | undefined;
+      const publicKeyFile = publicKeyUpload?.raw as File | undefined;
+
+      if (!privateKeyFile || !publicKeyFile) {
+        ElMessage.warning("请上传私钥证书和公钥证书");
+        return;
+      }
+
+      try {
+        const res: any = await saveWechatWithFiles(
+          {
+            storeUserId: String(storeUserId),
+            apiV3Key: form.wechatApiV3Key || "",
+            merchantSerialNumber: form.wechatApiCertSerialNo || "",
+            wechatAppId: form.wechatAppid || "",
+            wechatMchId: form.wechatMchld || "",
+            wechatMiniAppId: form.wechatMiniAppId || "",
+            wechatPayPublicKeyId: form.wechatPayPublicKeyId || "",
+            accountType: form.accountType || "wechat"
+          },
+          privateKeyFile,
+          publicKeyFile
+        );
+        if (res?.code === 200) {
+          ElMessage.success("保存成功");
+          emit("success");
+          emit("update:modelValue", false);
+        } else {
+          ElMessage.error(res?.msg || "保存失败");
+        }
+      } catch (e: any) {
+        ElMessage.error(e?.message || "保存失败");
+      }
+    }
+  });
+};
+
+watch(
+  () => props.modelValue,
+  visible => {
+    if (visible) fetchPayAccountConfig();
+  }
+);
+</script>
+
+<style scoped>
+.set-pay-key-upload :deep(.el-upload-dragger) {
+  padding: 20px;
+}
+.set-pay-upload-box {
+  color: var(--el-text-color-secondary);
+  text-align: center;
+}
+.set-pay-upload-box p {
+  margin: 8px 0 4px;
+  font-size: 14px;
+}
+.set-pay-upload-box span {
+  font-size: 12px;
+}
+</style>

+ 3 - 45
src/views/licenseManagement/entertainmentLicense.vue

@@ -92,7 +92,7 @@
                   fit="cover"
                   class="record-image"
                   :preview-src-list="changeRecordList.map(record => record.imgUrl)"
-                  :initial-index="index"
+                  :initial-index="Number(index)"
                 >
                   <template #error>
                     <div class="image-slot">
@@ -128,8 +128,7 @@ import {
   uploadContractImage,
   submitEntertainmentLicenseReview,
   getEntertainmentChangeRecords,
-  getStoreEntertainmentLicenceStatus,
-  ocrRequestUrl
+  getStoreEntertainmentLicenceStatus
 } from "@/api/modules/licenseManagement";
 import { localGet } from "@/utils";
 
@@ -435,47 +434,6 @@ const uploadSingleFile = async (file: UploadFile) => {
       if (!imageUrlList.value.includes(imageUrl)) {
         imageUrlList.value.push(imageUrl);
       }
-
-      // 图片上传成功后调用 OCR 接口进行识别校验(其他资质证明)
-      try {
-        const params = {
-          imageUrls: imageUrl,
-          ocrType: "BUSINESS_LICENSE",
-          storeId: userInfo.storeId,
-          storeUserId: userInfo.id
-        };
-        const res: any = await ocrRequestUrl(params);
-        if (res.code == 200) {
-          ElMessage.success("识别成功");
-        } else {
-          // OCR 识别失败:提示并删除当前图片
-          ElMessage.error(res?.msg || "识别失败,请重试");
-          const index = fileList.value.findIndex((f: any) => f.uid === file.uid);
-          if (index > -1) {
-            fileList.value.splice(index, 1);
-          }
-          const urlIndex = imageUrlList.value.indexOf(imageUrl);
-          if (urlIndex > -1) {
-            imageUrlList.value.splice(urlIndex, 1);
-          }
-          if (file.url && file.url.startsWith("blob:")) {
-            URL.revokeObjectURL(file.url);
-          }
-        }
-      } catch (error) {
-        // 调用接口异常时:提示并删除当前图片
-        const index = fileList.value.findIndex((f: any) => f.uid === file.uid);
-        if (index > -1) {
-          fileList.value.splice(index, 1);
-        }
-        const urlIndex = imageUrlList.value.indexOf(imageUrl);
-        if (urlIndex > -1) {
-          imageUrlList.value.splice(urlIndex, 1);
-        }
-        if (file.url && file.url.startsWith("blob:")) {
-          URL.revokeObjectURL(file.url);
-        }
-      }
     } else {
       throw new Error(result?.msg || "图片上传失败");
     }
@@ -620,7 +578,7 @@ const handleSubmitReplace = async () => {
     uploading.value = false;
     await initData();
   } catch (error) {
-    ElMessage.error("提交审核失败");
+    // ElMessage.error("提交审核失败");
   }
 };
 

+ 2 - 2
src/views/operationManagement/caseDetail.vue

@@ -11,10 +11,10 @@
             <div class="detail-label">所属活动 : {{ detail.activityName || detail.activityTitle || "-" }}</div>
           </div>
           <div class="detail-item">
-            <div class="detail-label">用户昵称 : {{ detail.signupName || "-" }}</div>
+            <div class="detail-label">用户昵称 : {{ detail.nickName || "-" }}</div>
           </div>
           <div class="detail-item">
-            <div class="detail-label">姓名 : {{ detail.userName || "-" }}</div>
+            <div class="detail-label">姓名 : {{ detail.signupName || "-" }}</div>
           </div>
           <div class="detail-item">
             <div class="detail-label">联系方式 : {{ detail.signupPhone || "-" }}</div>

+ 55 - 21
src/views/operationManagement/newActivity.vue

@@ -445,27 +445,41 @@ const rules = reactive({
           const end = new Date(endTime);
           start.setHours(0, 0, 0, 0);
           end.setHours(0, 0, 0, 0);
-          // if (start.getTime() >= end.getTime()) {
-          //   callback(new Error("报名开始时间必须早于报名结束时间"));
-          //   return;
-          // }
-          // 报名时间必须在活动时间范围内(活动开始日期~活动结束日期)
+
+          // 获取当天日期
+          const today = new Date();
+          today.setHours(0, 0, 0, 0);
+
+          // 1. 报名开始时间最早为当天
+          if (start.getTime() < today.getTime()) {
+            callback(new Error("报名开始时间不能早于当天"));
+            return;
+          }
+
+          // 2. 报名开始时间必须早于报名结束时间
+          if (start.getTime() >= end.getTime()) {
+            callback(new Error("报名开始时间必须早于报名结束时间"));
+            return;
+          }
+
+          // 3. 验证与活动时间的关系
           if (
             activityModel.value.activityTimeRange &&
             Array.isArray(activityModel.value.activityTimeRange) &&
             activityModel.value.activityTimeRange.length === 2
           ) {
-            const activityStart = new Date(activityModel.value.activityTimeRange[0]);
-            activityStart.setHours(0, 0, 0, 0);
             const activityEnd = new Date(activityModel.value.activityTimeRange[1]);
             activityEnd.setHours(0, 0, 0, 0);
 
-            if (start.getTime() < activityStart.getTime()) {
-              callback(new Error("报名开始时间必须在活动时间范围内"));
+            // 报名开始时间最晚要在活动结束时间之前
+            if (start.getTime() >= activityEnd.getTime()) {
+              callback(new Error("报名开始时间必须在活动结束时间之前"));
               return;
             }
+
+            // 报名结束时间不能超过活动结束时间
             if (end.getTime() > activityEnd.getTime()) {
-              callback(new Error("报名结束时间必须在活动时间范围内"));
+              callback(new Error("报名结束时间不能超过活动结束时间"));
               return;
             }
           }
@@ -595,24 +609,34 @@ const disabledDate = (time: Date) => {
   return time.getTime() < today.getTime();
 };
 
-// 禁用报名日期:只能选择活动时间范围内的日期(在活动开始日期与结束日期之间)
+// 禁用报名日期:报名开始时间最早为当天,报名结束时间不能超过活动结束时间
 const disabledSignupDate = (time: Date) => {
   const t = new Date(time);
   t.setHours(0, 0, 0, 0);
 
-  // 未选择活动时间时,不允许选择报名日期
-  if (!activityModel.value.activityTimeRange || activityModel.value.activityTimeRange.length !== 2) {
+  // 获取当天日期
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+
+  // 禁用早于当天的日期
+  if (t.getTime() < today.getTime()) {
     return true;
   }
 
-  const activityStart = new Date(activityModel.value.activityTimeRange[0]);
-  activityStart.setHours(0, 0, 0, 0);
+  // 未选择活动时间时,允许选择当天及之后的日期
+  if (!activityModel.value.activityTimeRange || activityModel.value.activityTimeRange.length !== 2) {
+    return false;
+  }
+
+  // 获取活动结束时间
   const activityEnd = new Date(activityModel.value.activityTimeRange[1]);
   activityEnd.setHours(0, 0, 0, 0);
 
-  // 只能选择活动时间区域内的日期:早于活动开始或晚于活动结束的日期禁用
-  if (t.getTime() < activityStart.getTime()) return true;
-  if (t.getTime() > activityEnd.getTime()) return true;
+  // 禁用超过活动结束时间的日期(报名结束时间不能超过活动结束时间)
+  if (t.getTime() > activityEnd.getTime()) {
+    return true;
+  }
+
   return false;
 };
 
@@ -1186,13 +1210,23 @@ watch(
       const signupEnd = new Date(activityModel.value.signupTimeRange[1]);
       signupEnd.setHours(0, 0, 0, 0);
 
-      // 报名时间必须在活动时间范围内,否则清空报名时间并提示
-      const outOfRange = signupStart.getTime() < activityStart.getTime() || signupEnd.getTime() > activityEnd.getTime();
+      // 检查报名时间是否符合新的规则:
+      // 1. 报名开始时间 >= 当天
+      // 2. 报名开始时间 < 活动结束时间
+      // 3. 报名结束时间 <= 活动结束时间
+      const today = new Date();
+      today.setHours(0, 0, 0, 0);
+
+      const outOfRange =
+        signupStart.getTime() < today.getTime() || // 报名开始时间早于当天
+        signupStart.getTime() >= activityEnd.getTime() || // 报名开始时间 >= 活动结束时间
+        signupEnd.getTime() > activityEnd.getTime(); // 报名结束时间 > 活动结束时间
+
       if (outOfRange) {
         activityModel.value.signupTimeRange = [];
         nextTick(() => {
           ruleFormRef.value?.clearValidate("signupTimeRange");
-          ElMessage.warning("活动时间已调整,报名时间必须在活动时间范围内,请重新选择报名时间");
+          ElMessage.warning("活动时间已调整,报名时间不符合规则,请重新选择报名时间");
         });
       } else {
         nextTick(() => {

+ 1 - 22
src/views/performance/edit.vue

@@ -160,34 +160,13 @@
               </el-form-item>
             </template>
             <template v-if="form.frequency === '每周定时进行'">
-              <el-form-item label="演出日期">
+              <el-form-item label="演出日期" required>
                 <el-checkbox-group v-model="form.weeklyPerformanceWeekdays" :disabled="viewMode">
                   <el-checkbox v-for="day in weekdays" :key="day" :label="day">
                     {{ day }}
                   </el-checkbox>
                 </el-checkbox-group>
               </el-form-item>
-              <el-form-item label="演出日期" required>
-                <div class="date-time-row">
-                  <el-date-picker
-                    v-model="weeklyDateStart"
-                    type="date"
-                    placeholder="年/月/日"
-                    value-format="YYYY-MM-DD"
-                    style="width: 140px"
-                    :disabled="viewMode"
-                  />
-                  <span class="sep">至</span>
-                  <el-date-picker
-                    v-model="weeklyDateEnd"
-                    type="date"
-                    placeholder="年/月/日"
-                    value-format="YYYY-MM-DD"
-                    style="width: 140px"
-                    :disabled="viewMode"
-                  />
-                </div>
-              </el-form-item>
               <el-form-item label="演出时间" required>
                 <div class="date-time-row">
                   <el-time-picker

+ 2 - 2
src/views/performance/index.vue

@@ -156,7 +156,7 @@ const columns: ColumnProps<PerformanceRow>[] = [
   },
   {
     prop: "startCreatedTime",
-    label: "演出开始时间",
+    label: "提交开始时间",
     isShow: false,
     search: {
       el: "date-picker",
@@ -169,7 +169,7 @@ const columns: ColumnProps<PerformanceRow>[] = [
   },
   {
     prop: "endCreatedTime",
-    label: "演出结束时间",
+    label: "提交结束时间",
     isShow: false,
     search: {
       el: "date-picker",

+ 86 - 5
src/views/priceList/edit.vue

@@ -43,9 +43,11 @@
             <div class="image-upload-wrap">
               <UploadImgs
                 v-model:file-list="imageFileList"
-                :api="uploadImgStore"
+                :api="handleCustomImageUpload"
                 :limit="9"
                 :file-size="5"
+                :width="'100px'"
+                :height="'100px'"
                 :disabled="viewMode"
                 class="price-list-upload"
                 @update:file-list="onImageListChange"
@@ -241,6 +243,8 @@
                 :api="uploadImgStore"
                 :limit="9"
                 :file-size="5"
+                :width="'100px'"
+                :height="'100px'"
                 :disabled="viewMode"
                 class="price-list-upload"
                 @update:file-list="onDetailImageListChange"
@@ -355,7 +359,7 @@
 </template>
 
 <script setup lang="ts" name="priceListEdit">
-import { ref, reactive, computed, onMounted, watch } from "vue";
+import { ref, reactive, computed, onMounted, watch, nextTick } from "vue";
 import { ElMessage } from "element-plus";
 import { useRoute, useRouter } from "vue-router";
 import type { FormInstance, UploadUserFile } from "element-plus";
@@ -450,6 +454,7 @@ const detailImageFileList = ref<UploadUserFile[]>([]);
 const isFoodModule = computed(() => getModuleTypeByBusinessSection(businessSection.value) === MODULE_TYPES.FOOD);
 
 const formRules = reactive({
+  images: [{ required: true, message: "请上传图片", trigger: "blur" }],
   name: [{ required: true, message: "请输入名称", trigger: "blur" }],
   totalPrice: [
     { required: true, message: "请输入价格", trigger: "blur" },
@@ -545,9 +550,75 @@ function calcRecommendedPrice() {
   }
 }
 
+// 自定义上传函数,正确处理响应格式(参考 personnelConfig/index.vue)
+const handleCustomImageUpload = async (formData: FormData): Promise<any> => {
+  try {
+    const response: any = await uploadImgStore(formData);
+
+    // API 返回格式: { code: 200, success: true, data: ["https://..."], msg: "操作成功" }
+    // 需要提取 response.data[0] 作为图片 URL
+    let imageUrl = "";
+
+    if (response && (response.code === 200 || response.code === "200")) {
+      if (Array.isArray(response.data) && response.data.length > 0) {
+        imageUrl = response.data[0];
+      } else if (typeof response.data === "string") {
+        imageUrl = response.data;
+      } else if (response.data?.fileUrl) {
+        imageUrl = response.data.fileUrl;
+      } else if (response.fileUrl) {
+        imageUrl = response.fileUrl;
+      }
+    }
+
+    if (!imageUrl) {
+      throw new Error("无法提取图片URL");
+    }
+
+    // 返回格式需要兼容 Imgs.vue 组件的处理逻辑
+    // 返回一个对象,其中 fileUrl 是图片URL,这样 uploadSuccess 会使用 response.fileUrl
+    const result = {
+      fileUrl: imageUrl, // 供 uploadSuccess 使用(会设置 uploadFile.url = response.fileUrl)
+      data: [imageUrl], // 备用
+      code: response.code,
+      msg: response.msg,
+      success: response.success
+    };
+
+    return result;
+  } catch (error) {
+    console.error("自定义上传函数 - 上传失败:", error);
+    throw error;
+  }
+};
+
+// 统一处理文件列表变化,更新 formModel.images(用逗号拼接)
+// 注意:这是多选,最后要用逗号拼接在一起
 function onImageListChange(list: UploadUserFile[]) {
-  const urls = list.map(f => (typeof f.url === "string" ? f.url : "")).filter(Boolean);
+  // 过滤掉 blob URL,只保留服务器 URL,然后用逗号拼接
+  const urls = list
+    .filter(f => {
+      const url = f.url;
+      if (!url || typeof url !== "string") return false;
+      // 排除 blob URL(临时预览 URL)
+      return !url.startsWith("blob:");
+    })
+    .map(f => {
+      const url = f.url;
+      if (url && typeof url === "string") {
+        return url.trim();
+      }
+      return "";
+    })
+    .filter(Boolean);
+
+  // 用逗号拼接所有 URL
   formModel.images = urls.join(",");
+
+  // 触发表单验证
+  if (ruleFormRef.value) {
+    ruleFormRef.value.validateField("images");
+  }
 }
 
 function onDetailImageListChange(list: UploadUserFile[]) {
@@ -558,10 +629,20 @@ function onDetailImageListChange(list: UploadUserFile[]) {
 function syncImageFileListFromModel() {
   const str = formModel.images || "";
   const urls = str ? str.split(",").filter(Boolean) : [];
-  imageFileList.value = urls.map((url, i) => ({ uid: i, name: `img-${i}`, url }));
+  imageFileList.value = urls.map((url, i) => ({
+    uid: Date.now() + i,
+    name: `img-${i}`,
+    url: url.trim(),
+    status: "success" as const
+  }));
   const detailStr = formModel.imageContent || "";
   const detailUrls = detailStr ? detailStr.split(",").filter(Boolean) : [];
-  detailImageFileList.value = detailUrls.map((url, i) => ({ uid: 1000 + i, name: `detail-${i}`, url }));
+  detailImageFileList.value = detailUrls.map((url, i) => ({
+    uid: Date.now() + 1000 + i,
+    name: `detail-${i}`,
+    url: url.trim(),
+    status: "success" as const
+  }));
 }
 
 // 详情接口返回转表单(美食接口返回 data.data.data 结构,cuisineType 1=菜品 2=套餐;非美食仅通用字段)

+ 782 - 11
src/views/storeDecoration/basicStoreInformation/index.vue

@@ -133,7 +133,7 @@
             />
           </el-form-item>
           <!-- 经纬度查询 -->
-          <el-form-item label="经纬度查询" prop="address">
+          <!-- <el-form-item label="经纬度查询" prop="address">
             <el-select
               v-model="formData.storePosition"
               filterable
@@ -148,7 +148,7 @@
                 <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> -->
           <!-- 经纬度展示 -->
           <el-form-item label="经纬度展示">
             <el-input v-model="formData.storePosition" disabled class="readonly-input">
@@ -159,9 +159,22 @@
               </template>
             </el-input>
           </el-form-item>
+          <!-- 店铺评价 -->
+          <el-form-item label="店铺评价" prop="storeEvaluate">
+            <el-input v-model="storeEvaluate1" type="text" :rows="1" placeholder="请输入评价1" show-word-limit />
+            <el-input
+              style="margin: 10px 0"
+              v-model="storeEvaluate2"
+              type="text"
+              :rows="1"
+              placeholder="请输入评价2"
+              show-word-limit
+            />
+            <el-input v-model="storeEvaluate3" type="text" :rows="1" placeholder="请输入评价3" show-word-limit />
+          </el-form-item>
           <!-- 经营板块 -->
           <el-form-item label="经营板块" prop="businessSection">
-            <el-radio-group v-model="formData.businessSection" class="business-section-radio-group">
+            <el-radio-group v-model="formData.businessSection" class="business-section-radio-group" disabled>
               <div v-for="section in businessSectionList" :key="section.dictId" class="businessSection-item">
                 <el-radio :value="section.dictId" :label="section.dictDetail">
                   {{ section.dictDetail }}
@@ -170,6 +183,14 @@
             </el-radio-group>
           </el-form-item>
 
+          <!-- 标签选择(仅当经营板块为生活服务时显示) -->
+          <el-form-item v-if="formData.businessSection === 3" label="标签选择" prop="storeTickets" required>
+            <el-radio-group v-model="formData.storeTickets" disabled>
+              <el-radio :value="1"> 装修公司 </el-radio>
+              <el-radio :value="0"> 其他 </el-radio>
+            </el-radio-group>
+          </el-form-item>
+
           <!-- 经营种类 -->
           <el-form-item label="经营种类" prop="businessTypeName">
             <el-input v-model="formData.businessTypeName" placeholder="请输入经营种类" clearable maxlength="50" />
@@ -179,7 +200,12 @@
           <el-form-item label="经营类目" prop="businessCategoryName">
             <el-input v-model="formData.businessCategoryName" placeholder="请输入经营类目" clearable maxlength="50" />
           </el-form-item>
-
+          <el-form-item label="设置收款账号">
+            <div style="color: #6c8ff8; cursor: pointer" @click="handleViewPayAccount">查看</div>
+          </el-form-item>
+          <el-form-item label="营业时间">
+            <div style="color: #6c8ff8; cursor: pointer" @click="handleViewBusinessHours">查看</div>
+          </el-form-item>
           <!-- 到期时间(只读) -->
           <!--          <el-form-item label="到期时间">-->
           <!--            <el-input v-model="formData.expirationTime" disabled class="readonly-input">-->
@@ -209,17 +235,599 @@
         <el-button type="primary" size="large" :loading="loading" @click="handleSubmit"> 保存 </el-button>
       </div>
     </el-form>
+    <!-- 收款账号弹窗(查看/编辑) -->
+    <GoReceivingAccount v-model="payAccountDialogVisible" />
+    <!-- 营业时间弹窗(查看/编辑) -->
+    <el-dialog
+      v-model="businessHoursDialogVisible"
+      title="营业时间"
+      width="560px"
+      destroy-on-close
+      append-to-body
+      @open="onBusinessHoursDialogOpen"
+    >
+      <p class="business-hours-tip">
+        特殊营业时间适用于特殊日期(如:春节/国庆/其他指定日期),设置成功后会在指定日期调整前台营业时间时显示
+      </p>
+      <div v-if="businessHoursDisplayList.length > 0" class="business-hours-list">
+        <div v-for="(item, index) in businessHoursDisplayList" :key="item.key" class="business-hours-item">
+          <div class="business-hours-item-header">
+            <span class="item-title">{{ item.title }}</span>
+            <el-tag :type="item.businessType === 1 ? 'success' : 'danger'" size="small">
+              {{ item.businessType === 1 ? "正常" : "特殊" }}
+            </el-tag>
+          </div>
+          <div class="business-hours-item-time">
+            {{ item.timeText }}
+          </div>
+          <div class="business-hours-item-actions">
+            <el-button type="primary" link @click="handleEditBusinessHours(item, index)"> 编辑 </el-button>
+            <el-button type="primary" link @click="handleDeleteBusinessHours(item, index)"> 删除 </el-button>
+          </div>
+        </div>
+      </div>
+      <el-empty v-else description="暂未添加营业时间" :image-size="80" />
+      <template #footer>
+        <div class="business-hours-dialog-footer">
+          <el-button @click="openAddNormalBusinessHours"> 添加正常营业时间 </el-button>
+          <el-button @click="openAddSpecialBusinessHours"> 添加特殊营业时间 </el-button>
+          <el-button type="primary" :loading="businessHoursSaving" @click="handleSaveBusinessHours"> 保存 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 添加/编辑 正常营业时间 -->
+    <el-dialog
+      v-model="normalBusinessHoursDialogVisible"
+      title="正常营业时间"
+      width="500px"
+      append-to-body
+      @close="resetNormalBusinessHoursForm"
+    >
+      <el-form label-width="120px">
+        <el-form-item label="请选择营业日">
+          <div class="day-buttons">
+            <el-button
+              v-for="day in weekDays"
+              :key="day.value"
+              :type="normalBusinessHoursForm.selectedDays.includes(day.value) ? 'primary' : 'default'"
+              @click="toggleNormalDay(day.value)"
+            >
+              {{ day.label }}
+            </el-button>
+          </div>
+        </el-form-item>
+        <el-form-item label="营业时间段">
+          <el-radio-group v-model="normalBusinessHoursForm.timeType">
+            <el-radio :value="'custom'"> 自定义 </el-radio>
+            <el-radio :value="'24hours'"> 24小时 </el-radio>
+          </el-radio-group>
+          <div v-if="normalBusinessHoursForm.timeType === 'custom'" class="time-picker-row">
+            <el-time-picker
+              v-model="normalBusinessHoursForm.startTime"
+              format="HH:mm"
+              value-format="HH:mm"
+              placeholder="开始时间"
+              style="width: 140px"
+            />
+            <span class="time-sep">至</span>
+            <el-time-picker
+              v-model="normalBusinessHoursForm.endTime"
+              format="HH:mm"
+              value-format="HH:mm"
+              placeholder="结束时间"
+              style="width: 140px"
+            />
+          </div>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="normalBusinessHoursDialogVisible = false"> 取消 </el-button>
+        <el-button type="primary" @click="confirmNormalBusinessHours"> 确定 </el-button>
+      </template>
+    </el-dialog>
+    <!-- 添加/编辑 特殊营业时间 -->
+    <el-dialog
+      v-model="specialBusinessHoursDialogVisible"
+      title="特殊营业时间"
+      width="500px"
+      append-to-body
+      @close="resetSpecialBusinessHoursForm"
+    >
+      <el-form label-width="120px">
+        <el-form-item label="请选择营业日">
+          <div class="day-buttons holiday-buttons">
+            <el-button
+              v-for="h in holidays"
+              :key="h.value"
+              :type="specialBusinessHoursForm.selectedHolidays.includes(h.value) ? 'primary' : 'default'"
+              @click="toggleSpecialHoliday(h.value)"
+            >
+              {{ h.label }}
+            </el-button>
+          </div>
+        </el-form-item>
+        <el-form-item label="营业时间段">
+          <el-radio-group v-model="specialBusinessHoursForm.timeType">
+            <el-radio :value="'custom'"> 自定义 </el-radio>
+            <el-radio :value="'24hours'"> 24小时 </el-radio>
+          </el-radio-group>
+          <div v-if="specialBusinessHoursForm.timeType === 'custom'" class="time-picker-row">
+            <el-time-picker
+              v-model="specialBusinessHoursForm.startTime"
+              format="HH:mm"
+              value-format="HH:mm"
+              placeholder="开始时间"
+              style="width: 140px"
+            />
+            <span class="time-sep">至</span>
+            <el-time-picker
+              v-model="specialBusinessHoursForm.endTime"
+              format="HH:mm"
+              value-format="HH:mm"
+              placeholder="结束时间"
+              style="width: 140px"
+            />
+          </div>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="specialBusinessHoursDialogVisible = false"> 取消 </el-button>
+        <el-button type="primary" @click="confirmSpecialBusinessHours"> 确定 </el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 <script setup lang="ts">
-import { ref, reactive, onMounted, watch, nextTick } from "vue";
-import { ElMessage, ElNotification } from "element-plus";
+import { ref, reactive, computed, onMounted, watch, nextTick } from "vue";
+import { ElMessage, ElNotification, ElMessageBox } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import { Search, Lock } from "@element-plus/icons-vue";
-import { getDistrict, getStoreDetail, saveStoreInfo, editStoreInfo } from "@/api/modules/storeDecoration";
+import {
+  getDistrict,
+  getStoreDetail,
+  saveStoreInfo,
+  editStoreInfo,
+  getStoreInfoBusinessHours,
+  addOrEditBusinessTime,
+  getHolidayList
+} from "@/api/modules/storeDecoration";
 import { getInputPrompt } from "@/api/modules/newLoginApi";
 import { useRoute } from "vue-router";
 import { localGet } from "@/utils";
+import GoReceivingAccount from "@/views/home/components/go-receivingAccount.vue";
+
+// 收款账号弹窗显隐
+const payAccountDialogVisible = ref(false);
+const handleViewPayAccount = () => {
+  payAccountDialogVisible.value = true;
+};
+
+// ========== 营业时间弹窗(参考 1.vue + businessHours 页) ==========
+const weekDays = [
+  { label: "周一", value: 1 },
+  { label: "周二", value: 2 },
+  { label: "周三", value: 3 },
+  { label: "周四", value: 4 },
+  { label: "周五", value: 5 },
+  { label: "周六", value: 6 },
+  { label: "周日", value: 7 }
+];
+const holidays = ref<{ label: string; value: string }[]>([]);
+const fetchHolidayList = async () => {
+  try {
+    let params = {
+      year: new Date().getFullYear(),
+      page: 1,
+      size: 500,
+      openFlag: 1,
+      holidayName: ""
+    };
+    const res: any = await getHolidayList(params);
+    const list = res?.data?.records ?? res?.data ?? (Array.isArray(res?.data) ? res.data : []);
+    holidays.value = (Array.isArray(list) ? list : []).map((item: any) => ({
+      label: item.festivalName ?? item.name ?? item.label ?? String(item.id ?? ""),
+      value: item.festivalName ?? item.name ?? item.label ?? String(item.id ?? "")
+    }));
+  } catch {
+    holidays.value = [];
+  }
+};
+interface NormalHoursItem {
+  id?: number | null;
+  days: number[];
+  timeType: "custom" | "24hours";
+  startTime?: string;
+  endTime?: string;
+}
+interface SpecialHoursItem {
+  id?: number | null;
+  holidays: string[];
+  timeType: "custom" | "24hours";
+  startTime?: string;
+  endTime?: string;
+  /** 接口返回的节日名称,用于回显 */
+  festivalName?: string;
+}
+const businessHoursDialogVisible = ref(false);
+const normalHours = ref<NormalHoursItem[]>([]);
+const specialHours = ref<SpecialHoursItem[]>([]);
+const businessHoursSaving = ref(false);
+
+const formatTimeRange = (startTime?: string, endTime?: string) => {
+  if (!startTime || !endTime) return "";
+  if (endTime <= startTime) return `${startTime}-次日${endTime}`;
+  return `${startTime}-${endTime}`;
+};
+
+const getBusinessHoursDisplayTitle = (item: NormalHoursItem | SpecialHoursItem, type: 1 | 2) => {
+  if (type === 1) {
+    const normal = item as NormalHoursItem;
+    const sorted = [...normal.days].sort((a, b) => a - b);
+    if (sorted.length === 7) return "全年";
+    const labels = sorted.map(d => weekDays.find(w => w.value === d)?.label).filter(Boolean);
+    return labels.join("、");
+  }
+  const special = item as SpecialHoursItem;
+  return special.festivalName || special.holidays.join("、");
+};
+
+const getBusinessHoursTimeText = (item: NormalHoursItem | SpecialHoursItem) => {
+  if (item.timeType === "24hours") return "24小时";
+  const start = item.startTime || "00:00";
+  const end = item.endTime || "00:00";
+  return formatTimeRange(start, end);
+};
+
+const businessHoursDisplayList = computed(() => {
+  const list: {
+    key: string;
+    title: string;
+    businessType: 1 | 2;
+    timeText: string;
+    source: "normal" | "special";
+    index: number;
+  }[] = [];
+  normalHours.value.forEach((item, i) => {
+    list.push({
+      key: `normal-${i}`,
+      title: getBusinessHoursDisplayTitle(item, 1),
+      businessType: 1,
+      timeText: getBusinessHoursTimeText(item),
+      source: "normal",
+      index: i
+    });
+  });
+  specialHours.value.forEach((item, i) => {
+    list.push({
+      key: `special-${i}`,
+      title: getBusinessHoursDisplayTitle(item, 2),
+      businessType: 2,
+      timeText: getBusinessHoursTimeText(item),
+      source: "special",
+      index: i
+    });
+  });
+  return list;
+});
+
+const convertApiDataToDisplay = (apiData: any[]) => {
+  const normalList: NormalHoursItem[] = [];
+  const specialList: SpecialHoursItem[] = [];
+  apiData.forEach((item: any) => {
+    if (item.businessType === 1) {
+      const businessDate = item.businessDate || "";
+      const dayLabels = businessDate.split("、");
+      const days: number[] = [];
+      dayLabels.forEach((label: string) => {
+        const day = weekDays.find(d => d.label === label.trim());
+        if (day) days.push(day.value);
+      });
+      const is24Hours = item.startTime === "00:00" && (item.endTime === "23:59" || item.endTime === "02:00");
+      normalList.push({
+        id: item.id,
+        days,
+        timeType: is24Hours ? "24hours" : "custom",
+        startTime: is24Hours ? undefined : item.startTime,
+        endTime: is24Hours ? undefined : item.endTime
+      });
+    } else if (item.businessType === 2) {
+      const businessDate = item.businessDate || "";
+      const holidayLabels = businessDate.split("、").map((h: string) => h.trim());
+      const is24Hours = item.startTime === "00:00" && (item.endTime === "23:59" || item.endTime === "02:00");
+      const festivalName = item.festivalName ?? item.holidayInfo?.festivalName ?? "";
+      specialList.push({
+        id: item.id,
+        holidays: holidayLabels,
+        timeType: is24Hours ? "24hours" : "custom",
+        startTime: is24Hours ? undefined : item.startTime,
+        endTime: is24Hours ? undefined : item.endTime,
+        festivalName: festivalName || undefined
+      });
+    }
+  });
+  return { normalList, specialList };
+};
+
+const convertDisplayDataToApi = () => {
+  const apiDataList: any[] = [];
+  const storeId = localGet("geeker-user")?.userInfo?.storeId || localGet("createdId");
+  if (!storeId) return apiDataList;
+  normalHours.value.forEach(item => {
+    const sortedDays = [...item.days].sort((a, b) => a - b);
+    const dayLabels = sortedDays.map(day => weekDays.find(d => d.value === day)?.label).filter(Boolean);
+    const businessDate = dayLabels.join("、");
+    const startTime = item.timeType === "24hours" ? "00:00" : item.startTime || "00:00";
+    const endTime = item.timeType === "24hours" ? "23:59" : item.endTime || "23:59";
+    apiDataList.push({
+      storeId: Number(storeId),
+      businessType: 1,
+      businessDate,
+      startTime,
+      endTime,
+      deleteFlag: 0,
+      createdTime: "",
+      createdUserId: 0,
+      updatedTime: "",
+      updatedUserId: 0
+    });
+  });
+  specialHours.value.forEach(item => {
+    const businessDate = item.holidays.join("、");
+    const startTime = item.timeType === "24hours" ? "00:00" : item.startTime || "00:00";
+    const endTime = item.timeType === "24hours" ? "23:59" : item.endTime || "23:59";
+    apiDataList.push({
+      storeId: Number(storeId),
+      businessType: 2,
+      businessDate,
+      startTime,
+      endTime,
+      deleteFlag: 0,
+      createdTime: "",
+      createdUserId: 0,
+      updatedTime: "",
+      updatedUserId: 0
+    });
+  });
+  return apiDataList;
+};
+
+const handleViewBusinessHours = () => {
+  businessHoursDialogVisible.value = true;
+};
+
+const onBusinessHoursDialogOpen = () => {
+  fetchBusinessTimeList();
+  fetchHolidayList();
+};
+
+const fetchBusinessTimeList = async () => {
+  const storeId = localGet("geeker-user")?.userInfo?.storeId || localGet("createdId");
+  if (!storeId) {
+    ElMessage.warning("未找到店铺信息");
+    return;
+  }
+  try {
+    const res: any = await getStoreInfoBusinessHours({ id: storeId });
+    const ok = res && (res.code === 200 || res.code === "200" || res.success) && res.data != null;
+    if (ok) {
+      const dataList = Array.isArray(res.data) ? res.data : res.data.list || [];
+      const { normalList, specialList } = convertApiDataToDisplay(dataList);
+      normalHours.value = normalList;
+      specialHours.value = specialList;
+    } else {
+      normalHours.value = [];
+      specialHours.value = [];
+    }
+  } catch {
+    normalHours.value = [];
+    specialHours.value = [];
+  }
+};
+
+const normalBusinessHoursDialogVisible = ref(false);
+const normalBusinessHoursEditIndex = ref<number | null>(null);
+const normalBusinessHoursForm = reactive({
+  selectedDays: [] as number[],
+  timeType: "custom" as "custom" | "24hours",
+  startTime: "",
+  endTime: ""
+});
+
+const specialBusinessHoursDialogVisible = ref(false);
+const specialBusinessHoursEditIndex = ref<number | null>(null);
+const specialBusinessHoursForm = reactive({
+  selectedHolidays: [] as string[],
+  timeType: "custom" as "custom" | "24hours",
+  startTime: "",
+  endTime: ""
+});
+
+const resetNormalBusinessHoursForm = () => {
+  normalBusinessHoursForm.selectedDays = [];
+  normalBusinessHoursForm.timeType = "custom";
+  normalBusinessHoursForm.startTime = "";
+  normalBusinessHoursForm.endTime = "";
+  normalBusinessHoursEditIndex.value = null;
+};
+
+const resetSpecialBusinessHoursForm = () => {
+  specialBusinessHoursForm.selectedHolidays = [];
+  specialBusinessHoursForm.timeType = "custom";
+  specialBusinessHoursForm.startTime = "";
+  specialBusinessHoursForm.endTime = "";
+  specialBusinessHoursEditIndex.value = null;
+};
+
+const toggleNormalDay = (day: number) => {
+  const idx = normalBusinessHoursForm.selectedDays.indexOf(day);
+  if (idx > -1) normalBusinessHoursForm.selectedDays.splice(idx, 1);
+  else normalBusinessHoursForm.selectedDays.push(day);
+};
+
+const toggleSpecialHoliday = (holiday: string) => {
+  const idx = specialBusinessHoursForm.selectedHolidays.indexOf(holiday);
+  if (idx > -1) specialBusinessHoursForm.selectedHolidays.splice(idx, 1);
+  else specialBusinessHoursForm.selectedHolidays.push(holiday);
+};
+
+const openAddNormalBusinessHours = () => {
+  if (normalHours.value.length > 0) {
+    ElMessage.warning("已有正常营业时间");
+    return;
+  }
+  normalBusinessHoursEditIndex.value = null;
+  resetNormalBusinessHoursForm();
+  normalBusinessHoursDialogVisible.value = true;
+};
+
+const openAddSpecialBusinessHours = () => {
+  if (specialHours.value.length >= 5) {
+    ElMessage.warning("最多添加5个特殊营业时间");
+    return;
+  }
+  specialBusinessHoursEditIndex.value = null;
+  resetSpecialBusinessHoursForm();
+  specialBusinessHoursDialogVisible.value = true;
+};
+
+const handleEditBusinessHours = (row: { source: "normal" | "special"; index: number }, _listIndex: number) => {
+  if (row.source === "normal") {
+    const item = normalHours.value[row.index];
+    normalBusinessHoursEditIndex.value = row.index;
+    normalBusinessHoursForm.selectedDays = [...item.days];
+    normalBusinessHoursForm.timeType = item.timeType;
+    normalBusinessHoursForm.startTime = item.startTime || "";
+    normalBusinessHoursForm.endTime = item.endTime || "";
+    normalBusinessHoursDialogVisible.value = true;
+  } else {
+    const item = specialHours.value[row.index];
+    specialBusinessHoursEditIndex.value = row.index;
+    specialBusinessHoursForm.selectedHolidays = [...item.holidays];
+    specialBusinessHoursForm.timeType = item.timeType;
+    specialBusinessHoursForm.startTime = item.startTime || "";
+    specialBusinessHoursForm.endTime = item.endTime || "";
+    specialBusinessHoursDialogVisible.value = true;
+  }
+};
+
+const confirmNormalBusinessHours = () => {
+  if (normalBusinessHoursForm.selectedDays.length === 0) {
+    ElMessage.warning("请选择营业日");
+    return;
+  }
+  if (normalBusinessHoursForm.timeType === "custom" && (!normalBusinessHoursForm.startTime || !normalBusinessHoursForm.endTime)) {
+    ElMessage.warning("请选择营业时间段");
+    return;
+  }
+  const newItem: NormalHoursItem = {
+    days: [...normalBusinessHoursForm.selectedDays],
+    timeType: normalBusinessHoursForm.timeType,
+    startTime: normalBusinessHoursForm.timeType === "custom" ? normalBusinessHoursForm.startTime : "00:00",
+    endTime: normalBusinessHoursForm.timeType === "custom" ? normalBusinessHoursForm.endTime : "23:59"
+  };
+  if (normalBusinessHoursEditIndex.value !== null) {
+    newItem.id = normalHours.value[normalBusinessHoursEditIndex.value].id;
+    normalHours.value[normalBusinessHoursEditIndex.value] = newItem;
+    ElMessage.success("编辑成功");
+  } else {
+    normalHours.value.push(newItem);
+    ElMessage.success("添加成功");
+  }
+  normalBusinessHoursDialogVisible.value = false;
+  resetNormalBusinessHoursForm();
+};
+
+const checkSpecialDateDuplicate = (holidays: string[]): boolean => {
+  const other = specialHours.value.filter((_, i) => i !== specialBusinessHoursEditIndex.value);
+  return other.some(item => holidays.some(h => item.holidays.includes(h)));
+};
+
+const confirmSpecialBusinessHours = () => {
+  if (specialBusinessHoursForm.selectedHolidays.length === 0) {
+    ElMessage.warning("请选择营业日");
+    return;
+  }
+  if (
+    specialBusinessHoursForm.timeType === "custom" &&
+    (!specialBusinessHoursForm.startTime || !specialBusinessHoursForm.endTime)
+  ) {
+    ElMessage.warning("请选择营业时间段");
+    return;
+  }
+  if (checkSpecialDateDuplicate(specialBusinessHoursForm.selectedHolidays)) {
+    ElMessage.warning("特殊营业时间的日期不能重复");
+    return;
+  }
+  const newItem: SpecialHoursItem = {
+    holidays: [...specialBusinessHoursForm.selectedHolidays],
+    timeType: specialBusinessHoursForm.timeType,
+    startTime: specialBusinessHoursForm.timeType === "custom" ? specialBusinessHoursForm.startTime : "00:00",
+    endTime: specialBusinessHoursForm.timeType === "custom" ? specialBusinessHoursForm.endTime : "23:59"
+  };
+  if (specialBusinessHoursEditIndex.value !== null) {
+    newItem.id = specialHours.value[specialBusinessHoursEditIndex.value].id;
+    specialHours.value[specialBusinessHoursEditIndex.value] = newItem;
+    ElMessage.success("编辑成功");
+  } else {
+    specialHours.value.push(newItem);
+    ElMessage.success("添加成功");
+  }
+  specialBusinessHoursDialogVisible.value = false;
+  resetSpecialBusinessHoursForm();
+};
+
+const handleDeleteBusinessHours = (row: { source: "normal" | "special"; index: number }, _listIndex: number) => {
+  const isNormal = row.source === "normal";
+  const doDelete = () => {
+    if (isNormal) {
+      if (normalHours.value.length <= 1) {
+        ElMessage.warning("至少存在一条营业时间");
+        return;
+      }
+      normalHours.value.splice(row.index, 1);
+    } else {
+      specialHours.value.splice(row.index, 1);
+    }
+    ElMessage.success("删除成功");
+  };
+  ElMessageBox.confirm("确认要删除当前营业时间吗?", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(doDelete)
+    .catch(() => {});
+};
+
+const handleSaveBusinessHours = async () => {
+  if (normalHours.value.length === 0) {
+    ElMessage.warning("至少添加一条营业时间");
+    return;
+  }
+  const storeId = localGet("geeker-user")?.userInfo?.storeId || localGet("createdId");
+  if (!storeId) {
+    ElMessage.warning("未找到店铺信息");
+    return;
+  }
+  businessHoursSaving.value = true;
+  try {
+    const apiDataList = convertDisplayDataToApi();
+    const result: any = await addOrEditBusinessTime(apiDataList);
+    if (result && (result.code === 200 || result.code === "200")) {
+      ElMessage.success("保存成功");
+      await fetchBusinessTimeList();
+      businessHoursDialogVisible.value = false;
+    } else {
+      ElMessage.error(result?.msg || "保存失败");
+      businessHoursDialogVisible.value = false;
+    }
+  } catch {
+    ElMessage.error("保存失败");
+    businessHoursDialogVisible.value = false;
+  } finally {
+    businessHoursSaving.value = false;
+  }
+};
 
 //地址集合
 const addressList = ref<any[]>([]);
@@ -231,6 +839,11 @@ const loading = ref(false);
 // 标记是否正在加载详情数据,用于防止触发 watch
 const isLoadingDetail = ref(false);
 
+// 店铺评价三个输入框的独立变量
+const storeEvaluate1 = ref("");
+const storeEvaluate2 = ref("");
+const storeEvaluate3 = ref("");
+
 // 表单数据
 const formData = reactive({
   id: "",
@@ -244,6 +857,7 @@ const formData = reactive({
   administrativeRegionDistrictAdcode: "",
   storeAddress: "",
   storeBlurb: "",
+  storeEvaluate: "",
   queryAddress: "",
   businessSection: "" as number | "",
   businessSectionName: "",
@@ -254,7 +868,8 @@ const formData = reactive({
   storePosition: "",
   storePositionLongitude: "",
   storePositionLatitude: "",
-  isChain: 0
+  isChain: 0,
+  storeTickets: "" as number | ""
 });
 
 // 经纬度查询
@@ -328,8 +943,38 @@ const rules = reactive<FormRules>({
   administrativeRegionDistrictAdcode: [{ required: true, message: "请选择区", trigger: "change" }],
   storeAddress: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
   storeBlurb: [{ required: true, message: "请输入门店简介", trigger: "blur" }],
+  storeEvaluate: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (!storeEvaluate1.value || !storeEvaluate2.value || !storeEvaluate3.value) {
+          callback(new Error("请填写完整的店铺评价(三个评价都需要填写)"));
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur"
+    }
+  ],
   queryAddress: [{ required: true, message: "请输入地址进行经纬度查询", trigger: "blur" }],
   businessSection: [{ required: true, message: "请选择经营板块", trigger: "change" }],
+  storeTickets: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        // 仅当经营板块为生活服务(值为3)时,storeTickets为必填
+        if (formData.businessSection === 3) {
+          if (value === "" || value === null || value === undefined) {
+            callback(new Error("请选择标签"));
+          } else {
+            callback();
+          }
+        } else {
+          callback();
+        }
+      },
+      trigger: "change"
+    }
+  ],
   businessTypeName: [{ required: true, message: "请输入经营种类", trigger: "blur" }],
   businessCategoryName: [{ required: true, message: "请输入经营类目", trigger: "blur" }]
 });
@@ -469,6 +1114,23 @@ const businessStatusReverseMap: Record<number, string> = {
 const handleSubmit = async () => {
   if (!formRef.value) return;
 
+  // 先验证三个店铺评价字段是否都填写
+  if (!storeEvaluate1.value || !storeEvaluate2.value || !storeEvaluate3.value) {
+    ElMessage.warning("请填写完整的店铺评价(三个评价都需要填写)");
+    // 手动触发验证,显示错误提示
+    formRef.value.validateField("storeEvaluate", () => {});
+    return;
+  }
+
+  // 验证标签选择(当经营板块为生活服务时)
+  if (formData.businessSection === 3) {
+    if (formData.storeTickets === "" || formData.storeTickets === null || formData.storeTickets === undefined) {
+      ElMessage.warning("请选择标签");
+      formRef.value.validateField("storeTickets", () => {});
+      return;
+    }
+  }
+
   await formRef.value.validate(async valid => {
     if (!valid) {
       ElMessage.warning("请完善表单信息");
@@ -493,6 +1155,11 @@ const handleSubmit = async () => {
       // 获取经营板块名称
       const selectedSection = businessSectionList.value.find(item => item.dictId === formData.businessSection);
 
+      // 将三个店铺评价用逗号拼接
+      formData.storeEvaluate = [storeEvaluate1.value, storeEvaluate2.value, storeEvaluate3.value]
+        .filter(item => item && item.trim())
+        .join(",");
+
       const submitData = {
         id: formData.id ? Number(formData.id) : undefined,
         isChain: formData.isChain,
@@ -502,6 +1169,7 @@ const handleSubmit = async () => {
         storeAddress: formData.storeAddress,
         storeArea: formData.storeArea,
         storeBlurb: formData.storeBlurb,
+        storeEvaluate: formData.storeEvaluate,
         queryAddress: formData.queryAddress,
         administrativeRegionProvinceAdcode: formData.administrativeRegionProvinceAdcode ?? "",
         administrativeRegionCityAdcode: formData.administrativeRegionCityAdcode ?? "",
@@ -514,7 +1182,10 @@ const handleSubmit = async () => {
         businessSection: formData.businessSection,
         businessSectionName: selectedSection ? selectedSection.dictDetail : "",
         businessTypeName: formData.businessTypeName,
-        businessCategoryName: formData.businessCategoryName
+        businessCategoryName: formData.businessCategoryName,
+        // 标签选择(仅当经营板块为生活服务时)
+        storeTickets:
+          formData.businessSection === 3 ? (formData.storeTickets !== "" ? formData.storeTickets : undefined) : undefined
       };
 
       let result;
@@ -567,6 +1238,19 @@ const getStoreDetailData = async () => {
       formData.businessStatus = storeData.businessStatus ?? "";
       formData.storeAddress = storeData.storeAddress ?? "";
       formData.storeBlurb = storeData.storeBlurb ?? "";
+      // 处理店铺评价:用逗号切割并分别赋值给三个输入框
+      if (storeData.storeEvaluate) {
+        const evaluateArray = storeData.storeEvaluate.split(",");
+        storeEvaluate1.value = evaluateArray[0]?.trim() || "";
+        storeEvaluate2.value = evaluateArray[1]?.trim() || "";
+        storeEvaluate3.value = evaluateArray[2]?.trim() || "";
+        formData.storeEvaluate = storeData.storeEvaluate;
+      } else {
+        storeEvaluate1.value = "";
+        storeEvaluate2.value = "";
+        storeEvaluate3.value = "";
+        formData.storeEvaluate = "";
+      }
       formData.expirationTime = storeData.expirationTime ?? "";
       formData.foodLicenceExpirationTime = storeData.foodLicenceExpirationTime ?? "";
       formData.storePosition = storeData.storePosition ?? "";
@@ -597,6 +1281,13 @@ const getStoreDetailData = async () => {
         formData.businessSection = storeData.businessSection;
         formData.businessSectionName = storeData.businessSectionName ?? "";
       }
+      // 设置标签选择(仅当经营板块为生活服务时)
+      if (storeData.businessSection === 3) {
+        formData.storeTickets =
+          storeData.storeTickets !== undefined && storeData.storeTickets !== null ? Number(storeData.storeTickets) : "";
+      } else {
+        formData.storeTickets = "";
+      }
       formData.businessTypeName = storeData.businessTypeName ?? "";
       // 如果 businessCategoryName 是 0,转换为空字符串
       const categoryName = storeData.businessCategoryName;
@@ -609,7 +1300,7 @@ const getStoreDetailData = async () => {
   }
 };
 
-// 监听经营板块变化,更新经营板块名称
+// 监听经营板块变化,更新经营板块名称(由于已禁用,此 watch 主要用于初始化)
 watch(
   () => formData.businessSection,
   newValue => {
@@ -617,6 +1308,8 @@ watch(
     if (isLoadingDetail.value) {
       return;
     }
+
+    // 更新经营板块名称
     if (newValue) {
       const selectedSection = businessSectionList.value.find(item => item.dictId === newValue);
       formData.businessSectionName = selectedSection ? selectedSection.dictDetail : "";
@@ -624,6 +1317,13 @@ watch(
       if (newValue === 3 && formData.businessCategoryName === "0") {
         formData.businessCategoryName = "";
       }
+      // 当切换到非生活服务时,清空标签选择
+      if (newValue !== 3) {
+        formData.storeTickets = "";
+      }
+    } else {
+      // 如果经营板块为空,也清空标签选择
+      formData.storeTickets = "";
     }
   }
 );
@@ -697,7 +1397,7 @@ onMounted(async () => {
 }
 
 // 响应式布局
-@media (width <= 1200px) {
+@media (width <=1200px) {
   .store-info-container {
     .store-form {
       .form-content {
@@ -716,4 +1416,75 @@ onMounted(async () => {
     flex: 0 0 auto;
   }
 }
+
+// 营业时间弹窗
+.business-hours-tip {
+  margin: 0 0 16px;
+  font-size: 13px;
+  line-height: 1.5;
+  color: #334154;
+}
+.business-hours-list {
+  max-height: 400px;
+  overflow-y: auto;
+}
+.business-hours-item {
+  padding: 14px 16px;
+  margin-bottom: 12px;
+  background: #fafafa;
+  border: 1px solid #f2f2f2;
+  border-radius: 8px;
+  .business-hours-item-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .item-title {
+      max-width: 75%;
+      overflow: hidden;
+      font-size: 14px;
+      font-weight: 600;
+      color: #334154;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+  .business-hours-item-time {
+    margin-top: 8px;
+    font-size: 13px;
+    color: #334154;
+  }
+  .business-hours-item-actions {
+    padding-top: 8px;
+    margin-top: 8px;
+    border-top: 1px solid #f2f2f2;
+    .el-button {
+      padding-left: 0;
+    }
+  }
+}
+.business-hours-dialog-footer {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  justify-content: flex-end;
+}
+.day-buttons,
+.holiday-buttons {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  .el-button {
+    margin: 0;
+  }
+}
+.time-picker-row {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  margin-top: 8px;
+}
+.time-sep {
+  font-size: 14px;
+  color: #606266;
+}
 </style>

Разница между файлами не показана из-за своего большого размера
+ 663 - 93
src/views/storeDecoration/officialPhotoAlbum/index.vue


+ 54 - 42
src/views/storeDecoration/personnelConfig/index.vue

@@ -36,27 +36,54 @@
           <div class="personnel-config-empty">暂无数据</div>
         </template>
         <template #operation="scope">
-          <el-button type="primary" link @click="editPersonnel(scope.row, 0)"> 编辑 </el-button>
-          <el-button
-            v-if="scope.row.onlineStatus === 0 || scope.row.onlineStatus === '0'"
-            type="primary"
-            link
-            @click="handleOffline(scope.row)"
-          >
-            下线
-          </el-button>
-          <el-button
-            v-else-if="scope.row.onlineStatus === 1 || scope.row.onlineStatus === '1'"
-            type="primary"
-            link
-            @click="handleOnline(scope.row)"
-          >
-            上线
-          </el-button>
-          <el-button v-if="scope.row.isPinned" type="primary" link @click="handleUnpin(scope.row)"> 取消置顶 </el-button>
-          <el-button v-else type="primary" link @click="handlePin(scope.row)"> 置顶 </el-button>
-          <el-button type="primary" link @click="deletePersonnelHandler(scope.row.id!, 0)"> 删除 </el-button>
-          <el-button type="primary" link @click="handleViewDetail(scope.row)"> 查看详情 </el-button>
+          <!-- 待审核(status === 0):只显示查看详情 -->
+          <template v-if="scope.row.status === 0 || scope.row.status === '0'">
+            <el-button type="primary" link @click="handleViewDetail(scope.row)"> 查看详情 </el-button>
+          </template>
+
+          <!-- 已驳回(status === 2):显示编辑、删除、查看详情 -->
+          <template v-else-if="scope.row.status === 2 || scope.row.status === '2'">
+            <el-button type="primary" link @click="editPersonnel(scope.row, 0)"> 编辑 </el-button>
+            <el-button type="primary" link @click="deletePersonnelHandler(scope.row.id!, 0)"> 删除 </el-button>
+            <el-button type="primary" link @click="handleViewDetail(scope.row)"> 查看详情 </el-button>
+          </template>
+
+          <!-- 已通过(status === 1):显示编辑、下线、置顶/取消置顶、删除、查看详情 -->
+          <template v-else-if="scope.row.status === 1 || scope.row.status === '1'">
+            <el-button type="primary" link @click="editPersonnel(scope.row, 0)"> 编辑 </el-button>
+            <el-button
+              v-if="scope.row.onlineStatus === 0 || scope.row.onlineStatus === '0'"
+              type="primary"
+              link
+              @click="handleOffline(scope.row)"
+            >
+              下线
+            </el-button>
+            <el-button
+              v-else-if="scope.row.onlineStatus === 1 || scope.row.onlineStatus === '1'"
+              type="primary"
+              link
+              @click="handleOnline(scope.row)"
+            >
+              上线
+            </el-button>
+            <el-button
+              v-if="scope.row.topStatus === 1 || scope.row.topStatus === '1'"
+              type="primary"
+              link
+              @click="handleUnpin(scope.row)"
+            >
+              取消置顶
+            </el-button>
+            <el-button v-else type="primary" link @click="handlePin(scope.row)"> 置顶 </el-button>
+            <el-button type="primary" link @click="deletePersonnelHandler(scope.row.id!, 0)"> 删除 </el-button>
+            <el-button type="primary" link @click="handleViewDetail(scope.row)"> 查看详情 </el-button>
+          </template>
+
+          <!-- 其他状态:默认显示查看详情 -->
+          <template v-else>
+            <el-button type="primary" link @click="handleViewDetail(scope.row)"> 查看详情 </el-button>
+          </template>
         </template>
       </ProTable>
     </div>
@@ -467,26 +494,9 @@ const columns = reactive<ColumnProps<any>[]>([
     }
   },
   {
-    prop: "submissionTime",
+    prop: "createdTime",
     label: "提交时间",
-    width: 180,
-    isShow: false,
-    search: {
-      el: "date-picker",
-      props: {
-        type: "datetimerange",
-        "range-separator": "至",
-        "start-placeholder": "请输入",
-        "end-placeholder": "请输入",
-        format: "YYYY-MM-DD HH:mm:ss",
-        "value-format": "YYYY-MM-DD HH:mm:ss"
-      },
-      key: "dateRange",
-      order: 5
-    },
-    render: (scope: any) => {
-      return scope.row.submissionTime || formatTime(scope.row.createdTime) || "—";
-    }
+    width: 180
   },
   { prop: "operation", label: "操作", fixed: "right", minWidth: 350 }
 ]);
@@ -531,10 +541,12 @@ const dataCallback = (data: any) => {
     tags: person.tag ? person.tag.split(",").map((tag: string) => tag.trim()) : [],
     description: person.personalIntroduction || "",
     onlineStatus: person.onlineStatus,
-    approvalStatus: person.status || person.approvalStatus, // 优先使用 status
+    status: person.status, // 审核状态:0 待审核,1 审核通过,2 审核拒绝
+    approvalStatus: person.status, // 保留 approvalStatus 用于表格列显示
     submissionTime: person.submissionTime || person.createTime || person.createdTime,
     createdTime: person.createTime || person.createdTime,
-    isPinned: person.isPinned || person.isTop || false
+    topStatus: person.topStatus, // 置顶状态:0 未置顶,1 已置顶
+    isPinned: person.topStatus === 1 || person.topStatus === "1" || person.isPinned || person.isTop || false // 保留 isPinned 用于兼容
   }));
 
   return {

+ 24 - 51
src/views/storeDecoration/storeHeadMap/index.vue

@@ -91,12 +91,12 @@
               :file-size="20"
               :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
               :border-radius="'8px'"
-              :api="singleImageUploadApi"
+              :api="uploadImageResult"
               :on-success="handleStoreOcrAfterUpload"
               :show-success-notification="false"
             >
               <template #tip>
-                <div class="upload-tip">{{ formData }} ({{ formData.singleImage ? "1" : "0" }}/1)</div>
+                <div class="upload-tip">({{ formData.singleImage ? "1" : "0" }}/1)</div>
               </template>
             </UploadImg>
           </el-form-item>
@@ -114,7 +114,7 @@
                 :width="'200px'"
                 :height="'112px'"
                 :border-radius="'8px'"
-                :api="multipleImageUploadApi"
+                :api="uploadImageResult"
                 :on-success="handleStoreOcrAfterUploadMore"
                 :show-success-notification="false"
               >
@@ -232,56 +232,17 @@ const previewData = reactive({
   reviews: 1853
 });
 
-// 显示的多图预览(最多显示3张)- 始终显示多图数据,不依赖当前模式
+// 显示的多图预览
 const displayImages = computed(() => {
   return formData.multipleImages.slice(0, 3).map(item => item.url || "");
 });
 
-// 单图模式:图片校验函数(已移除尺寸限制)
-const validateSingleImageDimensions = (file: File): Promise<boolean> => {
-  return new Promise((resolve, reject) => {
-    // 直接通过,不进行任何校验
-    resolve(true);
-  });
-};
-
-// 多图模式:图片校验函数(已移除比例限制)
-const validateMultipleImageDimensions = (file: File): Promise<boolean> => {
-  return new Promise((resolve, reject) => {
-    // 直接通过,不进行任何校验
-    resolve(true);
-  });
-};
-
-// 单图模式:自定义上传API
-const singleImageUploadApi = async (formData: FormData): Promise<any> => {
-  // 已移除尺寸校验,直接上传
-
-  // 校验通过后调用实际上传接口
-  const response: any = await uploadImg(formData);
-  // 处理返回格式:{ code, success, data: string[], msg }
-  if (response && response.code === 200 && response.data && Array.isArray(response.data) && response.data.length > 0) {
-    return {
-      data: {
-        fileUrl: response.data[0] // 取数组第一个元素作为图片URL
-      }
-    };
-  }
-  throw new Error(response?.msg || "上传失败");
-};
-
-// 多图模式:自定义上传API
-const multipleImageUploadApi = async (formData: FormData): Promise<any> => {
-  // 已移除比例校验,直接上传
-
-  // 校验通过后调用实际上传接口
+// 处理上传图片
+const uploadImageResult = async (formData: FormData): Promise<any> => {
   const response: any = await uploadImg(formData);
-  // 处理返回格式:{ code, success, data: string[], msg }
-  if (response && response.code === 200 && response.data && Array.isArray(response.data) && response.data.length > 0) {
+  if (response.code == 200) {
     return {
-      data: {
-        fileUrl: response.data[0] // 取数组第一个元素作为图片URL
-      }
+      fileUrl: response.data[0] // 取数组第一个元素作为图片URL,直接返回 fileUrl
     };
   }
   throw new Error(response?.msg || "上传失败");
@@ -385,7 +346,7 @@ const loadHeadImgByMode = async (targetMode: "single" | "multiple") => {
     const imgType = targetMode === "single" ? 20 : 21;
     const res: any = await getStoreHeadImg(storeId, imgType);
 
-    if (res && (res.code === 200 || res.code === "200") && res.data) {
+    if (res.code == 200) {
       const dataAny = res.data as any;
       const imgList = Array.isArray(dataAny) ? dataAny : dataAny.storeImgList || [];
 
@@ -470,15 +431,27 @@ const getStoreHeadImgData = async () => {
       }
     }
 
-    // 根据数据情况设置默认模式:优先单图,如果没有单图则使用多图
-    if (formData.singleImage) {
-      mode.value = "single";
+    // 根据数据情况设置默认模式:优先根据imgModeActive,如果有多图则使用多图模式
+    if (imgModeActive.value !== null && imgModeActive.value !== undefined) {
+      // 如果后端返回了imgMode,优先使用后端返回的值
+      // 0表示单图模式,1表示多图模式
+      mode.value = imgModeActive.value === 0 ? "single" : "multiple";
+      // 如果是多图模式,初始化拖拽排序
+      if (mode.value === "multiple") {
+        nextTick(() => {
+          initDragSort();
+        });
+      }
     } else if (formData.multipleImages.length > 0) {
+      // 如果有多图数据,优先显示多图模式
       mode.value = "multiple";
       // 初始化拖拽排序
       nextTick(() => {
         initDragSort();
       });
+    } else if (formData.singleImage) {
+      // 如果没有多图但有单图,显示单图模式
+      mode.value = "single";
     }
   } catch (error) {
     console.error("获取头图失败:", error);

+ 2 - 2
src/views/ticketManagement/couponDetail.vue

@@ -71,12 +71,12 @@
         <div class="model">
           <h3 style="font-weight: bold">领取规则:</h3>
           <!-- 用户领取规则 -->
-          <div class="detail-item">
+          <!-- <div class="detail-item">
             <div class="detail-label">用户领取规则</div>
             <div class="detail-value">
               {{ getClaimRuleText() }}
             </div>
-          </div>
+          </div> -->
           <!-- 用户是否需要收藏店铺领取 -->
           <div class="detail-item">
             <div class="detail-label">用户是否需要收藏店铺领取</div>

+ 3 - 3
src/views/ticketManagement/newCoupon.vue

@@ -83,7 +83,7 @@
             </el-input>
           </el-form-item>
           <!-- 用户领取规则 -->
-          <el-form-item label="用户领取规则" prop="claimRule" class="claim-rule-item">
+          <!-- <el-form-item label="用户领取规则" prop="claimRule" class="claim-rule-item">
             <template #label>
               <span>用户领取规则</span>
               <span class="label-tip">(用户间隔多久可以领取一次)</span>
@@ -93,7 +93,7 @@
               <el-radio value="week"> 每周一领 </el-radio>
               <el-radio value="month"> 每月一领 </el-radio>
             </el-radio-group>
-          </el-form-item>
+          </el-form-item> -->
           <!-- 用户是否需要收藏店铺领取 -->
           <el-form-item label="用户是否需要收藏店铺领取" prop="attentionCanReceived">
             <el-radio-group v-model="couponModel.attentionCanReceived" class="radio-group">
@@ -117,7 +117,7 @@
     </el-form>
     <!-- 底部按钮区域 -->
     <div class="button-container">
-      <el-button @click="() => handleSubmit('draft')"> 存草稿 </el-button>
+      <!-- <el-button @click="() => handleSubmit('draft')"> 存草稿 </el-button> -->
       <el-button type="primary" @click="() => handleSubmit()">
         {{ type === "add" ? "新建优惠券" : "编辑优惠券" }}
       </el-button>

Некоторые файлы не были показаны из-за большого количества измененных файлов