fix: 修复类型检查报错并统一有效时间比显示
This commit is contained in:
@@ -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:`。
|
- 提交信息按 Git 提交规范使用中文短句;样式类改动用 `style:`,清理类用 `refactor:` / `chore:`。
|
||||||
|
|
||||||
## 开发配置
|
## 开发配置
|
||||||
@@ -185,7 +186,7 @@ npm run dev
|
|||||||
npm run build
|
npm run build
|
||||||
npm run test
|
npm run test
|
||||||
npm run test:e2e
|
npm run test:e2e
|
||||||
npx vue-tsc --noEmit
|
npm run type-check
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run test:coverage
|
npm run test:coverage
|
||||||
```
|
```
|
||||||
@@ -194,7 +195,7 @@ npm run test:coverage
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx vite build
|
npx vite build
|
||||||
npx vue-tsc --noEmit
|
npm run type-check
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ describe('StartTask.vue', () => {
|
|||||||
expect(wrapper.vm.taskInfo.sessionState).toBe('PAUSED')
|
expect(wrapper.vm.taskInfo.sessionState).toBe('PAUSED')
|
||||||
expect(wrapper.vm.taskInfo.actualTime).toBe(10)
|
expect(wrapper.vm.taskInfo.actualTime).toBe(10)
|
||||||
expect(wrapper.vm.taskInfo.effectiveTime).toBe(20)
|
expect(wrapper.vm.taskInfo.effectiveTime).toBe(20)
|
||||||
|
// 后端返回 0.8,UI 统一按百分比展示
|
||||||
|
expect(wrapper.vm.taskInfo.effectivenessRatio).toBe('80.0%')
|
||||||
expect(wrapper.vm.timerRunning).toBe(false)
|
expect(wrapper.vm.timerRunning).toBe(false)
|
||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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('-')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -8,7 +8,9 @@ import {
|
|||||||
type ReviewFeedItem,
|
type ReviewFeedItem,
|
||||||
} from "@/api/review";
|
} from "@/api/review";
|
||||||
import { getSessionDetail } from "@/api/studySessions";
|
import { getSessionDetail } from "@/api/studySessions";
|
||||||
|
import type { SessionDetail } from "@/api/studySessions";
|
||||||
import { updateFragments } from "@/api/reportFragments";
|
import { updateFragments } from "@/api/reportFragments";
|
||||||
|
import { formatRatio } from "@/utils/format";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
||||||
|
|
||||||
@@ -42,14 +44,7 @@ const isEditing = ref(false);
|
|||||||
const editContent = ref("");
|
const editContent = ref("");
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
|
||||||
const sessionInfo = ref<{
|
const sessionInfo = ref<SessionDetail | null>(null);
|
||||||
taskName: string;
|
|
||||||
sessionNum: string;
|
|
||||||
actualTime?: number;
|
|
||||||
effectiveTime?: number;
|
|
||||||
effectivenessRatio?: number;
|
|
||||||
startTime?: string;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
const taskSessions = ref<{
|
const taskSessions = ref<{
|
||||||
sessionNum: string;
|
sessionNum: string;
|
||||||
@@ -68,11 +63,6 @@ const formatDuration = (seconds?: number): string => {
|
|||||||
return `${s}秒`;
|
return `${s}秒`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatRatio = (ratio?: number): string => {
|
|
||||||
if (ratio == null) return "-";
|
|
||||||
return `${(ratio * 100).toFixed(1)}%`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadTaskHistory = async (tn: string) => {
|
const loadTaskHistory = async (tn: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await getTaskReview(tn);
|
const res = await getTaskReview(tn);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { getFragmentsBySession, updateFragments } from "@/api/reportFragments";
|
|||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
||||||
import { renderMarkdown } from "@/utils/markdown";
|
import { renderMarkdown } from "@/utils/markdown";
|
||||||
|
import { formatRatio } from "@/utils/format";
|
||||||
import { useSessionHistory } from "@/components/composables/useSessionHistory";
|
import { useSessionHistory } from "@/components/composables/useSessionHistory";
|
||||||
import { useSummaryReport } from "@/components/composables/useSummaryReport";
|
import { useSummaryReport } from "@/components/composables/useSummaryReport";
|
||||||
import { useSessionExpectation } from "@/components/composables/useSessionExpectation";
|
import { useSessionExpectation } from "@/components/composables/useSessionExpectation";
|
||||||
@@ -110,7 +111,7 @@ const taskInfo = ref({
|
|||||||
lastStartTime: "",
|
lastStartTime: "",
|
||||||
actualTime: 0,
|
actualTime: 0,
|
||||||
effectiveTime: 0,
|
effectiveTime: 0,
|
||||||
effectivenessRatio: "--",
|
effectivenessRatio: "-",
|
||||||
pointerPosition: 0,
|
pointerPosition: 0,
|
||||||
systemMessage: "",
|
systemMessage: "",
|
||||||
});
|
});
|
||||||
@@ -306,7 +307,7 @@ const stopTimer = async () => {
|
|||||||
taskInfo.value.sessionState = d.sessionState ?? "PAUSED";
|
taskInfo.value.sessionState = d.sessionState ?? "PAUSED";
|
||||||
taskInfo.value.actualTime = d.actualTime ?? 0;
|
taskInfo.value.actualTime = d.actualTime ?? 0;
|
||||||
taskInfo.value.effectiveTime = d.effectiveTime ?? 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.startTime = d.startTime ?? taskInfo.value.startTime;
|
||||||
taskInfo.value.endTime = d.endTime ?? "";
|
taskInfo.value.endTime = d.endTime ?? "";
|
||||||
taskInfo.value.lastStartTime = d.lastStartTime ?? "";
|
taskInfo.value.lastStartTime = d.lastStartTime ?? "";
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ const removeTask = async () => {
|
|||||||
// ============ 优先级权重配置 ============
|
// ============ 优先级权重配置 ============
|
||||||
const weightsDialogVisible = ref(false);
|
const weightsDialogVisible = ref(false);
|
||||||
const savingWeights = 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: "urgencyWeight", label: "紧急性", value: 35 },
|
||||||
{ key: "importanceWeight", label: "重要性", value: 25 },
|
{ key: "importanceWeight", label: "重要性", value: 25 },
|
||||||
{ key: "contentDifficultyWeight", label: "内容难度", value: 20 },
|
{ key: "contentDifficultyWeight", label: "内容难度", value: 20 },
|
||||||
@@ -274,9 +274,11 @@ const saveWeights = async () => {
|
|||||||
}
|
}
|
||||||
savingWeights.value = true;
|
savingWeights.value = true;
|
||||||
try {
|
try {
|
||||||
const payload = Object.fromEntries(
|
// weightItems 的 key 已由 keyof PriorityWeights 约束,循环后必然覆盖全部维度
|
||||||
weightItems.value.map((item) => [item.key, item.value / 100])
|
const payload = {} as PriorityWeights;
|
||||||
) as unknown as PriorityWeights;
|
for (const item of weightItems.value) {
|
||||||
|
payload[item.key] = item.value / 100;
|
||||||
|
}
|
||||||
const res = await savePriorityWeights(payload);
|
const res = await savePriorityWeights(payload);
|
||||||
if (res?.code === 200) {
|
if (res?.code === 200) {
|
||||||
weightsDialogVisible.value = false;
|
weightsDialogVisible.value = false;
|
||||||
|
|||||||
@@ -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)}%`;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user