69 lines
1.6 KiB
TypeScript
69 lines
1.6 KiB
TypeScript
import { ref } from 'vue'
|
||
import { useAppStore } from '@/stores/app'
|
||
import { i18n } from '@/i18n'
|
||
|
||
const { t } = i18n.global
|
||
|
||
/**
|
||
* 检测是否支持 Clipboard API(需要安全上下文:HTTPS/localhost)
|
||
*/
|
||
function isClipboardSupported(): boolean {
|
||
return !!(navigator.clipboard && window.isSecureContext)
|
||
}
|
||
|
||
/**
|
||
* 降级方案:使用 textarea + execCommand
|
||
* 使用 textarea 而非 input,以正确处理多行文本
|
||
*/
|
||
function fallbackCopy(text: string): boolean {
|
||
const textarea = document.createElement('textarea')
|
||
textarea.value = text
|
||
textarea.style.cssText = 'position:fixed;left:-9999px;top:-9999px'
|
||
document.body.appendChild(textarea)
|
||
textarea.select()
|
||
try {
|
||
return document.execCommand('copy')
|
||
} finally {
|
||
document.body.removeChild(textarea)
|
||
}
|
||
}
|
||
|
||
export function useClipboard() {
|
||
const appStore = useAppStore()
|
||
const copied = ref(false)
|
||
|
||
const copyToClipboard = async (
|
||
text: string,
|
||
successMessage?: string
|
||
): Promise<boolean> => {
|
||
if (!text) return false
|
||
|
||
let success = false
|
||
|
||
if (isClipboardSupported()) {
|
||
try {
|
||
await navigator.clipboard.writeText(text)
|
||
success = true
|
||
} catch {
|
||
success = fallbackCopy(text)
|
||
}
|
||
} else {
|
||
success = fallbackCopy(text)
|
||
}
|
||
|
||
if (success) {
|
||
copied.value = true
|
||
appStore.showSuccess(successMessage || t('common.copiedToClipboard'))
|
||
setTimeout(() => {
|
||
copied.value = false
|
||
}, 2000)
|
||
} else {
|
||
appStore.showError(t('common.copyFailed'))
|
||
}
|
||
|
||
return success
|
||
}
|
||
|
||
return { copied, copyToClipboard }
|
||
}
|