add store closed day in event record calendar

This commit is contained in:
Timi
2026-08-13 14:50:35 +08:00
parent fa1f5f1f2d
commit e43ba9db8d
5 changed files with 82 additions and 7 deletions
+74 -6
View File
@@ -36,12 +36,15 @@
v-for="item in calendarDayList" v-for="item in calendarDayList"
:key="item.key" :key="item.key"
class="day-cell" class="day-cell"
:class="{muted: !item.inMonth, today: item.isToday, marked: 0 < item.records.length}" :class="{muted: !item.inMonth, today: item.isToday, marked: 0 < item.records.length, 'rest-day': item.isRestDay}"
type="button" type="button"
:disabled="!item.records.length" :disabled="!item.records.length"
@click="openDayRecords(item)" @click="openDayRecords(item)"
> >
<span class="date" v-text="item.day" /> <span class="date">
<span v-text="item.day" />
<span v-if="item.isRestDay" class="rest">休</span>
</span>
<span v-if="item.records.length" class="count" v-text="`${item.records.length} 条`" /> <span v-if="item.records.length" class="count" v-text="`${item.records.length} 条`" />
</button> </button>
</div> </div>
@@ -104,8 +107,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import StoreAPI from "@/api/StoreAPI";
import type {EventItem} from "@/types/EventItem"; import type {EventItem} from "@/types/EventItem";
import type {EventRecord} from "@/types/EventRecord"; import type {EventRecord} from "@/types/EventRecord";
import type {StoreBusinessDay} from "@/types/Store";
import {getCurrentStoreId} from "@/utils/store";
import {Time} from "timi-web"; import {Time} from "timi-web";
interface CalendarDay { interface CalendarDay {
@@ -114,6 +120,7 @@ interface CalendarDay {
dateText: string; dateText: string;
inMonth: boolean; inMonth: boolean;
isToday: boolean; isToday: boolean;
isRestDay: boolean;
records: EventRecord[]; records: EventRecord[];
} }
@@ -131,11 +138,13 @@ const props = withDefaults(defineProps<{
records: EventRecord[]; records: EventRecord[];
selectedEventIdList: string[]; selectedEventIdList: string[];
showEventFilter?: boolean; showEventFilter?: boolean;
storeId?: string;
}>(), { }>(), {
customerName: "", customerName: "",
emptyText: "当前月份暂无匹配登记", emptyText: "当前月份暂无匹配登记",
loading: false, loading: false,
showEventFilter: true showEventFilter: true,
storeId: ""
}); });
const emit = defineEmits<{ const emit = defineEmits<{
@@ -151,6 +160,8 @@ const dayRecordVisible = ref(false);
const eventFilterVisible = ref(false); const eventFilterVisible = ref(false);
const selectedDayTitle = ref(""); const selectedDayTitle = ref("");
const selectedDayRecords = ref<EventRecord[]>([]); const selectedDayRecords = ref<EventRecord[]>([]);
const businessDayList = ref<StoreBusinessDay[]>([]);
let businessDayRequestId = 0;
const selectedEventIdListProxy = computed({ const selectedEventIdListProxy = computed({
get: () => props.selectedEventIdList, get: () => props.selectedEventIdList,
@@ -166,6 +177,12 @@ const eventNameMap = computed(() => {
return map; return map;
}); });
const selectedEventIdSet = computed(() => new Set(props.selectedEventIdList)); const selectedEventIdSet = computed(() => new Set(props.selectedEventIdList));
const resolvedStoreId = computed(() => props.storeId || getCurrentStoreId());
const restDaySet = computed(() => new Set(
businessDayList.value
.filter(item => item.status === "CLOSED" && item.dateValue)
.map(item => item.dateValue as number)
));
const filteredRecords = computed(() => { const filteredRecords = computed(() => {
if (!props.showEventFilter || !props.selectedEventIdList.length) { if (!props.showEventFilter || !props.selectedEventIdList.length) {
return props.records; return props.records;
@@ -188,12 +205,42 @@ const calendarDayList = computed<CalendarDay[]>(() => {
dateText, dateText,
inMonth: date.getMonth() === props.modelValue.getMonth(), inMonth: date.getMonth() === props.modelValue.getMonth(),
isToday: dateText === todayText, isToday: dateText === todayText,
isRestDay: restDaySet.value.has(toDateValue(date)),
records: filteredRecords.value.filter(item => getRecordDateText(item) === dateText) records: filteredRecords.value.filter(item => getRecordDateText(item) === dateText)
}); });
} }
return list; return list;
}); });
watch([() => props.modelValue, resolvedStoreId], async () => {
await loadBusinessDays();
}, {
deep: true,
immediate: true
});
async function loadBusinessDays(): Promise<void> {
const storeId = resolvedStoreId.value;
if (!storeId) {
businessDayList.value = [];
return;
}
const requestId = ++businessDayRequestId;
const beginDate = getCalendarBeginDate(props.modelValue);
const endDate = addDays(beginDate, 41);
try {
const result = await StoreAPI.businessDayCalendar(storeId, toDateValue(beginDate), toDateValue(endDate));
if (requestId === businessDayRequestId) {
businessDayList.value = result || [];
}
} catch {
if (requestId === businessDayRequestId) {
businessDayList.value = [];
}
}
}
function shiftMonth(offset: number): void { function shiftMonth(offset: number): void {
emit("update:modelValue", addMonths(props.modelValue, offset)); emit("update:modelValue", addMonths(props.modelValue, offset));
} }
@@ -286,6 +333,10 @@ function addMonths(date: Date, months: number): Date {
return new Date(date.getFullYear(), date.getMonth() + months, 1, 0, 0, 0, 0); return new Date(date.getFullYear(), date.getMonth() + months, 1, 0, 0, 0, 0);
} }
function toDateValue(date: Date): number {
return Number(Time.formatDate(date).replaceAll("-", ""));
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@@ -353,13 +404,22 @@ function addMonths(date: Date, months: number): Date {
flex-direction: column; flex-direction: column;
.date { .date {
display: flex;
font-size: .875rem; font-size: .875rem;
align-items: center;
font-weight: 700;
}
.rest {
color: #dc2626;
margin-left: .125rem;
font-size: .625rem;
font-weight: 700; font-weight: 700;
} }
.count { .count {
width: 100%; width: 100%;
color: #2563eb; color: var(--td-brand-color);
overflow: hidden; overflow: hidden;
font-size: .625rem; font-size: .625rem;
font-weight: 700; font-weight: 700;
@@ -373,11 +433,15 @@ function addMonths(date: Date, months: number): Date {
} }
&.today { &.today {
border-color: #2563eb; color: var(--td-brand-color);
} }
&.marked { &.marked {
background: #eff6ff; background: var(--td-brand-color-1);
}
&.rest-day {
background: #fef2f2;
} }
&:disabled { &:disabled {
@@ -479,6 +543,10 @@ function addMonths(date: Date, months: number): Date {
&.marked { &.marked {
background: rgba(37, 99, 235, .2); background: rgba(37, 99, 235, .2);
} }
&.rest-day {
background: rgba(220, 38, 38, .12);
}
} }
} }
@@ -39,6 +39,7 @@
v-model:selected-event-id-list="selectedEventIdList" v-model:selected-event-id-list="selectedEventIdList"
:customer-id="selectedCustomer.id || ''" :customer-id="selectedCustomer.id || ''"
:customer-name="selectedCustomer.name" :customer-name="selectedCustomer.name"
:store-id="selectedCustomer.storeId"
@list="toEventRecordList" @list="toEventRecordList"
/> />
<customer-record-trend-chart <customer-record-trend-chart
@@ -5,6 +5,7 @@
class="calendar-section" class="calendar-section"
:event-list="eventList" :event-list="eventList"
:customer-name="customerName" :customer-name="customerName"
:store-id="storeId"
:loading="isCalendarLoading" :loading="isCalendarLoading"
:records="records" :records="records"
@list="value => emit('list', value)" @list="value => emit('list', value)"
@@ -23,6 +24,7 @@ import type { StoreEventRecordCalendarView } from "@/types/Store";
const props = defineProps<{ const props = defineProps<{
customerId: string; customerId: string;
customerName?: string; customerName?: string;
storeId?: string;
modelValue: Date; modelValue: Date;
selectedEventIdList: string[]; selectedEventIdList: string[];
}>(); }>();
@@ -6,6 +6,7 @@
empty-text="当前月份暂无登记事件记录" empty-text="当前月份暂无登记事件记录"
:loading="isCalendarLoading" :loading="isCalendarLoading"
:records="monthRecords" :records="monthRecords"
:store-id="storeId"
@list="value => emit('list', value)" @list="value => emit('list', value)"
/> />
</template> </template>
@@ -17,6 +18,7 @@ import EventRecordCalendar, { type EventRecordCalendarListPayload } from "@/comp
import type { EventItem } from "@/types/EventItem"; import type { EventItem } from "@/types/EventItem";
import type { EventRecord } from "@/types/EventRecord"; import type { EventRecord } from "@/types/EventRecord";
import type { StoreEventRecordCalendarView } from "@/types/Store"; import type { StoreEventRecordCalendarView } from "@/types/Store";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import { Time } from "timi-web"; import { Time } from "timi-web";
const emit = defineEmits<{ const emit = defineEmits<{
@@ -28,6 +30,8 @@ const selectedEventIdList = ref<string[]>([]);
const monthRecords = ref<EventRecord[]>([]); const monthRecords = ref<EventRecord[]>([]);
const isCalendarLoading = ref(false); const isCalendarLoading = ref(false);
const monthCursor = ref(getMonthStart(new Date())); const monthCursor = ref(getMonthStart(new Date()));
const currentStoreStore = useCurrentStoreStore();
const storeId = computed(() => currentStoreStore.currentStoreId);
let calendarRequestId = 0; let calendarRequestId = 0;
watch(monthCursor, async () => { watch(monthCursor, async () => {
+1 -1
View File
@@ -5,7 +5,7 @@
* @param backgroundColor 头像背景色 * @param backgroundColor 头像背景色
*/ */
export function buildFallbackAvatar(name?: string, backgroundColor = "#2563eb"): string { export function buildFallbackAvatar(name?: string, backgroundColor = "#2563eb"): string {
const text = encodeURIComponent((name?.trim() || "用户").slice(0, 2)); const text = encodeURIComponent((name?.trim() || "用户").slice(0, 1));
const fill = encodeURIComponent(backgroundColor); const fill = encodeURIComponent(backgroundColor);
return [ return [
"data:image/svg+xml;charset=UTF-8,", "data:image/svg+xml;charset=UTF-8,",