|
@@ -0,0 +1,445 @@
|
|
|
+<template>
|
|
|
+ <Container
|
|
|
+ :scrollX="true"
|
|
|
+ :scrollY="true"
|
|
|
+ :scroll-into-view="`item-${nowIndex}`"
|
|
|
+ :title="title"
|
|
|
+ @onSafeAreaChange="onSafeAreaChange"
|
|
|
+ :onBack="onBack"
|
|
|
+ v-bind="$attrs"
|
|
|
+ v-if="!open"
|
|
|
+ >
|
|
|
+ <template v-for="(item, parindex) in data" :key="parindex">
|
|
|
+ <view
|
|
|
+ :id="`item-${parindex}`"
|
|
|
+ v-if="parindex === nowIndex"
|
|
|
+ class="topic-item"
|
|
|
+ :style="{
|
|
|
+ width: `${safeArea.width}px`,
|
|
|
+ height: `${safeArea.height}px`,
|
|
|
+ flexShrink: 0, // 解决宽度无效问题
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <!-- 头部 -->
|
|
|
+ <view class="topic-header">
|
|
|
+ <view class="topic-header-left">
|
|
|
+ <view class="topic-type"> {{ styleMap[item.style] }}题 </view>
|
|
|
+ <view class="topic-count">
|
|
|
+ 第{{ parindex + 1 }}题/共{{ total }}题
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="star-icon" @tap="handleStar(item)">
|
|
|
+ <uni-icons
|
|
|
+ :type="item.star ? 'star-filled' : 'star'"
|
|
|
+ size="20"
|
|
|
+ color="#fe2624"
|
|
|
+ />
|
|
|
+ {{ item.star ? "已" : "" }}收藏
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 问题内容 -->
|
|
|
+ <view class="topic-content">
|
|
|
+ <uvParse
|
|
|
+ v-if="item.isImage"
|
|
|
+ :content="item.title"
|
|
|
+ class="question-text"
|
|
|
+ ></uvParse>
|
|
|
+ <rich-text
|
|
|
+ v-else
|
|
|
+ :nodes="item.title"
|
|
|
+ class="question-text"
|
|
|
+ ></rich-text>
|
|
|
+ <uvParse
|
|
|
+ :content="item.questions_ex"
|
|
|
+ v-if="item.style === 6 && item.isImage"
|
|
|
+ class="question-text"
|
|
|
+ ></uvParse>
|
|
|
+ <rich-text
|
|
|
+ v-else
|
|
|
+ :nodes="item.questions_ex"
|
|
|
+ class="question-text"
|
|
|
+ ></rich-text>
|
|
|
+ <questions
|
|
|
+ v-if="item.style !== 6"
|
|
|
+ v-for="(question, index) in item.questions"
|
|
|
+ :key="index"
|
|
|
+ :answerList="item.ansList"
|
|
|
+ :index="index"
|
|
|
+ :styleCount="item.style"
|
|
|
+ :question="question"
|
|
|
+ :showResult="item.showResult"
|
|
|
+ :parindex="parindex"
|
|
|
+ @select="handleSelect"
|
|
|
+ />
|
|
|
+ <view class="other" v-else>
|
|
|
+ <questions
|
|
|
+ v-for="(question, index) in item.questions"
|
|
|
+ :key="index"
|
|
|
+ :answerList="item.ansList"
|
|
|
+ :index="index"
|
|
|
+ :styleCount="item.style"
|
|
|
+ :question="question"
|
|
|
+ :showResult="item.showResult"
|
|
|
+ :parindex="parindex"
|
|
|
+ @select="handleSelect"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 答案展示 -->
|
|
|
+ <view v-if="item.showResult" class="answer-section">
|
|
|
+ <view class="answer-content">
|
|
|
+ <view class="answer-row">
|
|
|
+ <view class="answer-item border-r-primary">
|
|
|
+ 正确答案:
|
|
|
+ <text class="answer-text">{{
|
|
|
+ item.ansList.map((i) => i.label).join("")
|
|
|
+ }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="answer-item">
|
|
|
+ 我的答案:
|
|
|
+ <text class="answer-text">{{
|
|
|
+ item.selectAns.map((i) => i.value).join("")
|
|
|
+ }}</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="tip"
|
|
|
+ :style="{
|
|
|
+ color: item.isRight ? '#00be00' : '#f00',
|
|
|
+ }"
|
|
|
+ >{{ item.isRight ? "太棒了~" : "再接再励!" }}</view
|
|
|
+ >
|
|
|
+ </view>
|
|
|
+ <view class="parsing">
|
|
|
+ <p>解析:</p>
|
|
|
+ <scroll-view
|
|
|
+ :show-scrollbar="false"
|
|
|
+ scroll-y
|
|
|
+ class="parsing-text"
|
|
|
+ >
|
|
|
+ <rich-text :nodes="item.explain"></rich-text>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底部按钮 -->
|
|
|
+ <view class="button-group">
|
|
|
+ <button
|
|
|
+ v-if="parindex >= 1 && item.showResult"
|
|
|
+ class="prev-btn"
|
|
|
+ @tap="handlePage(item, parindex, 'prevPage')"
|
|
|
+ >
|
|
|
+ 上一题
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-if="parindex + 1 < total && item.showResult"
|
|
|
+ class="next-btn"
|
|
|
+ @tap="handlePage(item, parindex, 'nextPage')"
|
|
|
+ >
|
|
|
+ 下一题
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-if="parindex + 1 === total && item.showResult"
|
|
|
+ @click="emit('lookReport', data, submitter)"
|
|
|
+ >
|
|
|
+ 查看报告
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-if="!item.showResult"
|
|
|
+ @click="questionSubmit(item, parindex)"
|
|
|
+ >
|
|
|
+ 提交
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+ </Container>
|
|
|
+ <Modal
|
|
|
+ v-model:open="open"
|
|
|
+ title="温馨提示"
|
|
|
+ :submitter="submitter"
|
|
|
+ :onClose="onClose"
|
|
|
+ :onSubmit="onSubmit"
|
|
|
+ >
|
|
|
+ <view :style="{ margin: '10px 0' }">{{ submitter.context }}</view>
|
|
|
+ </Modal>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+// 答题组件
|
|
|
+import { ref, watchEffect } from "vue";
|
|
|
+import Questions from "./Questions.vue";
|
|
|
+import Container from "../Container/Container.vue";
|
|
|
+import Modal from "../Modal/Modal.vue";
|
|
|
+import uvParse from "@/uni_modules/uv-parse/components/uv-parse/uv-parse.vue";
|
|
|
+const styleMap = {
|
|
|
+ 2: "单选",
|
|
|
+ 3: "多选",
|
|
|
+ 6: "配伍",
|
|
|
+};
|
|
|
+
|
|
|
+const submitter = ref({
|
|
|
+ text: "提交查看",
|
|
|
+ closeText: "直接退出",
|
|
|
+ context: "您本次答题还未提交, 确定要退出答题吗?",
|
|
|
+ isEndQuestion: false,
|
|
|
+});
|
|
|
+
|
|
|
+const safeArea = ref({}); // 安全区域
|
|
|
+const data = ref([]); // 题目数据
|
|
|
+const open = ref(false); // 是否显示弹窗
|
|
|
+let r = null; // 异步方法
|
|
|
+
|
|
|
+// Props 定义
|
|
|
+const props = defineProps({
|
|
|
+ topics: {
|
|
|
+ type: Array,
|
|
|
+ default: () => [],
|
|
|
+ },
|
|
|
+ onStar: {
|
|
|
+ type: Function,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ total: {
|
|
|
+ type: Number,
|
|
|
+ default: 0,
|
|
|
+ },
|
|
|
+ title: String,
|
|
|
+});
|
|
|
+
|
|
|
+watchEffect(() => {
|
|
|
+ data.value = props.topics;
|
|
|
+});
|
|
|
+
|
|
|
+// Emits 定义
|
|
|
+const emit = defineEmits([
|
|
|
+ "prevPage",
|
|
|
+ "nextPage",
|
|
|
+ "answerChange",
|
|
|
+ "lookReport",
|
|
|
+]);
|
|
|
+
|
|
|
+const onBack = () =>
|
|
|
+ new Promise((resolve) => {
|
|
|
+ const isEndQuestion =
|
|
|
+ nowIndex.value === props.total - 1 &&
|
|
|
+ data.value[nowIndex.value].showResult;
|
|
|
+
|
|
|
+ if (isEndQuestion) {
|
|
|
+ submitter.value = {
|
|
|
+ ...submitter.value,
|
|
|
+ text: "查看报告",
|
|
|
+ context:
|
|
|
+ "您的答题报告已准备就绪!退出后将无法查看本次分析结果,且不保留本次答题记录,确定要放弃吗?",
|
|
|
+ isEndQuestion,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ open.value = true;
|
|
|
+ r = resolve;
|
|
|
+ });
|
|
|
+
|
|
|
+const onClose = () => r(true);
|
|
|
+const onSubmit = () =>
|
|
|
+ new Promise((res) => {
|
|
|
+ emit("lookReport", data.value, submitter.value);
|
|
|
+ res(true);
|
|
|
+ });
|
|
|
+// 响应式数据
|
|
|
+const nowIndex = ref(0);
|
|
|
+
|
|
|
+// 判断答案是否正确
|
|
|
+const isRight = (selectAns, rightAns) => {
|
|
|
+ return selectAns.every((item) => rightAns.includes(item));
|
|
|
+};
|
|
|
+
|
|
|
+// 收藏方法
|
|
|
+const handleStar = (item) => {
|
|
|
+ if (!props.onStar) return;
|
|
|
+ props.onStar(item).then((res) => {
|
|
|
+ uni.showToast({
|
|
|
+ title: res ? "已加入收藏" : "已移除收藏",
|
|
|
+ icon: "none",
|
|
|
+ });
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleSelect = ({ pid, checked, index, style }) => {
|
|
|
+ // 如果不是多选,就取消其他的选项
|
|
|
+ if (style !== 3) {
|
|
|
+ data.value[pid].questions = data.value[pid].questions.map((q) => ({
|
|
|
+ ...q,
|
|
|
+ checked: false,
|
|
|
+ }));
|
|
|
+ data.value[pid].selectAns = [];
|
|
|
+ }
|
|
|
+ // 更新选项
|
|
|
+ const item = data.value[pid].questions[index];
|
|
|
+ data.value[pid].questions[index].checked = !checked;
|
|
|
+ data.value[pid].questions[index].isRight = isRight(
|
|
|
+ [item.value],
|
|
|
+ data.value[pid].ansList.map((q) => q.label)
|
|
|
+ );
|
|
|
+
|
|
|
+
|
|
|
+ // 更新答案
|
|
|
+ data.value[pid].selectAns = data.value[pid].questions
|
|
|
+ .filter((q) => q.checked)
|
|
|
+ .map((q) => q.value);
|
|
|
+};
|
|
|
+
|
|
|
+const questionSubmit = (item) => {
|
|
|
+ item.showResult = true;
|
|
|
+ item.selectAns = item.questions.filter((q) => q.checked);
|
|
|
+ item.isRight = isRight(
|
|
|
+ item.selectAns.map((q) => q.value),
|
|
|
+ item.ansList.map((q) => q.label)
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const handlePage = (item, index, type) => {
|
|
|
+ nowIndex.value = index + (type === "prevPage" ? -1 : 1);
|
|
|
+ emit(type, { item, index });
|
|
|
+};
|
|
|
+
|
|
|
+const onSafeAreaChange = (s) => {
|
|
|
+ safeArea.value = s;
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@import "@/uni.scss";
|
|
|
+.other {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+.parsing-text {
|
|
|
+ height: 381rpx;
|
|
|
+ white-space: normal;
|
|
|
+}
|
|
|
+.answer-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 45rpx;
|
|
|
+ font-family: PingFang SC, PingFang SC;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #666666;
|
|
|
+}
|
|
|
+.topic-container {
|
|
|
+ width: 100vw;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.topic-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ position: relative;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.topic-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.topic-header-left {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.topic-type {
|
|
|
+ border: 1px solid $uni-primary;
|
|
|
+ padding: 0 8px;
|
|
|
+ border-radius: 6px;
|
|
|
+ color: $uni-primary;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.topic-count {
|
|
|
+ color: #333;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.topic-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-text {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 14px;
|
|
|
+ white-space: normal;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-section {
|
|
|
+ border-radius: 16rpx;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ padding: 24rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 32rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-row {
|
|
|
+ font-size: 14px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-item {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ align-items: center;
|
|
|
+ padding-right: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.tip {
|
|
|
+ margin-left: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.border-r-primary {
|
|
|
+ border-right: 2px solid $uni-primary;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-text {
|
|
|
+ color: $uni-primary;
|
|
|
+}
|
|
|
+
|
|
|
+.button-group {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ position: sticky;
|
|
|
+ bottom: 0;
|
|
|
+ margin-top: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.prev-btn {
|
|
|
+ flex: 1;
|
|
|
+ background-color: $uni-primary-light;
|
|
|
+ color: $uni-primary;
|
|
|
+}
|
|
|
+
|
|
|
+.next-btn {
|
|
|
+ flex: 1;
|
|
|
+ background-color: $uni-primary;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.star-icon {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #000000;
|
|
|
+}
|
|
|
+</style>
|