fix: 错误的文档导致文案错误
parent
94f7adc407
commit
b1072b825b
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue