From 8fb7586e9313b08fea9a832464e062678e7ae303 Mon Sep 17 00:00:00 2001 From: Xutaotaotao Date: Mon, 13 May 2024 19:53:28 +0800 Subject: [PATCH] feat: add imgFormatConversion --- package.json | 1 + src/.umi/core/route.tsx | 3 +- src/assets/img/imgFormatConversion.svg | 1 + src/layouts/index.module.scss | 132 ------------------ src/layouts/index.tsx | 55 +------- src/layouts/menu.tsx | 51 +++++++ src/pages/home.tsx | 4 +- src/pages/imgFormatConversion.tsx | 179 +++++++++++++++++++++++++ src/pages/imgScan.tsx | 7 +- src/utils/const.ts | 1 + src/utils/index.ts | 12 ++ yarn.lock | 5 + 12 files changed, 261 insertions(+), 190 deletions(-) create mode 100644 src/assets/img/imgFormatConversion.svg delete mode 100644 src/layouts/index.module.scss create mode 100644 src/layouts/menu.tsx create mode 100644 src/pages/imgFormatConversion.tsx create mode 100644 src/utils/const.ts diff --git a/package.json b/package.json index 7d7917e..21abfae 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "antd": "^5.16.4", "compressorjs": "^1.2.1", "gh-pages": "^6.1.1", + "heic2any": "^0.0.4", "json-diff-kit": "^1.0.29", "lodash": "^4.17.21", "react-draggable": "^4.4.6", diff --git a/src/.umi/core/route.tsx b/src/.umi/core/route.tsx index 836fa43..d31be74 100644 --- a/src/.umi/core/route.tsx +++ b/src/.umi/core/route.tsx @@ -19,13 +19,14 @@ if (process.env.NODE_ENV === 'development') { import React from 'react'; export async function getRoutes() { - const routes = {"imageCompress/singleImageCompress":{"path":"imageCompress/singleImageCompress","id":"imageCompress/singleImageCompress","parentId":"@@/global-layout"},"imageCompress/batchImageCompress":{"path":"imageCompress/batchImageCompress","id":"imageCompress/batchImageCompress","parentId":"@@/global-layout"},"imageCompress/index":{"path":"imageCompress","id":"imageCompress/index","parentId":"@@/global-layout"},"jsonFormatting":{"path":"jsonFormatting","id":"jsonFormatting","parentId":"@@/global-layout"},"imageSlicing":{"path":"imageSlicing","id":"imageSlicing","parentId":"@@/global-layout"},"jsonDiff":{"path":"jsonDiff","id":"jsonDiff","parentId":"@@/global-layout"},"imgScan":{"path":"imgScan","id":"imgScan","parentId":"@@/global-layout"},"index":{"path":"/","id":"index","parentId":"@@/global-layout"},"home":{"path":"home","id":"home","parentId":"@@/global-layout"},"@@/global-layout":{"id":"@@/global-layout","path":"/","isLayout":true}} as const; + const routes = {"imageCompress/singleImageCompress":{"path":"imageCompress/singleImageCompress","id":"imageCompress/singleImageCompress","parentId":"@@/global-layout"},"imageCompress/batchImageCompress":{"path":"imageCompress/batchImageCompress","id":"imageCompress/batchImageCompress","parentId":"@@/global-layout"},"imageCompress/index":{"path":"imageCompress","id":"imageCompress/index","parentId":"@@/global-layout"},"imgFormatConversion":{"path":"imgFormatConversion","id":"imgFormatConversion","parentId":"@@/global-layout"},"jsonFormatting":{"path":"jsonFormatting","id":"jsonFormatting","parentId":"@@/global-layout"},"imageSlicing":{"path":"imageSlicing","id":"imageSlicing","parentId":"@@/global-layout"},"jsonDiff":{"path":"jsonDiff","id":"jsonDiff","parentId":"@@/global-layout"},"imgScan":{"path":"imgScan","id":"imgScan","parentId":"@@/global-layout"},"index":{"path":"/","id":"index","parentId":"@@/global-layout"},"home":{"path":"home","id":"home","parentId":"@@/global-layout"},"@@/global-layout":{"id":"@@/global-layout","path":"/","isLayout":true}} as const; return { routes, routeComponents: { 'imageCompress/singleImageCompress': React.lazy(() => import(/* webpackChunkName: "src__pages__imageCompress__singleImageCompress" */'../../../src/pages/imageCompress/singleImageCompress.tsx')), 'imageCompress/batchImageCompress': React.lazy(() => import(/* webpackChunkName: "src__pages__imageCompress__batchImageCompress" */'../../../src/pages/imageCompress/batchImageCompress.tsx')), 'imageCompress/index': React.lazy(() => import(/* webpackChunkName: "src__pages__imageCompress__index" */'../../../src/pages/imageCompress/index.tsx')), +'imgFormatConversion': React.lazy(() => import(/* webpackChunkName: "src__pages__imgFormatConversion" */'../../../src/pages/imgFormatConversion.tsx')), 'jsonFormatting': React.lazy(() => import(/* webpackChunkName: "src__pages__jsonFormatting" */'../../../src/pages/jsonFormatting.tsx')), 'imageSlicing': React.lazy(() => import(/* webpackChunkName: "src__pages__imageSlicing" */'../../../src/pages/imageSlicing.tsx')), 'jsonDiff': React.lazy(() => import(/* webpackChunkName: "src__pages__jsonDiff" */'../../../src/pages/jsonDiff.tsx')), diff --git a/src/assets/img/imgFormatConversion.svg b/src/assets/img/imgFormatConversion.svg new file mode 100644 index 0000000..4854f40 --- /dev/null +++ b/src/assets/img/imgFormatConversion.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/layouts/index.module.scss b/src/layouts/index.module.scss deleted file mode 100644 index 21473cd..0000000 --- a/src/layouts/index.module.scss +++ /dev/null @@ -1,132 +0,0 @@ -.rootSidenav { - align-items: flex-start; - background: var(--semi-color-bg-0); - flex-direction: column; - overflow: hidden; - row-gap: 0; - width: 100%; - display: flex; - color: var(--semi-color-text-0); - font-family: Inter; - font-size: 20px; - font-weight: 600; - letter-spacing: -0.6px; - line-height: 28px; - text-align: left; - - .nav { - align-self: stretch; - height: 60px; - - .navigationHeaderLogo { - align-items: flex-start; - column-gap: 0; - display: inline-flex; - flex-shrink: 0; - - .semiIconsSemiLogo { - font-size: 36px; - color: var(--semi-color-text-0); - } - } - - .dIV { - align-items: center; - column-gap: 16px; - display: inline-flex; - flex-shrink: 0; - - .semiIconsFeishuLogo, - .semiIconsHelpCircle, - .semiIconsBell { - font-size: 20px; - color: var(--semi-color-text-2); - } - - .avatar { - height: 32px; - width: 32px; - } - } - } - .logo{ - cursor: pointer; - height: 48px; - } - .main { - align-items: flex-start; - column-gap: 0; - display: flex; - flex-shrink: 0; - align-self: stretch; - - .left { - height: calc(100vh - 60px); - - .navItem, - .navItem5 { - width: 223px; - - .iconIntro, - .iconRadio { - height: 20px; - position: relative; - width: 20px; - } - } - - .navItem1, - .navItem2, - .navItem3, - .navItem4, - .navItem6, - .navItem7 { - width: 223px; - - .iconHeart, - .iconCalendar, - .iconCheckbox, - .iconCalendar, - .iconList, - .iconToast { - height: 20px; - width: 20px; - } - } - } - - .right { - padding: 20px 20px 0px 20px; - overflow: auto; - height: calc(100vh - 60px); - - - .item { - min-width: 90px; - vertical-align: middle; - flex-shrink: 0; - } - - .frame1321317607 { - align-items: center; - background: var(--semi-color-fill-0); - border-radius: 12px; - display: flex; - flex-direction: row; - height: 20px; - overflow: hidden; - padding: 220px 499px; - flex-shrink: 0; - color: var(--semi-color-disabled-text); - font-size: 14px; - letter-spacing: -0.14px; - line-height: 20px; - - .yourContentHere { - vertical-align: middle; - min-width: 123px; - } - } - } - } -} diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 21eb598..4b5c873 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -4,61 +4,12 @@ import { IconGithubLogo, IconLikeHeart, } from "@douyinfe/semi-icons"; -import { IconImage, IconIntro } from "@douyinfe/semi-icons-lab"; import { Button, Layout, Nav, Popover,Image, Row, Col } from "@douyinfe/semi-ui"; import Logo from "@/assets/img/XTools.svg"; -import ImageCompressSvg from "@/assets/img/imageCompress.svg"; -import JsonSvg from "@/assets/img/json.svg"; -import JsonDiffSvg from "@/assets/img/jsonDiff.svg"; -import imgScanSvg from "@/assets/img/imgScan.svg"; import "@/assets/normalize.css"; -import styles from "./index.module.scss"; +import { NAV_MAP } from "./menu"; +const { Header, Sider, Content } = Layout; -const { Header, Footer, Sider, Content } = Layout; - -export const NavMap = [ - { - itemKey: "/home", - text: "首页", - icon: , - className: styles.navItem, - }, - { - itemKey: "/jsonFormatting", - text: "JSON格式化", - icon: , - className: styles.navItem1, - des:'将JSON格式化为可读性更好的格式' - }, - { - itemKey: "/jsonDiff", - text: "JSON对比", - icon: , - className: styles.navItem1, - des:'比较两个JSON对象,并展示差异' - }, - { - itemKey: "/imageSlicing", - text: "图片分割", - icon: , - className: styles.navItem1, - des:'将图片切割成多张小图片' - }, - { - itemKey: "/imageCompress", - text: "图片压缩", - icon: , - className: styles.navItem1, - des:'压缩图片大小' - }, - { - itemKey: "/imgScan", - text: "图片识别文字", - icon: , - className: styles.navItem1, - des:'识别图片中的文字' - }, -]; const XLayout = () => { const location = useLocation(); @@ -145,7 +96,7 @@ const XLayout = () => { onSelect={(item: any) => { history.push(item.itemKey as string); }} - items={NavMap} + items={NAV_MAP} footer={{ collapseButton: true, }} diff --git a/src/layouts/menu.tsx b/src/layouts/menu.tsx new file mode 100644 index 0000000..b84e96d --- /dev/null +++ b/src/layouts/menu.tsx @@ -0,0 +1,51 @@ + +import { IconImage, IconIntro } from "@douyinfe/semi-icons-lab"; +import ImageCompressSvg from "@/assets/img/imageCompress.svg"; +import JsonSvg from "@/assets/img/json.svg"; +import JsonDiffSvg from "@/assets/img/jsonDiff.svg"; +import imgScanSvg from "@/assets/img/imgScan.svg"; +import imgFormatConversion from "@/assets/img/imgFormatConversion.svg"; + +export const NAV_MAP = [ + { + itemKey: "/home", + text: "首页", + icon: , + }, + { + itemKey: "/jsonFormatting", + text: "JSON格式化", + icon: , + des:'将JSON格式化为可读性更好的格式' + }, + { + itemKey: "/jsonDiff", + text: "JSON对比", + icon: , + des:'比较两个JSON对象,并展示差异' + }, + { + itemKey: "/imageSlicing", + text: "图片分割", + icon: , + des:'将图片切割成多张小图片' + }, + { + itemKey: "/imageCompress", + text: "图片压缩", + icon: , + des:'压缩图片大小' + }, + { + itemKey: "/imgScan", + text: "图片识别文字", + icon: , + des:'识别图片中的文字' + }, + { + itemKey: "/imgFormatConversion", + text: "图片格式转换", + icon: , + des:'图片格式互相转换' + }, +]; \ No newline at end of file diff --git a/src/pages/home.tsx b/src/pages/home.tsx index c8c2ab6..4eeb97c 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -2,7 +2,7 @@ import { Card, Typography } from "@douyinfe/semi-ui"; import { Col, Row } from "antd"; import React from "react"; import { history } from "umi"; -import { NavMap } from "../layouts/index"; +import { NAV_MAP } from "../layouts/menu"; const { Title,Text } = Typography; @@ -12,7 +12,7 @@ const Home = () => { return (
- {NavMap.filter((item) => item.itemKey !== "/home").map( + {NAV_MAP.filter((item) => item.itemKey !== "/home").map( (item, index) => ( ([]); + const [currentImg, setCurrentImg] = useState(""); + const [format, setFormat] = useState(""); + const [spinning, setSpinning] = useState(false); + + const filesChange = async (info: UploadChangeParam) => { + setSpinning(true) + const newFile: FileWithPreview = { + ...info.file, + preview: "", + }; + setFileList([newFile]); + if (info.file.originFileObj) { + if(getFileExtension(newFile.name) === "heic") { + try { + const convertedBlob = await heic2any({ + blob: info.file.originFileObj, + toType: "image/jpeg", + quality: 1, + }); + const convertedImageUrl = URL.createObjectURL(convertedBlob as Blob); + setCurrentImg(convertedImageUrl); + newFile.preview = convertedImageUrl; // 更新文件预览 + } catch (error) { + console.error(error); + } + } else{ + let reader = new FileReader(); + reader.readAsDataURL(info.file.originFileObj); + reader.onload = async function (e: any) { + setCurrentImg(e.target.result); + }; + } + } + setSpinning(false) + }; + + const onChange = (e: any) => { + console.log("radio checked", e.target.value); + setFormat(e.target.value); + }; + + const downloadImg = () => { + if (currentImg && format) { + if (fileList[0].name.includes("svg")) { + downloadImageByCanvas(currentImg, fileList[0].name, format); + } else { + const a = document.createElement("a"); + a.href = currentImg; + a.download = `${fileList[0].name}converted.${format}`; + a.click(); + } + } + }; + + const downloadImageByCanvas = ( + base64Data: string, + fileName: string, + fileType: string + ) => { + const canvas = document.createElement("canvas"); + const ctx: any = canvas.getContext("2d"); + const img: any = new Image(); + img.src = base64Data; + img.onload = () => { + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + const linkSource = canvas.toDataURL(`image/${fileType}`); + const downloadLink = document.createElement("a"); + downloadLink.href = linkSource; + downloadLink.download = `${fileName}converted.${fileType}`; + downloadLink.click(); + }; + }; + + return ( +
+ + + { + setFileList([]); + setCurrentImg(""); + }} + > + 重新上传图片 + + ) : null + } + > + {currentImg ? ( + + ) : ( + + {}} + showUploadList={false} + > +

+ +

+

点击或拖拽上传图片,支持jpg、jpeg、png、heic、svg、webp格式

+
+
+ + )} +
+ + + +
+ 选择要转换成的图片格式 +
+ + {FORMATS.map((format) => ( + + {format} + + ))} + +
+ +
+
+ +
+
+ ); +} diff --git a/src/pages/imgScan.tsx b/src/pages/imgScan.tsx index 3c995fe..3471405 100644 --- a/src/pages/imgScan.tsx +++ b/src/pages/imgScan.tsx @@ -4,6 +4,7 @@ import { Button } from "@douyinfe/semi-ui"; import { useState } from "react"; import { createWorker } from "tesseract.js"; import { UploadChangeParam } from "antd/es/upload"; +import { BODY_HEIGHT } from "@/utils/const"; const { Dragger } = Upload; @@ -54,7 +55,7 @@ export default function ImgScan() { - +
{extractedText}
diff --git a/src/utils/const.ts b/src/utils/const.ts new file mode 100644 index 0000000..509755c --- /dev/null +++ b/src/utils/const.ts @@ -0,0 +1 @@ +export const BODY_HEIGHT = 'calc(100vh - 150px)'; \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 8ed1868..d5358ee 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,4 +4,16 @@ export function createRandomArray(length: number, min = 1, max = 1000) { array.push(Math.floor(Math.random() * (max - min + 1)) + min); } return array; +} + + export function getFileExtension(filename: string): string { + const parts = filename.split('.'); + if (parts.length === 1) { + return ''; + } + const extension = parts.pop(); + if (extension) { + return extension.toLowerCase(); + } + return ''; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b5eb9f5..306da88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4080,6 +4080,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +heic2any@^0.0.4: + version "0.0.4" + resolved "https://registry.npmmirror.com/heic2any/-/heic2any-0.0.4.tgz#eddb8e6fec53c8583a6e18b65069bb5e8d19028a" + integrity sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA== + history@5.3.0, history@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"