QrPanel.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { useRef } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { Button, QRCode, Tag, Tooltip, message } from 'antd';
  4. import { CopyOutlined, DownloadOutlined, PictureOutlined } from '@ant-design/icons';
  5. import { ClipboardManager, FileManager } from '@/utils';
  6. import { activateOnKey } from '@/utils/a11y';
  7. import './QrPanel.css';
  8. interface QrPanelProps {
  9. value: string;
  10. remark?: string;
  11. downloadName?: string;
  12. size?: number;
  13. showQr?: boolean;
  14. }
  15. async function svgToPngBlob(svgEl: SVGSVGElement | null, size: number): Promise<Blob | null> {
  16. if (!svgEl) return null;
  17. const svgData = new XMLSerializer().serializeToString(svgEl);
  18. const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
  19. const url = URL.createObjectURL(svgBlob);
  20. return new Promise<Blob | null>((resolve) => {
  21. const img = new Image();
  22. img.onload = () => {
  23. const canvas = document.createElement('canvas');
  24. canvas.width = size;
  25. canvas.height = size;
  26. const ctx = canvas.getContext('2d');
  27. if (!ctx) {
  28. URL.revokeObjectURL(url);
  29. resolve(null);
  30. return;
  31. }
  32. ctx.fillStyle = '#ffffff';
  33. ctx.fillRect(0, 0, size, size);
  34. ctx.drawImage(img, 0, 0, size, size);
  35. URL.revokeObjectURL(url);
  36. canvas.toBlob((blob) => resolve(blob), 'image/png');
  37. };
  38. img.onerror = () => {
  39. URL.revokeObjectURL(url);
  40. resolve(null);
  41. };
  42. img.src = url;
  43. });
  44. }
  45. function downloadImageBlob(blob: Blob, remark: string) {
  46. const url = URL.createObjectURL(blob);
  47. const link = document.createElement('a');
  48. link.href = url;
  49. link.download = `${remark || 'qrcode'}.png`;
  50. link.click();
  51. URL.revokeObjectURL(url);
  52. }
  53. export default function QrPanel({
  54. value,
  55. remark = '',
  56. downloadName = '',
  57. size = 360,
  58. showQr = true,
  59. }: QrPanelProps) {
  60. const { t } = useTranslation();
  61. const [messageApi, messageContextHolder] = message.useMessage();
  62. const qrRef = useRef<HTMLDivElement | null>(null);
  63. async function copy() {
  64. const ok = await ClipboardManager.copyText(value);
  65. if (ok) messageApi.success(t('copied'));
  66. }
  67. function download() {
  68. if (!downloadName) return;
  69. FileManager.downloadTextFile(value, downloadName);
  70. }
  71. async function copyImage() {
  72. const svgEl = qrRef.current?.querySelector('svg') as SVGSVGElement | null;
  73. const blob = await svgToPngBlob(svgEl, size);
  74. if (!blob) return;
  75. try {
  76. await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
  77. messageApi.success(t('copied'));
  78. } catch {
  79. downloadImageBlob(blob, remark);
  80. }
  81. }
  82. async function downloadImage() {
  83. const svgEl = qrRef.current?.querySelector('svg') as SVGSVGElement | null;
  84. const blob = await svgToPngBlob(svgEl, size);
  85. if (blob) downloadImageBlob(blob, remark);
  86. }
  87. return (
  88. <div className="qr-panel">
  89. {messageContextHolder}
  90. <div className="qr-panel-header">
  91. <Tag color="green" className="qr-remark">
  92. {remark}
  93. </Tag>
  94. <Tooltip title={t('copy')}>
  95. <Button size="small" icon={<CopyOutlined />} aria-label={t('copy')} onClick={copy} />
  96. </Tooltip>
  97. {showQr && (
  98. <Tooltip title={t('downloadImage')}>
  99. <Button
  100. size="small"
  101. icon={<PictureOutlined />}
  102. aria-label={t('downloadImage')}
  103. onClick={downloadImage}
  104. />
  105. </Tooltip>
  106. )}
  107. {downloadName && (
  108. <Tooltip title={t('download')}>
  109. <Button
  110. size="small"
  111. icon={<DownloadOutlined />}
  112. aria-label={t('download')}
  113. onClick={download}
  114. />
  115. </Tooltip>
  116. )}
  117. </div>
  118. {showQr && (
  119. <div
  120. ref={qrRef}
  121. className="qr-panel-canvas"
  122. role="button"
  123. tabIndex={0}
  124. aria-label={t('copy')}
  125. onClick={copyImage}
  126. onKeyDown={(event) => activateOnKey(copyImage)(event)}
  127. >
  128. <Tooltip title={t('copy')}>
  129. <QRCode
  130. className="qr-code"
  131. value={value}
  132. size={size}
  133. errorLevel="L"
  134. marginSize={2}
  135. type="svg"
  136. bordered={false}
  137. color="#000000"
  138. bgColor="#ffffff"
  139. />
  140. </Tooltip>
  141. </div>
  142. )}
  143. </div>
  144. );
  145. }