Files
gao-customer-h5/src/pages/store/component/StoreEventStructureChart.vue
T
2026-08-13 14:09:25 +08:00

181 lines
4.2 KiB
Vue

<template>
<section class="chart-section">
<h2>事件结构</h2>
<div ref="chartElement" class="chart" />
</section>
</template>
<script setup lang="ts">
import { Time } from "timi-web";
import type { EChartsOption } from "echarts";
import EventAPI from "@/api/EventAPI";
import StoreChartAPI from "@/api/StoreChartAPI";
import type { EventItem } from "@/types/EventItem";
import type { StoreEventRecordDailyStatView } from "@/types/Store";
import { buildStoreBaseChartOption, useStoreChart } from "@/pages/store/component/useStoreChart";
const props = defineProps<{
dateTextList: string[];
}>();
// ---------- 图表数据 ----------
const eventList = ref<EventItem[]>([]);
const statMap = ref<Record<string, StoreEventRecordDailyStatView[]>>({});
let statRequestId = 0;
const shouldRotateAxisLabel = computed(() => eventList.value.length > 4 || eventList.value.some(item => 6 < getEventName(item).length));
const chartOption = computed<EChartsOption>(() => ({
...buildStoreBaseChartOption(props.dateTextList),
grid: {
top: 24,
left: 12,
right: 12,
bottom: 0,
containLabel: true
},
legend: {
show: false
},
xAxis: {
type: "category",
data: eventList.value.map(getEventName),
axisTick: {
show: false
},
axisLabel: {
color: "#52607a",
hideOverlap: true,
interval: 0,
overflow: "truncate",
rotate: shouldRotateAxisLabel.value ? 45 : 0,
width: shouldRotateAxisLabel.value ? 72 : 56
}
},
series: [{
name: "登记次数",
type: "bar",
data: eventList.value.map(item => ({
value: countRecordByEvent(item.id || ""),
itemStyle: getEventColor(item) ? {
color: getEventColor(item)
} : undefined
}))
}]
}));
const { chartElement } = useStoreChart(chartOption);
watch(() => props.dateTextList, 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.dateTextList.length) {
statMap.value = {};
return;
}
const eventIdList = getEventIdList();
if (!eventIdList.length) {
statMap.value = {};
return;
}
const requestId = ++statRequestId;
const dailyStatList = await StoreChartAPI.eventRecordDailyStat({
beginAt: Time.toTimestamp(getDateStart(props.dateTextList[0])),
endAt: Time.toTimestamp(getDateEnd(props.dateTextList[props.dateTextList.length - 1])),
eventIdList
});
if (requestId === statRequestId) {
statMap.value = groupRecordDailyStatByEventId(dailyStatList);
}
}
function countRecordByEvent(eventId: string): number {
return (statMap.value[eventId] || []).reduce((sum, item) => sum + (item.dailyCount || 0), 0);
}
function getEventColor(event: EventItem): string | undefined {
return event.color || undefined;
}
function getEventName(event: EventItem): string {
return event.name || "未命名事件";
}
function getEventIdList(): string[] {
return eventList.value.map(item => item.id).filter((id): id is string => Boolean(id));
}
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">
.chart-section {
padding: 1rem;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
border-radius: 1.25rem;
h2 {
color: #0f172a;
margin: 0 0 .875rem 0;
font-size: 1rem;
font-weight: 700;
}
.chart {
width: 100%;
height: 16rem;
}
}
:global(html.theme-dark) {
.chart-section {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
h2 {
color: #e2e8f0;
}
}
}
</style>