This commit is contained in:
2025-10-07 17:03:59 +08:00
parent 6f345611eb
commit 437da9d588
9 changed files with 859 additions and 864 deletions

View File

@@ -142,4 +142,3 @@ const options = {
}, },
} }
</script> </script>

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card :title="title" size="small"> <n-card :title="title" size="small">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px"> 全面评估学习情况 </n-text>
全面评估学习情况
</n-text>
</template> </template>
<div class="chart"> <div class="chart">
<Chart type="bar" :data="data" :options="options" /> <Chart type="bar" :data="data" :options="options" />
@@ -173,11 +171,20 @@ const options = computed<ChartOptions<"bar" | "line">>(() => {
return `${dsLabel}: ${ctx.formattedValue}` return `${dsLabel}: ${ctx.formattedValue}`
}, },
footer: (items: TooltipItem<"bar">[]) => { footer: (items: TooltipItem<"bar">[]) => {
const barItems = items.filter(item => (item.dataset as any).yAxisID === "y") const barItems = items.filter(
(item) => (item.dataset as any).yAxisID === "y",
)
if (barItems.length >= 2) { if (barItems.length >= 2) {
const problemCount = barItems.find(item => item.dataset.label === "完成题目数")?.parsed.y || 0 const problemCount =
const submissionCount = barItems.find(item => item.dataset.label === "总提交次数")?.parsed.y || 0 barItems.find((item) => item.dataset.label === "完成题目数")
const efficiency = submissionCount > 0 ? ((problemCount / submissionCount) * 100).toFixed(1) : "0" ?.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 `AC率: ${efficiency}%`
} }
return "" return ""

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card :title="title" size="small" v-if="show"> <n-card :title="title" size="small" v-if="show">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px">反映刷题质量提升</n-text>
反映刷题质量提升
</n-text>
</template> </template>
<div class="chart"> <div class="chart">
<Chart type="line" :data="data" :options="options" /> <Chart type="line" :data="data" :options="options" />
@@ -69,7 +67,8 @@ const efficiencyData = computed(() => {
const efficiency = problemCount > 0 ? submissionCount / problemCount : 0 const efficiency = problemCount > 0 ? submissionCount / problemCount : 0
// 计算一次AC率百分比 // 计算一次AC率百分比
const onePassRate = problemCount > 0 ? (problemCount / submissionCount) * 100 : 0 const onePassRate =
problemCount > 0 ? (problemCount / submissionCount) * 100 : 0
return { return {
label: [ label: [
@@ -233,4 +232,3 @@ const options = computed<ChartOptions<"line">>(() => {
width: 100%; width: 100%;
} }
</style> </style>

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card title="过去一年的提交热力图" size="small"> <n-card title="过去一年的提交热力图" size="small">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px">激励持续学习</n-text>
激励持续学习
</n-text>
</template> </template>
<n-spin :show="aiStore.loading.heatmap"> <n-spin :show="aiStore.loading.heatmap">
<div class="heatmap-container" ref="containerRef"> <div class="heatmap-container" ref="containerRef">

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card :title="title" size="small" v-if="show"> <n-card :title="title" size="small" v-if="show">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px">追踪学习成长轨迹</n-text>
追踪学习成长轨迹
</n-text>
</template> </template>
<div class="chart"> <div class="chart">
<Chart type="line" :data="data" :options="options" /> <Chart type="line" :data="data" :options="options" />
@@ -87,7 +85,8 @@ const progressData = computed(() => {
totalProblems += problemCount totalProblems += problemCount
// 计算累计平均等级 // 计算累计平均等级
const avgGradeValue = totalProblems > 0 ? totalWeightedGrade / totalProblems : 0 const avgGradeValue =
totalProblems > 0 ? totalWeightedGrade / totalProblems : 0
return { return {
label: [ label: [

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card title="解题排名分布" size="small" v-if="show"> <n-card title="解题排名分布" size="small" v-if="show">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px">了解解题速度和竞争力</n-text>
了解解题速度和竞争力
</n-text>
</template> </template>
<div style="height: 300px"> <div style="height: 300px">
<Pie :data="data" :options="options" /> <Pie :data="data" :options="options" />
@@ -13,13 +11,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Pie } from "vue-chartjs" import { Pie } from "vue-chartjs"
import { import { Chart as ChartJS, ArcElement, Title, Tooltip, Legend } from "chart.js"
Chart as ChartJS,
ArcElement,
Title,
Tooltip,
Legend,
} from "chart.js"
import { useAIStore } from "oj/store/ai" import { useAIStore } from "oj/store/ai"
ChartJS.register(ArcElement, Title, Tooltip, Legend) ChartJS.register(ArcElement, Title, Tooltip, Legend)
@@ -111,8 +103,12 @@ const options = {
callbacks: { callbacks: {
label: (context: any) => { label: (context: any) => {
const count = context.parsed const count = context.parsed
const total = rankDistribution.value.reduce((sum, r) => sum + r.count, 0) const total = rankDistribution.value.reduce(
const percentage = total > 0 ? ((count / total) * 100).toFixed(1) : "0.0" (sum, r) => sum + r.count,
0,
)
const percentage =
total > 0 ? ((count / total) * 100).toFixed(1) : "0.0"
const label = context.label || "" const label = context.label || ""
return `${label}: ${count} 道题 (${percentage}%)` return `${label}: ${count} 道题 (${percentage}%)`
}, },
@@ -122,7 +118,10 @@ const options = {
if (problems.length > 0 && problems.length <= 5) { if (problems.length > 0 && problems.length <= 5) {
return problems return problems
} else if (problems.length > 5) { } else if (problems.length > 5) {
return [...problems.slice(0, 3), `... 还有 ${problems.length - 3} 道题`] return [
...problems.slice(0, 3),
`... 还有 ${problems.length - 3} 道题`,
]
} }
return "" return ""
}, },
@@ -131,4 +130,3 @@ const options = {
}, },
} }
</script> </script>

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card title="连续做题统计" size="small"> <n-card title="连续做题统计" size="small">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px">激励持续学习</n-text>
激励持续学习
</n-text>
</template> </template>
<n-spin :show="aiStore.loading.heatmap"> <n-spin :show="aiStore.loading.heatmap">
<n-grid :cols="2" :x-gap="12" :y-gap="12"> <n-grid :cols="2" :x-gap="12" :y-gap="12">
@@ -11,7 +9,10 @@
<n-statistic label="当前连续" :value="currentStreak"> <n-statistic label="当前连续" :value="currentStreak">
<template #suffix> <template #suffix>
<span style="font-size: 14px"></span> <span style="font-size: 14px"></span>
<span v-if="currentStreak > 0" style="font-size: 20px; margin-left: 4px"> <span
v-if="currentStreak > 0"
style="font-size: 20px; margin-left: 4px"
>
🔥 🔥
</span> </span>
</template> </template>
@@ -21,7 +22,10 @@
<n-statistic label="最长连续" :value="maxStreak"> <n-statistic label="最长连续" :value="maxStreak">
<template #suffix> <template #suffix>
<span style="font-size: 14px"></span> <span style="font-size: 14px"></span>
<span v-if="maxStreak >= 7" style="font-size: 20px; margin-left: 4px"> <span
v-if="maxStreak >= 7"
style="font-size: 20px; margin-left: 4px"
>
</span> </span>
</template> </template>
@@ -45,12 +49,8 @@
<n-divider style="margin: 12px 0" /> <n-divider style="margin: 12px 0" />
<n-flex vertical size="small"> <n-flex vertical size="small">
<n-text depth="2" style="font-size: 12px"> <n-text depth="2" style="font-size: 12px">
<span v-if="currentStreak === 0"> <span v-if="currentStreak === 0"> 开始做题建立学习连续记录 </span>
开始做题建立学习连续记录 <span v-else-if="currentStreak < 3"> 继续保持争取连续3天 </span>
</span>
<span v-else-if="currentStreak < 3">
继续保持争取连续3天
</span>
<span v-else-if="currentStreak < 7"> <span v-else-if="currentStreak < 7">
很棒继续保持一周连续记录 很棒继续保持一周连续记录
</span> </span>
@@ -175,4 +175,3 @@ const maxStreak = computed(() => streakData.value.maxStreak)
const weekCount = computed(() => streakData.value.weekCount) const weekCount = computed(() => streakData.value.weekCount)
const monthCount = computed(() => streakData.value.monthCount) const monthCount = computed(() => streakData.value.monthCount)
</script> </script>

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card :title="title" size="small" v-if="show"> <n-card :title="title" size="small" v-if="show">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px">可视化知识点覆盖面</n-text>
可视化知识点覆盖面
</n-text>
</template> </template>
<div class="chart"> <div class="chart">
<Radar :data="data" :options="options" /> <Radar :data="data" :options="options" />

View File

@@ -1,9 +1,7 @@
<template> <template>
<n-card title="时间活跃度分析" size="small" v-if="show"> <n-card title="时间活跃度分析" size="small" v-if="show">
<template #header-extra> <template #header-extra>
<n-text depth="3" style="font-size: 12px"> <n-text depth="3" style="font-size: 12px">发现最佳学习时段</n-text>
发现最佳学习时段
</n-text>
</template> </template>
<div style="height: 300px"> <div style="height: 300px">
<Bar :data="data" :options="options" /> <Bar :data="data" :options="options" />
@@ -75,7 +73,9 @@ const data = computed(() => {
const datasets = TIME_PERIODS.map((period, periodIndex) => { const datasets = TIME_PERIODS.map((period, periodIndex) => {
return { return {
label: period.label, label: period.label,
data: WEEKDAYS.map((_, weekday) => activityMatrix.value[weekday][periodIndex]), data: WEEKDAYS.map(
(_, weekday) => activityMatrix.value[weekday][periodIndex],
),
backgroundColor: getTimePeriodColor(periodIndex), backgroundColor: getTimePeriodColor(periodIndex),
borderColor: getTimePeriodColor(periodIndex), borderColor: getTimePeriodColor(periodIndex),
borderWidth: 1, borderWidth: 1,
@@ -150,4 +150,3 @@ const options = {
}, },
} }
</script> </script>