Skip to content

Commit 6df4f0e

Browse files
committed
fix: 修复HTTP环境下复制失败问题,添加降级复制方法
1 parent 2982369 commit 6df4f0e

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed

web/src/composables/useCopy.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* 复制到剪贴板工具函数
3+
* 兼容 HTTP 和 HTTPS 环境
4+
*/
5+
export function copyToClipboard(text) {
6+
return new Promise((resolve, reject) => {
7+
// 方法1: 现代浏览器的 Clipboard API (需要 HTTPS)
8+
if (navigator.clipboard && navigator.clipboard.writeText) {
9+
navigator.clipboard.writeText(text)
10+
.then(() => resolve())
11+
.catch(() => {
12+
// 降级到方法2
13+
fallbackCopy(text, resolve, reject)
14+
})
15+
} else {
16+
// 方法2: 传统方法 (兼容 HTTP)
17+
fallbackCopy(text, resolve, reject)
18+
}
19+
})
20+
}
21+
22+
/**
23+
* 降级复制方法:使用 execCommand
24+
* 兼容老版本浏览器和 HTTP 环境
25+
*/
26+
function fallbackCopy(text, resolve, reject) {
27+
const textarea = document.createElement('textarea')
28+
textarea.value = text
29+
textarea.style.position = 'fixed'
30+
textarea.style.top = '0'
31+
textarea.style.left = '0'
32+
textarea.style.opacity = '0'
33+
textarea.style.pointerEvents = 'none'
34+
35+
document.body.appendChild(textarea)
36+
textarea.focus()
37+
textarea.select()
38+
39+
try {
40+
const successful = document.execCommand('copy')
41+
document.body.removeChild(textarea)
42+
43+
if (successful) {
44+
resolve()
45+
} else {
46+
reject(new Error('复制命令执行失败'))
47+
}
48+
} catch (err) {
49+
document.body.removeChild(textarea)
50+
reject(err)
51+
}
52+
}

web/src/pages/DashboardPage.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ import { ref, computed, onMounted, watch, defineAsyncComponent } from 'vue'
209209
import { useI18n } from 'vue-i18n'
210210
import axios from 'axios'
211211
import { API_BASE, SHORT_BASE } from '../composables/shortBase'
212+
import { copyToClipboard } from '../composables/useCopy'
212213
213214
const TrendChart = defineAsyncComponent(() => import('../components/TrendChart.vue'))
214215
const PieDonut = defineAsyncComponent(() => import('../components/charts/PieDonut.vue'))
@@ -273,9 +274,14 @@ function getSharePercent(url) {
273274
return totalClicks.value > 0 ? (Number(url.totalVisits || 0) / totalClicks.value * 100) : 0
274275
}
275276
276-
function copyUrl(shortCode) {
277+
async function copyUrl(shortCode) {
277278
const url = `${SHORT_BASE}/${shortCode}`
278-
try { navigator.clipboard.writeText(url) } catch {}
279+
try {
280+
await copyToClipboard(url)
281+
// 可以添加提示,但需要状态管理
282+
} catch (e) {
283+
console.error('复制失败:', e)
284+
}
279285
}
280286
281287
// API调用

web/src/pages/StatsPage.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ import { ref, computed, onMounted, defineAsyncComponent, watch } from 'vue'
317317
import { useI18n } from 'vue-i18n'
318318
import { useRoute, useRouter } from 'vue-router'
319319
import { SHORT_BASE, API_BASE } from '../composables/shortBase'
320+
import { copyToClipboard } from '../composables/useCopy'
320321
import axios from 'axios'
321322
322323
const TrendChart = defineAsyncComponent(() => import('../components/TrendChart.vue'))
@@ -380,8 +381,12 @@ function getPercent(value, total) {
380381
return total > 0 ? Math.round(value / total * 100) : 0
381382
}
382383
383-
function copy(text) {
384-
try { navigator.clipboard.writeText(text) } catch {}
384+
async function copy(text) {
385+
try {
386+
await copyToClipboard(text)
387+
} catch (e) {
388+
console.error('复制失败:', e)
389+
}
385390
}
386391
387392
// API调用

0 commit comments

Comments
 (0)