1038 lines
28 KiB
Vue
1038 lines
28 KiB
Vue
<template>
|
||
<view class="rep-page">
|
||
<!-- 装饰背景 -->
|
||
<view class="bg-decor bg-decor-a"></view>
|
||
<view class="bg-decor bg-decor-b"></view>
|
||
<view class="bg-decor bg-decor-c"></view>
|
||
|
||
<!-- Tabs -->
|
||
<view class="tab-bar">
|
||
<view class="tab-segment">
|
||
<view
|
||
v-for="t in tabs"
|
||
:key="t.id"
|
||
:class="['tab-item', { active: activeType === t.id }]"
|
||
@click="activeType = t.id"
|
||
>
|
||
<text>{{ t.name }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view v-if="!pageLoading" class="rep-scroll" scroll-y :show-scrollbar="false">
|
||
<!-- 整体分析 -->
|
||
<view v-if="activeType === '0'" class="tab-content">
|
||
<!-- Score Hero Card -->
|
||
<view class="score-hero" :style="{ background: heroGradient }">
|
||
<view class="score-hero-deco"></view>
|
||
<view class="score-hero-content">
|
||
<view class="score-hero-circle">
|
||
<view class="score-ring" :style="ringStyle">
|
||
<view class="score-ring-inner">
|
||
<view class="score-ring-num">
|
||
<text class="num">{{ correctRate }}</text>
|
||
<text class="unit">%</text>
|
||
</view>
|
||
<text class="score-ring-label">正确率</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="score-hero-info">
|
||
<view class="paper-name">{{ jobAnalyse?.jobReportVo?.jobName || paperName }}</view>
|
||
<view class="hero-row">
|
||
<view class="hero-stat">
|
||
<view class="stat-num">
|
||
{{ Number(jobAnalyse?.jobReportVo?.markScore) || 0 }}
|
||
<text class="stat-num-unit">/{{ jobAnalyse?.jobReportVo?.totalScore || 100 }}</text>
|
||
</view>
|
||
<view class="stat-label">得分</view>
|
||
</view>
|
||
<view class="hero-stat">
|
||
<view class="stat-num">{{ costTimeText }}</view>
|
||
<view class="stat-label">用时</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 答题统计 -->
|
||
<view class="card">
|
||
<view class="card-title">
|
||
<view class="title-bar"></view>
|
||
<text>答题统计</text>
|
||
</view>
|
||
<view class="stat-grid">
|
||
<view class="stat-card correct">
|
||
<view class="stat-emoji">✓</view>
|
||
<view class="stat-value">{{ correctNum }}</view>
|
||
<view class="stat-tag">答对</view>
|
||
</view>
|
||
<view class="stat-card wrong">
|
||
<view class="stat-emoji">✕</view>
|
||
<view class="stat-value">{{ wrongNum }}</view>
|
||
<view class="stat-tag">答错</view>
|
||
</view>
|
||
<view class="stat-card unanswered">
|
||
<view class="stat-emoji">−</view>
|
||
<view class="stat-value">{{ unansweredNum }}</view>
|
||
<view class="stat-tag">未答</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 题型分布 -->
|
||
<view v-if="channelAnalyzeList.length" class="card">
|
||
<view class="card-title">
|
||
<view class="title-bar"></view>
|
||
<text>题型分布</text>
|
||
</view>
|
||
<view class="pie-row">
|
||
<view class="pie">
|
||
<view class="pie-circle" :style="pieChartStyle"></view>
|
||
<view class="pie-hole">
|
||
<text class="pie-hole-num">{{ totalQuestions }}</text>
|
||
<text class="pie-hole-label">题</text>
|
||
</view>
|
||
</view>
|
||
<view class="pie-legend">
|
||
<view
|
||
v-for="(item, idx) in channelAnalyzeList"
|
||
:key="idx"
|
||
class="legend-item"
|
||
>
|
||
<view
|
||
class="legend-dot"
|
||
:style="{ background: pieColors[idx % pieColors.length] }"
|
||
></view>
|
||
<text class="legend-name">{{ item.chanelTypeName }}</text>
|
||
<text class="legend-count">{{ item.titleCount }}题</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 难度分布 -->
|
||
<view v-if="difficultyAnalyzeList.length" class="card">
|
||
<view class="card-title">
|
||
<view class="title-bar"></view>
|
||
<text>难度分布</text>
|
||
</view>
|
||
<view class="bars">
|
||
<view
|
||
v-for="(item, idx) in difficultyAnalyzeList"
|
||
:key="idx"
|
||
class="bar-row"
|
||
>
|
||
<view class="bar-name">{{ item.difficultyName }}</view>
|
||
<view class="bar-track">
|
||
<view
|
||
class="bar-fill"
|
||
:style="{
|
||
width: getBarWidth(item.score) + '%',
|
||
background: difficultyColors[item.difficultyName] || '#818CF8',
|
||
}"
|
||
></view>
|
||
</view>
|
||
<view class="bar-value">{{ item.score }}<text class="bar-value-unit">分</text></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 答题详情 -->
|
||
<view v-else-if="activeType === '1'" class="tab-content">
|
||
<view class="card">
|
||
<view class="card-title">
|
||
<view class="title-bar"></view>
|
||
<text>答题情况 · 共{{ paperList.length }}题</text>
|
||
</view>
|
||
<view v-if="paperList.length" class="qnum-grid">
|
||
<view
|
||
v-for="(item, idx) in paperList"
|
||
:key="item.id"
|
||
:class="['qnum-card', getStatusClass(item)]"
|
||
@click="viewQuestion(idx)"
|
||
>
|
||
<text class="qnum-card-num">{{ idx + 1 }}</text>
|
||
<view class="qnum-card-status">
|
||
<text>{{ getStatusText(item) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<Empty v-else type="task" content="暂无题目数据" />
|
||
</view>
|
||
|
||
<view class="legend-card">
|
||
<view class="legend-card-item">
|
||
<view class="legend-mark correct"></view>
|
||
<text>正确</text>
|
||
</view>
|
||
<view class="legend-card-item">
|
||
<view class="legend-mark wrong"></view>
|
||
<text>错误</text>
|
||
</view>
|
||
<view class="legend-card-item">
|
||
<view class="legend-mark unanswered"></view>
|
||
<text>未答</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 知识点 -->
|
||
<view v-else-if="activeType === '2'" class="tab-content">
|
||
<view v-if="knowledgeList.length" class="knowledge-list">
|
||
<view
|
||
v-for="(item, idx) in knowledgeList"
|
||
:key="idx"
|
||
class="knowledge-item"
|
||
>
|
||
<view class="knowledge-head">
|
||
<view class="knowledge-name">{{ item.knowledgeName }}</view>
|
||
<view
|
||
class="knowledge-score"
|
||
:style="{ color: getKnowledgeScoreColor(item) }"
|
||
>
|
||
<text class="kn-num">{{ item.answerScore }}</text>
|
||
<text class="kn-divider">/</text>
|
||
<text class="kn-total">{{ item.titleScore }}</text>
|
||
</view>
|
||
</view>
|
||
<ProgressBar
|
||
:rate="getKnowledgeRate(item)"
|
||
:width="660"
|
||
v-bind="getKnowledgeColor(item)"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<Empty v-else type="task" content="暂无知识点数据" />
|
||
</view>
|
||
|
||
<view style="height: 80rpx"></view>
|
||
</scroll-view>
|
||
|
||
<!-- 加载 -->
|
||
<view v-if="pageLoading" class="page-loader">
|
||
<Loading text="加载报告中..." />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue';
|
||
import { onLoad } from '@dcloudio/uni-app';
|
||
import Empty from '@/components/Empty/index.vue';
|
||
import Loading from '@/components/Loading/index.vue';
|
||
import ProgressBar from '@/components/ProgressBar/index.vue';
|
||
import { getJobAnalyse, getKnowledgeAnalyse, getPaperRecordDetail } from '@/api/homework';
|
||
|
||
const recordId = ref('');
|
||
const paperName = ref('');
|
||
const pageLoading = ref(true);
|
||
const activeType = ref('0');
|
||
|
||
const jobAnalyse = ref<any>({});
|
||
const paperList = ref<any[]>([]);
|
||
const knowledgeList = ref<any[]>([]);
|
||
|
||
const tabs = [
|
||
{ id: '0', name: '整体分析' },
|
||
{ id: '1', name: '答题详情' },
|
||
{ id: '2', name: '知识点' },
|
||
];
|
||
|
||
const pieColors = ['#4F46E5', '#22C55E', '#F59E0B', '#EF4444', '#0EA5E9', '#A855F7', '#EC4899'];
|
||
const difficultyColors: Record<string, string> = {
|
||
'容易': '#22C55E',
|
||
'较易': '#0EA5E9',
|
||
'普通': '#F59E0B',
|
||
'较难': '#EF4444',
|
||
'困难': '#7C3AED',
|
||
};
|
||
|
||
const channelAnalyzeList = computed(() => jobAnalyse.value?.paperChanelAnalyzeList || []);
|
||
const difficultyAnalyzeList = computed(() => jobAnalyse.value?.paperDifficultyAnalyzeList || []);
|
||
|
||
const totalQuestions = computed(() => {
|
||
return channelAnalyzeList.value.reduce(
|
||
(sum: number, item: any) => sum + (item.titleCount || 0),
|
||
0,
|
||
);
|
||
});
|
||
|
||
const pieChartStyle = computed(() => {
|
||
const data = channelAnalyzeList.value;
|
||
if (!data.length) return {};
|
||
const total = data.reduce((sum: number, item: any) => sum + (item.titleCount || 0), 0);
|
||
if (total === 0) return {};
|
||
|
||
const parts: string[] = [];
|
||
let cur = 0;
|
||
data.forEach((item: any, idx: number) => {
|
||
const pct = (item.titleCount / total) * 100;
|
||
const color = pieColors[idx % pieColors.length];
|
||
const next = cur + pct;
|
||
parts.push(`${color} ${cur}% ${next}%`);
|
||
cur = next;
|
||
});
|
||
return { background: `conic-gradient(${parts.join(', ')})` };
|
||
});
|
||
|
||
const correctNum = computed(() => {
|
||
return paperList.value.filter(
|
||
(item) => item.correctResult === 1 || item.correctResult === true,
|
||
).length;
|
||
});
|
||
|
||
const wrongNum = computed(() => {
|
||
return paperList.value.filter(
|
||
(item) =>
|
||
(item.correctResult === 0 || item.correctResult === false) &&
|
||
(item.submitAnswer || item.submitAnswerPic),
|
||
).length;
|
||
});
|
||
|
||
const unansweredNum = computed(() => {
|
||
return paperList.value.filter((item) => !item.submitAnswer && !item.submitAnswerPic).length;
|
||
});
|
||
|
||
const correctRate = computed(() => {
|
||
const total = paperList.value.length;
|
||
if (total === 0) return 0;
|
||
return Math.round((correctNum.value / total) * 100);
|
||
});
|
||
|
||
const scoreColor = computed(() => {
|
||
const r = Number(correctRate.value) || 0;
|
||
if (r >= 80) return '#22C55E';
|
||
if (r >= 60) return '#4F46E5';
|
||
if (r >= 40) return '#F59E0B';
|
||
return '#EF4444';
|
||
});
|
||
|
||
const heroGradient = computed(() => {
|
||
const r = Number(correctRate.value) || 0;
|
||
if (r >= 80) return 'linear-gradient(135deg, #DCFCE7 0%, #BBF7D0 100%)';
|
||
if (r >= 60) return 'linear-gradient(135deg, #E0E7FF 0%, #C7D2FE 100%)';
|
||
if (r >= 40) return 'linear-gradient(135deg, #FEF3C7 0%, #FDE68A 100%)';
|
||
return 'linear-gradient(135deg, #FEE2E2 0%, #FECACA 100%)';
|
||
});
|
||
|
||
const ringStyle = computed(() => ({
|
||
background: `conic-gradient(${scoreColor.value} 0% ${correctRate.value}%, rgba(255,255,255,0.6) ${correctRate.value}% 100%)`,
|
||
}));
|
||
|
||
const costTimeText = computed(() => {
|
||
const ms = Number(jobAnalyse.value?.jobReportVo?.costTime || 0);
|
||
const total = Math.floor(ms / 1000);
|
||
const h = Math.floor(total / 3600);
|
||
const m = Math.floor((total % 3600) / 60);
|
||
const s = total % 60;
|
||
if (h > 0) return `${h}时${m}分${s}秒`;
|
||
if (m > 0) return `${m}分${s}秒`;
|
||
return `${s}秒`;
|
||
});
|
||
|
||
const getBarWidth = (score: number) => {
|
||
const max = Math.max(...difficultyAnalyzeList.value.map((i: any) => i.score || 0), 1);
|
||
return Math.round((score / max) * 100);
|
||
};
|
||
|
||
const getStatusClass = (item: any) => {
|
||
if (item.correctResult === 1 || item.correctResult === true) return 'correct';
|
||
if (!item.submitAnswer && !item.submitAnswerPic) return 'unanswered';
|
||
return 'wrong';
|
||
};
|
||
|
||
const getStatusText = (item: any) => {
|
||
if (item.correctResult === 1 || item.correctResult === true) return '正确';
|
||
if (!item.submitAnswer && !item.submitAnswerPic) return '未答';
|
||
return '错误';
|
||
};
|
||
|
||
const getKnowledgeRate = (item: any) => {
|
||
const t = Number(item.titleScore) || 1;
|
||
const s = Number(item.answerScore) || 0;
|
||
return s / t;
|
||
};
|
||
|
||
const getKnowledgeScoreColor = (item: any) => {
|
||
const r = getKnowledgeRate(item) * 100;
|
||
if (r >= 80) return '#16A34A';
|
||
if (r >= 60) return '#4F46E5';
|
||
if (r >= 40) return '#D97706';
|
||
return '#DC2626';
|
||
};
|
||
|
||
const getKnowledgeColor = (item: any) => {
|
||
const r = getKnowledgeRate(item) * 100;
|
||
if (r >= 80) return { bg: '#DCFCE7', border: '#BBF7D0', activeBg: 'linear-gradient(90deg, #4ADE80, #16A34A)' };
|
||
if (r >= 60) return { bg: '#E0E7FF', border: '#C7D2FE', activeBg: 'linear-gradient(90deg, #818CF8, #4F46E5)' };
|
||
if (r >= 40) return { bg: '#FEF3C7', border: '#FDE68A', activeBg: 'linear-gradient(90deg, #FBBF24, #D97706)' };
|
||
return { bg: '#FEE2E2', border: '#FECACA', activeBg: 'linear-gradient(90deg, #F87171, #DC2626)' };
|
||
};
|
||
|
||
const viewQuestion = (idx: number) => {
|
||
uni.navigateTo({
|
||
url: `/pages/doWork/index?recordId=${recordId.value}&reportFlag=1&idx=${idx}`,
|
||
});
|
||
};
|
||
|
||
onLoad((options: any) => {
|
||
recordId.value = options.id || '';
|
||
paperName.value = options.name ? decodeURIComponent(options.name) : '';
|
||
init();
|
||
});
|
||
|
||
const init = async () => {
|
||
pageLoading.value = true;
|
||
try {
|
||
const analyseRes = await getJobAnalyse({ recordId: recordId.value });
|
||
jobAnalyse.value = analyseRes.data || {};
|
||
|
||
const detailRes = await getPaperRecordDetail({
|
||
recordId: recordId.value,
|
||
reportFlag: 1,
|
||
});
|
||
paperList.value = detailRes.data?.subjectTitleVoList || [];
|
||
|
||
try {
|
||
const knowledgeRes = await getKnowledgeAnalyse({ recordId: recordId.value });
|
||
knowledgeList.value = knowledgeRes.data || [];
|
||
} catch (e) {
|
||
console.warn('[reports] knowledge err', e);
|
||
}
|
||
} catch (err) {
|
||
console.error('[reports] init err', err);
|
||
uni.showToast({ title: '加载失败', icon: 'none' });
|
||
} finally {
|
||
pageLoading.value = false;
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.rep-page {
|
||
min-height: 100vh;
|
||
background: #f5f7ff;
|
||
position: relative;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.bg-decor {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
filter: blur(60rpx);
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
|
||
&-a {
|
||
top: -100rpx;
|
||
left: -120rpx;
|
||
width: 360rpx;
|
||
height: 360rpx;
|
||
background: radial-gradient(circle, rgba(34, 197, 94, 0.3), transparent 70%);
|
||
}
|
||
|
||
&-b {
|
||
top: 280rpx;
|
||
right: -100rpx;
|
||
width: 320rpx;
|
||
height: 320rpx;
|
||
background: radial-gradient(circle, rgba(129, 140, 248, 0.35), transparent 70%);
|
||
}
|
||
|
||
&-c {
|
||
bottom: 80rpx;
|
||
left: -120rpx;
|
||
width: 320rpx;
|
||
height: 320rpx;
|
||
background: radial-gradient(circle, rgba(245, 158, 11, 0.25), transparent 70%);
|
||
}
|
||
}
|
||
|
||
/* Tabs */
|
||
.tab-bar {
|
||
padding: 12rpx 32rpx 0;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.tab-segment {
|
||
display: flex;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
border-radius: 999rpx;
|
||
padding: 8rpx;
|
||
box-shadow: inset 0 4rpx 8rpx rgba(99, 102, 241, 0.1);
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
height: 64rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 999rpx;
|
||
color: #94a3b8;
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
transition: color 200ms ease;
|
||
|
||
&.active {
|
||
background: linear-gradient(180deg, #818cf8 0%, #4f46e5 100%);
|
||
color: #ffffff;
|
||
font-weight: 700;
|
||
box-shadow:
|
||
0 6rpx 14rpx rgba(79, 70, 229, 0.3),
|
||
inset 0 -3rpx 0 rgba(0, 0, 0, 0.12);
|
||
}
|
||
}
|
||
|
||
.rep-scroll {
|
||
flex: 1;
|
||
height: 0;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.tab-content {
|
||
padding: 24rpx 32rpx 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 28rpx;
|
||
}
|
||
|
||
/* Score Hero */
|
||
.score-hero {
|
||
position: relative;
|
||
border-radius: 36rpx;
|
||
padding: 36rpx 28rpx;
|
||
overflow: hidden;
|
||
box-shadow:
|
||
0 16rpx 32rpx rgba(99, 102, 241, 0.18),
|
||
inset 0 -6rpx 0 rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.score-hero-deco {
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
radial-gradient(circle at 90% 20%, rgba(255, 255, 255, 0.6), transparent 50%),
|
||
radial-gradient(circle at 10% 90%, rgba(255, 255, 255, 0.3), transparent 50%);
|
||
}
|
||
|
||
.score-hero-content {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 28rpx;
|
||
}
|
||
|
||
.score-hero-circle {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.score-ring {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow:
|
||
0 12rpx 24rpx rgba(0, 0, 0, 0.1),
|
||
inset 0 -4rpx 0 rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.score-ring-inner {
|
||
width: 152rpx;
|
||
height: 152rpx;
|
||
border-radius: 50%;
|
||
background: #ffffff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow:
|
||
inset 0 6rpx 12rpx rgba(99, 102, 241, 0.08),
|
||
0 4rpx 8rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.score-ring-num {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 2rpx;
|
||
|
||
.num {
|
||
font-size: 64rpx;
|
||
font-weight: 800;
|
||
color: #312e81;
|
||
line-height: 1;
|
||
}
|
||
|
||
.unit {
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: #6366f1;
|
||
}
|
||
}
|
||
|
||
.score-ring-label {
|
||
margin-top: 6rpx;
|
||
font-size: 22rpx;
|
||
color: #6366f1;
|
||
}
|
||
|
||
.score-hero-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.paper-name {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #312e81;
|
||
line-height: 1.4;
|
||
margin-bottom: 16rpx;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
}
|
||
|
||
.hero-row {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.hero-stat {
|
||
flex: 1;
|
||
padding: 16rpx;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
border-radius: 20rpx;
|
||
backdrop-filter: blur(8rpx);
|
||
text-align: center;
|
||
|
||
.stat-num {
|
||
font-size: 28rpx;
|
||
font-weight: 800;
|
||
color: #312e81;
|
||
|
||
.stat-num-unit {
|
||
font-size: 18rpx;
|
||
font-weight: 600;
|
||
color: #94a3b8;
|
||
}
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 20rpx;
|
||
color: #6366f1;
|
||
margin-top: 4rpx;
|
||
}
|
||
}
|
||
|
||
/* Card 通用 */
|
||
.card {
|
||
background: #ffffff;
|
||
border-radius: 32rpx;
|
||
padding: 28rpx;
|
||
box-shadow:
|
||
0 12rpx 24rpx rgba(99, 102, 241, 0.12),
|
||
inset 0 -5rpx 0 rgba(199, 210, 254, 0.5);
|
||
}
|
||
|
||
.card-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.title-bar {
|
||
width: 8rpx;
|
||
height: 28rpx;
|
||
border-radius: 4rpx;
|
||
background: linear-gradient(180deg, #818cf8, #4f46e5);
|
||
}
|
||
|
||
text {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #312e81;
|
||
}
|
||
}
|
||
|
||
/* 答题统计 */
|
||
.stat-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.stat-card {
|
||
border-radius: 24rpx;
|
||
padding: 24rpx 12rpx;
|
||
text-align: center;
|
||
|
||
.stat-emoji {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
line-height: 56rpx;
|
||
margin: 0 auto 12rpx;
|
||
border-radius: 50%;
|
||
font-size: 36rpx;
|
||
font-weight: 800;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 44rpx;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
}
|
||
|
||
.stat-tag {
|
||
margin-top: 8rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&.correct {
|
||
background: linear-gradient(135deg, #dcfce7, #bbf7d0);
|
||
|
||
.stat-emoji {
|
||
background: linear-gradient(180deg, #4ade80, #16a34a);
|
||
box-shadow: 0 4rpx 12rpx rgba(22, 163, 74, 0.3);
|
||
}
|
||
|
||
.stat-value, .stat-tag { color: #15803d; }
|
||
}
|
||
|
||
&.wrong {
|
||
background: linear-gradient(135deg, #fee2e2, #fecaca);
|
||
|
||
.stat-emoji {
|
||
background: linear-gradient(180deg, #f87171, #dc2626);
|
||
box-shadow: 0 4rpx 12rpx rgba(220, 38, 38, 0.3);
|
||
}
|
||
|
||
.stat-value, .stat-tag { color: #b91c1c; }
|
||
}
|
||
|
||
&.unanswered {
|
||
background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
|
||
|
||
.stat-emoji {
|
||
background: linear-gradient(180deg, #94a3b8, #64748b);
|
||
box-shadow: 0 4rpx 12rpx rgba(100, 116, 139, 0.3);
|
||
}
|
||
|
||
.stat-value, .stat-tag { color: #475569; }
|
||
}
|
||
}
|
||
|
||
/* 饼图 */
|
||
.pie-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.pie {
|
||
position: relative;
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.pie-circle {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
box-shadow: 0 8rpx 16rpx rgba(99, 102, 241, 0.12);
|
||
}
|
||
|
||
.pie-hole {
|
||
position: absolute;
|
||
inset: 28rpx;
|
||
border-radius: 50%;
|
||
background: #ffffff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow:
|
||
inset 0 4rpx 8rpx rgba(0, 0, 0, 0.06),
|
||
0 2rpx 4rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.pie-hole-num {
|
||
font-size: 44rpx;
|
||
font-weight: 800;
|
||
color: #312e81;
|
||
line-height: 1;
|
||
}
|
||
|
||
.pie-hole-label {
|
||
font-size: 20rpx;
|
||
color: #94a3b8;
|
||
margin-top: 4rpx;
|
||
}
|
||
}
|
||
|
||
.pie-legend {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.legend-dot {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.legend-name {
|
||
flex: 1;
|
||
font-size: 24rpx;
|
||
color: #475569;
|
||
font-weight: 500;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.legend-count {
|
||
font-size: 24rpx;
|
||
color: #4f46e5;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
|
||
/* 难度柱状图 */
|
||
.bars {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18rpx;
|
||
}
|
||
|
||
.bar-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.bar-name {
|
||
width: 80rpx;
|
||
flex-shrink: 0;
|
||
font-size: 24rpx;
|
||
color: #475569;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.bar-track {
|
||
flex: 1;
|
||
height: 24rpx;
|
||
background: #f1f5f9;
|
||
border-radius: 999rpx;
|
||
overflow: hidden;
|
||
box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.bar-fill {
|
||
height: 100%;
|
||
border-radius: 999rpx;
|
||
transition: width 600ms ease-out;
|
||
box-shadow: inset 0 -2rpx 0 rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.bar-value {
|
||
width: 80rpx;
|
||
flex-shrink: 0;
|
||
text-align: right;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #312e81;
|
||
|
||
.bar-value-unit {
|
||
font-size: 18rpx;
|
||
color: #94a3b8;
|
||
margin-left: 2rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
/* 题号网格 */
|
||
.qnum-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.qnum-card {
|
||
aspect-ratio: 1;
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: transform 150ms ease;
|
||
|
||
&:active { transform: scale(0.92); }
|
||
|
||
.qnum-card-num {
|
||
font-size: 36rpx;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
}
|
||
|
||
.qnum-card-status {
|
||
margin-top: 4rpx;
|
||
font-size: 18rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&.correct {
|
||
background: linear-gradient(135deg, #dcfce7, #bbf7d0);
|
||
box-shadow: 0 4rpx 12rpx rgba(22, 163, 74, 0.18), inset 0 -3rpx 0 rgba(0, 0, 0, 0.05);
|
||
|
||
.qnum-card-num, .qnum-card-status { color: #15803d; }
|
||
}
|
||
|
||
&.wrong {
|
||
background: linear-gradient(135deg, #fee2e2, #fecaca);
|
||
box-shadow: 0 4rpx 12rpx rgba(220, 38, 38, 0.18), inset 0 -3rpx 0 rgba(0, 0, 0, 0.05);
|
||
|
||
.qnum-card-num, .qnum-card-status { color: #b91c1c; }
|
||
}
|
||
|
||
&.unanswered {
|
||
background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
|
||
box-shadow: 0 4rpx 12rpx rgba(100, 116, 139, 0.18), inset 0 -3rpx 0 rgba(0, 0, 0, 0.05);
|
||
|
||
.qnum-card-num, .qnum-card-status { color: #475569; }
|
||
}
|
||
}
|
||
|
||
.legend-card {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 32rpx;
|
||
padding: 20rpx;
|
||
background: #ffffff;
|
||
border-radius: 24rpx;
|
||
box-shadow:
|
||
0 8rpx 16rpx rgba(99, 102, 241, 0.08),
|
||
inset 0 -3rpx 0 rgba(199, 210, 254, 0.5);
|
||
}
|
||
|
||
.legend-card-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
|
||
text {
|
||
font-size: 24rpx;
|
||
color: #475569;
|
||
}
|
||
}
|
||
|
||
.legend-mark {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
border-radius: 8rpx;
|
||
|
||
&.correct {
|
||
background: linear-gradient(135deg, #4ade80, #16a34a);
|
||
}
|
||
|
||
&.wrong {
|
||
background: linear-gradient(135deg, #f87171, #dc2626);
|
||
}
|
||
|
||
&.unanswered {
|
||
background: linear-gradient(135deg, #94a3b8, #64748b);
|
||
}
|
||
}
|
||
|
||
/* 知识点 */
|
||
.knowledge-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.knowledge-item {
|
||
background: #ffffff;
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
box-shadow:
|
||
0 8rpx 16rpx rgba(99, 102, 241, 0.08),
|
||
inset 0 -3rpx 0 rgba(199, 210, 254, 0.5);
|
||
}
|
||
|
||
.knowledge-head {
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
margin-bottom: 16rpx;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.knowledge-name {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #312e81;
|
||
line-height: 1.4;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
}
|
||
|
||
.knowledge-score {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: baseline;
|
||
|
||
.kn-num {
|
||
font-size: 36rpx;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
}
|
||
|
||
.kn-divider {
|
||
font-size: 24rpx;
|
||
color: #94a3b8;
|
||
margin: 0 4rpx;
|
||
}
|
||
|
||
.kn-total {
|
||
font-size: 24rpx;
|
||
color: #94a3b8;
|
||
}
|
||
}
|
||
|
||
.page-loader {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(245, 247, 255, 0.6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 99;
|
||
}
|
||
</style>
|