From e92cd56bb56b111c1afe9922191f9897215ff6b0 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期五, 28 八月 2026 17:55:27 +0800
Subject: [PATCH] 提交数据库和服务器部署说明文档
---
frontend-admin/src/views/BookingSettingsView.vue | 215 ++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 160 insertions(+), 55 deletions(-)
diff --git a/frontend-admin/src/views/BookingSettingsView.vue b/frontend-admin/src/views/BookingSettingsView.vue
index 668cac2..e8cb048 100644
--- a/frontend-admin/src/views/BookingSettingsView.vue
+++ b/frontend-admin/src/views/BookingSettingsView.vue
@@ -29,7 +29,7 @@
<p class="fields-hint">预约字段变更即时生效</p>
<h3 class="section-title section-gap">可预约范围</h3>
- <el-form label-width="120px" class="block-form">
+ <el-form label-width="140px" class="block-form">
<el-form-item label="预约时段">
<el-radio-group v-model="rangeMode">
<el-radio-button value="SINGLE">单日</el-radio-button>
@@ -48,27 +48,33 @@
/>
</el-form-item>
</el-form>
+ <p class="fields-hint">选择多日时单日亦会生效</p>
<h3 class="section-title section-gap">节假日配置</h3>
<div class="holiday-toolbar">
- <span class="lbl">节假日/高温假:</span>
- <el-date-picker
- v-model="pickDate"
- type="date"
- value-format="YYYY-MM-DD"
- placeholder="选择日期"
- clearable
- style="width: 200px"
- />
- <el-button v-permission="'config:booking-settings:update'" type="primary" @click="addHoliday">添加</el-button>
- <el-button v-permission="'config:booking-settings:update'" @click="resetHolidays">重置</el-button>
+ <el-button @click="switchMonth(-1)">上月</el-button>
+ <div class="month-label">{{ currentMonthLabel }}</div>
+ <el-button @click="switchMonth(1)">下月</el-button>
+ <el-button v-permission="'config:booking-settings:update'" @click="pickCurrentMonthWeekends">快速选择本月周末</el-button>
</div>
- <div class="holiday-tags">
- <el-tag v-for="d in holidayDates" :key="d" closable class="tag-item" @close="removeHoliday(d)">
- {{ d }}
- </el-tag>
- <span v-if="holidayDates.length === 0" class="empty-hint">暂无已选节假日</span>
+ <div class="holiday-calendar">
+ <div class="week-head" v-for="w in weekNames" :key="w">{{ w }}</div>
+ <div
+ v-for="cell in calendarCells"
+ :key="cell.date"
+ class="day-cell"
+ :class="{
+ 'cell-out': !cell.inMonth,
+ 'cell-holiday': cell.inMonth && isHoliday(cell.date),
+ 'cell-workday': cell.inMonth && !isHoliday(cell.date),
+ }"
+ @click="toggleHoliday(cell)"
+ >
+ <div v-if="cell.inMonth" class="day-num">{{ cell.day }}</div>
+ <div v-if="cell.inMonth" class="day-flag">{{ isHoliday(cell.date) ? "休" : "班" }}</div>
+ </div>
</div>
+ <p class="fields-hint">点击日期会实时保存,无需再点“保存预约策略”</p>
<h3 class="section-title section-gap">车辆道闸权限</h3>
<div class="barrier-block">
@@ -86,9 +92,10 @@
</template>
<script setup lang="ts">
-import { onMounted, ref } from "vue";
+import { computed, onMounted, ref } from "vue";
import { ElMessage } from "element-plus";
import http from "../api/http";
+import { normalizeFormData } from "../utils/formHelper";
const LOCKED_CODES = new Set(["respondent", "visit_start_time"]);
@@ -148,9 +155,35 @@
const rangeMode = ref<"SINGLE" | "MULTI">("SINGLE");
const maxConsecutiveDays = ref(7);
const holidayDates = ref<string[]>([]);
-const pickDate = ref<string | null>(null);
const vehicleBarrierEnabled = ref(false);
const extrasSaving = ref(false);
+const holidaySaving = ref(false);
+const currentMonth = ref(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
+
+const weekNames = ["日", "一", "二", "三", "四", "五", "六"];
+
+const currentMonthLabel = computed(() => {
+ const y = currentMonth.value.getFullYear();
+ const m = `${currentMonth.value.getMonth() + 1}`.padStart(2, "0");
+ return `${y}-${m}`;
+});
+
+const calendarCells = computed(() => {
+ const first = new Date(currentMonth.value.getFullYear(), currentMonth.value.getMonth(), 1);
+ const start = new Date(first);
+ start.setDate(1 - first.getDay());
+ const cells: Array<{ date: string; day: number; inMonth: boolean }> = [];
+ for (let i = 0; i < 42; i++) {
+ const d = new Date(start);
+ d.setDate(start.getDate() + i);
+ cells.push({
+ date: formatLocalDate(d),
+ day: d.getDate(),
+ inMonth: d.getMonth() === currentMonth.value.getMonth(),
+ });
+ }
+ return cells;
+});
async function loadExtras() {
try {
@@ -172,33 +205,59 @@
}
}
-function addHoliday() {
- const d = pickDate.value?.trim();
- if (!d) {
- ElMessage.warning("请先选择日期");
- return;
- }
- if (holidayDates.value.includes(d)) {
- ElMessage.info("该日期已在列表中");
- return;
- }
- holidayDates.value = [...holidayDates.value, d].sort();
- pickDate.value = null;
+function isHoliday(date: string) {
+ return holidayDates.value.includes(date);
}
-function removeHoliday(d: string) {
- holidayDates.value = holidayDates.value.filter((x) => x !== d);
+function switchMonth(offset: number) {
+ const d = new Date(currentMonth.value);
+ d.setMonth(d.getMonth() + offset, 1);
+ currentMonth.value = d;
}
-async function resetHolidays() {
- pickDate.value = null;
+async function toggleHoliday(cell: { date: string; inMonth: boolean }) {
+ if (!cell.inMonth) return;
+ if (holidaySaving.value) return;
+ const nextHoliday = !isHoliday(cell.date);
+ holidaySaving.value = true;
try {
- const { data } = await http.get("/config/booking-settings");
- const hol = data.result?.holidayDates as string[] | undefined;
- holidayDates.value = Array.isArray(hol) ? [...hol] : [];
+ await http.patch("/config/booking-settings/holiday-date", {
+ date: cell.date,
+ holiday: nextHoliday,
+ });
+ if (nextHoliday) {
+ holidayDates.value = [...holidayDates.value, cell.date].sort();
+ } else {
+ holidayDates.value = holidayDates.value.filter((x) => x !== cell.date);
+ }
+ ElMessage.success(nextHoliday ? "已设为休息日" : "已取消休息日");
} catch (e: unknown) {
- holidayDates.value = [];
- ElMessage.error(e instanceof Error ? e.message : "重置失败");
+ ElMessage.error(e instanceof Error ? e.message : "操作失败");
+ } finally {
+ holidaySaving.value = false;
+ }
+}
+
+function formatLocalDate(date: Date) {
+ const y = date.getFullYear();
+ const m = `${date.getMonth() + 1}`.padStart(2, "0");
+ const d = `${date.getDate()}`.padStart(2, "0");
+ return `${y}-${m}-${d}`;
+}
+
+async function pickCurrentMonthWeekends() {
+ if (holidaySaving.value) return;
+ holidaySaving.value = true;
+ try {
+ await http.post("/config/booking-settings/holiday-month-weekends", {
+ month: currentMonthLabel.value,
+ });
+ await loadExtras();
+ ElMessage.success("已选择本月周末");
+ } catch (e: unknown) {
+ ElMessage.error(e instanceof Error ? e.message : "操作失败");
+ } finally {
+ holidaySaving.value = false;
}
}
@@ -212,12 +271,13 @@
}
extrasSaving.value = true;
try {
- await http.put("/config/booking-settings", {
+ const payload = normalizeFormData({
rangeMode: rangeMode.value,
maxConsecutiveDays: rangeMode.value === "MULTI" ? maxConsecutiveDays.value : maxConsecutiveDays.value ?? 7,
holidayDates: holidayDates.value,
vehicleBarrierEnabled: vehicleBarrierEnabled.value,
});
+ await http.put("/config/booking-settings", payload);
ElMessage.success("已保存");
await loadExtras();
} catch (e: unknown) {
@@ -271,28 +331,73 @@
}
.holiday-toolbar {
display: flex;
- flex-wrap: wrap;
align-items: center;
- gap: 10px;
+ gap: 12px;
margin-bottom: 12px;
}
-.lbl {
- font-size: 14px;
- color: #606266;
-}
-.holiday-tags {
+.month-label {
+ width: 120px;
+ height: 32px;
+ border: 1px solid #dcdfe6;
+ border-radius: 4px;
display: flex;
- flex-wrap: wrap;
- gap: 8px;
align-items: center;
- min-height: 32px;
+ justify-content: center;
+ color: #606266;
+ font-size: 14px;
}
-.tag-item {
- margin: 0;
+.holiday-calendar {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 8px;
+ border: 1px solid #d9ecff;
+ border-radius: 10px;
+ padding: 12px;
+ max-width: 760px;
}
-.empty-hint {
+.week-head {
+ text-align: center;
+ font-size: 14px;
+ color: #1f2937;
+}
+.day-cell {
+ min-height: 58px;
+ border-radius: 8px;
+ padding: 6px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+.cell-out {
+ cursor: default;
+ background: transparent;
+}
+.cell-holiday {
+ background: #fdecec;
+}
+.cell-workday {
+ background: #ffffff;
+}
+.day-num {
+ font-size: 22px;
+ line-height: 1;
+ color: #111827;
+}
+.day-flag {
+ align-self: flex-end;
font-size: 13px;
- color: #c0c4cc;
+ line-height: 18px;
+ padding: 0 4px;
+ border-radius: 4px;
+}
+.cell-holiday .day-flag {
+ color: #fff;
+ background: #ff4d4f;
+}
+.cell-workday .day-flag {
+ color: #fff;
+ background: #409eff;
}
.barrier-block {
max-width: 560px;
--
Gitblit v1.7.1