dedsudiyu 1 day ago
parent
commit
f4981720b6

+ 13 - 0
pages.json

@@ -1257,6 +1257,19 @@
 					}
 					}
 				},
 				},
 				{
 				{
+					"path": "views/practice/practicePlay",
+					"style": {
+						"navigationBarTitleText": "刷题练习",
+						"navigationBarButtons": [
+							{
+								"type": "text",
+								"text": "题号",
+								"color": "#1857d4"
+							}
+						]
+					}
+				},
+				{
 					"path": "views/approval/index",
 					"path": "views/approval/index",
 					"style": {
 					"style": {
 						"navigationBarTitleText": "实验室审批"
 						"navigationBarTitleText": "实验室审批"

+ 24 - 0
pages_safetyEducationExamination/api/index.js

@@ -173,6 +173,30 @@ export const examUserPracticeList = (data) => {
 		data: { ...data }
 		data: { ...data }
 	})
 	})
 };
 };
+//刷题练习-题目面板(左侧题号列表)
+export const examUserPracticePanel = (data) => {
+	return apiResquest({
+		url: `/exam/user/practice/panel`,
+		method: 'GET',
+		data: { ...data }
+	})
+};
+//刷题练习-单题详情
+export const examUserPracticeQuestionDetail = (data) => {
+	return apiResquest({
+		url: `/exam/user/practice/questionDetail`,
+		method: 'GET',
+		data: { ...data }
+	})
+};
+//刷题练习-提交单题答案
+export const examUserPracticeSubmit = (data) => {
+	return apiResquest({
+		url: `/exam/user/practice/submit`,
+		method: 'POST',
+		data: { ...data }
+	})
+};
 //查询毕业报告
 //查询毕业报告
 export const examElLabEduRelationGraduationReport = (data) => {
 export const examElLabEduRelationGraduationReport = (data) => {
   return apiResquest({
   return apiResquest({

+ 3 - 1
pages_safetyEducationExamination/views/practice/index.vue

@@ -140,7 +140,9 @@
 				this.loadPracticeList();
 				this.loadPracticeList();
 			},
 			},
 			goToPractice(item) {
 			goToPractice(item) {
-				// 跳转刷题详情,传递 knowledgePointId
+				uni.navigateTo({
+					url: `/pages_safetyEducationExamination/views/practice/practicePlay?knowledgePointId=${item.knowledgePointId || ''}&attemptId=${item.attemptId || ''}&examId=${item.examId || ''}`,
+				});
 			},
 			},
 		},
 		},
 	}
 	}

+ 783 - 0
pages_safetyEducationExamination/views/practice/practicePlay.vue

@@ -0,0 +1,783 @@
+<!-- 刷题练习页 -->
+<template>
+	<view class="practice-screen">
+		<!-- 练习用时顶栏(绿色) -->
+		<view class="timer-bar">
+			<text class="timer-label">练习用时</text>
+			<text class="timer-text">{{ timerDisplay }}</text>
+			<text class="timer-mode">刷题练习模式</text>
+		</view>
+
+		<!-- 答题统计行 -->
+		<view class="stat-row">
+			<view class="stat-item">
+				<text class="stat-num">{{ answered }}</text>
+				<text class="stat-label">已答</text>
+			</view>
+			<view class="stat-divider"></view>
+			<view class="stat-item">
+				<text class="stat-num stat-correct">{{ correct }}</text>
+				<text class="stat-label">正确</text>
+			</view>
+			<view class="stat-divider"></view>
+			<view class="stat-item">
+				<text class="stat-num stat-wrong">{{ incorrect }}</text>
+				<text class="stat-label">错误</text>
+			</view>
+			<view class="stat-divider"></view>
+			<view class="stat-item">
+				<text class="stat-num">{{ accuracyText }}</text>
+				<text class="stat-label">正确率</text>
+			</view>
+		</view>
+
+		<!-- 题号导航面板(底部弹出,仿 mockExamPlay) -->
+		<view v-if="showQuestionNav" class="q-nav-panel" @tap="closeNav">
+			<view class="q-nav-scroll-wrapper" @tap.stop>
+				<view class="q-nav-header">
+					<text class="q-nav-title">题目列表</text>
+					<text class="q-nav-close" @tap="closeNav">✕</text>
+				</view>
+				<scroll-view scroll-y class="q-nav-scroll" :style="'height:' + navScrollHeight + 'px'">
+					<view v-for="(group, gi) in questionTypeGroups" :key="gi" class="q-group">
+						<view class="q-group-title">{{ group.questionTypeName }}({{ (group.questions || []).length }}题)</view>
+						<view class="q-num-row">
+							<view
+								v-for="(item, qi) in group.questions"
+								:key="qi"
+								class="q-num"
+								:class="getNavNumClass(item, gi, qi)"
+								:data-gi="gi"
+								:data-qi="qi"
+								@tap="onNavNumTap"
+							>{{ item.seq }}</view>
+						</view>
+					</view>
+					<!-- 图例 -->
+					<view class="nav-legend">
+						<view class="legend-item"><view class="q-num legend-dot answered"></view><text>已答对</text></view>
+						<view class="legend-item"><view class="q-num legend-dot wrong"></view><text>答错</text></view>
+						<view class="legend-item"><view class="q-num legend-dot current"></view><text>当前</text></view>
+					</view>
+				</scroll-view>
+			</view>
+		</view>
+
+		<!-- 题目内容 -->
+		<scroll-view scroll-y class="q-scroll-content" :style="scrollStyle">
+			<view v-if="currentQuestion && currentQuestion.id" class="q-card">
+				<!-- 题目头部 -->
+				<view class="q-header">
+					<view class="q-header-left">
+						<text class="q-type-badge">{{ currentQuestion.questionTypeName }}</text>
+						<text class="q-num-text">第 {{ currentSeq }} 题</text>
+						<text v-if="currentQuestion.difficulty" class="diff-badge" :class="diffClass(currentQuestion.difficulty)">{{ diffName(currentQuestion.difficulty) }}</text>
+					</view>
+					<view class="view-answer-btn" @tap="toggleAnswer">
+						{{ viewAnswerShowType ? '关闭答案' : '查看答案' }}
+					</view>
+				</view>
+
+				<!-- 题干 -->
+				<view class="q-content">
+					<rich-text :nodes="currentQuestion.questionContent || ''"></rich-text>
+				</view>
+
+				<!-- 选项 -->
+				<view class="q-options">
+					<view
+						v-for="(opt, oi) in (currentQuestion.options || [])"
+						:key="oi"
+						class="q-option"
+						:class="{ selected: isSelected(opt.optionTag) }"
+						:data-tag="opt.optionTag"
+						@tap="onOptionTap"
+					>
+						<view class="q-option-label">{{ opt.optionTag }}</view>
+						<view class="q-option-content">
+							<rich-text :nodes="opt.optionContent || ''"></rich-text>
+						</view>
+					</view>
+				</view>
+
+				<!-- 查看答案区域 -->
+				<view v-if="viewAnswerShowType" class="answer-result-box">
+					<view class="answer-row">
+						<text class="answer-label">答案:</text>
+						<text class="answer-value">{{ formatAnswer(currentQuestion.correctAnswer) }}</text>
+					</view>
+					<view class="analysis-title">试题解析</view>
+					<view class="analysis-content">
+						<rich-text v-if="currentQuestion.analysis" :nodes="currentQuestion.analysis"></rich-text>
+						<text v-else class="analysis-empty">无</text>
+					</view>
+				</view>
+			</view>
+			<view v-else-if="loading" class="empty-tip">加载中...</view>
+			<view v-else class="empty-tip">暂无题目</view>
+			<view class="scroll-bottom-space"></view>
+		</scroll-view>
+
+		<!-- 上一题/下一题 -->
+		<view class="q-nav">
+			<view class="q-nav-btn" :class="{ disabled: isFirstQuestion }" @tap="prevQuestion">上一题</view>
+			<view class="q-nav-btn q-nav-center" @tap="toggleQuestionNav">选题</view>
+			<view class="q-nav-btn q-nav-btn-next" :class="{ disabled: isLastQuestion }" @tap="nextQuestion">下一题</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		examUserPracticePanel,
+		examUserPracticeQuestionDetail,
+		examUserPracticeSubmit,
+		examUserExamStart,
+	} from '../../api/index.js';
+
+	const DIFFICULTY_MAP = {
+		1: { name: '简单', cls: 'diff-easy' },
+		2: { name: '一般', cls: 'diff-normal' },
+		3: { name: '较难', cls: 'diff-hard' },
+		4: { name: '极难', cls: 'diff-extreme' },
+	};
+
+	export default {
+		data() {
+			return {
+				newData: {},
+				examInfo: {
+					knowledgePointName: '',
+					totalCount: 0,
+				},
+				questionTypeGroups: [],
+				currentQuestion: null,
+				groupIndex: 0,
+				questionIndex: 0,
+				viewAnswerShowType: false,
+				showQuestionNav: false,
+				navScrollHeight: 300,
+				loading: false,
+				// 计时器
+				elapsedSeconds: 0,
+				timerInterval: null,
+			}
+		},
+		computed: {
+			answered() {
+				let n = 0;
+				this.questionTypeGroups.forEach(g => {
+					if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 1 || q.answerStatus === 2) n++; });
+				});
+				return n;
+			},
+			correct() {
+				let n = 0;
+				this.questionTypeGroups.forEach(g => {
+					if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 1) n++; });
+				});
+				return n;
+			},
+			incorrect() {
+				let n = 0;
+				this.questionTypeGroups.forEach(g => {
+					if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 2) n++; });
+				});
+				return n;
+			},
+			accuracyText() {
+				let total = 0;
+				this.questionTypeGroups.forEach(g => { if (g.questions) total += g.questions.length; });
+				if (total === 0 || this.correct === 0) return '0%';
+				return (this.correct / total * 100).toFixed(0) + '%';
+			},
+			timerDisplay() {
+				const m = Math.floor(this.elapsedSeconds / 60);
+				const s = this.elapsedSeconds % 60;
+				return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
+			},
+			isFirstQuestion() {
+				return this.groupIndex === 0 && this.questionIndex === 0;
+			},
+			isLastQuestion() {
+				const last = this.questionTypeGroups.length - 1;
+				if (last < 0) return true;
+				const lastQ = this.questionTypeGroups[last].questions.length - 1;
+				return this.groupIndex === last && this.questionIndex === lastQ;
+			},
+			currentSeq() {
+				const g = this.questionTypeGroups[this.groupIndex];
+				if (!g || !g.questions[this.questionIndex]) return '-';
+				return g.questions[this.questionIndex].seq;
+			},
+			scrollStyle() {
+				// timer-bar ~48px + stat-row ~52px + bottom-nav ~44px
+				const sys = uni.getSystemInfoSync();
+				const h = sys.windowHeight - 48 - 52 - 44;
+				return 'height:' + (h > 200 ? h : 400) + 'px;';
+			},
+		},
+		onLoad(options) {
+			this.newData = {
+				knowledgePointId: options.knowledgePointId || '',
+				attemptId: options.attemptId || '',
+				examId: options.examId || '',
+			};
+			uni.setNavigationBarTitle({ title: '刷题练习' });
+			this.startTimer();
+			// 计算题号面板可滚动高度
+			const sys = uni.getSystemInfoSync();
+			this.navScrollHeight = Math.floor(sys.windowHeight * 0.5);
+			this.loadPanel();
+		},
+		onUnload() {
+			this.stopTimer();
+		},
+		// 支持导航栏"题号"按钮(pages.json 中配置 navigationBarButtons)
+		onNavigationBarButtonTap(e) {
+			if (e.index === 0) {
+				this.showQuestionNav = !this.showQuestionNav;
+			}
+		},
+		methods: {
+			closeNav() {
+				this.showQuestionNav = false;
+			},
+			toggleQuestionNav() {
+				this.showQuestionNav = !this.showQuestionNav;
+			},
+			toggleAnswer() {
+				this.viewAnswerShowType = !this.viewAnswerShowType;
+			},
+			onOptionTap(e) {
+				const tag = e.currentTarget.dataset.tag;
+				if (tag !== undefined) this.selectOption(tag);
+			},
+			onNavNumTap(e) {
+				const gi = Number(e.currentTarget.dataset.gi);
+				const qi = Number(e.currentTarget.dataset.qi);
+				this.jumpToQuestion(gi, qi);
+			},
+			startTimer() {				this.timerInterval = setInterval(() => {
+					this.elapsedSeconds++;
+				}, 1000);
+			},
+			stopTimer() {
+				if (this.timerInterval) clearInterval(this.timerInterval);
+			},
+			diffName(d) { return DIFFICULTY_MAP[d] ? DIFFICULTY_MAP[d].name : ''; },
+			diffClass(d) { return DIFFICULTY_MAP[d] ? DIFFICULTY_MAP[d].cls : ''; },
+			isSelected(tag) {
+				if (!this.currentQuestion || !this.currentQuestion.userAnswer) return false;
+				return this.currentQuestion.userAnswer.indexOf(tag) !== -1;
+			},
+			formatAnswer(ans) {
+				if (!ans) return '';
+				return ans.split(',').map(a => {
+					if (a === 'Y') return '对';
+					if (a === 'N') return '错';
+					return a;
+				}).join('、');
+			},
+			getNavNumClass(item, gi, qi) {
+				const isCurrent = this.groupIndex === gi && this.questionIndex === qi;
+				if (isCurrent) return 'current';
+				if (item.answerStatus === 1) return 'answered';
+				if (item.answerStatus === 2) return 'wrong';
+				return '';
+			},
+			// 加载左侧面板数据
+			loadPanel() {
+				if (this.loading) return;
+				this.loading = true;
+				examUserPracticePanel({ knowledgePointId: this.newData.knowledgePointId }).then(res => {
+					const resp = res.data || {};
+					if (resp.code !== 200) {
+						uni.showToast({ title: resp.message || '加载失败', icon: 'none' });
+						return;
+					}
+					const d = resp.data || {};
+					this.examInfo = {
+						knowledgePointName: d.knowledgePointName || '',
+						totalCount: d.totalCount || 0,
+					};
+					this.$set(this, 'questionTypeGroups', d.questionTypeGroups || []);
+					this.groupIndex = 0;
+					this.questionIndex = 0;
+					const first = (d.questionTypeGroups || [])[0];
+					if (first && first.questions && first.questions[0]) {
+						this.loadQuestionDetail(first.questions[0].questionId, first.questions[0].userAnswer);
+					}
+				}).catch(e => {
+					console.error('loadPanel error', e);
+					uni.showToast({ title: '加载失败', icon: 'none' });
+				}).finally(() => {
+					this.loading = false;
+				});
+			},
+			// 加载单题详情
+			loadQuestionDetail(questionId, userAnswer) {
+				examUserPracticeQuestionDetail({ questionId }).then(res => {
+					const resp = res.data || {};
+					if (resp.code !== 200) return;
+					const q = resp.data || {};
+					// 判断题补充选项
+					if (q.questionType == 3) {
+						q.options = [
+							{ optionContent: '正确', optionTag: 'Y', sortOrder: 1 },
+							{ optionContent: '错误', optionTag: 'N', sortOrder: 2 },
+						];
+					}
+					q.userAnswer = Array.isArray(userAnswer)
+						? userAnswer
+						: (userAnswer ? userAnswer.split(',') : []);
+					this.$set(this, 'currentQuestion', q);
+					this.$set(this, 'viewAnswerShowType', false);
+				}).catch(e => {
+					console.error('loadQuestionDetail error', e);
+				});
+			},
+			// 选择选项
+			selectOption(tag) {
+				if (!this.currentQuestion) return;
+				const type = this.currentQuestion.questionType;
+				let ans = [...(this.currentQuestion.userAnswer || [])];
+				if (type == 1 || type == 3) {
+					// 单选 / 判断:单选
+					ans = [tag];
+				} else if (type == 2) {
+					// 多选
+					const idx = ans.indexOf(tag);
+					if (idx === -1) {
+						ans.push(tag);
+					} else {
+						ans.splice(idx, 1);
+					}
+				}
+				this.$set(this.currentQuestion, 'userAnswer', ans);
+			},
+			// 提交当前题答案并切换题目
+			_submitThenGo(nextGroupIndex, nextQuestionIndex) {
+				const q = this.currentQuestion;
+				const hasAnswer = q && q.userAnswer && q.userAnswer[0];
+				if (!hasAnswer) {
+					this._goTo(nextGroupIndex, nextQuestionIndex);
+					return;
+				}
+				const userAnswerStr = q.userAnswer.join(',');
+				examUserPracticeSubmit({
+					attemptId: this.newData.attemptId,
+					questionId: q.id,
+					userAnswer: userAnswerStr,
+				}).then(res => {
+					const data = (res.data || {}).data || {};
+					// 回写 answerStatus 和 userAnswer 到面板 item
+					const answerStatus = data.answerStatus;
+					const g = this.questionTypeGroups[this.groupIndex];
+					if (g && g.questions[this.questionIndex]) {
+						this.$set(g.questions[this.questionIndex], 'answerStatus', answerStatus);
+						this.$set(g.questions[this.questionIndex], 'userAnswer', userAnswerStr);
+					}
+					this._goTo(nextGroupIndex, nextQuestionIndex);
+				}).catch(() => {
+					this._goTo(nextGroupIndex, nextQuestionIndex);
+				});
+			},
+			_goTo(gi, qi) {
+				this.groupIndex = gi;
+				this.questionIndex = qi;
+				const g = this.questionTypeGroups[gi];
+				if (!g) return;
+				const item = g.questions[qi];
+				if (!item) return;
+				this.loadQuestionDetail(item.questionId, item.userAnswer);
+			},
+			prevQuestion() {
+				if (this.isFirstQuestion) return;
+				let gi = this.groupIndex;
+				let qi = this.questionIndex;
+				if (qi > 0) {
+					qi--;
+				} else {
+					gi--;
+					qi = (this.questionTypeGroups[gi] || { questions: [] }).questions.length - 1;
+				}
+				this._submitThenGo(gi, qi);
+			},
+			nextQuestion() {
+				if (this.isLastQuestion) return;
+				let gi = this.groupIndex;
+				let qi = this.questionIndex;
+				const curGroup = this.questionTypeGroups[gi];
+				if (qi < curGroup.questions.length - 1) {
+					qi++;
+				} else {
+					gi++;
+					qi = 0;
+				}
+				this._submitThenGo(gi, qi);
+			},
+			// 从题号面板跳题
+			jumpToQuestion(gi, qi) {
+				this.showQuestionNav = false;
+				if (gi === this.groupIndex && qi === this.questionIndex) return;
+				this._submitThenGo(gi, qi);
+			},
+		},
+	}
+</script>
+
+<style lang="stylus" scoped>
+	.practice-screen
+		height 100%
+		display flex
+		flex-direction column
+		background #f0f4fb
+		overflow hidden
+
+	// 顶部绿色计时器
+	.timer-bar
+		display flex
+		align-items center
+		justify-content center
+		gap 16rpx
+		background linear-gradient(90deg, #52c41a, #389e0d)
+		padding 12rpx 32rpx
+		flex-shrink 0
+
+	.timer-label
+		color rgba(255,255,255,.8)
+		font-size 24rpx
+
+	.timer-text
+		color #fff
+		font-size 44rpx
+		font-weight 700
+		letter-spacing 4rpx
+
+	.timer-mode
+		color rgba(255,255,255,.6)
+		font-size 22rpx
+
+	// 统计行
+	.stat-row
+		display flex
+		align-items center
+		background #fff
+		padding 12rpx 0
+		flex-shrink 0
+		border-bottom 1rpx solid #e8edf3
+
+	.stat-item
+		flex 1
+		display flex
+		flex-direction column
+		align-items center
+
+	.stat-num
+		font-size 36rpx
+		font-weight 700
+		color #333
+
+	.stat-correct
+		color #52c41a
+
+	.stat-wrong
+		color #ff4d4f
+
+	.stat-label
+		font-size 22rpx
+		color #999
+		margin-top 2rpx
+
+	.stat-divider
+		width 1rpx
+		height 48rpx
+		background #e8edf3
+
+	// 题目内容滚动区
+	.q-scroll-content
+		flex 1
+		overflow hidden
+
+	.q-card
+		margin 24rpx
+		padding 28rpx
+		background #fff
+		border-radius 20rpx
+		border 1rpx solid #dde3ed
+
+	// 题头
+	.q-header
+		display flex
+		align-items center
+		justify-content space-between
+		margin-bottom 20rpx
+
+	.q-header-left
+		display flex
+		align-items center
+		flex-wrap wrap
+		gap 10rpx
+
+	.q-type-badge
+		background #1677ff
+		color #fff
+		font-size 22rpx
+		padding 4rpx 14rpx
+		border-radius 8rpx
+
+	.q-num-text
+		font-size 26rpx
+		font-weight 600
+		color #333
+
+	.diff-badge
+		font-size 20rpx
+		padding 2rpx 12rpx
+		border-radius 8rpx
+		border 1rpx solid transparent
+
+	.diff-easy
+		background #f6ffed
+		color #52c41a
+		border-color #b7eb8f
+
+	.diff-normal
+		background #e6f4ff
+		color #1677ff
+		border-color #91caff
+
+	.diff-hard
+		background #fff7e6
+		color #fa8c16
+		border-color #ffd591
+
+	.diff-extreme
+		background #f9f0ff
+		color #722ed1
+		border-color #d3adf7
+
+	.view-answer-btn
+		background #1677ff
+		color #fff
+		font-size 24rpx
+		padding 8rpx 24rpx
+		border-radius 10rpx
+		flex-shrink 0
+
+	// 题干
+	.q-content
+		font-size 28rpx
+		color #333
+		line-height 1.7
+		margin-bottom 24rpx
+
+	// 选项
+	.q-options
+		display flex
+		flex-direction column
+		gap 16rpx
+		margin-bottom 16rpx
+
+	.q-option
+		display flex
+		align-items center
+		border 1rpx solid #e8edf3
+		border-radius 12rpx
+		padding 20rpx 24rpx
+
+	.q-option.selected
+		border-color #1677ff
+		background #e6f7ff
+
+		.q-option-label
+			background #1677ff
+			color #fff
+
+	.q-option-label
+		width 48rpx
+		height 48rpx
+		line-height 48rpx
+		text-align center
+		border-radius 50%
+		background #f0f0f0
+		color #666
+		font-size 24rpx
+		font-weight 600
+		flex-shrink 0
+		margin-right 20rpx
+
+	.q-option-content
+		flex 1
+		font-size 28rpx
+		color #333
+
+	// 答案区
+	.answer-result-box
+		background #f5f5f5
+		border-radius 12rpx
+		padding 24rpx
+		margin-top 16rpx
+
+	.answer-row
+		font-size 26rpx
+		color #333
+		margin-bottom 12rpx
+
+	.answer-label
+		font-weight 600
+
+	.answer-value
+		color #52c41a
+		font-weight 600
+
+	.analysis-title
+		font-size 26rpx
+		font-weight 600
+		color #333
+		margin-bottom 8rpx
+
+	.analysis-content
+		font-size 26rpx
+		color #666
+		line-height 1.7
+		margin 0 20rpx
+
+	.analysis-empty
+		color #999
+
+	.scroll-bottom-space
+		height 40rpx
+
+	// 底部上一题/选题/下一题
+	.q-nav
+		display flex
+		background #fff
+		border-top 1rpx solid #e8edf3
+		flex-shrink 0
+
+	.q-nav-btn
+		flex 1
+		height 88rpx
+		line-height 88rpx
+		text-align center
+		font-size 28rpx
+		color #1677ff
+
+	.q-nav-btn.q-nav-btn-next
+		border-left 1rpx solid #e8edf3
+
+	.q-nav-center
+		border-left 1rpx solid #e8edf3
+		border-right 1rpx solid #e8edf3
+
+	.q-nav-btn.disabled
+		color #ccc
+
+	// 题号导航底部弹层
+	.q-nav-panel
+		position fixed
+		top 0
+		left 0
+		right 0
+		bottom 0
+		background rgba(0,0,0,0.45)
+		z-index 100
+		display flex
+		flex-direction column
+		justify-content flex-end
+
+	.q-nav-scroll-wrapper
+		background #fff
+		border-radius 32rpx 32rpx 0 0
+		max-height 70vh
+		display flex
+		flex-direction column
+
+	.q-nav-header
+		display flex
+		align-items center
+		justify-content space-between
+		padding 28rpx 28rpx 16rpx
+		flex-shrink 0
+
+	.q-nav-title
+		font-size 30rpx
+		font-weight 600
+		color #222
+
+	.q-nav-close
+		font-size 32rpx
+		color #999
+		padding 10rpx
+
+	.q-nav-scroll
+		overflow hidden
+		padding-bottom 40rpx
+
+	.q-group
+		padding 0 28rpx 16rpx
+
+	.q-group-title
+		font-size 24rpx
+		color #666
+		line-height 60rpx
+
+	.q-num-row
+		display flex
+		flex-wrap wrap
+
+	.q-num
+		width 72rpx
+		height 72rpx
+		line-height 72rpx
+		text-align center
+		font-size 24rpx
+		border-radius 12rpx
+		margin 8rpx
+		background #f4f6fa
+		color #333
+		border 1rpx solid transparent
+
+	.q-num.answered
+		background #f6ffed
+		color #52c41a
+		border-color #b7eb8f
+
+	.q-num.wrong
+		background #fff2f0
+		color #ff4d4f
+		border-color #ffa39e
+
+	.q-num.current
+		background #1677ff
+		color #fff
+
+	// 图例
+	.nav-legend
+		display flex
+		gap 24rpx
+		flex-wrap wrap
+		padding 12rpx 28rpx 24rpx
+
+	.legend-item
+		display flex
+		align-items center
+		gap 8rpx
+		font-size 22rpx
+		color #666
+
+	.legend-dot
+		width 40rpx
+		height 40rpx
+		margin 0
+		font-size 0
+
+	// 空态
+	.empty-tip
+		text-align center
+		line-height 120rpx
+		color #aaa
+		font-size 24rpx
+</style>