381 lines
9.6 KiB
Vue
381 lines
9.6 KiB
Vue
<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>
|