Przeglądaj źródła

Merge branch 'master' of http://192.168.1.222:3000/hnsh-smart-steward/smart-steward-admin

闪电 2 miesięcy temu
rodzic
commit
cb05e518f6

+ 45 - 1
apps/web-ele/src/router/routes/local.ts

@@ -60,6 +60,50 @@ const localRoutes: RouteRecordStringComponent[] = [
60 60
     path: '/examManage/myExamPaper/view/:id',
61 61
   },
62 62
   {
63
+    component: '/examManage/questionBank/cpn/questionEdit/index',
64
+    meta: {
65
+      activePath: '/examManage/questionBank',
66
+      icon: 'carbon:edit',
67
+      title: '试题编辑',
68
+      hideInMenu: true,
69
+    },
70
+    name: 'QuestionEdit',
71
+    path: '/examManage/questionBank/questionEdit/:id?',
72
+  },
73
+  {
74
+    component: '/examManage/questionBank/cpn/questionPreview/index',
75
+    meta: {
76
+      activePath: '/examManage/questionBank',
77
+      icon: 'carbon:view',
78
+      title: '试题预览',
79
+      hideInMenu: true,
80
+    },
81
+    name: 'QuestionPreview',
82
+    path: '/examManage/questionBank/questionPreview/:id',
83
+  },
84
+  {
85
+    component: '/examManage/examPaper/cpn/examEdit/index',
86
+    meta: {
87
+      activePath: '/examManage/examPaper',
88
+      icon: 'carbon:edit',
89
+      title: '试卷编辑',
90
+      hideInMenu: true,
91
+    },
92
+    name: 'ExamEdit',
93
+    path: '/examManage/examEdit/:id?',
94
+  },
95
+  {
96
+    component: '/examManage/examPaper/cpn/examPreview/index',
97
+    meta: {
98
+      activePath: '/examManage/examPaper',
99
+      icon: 'carbon:view',
100
+      title: '试卷预览',
101
+      hideInMenu: true,
102
+    },
103
+    name: 'ExamPreview',
104
+    path: '/examManage/examPaper/examPreview/:id',
105
+  },
106
+  {
63 107
     component: '/monitor/job/job-log/index',
64 108
     meta: {
65 109
       activePath: '/monitor/job',
@@ -106,7 +150,7 @@ const localRoutes: RouteRecordStringComponent[] = [
106 150
     component: '/knowledge/edit/index',
107 151
     meta: {
108 152
       activePath: '/knowledge/edit',
109
-      icon: 'carbon:data-base', 
153
+      icon: 'carbon:data-base',
110 154
       title: '知识库编辑',
111 155
       hideInMenu: true,
112 156
     },

+ 499 - 0
apps/web-ele/src/views/examManage/examPaper/cpns/examEdit/index.vue

@@ -0,0 +1,499 @@
1
+<script setup lang="ts">
2
+import { computed, ref } from 'vue';
3
+
4
+// 试卷表单数据
5
+const examForm = ref({
6
+  examName: '',
7
+  examCategory: '',
8
+});
9
+
10
+// 当前选中的题目索引
11
+const currentQuestionIndex = ref(0);
12
+
13
+// 新题目标题
14
+const newQuestionTitle = ref('');
15
+
16
+// 试卷题目数据
17
+const examQuestions = ref([
18
+  {
19
+    title: '第一题 选择题',
20
+    fixedQuestions: [
21
+      { type: '填空题', count: 120, score: 0 },
22
+      { type: '简答题', count: 120, score: 0 },
23
+      { type: '单选题', count: 120, score: 0 },
24
+      { type: '多选题', count: 120, score: 0 },
25
+    ],
26
+    randomQuestions: [
27
+      { type: '填空题', count: 120, score: 0 },
28
+      { type: '简答题', count: 120, score: 0 },
29
+      { type: '单选题', count: 120, score: 0 },
30
+      { type: '多选题', count: 120, score: 0 },
31
+    ],
32
+  },
33
+  {
34
+    title: '第二题 填空题',
35
+    fixedQuestions: [],
36
+    randomQuestions: [],
37
+  },
38
+  {
39
+    title: '第三题 判断题',
40
+    fixedQuestions: [],
41
+    randomQuestions: [],
42
+  },
43
+]);
44
+
45
+// 计算总题数
46
+const totalQuestions = computed(() => examQuestions.value.length);
47
+
48
+// 计算小题总数
49
+const totalSubQuestions = computed(() => {
50
+  return examQuestions.value.reduce((total, question) => {
51
+    return (
52
+      total + question.fixedQuestions.length + question.randomQuestions.length
53
+    );
54
+  }, 0);
55
+});
56
+
57
+// 计算总分
58
+const totalScore = computed(() => {
59
+  let score = 0;
60
+  examQuestions.value.forEach((question) => {
61
+    question.fixedQuestions.forEach((item) => {
62
+      score += item.score;
63
+    });
64
+    question.randomQuestions.forEach((item) => {
65
+      score += item.score;
66
+    });
67
+  });
68
+  return score;
69
+});
70
+
71
+// 获取单个题目总分
72
+const getQuestionTotalScore = (index: number) => {
73
+  const question = examQuestions.value[index];
74
+  let score = 0;
75
+  question.fixedQuestions.forEach((item) => {
76
+    score += item.score;
77
+  });
78
+  question.randomQuestions.forEach((item) => {
79
+    score += item.score;
80
+  });
81
+  return score;
82
+};
83
+
84
+// 计算分数变化
85
+const calculateScore = () => {
86
+  // 分数计算逻辑已通过computed属性实现
87
+};
88
+
89
+// 添加大题
90
+const addBigQuestion = () => {
91
+  examQuestions.value.push({
92
+    title: `第${examQuestions.value.length + 1}题 新题目`,
93
+    fixedQuestions: [],
94
+    randomQuestions: [],
95
+  });
96
+  currentQuestionIndex.value = examQuestions.value.length - 1;
97
+};
98
+</script>
99
+
100
+<template>
101
+  <div class="exam-edit-container">
102
+    <!-- 顶部区域 -->
103
+    <div class="top-section">
104
+      <div class="left-section">
105
+        <div class="form-item">
106
+          <label class="label">试卷名称:</label>
107
+          <el-input
108
+            v-model="examForm.examName"
109
+            placeholder="请输入试卷名称"
110
+            :maxlength="30"
111
+            show-word-limit
112
+            class="input"
113
+          />
114
+        </div>
115
+        <div class="form-item">
116
+          <label class="label">试卷分类:</label>
117
+          <el-select
118
+            v-model="examForm.examCategory"
119
+            placeholder="请选择"
120
+            class="select"
121
+          >
122
+            <el-option label="选择题" value="1" />
123
+            <el-option label="填空题" value="2" />
124
+            <el-option label="简答题" value="3" />
125
+            <el-option label="判断题" value="4" />
126
+          </el-select>
127
+        </div>
128
+      </div>
129
+      <div class="right-section">
130
+        <div class="stats">
131
+          <span>{{ totalQuestions }}大题</span>
132
+          <span class="separator">|</span>
133
+          <span>{{ totalSubQuestions }}小题</span>
134
+          <span class="separator">|</span>
135
+          <span>总分:{{ totalScore }}</span>
136
+        </div>
137
+        <el-button type="primary" size="default" class="save-btn">
138
+          保存
139
+        </el-button>
140
+      </div>
141
+    </div>
142
+
143
+    <!-- 主体区域 -->
144
+    <div class="main-section">
145
+      <!-- 左侧导航 -->
146
+      <div class="left-nav">
147
+        <div class="nav-title">大题导航</div>
148
+        <div
149
+          v-for="(question, index) in examQuestions"
150
+          :key="index"
151
+          class="nav-item"
152
+          :class="{ active: currentQuestionIndex === index }"
153
+          @click="currentQuestionIndex = index"
154
+        >
155
+          {{ question.title }}
156
+        </div>
157
+        <el-input
158
+          v-model="newQuestionTitle"
159
+          placeholder="大题名称"
160
+          class="new-question-input"
161
+        />
162
+      </div>
163
+
164
+      <!-- 右侧内容 -->
165
+      <div class="right-content">
166
+        <!-- 题目编辑区域 -->
167
+        <div
168
+          v-for="(question, index) in examQuestions"
169
+          :key="index"
170
+          class="question-block"
171
+          v-show="currentQuestionIndex === index"
172
+        >
173
+          <div class="question-header">
174
+            <span class="question-title">{{ question.title }}</span>
175
+            <el-button type="text" icon="EditPen" class="edit-btn">
176
+              编辑
177
+            </el-button>
178
+          </div>
179
+
180
+          <!-- 固定试题 -->
181
+          <div class="question-type-section">
182
+            <div class="section-title">固定试题</div>
183
+            <el-table :data="question.fixedQuestions" style="width: 100%">
184
+              <el-table-column prop="type" label="试题类型" width="120" />
185
+              <el-table-column prop="count" label="试题数" width="100" />
186
+              <el-table-column prop="score" label="每道题分数" width="120">
187
+                <template #default="scope">
188
+                  <el-input-number
189
+                    v-model="scope.row.score"
190
+                    :min="0"
191
+                    :max="100"
192
+                    size="small"
193
+                    @change="calculateScore"
194
+                  />
195
+                </template>
196
+              </el-table-column>
197
+              <el-table-column label="操作" width="120">
198
+                <template #default="scope">
199
+                  <el-button type="text" size="small" class="preview-btn">
200
+                    预览
201
+                  </el-button>
202
+                  <el-button type="text" size="small" class="delete-btn">
203
+                    删除
204
+                  </el-button>
205
+                </template>
206
+              </el-table-column>
207
+            </el-table>
208
+            <el-button type="text" icon="Plus" class="add-question-btn">
209
+              + 试题库中选题
210
+            </el-button>
211
+          </div>
212
+
213
+          <!-- 随机试题 -->
214
+          <div class="question-type-section">
215
+            <div class="section-title">随机试题</div>
216
+            <el-table :data="question.randomQuestions" style="width: 100%">
217
+              <el-table-column prop="type" label="试题类型" width="120" />
218
+              <el-table-column prop="count" label="试题数" width="100" />
219
+              <el-table-column prop="score" label="每道题分数" width="120">
220
+                <template #default="scope">
221
+                  <el-input-number
222
+                    v-model="scope.row.score"
223
+                    :min="0"
224
+                    :max="100"
225
+                    size="small"
226
+                    @change="calculateScore"
227
+                  />
228
+                </template>
229
+              </el-table-column>
230
+              <el-table-column label="操作" width="120">
231
+                <template #default="scope">
232
+                  <el-button type="text" size="small" class="preview-btn">
233
+                    预览
234
+                  </el-button>
235
+                  <el-button type="text" size="small" class="delete-btn">
236
+                    删除
237
+                  </el-button>
238
+                </template>
239
+              </el-table-column>
240
+            </el-table>
241
+            <el-button type="text" icon="Plus" class="add-question-btn">
242
+              + 试题库中选题
243
+            </el-button>
244
+          </div>
245
+
246
+          <div class="question-footer">
247
+            <span class="question-score"
248
+              >合计:{{ getQuestionTotalScore(index) }}分</span
249
+            >
250
+          </div>
251
+        </div>
252
+
253
+        <!-- 添加大题按钮 -->
254
+        <el-button
255
+          type="dashed"
256
+          icon="Plus"
257
+          class="add-big-question-btn"
258
+          @click="addBigQuestion"
259
+        >
260
+          + 添加大题
261
+        </el-button>
262
+      </div>
263
+    </div>
264
+  </div>
265
+</template>
266
+
267
+<style scoped>
268
+.exam-edit-container {
269
+  padding: 20px;
270
+  background-color: #f5f7fa;
271
+  min-height: 100vh;
272
+}
273
+
274
+/* 顶部区域 */
275
+.top-section {
276
+  display: flex;
277
+  justify-content: space-between;
278
+  align-items: flex-start;
279
+  margin-bottom: 20px;
280
+  background-color: white;
281
+  padding: 20px;
282
+  border-radius: 8px;
283
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
284
+}
285
+
286
+.left-section {
287
+  display: flex;
288
+  flex-direction: column;
289
+  gap: 20px;
290
+  flex: 1;
291
+}
292
+
293
+.form-item {
294
+  display: flex;
295
+  align-items: center;
296
+  gap: 10px;
297
+}
298
+
299
+.label {
300
+  font-size: 14px;
301
+  color: #606266;
302
+  min-width: 80px;
303
+  text-align: right;
304
+}
305
+
306
+.input {
307
+  width: 300px;
308
+}
309
+
310
+.select {
311
+  width: 300px;
312
+}
313
+
314
+.right-section {
315
+  display: flex;
316
+  flex-direction: column;
317
+  align-items: flex-end;
318
+  gap: 15px;
319
+}
320
+
321
+.stats {
322
+  display: flex;
323
+  align-items: center;
324
+  gap: 15px;
325
+  font-size: 14px;
326
+  color: #303133;
327
+}
328
+
329
+.separator {
330
+  color: #dcdfe6;
331
+  font-size: 16px;
332
+}
333
+
334
+.save-btn {
335
+  width: 100px;
336
+}
337
+
338
+/* 主体区域 */
339
+.main-section {
340
+  display: flex;
341
+  gap: 20px;
342
+  background-color: white;
343
+  padding: 20px;
344
+  border-radius: 8px;
345
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
346
+}
347
+
348
+/* 左侧导航 */
349
+.left-nav {
350
+  width: 250px;
351
+  border-right: 1px solid #e6e8eb;
352
+  padding-right: 20px;
353
+}
354
+
355
+.nav-title {
356
+  font-size: 16px;
357
+  font-weight: bold;
358
+  margin-bottom: 15px;
359
+  color: #303133;
360
+}
361
+
362
+.nav-item {
363
+  padding: 10px 15px;
364
+  margin-bottom: 10px;
365
+  border-radius: 4px;
366
+  cursor: pointer;
367
+  transition: all 0.3s;
368
+  background-color: #f5f7fa;
369
+}
370
+
371
+.nav-item:hover {
372
+  background-color: #ecf5ff;
373
+  color: #409eff;
374
+}
375
+
376
+.nav-item.active {
377
+  background-color: #ecf5ff;
378
+  color: #409eff;
379
+  border-left: 3px solid #409eff;
380
+}
381
+
382
+.new-question-input {
383
+  margin-top: 10px;
384
+}
385
+
386
+/* 右侧内容 */
387
+.right-content {
388
+  flex: 1;
389
+  padding-left: 20px;
390
+}
391
+
392
+/* 题目块 */
393
+.question-block {
394
+  margin-bottom: 30px;
395
+  padding: 20px;
396
+  background-color: #fafafa;
397
+  border-radius: 8px;
398
+}
399
+
400
+.question-header {
401
+  display: flex;
402
+  justify-content: space-between;
403
+  align-items: center;
404
+  margin-bottom: 20px;
405
+}
406
+
407
+.question-title {
408
+  font-size: 18px;
409
+  font-weight: bold;
410
+  color: #303133;
411
+}
412
+
413
+.edit-btn {
414
+  color: #409eff;
415
+}
416
+
417
+/* 题目类型区块 */
418
+.question-type-section {
419
+  margin-bottom: 20px;
420
+  padding: 15px;
421
+  background-color: white;
422
+  border-radius: 6px;
423
+}
424
+
425
+.section-title {
426
+  font-size: 16px;
427
+  font-weight: bold;
428
+  margin-bottom: 15px;
429
+  color: #303133;
430
+}
431
+
432
+.add-question-btn {
433
+  color: #409eff;
434
+  margin-top: 10px;
435
+}
436
+
437
+.question-footer {
438
+  text-align: right;
439
+  margin-top: 15px;
440
+  font-size: 14px;
441
+  color: #606266;
442
+}
443
+
444
+.question-score {
445
+  font-weight: bold;
446
+  color: #303133;
447
+}
448
+
449
+/* 添加大题按钮 */
450
+.add-big-question-btn {
451
+  width: 100%;
452
+  margin-top: 20px;
453
+}
454
+
455
+/* 操作按钮样式 */
456
+.preview-btn {
457
+  color: #409eff;
458
+  margin-right: 10px;
459
+}
460
+
461
+.delete-btn {
462
+  color: #f56c6c;
463
+}
464
+
465
+/* 响应式设计 */
466
+@media (max-width: 1200px) {
467
+  .input,
468
+  .select {
469
+    width: 250px;
470
+  }
471
+}
472
+
473
+@media (max-width: 768px) {
474
+  .top-section {
475
+    flex-direction: column;
476
+  }
477
+
478
+  .right-section {
479
+    align-items: flex-start;
480
+  }
481
+
482
+  .main-section {
483
+    flex-direction: column;
484
+  }
485
+
486
+  .left-nav {
487
+    width: 100%;
488
+    border-right: none;
489
+    border-bottom: 1px solid #e6e8eb;
490
+    padding-right: 0;
491
+    padding-bottom: 20px;
492
+    margin-bottom: 20px;
493
+  }
494
+
495
+  .right-content {
496
+    padding-left: 0;
497
+  }
498
+}
499
+</style>

+ 0 - 0
apps/web-ele/src/views/examManage/examPaper/cpns/examPreview.vue


Plik diff jest za duży
+ 467 - 1197
apps/web-ele/src/views/examManage/examPaper/index.vue


+ 583 - 0
apps/web-ele/src/views/examManage/questionBank/cpn/questionEdit/index.vue

@@ -0,0 +1,583 @@
1
+<template>
2
+  <div class="question-edit">
3
+    <div class="p-4 bg-white rounded-lg shadow-sm">
4
+      <div class="flex items-center justify-between mb-4">
5
+        <h2 class="text-lg font-semibold text-gray-800">
6
+          {{ id ? '编辑试题' : '新增试题' }}
7
+        </h2>
8
+      </div>
9
+      
10
+      <!-- 试题类型标签页 -->
11
+      <el-tabs v-model="activeTab" class="mb-6">
12
+        <el-tab-pane label="单选题" name="1">
13
+          <!-- 单选题内容 -->
14
+          <div class="space-y-6">
15
+            <!-- 试题属性 -->
16
+            <div class="p-4 rounded-lg">
17
+              <h3 class="text-md font-medium text-gray-700 mb-3">试题属性</h3>
18
+              <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
19
+                <el-form-item label="试题分类" label-width="80px">
20
+                  <el-select v-model="formData.category" placeholder="请选择试题分类">
21
+                    <el-option label="大类1" value="1" />
22
+                    <el-option label="大类2" value="2" />
23
+                    <el-option label="大类3" value="3" />
24
+                  </el-select>
25
+                </el-form-item>
26
+                <el-form-item label="试题难度" label-width="80px">
27
+                  <el-select v-model="formData.difficulty" placeholder="请选择试题难度">
28
+                    <el-option label="简单" value="1" />
29
+                    <el-option label="中等" value="2" />
30
+                    <el-option label="困难" value="3" />
31
+                  </el-select>
32
+                </el-form-item>
33
+              </div>
34
+            </div>
35
+            
36
+            <!-- 题目(题干内容) -->
37
+            <div>
38
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">题目(题干内容)</h3>
39
+              <TinymceEditor v-model="formData.content" :height="300" />
40
+            </div>
41
+            
42
+            <!-- 选项 -->
43
+            <div>
44
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">选项</h3>
45
+              <div class="space-y-3">
46
+                <div 
47
+                  v-for="(option, index) in formData.options" 
48
+                  :key="index"
49
+                  class="rounded-lg"
50
+                >
51
+                  <div class="items-start gap-2">
52
+                    <div class="flex items-center">
53
+                      <span class="ml-2 font-medium" style="margin-right: 15px;">{{ String.fromCharCode(65 + index) }}.</span>
54
+                      <el-radio 
55
+                        v-model="formData.correctAnswer" 
56
+                        :label="String.fromCharCode(65 + index)"
57
+                        class="mt-1"
58
+                      />
59
+                    </div>
60
+                    <div class="flex-1">
61
+                      <!-- 选项内容 -->
62
+                      <div class="mb-2">
63
+                        <TinymceEditor v-model="option.content" :height="200" />
64
+                      </div>
65
+                      <!-- 删除选项按钮 -->
66
+                      <div class="flex justify-end">
67
+                        <el-button 
68
+                          type="danger" 
69
+                          text 
70
+                          size="small"
71
+                          @click="removeOption(index)"
72
+                          :disabled="formData.options.length <= 2"
73
+                        >
74
+                          删除选项
75
+                        </el-button>
76
+                      </div>
77
+                    </div>
78
+                  </div>
79
+                </div>
80
+                <!-- 添加选项按钮 -->
81
+                <div class="flex justify-center">
82
+                  <el-button type="primary" plain @click="addOption">
83
+                    添加一个新选项
84
+                  </el-button>
85
+                </div>
86
+              </div>
87
+            </div>
88
+            
89
+            <!-- 答案解析 -->
90
+            <div>
91
+              <h3 class="text-md font-medium text-gray-700 mb-3"  style="border-left: 4px solid #007bff; padding-left: 10px;">答案解析</h3>
92
+              <div class="flex items-center gap-2 mb-2">
93
+                <el-button type="primary" plain size="small">
94
+                  引用
95
+                </el-button>
96
+              </div>
97
+              <TinymceEditor v-model="formData.analysis" :height="300" />
98
+            </div>
99
+            
100
+            <!-- 操作按钮 -->
101
+            <div class="flex justify-start gap-3 pt-4 border-t">
102
+              <el-button type="primary" @click="handleSaveAndContinue">
103
+                保存并继续添加
104
+              </el-button>
105
+              <el-button type="primary" plain @click="handleSaveAndClose">
106
+                保存并关闭
107
+              </el-button>
108
+            </div>
109
+          </div>
110
+        </el-tab-pane>
111
+        <el-tab-pane label="多选题" name="2">
112
+          <!-- 多选题内容 -->
113
+          <div class="space-y-6">
114
+            <!-- 试题属性 -->
115
+            <div class="p-4 rounded-lg">
116
+              <h3 class="text-md font-medium text-gray-700 mb-3">试题属性</h3>
117
+              <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
118
+                <el-form-item label="试题分类" label-width="80px">
119
+                  <el-select v-model="formData.category" placeholder="请选择试题分类">
120
+                    <el-option label="大类1" value="1" />
121
+                    <el-option label="大类2" value="2" />
122
+                    <el-option label="大类3" value="3" />
123
+                  </el-select>
124
+                </el-form-item>
125
+                <el-form-item label="试题难度" label-width="80px">
126
+                  <el-select v-model="formData.difficulty" placeholder="请选择试题难度">
127
+                    <el-option label="简单" value="1" />
128
+                    <el-option label="中等" value="2" />
129
+                    <el-option label="困难" value="3" />
130
+                  </el-select>
131
+                </el-form-item>
132
+              </div>
133
+            </div>
134
+            
135
+            <!-- 题目(题干内容) -->
136
+            <div>
137
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">题目(题干内容)</h3>
138
+              <TinymceEditor v-model="formData.content" :height="300" />
139
+            </div>
140
+            
141
+            <!-- 选项 -->
142
+            <div>
143
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">选项</h3>
144
+              <div class="space-y-3">
145
+                <div 
146
+                  v-for="(option, index) in formData.options" 
147
+                  :key="index"
148
+                  class="rounded-lg"
149
+                >
150
+                  <div class="items-start gap-2">
151
+                    <div class="flex items-center">
152
+                      <span class="ml-2 font-medium" style="margin-right: 15px;">{{ String.fromCharCode(65 + index) }}.</span>
153
+                      <el-checkbox 
154
+                        v-model="formData.correctAnswers" 
155
+                        :label="String.fromCharCode(65 + index)"
156
+                        class="mt-1"
157
+                      />
158
+                    </div>
159
+                    <div class="flex-1">
160
+                      <!-- 选项内容 -->
161
+                      <div class="mb-2">
162
+                        <TinymceEditor v-model="option.content" :height="200" />
163
+                      </div>
164
+                      <!-- 删除选项按钮 -->
165
+                      <div class="flex justify-end">
166
+                        <el-button 
167
+                          type="danger" 
168
+                          text 
169
+                          size="small"
170
+                          @click="removeOption(index)"
171
+                          :disabled="formData.options.length <= 2"
172
+                        >
173
+                          删除选项
174
+                        </el-button>
175
+                      </div>
176
+                    </div>
177
+                  </div>
178
+                </div>
179
+                <!-- 添加选项按钮 -->
180
+                <div class="flex justify-center">
181
+                  <el-button type="primary" plain @click="addOption">
182
+                    添加一个新选项
183
+                  </el-button>
184
+                </div>
185
+              </div>
186
+            </div>
187
+            
188
+            <!-- 答案解析 -->
189
+            <div>
190
+              <h3 class="text-md font-medium text-gray-700 mb-3"  style="border-left: 4px solid #007bff; padding-left: 10px;">答案解析</h3>
191
+              <div class="flex items-center gap-2 mb-2">
192
+                <el-button type="primary" plain size="small">
193
+                  引用
194
+                </el-button>
195
+              </div>
196
+              <TinymceEditor v-model="formData.analysis" :height="300" />
197
+            </div>
198
+            
199
+            <!-- 操作按钮 -->
200
+            <div class="flex justify-start gap-3 pt-4 border-t">
201
+              <el-button type="primary" @click="handleSaveAndContinue">
202
+                保存并继续添加
203
+              </el-button>
204
+              <el-button type="primary" plain @click="handleSaveAndClose">
205
+                保存并关闭
206
+              </el-button>
207
+            </div>
208
+          </div>
209
+        </el-tab-pane>
210
+        <el-tab-pane label="判断题" name="3">
211
+          <!-- 判断题内容 -->
212
+          <div class="space-y-6">
213
+            <!-- 试题属性 -->
214
+            <div class="p-4 rounded-lg">
215
+              <h3 class="text-md font-medium text-gray-700 mb-3">试题属性</h3>
216
+              <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
217
+                <el-form-item label="试题分类" label-width="80px">
218
+                  <el-select v-model="formData.category" placeholder="请选择试题分类">
219
+                    <el-option label="大类1" value="1" />
220
+                    <el-option label="大类2" value="2" />
221
+                    <el-option label="大类3" value="3" />
222
+                  </el-select>
223
+                </el-form-item>
224
+                <el-form-item label="试题难度" label-width="80px">
225
+                  <el-select v-model="formData.difficulty" placeholder="请选择试题难度">
226
+                    <el-option label="简单" value="1" />
227
+                    <el-option label="中等" value="2" />
228
+                    <el-option label="困难" value="3" />
229
+                  </el-select>
230
+                </el-form-item>
231
+              </div>
232
+            </div>
233
+            
234
+            <!-- 题目(题干内容) -->
235
+            <div>
236
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">题目(题干内容)</h3>
237
+              <TinymceEditor v-model="formData.content" :height="300" />
238
+            </div>
239
+            
240
+            <!-- 选项 -->
241
+            <div>
242
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">选项</h3>
243
+              <div class="space-y-3">
244
+                <!-- 正确选项 -->
245
+                <div class="rounded-lg">
246
+                  <div class="items-start gap-2">
247
+                    <div class="flex items-center">
248
+                      <el-radio v-model="formData.correctAnswer" label="正确" class="mt-1" />
249
+                       <el-radio v-model="formData.correctAnswer" label="错误" class="mt-1" />
250
+                    </div>
251
+                    <div class="flex-1">
252
+                      <!-- 选项内容 -->
253
+                      <div class="mb-2">
254
+                        <TinymceEditor v-model="formData.options[0].content" :height="200" />
255
+                      </div>
256
+                    </div>
257
+                  </div>
258
+                </div>
259
+              </div>
260
+            </div>
261
+            
262
+            <!-- 答案解析 -->
263
+            <div>
264
+              <h3 class="text-md font-medium text-gray-700 mb-3"  style="border-left: 4px solid #007bff; padding-left: 10px;">答案解析</h3>
265
+              <div class="flex items-center gap-2 mb-2">
266
+                <el-button type="primary" plain size="small">
267
+                  引用
268
+                </el-button>
269
+              </div>
270
+              <TinymceEditor v-model="formData.analysis" :height="300" />
271
+            </div>
272
+            
273
+            <!-- 操作按钮 -->
274
+            <div class="flex justify-start gap-3 pt-4 border-t">
275
+              <el-button type="primary" @click="handleSaveAndContinue">
276
+                保存并继续添加
277
+              </el-button>
278
+              <el-button type="primary" plain @click="handleSaveAndClose">
279
+                保存并关闭
280
+              </el-button>
281
+            </div>
282
+          </div>
283
+        </el-tab-pane>
284
+        <el-tab-pane label="填空题" name="4">
285
+          <!-- 填空题内容 -->
286
+          <div class="space-y-6">
287
+            <!-- 试题属性 -->
288
+            <div class="p-4 rounded-lg">
289
+              <h3 class="text-md font-medium text-gray-700 mb-3">试题属性</h3>
290
+              <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
291
+                <el-form-item label="试题分类" label-width="80px">
292
+                  <el-select v-model="formData.category" placeholder="请选择试题分类">
293
+                    <el-option label="大类1" value="1" />
294
+                    <el-option label="大类2" value="2" />
295
+                    <el-option label="大类3" value="3" />
296
+                  </el-select>
297
+                </el-form-item>
298
+                <el-form-item label="试题难度" label-width="80px">
299
+                  <el-select v-model="formData.difficulty" placeholder="请选择试题难度">
300
+                    <el-option label="简单" value="1" />
301
+                    <el-option label="中等" value="2" />
302
+                    <el-option label="困难" value="3" />
303
+                  </el-select>
304
+                </el-form-item>
305
+              </div>
306
+            </div>
307
+            
308
+            <!-- 题目(题干内容) -->
309
+            <div>
310
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">题目(题干内容)</h3>
311
+              <TinymceEditor v-model="formData.content" :height="300" />
312
+            </div>
313
+            
314
+            <!-- 答案 -->
315
+            <div>
316
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">答案</h3>
317
+              <div class="space-y-3">
318
+                <div v-for="(blank, index) in formData.blanks" :key="index" class="flex items-center space-x-2">
319
+                  <span class="text-gray-600">填空{{ index + 1 }}:</span>
320
+                  <el-input 
321
+                    v-model="blank.answer" 
322
+                    placeholder="请输入答案" 
323
+                    class="flex-1"
324
+                  />
325
+                  <el-button 
326
+                    type="danger" 
327
+                    size="small" 
328
+                    :icon="Delete" 
329
+                    @click="removeBlank(index)"
330
+                    :disabled="formData.blanks.length <= 2"
331
+                  />
332
+                </div>
333
+                <el-button 
334
+                  type="primary" 
335
+                  size="small" 
336
+                  :icon="Plus" 
337
+                  @click="addBlank"
338
+                >
339
+                  添加填空
340
+                </el-button>
341
+              </div>
342
+            </div>
343
+            
344
+            <!-- 答案解析 -->
345
+            <div>
346
+              <h3 class="text-md font-medium text-gray-700 mb-3"  style="border-left: 4px solid #007bff; padding-left: 10px;">答案解析</h3>
347
+              <div class="flex items-center gap-2 mb-2">
348
+                <el-button type="primary" plain size="small">
349
+                  引用
350
+                </el-button>
351
+              </div>
352
+              <TinymceEditor v-model="formData.analysis" :height="300" />
353
+            </div>
354
+            
355
+            <!-- 操作按钮 -->
356
+            <div class="flex justify-start gap-3 pt-4 border-t">
357
+              <el-button type="primary" @click="handleSaveAndContinue">
358
+                保存并继续添加
359
+              </el-button>
360
+              <el-button type="primary" plain @click="handleSaveAndClose">
361
+                保存并关闭
362
+              </el-button>
363
+            </div>
364
+          </div>
365
+        </el-tab-pane>
366
+        <el-tab-pane label="简答题" name="5">
367
+          <!-- 简答题内容 -->
368
+          <div class="space-y-6">
369
+            <!-- 试题属性 -->
370
+            <div class="p-4 rounded-lg">
371
+              <h3 class="text-md font-medium text-gray-700 mb-3">试题属性</h3>
372
+              <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
373
+                <el-form-item label="试题分类" label-width="80px">
374
+                  <el-select v-model="formData.category" placeholder="请选择试题分类">
375
+                    <el-option label="大类1" value="1" />
376
+                    <el-option label="大类2" value="2" />
377
+                    <el-option label="大类3" value="3" />
378
+                  </el-select>
379
+                </el-form-item>
380
+                <el-form-item label="试题难度" label-width="80px">
381
+                  <el-select v-model="formData.difficulty" placeholder="请选择试题难度">
382
+                    <el-option label="简单" value="1" />
383
+                    <el-option label="中等" value="2" />
384
+                    <el-option label="困难" value="3" />
385
+                  </el-select>
386
+                </el-form-item>
387
+              </div>
388
+            </div>
389
+            
390
+            <!-- 题目(题干内容) -->
391
+            <div>
392
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">题目(题干内容)</h3>
393
+              <TinymceEditor v-model="formData.content" :height="300" />
394
+            </div>
395
+            
396
+            <!-- 答案 -->
397
+            <div>
398
+              <h3 class="text-md font-medium text-gray-700 mb-3" style="border-left: 4px solid #007bff; padding-left: 10px;">答案</h3>
399
+              <div class="space-y-3">
400
+                <div>
401
+                  <TinymceEditor v-model="formData.correctAnswer" :height="200" />
402
+                </div>
403
+              </div>
404
+            </div>
405
+            
406
+            <!-- 答案解析 -->
407
+            <div>
408
+              <h3 class="text-md font-medium text-gray-700 mb-3"  style="border-left: 4px solid #007bff; padding-left: 10px;">答案解析</h3>
409
+              <div class="flex items-center gap-2 mb-2">
410
+                <el-button type="primary" plain size="small">
411
+                  引用
412
+                </el-button>
413
+              </div>
414
+              <TinymceEditor v-model="formData.analysis" :height="300" />
415
+            </div>
416
+            
417
+            <!-- 操作按钮 -->
418
+            <div class="flex justify-start gap-3 pt-4 border-t">
419
+              <el-button type="primary" @click="handleSaveAndContinue">
420
+                保存并继续添加
421
+              </el-button>
422
+              <el-button type="primary" plain @click="handleSaveAndClose">
423
+                保存并关闭
424
+              </el-button>
425
+            </div>
426
+          </div>
427
+        </el-tab-pane>
428
+      </el-tabs>
429
+    </div>
430
+  </div>
431
+</template>
432
+
433
+<script setup lang="ts">
434
+import { ref, onMounted } from 'vue';
435
+import { useRoute, useRouter } from 'vue-router';
436
+import TinymceEditor from '#/components/tinymce/src/editor.vue';
437
+import { Plus, Delete } from '@element-plus/icons-vue';
438
+
439
+const route = useRoute();
440
+const router = useRouter();
441
+const id = route.params.id as string;
442
+
443
+// 激活的标签页
444
+const activeTab = ref('1');
445
+
446
+// 表单数据
447
+const formData = ref({
448
+  category: '',
449
+  difficulty: '',
450
+  content: '',
451
+  options: [
452
+    { content: '' },
453
+    { content: '' }
454
+  ],
455
+  blanks: [
456
+    { answer: '' },
457
+    { answer: '' }
458
+  ],
459
+  correctAnswer: 'A',
460
+  correctAnswers: ['A'],
461
+  analysis: ''
462
+});
463
+
464
+// 添加选项
465
+const addOption = () => {
466
+  formData.value.options.push({ content: '' });
467
+};
468
+
469
+// 删除选项
470
+const removeOption = (index: number) => {
471
+  if (formData.value.options.length > 2) {
472
+    const optionLabel = String.fromCharCode(65 + index);
473
+    formData.value.options.splice(index, 1);
474
+    // 如果删除的是当前选中的答案,重新设置选中第一个选项
475
+    if (optionLabel === formData.value.correctAnswer) {
476
+      formData.value.correctAnswer = 'A';
477
+    }
478
+    // 如果删除的是多选题的正确答案之一,从数组中移除
479
+    if (formData.value.correctAnswers) {
480
+      formData.value.correctAnswers = formData.value.correctAnswers.filter(
481
+        answer => answer !== optionLabel
482
+      );
483
+    }
484
+  }
485
+};
486
+
487
+// 添加填空
488
+const addBlank = () => {
489
+  formData.value.blanks.push({ answer: '' });
490
+};
491
+
492
+// 删除填空
493
+const removeBlank = (index: number) => {
494
+  if (formData.value.blanks.length > 2) {
495
+    formData.value.blanks.splice(index, 1);
496
+  }
497
+};
498
+
499
+// 加载试题数据
500
+const loadQuestionData = async () => {
501
+  if (id) {
502
+    // 这里应该调用API获取试题详情
503
+    // 暂时使用模拟数据
504
+    formData.value = {
505
+      category: '1',
506
+      difficulty: '2',
507
+      content: '这是一道单选题测试题,用于展示编辑功能。',
508
+      options: [
509
+        { content: '选项A内容' },
510
+        { content: '选项B内容' },
511
+        { content: '选项C内容' }
512
+      ],
513
+      correctAnswer: 'B',
514
+      analysis: '这是答案解析,解释为什么选择B选项。'
515
+    };
516
+  }
517
+};
518
+
519
+// 保存并继续添加
520
+const handleSaveAndContinue = async () => {
521
+  // 这里应该调用API保存试题
522
+  console.log('保存试题:', formData.value);
523
+  // 保存成功后重置表单
524
+  formData.value = {
525
+    category: formData.value.category, // 保持分类和难度不变
526
+    difficulty: formData.value.difficulty,
527
+    content: '',
528
+    options: [
529
+      { content: '' },
530
+      { content: '' }
531
+    ],
532
+    blanks: [
533
+      { answer: '' },
534
+      { answer: '' }
535
+    ],
536
+    correctAnswer: 'A',
537
+    correctAnswers: ['A'],
538
+    analysis: ''
539
+  };
540
+};
541
+
542
+// 保存并关闭
543
+const handleSaveAndClose = async () => {
544
+  // 这里应该调用API保存试题
545
+  console.log('保存试题:', formData.value);
546
+  // 保存成功后返回上一页
547
+  router.push('/examManage/questionBank');
548
+};
549
+
550
+onMounted(() => {
551
+  loadQuestionData();
552
+});
553
+</script>
554
+
555
+<style scoped>
556
+.question-edit {
557
+  min-height: 100vh;
558
+  background-color: #f5f7fa;
559
+  padding: 20px;
560
+}
561
+
562
+/* 富文本编辑器样式 */
563
+:deep(.el-textarea) {
564
+  font-family: inherit;
565
+}
566
+
567
+:deep(.el-textarea__inner) {
568
+  border: none;
569
+  resize: none;
570
+  padding: 0;
571
+  font-size: 14px;
572
+  line-height: 1.5;
573
+}
574
+
575
+:deep(.el-textarea__inner:focus) {
576
+  box-shadow: none;
577
+}
578
+</style>
579
+
580
+
581
+
582
+
583
+

+ 118 - 0
apps/web-ele/src/views/examManage/questionBank/cpn/questionPreview/index.vue

@@ -0,0 +1,118 @@
1
+<template>
2
+  <div class="question-preview">
3
+    <div class="p-4 bg-white rounded-lg shadow-sm">
4
+      <div class="flex items-center justify-between mb-4">
5
+        <h2 class="text-lg font-semibold text-gray-800">
6
+          试题预览
7
+        </h2>
8
+        <el-button @click="handleBack">返回列表</el-button>
9
+      </div>
10
+      
11
+      <!-- 试题预览内容 -->
12
+      <div v-if="questionData" class="question-content">
13
+        <div class="mb-4">
14
+          <h3 class="text-lg font-medium text-gray-900 mb-2">
15
+            试题内容
16
+          </h3>
17
+          <p class="text-gray-700">{{ questionData.content }}</p>
18
+        </div>
19
+        
20
+        <div class="mb-4">
21
+          <h3 class="text-lg font-medium text-gray-900 mb-2">
22
+            试题信息
23
+          </h3>
24
+          <el-descriptions :column="2" border>
25
+            <el-descriptions-item label="试题类型">
26
+              {{ getQuestionTypeText(questionData.questionType) }}
27
+            </el-descriptions-item>
28
+            <el-descriptions-item label="难度等级">
29
+              {{ getDifficultyText(questionData.difficulty) }}
30
+            </el-descriptions-item>
31
+            <el-descriptions-item label="所属章节">
32
+              {{ questionData.chapterName }}
33
+            </el-descriptions-item>
34
+            <el-descriptions-item label="分值">
35
+              {{ questionData.score }}分
36
+            </el-descriptions-item>
37
+          </el-descriptions>
38
+        </div>
39
+      </div>
40
+      
41
+      <div v-else class="text-center py-10 text-gray-500">
42
+        加载中...
43
+      </div>
44
+    </div>
45
+  </div>
46
+</template>
47
+
48
+<script setup lang="ts">
49
+import { ref, onMounted } from 'vue';
50
+import { useRoute, useRouter } from 'vue-router';
51
+
52
+const route = useRoute();
53
+const router = useRouter();
54
+const id = route.params.id as string;
55
+
56
+// 试题数据
57
+const questionData = ref(null);
58
+
59
+// 加载试题数据
60
+const loadQuestionData = async () => {
61
+  if (id) {
62
+    // 这里应该调用API获取试题详情
63
+    // 暂时使用模拟数据
64
+    questionData.value = {
65
+      id: id,
66
+      content: '这是一道测试题,用于预览功能展示。',
67
+      questionType: '1',
68
+      difficulty: '2',
69
+      chapterId: '1',
70
+      chapterName: '第一章 基础知识',
71
+      score: 10,
72
+    };
73
+  }
74
+};
75
+
76
+// 获取试题类型文本
77
+const getQuestionTypeText = (type) => {
78
+  const typeMap = {
79
+    '1': '单选题',
80
+    '2': '多选题',
81
+    '3': '判断题',
82
+    '4': '填空题',
83
+    '5': '简答题',
84
+  };
85
+  return typeMap[type] || '未知类型';
86
+};
87
+
88
+// 获取难度等级文本
89
+const getDifficultyText = (difficulty) => {
90
+  const difficultyMap = {
91
+    '1': '简单',
92
+    '2': '中等',
93
+    '3': '困难',
94
+  };
95
+  return difficultyMap[difficulty] || '未知难度';
96
+};
97
+
98
+// 返回列表
99
+const handleBack = () => {
100
+  router.push('/examManage/questionBank');
101
+};
102
+
103
+onMounted(() => {
104
+  loadQuestionData();
105
+});
106
+</script>
107
+
108
+<style scoped>
109
+.question-preview {
110
+  min-height: 100vh;
111
+  background-color: #f5f7fa;
112
+  padding: 20px;
113
+}
114
+
115
+.question-content {
116
+  line-height: 1.6;
117
+}
118
+</style>

Plik diff jest za duży
+ 504 - 1465
apps/web-ele/src/views/examManage/questionBank/index.vue


+ 0 - 812
apps/web-ele/src/views/examManage/questionBank/index.vue.bak

@@ -1,812 +0,0 @@
1
-<script lang="ts" setup>
2
-// 首先导入必要的依赖
3
-import { Page } from '@vben/common-ui';
4
-import { ref, onMounted, onUnmounted } from 'vue';
5
-
6
-// 定义分类接口
7
-interface Category {
8
-  id: number;
9
-  name: string;
10
-  updateTime: string;
11
-  count: number;
12
-}
13
-
14
-// 然后定义并初始化题库分类数据
15
-const categories = ref<Category[]>([
16
-  { id: 1, name: '人员管理题库', updateTime: '2025-11-11', count: 199 },
17
-  { id: 2, name: '运营管理题库', updateTime: '2025-11-11', count: 199 },
18
-  { id: 3, name: '消防知识题库', updateTime: '2025-11-11', count: 199 },
19
-  { id: 4, name: '日常操作题库', updateTime: '2025-11-11', count: 199 },
20
-  { id: 5, name: '安全管理题库', updateTime: '2025-11-11', count: 199 },
21
-  { id: 6, name: '质量管理题库', updateTime: '2025-11-11', count: 199 },
22
-  { id: 7, name: '设备维护题库', updateTime: '2025-11-11', count: 199 },
23
-  { id: 8, name: '环境管理题库', updateTime: '2025-11-11', count: 199 }
24
-]);
25
-
26
-// 模拟题目数据
27
-const questions = ref([
28
-  {
29
-    id: 1,
30
-    type: 'single_choice',
31
-    content: '1.题目题目题目题目题目题目题目题目题目题目。',
32
-    options: ['选项A', '选项B', '选项C', '选项D'],
33
-    answer: '选项A'
34
-  },
35
-  {
36
-    id: 2,
37
-    type: 'single_choice',
38
-    content: '2.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
39
-    options: ['选项A', '选项B', '选项C', '选项D'],
40
-    answer: '选项A'
41
-  },
42
-  {
43
-    id: 3,
44
-    type: 'multiple_choice',
45
-    content: '3.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
46
-    options: ['选项A', '选项B', '选项C', '选项D'],
47
-    answer: '选项A、选项B、选项C'
48
-  },
49
-  {
50
-    id: 4,
51
-    type: 'multiple_choice',
52
-    content: '4.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
53
-    options: ['选项A', '选项B', '选项C', '选项D'],
54
-    answer: '选项A、选项B、选项C'
55
-  },
56
-  {
57
-    id: 5,
58
-    type: 'fill_blank',
59
-    content: '5.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
60
-    answer: '答案答案答案答案答案答案答案答案答案 ______ 答案答案答案答案答案答案答案答案答案答案答案答案。'
61
-  },
62
-  {
63
-    id: 6,
64
-    type: 'fill_blank',
65
-    content: '6.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
66
-    answer: '答案答案答案答案答案答案答案 ______、______、______ 答案答案答案答案答案答案答案。'
67
-  },
68
-  {
69
-    id: 7,
70
-    type: 'short_answer',
71
-    content: '7.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
72
-    answer: '答案答案答案答案答案...'
73
-  }
74
-]);
75
-
76
-// 分页数据
77
-const currentPage = ref(1);
78
-const pageSize = ref(7);
79
-const total = ref(50);
80
-
81
-// 填空题替换方法
82
-const replaceBlanks = (content : string) => {
83
-  return content.replace(/______/g, '<input type="text" class="blank-input" />');
84
-};
85
-
86
-// 分页方法
87
-const handlePageChange = (page: number) => {
88
-  currentPage.value = page;
89
-  // 这里可以添加加载数据的逻辑
90
-};
91
-
92
-// 弹窗相关
93
-const isModalVisible = ref(false);
94
-const modalTitle = ref('');
95
-const currentQuestion = ref(null);
96
-
97
-// 选中的题库分类
98
-const selectedCategory = ref(1);
99
-
100
-// 选择题库分类
101
-const selectCategory = (id: number) => {
102
-  selectedCategory.value = id;
103
-};
104
-
105
-// 获取题型标签
106
-const getQuestionTypeLabel = (type: string): string => {
107
-  const typeMap: any = {
108
-    'single_choice': '(单项选择题)',
109
-    'multiple_choice': '(多项选择题)',
110
-    'fill_blank': '(填空题)',
111
-    'short_answer': '(简答题)'
112
-  };
113
-  return typeMap[type] || '';
114
-};
115
-
116
-// 打开新增题目弹窗
117
-const openAddModal = () => {
118
-  modalTitle.value = '添加题目';
119
-  currentQuestion.value = null;
120
-  isModalVisible.value = true;
121
-};
122
-
123
-// 打开编辑题目弹窗
124
-const openEditModal = (question: any) => {
125
-  modalTitle.value = '编辑题目';
126
-  currentQuestion.value = { ...question };
127
-  isModalVisible.value = true;
128
-};
129
-
130
-// 关闭弹窗
131
-const closeModal = () => {
132
-  isModalVisible.value = false;
133
-  currentQuestion.value = null;
134
-};
135
-
136
-// 滚动加载相关
137
-const categoryListRef = ref<HTMLElement | null>(null);
138
-const isLoading = ref(false);
139
-const hasMore = ref(true);
140
-
141
-// 滚动事件处理
142
-const handleScroll = (e: Event) => {
143
-  const target = e.target as HTMLElement;
144
-  const { scrollTop, scrollHeight, clientHeight } = target;
145
-  
146
-  // 当滚动到底部20px内时触发加载
147
-  if (scrollTop + clientHeight >= scrollHeight - 20 && !isLoading.value && hasMore.value) {
148
-    loadMoreCategories();
149
-  }
150
-};
151
-
152
-// 加载更多分类
153
-const loadMoreCategories = async () => {
154
-  if (isLoading.value || !hasMore.value) return;
155
-  
156
-  isLoading.value = true;
157
-  
158
-  // 模拟异步加载
159
-  await new Promise(resolve => setTimeout(resolve, 1000));
160
-  
161
-  // 添加新的模拟数据
162
-  const newCategories = Array.from({ length: 4 }, (_, index) => ({
163
-    id: categories.value.length + index + 1,
164
-    name: `新题库${categories.value.length + index + 1}`,
165
-    updateTime: '2025-11-11',
166
-    count: 199
167
-  }));
168
-  
169
-  categories.value = [...categories.value, ...newCategories];
170
-  
171
-  // 模拟没有更多数据
172
-  if (categories.value.length >= 20) {
173
-    hasMore.value = false;
174
-  }
175
-  
176
-  isLoading.value = false;
177
-};
178
-
179
-// 组件挂载时添加滚动监听
180
-onMounted(() => {
181
-  if (categoryListRef.value) {
182
-    categoryListRef.value.addEventListener('scroll', handleScroll);
183
-  }
184
-});
185
-
186
-// 组件卸载时移除滚动监听
187
-onUnmounted(() => {
188
-  if (categoryListRef.value) {
189
-    categoryListRef.value.removeEventListener('scroll', handleScroll);
190
-  }
191
-});
192
-
193
-
194
-</script>
195
-
196
-<template>
197
-  <Page>
198
-    <div class="question-bank-container">
199
-      <!-- 左侧题库分类 -->
200
-      <div class="left-panel">
201
-        <div class="panel-header">
202
-          <h2>题库分类</h2>
203
-          <button class="add-btn">添加题目类型</button>
204
-        </div>
205
-        <div class="category-list" ref="categoryListRef">
206
-          <div 
207
-            v-for="category in categories" 
208
-            :key="category.id" 
209
-            class="category-card"
210
-            :class="{ active: selectedCategory === category.id }"
211
-            @click="selectCategory(category.id)"
212
-          >
213
-            <div class="category-name">{{ category.name }}</div>
214
-            <div class="category-info">
215
-              <span>更新时间: {{ category.updateTime }}</span>
216
-              <span>题库数量: {{ category.count }}</span>
217
-            </div>
218
-          </div>
219
-          
220
-          <!-- 加载更多提示 -->
221
-          <div v-if="isLoading" class="loading">加载中...</div>
222
-          <div v-else-if="!hasMore" class="no-more">没有更多数据了</div>
223
-        </div>
224
-      </div>
225
-      
226
-      <!-- 右侧题目列表 -->
227
-      <div class="right-panel">
228
-        <div class="panel-header">
229
-          <h2>题目列表</h2>
230
-          <button class="add-btn">添加题目</button>
231
-        </div>
232
-        <div class="question-list">
233
-          <div 
234
-            v-for="question in questions" 
235
-            :key="question.id" 
236
-            class="question-item"
237
-          >
238
-            <!-- 题目左侧内容 -->
239
-            <div class="question-content">
240
-              <div class="question-text" :data-type="getQuestionTypeLabel(question.type)">{{ question.content }}</div>
241
-              
242
-              <!-- 单选题 -->
243
-              <div v-if="question.type === 'single_choice'" class="options">
244
-                <div v-for="(option, index) in question.options" :key="index" class="option-item">
245
-                  <input type="radio" :id="`q${question.id}_option${index}`" :name="`q${question.id}`" />
246
-                  <label :for="`q${question.id}_option${index}`">{{ option }}</label>
247
-                </div>
248
-              </div>
249
-              
250
-              <!-- 多选题 -->
251
-              <div v-else-if="question.type === 'multiple_choice'" class="options">
252
-                <div v-for="(option, index) in question.options" :key="index" class="option-item">
253
-                  <input type="checkbox" :id="`q${question.id}_option${index}`" />
254
-                  <label :for="`q${question.id}_option${index}`">{{ option }}</label>
255
-                </div>
256
-              </div>
257
-              
258
-              <!-- 填空题 -->
259
-              <div v-else-if="question.type === 'fill_blank'" class="fill-blank">
260
-                 <div v-html="replaceBlanks(question.content)"></div>
261
-              </div>
262
-              
263
-              <!-- 简答题 -->
264
-              <div v-else-if="question.type === 'short_answer'" class="short-answer">
265
-                <textarea class="answer-input" rows="4" placeholder="请输入答案"></textarea>
266
-              </div>
267
-            </div>
268
-            
269
-            <!-- 题目右侧内容 -->
270
-            <div class="question-right">
271
-              <div class="answer">
272
-                <span class="answer-label">答案:</span>
273
-                <span class="answer-content">{{ question.answer }}</span>
274
-              </div>
275
-              
276
-              <div class="question-actions">
277
-                <button class="edit-btn" @click="openEditModal(question)">编辑</button>
278
-                <button class="quote-btn">引用</button>
279
-                <button class="delete-btn">删除</button>
280
-              </div>
281
-            </div>
282
-          </div>
283
-          
284
-          <!-- 分页 -->
285
-          <div class="pagination">
286
-            <span class="total">共 {{ total }} 条</span>
287
-            <div class="page-numbers">
288
-              <button class="page-btn" :disabled="currentPage === 1">&lt;</button>
289
-              <button 
290
-                v-for="page in 5" 
291
-                :key="page" 
292
-                class="page-btn" 
293
-                :class="{ active: page === currentPage }"
294
-                @click="handlePageChange(page)"
295
-              >
296
-                {{ page }}
297
-              </button>
298
-              <button class="page-btn">&gt;</button>
299
-            </div>
300
-            <div class="page-input">
301
-              <span>跳至</span>
302
-              <input type="number" class="page-input-box" :value="currentPage" min="1" :max="Math.ceil(total/pageSize)" />
303
-              <span>页</span>
304
-            </div>
305
-          </div>
306
-        </div>
307
-      </div>
308
-    </div>
309
-    
310
-    <!-- 弹窗组件 -->
311
-    <div v-if="isModalVisible" class="modal-overlay">
312
-      <div class="modal">
313
-        <div class="modal-header">
314
-          <h3>{{ modalTitle }}</h3>
315
-          <button class="close-btn" @click="closeModal">&times;</button>
316
-        </div>
317
-        <div class="modal-body">
318
-          <!-- 弹窗内容先为空,后续可以根据需要添加表单 -->
319
-          <div class="empty-content">
320
-            <p>题目编辑表单将在这里实现</p>
321
-          </div>
322
-        </div>
323
-        <div class="modal-footer">
324
-          <button class="cancel-btn" @click="closeModal">取消</button>
325
-          <button class="confirm-btn">确定</button>
326
-        </div>
327
-      </div>
328
-    </div>
329
-  </Page>
330
-</template>
331
-
332
-<style lang="scss" scoped>
333
-.question-bank-container {
334
-  display: grid;
335
-  grid-template-columns: 300px 1fr;
336
-  gap: 20px;
337
-  padding: 16px;
338
-  height: calc(100vh - 80px);
339
-  overflow: hidden;
340
-}
341
-
342
-.left-panel,
343
-.right-panel {
344
-  background-color: #ffffff;
345
-  border-radius: 6px;
346
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
347
-  display: flex;
348
-  flex-direction: column;
349
-}
350
-
351
-.panel-header {
352
-  display: flex;
353
-  justify-content: space-between;
354
-  align-items: center;
355
-  padding: 16px;
356
-  border-bottom: 1px solid #e8e8e8;
357
-}
358
-
359
-.panel-header h2 {
360
-  margin: 0;
361
-  font-size: 16px;
362
-  font-weight: 600;
363
-  color: #333;
364
-}
365
-
366
-.add-btn {
367
-  padding: 6px 16px;
368
-  background-color: #1890ff;
369
-  color: white;
370
-  border: none;
371
-  border-radius: 4px;
372
-  cursor: pointer;
373
-  font-size: 14px;
374
-  transition: background-color 0.3s;
375
-  
376
-  &:hover {
377
-    background-color: #40a9ff;
378
-  }
379
-}
380
-
381
-.category-list,
382
-.question-list {
383
-  flex: 1;
384
-  overflow-y: auto;
385
-  padding: 16px;
386
-}
387
-
388
-.category-card {
389
-  background-color: #f5f5f5;
390
-  border-radius: 6px;
391
-  padding: 12px;
392
-  margin-bottom: 12px;
393
-  cursor: pointer;
394
-  transition: all 0.3s;
395
-  border: 2px solid transparent;
396
-  
397
-  &:hover {
398
-    background-color: #e6f7ff;
399
-    box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
400
-  }
401
-  
402
-  &.active {
403
-    background-color: #e6f7ff;
404
-    border-color: #1890ff;
405
-    box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
406
-  }
407
-}
408
-
409
-.category-name {
410
-  font-weight: 600;
411
-  color: #333;
412
-  margin-bottom: 8px;
413
-  font-size: 14px;
414
-}
415
-
416
-.category-info {
417
-  display: flex;
418
-  flex-direction: column;
419
-  font-size: 12px;
420
-  color: #666;
421
-  gap: 4px;
422
-}
423
-
424
-.loading,
425
-.no-more {
426
-  text-align: center;
427
-  padding: 12px;
428
-  color: #999;
429
-  font-size: 14px;
430
-}
431
-
432
-/* 题目列表样式 */
433
-.question-item {
434
-  display: flex;
435
-  border: 1px solid #e8e8e8;
436
-  border-radius: 6px;
437
-  margin-bottom: 16px;
438
-  overflow: hidden;
439
-  background-color: #fff;
440
-  transition: box-shadow 0.3s;
441
-  
442
-  &:hover {
443
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
444
-  }
445
-}
446
-
447
-.question-content {
448
-  flex: 1;
449
-  padding: 16px;
450
-  border-right: 1px solid #e8e8e8;
451
-}
452
-
453
-.question-text {
454
-  font-size: 14px;
455
-  line-height: 1.5;
456
-  margin-bottom: 12px;
457
-  color: #333;
458
-  
459
-  /* 添加题型标签 */
460
-  &::after {
461
-    content: attr(data-type);
462
-    font-size: 12px;
463
-    padding: 2px 8px;
464
-    border-radius: 4px;
465
-    background-color: #e8f4ff;
466
-    color: #1890ff;
467
-    margin-left: 8px;
468
-  }
469
-}
470
-
471
-.options {
472
-  display: flex;
473
-  gap: 50px;
474
-}
475
-
476
-.option-item {
477
-  display: flex;
478
-  align-items: center;
479
-  gap: 8px;
480
-  font-size: 14px;
481
-  color: #666;
482
-}
483
-
484
-.fill-blank {
485
-  margin-top: 8px;
486
-  
487
-  .blank-input {
488
-    width: 100px;
489
-    margin: 0 4px;
490
-    padding: 4px 8px;
491
-    border: 1px solid #d9d9d9;
492
-    border-radius: 4px;
493
-    font-size: 14px;
494
-  }
495
-}
496
-
497
-.short-answer {
498
-  margin-top: 8px;
499
-  
500
-  .answer-input {
501
-    width: 100%;
502
-    padding: 8px;
503
-    border: 1px solid #d9d9d9;
504
-    border-radius: 4px;
505
-    font-size: 14px;
506
-    resize: vertical;
507
-  }
508
-}
509
-
510
-.question-right {
511
-  width: 300px;
512
-  padding: 16px;
513
-  display: flex;
514
-  flex-direction: column;
515
-  justify-content: space-between;
516
-  background-color: #fafafa;
517
-}
518
-
519
-.answer {
520
-  margin-bottom: 16px;
521
-  
522
-  .answer-label {
523
-    font-weight: 600;
524
-    color: #333;
525
-    font-size: 14px;
526
-  }
527
-  
528
-  .answer-content {
529
-    color: #666;
530
-    font-size: 14px;
531
-    word-break: break-word;
532
-  }
533
-}
534
-
535
-.question-actions {
536
-  display: flex;
537
-  gap: 8px;
538
-}
539
-
540
-.edit-btn,
541
-.quote-btn,
542
-.delete-btn {
543
-  padding: 6px 12px;
544
-  border: none;
545
-  border-radius: 4px;
546
-  cursor: pointer;
547
-  font-size: 14px;
548
-  transition: background-color 0.3s;
549
-}
550
-
551
-.edit-btn {
552
-  background-color: #1890ff;
553
-  color: white;
554
-  
555
-  &:hover {
556
-    background-color: #40a9ff;
557
-  }
558
-}
559
-
560
-.quote-btn {
561
-  background-color: #52c41a;
562
-  color: white;
563
-  
564
-  &:hover {
565
-    background-color: #73d13d;
566
-  }
567
-}
568
-
569
-.delete-btn {
570
-  background-color: #ff4d4f;
571
-  color: white;
572
-  
573
-  &:hover {
574
-    background-color: #ff7875;
575
-  }
576
-}
577
-
578
-/* 分页样式 */
579
-.pagination {
580
-  display: flex;
581
-  justify-content: space-between;
582
-  align-items: center;
583
-  padding: 16px 0;
584
-  border-top: 1px solid #e8e8e8;
585
-  margin-top: 16px;
586
-}
587
-
588
-.total {
589
-  font-size: 14px;
590
-  color: #666;
591
-}
592
-
593
-.page-numbers {
594
-  display: flex;
595
-  gap: 4px;
596
-}
597
-
598
-.page-btn {
599
-  padding: 4px 12px;
600
-  border: 1px solid #d9d9d9;
601
-  background-color: white;
602
-  border-radius: 4px;
603
-  cursor: pointer;
604
-  font-size: 14px;
605
-  transition: all 0.3s;
606
-  
607
-  &:hover {
608
-    border-color: #1890ff;
609
-    color: #1890ff;
610
-  }
611
-  
612
-  &.active {
613
-    background-color: #1890ff;
614
-    color: white;
615
-    border-color: #1890ff;
616
-  }
617
-  
618
-  &:disabled {
619
-    opacity: 0.5;
620
-    cursor: not-allowed;
621
-    
622
-    &:hover {
623
-      border-color: #d9d9d9;
624
-      color: #666;
625
-    }
626
-  }
627
-}
628
-
629
-.page-input {
630
-  display: flex;
631
-  align-items: center;
632
-  gap: 8px;
633
-  font-size: 14px;
634
-  color: #666;
635
-  
636
-  .page-input-box {
637
-    width: 50px;
638
-    padding: 4px 8px;
639
-    border: 1px solid #d9d9d9;
640
-    border-radius: 4px;
641
-    font-size: 14px;
642
-    text-align: center;
643
-  }
644
-}
645
-
646
-/* 滚动条样式优化 */
647
-.category-list::-webkit-scrollbar,
648
-.question-list::-webkit-scrollbar,
649
-.modal::-webkit-scrollbar {
650
-  width: 8px;
651
-  height: 8px;
652
-}
653
-
654
-.category-list::-webkit-scrollbar-track,
655
-.question-list::-webkit-scrollbar-track,
656
-.modal::-webkit-scrollbar-track {
657
-  background: #f1f1f1;
658
-  border-radius: 4px;
659
-}
660
-
661
-.category-list::-webkit-scrollbar-thumb,
662
-.question-list::-webkit-scrollbar-thumb,
663
-.modal::-webkit-scrollbar-thumb {
664
-  background: #c1c1c1;
665
-  border-radius: 4px;
666
-}
667
-
668
-.category-list::-webkit-scrollbar-thumb:hover,
669
-.question-list::-webkit-scrollbar-thumb:hover,
670
-.modal::-webkit-scrollbar-thumb:hover {
671
-  background: #a8a8a8;
672
-}
673
-
674
-/* 弹窗样式 */
675
-.modal-overlay {
676
-  position: fixed;
677
-  top: 0;
678
-  left: 0;
679
-  right: 0;
680
-  bottom: 0;
681
-  background-color: rgba(0, 0, 0, 0.5);
682
-  display: flex;
683
-  justify-content: center;
684
-  align-items: center;
685
-  z-index: 1000;
686
-  animation: fadeIn 0.3s ease;
687
-}
688
-
689
-.modal {
690
-  background-color: white;
691
-  border-radius: 8px;
692
-  width: 600px;
693
-  max-width: 90%;
694
-  max-height: 90vh;
695
-  overflow-y: auto;
696
-  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
697
-  animation: slideUp 0.3s ease;
698
-}
699
-
700
-/* 动画效果 */
701
-@keyframes fadeIn {
702
-  from { opacity: 0; }
703
-  to { opacity: 1; }
704
-}
705
-
706
-@keyframes slideUp {
707
-  from {
708
-    opacity: 0;
709
-    transform: translateY(20px);
710
-  }
711
-  to {
712
-    opacity: 1;
713
-    transform: translateY(0);
714
-  }
715
-}
716
-
717
-.modal {
718
-  background-color: white;
719
-  border-radius: 8px;
720
-  width: 600px;
721
-  max-width: 90%;
722
-  max-height: 90vh;
723
-  overflow-y: auto;
724
-  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
725
-}
726
-
727
-.modal-header {
728
-  display: flex;
729
-  justify-content: space-between;
730
-  align-items: center;
731
-  padding: 16px 20px;
732
-  border-bottom: 1px solid #e8e8e8;
733
-}
734
-
735
-.modal-header h3 {
736
-  margin: 0;
737
-  font-size: 18px;
738
-  font-weight: 600;
739
-  color: #333;
740
-}
741
-
742
-.close-btn {
743
-  background: none;
744
-  border: none;
745
-  font-size: 24px;
746
-  color: #999;
747
-  cursor: pointer;
748
-  padding: 0;
749
-  width: 32px;
750
-  height: 32px;
751
-  display: flex;
752
-  align-items: center;
753
-  justify-content: center;
754
-  border-radius: 4px;
755
-  transition: all 0.3s;
756
-  
757
-  &:hover {
758
-    background-color: #f5f5f5;
759
-    color: #666;
760
-  }
761
-}
762
-
763
-.modal-body {
764
-  padding: 20px;
765
-}
766
-
767
-.empty-content {
768
-  text-align: center;
769
-  padding: 40px 20px;
770
-  color: #999;
771
-  font-size: 14px;
772
-}
773
-
774
-.modal-footer {
775
-  display: flex;
776
-  justify-content: flex-end;
777
-  gap: 12px;
778
-  padding: 16px 20px;
779
-  border-top: 1px solid #e8e8e8;
780
-  background-color: #fafafa;
781
-}
782
-
783
-.cancel-btn,
784
-.confirm-btn {
785
-  padding: 8px 20px;
786
-  border: 1px solid #d9d9d9;
787
-  border-radius: 4px;
788
-  cursor: pointer;
789
-  font-size: 14px;
790
-  transition: all 0.3s;
791
-}
792
-
793
-.cancel-btn {
794
-  background-color: white;
795
-  color: #666;
796
-  
797
-  &:hover {
798
-    border-color: #1890ff;
799
-    color: #1890ff;
800
-  }
801
-}
802
-
803
-.confirm-btn {
804
-  background-color: #1890ff;
805
-  color: white;
806
-  border-color: #1890ff;
807
-  
808
-  &:hover {
809
-    background-color: #40a9ff;
810
-  }
811
-}
812
-</style>

+ 0 - 810
apps/web-ele/src/views/examManage/questionBank/index.vue.old

@@ -1,810 +0,0 @@
1
-<script setup>
2
-// 首先导入必要的依赖
3
-import { Page } from '@vben/common-ui';
4
-import { ref, onMounted, onUnmounted } from 'vue';
5
-
6
-// 定义并初始化题库分类数据
7
-const categories = ref([
8
-  { id: 1, name: '人员管理题库', updateTime: '2025-11-11', count: 199 },
9
-  { id: 2, name: '运营管理题库', updateTime: '2025-11-11', count: 199 },
10
-  { id: 3, name: '消防知识题库', updateTime: '2025-11-11', count: 199 },
11
-  { id: 4, name: '日常操作题库', updateTime: '2025-11-11', count: 199 },
12
-  { id: 5, name: '安全管理题库', updateTime: '2025-11-11', count: 199 },
13
-  { id: 6, name: '质量管理题库', updateTime: '2025-11-11', count: 199 },
14
-  { id: 7, name: '设备维护题库', updateTime: '2025-11-11', count: 199 },
15
-  { id: 8, name: '环境管理题库', updateTime: '2025-11-11', count: 199 }
16
-]);
17
-
18
-// 模拟题目数据
19
-const questions = ref([
20
-  {
21
-    id: 1,
22
-    type: 'single_choice',
23
-    content: '1.题目题目题目题目题目题目题目题目题目题目。',
24
-    options: ['选项A', '选项B', '选项C', '选项D'],
25
-    answer: '选项A'
26
-  },
27
-  {
28
-    id: 2,
29
-    type: 'single_choice',
30
-    content: '2.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
31
-    options: ['选项A', '选项B', '选项C', '选项D'],
32
-    answer: '选项A'
33
-  },
34
-  {
35
-    id: 3,
36
-    type: 'multiple_choice',
37
-    content: '3.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
38
-    options: ['选项A', '选项B', '选项C', '选项D'],
39
-    answer: '选项A、选项B、选项C'
40
-  },
41
-  {
42
-    id: 4,
43
-    type: 'multiple_choice',
44
-    content: '4.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
45
-    options: ['选项A', '选项B', '选项C', '选项D'],
46
-    answer: '选项A、选项B、选项C'
47
-  },
48
-  {
49
-    id: 5,
50
-    type: 'fill_blank',
51
-    content: '5.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
52
-    answer: '答案答案答案答案答案答案答案答案答案 ______ 答案答案答案答案答案答案答案答案答案答案答案答案。'
53
-  },
54
-  {
55
-    id: 6,
56
-    type: 'fill_blank',
57
-    content: '6.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
58
-    answer: '答案答案答案答案答案答案答案 ______、______、______ 答案答案答案答案答案答案答案。'
59
-  },
60
-  {
61
-    id: 7,
62
-    type: 'short_answer',
63
-    content: '7.题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目。',
64
-    answer: '答案答案答案答案答案...'
65
-  }
66
-]);
67
-
68
-// 分页数据
69
-const currentPage = ref(1);
70
-const pageSize = ref(7);
71
-const total = ref(50);
72
-
73
-// 填空题替换方法
74
-const replaceBlanks = (content) => {
75
-  return content.replace(/______/g, '<input type="text" class="blank-input" />');
76
-};
77
-
78
-// 分页方法
79
-const handlePageChange = (page) => {
80
-  currentPage.value = page;
81
-  // 这里可以添加加载数据的逻辑
82
-};
83
-
84
-// 弹窗相关
85
-const isModalVisible = ref(false);
86
-const modalTitle = ref('');
87
-const currentQuestion = ref(null);
88
-
89
-// 选中的题库分类
90
-const selectedCategory = ref(1);
91
-
92
-// 选择题库分类
93
-const selectCategory = (id) => {
94
-  selectedCategory.value = id;
95
-};
96
-
97
-// 获取题型标签
98
-const getQuestionTypeLabel = (type) => {
99
-  const typeMap = {
100
-    'single_choice': '(单项选择题)',
101
-    'multiple_choice': '(多项选择题)',
102
-    'fill_blank': '(填空题)',
103
-    'short_answer': '(简答题)'
104
-  };
105
-  return typeMap[type] || '';
106
-};
107
-
108
-// 打开新增题目弹窗
109
-const openAddModal = () => {
110
-  modalTitle.value = '添加题目';
111
-  currentQuestion.value = null;
112
-  isModalVisible.value = true;
113
-};
114
-
115
-// 打开编辑题目弹窗
116
-const openEditModal = (question: any) => {
117
-  modalTitle.value = '编辑题目';
118
-  currentQuestion.value = { ...question };
119
-  isModalVisible.value = true;
120
-};
121
-
122
-// 关闭弹窗
123
-const closeModal = () => {
124
-  isModalVisible.value = false;
125
-  currentQuestion.value = null;
126
-};
127
-
128
-// 滚动加载相关
129
-const categoryListRef = ref<HTMLElement | null>(null);
130
-const isLoading = ref(false);
131
-const hasMore = ref(true);
132
-
133
-// 滚动事件处理
134
-const handleScroll = (e: Event) => {
135
-  const target = e.target as HTMLElement;
136
-  const { scrollTop, scrollHeight, clientHeight } = target;
137
-  
138
-  // 当滚动到底部20px内时触发加载
139
-  if (scrollTop + clientHeight >= scrollHeight - 20 && !isLoading.value && hasMore.value) {
140
-    loadMoreCategories();
141
-  }
142
-};
143
-
144
-// 加载更多分类
145
-const loadMoreCategories = async () => {
146
-  if (isLoading.value || !hasMore.value) return;
147
-  
148
-  isLoading.value = true;
149
-  
150
-  // 模拟异步加载
151
-  await new Promise(resolve => setTimeout(resolve, 1000));
152
-  
153
-  // 添加新的模拟数据
154
-  const newCategories = Array.from({ length: 4 }, (_, index) => ({
155
-    id: categories.value.length + index + 1,
156
-    name: `新题库${categories.value.length + index + 1}`,
157
-    updateTime: '2025-11-11',
158
-    count: 199
159
-  }));
160
-  
161
-  categories.value = [...categories.value, ...newCategories];
162
-  
163
-  // 模拟没有更多数据
164
-  if (categories.value.length >= 20) {
165
-    hasMore.value = false;
166
-  }
167
-  
168
-  isLoading.value = false;
169
-};
170
-
171
-// 组件挂载时添加滚动监听
172
-onMounted(() => {
173
-  if (categoryListRef.value) {
174
-    categoryListRef.value.addEventListener('scroll', handleScroll);
175
-  }
176
-});
177
-
178
-// 组件卸载时移除滚动监听
179
-onUnmounted(() => {
180
-  if (categoryListRef.value) {
181
-    categoryListRef.value.removeEventListener('scroll', handleScroll);
182
-  }
183
-});
184
-
185
-// 显式暴露变量给模板
186
-defineExpose({
187
-  categories,
188
-  selectedCategory,
189
-  selectCategory
190
-});
191
-
192
-</script>
193
-
194
-<template>
195
-  <Page>
196
-    <div class="question-bank-container">
197
-      <!-- 左侧题库分类 -->
198
-      <div class="left-panel">
199
-        <div class="panel-header">
200
-          <h2>题库分类</h2>
201
-          <button class="add-btn">添加题目类型</button>
202
-        </div>
203
-        <div class="category-list" ref="categoryListRef">
204
-          <div 
205
-            v-for="category in categories"
206
-            :key="category.id" 
207
-            class="category-card"
208
-            :class="{ active: selectedCategory === category.id }"
209
-            @click="selectCategory(category.id)"
210
-          >
211
-            <div class="category-name">{{ category.name }}</div>
212
-            <div class="category-info">
213
-              <span>更新时间: {{ category.updateTime }}</span>
214
-              <span>题库数量: {{ category.count }}</span>
215
-            </div>
216
-          </div>
217
-          
218
-          <!-- 加载更多提示 -->
219
-          <div v-if="isLoading" class="loading">加载中...</div>
220
-          <div v-else-if="!hasMore" class="no-more">没有更多数据了</div>
221
-        </div>
222
-      </div>
223
-      
224
-      <!-- 右侧题目列表 -->
225
-      <div class="right-panel">
226
-        <div class="panel-header">
227
-          <h2>题目列表</h2>
228
-          <button class="add-btn">添加题目</button>
229
-        </div>
230
-        <div class="question-list">
231
-          <div 
232
-            v-for="question in questions" 
233
-            :key="question.id" 
234
-            class="question-item"
235
-          >
236
-            <!-- 题目左侧内容 -->
237
-            <div class="question-content">
238
-              <div class="question-text" :data-type="getQuestionTypeLabel(question.type)">{{ question.content }}</div>
239
-              
240
-              <!-- 单选题 -->
241
-              <div v-if="question.type === 'single_choice'" class="options">
242
-                <div v-for="(option, index) in question.options" :key="index" class="option-item">
243
-                  <input type="radio" :id="`q${question.id}_option${index}`" :name="`q${question.id}`" />
244
-                  <label :for="`q${question.id}_option${index}`">{{ option }}</label>
245
-                </div>
246
-              </div>
247
-              
248
-              <!-- 多选题 -->
249
-              <div v-else-if="question.type === 'multiple_choice'" class="options">
250
-                <div v-for="(option, index) in question.options" :key="index" class="option-item">
251
-                  <input type="checkbox" :id="`q${question.id}_option${index}`" />
252
-                  <label :for="`q${question.id}_option${index}`">{{ option }}</label>
253
-                </div>
254
-              </div>
255
-              
256
-              <!-- 填空题 -->
257
-              <div v-else-if="question.type === 'fill_blank'" class="fill-blank">
258
-                 <div v-html="replaceBlanks(question.content)"></div>
259
-              </div>
260
-              
261
-              <!-- 简答题 -->
262
-              <div v-else-if="question.type === 'short_answer'" class="short-answer">
263
-                <textarea class="answer-input" rows="4" placeholder="请输入答案"></textarea>
264
-              </div>
265
-            </div>
266
-            
267
-            <!-- 题目右侧内容 -->
268
-            <div class="question-right">
269
-              <div class="answer">
270
-                <span class="answer-label">答案:</span>
271
-                <span class="answer-content">{{ question.answer }}</span>
272
-              </div>
273
-              
274
-              <div class="question-actions">
275
-                <button class="edit-btn" @click="openEditModal(question)">编辑</button>
276
-                <button class="quote-btn">引用</button>
277
-                <button class="delete-btn">删除</button>
278
-              </div>
279
-            </div>
280
-          </div>
281
-          
282
-          <!-- 分页 -->
283
-          <div class="pagination">
284
-            <span class="total">共 {{ total }} 条</span>
285
-            <div class="page-numbers">
286
-              <button class="page-btn" :disabled="currentPage === 1">&lt;</button>
287
-              <button 
288
-                v-for="page in 5" 
289
-                :key="page" 
290
-                class="page-btn" 
291
-                :class="{ active: page === currentPage }"
292
-                @click="handlePageChange(page)"
293
-              >
294
-                {{ page }}
295
-              </button>
296
-              <button class="page-btn">&gt;</button>
297
-            </div>
298
-            <div class="page-input">
299
-              <span>跳至</span>
300
-              <input type="number" class="page-input-box" :value="currentPage" min="1" :max="Math.ceil(total/pageSize)" />
301
-              <span>页</span>
302
-            </div>
303
-          </div>
304
-        </div>
305
-      </div>
306
-    </div>
307
-    
308
-    <!-- 弹窗组件 -->
309
-    <div v-if="isModalVisible" class="modal-overlay">
310
-      <div class="modal">
311
-        <div class="modal-header">
312
-          <h3>{{ modalTitle }}</h3>
313
-          <button class="close-btn" @click="closeModal">&times;</button>
314
-        </div>
315
-        <div class="modal-body">
316
-          <!-- 弹窗内容先为空,后续可以根据需要添加表单 -->
317
-          <div class="empty-content">
318
-            <p>题目编辑表单将在这里实现</p>
319
-          </div>
320
-        </div>
321
-        <div class="modal-footer">
322
-          <button class="cancel-btn" @click="closeModal">取消</button>
323
-          <button class="confirm-btn">确定</button>
324
-        </div>
325
-      </div>
326
-    </div>
327
-  </Page>
328
-</template>
329
-
330
-<style lang="scss" scoped>
331
-.question-bank-container {
332
-  display: grid;
333
-  grid-template-columns: 300px 1fr;
334
-  gap: 20px;
335
-  padding: 16px;
336
-  height: calc(100vh - 80px);
337
-  overflow: hidden;
338
-}
339
-
340
-.left-panel,
341
-.right-panel {
342
-  background-color: #ffffff;
343
-  border-radius: 6px;
344
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
345
-  display: flex;
346
-  flex-direction: column;
347
-}
348
-
349
-.panel-header {
350
-  display: flex;
351
-  justify-content: space-between;
352
-  align-items: center;
353
-  padding: 16px;
354
-  border-bottom: 1px solid #e8e8e8;
355
-}
356
-
357
-.panel-header h2 {
358
-  margin: 0;
359
-  font-size: 16px;
360
-  font-weight: 600;
361
-  color: #333;
362
-}
363
-
364
-.add-btn {
365
-  padding: 6px 16px;
366
-  background-color: #1890ff;
367
-  color: white;
368
-  border: none;
369
-  border-radius: 4px;
370
-  cursor: pointer;
371
-  font-size: 14px;
372
-  transition: background-color 0.3s;
373
-  
374
-  &:hover {
375
-    background-color: #40a9ff;
376
-  }
377
-}
378
-
379
-.category-list,
380
-.question-list {
381
-  flex: 1;
382
-  overflow-y: auto;
383
-  padding: 16px;
384
-}
385
-
386
-.category-card {
387
-  background-color: #f5f5f5;
388
-  border-radius: 6px;
389
-  padding: 12px;
390
-  margin-bottom: 12px;
391
-  cursor: pointer;
392
-  transition: all 0.3s;
393
-  border: 2px solid transparent;
394
-  
395
-  &:hover {
396
-    background-color: #e6f7ff;
397
-    box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
398
-  }
399
-  
400
-  &.active {
401
-    background-color: #e6f7ff;
402
-    border-color: #1890ff;
403
-    box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
404
-  }
405
-}
406
-
407
-.category-name {
408
-  font-weight: 600;
409
-  color: #333;
410
-  margin-bottom: 8px;
411
-  font-size: 14px;
412
-}
413
-
414
-.category-info {
415
-  display: flex;
416
-  flex-direction: column;
417
-  font-size: 12px;
418
-  color: #666;
419
-  gap: 4px;
420
-}
421
-
422
-.loading,
423
-.no-more {
424
-  text-align: center;
425
-  padding: 12px;
426
-  color: #999;
427
-  font-size: 14px;
428
-}
429
-
430
-/* 题目列表样式 */
431
-.question-item {
432
-  display: flex;
433
-  border: 1px solid #e8e8e8;
434
-  border-radius: 6px;
435
-  margin-bottom: 16px;
436
-  overflow: hidden;
437
-  background-color: #fff;
438
-  transition: box-shadow 0.3s;
439
-  
440
-  &:hover {
441
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
442
-  }
443
-}
444
-
445
-.question-content {
446
-  flex: 1;
447
-  padding: 16px;
448
-  border-right: 1px solid #e8e8e8;
449
-}
450
-
451
-.question-text {
452
-  font-size: 14px;
453
-  line-height: 1.5;
454
-  margin-bottom: 12px;
455
-  color: #333;
456
-  
457
-  /* 添加题型标签 */
458
-  &::after {
459
-    content: attr(data-type);
460
-    font-size: 12px;
461
-    padding: 2px 8px;
462
-    border-radius: 4px;
463
-    background-color: #e8f4ff;
464
-    color: #1890ff;
465
-    margin-left: 8px;
466
-  }
467
-}
468
-
469
-.options {
470
-  display: flex;
471
-  gap: 50px;
472
-}
473
-
474
-.option-item {
475
-  display: flex;
476
-  align-items: center;
477
-  gap: 8px;
478
-  font-size: 14px;
479
-  color: #666;
480
-}
481
-
482
-.fill-blank {
483
-  margin-top: 8px;
484
-  
485
-  .blank-input {
486
-    width: 100px;
487
-    margin: 0 4px;
488
-    padding: 4px 8px;
489
-    border: 1px solid #d9d9d9;
490
-    border-radius: 4px;
491
-    font-size: 14px;
492
-  }
493
-}
494
-
495
-.short-answer {
496
-  margin-top: 8px;
497
-  
498
-  .answer-input {
499
-    width: 100%;
500
-    padding: 8px;
501
-    border: 1px solid #d9d9d9;
502
-    border-radius: 4px;
503
-    font-size: 14px;
504
-    resize: vertical;
505
-  }
506
-}
507
-
508
-.question-right {
509
-  width: 300px;
510
-  padding: 16px;
511
-  display: flex;
512
-  flex-direction: column;
513
-  justify-content: space-between;
514
-  background-color: #fafafa;
515
-}
516
-
517
-.answer {
518
-  margin-bottom: 16px;
519
-  
520
-  .answer-label {
521
-    font-weight: 600;
522
-    color: #333;
523
-    font-size: 14px;
524
-  }
525
-  
526
-  .answer-content {
527
-    color: #666;
528
-    font-size: 14px;
529
-    word-break: break-word;
530
-  }
531
-}
532
-
533
-.question-actions {
534
-  display: flex;
535
-  gap: 8px;
536
-}
537
-
538
-.edit-btn,
539
-.quote-btn,
540
-.delete-btn {
541
-  padding: 6px 12px;
542
-  border: none;
543
-  border-radius: 4px;
544
-  cursor: pointer;
545
-  font-size: 14px;
546
-  transition: background-color 0.3s;
547
-}
548
-
549
-.edit-btn {
550
-  background-color: #1890ff;
551
-  color: white;
552
-  
553
-  &:hover {
554
-    background-color: #40a9ff;
555
-  }
556
-}
557
-
558
-.quote-btn {
559
-  background-color: #52c41a;
560
-  color: white;
561
-  
562
-  &:hover {
563
-    background-color: #73d13d;
564
-  }
565
-}
566
-
567
-.delete-btn {
568
-  background-color: #ff4d4f;
569
-  color: white;
570
-  
571
-  &:hover {
572
-    background-color: #ff7875;
573
-  }
574
-}
575
-
576
-/* 分页样式 */
577
-.pagination {
578
-  display: flex;
579
-  justify-content: space-between;
580
-  align-items: center;
581
-  padding: 16px 0;
582
-  border-top: 1px solid #e8e8e8;
583
-  margin-top: 16px;
584
-}
585
-
586
-.total {
587
-  font-size: 14px;
588
-  color: #666;
589
-}
590
-
591
-.page-numbers {
592
-  display: flex;
593
-  gap: 4px;
594
-}
595
-
596
-.page-btn {
597
-  padding: 4px 12px;
598
-  border: 1px solid #d9d9d9;
599
-  background-color: white;
600
-  border-radius: 4px;
601
-  cursor: pointer;
602
-  font-size: 14px;
603
-  transition: all 0.3s;
604
-  
605
-  &:hover {
606
-    border-color: #1890ff;
607
-    color: #1890ff;
608
-  }
609
-  
610
-  &.active {
611
-    background-color: #1890ff;
612
-    color: white;
613
-    border-color: #1890ff;
614
-  }
615
-  
616
-  &:disabled {
617
-    opacity: 0.5;
618
-    cursor: not-allowed;
619
-    
620
-    &:hover {
621
-      border-color: #d9d9d9;
622
-      color: #666;
623
-    }
624
-  }
625
-}
626
-
627
-.page-input {
628
-  display: flex;
629
-  align-items: center;
630
-  gap: 8px;
631
-  font-size: 14px;
632
-  color: #666;
633
-  
634
-  .page-input-box {
635
-    width: 50px;
636
-    padding: 4px 8px;
637
-    border: 1px solid #d9d9d9;
638
-    border-radius: 4px;
639
-    font-size: 14px;
640
-    text-align: center;
641
-  }
642
-}
643
-
644
-/* 滚动条样式优化 */
645
-.category-list::-webkit-scrollbar,
646
-.question-list::-webkit-scrollbar,
647
-.modal::-webkit-scrollbar {
648
-  width: 8px;
649
-  height: 8px;
650
-}
651
-
652
-.category-list::-webkit-scrollbar-track,
653
-.question-list::-webkit-scrollbar-track,
654
-.modal::-webkit-scrollbar-track {
655
-  background: #f1f1f1;
656
-  border-radius: 4px;
657
-}
658
-
659
-.category-list::-webkit-scrollbar-thumb,
660
-.question-list::-webkit-scrollbar-thumb,
661
-.modal::-webkit-scrollbar-thumb {
662
-  background: #c1c1c1;
663
-  border-radius: 4px;
664
-}
665
-
666
-.category-list::-webkit-scrollbar-thumb:hover,
667
-.question-list::-webkit-scrollbar-thumb:hover,
668
-.modal::-webkit-scrollbar-thumb:hover {
669
-  background: #a8a8a8;
670
-}
671
-
672
-/* 弹窗样式 */
673
-.modal-overlay {
674
-  position: fixed;
675
-  top: 0;
676
-  left: 0;
677
-  right: 0;
678
-  bottom: 0;
679
-  background-color: rgba(0, 0, 0, 0.5);
680
-  display: flex;
681
-  justify-content: center;
682
-  align-items: center;
683
-  z-index: 1000;
684
-  animation: fadeIn 0.3s ease;
685
-}
686
-
687
-.modal {
688
-  background-color: white;
689
-  border-radius: 8px;
690
-  width: 600px;
691
-  max-width: 90%;
692
-  max-height: 90vh;
693
-  overflow-y: auto;
694
-  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
695
-  animation: slideUp 0.3s ease;
696
-}
697
-
698
-/* 动画效果 */
699
-@keyframes fadeIn {
700
-  from { opacity: 0; }
701
-  to { opacity: 1; }
702
-}
703
-
704
-@keyframes slideUp {
705
-  from {
706
-    opacity: 0;
707
-    transform: translateY(20px);
708
-  }
709
-  to {
710
-    opacity: 1;
711
-    transform: translateY(0);
712
-  }
713
-}
714
-
715
-.modal {
716
-  background-color: white;
717
-  border-radius: 8px;
718
-  width: 600px;
719
-  max-width: 90%;
720
-  max-height: 90vh;
721
-  overflow-y: auto;
722
-  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
723
-}
724
-
725
-.modal-header {
726
-  display: flex;
727
-  justify-content: space-between;
728
-  align-items: center;
729
-  padding: 16px 20px;
730
-  border-bottom: 1px solid #e8e8e8;
731
-}
732
-
733
-.modal-header h3 {
734
-  margin: 0;
735
-  font-size: 18px;
736
-  font-weight: 600;
737
-  color: #333;
738
-}
739
-
740
-.close-btn {
741
-  background: none;
742
-  border: none;
743
-  font-size: 24px;
744
-  color: #999;
745
-  cursor: pointer;
746
-  padding: 0;
747
-  width: 32px;
748
-  height: 32px;
749
-  display: flex;
750
-  align-items: center;
751
-  justify-content: center;
752
-  border-radius: 4px;
753
-  transition: all 0.3s;
754
-  
755
-  &:hover {
756
-    background-color: #f5f5f5;
757
-    color: #666;
758
-  }
759
-}
760
-
761
-.modal-body {
762
-  padding: 20px;
763
-}
764
-
765
-.empty-content {
766
-  text-align: center;
767
-  padding: 40px 20px;
768
-  color: #999;
769
-  font-size: 14px;
770
-}
771
-
772
-.modal-footer {
773
-  display: flex;
774
-  justify-content: flex-end;
775
-  gap: 12px;
776
-  padding: 16px 20px;
777
-  border-top: 1px solid #e8e8e8;
778
-  background-color: #fafafa;
779
-}
780
-
781
-.cancel-btn,
782
-.confirm-btn {
783
-  padding: 8px 20px;
784
-  border: 1px solid #d9d9d9;
785
-  border-radius: 4px;
786
-  cursor: pointer;
787
-  font-size: 14px;
788
-  transition: all 0.3s;
789
-}
790
-
791
-.cancel-btn {
792
-  background-color: white;
793
-  color: #666;
794
-  
795
-  &:hover {
796
-    border-color: #1890ff;
797
-    color: #1890ff;
798
-  }
799
-}
800
-
801
-.confirm-btn {
802
-  background-color: #1890ff;
803
-  color: white;
804
-  border-color: #1890ff;
805
-  
806
-  &:hover {
807
-    background-color: #40a9ff;
808
-  }
809
-}
810
-</style>

Plik diff jest za duży
+ 0 - 1574
apps/web-ele/src/views/examManage/questionBank/index2.vue


+ 0 - 31
apps/web-ele/src/views/examManage/questionBank/test.js

@@ -1,31 +0,0 @@
1
-import { ref } from 'vue';
2
-
3
-// 定义并初始化题库分类数据
4
-const categories = ref([
5
-  { id: 1, name: '人员管理题库', updateTime: '2025-11-11', count: 199 },
6
-  { id: 2, name: '运营管理题库', updateTime: '2025-11-11', count: 199 },
7
-  { id: 3, name: '消防知识题库', updateTime: '2025-11-11', count: 199 },
8
-  { id: 4, name: '日常操作题库', updateTime: '2025-11-11', count: 199 },
9
-  { id: 5, name: '安全管理题库', updateTime: '2025-11-11', count: 199 },
10
-  { id: 6, name: '质量管理题库', updateTime: '2025-11-11', count: 199 },
11
-  { id: 7, name: '设备维护题库', updateTime: '2025-11-11', count: 199 },
12
-  { id: 8, name: '环境管理题库', updateTime: '2025-11-11', count: 199 }
13
-]);
14
-
15
-// 选择题库分类
16
-const selectCategory = (id) => {
17
-  console.log('Selected category:', id);
18
-};
19
-
20
-// 获取题型标签
21
-const getQuestionTypeLabel = (type) => {
22
-  const typeMap = {
23
-    'single_choice': '(单项选择题)',
24
-    'multiple_choice': '(多项选择题)',
25
-    'fill_blank': '(填空题)',
26
-    'short_answer': '(简答题)'
27
-  };
28
-  return typeMap[type] || '';
29
-};
30
-
31
-console.log('Test completed successfully!');