Skip to content

Commit dbb57d8

Browse files
committed
perf: 优化复制逻辑
1 parent 3b033d0 commit dbb57d8

File tree

5 files changed

+75
-33
lines changed

5 files changed

+75
-33
lines changed

src/utils/copy.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export function copyToClip(text: string) {
2+
return new Promise((resolve, reject) => {
3+
try {
4+
const input: HTMLTextAreaElement = document.createElement('textarea')
5+
input.setAttribute('readonly', 'readonly')
6+
input.value = text
7+
document.body.appendChild(input)
8+
input.select()
9+
if (document.execCommand('copy'))
10+
document.execCommand('copy')
11+
document.body.removeChild(input)
12+
resolve(text)
13+
}
14+
catch (error) {
15+
reject(error)
16+
}
17+
})
18+
}

src/views/chat/components/Message/Text.vue

+42-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<script lang="ts" setup>
2-
import { computed, ref } from 'vue'
2+
import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue'
33
import MarkdownIt from 'markdown-it'
44
import mdKatex from '@traptitech/markdown-it-katex'
55
import mila from 'markdown-it-link-attributes'
66
import hljs from 'highlight.js'
77
import { useBasicLayout } from '@/hooks/useBasicLayout'
88
import { t } from '@/locales'
9+
import { copyToClip } from '@/utils/copy'
910
1011
interface Props {
1112
inversion?: boolean
@@ -22,7 +23,7 @@ const { isMobile } = useBasicLayout()
2223
const textRef = ref<HTMLElement>()
2324
2425
const mdi = new MarkdownIt({
25-
html: true,
26+
html: false,
2627
linkify: true,
2728
highlight(code, language) {
2829
const validLang = !!(language && hljs.getLanguage(language))
@@ -61,7 +62,45 @@ function highlightBlock(str: string, lang?: string) {
6162
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t('chat.copyCode')}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
6263
}
6364
64-
defineExpose({ textRef })
65+
function addCopyEvents() {
66+
if (textRef.value) {
67+
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
68+
copyBtn.forEach((btn) => {
69+
btn.addEventListener('click', () => {
70+
const code = btn.parentElement?.nextElementSibling?.textContent
71+
if (code) {
72+
copyToClip(code).then(() => {
73+
btn.textContent = '复制成功'
74+
setTimeout(() => {
75+
btn.textContent = '复制代码'
76+
}, 1000)
77+
})
78+
}
79+
})
80+
})
81+
}
82+
}
83+
84+
function removeCopyEvents() {
85+
if (textRef.value) {
86+
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
87+
copyBtn.forEach((btn) => {
88+
btn.removeEventListener('click', () => {})
89+
})
90+
}
91+
}
92+
93+
onMounted(() => {
94+
addCopyEvents()
95+
})
96+
97+
onUpdated(() => {
98+
addCopyEvents()
99+
})
100+
101+
onUnmounted(() => {
102+
removeCopyEvents()
103+
})
65104
</script>
66105

67106
<template>

src/views/chat/components/Message/index.vue

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<script setup lang='ts'>
22
import { computed, ref } from 'vue'
3-
import { NDropdown } from 'naive-ui'
3+
import { NDropdown, useMessage } from 'naive-ui'
44
import AvatarComponent from './Avatar.vue'
55
import TextComponent from './Text.vue'
66
import { SvgIcon } from '@/components/common'
7-
import { copyText } from '@/utils/format'
87
import { useIconRender } from '@/hooks/useIconRender'
98
import { t } from '@/locales'
109
import { useBasicLayout } from '@/hooks/useBasicLayout'
10+
import { copyToClip } from '@/utils/copy'
1111
1212
interface Props {
1313
dateTime?: string
@@ -30,6 +30,8 @@ const { isMobile } = useBasicLayout()
3030
3131
const { iconRender } = useIconRender()
3232
33+
const message = useMessage()
34+
3335
const textRef = ref<HTMLElement>()
3436
3537
const asRawText = ref(props.inversion)
@@ -64,7 +66,7 @@ const options = computed(() => {
6466
function handleSelect(key: 'copyText' | 'delete' | 'toggleRenderType') {
6567
switch (key) {
6668
case 'copyText':
67-
copyText({ text: props.text ?? '' })
69+
handleCopy()
6870
return
6971
case 'toggleRenderType':
7072
asRawText.value = !asRawText.value
@@ -78,6 +80,16 @@ function handleRegenerate() {
7880
messageRef.value?.scrollIntoView()
7981
emit('regenerate')
8082
}
83+
84+
async function handleCopy() {
85+
try {
86+
await copyToClip(props.text || '')
87+
message.success('复制成功')
88+
}
89+
catch {
90+
message.error('复制失败')
91+
}
92+
}
8193
</script>
8294

8395
<template>

src/views/chat/hooks/useCopyCode.ts

-24
This file was deleted.

src/views/chat/index.vue

-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import html2canvas from 'html2canvas'
88
import { Message } from './components'
99
import { useScroll } from './hooks/useScroll'
1010
import { useChat } from './hooks/useChat'
11-
import { useCopyCode } from './hooks/useCopyCode'
1211
import { useUsingContext } from './hooks/useUsingContext'
1312
import HeaderComponent from './components/Header/index.vue'
1413
import { HoverButton, SvgIcon } from '@/components/common'
@@ -27,8 +26,6 @@ const ms = useMessage()
2726
2827
const chatStore = useChatStore()
2928
30-
useCopyCode()
31-
3229
const { isMobile } = useBasicLayout()
3330
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
3431
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()

0 commit comments

Comments
 (0)