Skip to content

Commit 9e2fcf4

Browse files
author
Kerwin
committed
perf: 优化复制逻辑
1 parent bc27535 commit 9e2fcf4

File tree

12 files changed

+130
-183
lines changed

12 files changed

+130
-183
lines changed

config/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

config/proxy.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/components/common/Setting/About.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ onMounted(() => {
8888
<div class="flex-1">
8989
<NInput :value="config && config.accessToken" placeholder="" @input="(val) => { if (config) config.accessToken = val }" />
9090
</div>
91+
<p>
92+
<a target="_blank" href="https://chat.openai.com/api/auth/session">Get Token</a>
93+
</p>
9194
</div>
9295
<div v-if="!isChatGPTAPI" class="flex items-center space-x-4">
9396
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.reverseProxy') }}</span>

src/utils/copy.ts

Lines changed: 18 additions & 0 deletions
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/utils/crypto/index.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/utils/format/index.ts

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/utils/storage/index.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,57 @@
1-
export * from './local'
1+
interface StorageData<T = any> {
2+
data: T
3+
expire: number | null
4+
}
5+
6+
export function createLocalStorage(options?: { expire?: number | null }) {
7+
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
8+
9+
const { expire } = Object.assign({ expire: DEFAULT_CACHE_TIME }, options)
10+
11+
function set<T = any>(key: string, data: T) {
12+
const storageData: StorageData<T> = {
13+
data,
14+
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
15+
}
16+
17+
const json = JSON.stringify(storageData)
18+
window.localStorage.setItem(key, json)
19+
}
20+
21+
function get(key: string) {
22+
const json = window.localStorage.getItem(key)
23+
if (json) {
24+
let storageData: StorageData | null = null
25+
26+
try {
27+
storageData = JSON.parse(json)
28+
}
29+
catch {
30+
// Prevent failure
31+
}
32+
33+
if (storageData) {
34+
const { data, expire } = storageData
35+
if (expire === null || expire >= Date.now())
36+
return data
37+
}
38+
39+
remove(key)
40+
return null
41+
}
42+
}
43+
44+
function remove(key: string) {
45+
window.localStorage.removeItem(key)
46+
}
47+
48+
function clear() {
49+
window.localStorage.clear()
50+
}
51+
52+
return { set, get, remove, clear }
53+
}
54+
55+
export const ls = createLocalStorage()
56+
57+
export const ss = createLocalStorage({ expire: null })

src/utils/storage/local.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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'
@@ -22,7 +22,7 @@ const { isMobile } = useBasicLayout()
2222
const textRef = ref<HTMLElement>()
2323
2424
const mdi = new MarkdownIt({
25-
html: true,
25+
html: false,
2626
linkify: true,
2727
highlight(code, language) {
2828
const validLang = !!(language && hljs.getLanguage(language))
@@ -61,7 +61,41 @@ function highlightBlock(str: string, lang?: string) {
6161
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>`
6262
}
6363
64-
defineExpose({ textRef })
64+
function addCopyEvents() {
65+
if (textRef.value) {
66+
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
67+
copyBtn.forEach((btn) => {
68+
btn.addEventListener('click', () => {
69+
const code = btn.parentElement?.nextElementSibling?.textContent
70+
if (code) {
71+
copyToClip(code).then(() => {
72+
btn.textContent = '复制成功'
73+
setTimeout(() => {
74+
btn.textContent = '复制代码'
75+
}, 1000)
76+
})
77+
}
78+
})
79+
})
80+
}
81+
}
82+
function removeCopyEvents() {
83+
if (textRef.value) {
84+
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
85+
copyBtn.forEach((btn) => {
86+
btn.removeEventListener('click', () => { })
87+
})
88+
}
89+
}
90+
onMounted(() => {
91+
addCopyEvents()
92+
})
93+
onUpdated(() => {
94+
addCopyEvents()
95+
})
96+
onUnmounted(() => {
97+
removeCopyEvents()
98+
})
6599
</script>
66100

67101
<template>

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

Lines changed: 15 additions & 3 deletions
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, NPopover } from 'naive-ui'
3+
import { NDropdown, NPopover, 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
@@ -36,6 +36,8 @@ const { isMobile } = useBasicLayout()
3636
3737
const { iconRender } = useIconRender()
3838
39+
const message = useMessage()
40+
3941
const textRef = ref<HTMLElement>()
4042
4143
const asRawText = ref(props.inversion)
@@ -72,7 +74,7 @@ const options = computed(() => {
7274
function handleSelect(key: 'copyText' | 'delete' | 'toggleRenderType') {
7375
switch (key) {
7476
case 'copyText':
75-
copyText({ text: props.text ?? '' })
77+
handleCopy()
7678
return
7779
case 'toggleRenderType':
7880
asRawText.value = !asRawText.value
@@ -86,6 +88,16 @@ function handleRegenerate() {
8688
messageRef.value?.scrollIntoView()
8789
emit('regenerate')
8890
}
91+
92+
async function handleCopy() {
93+
try {
94+
await copyToClip(props.text || '')
95+
message.success('复制成功')
96+
}
97+
catch {
98+
message.error('复制失败')
99+
}
100+
}
89101
</script>
90102

91103
<template>

0 commit comments

Comments
 (0)