2026-05-30 17:07:53 +08:00

381 lines
9.6 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>
<view class="wrong-page">
<view class="bg-blur bg-blur-a"></view>
<view class="bg-blur bg-blur-b"></view>
<scroll-view class="page-scroll" scroll-y :show-scrollbar="false">
<view class="hero">
<view>
<view class="eyebrow">错题练习</view>
<view class="hero-title">把失分题变成提分点</view>
<view class="hero-desc">按学科题型和掌握状态整理错题复习时更聚焦</view>
</view>
<view class="hero-badge">
<text class="hero-badge-num">{{ totalWrong }}</text>
<text class="hero-badge-text">道错题</text>
</view>
</view>
<view v-if="!pageLoading && !loadError" class="content">
<view class="subject-grid">
<view
v-for="subject in subjectStats"
:key="subject.name"
class="subject-card"
@click="openSubject(subject)"
>
<view class="subject-icon" :class="subject.tone">
<text>{{ subject.shortName }}</text>
</view>
<view class="subject-main">
<view class="subject-name">{{ subject.name }}</view>
<view class="subject-count">{{ subject.total }} 道待复盘</view>
</view>
<view class="subject-meta">
<view>
<text>{{ subject.newCount }}</text>
<text>新增</text>
</view>
<view>
<text>{{ subject.reviewCount }}</text>
<text>复习本</text>
</view>
<view>
<text>{{ subject.masteredCount }}</text>
<text>已掌握</text>
</view>
</view>
</view>
</view>
</view>
<view v-else class="loading-wrap">
<xy-Empty type="loading" content="正在整理错题..." />
</view>
<view v-if="!pageLoading && loadError" class="error-wrap">
<xy-Empty type="network" :content="loadError" />
<view class="retry-btn" @click="loadSummary">点击重试</view>
</view>
<view class="safe-bottom"></view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import type { WrongQuestionSummaryResult } from '@/api/parent';
import { useParentStore } from '@/state/modules/parent';
import { useUserStore } from '@/state/modules/user';
import { buildSubjectStatRow, fetchWrongSummary } from './shared';
const userStore = useUserStore();
const parentStore = useParentStore();
const { userInfo } = storeToRefs(userStore);
const pageLoading = ref(true);
const summaryData = ref<WrongQuestionSummaryResult | null>(null);
const loadError = ref('');
const skipFirstOnShow = ref(true);
const totalWrong = computed(() => {
const subjects = summaryData.value?.subjects || [];
if (subjects.length) {
return subjects.reduce((sum, item) => sum + (item.totalCount || 0), 0);
}
return 0;
});
const subjectStats = computed(() => {
const apiSubjects = summaryData.value?.subjects || [];
return apiSubjects.map((item, index) => ({
...buildSubjectStatRow(
item.subjectName || '其他',
{
total: item.totalCount || 0,
newCount: item.newCount || 0,
reviewCount: item.reviewCount || 0,
masteredCount: item.masteredCount || 0,
},
index,
),
subjectId: item.subjectId,
}));
});
const openSubject = (subject: { name: string; subjectId?: number }) => {
const query = [`subject=${encodeURIComponent(subject.name)}`];
if (subject.subjectId != null) {
query.push(`subjectId=${subject.subjectId}`);
}
const userId = parentStore.activeChild.id || parentStore.activeChildId;
if (userId) {
query.push(`userId=${userId}`);
}
uni.navigateTo({
url: `/pages/wrong/list?${query.join('&')}`,
});
};
const loadSummary = async () => {
if (pageLoading.value) return;
pageLoading.value = true;
loadError.value = '';
try {
summaryData.value = await fetchWrongSummary();
} catch (err) {
console.error('[wrong/index] summary load failed', err);
summaryData.value = null;
loadError.value = '错题数据加载失败,请检查网络后重试';
} finally {
pageLoading.value = false;
}
};
onShow(async () => {
if (skipFirstOnShow.value) {
skipFirstOnShow.value = false;
return;
}
try {
if (!userInfo.value.id) {
await userStore.fetchParentUserInfo();
}
if (userInfo.value.id) {
await parentStore.fetchBindChildren(userInfo.value.id);
}
} catch {
/* ignore */
}
await loadSummary();
});
onLoad(() => {
pageLoading.value = false;
loadSummary();
});
</script>
<style lang="scss" scoped>
.wrong-page {
position: relative;
min-height: 100vh;
background: linear-gradient(180deg, #ecfeff 0%, #f8fafc 42%, #ffffff 100%);
overflow: hidden;
}
.bg-blur {
position: absolute;
border-radius: 50%;
filter: blur(56rpx);
pointer-events: none;
z-index: 0;
}
.bg-blur-a {
top: 40rpx;
right: -120rpx;
width: 320rpx;
height: 320rpx;
background: rgba(34, 211, 238, 0.3);
}
.bg-blur-b {
top: 520rpx;
left: -140rpx;
width: 300rpx;
height: 300rpx;
background: rgba(34, 197, 94, 0.18);
}
.page-scroll {
position: relative;
z-index: 1;
height: 100vh;
}
.hero {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
padding: 40rpx 32rpx 24rpx;
}
.eyebrow {
display: inline-flex;
padding: 8rpx 18rpx;
border-radius: 999rpx;
color: #0e7490;
background: rgba(207, 250, 254, 0.9);
font-size: 22rpx;
font-weight: 800;
}
.hero-title {
margin-top: 18rpx;
color: #164e63;
font-size: 46rpx;
font-weight: 900;
line-height: 1.16;
}
.hero-desc {
max-width: 460rpx;
margin-top: 10rpx;
color: #64748b;
font-size: 24rpx;
line-height: 1.5;
}
.hero-badge {
width: 156rpx;
height: 156rpx;
border-radius: 48rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: #ffffff;
background: linear-gradient(180deg, #22d3ee 0%, #0891b2 100%);
box-shadow: 0 18rpx 34rpx rgba(8, 145, 178, 0.22), inset 0 -6rpx 0 rgba(0, 0, 0, 0.08);
}
.hero-badge-num {
font-size: 46rpx;
font-weight: 900;
line-height: 1;
}
.hero-badge-text {
margin-top: 8rpx;
font-size: 22rpx;
font-weight: 700;
}
.content {
padding: 0 28rpx;
}
.subject-grid {
display: flex;
flex-direction: column;
gap: 18rpx;
}
.subject-card {
display: grid;
grid-template-columns: 96rpx 1fr;
gap: 20rpx;
padding: 24rpx;
border: 2rpx solid rgba(226, 232, 240, 0.9);
border-radius: 32rpx;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 14rpx 28rpx rgba(8, 145, 178, 0.08);
transition: transform 160ms ease;
}
.subject-card:active {
transform: scale(0.98);
}
.subject-icon {
grid-row: span 2;
width: 96rpx;
height: 96rpx;
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 34rpx;
font-weight: 900;
}
.subject-icon.blue { background: linear-gradient(180deg, #38bdf8, #0284c7); }
.subject-icon.green { background: linear-gradient(180deg, #4ade80, #16a34a); }
.subject-icon.orange { background: linear-gradient(180deg, #fbbf24, #f97316); }
.subject-icon.rose { background: linear-gradient(180deg, #fb7185, #e11d48); }
.subject-icon.violet { background: linear-gradient(180deg, #a78bfa, #7c3aed); }
.subject-main {
min-width: 0;
}
.subject-name {
color: #164e63;
font-size: 30rpx;
font-weight: 900;
}
.subject-count {
margin-top: 6rpx;
color: #64748b;
font-size: 22rpx;
}
.subject-meta {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
}
.subject-meta view {
padding: 12rpx 10rpx;
border-radius: 18rpx;
background: #f8fafc;
text-align: center;
}
.subject-meta text {
display: block;
}
.subject-meta text:first-child {
color: #0891b2;
font-size: 28rpx;
font-weight: 900;
}
.subject-meta text:last-child {
margin-top: 2rpx;
color: #64748b;
font-size: 20rpx;
}
.loading-wrap {
height: 560rpx;
}
.error-wrap {
height: 560rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20rpx;
}
.retry-btn {
min-width: 220rpx;
height: 72rpx;
padding: 0 32rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 24rpx;
font-weight: 700;
background: linear-gradient(180deg, #22d3ee 0%, #0891b2 100%);
box-shadow: 0 10rpx 18rpx rgba(8, 145, 178, 0.2);
}
.safe-bottom {
height: 48rpx;
}
</style>