vue_zhongyou 1.0.24 → 1.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package//345/212/237/350/203/275/344/273/243/347/240/201/AI/345/257/271/350/257/235/aiChatPage.vue
CHANGED
|
@@ -125,6 +125,30 @@ import ChatForm from '@/components/ChatForm.vue'
|
|
|
125
125
|
import NewChatCardList from '@/components/newChatCardList.vue'
|
|
126
126
|
import DynamicMobileForm from '@/components/dynamicMobileForm.vue'
|
|
127
127
|
import inputCom from '@/components/inputCom.vue'
|
|
128
|
+
import request from '@/api/request'
|
|
129
|
+
// 引入核心库
|
|
130
|
+
import { marked } from 'marked';
|
|
131
|
+
import hljs from 'highlight.js';
|
|
132
|
+
// 引入高亮样式(任选一种)
|
|
133
|
+
import 'highlight.js/styles/github.css';
|
|
134
|
+
|
|
135
|
+
// 配置marked
|
|
136
|
+
marked.setOptions({
|
|
137
|
+
renderer: new marked.Renderer(),
|
|
138
|
+
highlight: function(code, lang) {
|
|
139
|
+
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
140
|
+
return hljs.highlight(code, { language }).value;
|
|
141
|
+
},
|
|
142
|
+
langPrefix: 'hljs language-',
|
|
143
|
+
pedantic: false,
|
|
144
|
+
gfm: true,
|
|
145
|
+
breaks: true,
|
|
146
|
+
sanitize: false,
|
|
147
|
+
smartLists: true,
|
|
148
|
+
smartypants: false,
|
|
149
|
+
xhtml: false
|
|
150
|
+
});
|
|
151
|
+
|
|
128
152
|
|
|
129
153
|
// 消息列表
|
|
130
154
|
const messages = ref([])
|
|
@@ -138,10 +162,19 @@ const chatContainerRef = ref(null)
|
|
|
138
162
|
const sessionId = ref('')
|
|
139
163
|
// 输入组件引用
|
|
140
164
|
const inputComRef = ref(null)
|
|
141
|
-
//
|
|
142
|
-
const
|
|
165
|
+
// Axios取消请求控制器
|
|
166
|
+
const abortControllerRef = ref(null)
|
|
143
167
|
// 当前正在接收流式响应的消息索引
|
|
144
168
|
const streamingMessageIndex = ref(-1)
|
|
169
|
+
// SSE重连相关
|
|
170
|
+
const reconnectAttempts = ref(0)
|
|
171
|
+
const maxReconnectAttempts = ref(3)
|
|
172
|
+
const reconnectDelay = ref(1000) // 初始重连延迟1秒
|
|
173
|
+
const reconnectTimerRef = ref(null)
|
|
174
|
+
// 当前请求体,用于重连时重新发送
|
|
175
|
+
const currentRequestBody = ref(null)
|
|
176
|
+
// 当前用户输入,用于重连时重新发送
|
|
177
|
+
const currentUserInput = ref('')
|
|
145
178
|
|
|
146
179
|
|
|
147
180
|
// 判断消息类型
|
|
@@ -159,6 +192,7 @@ const isCardListMessage = (message) => {
|
|
|
159
192
|
|
|
160
193
|
// 初始化欢迎消息
|
|
161
194
|
onMounted(() => {
|
|
195
|
+
|
|
162
196
|
addMessage('assistant', '您好!有什么可以帮助您的吗?', false)
|
|
163
197
|
scrollToBottom()
|
|
164
198
|
})
|
|
@@ -235,11 +269,21 @@ const sendMessage = async () => {
|
|
|
235
269
|
const text = inputComRef.value.inputText.trim()
|
|
236
270
|
if (!text || isLoading.value) return
|
|
237
271
|
|
|
238
|
-
//
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
eventSourceRef.value = null
|
|
272
|
+
// 取消之前的请求和重连定时器
|
|
273
|
+
if (abortControllerRef.value) {
|
|
274
|
+
abortControllerRef.value.abort()
|
|
242
275
|
}
|
|
276
|
+
if (reconnectTimerRef.value) {
|
|
277
|
+
clearTimeout(reconnectTimerRef.value)
|
|
278
|
+
reconnectTimerRef.value = null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 重置重连计数器和延迟
|
|
282
|
+
reconnectAttempts.value = 0
|
|
283
|
+
reconnectDelay.value = 1000
|
|
284
|
+
|
|
285
|
+
// 保存当前请求信息,用于重连
|
|
286
|
+
currentUserInput.value = text
|
|
243
287
|
|
|
244
288
|
// 添加用户消息
|
|
245
289
|
addMessage('user', text)
|
|
@@ -267,60 +311,159 @@ const sendMessage = async () => {
|
|
|
267
311
|
}
|
|
268
312
|
}
|
|
269
313
|
}
|
|
314
|
+
|
|
315
|
+
// 保存请求体,用于重连
|
|
316
|
+
currentRequestBody.value = requestBody
|
|
317
|
+
|
|
318
|
+
// 执行SSE请求
|
|
319
|
+
await executeSSERequest(requestBody, text)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 执行SSE请求
|
|
323
|
+
const executeSSERequest = async (requestBody, userText) => {
|
|
324
|
+
// 创建新的AbortController
|
|
325
|
+
abortControllerRef.value = new AbortController()
|
|
270
326
|
|
|
271
327
|
try {
|
|
272
|
-
//
|
|
328
|
+
// 使用fetch API发送SSE请求
|
|
273
329
|
const baseURL = import.meta.env.VITE_API_BASE_URL || ''
|
|
274
|
-
const url =
|
|
330
|
+
const url = `http://localhost:8080/ai/chat`
|
|
275
331
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
332
|
+
const response = await fetch(url, {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: {
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
'Accept': 'text/event-stream',
|
|
337
|
+
'Cache-Control': 'no-cache'
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify({
|
|
340
|
+
message: userText
|
|
341
|
+
}),
|
|
342
|
+
signal: abortControllerRef.value.signal
|
|
343
|
+
})
|
|
281
344
|
|
|
282
|
-
|
|
345
|
+
if (!response.ok) {
|
|
346
|
+
throw new Error(`HTTP error! status: ${response.status}`)
|
|
347
|
+
}
|
|
283
348
|
|
|
284
|
-
//
|
|
285
|
-
|
|
349
|
+
// 使用ReadableStream处理流式响应
|
|
350
|
+
const reader = response.body.getReader()
|
|
351
|
+
const decoder = new TextDecoder()
|
|
352
|
+
let buffer = ''
|
|
286
353
|
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
const data = JSON.parse(event.data)
|
|
291
|
-
handleSSEMessage(data)
|
|
292
|
-
} catch (error) {
|
|
293
|
-
console.error('解析SSE消息失败:', error)
|
|
294
|
-
}
|
|
295
|
-
}
|
|
354
|
+
// 重置重连计数器(连接成功)
|
|
355
|
+
reconnectAttempts.value = 0
|
|
296
356
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
console.
|
|
300
|
-
|
|
357
|
+
while (true) {
|
|
358
|
+
const { done, value } = await reader.read()
|
|
359
|
+
console.log(done, value);
|
|
360
|
+
|
|
361
|
+
if (done) {
|
|
362
|
+
console.log('SSE流读取完成')
|
|
363
|
+
// 流正常结束,不需要重连
|
|
364
|
+
break
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 解码数据块
|
|
368
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
369
|
+
console.log(chunk);
|
|
370
|
+
|
|
371
|
+
buffer += chunk
|
|
372
|
+
|
|
373
|
+
// 处理buffer中的完整消息
|
|
374
|
+
const lines = buffer.split('\n')
|
|
375
|
+
buffer = lines.pop() || '' // 保留最后一个不完整的行
|
|
376
|
+
|
|
377
|
+
for (const line of lines) {
|
|
378
|
+
if (line.startsWith('data: ')) {
|
|
379
|
+
const dataStr = line.slice(6).trim()
|
|
380
|
+
if (dataStr) {
|
|
381
|
+
try {
|
|
382
|
+
const data = JSON.parse(dataStr)
|
|
383
|
+
handleSSEMessage(data)
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error('解析SSE消息失败:', error, dataStr)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
301
390
|
}
|
|
302
391
|
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
392
|
+
// 处理buffer中剩余的数据
|
|
393
|
+
if (buffer.trim()) {
|
|
394
|
+
const lines = buffer.split('\n')
|
|
395
|
+
for (const line of lines) {
|
|
396
|
+
if (line.startsWith('data: ')) {
|
|
397
|
+
const dataStr = line.slice(6).trim()
|
|
398
|
+
if (dataStr) {
|
|
399
|
+
try {
|
|
400
|
+
const data = JSON.parse(dataStr)
|
|
401
|
+
handleSSEMessage(data)
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error('解析SSE消息失败:', error, dataStr)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
306
408
|
}
|
|
307
409
|
|
|
410
|
+
// 请求完成
|
|
411
|
+
console.log('SSE请求完成')
|
|
308
412
|
} catch (error) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
413
|
+
if (error.name === 'AbortError') {
|
|
414
|
+
console.log('请求已取消')
|
|
415
|
+
} else {
|
|
416
|
+
console.error('SSE请求失败:', error)
|
|
417
|
+
// 处理SSE错误并重试
|
|
418
|
+
handleSSEError('连接失败,请重试')
|
|
419
|
+
// 尝试重连
|
|
420
|
+
attemptReconnect()
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 尝试重新连接
|
|
426
|
+
const attemptReconnect = () => {
|
|
427
|
+
// 检查是否达到最大重试次数
|
|
428
|
+
if (reconnectAttempts.value >= maxReconnectAttempts.value) {
|
|
429
|
+
console.error('已达到最大重连次数,停止重连')
|
|
430
|
+
// 更新消息状态
|
|
431
|
+
const messageIndex = streamingMessageIndex.value
|
|
432
|
+
if (messageIndex !== -1 && messages.value[messageIndex]) {
|
|
433
|
+
messages.value[messageIndex].loading = false
|
|
434
|
+
messages.value[messageIndex].content = '连接已断开,建议重试'
|
|
435
|
+
}
|
|
436
|
+
isLoading.value = false
|
|
437
|
+
return
|
|
312
438
|
}
|
|
439
|
+
|
|
440
|
+
// 增加重连计数器
|
|
441
|
+
reconnectAttempts.value++
|
|
442
|
+
|
|
443
|
+
// 指数退避策略,每次重连延迟翻倍
|
|
444
|
+
const delay = reconnectDelay.value * Math.pow(2, reconnectAttempts.value - 1)
|
|
445
|
+
|
|
446
|
+
console.log(`SSE连接失败,${delay}ms后尝试第${reconnectAttempts.value}次重连...`)
|
|
447
|
+
|
|
448
|
+
// 设置重连定时器
|
|
449
|
+
reconnectTimerRef.value = setTimeout(async () => {
|
|
450
|
+
console.log(`尝试第${reconnectAttempts.value}次重连...`)
|
|
451
|
+
|
|
452
|
+
// 重新执行SSE请求
|
|
453
|
+
await executeSSERequest(currentRequestBody.value, currentUserInput.value)
|
|
454
|
+
}, delay)
|
|
313
455
|
}
|
|
314
456
|
|
|
315
457
|
// 处理SSE消息
|
|
316
458
|
const handleSSEMessage = (data) => {
|
|
459
|
+
|
|
317
460
|
const messageIndex = streamingMessageIndex.value
|
|
461
|
+
|
|
318
462
|
if (messageIndex === -1 || !messages.value[messageIndex]) return
|
|
319
463
|
|
|
320
464
|
const currentMessage = messages.value[messageIndex]
|
|
321
|
-
|
|
322
465
|
// 根据消息类型处理
|
|
323
|
-
if (data.type === 'text') {
|
|
466
|
+
if (data.type === 'text' || data.type === 'message') {
|
|
324
467
|
// 文本流式更新
|
|
325
468
|
if (typeof currentMessage.content !== 'string') {
|
|
326
469
|
currentMessage.content = ''
|
|
@@ -340,7 +483,7 @@ const handleSSEMessage = (data) => {
|
|
|
340
483
|
for (let key in data) {
|
|
341
484
|
currentMessage[key] = data[key]
|
|
342
485
|
}
|
|
343
|
-
} else if (data.type === '
|
|
486
|
+
} else if (data.type === 'end') {
|
|
344
487
|
// 流式传输完成
|
|
345
488
|
currentMessage.loading = false
|
|
346
489
|
isLoading.value = false
|
|
@@ -348,16 +491,17 @@ const handleSSEMessage = (data) => {
|
|
|
348
491
|
// 为AI助手消息添加推荐信息
|
|
349
492
|
currentMessage.suggestions = generateSuggestions(currentMessage.content)
|
|
350
493
|
|
|
351
|
-
//
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
eventSourceRef.value = null
|
|
494
|
+
// 清理请求控制器
|
|
495
|
+
if (abortControllerRef.value) {
|
|
496
|
+
abortControllerRef.value = null
|
|
355
497
|
}
|
|
356
498
|
streamingMessageIndex.value = -1
|
|
357
499
|
} else if (data.type === 'error') {
|
|
358
500
|
// 错误消息
|
|
359
501
|
handleSSEError(data.message || '服务器错误')
|
|
360
502
|
}
|
|
503
|
+
console.log(messages.value);
|
|
504
|
+
|
|
361
505
|
}
|
|
362
506
|
|
|
363
507
|
// 处理SSE错误
|
|
@@ -375,26 +519,39 @@ const handleSSEError = (errorMessage = '连接中断,请重试') => {
|
|
|
375
519
|
isLoading.value = false
|
|
376
520
|
showToast(errorMessage)
|
|
377
521
|
|
|
378
|
-
//
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
eventSourceRef.value = null
|
|
522
|
+
// 清理请求控制器
|
|
523
|
+
if (abortControllerRef.value) {
|
|
524
|
+
abortControllerRef.value = null
|
|
382
525
|
}
|
|
383
526
|
streamingMessageIndex.value = -1
|
|
384
527
|
}
|
|
385
528
|
|
|
386
529
|
// 组件卸载时清理SSE连接
|
|
387
530
|
onUnmounted(() => {
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
|
|
531
|
+
if (abortControllerRef.value) {
|
|
532
|
+
abortControllerRef.value.abort()
|
|
533
|
+
abortControllerRef.value = null
|
|
534
|
+
}
|
|
535
|
+
// 清理重连定时器
|
|
536
|
+
if (reconnectTimerRef.value) {
|
|
537
|
+
clearTimeout(reconnectTimerRef.value)
|
|
538
|
+
reconnectTimerRef.value = null
|
|
391
539
|
}
|
|
392
540
|
})
|
|
393
541
|
|
|
394
|
-
//
|
|
542
|
+
// 格式化消息内容(支持Markdown)
|
|
395
543
|
const formatMessage = (content) => {
|
|
396
544
|
if (!content) return ''
|
|
397
|
-
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
// 使用marked将Markdown转换为HTML
|
|
548
|
+
const html = marked(content)
|
|
549
|
+
return html
|
|
550
|
+
} catch (error) {
|
|
551
|
+
console.error('Markdown转换失败:', error)
|
|
552
|
+
// 如果转换失败,回退到简单的换行处理
|
|
553
|
+
return content.replace(/\n/g, '<br>')
|
|
554
|
+
}
|
|
398
555
|
}
|
|
399
556
|
|
|
400
557
|
// 格式化时间
|
|
@@ -625,6 +782,7 @@ const handleLoadMore = (message) => {
|
|
|
625
782
|
// max-width: 75%;
|
|
626
783
|
flex: 1;
|
|
627
784
|
gap: 6px;
|
|
785
|
+
overflow: hidden;
|
|
628
786
|
}
|
|
629
787
|
|
|
630
788
|
.message-bubble {
|
|
@@ -632,11 +790,216 @@ const handleLoadMore = (message) => {
|
|
|
632
790
|
word-wrap: break-word;
|
|
633
791
|
word-break: break-word;
|
|
634
792
|
line-height: 1.5;
|
|
793
|
+
max-width: 100%;
|
|
794
|
+
box-sizing: border-box;
|
|
795
|
+
overflow: hidden;
|
|
635
796
|
}
|
|
636
797
|
|
|
637
798
|
.message-text {
|
|
638
799
|
font-size: 15px;
|
|
639
800
|
white-space: pre-wrap;
|
|
801
|
+
overflow: hidden;
|
|
802
|
+
|
|
803
|
+
// Markdown样式隔离 - 使用深度选择器控制所有Markdown元素
|
|
804
|
+
:deep(*) {
|
|
805
|
+
margin: 0;
|
|
806
|
+
padding: 0;
|
|
807
|
+
box-sizing: border-box;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// 标题样式
|
|
811
|
+
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
|
|
812
|
+
margin: 0.5em 0 0.3em 0;
|
|
813
|
+
font-weight: 600;
|
|
814
|
+
line-height: 1.4;
|
|
815
|
+
color: inherit;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
:deep(h1) {
|
|
819
|
+
font-size: 1.5em;
|
|
820
|
+
border-bottom: 1px solid #eaecef;
|
|
821
|
+
padding-bottom: 0.3em;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
:deep(h2) {
|
|
825
|
+
font-size: 1.3em;
|
|
826
|
+
border-bottom: 1px solid #eaecef;
|
|
827
|
+
padding-bottom: 0.3em;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
:deep(h3) {
|
|
831
|
+
font-size: 1.15em;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
:deep(h4) {
|
|
835
|
+
font-size: 1.05em;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// 段落样式
|
|
839
|
+
:deep(p) {
|
|
840
|
+
margin: 0.3em 0;
|
|
841
|
+
line-height: 1.6;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// 列表样式
|
|
845
|
+
:deep(ul), :deep(ol) {
|
|
846
|
+
margin: 0.5em 0;
|
|
847
|
+
padding-left: 1.5em;
|
|
848
|
+
list-style-position: outside;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
:deep(ul) {
|
|
852
|
+
list-style-type: disc;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
:deep(ol) {
|
|
856
|
+
list-style-type: decimal;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
:deep(li) {
|
|
860
|
+
margin: 0.25em 0;
|
|
861
|
+
line-height: 1.6;
|
|
862
|
+
padding-left: 0.3em;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
:deep(ul ul), :deep(ol ul), :deep(ul ol), :deep(ol ol) {
|
|
866
|
+
margin: 0.25em 0;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// 嵌套列表样式
|
|
870
|
+
:deep(ul ul) {
|
|
871
|
+
list-style-type: circle;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
:deep(ul ul ul) {
|
|
875
|
+
list-style-type: square;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// 代码样式
|
|
879
|
+
:deep(code) {
|
|
880
|
+
font-family: 'Courier New', Courier, monospace;
|
|
881
|
+
background-color: rgba(0, 0, 0, 0.06);
|
|
882
|
+
padding: 0.2em 0.4em;
|
|
883
|
+
border-radius: 3px;
|
|
884
|
+
font-size: 0.9em;
|
|
885
|
+
color: #e83e8c;
|
|
886
|
+
word-break: break-all;
|
|
887
|
+
word-wrap: break-word;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
:deep(pre) {
|
|
891
|
+
background-color: #f6f8fa;
|
|
892
|
+
border: 1px solid #e1e4e8;
|
|
893
|
+
border-radius: 6px;
|
|
894
|
+
padding: 12px;
|
|
895
|
+
margin: 0.5em 0;
|
|
896
|
+
overflow-x: auto;
|
|
897
|
+
overflow-y: hidden;
|
|
898
|
+
font-size: 0.85em;
|
|
899
|
+
line-height: 1.5;
|
|
900
|
+
max-width: 100%;
|
|
901
|
+
box-sizing: border-box;
|
|
902
|
+
-webkit-overflow-scrolling: touch;
|
|
903
|
+
word-break: break-all;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
:deep(pre code) {
|
|
907
|
+
background-color: transparent;
|
|
908
|
+
padding: 0;
|
|
909
|
+
border-radius: 0;
|
|
910
|
+
font-size: inherit;
|
|
911
|
+
color: inherit;
|
|
912
|
+
word-break: break-all;
|
|
913
|
+
word-wrap: break-word;
|
|
914
|
+
white-space: pre-wrap;
|
|
915
|
+
overflow-wrap: anywhere;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// 引用样式
|
|
919
|
+
:deep(blockquote) {
|
|
920
|
+
margin: 0.5em 0;
|
|
921
|
+
padding: 0.5em 1em;
|
|
922
|
+
border-left: 4px solid #dfe2e5;
|
|
923
|
+
background-color: #f6f8fa;
|
|
924
|
+
color: #6a737d;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// 表格样式
|
|
928
|
+
:deep(table) {
|
|
929
|
+
border-collapse: collapse;
|
|
930
|
+
width: 100%;
|
|
931
|
+
margin: 0.5em 0;
|
|
932
|
+
font-size: 0.95em;
|
|
933
|
+
display: block;
|
|
934
|
+
overflow-x: auto;
|
|
935
|
+
-webkit-overflow-scrolling: touch;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
:deep(table th), :deep(table td) {
|
|
939
|
+
border: 1px solid #dfe2e5;
|
|
940
|
+
padding: 6px 12px;
|
|
941
|
+
text-align: left;
|
|
942
|
+
white-space: nowrap;
|
|
943
|
+
min-width: 80px;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
:deep(table th) {
|
|
947
|
+
background-color: #f6f8fa;
|
|
948
|
+
font-weight: 600;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
:deep(table tr:nth-child(even)) {
|
|
952
|
+
background-color: #fafbfc;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// 链接样式
|
|
956
|
+
:deep(a) {
|
|
957
|
+
color: #1989fa;
|
|
958
|
+
text-decoration: none;
|
|
959
|
+
border-bottom: 1px solid transparent;
|
|
960
|
+
transition: border-bottom-color 0.2s;
|
|
961
|
+
word-break: break-all;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
:deep(a:hover) {
|
|
965
|
+
border-bottom-color: #1989fa;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// 图片样式
|
|
969
|
+
:deep(img) {
|
|
970
|
+
max-width: 100%;
|
|
971
|
+
height: auto;
|
|
972
|
+
border-radius: 4px;
|
|
973
|
+
margin: 0.5em 0;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// 分隔线样式
|
|
977
|
+
:deep(hr) {
|
|
978
|
+
border: none;
|
|
979
|
+
border-top: 2px solid #e1e4e8;
|
|
980
|
+
margin: 1em 0;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// 强调样式
|
|
984
|
+
:deep(strong), :deep(b) {
|
|
985
|
+
font-weight: 600;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
:deep(em), :deep(i) {
|
|
989
|
+
font-style: italic;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// 删除线样式
|
|
993
|
+
:deep(del), :deep(s) {
|
|
994
|
+
text-decoration: line-through;
|
|
995
|
+
color: #6a737d;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// 任务列表样式
|
|
999
|
+
:deep(input[type="checkbox"]) {
|
|
1000
|
+
margin-right: 0.5em;
|
|
1001
|
+
vertical-align: middle;
|
|
1002
|
+
}
|
|
640
1003
|
}
|
|
641
1004
|
|
|
642
1005
|
.message-output {
|
|
@@ -648,6 +1011,57 @@ const handleLoadMore = (message) => {
|
|
|
648
1011
|
background-color: #f8f9fa;
|
|
649
1012
|
border-radius: 8px;
|
|
650
1013
|
// border-left: 3px solid #1989fa;
|
|
1014
|
+
|
|
1015
|
+
// Markdown样式隔离
|
|
1016
|
+
:deep(*) {
|
|
1017
|
+
margin: 0;
|
|
1018
|
+
padding: 0;
|
|
1019
|
+
box-sizing: border-box;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
:deep(p) {
|
|
1023
|
+
margin: 0.3em 0;
|
|
1024
|
+
line-height: 1.6;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
:deep(ul), :deep(ol) {
|
|
1028
|
+
margin: 0.5em 0;
|
|
1029
|
+
padding-left: 1.5em;
|
|
1030
|
+
list-style-position: outside;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
:deep(ul) {
|
|
1034
|
+
list-style-type: disc;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
:deep(ol) {
|
|
1038
|
+
list-style-type: decimal;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
:deep(li) {
|
|
1042
|
+
margin: 0.25em 0;
|
|
1043
|
+
line-height: 1.6;
|
|
1044
|
+
padding-left: 0.3em;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
:deep(code) {
|
|
1048
|
+
font-family: 'Courier New', Courier, monospace;
|
|
1049
|
+
background-color: rgba(0, 0, 0, 0.06);
|
|
1050
|
+
padding: 0.2em 0.4em;
|
|
1051
|
+
border-radius: 3px;
|
|
1052
|
+
font-size: 0.9em;
|
|
1053
|
+
color: #e83e8c;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
:deep(a) {
|
|
1057
|
+
color: #1989fa;
|
|
1058
|
+
text-decoration: none;
|
|
1059
|
+
border-bottom: 1px solid transparent;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
:deep(a:hover) {
|
|
1063
|
+
border-bottom-color: #1989fa;
|
|
1064
|
+
}
|
|
651
1065
|
}
|
|
652
1066
|
|
|
653
1067
|
.message-time {
|