新增Signature签字组件

master
LiuJiaNan 2025-11-15 11:39:20 +08:00
parent 7485d77973
commit a4e026b838
6 changed files with 136 additions and 2 deletions

View File

@ -59,12 +59,12 @@ const FormBuilder = (props) => {
{submitButtonText}
</Button>
)}
{extraActionButtons}
{showCancelButton && (
<Button onClick={handleCancel}>
{cancelButtonText}
</Button>
)}
{extraActionButtons}
</Space>
)}
</Col>

24
components/Signature/index.d.ts vendored Normal file
View File

@ -0,0 +1,24 @@
import type { FC } from "react";
export interface SignatureValue {
/** 签字时间YYYY-MM-DD HH:mm:ss */
time: string;
/** 签字图片的base64编码 */
base64: string;
}
export interface SignatureProps {
/** 确认签字回调 */
onConfirm: (value: SignatureValue) => void;
/** 签字区域宽度,默认为 752 */
width?: number;
/** 签字区域高度,默认为 300 */
height?: number;
}
/**
*
*/
declare const Signature: FC<SignatureProps>;
export default Signature;

View File

@ -0,0 +1,87 @@
import { Button, Image, message, Modal } from "antd";
import dayjs from "dayjs";
import { useRef, useState } from "react";
import SignatureCanvas from "react-signature-canvas";
import { base642File } from "../../utils";
/**
* 签字组件
*/
function Signature(props) {
const {
onConfirm,
width = 752,
height = 300,
...restProps
} = props;
const [signatureModalOpen, setSignatureModalOpen] = useState(false);
const signatureCanvas = useRef(null);
const [base64, setBase64] = useState("");
const onOk = () => {
if (signatureCanvas.current.isEmpty()) {
message.warning("请签名");
return;
}
const base64 = signatureCanvas.current.toDataURL();
setBase64(base64);
onConfirm({
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
base64,
file: base642File(base64),
});
signatureCanvas.current.clear();
setSignatureModalOpen(false);
};
return (
<>
<div>
<Button
type="primary"
onClick={() => {
setSignatureModalOpen(true);
}}
>
{base64 ? "重新签字" : "手写签字"}
</Button>
</div>
{base64 && (
<div style={{ border: "1px dashed #d9d9d9", width, height, marginTop: 16 }}>
<Image src={base64} />
</div>
)}
<Modal
title="签字"
width={800}
open={signatureModalOpen}
onCancel={() => setSignatureModalOpen(false)}
footer={[
<Button key="clear" onClick={() => signatureCanvas.current.clear()}>重签</Button>,
<Button
key="cancel"
onClick={() => {
setSignatureModalOpen(false);
signatureCanvas.current.clear();
}}
>
取消
</Button>,
<Button key="ok" type="primary" onClick={onOk}>确定</Button>,
]}
>
<div style={{ border: "1px dashed #d9d9d9" }}>
<SignatureCanvas
ref={signatureCanvas}
penColor="black"
canvasProps={{ width, height }}
{...restProps}
/>
</div>
</Modal>
</>
);
}
export default Signature;

View File

@ -31,6 +31,7 @@
"dayjs": "^1.11.18",
"lodash-es": "^4.17.21",
"react": "^18.3.1",
"react-pdf": "^10.2.0"
"react-pdf": "^10.2.0",
"react-signature-canvas": "^1.1.0-alpha.2"
}
}

5
utils/index.d.ts vendored
View File

@ -261,6 +261,11 @@ export function getIndexColumn(pagination: false | BasePaginationConfig): {
*/
export function getFileUrl(): string;
/**
* base64File
*/
export function base642File(base64: string, filename?: string): File;
/**
*
*/

View File

@ -61,6 +61,23 @@ export function image2Base642(file) {
});
}
/**
base64转File对象
*/
export function base642File(base64, filename = "file") {
const arr = base64.split(",");
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
/**
* 判断图片是否可访问成功
*/