zhuli 3 недель назад
Родитель
Сommit
1879c45b88

+ 184 - 90
src/views/appoinmentManagement/infoManagement.vue

@@ -167,40 +167,42 @@
           </el-select>
         </el-form-item>
         <template v-for="(item, idx) in form.specialList" :key="item.name + '-' + idx">
-          <el-form-item :label="item.name">
-            <el-radio-group v-model="item.allDay">
-              <el-radio label="allDay" :disabled="!!specialAllDayDisabledMap[item.name]"> 全天 </el-radio>
-              <el-radio label="notAllDay"> 非全天 </el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <div v-if="item.allDay === 'notAllDay'" class="special-time-row">
-            <el-form-item label="开始时间" label-width="450px">
-              <el-time-picker
-                v-model="item.startTime"
-                format="HH:mm"
-                value-format="HH:mm"
-                placeholder="选择开始时间"
-                style="width: 160px"
-                :disabled-hours="() => specialDisabledHours()"
-                :disabled-minutes="(h: number) => specialDisabledMinutes(h)"
-                @visible-change="v => v && initSpecialPickerRange(item.name, 'start')"
-                @change="() => onSpecialTimeChange(item.name, 'start')"
-              />
-            </el-form-item>
-            <el-form-item label="结束时间" label-width="450px">
-              <el-time-picker
-                v-model="item.endTime"
-                format="HH:mm"
-                value-format="HH:mm"
-                placeholder="选择结束时间(次日)"
-                style="width: 160px"
-                :disabled-hours="() => specialDisabledHours()"
-                :disabled-minutes="(h: number) => specialDisabledMinutes(h)"
-                @visible-change="v => v && initSpecialPickerRange(item.name, 'end')"
-                @change="() => onSpecialTimeChange(item.name, 'end')"
-              />
+          <template v-if="showSpecialRowTimeUI(item)">
+            <el-form-item :label="item.name">
+              <el-radio-group v-model="item.allDay">
+                <el-radio label="allDay" :disabled="!!specialAllDayDisabledMap[item.name]"> 全天 </el-radio>
+                <el-radio label="notAllDay"> 非全天 </el-radio>
+              </el-radio-group>
             </el-form-item>
-          </div>
+            <div v-if="item.allDay === 'notAllDay'" class="special-time-row" style="display: flex">
+              <el-form-item label="时间设置" label-width="450px">
+                <el-time-picker
+                  v-model="item.startTime"
+                  format="HH:mm"
+                  value-format="HH:mm"
+                  placeholder="选择开始时间"
+                  style="width: 160px"
+                  :disabled-hours="() => specialDisabledHours()"
+                  :disabled-minutes="(h: number) => specialDisabledMinutes(h)"
+                  @visible-change="v => v && initSpecialPickerRange(item.name, 'start')"
+                  @change="() => onSpecialTimeChange(item.name, 'start')"
+                />
+              </el-form-item>
+              <el-form-item label="" label-width="10px">
+                <el-time-picker
+                  v-model="item.endTime"
+                  format="HH:mm"
+                  value-format="HH:mm"
+                  placeholder="选择结束时间"
+                  style="width: 160px"
+                  :disabled-hours="() => specialDisabledHours()"
+                  :disabled-minutes="(h: number) => specialDisabledMinutes(h)"
+                  @visible-change="v => v && initSpecialPickerRange(item.name, 'end')"
+                  @change="() => onSpecialTimeChange(item.name, 'end')"
+                />
+              </el-form-item>
+            </div>
+          </template>
         </template>
       </el-form>
     </div>
@@ -252,7 +254,9 @@ const form = reactive({
     startTime: string;
     endTime: string;
     id?: number;
-    essentialId?: number;
+    essentialId?: number | string;
+    /** 与商家端:用户在选择节日中勾选的行才展示时间配置;接口回显匹配上的也为 true */
+    userPickedSpecial?: boolean;
   }[]
 });
 
@@ -312,6 +316,57 @@ const allowedFestivalSet = computed(() => {
   return set;
 });
 
+/** 门店单条 businessType=2:均为 00:00 为全天,否则非全天;元旦固定非全天(与商家端 infoManagement) */
+function allDayFromStoreSpecialRow(storeRow: any): "allDay" | "notAllDay" {
+  if (!storeRow || Number(storeRow.businessType) !== 2) return "notAllDay";
+  const nm = String(
+    (storeRow.holidayInfo && storeRow.holidayInfo.festivalName) || storeRow.businessDate || storeRow.holidayType || ""
+  ).trim();
+  if (nm === "元旦") return "notAllDay";
+  const st = String(storeRow.startTime ?? "").trim();
+  const et = String(storeRow.endTime ?? "").trim();
+  return st === "00:00" && et === "00:00" ? "allDay" : "notAllDay";
+}
+
+function allDayFromStoreByHolidayName(holidayName: string): "allDay" | "notAllDay" {
+  const n = String(holidayName || "").trim();
+  if (!n) return "allDay";
+  const list = Array.isArray(listFromStoreInfo.value) ? listFromStoreInfo.value : [];
+  const row = list.find((item: any) => {
+    if (Number(item.businessType) !== 2) return false;
+    const fn = (item.holidayInfo && item.holidayInfo.festivalName != null ? String(item.holidayInfo.festivalName) : "").trim();
+    const bd = item.businessDate != null ? String(item.businessDate).trim() : "";
+    const ht = item.holidayType != null ? String(item.holidayType).trim() : "";
+    return fn === n || bd === n || ht === n;
+  });
+  return row ? allDayFromStoreSpecialRow(row) : "allDay";
+}
+
+function getStoreSpecialRowMeta(name: string): { id?: number; essentialId?: number | string } {
+  const n = String(name || "").trim();
+  const list = Array.isArray(listFromStoreInfo.value) ? listFromStoreInfo.value : [];
+  const row = list.find((item: any) => {
+    if (Number(item.businessType) !== 2) return false;
+    const fn = (item.holidayInfo && item.holidayInfo.festivalName != null ? String(item.holidayInfo.festivalName) : "").trim();
+    const bd = item.businessDate != null ? String(item.businessDate).trim() : "";
+    const ht = item.holidayType != null ? String(item.holidayType).trim() : "";
+    return fn === n || bd === n || ht === n;
+  });
+  if (!row) return {};
+  return { id: row.id != null ? Number(row.id) : undefined, essentialId: row.essentialId };
+}
+
+function rowHasSpecialTimeConfigured(s: (typeof form.specialList)[0]): boolean {
+  const st = String(s.startTime ?? "").trim();
+  const et = String(s.endTime ?? "").trim();
+  if (s.allDay === "allDay" && st === "00:00" && et === "00:00") return true;
+  return st !== "" && et !== "";
+}
+
+function showSpecialRowTimeUI(s: (typeof form.specialList)[0]): boolean {
+  return rowHasSpecialTimeConfigured(s) || s.userPickedSpecial === true;
+}
+
 const selectedHolidayNames = computed({
   get: () => form.specialList.map(s => s.name),
   set: (val: string[]) => {
@@ -319,7 +374,23 @@ const selectedHolidayNames = computed({
     form.specialList.forEach(s => {
       existingMap[s.name] = s;
     });
-    form.specialList = val.map(name => existingMap[name] || { name, allDay: "allDay", startTime: "", endTime: "" });
+    form.specialList = val.map(name => {
+      const ex = existingMap[name];
+      const ad = allDayFromStoreByHolidayName(name);
+      const meta = getStoreSpecialRowMeta(name);
+      if (ex) {
+        return { ...ex, allDay: ad, userPickedSpecial: true };
+      }
+      return {
+        name,
+        allDay: ad,
+        startTime: "",
+        endTime: "",
+        userPickedSpecial: true,
+        id: meta.id,
+        essentialId: meta.essentialId
+      };
+    });
   }
 });
 
@@ -573,20 +644,8 @@ async function fetchStoreInfoBusinessHours() {
     const list = Array.isArray(res?.data) ? res.data : [];
     listFromStoreInfo.value = list;
     const normal = list.find((item: any) => item.businessType === 1);
-    const specialItems = list.filter((item: any) => item.businessType === 2);
-    if (specialItems.length && !form.specialList.length) {
-      form.specialList = specialItems.map((s: any, index: number) => {
-        const name = (s.holidayInfo && s.holidayInfo.festivalName) || s.businessDate || s.holidayType || `特殊营业${index + 1}`;
-        return {
-          name,
-          allDay: "allDay" as const,
-          startTime: "",
-          endTime: "",
-          id: s.id,
-          essentialId: s.essentialId
-        };
-      });
-    }
+    /** 与商家端:特殊营业由 getBookingBusinessHours + applyListBookingToForm 回填,此处不占位 */
+    form.specialList = [];
     if (normal && !form.normalBook.startTime && !form.normalBook.endTime) {
       form.normalBook.startTime = normal.startTime || "";
       form.normalBook.endTime = normal.endTime || "23:59";
@@ -614,42 +673,64 @@ function applyListBookingToForm() {
     }
   }
 
+  const storeInfoList = Array.isArray(listFromStoreInfo.value) ? listFromStoreInfo.value : [];
+  const specialFromStore = storeInfoList.filter((item: any) => item.businessType === 2);
   const specialFromBooking = list.filter((item: any) => item.businessType === 2);
-  const specialFromStore = listFromStoreInfo.value.filter((item: any) => item.businessType === 2);
-  if (specialFromStore.length && form.specialList.length) {
-    form.specialList = specialFromStore.map((s: any, index: number) => {
-      const name = (s.holidayInfo && s.holidayInfo.festivalName) || s.businessDate || s.holidayType || `特殊营业${index + 1}`;
-      const match = specialFromBooking.find(
-        (b: any) =>
-          String(b.id) === String(s.id) ||
-          String(b.essentialId) === String(s.essentialId) ||
-          (b.holidayType != null && String(b.holidayType).trim() === String(name).trim())
-      );
-      if (!match) {
-        return {
-          name,
-          allDay: "allDay" as const,
-          startTime: "",
-          endTime: "",
-          id: s.id,
-          essentialId: s.essentialId
-        };
-      }
-      const startTime = match.startTime || "";
-      const endTimeRaw = match.endTime || "23:59";
-      /** 与 merchant 一致:特殊营业结束时间回显不扣「结束前不可预订」 */
-      const endTime = endTimeRaw;
-      const isAllDay = match.bookingTimeType === 1 || (startTime === "00:00" && endTimeRaw === "00:00");
+  const bookingEmpty = !list.length;
+  const useStoreInfoForSpecial = !list.length || !specialFromBooking.length;
+
+  /** 预约接口返回的 holidayType 集合;仅在此集合内的门店节日才回显预订时间(与商家端 applyListBookingToForm) */
+  const bookingHolidayTypeSet = new Set(
+    specialFromBooking.map((b: any) => (b.holidayType != null ? String(b.holidayType).trim() : "")).filter(Boolean)
+  );
+
+  if (useStoreInfoForSpecial) {
+    if (bookingEmpty) {
+      form.specialList = [];
+      return;
+    }
+    /** 有预约数据但未配置特殊营业行:不占位,用户先选节日 */
+    form.specialList = [];
+    return;
+  }
+
+  /** 仅 getBookingBusinessHours 中 holidayType 与门店节日匹配时回显时间;allDay 以门店营业时间为准 */
+  form.specialList = specialFromStore.map((s: any, index: number) => {
+    const name = (s.holidayInfo && s.holidayInfo.festivalName) || s.businessDate || s.holidayType || `特殊营业${index + 1}`;
+    const storeFestivalName = (
+      s.holidayInfo && s.holidayInfo.festivalName != null ? String(s.holidayInfo.festivalName) : ""
+    ).trim();
+    const shouldEchoTime = bookingHolidayTypeSet.has(storeFestivalName);
+    const match = specialFromBooking.find(
+      (b: any) =>
+        String(b.id) === String(s.id) ||
+        String(b.essentialId) === String(s.essentialId) ||
+        (storeFestivalName !== "" && (b.holidayType != null ? String(b.holidayType) : "").trim() === storeFestivalName)
+    );
+    if (!match || !shouldEchoTime) {
       return {
         name,
-        allDay: isAllDay ? "allDay" : "notAllDay",
-        startTime,
-        endTime,
+        allDay: allDayFromStoreSpecialRow(s),
+        startTime: "",
+        endTime: "",
         id: s.id,
-        essentialId: s.essentialId
+        essentialId: s.essentialId,
+        userPickedSpecial: false
       };
-    });
-  }
+    }
+    const startTime = match.startTime || "";
+    const endTimeRaw = match.endTime || "23:59";
+    const endTime = endTimeRaw;
+    return {
+      name,
+      allDay: allDayFromStoreSpecialRow(s),
+      startTime,
+      endTime,
+      id: s.id,
+      essentialId: s.essentialId,
+      userPickedSpecial: true
+    };
+  });
 }
 
 async function fetchBookingBusinessHours() {
@@ -746,6 +827,24 @@ async function onSave() {
   saveLoading.value = true;
   try {
     const businessDate = listFromStoreInfo.value.find((item: any) => item.businessType === 1)?.businessDate;
+    const specialListBuilt: any[] = [];
+    let sort = 0;
+    for (const cur of form.specialList) {
+      if (!showSpecialRowTimeUI(cur)) continue;
+      const isAllDay = cur.allDay === "allDay";
+      const st = (cur.startTime || "").trim();
+      const et = (cur.endTime || "").trim();
+      if (!isAllDay && !st && !et) continue;
+      specialListBuilt.push({
+        bookingTimeType: isAllDay ? 1 : 0,
+        businessType: 2,
+        holidayType: cur.name || "",
+        startTime: cur.startTime || "00:00",
+        endTime: cur.endTime || "00:00",
+        sort: sort++,
+        essentialId: cur.essentialId
+      });
+    }
     const params: Record<string, any> = {
       storeId: Number(storeId),
       retainPositionFlag: form.base.keepPosition === "keep" ? 1 : 0,
@@ -762,17 +861,11 @@ async function onSave() {
         bookingTimeType: form.normalBook.timeType === "allDay" ? 1 : 0,
         startTime: form.normalBook.startTime || "",
         endTime: form.normalBook.endTime || ""
-      },
-      specialBusinessHoursList: form.specialList.map((cur, i) => ({
-        bookingTimeType: cur.allDay === "allDay" ? 1 : 0,
-        businessType: 2,
-        holidayType: cur.name || "",
-        startTime: cur.startTime || "",
-        endTime: cur.endTime || "",
-        sort: i,
-        essentialId: cur.essentialId
-      }))
+      }
     };
+    if (specialListBuilt.length > 0) {
+      params.specialBusinessHoursList = specialListBuilt;
+    }
     await bookingSettingsSave(params);
     ElMessage.success("保存成功");
   } catch (e: any) {
@@ -829,6 +922,7 @@ onMounted(async () => {
     margin-bottom: 18px;
   }
   :deep(.el-form-item__label) {
+    align-items: center;
     font-weight: 400;
     line-height: 20px;
     color: #606266;

+ 130 - 43
src/views/storeDecoration/businessHours/index.vue

@@ -79,18 +79,19 @@
       </template>
     </el-dialog>
 
-    <!-- 特殊营业时间 -->
+    <!-- 特殊营业时间(与商家端 addBody:多选节日 = 多条记录,每条一个 businessDate + holidayId) -->
     <el-dialog v-model="specialDialogVisible" :title="specialDialogTitle" width="600px" @close="resetSpecialForm">
       <el-form :model="specialForm" label-width="140px">
-        <el-form-item label="请选择营业日">
+        <el-form-item label="请选择特殊日期">
           <div class="holiday-buttons">
             <el-button
               v-for="holiday in holidays"
               :key="holiday.value + '-' + holiday.id"
               :type="specialForm.selectedHolidays.includes(holiday.value) ? 'primary' : 'default'"
+              class="holiday-btn"
               @click="toggleHoliday(holiday.value)"
             >
-              {{ holiday.label }}
+              <span class="holiday-btn-label">{{ holiday.label }}</span>
             </el-button>
           </div>
         </el-form-item>
@@ -119,9 +120,12 @@
         </el-form-item>
       </el-form>
       <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="specialDialogVisible = false"> 取消 </el-button>
-          <el-button type="primary" @click="confirmSpecialHours"> 确定 </el-button>
+        <div class="dialog-footer dialog-footer-special">
+          <el-button @click="resetSpecialFormFields"> 重置 </el-button>
+          <div class="dialog-footer-right">
+            <el-button @click="specialDialogVisible = false"> 取消 </el-button>
+            <el-button type="primary" @click="confirmSpecialHours"> 确定 </el-button>
+          </div>
         </div>
       </template>
     </el-dialog>
@@ -144,8 +148,8 @@ const weekDays = [
   { label: "周日", value: 7 }
 ];
 
-/** 与商家端 1.vue / go-businessHours:节日带 id,保存时作 essentialId */
-const holidays = ref<{ label: string; value: string; id: number }[]>([]);
+/** 与商家端 addBody getHolidayList:name/value/id + festivalDate、specialEndTime 展示 */
+const holidays = ref<{ label: string; value: string; id: number; festivalDate?: string; specialEndTime?: string }[]>([]);
 
 const loadHolidays = async () => {
   try {
@@ -162,7 +166,9 @@ const loadHolidays = async () => {
       .map((record: any) => ({
         label: record.festivalName ?? record.holidayName ?? record.name ?? "",
         value: record.festivalName ?? record.holidayName ?? record.name ?? "",
-        id: record.id != null ? Number(record.id) : 0
+        id: record.id != null ? Number(record.id) : 0,
+        festivalDate: record.festivalDate != null ? String(record.festivalDate) : "",
+        specialEndTime: record.endTime != null ? String(record.endTime) : ""
       }))
       .filter((item: { label: string; value: string }) => item.value);
   } catch {
@@ -281,13 +287,14 @@ const normalForm = reactive({
 
 const specialDialogVisible = ref(false);
 const specialEditIndex = ref<number | null>(null);
-const specialDialogTitle = computed(() => (specialEditIndex.value !== null ? "特殊营业时间" : "添加特殊营业时间"));
+const specialDialogTitle = computed(() => "营业时间");
 
 const specialForm = reactive({
   selectedHolidays: [] as string[],
   timeType: "custom" as "custom" | "24hours",
-  startTime: "",
-  endTime: ""
+  /** 与商家端 addBody 默认:自定义 + 00:00 */
+  startTime: "00:00",
+  endTime: "00:00"
 });
 
 function getStoreId(): number | string | null {
@@ -411,9 +418,10 @@ function handleEditRow(row: { source: "normal" | "special"; index: number }) {
     const item = specialHours.value[row.index];
     specialForm.selectedHolidays = [...item.holidays];
     specialForm.timeType = item.timeType;
-    specialForm.startTime = item.timeType === "custom" ? item.startTime || "" : "";
-    specialForm.endTime = item.timeType === "custom" ? item.endTime || "" : "";
+    specialForm.startTime = item.timeType === "custom" ? item.startTime || "00:00" : "00:00";
+    specialForm.endTime = item.timeType === "custom" ? item.endTime || "00:00" : "00:00";
     specialDialogVisible.value = true;
+    loadHolidays();
   }
 }
 
@@ -442,6 +450,7 @@ function openAddSpecialDialog() {
   specialEditIndex.value = null;
   resetSpecialForm();
   specialDialogVisible.value = true;
+  loadHolidays();
 }
 
 function resetNormalForm() {
@@ -455,11 +464,19 @@ function resetNormalForm() {
 function resetSpecialForm() {
   specialForm.selectedHolidays = [];
   specialForm.timeType = "custom";
-  specialForm.startTime = "";
-  specialForm.endTime = "";
+  specialForm.startTime = "00:00";
+  specialForm.endTime = "00:00";
   specialEditIndex.value = null;
 }
 
+/** 弹窗内「重置」:与商家端 addBody reset 一致,不关弹窗、不清编辑下标 */
+function resetSpecialFormFields() {
+  specialForm.selectedHolidays = [];
+  specialForm.timeType = "custom";
+  specialForm.startTime = "00:00";
+  specialForm.endTime = "00:00";
+}
+
 function confirmNormalHours() {
   if (normalForm.selectedDays.length === 0) {
     ElMessage.warning("请选择营业日");
@@ -487,50 +504,92 @@ function confirmNormalHours() {
   ElMessage.success(editingNormal ? "编辑成功" : "添加成功");
 }
 
-function checkDateDuplicate(holidayValues: string[]): boolean {
-  const other = specialHours.value.filter((_, i) => i !== specialEditIndex.value);
+function checkDateDuplicate(holidayValues: string[], excludeIndex: number | null): boolean {
+  const other = specialHours.value.filter((_, i) => i !== excludeIndex);
   return other.some(item => holidayValues.some(h => item.holidays.includes(h)));
 }
 
+/**
+ * 与商家端 businessHours handleConfirm + addBody submit:
+ * - 新增:多选节日 → 多条记录,每条 businessDate 为单个节日名 + 对应 holidayId;最多 5 条;逐条 unshift 顺序与小程序一致
+ * - 编辑:仅取第一个选中项(与小程序 list.length>1 时 list=[list[0]])
+ */
 function confirmSpecialHours() {
   if (specialForm.selectedHolidays.length === 0) {
-    ElMessage.warning("请选择营业日");
+    ElMessage.warning("请选择特殊日期");
     return;
   }
   if (specialForm.timeType === "custom" && (!specialForm.startTime || !specialForm.endTime)) {
     ElMessage.warning("请选择营业时间段");
     return;
   }
-  if (checkDateDuplicate(specialForm.selectedHolidays)) {
-    ElMessage.warning("选择的日期重复");
+
+  const editingSpecial = specialEditIndex.value !== null;
+  const excludeIdx = editingSpecial ? specialEditIndex.value : null;
+
+  if (editingSpecial) {
+    const selected = [specialForm.selectedHolidays[0]];
+    if (checkDateDuplicate(selected, excludeIdx)) {
+      ElMessage.warning("选择的日期重复");
+      return;
+    }
+    const val = selected[0];
+    const holidayOpt = holidays.value.find(h => h.value === val);
+    const holidayIdFromList = holidayOpt?.id != null && holidayOpt.id > 0 ? holidayOpt.id : undefined;
+    const prev = specialHours.value[specialEditIndex.value!];
+    const newItem: SpecialHoursItem = {
+      holidays: [val],
+      timeType: specialForm.timeType,
+      startTime: specialForm.timeType === "custom" ? specialForm.startTime : undefined,
+      endTime: specialForm.timeType === "custom" ? specialForm.endTime : undefined,
+      festivalName: val,
+      holidayId: holidayIdFromList ?? prev.holidayId,
+      essentialId: holidayIdFromList ?? prev.essentialId
+    };
+    newItem.id = prev.id;
+    if (specialForm.timeType === "24hours") {
+      newItem.startTime = undefined;
+      newItem.endTime = undefined;
+    }
+    specialHours.value[specialEditIndex.value!] = newItem;
+    specialDialogVisible.value = false;
+    resetSpecialForm();
+    ElMessage.success("编辑成功");
     return;
   }
-  const firstVal = specialForm.selectedHolidays[0];
-  const holidayOpt = holidays.value.find(h => h.value === firstVal);
-  const holidayIdFromList = holidayOpt?.id != null && holidayOpt.id > 0 ? holidayOpt.id : undefined;
-
-  const newItem: SpecialHoursItem = {
-    holidays: [...specialForm.selectedHolidays],
-    timeType: specialForm.timeType,
-    startTime: specialForm.timeType === "custom" ? specialForm.startTime : undefined,
-    endTime: specialForm.timeType === "custom" ? specialForm.endTime : undefined,
-    festivalName: specialForm.selectedHolidays.join("、")
-  };
-  newItem.holidayId =
-    holidayIdFromList ?? (specialEditIndex.value !== null ? specialHours.value[specialEditIndex.value].holidayId : undefined);
-  newItem.essentialId =
-    newItem.holidayId ?? (specialEditIndex.value !== null ? specialHours.value[specialEditIndex.value].essentialId : undefined);
 
-  const editingSpecial = specialEditIndex.value !== null;
-  if (editingSpecial && specialEditIndex.value !== null) {
-    newItem.id = specialHours.value[specialEditIndex.value].id;
-    specialHours.value[specialEditIndex.value] = newItem;
-  } else {
-    specialHours.value.unshift(newItem);
+  const selected = [...specialForm.selectedHolidays];
+  if (specialHours.value.length + selected.length > 5) {
+    ElMessage.warning("最多添加5个特殊营业时间");
+    return;
+  }
+  for (const val of selected) {
+    if (checkDateDuplicate([val], null)) {
+      ElMessage.warning("选择的日期重复");
+      return;
+    }
+  }
+
+  const newItems: SpecialHoursItem[] = selected.map(val => {
+    const holidayOpt = holidays.value.find(h => h.value === val);
+    const holidayIdFromList = holidayOpt?.id != null && holidayOpt.id > 0 ? holidayOpt.id : undefined;
+    return {
+      holidays: [val],
+      timeType: specialForm.timeType,
+      startTime: specialForm.timeType === "custom" ? specialForm.startTime : undefined,
+      endTime: specialForm.timeType === "custom" ? specialForm.endTime : undefined,
+      festivalName: val,
+      holidayId: holidayIdFromList,
+      essentialId: holidayIdFromList
+    };
+  });
+
+  for (let i = newItems.length - 1; i >= 0; i--) {
+    specialHours.value.unshift(newItems[i]);
   }
   specialDialogVisible.value = false;
   resetSpecialForm();
-  ElMessage.success(editingSpecial ? "编辑成功" : "添加成功");
+  ElMessage.success(newItems.length > 1 ? `已添加 ${newItems.length} 条特殊营业时间` : "添加成功");
 }
 
 /** 与商家端 1.vue save:全天为 00:00-00:00;特殊项带 essentialId */
@@ -713,4 +772,32 @@ async function handleSave() {
   gap: 12px;
   justify-content: flex-end;
 }
+.dialog-footer-special {
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+}
+.dialog-footer-right {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+.holiday-btn {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-height: 48px;
+  padding: 8px 12px;
+  line-height: 1.3;
+}
+.holiday-btn-label {
+  font-size: 14px;
+}
+.holiday-btn-date {
+  margin-top: 2px;
+  font-size: 12px;
+  font-weight: normal;
+  opacity: 0.85;
+}
 </style>