liuzhuo 10 horas atrás
pai
commit
ae41479c42

+ 1
- 0
package.json Ver arquivo

@@ -32,6 +32,7 @@
32 32
 		"marked": "^4.3.0",
33 33
 		"pinia": "^2.1.7",
34 34
 		"pinia-plugin-persistedstate": "^3.2.1",
35
+		"qrcode": "^1.5.4",
35 36
 		"qs": "^6.11.2",
36 37
 		"vant": "^4.0.0",
37 38
 		"video.js": "^8.23.4",

+ 118
- 0
src/components/QuizResultPopup.vue Ver arquivo

@@ -0,0 +1,118 @@
1
+
2
+<template>
3
+  <van-popup v-model:show="innerShow" position="top" style="height: 100%">
4
+    <van-sticky>
5
+      <van-nav-bar title="错题回顾" left-arrow @click-left="close" />
6
+    </van-sticky>
7
+
8
+    <div v-if="loading" class="loading-wrapper">
9
+      <van-loading>加载中...</van-loading>
10
+    </div>
11
+
12
+    <div v-else-if="error" class="error">加载失败:{{ error }}</div>
13
+
14
+    <div v-else>
15
+      <div style="margin: 10px 20px; font-weight: bold;">
16
+        本次得分:{{ totalScore }}
17
+      </div>
18
+      <van-divider />
19
+
20
+      <!-- 遍历错题/全部题 -->
21
+      <div v-for="item in mistakeData" :key="item.id" class="question">
22
+        <p>
23
+          <span v-if="item.category === '单选'">[单选]</span>
24
+          <span v-if="item.category === '多选'">[多选]</span>
25
+          <span v-if="item.category === '判断'">[判断]</span>
26
+          {{ item.stem }}
27
+        </p>
28
+
29
+        <p>
30
+          <span :style="{ color: item.userAnswer === item.answer ? '#007aff' : 'red' }">
31
+            提交答案: {{ item.userAnswer || '未作答' }}
32
+          </span>
33
+        </p>
34
+        <p style="color: #007aff">正确答案: {{ item.answer }}</p>
35
+
36
+        <div v-if="item.category === '单选' || item.category === '多选'" class="kong">
37
+          <div>A. {{ item.optionA }}</div>
38
+          <div>B. {{ item.optionB }}</div>
39
+          <div>C. {{ item.optionC }}</div>
40
+          <div v-if="item.optionD">D. {{ item.optionD }}</div>
41
+          <div v-if="item.optionE">E. {{ item.optionE }}</div>
42
+        </div>
43
+        <div v-if="item.category === '判断'" class="kong">
44
+          <div>A. 正确</div>
45
+          <div>B. 错误</div>
46
+        </div>
47
+
48
+        <van-divider />
49
+      </div>
50
+
51
+      <!-- ✅ 新增:底部确定按钮 -->
52
+      <div style="text-align: center; margin: 20px 0;">
53
+        <van-button type="primary" size="large" @click="close">确定</van-button>
54
+      </div>
55
+    </div>
56
+  </van-popup>
57
+</template>
58
+
59
+<script setup>
60
+import { getCurrentInstance, ref, watch } from 'vue';
61
+import { showFailToast } from 'vant';
62
+const { proxy } = getCurrentInstance();
63
+const props = defineProps({
64
+  show: Boolean,
65
+  courseId: String,
66
+  userId: String
67
+});
68
+
69
+const emit = defineEmits(['update:show']);
70
+
71
+const innerShow = ref(false);
72
+const loading = ref(false);
73
+const error = ref('');
74
+const mistakeData = ref([]);
75
+const totalScore = ref(0);
76
+
77
+// 同步外部 show 控制
78
+watch(() => props.show, (val) => {
79
+  innerShow.value = val;
80
+  if (val) {
81
+    loadMistakeData();
82
+  }
83
+});
84
+
85
+const close = () => {
86
+  innerShow.value = false;
87
+  emit('update:show', false);
88
+};
89
+
90
+const loadMistakeData = async () => {
91
+  loading.value = true;
92
+  error.value = '';
93
+  try {
94
+    const query = { headId: props.courseId };
95
+    const res = await proxy.$axios.post('/sgsafe/ExamLine/queryMistake', {
96
+      params: JSON.stringify(query)
97
+    });
98
+
99
+    if (res.data.code === 0) {
100
+      const data = res.data.data || [];
101
+      mistakeData.value = data;
102
+
103
+      // 注意:queryMistake 返回的是每道题的 userAnswer 和 answer
104
+      // 总分通常是 userScore 字段之和
105
+      totalScore.value = data.reduce((sum, item) => sum + (Number(item.userScore) || 0), 0);
106
+    } else {
107
+      error.value = res.data.msg || '获取错题失败';
108
+      showFailToast(error.value);
109
+    }
110
+  } catch (err) {
111
+    console.error('加载错题失败', err);
112
+    error.value = '网络错误';
113
+    showFailToast('加载错题失败');
114
+  } finally {
115
+    loading.value = false;
116
+  }
117
+};
118
+</script>

+ 9
- 2
src/router/index.ts Ver arquivo

@@ -699,11 +699,18 @@ const router = createRouter({
699 699
 			name: '人员学习',
700 700
 			component: () => import('@/view/dati/classOne/learning.vue')
701 701
 		},
702
+		{
703
+			path: '/fcbkdatistart',
704
+			name: '逢查必考跳转答题',
705
+			component: () => import('@/view/dati/examCheck/fcbkdatistart.vue')
706
+		},
702 707
 
703 708
 
704 709
 	]
705 710
 })
706
-
711
+function isInWeCom(): boolean {
712
+	return /wxwork/i.test(navigator.userAgent);
713
+}
707 714
 // 路由守卫
708 715
 router.beforeEach(async (to, from, next) => {
709 716
 
@@ -781,7 +788,7 @@ router.beforeEach(async (to, from, next) => {
781 788
 	}
782 789
 	console.log(to);
783 790
 	if (to.path == '/Home1'||to.path=='/yinhuan/todo_detail/index'|| to.path == '/lz-rcd-detail'||to.path == '/emergencyResources'||to.path == '/cardManager/specialWork'||to.path == '/cardManager/equipment'||to.path == '/cardManager/engineer') {
784
-
791
+		console.log("++++++判断一下是不是企微或者掌上山钢扫码:"+isInWeCom());
785 792
 		/*const token = localStorage.getItem('token');
786 793
 		// 判断是否已经初始化过用户信息
787 794
 		const hasUserInfo = !!localStorage.getItem('userId');

+ 20
- 10
src/view/dati/checkTake/jieguo2.vue Ver arquivo

@@ -116,7 +116,7 @@
116 116
 		proxy
117 117
 	} = getCurrentInstance()
118 118
 	const route = useRoute();
119
-  const userAnswers = ref({});
119
+  const userAnswers = reactive({});
120 120
 
121 121
   const questions = ref([]);
122 122
   //获取试卷
@@ -135,22 +135,32 @@ const confirmResult = () => {
135 135
   showResult.value=false
136 136
 }
137 137
   const getForm = async () => {
138
-    var url = '/sgsafe/ExamLine/query'
139
-    const query = ref({
140
-      headId: courseId
141
-    })
142
-    var param = {
143
-      params: JSON.stringify(query.value)
144
-    }
138
+    const url = '/sgsafe/ExamLine/query';
139
+    const query = {
140
+      headId: courseId.value // ✅ 不需要用 ref 包裹临时对象
141
+    };
142
+    const param = {
143
+      params: JSON.stringify(query)
144
+    };
145 145
     try {
146 146
       const res = await proxy.$axios.post(url, param);
147 147
       if (res.data.code === 0) {
148
-        questions.value = res.data.data
148
+        questions.value = res.data.data;
149
+
150
+        // ✅ 修复点2:清空 reactive 对象(不能用 .value)
151
+        for (const key in userAnswers) {
152
+          delete userAnswers[key];
153
+        }
154
+
155
+        // ✅ 修复点3:直接赋值,不要 .value!
156
+        res.data.data.forEach(q => {
157
+          userAnswers[q.id] = q.userAnswer; // 假设后端字段名为 userAnswer
158
+        });
149 159
       } else {
150 160
         console.log('操作失败!' + res.data.msg);
151 161
       }
152 162
     } catch (error) {
153
-      console.log('请求出错:', questions);
163
+      console.log('请求出错:', error);
154 164
     }
155 165
   };
156 166
 

+ 1
- 1
src/view/dati/classOne/line.vue Ver arquivo

@@ -212,7 +212,7 @@ const route = useRoute();
212 212
 
213 213
 const courseId = route.query.courseId;
214 214
 const userId = route.query.userId;
215
-if (userId == '' || userId == 'undefined') userId = localStorage.getItem('userId')
215
+
216 216
 const todayStr = route.query.todayStr;
217 217
 
218 218
 const questions = ref([]);

+ 49
- 5
src/view/dati/classOne/sectionList.vue Ver arquivo

@@ -54,27 +54,34 @@
54 54
             </template>
55 55
 
56 56
             <template #right>
57
-
58 57
               <div style="display: flex; align-items: center; justify-content: flex-end; height: 100%;">
59 58
                 <van-button v-if="item.studyorExam =='study'" @click="goaddLearn(item)" class="red-rounded-box-wide" text="学习"/>
59
+                <van-button
60
+                  v-if="item.isFinish === '1'"
61
+                  @click="openWrongRecord(item)"
62
+                  class="red-rounded-box-wide"
63
+                  text="错题集"
64
+                />
60 65
                 <van-button v-else-if="item.studyorExam =='exam'"  @click="goaddPeo(item)" class="red-rounded-box-wide" text="考试"/>
61
-
62 66
               </div>
63
-
64 67
             </template>
65 68
           </van-swipe-cell>
66 69
         </div>
67 70
 
68 71
       </van-list>
69 72
     </van-pull-refresh>
70
-
73
+    <QuizResultPopup
74
+      v-model:show="showWrongPopup"
75
+      :course-id="selectedCourseId"
76
+      :user-id="userId"
77
+    />
71 78
   </div>
72 79
 </template>
73 80
 
74 81
 <script setup>
75 82
 import { ref, reactive, onMounted, getCurrentInstance, nextTick, toRaw } from 'vue';
76 83
 import { Dialog, showDialog, showSuccessToast, showToast, Toast } from 'vant';
77
-
84
+import QuizResultPopup from '@/components/QuizResultPopup.vue'; // 路径按你实际的改
78 85
 const { proxy } = getCurrentInstance();
79 86
 
80 87
 
@@ -243,6 +250,43 @@ const goaddLearn =async (item) => {
243 250
     }
244 251
   })
245 252
 }
253
+
254
+const showWrongPopup = ref(false);
255
+const selectedCourseId = ref('');
256
+const openWrongRecord = (item) => {
257
+  selectedCourseId.value = item.id; // 或 item.courseId,看你字段名
258
+  showWrongPopup.value = true;
259
+};
260
+
261
+const openWrongQuestions = async (item) => {
262
+  // 1. 先获取该小节的所有题目(和你 goaddPeo 里 prepareQuizData 一样)
263
+  await prepareQuizData(item.id);
264
+
265
+  // 2. 模拟或获取用户的答题记录(你可能需要调另一个接口)
266
+  //    假设你已经有 userAnswers 数据结构,或者可以从本地/接口获取
267
+  //    这里我们假设有一个 getUserAnswers(sectionId) 方法
268
+
269
+  const userAnswers = await getUserAnswers(item.id); // ← 你需要实现这个!
270
+
271
+  // 3. 把题目 + 用户答案 + 得分等传给弹窗
272
+  currentSectionWrongData.value = {
273
+    questions: questionData.value,
274
+    userAnswers: userAnswers,
275
+    totalScore: calculateScore(questionData.value, userAnswers), // 你自己的评分逻辑
276
+    sectionName: item.sectionName
277
+  };
278
+
279
+  // 4. 显示弹窗
280
+  showWrongQuestionsPopup.value = true;
281
+};
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
246 290
 const  questionData=ref([])
247 291
 const prepareQuizData = async (sectionId) => {
248 292
 

+ 725
- 0
src/view/dati/examCheck/fcbkdatistart.vue Ver arquivo

@@ -0,0 +1,725 @@
1
+<template>
2
+  <van-sticky>
3
+    <van-nav-bar>
4
+      <!-- <template #left>
5
+        <van-icon name="arrow-left" size="18" @click="goBack" />
6
+      </template> -->
7
+      <template #title> 答题考试 </template>
8
+      <template #right>
9
+        <!-- 提交按钮 -->
10
+        <van-button @click="checkBeforeSubmit" round color="linear-gradient(to right, #18FFFF, #304FFE)"
11
+                    style="height: 30px; width: 80px">交卷</van-button>
12
+      </template>
13
+    </van-nav-bar>
14
+  </van-sticky>
15
+  <van-overlay :show="overlayloading">
16
+    <div class="wrapper">
17
+      <van-loading color="#0094ff"> 加载中... </van-loading>
18
+    </div>
19
+  </van-overlay>
20
+
21
+  <div class="quiz-page">
22
+    <div class="question-number">
23
+      {{ activeIndex + 1 }}/{{ questions.length }}
24
+    </div>
25
+    <div v-if="questions.length > 0" class="question-content">
26
+      <!-- 题干 -->
27
+      <p class="kong">
28
+        <img
29
+          :src="getQuestionTypeImage(currentQuestion.category)"
30
+          class="question-type-img"
31
+          alt=""
32
+        />
33
+        {{ currentQuestion.stem }}
34
+      </p>
35
+
36
+
37
+      <!-- 单选题 -->
38
+      <div v-if="currentQuestion.category === '单选'">
39
+        <van-radio-group v-model="userAnswers[currentQuestion.id]">
40
+          <van-radio :name="'A'" class="kong">A.{{ currentQuestion.optionA }}</van-radio>
41
+          <van-radio :name="'B'" class="kong">B.{{ currentQuestion.optionB }}</van-radio>
42
+          <van-radio :name="'C'" class="kong">C.{{ currentQuestion.optionC }}</van-radio>
43
+          <van-radio :name="'D'" class="kong" v-if="currentQuestion.optionD">D.
44
+            {{ currentQuestion.optionD }}</van-radio>
45
+          <van-radio :name="'E'" class="kong" v-if="currentQuestion.optionE">E.
46
+            {{ currentQuestion.optionE }}</van-radio>
47
+        </van-radio-group>
48
+      </div>
49
+
50
+      <!-- 多选题 -->
51
+      <div v-if="currentQuestion.category === '多选'">
52
+        <van-checkbox-group v-model="userAnswers[currentQuestion.id]" shape="square">
53
+          <van-checkbox :name="'A'" class="kong">A.{{ currentQuestion.optionA }}</van-checkbox>
54
+          <van-checkbox :name="'B'" class="kong">B.{{ currentQuestion.optionB }}</van-checkbox>
55
+          <van-checkbox :name="'C'" class="kong">C.{{ currentQuestion.optionC }}</van-checkbox>
56
+          <van-checkbox :name="'D'" class="kong"
57
+                        v-if="currentQuestion.optionD">D.{{ currentQuestion.optionD }}</van-checkbox>
58
+          <van-checkbox :name="'E'" class="kong"
59
+                        v-if="currentQuestion.optionE">E.{{ currentQuestion.optionE }}</van-checkbox>
60
+        </van-checkbox-group>
61
+      </div>
62
+
63
+      <!-- 判断题 -->
64
+      <div v-if="currentQuestion.category === '判断'">
65
+        <van-radio-group v-model="userAnswers[currentQuestion.id]">
66
+          <van-radio :name="'A'" class="kong">A.正确</van-radio>
67
+          <van-radio :name="'B'" class="kong">B.错误</van-radio>
68
+        </van-radio-group>
69
+      </div>
70
+    </div>
71
+
72
+    <!-- 底部固定栏 -->
73
+    <div class="footer">
74
+      <van-button @click="prevQuestion" :disabled="activeIndex === 0"
75
+                  style="height: 40px; width: 45%">上一题</van-button>
76
+      <van-button @click="nextQuestion" :disabled="activeIndex === questions.length - 1" style="
77
+          height: 40px;
78
+          width: 45%;
79
+          background-color: var(--van-radio-checked-icon-color);
80
+          border-color: var(--van-radio-checked-icon-color);
81
+          color: #fff;
82
+        ">下一题</van-button>
83
+    </div>
84
+
85
+    <!-- 提交前确认弹窗 -->
86
+    <van-dialog v-model:show="confirmSubmitDialog" title="确认交卷" show-cancel-button @confirm="submitForm">
87
+      <p :class="{ 'van-dialog__message': true, 'text-center': true }">
88
+        <span v-if="hasUnanswered">{{ unansweredText }}</span>
89
+        <span v-else>{{ completedText }}</span>
90
+      </p>
91
+    </van-dialog>
92
+
93
+    <!-- 结果弹窗 -->
94
+    <van-popup v-model:show="showResult" position="top" style="height: 100%">
95
+      <van-sticky>
96
+        <van-nav-bar title="答题结果" />
97
+      </van-sticky>
98
+      <div style="
99
+          margin-top: 10px;
100
+          margin-left: 20px;
101
+          margin-bottom: 20px;
102
+          font-weight: bold;
103
+        ">
104
+        本次得分:{{ totalScore }}
105
+        <!--取接口成绩-->
106
+      </div>
107
+      <van-divider />
108
+      <!-- 题干 -->
109
+      <div v-for="question in questions" :key="question.id" class="question">
110
+        <p>
111
+          <span v-if="question.category === '单选'">[单选]</span>
112
+          <span v-if="question.category === '多选'">[多选]</span>
113
+          <span v-if="question.category === '判断'">[判断]</span>
114
+          {{ question.stem }}
115
+        </p>
116
+        <!-- 显示提交答案 -->
117
+        <p>
118
+					<span :style="{
119
+              color: Array.isArray(userAnswers[question.id])
120
+                ? userAnswers[question.id].sort().join('') ===
121
+                  question.answer
122
+                  ? '#007aff'
123
+                  : 'red'
124
+                : userAnswers[question.id] === question.answer
125
+                ? '#007aff'
126
+                : 'red',
127
+            }">
128
+						提交答案:{{
129
+              Array.isArray(userAnswers[question.id])
130
+                ? userAnswers[question.id].sort().join("")
131
+                : userAnswers[question.id] || "未作答"
132
+            }}
133
+					</span>
134
+        </p>
135
+        <!-- 显示正确答案 -->
136
+        <p style="color: #007aff">正确答案:{{ question.answer }}</p>
137
+        <div v-if="question.category === '单选'" class="kong">
138
+          <div>A. {{ question.optionA }}</div>
139
+          <div>B. {{ question.optionB }}</div>
140
+          <div>C. {{ question.optionC }}</div>
141
+          <div v-if="question.optionD">D. {{ question.optionD }}</div>
142
+          <div v-if="question.optionE">E. {{ question.optionE }}</div>
143
+        </div>
144
+        <div v-if="question.category === '多选'" class="kong">
145
+          <div>A. {{ question.optionA }}</div>
146
+          <div>B. {{ question.optionB }}</div>
147
+          <div>C. {{ question.optionC }}</div>
148
+          <div v-if="question.optionD">D. {{ question.optionD }}</div>
149
+          <div v-if="question.optionE">E. {{ question.optionE }}</div>
150
+        </div>
151
+        <div v-if="question.category === '判断'" class="kong">
152
+          <div>A.正确</div>
153
+          <div>B.错误</div>
154
+        </div>
155
+
156
+        <!-- AI解析按钮和内容 -->
157
+        <div style="margin: 10px 0;">
158
+          <van-button
159
+            type="primary"
160
+            size="small"
161
+            :loading="analysisLoading[question.id]"
162
+            @click="generateAIAnalysis(question,true)"
163
+          >
164
+            {{ aiAnalysis[question.id] ? '重新解析' : 'AI解析' }}
165
+          </van-button>
166
+
167
+          <!-- 显示AI解析内容 -->
168
+          <div v-if="aiAnalysis[question.id]" class="ai-analysis-content">
169
+            <div v-html="renderAnalysis(aiAnalysis[question.id])"></div>
170
+          </div>
171
+        </div>
172
+
173
+        <van-divider />
174
+      </div>
175
+      <div style="margin-top: 20px; text-align: center; margin-bottom: 20px">
176
+        <van-button class="questionBtn" type="primary" @click="confirmResult">确定</van-button>
177
+      </div>
178
+    </van-popup>
179
+  </div>
180
+</template>
181
+
182
+<script setup>
183
+import {
184
+  onMounted,
185
+  ref,
186
+  getCurrentInstance,
187
+  computed
188
+} from "vue";
189
+import {
190
+  showConfirmDialog,
191
+  showSuccessToast,
192
+  showFailToast,
193
+  showDialog
194
+} from 'vant';
195
+import {
196
+  useRouter,
197
+  useRoute
198
+} from "vue-router";
199
+import {
200
+  examResult,
201
+  formNew,
202
+  myDefaultCourse,
203
+  saveScore,
204
+  sortData,
205
+} from "@/api/dati";
206
+const {
207
+  proxy
208
+} = getCurrentInstance()
209
+
210
+const router = useRouter();
211
+const route = useRoute();
212
+const query = route.query;
213
+
214
+// 1. 从本地存储获取当前用户的 userCode(字符串)
215
+const currentUserCode = localStorage.getItem('userCode');
216
+
217
+// 2. 包装成数组(即使为空也安全)
218
+const userCodeArray = currentUserCode ? [currentUserCode] : [];
219
+
220
+// 3. 按原来的方式生成 userDesc 字符串
221
+const userDesc = userCodeArray.join(',');
222
+const getExamContext = () => ({
223
+  id: route.query.examId || '',
224
+  testRole: route.query.testRole || '',
225
+  checkTime: route.query.checkTime || '',
226
+  checkName: route.query.checkName || '',
227
+  testType: route.query.testType || '',
228
+  addId: route.query.addId || '',
229
+  userDesc: userDesc,
230
+
231
+});
232
+
233
+const   userId = localStorage.getItem('userId');
234
+const questions = ref([]);
235
+
236
+const userAnswers = ref({});
237
+const activeIndex = ref(0);
238
+const totalScore = ref(0);
239
+const showResult = ref(false);
240
+
241
+const confirmSubmitDialog = ref(false);
242
+const hasUnanswered = ref(false);
243
+const unansweredText = "有题目未完成,是否确认交卷?";
244
+const completedText = "已完成所有题目,是否确认交卷?";
245
+const overlayloading = ref(false);
246
+// 在组件挂载时获取试卷
247
+onMounted(async () => {
248
+  overlayloading.value = true;
249
+  await saveChecUser();
250
+  await getForm();
251
+  overlayloading.value = false;
252
+});
253
+const handData = ref({});
254
+
255
+const saveChecUser = async () => {
256
+  const examContext = getExamContext();
257
+  console.log("接到的参数:",examContext);
258
+  var url = '/sgsafe/ExamHead/saveChecUser';
259
+  var param = {
260
+    params: JSON.stringify(examContext)
261
+  };
262
+  const res = await proxy.$axios.post(url, param);
263
+    if (res.data.code == '0') {
264
+    handData.value =  res.data.data;
265
+    } else {
266
+      showFailToast('操作失败!' + res.data.msg)
267
+    }
268
+
269
+
270
+}
271
+//获取试卷
272
+const getForm = async () => {
273
+  console.log("headId为",handData.value.id);
274
+  var url = '/sgsafe/ExamLine/query'
275
+  const query1 = ref({
276
+    headId:   handData.value.id
277
+  })
278
+  var param = {
279
+    params: JSON.stringify(query1.value)
280
+  }
281
+  try {
282
+    const res = await proxy.$axios.post(url, param);
283
+    if (res.data.code === 0) {
284
+      questions.value = res.data.data
285
+    } else {
286
+      console.log('操作失败!' + res.data.msg);
287
+    }
288
+  } catch (error) {
289
+    console.log('请求出错:', questions);
290
+  }
291
+};
292
+
293
+// 获取当前题目
294
+const currentQuestion = computed(() => {
295
+  return questions.value[activeIndex.value];
296
+});
297
+
298
+// 获取题目类型对应的图片路径
299
+import danxuan from '@/assets/img/dx.svg'
300
+import duoxuanImg from '@/assets/img/ksdx.svg'
301
+import panduanImg from '@/assets/img/kspd.svg'
302
+const getQuestionTypeImage = (category) => {
303
+  switch (category) {
304
+    case "单选": // 单选
305
+      return danxuan;
306
+    case "多选": // 多选
307
+      return duoxuanImg;
308
+    case "判断": // 判断
309
+      return panduanImg;
310
+    default:
311
+      return "";
312
+  }
313
+};
314
+
315
+//返回答题首页
316
+const goBack = () => {
317
+  router.push({
318
+    path: "/dailyproblem"
319
+  });
320
+};
321
+
322
+// 切换到下一题
323
+const nextQuestion = () => {
324
+  if (activeIndex.value < questions.value.length - 1) {
325
+    activeIndex.value++;
326
+  }
327
+};
328
+
329
+// 切换到上一题
330
+const prevQuestion = () => {
331
+  if (activeIndex.value > 0) {
332
+    activeIndex.value--;
333
+  }
334
+};
335
+
336
+
337
+const getUserAnswers = () => {
338
+  let useranswers = [];
339
+  questions.value.forEach((question) => {
340
+    const userAnswer = userAnswers.value[question.id]; // 获取用户的答案
341
+    let userAnswerString;
342
+    if (Array.isArray(userAnswer)) {
343
+      // 多选题,将数组转换为字符串
344
+      userAnswerString = userAnswer.sort().join(""); // 排序并转换为字符串,如 "ABC"
345
+    } else {
346
+      // 单选题,直接是字符串
347
+      userAnswerString = userAnswer || ""; // 如果未选择答案,则设为空字符串
348
+    }
349
+    // 将答案保存到 answers 数组中
350
+    useranswers.push({
351
+      id: question.id, // 题目 ID
352
+      userAnswer: userAnswerString, // 用户的答案
353
+    });
354
+  });
355
+  return useranswers;
356
+};
357
+
358
+//交卷
359
+const submitForm = async () => {
360
+  overlayloading.value = true;
361
+  try {
362
+    let answers = getUserAnswers();
363
+    //console.log('answers.value', answers)
364
+    var url = '/sgsafe/ExamLine/appSaveMyScore'
365
+    var param = {
366
+      json: JSON.stringify(
367
+        answers
368
+      )
369
+    }
370
+    try {
371
+      const res = await proxy.$axios.post(url, param);
372
+      if (res.data.code === 0) {
373
+        showSuccessToast("保存成功")
374
+      } else {
375
+        console.log('操作失败!' + res.data.msg);
376
+      }
377
+    } catch (error) {
378
+      console.log('请求出错:', questions);
379
+    }
380
+    const courseId = handData.value.id;
381
+    const today = new Date();
382
+    const year = today.getFullYear();
383
+    const month = String(today.getMonth() + 1).padStart(2, '0');
384
+    const day = String(today.getDate()).padStart(2, '0');
385
+    const todayStr = `${year}-${month}-${day}`;// ✅ 来自 saveChecUser 返回
386
+    //开始判卷
387
+    var url2 = '/sgsafe/Package/doProc'
388
+    var param2 = {
389
+      procName: 'safeplat.sxsp_grade_exam_p',
390
+      param: JSON.stringify([courseId])
391
+    }
392
+    try {
393
+      const res2 = await proxy.$axios.post(url2, param2);
394
+      if (res2.data.code === 0) {
395
+        consle.log("courseId:" + courseId + "判卷完成!")
396
+      } else {
397
+        console.log('操作失败!' + res.data.msg);
398
+      }
399
+    } catch (error) {
400
+      console.log('请求出错:', questions);
401
+    }
402
+
403
+    overlayloading.value = false;
404
+
405
+    //查询本日答题次数和分数
406
+    var url3 = '/sgsafe/DailyExam/appQueryMyScore'
407
+    const query3 = ref({
408
+      userId: userId,
409
+      examDate: todayStr,
410
+      headId: courseId,
411
+    })
412
+    var param3 = {
413
+      params: JSON.stringify(query3.value)
414
+    }
415
+    const res3 = await proxy.$axios.post(url3, param3);
416
+    if (res3.data.code === 0) {
417
+      console.log(res3.data)
418
+      if (res3.data.data.dailyExamList.length==0){
419
+        var url4='/sgsafe/ExamHead/queryByheadId'
420
+        var param4 = {
421
+          headId: courseId
422
+        }
423
+        const res4 = await proxy.$axios.post(url4, param4);
424
+        if (res4.data.code === 0) {
425
+          totalScore.value = res4.data.data.totalScore;
426
+        }
427
+        showConfirmDialog({
428
+          message: "判卷完成",
429
+          confirmButtonText: "查看本次答题结果",
430
+          cancelButtonText: "退出答题"
431
+        })
432
+          .then(() => {
433
+            showResult.value = true;
434
+          })
435
+          .catch(() => {
436
+
437
+            router.replace({
438
+              path: "/dailyproblem"
439
+            });
440
+          })
441
+      }else {
442
+        let times = res3.data.data.dailyExamList[0].examCounts;
443
+        totalScore.value = res3.data.data.headScore;
444
+        showConfirmDialog({
445
+          message: "判卷完成",
446
+          confirmButtonText: "查看本次答题结果",
447
+          cancelButtonText: times == "3" ? "退出答题" : "继续答题",
448
+        })
449
+          .then(() => {
450
+            showResult.value = true;
451
+          })
452
+          .catch(() => {
453
+            if (times == "3") {
454
+              router.replace({
455
+                path: "/dailyproblem"
456
+              });
457
+            } else {
458
+              router.push({
459
+                path: "/dailyproblem"
460
+              });
461
+            }
462
+          });
463
+      }
464
+
465
+
466
+    } else {
467
+      console.log('操作失败!' + res3.data.msg);
468
+    }
469
+
470
+  } catch (error) {
471
+    console.error("出错:", error);
472
+    showFailToast("交卷失败");
473
+  }
474
+};
475
+
476
+// 确认结果并返回
477
+const confirmResult = () => {
478
+  showResult.value = false;
479
+  // router.back();
480
+  router.back()
481
+};
482
+
483
+// 检查是否所有题目都已作答
484
+const checkBeforeSubmit = () => {
485
+  hasUnanswered.value = questions.value.some((question) => {
486
+    const userAnswer = userAnswers.value[question.id];
487
+    return (
488
+      !userAnswer || (Array.isArray(userAnswer) && userAnswer.length === 0)
489
+    );
490
+  });
491
+  confirmSubmitDialog.value = true;
492
+};
493
+
494
+// AI解析功能
495
+// AI解析相关变量
496
+const aiAnalysis = ref({}); // 存储每道题的AI解析内容
497
+const analysisLoading = ref({}); // 存储每道题的解析加载状态
498
+import { fetchHuaweiResponse } from "@/tools/deepseek.js";
499
+// 动态导入依赖
500
+let marked, DOMPurify;
501
+
502
+const initMarkdownLibs = async () => {
503
+  try {
504
+    // 尝试导入marked
505
+    const markedModule = await import('marked');
506
+    marked = markedModule.marked || markedModule.default || markedModule;
507
+
508
+    // 尝试导入DOMPurify
509
+    const dompurifyModule = await import('dompurify');
510
+    DOMPurify = dompurifyModule.default || dompurifyModule;
511
+  } catch (error) {
512
+    console.warn('Markdown libraries not available, using plain text', error);
513
+    // 如果导入失败,使用基础功能
514
+    marked = {
515
+      parse: (text) => text
516
+    };
517
+    DOMPurify = {
518
+      sanitize: (html) => html
519
+    };
520
+  }
521
+};
522
+
523
+// 在组件挂载时初始化
524
+onMounted(() => {
525
+  initMarkdownLibs();
526
+});
527
+
528
+// 生成AI解析
529
+const generateAIAnalysis = async (question, force = false) => {
530
+  // 如果该题已有解析且不是强制重新生成,直接返回
531
+  if (aiAnalysis.value[question.id] && !force) {
532
+    return;
533
+  }
534
+
535
+  // 如果是重新解析,先清空之前的内容
536
+  if (force) {
537
+    aiAnalysis.value[question.id] = '';
538
+  }
539
+
540
+  // 确保依赖已加载
541
+  if (!marked || !DOMPurify) {
542
+    await initMarkdownLibs();
543
+  }
544
+
545
+  // 设置加载状态
546
+  analysisLoading.value[question.id] = true;
547
+
548
+  try {
549
+    // 构造提示词
550
+    let prompt = `请为以下题目提供详细解析:
551
+题目类型:${question.category}题干:${question.stem}`;
552
+
553
+    // 添加选项
554
+    if (question.optionA) prompt += `\nA. ${question.optionA}`;
555
+    if (question.optionB) prompt += `\nB. ${question.optionB}`;
556
+    if (question.optionC) prompt += `\nC. ${question.optionC}`;
557
+    if (question.optionD) prompt += `\nD. ${question.optionD}`;
558
+    if (question.optionE) prompt += `\nE. ${question.optionE}`;
559
+
560
+    prompt += `\n正确答案:${question.answer}`;
561
+
562
+    // 添加用户答案(如果已作答)
563
+    const userAnswer = userAnswers.value[question.id];
564
+    if (userAnswer) {
565
+      const userAnswerString = Array.isArray(userAnswer)
566
+        ? userAnswer.sort().join("")
567
+        : userAnswer;
568
+      prompt += `\n用户答案:${userAnswerString}`;
569
+    }
570
+
571
+    prompt += `\n\n请提供以下内容:
572
+1. 正确答案的解释
573
+2. 为什么其他选项不正确(如果用户答案错误)
574
+3. 相关知识点说明`;
575
+
576
+    // 构造消息对象
577
+    const messages = [
578
+      {
579
+        role: "user",
580
+        content: prompt
581
+      }
582
+    ];
583
+
584
+    // 调用AI接口
585
+    fetchHuaweiResponse(
586
+      messages,
587
+      (content, isThinking, isEnd) => {
588
+        // 实时更新解析内容
589
+        aiAnalysis.value[question.id] = content;
590
+
591
+        // 如果是最终结果,停止加载状态
592
+        if (isEnd) {
593
+          analysisLoading.value[question.id] = false;
594
+        }
595
+      },
596
+      null
597
+    );
598
+  } catch (error) {
599
+    console.error('AI解析生成失败:', error);
600
+    analysisLoading.value[question.id] = false;
601
+    aiAnalysis.value[question.id] = '解析生成失败';
602
+  }
603
+};
604
+
605
+// 解析内容转换为HTML
606
+const renderAnalysis = (content) => {
607
+  if (!content) return '';
608
+
609
+  try {
610
+    // 确保依赖已加载
611
+    if (!marked || !DOMPurify) {
612
+      return content.replace(/\n/g, '<br>');
613
+    }
614
+
615
+    const html = marked.parse ? marked.parse(content) : marked(content);
616
+    return DOMPurify.sanitize ? DOMPurify.sanitize(html) : html;
617
+  } catch (error) {
618
+    console.error('Markdown解析错误:', error);
619
+    return content.replace(/\n/g, '<br>');
620
+  }
621
+};
622
+
623
+
624
+
625
+
626
+</script>
627
+
628
+<style scoped>
629
+.quiz-page {
630
+  padding: 20px;
631
+}
632
+
633
+.question-type-img {
634
+  width: 54px;
635
+  height: 20px;
636
+}
637
+
638
+.question {
639
+  margin-left: 20px;
640
+  margin-right: 20px;
641
+}
642
+
643
+.kong {
644
+  margin-bottom: 20px;
645
+}
646
+
647
+.footer {
648
+  position: fixed;
649
+  bottom: 0;
650
+  left: 0;
651
+  width: 100%;
652
+  background-color: #fff;
653
+  display: flex;
654
+  justify-content: space-around;
655
+  align-items: center;
656
+  margin-bottom: 10px;
657
+}
658
+
659
+.van-dialog__message {
660
+  text-align: center;
661
+}
662
+
663
+.questionBtn {
664
+  width: 40%;
665
+}
666
+
667
+/* 遮罩 */
668
+.wrapper {
669
+  display: flex;
670
+  align-items: center;
671
+  justify-content: center;
672
+  height: 100%;
673
+}
674
+
675
+.van-overlay {
676
+  z-index: 2;
677
+  background-color: rgba(0, 0, 0, 0.5);
678
+}
679
+
680
+.ai-analysis-content {
681
+  margin-top: 10px;
682
+  padding: 10px;
683
+  background-color: #f5f5f5;
684
+  border-radius: 4px;
685
+  font-size: 14px;
686
+  line-height: 1.6;
687
+}
688
+
689
+.ai-analysis-content :deep(h1),
690
+.ai-analysis-content :deep(h2),
691
+.ai-analysis-content :deep(h3) {
692
+  margin: 10px 0;
693
+  font-weight: bold;
694
+  font-size: 16px;
695
+}
696
+
697
+.ai-analysis-content :deep(p) {
698
+  margin: 8px 0;
699
+}
700
+
701
+.ai-analysis-content :deep(ul),
702
+.ai-analysis-content :deep ol {
703
+  padding-left: 20px;
704
+  margin: 8px 0;
705
+}
706
+
707
+.ai-analysis-content :deep(li) {
708
+  margin: 4px 0;
709
+}
710
+
711
+.ai-analysis-content :deep(code) {
712
+  padding: 2px 4px;
713
+  background-color: #e0e0e0;
714
+  border-radius: 3px;
715
+  font-family: monospace;
716
+}
717
+
718
+.ai-analysis-content :deep(pre) {
719
+  padding: 10px;
720
+  background-color: #e0e0e0;
721
+  border-radius: 4px;
722
+  overflow-x: auto;
723
+}
724
+
725
+</style>

+ 77
- 9
src/view/dati/examCheck/index.vue Ver arquivo

@@ -55,7 +55,19 @@
55 55
       </template>
56 56
       <div style="padding: 30px;">确定要删除该项目吗?</div>
57 57
     </van-dialog>
58
-
58
+    <!-- 二维码弹窗 -->
59
+    <van-dialog
60
+      v-model:show="qrDialogVisible"
61
+      title="扫码添加人员"
62
+      show-cancel-button
63
+      close-on-popstate
64
+      @cancel="qrDialogVisible = false"
65
+    >
66
+      <div class="qr-dialog-content">
67
+        <img v-if="qrDataUrl" :src="qrDataUrl" alt="二维码" class="qr-code" />
68
+        <p class="qr-tip">请使用手机扫码 App 扫描上方二维码</p>
69
+      </div>
70
+    </van-dialog>
59 71
   </div>
60 72
 </template>
61 73
 
@@ -65,6 +77,15 @@ import { Dialog, showDialog, showSuccessToast, showToast, Toast } from 'vant';
65 77
 
66 78
 const { proxy } = getCurrentInstance();
67 79
 
80
+//生成二维码
81
+import QRCode from 'qrcode';
82
+
83
+// 新增状态
84
+const qrDialogVisible = ref(false);
85
+const qrDataUrl = ref('');
86
+const currentExamItem = ref(null); // 用于临时保存当前 item
87
+
88
+
68 89
 const onClickLeft = () => {
69 90
   history.back();
70 91
 };
@@ -124,14 +145,39 @@ const handAdd =  () => {
124 145
     } });
125 146
 
126 147
 };
127
-const goaddPeo = (item) => {
128
-  router.push({
129
-    path: '/addPeo',
130
-    query: {
131
-      data: JSON.stringify(item)
132
-    }
133
-  })
134
-}
148
+import.meta.env.VITE_BASE_API
149
+const goaddPeo = async (item) => {
150
+  currentExamItem.value = item;
151
+
152
+  //
153
+  const baseUrl = window.location.origin + '/sgsafeh5/fcbkdatistart';
154
+
155
+  const url = new URL(baseUrl);
156
+  url.searchParams.set('examId', item.id);           // 考试ID
157
+  url.searchParams.set('testRole', item.testRole); //规则id
158
+  url.searchParams.set('checkTime', item.checkTime); //考试时间
159
+  url.searchParams.set('checkName', item.checkName || '');
160
+  url.searchParams.set('testType', item.testType || '');//考试类型
161
+
162
+   url.searchParams.set('addId', localStorage.getItem('userId') || '');
163
+
164
+  const qrContent = url.toString();
165
+
166
+  try {
167
+    qrDataUrl.value = await QRCode.toDataURL(qrContent, {
168
+      width: 240,
169
+      margin: 2,
170
+      color: {
171
+        dark: '#000000',
172
+        light: '#ffffff'
173
+      }
174
+    });
175
+    qrDialogVisible.value = true;
176
+  } catch (err) {
177
+    console.error('二维码生成失败:', err);
178
+    Toast('二维码生成失败,请重试');
179
+  }
180
+};
135 181
 
136 182
 const edits = (row) => {
137 183
   kz.value = true;
@@ -845,4 +891,26 @@ const closeSwipe = (idx) => {
845 891
   background-size: auto 100%;
846 892
   background-repeat: no-repeat;
847 893
 }
894
+.qr-dialog-content {
895
+  display: flex;
896
+  flex-direction: column;
897
+  align-items: center;
898
+  padding: 20px 0;
899
+}
900
+
901
+.qr-code {
902
+  width: 240px;
903
+  height: 240px;
904
+  background: #fff;
905
+  padding: 8px;
906
+  border-radius: 8px;
907
+  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
908
+}
909
+
910
+.qr-tip {
911
+  margin-top: 12px;
912
+  font-size: 13px;
913
+  color: #666;
914
+  text-align: center;
915
+}
848 916
 </style>

Carregando…
Cancelar
Salvar