可用业务场景
报销单据审批中,高亮发票部分
需求
后台返回一张图片或者pdf
、返回一组坐标
,坐标类型[number,number,number,number]
,分别代表了x、y、width、height。需要根据坐标在图片上高亮出来坐标位置。如下图
高亮的坐标是:
const rect: Rect[] = [
[100, 100, 200, 200],
[200, 300, 200, 200],
];
技术选型
- dom转成图片:html2canvas
- pdf预览:pdfjs-dist、react-pdf
- 遮照:纯css实现(四个绝对定位的dom)
这里的react-pdf使用的是V4,用来兼容IE11
遮照也可以换成是一个矩形框,看具体需求,我这里的需求是遮照高亮
代码
组件部分
/*
* @Author: Do not edit
* @Date: 2023-08-25 13:48:06
* @LastEditors: atwlee
* @LastEditTime: 2023-08-28 14:08:34
* @Descripttion:
* @FilePath: /test/src/pages/generate.tsx
*/
import { FC, useEffect, useRef } from "react";
import styles from "./index.modules.less";
import html2canvas from "html2canvas";
import { Document, Page, pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.js",
import.meta.url
).toString();
export type Rect = [number, number, number, number];
interface GenerateProps {
fileUrl: string;
rects: Rect[];
onGenerateCallback: (imgs: string[]) => void;
fileType: "img" | "pdf";
fileSize?: [number, number];
}
const Index: FC<GenerateProps> = (props) => {
const { fileUrl, rects, onGenerateCallback, fileType, fileSize } = props;
const divRef = useRef<HTMLDivElement>(null);
const handleGenerateImg = () => {
const results: string[] = [];
rects.forEach((item, index) => {
html2canvas(
divRef.current!.querySelector(`[data-key="generate${index}"]`)!,
{
useCORS: true,
}
).then((canvas) => {
results.push(canvas.toDataURL("image/png"));
results.length === rects.length && onGenerateCallback(results);
});
});
};
const pdf2img = useRef<string[]>([]);
const onPageLoadSuccess = () => {
rects.forEach((item, index) => {
html2canvas(
divRef.current!.querySelector(`[data-key="generate${index}"]`)!,
{
useCORS: true,
}
).then((canvas) => {
pdf2img.current.push(canvas.toDataURL("image/png"));
if (pdf2img.current.length === rects.length) {
onGenerateCallback(pdf2img.current);
pdf2img.current = [];
}
});
});
};
useEffect(() => {
fileType === "img" && handleGenerateImg();
}, [fileUrl, rects, fileType]);
return (
<div ref={divRef} className={styles.contanier}>
{/* pdf */}
{fileType === "pdf" && (
<Document file={fileUrl}>
{rects.map((i, index) => (
<div
className={styles.rectItem}
key={index}
data-key={`generate${index}`}
>
<Page
pageNumber={1}
width={fileSize?.[0]}
height={fileSize?.[1]}
onRenderSuccess={onPageLoadSuccess}
/>
<div className={styles.coverTop} style={{ height: i[1] }} />
<div
className={styles.coverRight}
style={{
left: i[0] + i[2],
top: i[1],
height: i[3],
}}
/>
<div
className={styles.coverBottom}
style={{ top: i[1] + i[3] }}
/>
<div
className={styles.coverLeft}
style={{ width: i[0], top: i[1], height: i[3] }}
/>
</div>
))}
</Document>
)}
{/* img */}
{fileType === "img" &&
rects.map((i, index) => (
<div
className={styles.rectItem}
key={index}
data-key={`generate${index}`}
>
<img src={fileUrl} width={fileSize?.[0]} height={fileSize?.[1]} />
<div className={styles.coverTop} style={{ height: i[1] }} />
<div
className={styles.coverRight}
style={{
left: i[0] + i[2],
top: i[1],
height: i[3],
}}
/>
<div className={styles.coverBottom} style={{ top: i[1] + i[3] }} />
<div
className={styles.coverLeft}
style={{ width: i[0], top: i[1], height: i[3] }}
/>
</div>
))}
</div>
);
};
export default Index;
使用
/*
* @Author: Do not edit
* @Date: 2023-08-24 15:57:05
* @LastEditors: atwlee
* @LastEditTime: 2023-08-28 14:13:37
* @Descripttion:
* @FilePath: /test/src/pages/index.tsx
*/
import { useState } from "react";
import Generate from "./generate";
import type { Rect } from "./generate";
import yayJpg from "./yay.jpg";
import pdfUrl from "./redv2.pdf";
const rect: Rect[] = [
[100, 100, 200, 200],
[200, 300, 200, 200],
];
export default function HomePage() {
const [imgs, setImgs] = useState<string[]>([]);
const onGenerateCallback = (img: string[]) => {
setImgs(img);
};
const hiddenStyle = { height: 0, overflow: "hidden" };
return (
<div>
<h2>Yay! Welcome to umi!</h2>
<div style={hiddenStyle}>
<Generate
// fileType="pdf"
fileType="img"
// fileUrl={pdfUrl}
// fileUrl={'https://www.sdta.cn/pdf/e-map.pdf'}
fileUrl={yayJpg}
// fileUrl={
// "https://img1.baidu.com/it/u=2488875768,1454762303&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800"
// }
rects={rect}
onGenerateCallback={onGenerateCallback}
/>
</div>
{imgs.map((i, index) => {
return <img src={i} key={index} alt="" />;
})}
</div>
);
}
demo源码
PS
图片的话不用在意实际的宽度和高度,当然如果有更好。pdf不知道需不需要实际的宽度和高度,这里抛出去了fileSize的属性,demo里没有使用,没有测试。