From fcf1dec549c74a809b7e9dd43392c3ae3b46e852 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 1 Aug 2026 17:41:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(douyu):=20=E4=BA=8C=E7=BB=B4=E7=A0=81?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E5=A2=9E=E5=8A=A0=E5=A4=8D=E5=88=B6=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81/=E5=A4=8D=E5=88=B6=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 QrActions 组件:复制二维码图片、复制二维码链接(含 execCommand 降级) - 精英宝典绑定/电竞手册/支付三个弹窗统一功能按钮在上、操作按钮在下 --- web/frontend/src/components/QrActions.tsx | 86 +++++++++++++++++++++++ web/frontend/src/pages/DouyuTasksPage.tsx | 19 ++++- 2 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 web/frontend/src/components/QrActions.tsx diff --git a/web/frontend/src/components/QrActions.tsx b/web/frontend/src/components/QrActions.tsx new file mode 100644 index 0000000..3c3c438 --- /dev/null +++ b/web/frontend/src/components/QrActions.tsx @@ -0,0 +1,86 @@ +import { useState } from 'react'; +import type { RefObject } from 'react'; +import { Button, Space } from 'antd'; +import { CopyOutlined, LinkOutlined } from '@ant-design/icons'; +import { copyImageToClipboard } from '../utils/exchangeImage'; +import { getErrorMessage } from '../utils/error'; +import { message } from '../utils/antdMessage'; + +interface Props { + wrapRef: RefObject; + url?: string; + disabled?: boolean; +} + +function copyTextToClipboard(text: string): Promise { + if (navigator.clipboard?.writeText) { + return navigator.clipboard.writeText(text); + } + // fallback:利用 textarea + execCommand,兼容 http 远程访问 + return new Promise((resolve, reject) => { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.left = '-9999px'; + document.body.appendChild(ta); + ta.select(); + try { + const ok = document.execCommand('copy'); + document.body.removeChild(ta); + if (ok) { + resolve(); + } else { + reject(new Error('execCommand copy failed')); + } + } catch (e) { + document.body.removeChild(ta); + reject(e); + } + }); +} + +export default function QrActions({ wrapRef, url, disabled }: Props) { + const [copyingImage, setCopyingImage] = useState(false); + const [copyingLink, setCopyingLink] = useState(false); + + const copyImage = async () => { + const canvas = wrapRef.current?.querySelector('canvas'); + if (!canvas) return; + setCopyingImage(true); + try { + const blob = await new Promise((resolve, reject) => { + canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('二维码图片导出失败'))), 'image/png'); + }); + await copyImageToClipboard(blob); + message.success('二维码已复制到剪贴板'); + } catch (error) { + message.error(getErrorMessage(error)); + } finally { + setCopyingImage(false); + } + }; + + const copyLink = async () => { + if (!url) return; + setCopyingLink(true); + try { + await copyTextToClipboard(url); + message.success('二维码链接已复制'); + } catch (error) { + message.error(getErrorMessage(error)); + } finally { + setCopyingLink(false); + } + }; + + return ( + + + + + ); +} diff --git a/web/frontend/src/pages/DouyuTasksPage.tsx b/web/frontend/src/pages/DouyuTasksPage.tsx index 91fdee0..4a19d73 100644 --- a/web/frontend/src/pages/DouyuTasksPage.tsx +++ b/web/frontend/src/pages/DouyuTasksPage.tsx @@ -16,6 +16,7 @@ import { type DouyuTaskItem, } from '../api/modules'; import ExchangeResultImage from '../components/ExchangeResultImage'; +import QrActions from '../components/QrActions'; import { usePermissions } from '../hooks/usePermissions'; import { useWebSocketLogs } from '../hooks/useWebSocketLogs'; import { @@ -279,6 +280,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) const [activeQrTaskId, setActiveQrTaskId] = useState(null); const [esportsBindTask, setEsportsBindTask] = useState(null); const [payTask, setPayTask] = useState(null); + const qrCanvasWrapRef = useRef(null); + const esportsQrCanvasWrapRef = useRef(null); + const payQrCanvasWrapRef = useRef(null); const autoOpenedQrTaskIds = useRef>(new Set()); const autoOpenedEsportsBindTaskIds = useRef>(new Set()); const autoOpenedPayTaskIds = useRef>(new Set()); @@ -1608,13 +1612,16 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) {esportsQrUrl ? (
- +
+ +
请在腾讯页面选择角色,完成后查询最新角色。
) : null} +