webview.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <template>
  2. <view v-if="loading" class="loading-container">
  3. <text class="loading-text">正在加载...</text>
  4. </view>
  5. <view v-else-if="errorMessage" class="loading-container">
  6. <text class="error-text">{{ errorMessage }}</text>
  7. </view>
  8. <!-- 模式 1: 显示维度列表 -->
  9. <view v-else-if="showExamList" class="exam-list-container">
  10. <view class="title">请选择要参加的测评维度</view>
  11. <view v-for="(item, index) in examList" :key="index" class="exam-item" @click="selectExam(item.url)">
  12. <view class="exam-left">
  13. <text class="exam-name">{{ item.name || '专业能力测评' }}</text>
  14. <text v-if="item.isFinished" class="exam-status done">已完成</text>
  15. </view>
  16. <text :class="['start-btn', item.isFinished ? 'retry' : '']">{{ item.isFinished ? '重考' : '开始' }}</text>
  17. </view>
  18. <!-- 查看考试报告按钮:始终显示,未考完为灰色不可点击 -->
  19. <view class="result-footer">
  20. <button :class="['report-btn', allFinished ? '' : 'disabled']" :disabled="!allFinished" @tap="goToResult">
  21. 查看考试报告
  22. </button>
  23. <text v-if="!allFinished" class="report-hint">完成所有维度测评后可查看报告</text>
  24. </view>
  25. </view>
  26. <!-- 模式 2: 兜底 Web-view -->
  27. <web-view v-else-if="url" :src="url"></web-view>
  28. </template>
  29. <script setup>
  30. import { ref } from 'vue'
  31. import { onLoad, onShow } from '@dcloudio/uni-app'
  32. import { kaoshixingSilentLogin, getEvaluationResult } from '../../api/assessment.js'
  33. const url = ref('')
  34. const loading = ref(true)
  35. const errorMessage = ref('')
  36. const examList = ref([])
  37. const showExamList = ref(false)
  38. const allFinished = ref(false)
  39. const assessmentId = ref('')
  40. const selectExam = (examUrl) => {
  41. if (!examUrl) {
  42. uni.showToast({ title: '考试链接无效', icon: 'none' })
  43. return
  44. }
  45. uni.setStorageSync('temp_exam_url', examUrl)
  46. uni.navigateTo({
  47. url: `/pages/assessment/quiz?from=kaoshixing&assessmentId=${encodeURIComponent(assessmentId.value || '')}`
  48. })
  49. }
  50. const goToResult = () => {
  51. if (!assessmentId.value || assessmentId.value === 'undefined') {
  52. uni.showToast({ title: '参数错误', icon: 'none' })
  53. return
  54. }
  55. uni.navigateTo({
  56. url: `/pages/assessment/result?id=${assessmentId.value}`
  57. })
  58. }
  59. onShow(() => {
  60. if (showExamList.value) {
  61. checkEvaluationStatus()
  62. }
  63. })
  64. const checkEvaluationStatus = async () => {
  65. try {
  66. const userInfo = uni.getStorageSync('userInfo') || {}
  67. const studentId = userInfo.studentId || userInfo.id
  68. if (!studentId || !assessmentId.value || assessmentId.value === 'undefined') return
  69. const res = await getEvaluationResult(assessmentId.value, studentId)
  70. if (res.code === 200 && res.data) {
  71. allFinished.value = res.data.allFinished || false
  72. // 更新每个维度的完成状态
  73. const abilityResults = res.data.abilityResults || []
  74. if (abilityResults.length > 0) {
  75. examList.value = examList.value.map(exam => {
  76. const matched = abilityResults.find(a =>
  77. a.name === exam.name || a.abilityName === exam.name
  78. )
  79. return {
  80. ...exam,
  81. isFinished: matched ? matched.isPass !== undefined : false
  82. }
  83. })
  84. }
  85. }
  86. } catch (error) {
  87. console.error('检查测评状态失败:', error)
  88. }
  89. }
  90. onLoad(async (options) => {
  91. assessmentId.value = options.assessmentId || ''
  92. const mode = options.mode || ''
  93. const fallbackUrl = options.fallbackUrl ? decodeURIComponent(options.fallbackUrl) : ''
  94. // 处理直接传入的 URL (用于单次考试或报告查看)
  95. if (options.url && mode !== 'kaoshixing') {
  96. // 重新拼接可能被拆散的 URL
  97. let combinedUrl = decodeURIComponent(options.url)
  98. const queryParams = []
  99. for (let key in options) {
  100. if (key !== 'assessmentId' && key !== 'mode' && key !== 'url') {
  101. queryParams.push(`${key}=${options[key]}`)
  102. }
  103. }
  104. if (queryParams.length > 0) {
  105. combinedUrl += (combinedUrl.indexOf('?') !== -1 ? '&' : '?') + queryParams.join('&')
  106. }
  107. url.value = combinedUrl
  108. loading.value = false
  109. return
  110. }
  111. if (mode !== 'kaoshixing') {
  112. url.value = fallbackUrl
  113. if (!url.value) errorMessage.value = '缺少测评链接'
  114. loading.value = false
  115. return
  116. }
  117. // 考试星列表模式
  118. try {
  119. const userInfo = uni.getStorageSync('userInfo') || {}
  120. const studentId = userInfo.studentId || userInfo.id
  121. const studentName = userInfo.name || userInfo.nickName || '学员'
  122. const loginRes = await kaoshixingSilentLogin({
  123. user_id: String(studentId),
  124. user_name: studentName,
  125. department: '学员',
  126. evaluationId: assessmentId.value
  127. })
  128. if (loginRes.code === 200 && loginRes.data) {
  129. if (loginRes.data.exams && loginRes.data.exams.length > 0) {
  130. examList.value = loginRes.data.exams
  131. showExamList.value = true
  132. checkEvaluationStatus()
  133. } else if (loginRes.data.url) {
  134. uni.setStorageSync('temp_exam_url', loginRes.data.url)
  135. uni.redirectTo({
  136. url: `/pages/assessment/quiz?from=kaoshixing&assessmentId=${encodeURIComponent(assessmentId.value || '')}`
  137. })
  138. return
  139. }
  140. } else {
  141. throw new Error(loginRes.msg || '考试星登录失败')
  142. }
  143. } catch (error) {
  144. errorMessage.value = error.message || '加载失败'
  145. } finally {
  146. loading.value = false
  147. }
  148. })
  149. </script>
  150. <style scoped>
  151. .loading-container {
  152. display: flex;
  153. flex-direction: column;
  154. align-items: center;
  155. justify-content: center;
  156. height: 100vh;
  157. background-color: #f8f9fb;
  158. }
  159. .loading-text { font-size: 28rpx; color: #999; }
  160. .error-text { font-size: 28rpx; color: #ff4d4f; padding: 0 40rpx; text-align: center; }
  161. .exam-list-container { padding: 40rpx 30rpx; padding-bottom: 200rpx; min-height: 100vh; background-color: #fff; }
  162. .exam-list-container .title { font-size: 32rpx; font-weight: bold; margin-bottom: 40rpx; text-align: center; }
  163. .exam-item { display: flex; justify-content: space-between; align-items: center; padding: 30rpx; background-color: #f8f9fb; border-radius: 12rpx; margin-bottom: 24rpx; }
  164. .exam-left { display: flex; flex-direction: column; gap: 8rpx; flex: 1; }
  165. .exam-name { font-size: 28rpx; color: #333; }
  166. .exam-status { font-size: 22rpx; }
  167. .exam-status.done { color: #52c41a; }
  168. .start-btn { font-size: 24rpx; color: #fff; background-color: #2b5cff; padding: 10rpx 30rpx; border-radius: 30rpx; flex-shrink: 0; }
  169. .start-btn.retry { background-color: #FF9500; }
  170. .result-footer { position: fixed; bottom: 40rpx; left: 30rpx; right: 30rpx; display: flex; flex-direction: column; align-items: center; gap: 16rpx; }
  171. .report-btn { background-color: #2b5cff; color: #fff; border-radius: 44rpx; font-size: 30rpx; width: 100%; text-align: center; }
  172. .report-btn.disabled { background-color: #ccc; color: #fff; }
  173. .report-hint { font-size: 22rpx; color: #999; text-align: center; }
  174. </style>