Jelajahi Sumber

入驻加营业时间

zhuli 1 bulan lalu
induk
melakukan
6392f23637

+ 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);
 };
 
 //获取营业时间列表

+ 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 判断是否有操作权限

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

+ 51 - 2
src/views/home/components/go-flow.vue

@@ -224,6 +224,17 @@
                   {{ 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
@@ -275,6 +286,13 @@
     @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"
@@ -292,6 +310,7 @@ import { Plus } from "@element-plus/icons-vue";
 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";
 
@@ -306,6 +325,12 @@ 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: "个人实名"
@@ -628,6 +653,7 @@ const setStep = (val: number) => {
 // 第二步表单
 const step2FormRef = ref<FormInstance>();
 const step2Form = reactive({
+  appointment: "",
   businessSecondMeal: 1,
   storeName: "",
   storeCapacity: 1,
@@ -1264,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("确认提交入驻申请吗?", "提示", {

+ 650 - 3
src/views/storeDecoration/basicStoreInformation/index.vue

@@ -203,6 +203,9 @@
           <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">-->
@@ -234,14 +237,160 @@
     </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";
@@ -253,6 +402,433 @@ 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[]>([]);
 const addressMap = new Map();
@@ -838,4 +1414,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>