Sfoglia il codice sorgente

feat(规则管理): 新增词库管理页面和任务列表功能

- 新增 `words.vue` 页面,用于管理质检词库,支持词库分类、词条增删改查、批量导入等功能
- 在 `list.vue` 中新增任务列表功能,支持任务筛选、批量导出、批量删除等操作
- 添加相关 CSS 样式以支持新功能
闪电 8 mesi fa
parent
commit
c0727d353f
3 ha cambiato i file con 468 aggiunte e 3 eliminazioni
  1. 53 0
      src/assets/style.css
  2. 235 3
      src/views/rules/list.vue
  3. 180 0
      src/views/rules/words.vue

+ 53 - 0
src/assets/style.css

672
   right: 2rem;
672
   right: 2rem;
673
 }
673
 }
674
 
674
 
675
+.bottom-1 {
676
+  bottom: 0.25rem;
677
+}
678
+
675
 .-z-0 {
679
 .-z-0 {
676
   z-index: 0;
680
   z-index: 0;
677
 }
681
 }
696
   margin: 0.5rem;
700
   margin: 0.5rem;
697
 }
701
 }
698
 
702
 
703
+.m-1 {
704
+  margin: 0.25rem;
705
+}
706
+
699
 .mx-2 {
707
 .mx-2 {
700
   margin-left: 0.5rem;
708
   margin-left: 0.5rem;
701
   margin-right: 0.5rem;
709
   margin-right: 0.5rem;
945
   width: 220px;
953
   width: 220px;
946
 }
954
 }
947
 
955
 
956
+.w-60 {
957
+  width: 15rem;
958
+}
959
+
948
 .min-w-0 {
960
 .min-w-0 {
949
   min-width: 0px;
961
   min-width: 0px;
950
 }
962
 }
970
   transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
982
   transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
971
 }
983
 }
972
 
984
 
985
+.rotate-180 {
986
+  --tw-rotate: 180deg;
987
+  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
988
+}
989
+
973
 .transform {
990
 .transform {
974
   transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
991
   transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
975
 }
992
 }
998
   grid-template-columns: repeat(2, minmax(0, 1fr));
1015
   grid-template-columns: repeat(2, minmax(0, 1fr));
999
 }
1016
 }
1000
 
1017
 
1018
+.grid-cols-6 {
1019
+  grid-template-columns: repeat(6, minmax(0, 1fr));
1020
+}
1021
+
1022
+.grid-cols-8 {
1023
+  grid-template-columns: repeat(8, minmax(0, 1fr));
1024
+}
1025
+
1001
 .flex-col {
1026
 .flex-col {
1002
   flex-direction: column;
1027
   flex-direction: column;
1003
 }
1028
 }
1010
   align-items: center;
1035
   align-items: center;
1011
 }
1036
 }
1012
 
1037
 
1038
+.items-baseline {
1039
+  align-items: baseline;
1040
+}
1041
+
1013
 .justify-end {
1042
 .justify-end {
1014
   justify-content: flex-end;
1043
   justify-content: flex-end;
1015
 }
1044
 }
1080
   margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
1109
   margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
1081
 }
1110
 }
1082
 
1111
 
1112
+.space-y-4 > :not([hidden]) ~ :not([hidden]) {
1113
+  --tw-space-y-reverse: 0;
1114
+  margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
1115
+  margin-bottom: calc(1rem * var(--tw-space-y-reverse));
1116
+}
1117
+
1118
+.overflow-auto {
1119
+  overflow: auto;
1120
+}
1121
+
1083
 .overflow-hidden {
1122
 .overflow-hidden {
1084
   overflow: hidden;
1123
   overflow: hidden;
1085
 }
1124
 }
1593
   transition-duration: 150ms;
1632
   transition-duration: 150ms;
1594
 }
1633
 }
1595
 
1634
 
1635
+.duration-200 {
1636
+  transition-duration: 200ms;
1637
+}
1638
+
1639
+.hover\:border-blue-500:hover {
1640
+  --tw-border-opacity: 1;
1641
+  border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
1642
+}
1643
+
1596
 .hover\:bg-gray-100:hover {
1644
 .hover\:bg-gray-100:hover {
1597
   --tw-bg-opacity: 1;
1645
   --tw-bg-opacity: 1;
1598
   background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
1646
   background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
1628
   color: rgb(75 85 99 / var(--tw-text-opacity, 1));
1676
   color: rgb(75 85 99 / var(--tw-text-opacity, 1));
1629
 }
1677
 }
1630
 
1678
 
1679
+.hover\:text-blue-500:hover {
1680
+  --tw-text-opacity: 1;
1681
+  color: rgb(59 130 246 / var(--tw-text-opacity, 1));
1682
+}
1683
+
1631
 .hover\:shadow-xl:hover {
1684
 .hover\:shadow-xl:hover {
1632
   --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
1685
   --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
1633
   --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
1686
   --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);

+ 235 - 3
src/views/rules/list.vue

225
                 </div>
225
                 </div>
226
               </div>
226
               </div>
227
             </el-tab-pane>
227
             </el-tab-pane>
228
-            <el-tab-pane label="更新信息" name="third">Role</el-tab-pane>
229
-            <el-tab-pane label="任务列表" name="fourth">Role</el-tab-pane>
228
+            <el-tab-pane label="更新信息" name="third">
229
+              <div class="p-4">
230
+                <el-timeline>
231
+                  <el-timeline-item v-for="(log, index) in operationLogs" :key="index" placement="top">
232
+                    <el-card>
233
+                      <div class="text-xs text-gray-500 mb-2">{{ log.operationTime }}</div>
234
+                      <div class="flex items-baseline">
235
+                        <h4 class="text-base font-bold mr-2">{{ log.operator }}</h4>
236
+                        <span>{{ log.operationContent }}</span>
237
+                      </div>
238
+
239
+                    </el-card>
240
+                  </el-timeline-item>
241
+                </el-timeline>
242
+              </div>
243
+            </el-tab-pane>
244
+            <el-tab-pane label="任务列表" name="fourth">
245
+              <div class="mb-4 px-4">
246
+                
247
+                <div class="flex items-center justify-between mb-2 cursor-pointer" @click="toggleFilterPanel">
248
+                  <h3 class="text-lg font-medium mb-4">任务列表</h3>
249
+                  <div class="flex items-center">
250
+                    <span class="mr-2 text-sm text-gray-500">{{ showFilterPanel ? '折叠筛选' : '展开筛选' }}</span>
251
+                    <el-icon :class="[showFilterPanel ? 'rotate-180' : '']">
252
+                      <ArrowDown />
253
+                    </el-icon>
254
+                  </div>
255
+                </div>
256
+                
257
+                <el-collapse-transition>
258
+                  <div v-show="showFilterPanel" class="space-y-4">
259
+                    <div class="grid grid-cols-4 gap-4">
260
+                      <el-input v-model="searchForm.callNumber" placeholder="外呼号码" />
261
+                      
262
+                      <el-select v-model="searchForm.fatalItem" placeholder="致命项">
263
+                        <el-option label="是" value="1" />
264
+                        <el-option label="否" value="0" />
265
+                      </el-select>
266
+                      <el-select v-model="searchForm.agentName" placeholder="坐席名称">
267
+                        <el-option v-for="item in agentOptions" :key="item.value" :label="item.label"
268
+                          :value="item.value" />
269
+                      </el-select>
270
+                      <el-select v-model="searchForm.scoreRange" placeholder="得分区间">
271
+                        <el-option label="60分以下" value="0-60" />
272
+                        <el-option label="60-70分" value="60-70" />
273
+                        <el-option label="70-80分" value="70-80" />
274
+                        <el-option label="80-90分" value="80-90" />
275
+                        <el-option label="90-100分" value="90-100" />
276
+                      </el-select>
277
+                      <el-select v-model="searchForm.result" placeholder="质检结果">
278
+                        <el-option label="合格" value="pass" />
279
+                        <el-option label="不合格" value="fail" />
280
+                      </el-select>
281
+                      <el-date-picker v-model="searchForm.callTime" type="daterange" range-separator="至"
282
+                        start-placeholder="开始日期" end-placeholder="结束日期" class="mr-4" style="margin-right: 1rem;"/>
283
+                    </div>
284
+
285
+                    <div class="flex justify-end gap-4">
286
+                      <el-button @click="resetSearch">重置</el-button>
287
+                      <el-button type="primary" @click="handleSearch">查询</el-button>
288
+                    </div>
289
+                  </div>
290
+                </el-collapse-transition>
291
+              </div>
292
+              <div class="flex items-center gap-4 mb-4">
293
+                  <el-button type="primary" @click="handleBatchExport">批量导出</el-button>
294
+                  <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
295
+                </div>
296
+              <el-table :data="taskList" stripe style="width: 100%" v-loading="loading">
297
+                <el-table-column type="selection" width="55" />
298
+                <el-table-column prop="id" label="质检 Id" fixed="left" width="120" />
299
+                <el-table-column prop="callNumber" label="外呼号码" width="120" />
300
+                <el-table-column prop="callDate" label="外呼日期" width="180" />
301
+                <el-table-column prop="fatalItem" label="致命项" width="100" />
302
+                <el-table-column prop="score" label="质检得分" width="100" />
303
+                <el-table-column prop="agentName" label="坐席名称" width="120" />
304
+                <el-table-column label="录音" width="80">
305
+                  <template #default>
306
+                    <el-icon class="cursor-pointer">
307
+                      <Headset />
308
+                    </el-icon>
309
+                  </template>
310
+                </el-table-column>
311
+                <el-table-column prop="result" label="质检结果" width="120" />
312
+                <el-table-column prop="reviewTime" label="人工复检时间" width="180" />
313
+                <el-table-column prop="reviewNote" label="复检备注" />
314
+                <el-table-column prop="reviewResult" label="复检结果" width="120" />
315
+                <el-table-column label="操作" fixed="right" width="120">
316
+                  <template #default>
317
+                    <el-link type="primary" :underline="false" @click="handleView">查看</el-link>
318
+                  </template>
319
+                </el-table-column>
320
+              </el-table>
321
+
322
+              <div class="mt-4 flex justify-end ">
323
+                <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total"
324
+                  layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
325
+                  @current-change="handleCurrentChange" />
326
+              </div>
327
+            </el-tab-pane>
230
           </el-tabs>
328
           </el-tabs>
231
 
329
 
232
 
330
 
243
 import { Edit, Delete, Search, Plus, MoreFilled } from '@element-plus/icons-vue';
341
 import { Edit, Delete, Search, Plus, MoreFilled } from '@element-plus/icons-vue';
244
 import * as moment from 'moment';
342
 import * as moment from 'moment';
245
 import * as echarts from 'echarts';
343
 import * as echarts from 'echarts';
344
+import { ElMessage } from 'element-plus'
246
 
345
 
247
 const searchQuery = ref('');
346
 const searchQuery = ref('');
248
 const selectedModel = ref(0);
347
 const selectedModel = ref(0);
249
 const currentPage = ref(1);
348
 const currentPage = ref(1);
250
 const pageSize = 10;
349
 const pageSize = 10;
350
+
351
+// 任务列表相关状态
352
+const loading = ref(false);
353
+const taskList = ref([
354
+  {
355
+    id: 'QC001',
356
+    callNumber: '13800138000',
357
+    callDate: '2023-05-15 10:30:22',
358
+    fatalItem: '否',
359
+    score: 85,
360
+    agentName: '坐席A',
361
+    result: '合格',
362
+    reviewTime: '2023-05-15 11:30:00',
363
+    reviewNote: '无异常',
364
+    reviewResult: '通过'
365
+  },
366
+  {
367
+    id: 'QC002',
368
+    callNumber: '13900139000',
369
+    callDate: '2023-05-16 14:20:15',
370
+    fatalItem: '是',
371
+    score: 55,
372
+    agentName: '坐席B',
373
+    result: '不合格',
374
+    reviewTime: '2023-05-16 15:30:00',
375
+    reviewNote: '发现致命项',
376
+    reviewResult: '不通过'
377
+  }
378
+]);
379
+const total = ref(0);
380
+const showFilterPanel = ref(false);
381
+const agentOptions = ref([
382
+  { label: '坐席A', value: 'agentA' },
383
+  { label: '坐席B', value: 'agentB' },
384
+  { label: '坐席C', value: 'agentC' }
385
+]);
386
+const searchForm = ref({
387
+  callNumber: '',
388
+  callTime: [],
389
+  fatalItem: '',
390
+  agentName: '',
391
+  scoreRange: [],
392
+  result: ''
393
+});
251
 const scoreChart: any = ref<HTMLElement | null>(null);
394
 const scoreChart: any = ref<HTMLElement | null>(null);
252
 const issuesChart: any = ref<HTMLElement | null>(null);
395
 const issuesChart: any = ref<HTMLElement | null>(null);
253
 
396
 
262
   effectiveDate: [],
405
   effectiveDate: [],
263
   conditions: [],
406
   conditions: [],
264
 });
407
 });
408
+
409
+const operationLogs = ref([
410
+  {
411
+    operator: '管理员',
412
+    operationTime: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'),
413
+    operationContent: '创建了新规则',
414
+    operationType: 'create'
415
+  },
416
+  {
417
+    operator: '测试用户',
418
+    operationTime: moment().subtract(2, 'hours').format('YYYY-MM-DD HH:mm:ss'),
419
+    operationContent: '更新了规则条件',
420
+    operationType: 'update'
421
+  },
422
+  {
423
+    operator: '系统',
424
+    operationTime: moment().subtract(30, 'minutes').format('YYYY-MM-DD HH:mm:ss'),
425
+    operationContent: '删除了无效规则',
426
+    operationType: 'delete'
427
+  }
428
+]);
265
 const selectModel = (index: number) => {
429
 const selectModel = (index: number) => {
266
   selectedModel.value = index;
430
   selectedModel.value = index;
267
 };
431
 };
281
   // 取消逻辑
445
   // 取消逻辑
282
 };
446
 };
283
 
447
 
448
+const handleBatchExport = () => {
449
+  if (!taskList.value.length) {
450
+    ElMessage.warning('请选择要导出的数据');
451
+    return;
452
+  }
453
+  // 批量导出逻辑
454
+};
455
+
456
+const handleBatchDelete = () => {
457
+  if (!taskList.value.length) {
458
+    ElMessage.warning('请选择要删除的数据');
459
+    return;
460
+  }
461
+  // 批量删除逻辑
462
+};
463
+
284
 const getPageList = async () => {
464
 const getPageList = async () => {
285
   const params = {
465
   const params = {
286
     ruleName: searchQuery.value,
466
     ruleName: searchQuery.value,
293
   }
473
   }
294
 }
474
 }
295
 
475
 
476
+// 获取任务列表
477
+const getTaskList = async () => {
478
+  loading.value = true;
479
+  try {
480
+    // const params = {
481
+    //   ...searchForm.value,
482
+    //   pageNum: currentPage.value,
483
+    //   pageSize: pageSize.value
484
+    // };
485
+    // const { data, total: totalCount } = await getPageListData(`/quality/tasks`, params);
486
+    // taskList.value = data;
487
+    // total.value = totalCount;
488
+  } finally {
489
+    loading.value = false;
490
+  }
491
+};
492
+
493
+// 查询
494
+const toggleFilterPanel = () => {
495
+  showFilterPanel.value = !showFilterPanel.value;
496
+};
497
+
498
+const handleSearch = () => {
499
+  currentPage.value = 1;
500
+  getTaskList();
501
+};
502
+
503
+// 重置
504
+const resetSearch = () => {
505
+  searchForm.value = {
506
+    callNumber: '',
507
+    callTime: [],
508
+    fatalItem: '',
509
+    agentName: '',
510
+    scoreRange: [],
511
+    result: ''
512
+  };
513
+  handleSearch();
514
+};
515
+
516
+// 分页变化
517
+const handleSizeChange = (val: number) => {
518
+  pageSize.value = val;
519
+  getTaskList();
520
+};
521
+
522
+const handleCurrentChange = (val: number) => {
523
+  currentPage.value = val;
524
+  getTaskList();
525
+};
526
+
296
 const doScroll = () => {
527
 const doScroll = () => {
297
   console.log('doScroll')
528
   console.log('doScroll')
298
   const scrollwrapper: any = cardListRef.value
529
   const scrollwrapper: any = cardListRef.value
432
 
663
 
433
 onMounted(() => {
664
 onMounted(() => {
434
   // initChart();
665
   // initChart();
435
-  getPageList()
666
+  getPageList();
667
+  getTaskList();
436
 })
668
 })
437
 </script>
669
 </script>
438
 <style scoped>
670
 <style scoped>

+ 180 - 0
src/views/rules/words.vue

1
+<template>
2
+  <div class="app-container bg-gray-50 flex flex-col h-full">
3
+    <!-- 上部:质检词库 -->
4
+    <div class="flex-1 overflow-auto">
5
+      <div class="bg-white rounded-lg shadow p-4">
6
+        <h2 class="text-lg font-semibold mb-4">质检词库</h2>
7
+        <div class="grid grid-cols-8 gap-4">
8
+          <div 
9
+            v-for="(item, index) in wordCategories" 
10
+            :key="index" 
11
+            class="flex flex-col items-center p-4 border rounded-lg hover:bg-gray-50 hover:border-blue-500 shadow-sm relative group"
12
+          >
13
+            <span 
14
+              class="font-medium mb-2 cursor-pointer hover:text-blue-500"
15
+              @click="showWordDrawer(index)"
16
+            >
17
+              {{ item.name }}
18
+            </span>
19
+            <span class="text-gray-500 text-sm mb-2">{{ item.count }}条</span>
20
+            <div class="w-full flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 absolute bottom-1 left-0 px-4">
21
+              <el-link 
22
+                type="danger" 
23
+                size="small"
24
+                @click="handleDelete(index)"
25
+                class="flex-1"
26
+              >
27
+                删除
28
+              </el-link>
29
+              <el-link 
30
+                type="warning" 
31
+                size="small"
32
+                @click="handleEdit(index)"
33
+                class="flex-1"
34
+              >
35
+                编辑
36
+              </el-link>
37
+            </div>
38
+          </div>
39
+        </div>
40
+      </div>
41
+    </div>
42
+
43
+    <!-- 下部:批量导入 -->
44
+    <div class="border-t mt-4">
45
+      <div class="bg-white rounded-lg shadow p-4">
46
+        <h2 class="text-lg font-semibold mb-4">批量导入</h2>
47
+        <div class="flex space-x-4">
48
+          <el-upload
49
+            action=""
50
+            :file-list="fileList"
51
+            :before-upload="beforeUpload"
52
+            class="flex-1"
53
+          >
54
+            <el-button type="primary"><el-icon><Upload /></el-icon>上传文件</el-button>
55
+            <template #tip>
56
+              <div class="el-upload__tip">
57
+                <span v-if="fileList.length > 0">{{ fileList[0].name }}</span>
58
+              </div>
59
+            </template>
60
+          </el-upload>
61
+          <el-button @click="downloadTemplate"><el-icon><Download /></el-icon>下载模板</el-button>
62
+          <el-button type="success" @click="importWords"><el-icon><UploadFilled /></el-icon>导入词库</el-button>
63
+        </div>
64
+      </div>
65
+    </div>
66
+
67
+    <!-- 词条抽屉 -->
68
+    <el-drawer 
69
+      v-model="drawerVisible" 
70
+      title="词条列表" 
71
+      size="40%"
72
+    >
73
+      <div class="p-4">
74
+        <!-- <el-input 
75
+          v-model="searchQuery" 
76
+          placeholder="搜索词条" 
77
+          class="mb-4" 
78
+          @keyup.enter="filterWords"
79
+        /> -->
80
+        <div class="relative mb-4">
81
+            <input type="text" v-model="searchQuery" placeholder="搜索规则"
82
+              class="w-full pl-10 pr-4 py-2 text-sm border rounded-lg focus:outline-none focus:border-blue-500"
83
+              @keydown.enter="filterWords" />
84
+            <el-icon class="absolute left-3 top-2.5 text-gray-400" style="position: absolute;">
85
+              <Search />
86
+            </el-icon>
87
+          </div>
88
+        <div class="flex flex-wrap gap-2">
89
+          <el-tag 
90
+            class="m-1 cursor-pointer"
91
+            @click="addNewWord"
92
+          >
93
+            <el-icon><Plus /></el-icon> 添加
94
+          </el-tag>
95
+          <el-tag 
96
+            v-for="(word, idx) in filteredWords" 
97
+            :key="idx"
98
+            class="m-1"
99
+            closable
100
+            @close="removeWord(idx)"
101
+          >
102
+            {{ word }}
103
+          </el-tag>
104
+      </div>
105
+      </div>
106
+    </el-drawer>
107
+  </div>
108
+</template>
109
+
110
+<script lang="ts" setup name="RuleWord">
111
+import { ref } from 'vue'
112
+
113
+const wordCategories = ref([
114
+  { name: '敏感词', count: 12 },
115
+  { name: '违禁词', count: 8 },
116
+  { name: '广告词', count: 5 },
117
+])
118
+
119
+const drawerVisible = ref(false)
120
+const currentWords = ref<string[]>([])
121
+const currentCategoryIndex = ref(0)
122
+const fileList = ref<File[]>([])
123
+const searchQuery = ref('')
124
+const filteredWords = ref<string[]>([])
125
+
126
+const filterWords = () => {
127
+  if (!searchQuery.value) {
128
+    filteredWords.value = [...currentWords.value]
129
+    return
130
+  }
131
+  filteredWords.value = currentWords.value.filter(word => 
132
+    word.toLowerCase().includes(searchQuery.value.toLowerCase())
133
+  )
134
+}
135
+
136
+const addNewWord = () => {
137
+  const newWord = prompt('请输入新词条')
138
+  if (newWord) {
139
+    currentWords.value.push(newWord)
140
+    filterWords()
141
+  }
142
+}
143
+
144
+const removeWord = (index: number) => {
145
+  currentWords.value.splice(index, 1)
146
+  filterWords()
147
+}
148
+
149
+const showWordDrawer = (index: number) => {
150
+  currentCategoryIndex.value = index
151
+  // 模拟获取词条数据
152
+  currentWords.value = Array.from({ length: wordCategories.value[index].count }, (_, i) => 
153
+    `${wordCategories.value[index].name}-${i + 1}`
154
+  )
155
+  filteredWords.value = [...currentWords.value]
156
+  drawerVisible.value = true
157
+}
158
+
159
+const beforeUpload = (file: File) => {
160
+  console.log('上传文件:', file)
161
+  fileList.value = [file]
162
+  return false
163
+}
164
+
165
+const downloadTemplate = () => {
166
+  console.log('下载模板')
167
+}
168
+
169
+const importWords = () => {
170
+  console.log('导入词库')
171
+}
172
+
173
+const handleDelete = (index: number) => {
174
+  console.log('删除词库:', index)
175
+}
176
+
177
+const handleEdit = (index: number) => {
178
+  console.log('编辑词库:', index)
179
+}
180
+</script>