From c13c0ff5bd633d2121d21ceff19134735f7bfd9a Mon Sep 17 00:00:00 2001 From: KuaiYu95 Date: Wed, 3 Jan 2024 15:35:35 +0800 Subject: [PATCH 1/5] feat: less css converter --- .gitignore | 2 +- declare.d.ts | 1 + package-lock.json | 182 ++++++++++++++++++++++++++ package.json | 3 + src/pages/less2css.tsx | 289 +++++++++++++++++++++++++++++++++++++++++ src/utils/tools.ts | 7 + tsconfig.json | 2 +- 7 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 declare.d.ts create mode 100644 src/pages/less2css.tsx diff --git a/.gitignore b/.gitignore index 6bf6273..cf40755 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,8 @@ yarn-error.log* .vercel # typescript -*.tsbuildinfo next-env.d.ts +*.tsbuildinfo .yalc yalc yalc.lock \ No newline at end of file diff --git a/declare.d.ts b/declare.d.ts new file mode 100644 index 0000000..9aa7166 --- /dev/null +++ b/declare.d.ts @@ -0,0 +1 @@ +declare module 'css2less'; diff --git a/package-lock.json b/package-lock.json index fb67145..ba2a3ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,12 +31,14 @@ "change-case": "^5.3.0", "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", + "css2less": "^0.1.4", "eslint-config-next": "^14.0.4", "html-entities": "^2.4.0", "ip": "^1.1.8", "js-yaml": "^4.1.0", "jsfuck": "^0.4.0", "json-2-csv": "^5.0.1", + "less": "^4.2.0", "next": "^14.0.4", "next-compose-plugins": "^2.2.1", "next-transpile-modules": "^10.0.1", @@ -75,6 +77,7 @@ "@types/big-integer": "^0.0.31", "@types/crypto-js": "^4.2.0", "@types/ip": "^1.1.3", + "@types/less": "^3.0.6", "@types/node": "20.1.5", "@types/node-forge": "^1.3.10", "@types/qs": "^6.9.7", @@ -2055,6 +2058,12 @@ "resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/less": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/less/-/less-3.0.6.tgz", + "integrity": "sha512-PecSzorDGdabF57OBeQO/xFbAkYWo88g4Xvnsx7LRwqLC17I7OoKtA3bQB9uXkY6UkMWCOsA8HSVpaoitscdXw==", + "dev": true + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", @@ -3130,6 +3139,14 @@ "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dependencies": { + "is-what": "^3.14.1" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -3211,6 +3228,11 @@ "postcss-value-parser": "^3.3.0" } }, + "node_modules/css2less": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/css2less/-/css2less-0.1.4.tgz", + "integrity": "sha512-3fvKdQGpMc+NuFxgYyQHM/HbtVydXXTAt1FH7yihYoWq52ydPwfTF5W5OU/zTx2rNZ4L9Nj4qrlWshsLS4Z3fg==" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", @@ -3522,6 +3544,18 @@ "node": ">=10.13.0" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", @@ -4866,6 +4900,18 @@ "node": ">=14" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.0.tgz", @@ -4880,6 +4926,18 @@ "integrity": "sha512-+F8B0ip7w6DskaN3bd3GyTvlPMiBfd3kom3qo+k/1jFNkIqtIUqrD0t7r97qHaBtDkTp/vp1DLAU3lqyj0szFA==", "peer": true }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5302,6 +5360,11 @@ "get-intrinsic": "^1.1.1" } }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", @@ -5539,6 +5602,40 @@ "node": ">=0.10" } }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", @@ -5863,6 +5960,28 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/map-obj/-/map-obj-4.3.0.tgz", @@ -6387,6 +6506,18 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -6528,6 +6659,22 @@ "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, "node_modules/next": { "version": "14.0.4", "resolved": "https://registry.npmmirror.com/next/-/next-14.0.4.tgz", @@ -6856,6 +7003,14 @@ "node": ">=8" } }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", @@ -6918,6 +7073,15 @@ "node": ">=0.10" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/popmotion": { "version": "9.3.6", "resolved": "https://registry.npmmirror.com/popmotion/-/popmotion-9.3.6.tgz", @@ -7016,6 +7180,12 @@ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "optional": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -7922,6 +8092,18 @@ "is-regex": "^1.1.4" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "optional": true + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.0.tgz", diff --git a/package.json b/package.json index a963e80..2df8b97 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "js-yaml": "^4.1.0", "jsfuck": "^0.4.0", "json-2-csv": "^5.0.1", + "less": "^4.2.0", "next": "^14.0.4", "next-compose-plugins": "^2.2.1", "next-transpile-modules": "^10.0.1", @@ -75,6 +76,7 @@ "slick-carousel": "^1.8.1", "sql-formatter": "^15.0.2", "sql.js": "^1.9.0", + "css2less": "^0.1.4", "swiper": "^8.4.7", "typescript": "5.0.4", "xlsx": "^0.18.5", @@ -87,6 +89,7 @@ "@types/big-integer": "^0.0.31", "@types/crypto-js": "^4.2.0", "@types/ip": "^1.1.3", + "@types/less": "^3.0.6", "@types/node": "20.1.5", "@types/node-forge": "^1.3.10", "@types/qs": "^6.9.7", diff --git a/src/pages/less2css.tsx b/src/pages/less2css.tsx new file mode 100644 index 0000000..a9b0971 --- /dev/null +++ b/src/pages/less2css.tsx @@ -0,0 +1,289 @@ +import MainContent from '@/components/MainContent'; +import alert from '@/components/Alert'; +import { Box, Button, Stack } from '@mui/material'; +import { useCallback, useEffect, useState } from 'react'; +import AceEditor from 'react-ace'; +import SwapHorizontalCircleIcon from '@mui/icons-material/SwapHorizontalCircle'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import LESS from 'less'; +import c2l from 'css2less'; + +import 'ace-builds/src-noconflict/ext-language_tools'; +import 'ace-builds/src-noconflict/theme-monokai'; +import 'ace-builds/src-noconflict/mode-less'; +import 'ace-builds/src-noconflict/mode-css'; + +/** + * A => Less + * B => CSS + * + * 适用于两种语言的互转模版 + * 1. 更改 import ace-builds/src-noconflict/mode-* + * 2. 更改所有 enum 枚举值 + * 3. 在 a2b() 函数和 b2a() 函数中添加转换逻辑 return 转换结果 + * */ + +// 转换顺序 +enum ConvertType { + A2B = 1, + B2A = 2, +} +// 导出文件 MIME 类型;未知设为空 +enum ConvertFileType { + A2B = 'text/css', + B2A = '', +} +// 导出文件名;未知 MIME 类型补充后缀 +enum ExportType { + A2B = 'lesstocss', + B2A = 'csstoless.less', +} +// 按钮文字 +enum ButtonText { + A2B = 'LESS', + B2A = 'CSS', +} +// 导入限制类型 +enum InputAccept { + A2B = '.less', + B2A = '.css', +} +// ace 编辑器 mode 类型 +enum AceMode { + A2B = 'less', + B2A = 'css', +} + +const _C = () => { + const [a, setA] = useState(''); + const [b, setB] = useState(''); + const [convert, setConvert] = useState(ConvertType.A2B); + const [error, setError] = useState(''); + + // 处理 a2b + const a2b = async (v: string) => { + try { + const res = await LESS.render(v); + setB(res.css); + } catch (e) { + setError(String(e)); + } + }; + + // 处理 b2a + const b2a = async (v: string) => { + try { + const res = await c2l(v, {}); + setA(res); + } catch (e) { + setError(String(e) || '未知错误'); + } + }; + + const saveStringToFile = () => { + const blob = + convert === ConvertType.A2B + ? new Blob([b], { type: ConvertFileType.A2B }) + : new Blob([a], { type: ConvertFileType.B2A }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = + convert === ConvertType.A2B ? ExportType.A2B : ExportType.B2A; + + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + }; + + const handleClick = useCallback(() => { + alert.success('复制成功'); + }, []); + + const handleButtonClick = () => { + const fileInput = document.getElementById('fileInput'); + fileInput?.click(); + }; + + const handleFileChange = (event: any) => { + const file = event.target.files[0]; + if (file) { + if ( + file.type === + (convert === ConvertType.A2B + ? ConvertFileType.B2A + : ConvertFileType.A2B) + ) { + const reader = new FileReader(); + reader.onload = (e: any) => { + const content = e.target.result; + if (convert === ConvertType.A2B) { + setA(content); + } else setB(content); + setError(''); + }; + + reader.readAsText(file); + } else { + setA(''); + setError('Invalid file type.'); + } + } + }; + + useEffect(() => { + if (a.trim() === '') { + setB(''); + setError(''); + return; + } + if (convert === ConvertType.A2B) { + try { + a2b(a); + setError(''); + } catch (e) { + setError(String(e)); + } + } + }, [a, convert]); + + useEffect(() => { + if (!b) { + setA(''); + setError(''); + return; + } + if (convert === ConvertType.B2A) { + // 判断是否是数组或对象 + try { + b2a(b); + setError(''); + } catch (e) { + setError(String(e)); + } + } else { + setError(''); + } + }, [b, convert]); + + return ( + + + + + + + {convert === ConvertType.B2A ? ButtonText.B2A : ButtonText.A2B} + + + + setConvert( + convert === ConvertType.B2A ? ConvertType.A2B : ConvertType.B2A + ) + } + > + + + + + {convert === ConvertType.B2A ? ButtonText.A2B : ButtonText.B2A} + + + + + + + + + + + + + + + + + ); +}; + +export default _C; diff --git a/src/utils/tools.ts b/src/utils/tools.ts index 0a054ee..b95f7bc 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -381,4 +381,11 @@ export const allTools: Tool[] = [ key: [], subTitle: '在线颜色吸取器,可以快速生成十种常用颜色的代码', }, + { + label: 'LESS CSS 互转', + tags: [Tags.DEV], + path: '/less2css', + key: [], + subTitle: '支持 less 转 css,css 转 less', + }, ]; diff --git a/tsconfig.json b/tsconfig.json index 6b546c9..4e8ca24 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "imgLoader.js"], + "include": ["next-env.d.ts", "*.d.ts", "**/*.ts", "**/*.tsx", "imgLoader.js"], "exclude": ["node_modules"] } From fc4afc90809d1c1ac4725956397a47ddd82a5880 Mon Sep 17 00:00:00 2001 From: KuaiYu95 Date: Thu, 4 Jan 2024 16:09:19 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20markdown=20html=20=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E4=BA=92=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 55 ++++++++ package.json | 5 + src/pages/less2css.tsx | 4 +- src/pages/md2html.tsx | 310 +++++++++++++++++++++++++++++++++++++++++ src/utils/tools.ts | 7 + 5 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 src/pages/md2html.tsx diff --git a/package-lock.json b/package-lock.json index ba2a3ba..0c8deed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", "css2less": "^0.1.4", + "dompurify": "^3.0.6", "eslint-config-next": "^14.0.4", "html-entities": "^2.4.0", "ip": "^1.1.8", @@ -39,6 +40,7 @@ "jsfuck": "^0.4.0", "json-2-csv": "^5.0.1", "less": "^4.2.0", + "marked": "^11.1.1", "next": "^14.0.4", "next-compose-plugins": "^2.2.1", "next-transpile-modules": "^10.0.1", @@ -66,6 +68,7 @@ "sql-formatter": "^15.0.2", "sql.js": "^1.9.0", "swiper": "^8.4.7", + "turndown": "^7.1.2", "typescript": "5.0.4", "xlsx": "^0.18.5", "xmorse": "^1.0.0", @@ -76,6 +79,7 @@ "@commitlint/config-conventional": "^18.4.3", "@types/big-integer": "^0.0.31", "@types/crypto-js": "^4.2.0", + "@types/dompurify": "^3.0.5", "@types/ip": "^1.1.3", "@types/less": "^3.0.6", "@types/node": "20.1.5", @@ -86,6 +90,7 @@ "@types/react-dom": "18.2.4", "@types/react-syntax-highlighter": "^15.5.11", "@types/sql.js": "^1.4.9", + "@types/turndown": "^5.0.4", "@typescript-eslint/eslint-plugin": "^6.15.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", @@ -2014,6 +2019,15 @@ "@types/ms": "*" } }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/emscripten": { "version": "1.39.10", "resolved": "https://registry.npmmirror.com/@types/emscripten/-/emscripten-1.39.10.tgz", @@ -2202,6 +2216,18 @@ "@types/node": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, + "node_modules/@types/turndown": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/@types/turndown/-/turndown-5.0.4.tgz", + "integrity": "sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.10.tgz", @@ -3474,6 +3500,16 @@ "ssr-window": "^4.0.0" } }, + "node_modules/domino": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/domino/-/domino-2.1.6.tgz", + "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" + }, + "node_modules/dompurify": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.0.6.tgz", + "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==" + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmmirror.com/dot-prop/-/dot-prop-5.3.0.tgz", @@ -5996,6 +6032,17 @@ "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.3.tgz", "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==" }, + "node_modules/marked": { + "version": "11.1.1", + "resolved": "https://registry.npmmirror.com/marked/-/marked-11.1.1.tgz", + "integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mdast-util-definitions": { "version": "5.1.2", "resolved": "https://registry.npmmirror.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", @@ -8787,6 +8834,14 @@ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/turndown": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/turndown/-/turndown-7.1.2.tgz", + "integrity": "sha512-ntI9R7fcUKjqBP6QU8rBK2Ehyt8LAzt3UBT9JR9tgo6GtuKvyUzpayWmeMKJw1DPdXzktvtIT8m2mVXz+bL/Qg==", + "dependencies": { + "domino": "^2.1.6" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 2df8b97..d25e0c2 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "change-case": "^5.3.0", "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", + "dompurify": "^3.0.6", "eslint-config-next": "^14.0.4", "html-entities": "^2.4.0", "ip": "^1.1.8", @@ -50,6 +51,7 @@ "jsfuck": "^0.4.0", "json-2-csv": "^5.0.1", "less": "^4.2.0", + "marked": "^11.1.1", "next": "^14.0.4", "next-compose-plugins": "^2.2.1", "next-transpile-modules": "^10.0.1", @@ -78,6 +80,7 @@ "sql.js": "^1.9.0", "css2less": "^0.1.4", "swiper": "^8.4.7", + "turndown": "^7.1.2", "typescript": "5.0.4", "xlsx": "^0.18.5", "xmorse": "^1.0.0", @@ -88,6 +91,7 @@ "@commitlint/config-conventional": "^18.4.3", "@types/big-integer": "^0.0.31", "@types/crypto-js": "^4.2.0", + "@types/dompurify": "^3.0.5", "@types/ip": "^1.1.3", "@types/less": "^3.0.6", "@types/node": "20.1.5", @@ -98,6 +102,7 @@ "@types/react-dom": "18.2.4", "@types/react-syntax-highlighter": "^15.5.11", "@types/sql.js": "^1.4.9", + "@types/turndown": "^5.0.4", "@typescript-eslint/eslint-plugin": "^6.15.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", diff --git a/src/pages/less2css.tsx b/src/pages/less2css.tsx index a9b0971..22f5f98 100644 --- a/src/pages/less2css.tsx +++ b/src/pages/less2css.tsx @@ -251,7 +251,7 @@ const _C = () => { height: 'calc(100vh - 310px)', }} value={convert === ConvertType.B2A ? b : a} - mode={convert === ConvertType.B2A ? AceMode.A2B : AceMode.B2A} + mode={convert === ConvertType.B2A ? AceMode.B2A : AceMode.A2B} theme='monokai' onChange={convert === ConvertType.B2A ? setB : setA} editorProps={{ $blockScrolling: true }} @@ -265,7 +265,7 @@ const _C = () => { height: 'calc(100vh - 310px)', }} value={error || (convert === ConvertType.B2A ? a : b)} - mode={convert === ConvertType.B2A ? AceMode.B2A : AceMode.A2B} + mode={convert === ConvertType.B2A ? AceMode.A2B : AceMode.B2A} theme='monokai' readOnly editorProps={{ $blockScrolling: true }} diff --git a/src/pages/md2html.tsx b/src/pages/md2html.tsx new file mode 100644 index 0000000..263648c --- /dev/null +++ b/src/pages/md2html.tsx @@ -0,0 +1,310 @@ +import MainContent from '@/components/MainContent'; +import alert from '@/components/Alert'; +import { Box, Button, Stack } from '@mui/material'; +import { useCallback, useEffect, useState } from 'react'; +import AceEditor from 'react-ace'; +import SwapHorizontalCircleIcon from '@mui/icons-material/SwapHorizontalCircle'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import TurndownService from 'turndown'; +import { marked } from 'marked'; +import * as DOMPurify from 'dompurify'; + +import 'ace-builds/src-noconflict/ext-language_tools'; +import 'ace-builds/src-noconflict/theme-monokai'; +import 'ace-builds/src-noconflict/mode-html'; +import 'ace-builds/src-noconflict/mode-markdown'; + +/** + * A => markdown + * B => html + * + * 适用于两种语言的互转模版 + * 1. 更改 import ace-builds/src-noconflict/mode-* + * 2. 更改所有 enum 枚举值 + * 3. 在 a2b() 函数和 b2a() 函数中添加转换逻辑 return 转换结果 + * */ + +// 转换顺序 +enum ConvertType { + A2B = 1, + B2A = 2, +} +// 导出文件 MIME 类型;未知设为空 +enum ConvertFileType { + A2B = 'text/html', + B2A = '', +} +// 导出文件名;未知 MIME 类型补充后缀 +enum ExportType { + A2B = 'mdtohtml', + B2A = 'htmltomd.md', +} +// 按钮文字 +enum ButtonText { + A2B = 'MARKDOWN', + B2A = 'HTML', +} +// 导入限制类型 +enum InputAccept { + A2B = '.md', + B2A = '.html', +} +// ace 编辑器 mode 类型 +enum AceMode { + A2B = 'markdown', + B2A = 'html', +} + +const _C = () => { + const [a, setA] = useState(''); + const [b, setB] = useState(''); + const [convert, setConvert] = useState(ConvertType.A2B); + const [error, setError] = useState(''); + + const postprocess = (v: string) => { + return DOMPurify.sanitize(v); + }; + + marked.use({ + pedantic: false, + gfm: true, + breaks: false, + hooks: { + postprocess, + }, + }); + + // 处理 a2b + const a2b = async (v: string) => { + try { + const html = await marked.parse( + v.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/, '') + ); + setB(html); + } catch (e) { + setError(String(e)); + } + }; + + // 处理 b2a + const b2a = async (v: string) => { + try { + const td = new TurndownService({ + headingStyle: 'atx', + hr: '---', + codeBlockStyle: 'fenced', + }); + const md = await td.turndown(v); + setA(md); + } catch (e) { + setError(String(e) || '未知错误'); + } + }; + + const saveStringToFile = () => { + const blob = + convert === ConvertType.A2B + ? new Blob([b], { type: ConvertFileType.A2B }) + : new Blob([a], { type: ConvertFileType.B2A }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = + convert === ConvertType.A2B ? ExportType.A2B : ExportType.B2A; + + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + }; + + const handleClick = useCallback(() => { + alert.success('复制成功'); + }, []); + + const handleButtonClick = () => { + const fileInput = document.getElementById('fileInput'); + fileInput?.click(); + }; + + const handleFileChange = (event: any) => { + const file = event.target.files[0]; + if (file) { + if ( + file.type === + (convert === ConvertType.A2B + ? ConvertFileType.B2A + : ConvertFileType.A2B) + ) { + const reader = new FileReader(); + reader.onload = (e: any) => { + const content = e.target.result; + if (convert === ConvertType.A2B) { + setA(content); + } else setB(content); + setError(''); + }; + + reader.readAsText(file); + } else { + setA(''); + setError('Invalid file type.'); + } + } + }; + + useEffect(() => { + if (a.trim() === '') { + setB(''); + setError(''); + return; + } + if (convert === ConvertType.A2B) { + try { + a2b(a); + setError(''); + } catch (e) { + setError(String(e)); + } + } + }, [a, convert]); + + useEffect(() => { + if (!b) { + setA(''); + setError(''); + return; + } + if (convert === ConvertType.B2A) { + // 判断是否是数组或对象 + try { + b2a(b); + setError(''); + } catch (e) { + setError(String(e)); + } + } else { + setError(''); + } + }, [b, convert]); + + return ( + + + + + + + {convert === ConvertType.B2A ? ButtonText.B2A : ButtonText.A2B} + + + + setConvert( + convert === ConvertType.B2A ? ConvertType.A2B : ConvertType.B2A + ) + } + > + + + + + {convert === ConvertType.B2A ? ButtonText.A2B : ButtonText.B2A} + + + + + + + + + + + + + + + + + ); +}; + +export default _C; diff --git a/src/utils/tools.ts b/src/utils/tools.ts index b95f7bc..404eb7c 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -388,4 +388,11 @@ export const allTools: Tool[] = [ key: [], subTitle: '支持 less 转 css,css 转 less', }, + { + label: 'markdown html 格式互转', + tags: [Tags.DEV], + path: '/md2html', + key: [], + subTitle: 'markdown html 格式互转', + }, ]; From a8a1466b5288618a3b9146a3592887bc888cf6ee Mon Sep 17 00:00:00 2001 From: KuaiYu95 Date: Thu, 4 Jan 2024 16:10:56 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20json=20xml=20=20=E4=BA=92=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 34 ++++- package.json | 2 + src/pages/json2xml.tsx | 293 +++++++++++++++++++++++++++++++++++++++++ src/pages/md2html.tsx | 4 +- src/utils/tools.ts | 7 + 5 files changed, 336 insertions(+), 4 deletions(-) create mode 100644 src/pages/json2xml.tsx diff --git a/package-lock.json b/package-lock.json index 0c8deed..6ac8ed7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "turndown": "^7.1.2", "typescript": "5.0.4", "xlsx": "^0.18.5", + "xml2js": "^0.6.2", "xmorse": "^1.0.0", "yarn": "^1.22.21" }, @@ -91,6 +92,7 @@ "@types/react-syntax-highlighter": "^15.5.11", "@types/sql.js": "^1.4.9", "@types/turndown": "^5.0.4", + "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^6.15.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", @@ -2233,6 +2235,15 @@ "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.10.tgz", "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.15.0", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", @@ -8148,8 +8159,7 @@ "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "optional": true + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/scheduler": { "version": "0.23.0", @@ -9301,6 +9311,26 @@ "node": ">=0.8" } }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmorse": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/xmorse/-/xmorse-1.0.0.tgz", diff --git a/package.json b/package.json index d25e0c2..f02552c 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "turndown": "^7.1.2", "typescript": "5.0.4", "xlsx": "^0.18.5", + "xml2js": "^0.6.2", "xmorse": "^1.0.0", "yarn": "^1.22.21" }, @@ -103,6 +104,7 @@ "@types/react-syntax-highlighter": "^15.5.11", "@types/sql.js": "^1.4.9", "@types/turndown": "^5.0.4", + "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^6.15.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", diff --git a/src/pages/json2xml.tsx b/src/pages/json2xml.tsx new file mode 100644 index 0000000..59a21c9 --- /dev/null +++ b/src/pages/json2xml.tsx @@ -0,0 +1,293 @@ +import MainContent from '@/components/MainContent'; +import alert from '@/components/Alert'; +import { Box, Button, Stack } from '@mui/material'; +import { useCallback, useEffect, useState } from 'react'; +import AceEditor from 'react-ace'; +import SwapHorizontalCircleIcon from '@mui/icons-material/SwapHorizontalCircle'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { parseStringPromise, Builder } from 'xml2js'; + +import 'ace-builds/src-noconflict/ext-language_tools'; +import 'ace-builds/src-noconflict/theme-monokai'; +import 'ace-builds/src-noconflict/mode-json'; +import 'ace-builds/src-noconflict/mode-xml'; + +/** + * A => json + * B => xml + * + * 适用于两种语言的互转模版 + * 1. 更改 import ace-builds/src-noconflict/mode-* + * 2. 更改所有 enum 枚举值 + * 3. 在 a2b() 函数和 b2a() 函数中添加转换逻辑 return 转换结果 + * */ + +// 转换顺序 +enum ConvertType { + A2B = 1, + B2A = 2, +} +// 导出文件 MIME 类型;未知设为空 +enum ConvertFileType { + A2B = 'application/json', + B2A = 'application/xml', +} +// 导出文件名;未知 MIME 类型补充后缀 +enum ExportType { + A2B = 'jsontoxml', + B2A = 'xmltojson', +} +// 按钮文字 +enum ButtonText { + A2B = 'JSON', + B2A = 'XML', +} +// 导入限制类型 +enum InputAccept { + A2B = '.json', + B2A = '.xml', +} +// ace 编辑器 mode 类型 +enum AceMode { + A2B = 'json', + B2A = 'xml', +} + +const _C = () => { + const [a, setA] = useState(''); + const [b, setB] = useState(''); + const [convert, setConvert] = useState(ConvertType.A2B); + const [error, setError] = useState(''); + + // 处理 a2b + const a2b = async (v: string) => { + try { + const builder = new Builder(); + const xml = builder.buildObject(JSON.parse(v)); + setB(xml); + } catch (e) { + setError(String(e)); + } + }; + + // 处理 b2a + const b2a = async (v: string) => { + try { + const json = await parseStringPromise(v, { explicitArray: false }); + setA(JSON.stringify(json, null, 2)); + } catch (e) { + setError(String(e) || '未知错误'); + } + }; + + const saveStringToFile = () => { + const blob = + convert === ConvertType.A2B + ? new Blob([b], { type: ConvertFileType.B2A }) + : new Blob([a], { type: ConvertFileType.A2B }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = + convert === ConvertType.A2B ? ExportType.A2B : ExportType.B2A; + + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + }; + + const handleClick = useCallback(() => { + alert.success('复制成功'); + }, []); + + const handleButtonClick = () => { + const fileInput = document.getElementById('fileInput'); + fileInput?.click(); + }; + + const handleFileChange = (event: any) => { + const file = event.target.files[0]; + if (file) { + console.log( + '🐵 ~ file: json2xml.tsx:112 ~ handleFileChange ~ file:', + file + ); + if ( + file.type === + (convert === ConvertType.A2B + ? ConvertFileType.A2B + : ConvertFileType.B2A) + ) { + const reader = new FileReader(); + reader.onload = (e: any) => { + const content = e.target.result; + if (convert === ConvertType.A2B) { + setA(content); + } else setB(content); + setError(''); + }; + + reader.readAsText(file); + } else { + setA(''); + setError('Invalid file type.'); + } + } + }; + + useEffect(() => { + if (a.trim() === '') { + setB(''); + setError(''); + return; + } + if (convert === ConvertType.A2B) { + try { + a2b(a); + setError(''); + } catch (e) { + setError(String(e)); + } + } + }, [a, convert]); + + useEffect(() => { + if (!b) { + setA(''); + setError(''); + return; + } + if (convert === ConvertType.B2A) { + // 判断是否是数组或对象 + try { + b2a(b); + setError(''); + } catch (e) { + setError(String(e)); + } + } else { + setError(''); + } + }, [b, convert]); + + return ( + + + + + + + {convert === ConvertType.B2A ? ButtonText.B2A : ButtonText.A2B} + + + + setConvert( + convert === ConvertType.B2A ? ConvertType.A2B : ConvertType.B2A + ) + } + > + + + + + {convert === ConvertType.B2A ? ButtonText.A2B : ButtonText.B2A} + + + + + + + + + + + + + + + + + ); +}; + +export default _C; diff --git a/src/pages/md2html.tsx b/src/pages/md2html.tsx index 263648c..31ef12e 100644 --- a/src/pages/md2html.tsx +++ b/src/pages/md2html.tsx @@ -272,9 +272,9 @@ const _C = () => { height: 'calc(100vh - 310px)', }} value={convert === ConvertType.B2A ? b : a} - mode={convert === ConvertType.B2A ? AceMode.A2B : AceMode.B2A} theme='monokai' onChange={convert === ConvertType.B2A ? setB : setA} + mode={convert === ConvertType.B2A ? AceMode.B2A : AceMode.A2B} editorProps={{ $blockScrolling: true }} /> { height: 'calc(100vh - 310px)', }} value={error || (convert === ConvertType.B2A ? a : b)} - mode={convert === ConvertType.B2A ? AceMode.B2A : AceMode.A2B} + mode={convert === ConvertType.B2A ? AceMode.A2B : AceMode.B2A} theme='monokai' readOnly editorProps={{ $blockScrolling: true }} diff --git a/src/utils/tools.ts b/src/utils/tools.ts index 404eb7c..01f1e59 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -395,4 +395,11 @@ export const allTools: Tool[] = [ key: [], subTitle: 'markdown html 格式互转', }, + { + label: 'JSON XML 互转', + tags: [Tags.JSON], + path: '/json2xml', + key: [], + subTitle: 'JSON 转 XML,XML 转 JSON', + }, ]; From 0d5898b7aae64ba5f3d2c31990e586f07e63111e Mon Sep 17 00:00:00 2001 From: KuaiYu95 Date: Thu, 4 Jan 2024 16:32:52 +0800 Subject: [PATCH 4/5] fix: package lock file --- package-lock.json | 120 ---------------------------------------------- 1 file changed, 120 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ac8ed7..e560ccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1747,21 +1747,6 @@ "glob": "7.1.7" } }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", - "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-darwin-x64": { "version": "14.0.4", "resolved": "https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz", @@ -1777,111 +1762,6 @@ "node": ">= 10" } }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", - "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", - "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", - "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", - "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", - "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", - "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", - "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", From ffbc01c679915bfb0c4226f0e5c474506ef798e9 Mon Sep 17 00:00:00 2001 From: KuaiYu95 Date: Thu, 4 Jan 2024 17:52:32 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E5=9B=BE=E7=89=87=20OCR=20?= =?UTF-8?q?=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 228 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- src/pages/ocr.tsx | 112 ++++++++++++++++++++++ src/utils/tools.ts | 7 ++ 4 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 src/pages/ocr.tsx diff --git a/package-lock.json b/package-lock.json index e560ccd..8c1eeed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "sql-formatter": "^15.0.2", "sql.js": "^1.9.0", "swiper": "^8.4.7", + "tesseract.js": "^5.0.4", "turndown": "^7.1.2", "typescript": "5.0.4", "xlsx": "^0.18.5", @@ -2652,6 +2653,11 @@ "resolved": "https://registry.npmmirror.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -4839,6 +4845,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.0.tgz", @@ -5062,6 +5073,11 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5265,6 +5281,11 @@ "node": ">= 0.4" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -6672,6 +6693,25 @@ "enhanced-resolve": "^5.10.0" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.3.1.tgz", @@ -6851,6 +6891,14 @@ "node": ">=14.16" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.3.tgz", @@ -8617,6 +8665,34 @@ "node": ">=6" } }, + "node_modules/tesseract.js": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/tesseract.js/-/tesseract.js-5.0.4.tgz", + "integrity": "sha512-GCIoSQMZlvTP2AaHrjUOH29/oyO7ZyHVe+BhTexEcO7/nDClRVDRjl2sYJLOWSSNbTDrm5q2m1+gfaf3lUrZ5Q==", + "hasInstallScript": true, + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-electron": "^2.2.2", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^5.0.0", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/tesseract.js-core/-/tesseract.js-core-5.0.0.tgz", + "integrity": "sha512-lJur5LzjinW5VYMKlVNnBU2JPLpO+A9VqAYBeuV+ZgH0hKvsnm+536Yyp+/zRTBdLe7D6Kok0FN9g+TE4J8qGA==" + }, + "node_modules/tesseract.js/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/text-extensions/-/text-extensions-2.4.0.tgz", @@ -8678,6 +8754,11 @@ "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", @@ -9015,6 +9096,11 @@ "unist-util-stringify-position": "^3.0.0" } }, + "node_modules/wasm-feature-detect": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/wasm-feature-detect/-/wasm-feature-detect-1.6.1.tgz", + "integrity": "sha512-R1i9ED8UlLu/foILNB1ck9XS63vdtqU/tP1MCugVekETp/ySCrBZRk5I/zI67cI1wlQYeSonNm1PLjDHZDNg6g==" + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.0.tgz", @@ -9027,6 +9113,20 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", @@ -9331,10 +9431,138 @@ "node": ">=10" } }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "engines": { + "node": "*" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", + "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", + "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", + "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", + "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", + "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", + "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", + "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", + "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/package.json b/package.json index f02552c..6632747 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "change-case": "^5.3.0", "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", + "css2less": "^0.1.4", "dompurify": "^3.0.6", "eslint-config-next": "^14.0.4", "html-entities": "^2.4.0", @@ -78,8 +79,8 @@ "slick-carousel": "^1.8.1", "sql-formatter": "^15.0.2", "sql.js": "^1.9.0", - "css2less": "^0.1.4", "swiper": "^8.4.7", + "tesseract.js": "^5.0.4", "turndown": "^7.1.2", "typescript": "5.0.4", "xlsx": "^0.18.5", diff --git a/src/pages/ocr.tsx b/src/pages/ocr.tsx new file mode 100644 index 0000000..319ef7e --- /dev/null +++ b/src/pages/ocr.tsx @@ -0,0 +1,112 @@ +import alertActions from '@/components/Alert'; +import MainContent from '@/components/MainContent'; +import { Box, Button } from '@mui/material'; +import { useCallback, useState } from 'react'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import { createWorker } from 'tesseract.js'; + +const sxWrap = { + fontSize: '12px', + p: 2, + mt: 2, + borderRadius: '4px', + backgroundColor: 'rgba(0, 0, 0, 0.05)', + color: 'rgba(0, 0, 0, 0.5)', +}; + +const _C = () => { + const [text, setText] = useState(''); + const [error, setError] = useState(''); + const [status, setStatus] = useState(''); + + const upload = () => { + const fileInput = document.getElementById('fileInput'); + fileInput?.click(); + }; + + const copy = useCallback(() => { + alertActions.success('复制成功'); + }, []); + + const convert = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setText(''); + setError(''); + try { + const imgUrl = URL.createObjectURL(file); + + const worker = await createWorker( + 'eng+chi_sim+rus+deu+fra+jpn+kor', + 1, + { + logger: (m) => { + setStatus( + JSON.stringify({ + status: m.status, + progress: (m.progress * 100).toFixed(2) + '%', + }) + ); + }, + } + ); + const res: any = await worker.recognize(imgUrl); + setText(res.data.text); + } catch (error) { + setError(String(error)); + } + } + }; + + return ( + + + + + + {text + ? '识别成功' + : status || '点击上方按钮,上传图片即可识别图片中的文字!'} + + {text && ( + + + {text} + + + + 复制 + + + + )} + {error && {error}} + + + ); +}; + +export default _C; diff --git a/src/utils/tools.ts b/src/utils/tools.ts index 01f1e59..748218c 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -402,4 +402,11 @@ export const allTools: Tool[] = [ key: [], subTitle: 'JSON 转 XML,XML 转 JSON', }, + { + label: '图片 OCR 识别', + tags: [Tags.IMAGE], + path: '/ocr', + key: [], + subTitle: '支持识别中文、英语、俄语、德语、法语、日语、韩语', + }, ];