|
@@ -1,8 +1,8 @@
|
1
|
1
|
<!--
|
2
|
|
- * @Author: wrh
|
|
2
|
+ * @Author: ysh
|
3
|
3
|
* @Date: 2025-04-07 14:14:05
|
4
|
|
- * @LastEditors: wrh
|
5
|
|
- * @LastEditTime: 2025-07-21 17:06:36
|
|
4
|
+ * @LastEditors: Please set LastEditors
|
|
5
|
+ * @LastEditTime: 2025-07-22 16:07:39
|
6
|
6
|
-->
|
7
|
7
|
<template>
|
8
|
8
|
<div class="app-container">
|
|
@@ -198,6 +198,24 @@
|
198
|
198
|
<div class="message-text" v-html="formatMessage(msg.text)"></div>
|
199
|
199
|
<div class="message-time">{{ formatMessageTime(msg.time) }}</div>
|
200
|
200
|
</div>
|
|
201
|
+ <!-- 用户消息的文件列表 -->
|
|
202
|
+ <div v-if="msg.type === 'user' && msg.chatId && msg.fileList && msg.fileList.length > 0"
|
|
203
|
+ class="message-files">
|
|
204
|
+ <div class="files-header">
|
|
205
|
+ <el-icon>
|
|
206
|
+ <Document />
|
|
207
|
+ </el-icon>
|
|
208
|
+ <span>附件 ({{ msg.fileList.length }})</span>
|
|
209
|
+ </div>
|
|
210
|
+ <div class="files-list">
|
|
211
|
+ <div v-for="file in msg.fileList" :key="file.documentId" class="file-item-display">
|
|
212
|
+ <el-icon class="file-icon">
|
|
213
|
+ <Document />
|
|
214
|
+ </el-icon>
|
|
215
|
+ <span class="file-name">{{ file.path }}</span>
|
|
216
|
+ </div>
|
|
217
|
+ </div>
|
|
218
|
+ </div>
|
201
|
219
|
</div>
|
202
|
220
|
</div>
|
203
|
221
|
<!-- 加载状态 -->
|
|
@@ -222,6 +240,30 @@
|
222
|
240
|
|
223
|
241
|
<!-- 输入框区域 -->
|
224
|
242
|
<div class="input-container">
|
|
243
|
+ <!-- 文件列表显示区域 -->
|
|
244
|
+ <div v-if="selectedFiles.length > 0 || isUploading" class="file-list-container">
|
|
245
|
+ <div class="file-list-header">
|
|
246
|
+ <span v-if="isUploading">正在上传文件...</span>
|
|
247
|
+ <span v-else>已上传 {{ selectedFiles.length }} 个文件</span>
|
|
248
|
+ <el-icon v-if="isUploading" class="loading-icon">
|
|
249
|
+ <Loading />
|
|
250
|
+ </el-icon>
|
|
251
|
+ </div>
|
|
252
|
+ <div class="file-list">
|
|
253
|
+ <div v-for="(file, index) in selectedFiles" :key="index" class="file-item">
|
|
254
|
+ <div class="file-info">
|
|
255
|
+ <el-icon class="file-icon">
|
|
256
|
+ <Document />
|
|
257
|
+ </el-icon>
|
|
258
|
+ <span class="file-name">{{ file.name }}</span>
|
|
259
|
+ <span class="file-size">({{ formatFileSize(file.size) }})</span>
|
|
260
|
+ </div>
|
|
261
|
+ <el-button v-if="!isUploading" size="small" circle :icon="Delete" class="remove-btn"
|
|
262
|
+ @click="removeFile(index)" />
|
|
263
|
+ </div>
|
|
264
|
+ </div>
|
|
265
|
+ </div>
|
|
266
|
+
|
225
|
267
|
<div class="input-wrapper">
|
226
|
268
|
<el-input v-model="inputMessage" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 6 }"
|
227
|
269
|
placeholder="输入您的问题..." @keydown.enter.prevent="handleEnter" @keydown.ctrl.enter="handleCtrlEnter"
|
|
@@ -235,6 +277,10 @@
|
235
|
277
|
<div class="input-tips">
|
236
|
278
|
<span>按 Enter 发送,Ctrl + Enter 换行</span>
|
237
|
279
|
</div>
|
|
280
|
+
|
|
281
|
+ <!-- 隐藏的文件选择input -->
|
|
282
|
+ <input ref="fileInput" type="file" multiple style="display: none" @change="handleFileSelect"
|
|
283
|
+ accept=".txt,.doc,.docx,.pdf,.xlsx,.xls,.ppt,.pptx,.md" />
|
238
|
284
|
</div>
|
239
|
285
|
</div>
|
240
|
286
|
</div>
|
|
@@ -244,10 +290,11 @@
|
244
|
290
|
|
245
|
291
|
<script setup name=''>
|
246
|
292
|
import { reactive, toRefs, onBeforeMount, onMounted, nextTick, ref, computed, getCurrentInstance } from 'vue'
|
247
|
|
-import { Plus, Delete, User, ChatDotRound, Paperclip, Promotion, MoreFilled } from '@element-plus/icons-vue'
|
|
293
|
+import { Plus, Delete, User, ChatDotRound, Paperclip, Promotion, MoreFilled, Document, Loading } from '@element-plus/icons-vue'
|
248
|
294
|
import { listTopic, getTopic, delTopic, addTopic, updateTopic } from "@/api/llm/topic";
|
249
|
295
|
import { listChat, addChat, updateChat } from "@/api/llm/chat";
|
250
|
|
-import { getAnswer } from "@/api/llm/session";
|
|
296
|
+import { listDocument, uploadDocument } from "@/api/llm/document";
|
|
297
|
+import { getAnswer, getAnswerWithDocument } from "@/api/llm/session";
|
251
|
298
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
252
|
299
|
import useUserStore from '@/store/modules/user'
|
253
|
300
|
import logoImg from '@/assets/images/logo.png'
|
|
@@ -271,6 +318,11 @@ const inputMessage = ref('');
|
271
|
318
|
const isLoading = ref(false);
|
272
|
319
|
const messageScrollbar = ref(null);
|
273
|
320
|
const showNewChatWelcome = ref(false);
|
|
321
|
+const fileInput = ref(null);
|
|
322
|
+const selectedFiles = ref([]);
|
|
323
|
+const isUploading = ref(false);
|
|
324
|
+const documentChartId = ref('');
|
|
325
|
+const messageFileMap = ref(new Map()); // 存储消息对应的文件列表,key为chartId
|
274
|
326
|
|
275
|
327
|
const classifiedRecent = ref({
|
276
|
328
|
today: [],
|
|
@@ -303,7 +355,7 @@ const handleAction = (command) => {
|
303
|
355
|
deleteTopic(item);
|
304
|
356
|
}
|
305
|
357
|
};
|
306
|
|
-
|
|
358
|
+// 删除话题
|
307
|
359
|
const deleteTopic = async (item) => {
|
308
|
360
|
try {
|
309
|
361
|
await ElMessageBox.confirm('确定要删除这个对话吗?', '提示', {
|
|
@@ -366,6 +418,10 @@ const loadChatMessages = async (topicId) => {
|
366
|
418
|
try {
|
367
|
419
|
const response = await listChat({ topicId: topicId, pageSize: 1000 });
|
368
|
420
|
chatMessages.value = response.rows || [];
|
|
421
|
+
|
|
422
|
+ // 同时加载具有文件的对话
|
|
423
|
+ await loadMessageFiles();
|
|
424
|
+
|
369
|
425
|
await nextTick();
|
370
|
426
|
scrollToBottom();
|
371
|
427
|
} catch (error) {
|
|
@@ -373,6 +429,31 @@ const loadChatMessages = async (topicId) => {
|
373
|
429
|
}
|
374
|
430
|
};
|
375
|
431
|
|
|
432
|
+const loadMessageFiles = async () => {
|
|
433
|
+ try {
|
|
434
|
+ // 清空之前的文件映射
|
|
435
|
+ messageFileMap.value.clear();
|
|
436
|
+ // 获取所有有chatId的消息
|
|
437
|
+ const messagesWithChartId = chatMessages.value.filter(msg => msg.chatId);
|
|
438
|
+
|
|
439
|
+ // 为每个chartId获取文件列表
|
|
440
|
+ const promises = messagesWithChartId.map(async (msg) => {
|
|
441
|
+ try {
|
|
442
|
+ const fileResponse = await listDocument({ chatId: msg.chatId });
|
|
443
|
+ if (fileResponse.rows && fileResponse.rows.length > 0) {
|
|
444
|
+ messageFileMap.value.set(msg.chatId, fileResponse.rows);
|
|
445
|
+ }
|
|
446
|
+ } catch (error) {
|
|
447
|
+ console.error(`Failed to load files for chatId ${msg.chatId}:`, error);
|
|
448
|
+ }
|
|
449
|
+ });
|
|
450
|
+
|
|
451
|
+ await Promise.all(promises);
|
|
452
|
+ } catch (error) {
|
|
453
|
+ console.error('Failed to load message files:', error);
|
|
454
|
+ }
|
|
455
|
+};
|
|
456
|
+
|
376
|
457
|
const sendMessage = async () => {
|
377
|
458
|
if (!inputMessage.value.trim() || isLoading.value) return;
|
378
|
459
|
|
|
@@ -384,7 +465,8 @@ const sendMessage = async () => {
|
384
|
465
|
userId: userStore.id,
|
385
|
466
|
input: inputMessage.value,
|
386
|
467
|
topicId: currentTopicId.value,
|
387
|
|
- inputTime: proxy.parseTime(new Date(), '{y}-{m}-{d}')
|
|
468
|
+ inputTime: proxy.parseTime(new Date(), '{y}-{m}-{d}'),
|
|
469
|
+ chatId: documentChartId.value || null
|
388
|
470
|
};
|
389
|
471
|
|
390
|
472
|
// 添加用户消息到聊天记录
|
|
@@ -392,6 +474,15 @@ const sendMessage = async () => {
|
392
|
474
|
const messageToSend = inputMessage.value;
|
393
|
475
|
inputMessage.value = '';
|
394
|
476
|
|
|
477
|
+ // 暂存文件上传ID,用于后续查询
|
|
478
|
+ const uploadedFileId = documentChartId.value;
|
|
479
|
+
|
|
480
|
+ // 清空文档chartId,确保每次上传只对下一条消息有效
|
|
481
|
+ documentChartId.value = '';
|
|
482
|
+
|
|
483
|
+ // 清空已上传的文件列表
|
|
484
|
+ selectedFiles.value = [];
|
|
485
|
+
|
395
|
486
|
await nextTick();
|
396
|
487
|
scrollToBottom();
|
397
|
488
|
|
|
@@ -418,8 +509,15 @@ const sendMessage = async () => {
|
418
|
509
|
// 发送消息到后端,得到回答,并更新到数据库
|
419
|
510
|
try {
|
420
|
511
|
isLoading.value = true;
|
421
|
|
- const answer = await getAnswer({ topicId: currentTopicId.value, question: userMessage.input });
|
422
|
|
-
|
|
512
|
+
|
|
513
|
+ let answer;
|
|
514
|
+ // 判断是否存在附件,存在则使用getAnswerWithDocument API
|
|
515
|
+ if (uploadedFileId) {
|
|
516
|
+ answer = await getAnswerWithDocument({ topicId: currentTopicId.value, chatId: uploadedFileId, question: userMessage.input });
|
|
517
|
+ } else {
|
|
518
|
+ answer = await getAnswer({ topicId: currentTopicId.value, question: userMessage.input });
|
|
519
|
+ }
|
|
520
|
+
|
423
|
521
|
// 使用Vue的响应式更新方法
|
424
|
522
|
const messageIndex = chatMessages.value.length - 1;
|
425
|
523
|
if (messageIndex >= 0) {
|
|
@@ -430,10 +528,35 @@ const sendMessage = async () => {
|
430
|
528
|
outputTime: proxy.parseTime(new Date(), '{y}-{m}-{d}')
|
431
|
529
|
};
|
432
|
530
|
}
|
433
|
|
-
|
|
531
|
+
|
434
|
532
|
// 保存到数据库
|
435
|
|
- await addChat(chatMessages.value[messageIndex]);
|
436
|
|
-
|
|
533
|
+ const savedMessage = await addChat(chatMessages.value[messageIndex]);
|
|
534
|
+
|
|
535
|
+ // 如果保存成功,更新消息的实际ID
|
|
536
|
+ if (savedMessage && savedMessage.chatId) {
|
|
537
|
+ chatMessages.value[messageIndex].chatId = savedMessage.chatId;
|
|
538
|
+ }
|
|
539
|
+
|
|
540
|
+ // 如果当前消息包含文件,使用上传时的ID查询文件列表
|
|
541
|
+ if (uploadedFileId) {
|
|
542
|
+ try {
|
|
543
|
+ const fileResponse = await listDocument({ chatId: uploadedFileId });
|
|
544
|
+ if (fileResponse.rows && fileResponse.rows.length > 0) {
|
|
545
|
+ // 使用消息的chatId作为key存储文件列表
|
|
546
|
+ const messageChatId = chatMessages.value[messageIndex].chatId;
|
|
547
|
+ const keyToUse = messageChatId || uploadedFileId;
|
|
548
|
+ messageFileMap.value.set(keyToUse, fileResponse.rows);
|
|
549
|
+
|
|
550
|
+ // 确保消息有chatId用于显示
|
|
551
|
+ if (!chatMessages.value[messageIndex].chatId) {
|
|
552
|
+ chatMessages.value[messageIndex].chatId = uploadedFileId;
|
|
553
|
+ }
|
|
554
|
+ }
|
|
555
|
+ } catch (error) {
|
|
556
|
+ console.error('Failed to load files for new message:', error);
|
|
557
|
+ }
|
|
558
|
+ }
|
|
559
|
+
|
437
|
560
|
isLoading.value = false;
|
438
|
561
|
await nextTick();
|
439
|
562
|
scrollToBottom();
|
|
@@ -452,7 +575,47 @@ const handleCtrlEnter = () => {
|
452
|
575
|
};
|
453
|
576
|
|
454
|
577
|
const handleFileUpload = () => {
|
455
|
|
- ElMessage.info('文件上传功能开发中...');
|
|
578
|
+ if (fileInput.value) {
|
|
579
|
+ fileInput.value.click();
|
|
580
|
+ }
|
|
581
|
+};
|
|
582
|
+
|
|
583
|
+const handleFileSelect = async (event) => {
|
|
584
|
+ const files = Array.from(event.target.files);
|
|
585
|
+ if (files.length > 0) {
|
|
586
|
+ // 清空input值,允许选择相同文件
|
|
587
|
+ event.target.value = '';
|
|
588
|
+
|
|
589
|
+ // 立即上传文件
|
|
590
|
+ try {
|
|
591
|
+ isUploading.value = true;
|
|
592
|
+ let res = await uploadDocument(files);
|
|
593
|
+ documentChartId.value = res.chatId;
|
|
594
|
+ // 上传成功后,将文件添加到已上传列表
|
|
595
|
+ selectedFiles.value = [...selectedFiles.value, ...files];
|
|
596
|
+ ElMessage.success(`成功上传 ${files.length} 个文件`);
|
|
597
|
+
|
|
598
|
+ } catch (error) {
|
|
599
|
+ ElMessage.error('文件上传失败');
|
|
600
|
+ console.error('File upload error:', error);
|
|
601
|
+ } finally {
|
|
602
|
+ isUploading.value = false;
|
|
603
|
+ }
|
|
604
|
+ }
|
|
605
|
+};
|
|
606
|
+
|
|
607
|
+const removeFile = (index) => {
|
|
608
|
+ selectedFiles.value.splice(index, 1);
|
|
609
|
+};
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+const formatFileSize = (bytes) => {
|
|
614
|
+ if (bytes === 0) return '0 B';
|
|
615
|
+ const k = 1024;
|
|
616
|
+ const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
617
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
618
|
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
456
|
619
|
};
|
457
|
620
|
|
458
|
621
|
const scrollToBottom = () => {
|
|
@@ -579,12 +742,16 @@ const flatMessages = computed(() => {
|
579
|
742
|
for (const msg of chatMessages.value) {
|
580
|
743
|
// 处理用户消息
|
581
|
744
|
if (msg.input && msg.input.trim()) {
|
582
|
|
- arr.push({
|
|
745
|
+ const fileList = msg.chatId ? messageFileMap.value.get(msg.chatId) || [] : [];
|
|
746
|
+ const userMessage = {
|
583
|
747
|
type: 'user',
|
584
|
748
|
text: msg.input,
|
585
|
749
|
time: msg.inputTime,
|
586
|
750
|
id: msg.chatId || (msg.topicId + '_input_' + msg.inputTime),
|
587
|
|
- })
|
|
751
|
+ chatId: msg.chatId,
|
|
752
|
+ fileList: fileList
|
|
753
|
+ };
|
|
754
|
+ arr.push(userMessage);
|
588
|
755
|
}
|
589
|
756
|
// 处理AI回答消息
|
590
|
757
|
if (msg.output && msg.output.trim()) {
|
|
@@ -662,20 +829,20 @@ onMounted(() => {
|
662
|
829
|
height: 100%;
|
663
|
830
|
padding: 20px;
|
664
|
831
|
overflow-y: auto;
|
665
|
|
-
|
|
832
|
+
|
666
|
833
|
&::-webkit-scrollbar {
|
667
|
834
|
width: 6px;
|
668
|
835
|
}
|
669
|
|
-
|
|
836
|
+
|
670
|
837
|
&::-webkit-scrollbar-track {
|
671
|
838
|
background: #f1f1f1;
|
672
|
839
|
border-radius: 3px;
|
673
|
840
|
}
|
674
|
|
-
|
|
841
|
+
|
675
|
842
|
&::-webkit-scrollbar-thumb {
|
676
|
843
|
background: #c1c1c1;
|
677
|
844
|
border-radius: 3px;
|
678
|
|
-
|
|
845
|
+
|
679
|
846
|
&:hover {
|
680
|
847
|
background: #a8a8a8;
|
681
|
848
|
}
|
|
@@ -799,6 +966,65 @@ onMounted(() => {
|
799
|
966
|
text-align: right;
|
800
|
967
|
}
|
801
|
968
|
}
|
|
969
|
+
|
|
970
|
+ .message-files {
|
|
971
|
+ margin-top: 8px;
|
|
972
|
+ border: 1px solid #e9ecef;
|
|
973
|
+ border-radius: 8px;
|
|
974
|
+ background-color: #f8f9fa;
|
|
975
|
+ padding: 8px;
|
|
976
|
+
|
|
977
|
+ .files-header {
|
|
978
|
+ display: flex;
|
|
979
|
+ align-items: center;
|
|
980
|
+ gap: 6px;
|
|
981
|
+ font-size: 12px;
|
|
982
|
+ color: #666;
|
|
983
|
+ margin-bottom: 6px;
|
|
984
|
+ font-weight: 500;
|
|
985
|
+
|
|
986
|
+ .el-icon {
|
|
987
|
+ font-size: 14px;
|
|
988
|
+ }
|
|
989
|
+ }
|
|
990
|
+
|
|
991
|
+ .files-list {
|
|
992
|
+ .file-item-display {
|
|
993
|
+ display: flex;
|
|
994
|
+ align-items: center;
|
|
995
|
+ gap: 6px;
|
|
996
|
+ padding: 4px 8px;
|
|
997
|
+ background-color: white;
|
|
998
|
+ border-radius: 4px;
|
|
999
|
+ margin-bottom: 4px;
|
|
1000
|
+ border: 1px solid #e9ecef;
|
|
1001
|
+
|
|
1002
|
+ &:last-child {
|
|
1003
|
+ margin-bottom: 0;
|
|
1004
|
+ }
|
|
1005
|
+
|
|
1006
|
+ .file-icon {
|
|
1007
|
+ color: #666;
|
|
1008
|
+ font-size: 14px;
|
|
1009
|
+ }
|
|
1010
|
+
|
|
1011
|
+ .file-name {
|
|
1012
|
+ flex: 1;
|
|
1013
|
+ font-size: 12px;
|
|
1014
|
+ color: #333;
|
|
1015
|
+ white-space: nowrap;
|
|
1016
|
+ overflow: hidden;
|
|
1017
|
+ text-overflow: ellipsis;
|
|
1018
|
+ }
|
|
1019
|
+
|
|
1020
|
+ .file-size {
|
|
1021
|
+ font-size: 11px;
|
|
1022
|
+ color: #999;
|
|
1023
|
+ white-space: nowrap;
|
|
1024
|
+ }
|
|
1025
|
+ }
|
|
1026
|
+ }
|
|
1027
|
+ }
|
802
|
1028
|
}
|
803
|
1029
|
}
|
804
|
1030
|
|
|
@@ -830,6 +1056,98 @@ onMounted(() => {
|
830
|
1056
|
padding: 16px;
|
831
|
1057
|
background-color: white;
|
832
|
1058
|
|
|
1059
|
+ .file-list-container {
|
|
1060
|
+ margin-bottom: 12px;
|
|
1061
|
+ border: 1px solid #e9ecef;
|
|
1062
|
+ border-radius: 8px;
|
|
1063
|
+ background-color: #f8f9fa;
|
|
1064
|
+
|
|
1065
|
+ .file-list-header {
|
|
1066
|
+ display: flex;
|
|
1067
|
+ justify-content: space-between;
|
|
1068
|
+ align-items: center;
|
|
1069
|
+ padding: 8px 12px;
|
|
1070
|
+ border-bottom: 1px solid #e9ecef;
|
|
1071
|
+ font-size: 14px;
|
|
1072
|
+ font-weight: 500;
|
|
1073
|
+ color: #333;
|
|
1074
|
+
|
|
1075
|
+ .loading-icon {
|
|
1076
|
+ animation: rotate 1s linear infinite;
|
|
1077
|
+ color: #007bff;
|
|
1078
|
+ font-size: 16px;
|
|
1079
|
+ }
|
|
1080
|
+ }
|
|
1081
|
+
|
|
1082
|
+ .file-list {
|
|
1083
|
+ max-height: 200px;
|
|
1084
|
+ overflow-y: auto;
|
|
1085
|
+ padding: 8px;
|
|
1086
|
+
|
|
1087
|
+ .file-item {
|
|
1088
|
+ display: flex;
|
|
1089
|
+ justify-content: space-between;
|
|
1090
|
+ align-items: center;
|
|
1091
|
+ padding: 8px 12px;
|
|
1092
|
+ margin-bottom: 4px;
|
|
1093
|
+ background-color: white;
|
|
1094
|
+ border-radius: 6px;
|
|
1095
|
+ border: 1px solid #e9ecef;
|
|
1096
|
+ transition: all 0.2s ease;
|
|
1097
|
+
|
|
1098
|
+ &:hover {
|
|
1099
|
+ border-color: #007bff;
|
|
1100
|
+ box-shadow: 0 2px 4px rgba(0, 123, 255, 0.1);
|
|
1101
|
+ }
|
|
1102
|
+
|
|
1103
|
+ &:last-child {
|
|
1104
|
+ margin-bottom: 0;
|
|
1105
|
+ }
|
|
1106
|
+
|
|
1107
|
+ .file-info {
|
|
1108
|
+ display: flex;
|
|
1109
|
+ align-items: center;
|
|
1110
|
+ flex: 1;
|
|
1111
|
+ min-width: 0;
|
|
1112
|
+
|
|
1113
|
+ .file-icon {
|
|
1114
|
+ margin-right: 8px;
|
|
1115
|
+ color: #666;
|
|
1116
|
+ font-size: 16px;
|
|
1117
|
+ }
|
|
1118
|
+
|
|
1119
|
+ .file-name {
|
|
1120
|
+ font-size: 14px;
|
|
1121
|
+ color: #333;
|
|
1122
|
+ white-space: nowrap;
|
|
1123
|
+ overflow: hidden;
|
|
1124
|
+ text-overflow: ellipsis;
|
|
1125
|
+ margin-right: 8px;
|
|
1126
|
+ }
|
|
1127
|
+
|
|
1128
|
+ .file-size {
|
|
1129
|
+ font-size: 12px;
|
|
1130
|
+ color: #999;
|
|
1131
|
+ white-space: nowrap;
|
|
1132
|
+ }
|
|
1133
|
+ }
|
|
1134
|
+
|
|
1135
|
+ .remove-btn {
|
|
1136
|
+ color: #dc3545;
|
|
1137
|
+ border: none;
|
|
1138
|
+ background: transparent;
|
|
1139
|
+ padding: 4px;
|
|
1140
|
+ min-height: auto;
|
|
1141
|
+
|
|
1142
|
+ &:hover {
|
|
1143
|
+ background-color: rgba(220, 53, 69, 0.1);
|
|
1144
|
+ color: #dc3545;
|
|
1145
|
+ }
|
|
1146
|
+ }
|
|
1147
|
+ }
|
|
1148
|
+ }
|
|
1149
|
+ }
|
|
1150
|
+
|
833
|
1151
|
.input-wrapper {
|
834
|
1152
|
position: relative;
|
835
|
1153
|
border: 1px solid #e9ecef;
|
|
@@ -989,4 +1307,14 @@ onMounted(() => {
|
989
|
1307
|
transform: scale(1);
|
990
|
1308
|
}
|
991
|
1309
|
}
|
|
1310
|
+
|
|
1311
|
+@keyframes rotate {
|
|
1312
|
+ from {
|
|
1313
|
+ transform: rotate(0deg);
|
|
1314
|
+ }
|
|
1315
|
+
|
|
1316
|
+ to {
|
|
1317
|
+ transform: rotate(360deg);
|
|
1318
|
+ }
|
|
1319
|
+}
|
992
|
1320
|
</style>
|