testTopic.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <div class="test-topic">
  3. <!-- 测试标题 -->
  4. <div class="test-title" @click="handleClick">
  5. <el-icon><ArrowLeftBold /></el-icon>
  6. {{ topicTitle }}
  7. </div>
  8. <!-- 测试内容 -->
  9. <div class="test-content">
  10. <!-- 左侧 题目 -->
  11. <div class="content-left">
  12. <div class="question-container">
  13. <!-- 添加题目进度显示 -->
  14. <div class="question-progress">
  15. 第 {{ currentQuestionIndex + 1 }}/{{ questions.length }} 题
  16. </div>
  17. <!-- 添加题目内容 -->
  18. <div class="question-content">
  19. <!-- 题目 -->
  20. <div class="question-title" v-html="questions[currentQuestionIndex]?.qcontent"></div>
  21. <!-- 选项 -->
  22. <div class="question-options">
  23. <el-radio-group
  24. v-model="selectedOption"
  25. size="large"
  26. class="radio-button-group custom-radio-group"
  27. >
  28. <el-radio-button
  29. v-for="option in questions[currentQuestionIndex]?.questionOptionsList || []"
  30. :key="option.oid"
  31. :label="option.oid"
  32. >{{ option.ovalue }}. {{ option.oname }}</el-radio-button
  33. >
  34. </el-radio-group>
  35. </div>
  36. </div>
  37. <!-- 添加题目导航按钮盒子 -->
  38. <div class="question-navigation">
  39. <el-button
  40. type="text"
  41. border
  42. class="prev-question-btn"
  43. @click="handlePrevQuestion"
  44. >上一题</el-button
  45. >
  46. <!-- 下一题按钮样式 最后一题显示为提交 -->
  47. <el-button
  48. type="primary"
  49. class="next-question-btn"
  50. @click="currentQuestionIndex === questions.length - 1 ? handleSubmit() : handleNextQuestion()"
  51. >{{ currentQuestionIndex === questions.length - 1 ? '提交' : '下一题' }}</el-button
  52. >
  53. </div>
  54. </div>
  55. </div>
  56. <!-- 右侧答题卡 -->
  57. <div class="content-right">
  58. <div class="center-box">
  59. <div class="box-title">答题卡</div>
  60. <div class="box-content">
  61. <div class="number-buttons">
  62. <el-button
  63. v-for="num in questions.length"
  64. :key="num"
  65. class="circle-btn"
  66. :class="{
  67. clicked: clickedNumbers.includes(num),
  68. current: num === currentQuestionIndex + 1
  69. }"
  70. @click="handleButtonClick(num)"
  71. >{{ num }}</el-button
  72. >
  73. </div>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. </div>
  79. </template>
  80. <script setup>
  81. import { ref,watch ,onMounted} from 'vue'
  82. import { ArrowLeftBold } from '@element-plus/icons-vue'
  83. import { useRouter,useRoute } from 'vue-router'
  84. import {QuestionTopicList, QuestionTopicSave} from '@/api/question/test.js'
  85. import { ElMessage } from 'element-plus'
  86. const router = useRouter()
  87. const route = useRoute()
  88. const selectedOption = ref(null)
  89. const clickedNumbers = ref([])
  90. // 当前题目索引
  91. const currentQuestionIndex = ref(0)
  92. const userAnswers = ref([]) // 存储用户答案
  93. const userQuestAnswers = ref([]) // 存储用户答案
  94. const handleClick = () => {
  95. router.push({
  96. path: '/evaluation',
  97. })
  98. }
  99. // 渲染列表标题
  100. const topicTitle = ref(
  101. route.query.title
  102. )
  103. // 添加题目数据
  104. const qrId = ref()
  105. const questResultId = ref()
  106. const questions = ref([])
  107. onMounted(()=>{
  108. // 获取路由参数中的id
  109. qrId.value = route.query.id
  110. if (qrId.value) {
  111. // 将id作为参数传递给QuestionTopicList接口
  112. QuestionTopicList({ qrId: qrId.value }).then(res=>{
  113. questResultId.value = res.data.qrResultId
  114. // 将接口返回的题目数据赋值给questions
  115. questions.value = res.data.questionnaire.questionList || []
  116. // 初始化用户答案数组
  117. userAnswers.value = new Array(questions.value.length).fill(null)
  118. // 初始化currentQuestionIndex
  119. if (questions.value.length > 0) {
  120. currentQuestionIndex.value = 0
  121. }
  122. }).catch(err => {
  123. console.error('获取题目失败:', err)
  124. })
  125. }
  126. })
  127. // 上一题按钮逻辑
  128. const handlePrevQuestion = () => {
  129. if (currentQuestionIndex.value > 0) {
  130. // 保存当前题目的答案
  131. userAnswers.value[currentQuestionIndex.value] = {
  132. qid: questions.value[currentQuestionIndex.value].qid,
  133. oid: selectedOption.value
  134. };
  135. currentQuestionIndex.value--;
  136. // 加载上一题的答案
  137. selectedOption.value = userAnswers.value[currentQuestionIndex.value]?.oid || null;
  138. }
  139. }
  140. // 下一题按钮逻辑
  141. const handleNextQuestion = () => {
  142. if (currentQuestionIndex.value < questions.value.length - 1) {
  143. // 保存当前题目的答案
  144. userAnswers.value[currentQuestionIndex.value] = {
  145. qid: questions.value[currentQuestionIndex.value].qid,
  146. oid: selectedOption.value
  147. };
  148. currentQuestionIndex.value++;
  149. // 加载下一题的答案
  150. selectedOption.value = userAnswers.value[currentQuestionIndex.value]?.oid || null;
  151. }
  152. }
  153. // 提交
  154. const handleSubmit = () => {
  155. // 保存最后一题答案
  156. userAnswers.value[currentQuestionIndex.value] = {
  157. qid: questions.value[currentQuestionIndex.value].qid,
  158. oid: selectedOption.value
  159. };
  160. // 检查是否所有题目都已回答
  161. const allAnswered = userAnswers.value.every(answer => answer?.oid !== null && answer?.oid !== undefined)
  162. if(!allAnswered){
  163. ElMessage.error('检测到有题目未完成,请回答所有题目后再提交')
  164. return;
  165. }
  166. // 答完全部答完跳转到提交页面
  167. QuestionTopicSave({
  168. "qrId": Number(qrId.value),
  169. "qrResultId": Number(questResultId.value),
  170. "questionList": userAnswers.value
  171. }).then(res=>{
  172. ElMessage.success('提交成功')
  173. router.push('/testSubmit')
  174. })
  175. }
  176. // 答题卡
  177. const handleButtonClick = (num) => {
  178. // 切换到对应题目
  179. currentQuestionIndex.value = num - 1
  180. // 加载该题目的答案
  181. selectedOption.value = userAnswers.value[currentQuestionIndex.value]?.oid || null
  182. }
  183. // 监听选项变化,自动更新答题卡状态
  184. watch(selectedOption, (newVal) => {
  185. if (newVal !== null) {
  186. const currentQuestionNumber = currentQuestionIndex.value + 1
  187. // 保存当前题目的答案
  188. userAnswers.value[currentQuestionIndex.value] = {
  189. qid: questions.value[currentQuestionIndex.value].qid,
  190. oid: newVal
  191. }
  192. if (!clickedNumbers.value.includes(currentQuestionNumber)) {
  193. clickedNumbers.value.push(currentQuestionNumber)
  194. }
  195. }
  196. })
  197. </script>
  198. <style scoped lang="scss">
  199. @use 'sass:math';
  200. // 定义rpx转换函数
  201. @function rpx($px) {
  202. @return math.div($px, 750) * 100vw;
  203. }
  204. .test-topic {
  205. position: fixed;
  206. top: 0;
  207. left: 0;
  208. right: 0;
  209. bottom: 0;
  210. display: flex;
  211. flex-direction: column;
  212. background-color: #e2ddfc;
  213. gap: rpx(0);
  214. }
  215. .test-title {
  216. width: 100%;
  217. height: rpx(30);
  218. font-size: rpx(10);
  219. cursor: pointer;
  220. color: black;
  221. display: flex; /* 设置flex布局 */
  222. align-items: center; /* 垂直居中 */
  223. padding-left: rpx(15); /* 添加左侧内边距 */
  224. gap: rpx(5); /* 设置图标和文字间距 */
  225. }
  226. .test-content {
  227. width: 100%;
  228. height: 100%;
  229. display: flex; /* 使用flex布局使子元素并排 */
  230. gap: rpx(10); /* 左右盒子间距 */
  231. }
  232. // 左侧盒子样式 题目
  233. .content-left {
  234. flex: 1.5;
  235. border-radius: rpx(5);
  236. padding: rpx(10);
  237. }
  238. .question-container {
  239. width: 100%;
  240. height: 100%;
  241. padding-left: rpx(40);
  242. box-sizing: border-box;
  243. position: relative; // 相对定位
  244. }
  245. /* 添加题目进度样式 */
  246. .question-progress {
  247. font-size: rpx(12);
  248. font-weight: bold;
  249. color: #9e78e7;
  250. text-align: left;
  251. }
  252. // 题目
  253. .question-content {
  254. // margin-top: 20px;
  255. ::v-deep(.el-radio-button) {
  256. // 添加选项选中状态样式
  257. &.is-active .el-radio-button__inner {
  258. background-color: #9e78e7;
  259. border-color: #9e78e7;
  260. color: white;
  261. }
  262. }
  263. }
  264. .question-title {
  265. font-size: rpx(12);
  266. text-align: left;
  267. color: black;
  268. margin-bottom: 15px;
  269. line-height: 1.5;
  270. }
  271. .question-options {
  272. display: flex;
  273. flex-direction: column;
  274. gap: 10px;
  275. }
  276. .radio-button-group {
  277. display: flex;
  278. flex-direction: column;
  279. text-align: left;
  280. gap: 10px;
  281. }
  282. .el-radio-button {
  283. width: 100%;
  284. margin-top: rpx(10);
  285. justify-content: flex-start;
  286. }
  287. .el-radio-button ::v-deep(.el-radio-button__inner) {
  288. width: fit-content; //按钮宽度跟随文字多少变化
  289. height: rpx(25);
  290. display: flex;
  291. align-items: center;
  292. text-align: left;
  293. font-size: rpx(8);
  294. border-radius: rpx(5);
  295. color: black;
  296. border: 1px solid white;
  297. background: rgb(255, 255, 255, 0.5);
  298. }
  299. // 上一题 下一题按钮
  300. /* 添加导航按钮样式 */
  301. .question-navigation {
  302. display: flex;
  303. margin-top: rpx(20); // 与题目内容保持间距
  304. // 绝对定位到底部
  305. position: absolute;
  306. bottom: rpx(25);
  307. left: rpx(40);
  308. right: 0;
  309. // 上一题按钮样式
  310. ::v-deep(.prev-question-btn) {
  311. border: 1px solid #9e78e7; // 添加边框
  312. color: #9e78e7; // 设置文字颜色
  313. font-size: rpx(8);
  314. background-color: transparent; // 透明背景
  315. &:hover {
  316. background-color: #9e78e7; // hover效果
  317. color: white;
  318. }
  319. }
  320. // 下一题按钮样式
  321. ::v-deep(.next-question-btn) {
  322. background-color: #9e78e7; // 更换为紫色
  323. border-color: #9e78e7;
  324. font-size: rpx(8);
  325. &:hover {
  326. background-color: #8a63d2; // hover加深颜色
  327. border-color: #8a63d2;
  328. }
  329. }
  330. }
  331. .question-navigation .el-button {
  332. width: rpx(80); // 固定按钮宽度
  333. height: rpx(20);
  334. border-radius: rpx(5);
  335. }
  336. // 右侧盒子样式 答题卡
  337. .content-right {
  338. flex: 1;
  339. border-radius: rpx(5);
  340. padding: rpx(10);
  341. display: flex; /* 使用flex布局 */
  342. flex-direction: column; /* 垂直方向排列 */
  343. align-items: center; /* 水平居中 */
  344. justify-content: center; /* 垂直居中 */
  345. }
  346. // 答题卡
  347. .center-box {
  348. width: rpx(200); /* 宽度 */
  349. height: 100%; /* 高度 */
  350. background-color: #fefefe80;
  351. border-radius: rpx(5);
  352. border: 1px solid white;
  353. display: flex;
  354. flex-direction: column; /* 垂直排列子元素 */
  355. padding: rpx(10);
  356. gap: rpx(5); /* 标题和内容间距 */
  357. box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  358. }
  359. // 标题盒子样式
  360. .box-title {
  361. width: 100%;
  362. height: rpx(20);
  363. font-size: rpx(11);
  364. font-weight: bold;
  365. border-radius: rpx(3);
  366. line-height: rpx(20);
  367. }
  368. // 内容盒子样式
  369. .box-content {
  370. height: 100%;
  371. border-radius: rpx(3);
  372. padding: rpx(5);
  373. overflow: auto; /* 内容溢出时显示滚动条 */
  374. }
  375. // 数字按钮容器
  376. .number-buttons {
  377. display: flex;
  378. justify-content: flex-start; // 将center改为flex-start实现左对齐
  379. gap: rpx(8);
  380. flex-wrap: wrap; // 添加换行属性
  381. }
  382. // 圆形按钮样式
  383. .circle-btn {
  384. width: rpx(25);
  385. height: rpx(25);
  386. margin-left: 0;
  387. border-radius: 50%; // 圆形显示
  388. display: flex;
  389. align-items: center;
  390. justify-content: center;
  391. font-size: rpx(10);
  392. background-color: transparent;
  393. border: 1px solid white;
  394. color: #909090;
  395. min-width: auto; // 移除Element Plus按钮默认最小宽度
  396. }
  397. .circle-btn.current {
  398. border: 2px solid #9e78e7;
  399. }
  400. .circle-btn:focus,
  401. .circle-btn:active,
  402. .circle-btn.clicked {
  403. color: white;
  404. background-color: #9e78e7;
  405. }
  406. .circle-btn:hover{
  407. border: 2px solid #9e78e7;
  408. }
  409. </style>