diff --git a/src/pages/event/EventRecordRank.vue b/src/pages/event/EventRecordRank.vue index ca85a09..79e7b17 100644 --- a/src/pages/event/EventRecordRank.vue +++ b/src/pages/event/EventRecordRank.vue @@ -85,20 +85,29 @@ @@ -138,6 +147,7 @@ import type { CustomerEventRankView, EventRecordRankPage, EventRecordRankType } import type { EventItem } from "@/types/EventItem"; import { getCustomerPhotoURL } from "@/utils/customer"; import { getErrorMessage } from "@/utils/error"; +import { formatEventNumberValue } from "@/utils/eventValue"; import { permissionCodes } from "@/utils/permission"; type DateFilterType = "all" | "today" | "yesterday" | "lastWeek" | "thisMonth" | "custom"; @@ -152,7 +162,8 @@ const router = useRouter(); const rankTypeOptions: Array<{ label: string; value: EventRecordRankType }> = [ { label: "按登记天数", value: "TOTAL_DAYS" }, { label: "按登记次数", value: "TOTAL_COUNT" }, - { label: "按连续天数", value: "CONTINUOUS_DAYS" } + { label: "按连续天数", value: "CONTINUOUS_DAYS" }, + { label: "按登记数值", value: "TOTAL_NUMBER_VALUE" } ]; const dateOptions: Array<{ label: string; value: DateFilterType }> = [ { label: "全部时间", value: "all" }, @@ -177,25 +188,45 @@ let initialized = false; const isCustomRange = computed(() => dateFilterType.value === "custom"); const rankTypeLabel = computed(() => rankTypeOptions.find(item => item.value === rankType.value)?.label || "按登记天数"); const dateFilterLabel = computed(() => dateOptions.find(item => item.value === dateFilterType.value)?.label || "全部时间"); -const eventOptions = computed(() => eventList.value.map(item => ({ +const selectableEventList = computed(() => rankType.value === "TOTAL_NUMBER_VALUE" + ? eventList.value.filter(item => item.valueType === "NUMBER") + : eventList.value); +const eventOptions = computed(() => selectableEventList.value.map(item => ({ label: item.name || "未命名事件", value: item.id || "" })).filter(item => item.value)); const eventFilterLabel = computed(() => { - if (!eventFilter.value.length || eventFilter.value.length === eventList.value.length) { - return "全部事件"; + if (!eventFilter.value.length || eventFilter.value.length === selectableEventList.value.length) { + return rankType.value === "TOTAL_NUMBER_VALUE" ? "全部有数值事件" : "全部事件"; } - return eventList.value + return selectableEventList.value .filter(item => item.id && eventFilter.value.includes(item.id)) .map(item => item.name || "未命名事件") .join("、"); }); -const canLoadRank = computed(() => rankType.value !== "CONTINUOUS_DAYS" || eventFilter.value.length === 1); +const selectedNumberEvent = computed(() => { + if (rankType.value !== "TOTAL_NUMBER_VALUE" || eventFilter.value.length !== 1) { + return undefined; + } + return selectableEventList.value.find(item => item.id === eventFilter.value[0]); +}); +const canLoadRank = computed(() => { + if (rankType.value === "CONTINUOUS_DAYS") { + return eventFilter.value.length === 1; + } + if (rankType.value === "TOTAL_NUMBER_VALUE") { + return Boolean(selectedNumberEvent.value); + } + return true; +}); const emptyStateText = computed(() => { if (isLoading.value) { return "正在加载登记记录排行"; } + if (rankType.value === "TOTAL_NUMBER_VALUE") { + return "数值排行需要选择单个有数值事件"; + } return canLoadRank.value ? "暂无登记记录排行" : "连续排行需要选择单个登记事件"; }); @@ -299,6 +330,7 @@ async function loadEvents(): Promise { eventFilter.value = eventIdList.length ? eventIdList : eventList.value.map(item => item.id).filter((item): item is string => Boolean(item)); + normalizeEventFilter(); } async function selectRankType(value: DropdownValue): Promise { @@ -307,11 +339,14 @@ async function selectRankType(value: DropdownValue): Promise { } rankType.value = parseRankType(value); + const filterChanged = normalizeEventFilter(); await replaceFilterQuery(); if (!canLoadRank.value) { - Toast.warning("连续排行需要选择单个登记事件"); + Toast.warning(rankType.value === "TOTAL_NUMBER_VALUE" ? "数值排行需要选择单个有数值事件" : "连续排行需要选择单个登记事件"); + } + if (!filterChanged) { + await reloadRanks(); } - await reloadRanks(); } async function selectDateFilter(value: DropdownValue): Promise { @@ -348,9 +383,14 @@ function getCustomerInitial(item: CustomerEventRankView): string { return getCustomerName(item).slice(0, 1); } +function getTotalNumberValueText(item: CustomerEventRankView): string { + return formatEventNumberValue(selectedNumberEvent.value, item.totalNumberValue); +} + function buildRankReq(): Omit { const [beginAt, endAt] = buildUnixRange(); - const selectedAllEvent = rankType.value !== "CONTINUOUS_DAYS" && (!eventFilter.value.length || eventFilter.value.length === eventList.value.length); + const selectedAllEvent = (rankType.value === "TOTAL_DAYS" || rankType.value === "TOTAL_COUNT") + && (!eventFilter.value.length || eventFilter.value.length === eventList.value.length); return { beginAt, endAt, @@ -387,6 +427,19 @@ function parseEventIdList(value: unknown): string[] { return text.split(",").map(item => item.trim()).filter(Boolean); } +function normalizeEventFilter(): boolean { + const eventIdSet = new Set(selectableEventList.value.map(item => item.id).filter((item): item is string => Boolean(item))); + let nextEventIdList = eventFilter.value.filter(item => eventIdSet.has(item)); + if (!nextEventIdList.length) { + nextEventIdList = [...eventIdSet]; + } + const changed = nextEventIdList.join(",") !== eventFilter.value.join(","); + if (changed) { + eventFilter.value = nextEventIdList; + } + return changed; +} + function buildFilterQuery(): Record { return { rankType: rankType.value, diff --git a/src/types/EventRecord.ts b/src/types/EventRecord.ts index 9e01545..927000b 100644 --- a/src/types/EventRecord.ts +++ b/src/types/EventRecord.ts @@ -53,7 +53,7 @@ export type EventRecordRankPage = { endAt?: number; } & BasePage; -export type EventRecordRankType = "TOTAL_DAYS" | "TOTAL_COUNT" | "CONTINUOUS_DAYS"; +export type EventRecordRankType = "TOTAL_DAYS" | "TOTAL_COUNT" | "CONTINUOUS_DAYS" | "TOTAL_NUMBER_VALUE"; export type CustomerEventRankView = { rank?: number; @@ -62,6 +62,7 @@ export type CustomerEventRankView = { totalEventDays?: number; totalEventCount?: number; recentContinuousEventDays?: number; + totalNumberValue?: number; latestRegisteredAt?: number; };