fix: 错误的文档导致文案错误

master
xjs 2026-07-29 16:14:38 +08:00
parent 94f7adc407
commit b1072b825b
3 changed files with 105 additions and 23 deletions

View File

@ -18,7 +18,7 @@
</button>
</header>
<div class="flex shrink-0 items-center bg-white">
<div class="flex shrink-0 items-center bg-white" @compositionstart="onCompositionStart" @compositionend="onCompositionEnd">
<label class="sr-only" :for="searchInputId">{{ searchPlaceholder }}</label>
<VanSearch
ref="searchRef"
@ -30,6 +30,7 @@
:id="searchInputId"
:placeholder="searchPlaceholder"
@clear="handleClear"
@update:model-value="handleSearchInput"
@search="handleSearch" />
<button
class="shrink-0 border-0 bg-transparent py-[1.5rem] pl-[2.5rem] pr-[3.75rem] text-[4rem] text-[#1580FF] leading-none"
@ -96,7 +97,7 @@
</template>
<script setup lang="ts">
import { computed, nextTick, onBeforeMount, ref, useId, watch, type HTMLAttributes } from "vue";
import { computed, nextTick, onBeforeMount, onBeforeUnmount, ref, useId, watch, type HTMLAttributes } from "vue";
import {
Empty as VanEmpty,
Highlight as VanHighlight,
@ -169,6 +170,7 @@
const searchKeyword = ref("");
const submittedKeyword = ref("");
const isComposing = ref(false);
const activeRegionIndex = ref(0);
const visibleSchools = ref<SchoolOption[]>([]);
const currentPage = ref(0);
@ -186,6 +188,8 @@
const regions = ref<SchoolRegion[]>([]);
let regionsLoaded: Promise<void> = Promise.resolve();
let regionsLoading = false;
let searchTimer: ReturnType<typeof setTimeout> | null = null;
const SEARCH_DEBOUNCE_MS = 300;
// Promise
const loadRegions = (): Promise<void> => {
@ -271,14 +275,46 @@
loading.value = false;
};
const clearSearchTimer = (): void => {
if (searchTimer) {
clearTimeout(searchTimer);
searchTimer = null;
}
};
const handleSearch = (value: string | number = searchKeyword.value): void => {
if (isComposing.value) return;
clearSearchTimer();
const keyword = String(value).trim();
searchKeyword.value = keyword;
submittedKeyword.value = keyword;
emit("search", keyword);
};
const handleSearchInput = (value: string | number): void => {
if (isComposing.value) return;
clearSearchTimer();
searchTimer = setTimeout(() => {
searchTimer = null;
handleSearch(value);
}, SEARCH_DEBOUNCE_MS);
};
const onCompositionStart = (): void => {
isComposing.value = true;
clearSearchTimer();
};
const onCompositionEnd = (): void => {
isComposing.value = false;
// Vue compositionend
void nextTick(() => handleSearch(searchKeyword.value));
};
const showAllSchools = (): void => {
clearSearchTimer();
searchKeyword.value = "";
submittedKeyword.value = "";
emit("search", "");
@ -313,7 +349,9 @@
};
const handleOpened = (): void => {
nextTick(() => searchRef.value?.focus());
if (searchKeyword.value.trim()) {
void nextTick(() => searchRef.value?.focus());
}
};
const handleCancel = (): void => {
@ -327,6 +365,7 @@
// onBeforeMount watch(show)
// keep-alive VanList
onBeforeMount(loadRegions);
onBeforeUnmount(clearSearchTimer);
// onBeforeMount
//
@ -405,6 +444,6 @@
}
:deep(.van-empty) {
--van-empty-padding: 0;
}
--van-empty-padding: 0;
}
</style>

View File

@ -3,10 +3,9 @@ import { showToast } from "vant";
import "vant/es/toast/style";
import {
cancelReceptionTask,
fetchReceptionStatistics,
fetchReceptionSummary,
fetchReceptionTaskPage,
type ReceptionStatisticsDTO,
type ReceptionPaymentStatusCode,
type ReceptionTaskDTO,
type ReceptionTaskStatus,
} from "@/api/reception";
@ -14,12 +13,27 @@ import { useUserStore } from "@/store/user";
const ACTIVE_TASK_PAGE_SIZE = 100;
const ACTIVE_STATUSES: ReadonlyArray<ReceptionTaskStatus> = [2, 3];
const OVERVIEW_PAGE_SIZE = 1;
const PAYMENT_STATUS_CODES: readonly ReceptionPaymentStatusCode[] = ["paid", "unpaid", "refunded", "toVerify"];
interface ActiveTaskResult {
tasks: ReceptionTaskDTO[];
message: string;
}
interface ReceptionOverview {
total: number;
paid: number;
unpaid: number;
refunded: number;
toVerify: number;
}
interface ReceptionOverviewResult {
data: ReceptionOverview | null;
message: string;
}
const fetchActiveTasks = async (): Promise<ActiveTaskResult> => {
const result = await fetchReceptionTaskPage({
page: 1,
@ -35,11 +49,44 @@ const fetchActiveTasks = async (): Promise<ActiveTaskResult> => {
};
};
/** 使用接待数据页同一分页接口的 total 构建累计缴费概览。 */
const fetchReceptionOverview = async (): Promise<ReceptionOverviewResult> => {
const [allResult, ...paymentResults] = await Promise.all([
fetchReceptionTaskPage({ page: 1, pageSize: OVERVIEW_PAGE_SIZE, scope: "completed" }),
...PAYMENT_STATUS_CODES.map((paymentStatusCode) =>
fetchReceptionTaskPage({
page: 1,
pageSize: OVERVIEW_PAGE_SIZE,
scope: "completed",
paymentStatusCode,
}),
),
]);
const results = [allResult, ...paymentResults];
const failedResult = results.find((result) => !result.data);
if (failedResult) {
return { data: null, message: failedResult.message };
}
const [paidResult, unpaidResult, refundedResult, toVerifyResult] = paymentResults;
return {
data: {
total: allResult.data?.total ?? 0,
paid: paidResult?.data?.total ?? 0,
unpaid: unpaidResult?.data?.total ?? 0,
refunded: refundedResult?.data?.total ?? 0,
toVerify: toVerifyResult?.data?.total ?? 0,
},
message: "",
};
};
/** /reception/mine 的个人摘要、累计统计和进行中任务。 */
export function useReceptionMine() {
const userStore = useUserStore();
const summary = computed(() => userStore.getReceptionSummary);
const statistics = ref<ReceptionStatisticsDTO | null>(null);
const overview = ref<ReceptionOverview | null>(null);
const ongoingTasks = ref<ReceptionTaskDTO[]>([]);
const loading = ref(false);
const error = ref(false);
@ -52,17 +99,13 @@ export function useReceptionMine() {
error.value = false;
try {
const [summaryResult, statisticsResult, activeTaskResult] = await Promise.all([
fetchReceptionSummary(),
fetchReceptionStatistics({ scope: "mine" }),
fetchActiveTasks(),
]);
const [summaryResult, overviewResult, activeTaskResult] = await Promise.all([fetchReceptionSummary(), fetchReceptionOverview(), fetchActiveTasks()]);
if (summaryResult.data) userStore.setReceptionSummary(summaryResult.data);
if (statisticsResult.data) statistics.value = statisticsResult.data;
if (overviewResult.data) overview.value = overviewResult.data;
ongoingTasks.value = activeTaskResult.tasks;
const messages = [summaryResult.message, statisticsResult.message, activeTaskResult.message].filter(Boolean);
const messages = [summaryResult.message, overviewResult.message, activeTaskResult.message].filter(Boolean);
error.value = messages.length > 0;
if (messages[0]) showToast(messages[0]);
} finally {
@ -95,7 +138,7 @@ export function useReceptionMine() {
return {
summary,
statistics,
overview,
ongoingTasks,
loading,
error,

View File

@ -34,7 +34,7 @@
:key="item.label"
class="flex flex-1 flex-col items-center"
:class="{ 'border-l border-solid border-[#EEF0F2]': index > 0 }">
<span class="din-bold text-[5rem] font-700 leading-none text-[#000]">{{ item.value }}</span>
<span class="din-bold text-[5rem] font-700 leading-none" :style="{ color: item.color }">{{ item.value }}</span>
<span class="mt-4 text-[3.25rem] leading-none text-[#666]">{{ item.label }}</span>
</div>
</div>
@ -138,18 +138,18 @@
import { useReceptionMine } from "@/composables/useReceptionMine";
const router = useRouter();
const { summary, statistics, ongoingTasks, loading, error, cancellingTaskId, loadMine, cancelTask } = useReceptionMine();
const { summary, overview, ongoingTasks, loading, error, cancellingTaskId, loadMine, cancelTask } = useReceptionMine();
const showCancelDialog = ref(false);
const pendingCancelTask = ref<ReceptionTaskDTO | null>(null);
type CancelDialogAction = "confirm" | "cancel";
const receptionistGreeting = computed(() => (summary.value?.receptionistName ? `${summary.value.receptionistName}老师,辛苦了` : "辛苦了"));
const stats = computed(() => [
{ label: "待接待", value: summary.value?.waitingCount ?? statistics.value?.waitingCount ?? 0 },
{ label: "接待中", value: statistics.value?.inProgressCount ?? summary.value?.inProgressCount ?? 0 },
{ label: "已完成", value: statistics.value?.completedCount ?? summary.value?.totalCompletedCount ?? 0 },
{ label: "已报名", value: statistics.value?.registeredCount ?? 0 },
{ label: "已缴费", value: statistics.value?.paidCount ?? 0 },
{ label: "总接待", value: overview.value?.total ?? 0, color: "#000" },
{ label: "已缴费", value: overview.value?.paid ?? 0, color: "#000" },
{ label: "未缴费", value: overview.value?.unpaid ?? 0, color: "#FF2928" },
{ label: "已退费", value: overview.value?.refunded ?? 0, color: "#000" },
{ label: "待核销", value: overview.value?.toVerify ?? 0, color: "#000" },
]);
const formatCheckinTime = (value: string): string => {