fix store chart style

This commit is contained in:
Timi
2026-08-12 17:06:02 +08:00
parent f14e36c730
commit a02fa239c2
6 changed files with 128 additions and 3 deletions
+23
View File
@@ -63,6 +63,9 @@
:title="getRecordEventName(item)" :title="getRecordEventName(item)"
:description="getRecordCustomerName(item)" :description="getRecordCustomerName(item)"
:note="`${formatRecordTime(item.registeredAt)}`" :note="`${formatRecordTime(item.registeredAt)}`"
arrow
hover
@click="toRecordDetail(item)"
/> />
</t-cell-group> </t-cell-group>
<div v-else class="empty-block">该日暂无登记事件记录</div> <div v-else class="empty-block">该日暂无登记事件记录</div>
@@ -130,6 +133,8 @@ const emit = defineEmits<{
"update:selectedEventIdList": [value: string[]]; "update:selectedEventIdList": [value: string[]];
}>(); }>();
const route = useRoute();
const router = useRouter();
const weekNameList = ["日", "一", "二", "三", "四", "五", "六"]; const weekNameList = ["日", "一", "二", "三", "四", "五", "六"];
const dayRecordVisible = ref(false); const dayRecordVisible = ref(false);
const eventFilterVisible = ref(false); const eventFilterVisible = ref(false);
@@ -192,6 +197,20 @@ function openDayRecords(day: CalendarDay): void {
dayRecordVisible.value = true; dayRecordVisible.value = true;
} }
async function toRecordDetail(record: EventRecord): Promise<void> {
if (!record.id) {
return;
}
dayRecordVisible.value = false;
await router.push({
path: `/management/registration-event-records/${record.id}`,
query: {
transitionDepth: String(getCurrentRouteDepth() + 1)
}
});
}
function getRecordEventName(record?: EventRecord): string { function getRecordEventName(record?: EventRecord): string {
if (!record) { if (!record) {
return ""; return "";
@@ -226,6 +245,10 @@ function isInCurrentMonth(value?: number): boolean {
return !!date && date.getFullYear() === props.modelValue.getFullYear() && date.getMonth() === props.modelValue.getMonth(); return !!date && date.getFullYear() === props.modelValue.getFullYear() && date.getMonth() === props.modelValue.getMonth();
} }
function getCurrentRouteDepth(): number {
return Number(route.meta.depth ?? 0);
}
function getMonthStart(date: Date): Date { function getMonthStart(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0); return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
} }
+21
View File
@@ -30,6 +30,11 @@ const pageBackground = computed(() => globalUIStore.bodyBackground);
const pathCache = new Map<string, number>(); const pathCache = new Map<string, number>();
function calcDepth(sourceRoute: RouteLocationNormalized): number { function calcDepth(sourceRoute: RouteLocationNormalized): number {
const queryDepth = parseTransitionDepth(sourceRoute.query.transitionDepth);
if (queryDepth !== undefined) {
return queryDepth;
}
if (!sourceRoute.meta.dynamicDepth) { if (!sourceRoute.meta.dynamicDepth) {
return Number(sourceRoute.meta.depth ?? 0); return Number(sourceRoute.meta.depth ?? 0);
} }
@@ -47,6 +52,22 @@ function calcDepth(sourceRoute: RouteLocationNormalized): number {
return depth; return depth;
} }
function parseTransitionDepth(value: unknown): number | undefined {
if (Array.isArray(value)) {
return parseTransitionDepth(value[0]);
}
if (typeof value !== "string") {
return undefined;
}
const depth = Number(value);
if (!Number.isInteger(depth) || depth < 0 || 20 < depth) {
return undefined;
}
return depth;
}
const unregisterGuard = router.beforeEach((to, from) => { const unregisterGuard = router.beforeEach((to, from) => {
const toDepth = calcDepth(to); const toDepth = calcDepth(to);
const fromDepth = calcDepth(from); const fromDepth = calcDepth(from);
@@ -27,6 +27,16 @@ const statList = ref<CustomerGenderStatView[]>([]);
const chartOption = computed<EChartsOption>(() => ({ const chartOption = computed<EChartsOption>(() => ({
...buildStoreBaseChartOption(props.dateTextList), ...buildStoreBaseChartOption(props.dateTextList),
grid: {
top: 24,
left: 12,
right: 12,
bottom: 12,
containLabel: true
},
legend: {
show: false
},
tooltip: { tooltip: {
trigger: "item" trigger: "item"
}, },
@@ -1,7 +1,11 @@
<template> <template>
<section class="chart-section"> <section class="chart-section">
<h2>登记量趋势</h2> <h2>登记量趋势</h2>
<div ref="chartElement" class="chart" /> <div
ref="chartElement"
class="chart"
:style="chartStyle"
/>
<chart-range-slider <chart-range-slider
v-if="showSlider" v-if="showSlider"
:model-value="sliderValue" :model-value="sliderValue"
@@ -35,6 +39,7 @@ let statRequestId = 0;
// ---------- 图表窗口 ---------- // ---------- 图表窗口 ----------
const chartWidth = ref(window.innerWidth);
const dateTextList = computed(() => props.dateTextList); const dateTextList = computed(() => props.dateTextList);
const { const {
rangeText, rangeText,
@@ -44,8 +49,21 @@ const {
visibleDateTextList, visibleDateTextList,
handleSliderChange handleSliderChange
} = useStoreChartWindow(dateTextList); } = useStoreChartWindow(dateTextList);
const legendRowCount = computed(() => countLegendRows(eventList.value.map(event => event.name || "未命名事件")));
const chartHeight = computed(() => `${16 + Math.max(0, legendRowCount.value - 1) * 1.5}rem`);
const legendBottom = computed(() => 20 + legendRowCount.value * 24);
const chartStyle = computed(() => ({
height: chartHeight.value
}));
const chartOption = computed<EChartsOption>(() => ({ const chartOption = computed<EChartsOption>(() => ({
...buildStoreBaseChartOption(visibleDateTextList.value), ...buildStoreBaseChartOption(visibleDateTextList.value),
grid: {
top: Math.max(48, legendBottom.value),
left: 12,
right: 12,
bottom: 12,
containLabel: true
},
series: eventList.value.map(event => ({ series: eventList.value.map(event => ({
name: event.name || "未命名事件", name: event.name || "未命名事件",
type: "bar", type: "bar",
@@ -58,6 +76,7 @@ const chartOption = computed<EChartsOption>(() => ({
})); }));
const { chartElement } = useStoreChart(chartOption); const { chartElement } = useStoreChart(chartOption);
let resizeObserver: ResizeObserver | undefined;
watch(() => props.dateTextList, async () => { watch(() => props.dateTextList, async () => {
await loadDailyStat(); await loadDailyStat();
@@ -66,10 +85,20 @@ watch(() => props.dateTextList, async () => {
}); });
onMounted(async () => { onMounted(async () => {
resizeObserver = new window.ResizeObserver(updateChartWidth);
if (chartElement.value) {
resizeObserver.observe(chartElement.value);
updateChartWidth();
}
await loadEvents(); await loadEvents();
await loadDailyStat(); await loadDailyStat();
}); });
onBeforeUnmount(() => {
resizeObserver?.disconnect();
resizeObserver = undefined;
});
async function loadEvents(): Promise<void> { async function loadEvents(): Promise<void> {
eventList.value = await EventAPI.listValid(); eventList.value = await EventAPI.listValid();
} }
@@ -110,6 +139,30 @@ function getEventIdList(): string[] {
return eventList.value.map(item => item.id).filter((id): id is string => Boolean(id)); return eventList.value.map(item => item.id).filter((id): id is string => Boolean(id));
} }
function updateChartWidth(): void {
const width = chartElement.value?.clientWidth || window.innerWidth;
chartWidth.value = Math.max(1, width);
}
function countLegendRows(nameList: string[]): number {
let rowCount = 1;
let rowWidth = 0;
const maxWidth = Math.max(160, chartWidth.value - 24);
for (const name of nameList) {
const itemWidth = estimateLegendItemWidth(name);
if (0 < rowWidth && maxWidth < rowWidth + itemWidth) {
rowCount++;
rowWidth = 0;
}
rowWidth += itemWidth;
}
return rowCount;
}
function estimateLegendItemWidth(name: string): number {
return Math.min(chartWidth.value, 38 + Array.from(name).length * 14);
}
function groupRecordDailyStatByEventId(list: StoreEventRecordDailyStatView[]): Record<string, StoreEventRecordDailyStatView[]> { function groupRecordDailyStatByEventId(list: StoreEventRecordDailyStatView[]): Record<string, StoreEventRecordDailyStatView[]> {
const map: Record<string, StoreEventRecordDailyStatView[]> = {}; const map: Record<string, StoreEventRecordDailyStatView[]> = {};
for (const item of list) { for (const item of list) {
@@ -24,19 +24,32 @@ const eventList = ref<EventItem[]>([]);
const statMap = ref<Record<string, StoreEventRecordDailyStatView[]>>({}); const statMap = ref<Record<string, StoreEventRecordDailyStatView[]>>({});
let statRequestId = 0; let statRequestId = 0;
const shouldRotateAxisLabel = computed(() => eventList.value.length > 4 || eventList.value.some(item => 6 < getEventName(item).length));
const chartOption = computed<EChartsOption>(() => ({ const chartOption = computed<EChartsOption>(() => ({
...buildStoreBaseChartOption(props.dateTextList), ...buildStoreBaseChartOption(props.dateTextList),
grid: {
top: 24,
left: 12,
right: 12,
bottom: shouldRotateAxisLabel.value ? 68 : 36,
containLabel: true
},
legend: { legend: {
show: false show: false
}, },
xAxis: { xAxis: {
type: "category", type: "category",
data: eventList.value.map(item => item.name || "未命名事件"), data: eventList.value.map(getEventName),
axisTick: { axisTick: {
show: false show: false
}, },
axisLabel: { axisLabel: {
color: "#52607a" color: "#52607a",
hideOverlap: true,
interval: 0,
overflow: "truncate",
rotate: shouldRotateAxisLabel.value ? 45 : 0,
width: shouldRotateAxisLabel.value ? 72 : 56
} }
}, },
series: [{ series: [{
@@ -99,6 +112,10 @@ function getEventColor(event: EventItem): string | undefined {
return event.color || undefined; return event.color || undefined;
} }
function getEventName(event: EventItem): string {
return event.name || "未命名事件";
}
function getEventIdList(): string[] { function getEventIdList(): string[] {
return eventList.value.map(item => item.id).filter((id): id is string => Boolean(id)); return eventList.value.map(item => item.id).filter((id): id is string => Boolean(id));
} }
@@ -87,6 +87,7 @@ export function useStoreChart(option: ComputedRef<EChartsOption> | Ref<EChartsOp
} }
chart = chart || init(chartElement.value); chart = chart || init(chartElement.value);
chart.setOption(option.value, true); chart.setOption(option.value, true);
chart.resize();
} }
function resizeChart(): void { function resizeChart(): void {