316 lines
7.0 KiB
Vue
316 lines
7.0 KiB
Vue
<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>
|