1. 生成二维码直接借助qrcode这个npm库即可生成二维码,但接到的需求要求是在鼠标点击的附近展示出二维码,点击弹窗里面的复制按钮即可赋值带标题的二维码图片到剪切板,实现效果如下图:
主要实现要点:
1.利用qrcode 生成二维码;
2.蚂蚁金服组件气泡弹窗Popover,弹出点击位置弹窗;
3. 点击复制时,利用domimage这个npm包,直接将dom布局转化成图片;
4. base64图片转成blob对象,创建剪切板对象;
编写如下组件
import React, { useRef, useState, Fragment } from 'react';
import { Button, message, Popover } from 'antd';
import styles from './index.less';
import { base64ToBlob, copyToClipboard } from '@/utils/utils';
import QRcode from 'qrcode';
import domtoimage from 'dom-to-image';
interface dataProp {
selectPaperTitle: string;
url: string;
menu: string;
}
const QrcodeMenu: React.FC<dataProp> = (props) => {
const { selectPaperTitle, url, menu } = props;
const [qrcodeUrl, setQrCodeUrl] = useState<string>(url);
const imgDom = useRef<any>(null);
const content = (
<Fragment>
<div ref={imgDom} className={styles.container}>
<div className={styles.title}>{selectPaperTitle}</div>
<img className={styles.img} src={qrcodeUrl} alt="链接二维码" />
</div>
<div className={styles.btncontainer}>
<Button
className={styles.btn}
type="primary"
onClick={() => {
try {
domtoimage.toPng(imgDom?.current).then((codeurl: string) => {
copyToClipboard(base64ToBlob(codeurl));
message.success('二维码复制成功');
});
} catch {
message.error('二维码复制失败');
}
}}
>
复制
</Button>
</div>
</Fragment>
);
return (
<Popover
onVisibleChange={async (visible: any) => {
if (visible) {
QRcode.toDataURL(url, { margin: 0, width: 160 }).then((res) => setQrCodeUrl(res));
}
}}
autoAdjustOverflow={false}
overlayClassName={styles.menu}
placement="leftBottom"
content={content}
trigger={['hover']}
>
{menu}
</Popover>
);
};
export default QrcodeMenu;
2.复制到剪切板的功能实现:
utils.js文件
// 复制png图片到粘贴板
export function copyToClipboard(blob: Blob | null) {
if (blob) {
const clipboardItem: any = new ClipboardItem({ [blob.type]: blob });
navigator?.clipboard?.write([clipboardItem]);
}
}
/**
* base64转blob
*/
export function base64ToBlob(urlData: string, type?: string) {
const arr = urlData.split(',');
const mime = arr[0]?.match(/:(.*?);/)?.[1] || type;
// 去掉url的头,并转化为byte
const bytes = window.atob(arr[1]);
// 处理异常,将ascii码小于0的转换为大于0
const ab = new ArrayBuffer(bytes.length);
// 生成视图(直接针对内存):8位无符号整数,长度1个字节
const ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i += 1) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], {
type: mime,
});
}
3. index.less
.container {
padding: 16px;
background: #fff;
.title {
margin-bottom: 16px;
color: rgba(0, 0, 0, 0.85);
font-weight: 400;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
line-height: 20px;
}
.img {
display: block;
margin: 0 auto;
}
.btn {
display: block;
margin: 0 auto;
}
}
.btncontainer {
text-align: center;
}
.menu {
width: 240px;
padding-right: 0;
background: #fff;
border-radius: 2px;
box-shadow: 0 9px 28px 8px rgba(0, 0, 0, 0.05), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12);
:global {
.ant-popover-arrow {
display: none;
}
.ant-popover-inner-content {
height: auto;
padding: 0 0 16px;
}
}
}
4. 引入方式:
<QrcodeMenu selectPaperTitle={r.title} menu={'二维码'} url={r.url} />