miaofuhao %!s(int64=2) %!d(string=před) týdny
rodič
revize
00500dc256

+ 2 - 1
apps/web-ele/.env.analyze

@@ -2,6 +2,7 @@
2 2
 VITE_BASE=/
3 3
 
4 4
 # Basic interface address SPA
5
-VITE_GLOB_API_URL=/api
5
+# VITE_GLOB_API_URL=/api
6
+VITE_GLOB_API_URL=http://39.164.159.226:8088
6 7
 
7 8
 VITE_VISUALIZER=true

+ 2 - 1
apps/web-ele/.env.production

@@ -1,7 +1,8 @@
1 1
 VITE_BASE=/
2 2
 
3 3
 # 接口地址
4
-VITE_GLOB_API_URL=/api
4
+# VITE_GLOB_API_URL=/api
5
+VITE_GLOB_API_URL=http://39.164.159.226:8088
5 6
 
6 7
 # 是否开启压缩,可以设置为 none, brotli, gzip
7 8
 VITE_COMPRESS=none

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

@@ -82,7 +82,7 @@ const localRoutes: RouteRecordStringComponent[] = [
82 82
     path: '/examManage/questionBank/questionPreview/:id',
83 83
   },
84 84
   {
85
-    component: '/examManage/examPaper/cpn/examEdit/index',
85
+    component: '/examManage/examPaper/cpn/examEdit',
86 86
     meta: {
87 87
       activePath: '/examManage/examPaper',
88 88
       icon: 'carbon:edit',
@@ -93,7 +93,7 @@ const localRoutes: RouteRecordStringComponent[] = [
93 93
     path: '/examManage/examEdit/:id?',
94 94
   },
95 95
   {
96
-    component: '/examManage/examPaper/cpn/examPreview/index',
96
+    component: '/examManage/examPaper/cpn/examPreview',
97 97
     meta: {
98 98
       activePath: '/examManage/examPaper',
99 99
       icon: 'carbon:view',

+ 948 - 0
apps/web-ele/src/views/examManage/examPaper/cpn/examEdit.vue

@@ -0,0 +1,948 @@
1
+<script setup lang="ts">
2
+import { ref, computed } from 'vue';
3
+import { ElAnchor, ElAnchorLink, ElCollapse, ElCollapseItem, ElTable } from 'element-plus';
4
+import ExamPreview from './examPreview.vue';
5
+
6
+// 试卷表单数据
7
+const examForm = ref({
8
+  examName: '',
9
+  examCategory: '',
10
+});
11
+
12
+// 试卷题目数据
13
+const examQuestions = ref([
14
+  {
15
+    title: '第一题 选择题',
16
+    fixedQuestions: [
17
+      { type: '填空题', count: 120, score: 0 },
18
+      { type: '简答题', count: 120, score: 0 },
19
+      { type: '单选题', count: 120, score: 0 },
20
+      { type: '多选题', count: 120, score: 0 },
21
+    ],
22
+    randomQuestions: [
23
+      { type: '填空题', count: 120, score: 0 },
24
+      { type: '简答题', count: 120, score: 0 },
25
+      { type: '单选题', count: 120, score: 0 },
26
+      { type: '多选题', count: 120, score: 0 },
27
+    ],
28
+  },
29
+  {
30
+    title: '第二题 填空题',
31
+    fixedQuestions: [],
32
+    randomQuestions: [],
33
+  },
34
+  {
35
+    title: '第三题 判断题',
36
+    fixedQuestions: [],
37
+    randomQuestions: [],
38
+  },
39
+]);
40
+
41
+// 折叠面板状态,默认展开所有面板
42
+const activeCollapse = ref<(string | number | boolean)[]>(examQuestions.value.map((_, index) => index));
43
+
44
+// 计算总题数
45
+const totalQuestions = computed(() => examQuestions.value.length);
46
+
47
+// 计算小题总数
48
+const totalSubQuestions = computed(() => {
49
+  return examQuestions.value.reduce((total, question) => {
50
+    return (
51
+      total + question.fixedQuestions.length + question.randomQuestions.length
52
+    );
53
+  }, 0);
54
+});
55
+
56
+// 计算总分
57
+const totalScore = computed(() => {
58
+  let score = 0;
59
+  examQuestions.value.forEach((question) => {
60
+    question.fixedQuestions.forEach((item) => {
61
+      score += item.score;
62
+    });
63
+    question.randomQuestions.forEach((item) => {
64
+      score += item.score;
65
+    });
66
+  });
67
+  return score;
68
+});
69
+
70
+// 获取单个题目总分
71
+const getQuestionTotalScore = (index: number) => {
72
+  const question = examQuestions.value[index];
73
+  let score = 0;
74
+  question.fixedQuestions.forEach((item) => {
75
+    score += item.score;
76
+  });
77
+  question.randomQuestions.forEach((item) => {
78
+    score += item.score;
79
+  });
80
+  return score;
81
+};
82
+
83
+// 添加大题
84
+const addBigQuestion = () => {
85
+  examQuestions.value.push({
86
+    title: `第${examQuestions.value.length + 1}题 新题目`,
87
+    fixedQuestions: [],
88
+    randomQuestions: [],
89
+  });
90
+};
91
+// 容器引用
92
+const containerRef = ref<HTMLElement | null>(null);
93
+// 试题表格引用
94
+const questionTable = ref<InstanceType<typeof ElTable> | null>(null);
95
+
96
+const handleClick = (e: MouseEvent) => {
97
+  e.preventDefault();
98
+};
99
+
100
+// 弹框相关
101
+const dialogVisible = ref(false);
102
+const currentQuestionIndex = ref(-1);
103
+const currentQuestionType = ref(''); // 'fixed' or 'random'
104
+
105
+// 搜索表单
106
+const searchForm = ref({
107
+  questionName: '',
108
+  questionType: '',
109
+  questionCategory: '',
110
+  questionDifficulty: '',
111
+});
112
+
113
+// 模拟试题数据
114
+const questionList = ref([
115
+  { id: 1, name: '题目1', type: '单选题', category: '分类1', difficulty: '简单' },
116
+  { id: 2, name: '题目2', type: '多选题', category: '分类2', difficulty: '中等' },
117
+  { id: 3, name: '题目3', type: '填空题', category: '分类1', difficulty: '困难' },
118
+  { id: 4, name: '题目4', type: '简答题', category: '分类3', difficulty: '简单' },
119
+  { id: 5, name: '题目5', type: '判断题', category: '分类2', difficulty: '中等' },
120
+]);
121
+
122
+// 分页数据
123
+const currentPage = ref(1);
124
+const pageSize = ref(20);
125
+const total = ref(37);
126
+
127
+// 存储选中的试题
128
+const selectedQuestions = ref<any[]>([]);
129
+
130
+// 处理选择变化
131
+const handleSelectionChange = (val: any[]) => {
132
+  selectedQuestions.value = val;
133
+};
134
+
135
+// 打开弹框
136
+const openDialog = (index: number, type: string) => {
137
+  currentQuestionIndex.value = index;
138
+  currentQuestionType.value = type;
139
+  // 清空之前的选择
140
+  selectedQuestions.value = [];
141
+  dialogVisible.value = true;
142
+};
143
+
144
+// 关闭弹框
145
+const closeDialog = () => {
146
+  dialogVisible.value = false;
147
+};
148
+
149
+// 查询
150
+const handleSearch = () => {
151
+  // 实际项目中这里会调用API获取数据
152
+  console.log('搜索参数:', searchForm.value);
153
+};
154
+
155
+// 重置
156
+const handleReset = () => {
157
+  searchForm.value = {
158
+    questionName: '',
159
+    questionType: '',
160
+    questionCategory: '',
161
+    questionDifficulty: '',
162
+  };
163
+};
164
+
165
+// 预览
166
+const previewQuestion = (id: number) => {
167
+  console.log('预览题目:', id);
168
+};
169
+
170
+// 分页方法
171
+const handleSizeChange = (size: number) => {
172
+  pageSize.value = size;
173
+  // 实际项目中这里会重新调用API获取数据
174
+  console.log('每页条数:', size);
175
+};
176
+
177
+const handleCurrentChange = (current: number) => {
178
+  currentPage.value = current;
179
+  // 实际项目中这里会重新调用API获取数据
180
+  console.log('当前页码:', current);
181
+};
182
+
183
+// 保存选中的试题
184
+const saveQuestions = () => {
185
+  if (currentQuestionIndex.value === -1 || !currentQuestionType.value) {
186
+    return;
187
+  }
188
+
189
+  // 使用通过handleSelectionChange方法存储的选中数据
190
+  const selectedRows = selectedQuestions.value;
191
+  
192
+  if (selectedRows.length === 0) {
193
+    return;
194
+  }
195
+
196
+  // 根据类型添加到对应的试题列表
197
+  const question = examQuestions.value[currentQuestionIndex.value];
198
+  if (currentQuestionType.value === 'fixed') {
199
+    // 为每个选中的试题创建一个新的对象,包含必要的字段
200
+    selectedRows.forEach((row: any) => {
201
+      question.fixedQuestions.push({
202
+        type: row.type,
203
+        count: 1, // 每个试题算1个
204
+        score: 0 // 默认分数为0
205
+      });
206
+    });
207
+  } else if (currentQuestionType.value === 'random') {
208
+    // 为每个选中的试题创建一个新的对象,包含必要的字段
209
+    selectedRows.forEach((row: any) => {
210
+      question.randomQuestions.push({
211
+        type: row.type,
212
+        count: 1, // 每个试题算1个
213
+        score: 0 // 默认分数为0
214
+      });
215
+    });
216
+  }
217
+
218
+  // 关闭弹框
219
+  closeDialog();
220
+};
221
+
222
+// 随机试题设置表单
223
+const randomQuestionForm = ref({
224
+  questionTypes: [] as string[],
225
+  questionCategories: [] as string[],
226
+  questionDifficulty: '',
227
+  questionCount: 1,
228
+});
229
+
230
+// 题型选项
231
+const questionTypeOptions = [
232
+  { label: '单选题', value: '单选题' },
233
+  { label: '多选题', value: '多选题' },
234
+  { label: '填空题', value: '填空题' },
235
+  { label: '简答题', value: '简答题' },
236
+  { label: '判断题', value: '判断题' },
237
+];
238
+
239
+// 分类选项
240
+const questionCategoryOptions = [
241
+  { label: '分类1', value: '分类1' },
242
+  { label: '分类2', value: '分类2' },
243
+  { label: '分类3', value: '分类3' },
244
+];
245
+
246
+// 难度选项
247
+const questionDifficultyOptions = [
248
+  { label: '简单', value: '简单' },
249
+  { label: '中等', value: '中等' },
250
+  { label: '困难', value: '困难' },
251
+];
252
+
253
+// 预览相关
254
+const previewDialogVisible = ref(false);
255
+const previewQuestions = ref<any[]>([]);
256
+
257
+// 打开预览弹框
258
+const openPreviewDialog = (index: number, questionType: string, questionIndex: number) => {
259
+  const question = examQuestions.value[index];
260
+  const targetQuestions = questionType === 'fixed' ? question.fixedQuestions : question.randomQuestions;
261
+  const selectedQuestion = targetQuestions[questionIndex];
262
+  
263
+  // 模拟生成预览数据
264
+  generatePreviewData(selectedQuestion.type, selectedQuestion.count);
265
+  
266
+  previewDialogVisible.value = true;
267
+};
268
+
269
+// 关闭预览弹框
270
+const closePreviewDialog = () => {
271
+  previewDialogVisible.value = false;
272
+};
273
+
274
+// 生成预览数据
275
+const generatePreviewData = (type: string, count: number) => {
276
+  previewQuestions.value = [];
277
+  
278
+  for (let i = 0; i < count; i++) {
279
+    switch (type) {
280
+      case '单选题':
281
+        previewQuestions.value.push({
282
+          id: `single-${i}`,
283
+          questionType: '单选题',
284
+          questionTitle: '这是本题的题目,请选择',
285
+          options: [
286
+            { content: '答案1', isCorrect: true },
287
+            { content: '答案2', isCorrect: false },
288
+            { content: '答案3', isCorrect: false },
289
+            { content: '答案4', isCorrect: false }
290
+          ],
291
+          score: 2
292
+        });
293
+        break;
294
+      case '多选题':
295
+        previewQuestions.value.push({
296
+          id: `multiple-${i}`,
297
+          questionType: '多选题',
298
+          questionTitle: '这是本题的题目,请选择',
299
+          options: [
300
+            { content: '答案1', isCorrect: true },
301
+            { content: '答案2', isCorrect: true },
302
+            { content: '答案3', isCorrect: false },
303
+            { content: '答案4', isCorrect: false }
304
+          ],
305
+          score: 4
306
+        });
307
+        break;
308
+      case '判断题':
309
+        previewQuestions.value.push({
310
+          id: `judge-${i}`,
311
+          questionType: '判断题',
312
+          questionTitle: '这是本题的题目,请判断',
313
+          options: [
314
+            { content: '正确', isCorrect: true },
315
+            { content: '错误', isCorrect: false }
316
+          ],
317
+          score: 1
318
+        });
319
+        break;
320
+      case '填空题':
321
+        previewQuestions.value.push({
322
+          id: `fill-${i}`,
323
+          questionType: '填空题',
324
+          questionTitle: '这是本题的题目,请作答',
325
+          correctAnswer: '这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案。这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案',
326
+          score: 3
327
+        });
328
+        break;
329
+      case '简答题':
330
+        previewQuestions.value.push({
331
+          id: `essay-${i}`,
332
+          questionType: '简答题',
333
+          questionTitle: '这是本题的题目,请作答',
334
+          correctAnswer: '这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案。这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案这是正确答案',
335
+          score: 6
336
+        });
337
+        break;
338
+    }
339
+  }
340
+};
341
+
342
+// 保存随机试题设置
343
+const saveRandomQuestions = () => {
344
+  if (currentQuestionIndex.value === -1) {
345
+    return;
346
+  }
347
+
348
+  // 获取当前设置的随机试题参数
349
+  const { questionTypes, questionCategories, questionDifficulty, questionCount } = randomQuestionForm.value;
350
+
351
+  // 确保至少选择了一种题型
352
+  if (!questionTypes || questionTypes.length === 0) {
353
+    return;
354
+  }
355
+
356
+  // 根据类型添加到对应的试题列表
357
+  const question = examQuestions.value[currentQuestionIndex.value];
358
+  
359
+  // 为每种选中的题型创建一个随机试题设置
360
+  questionTypes.forEach((type: string) => {
361
+    question.randomQuestions.push({
362
+      type: type,
363
+      count: questionCount,
364
+      score: 0 // 默认分数为0
365
+    });
366
+  });
367
+
368
+  // 关闭弹框
369
+  closeDialog();
370
+};
371
+</script>
372
+
373
+<template>
374
+  <div class="exam-edit-container">
375
+    <!-- 顶部区域 -->
376
+    <div class="top-section">
377
+      <div class="left-section">
378
+        <div class="form-item">
379
+          <label class="label">试卷名称:</label>
380
+          <el-input
381
+            v-model="examForm.examName"
382
+            placeholder="请输入试卷名称"
383
+            :maxlength="30"
384
+            show-word-limit
385
+            class="input"
386
+          />
387
+        </div>
388
+        <div class="form-item">
389
+          <label class="label">试卷分类:</label>
390
+          <el-select
391
+            v-model="examForm.examCategory"
392
+            placeholder="请选择"
393
+            class="select"
394
+          >
395
+            <el-option label="选择题" value="1" />
396
+            <el-option label="填空题" value="2" />
397
+            <el-option label="简答题" value="3" />
398
+            <el-option label="判断题" value="4" />
399
+          </el-select>
400
+        </div>
401
+      </div>
402
+      <div class="right-section">
403
+        <div class="stats">
404
+          <span>{{ totalQuestions }}大题</span>
405
+          <span class="separator">|</span>
406
+          <span>{{ totalSubQuestions }}小题</span>
407
+          <span class="separator">|</span>
408
+          <span>总分:{{ totalScore }}</span>
409
+        </div>
410
+        <el-button type="primary" size="default" class="save-btn">
411
+          保存
412
+        </el-button>
413
+      </div>
414
+    </div>
415
+
416
+    <!-- 主体区域 -->
417
+    <div class="main-section">
418
+      <!-- 左侧导航 -->
419
+      <div class="left-nav">
420
+        <div class="nav-title">大题导航</div>
421
+        <ElAnchor  
422
+            :container="containerRef"
423
+            direction="vertical"
424
+            default-active="question-0"
425
+            type="default"
426
+            :offset="30"
427
+            @click="handleClick">
428
+            <ElAnchorLink
429
+              v-for="(question, index) in examQuestions"
430
+              :key="index"
431
+              :href="`#question-${index}`"
432
+              :title="question.title"
433
+            />
434
+        </ElAnchor>
435
+      </div>
436
+      <!-- 右侧内容 -->
437
+      <div class="right-content" ref="containerRef">
438
+        <div
439
+          v-for="(question, index) in examQuestions"
440
+          :key="index"
441
+          :id="`question-${index}`"
442
+          class="question-block"
443
+        >
444
+          <!-- 移除原来的question-header -->
445
+          <el-collapse v-model="activeCollapse">
446
+            <el-collapse-item :name="index">
447
+              <template #title>
448
+                <div style="display: flex; justify-content: space-between; align-items: center; font-size: 18px; width: 100%;">
449
+                  <span>{{question.title}}</span>
450
+                  <span>合计:{{getQuestionTotalScore(index)}}分</span>
451
+                </div>
452
+              </template>
453
+              <!-- 固定试题区块 -->
454
+              <div class="question-type-section">
455
+                <div class="section-title">固定试题</div>
456
+                <el-table :data="question.fixedQuestions" style="width: 100%" :header-cell-style="{ backgroundColor: '#F7F9FA', color: '#31373D' }">
457
+                  <el-table-column prop="type" label="试题类型" />
458
+                  <el-table-column prop="count" label="试题数" />
459
+                  <el-table-column prop="score" label="每道题分数" >
460
+                    <template #default="scope">
461
+                      <el-input-number
462
+                        v-model="scope.row.score"
463
+                        :min="0"
464
+                        :max="100"
465
+                        size="small"
466
+                      />
467
+                    </template>
468
+                  </el-table-column>
469
+                  <el-table-column label="操作" >
470
+                    <template #default="scope">
471
+                      <el-button type="text" size="small" class="preview-btn" @click="openPreviewDialog(index, 'fixed', scope.$index)">
472
+                        预览
473
+                      </el-button>
474
+                      <el-button type="text" size="small" class="delete-btn">
475
+                        删除
476
+                      </el-button>
477
+                    </template>
478
+                  </el-table-column>
479
+                </el-table>
480
+                <el-button type="text" icon="Plus" class="add-question-btn" @click="openDialog(index, 'fixed')">
481
+                  + 试题库中选题
482
+                </el-button>
483
+              </div>
484
+              
485
+              <!-- 随机试题区块 -->
486
+              <div class="question-type-section">
487
+                <div class="section-title">随机试题</div>
488
+                <el-table :data="question.randomQuestions" style="width: 100%" :header-cell-style="{ backgroundColor: '#F7F9FA', color: '#31373D' }">
489
+                  <el-table-column prop="type" label="试题类型" />
490
+                  <el-table-column prop="count" label="试题数" />
491
+                  <el-table-column prop="score" label="每道题分数" >
492
+                    <template #default="scope">
493
+                      <el-input-number
494
+                        v-model="scope.row.score"
495
+                        :min="0"
496
+                        :max="100"
497
+                        size="small"
498
+                      />
499
+                    </template>
500
+                  </el-table-column>
501
+                  <el-table-column label="操作" >
502
+                    <template #default="scope">
503
+                      <el-button type="text" size="small" class="preview-btn" @click="openPreviewDialog(index, 'random', scope.$index)">
504
+                        预览
505
+                      </el-button>
506
+                      <el-button type="text" size="small" class="delete-btn">
507
+                        删除
508
+                      </el-button>
509
+                    </template>
510
+                  </el-table-column>
511
+                </el-table>
512
+                <el-button type="text" icon="Plus" class="add-question-btn" @click="openDialog(index, 'random')">
513
+                  + 试题库中选题
514
+                </el-button>
515
+              </div>
516
+            </el-collapse-item>
517
+          </el-collapse>
518
+        </div>
519
+
520
+        <el-button
521
+          type="dashed"
522
+          icon="Plus"
523
+          class="add-big-question-btn"
524
+          @click="addBigQuestion"
525
+        >
526
+          + 添加大题
527
+        </el-button>
528
+      </div>
529
+    </div>
530
+  </div>
531
+
532
+  <!-- 添加固定试题弹框 -->
533
+  <el-dialog
534
+    v-if="currentQuestionType === 'fixed'"
535
+    title="添加固定试题"
536
+    v-model="dialogVisible"
537
+    width="1000px"
538
+  >
539
+    <!-- 搜索表单 -->
540
+    <div class="search-form" style="margin-bottom: 20px;">
541
+      <el-form :model="searchForm" label-width="80px">
542
+        <el-row :gutter="20">
543
+          <el-col :span="6">
544
+            <el-form-item label="题目名称">
545
+              <el-input v-model="searchForm.questionName" placeholder="请输入" />
546
+            </el-form-item>
547
+          </el-col>
548
+          <el-col :span="6">
549
+            <el-form-item label="试题题型">
550
+              <el-select v-model="searchForm.questionType" placeholder="请选择">
551
+                <el-option label="单选题" value="单选题" />
552
+                <el-option label="多选题" value="多选题" />
553
+                <el-option label="填空题" value="填空题" />
554
+                <el-option label="简答题" value="简答题" />
555
+                <el-option label="判断题" value="判断题" />
556
+              </el-select>
557
+            </el-form-item>
558
+          </el-col>
559
+          <el-col :span="6">
560
+            <el-form-item label="试题分类">
561
+              <el-select v-model="searchForm.questionCategory" placeholder="请选择">
562
+                <el-option label="分类1" value="分类1" />
563
+                <el-option label="分类2" value="分类2" />
564
+                <el-option label="分类3" value="分类3" />
565
+              </el-select>
566
+            </el-form-item>
567
+          </el-col>
568
+          <el-col :span="6">
569
+            <el-form-item label="试题难度">
570
+              <el-select v-model="searchForm.questionDifficulty" placeholder="请选择">
571
+                <el-option label="简单" value="简单" />
572
+                <el-option label="中等" value="中等" />
573
+                <el-option label="困难" value="困难" />
574
+              </el-select>
575
+            </el-form-item>
576
+          </el-col>
577
+        </el-row>
578
+        <el-row>
579
+          <el-col :span="24" style="text-align: right;">
580
+            <el-button type="primary" @click="handleSearch">查询</el-button>
581
+            <el-button @click="handleReset">重置</el-button>
582
+          </el-col>
583
+        </el-row>
584
+      </el-form>
585
+    </div>
586
+
587
+    <!-- 试题列表 -->
588
+    <el-table ref="questionTable" :data="questionList" style="width: 100%" @selection-change="handleSelectionChange">
589
+      <el-table-column type="selection" width="55" />
590
+      <el-table-column prop="name" label="题目名称" />
591
+      <el-table-column prop="type" label="试题题型" />
592
+      <el-table-column prop="category" label="试题分类" />
593
+      <el-table-column prop="difficulty" label="试题难度" />
594
+      <el-table-column label="操作" width="100">
595
+        <template #default="scope">
596
+          <el-button type="text" size="small" @click="previewQuestion(scope.row.id)">预览</el-button>
597
+        </template>
598
+      </el-table-column>
599
+    </el-table>
600
+
601
+    <!-- 分页 -->
602
+    <div class="pagination" style="margin-top: 20px; text-align: right;">
603
+      <el-pagination
604
+        v-model:current-page="currentPage"
605
+        v-model:page-size="pageSize"
606
+        :page-sizes="[20, 50, 100]"
607
+        layout="total, sizes, prev, pager, next, jumper"
608
+        :total="total"
609
+        @size-change="handleSizeChange"
610
+        @current-change="handleCurrentChange"
611
+      />
612
+    </div>
613
+
614
+    <!-- 底部按钮 -->
615
+    <template #footer>
616
+      <span class="dialog-footer">
617
+        <el-button @click="closeDialog">取消</el-button>
618
+        <el-button type="primary" @click="saveQuestions">保存</el-button>
619
+      </span>
620
+    </template>
621
+  </el-dialog>
622
+
623
+  <!-- 添加随机试题弹框 -->
624
+  <el-dialog
625
+    v-if="currentQuestionType === 'random'"
626
+    title="添加随机试题"
627
+    v-model="dialogVisible"
628
+    width="500px"
629
+  >
630
+    <el-form :model="randomQuestionForm" label-width="100px">
631
+      <el-form-item label="试题类型">
632
+        <el-select
633
+          v-model="randomQuestionForm.questionTypes"
634
+          multiple
635
+          placeholder="请选择试题类型"
636
+          style="width: 100%"
637
+        >
638
+          <el-option
639
+            v-for="option in questionTypeOptions"
640
+            :key="option.value"
641
+            :label="option.label"
642
+            :value="option.value"
643
+          />
644
+        </el-select>
645
+      </el-form-item>
646
+      
647
+      <el-form-item label="试题分类">
648
+        <el-select
649
+          v-model="randomQuestionForm.questionCategories"
650
+          multiple
651
+          placeholder="请选择试题分类"
652
+          style="width: 100%"
653
+        >
654
+          <el-option
655
+            v-for="option in questionCategoryOptions"
656
+            :key="option.value"
657
+            :label="option.label"
658
+            :value="option.value"
659
+          />
660
+        </el-select>
661
+      </el-form-item>
662
+      
663
+      <el-form-item label="试题难度">
664
+        <el-select
665
+          v-model="randomQuestionForm.questionDifficulty"
666
+          placeholder="请选择试题难度"
667
+          style="width: 100%"
668
+        >
669
+          <el-option
670
+            v-for="option in questionDifficultyOptions"
671
+            :key="option.value"
672
+            :label="option.label"
673
+            :value="option.value"
674
+          />
675
+        </el-select>
676
+      </el-form-item>
677
+      
678
+      <el-form-item label="试题数量">
679
+        <el-input-number
680
+          v-model="randomQuestionForm.questionCount"
681
+          :min="1"
682
+          :max="100"
683
+          placeholder="请输入试题数量"
684
+          style="width: 100%"
685
+        />
686
+      </el-form-item>
687
+    </el-form>
688
+
689
+    <!-- 底部按钮 -->
690
+    <template #footer>
691
+      <span class="dialog-footer">
692
+        <el-button @click="closeDialog">取消</el-button>
693
+        <el-button type="primary" @click="saveRandomQuestions">保存</el-button>
694
+      </span>
695
+    </template>
696
+  </el-dialog>
697
+
698
+<!-- 预览弹框 -->
699
+<el-dialog
700
+  title="试题预览"
701
+  v-model="previewDialogVisible"
702
+  width="800px"
703
+>
704
+  <ExamPreview :questions="previewQuestions" />
705
+</el-dialog>
706
+</template>
707
+
708
+<style scoped>
709
+  
710
+  .exam-edit-container {
711
+    padding: 1rem;
712
+    background-color: #f5f7fa;
713
+    height: calc(100vh - 90px);
714
+  }
715
+
716
+  /* 顶部区域 */
717
+  .top-section {
718
+    display: flex;
719
+    justify-content: space-between;
720
+    align-items: flex-start;
721
+    margin-bottom: 10px;
722
+    background-color: white;
723
+    padding: 20px;
724
+    border-radius: 8px;
725
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
726
+  }
727
+
728
+  .left-section {
729
+    display: flex;
730
+    flex-direction: column;
731
+    gap: 20px;
732
+    flex: 1;
733
+  }
734
+
735
+  .form-item {
736
+    display: flex;
737
+    align-items: center;
738
+    gap: 10px;
739
+  }
740
+
741
+  .label {
742
+    font-size: 14px;
743
+    color: #000;
744
+    min-width: 80px;
745
+    font-weight: 400;
746
+    text-align: right;
747
+  }
748
+
749
+  .input {
750
+    width: 400px;
751
+  }
752
+
753
+  .select {
754
+    width: 230px;
755
+  }
756
+
757
+  .right-section {
758
+    display: flex;
759
+    flex-direction: column;
760
+    align-items: flex-end;
761
+    gap: 15px;
762
+  }
763
+
764
+  .stats {
765
+    display: flex;
766
+    align-items: center;
767
+    gap: 15px;
768
+    font-size: 14px;
769
+    color: #303133;
770
+  }
771
+
772
+  .separator {
773
+    color: #dcdfe6;
774
+    font-size: 16px;
775
+  }
776
+
777
+  .save-btn {
778
+    width: 100px;
779
+    margin-top: 18px;
780
+  }
781
+
782
+  /* 主体区域 */
783
+  .main-section {
784
+    display: flex;
785
+    border-radius: 8px;
786
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
787
+  }
788
+
789
+  /* 左侧导航 */
790
+  .left-nav {
791
+    width: 350px;
792
+    padding: 20px;
793
+    margin-right: 10px;
794
+    background-color: white;
795
+    height: calc(100vh - 245px);
796
+  }
797
+
798
+  .nav-title {
799
+    font-size: 16px;
800
+    font-weight: bold;
801
+    margin-bottom: 15px;
802
+    color: #303133;
803
+  }
804
+
805
+  /* ElAnchor and ElAnchorLink styles */
806
+  :deep(.el-anchor) {
807
+    padding-left: 0;
808
+  }
809
+  :deep(.el-anchor__list) {
810
+    padding-left: 0 !important;
811
+  }
812
+
813
+  :deep(.el-anchor__link) { 
814
+    padding-left: 15px;
815
+    margin-bottom: 10px;
816
+    border-radius: 4px;
817
+    cursor: pointer;
818
+    transition: all 0.3s;
819
+    background-color: #ffffff;
820
+    border: 1px solid #e6e8eb;
821
+    position: relative;
822
+    width: 315px;
823
+    height: 34px;
824
+    color: #303133;
825
+    box-sizing: border-box;
826
+  }
827
+
828
+  :deep(.el-anchor__link:hover) {
829
+    background-color: #ecf5ff;
830
+    color: #409eff;
831
+  }
832
+
833
+  :deep(.el-anchor__link.is-active) {
834
+    background-color: #ecf5ff;
835
+    color: #409eff;
836
+    border-left: 3px solid #409eff;
837
+  }
838
+  :deep(.el-anchor__link-title) {
839
+    font-size: 14px;
840
+    font-weight: 400;
841
+    color: #303133;
842
+    padding: 0;
843
+    white-space: normal;
844
+    line-height: 1.4;
845
+  }
846
+
847
+  :deep(.el-anchor__link.active .el-anchor__link-title) { 
848
+    color: #409eff;
849
+  }
850
+
851
+  :deep(.el-anchor-fix) {
852
+    display: none;
853
+  }
854
+
855
+  /* 右侧内容 */
856
+  .right-content {
857
+    flex: 1;
858
+    background-color: white;
859
+    border-radius: 8px;
860
+    height: calc(100vh - 245px);
861
+    overflow-y: auto;
862
+    /* 隐藏滚动条 */
863
+    -ms-overflow-style: none;  /* Internet Explorer 10+ */
864
+    scrollbar-width: none;  /* Firefox */
865
+    ::-webkit-scrollbar {
866
+      display: none;
867
+    }
868
+  }
869
+
870
+  /* 题目块 */
871
+  .question-block {
872
+    margin-bottom: 20px;
873
+    padding: 0px 20px;
874
+    background-color: #ffffff;
875
+    border-radius: 8px;
876
+  }
877
+
878
+  /* 题目类型区块 */
879
+  .question-type-section {
880
+    margin-bottom: 20px;
881
+    padding: 15px;
882
+    background-color: white;
883
+    border-radius: 6px;
884
+  }
885
+
886
+  .section-title {
887
+    font-size: 16px;
888
+    font-weight: bold;
889
+    margin-bottom: 15px;
890
+    color: #303133;
891
+  }
892
+
893
+  .add-question-btn {
894
+    color: #409eff;
895
+    margin-top: 10px;
896
+  }
897
+
898
+  /* 添加大题按钮 */
899
+  .add-big-question-btn {
900
+    width: 100%;
901
+    margin-top: 20px;
902
+  }
903
+
904
+  /* 操作按钮样式 */
905
+  .preview-btn {
906
+    color: #409eff;
907
+    margin-right: 10px;
908
+  }
909
+
910
+  .delete-btn {
911
+    color: #f56c6c;
912
+  }
913
+
914
+  /* 响应式设计 */
915
+  @media (max-width: 1200px) {
916
+    .input,
917
+    .select {
918
+      width: 250px;
919
+    }
920
+  }
921
+
922
+  @media (max-width: 768px) {
923
+    .top-section {
924
+      flex-direction: column;
925
+    }
926
+
927
+    .right-section {
928
+      align-items: flex-start;
929
+    }
930
+
931
+    .main-section {
932
+      flex-direction: column;
933
+    }
934
+
935
+    .left-nav {
936
+      width: 100%;
937
+      border-right: none;
938
+      border-bottom: 1px solid #e6e8eb;
939
+      padding-right: 0;
940
+      padding-bottom: 20px;
941
+      margin-bottom: 20px;
942
+    }
943
+
944
+    .right-content {
945
+      padding-left: 0;
946
+    }
947
+  }
948
+</style>

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

@@ -0,0 +1,212 @@
1
+<template>
2
+  <div v-if="displayQuestions.length > 0" class="questions-container">
3
+    <div v-for="(question, index) in displayQuestions" :key="question.id" class="preview-question-item">
4
+      
5
+      <!-- 单选题、多选题、判断题 -->
6
+      <div v-if="question.options" class="question-content">
7
+        <p class="question-title">{{ index + 1 }}. {{ question.title }}</p>
8
+        <div class="options-list">
9
+          <div 
10
+            v-for="(option, optIndex) in question.options" 
11
+            :key="optIndex" 
12
+            class="option-item"
13
+            :class="{ 'correct-option': option.isCorrect }"
14
+          >
15
+            <input 
16
+              :type="['单选题', '判断题'].includes(getQuestionType(question.type)) ? 'radio' : 'checkbox'" 
17
+              :name="['单选题', '判断题'].includes(getQuestionType(question.type)) ? question.id : `${question.id}_${optIndex}`" 
18
+              :disabled="true"
19
+              :checked="option.isCorrect"
20
+            >
21
+            <span class="option-content">{{ String.fromCharCode(65 + optIndex) }}. {{ option.text }}</span>
22
+          </div>
23
+        </div>
24
+      </div>
25
+      
26
+      <!-- 填空题、简答题 -->
27
+      <div v-else class="question-content">
28
+        <p class="question-title">{{ index + 1 }}. {{ question.title }}</p>
29
+        <div class="answer-section">
30
+          <p class="answer-label">正确答案:</p>
31
+          <p class="answer-content">{{ question.correctAnswer }}</p>
32
+        </div>
33
+      </div>
34
+    </div>
35
+    <!-- 如果有更多题目,显示提示 -->
36
+    <div v-if="questions.length > maxQuestions" class="more-questions-hint">
37
+      注:仅显示前 {{ maxQuestions }} 题,共 {{ questions.length }} 题
38
+    </div>
39
+  </div>
40
+  <div v-else class="no-questions">
41
+    暂无预览数据
42
+  </div>
43
+</template>
44
+
45
+<script setup lang="ts">
46
+import { computed } from 'vue';
47
+
48
+// 定义组件属性
49
+interface Option {
50
+  id: number;
51
+  text: string;
52
+  isCorrect: boolean;
53
+}
54
+
55
+interface Question {
56
+  id: number;
57
+  type: number;
58
+  title: string;
59
+  options?: Option[];
60
+  correctAnswer?: string;
61
+  score: number;
62
+}
63
+
64
+const props = defineProps<{
65
+  questions: Question[];
66
+  maxQuestions?: number;
67
+}>();
68
+
69
+// 设置默认显示题数为5
70
+const maxQuestions = props.maxQuestions || 30;
71
+
72
+// 计算要显示的题目
73
+const displayQuestions = computed(() => {
74
+  return props.questions.slice(0, maxQuestions);
75
+});
76
+
77
+// 根据类型ID获取题目类型名称
78
+const getQuestionType = (type: number): string => {
79
+  switch(type) {
80
+    case 1: return '单选题';
81
+    case 2: return '多选题';
82
+    case 3: return '判断题';
83
+    case 4: return '填空题';
84
+    case 5: return '简答题';
85
+    default: return '其他题型';
86
+  }
87
+};
88
+</script>
89
+
90
+<style scoped>
91
+  .questions-container {
92
+    max-height:600px;
93
+    overflow-y: auto;
94
+    padding-right: 10px;
95
+  }
96
+
97
+  .preview-question-item {
98
+    margin-bottom: 20px;
99
+    padding-bottom: 20px;
100
+    border-bottom: 1px solid #e6e8eb;
101
+  }
102
+
103
+  .preview-question-item:last-child {
104
+    border-bottom: none;
105
+    margin-bottom: 0;
106
+    padding-bottom: 0;
107
+  }
108
+
109
+  .question-header {
110
+    display: flex;
111
+    align-items: center;
112
+    margin-bottom: 10px;
113
+  }
114
+
115
+  .question-type {
116
+    font-weight: bold;
117
+    color: #303133;
118
+    margin-right: 10px;
119
+  }
120
+
121
+  .question-score {
122
+    color: #909399;
123
+    font-size: 14px;
124
+  }
125
+
126
+  .question-content {
127
+    margin-left: 20px;
128
+  }
129
+
130
+  .question-title {
131
+    font-size: 16px;
132
+    color: #303133;
133
+    margin-bottom: 15px;
134
+    line-height: 1.5;
135
+  }
136
+
137
+  .options-list {
138
+    margin-left: 20px;
139
+  }
140
+
141
+  .option-item {
142
+    display: flex;
143
+    align-items: center;
144
+    margin-bottom: 10px;
145
+    padding: 8px 12px;
146
+    border-radius: 4px;
147
+    transition: all 0.3s;
148
+  }
149
+
150
+  .option-item:hover {
151
+    background-color: #f5f7fa;
152
+  }
153
+
154
+  .option-item input[type="radio"],
155
+  .option-item input[type="checkbox"] {
156
+    margin-right: 10px;
157
+  }
158
+
159
+  .option-content {
160
+    font-size: 14px;
161
+    color: #606266;
162
+    line-height: 1.5;
163
+  }
164
+
165
+  .correct-option {
166
+    background-color: #f0f9eb;
167
+    border-left: 3px solid #67c23a;
168
+  }
169
+
170
+  .correct-option .option-content {
171
+    color: #303133;
172
+    font-weight: 500;
173
+  }
174
+
175
+  .answer-section {
176
+    margin-top: 10px;
177
+    padding: 12px;
178
+    background-color: #f5f7fa;
179
+    border-radius: 4px;
180
+  }
181
+
182
+  .answer-label {
183
+    font-weight: bold;
184
+    color: #303133;
185
+    margin-bottom: 8px;
186
+  }
187
+
188
+  .answer-content {
189
+    font-size: 14px;
190
+    color: #606266;
191
+    line-height: 1.5;
192
+    white-space: pre-wrap;
193
+    word-break: break-all;
194
+  }
195
+
196
+  .no-questions {
197
+    text-align: center;
198
+    color: #909399;
199
+    padding: 40px 0;
200
+  }
201
+
202
+  .more-questions-hint {
203
+    text-align: center;
204
+    color: #909399;
205
+    font-size: 14px;
206
+    margin-top: 20px;
207
+    padding: 10px;
208
+    background-color: #f5f7fa;
209
+    border-radius: 4px;
210
+  }
211
+</style>
212
+

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

@@ -1,499 +0,0 @@
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>

+ 146 - 3
apps/web-ele/src/views/examManage/examPaper/index.vue

@@ -19,6 +19,7 @@ import {
19 19
 } from '@element-plus/icons-vue';
20 20
 import {
21 21
   ElButton,
22
+  ElDialog,
22 23
   ElIcon,
23 24
   ElMessage,
24 25
   ElMessageBox,
@@ -28,6 +29,9 @@ import {
28 29
 
29 30
 import { useVbenVxeGrid } from '#/adapter/vxe-table';
30 31
 
32
+// 导入预览组件
33
+import ExamPreview from './cpn/examPreview.vue';
34
+
31 35
 // 路由实例
32 36
 const router = useRouter();
33 37
 const route = useRoute();
@@ -298,13 +302,137 @@ const deleteHandle = () => {
298 302
 // 编辑试卷
299 303
 const editExam = (row) => {
300 304
   // 跳转到编辑页面,并传递试卷ID
301
-  router.push(`/examManage/examEdit?id=${row.id}`);
305
+  router.push(`/examManage/examEdit/${row.id}`);
302 306
 };
303 307
 
308
+// 预览弹框状态
309
+const previewDialogVisible = ref(false);
310
+const previewExamData = ref(null);
311
+
304 312
 // 预览试卷
305 313
 const previewExam = (row) => {
306
-  // 跳转到预览页面,并传递试卷ID
307
-  router.push(`/examManage/examPreview?id=${row.id}`);
314
+  // 模拟试卷数据,实际项目中应该通过API获取
315
+  previewExamData.value = {
316
+    id: row.id,
317
+    name: row.name,
318
+    questions: [
319
+      {
320
+        id: 1,
321
+        type: 1,
322
+        title: '以下哪个是Vue的核心特性?',
323
+        score: 5,
324
+        options: [
325
+          { id: 1, text: '响应式数据绑定', isCorrect: true },
326
+          { id: 2, text: '命令式编程', isCorrect: false },
327
+          { id: 3, text: '双向数据流', isCorrect: false },
328
+          { id: 4, text: '静态模板', isCorrect: false }
329
+        ],
330
+        correctAnswer: 'A'
331
+      },
332
+      {
333
+        id: 2,
334
+        type: 2,
335
+        title: 'Vue组件的生命周期钩子中,哪个在组件挂载后调用?',
336
+        score: 5,
337
+        options: [
338
+          { id: 1, text: 'beforeCreate', isCorrect: false },
339
+          { id: 2, text: 'created', isCorrect: false },
340
+          { id: 3, text: 'mounted', isCorrect: true },
341
+          { id: 4, text: 'beforeMount', isCorrect: false }
342
+        ],
343
+        correctAnswer: 'C'
344
+      },
345
+      {
346
+        id: 3,
347
+        type: 3,
348
+        title: 'Vuex是Vue的状态管理库。',
349
+        score: 2,
350
+        options: [
351
+          { id: 1, text: '正确', isCorrect: true },
352
+          { id: 2, text: '错误', isCorrect: false }
353
+        ],
354
+        correctAnswer: '正确'
355
+      },
356
+      {
357
+        id: 4,
358
+        type: 1,
359
+        title: 'Vue中,哪个指令用于条件渲染?',
360
+        score: 5,
361
+        options: [
362
+          { id: 1, text: 'v-for', isCorrect: false },
363
+          { id: 2, text: 'v-if', isCorrect: true },
364
+          { id: 3, text: 'v-bind', isCorrect: false },
365
+          { id: 4, text: 'v-on', isCorrect: false }
366
+        ],
367
+        correctAnswer: 'B'
368
+      },
369
+      {
370
+        id: 5,
371
+        type: 2,
372
+        title: '以下哪些是Vue的内置指令?(多选)',
373
+        score: 8,
374
+        options: [
375
+          { id: 1, text: 'v-model', isCorrect: true },
376
+          { id: 2, text: 'v-show', isCorrect: true },
377
+          { id: 3, text: 'v-class', isCorrect: false },
378
+          { id: 4, text: 'v-style', isCorrect: false }
379
+        ],
380
+        correctAnswer: 'A,B'
381
+      },
382
+      {
383
+        id: 6,
384
+        type: 3,
385
+        title: 'Vue 3兼容Vue 2的所有语法。',
386
+        score: 2,
387
+        options: [
388
+          { id: 1, text: '正确', isCorrect: false },
389
+          { id: 2, text: '错误', isCorrect: true }
390
+        ],
391
+        correctAnswer: '错误'
392
+      },
393
+      {
394
+        id: 7,
395
+        type: 4,
396
+        title: 'Vue中,______是用于在组件之间传递数据的属性。',
397
+        score: 3,
398
+        correctAnswer: 'props'
399
+      },
400
+      {
401
+        id: 8,
402
+        type: 5,
403
+        title: '请简述Vue的响应式原理。',
404
+        score: 10,
405
+        correctAnswer: 'Vue的响应式原理基于Object.defineProperty()方法,通过对数据对象的属性进行getter和setter的劫持,实现数据变化时自动更新视图。当数据被访问时触发getter,当数据被修改时触发setter,setter会通知依赖收集器更新相关的视图。'
406
+      },
407
+      {
408
+        id: 9,
409
+        type: 1,
410
+        title: 'Vue Router的模式中,哪种需要后端配置支持?',
411
+        score: 5,
412
+        options: [
413
+          { id: 1, text: 'hash', isCorrect: false },
414
+          { id: 2, text: 'history', isCorrect: true },
415
+          { id: 3, text: 'abstract', isCorrect: false },
416
+          { id: 4, text: 'memory', isCorrect: false }
417
+        ],
418
+        correctAnswer: 'B'
419
+      },
420
+      {
421
+        id: 10,
422
+        type: 2,
423
+        title: 'Vue 3中新增的Composition API包括哪些?(多选)',
424
+        score: 8,
425
+        options: [
426
+          { id: 1, text: 'setup()', isCorrect: true },
427
+          { id: 2, text: 'ref()', isCorrect: true },
428
+          { id: 3, text: 'reactive()', isCorrect: true },
429
+          { id: 4, text: 'watchEffect()', isCorrect: true }
430
+        ],
431
+        correctAnswer: 'A,B,C,D'
432
+      }
433
+    ]
434
+  };
435
+  previewDialogVisible.value = true;
308 436
 };
309 437
 
310 438
 // 复制试卷
@@ -472,6 +600,17 @@ onMounted(() => {
472 600
         </div>
473 601
       </div>
474 602
     </div>
603
+
604
+    <!-- 预览弹框 -->
605
+    <ElDialog
606
+      v-model="previewDialogVisible"
607
+      :title="`试卷预览 - ${previewExamData?.name || ''}`"
608
+      width="800px"
609
+      top="5vh"
610
+      :close-on-click-modal="false"
611
+    >
612
+      <ExamPreview v-if="previewExamData" :questions="previewExamData.questions" />
613
+    </ElDialog>
475 614
   </Page>
476 615
 </template>
477 616
 
@@ -606,3 +745,7 @@ onMounted(() => {
606 745
   display: flex;
607 746
 }
608 747
 </style>
748
+
749
+
750
+
751
+

File diff suppressed because it is too large
+ 0 - 1255
apps/web-ele/src/views/examManage/examPaper/index2.vue


+ 426 - 0
apps/web-ele/src/views/examManage/paperManage/cpn/paper-drawer.vue

@@ -0,0 +1,426 @@
1
+<script lang="ts" setup>
2
+import { ref, reactive } from 'vue';
3
+import { ElMessage } from 'element-plus';
4
+
5
+// 抽屉可见性
6
+const drawerVisible = ref(false);
7
+
8
+// 考试设置表单数据
9
+const examForm = reactive({
10
+  name: '',
11
+  category: '',
12
+  description: '',
13
+  totalScore: '',
14
+  passingScore: '',
15
+  startTime: '',
16
+  endTime: '',
17
+  duration: '',
18
+  executorCount: 23, // 模拟已选择23人
19
+});
20
+
21
+// 考试分类选项
22
+const categoryOptions = [
23
+  { label: '基础知识', value: 'basic' },
24
+  { label: '框架知识', value: 'framework' },
25
+  { label: '前端基础', value: 'frontend' },
26
+  { label: '后端基础', value: 'backend' },
27
+  { label: '综合测试', value: 'comprehensive' },
28
+];
29
+
30
+// 试卷分类选项
31
+const paperCategoryOptions = [
32
+  { label: '基础知识', value: 'basic' },
33
+  { label: '框架知识', value: 'framework' },
34
+  { label: '前端基础', value: 'frontend' },
35
+  { label: '后端基础', value: 'backend' },
36
+  { label: '综合测试', value: 'comprehensive' },
37
+];
38
+
39
+// 试卷名称选项
40
+const paperNameOptions = [
41
+  { label: '基础知识测试卷', value: '1', category: '基础知识', creator: '管理员', totalScore: 100, questionCount: 20, updateDate: '2024-01-15' },
42
+  { label: 'Vue框架测试卷', value: '2', category: '框架知识', creator: '教师1', totalScore: 120, questionCount: 25, updateDate: '2024-01-16' },
43
+  { label: '前端综合测试卷', value: '3', category: '前端基础', creator: '教师2', totalScore: 150, questionCount: 30, updateDate: '2024-01-17' },
44
+  { label: '后端综合测试卷', value: '4', category: '后端基础', creator: '教师3', totalScore: 120, questionCount: 25, updateDate: '2024-01-18' },
45
+  { label: '全栈综合测试卷', value: '5', category: '综合测试', creator: '管理员', totalScore: 200, questionCount: 40, updateDate: '2024-01-19' },
46
+];
47
+
48
+// 已选择的试卷
49
+const selectedPapers = ref([
50
+  { id: '1', name: '基础知识测试卷', category: '基础知识', creator: '管理员', totalScore: 100, questionCount: 20, updateDate: '2024-01-15' },
51
+  { id: '2', name: 'Vue框架测试卷', category: '框架知识', creator: '教师1', totalScore: 120, questionCount: 25, updateDate: '2024-01-16' },
52
+]);
53
+
54
+// 选中的试卷分类
55
+const selectedPaperCategory = ref('');
56
+
57
+// 选中的试卷名称
58
+const selectedPaperNames = ref([]);
59
+
60
+// 打开抽屉
61
+const openDrawer = () => {
62
+  drawerVisible.value = true;
63
+};
64
+
65
+// 关闭抽屉
66
+const closeDrawer = () => {
67
+  drawerVisible.value = false;
68
+};
69
+
70
+// 选择考试执行人
71
+const selectExecutors = () => {
72
+  ElMessage.info('选择考试Executor的功能待实现');
73
+};
74
+
75
+// 删除已选择的试卷
76
+const removePaper = (id) => {
77
+  selectedPapers.value = selectedPapers.value.filter(paper => paper.id !== id);
78
+};
79
+
80
+// 发布考试
81
+const publishExam = () => {
82
+  // 表单验证
83
+  if (!examForm.name) {
84
+    ElMessage.error('请输入考试名称');
85
+    return;
86
+  }
87
+  if (!examForm.category) {
88
+    ElMessage.error('请选择考试分类');
89
+    return;
90
+  }
91
+  if (!examForm.totalScore) {
92
+    ElMessage.error('请输入考试总分');
93
+    return;
94
+  }
95
+  if (!examForm.passingScore) {
96
+    ElMessage.error('请输入及格分');
97
+    return;
98
+  }
99
+  if (!examForm.startTime || !examForm.endTime) {
100
+    ElMessage.error('请选择考试时间');
101
+    return;
102
+  }
103
+  if (!examForm.duration) {
104
+    ElMessage.error('请输入答卷时长');
105
+    return;
106
+  }
107
+  if (selectedPapers.value.length === 0) {
108
+    ElMessage.error('请选择至少一张试卷');
109
+    return;
110
+  }
111
+  
112
+  // 模拟发布考试
113
+  console.log('发布考试:', examForm);
114
+  console.log('选择的试卷:', selectedPapers.value);
115
+  ElMessage.success('考试发布成功');
116
+  closeDrawer();
117
+};
118
+
119
+// 监听试卷名称选择变化
120
+const handlePaperNameChange = (values) => {
121
+  // 模拟根据选择的试卷名称获取试卷信息
122
+  selectedPapers.value = paperNameOptions
123
+    .filter(paper => values.includes(paper.value))
124
+    .map(paper => ({
125
+      id: paper.value,
126
+      name: paper.label,
127
+      category: paper.category,
128
+      creator: paper.creator,
129
+      totalScore: paper.totalScore,
130
+      questionCount: paper.questionCount,
131
+      updateDate: paper.updateDate,
132
+    }));
133
+};
134
+
135
+// 暴露方法给父组件
136
+defineExpose({
137
+  openDrawer,
138
+});
139
+</script>
140
+
141
+<template>
142
+  <div>
143
+    <!-- 发布考试抽屉 -->
144
+    <el-drawer
145
+      v-model="drawerVisible"
146
+      title="发布考试"
147
+      size="1200px"
148
+      destroy-on-close
149
+    >
150
+      <div class="exam-publish-drawer">
151
+        <!-- 第一部分:考试设置 -->
152
+        <div class="exam-setting-section">
153
+          <h3 class="section-title">考试设置</h3>
154
+          
155
+          <el-form
156
+            :model="examForm"
157
+            label-position="top"
158
+            class="grid-cols-1 md:grid-cols-2 gap-4"
159
+          >
160
+            <!-- 考试名称 -->
161
+            <el-form-item label="考试名称" class="form-item-with-border">
162
+              <el-input
163
+                v-model="examForm.name"
164
+                placeholder="请输入考试名称"
165
+                maxlength="30"
166
+                show-word-limit
167
+              />
168
+            </el-form-item>
169
+            
170
+            <!-- 考试分类 -->
171
+            <el-form-item label="考试分类" class="form-item-with-border">
172
+              <el-select
173
+                v-model="examForm.category"
174
+                placeholder="请选择考试分类"
175
+                style="width: 100%"
176
+              >
177
+                <el-option
178
+                  v-for="option in categoryOptions"
179
+                  :key="option.value"
180
+                  :label="option.label"
181
+                  :value="option.value"
182
+                />
183
+              </el-select>
184
+            </el-form-item>
185
+            
186
+            <!-- 考试说明 -->
187
+            <el-form-item label="考试说明" class="form-item-with-border md:col-span-2">
188
+              <el-input
189
+                v-model="examForm.description"
190
+                type="textarea"
191
+                placeholder="请输入考试说明"
192
+                rows="3"
193
+              />
194
+            </el-form-item>
195
+            
196
+            <!-- 考试总分 -->
197
+            <el-form-item label="考试总分" class="form-item-with-border">
198
+              <el-input
199
+                v-model="examForm.totalScore"
200
+                placeholder="请输入考试总分"
201
+                type="number"
202
+                min="1"
203
+              >
204
+                <template #append>分</template>
205
+              </el-input>
206
+            </el-form-item>
207
+            
208
+            <!-- 及格分 -->
209
+            <el-form-item label="及格分" class="form-item-with-border">
210
+              <el-input
211
+                v-model="examForm.passingScore"
212
+                placeholder="请输入及格分"
213
+                type="number"
214
+                min="1"
215
+              >
216
+                <template #append>分</template>
217
+              </el-input>
218
+            </el-form-item>
219
+            
220
+            <!-- 考试时间 -->
221
+            <el-form-item label="考试时间" class="form-item-with-border md:col-span-2">
222
+              <el-date-picker
223
+                v-model="examForm.startTime"
224
+                type="datetime"
225
+                placeholder="开始时间"
226
+                style="width: 48%"
227
+              />
228
+              <span class="mx-2">至</span>
229
+              <el-date-picker
230
+                v-model="examForm.endTime"
231
+                type="datetime"
232
+                placeholder="结束时间"
233
+                style="width: 48%"
234
+              />
235
+            </el-form-item>
236
+            
237
+            <!-- 答卷时长 -->
238
+            <el-form-item label="答卷时长" class="form-item-with-border md:col-span-2">
239
+              <el-input
240
+                v-model="examForm.duration"
241
+                placeholder="请输入答卷时长"
242
+                type="number"
243
+                min="1"
244
+                style="width: 300px"
245
+              >
246
+                <template #append>分钟</template>
247
+              </el-input>
248
+            </el-form-item>
249
+            
250
+            <!-- 考试执行人 -->
251
+            <el-form-item label="考试执行人" class="form-item-with-border md:col-span-2">
252
+              <el-button @click="selectExecutors">
253
+                选择考试Executor(已选择{{ examForm.executorCount }}人)
254
+              </el-button>
255
+            </el-form-item>
256
+          </el-form>
257
+        </div>
258
+        
259
+        <!-- 第二部分:选择试卷 -->
260
+        <div class="select-paper-section">
261
+          <h3 class="section-title">选择试卷</h3>
262
+          
263
+          <div class="grid-cols-1 md:grid-cols-2 gap-4 mb-4">
264
+            <!-- 选择试卷分类 -->
265
+            <el-form-item label="选择试卷分类" class="form-item-with-border">
266
+              <el-select
267
+                v-model="selectedPaperCategory"
268
+                placeholder="请选择试卷分类"
269
+                filterable
270
+                allow-create
271
+                default-first-option
272
+                style="width: 100%"
273
+              >
274
+                <el-option
275
+                  v-for="option in paperCategoryOptions"
276
+                  :key="option.value"
277
+                  :label="option.label"
278
+                  :value="option.value"
279
+                />
280
+              </el-select>
281
+            </el-form-item>
282
+            
283
+            <!-- 选择试卷名称 -->
284
+            <el-form-item label="选择试卷名称" class="form-item-with-border">
285
+              <el-select
286
+                v-model="selectedPaperNames"
287
+                multiple
288
+                filterable
289
+                allow-create
290
+                default-first-option
291
+                placeholder="请选择试卷名称"
292
+                style="width: 100%"
293
+                @change="handlePaperNameChange"
294
+              >
295
+                <el-option
296
+                  v-for="option in paperNameOptions"
297
+                  :key="option.value"
298
+                  :label="option.label"
299
+                  :value="option.value"
300
+                />
301
+              </el-select>
302
+            </el-form-item>
303
+          </div>
304
+          
305
+          <!-- 选择的试卷列表 -->
306
+          <div class="selected-papers-list" v-if="selectedPapers.length > 0">
307
+            <el-table :data="selectedPapers" style="width: 100%" :header-cell-style="{ backgroundColor: '#409EFF', color: '#fff' }">
308
+              <el-table-column prop="name" label="试卷名称" min-width="200" />
309
+              <el-table-column prop="category" label="类别" min-width="120" />
310
+              <el-table-column prop="creator" label="创建人" min-width="120" />
311
+              <el-table-column prop="totalScore" label="试卷总分" min-width="100" />
312
+              <el-table-column prop="questionCount" label="试题总数" min-width="100" />
313
+              <el-table-column prop="updateDate" label="更新日期" min-width="120" />
314
+              <el-table-column label="操作" width="80" fixed="right">
315
+                <template #default="{ row }">
316
+                  <el-button
317
+                    type="text"
318
+                    size="small"
319
+                    @click="removePaper(row.id)"
320
+                    class="text-red-500"
321
+                  >
322
+                    删除
323
+                  </el-button>
324
+                </template>
325
+              </el-table-column>
326
+            </el-table>
327
+          </div>
328
+        </div>
329
+        
330
+        <!-- 操作按钮 -->
331
+        <div class="action-buttons">
332
+          <el-button @click="closeDrawer">取消</el-button>
333
+          <el-button type="primary" @click="publishExam">发布</el-button>
334
+        </div>
335
+      </div>
336
+    </el-drawer>
337
+  </div>
338
+</template>
339
+
340
+<style lang="scss" scoped>
341
+.exam-publish-drawer {
342
+  padding: 20px;
343
+}
344
+
345
+:deep(.el-drawer__header) {
346
+
347
+  .el-drawer__title {
348
+    color: #000;
349
+  }
350
+}
351
+
352
+.section-title {
353
+  position: relative;
354
+  margin-bottom: 20px;
355
+  padding-left: 12px;
356
+  font-size: 16px;
357
+  font-weight: 500;
358
+  color: #303133;
359
+  
360
+  &::before {
361
+    content: '';
362
+    position: absolute;
363
+    left: 0;
364
+    top: 0;
365
+    bottom: 0;
366
+    width: 3px;
367
+    background-color: #409eff;
368
+    border-radius: 2px;
369
+  }
370
+}
371
+
372
+.form-item-with-border {
373
+  .el-form-item__label {
374
+    position: relative;
375
+    padding-left: 12px;
376
+    
377
+    &::before {
378
+      content: '';
379
+      position: absolute;
380
+      left: 0;
381
+      top: 50%;
382
+      transform: translateY(-50%);
383
+      width: 3px;
384
+      height: 12px;
385
+      background-color: #409eff;
386
+      border-radius: 2px;
387
+    }
388
+  }
389
+}
390
+
391
+.grid-cols-1 {
392
+  display: grid;
393
+  grid-template-columns: 1fr;
394
+}
395
+
396
+.md\:grid-cols-2 {
397
+  @media (min-width: 768px) {
398
+    grid-template-columns: repeat(2, 1fr);
399
+  }
400
+}
401
+
402
+.md\:col-span-2 {
403
+  @media (min-width: 768px) {
404
+    grid-column: span 2;
405
+  }
406
+}
407
+
408
+.gap-4 {
409
+  gap: 1rem;
410
+}
411
+
412
+.mb-4 {
413
+  margin-bottom: 1rem;
414
+}
415
+
416
+.action-buttons {
417
+  margin-top: 30px;
418
+  display: flex;
419
+  justify-content: flex-start;
420
+  gap: 12px;
421
+}
422
+
423
+.selected-papers-list {
424
+  margin-top: 20px;
425
+}
426
+</style>

+ 755 - 0
apps/web-ele/src/views/examManage/paperManage/index.vue

@@ -0,0 +1,755 @@
1
+<script lang="ts" setup>
2
+import type { VbenFormProps } from '@vben/common-ui';
3
+
4
+import type { VxeGridProps } from '#/adapter/vxe-table';
5
+
6
+import { onMounted, ref } from 'vue';
7
+import { useRoute, useRouter } from 'vue-router';
8
+
9
+import { Page } from '@vben/common-ui';
10
+
11
+import {
12
+  CopyDocument,
13
+  DArrowLeft,
14
+  Delete,
15
+  Download,
16
+  Edit,
17
+  Expand,
18
+  Link,
19
+  Plus,
20
+} from '@element-plus/icons-vue';
21
+import {
22
+  ElButton,
23
+  ElDialog,
24
+  ElIcon,
25
+  ElMessage,
26
+  ElMessageBox,
27
+  ElSpace,
28
+  ElTree,
29
+} from 'element-plus';
30
+
31
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
32
+import PaperDrawer from './cpn/paper-drawer.vue';
33
+
34
+// 路由实例
35
+const router = useRouter();
36
+const route = useRoute();
37
+const classId = route.params.classId;
38
+
39
+// 菜单折叠状态
40
+const isCollapse = ref(false);
41
+
42
+// 章节数据
43
+const chapterData = ref([
44
+  {
45
+    id: 1,
46
+    label: '第一章 基础知识',
47
+    children: [
48
+      { id: 2, label: '1.1 概述', saved: true },
49
+      { id: 3, label: '1.2 基本概念', saved: false },
50
+      { id: 4, label: '1.3 核心原理', saved: true },
51
+    ],
52
+  },
53
+  {
54
+    id: 5,
55
+    label: '第二章 进阶知识',
56
+    children: [
57
+      { id: 6, label: '2.1 高级特性', saved: true },
58
+      { id: 7, label: '2.2 最佳实践', saved: true },
59
+    ],
60
+  },
61
+  {
62
+    id: 8,
63
+    label: '第三章 案例分析',
64
+    children: [
65
+      { id: 9, label: '3.1 案例一', saved: false },
66
+      { id: 10, label: '3.2 案例二', saved: true },
67
+    ],
68
+  },
69
+]);
70
+
71
+// 选中的章节
72
+const selectedChapter = ref({
73
+  id: '1',
74
+  label: '第一章 基础知识',
75
+});
76
+
77
+// 模拟试卷数据
78
+const mockExamData = ref([
79
+  {
80
+    id: 1,
81
+    name: '基础知识测试卷',
82
+    category: '基础知识',
83
+    creator: '管理员',
84
+    totalScore: 100,
85
+    questionCount: 20,
86
+    updateDate: '2024-01-15',
87
+  },
88
+  {
89
+    id: 2,
90
+    name: 'Vue框架测试卷',
91
+    category: '框架知识',
92
+    creator: '教师1',
93
+    totalScore: 120,
94
+    questionCount: 25,
95
+    updateDate: '2024-01-16',
96
+  },
97
+  {
98
+    id: 3,
99
+    name: '前端综合测试卷',
100
+    category: '前端基础',
101
+    creator: '教师2',
102
+    totalScore: 150,
103
+    questionCount: 30,
104
+    updateDate: '2024-01-17',
105
+  },
106
+]);
107
+
108
+// 表单配置
109
+const formOptions: VbenFormProps = {
110
+  commonConfig: {
111
+    labelWidth: 80,
112
+    componentProps: {
113
+      allowClear: true,
114
+    },
115
+  },
116
+  schema: [
117
+    {
118
+      component: 'Input',
119
+      fieldName: 'name',
120
+      label: '试卷名称',
121
+      componentProps: {
122
+        placeholder: '请输入试卷名称',
123
+      },
124
+    },
125
+  ],
126
+  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',
127
+};
128
+
129
+// 表格配置
130
+const gridOptions: VxeGridProps = {
131
+  checkboxConfig: {
132
+    highlight: true,
133
+    reserve: true,
134
+    trigger: 'default',
135
+  },
136
+  columns: [
137
+    {
138
+      type: 'checkbox',
139
+      width: 80,
140
+    },
141
+    {
142
+      field: 'name',
143
+      title: '试卷名称',
144
+      minWidth: 200,
145
+    },
146
+    {
147
+      field: 'category',
148
+      title: '类别',
149
+      minWidth: 120,
150
+    },
151
+    {
152
+      field: 'creator',
153
+      title: '创建人',
154
+      minWidth: 120,
155
+    },
156
+    {
157
+      field: 'totalScore',
158
+      title: '试卷总分',
159
+      minWidth: 100,
160
+    },
161
+    {
162
+      field: 'questionCount',
163
+      title: '试题总数',
164
+      minWidth: 100,
165
+    },
166
+    {
167
+      field: 'updateDate',
168
+      title: '更新日期',
169
+      minWidth: 120,
170
+    },
171
+    {
172
+      field: 'action',
173
+      fixed: 'right',
174
+      slots: { default: 'action' },
175
+      title: '操作',
176
+      width: 330,
177
+    },
178
+  ],
179
+  size: 'medium',
180
+  // 固定表格高度,避免高度不断增加
181
+  height: 800,
182
+  // 确保表格有唯一ID
183
+  id: 'unique-exam-paper-table',
184
+  // 分页配置
185
+  pagerConfig: {
186
+    pageSize: 20,
187
+    currentPage: 1,
188
+    pageSizes: [10, 20, 50, 100],
189
+  },
190
+  proxyConfig: {
191
+    enabled: true,
192
+    autoLoad: true,
193
+    page: {
194
+      currentKey: 'currentPage',
195
+      sizeKey: 'pageSize',
196
+    },
197
+    ajax: {
198
+      query: async ({ page }, formValues = {}) => {
199
+        // 模拟API请求
200
+        console.log('查询参数:', formValues, page);
201
+
202
+        // 模拟搜索过滤
203
+        let filteredData = [...mockExamData.value];
204
+
205
+        if (formValues.name) {
206
+          filteredData = filteredData.filter((item) =>
207
+            item.name.includes(formValues.name),
208
+          );
209
+        }
210
+
211
+        // 模拟分页
212
+        const start = (page.currentPage - 1) * page.pageSize;
213
+        const end = start + page.pageSize;
214
+        const paginatedData = filteredData.slice(start, end);
215
+        console.log('分页数据:', paginatedData);
216
+
217
+        // 确保返回格式正确,使用 items 作为数据键
218
+        return {
219
+          items: paginatedData,
220
+          total: filteredData.length,
221
+        };
222
+      },
223
+    },
224
+  },
225
+  rowConfig: {
226
+    keyField: 'id',
227
+  },
228
+  toolbarConfig: {
229
+    custom: true,
230
+    refresh: true,
231
+    zoom: true,
232
+  },
233
+};
234
+
235
+// 使用表格
236
+const [BasicTable, basicTableApi] = useVbenVxeGrid({
237
+  formOptions,
238
+  gridOptions,
239
+});
240
+
241
+// 试卷抽屉组件引用
242
+const paperDrawerRef = ref(null);
243
+
244
+// 点击章节
245
+const handleChapterClick = (node) => {
246
+  selectedChapter.value = {
247
+    id: node.id,
248
+    label: node.label,
249
+  };
250
+  // 触发表格刷新,模拟获取对应章节的试卷
251
+  basicTableApi.reload();
252
+};
253
+
254
+// 添加章节
255
+const addChapter = (parentId = null) => {
256
+  ElMessage.info('添加章节功能待实现');
257
+};
258
+
259
+// 编辑章节
260
+const editChapter = (node) => {
261
+  ElMessage.info('编辑章节功能待实现');
262
+};
263
+
264
+// 删除章节
265
+const deleteChapter = (node) => {
266
+  ElMessage.info('删除章节功能待实现');
267
+};
268
+
269
+// 新增试卷
270
+const addExam = () => {
271
+  // 打开抽屉
272
+  paperDrawerRef.value.openDrawer();
273
+};
274
+
275
+// 添加时间
276
+const addTime = () => {
277
+  ElMessage.info('添加时间功能待实现');
278
+};
279
+
280
+// 删除试卷
281
+const deleteExam = (row) => {
282
+  ElMessage.info('删除试卷功能待实现');
283
+};
284
+
285
+// 批量删除试卷
286
+const deleteHandle = () => {
287
+  const checkRecords = basicTableApi.grid.getCheckboxRecords();
288
+  const ids = checkRecords.map((item: any) => item.id);
289
+  if (ids.length <= 0) {
290
+    return;
291
+  }
292
+
293
+  ElMessageBox.confirm(`确认删除选中的${ids.length}条数据吗?`, '提示', {
294
+    confirmButtonText: '确定',
295
+    cancelButtonText: '取消',
296
+    type: 'warning',
297
+  }).then(async () => {
298
+    // 模拟批量删除操作
299
+    console.log('批量删除试卷:', ids);
300
+    await basicTableApi.reload();
301
+  });
302
+};
303
+
304
+// 编辑试卷
305
+const editExam = (row) => {
306
+  // 跳转到编辑页面,并传递试卷ID
307
+  router.push(`/examManage/examEdit/${row.id}`);
308
+};
309
+
310
+// 预览弹框状态
311
+const previewDialogVisible = ref(false);
312
+const previewExamData = ref(null);
313
+
314
+// 预览试卷
315
+const previewExam = (row) => {
316
+  // 模拟试卷数据,实际项目中应该通过API获取
317
+  previewExamData.value = {
318
+    id: row.id,
319
+    name: row.name,
320
+    questions: [
321
+      {
322
+        id: 1,
323
+        type: 1,
324
+        title: '以下哪个是Vue的核心特性?',
325
+        score: 5,
326
+        options: [
327
+          { id: 1, text: '响应式数据绑定', isCorrect: true },
328
+          { id: 2, text: '命令式编程', isCorrect: false },
329
+          { id: 3, text: '双向数据流', isCorrect: false },
330
+          { id: 4, text: '静态模板', isCorrect: false }
331
+        ],
332
+        correctAnswer: 'A'
333
+      },
334
+      {
335
+        id: 2,
336
+        type: 2,
337
+        title: 'Vue组件的生命周期钩子中,哪个在组件挂载后调用?',
338
+        score: 5,
339
+        options: [
340
+          { id: 1, text: 'beforeCreate', isCorrect: false },
341
+          { id: 2, text: 'created', isCorrect: false },
342
+          { id: 3, text: 'mounted', isCorrect: true },
343
+          { id: 4, text: 'beforeMount', isCorrect: false }
344
+        ],
345
+        correctAnswer: 'C'
346
+      },
347
+      {
348
+        id: 3,
349
+        type: 3,
350
+        title: 'Vuex是Vue的状态管理库。',
351
+        score: 2,
352
+        options: [
353
+          { id: 1, text: '正确', isCorrect: true },
354
+          { id: 2, text: '错误', isCorrect: false }
355
+        ],
356
+        correctAnswer: '正确'
357
+      },
358
+      {
359
+        id: 4,
360
+        type: 1,
361
+        title: 'Vue中,哪个指令用于条件渲染?',
362
+        score: 5,
363
+        options: [
364
+          { id: 1, text: 'v-for', isCorrect: false },
365
+          { id: 2, text: 'v-if', isCorrect: true },
366
+          { id: 3, text: 'v-bind', isCorrect: false },
367
+          { id: 4, text: 'v-on', isCorrect: false }
368
+        ],
369
+        correctAnswer: 'B'
370
+      },
371
+      {
372
+        id: 5,
373
+        type: 2,
374
+        title: '以下哪些是Vue的内置指令?(多选)',
375
+        score: 8,
376
+        options: [
377
+          { id: 1, text: 'v-model', isCorrect: true },
378
+          { id: 2, text: 'v-show', isCorrect: true },
379
+          { id: 3, text: 'v-class', isCorrect: false },
380
+          { id: 4, text: 'v-style', isCorrect: false }
381
+        ],
382
+        correctAnswer: 'A,B'
383
+      },
384
+      {
385
+        id: 6,
386
+        type: 3,
387
+        title: 'Vue 3兼容Vue 2的所有语法。',
388
+        score: 2,
389
+        options: [
390
+          { id: 1, text: '正确', isCorrect: false },
391
+          { id: 2, text: '错误', isCorrect: true }
392
+        ],
393
+        correctAnswer: '错误'
394
+      },
395
+      {
396
+        id: 7,
397
+        type: 4,
398
+        title: 'Vue中,______是用于在组件之间传递数据的属性。',
399
+        score: 3,
400
+        correctAnswer: 'props'
401
+      },
402
+      {
403
+        id: 8,
404
+        type: 5,
405
+        title: '请简述Vue的响应式原理。',
406
+        score: 10,
407
+        correctAnswer: 'Vue的响应式原理基于Object.defineProperty()方法,通过对数据对象的属性进行getter和setter的劫持,实现数据变化时自动更新视图。当数据被访问时触发getter,当数据被修改时触发setter,setter会通知依赖收集器更新相关的视图。'
408
+      },
409
+      {
410
+        id: 9,
411
+        type: 1,
412
+        title: 'Vue Router的模式中,哪种需要后端配置支持?',
413
+        score: 5,
414
+        options: [
415
+          { id: 1, text: 'hash', isCorrect: false },
416
+          { id: 2, text: 'history', isCorrect: true },
417
+          { id: 3, text: 'abstract', isCorrect: false },
418
+          { id: 4, text: 'memory', isCorrect: false }
419
+        ],
420
+        correctAnswer: 'B'
421
+      },
422
+      {
423
+        id: 10,
424
+        type: 2,
425
+        title: 'Vue 3中新增的Composition API包括哪些?(多选)',
426
+        score: 8,
427
+        options: [
428
+          { id: 1, text: 'setup()', isCorrect: true },
429
+          { id: 2, text: 'ref()', isCorrect: true },
430
+          { id: 3, text: 'reactive()', isCorrect: true },
431
+          { id: 4, text: 'watchEffect()', isCorrect: true }
432
+        ],
433
+        correctAnswer: 'A,B,C,D'
434
+      }
435
+    ]
436
+  };
437
+  previewDialogVisible.value = true;
438
+};
439
+
440
+// 复制试卷
441
+const copyExam = (row) => {
442
+  ElMessage.info('复制试卷功能待实现');
443
+};
444
+// 生成试卷链接
445
+const generateLink = (row) => {
446
+  ElMessage.info('生成试卷链接功能待实现');
447
+};
448
+// 下载试卷
449
+const downloadExam = (row) => {
450
+  ElMessage.info('下载试卷功能待实现');
451
+};
452
+
453
+// 页面加载时获取数据
454
+onMounted(() => {
455
+  // 默认选中第一个章节
456
+  if (chapterData.value.length > 0) {
457
+    handleChapterClick(chapterData.value[0]);
458
+  }
459
+});
460
+</script>
461
+
462
+<template>
463
+  <Page :auto-content-height="true">
464
+    <div class="knowledge-detail">
465
+      <!-- 主体内容区 -->
466
+      <div class="main-content">
467
+        <!-- 左侧目录 -->
468
+        <div class="left-sidebar" :class="{ collapsed: isCollapse }">
469
+          <!-- 折叠按钮 -->
470
+          <div class="collapse-btn" @click="isCollapse = true">
471
+            <ElIcon><DArrowLeft /></ElIcon>
472
+          </div>
473
+
474
+          <div class="sidebar-content">
475
+            <!-- 章节树 -->
476
+            <div class="chapter-tree">
477
+              <ElTree
478
+                :data="chapterData"
479
+                node-key="id"
480
+                default-expand-all
481
+                :expand-on-click-node="false"
482
+                @node-click="handleChapterClick"
483
+              >
484
+                <template #default="{ node, data }">
485
+                  <div class="tree-node-content">
486
+                    <span>{{ node.label }}</span>
487
+                    <div class="tree-node-actions">
488
+                      <ElButton
489
+                        type="text"
490
+                        size="small"
491
+                        circle
492
+                        @click.stop="addExam"
493
+                        title="添加试卷"
494
+                      >
495
+                        <ElIcon><Plus /></ElIcon>
496
+                      </ElButton>
497
+                      <ElButton
498
+                        type="text"
499
+                        size="small"
500
+                        circle
501
+                        @click.stop="editChapter(data)"
502
+                        title="编辑章节"
503
+                      >
504
+                        <ElIcon><Edit /></ElIcon>
505
+                      </ElButton>
506
+                      <ElButton
507
+                        type="text"
508
+                        size="small"
509
+                        circle
510
+                        @click.stop="deleteChapter(data)"
511
+                        title="删除章节"
512
+                      >
513
+                        <ElIcon><Delete /></ElIcon>
514
+                      </ElButton>
515
+                    </div>
516
+                  </div>
517
+                </template>
518
+              </ElTree>
519
+            </div>
520
+          </div>
521
+        </div>
522
+
523
+        <!-- 中间内容 -->
524
+        <div class="center-content">
525
+          <ElIcon
526
+            v-if="isCollapse"
527
+            :size="20"
528
+            class="collapse-control-btn"
529
+            @click="isCollapse = false"
530
+          >
531
+            <Expand />
532
+          </ElIcon>
533
+
534
+          <!-- 试卷列表区域 -->
535
+          <div class="question-list">
536
+            <!-- 试卷列表 -->
537
+            <BasicTable table-title="试卷管理列表">
538
+              <template #toolbar-tools>
539
+                <ElSpace>
540
+                  <ElButton
541
+                    type="danger"
542
+                    :disabled="
543
+                      !(basicTableApi?.grid?.getCheckboxRecords?.()?.length > 0)
544
+                    "
545
+                    @click="deleteHandle"
546
+                  >
547
+                    批量删除
548
+                  </ElButton>
549
+                  <ElButton type="primary" @click="addExam">
550
+                    发布考试
551
+                  </ElButton>
552
+                </ElSpace>
553
+              </template>
554
+              <template #action="{ row }">
555
+                <div class="action-buttons">
556
+                  <ElButton
557
+                    type="text"
558
+                    size="small"
559
+                    @click="previewExam(row)"
560
+                    style="margin-left: 0px"
561
+                  >
562
+                    预览
563
+                  </ElButton>
564
+                  <ElButton
565
+                    type="text"
566
+                    size="small"
567
+                    @click="copyExam(row)"
568
+                    style="margin-left: 0px"
569
+                  >
570
+                    
571
+                    <ElIcon><CopyDocument /></ElIcon>
572
+                    复制
573
+                  </ElButton>
574
+                  <ElButton
575
+                    type="text"
576
+                    size="small"
577
+                    @click="generateLink(row)"
578
+                    style="margin-left: 0px"
579
+                  >
580
+                    <ElIcon><Link /></ElIcon>
581
+                    生成链接
582
+                  </ElButton>
583
+
584
+                  <ElButton
585
+                    type="text"
586
+                    size="small"
587
+                    @click="downloadExam(row)"
588
+                    style="margin-left: 0px"
589
+                  >
590
+                    <ElIcon><Download /></ElIcon>
591
+                    下载
592
+                  </ElButton>
593
+                  <ElButton
594
+                    type="text"
595
+                    size="small"
596
+                    @click="editExam(row)"
597
+                    style="margin-left: 0px"
598
+                  >
599
+                    <ElIcon><Edit /></ElIcon>
600
+                    编辑
601
+                  </ElButton>
602
+                  <ElButton
603
+                    type="text"
604
+                    size="small"
605
+                    @click="deleteExam(row)"
606
+                    class="text-red-500"
607
+                    style="margin-left: 0px"
608
+                  >
609
+                    <ElIcon><Delete /></ElIcon>
610
+                    删除
611
+                  </ElButton>
612
+                </div>
613
+              </template>
614
+            </BasicTable>
615
+          </div>
616
+        </div>
617
+      </div>
618
+    </div>
619
+    
620
+    <!-- 试卷抽屉组件 -->
621
+    <PaperDrawer ref="paperDrawerRef" />
622
+  </Page>
623
+</template>
624
+
625
+<style lang="scss" scoped>
626
+.knowledge-detail {
627
+  display: flex;
628
+  flex-direction: column;
629
+  height: calc(100vh - 122px);
630
+}
631
+
632
+/* 顶部导航 */
633
+.top-nav {
634
+  display: flex;
635
+  align-items: center;
636
+  justify-content: flex-end;
637
+  padding: 16px 20px;
638
+  margin-bottom: 10px;
639
+  background-color: #fff;
640
+  border-bottom: 1px solid #e8e8e8;
641
+}
642
+
643
+.nav-right {
644
+  display: flex;
645
+  gap: 12px;
646
+}
647
+
648
+/* 主体内容区 */
649
+.main-content {
650
+  display: flex;
651
+  flex: 1;
652
+  gap: 10px;
653
+}
654
+
655
+/* 左侧边栏 */
656
+.left-sidebar {
657
+  position: relative;
658
+  width: 250px;
659
+  overflow: hidden;
660
+  background-color: #fff;
661
+  border-radius: 8px;
662
+  box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
663
+  transition: all 0.3s ease;
664
+}
665
+
666
+.left-sidebar.collapsed {
667
+  width: 0;
668
+  padding: 0;
669
+  margin: 0;
670
+}
671
+
672
+.collapse-btn {
673
+  width: 24px;
674
+  height: 24px;
675
+  padding: 15px;
676
+  line-height: 24px;
677
+  text-align: center;
678
+  cursor: pointer;
679
+}
680
+
681
+.sidebar-content {
682
+  padding: 15px;
683
+  transition: opacity 0.3s ease;
684
+}
685
+
686
+.left-sidebar.collapsed .sidebar-content {
687
+  pointer-events: none;
688
+  opacity: 0;
689
+}
690
+
691
+.chapter-tree {
692
+  max-height: calc(100vh - 300px);
693
+  overflow-y: auto;
694
+}
695
+
696
+.tree-node-content {
697
+  display: flex;
698
+  align-items: center;
699
+  justify-content: space-between;
700
+  width: 100%;
701
+}
702
+
703
+.tree-node-actions {
704
+  display: none;
705
+  gap: 4px;
706
+}
707
+
708
+.tree-node-content:hover .tree-node-actions {
709
+  display: flex;
710
+}
711
+
712
+.tree-node-actions button {
713
+  margin-left: 0 !important;
714
+}
715
+
716
+/* 中间内容区 */
717
+.center-content {
718
+  position: relative;
719
+  flex: 1;
720
+  overflow-y: auto;
721
+  background-color: #fff;
722
+  border-radius: 8px;
723
+  box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
724
+}
725
+
726
+.collapse-control-btn {
727
+  position: absolute;
728
+  top: 15px;
729
+  left: 10px;
730
+  z-index: 10;
731
+  padding: 4px;
732
+  margin-right: 10px;
733
+  font-size: 20px;
734
+  color: #666;
735
+  cursor: pointer;
736
+  border-radius: 4px;
737
+  transition: color 0.3s;
738
+}
739
+
740
+.collapse-control-btn:hover {
741
+  color: #1890ff;
742
+  background-color: #f0f9ff;
743
+}
744
+
745
+.question-list-title {
746
+  margin-bottom: 20px;
747
+  font-size: 18px;
748
+  font-weight: 500;
749
+  color: #303133;
750
+}
751
+
752
+.action-buttons {
753
+  display: flex;
754
+}
755
+</style>

+ 2 - 2
packages/@core/preferences/src/config.ts

@@ -95,7 +95,7 @@ const defaultPreferences: Preferences = {
95 95
     colorPrimary: 'hsl(212 100% 45%)',
96 96
     colorSuccess: 'hsl(144 57% 58%)',
97 97
     colorWarning: 'hsl(42 84% 61%)',
98
-    mode: 'dark',
98
+    mode: 'light',
99 99
     radius: '0.5',
100 100
     semiDarkHeader: false,
101 101
     semiDarkSidebar: false,
@@ -118,4 +118,4 @@ const defaultPreferences: Preferences = {
118 118
   },
119 119
 };
120 120
 
121
-export { defaultPreferences };
121
+export { defaultPreferences };