diff --git a/AGENTS.md b/AGENTS.md index 9631c7c..ff6dbe8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -168,7 +168,8 @@ lpt-fe/src/ ### 提交前检查 -- 运行 `npx vue-tsc --noEmit`、`npm run lint`(0 error 才可提交;`no-explicit-any` 允许存量警告,新代码避免 any)、`npm run test`、`npm run build`。 +- 运行 `npm run type-check`、`npm run lint`(0 error 才可提交;`no-explicit-any` 允许存量警告,新代码避免 any)、`npm run test`、`npm run build`。 +- **不要用 `npx vue-tsc --noEmit` 作为检查手段**:`tsconfig.json` 是 solution 风格(`files: []` + 仅 `references`),该命令会解析到根配置而检查 0 个源文件,永远假绿。Docker 构建跑的是 `npm run type-check`(`vue-tsc --build --force`,走 project references),只有它才真正校验。 - 提交信息按 Git 提交规范使用中文短句;样式类改动用 `style:`,清理类用 `refactor:` / `chore:`。 ## 开发配置 @@ -185,7 +186,7 @@ npm run dev npm run build npm run test npm run test:e2e -npx vue-tsc --noEmit +npm run type-check npm run lint npm run test:coverage ``` @@ -194,7 +195,7 @@ npm run test:coverage ```bash npx vite build -npx vue-tsc --noEmit +npm run type-check ``` ## Docker diff --git a/src/__tests__/components/StartTask.spec.ts b/src/__tests__/components/StartTask.spec.ts index a78055a..31ff8e9 100644 --- a/src/__tests__/components/StartTask.spec.ts +++ b/src/__tests__/components/StartTask.spec.ts @@ -113,6 +113,8 @@ describe('StartTask.vue', () => { expect(wrapper.vm.taskInfo.sessionState).toBe('PAUSED') expect(wrapper.vm.taskInfo.actualTime).toBe(10) expect(wrapper.vm.taskInfo.effectiveTime).toBe(20) + // 后端返回 0.8,UI 统一按百分比展示 + expect(wrapper.vm.taskInfo.effectivenessRatio).toBe('80.0%') expect(wrapper.vm.timerRunning).toBe(false) wrapper.unmount() }) diff --git a/src/__tests__/utils/format.spec.ts b/src/__tests__/utils/format.spec.ts new file mode 100644 index 0000000..2b9effb --- /dev/null +++ b/src/__tests__/utils/format.spec.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest' +import { formatRatio } from '@/utils/format' + +describe('formatRatio', () => { + it('把 0~1 的比率格式化为一位小数的百分比', () => { + expect(formatRatio(0.8)).toBe('80.0%') + expect(formatRatio(0.8571)).toBe('85.7%') + expect(formatRatio(1)).toBe('100.0%') + expect(formatRatio(0)).toBe('0.0%') + }) + + it('兼容后端返回字符串形式的比率', () => { + expect(formatRatio('0.8')).toBe('80.0%') + }) + + it('空值与非法值统一显示 "-"', () => { + expect(formatRatio()).toBe('-') + expect(formatRatio(null)).toBe('-') + expect(formatRatio('')).toBe('-') + expect(formatRatio('abc')).toBe('-') + }) +}) diff --git a/src/components/ReviewDetail.vue b/src/components/ReviewDetail.vue index db98f72..787b439 100644 --- a/src/components/ReviewDetail.vue +++ b/src/components/ReviewDetail.vue @@ -8,7 +8,9 @@ import { type ReviewFeedItem, } from "@/api/review"; import { getSessionDetail } from "@/api/studySessions"; +import type { SessionDetail } from "@/api/studySessions"; import { updateFragments } from "@/api/reportFragments"; +import { formatRatio } from "@/utils/format"; import { ElMessage } from "element-plus"; import MarkdownRenderer from "@/components/MarkdownRenderer.vue"; @@ -42,14 +44,7 @@ const isEditing = ref(false); const editContent = ref(""); const saving = ref(false); -const sessionInfo = ref<{ - taskName: string; - sessionNum: string; - actualTime?: number; - effectiveTime?: number; - effectivenessRatio?: number; - startTime?: string; -} | null>(null); +const sessionInfo = ref(null); const taskSessions = ref<{ sessionNum: string; @@ -68,11 +63,6 @@ const formatDuration = (seconds?: number): string => { return `${s}秒`; }; -const formatRatio = (ratio?: number): string => { - if (ratio == null) return "-"; - return `${(ratio * 100).toFixed(1)}%`; -}; - const loadTaskHistory = async (tn: string) => { try { const res = await getTaskReview(tn); diff --git a/src/components/StartTask.vue b/src/components/StartTask.vue index cb34245..0819d2c 100644 --- a/src/components/StartTask.vue +++ b/src/components/StartTask.vue @@ -18,6 +18,7 @@ import { getFragmentsBySession, updateFragments } from "@/api/reportFragments"; import router from "@/router"; import MarkdownRenderer from "@/components/MarkdownRenderer.vue"; import { renderMarkdown } from "@/utils/markdown"; +import { formatRatio } from "@/utils/format"; import { useSessionHistory } from "@/components/composables/useSessionHistory"; import { useSummaryReport } from "@/components/composables/useSummaryReport"; import { useSessionExpectation } from "@/components/composables/useSessionExpectation"; @@ -110,7 +111,7 @@ const taskInfo = ref({ lastStartTime: "", actualTime: 0, effectiveTime: 0, - effectivenessRatio: "--", + effectivenessRatio: "-", pointerPosition: 0, systemMessage: "", }); @@ -306,7 +307,7 @@ const stopTimer = async () => { taskInfo.value.sessionState = d.sessionState ?? "PAUSED"; taskInfo.value.actualTime = d.actualTime ?? 0; taskInfo.value.effectiveTime = d.effectiveTime ?? 0; - taskInfo.value.effectivenessRatio = d.effectivenessRatio ?? 0; + taskInfo.value.effectivenessRatio = formatRatio(d.effectivenessRatio); taskInfo.value.startTime = d.startTime ?? taskInfo.value.startTime; taskInfo.value.endTime = d.endTime ?? ""; taskInfo.value.lastStartTime = d.lastStartTime ?? ""; diff --git a/src/components/Study.vue b/src/components/Study.vue index 3abbe6c..676f983 100644 --- a/src/components/Study.vue +++ b/src/components/Study.vue @@ -235,7 +235,7 @@ const removeTask = async () => { // ============ 优先级权重配置 ============ const weightsDialogVisible = ref(false); const savingWeights = ref(false); -const weightItems = ref([ +const weightItems = ref<{ key: keyof PriorityWeights; label: string; value: number }[]>([ { key: "urgencyWeight", label: "紧急性", value: 35 }, { key: "importanceWeight", label: "重要性", value: 25 }, { key: "contentDifficultyWeight", label: "内容难度", value: 20 }, @@ -274,9 +274,11 @@ const saveWeights = async () => { } savingWeights.value = true; try { - const payload = Object.fromEntries( - weightItems.value.map((item) => [item.key, item.value / 100]) - ) as unknown as PriorityWeights; + // weightItems 的 key 已由 keyof PriorityWeights 约束,循环后必然覆盖全部维度 + const payload = {} as PriorityWeights; + for (const item of weightItems.value) { + payload[item.key] = item.value / 100; + } const res = await savePriorityWeights(payload); if (res?.code === 200) { weightsDialogVisible.value = false; diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..8f7490d --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,10 @@ +/** + * 把 0~1 的比率格式化为百分比文案。 + * 后端可能返回数字或字符串,空值与非法值统一显示 "-"。 + */ +export const formatRatio = (ratio?: number | string | null): string => { + if (ratio == null || ratio === "") return "-"; + const value = Number(ratio); + if (Number.isNaN(value)) return "-"; + return `${(value * 100).toFixed(1)}%`; +};