From 5b8e0f866b2a106735b6f6a4933b8fc28b6de66b Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 6 Aug 2026 07:53:06 -0600 Subject: [PATCH] =?UTF-8?q?refactor(reaction):=20=E6=94=B6=E6=95=9B?= =?UTF-8?q?=E5=8D=95=E9=80=89=E6=8F=90=E4=BA=A4=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/oj/api.ts | 41 ++++- src/oj/problem/components/ProblemReaction.vue | 146 +++++++----------- src/oj/problem/components/SubmitCode.vue | 9 +- src/utils/constants.ts | 2 - src/utils/types.ts | 2 +- 5 files changed, 98 insertions(+), 102 deletions(-) diff --git a/src/oj/api.ts b/src/oj/api.ts index 8cd12a0..04602a3 100644 --- a/src/oj/api.ts +++ b/src/oj/api.ts @@ -1,9 +1,10 @@ -import http from "utils/http" +import http, { type ApiResponse } from "utils/http" import { filterResult } from "oj/transforms" import type { Exercise, Problem, ReactionKey, + ReactionState, Submission, SubmissionListPayload, SubmitCodePayload, @@ -225,11 +226,43 @@ export function getMessageList(offset = 0, limit = 10) { } export function getReaction(problemID: number) { - return http.get("reaction", { params: { problem_id: problemID } }) + return http + .get("reaction", { + params: { problem_id: problemID }, + }) + .then(normalizeReactionResponse) } -export function setReaction(problemID: number, types: ReactionKey[]) { - return http.post("reaction", { problem_id: problemID, types }) +interface ReactionWireState { + mine: ReactionKey[] | ReactionKey | null + mine_type?: ReactionKey | null + counts: ReactionState["counts"] +} + +function normalizeReactionResponse( + res: ApiResponse, +): ApiResponse { + const legacyMine = Array.isArray(res.data.mine) + ? (res.data.mine[0] ?? null) + : res.data.mine + return { + ...res, + data: { + mine: res.data.mine_type ?? legacyMine, + counts: res.data.counts, + }, + } +} + +export function setReaction(problemID: number, type: ReactionKey) { + return http + .post("reaction", { + problem_id: problemID, + type, + // 过渡期同时发送旧字段:新前端先上线时,旧后端也能接受单选提交。 + types: [type], + }) + .then(normalizeReactionResponse) } // TODO: 这个API有问题 diff --git a/src/oj/problem/components/ProblemReaction.vue b/src/oj/problem/components/ProblemReaction.vue index b186d6c..afd9d26 100644 --- a/src/oj/problem/components/ProblemReaction.vue +++ b/src/oj/problem/components/ProblemReaction.vue @@ -1,34 +1,34 @@ @@ -38,63 +38,52 @@ import { storeToRefs } from "pinia" import { getReaction, setReaction } from "oj/api" import { useProblemStore } from "oj/store/problem" import { useUserStore } from "shared/store/user" -import { MAX_REACTIONS, REACTIONS } from "utils/constants" +import { REACTIONS } from "utils/constants" import type { ReactionCounts, ReactionKey } from "utils/types" +const emit = defineEmits<{ submitted: [] }>() + const userStore = useUserStore() const problemStore = useProblemStore() const { problem } = storeToRefs(problemStore) const message = useMessage() -// selected 是本地待提交的选择,提交成功后就是 mine,之后不可再改 -const selected = ref([]) +const mine = ref(null) const counts = ref(null) -const submitting = ref(false) +// 正在提交的那个 key,用来只给被点的按钮转圈 +const submitting = ref(null) const solved = computed(() => problem.value?.my_status === 0) // 评价一次定终身:后端已存在记录就锁死,只剩查看 const locked = ref(false) -function isDisabled(key: ReactionKey) { - if (!solved.value || locked.value || submitting.value) return true - if (selected.value.includes(key)) return false - return selected.value.length >= MAX_REACTIONS -} - -function tooltipOf(key: ReactionKey, label: string) { +function tooltipOf(label: string) { if (!solved.value) return "完成本题后可以评价" - if (locked.value) return `${label}(已评价,不能修改)` - if (isDisabled(key)) return `最多选 ${MAX_REACTIONS} 个,先取消一个` - return label + return `${label}(已评价,不能修改)` } -function toggle(key: ReactionKey) { - selected.value = selected.value.includes(key) - ? selected.value.filter((k) => k !== key) - : [...selected.value, key] -} - -async function submit() { - if (!problem.value || !selected.value.length) return - submitting.value = true +async function pick(key: ReactionKey) { + if (!problem.value) return + submitting.value = key try { - const res = await setReaction(problem.value.id, selected.value) - selected.value = res.data.mine + const res = await setReaction(problem.value.id, key) + mine.value = res.data.mine counts.value = res.data.counts locked.value = true + emit("submitted") } catch { message.error("提交失败,请重试") } finally { - submitting.value = false + submitting.value = null } } async function load() { if (!problem.value) return const res = await getReaction(problem.value.id) - selected.value = res.data.mine + mine.value = res.data.mine counts.value = res.data.counts - locked.value = res.data.mine.length > 0 + locked.value = res.data.mine !== null } onMounted(() => { @@ -103,54 +92,25 @@ onMounted(() => { diff --git a/src/oj/problem/components/SubmitCode.vue b/src/oj/problem/components/SubmitCode.vue index 3e37472..f537400 100644 --- a/src/oj/problem/components/SubmitCode.vue +++ b/src/oj/problem/components/SubmitCode.vue @@ -40,6 +40,11 @@ const router = useRouter() const [commentPanel] = useToggle() const message = useMessage() +// 评价提交后停一下让用户看到选中态,再收起弹窗 +function closeCommentPanel() { + setTimeout(() => (commentPanel.value = false), 800) +} + const { isDesktop } = useBreakpoints() // ==================== 烟花效果 ==================== @@ -73,7 +78,7 @@ const { start: startCooldown, isPending: isCooldown } = useTimeout(5000, { const { start: showCommentPanelDelayed } = useTimeoutFn( async () => { const res = await getReaction(problem.value!.id) - if (res.data.mine.length === 0) { + if (res.data.mine === null) { commentPanel.value = true } }, @@ -264,6 +269,6 @@ watch( :style="{ maxWidth: isDesktop && '50vw', maxHeight: '80vh' }" v-model:show="commentPanel" > - + diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e64e1fa..fe0b84c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -368,5 +368,3 @@ export const REACTIONS: { { key: "interesting", label: "有意思", icon: "fluent-emoji:star-struck" }, { key: "want_explain", label: "想听讲解", icon: "fluent-emoji:books" }, ] - -export const MAX_REACTIONS = 3 diff --git a/src/utils/types.ts b/src/utils/types.ts index 1828d36..c9f92f4 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -606,7 +606,7 @@ export type ReactionKey = export type ReactionCounts = Record export interface ReactionState { - mine: ReactionKey[] + mine: ReactionKey | null counts: ReactionCounts | null }