From fea9c97209e001538c89a8c784c44803faffc6b3 Mon Sep 17 00:00:00 2001 From: popWheat <49830650+j10ccc@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:41:39 +0800 Subject: [PATCH] refactor(exam): rewrite in hooks (#158) --- src/components/ExamQuickView/index.vue | 71 ++++++++----------------- src/pages/exam/index.vue | 57 ++++++++++---------- src/services/services/zfService.ts | 2 +- src/store/service/exam/collections.ts | 72 ++++++++++++++++++++++++++ src/store/service/exam/queryOptions.ts | 26 ++++++++++ src/utils/promise.ts | 23 ++++++++ 6 files changed, 171 insertions(+), 80 deletions(-) create mode 100644 src/store/service/exam/collections.ts create mode 100644 src/store/service/exam/queryOptions.ts create mode 100644 src/utils/promise.ts diff --git a/src/components/ExamQuickView/index.vue b/src/components/ExamQuickView/index.vue index d5c483ea..10ffdc86 100644 --- a/src/components/ExamQuickView/index.vue +++ b/src/components/ExamQuickView/index.vue @@ -11,13 +11,13 @@ 近期考试 ({{ updateTimeString }}) 未查询到近日考试信息 { - if (updateTime.value !== undefined) return dayjs(updateTime.value).fromNow(); - else return "更新失败!"; -}); +const examStore = useExamStore(); /** - * 筛选近期考试 - * - * 未来3日 + * 筛选出未来 3 日的考试 */ -const filteredExamItems = computed(() => { - let list: Exam[] = []; - const exam = ZFService.getExamInfo(selectTerm.value)?.data; - try { - list = exam.filter(item => { - if (item.examTime === "未放开不可查") return 0; - const { date, start } = getExamTime(item.examTime); - // 距离考试的剩余时间(ms),为正表示考试为开始,为负表示考试结束 - const resDay = timeUtils.getDayInterval( - new Date(date + " " + start + ":00") - ); - return (resDay <= 3 && resDay >= 0 && examState(item.examTime) !== "after"); - }); - } catch (e) { - console.error(e); - } - return list.sort((a, b) => { +const filteredExams = computed(() => { + const examsInTerm = examStore.queryByTermSync()?.exams || []; + const filtered = examsInTerm.filter(item => { + if (item.examTime === "未放开不可查") return 0; + const { date, start } = getExamTime(item.examTime); + // 距离考试的剩余时间(ms),为正表示考试为开始,为负表示考试结束 + const resDay = timeUtils.getDayInterval( + new Date(date + " " + start + ":00") + ); + return (resDay <= 3 && resDay >= 0 && examState(item.examTime) !== "after"); + }); + return filtered.sort((a, b) => { const { date: dateA, start: timeA } = getExamTime(a.examTime); const { date: dateB, start: timeB } = getExamTime(b.examTime); return dayjs(`${dateA}-${timeA}`) < dayjs(`${dateB}-${timeB}`) ? 1 : -1; }); }); -const updateTime = computed(() => { - let updata: Date | null = null; - try { - updata = ZFService.getExamInfo(selectTerm.value)?.updateTime; - if (updata === null) return undefined; - else return updata; - } catch { - return undefined; - } +const updateTimeString = computed(() => { + const updateTime = examStore.queryByTermSync()?.updateTime; + if (examStore.error || !updateTime) return "更新失败"; + return dayjs(updateTime).fromNow(); }); function nav2Exam() { @@ -169,8 +146,4 @@ function examState(examTimeString: string) { else return "after"; } -onMounted(() => { - ZFService.updateExamInfo(selectTerm.value); -}); - diff --git a/src/pages/exam/index.vue b/src/pages/exam/index.vue index 7a65bd48..fa476660 100644 --- a/src/pages/exam/index.vue +++ b/src/pages/exam/index.vue @@ -14,11 +14,11 @@ - + 无记录 - + @@ -118,8 +118,7 @@ diff --git a/src/services/services/zfService.ts b/src/services/services/zfService.ts index d9e41e5e..0b436cf3 100644 --- a/src/services/services/zfService.ts +++ b/src/services/services/zfService.ts @@ -6,7 +6,7 @@ import { request } from "@/utils"; import { Room } from "@/types/Room"; export default class ZFService { - static getExamInfo(params: { year: string; term: string }) { + static getExamInfo(params: { year: string; term: "上" | "下" | "短" }) { return request( api.zf.examInfo, { method: "POST", diff --git a/src/store/service/exam/collections.ts b/src/store/service/exam/collections.ts new file mode 100644 index 00000000..d27d3004 --- /dev/null +++ b/src/store/service/exam/collections.ts @@ -0,0 +1,72 @@ +import { useRequestNext } from "@/hooks"; +import { ZFService } from "@/services"; +import useGeneralInfoStore from "@/store/system/generalInfo"; +import { Exam } from "@/types/Exam"; +import { persistedStorage, RequestError } from "@/utils"; +import { withRespDataNeverNull } from "@/utils/promise"; +import Taro from "@tarojs/taro"; +import { defineStore, storeToRefs } from "pinia"; +import { ref } from "vue"; + +type TermExamCollection = { + exams: Exam[], + year: string; + term: "上" | "下" | "短"; + updateTime: string; +}; + +const useExamStore = defineStore("exam/collections", () => { + const { info: generalInfo } = storeToRefs(useGeneralInfoStore()); + const collections = ref([]); + + const { error, loading, run: fetchExam } = useRequestNext( + withRespDataNeverNull(ZFService.getExamInfo), { + defaultParams: { + year: generalInfo.value.termYear, + term: generalInfo.value.term + }, + initialData: [], + onSuccess: (exams, params) => { + const { year, term } = params!; + const existedIndex = collections.value.findIndex(_ => _.year === year && _.term === term); + + if (existedIndex !== -1) { + collections.value[existedIndex] = { + ...collections.value[existedIndex], + exams, + updateTime: Date().toString() + }; + } else { + collections.value.push({ exams, year, term, updateTime: Date().toString() }); + } + }, + onError: (e) => { + if (e instanceof RequestError) { + Taro.showToast({ title: `更新成绩失败: ${e.message}`, icon: "none" }); + } + } + } + ); + + function queryByTermSync(options?: { year: string, term: "上" | "下" | "短" }) { + const { year, term } = options ?? { term: generalInfo.value.term, year: generalInfo.value.termYear }; + const existed = collections.value.find(_ => _.year === year && _.term === term); + + return existed; + } + + return { + loading, + collections, + fetchExam, + error, + queryByTermSync + }; +}, { + persist: { + storage: persistedStorage, + pick: ["collections"] + } +}); + +export default useExamStore; diff --git a/src/store/service/exam/queryOptions.ts b/src/store/service/exam/queryOptions.ts new file mode 100644 index 00000000..a2751814 --- /dev/null +++ b/src/store/service/exam/queryOptions.ts @@ -0,0 +1,26 @@ +import useGeneralInfoStore from "@/store/system/generalInfo"; +import { defineStore, storeToRefs } from "pinia"; +import { ref } from "vue"; + +const useExamQueryOptionsStore = defineStore("exam/queryOptions", () => { + const { info: generalInfo } = storeToRefs(useGeneralInfoStore()); + + const term = ref<"上" | "下" | "短">(generalInfo.value.scoreTerm); + const year = ref(generalInfo.value.scoreYear); + + function setOption(value: { + term: "上" | "下" | "短"; + year: string; + }) { + term.value = value.term; + year.value = value.year; + } + + return { + term, + year, + setOption + }; +}); + +export default useExamQueryOptionsStore; diff --git a/src/utils/promise.ts b/src/utils/promise.ts new file mode 100644 index 00000000..10bdd902 --- /dev/null +++ b/src/utils/promise.ts @@ -0,0 +1,23 @@ +import RequestError, { MPErrorCode } from "./request/requestError"; + +/** + * 当 promise 返回的结果为 null 时抛出异常 + * + * 正方的每个接口都有较大的概率返回 data: null + */ +export function withRespDataNeverNull( + fetcher: (...args: Args) => Promise, + options?: { + errMsg?: string + } +) { + const { errMsg = "响应数据格式异常" } = options ?? {}; + + return async (...args: Args) => { + const resp = await fetcher(...args); + if (resp === null) + throw new RequestError(errMsg, MPErrorCode.MP_INVALID_DATA_VALUE); + + return resp as T; + }; +}