lingxixue-new-web/src/views/Subjects/SubjectAnalysis.vue
2026-05-01 12:24:23 +09:00

316 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="subject-analysis">
<SubPageHeader>
<template #center>
<div class="subject-tabs">
<button
v-for="item in subjectTabs"
:key="item"
class="subject-tab"
:class="{ active: item === activeSubject }"
@click="switchSubject(item)"
>
{{ item }}
</button>
</div>
</template>
</SubPageHeader>
<main class="analysis-body">
<section class="analysis-panel">
<header class="panel-header">
<div class="dimension-group">
<LmFilterTabs v-model="activeDimension" :options="dimensionTabs" mode="compact" />
</div>
<div class="time-filters">
<LmFilterTabs v-model="activeTime" :options="timeFilters" mode="compact" />
<span class="update-tip">今日数据每天0点更新</span>
</div>
</header>
<div class="panel-content">
<h2 class="section-title">单词统计</h2>
<div class="stats-grid">
<article v-for="item in statCards" :key="item.title" class="stat-card" :class="item.tone">
<h3>{{ item.title }}</h3>
<p>
<span class="value">{{ item.value }}</span>
<span class="unit">{{ item.unit }}</span>
</p>
</article>
</div>
<div class="carousel-indicator">
<span v-for="idx in 8" :key="idx" class="dot" :class="{ active: idx === 1 }"></span>
</div>
</div>
</section>
</main>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import SubPageHeader from "@/components/layout/SubPageHeader.vue";
import LmFilterTabs from "@/components/lm/LmFilterTabs.vue";
type Subject = "英语" | "数学" | "语文" | "科学";
type DimensionKey = "word" | "read" | "language" | "duration" | "video" | "map" | "practice" | "drill";
type TimeKey = "today" | "week" | "total";
const route = useRoute();
const router = useRouter();
const subjectTabs: Subject[] = ["英语", "数学", "语文", "科学"];
const activeSubject = computed<Subject>(() => {
const raw = String(route.params.subject || "英语");
return subjectTabs.includes(raw as Subject) ? (raw as Subject) : "英语";
});
const dimensionTabs: Array<{ key: DimensionKey; label: string }> = [
{ key: "word", label: "单词" },
{ key: "read", label: "阅读" },
{ key: "language", label: "语感" },
{ key: "duration", label: "学习时长" },
{ key: "video", label: "视频学习" },
{ key: "map", label: "知识图谱" },
{ key: "practice", label: "练习" },
{ key: "drill", label: "刷题" },
];
const timeFilters: Array<{ key: TimeKey; label: string }> = [
{ key: "today", label: "今日" },
{ key: "week", label: "近一周" },
{ key: "total", label: "总计" },
];
const activeDimension = ref<DimensionKey>("word");
const activeTime = ref<TimeKey>("today");
const statCards = [
{ title: "陌生", value: 0, unit: "词", tone: "tone-red" },
{ title: "认识", value: 0, unit: "词", tone: "tone-orange" },
{ title: "熟悉", value: 0, unit: "词", tone: "tone-yellow" },
{ title: "熟悉", value: 0, unit: "词", tone: "tone-green" },
{ title: "总计", value: 0, unit: "词", tone: "tone-blue" },
{ title: "新词数", value: 0, unit: "词", tone: "tone-blue" },
{ title: "复习数", value: 0, unit: "词", tone: "tone-blue" },
{ title: "有效学习时长", value: 0, unit: "s", tone: "tone-blue" },
];
function switchSubject(subject: Subject) {
if (subject === activeSubject.value) return;
router.replace({
name: "subject-analysis",
params: { subject },
});
}
</script>
<style scoped lang="scss">
.subject-analysis {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.subject-tabs {
display: flex;
align-items: center;
gap: 52px;
}
.subject-tab {
border: none;
background: transparent;
color: #323946;
font-size: 40px;
line-height: 1.1;
cursor: pointer;
transition: color 0.2s;
&.active {
color: #2d6fe0;
font-weight: 600;
}
}
.analysis-body {
flex: 1;
min-height: 0;
padding: 0 64px 32px;
}
.analysis-panel {
width: 100%;
max-width: 1792px;
height: 100%;
margin: 0 auto;
display: flex;
flex-direction: column;
background: rgba(255, 255, 255, 0.25);
border: 2px solid rgba(255, 255, 255, 0.72);
border-radius: 20px;
overflow: hidden;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
gap: 16px;
}
.dimension-group {
min-width: 0;
}
.time-filters {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.update-tip {
font-size: 20px;
color: #9aa3b5;
white-space: nowrap;
}
.panel-content {
flex: 1;
min-height: 0;
padding: 34px 24px 20px;
background: rgba(255, 255, 255, 0.55);
display: flex;
flex-direction: column;
}
.section-title {
text-align: center;
font-size: 48px;
font-weight: 600;
color: #111;
}
.stats-grid {
margin-top: 28px;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
.stat-card {
position: relative;
min-height: 178px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.8);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 18px 22px 16px;
overflow: hidden;
&::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(145deg, transparent 0 56%, rgba(255, 255, 255, 0.38) 57%, transparent 61%),
linear-gradient(145deg, transparent 0 78%, rgba(255, 255, 255, 0.28) 79%, transparent 82%);
pointer-events: none;
}
h3 {
position: relative;
z-index: 1;
font-size: 42px;
color: #121212;
}
p {
position: relative;
z-index: 1;
text-align: center;
}
.value {
font-size: 48px;
font-weight: 600;
line-height: 1;
color: #2f76eb;
}
.unit {
margin-left: 8px;
font-size: 40px;
color: #121212;
}
}
.tone-red {
background:
linear-gradient(145deg, rgba(255, 238, 241, 0.92), rgba(255, 247, 247, 0.92));
.value {
color: #df4545;
}
}
.tone-orange {
background:
linear-gradient(145deg, rgba(255, 245, 233, 0.92), rgba(255, 249, 241, 0.92));
.value {
color: #e0873c;
}
}
.tone-yellow {
background:
linear-gradient(145deg, rgba(255, 251, 234, 0.92), rgba(255, 253, 243, 0.92));
.value {
color: #d7a734;
}
}
.tone-green {
background:
linear-gradient(145deg, rgba(235, 255, 248, 0.92), rgba(243, 255, 252, 0.92));
.value {
color: #2bbf89;
}
}
.tone-blue {
background:
linear-gradient(145deg, rgba(237, 248, 255, 0.92), rgba(245, 251, 255, 0.92));
}
.carousel-indicator {
margin-top: auto;
display: flex;
justify-content: center;
gap: 8px;
padding-top: 14px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 999px;
background: #d7dded;
&.active {
width: 28px;
background: #4f86ef;
}
}
</style>