feat(douyu): 二维码弹窗增加复制二维码/复制链接按钮
- 新增 QrActions 组件:复制二维码图片、复制二维码链接(含 execCommand 降级) - 精英宝典绑定/电竞手册/支付三个弹窗统一功能按钮在上、操作按钮在下
This commit is contained in:
@@ -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<HTMLDivElement | null>;
|
||||||
|
url?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyTextToClipboard(text: string): Promise<void> {
|
||||||
|
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<Blob>((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 (
|
||||||
|
<Space size={8}>
|
||||||
|
<Button size="small" icon={<CopyOutlined />} loading={copyingImage} disabled={disabled} onClick={copyImage}>
|
||||||
|
复制二维码
|
||||||
|
</Button>
|
||||||
|
<Button size="small" icon={<LinkOutlined />} loading={copyingLink} disabled={disabled || !url} onClick={copyLink}>
|
||||||
|
复制链接
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
type DouyuTaskItem,
|
type DouyuTaskItem,
|
||||||
} from '../api/modules';
|
} from '../api/modules';
|
||||||
import ExchangeResultImage from '../components/ExchangeResultImage';
|
import ExchangeResultImage from '../components/ExchangeResultImage';
|
||||||
|
import QrActions from '../components/QrActions';
|
||||||
import { usePermissions } from '../hooks/usePermissions';
|
import { usePermissions } from '../hooks/usePermissions';
|
||||||
import { useWebSocketLogs } from '../hooks/useWebSocketLogs';
|
import { useWebSocketLogs } from '../hooks/useWebSocketLogs';
|
||||||
import {
|
import {
|
||||||
@@ -279,6 +280,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
const [activeQrTaskId, setActiveQrTaskId] = useState<number | null>(null);
|
const [activeQrTaskId, setActiveQrTaskId] = useState<number | null>(null);
|
||||||
const [esportsBindTask, setEsportsBindTask] = useState<DouyuTaskItem | null>(null);
|
const [esportsBindTask, setEsportsBindTask] = useState<DouyuTaskItem | null>(null);
|
||||||
const [payTask, setPayTask] = useState<DouyuTaskItem | null>(null);
|
const [payTask, setPayTask] = useState<DouyuTaskItem | null>(null);
|
||||||
|
const qrCanvasWrapRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const esportsQrCanvasWrapRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const payQrCanvasWrapRef = useRef<HTMLDivElement | null>(null);
|
||||||
const autoOpenedQrTaskIds = useRef<Set<number>>(new Set());
|
const autoOpenedQrTaskIds = useRef<Set<number>>(new Set());
|
||||||
const autoOpenedEsportsBindTaskIds = useRef<Set<number>>(new Set());
|
const autoOpenedEsportsBindTaskIds = useRef<Set<number>>(new Set());
|
||||||
const autoOpenedPayTaskIds = useRef<Set<number>>(new Set());
|
const autoOpenedPayTaskIds = useRef<Set<number>>(new Set());
|
||||||
@@ -1608,13 +1612,16 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
|
|
||||||
{esportsQrUrl ? (
|
{esportsQrUrl ? (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<QRCode value={esportsQrUrl} size={220} bordered={false} />
|
<div ref={esportsQrCanvasWrapRef}>
|
||||||
|
<QRCode value={esportsQrUrl} size={220} bordered={false} />
|
||||||
|
</div>
|
||||||
<Text type="secondary" style={{ display: 'block', marginTop: 8 }}>
|
<Text type="secondary" style={{ display: 'block', marginTop: 8 }}>
|
||||||
请在腾讯页面选择角色,完成后查询最新角色。
|
请在腾讯页面选择角色,完成后查询最新角色。
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<QrActions wrapRef={esportsQrCanvasWrapRef} url={esportsQrUrl} disabled={!esportsQrUrl} />
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button
|
<Button
|
||||||
icon={<SearchOutlined />}
|
icon={<SearchOutlined />}
|
||||||
@@ -1693,7 +1700,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
{qrStatusText}
|
{qrStatusText}
|
||||||
</Tag>
|
</Tag>
|
||||||
{qrUrl ? (
|
{qrUrl ? (
|
||||||
<QRCode value={qrUrl} size={220} bordered={false} />
|
<div ref={qrCanvasWrapRef}>
|
||||||
|
<QRCode value={qrUrl} size={220} bordered={false} />
|
||||||
|
</div>
|
||||||
) : qrBindReady ? (
|
) : qrBindReady ? (
|
||||||
<Text type="secondary">绑定二维码暂未返回</Text>
|
<Text type="secondary">绑定二维码暂未返回</Text>
|
||||||
) : qrTask?.status === 'running' ? (
|
) : qrTask?.status === 'running' ? (
|
||||||
@@ -1701,6 +1710,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
) : (
|
) : (
|
||||||
<Text type="secondary">本次未生成二维码</Text>
|
<Text type="secondary">本次未生成二维码</Text>
|
||||||
)}
|
)}
|
||||||
|
<QrActions wrapRef={qrCanvasWrapRef} url={qrUrl} disabled={!qrUrl} />
|
||||||
{qrBindReady ? (
|
{qrBindReady ? (
|
||||||
<>
|
<>
|
||||||
<Tag color="gold" style={{ fontSize: 16, padding: '6px 16px' }}>
|
<Tag color="gold" style={{ fontSize: 16, padding: '6px 16px' }}>
|
||||||
@@ -1774,10 +1784,13 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
)}
|
)}
|
||||||
<Space direction="vertical" align="center" size={12}>
|
<Space direction="vertical" align="center" size={12}>
|
||||||
{payUrl ? (
|
{payUrl ? (
|
||||||
<QRCode value={payUrl} size={220} bordered={false} />
|
<div ref={payQrCanvasWrapRef}>
|
||||||
|
<QRCode value={payUrl} size={220} bordered={false} />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Text type="secondary">支付码生成中...</Text>
|
<Text type="secondary">支付码生成中...</Text>
|
||||||
)}
|
)}
|
||||||
|
<QrActions wrapRef={payQrCanvasWrapRef} url={payUrl} disabled={!payUrl} />
|
||||||
<Text>{payTask.message || '请扫码支付'}</Text>
|
<Text>{payTask.message || '请扫码支付'}</Text>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user