fix: copy button do not work

This commit is contained in:
jialin
2026-02-11 15:30:35 +08:00
parent 2ba94f7524
commit e66de732ac
3 changed files with 124 additions and 42 deletions
+113 -34
View File
@@ -1,58 +1,137 @@
import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons'; import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Typography } from 'antd'; import { Button, message, Tooltip } from 'antd';
import { TextProps } from 'antd/es/typography/Text'; import React, {
import React, { useMemo } from 'react'; useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import AutoTooltip from '../auto-tooltip';
type CopyButtonProps = { type CopyButtonProps = {
children?: React.ReactNode; children?: React.ReactNode;
text: string; text: string;
fontSize?: string; fontSize?: string;
type?: 'text' | 'primary' | 'dashed' | 'link' | 'default';
size?: 'small' | 'middle' | 'large';
shape?: 'circle' | 'round' | 'default';
tips?: string;
placement?:
| 'top'
| 'left'
| 'right'
| 'bottom'
| 'topLeft'
| 'topRight'
| 'bottomLeft'
| 'bottomRight';
btnStyle?: React.CSSProperties; btnStyle?: React.CSSProperties;
style?: React.CSSProperties; style?: React.CSSProperties;
format?: 'text/plain' | 'text/html';
}; };
const CopyButton: React.FC<CopyButtonProps & TextProps> = ({ const CopyButton: React.FC<CopyButtonProps> = ({
children, children,
tips,
text, text,
type = 'text',
shape = 'default',
fontSize = '14px', fontSize = '14px',
style, style,
btnStyle, btnStyle,
format = 'text/plain', placement,
...rest size = 'small'
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const [copied, setCopied] = useState(false);
const timerRef = useRef<number>();
const tooltips = useMemo(() => { const resetCopied = () => {
return [ window.clearTimeout(timerRef.current);
intl.formatMessage({ id: 'common.button.copy' }), timerRef.current = window.setTimeout(() => {
intl.formatMessage({ id: 'common.button.copied' }) setCopied(false);
]; }, 3000);
}, [intl]); };
/**
* fallbackexecCommandold Safari / NON-HTTPS
*/
const legacyCopy = (value: string) => {
const textarea = document.createElement('textarea');
textarea.value = value;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
return true;
} catch {
return false;
} finally {
document.body.removeChild(textarea);
}
};
const handleCopy = useCallback(async () => {
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
} else {
const success = legacyCopy(text);
if (!success) throw new Error('legacy copy failed');
}
setCopied(true);
} catch {
message.error(intl.formatMessage({ id: 'common.copy.fail' }) as string);
}
}, [text, intl]);
const tipTitle = useMemo(() => {
if (copied) {
return intl.formatMessage({ id: 'common.button.copied' });
}
return tips ?? intl.formatMessage({ id: 'common.button.copy' });
}, [copied, tips, intl]);
useEffect(() => {
resetCopied();
}, [copied]);
return ( return (
<Typography.Paragraph <div className="flex-center gap-4" style={{ minWidth: 16 }}>
style={{ ...btnStyle, marginBottom: 0 }} {children && (
{...rest} <AutoTooltip minWidth={20} ghost>
copyable={{ {children}
text: text, </AutoTooltip>
format: format, )}
tooltips: [ <Tooltip title={tipTitle} placement={placement}>
intl.formatMessage({ id: 'common.button.copy' }), <span>
intl.formatMessage({ id: 'common.button.copied' }) <Button
], className="copy-button"
icon: [ type={type}
<CopyOutlined style={{ fontSize: fontSize, ...style }} key="copy" />, shape={shape}
<CheckCircleFilled size={size}
style={{ color: 'var(--ant-color-success)', fontSize: fontSize }} onClick={handleCopy}
key="copied" style={{ ...btnStyle }}
/> icon={
] copied ? (
}} <CheckCircleFilled
> style={{
{children} color: 'var(--ant-color-success)',
</Typography.Paragraph> fontSize
}}
/>
) : (
<CopyOutlined style={{ fontSize, ...style }} />
)
}
></Button>
</span>
</Tooltip>
</div>
); );
}; };
@@ -285,7 +285,14 @@ const AddModal: React.FC<AddModalProps> = ({
<SealInput.Input <SealInput.Input
label={intl.formatMessage({ id: 'apikeys.form.apikey' })} label={intl.formatMessage({ id: 'apikeys.form.apikey' })}
value={apikeyValue} value={apikeyValue}
addAfter={<CopyButton text={apikeyValue}></CopyButton>} addAfter={
<CopyButton
text={apikeyValue}
shape="default"
size="middle"
type="text"
></CopyButton>
}
></SealInput.Input> ></SealInput.Input>
</Form.Item> </Form.Item>
)} )}
+3 -7
View File
@@ -53,7 +53,8 @@ const RowWrapper = styled.div`
const InfoItem = styled.div` const InfoItem = styled.div`
display: grid; display: grid;
grid-template-columns: max-content 1fr; align-items: center;
grid-template-columns: max-content minmax(0, 1fr);
gap: 8px; gap: 8px;
.label { .label {
color: var(--ant-color-text-tertiary); color: var(--ant-color-text-tertiary);
@@ -136,12 +137,7 @@ export const VersionItem: React.FC<VersionItemProps> = ({ data }) => {
<span className="label"> <span className="label">
{intl.formatMessage({ id: 'backend.imageName' })}: {intl.formatMessage({ id: 'backend.imageName' })}:
</span> </span>
<CopyButton <CopyButton text={data.image_name} type="link">
text={data.image_name}
ellipsis={{
tooltip: !data.is_built_in ? data.image_name : false
}}
>
{data.is_built_in {data.is_built_in
? intl.formatMessage({ id: 'backend.versionInfo.autoImage' }) ? intl.formatMessage({ id: 'backend.versionInfo.autoImage' })
: data.image_name} : data.image_name}