update charts

This commit is contained in:
Timi
2026-08-12 16:24:23 +08:00
parent 2a64f2ad72
commit 826f1b2dd0
14 changed files with 820 additions and 319 deletions
@@ -0,0 +1,124 @@
<template>
<div v-for="item in summaryList" :key="item.label" class="summary-item">
<span v-text="item.label" />
<strong v-text="item.value" />
</div>
</template>
<script setup lang="ts">
import { Time } from "timi-web";
import CustomerChartAPI from "@/api/CustomerChartAPI";
import EventAPI from "@/api/EventAPI";
import type { Customer } from "@/types/Customer";
import type { EventItem } from "@/types/EventItem";
import type { StoreEventRecordCalendarView } from "@/types/Store";
interface SummaryItem {
label: string;
value: string;
}
const props = defineProps<{
customer: Customer;
monthCursor: Date;
selectedEventIdList: string[];
}>();
// ---------- 摘要数据 ----------
const eventList = ref<EventItem[]>([]);
const monthRecordList = ref<StoreEventRecordCalendarView[]>([]);
let summaryRequestId = 0;
const eventNameMap = computed(() => {
const map = new Map<string, string>();
for (const item of eventList.value) {
if (item.id) {
map.set(item.id, item.name || "未命名事件");
}
}
return map;
});
const summaryList = computed<SummaryItem[]>(() => [
{
label: "本月登记",
value: `${monthRecordList.value.length} 次`
},
{
label: "转介绍",
value: `${props.customer.invitedCount || 0} 人`
},
{
label: "最近登记",
value: getRecordEventName(monthRecordList.value[0]) || "暂无"
}
]);
watch([
() => props.customer.id,
() => props.monthCursor,
() => props.selectedEventIdList
], async () => {
await loadMonthRecords();
}, {
deep: true
});
onMounted(async () => {
await loadEvents();
await loadMonthRecords();
});
async function loadEvents(): Promise<void> {
eventList.value = await EventAPI.listValid();
}
async function loadMonthRecords(): Promise<void> {
const customerId = props.customer.id;
if (!customerId || !props.selectedEventIdList.length) {
monthRecordList.value = [];
return;
}
const requestId = ++summaryRequestId;
const begin = getMonthStart(props.monthCursor);
const end = getDateEnd(addDays(addMonths(begin, 1), -1));
const recordList = await CustomerChartAPI.eventRecordCalendar({
customerId,
beginAt: Time.toTimestamp(begin),
endAt: Time.toTimestamp(end),
eventIdList: props.selectedEventIdList
});
if (requestId === summaryRequestId) {
monthRecordList.value = recordList;
}
}
function getRecordEventName(record?: StoreEventRecordCalendarView): string {
if (!record) {
return "";
}
return record.eventId ? eventNameMap.value.get(record.eventId) || "未知事件" : "未知事件";
}
// ---------- 日期 ----------
function getMonthStart(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
}
function getDateEnd(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
}
function addMonths(date: Date, months: number): Date {
return new Date(date.getFullYear(), date.getMonth() + months, date.getDate(), 0, 0, 0, 0);
}
function addDays(date: Date, days: number): Date {
const value = new Date(date);
value.setDate(value.getDate() + days);
return value;
}
</script>
@@ -5,22 +5,24 @@
class="calendar-section"
:event-list="eventList"
:customer-name="customerName"
:loading="loading"
:loading="isCalendarLoading"
:records="records"
/>
</template>
<script setup lang="ts">
import { Time } from "timi-web";
import CustomerChartAPI from "@/api/CustomerChartAPI";
import EventAPI from "@/api/EventAPI";
import EventRecordCalendar from "@/components/EventRecordCalendar.vue";
import type { EventItem } from "@/types/EventItem";
import type { EventRecord } from "@/types/EventRecord";
import type { StoreEventRecordCalendarView } from "@/types/Store";
const props = defineProps<{
customerId: string;
customerName?: string;
eventList: EventItem[];
loading?: boolean;
modelValue: Date;
records: EventRecord[];
selectedEventIdList: string[];
}>();
@@ -38,6 +40,100 @@ const selectedEventIdListProxy = computed({
get: () => props.selectedEventIdList,
set: value => emit("update:selectedEventIdList", value)
});
// ---------- 日历数据 ----------
const eventList = ref<EventItem[]>([]);
const records = ref<EventRecord[]>([]);
const isCalendarLoading = ref(false);
let calendarRequestId = 0;
watch([
() => props.customerId,
() => props.modelValue,
() => props.selectedEventIdList
], async () => {
await loadMonthRecords();
}, {
deep: true
});
onMounted(async () => {
await loadEvents();
await loadMonthRecords();
});
async function loadEvents(): Promise<void> {
eventList.value = await EventAPI.listValid();
const eventIdList = getEventIdList();
emit("update:selectedEventIdList", props.selectedEventIdList.length ? props.selectedEventIdList.filter(item => eventIdList.includes(item)) : eventIdList);
}
async function loadMonthRecords(): Promise<void> {
if (!props.customerId || !props.selectedEventIdList.length) {
records.value = [];
return;
}
const requestId = ++calendarRequestId;
const begin = getCalendarBeginDate(props.modelValue);
const end = getDateEnd(addDays(begin, 41));
isCalendarLoading.value = true;
try {
const calendarRecordList = await CustomerChartAPI.eventRecordCalendar({
customerId: props.customerId,
beginAt: Time.toTimestamp(begin),
endAt: Time.toTimestamp(end),
eventIdList: props.selectedEventIdList
});
if (requestId === calendarRequestId) {
records.value = calendarRecordList.map(buildCalendarRecord);
}
} finally {
if (requestId === calendarRequestId) {
isCalendarLoading.value = false;
}
}
}
function getEventIdList(): string[] {
return eventList.value.map(item => item.id).filter((id): id is string => Boolean(id));
}
function buildCalendarRecord(record: StoreEventRecordCalendarView): EventRecord {
return {
id: record.id,
customerId: record.customerId,
eventId: record.eventId,
registeredAt: record.registeredAt,
customer: record.customerId || record.customerCode || record.customerName ? {
id: record.customerId,
code: record.customerCode,
name: record.customerName
} : undefined
};
}
// ---------- 日期 ----------
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 getDateEnd(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
}
function addDays(date: Date, days: number): Date {
const value = new Date(date);
value.setDate(value.getDate() + days);
return value;
}
</script>
<style scoped lang="less">
@@ -6,20 +6,28 @@
</template>
<script setup lang="ts">
import { Time } from "timi-web";
import type { EChartsOption } from "echarts";
import CustomerChartAPI from "@/api/CustomerChartAPI";
import EventAPI from "@/api/EventAPI";
import type { EventItem } from "@/types/EventItem";
import type { StoreEventRecordDailyStatView } from "@/types/Store";
import { buildCustomerBaseChartOption, useCustomerChart } from "@/pages/customer/component/useCustomerChart";
const props = defineProps<{
customerId: string;
dateTextList: string[];
eventList: EventItem[];
selectedEventIdList: string[];
statMap: Record<string, StoreEventRecordDailyStatView[]>;
}>();
// ---------- 图表数据 ----------
const eventList = ref<EventItem[]>([]);
const statMap = ref<Record<string, StoreEventRecordDailyStatView[]>>({});
let statRequestId = 0;
const selectedEventIdSet = computed(() => new Set(props.selectedEventIdList));
const visibleEventList = computed(() => props.eventList.filter(item => item.id && selectedEventIdSet.value.has(item.id)));
const visibleEventList = computed(() => eventList.value.filter(item => item.id && selectedEventIdSet.value.has(item.id)));
const chartOption = computed<EChartsOption>(() => ({
...buildCustomerBaseChartOption(props.dateTextList),
xAxis: {
@@ -48,13 +56,76 @@ const chartOption = computed<EChartsOption>(() => ({
const { chartElement } = useCustomerChart(chartOption);
watch([
() => props.customerId,
() => props.dateTextList,
() => props.selectedEventIdList
], async () => {
await loadDailyStat();
}, {
deep: true
});
onMounted(async () => {
await loadEvents();
await loadDailyStat();
});
async function loadEvents(): Promise<void> {
eventList.value = await EventAPI.listValid();
}
async function loadDailyStat(): Promise<void> {
if (!props.customerId || !props.dateTextList.length || !props.selectedEventIdList.length) {
statMap.value = {};
return;
}
const requestId = ++statRequestId;
const dailyStatList = await CustomerChartAPI.eventRecordDailyStat({
customerId: props.customerId,
beginAt: Time.toTimestamp(getDateStart(props.dateTextList[0])),
endAt: Time.toTimestamp(getDateEnd(props.dateTextList[props.dateTextList.length - 1])),
eventIdList: props.selectedEventIdList
});
if (requestId === statRequestId) {
statMap.value = groupRecordDailyStatByEventId(dailyStatList);
}
}
function countRecordByEvent(eventId: string): number {
return (props.statMap[eventId] || []).reduce((sum, item) => sum + (item.dailyCount || 0), 0);
return (statMap.value[eventId] || []).reduce((sum, item) => sum + (item.dailyCount || 0), 0);
}
function getEventColor(event: EventItem): string | undefined {
return event.color || undefined;
}
function groupRecordDailyStatByEventId(list: StoreEventRecordDailyStatView[]): Record<string, StoreEventRecordDailyStatView[]> {
const map: Record<string, StoreEventRecordDailyStatView[]> = {};
for (const item of list) {
if (!item.eventId) {
continue;
}
if (!map[item.eventId]) {
map[item.eventId] = [];
}
map[item.eventId].push(item);
}
return map;
}
// ---------- 日期 ----------
function getDateStart(dateText: string): Date {
const [year, month, day] = dateText.split("-").map(Number);
return new Date(year, month - 1, day, 0, 0, 0, 0);
}
function getDateEnd(dateText: string): Date {
const [year, month, day] = dateText.split("-").map(Number);
return new Date(year, month - 1, day, 23, 59, 59, 999);
}
</script>
<style scoped lang="less">
@@ -14,17 +14,26 @@
</template>
<script setup lang="ts">
import { Time } from "timi-web";
import type { EChartsOption } from "echarts";
import CustomerChartAPI from "@/api/CustomerChartAPI";
import ChartRangeSlider from "@/components/ChartRangeSlider.vue";
import type { StoreEventRecordDailyStatView } from "@/types/Store";
import { buildCustomerBaseChartOption, useCustomerChart, useCustomerChartWindow } from "@/pages/customer/component/useCustomerChart";
const props = defineProps<{
customerId: string;
dateTextList: string[];
selectedEventIdList: string[];
statMap: Record<string, StoreEventRecordDailyStatView[]>;
}>();
// ---------- 图表数据 ----------
const statMap = ref<Record<string, StoreEventRecordDailyStatView[]>>({});
let statRequestId = 0;
// ---------- 图表窗口 ----------
const dateTextList = computed(() => props.dateTextList);
const {
rangeText,
@@ -48,9 +57,64 @@ const chartOption = computed<EChartsOption>(() => ({
const { chartElement } = useCustomerChart(chartOption);
watch([
() => props.customerId,
() => props.dateTextList,
() => props.selectedEventIdList
], async () => {
await loadDailyStat();
}, {
deep: true,
immediate: true
});
async function loadDailyStat(): Promise<void> {
if (!props.customerId || !props.dateTextList.length || !props.selectedEventIdList.length) {
statMap.value = {};
return;
}
const requestId = ++statRequestId;
const dailyStatList = await CustomerChartAPI.eventRecordDailyStat({
customerId: props.customerId,
beginAt: Time.toTimestamp(getDateStart(props.dateTextList[0])),
endAt: Time.toTimestamp(getDateEnd(props.dateTextList[props.dateTextList.length - 1])),
eventIdList: props.selectedEventIdList
});
if (requestId === statRequestId) {
statMap.value = groupRecordDailyStatByEventId(dailyStatList);
}
}
function countRecordByDate(dateText: string): number {
const dateValue = Number(dateText.replaceAll("-", ""));
return props.selectedEventIdList.reduce((sum, eventId) => sum + (props.statMap[eventId]?.find(item => item.dateValue === dateValue)?.dailyCount || 0), 0);
return props.selectedEventIdList.reduce((sum, eventId) => sum + (statMap.value[eventId]?.find(item => item.dateValue === dateValue)?.dailyCount || 0), 0);
}
function groupRecordDailyStatByEventId(list: StoreEventRecordDailyStatView[]): Record<string, StoreEventRecordDailyStatView[]> {
const map: Record<string, StoreEventRecordDailyStatView[]> = {};
for (const item of list) {
if (!item.eventId) {
continue;
}
if (!map[item.eventId]) {
map[item.eventId] = [];
}
map[item.eventId].push(item);
}
return map;
}
// ---------- 日期 ----------
function getDateStart(dateText: string): Date {
const [year, month, day] = dateText.split("-").map(Number);
return new Date(year, month - 1, day, 0, 0, 0, 0);
}
function getDateEnd(dateText: string): Date {
const [year, month, day] = dateText.split("-").map(Number);
return new Date(year, month - 1, day, 23, 59, 59, 999);
}
</script>