diff --git a/src/components/EventRecordCalendar.vue b/src/components/EventRecordCalendar.vue
index 429bbac..74370d6 100644
--- a/src/components/EventRecordCalendar.vue
+++ b/src/components/EventRecordCalendar.vue
@@ -63,6 +63,9 @@
:title="getRecordEventName(item)"
:description="getRecordCustomerName(item)"
:note="`${formatRecordTime(item.registeredAt)}`"
+ arrow
+ hover
+ @click="toRecordDetail(item)"
/>
该日暂无登记事件记录
@@ -130,6 +133,8 @@ const emit = defineEmits<{
"update:selectedEventIdList": [value: string[]];
}>();
+const route = useRoute();
+const router = useRouter();
const weekNameList = ["日", "一", "二", "三", "四", "五", "六"];
const dayRecordVisible = ref(false);
const eventFilterVisible = ref(false);
@@ -192,6 +197,20 @@ function openDayRecords(day: CalendarDay): void {
dayRecordVisible.value = true;
}
+async function toRecordDetail(record: EventRecord): Promise {
+ 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 {
if (!record) {
return "";
@@ -226,6 +245,10 @@ function isInCurrentMonth(value?: number): boolean {
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 {
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
}
diff --git a/src/components/PageTransition.vue b/src/components/PageTransition.vue
index 2a5d93b..f2c6eee 100644
--- a/src/components/PageTransition.vue
+++ b/src/components/PageTransition.vue
@@ -30,6 +30,11 @@ const pageBackground = computed(() => globalUIStore.bodyBackground);
const pathCache = new Map();
function calcDepth(sourceRoute: RouteLocationNormalized): number {
+ const queryDepth = parseTransitionDepth(sourceRoute.query.transitionDepth);
+ if (queryDepth !== undefined) {
+ return queryDepth;
+ }
+
if (!sourceRoute.meta.dynamicDepth) {
return Number(sourceRoute.meta.depth ?? 0);
}
@@ -47,6 +52,22 @@ function calcDepth(sourceRoute: RouteLocationNormalized): number {
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 toDepth = calcDepth(to);
const fromDepth = calcDepth(from);
diff --git a/src/pages/store/component/StoreCustomerGenderChart.vue b/src/pages/store/component/StoreCustomerGenderChart.vue
index a129b02..65585a8 100644
--- a/src/pages/store/component/StoreCustomerGenderChart.vue
+++ b/src/pages/store/component/StoreCustomerGenderChart.vue
@@ -27,6 +27,16 @@ const statList = ref([]);
const chartOption = computed(() => ({
...buildStoreBaseChartOption(props.dateTextList),
+ grid: {
+ top: 24,
+ left: 12,
+ right: 12,
+ bottom: 12,
+ containLabel: true
+ },
+ legend: {
+ show: false
+ },
tooltip: {
trigger: "item"
},
diff --git a/src/pages/store/component/StoreEventRecordTrendChart.vue b/src/pages/store/component/StoreEventRecordTrendChart.vue
index 20c8f29..8582a70 100644
--- a/src/pages/store/component/StoreEventRecordTrendChart.vue
+++ b/src/pages/store/component/StoreEventRecordTrendChart.vue
@@ -1,7 +1,11 @@
登记量趋势
-
+
props.dateTextList);
const {
rangeText,
@@ -44,8 +49,21 @@ const {
visibleDateTextList,
handleSliderChange
} = 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(() => ({
...buildStoreBaseChartOption(visibleDateTextList.value),
+ grid: {
+ top: Math.max(48, legendBottom.value),
+ left: 12,
+ right: 12,
+ bottom: 12,
+ containLabel: true
+ },
series: eventList.value.map(event => ({
name: event.name || "未命名事件",
type: "bar",
@@ -58,6 +76,7 @@ const chartOption = computed(() => ({
}));
const { chartElement } = useStoreChart(chartOption);
+let resizeObserver: ResizeObserver | undefined;
watch(() => props.dateTextList, async () => {
await loadDailyStat();
@@ -66,10 +85,20 @@ watch(() => props.dateTextList, async () => {
});
onMounted(async () => {
+ resizeObserver = new window.ResizeObserver(updateChartWidth);
+ if (chartElement.value) {
+ resizeObserver.observe(chartElement.value);
+ updateChartWidth();
+ }
await loadEvents();
await loadDailyStat();
});
+onBeforeUnmount(() => {
+ resizeObserver?.disconnect();
+ resizeObserver = undefined;
+});
+
async function loadEvents(): Promise {
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));
}
+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 {
const map: Record = {};
for (const item of list) {
diff --git a/src/pages/store/component/StoreEventStructureChart.vue b/src/pages/store/component/StoreEventStructureChart.vue
index b93952a..92b90dd 100644
--- a/src/pages/store/component/StoreEventStructureChart.vue
+++ b/src/pages/store/component/StoreEventStructureChart.vue
@@ -24,19 +24,32 @@ const eventList = ref([]);
const statMap = ref>({});
let statRequestId = 0;
+const shouldRotateAxisLabel = computed(() => eventList.value.length > 4 || eventList.value.some(item => 6 < getEventName(item).length));
const chartOption = computed(() => ({
...buildStoreBaseChartOption(props.dateTextList),
+ grid: {
+ top: 24,
+ left: 12,
+ right: 12,
+ bottom: shouldRotateAxisLabel.value ? 68 : 36,
+ containLabel: true
+ },
legend: {
show: false
},
xAxis: {
type: "category",
- data: eventList.value.map(item => item.name || "未命名事件"),
+ data: eventList.value.map(getEventName),
axisTick: {
show: false
},
axisLabel: {
- color: "#52607a"
+ color: "#52607a",
+ hideOverlap: true,
+ interval: 0,
+ overflow: "truncate",
+ rotate: shouldRotateAxisLabel.value ? 45 : 0,
+ width: shouldRotateAxisLabel.value ? 72 : 56
}
},
series: [{
@@ -99,6 +112,10 @@ 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));
}
diff --git a/src/pages/store/component/useStoreChart.ts b/src/pages/store/component/useStoreChart.ts
index 25c8bfb..d910ad7 100644
--- a/src/pages/store/component/useStoreChart.ts
+++ b/src/pages/store/component/useStoreChart.ts
@@ -87,6 +87,7 @@ export function useStoreChart(option: ComputedRef | Ref