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