2 次代碼提交 ddff947b03 ... a70a62e749

作者 SHA1 備註 提交日期
  闪电 a70a62e749 mod: 合并冲突 2 周之前
  闪电 e7872bf2a2 fix: 试题增加引用知识库 2 周之前

+ 3 - 0
apps/web-ele/.env.development

@@ -22,3 +22,6 @@ VITE_DEVTOOLS=false
22 22
 
23 23
 # 是否注入全局loading
24 24
 VITE_INJECT_APP_LOADING=true
25
+
26
+# 移动端考试分享地址
27
+VITE_MOBILE_EXAM_URL=http://localhost:9000/%23/pages/knowledge/exam/start?id={id}

+ 3 - 0
apps/web-ele/.env.production

@@ -18,3 +18,6 @@ VITE_INJECT_APP_LOADING=true
18 18
 
19 19
 # 打包后是否生成dist.zip
20 20
 VITE_ARCHIVER=true
21
+
22
+# 移动端考试分享地址
23
+VITE_MOBILE_EXAM_URL=http://39.164.159.226:8087/%23/pages/knowledge/exam/start?id={id}

+ 160 - 59
apps/web-ele/src/components/knowledgemain/index.vue

@@ -1,19 +1,35 @@
1 1
 <script lang="ts" setup>
2
-//@ts-ignore
3
-import { computed, onMounted, ref, defineProps } from 'vue';
4
-import { Clock, DArrowLeft, Expand, HomeFilled } from '@element-plus/icons-vue';
5
-import { ElIcon, ElTree } from 'element-plus';
2
+// @ts-ignore
3
+import { computed, defineProps, onMounted, ref } from 'vue';
4
+
5
+import { Clock, Expand, HomeFilled } from '@element-plus/icons-vue';
6
+import { ElIcon, ElMessage, ElTree } from 'element-plus';
7
+
8
+import { queryKnowledgeCategory } from '#/api/knowledge/category';
6 9
 import {
7 10
   getKnowledgeContentDetail,
8 11
   queryKnowledgeContentlist,
9 12
 } from '#/api/knowledge/content';
10
-import { queryKnowledgeCategoryDetail } from '#/api/knowledge/category';
13
+
11 14
 const props = defineProps({
12
-  classId: {
13
-    type: String,
14
-    default: '',
15
+  // classId: {
16
+  //   type: String,
17
+  //   default: '',
18
+  // },
19
+  data: {
20
+    type: Object,
21
+    default: () => ({}),
15 22
   },
16 23
 });
24
+
25
+const selectedCategoryInfo = ref({
26
+  categoryId: '',
27
+  categoryName: '',
28
+  contentId: '',
29
+  contentPath: '', // 分类路径,例如:1/2/3
30
+  contentNamePath: '', // 分类名称路径,例如:分类1/分类2/分类3
31
+}) as any;
32
+const selectedCategoryId = ref(props.data?.categoryId || '');
17 33
 // 菜单折叠状态
18 34
 const isCollapse = ref(false);
19 35
 // 章节数据
@@ -24,36 +40,62 @@ const isEdit = ref(false);
24 40
 const ChapterId = ref(null);
25 41
 const KnowledgeDetails = ref({}) as any;
26 42
 // 查询章节数据
27
-const chapterDatafn = async () => {
43
+const chapterDatafn = async ({ initContentId }: any) => {
28 44
   chapterDataloading.value = true;
45
+  // 初始化选择数据
46
+  selectedCategoryInfo.value = {
47
+    categoryId: '',
48
+    categoryName: '',
49
+    contentId: '',
50
+    contentPath: '', // 分类路径,例如:1/2/3
51
+    contentNamePath: '', // 分类名称路径,例如:分类1/分类2/分类3
52
+  };
29 53
   try {
30
-    const res = await queryKnowledgeContentlist(props.classId);
54
+    const res = await queryKnowledgeContentlist(selectedCategoryId.value);
55
+    KnowledgeDetails.value = categoryList.value.find(
56
+      (item: any) => item.id === selectedCategoryId.value,
57
+    );
58
+    if (KnowledgeDetails.value) {
59
+      selectedCategoryInfo.value = {
60
+        categoryId: selectedCategoryId.value,
61
+        categoryName: KnowledgeDetails.value.name,
62
+        contentId: initContentId,
63
+        contentPath: '', // 分类路径,例如:1/2/3
64
+        contentNamePath: '', // 分类名称路径,例如:分类1/分类2/分类3
65
+      };
66
+    }
31 67
     if (res.code === 200) {
32
-      console.log('章节数据', res.data);
33 68
       chapterData.value = res.data || [];
34 69
       if (chapterData.value.length > 0) {
35
-        handleChapterClick({ id: chapterData.value[0]?.id });
70
+        handleChapterClick({ id: initContentId || chapterData.value[0]?.id });
36 71
       }
37 72
     }
38
-  } catch (error) {
39
-    console.log(error);
73
+  } catch {
74
+    // 处理异常
40 75
   } finally {
41 76
     chapterDataloading.value = false;
42 77
   }
43 78
 };
44
-// 查询知识分类详情
45
-const queryKnowledgeCategoryDetailfn = async () => {
79
+
80
+const categoryList = ref([]) as any;
81
+// 查询知识分类列表
82
+const queryKnowledgeCategoryListfn = async () => {
46 83
   try {
47
-    const res = await queryKnowledgeCategoryDetail(props.classId);
48
-    KnowledgeDetails.value = res;
84
+    const res = await queryKnowledgeCategory({
85
+      pageSize: 10_000,
86
+    });
87
+    categoryList.value = res.rows || [];
49 88
   } catch (error) {
50
-    console.log(error);
89
+    console.error(error);
51 90
   }
52 91
 };
53 92
 
54 93
 onMounted(() => {
55
-  queryKnowledgeCategoryDetailfn();
56
-  chapterDatafn();
94
+  queryKnowledgeCategoryListfn();
95
+  if (props.data?.categoryId) {
96
+    selectedCategoryId.value = props.data?.categoryId;
97
+    chapterDatafn({ initContentId: props.data?.contentId });
98
+  }
57 99
 });
58 100
 // 选中的章节
59 101
 const selectedChapter = ref({}) as any;
@@ -65,7 +107,6 @@ const handleChapterClick = async (node: any) => {
65 107
   try {
66 108
     const res = await getKnowledgeContentDetail(node.id);
67 109
     if (res.code === 200) {
68
-      console.log('点击章节', res.data);
69 110
       selectedChapter.value = {
70 111
         id: res.data.id,
71 112
         label: res.data.title,
@@ -74,33 +115,13 @@ const handleChapterClick = async (node: any) => {
74 115
         attachmentUrl: res.data.attachmentUrl,
75 116
       };
76 117
     }
77
-  } catch (error) {
78
-    console.log(error);
118
+  } catch {
119
+    //
79 120
   } finally {
80 121
     centerloading.value = false;
81 122
   }
82 123
 };
83
-// 递归查找节点路径
84
-const findNodePath = (
85
-  data: any[],
86
-  targetId: number,
87
-  path: any[] = [],
88
-): any[] | null => {
89
-  for (let i = 0; i < data.length; i++) {
90
-    const node = data[i];
91
-    const currentPath = [...path, { data, index: i }];
92
-    if (node.id === targetId) {
93
-      return currentPath;
94
-    }
95
-    if (node.children && node.children.length > 0) {
96
-      const result = findNodePath(node.children, targetId, currentPath);
97
-      if (result) {
98
-        return result;
99
-      }
100
-    }
101
-  }
102
-  return null;
103
-};
124
+
104 125
 const imageExt = new Set(['bmp', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp']);
105 126
 const isImage = (url: string) => {
106 127
   if (!url) return false;
@@ -124,6 +145,78 @@ const nonImageList = computed(() => {
124 145
   const result = urls.filter((u: any) => !isImage(u));
125 146
   return result;
126 147
 });
148
+
149
+interface Category {
150
+  id: string;
151
+  title: string;
152
+  parentId: null | string;
153
+  children?: Category[];
154
+}
155
+
156
+// 通过递归方式获取分类路径和分类名称路径
157
+const getCategoryPath = (
158
+  chapterId: string,
159
+): { namePath: string; path: string } => {
160
+  // 递归搜索函数
161
+  const findPath = (
162
+    categories: Category[],
163
+    targetId: string,
164
+  ): Category[] | null => {
165
+    for (const category of categories) {
166
+      if (category.id === targetId) {
167
+        return [category];
168
+      }
169
+
170
+      if (category.children && category.children.length > 0) {
171
+        const childPath = findPath(category.children, targetId);
172
+        if (childPath) {
173
+          return [category, ...childPath];
174
+        }
175
+      }
176
+    }
177
+    return null;
178
+  };
179
+
180
+  // 查找路径
181
+  const pathArray = findPath(chapterData.value, chapterId);
182
+
183
+  if (!pathArray || pathArray.length === 0) {
184
+    return { path: '', namePath: '' };
185
+  }
186
+
187
+  // 构建路径字符串
188
+  const path = `${pathArray.map((item) => item.id).join('/')}`;
189
+  const namePath = `${pathArray.map((item) => item.title).join('/')}`;
190
+
191
+  return { path, namePath };
192
+};
193
+
194
+const confirmSelection = () => {
195
+  // 处理确认选择的逻辑
196
+  if (!selectedCategoryId.value) {
197
+    ElMessage.error('请选择分类');
198
+    return;
199
+  }
200
+  if (!selectedChapter.value.id) {
201
+    ElMessage.error('请选择章节');
202
+    return;
203
+  }
204
+
205
+  const { path, namePath }: any = getCategoryPath(selectedChapter.value.id);
206
+
207
+  const result = {
208
+    categoryId: selectedCategoryId.value,
209
+    categoryName: selectedCategoryInfo.value.categoryName,
210
+    contentId: selectedChapter.value.id,
211
+    contentPath: path,
212
+    contentNamePath: namePath,
213
+  };
214
+  return result;
215
+};
216
+
217
+defineExpose({
218
+  confirmSelection,
219
+});
127 220
 </script>
128 221
 
129 222
 <template>
@@ -136,14 +229,24 @@ const nonImageList = computed(() => {
136 229
         :class="{ collapsed: isCollapse }"
137 230
         v-loading="chapterDataloading"
138 231
       >
139
-        <!-- 折叠按钮 -->
140
-        <div class="collapse-btn" @click="isCollapse = true">
141
-          <ElIcon><DArrowLeft /></ElIcon>
232
+        <div class="m-5">
233
+          <ElSelect
234
+            v-model="selectedCategoryId"
235
+            placeholder="请选择分类"
236
+            @change="chapterDatafn"
237
+          >
238
+            <ElOption
239
+              v-for="item in categoryList"
240
+              :key="item.id"
241
+              :label="item.name"
242
+              :value="item.id"
243
+            />
244
+          </ElSelect>
142 245
         </div>
143 246
 
144 247
         <div class="sidebar-content">
145 248
           <!-- 知识库信息 -->
146
-          <div class="knowledge-info">
249
+          <!-- <div class="knowledge-info" v-if="selectedCategoryId">
147 250
             <div class="knowledge-icon">
148 251
               <span
149 252
                 v-if="KnowledgeDetails?.icon?.includes('iconfont')"
@@ -156,15 +259,12 @@ const nonImageList = computed(() => {
156 259
               <img v-else :src="KnowledgeDetails?.iconUrl?.[0] || ''" alt="" />
157 260
             </div>
158 261
             <div class="knowledge-title">{{ KnowledgeDetails?.name }}</div>
159
-          </div>
262
+          </div> -->
160 263
 
161 264
           <!-- 主页按钮 -->
162
-          <div class="home-btn">
265
+          <div class="home-btn" v-if="selectedCategoryId">
163 266
             <ElIcon><HomeFilled /></ElIcon>
164 267
             <span :class="{ collapsed: isCollapse }">主页</span>
165
-            <!-- <ElIcon style="margin-left: 120px" @click="addMenu">
166
-                <Plus />
167
-              </ElIcon> -->
168 268
           </div>
169 269
 
170 270
           <!-- 章节树 -->
@@ -175,7 +275,7 @@ const nonImageList = computed(() => {
175 275
               :props="{ label: 'title', children: 'children' }"
176 276
               default-expand-all
177 277
               :expand-on-click-node="false"
178
-              :current-node-key="chapterData[0]?.id"
278
+              :current-node-key="ChapterId"
179 279
               highlight-current
180 280
               @node-click="handleChapterClick"
181 281
             >
@@ -208,15 +308,15 @@ const nonImageList = computed(() => {
208 308
         <div class="content-header">
209 309
           <h2 class="content-title">{{ selectedChapter.label }}</h2>
210 310
           <div class="content-meta">
211
-            <ElIcon class="meta-icon" v-if="selectedChapter.updatedTime"
212
-              ><Clock
213
-            /></ElIcon>
311
+            <ElIcon class="meta-icon" v-if="selectedChapter.updatedTime">
312
+              <Clock />
313
+            </ElIcon>
214 314
             <span>{{ selectedChapter.updatedTime }}</span>
215 315
           </div>
216 316
         </div>
217 317
 
218 318
         <!-- 内容主体 -->
219
-        <div class="content-body">
319
+        <div class="content-body" v-if="selectedCategoryId">
220 320
           <div class="content-text" v-html="selectedChapter.content"></div>
221 321
           <!-- 附件 -->
222 322
           <div class="content-attachments">
@@ -296,6 +396,7 @@ const nonImageList = computed(() => {
296 396
             </div>
297 397
           </div>
298 398
         </div>
399
+        <!-- <div v-else>暂无数据</div> -->
299 400
       </div>
300 401
     </div>
301 402
   </div>

+ 55 - 24
apps/web-ele/src/views/examManage/exam/cpn/edit/drawer.vue

@@ -27,6 +27,23 @@ const emit = defineEmits<{ reload: [] }>();
27 27
 // 抽屉可见性
28 28
 const drawerVisible = ref(false);
29 29
 
30
+const clearForm = () => {
31
+  Object.assign(examForm, {
32
+    examName: '',
33
+    categoryId: '',
34
+    remark: '',
35
+    paperId: '',
36
+    passScore: '',
37
+    examStartTime: '',
38
+    examEndTime: '',
39
+    examDuration: '',
40
+    executorCount: 0, // 模拟已选择23人
41
+    executorUsers: [],
42
+    paperCategoryId: '',
43
+  });
44
+  selectedPapers.value = [];
45
+};
46
+
30 47
 const isUpdateRef = ref<boolean>(false);
31 48
 const [Drawer, drawerApi] = useVbenDrawer({
32 49
   async onOpenChange(isOpen: any) {
@@ -72,6 +89,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
72 89
   },
73 90
   onClosed() {
74 91
     // formApi.resetForm();
92
+    clearForm();
75 93
   },
76 94
   async onConfirm() {
77 95
     try {
@@ -147,6 +165,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
147 165
     } catch (error) {
148 166
       console.error(error);
149 167
     } finally {
168
+      clearForm();
150 169
       this.confirmLoading = false;
151 170
     }
152 171
   },
@@ -255,6 +274,15 @@ const getPaperCategoryOptions = async () => {
255 274
   paperCategoryTree.value = res.rows || [];
256 275
 };
257 276
 
277
+const handleStartTimeChange = (val: any) => {
278
+  examForm.examDuration =
279
+    val && examForm.examStartTime && examForm.examEndTime
280
+      ? dayjs(examForm.examEndTime)
281
+          .diff(dayjs(examForm.examStartTime), 'minute')
282
+          .toString()
283
+      : '';
284
+};
285
+
258 286
 onMounted(async () => {
259 287
   getCategoryOptions().then();
260 288
   getPaperCategoryOptions().then();
@@ -337,30 +365,6 @@ onMounted(async () => {
337 365
               </ElInput>
338 366
             </ElFormItem> -->
339 367
 
340
-            <!-- 及格分 -->
341
-            <ElFormItem label="及格分" class="form-item-with-border" required>
342
-              <ElInput
343
-                v-model="examForm.passScore"
344
-                placeholder="请输入及格分"
345
-                type="number"
346
-                min="1"
347
-              >
348
-                <template #append>分</template>
349
-              </ElInput>
350
-            </ElFormItem>
351
-
352
-            <!-- 答卷时长 -->
353
-            <ElFormItem label="答卷时长" class="form-item-with-border" required>
354
-              <ElInput
355
-                v-model="examForm.examDuration"
356
-                placeholder="请输入答卷时长"
357
-                type="number"
358
-                min="1"
359
-              >
360
-                <template #append>分钟</template>
361
-              </ElInput>
362
-            </ElFormItem>
363
-
364 368
             <!-- 考试时间 -->
365 369
             <ElFormItem
366 370
               label="考试时间"
@@ -371,17 +375,44 @@ onMounted(async () => {
371 375
                 v-model="examForm.examStartTime"
372 376
                 type="datetime"
373 377
                 placeholder="开始时间"
378
+                @change="handleStartTimeChange"
374 379
                 style="width: calc(50% - 15px)"
375 380
               />
376 381
               <span style="margin: 0 6px">至</span>
377 382
               <ElDatePicker
378 383
                 v-model="examForm.examEndTime"
379 384
                 type="datetime"
385
+                @change="handleStartTimeChange"
380 386
                 placeholder="结束时间"
381 387
                 style="width: calc(50% - 15px)"
382 388
               />
383 389
             </ElFormItem>
384 390
 
391
+            <!-- 答卷时长 -->
392
+            <ElFormItem label="答卷时长" class="form-item-with-border" required>
393
+              <ElInput
394
+                v-model="examForm.examDuration"
395
+                placeholder="请输入答卷时长"
396
+                type="number"
397
+                min="1"
398
+                disabled
399
+              >
400
+                <template #append>分钟</template>
401
+              </ElInput>
402
+            </ElFormItem>
403
+
404
+            <!-- 及格分 -->
405
+            <ElFormItem label="及格分" class="form-item-with-border" required>
406
+              <ElInput
407
+                v-model="examForm.passScore"
408
+                placeholder="请输入及格分"
409
+                type="number"
410
+                min="1"
411
+              >
412
+                <template #append>分</template>
413
+              </ElInput>
414
+            </ElFormItem>
415
+
385 416
             <!-- 考试执行人 -->
386 417
             <ElFormItem
387 418
               label="考试执行人"

+ 12 - 2
apps/web-ele/src/views/examManage/exam/index.vue

@@ -36,6 +36,7 @@ import { deleteExam, examList } from '#/api/exam/exam/exam';
36 36
 // @ts-ignore
37 37
 import PaperPreview from '#/components/paper';
38 38
 // @ts-ignore
39
+// @ts-ignore
39 40
 
40 41
 import { queryFormSchema, tableColumns } from './config-data';
41 42
 // @ts-ignore
@@ -258,8 +259,17 @@ const updateExam = (row: any) => {
258 259
 
259 260
 const generateLink = (row: any) => {
260 261
   const link = `${window.location.origin}/examin/${row.id}`;
261
-  const { copy } = useClipboard({ legacy: true, source: link });
262
-  copy(link);
262
+  const mobileLink = `${import.meta.env.VITE_MOBILE_EXAM_URL.replace('%23', '#').replace('{id}', row.id)}`;
263
+  // console.log(import.meta.env.VITE_MOBILE_EXAM_URL, mobileLink);
264
+  const copyContent = `考试名称:${row.examName}
265
+开始时间:${row.examStartTime}
266
+结束时间:${row.examEndTime}
267
+考试时长:${row.examDuration || 0} 分钟
268
+电脑端链接:${link}
269
+移动端链接:${mobileLink}
270
+  `;
271
+  const { copy } = useClipboard({ legacy: true, source: copyContent });
272
+  copy(copyContent);
263 273
   ElMessage.success(`已复制到剪贴板`);
264 274
 };
265 275
 

+ 41 - 5
apps/web-ele/src/views/examManage/myExamPaper/cpn/endexamin/result.vue

@@ -2,8 +2,10 @@
2 2
 import { onMounted, ref } from 'vue';
3 3
 import { useRoute } from 'vue-router';
4 4
 
5
+// // @ts-ignore
6
+// import { useVbenModal } from '@vben/common-ui';
5 7
 // @ts-ignore
6
-import { Page } from '@vben/common-ui';
8
+import { Page, useVbenModal } from '@vben/common-ui';
7 9
 
8 10
 import { CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue';
9 11
 
@@ -13,6 +15,7 @@ import { getAnswerResult } from '#/api/exam/exam/answer';
13 15
 import { examPaperDetail } from '#/api/exam/exam/exam';
14 16
 // @ts-ignore
15 17
 import { getParticipantById } from '#/api/exam/exam/participant';
18
+import knowledgemain from '#/components/knowledgemain/index.vue';
16 19
 
17 20
 const route = useRoute();
18 21
 const examId = Number(route.params.id || 0);
@@ -54,6 +57,23 @@ const parseJson = (str: string) => {
54 57
   }
55 58
 };
56 59
 
60
+const knowledgeRef: any = ref(null);
61
+const selectedCategoryInfo = ref<any>({});
62
+const [quoteModal, quoteModalApi] = useVbenModal({
63
+  title: '引用试题',
64
+  showCancelBtn: false,
65
+  class: 'quote-modal',
66
+  onConfirm: () => {
67
+    const result = knowledgeRef.value.confirmSelection();
68
+    if (result) {
69
+      // formData.value = result;
70
+      // console.log(result);
71
+      selectedCategoryInfo.value = result;
72
+      quoteModalApi.close();
73
+    }
74
+  },
75
+});
76
+
57 77
 // 根据paperQuestionId获取答题信息
58 78
 const getAnswerByPaperQuestionId = (paperQuestionId: number) => {
59 79
   return (
@@ -92,6 +112,11 @@ const getQuestionsByBigIndex = () => {
92 112
     .map((key) => grouped[key]);
93 113
 };
94 114
 
115
+const handleQuote = (str: string) => {
116
+  selectedCategoryInfo.value = JSON.parse(str || '{}');
117
+  quoteModalApi.open();
118
+};
119
+
95 120
 const init = async () => {
96 121
   // 并发请求接口获取数据
97 122
   const [examInfoRes, participantRes, answerRes] = await Promise.all([
@@ -281,12 +306,19 @@ onMounted(async () => {
281 306
                 </span>
282 307
               </div>
283 308
               <div class="exam_info_left_content_answer_desc">
284
-                答案解析:
285
-                <span class="exam_info_left_content_answer_content">
309
+                答案解析:
310
+                <ElLink
311
+                  v-if="question.info.analysisReference"
312
+                  type="primary"
313
+                  @click="handleQuote(question.info.analysisReference)"
314
+                >
315
+                  查看引用解析
316
+                </ElLink>
317
+                <div class="exam_info_left_content_answer_content">
286 318
                   <span
287 319
                     v-html="question.info.answerAnalysis || '暂无解析'"
288 320
                   ></span>
289
-                </span>
321
+                </div>
290 322
               </div>
291 323
               <div class="exam_info_left_content_answer_title">
292 324
                 得分:
@@ -367,6 +399,9 @@ onMounted(async () => {
367 399
         </div>
368 400
       </div>
369 401
     </div>
402
+    <quoteModal>
403
+      <knowledgemain ref="knowledgeRef" :data="selectedCategoryInfo" />
404
+    </quoteModal>
370 405
   </Page>
371 406
 </template>
372 407
 <style lang="scss" scoped>
@@ -463,7 +498,7 @@ onMounted(async () => {
463 498
             font-size: 18px;
464 499
             font-weight: 500;
465 500
             color: #31373d;
466
-            display: flex;
501
+            // display: flex;
467 502
             align-items: center;
468 503
             flex-wrap: wrap;
469 504
             span {
@@ -474,6 +509,7 @@ onMounted(async () => {
474 509
               }
475 510
             }
476 511
             .exam_info_left_content_answer_content {
512
+              margin-top: 12px;
477 513
               margin-left: 8px;
478 514
               font-size: 16px;
479 515
               font-weight: 400;

+ 1 - 1
apps/web-ele/src/views/examManage/myExamPaper/cpn/startexamin/index.vue

@@ -72,7 +72,7 @@ const loadAnswersFromStorage = () => {
72 72
 const initCountdown = () => {
73 73
   const startTime = examInfo.value.examStartTime;
74 74
 
75
-  const examDuration = examInfo.value.examDuration * 600_000;
75
+  const examDuration = examInfo.value.examDuration * 60_000;
76 76
 
77 77
   if (startTime) {
78 78
     // 页面刷新或重新进入,根据开始时间计算剩余时间

+ 35 - 6
apps/web-ele/src/views/examManage/questionBank/cpn/questionEdit/index.vue

@@ -1,9 +1,9 @@
1 1
 <script setup lang="ts">
2 2
 import { onMounted, ref } from 'vue';
3 3
 import { useRoute } from 'vue-router';
4
+
4 5
 // @ts-ignore
5 6
 import { useVbenModal } from '@vben/common-ui';
6
-
7 7
 // @ts-ignore
8 8
 import { useTabs } from '@vben/hooks';
9 9
 
@@ -15,9 +15,9 @@ import {
15 15
   updateQuestion,
16 16
   // @ts-ignore
17 17
 } from '#/api/exam/question/question';
18
+import knowledgemain from '#/components/knowledgemain/index.vue';
18 19
 // @ts-ignore
19 20
 import TinymceEditor from '#/components/tinymce/src/editor.vue';
20
-import knowledgemain from '#/components/knowledgemain/index.vue';
21 21
 // @ts-ignore
22 22
 const { closeCurrentTab } = useTabs();
23 23
 
@@ -25,6 +25,8 @@ const route = useRoute();
25 25
 // const router = useRouter();
26 26
 const id = route.query.id as string;
27 27
 
28
+const selectedCategoryInfo: any = ref({});
29
+
28 30
 // 分类树形数据
29 31
 const categoryTreeData: any = ref([]);
30 32
 
@@ -106,7 +108,11 @@ const loadQuestionData = async () => {
106 108
         options: question.options ? JSON.parse(question.options) : ['', ''], // 解析JSON字符串为数组
107 109
         correctAnswer: JSON.parse(question.correctAnswer), // 解析JSON字符串为数组
108 110
         analysis: question.answerAnalysis || '',
111
+        // analysisReference: question.analysisReference || '',
109 112
       };
113
+      if (question.analysisReference) {
114
+        selectedCategoryInfo.value = JSON.parse(question.analysisReference);
115
+      }
110 116
     } catch (error) {
111 117
       console.error('获取试题详情失败:', error);
112 118
     }
@@ -198,6 +204,10 @@ const processQuestionData = () => {
198 204
     }
199 205
   }
200 206
 
207
+  if (selectedCategoryInfo.value.contentId) {
208
+    saveData.analysisReference = JSON.stringify(selectedCategoryInfo.value);
209
+  }
210
+
201 211
   return saveData;
202 212
 };
203 213
 
@@ -266,12 +276,21 @@ onMounted(() => {
266 276
   fetchCategories();
267 277
 });
268 278
 
279
+const knowledgeRef: any = ref(null);
280
+
269 281
 const [quoteModal, quoteModalApi] = useVbenModal({
270 282
   title: '引用试题',
271 283
   showCancelBtn: false,
272 284
   class: 'quote-modal',
273
-  // onConfirm:()=>{
274
-  // }
285
+  onConfirm: () => {
286
+    const result = knowledgeRef.value.confirmSelection();
287
+    if (result) {
288
+      // formData.value = result;
289
+      // console.log(result);
290
+      selectedCategoryInfo.value = result;
291
+      quoteModalApi.close();
292
+    }
293
+  },
275 294
 });
276 295
 
277 296
 const handleQuote = () => {
@@ -526,10 +545,20 @@ const handleQuote = () => {
526 545
           </h3>
527 546
           <div class="mb-2 flex items-center gap-2">
528 547
             <!--TODO: 引用功能未实现-->
529
-            <el-button type="default" plain size="small" @click="handleQuote">
548
+            <el-button
549
+              v-if="!selectedCategoryInfo.contentNamePath"
550
+              type="default"
551
+              plain
552
+              size="small"
553
+              @click="handleQuote"
554
+            >
530 555
               引用
531 556
             </el-button>
557
+            <el-link type="primary" :underline="false" @click="handleQuote">
558
+              {{ selectedCategoryInfo.contentNamePath || '' }}
559
+            </el-link>
532 560
           </div>
561
+
533 562
           <TinymceEditor
534 563
             v-model="formData.analysis"
535 564
             :height="300"
@@ -549,7 +578,7 @@ const handleQuote = () => {
549 578
       </div>
550 579
     </div>
551 580
     <quoteModal>
552
-      <knowledgemain :classId="id" />
581
+      <knowledgemain ref="knowledgeRef" :data="selectedCategoryInfo" />
553 582
     </quoteModal>
554 583
   </div>
555 584
 </template>

+ 112 - 41
apps/web-ele/src/views/knowledge/type/create.vue

@@ -1,21 +1,38 @@
1 1
 <script lang="ts" setup>
2 2
 import { onMounted, ref, watch } from 'vue';
3
+
4
+// @ts-ignore
5
+import { useVbenModal } from '@vben/common-ui';
6
+import { useAccessStore } from '@vben/stores';
7
+
8
+import { Delete, Upload } from '@element-plus/icons-vue';
3 9
 import {
10
+  ElButton,
4 11
   ElForm,
5 12
   ElFormItem,
13
+  ElIcon,
6 14
   ElInput,
7
-  ElSelect,
15
+  ElMessage,
8 16
   ElOption,
17
+  ElSelect,
9 18
   ElUpload,
10
-  ElMessage,
11
-  ElButton,
12
-  ElIcon,
13 19
 } from 'element-plus';
14
-import { Upload, Plus, Delete } from '@element-plus/icons-vue';
15
-import '#/assets/iconfonts/font/iconfont.css';
16
-import { useAccessStore } from '@vben/stores';
17
-import { getUploadAction } from '#/components/upload/config';
20
+
18 21
 import { queryKnowledgeCategory } from '#/api/knowledge/category';
22
+// @ts-ignore
23
+import { getPostList } from '#/api/system/post/post';
24
+// @ts-ignore
25
+import { SelectUsers } from '#/components/selectUsers';
26
+import { getUploadAction } from '#/components/upload/config';
27
+
28
+import '#/assets/iconfonts/font/iconfont.css';
29
+// 定义组件的props
30
+const props = defineProps<{
31
+  initialData?: FormData;
32
+  mode: string;
33
+}>();
34
+// 定义事件
35
+const emit = defineEmits(['close', 'save']);
19 36
 const accessStore = useAccessStore();
20 37
 // 定义表单数据类型
21 38
 interface FormData {
@@ -23,32 +40,53 @@ interface FormData {
23 40
   description: string;
24 41
   upiconname: string;
25 42
   icon: string;
26
-  managers: any[];
27
-  visibleApps: any[];
43
+  managePermission: string;
44
+  managerNames: string;
45
+  visibleRole: any[];
28 46
   visiblePositions: any[];
29 47
   association: string;
30 48
 }
31 49
 
32
-// 定义组件的props
33
-const props = defineProps<{
34
-  mode: string;
35
-  initialData?: FormData;
36
-}>();
37
-
38
-// 定义事件
39
-const emit = defineEmits(['close', 'save']);
40
-
41 50
 // 表单引用
42 51
 const formRef = ref();
43 52
 
53
+// 选人组件引用
54
+const userSelectRef: any = ref(null);
55
+
56
+// 选人模态框
57
+const [UserSelectModal, userSelectModalApi] = useVbenModal({
58
+  title: '选择知识库管理员',
59
+  width: 1000,
60
+  height: 800,
61
+  zIndex: 100_000,
62
+  closeOnClickModal: false,
63
+  showCancelBtn: false,
64
+  onConfirm: async () => {
65
+    const users = await userSelectRef.value.confirmSelection();
66
+    if (users?.length) {
67
+      // 更新表单数据
68
+      form.value.managePermission = users.map((user: any) => user.id).join(',');
69
+      form.value.managerNames = users.map((user: any) => user.name).join(',');
70
+      // 关闭模态框
71
+      userSelectModalApi.close();
72
+    }
73
+  },
74
+});
75
+
76
+// 打开选人模态框
77
+const openUserSelectModal = () => {
78
+  userSelectModalApi.open();
79
+};
80
+
44 81
 // 表单数据
45 82
 const form = ref<FormData>({
46 83
   name: '',
47 84
   description: '',
48 85
   upiconname: '',
49 86
   icon: '',
50
-  managers: [],
51
-  visibleApps: [],
87
+  managePermission: '',
88
+  managerNames: '',
89
+  visibleRole: [],
52 90
   visiblePositions: [],
53 91
   association: '',
54 92
 });
@@ -159,10 +197,29 @@ const createCategory = async () => {
159 197
     console.log(error);
160 198
   }
161 199
 };
200
+const getPositionList = async () => {
201
+  try {
202
+    const res = await getPostList({
203
+      pageSize: 10_000,
204
+    });
205
+    // 将岗位数据转换为选项格式
206
+    positionList.value =
207
+      res.rows?.map((item: any) => ({
208
+        label: item.postName,
209
+        value: item.postId,
210
+      })) || [];
211
+  } catch (error) {
212
+    console.warn('获取岗位列表失败:', error);
213
+  }
214
+};
215
+const knowledgeList = ref([]) as any;
216
+// 岗位列表
217
+const positionList = ref([]) as any;
162 218
 onMounted(() => {
163 219
   createCategory();
220
+  getPositionList();
164 221
 });
165
-const knowledgeList = ref([]) as any;
222
+
166 223
 // 上传图片处理
167 224
 // const uploadFile = (file: any) => {
168 225
 //   // 这里可以添加图片上传逻辑
@@ -199,10 +256,12 @@ const handleDeleteIcon = () => {
199 256
 const submitForm = () => {
200 257
   // 这里可以添加表单提交逻辑
201 258
   formRef.value.validate((valid: any) => {
202
-    console.log(form.value);
203
-
204 259
     if (valid) {
205
-      emit('save', form.value);
260
+      emit('save', {
261
+        ...form.value,
262
+        visiblePositions: form.value.visiblePositions.join(','),
263
+        visibleRole: form.value.visibleRole.join(','),
264
+      });
206 265
       ElMessage.success(props.mode === 'create' ? '创建成功' : '编辑成功');
207 266
     } else {
208 267
       ElMessage.error('请填写正确的信息');
@@ -302,24 +361,24 @@ const closeForm = () => {
302 361
       label="知识库管理员(管理员具有知识库编辑、删除的权限)"
303 362
       prop="managers"
304 363
     >
305
-      <ElSelect
306
-        v-model="form.managers"
307
-        placeholder="可多选"
308
-        multiple
309
-        filterable
364
+      <ElInput
365
+        v-model="form.managerNames"
366
+        disabled
367
+        placeholder="请选择管理员"
368
+        readonly
310 369
       >
311
-        <ElOption label="管理员1" value="admin1" />
312
-        <ElOption label="管理员2" value="admin2" />
313
-        <ElOption label="管理员3" value="admin3" />
314
-      </ElSelect>
370
+        <template #append>
371
+          <ElButton type="primary" @click="openUserSelectModal">选择</ElButton>
372
+        </template>
373
+      </ElInput>
315 374
     </ElFormItem>
316 375
 
317 376
     <!-- 可见范围 -->
318 377
     <ElFormItem label="可见范围(可查看该知识库权限)">
319 378
       <div class="visible-range" style="width: 100%">
320
-        <ElFormItem label="端" label-position="right" prop="visibleApps">
379
+        <ElFormItem label="端" label-position="right" prop="visibleRole">
321 380
           <ElSelect
322
-            v-model="form.visibleApps"
381
+            v-model="form.visibleRole"
323 382
             placeholder="可多选"
324 383
             multiple
325 384
             filterable
@@ -327,7 +386,7 @@ const closeForm = () => {
327 386
           >
328 387
             <ElOption label="PC端" value="pc" />
329 388
             <ElOption label="移动端" value="mobile" />
330
-            <ElOption label="小程序" value="miniprogram" />
389
+            <!-- <ElOption label="小程序" value="miniprogram" /> -->
331 390
           </ElSelect>
332 391
         </ElFormItem>
333 392
         <ElFormItem label="岗位" label-position="right" prop="visiblePositions">
@@ -338,16 +397,23 @@ const closeForm = () => {
338 397
             filterable
339 398
             style="width: 100%"
340 399
           >
341
-            <ElOption label="经理" value="manager" />
400
+            <ElOption
401
+              v-for="item in positionList"
402
+              :key="item.value"
403
+              :label="item.label"
404
+              :value="item.value"
405
+            />
406
+            <!-- <ElOption label="经理" value="manager" />
342 407
             <ElOption label="员工" value="employee" />
343
-            <ElOption label="实习生" value="intern" />
408
+            <ElOption label="实习生" value="intern" /> -->
409
+            <!-- <ElSelect> -->
344 410
           </ElSelect>
345 411
         </ElFormItem>
346 412
       </div>
347 413
     </ElFormItem>
348 414
 
349 415
     <!-- 知识库关联 -->
350
-    <ElFormItem
416
+    <!-- <ElFormItem
351 417
       label="知识库关联(关联后,两个知识库内容同步编辑)"
352 418
       prop="association"
353 419
     >
@@ -359,7 +425,7 @@ const closeForm = () => {
359 425
           :value="String(item.id)"
360 426
         />
361 427
       </ElSelect>
362
-    </ElFormItem>
428
+    </ElFormItem> -->
363 429
 
364 430
     <!-- 操作按钮 -->
365 431
     <div class="form-actions">
@@ -367,6 +433,11 @@ const closeForm = () => {
367 433
       <ElButton @click="closeForm">取消</ElButton>
368 434
     </div>
369 435
   </ElForm>
436
+
437
+  <!-- 选择人员模态框 -->
438
+  <UserSelectModal class="w-[700px]">
439
+    <SelectUsers ref="userSelectRef" />
440
+  </UserSelectModal>
370 441
 </template>
371 442
 
372 443
 <style lang="scss" scoped>

+ 0 - 21
apps/web-ele/src/views/schedule/detail/components/gun-check.vue

@@ -157,27 +157,6 @@ const gridOptions: any = {
157 157
       query: async () => {
158 158
         // 处理数据,确保计算列正确
159 159
         const items = props.taskDetail?.records || [];
160
-        // 计算误差率和平均误差
161
-        // items.forEach((item: any) => {
162
-        //   // 计算误差率第一次
163
-        //   item.errorRateFirst =
164
-        //     item.actualValueFirst && item.actualValueFirst !== 0
165
-        //       ? ((item.actualValueFirst - item.machineValueFirst) /
166
-        //           item.actualValueFirst) *
167
-        //         1000
168
-        //       : 0;
169
-
170
-        //   // 计算误差率第二次
171
-        //   item.errorRateSecond =
172
-        //     item.actualValueSecond && item.actualValueSecond !== 0
173
-        //       ? ((item.actualValueSecond - item.machineValueSecond) /
174
-        //           item.actualValueSecond) *
175
-        //         1000
176
-        //       : 0;
177
-
178
-        //   // 计算平均误差
179
-        //   item.avgErrorRate = (item.errorRateFirst + item.errorRateSecond) / 2;
180
-        // });
181 160
         return {
182 161
           items,
183 162
         };

+ 18 - 10
apps/web-ele/src/views/schedule/view/index.vue

@@ -152,16 +152,20 @@ const querytodoaskslistfn = async ({
152 152
   try {
153 153
     const stationId = searchParams.value.station.split('-')[1];
154 154
     const executorId = searchParams.value.station.split('-')[0];
155
-    const res = await querytodoaskslist({
155
+    const params: any = {
156 156
       showStartTime,
157 157
       showEndTime,
158 158
       authStatusStr,
159
-      stationId,
159
+      // stationId,
160 160
       executorId,
161
-      taskStatusStr: '0,1,3',
161
+      taskStatusStr: '-1,0,1,3',
162 162
       // userId,
163 163
       // isOverdue: '0'
164
-    });
164
+    };
165
+    if (stationId && stationId !== '0') {
166
+      params.stationId = stationId;
167
+    }
168
+    const res = await querytodoaskslist(params);
165 169
     daylist.value = res || [];
166 170
   } catch (error) {
167 171
     console.error(error);
@@ -175,19 +179,23 @@ const queryoverduelistfn = async ({ authStatusStr }: any) => {
175 179
   try {
176 180
     const stationId = searchParams.value.station.split('-')[1];
177 181
     const executorId = searchParams.value.station.split('-')[0];
178
-    const res = await querytodoaskslist({
182
+    const params: any = {
179 183
       authStatusStr,
180 184
       status: '2',
181 185
       executorId,
182
-      stationId,
186
+      // stationId,
183 187
       showStartTime: dayjs(currentDate.value).format('YYYY-MM-DD 00:00:00'),
184 188
       showEndTime: dayjs(currentDate.value)
185 189
         .add(1, 'day')
186 190
         .format('YYYY-MM-DD 23:59:59'),
187
-    });
191
+    };
192
+    if (stationId && stationId !== '0') {
193
+      params.stationId = stationId;
194
+    }
195
+    const res = await querytodoaskslist(params);
188 196
     overduedaylist.value = res || [];
189 197
   } catch (error) {
190
-    console.log(error);
198
+    console.error(error);
191 199
   } finally {
192 200
     mainloing.value = false;
193 201
   }
@@ -525,8 +533,8 @@ const createWorkOrderDrawerEvent = () => {
525 533
               <ElOption
526 534
                 v-for="item in gasstationlist"
527 535
                 :key="`${item.userId}-${item.stationId}`"
528
-                :label="`${item.nickName}-${item.postName}${item.stationName ? '-' + item.stationName : ''}`"
529
-                :value="`${item.userId}-${item.stationId}`"
536
+                :label="`${item.nickName}-${item.postName}${item.stationName ? `-${item.stationName}` : ''}`"
537
+                :value="`${item.userId}-${item.stationId || 0}`"
530 538
               />
531 539
             </ElSelect>
532 540
           </div>