From 4c130cee9d433dc0c21d8fe73a10eb65acad2a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E9=AA=84?= <5307576@qq.com> Date: Mon, 15 Jun 2026 17:55:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=94=B9=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/cases.ts | 62 ++++++++++++++++++++++++++ pages/cases/cases.vue | 13 +++++- pages/matching/matching.vue | 89 +++++++++++++++++++++++++++++++------ 3 files changed, 148 insertions(+), 16 deletions(-) diff --git a/api/cases.ts b/api/cases.ts index 60d1cca..c537993 100644 --- a/api/cases.ts +++ b/api/cases.ts @@ -48,6 +48,13 @@ export type CaseListPage = { results: ClinicalCase[] } +export type CaseListPrefetch = { + source: CaseListSource + query: CaseListQuery + page: CaseListPage + cachedAt: number +} + type QueryValue = string | number | boolean | null | undefined type ServerCaseListPage = { @@ -88,6 +95,8 @@ const CASE_LIST_PATHS: Record = { } const TONES: CaseTone[] = ['blue', 'teal', 'pink', 'orange', 'purple', 'green'] +const CASE_LIST_PREFETCH_STORAGE_KEY = 'clinical-thinking-prefetched-case-list' +const CASE_LIST_PREFETCH_MAX_AGE_MS = 60 * 1000 export async function fetchCaseList(source: CaseListSource = 'recommended', query: CaseListQuery = {}): Promise { const page = await fetchCaseListPage(source, query) @@ -102,6 +111,30 @@ export async function fetchCaseListPage(source: CaseListSource = 'recommended', return page } +export function savePrefetchedCaseList(payload: CaseListPrefetch) { + uni.setStorageSync(CASE_LIST_PREFETCH_STORAGE_KEY, payload) +} + +export function clearPrefetchedCaseList() { + uni.removeStorageSync(CASE_LIST_PREFETCH_STORAGE_KEY) +} + +export function takePrefetchedCaseList(source: CaseListSource, query: CaseListQuery): CaseListPage | null { + const value = uni.getStorageSync(CASE_LIST_PREFETCH_STORAGE_KEY) + if (!isCaseListPrefetch(value)) return null + + const isFresh = Date.now() - value.cachedAt <= CASE_LIST_PREFETCH_MAX_AGE_MS + if (!isFresh) { + clearPrefetchedCaseList() + return null + } + + if (value.source !== source || !isSameCaseListQuery(value.query, query)) return null + + clearPrefetchedCaseList() + return value.page +} + export function readStoredClinicalCase() { const value = uni.getStorageSync('clinical-thinking-selected-case') if (value && typeof value === 'object') return value as ClinicalCase @@ -224,6 +257,35 @@ function withQuery(path: string, query: Record) { return params ? `${path}?${params}` : path } +function isCaseListPrefetch(value: unknown): value is CaseListPrefetch { + if (!value || typeof value !== 'object') return false + const payload = value as Partial + return isCaseListSource(payload.source) && + typeof payload.query === 'object' && + payload.query !== null && + !!payload.page && + typeof payload.page === 'object' && + Array.isArray(payload.page.results) && + typeof payload.cachedAt === 'number' +} + +function isCaseListSource(value: unknown): value is CaseListSource { + return value === 'recommended' || + value === 'specialty' || + value === 'weak' || + value === 'teaching' || + value === 'teacher-task' +} + +function isSameCaseListQuery(left: CaseListQuery, right: CaseListQuery) { + const keys: Array = ['search', 'case_type', 'difficulty', 'department', 'page', 'page_size'] + return keys.every(key => normalizeQueryValue(left[key]) === normalizeQueryValue(right[key])) +} + +function normalizeQueryValue(value: unknown) { + return value === undefined || value === null || value === '' ? '' : String(value) +} + function readErrorMessage(data: unknown, fallback: string) { if (data && typeof data === 'object') { const payload = data as Record diff --git a/pages/cases/cases.vue b/pages/cases/cases.vue index 9e0e8a7..a201eed 100644 --- a/pages/cases/cases.vue +++ b/pages/cases/cases.vue @@ -141,7 +141,13 @@