refactor event
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
<template>
|
||||
<section class="register-record-calendar">
|
||||
<div class="calendar-head">
|
||||
<t-button class="month-btn" shape="square" variant="text" @click="shiftMonth(-1)">
|
||||
<template #icon>
|
||||
<t-icon name="chevron-left" />
|
||||
</template>
|
||||
</t-button>
|
||||
<h2 v-text="monthTitle" />
|
||||
<div class="head-actions">
|
||||
<t-button class="month-btn" shape="square" variant="text" @click="shiftMonth(1)">
|
||||
<template #icon>
|
||||
<t-icon name="chevron-right" />
|
||||
</template>
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="showEventFilter"
|
||||
class="filter-btn"
|
||||
theme="primary"
|
||||
size="extra-small"
|
||||
variant="outline"
|
||||
@click="eventFilterVisible = true"
|
||||
content="登记事件"
|
||||
>
|
||||
<template #icon>
|
||||
<t-icon name="filter" />
|
||||
</template>
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="week-line">
|
||||
<span v-for="item in weekNameList" :key="item" v-text="item" />
|
||||
</div>
|
||||
<div class="calendar-grid">
|
||||
<button
|
||||
v-for="item in calendarDayList"
|
||||
:key="item.key"
|
||||
class="day-cell"
|
||||
:class="{muted: !item.inMonth, today: item.isToday, marked: 0 < item.records.length}"
|
||||
type="button"
|
||||
:disabled="!item.records.length"
|
||||
@click="openDayRecords(item)"
|
||||
>
|
||||
<span class="date" v-text="item.day" />
|
||||
<span v-if="item.records.length" class="count" v-text="`${item.records.length} 条`" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="loading" class="empty-block">正在加载登记日历</div>
|
||||
<div v-else-if="!currentMonthFilteredRecords.length" class="empty-block" v-text="emptyText" />
|
||||
<t-popup v-model="dayRecordVisible" placement="bottom">
|
||||
<div class="day-record-panel">
|
||||
<div class="popup-head">
|
||||
<h3 v-text="selectedDayTitle" />
|
||||
<t-button theme="default" variant="text" size="small" @click="dayRecordVisible = false">
|
||||
关闭
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="popup-body">
|
||||
<t-cell-group v-if="selectedDayRecords.length" theme="card">
|
||||
<t-cell
|
||||
v-for="item in selectedDayRecords"
|
||||
:key="item.id"
|
||||
:title="getRecordEventName(item)"
|
||||
:description="getRecordCustomerName(item)"
|
||||
:note="`${formatRecordTime(item.registeredAt)}`"
|
||||
/>
|
||||
</t-cell-group>
|
||||
<div v-else class="empty-block">该日暂无登记事件记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-popup>
|
||||
<t-popup v-model="eventFilterVisible" placement="bottom">
|
||||
<div class="filter-popup">
|
||||
<div class="popup-head">
|
||||
<div class="title">登记事件筛选</div>
|
||||
<t-button theme="default" variant="text" size="small" @click="eventFilterVisible = false">
|
||||
关闭
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="popup-body">
|
||||
<t-checkbox-group v-model="selectedEventIdListProxy" class="items">
|
||||
<t-checkbox
|
||||
class="item"
|
||||
v-for="item in eventList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
block
|
||||
borderless
|
||||
/>
|
||||
</t-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</t-popup>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {EventItem} from "@/types/EventItem";
|
||||
import type {EventRecord} from "@/types/EventRecord";
|
||||
import {Time} from "timi-web";
|
||||
|
||||
interface CalendarDay {
|
||||
key: string;
|
||||
day: string;
|
||||
dateText: string;
|
||||
inMonth: boolean;
|
||||
isToday: boolean;
|
||||
records: EventRecord[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
eventList: EventItem[];
|
||||
customerName?: string;
|
||||
emptyText?: string;
|
||||
loading?: boolean;
|
||||
modelValue: Date;
|
||||
records: EventRecord[];
|
||||
selectedEventIdList: string[];
|
||||
showEventFilter?: boolean;
|
||||
}>(), {
|
||||
customerName: "",
|
||||
emptyText: "当前月份暂无匹配登记",
|
||||
loading: false,
|
||||
showEventFilter: true
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: Date];
|
||||
"update:selectedEventIdList": [value: string[]];
|
||||
}>();
|
||||
|
||||
const weekNameList = ["日", "一", "二", "三", "四", "五", "六"];
|
||||
const dayRecordVisible = ref(false);
|
||||
const eventFilterVisible = ref(false);
|
||||
const selectedDayTitle = ref("");
|
||||
const selectedDayRecords = ref<EventRecord[]>([]);
|
||||
|
||||
const selectedEventIdListProxy = computed({
|
||||
get: () => props.selectedEventIdList,
|
||||
set: value => emit("update:selectedEventIdList", value)
|
||||
});
|
||||
const eventNameMap = computed(() => {
|
||||
const map = new Map<string, string>();
|
||||
for (const item of props.eventList) {
|
||||
if (item.id) {
|
||||
map.set(item.id, item.name || "未命名事件");
|
||||
}
|
||||
}
|
||||
return map;
|
||||
});
|
||||
const selectedEventIdSet = computed(() => new Set(props.selectedEventIdList));
|
||||
const filteredRecords = computed(() => {
|
||||
if (!props.showEventFilter || !props.selectedEventIdList.length) {
|
||||
return props.records;
|
||||
}
|
||||
|
||||
return props.records.filter(item => item.eventId && selectedEventIdSet.value.has(item.eventId));
|
||||
});
|
||||
const currentMonthFilteredRecords = computed(() => filteredRecords.value.filter(item => isInCurrentMonth(item.registeredAt)));
|
||||
const monthTitle = computed(() => `${props.modelValue.getFullYear()} 年 ${props.modelValue.getMonth() + 1} 月`);
|
||||
const calendarDayList = computed<CalendarDay[]>(() => {
|
||||
const beginDate = getCalendarBeginDate(props.modelValue);
|
||||
const todayText = Time.formatDate(new Date());
|
||||
const list: CalendarDay[] = [];
|
||||
for (let index = 0; index < 42; index += 1) {
|
||||
const date = addDays(beginDate, index);
|
||||
const dateText = Time.formatDate(date);
|
||||
list.push({
|
||||
key: dateText,
|
||||
day: String(date.getDate()),
|
||||
dateText,
|
||||
inMonth: date.getMonth() === props.modelValue.getMonth(),
|
||||
isToday: dateText === todayText,
|
||||
records: filteredRecords.value.filter(item => getRecordDateText(item) === dateText)
|
||||
});
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
function shiftMonth(offset: number): void {
|
||||
emit("update:modelValue", addMonths(props.modelValue, offset));
|
||||
}
|
||||
|
||||
function openDayRecords(day: CalendarDay): void {
|
||||
if (!day.records.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedDayTitle.value = `${day.dateText} 登记事件记录`;
|
||||
selectedDayRecords.value = [...day.records].sort((left, right) => (right.registeredAt || 0) - (left.registeredAt || 0));
|
||||
dayRecordVisible.value = true;
|
||||
}
|
||||
|
||||
function getRecordEventName(record?: EventRecord): string {
|
||||
if (!record) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return record.event?.name || (record.eventId ? eventNameMap.value.get(record.eventId) : "") || "未知事件";
|
||||
}
|
||||
|
||||
function getRecordCustomerName(record: EventRecord): string {
|
||||
return record.customer?.name || props.customerName || record.customerId || "未知客户";
|
||||
}
|
||||
|
||||
function formatRecordTime(value?: number): string {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const date = Time.toDateObject(value);
|
||||
if (!date) {
|
||||
return "-";
|
||||
}
|
||||
return Time.formatDateTime(date);
|
||||
}
|
||||
|
||||
function getRecordDateText(record: EventRecord): string {
|
||||
const date = Time.toDateObject(record.registeredAt);
|
||||
return date ? Time.formatDate(date) : "";
|
||||
}
|
||||
|
||||
function isInCurrentMonth(value?: number): boolean {
|
||||
const date = Time.toDateObject(value);
|
||||
return !!date && date.getFullYear() === props.modelValue.getFullYear() && date.getMonth() === props.modelValue.getMonth();
|
||||
}
|
||||
|
||||
function getMonthStart(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
function getCalendarBeginDate(date: Date): Date {
|
||||
const monthStart = getMonthStart(date);
|
||||
return addDays(monthStart, -monthStart.getDay());
|
||||
}
|
||||
|
||||
function addDays(date: Date, days: number): Date {
|
||||
const value = new Date(date);
|
||||
value.setDate(value.getDate() + days);
|
||||
return value;
|
||||
}
|
||||
|
||||
function addMonths(date: Date, months: number): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth() + months, 1, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.register-record-calendar {
|
||||
gap: .875rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
background: rgba(255, 255, 255, .92);
|
||||
box-shadow: var(--td-shadow-1);
|
||||
border-radius: .75rem;
|
||||
flex-direction: column;
|
||||
|
||||
.calendar-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.month-btn {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.head-actions {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.week-line,
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.week-line {
|
||||
color: #52607a;
|
||||
font-size: .75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
gap: .375rem;
|
||||
|
||||
.day-cell {
|
||||
padding: .25rem;
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
color: #0f172a;
|
||||
border: 1px solid var(--td-component-border);
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
align-items: flex-start;
|
||||
border-radius: .25rem;
|
||||
flex-direction: column;
|
||||
|
||||
.date {
|
||||
font-size: .875rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 100%;
|
||||
color: #2563eb;
|
||||
overflow: hidden;
|
||||
font-size: .625rem;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.muted {
|
||||
color: #94a3b8;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
&.today {
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
&.marked {
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-block {
|
||||
padding: 1rem;
|
||||
color: #52607a;
|
||||
text-align: center;
|
||||
background: #f7faff;
|
||||
border-radius: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.day-record-panel,
|
||||
.filter-popup {
|
||||
gap: .875rem;
|
||||
padding: 1rem 0 calc(1rem + env(safe-area-inset-bottom, 0)) 0;
|
||||
display: flex;
|
||||
background: #f5f5f8;
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
flex-direction: column;
|
||||
max-height: min(80vh, 40rem);
|
||||
|
||||
h3,
|
||||
.title {
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.popup-head {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
padding: 0 1rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-popup {
|
||||
padding: 1rem 1rem calc(1rem + env(safe-area-inset-bottom, 0)) 1rem;
|
||||
|
||||
.popup-head,
|
||||
.popup-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.items {
|
||||
gap: .5rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
.item {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.register-record-calendar {
|
||||
background: rgba(15, 23, 42, .88);
|
||||
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
|
||||
|
||||
.calendar-head {
|
||||
h2 {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
}
|
||||
|
||||
.week-line {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
.day-cell {
|
||||
color: #e2e8f0;
|
||||
background: rgba(30, 41, 59, .9);
|
||||
|
||||
.count {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
&.muted {
|
||||
color: #94a3b8;
|
||||
background: rgba(15, 23, 42, .7);
|
||||
}
|
||||
|
||||
&.marked {
|
||||
background: rgba(37, 99, 235, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-block {
|
||||
color: #94a3b8;
|
||||
background: rgba(30, 41, 59, .9);
|
||||
}
|
||||
}
|
||||
|
||||
.day-record-panel,
|
||||
.filter-popup {
|
||||
background: #10161f;
|
||||
|
||||
h3,
|
||||
.title {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user