Skip to content

Commit

Permalink
refactor(exam): rewrite in hooks (#158)
Browse files Browse the repository at this point in the history
  • Loading branch information
j10ccc authored Nov 13, 2024
1 parent 4f2e5e4 commit fea9c97
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 80 deletions.
71 changes: 22 additions & 49 deletions src/components/ExamQuickView/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
近期考试 ({{ updateTimeString }})
</text>
<card
v-if="!filteredExamItems || filteredExamItems.length === 0"
v-if="!filteredExams || filteredExams.length === 0"
style="text-align: center"
>
未查询到近日考试信息
</card>
<card
v-for="item in filteredExamItems"
v-for="item in filteredExams"
v-else
:key="item.id"
class="exam-card"
Expand Down Expand Up @@ -71,62 +71,39 @@ import Card from "../Card/index.vue";
import QuickView from "../QuickView/index.vue";
import Taro from "@tarojs/taro";
import "./index.scss";
import { computed, onMounted, ref } from "vue";
import { systemStore } from "@/store";
import { ZFService } from "@/services";
import { computed } from "vue";
import dayjs from "dayjs";
import { timeUtils } from "@/utils";
import { Exam } from "@/types/Exam";
import useExamStore from "@/store/service/exam/collections";
const emit = defineEmits(["showHelp"]);
const selectTerm = ref({
year: systemStore.generalInfo.termYear,
term: systemStore.generalInfo.score || systemStore.generalInfo.term
});
const updateTimeString = computed(() => {
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() {
Expand Down Expand Up @@ -169,8 +146,4 @@ function examState(examTimeString: string) {
else return "after";
}
onMounted(() => {
ZFService.updateExamInfo(selectTerm.value);
});
</script>
57 changes: 27 additions & 30 deletions src/pages/exam/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
</view>
</view>
<view class="flex-column">
<card v-if="!exam || exam.length === 0" style="text-align: center">
<card v-if="examList.length === 0" style="text-align: center">
<view>无记录</view>
</card>
<card
v-for="item in exam"
v-for="item in examList"
:key="item.id"
size="small"
class="exam-card"
Expand Down Expand Up @@ -103,23 +103,22 @@
<view class="col">
<term-picker
class="picker"
:year="selectTerm.year"
:term="selectTerm.term"
:year="queryOptions.year"
:term="queryOptions.term"
:selectflag="0"
@changed="termChanged"
/>
</view>
<view class="col">
<refresh-button :is-refreshing="isRefreshing" @refresh="refresh" />
<refresh-button :is-refreshing="examStore.loading" @refresh="refresh" />
</view>
</bottom-panel>
<w-modal v-model:show="showModal" title="公告" :content="helpContent" />
</theme-config>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { serviceStore, systemStore } from "@/store";
import { computed, ref } from "vue";
import {
BottomPanel,
Card,
Expand All @@ -133,36 +132,40 @@ import {
WDescriptionsItem,
WModal
} from "@/components";
import { ZFService } from "@/services";
import dayjs, { ConfigType } from "dayjs";
import { helpText } from "@/constants/copywriting";
import { Image as TaroImage } from "@tarojs/components";
import ExamCoverImage from "@/assets/photos/exam.svg";
import "./index.scss";
import useExamStore from "@/store/service/exam/collections";
import useExamQueryOptionsStore from "@/store/service/exam/queryOptions";
const examStore = useExamStore();
const queryOptions = useExamQueryOptionsStore();
const selectTerm = ref({
year: systemStore.generalInfo.termYear,
term: systemStore.generalInfo.term
});
const isRefreshing = ref(false);
const exam = computed(() => {
return ZFService.getExamInfo(selectTerm.value)?.data;
});
const showModal = ref(false);
const helpContent = helpText.exam;
const examList = computed(() => {
const { year, term } = queryOptions;
const collection = examStore.collections.find(
(item) => item.year === year && item.term === term
);
return collection?.exams ?? [];
});
async function termChanged(e) {
isRefreshing.value = true;
selectTerm.value = e;
await ZFService.updateExamInfo(e);
isRefreshing.value = false;
queryOptions.setOption(e);
examStore.fetchExam(e);
}
async function refresh() {
if (isRefreshing.value) return;
isRefreshing.value = true;
await ZFService.updateExamInfo(selectTerm.value);
isRefreshing.value = false;
if (examStore.loading) return;
examStore.fetchExam({
year: queryOptions.year,
term: queryOptions.term
});
}
function getDetailedTime(timeString: string) {
Expand All @@ -180,10 +183,4 @@ function showHelp() {
showModal.value = true;
}
onMounted(async () => {
if (serviceStore.user.isBindZF) {
await refresh();
}
});
</script>
2 changes: 1 addition & 1 deletion src/services/services/zfService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Exam[]>(
api.zf.examInfo, {
method: "POST",
Expand Down
72 changes: 72 additions & 0 deletions src/store/service/exam/collections.ts
Original file line number Diff line number Diff line change
@@ -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<TermExamCollection[]>([]);

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;
26 changes: 26 additions & 0 deletions src/store/service/exam/queryOptions.ts
Original file line number Diff line number Diff line change
@@ -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;
23 changes: 23 additions & 0 deletions src/utils/promise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import RequestError, { MPErrorCode } from "./request/requestError";

/**
* 当 promise 返回的结果为 null 时抛出异常
*
* 正方的每个接口都有较大的概率返回 data: null
*/
export function withRespDataNeverNull<T, Args extends any[]>(
fetcher: (...args: Args) => Promise<T>,
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;
};
}

0 comments on commit fea9c97

Please sign in to comment.