当前位置: 首页 > article >正文

实现 React 电子签名功能:从零开始构建一个完整的解决方案

需求概述

我们希望通过 React 构建一个简单的电子签名组件,用户可以在画布上手写签名,完成后可以保存签名并将其上传到服务器。具体需求如下:

  • 用户可以在画布上自由绘制签名。
  • 提供“清除”按钮,允许用户重置签名。
  • 提供“保存”按钮,将签名保存为图片文件并上传到服务器。
  • 签名应以 Base64 格式保存,并转换为文件流后上传。
  • 使用 MinIO 作为文件存储服务,确保签名文件的安全性和持久性。

技术栈选择

  • react-signature-canvas:一个轻量级的 React 组件,允许用户在画布上绘制签名。
  • minio.js:用于与 MinIO 服务器交互,上传签名文件。
  • BottomClock:自定义组件,用于提供底部的操作按钮(重写和确认)。

安装依赖

安装所需的依赖库:

npm install react-signature-canvas antd-mobile @minio-js/minio
  • react-signature-canvas:用于绘制签名。
  • @minio-js/minio:用于与 MinIO 服务器进行文件上传。

创建签名组件

引入 SignatureCanvas

react-signature-canvas 是一个非常方便的 React 组件,它提供了画布绘制的功能。我们可以通过 ref 来控制画布的行为,例如清除签名、获取签名数据等。

import React, { useRef } from "react";
import SignatureCanvas from "react-signature-canvas";

const sigCanvas = useRef();

// 清除签名
const clearSignature = () => {
  if (sigCanvas.current) {
    sigCanvas.current.clear();
  }
};

// 获取签名的 Base64 数据
const getSignatureData = () => {
  if (sigCanvas.current) {
    return sigCanvas.current.toDataURL();
  }
  return null;
};
将 Base64 转换为文件流

签名数据是以 Base64 格式返回的,为了将其上传到服务器,我们需要将其转换为文件流。我们可以编写一个辅助函数 dataURLtoFile 来完成这个任务:

const dataURLtoFile = (base64, fileName) => {
  const data = base64.split(",");
  const type = data[0].match(/:(.*?);/)[1];
  const suffix = type.split("/")[1];
  const bstr = window.atob(data[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], `${fileName}.${suffix}`, { type: type });
};
保存签名并上传
import { uploadFileFun } from "@/utils/minio.js";
import { Toast } from "antd-mobile";

const saveSignature = async () => {
  const isEmpty = sigCanvas.current.isEmpty(); // 检查是否为空
  if (!isEmpty && sigCanvas.current) {
    const dataUrl = sigCanvas.current.toDataURL(); // 获取签名的 Base64 数据
    try {
      const file = dataURLtoFile(dataUrl, "signature");
      const response = await uploadFileFun(file); // 上传文件到 MinIO
      console.log("签名已成功上传:", response);
      props.saveSignature(response); // 将上传结果传递给父组件
    } catch (error) {
      console.error("上传失败:", error);
      Toast.show({
        content: "签名上传失败,请稍后再试。",
      });
    }
  } else {
    Toast.show({
      content: "请先完成签名。",
    });
  }
};

完整的签名组件代码

import React, { useRef } from "react";
import SignatureCanvas from "react-signature-canvas";
import BottomClock from "@/components/BottomClick/index";
import { Toast } from "antd-mobile";
import { uploadFileFun } from "@/utils/minio.js";

export default function Signature(props) {
  const sigCanvas = useRef();

  // 清除签名
  const clearSignature = () => {
    if (sigCanvas.current) {
      sigCanvas.current.clear();
    }
  };

  // Base64格式转文件流
  const dataURLtoFile = (base64, fileName) => {
    const data = base64.split(",");
    const type = data[0].match(/:(.*?);/)[1];
    const suffix = type.split("/")[1];
    const bstr = window.atob(data[1]);
    let n = bstr.length;
    const u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], `${fileName}.${suffix}`, { type: type });
  };

  // 保存签名
  const saveSignature = async () => {
    const isEmpty = sigCanvas.current.isEmpty(); // 检查是否为空
    if (!isEmpty && sigCanvas.current) {
      const dataUrl = sigCanvas.current.toDataURL(); // 获取签名的 Base64 数据
      try {
        const file = dataURLtoFile(dataUrl, "signature");
        const response = await uploadFileFun(file); // 上传文件到 MinIO
        console.log("签名已成功上传:", response);
        props.saveSignature(response); // 将上传结果传递给父组件
      } catch (error) {
        console.error("上传失败:", error);
        Toast.show({
          content: "签名上传失败,请稍后再试。",
        });
      }
    } else {
      Toast.show({
        content: "请先完成签名。",
      });
    }
  };

  return (
    <div className="signaturecontainer">
      <SignatureCanvas
        penColor="#0000ff" // 设置画笔颜色为蓝色
        maxWidth={6}
        canvasProps={{
          className: "sigCanvas",
        }}
        ref={sigCanvas}
      />
      <div className="button-container">
        <BottomClock
          reset={"重写"}
          confirm={"确认"}
          onReset={clearSignature}
          onSubmit={saveSignature}
        />
      </div>
    </div>
  );
}

文件上传到 MinIO

配置 MinIO

MinIO 是一个高性能的对象存储系统,适合用于存储文件。我们可以通过 minio.js 库与 MinIO 服务器进行交互。首先,确保你已经安装并配置了 MinIO 服务器,并且有一个可用的存储桶。

编写上传函数

@/utils/minio.js 中编写一个上传文件的函数 uploadFileFun,该函数将接收文件对象并将其上传到 MinIO 服务器:

import { Client, S3Error } from "@minio-js/minio";

const minioClient = new Client({
  endPoint: "your-minio-endpoint", // 替换为你的 MinIO 服务器地址
  port: 9000, // 替换为你的 MinIO 服务器端口
  useSSL: false, // 是否使用 HTTPS
  accessKey: "your-access-key", // 替换为你的 MinIO 访问密钥
  secretKey: "your-secret-key", // 替换为你的 MinIO 秘钥
});

export const uploadFileFun = async (file) => {
  try {
    const bucketName = "signatures"; // 存储桶名称
    const objectName = file.name; // 文件名
    const metaData = {
      "Content-Type": file.type,
    };

    // 上传文件到 MinIO
    await minioClient.putObject(bucketName, objectName, file, metaData);

    // 获取文件的 URL
    const url = await minioClient.presignedGetObject(bucketName, objectName, 60 * 60 * 24); // 签名链接有效期为 24 小时
    return url;
  } catch (err) {
    if (err instanceof S3Error) {
      console.error("MinIO 错误:", err);
    } else {
      console.error("未知错误:", err);
    }
    throw err;
  }
};

参考资料

  • React Signature Canvas
  • MinIO JavaScript SDK
  • Ant Design Mobile

http://www.kler.cn/a/448721.html

相关文章:

  • Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
  • 重拾设计模式--外观模式
  • AI的进阶之路:从机器学习到深度学习的演变(四)
  • SpringBoot相关漏洞学习资料
  • vue2 - Day03 - (生命周期、组件、组件通信)
  • 算法设计期末复习
  • Unity全局雾效
  • 深度学习革新音乐转录
  • MQTT实现集群分布式消费
  • 计算机前沿技术-人工智能算法-大语言模型-最新研究进 2024-12-22
  • XRP价格跌破2.20美元 1.94美元是否下一波牛市的关键支撑?
  • 【再谈设计模式】外观模式~复杂系统交互的简化大师
  • 0.gitlab ubuntu20.04 部署问题解决
  • 理解并使用Linux 内核中的 Tracepoint
  • C++ 基本语法
  • jenkins启动脚本,jar包自动化启动脚本
  • 如何解决微信小程序使用webview无法打开
  • Windows系统中使用git常见问题解决方案
  • 【项目实战】redis实现websocket分布式消息推送服务
  • 在 C# 中实现的目录基础操作
  • 【SpringBoot】日志文件
  • SpringBoot 整合 SQLite 数据库
  • 深入解析:Python中的决策树与随机森林
  • vue CSS 自定义宽高 翻页 剥离 效果
  • 解决 Ubuntu 24 连接正点 I.MX6ULL 的 uboot 使用 nfs 出现 Loading: T T T T T T T T
  • RCE总结