diff --git a/apps/api/src/routes/ai.ts b/apps/api/src/routes/ai.ts
index a7c3a07..6db7e08 100644
--- a/apps/api/src/routes/ai.ts
+++ b/apps/api/src/routes/ai.ts
@@ -102,6 +102,7 @@ async function buildDetail(user: AuthUser, start: string, end: string) {
const problemIds = firstAc.map((item) => item.problemId)
if (!problemIds.length) return aiDetailSchema.parse({
user: user.username, className: user.className, start, end, solved: [], flowcharts: [], grade: "", tags: {}, difficulty: {}, contestCount: 0,
+ rankScope: "global",
})
const classUsers = user.className ? await db.select({ id: schema.user.id }).from(schema.user).where(eq(schema.user.className, user.className)) : []
const scopeIds = classUsers.length > 1 ? classUsers.map((item) => item.id) : null
@@ -166,6 +167,7 @@ async function buildDetail(user: AuthUser, start: string, end: string) {
user: user.username, className: user.className, start, end, solved, flowcharts,
grade: averageGrade(solved.map((item) => item.grade)), tags: topTags, difficulty,
contestCount: new Set(solved.flatMap((item) => item.problem.contestId ?? [])).size,
+ rankScope: scopeIds ? "class" : "global",
})
}
@@ -258,7 +260,8 @@ async function buildDuration(user: AuthUser, endText: string, duration: string)
const from = bucket.start.getTime()
const to = bucket.end.getTime()
const inRange = rows.filter((row) => row.time >= from && row.time <= to)
- const solved = [...new Set(inRange.filter((row) => accepted.includes(row.result)).map((row) => row.problemId))]
+ const acceptedRows = inRange.filter((row) => accepted.includes(row.result))
+ const solved = [...new Set(acceptedRows.map((row) => row.problemId))]
return durationDataSchema.parse({
unit: config.unit,
index: config.count - 1 - index,
@@ -266,6 +269,7 @@ async function buildDuration(user: AuthUser, endText: string, duration: string)
end: bucket.end.toISOString(),
grade: solved.length ? bucketGrade(solved, from, to) : "",
problemCount: solved.length,
+ acceptedCount: acceptedRows.length,
submissionCount: inRange.length,
})
})
diff --git a/apps/web/src/oj/ai/components/DifficultyGradeChart.vue b/apps/web/src/oj/ai/components/DifficultyGradeChart.vue
index b21aa4b..5ebda95 100644
--- a/apps/web/src/oj/ai/components/DifficultyGradeChart.vue
+++ b/apps/web/src/oj/ai/components/DifficultyGradeChart.vue
@@ -6,7 +6,7 @@
-
+
@@ -23,11 +23,13 @@ import {
Legend,
} from "chart.js"
import { useAIStore } from "oj/store/ai"
+import { useChartTheme } from "shared/composables/chartTheme"
import type { Grade } from "utils/types"
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)
const aiStore = useAIStore()
+const { chartKey } = useChartTheme()
// 难度和等级的顺序(后端返回的是中文)
const difficultyOrder = ["简单", "中等", "困难"]
diff --git a/apps/web/src/oj/ai/components/DurationChart.vue b/apps/web/src/oj/ai/components/DurationChart.vue
index 2039f12..f0fec77 100644
--- a/apps/web/src/oj/ai/components/DurationChart.vue
+++ b/apps/web/src/oj/ai/components/DurationChart.vue
@@ -4,7 +4,7 @@
全面评估学习情况
-
+
@@ -25,6 +25,7 @@ import {
LineController,
} from "chart.js"
import { useAIStore } from "oj/store/ai"
+import { useChartTheme } from "shared/composables/chartTheme"
import { parseTime } from "utils/functions"
// 注册混合图表(Bar + Line)所需的 Chart.js 组件
@@ -42,6 +43,7 @@ ChartJS.register(
)
const aiStore = useAIStore()
+const { chartKey } = useChartTheme()
const gradeOrder = ["C", "B", "A", "S"] as const
@@ -89,7 +91,6 @@ const data = computed>(() => {
spanGaps: false,
tension: 0.4,
yAxisID: "y1",
- barThickness: 10,
order: 1,
borderWidth: 2,
pointRadius: 4,
@@ -128,8 +129,11 @@ const options = computed>(() => {
max: gradeOrder.length - 0.5,
ticks: {
stepSize: 1,
+ // 轴是 -0.5 ~ 3.5,chart.js 生成的刻度值就是 -0.5/0.5/1.5/2.5/3.5,
+ // 直接拿去索引 gradeOrder 全是 undefined —— 右轴的 S/A/B/C 一个都不会显示。
+ // 四舍五入到整数档再取,和 ProgressChart 的写法一致
callback: (v) => {
- const idx = Number(v)
+ const idx = Math.round(Number(v))
return gradeOrder[idx] || ""
},
},
@@ -167,24 +171,15 @@ const options = computed>(() => {
}
return `${dsLabel}: ${ctx.formattedValue}`
},
+ // AC 率直接读该周期的 acceptedCount / submissionCount。原来拿柱子上的
+ // 「完成题目数 / 总提交次数」当 AC 率,分子是去重后的题数,不是一回事
footer: (items: TooltipItem<"bar" | "line">[]) => {
- const barItems = items.filter(
- (item) => (item.dataset as any).yAxisID === "y",
- )
- if (barItems.length >= 2) {
- const problemCount =
- barItems.find((item) => item.dataset.label === "完成题目数")
- ?.parsed.y || 0
- const submissionCount =
- barItems.find((item) => item.dataset.label === "总提交次数")
- ?.parsed.y || 0
- const efficiency =
- submissionCount > 0
- ? ((problemCount / submissionCount) * 100).toFixed(1)
- : "0"
- return `AC率: ${efficiency}%`
- }
- return ""
+ const index = items[0]?.dataIndex
+ const bucket =
+ index === undefined ? undefined : aiStore.durationData[index]
+ if (!bucket || bucket.submissionCount === 0) return ""
+ const rate = (bucket.acceptedCount / bucket.submissionCount) * 100
+ return `AC率: ${rate.toFixed(1)}%(通过 ${bucket.acceptedCount} / 共 ${bucket.submissionCount} 次)`
},
},
},
diff --git a/apps/web/src/oj/ai/components/EfficiencyChart.vue b/apps/web/src/oj/ai/components/EfficiencyChart.vue
index 0e724e6..02f4c8b 100644
--- a/apps/web/src/oj/ai/components/EfficiencyChart.vue
+++ b/apps/web/src/oj/ai/components/EfficiencyChart.vue
@@ -4,7 +4,7 @@
反映刷题质量提升
-
+
@@ -23,6 +23,7 @@ import {
Filler,
} from "chart.js"
import { useAIStore } from "oj/store/ai"
+import { useChartTheme } from "shared/composables/chartTheme"
import { parseTime } from "utils/functions"
// 注册折线图所需的 Chart.js 组件
@@ -38,6 +39,7 @@ ChartJS.register(
)
const aiStore = useAIStore()
+const { chartKey } = useChartTheme()
const title = computed(() => {
if (aiStore.duration === "months:2") {
@@ -60,15 +62,17 @@ const show = computed(() => {
const efficiencyData = computed(() => {
return aiStore.durationData.map((duration) => {
const problemCount = duration.problemCount || 0
+ const acceptedCount = duration.acceptedCount || 0
const submissionCount = duration.submissionCount || 0
// 计算效率:提交次数/完成题目数
// 值越接近1,说明一次AC率越高
const efficiency = problemCount > 0 ? submissionCount / problemCount : 0
- // AC率:AC题目数 / 总提交次数(越高说明提交质量越好)
- const onePassRate =
- submissionCount > 0 ? (problemCount / submissionCount) * 100 : 0
+ // AC 率 = 判为通过的提交数 / 总提交数。原来分子用的是去重后的题目数,
+ // 那既不是 AC 率,算出来还正好是上面 efficiency 的倒数 —— 双轴画的是同一个数
+ const acRate =
+ submissionCount > 0 ? (acceptedCount / submissionCount) * 100 : 0
return {
label: [
@@ -76,8 +80,9 @@ const efficiencyData = computed(() => {
parseTime(duration.end, "M月D日"),
].join("~"),
efficiency: efficiency,
- onePassRate: onePassRate,
+ acRate: acRate,
problemCount: problemCount,
+ acceptedCount: acceptedCount,
submissionCount: submissionCount,
}
})
@@ -107,7 +112,7 @@ const data = computed>(() => {
},
{
label: "提交AC率",
- data: efficiency.map((e) => e.onePassRate),
+ data: efficiency.map((e) => e.acRate),
borderColor: "rgb(34, 197, 94)",
backgroundColor: "rgba(34, 197, 94, 0.1)",
tension: 0.4,
@@ -203,8 +208,8 @@ const options = computed(() => {
} else {
// 提交AC率
return [
- `${dsLabel}: ${item.onePassRate.toFixed(1)}%`,
- `提示: AC题目数 / 总提交次数,越高表示提交质量越好`,
+ `${dsLabel}: ${item.acRate.toFixed(1)}%`,
+ `通过 ${item.acceptedCount} 次 / 共提交 ${item.submissionCount} 次`,
]
}
},
diff --git a/apps/web/src/oj/ai/components/Heatmap.vue b/apps/web/src/oj/ai/components/Heatmap.vue
index 2c4f4a8..5aa8f19 100644
--- a/apps/web/src/oj/ai/components/Heatmap.vue
+++ b/apps/web/src/oj/ai/components/Heatmap.vue
@@ -4,7 +4,11 @@
激励持续学习
-
+