From 39c5817bd4d0cc7fd052a2f40ecb771510f6460e Mon Sep 17 00:00:00 2001 From: "wkylin.w@gmail.com" Date: Mon, 30 Dec 2024 13:42:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B0=E5=AD=97=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 23 +++ package.json | 1 + .../hooks/useCopyToClipboard/index.ts | 4 +- .../stateless/ReactSignature/index.jsx | 0 .../stateless/ReactSignature/index.tsx | 182 ++++++++++++++++++ src/pages/home/index.jsx | 4 + 6 files changed, 212 insertions(+), 2 deletions(-) delete mode 100644 src/components/stateless/ReactSignature/index.jsx create mode 100644 src/components/stateless/ReactSignature/index.tsx diff --git a/package-lock.json b/package-lock.json index 56d8f732..16ce0cec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@react-three/fiber": "^8.17.10", "@reduxjs/toolkit": "^2.5.0", "@smakss/react-scroll-direction": "^4.2.0", + "@uiw/react-signature": "^1.3.1", "@use-gesture/react": "^10.3.1", "animate.css": "^4.1.1", "antd": "^5.22.5", @@ -8956,6 +8957,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@uiw/react-signature": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/@uiw/react-signature/-/react-signature-1.3.1.tgz", + "integrity": "sha512-qmuaXCKc4tRmc+qdysS1vjUBd9dBwLYVjdCOhCAxEExyl9KjgqmGN3rA/bwOteevPXFerHcEcXpNIIgwJjhVhA==", + "license": "MIT", + "dependencies": { + "perfect-freehand": "^1.2.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", @@ -29941,6 +29958,12 @@ "path2d": "^0.2.1" } }, + "node_modules/perfect-freehand": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/perfect-freehand/-/perfect-freehand-1.2.2.tgz", + "integrity": "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==", + "license": "MIT" + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", diff --git a/package.json b/package.json index 1e2278d6..aaa4761a 100644 --- a/package.json +++ b/package.json @@ -222,6 +222,7 @@ "@react-three/fiber": "^8.17.10", "@reduxjs/toolkit": "^2.5.0", "@smakss/react-scroll-direction": "^4.2.0", + "@uiw/react-signature": "^1.3.1", "@use-gesture/react": "^10.3.1", "animate.css": "^4.1.1", "antd": "^5.22.5", diff --git a/src/components/hooks/useCopyToClipboard/index.ts b/src/components/hooks/useCopyToClipboard/index.ts index a87219e6..830c529c 100644 --- a/src/components/hooks/useCopyToClipboard/index.ts +++ b/src/components/hooks/useCopyToClipboard/index.ts @@ -2,6 +2,6 @@ import useCopyToClipboard from './use-copy-clipboard' import useCopy from './use-copy' -export default useCopy +export default useCopyToClipboard -export { useCopyToClipboard } +export { useCopy } diff --git a/src/components/stateless/ReactSignature/index.jsx b/src/components/stateless/ReactSignature/index.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/stateless/ReactSignature/index.tsx b/src/components/stateless/ReactSignature/index.tsx new file mode 100644 index 00000000..940812a6 --- /dev/null +++ b/src/components/stateless/ReactSignature/index.tsx @@ -0,0 +1,182 @@ +'use client' +import clsx from 'clsx' +import Signature, { type SignatureRef } from '@uiw/react-signature' +import { CheckIcon, CopyIcon, DownloadIcon, Eraser, RefreshCcwIcon } from 'lucide-react' +import React, { type ComponentProps, useRef, useState } from 'react' +import { useCopy } from '@hooks/useCopyToClipboard' + +export function ReactSignature({ className, ...props }: ComponentProps) { + const [readonly, setReadonly] = useState(false) + const $svg = useRef(null) + + const handleClear = () => $svg.current?.clear() + + const handleValidate = () => { + if (readonly) { + $svg.current?.clear() + setReadonly(false) + } else { + setReadonly(true) + } + } + + return ( +
+

数字签名:

+ +
+ + {readonly && ( + <> + + + + )} + {!readonly && } +
+
+ ) +} + +function prepareSvgElement(svgElement: SVGSVGElement) { + const svgElm = svgElement.cloneNode(true) as SVGSVGElement + const clientWidth = svgElement.clientWidth + const clientHeight = svgElement.clientHeight + svgElm.removeAttribute('style') + svgElm.setAttribute('width', `${clientWidth}px`) + svgElm.setAttribute('height', `${clientHeight}px`) + svgElm.setAttribute('viewBox', `0 0 ${clientWidth} ${clientHeight}`) + return { svgElm, clientWidth, clientHeight } +} + +function ValidateButton({ + readonly, + onClick, +}: Readonly<{ + readonly: boolean + onClick: () => void +}>) { + return ( + + ) +} + +function DownloadButton({ + svgElement, +}: Readonly<{ + svgElement: SVGSVGElement | undefined | null +}>) { + const handleDownloadImage = () => { + if (!svgElement) { + return + } + + const { svgElm, clientWidth, clientHeight } = prepareSvgElement(svgElement) + + const data = new XMLSerializer().serializeToString(svgElm) + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + const img = new Image() + img.onload = () => { + canvas.width = clientWidth ?? 0 + canvas.height = clientHeight ?? 0 + ctx?.drawImage(img, 0, 0) + const a = document.createElement('a') + a.download = 'signature.png' + a.href = canvas.toDataURL('image/png') + a.click() + } + img.src = `data:image/svg+xml;base64,${window.btoa(decodeURIComponent(encodeURIComponent(data)))}` + } + + return ( + + ) +} + +function CopySvgButton({ + svgElement, +}: Readonly<{ + svgElement: SVGSVGElement | undefined | null +}>) { + const [_, copyText, isCopied] = useCopy() + + const handleCopySvg = () => { + if (!svgElement) { + return + } + + const { svgElm } = prepareSvgElement(svgElement) + copyText(svgElm.outerHTML) + } + + return ( + + ) +} + +function ClearButton({ onClick }: Readonly<{ onClick: () => void }>) { + return ( + + ) +} diff --git a/src/pages/home/index.jsx b/src/pages/home/index.jsx index 188f25ae..19b7eacd 100644 --- a/src/pages/home/index.jsx +++ b/src/pages/home/index.jsx @@ -36,6 +36,7 @@ import ShiCode from '@stateless/ShiCode' import DynamicBackground from '@stateless/DynamicBackground' import ContentPlaceholder from '@stateless/ContentPlaceholder' import SkeletonFix from '@stateless/SkeletonFix' +import { ReactSignature } from '@stateless/ReactSignature' import { oneApiChat, prettyObject, randomNum } from '@utils/aidFn' import { fireConfetti } from '@utils/confetti' @@ -398,6 +399,9 @@ const Home = () => {
+
+ +