18 | {grid.map((row, rowIndex) => (
19 |
20 | {row.map((_, colIndex) => (
21 |
22 | ))}
23 |
24 | ))}
25 |
26 | );
27 | };
28 |
29 | export default ScreenSplitIcon;
--------------------------------------------------------------------------------
/src/pages/jsonFormatting.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Col, Row, message } from "antd"
3 | import { Input } from 'antd';
4 | import ReactJson from 'react-json-view'
5 | import './index.scss'
6 | import { BODY_HEIGHT } from "@/utils/const";
7 |
8 | const { TextArea } = Input;
9 |
10 | const JsonFormatting = () => {
11 | const [jsonData, setJsonData] = useState({});
12 |
13 | const handleJsonData = (e:any) => {
14 | try {
15 | setJsonData(JSON.parse(e.target.value))
16 | } catch {
17 | message.error('请检查输入的JSON格式是否正确')
18 | }
19 | }
20 |
21 | return
22 |
23 |
31 | }
32 |
33 | export default JsonFormatting
--------------------------------------------------------------------------------
/src/.umi/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "importHelpers": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 | "esModuleInterop": true,
17 | "sourceMap": true,
18 | "baseUrl": "../../",
19 | "strict": true,
20 | "resolveJsonModule": true,
21 | "allowSyntheticDefaultImports": true,
22 | "paths": {
23 | "@/*": [
24 | "src/*"
25 | ],
26 | "@@/*": [
27 | "src/.umi/*"
28 | ],
29 | "umi": [
30 | "/Users/xutaotao/Documents/s/XTools/node_modules/umi"
31 | ],
32 | "umi/typings": [
33 | "src/.umi/typings"
34 | ]
35 | }
36 | },
37 | "include": [
38 | "../../.umirc.ts",
39 | "../../.umirc.*.ts",
40 | "../../**/*.d.ts",
41 | "../../**/*.ts",
42 | "../../**/*.tsx"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export function createRandomArray(length: number, min = 1, max = 1000) {
2 | const array = [];
3 | for (let i = 0; i < length; i++) {
4 | array.push(Math.floor(Math.random() * (max - min + 1)) + min);
5 | }
6 | return array;
7 | }
8 |
9 | export function getFileExtension(filename: string): string {
10 | const parts = filename.split('.');
11 | if (parts.length === 1) {
12 | return '';
13 | }
14 | const extension = parts.pop();
15 | if (extension) {
16 | return extension.toLowerCase();
17 | }
18 | return '';
19 | }
20 |
21 | export function formatMs(msTotal: number) {
22 | const ms = msTotal % 1000;
23 | const secs = ((msTotal - ms) / 1000) % 60;
24 | const mins = (((msTotal - ms) / 1000 - secs) / 60) % 60;
25 | const hrs = (((msTotal - ms) / 1000 - secs) / 60 - mins) / 60;
26 | const hrsString = hrs > 0 ? `${hrs.toString().padStart(2, '0')}:` : '';
27 |
28 | return `${hrsString}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms
29 | .toString()
30 | .padStart(3, '0')}`;
31 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "author": "Xutaotaotao",
4 | "scripts": {
5 | "dev": "cross-env ENV=dev umi dev",
6 | "build": "umi build",
7 | "postinstall": "umi setup",
8 | "setup": "umi setup",
9 | "start": "npm run dev"
10 | },
11 | "dependencies": {
12 | "umi": "^4.1.10",
13 | "umi-plugin-gh-pages": "^1.0.1"
14 | },
15 | "devDependencies": {
16 | "@douyinfe/semi-icons-lab": "^2.56.3",
17 | "@douyinfe/semi-ui": "^2.57.0",
18 | "@types/lodash": "^4.17.1",
19 | "@types/react": "^18.0.33",
20 | "@types/react-dom": "^18.0.11",
21 | "antd": "^5.16.4",
22 | "colord": "^2.9.3",
23 | "compressorjs": "^1.2.1",
24 | "cross-env": "^7.0.3",
25 | "gh-pages": "^6.1.1",
26 | "glfx": "^0.0.4",
27 | "heic2any": "^0.0.4",
28 | "json-diff-kit": "^1.0.29",
29 | "lodash": "^4.17.21",
30 | "react-draggable": "^4.4.6",
31 | "react-i18next": "^14.1.1",
32 | "react-json-view": "^1.21.3",
33 | "tesseract.js": "^5.1.0",
34 | "tui-image-editor": "^3.15.3",
35 | "typescript": "^5.0.3"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/assets/img/imgEdit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # XTools
2 |
3 | 
4 |
5 | 
6 |
7 | ## 介绍
8 | XTools 是一款功能强大的在线工具集合,致力于为用户提供便捷高效的工作体验。完全本地化工具,无需云端服务器,不担心隐私泄露。
9 |
10 | ## 访问地址
11 | - 国内地址(服务器配置较低,首次进入可能需要较长时间加载)
12 |
13 | [https://taotaoxu.com/XTools/](https://taotaoxu.com/XTools/)
14 |
15 | - Github站点(科学上网可用这个站点)
16 |
17 | [https://xutaotaotao.github.io/XTools/](https://xutaotaotao.github.io/XTools/)
18 |
19 | ## 功能特点
20 | 1. 多种实用工具:XTools 整合了各类实用工具,涵盖了日常生活、工作学习等方方面面,满足您不同需求。
21 | 2. 用户友好设计:界面简洁清晰,操作简单易懂,让您能够轻松上手使用各种工具。
22 | 3. 高效便捷:XTools 提供快速高效的工具服务,帮助您快速解决问题,提升工作效率。
23 | 4. 完全免费:XTools 所提供的所有工具均为免费使用,让您尽情享受便利而不花一分钱。
24 |
25 | ## 现阶段实现功能
26 |
27 | ### JSON对比
28 | 比较两个JSON对象,并展示差异
29 |
30 | ### 图片分割
31 | 将图片切割成多张小图片
32 | ### 图片压缩
33 | 压缩图片大小,支持单个和批量压缩
34 | ### 图片识别文字
35 | 纯前端实现识别图片中的文字
36 | ### 图片格式转换
37 | 图片格式互相转换
38 | ### 计时器
39 | 用于监控事物的持续时间
40 | ### 颜色转换器
41 | 在不同格式之间转换颜色
42 |
--------------------------------------------------------------------------------
/src/assets/normalize.css:
--------------------------------------------------------------------------------
1 |
2 | html, body, div, span, applet, object, iframe,
3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
4 | a, abbr, acronym, address, big, cite, code,
5 | del, dfn, em, img, ins, kbd, q, s, samp,
6 | small, strike, strong, sub, sup, tt, var,
7 | b, u, i, center,
8 | dl, dt, dd, ol, ul, li,
9 | fieldset, form, label, legend,
10 | table, caption, tbody, tfoot, thead, tr, th, td,
11 | article, aside, canvas, details, embed,
12 | figure, figcaption, footer, header, hgroup,
13 | menu, nav, output, ruby, section, summary,
14 | time, mark, audio, video {
15 | margin: 0;
16 | padding: 0;
17 | border: 0;
18 | font-size: 100%;
19 | font: inherit;
20 | vertical-align: baseline;
21 | }
22 | /* HTML5 display-role reset for older browsers */
23 | article, aside, details, figcaption, figure,
24 | footer, header, hgroup, menu, nav, section {
25 | display: block;
26 | }
27 | body {
28 | line-height: 1;
29 | }
30 | ol, ul {
31 | list-style: none;
32 | }
33 | blockquote, q {
34 | quotes: none;
35 | }
36 | blockquote:before, blockquote:after,
37 | q:before, q:after {
38 | content: '';
39 | content: none;
40 | }
41 | table {
42 | border-collapse: collapse;
43 | border-spacing: 0;
44 | }
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Col,Space, Row } from "antd";
2 | import { Typography } from '@douyinfe/semi-ui';
3 | import { history } from "umi";
4 | import bigLogo from "@/assets/img/biglogo1.svg";
5 | import "./index.scss";
6 | const { Title } = Typography;
7 |
8 |
9 | export default function AppPage() {
10 |
11 | const goHome = () => {
12 | history.push('/home')
13 | }
14 |
15 | const goGithub = () => {
16 | window.open('https://github.com/xutaotaotao/XTools')
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 | 专注工具,助力高效
24 | 完全本地化工具,无需云端,不担心隐私泄露
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/src/pages/imgEditor/index.tsx:
--------------------------------------------------------------------------------
1 | import ImageEditor from "tui-image-editor";
2 | import "tui-color-picker/dist/tui-color-picker.css";
3 | import "tui-image-editor/dist/tui-image-editor.css";
4 | import { useEffect, useRef } from "react";
5 | import TestImg from "@/assets/img/test.jpg"
6 |
7 | export default function ImageEditorPage() {
8 | const editorRef = useRef
(null);
9 |
10 | const options = {
11 | includeUI: {
12 | menuBarPosition: "left",
13 | initMenu: "filter",
14 | loadImage:{
15 | path:TestImg,
16 | name:'预览'
17 | },
18 | locale:{
19 | Download: "下载",
20 | Load: "上传",
21 | },
22 | theme:{
23 | "common.bi.image": "",
24 | "downloadButton.backgroundColor": "#00a9ff",
25 | "downloadButton.border":"#00a9ff"
26 | }
27 | },
28 | selectionStyle: {
29 | cornerSize: 20,
30 | rotatingPointOffset: 70,
31 | },
32 |
33 | };
34 |
35 | useEffect(() => {
36 | if (!editorRef.current) return;
37 | new ImageEditor(editorRef.current,options);
38 | }, []);
39 |
40 | return
43 |
44 | }
--------------------------------------------------------------------------------
/src/.umi/exports.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | // defineApp
5 | export { defineApp } from './core/defineApp'
6 | export type { RuntimeConfig } from './core/defineApp'
7 | // plugins
8 | // plugins types.d.ts
9 | // @umijs/renderer-*
10 | export { createBrowserHistory, createHashHistory, createMemoryHistory, Helmet, HelmetProvider, createSearchParams, generatePath, matchPath, matchRoutes, Navigate, NavLink, Outlet, resolvePath, useLocation, useMatch, useNavigate, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes, useSearchParams, useAppData, useClientLoaderData, useRouteProps, useSelectedRoutes, useServerLoaderData, renderClient, __getRoot, Link, useRouteData, __useFetcher, withRouter } from '/Users/xutaotao/Documents/s/XTools/node_modules/@umijs/renderer-react';
11 | export type { History } from '/Users/xutaotao/Documents/s/XTools/node_modules/@umijs/renderer-react'
12 | // umi/client/client/plugin
13 | export { ApplyPluginsType, PluginManager } from '/Users/xutaotao/Documents/s/XTools/node_modules/umi/client/client/plugin.js';
14 | export { history, createHistory } from './core/history';
15 | export { terminal } from './core/terminal';
16 | // react ssr
17 | export const useServerInsertedHTML: Function = () => {};
18 | // test
19 | export { TestBrowser } from './testBrowser';
20 |
--------------------------------------------------------------------------------
/src/assets/img/imgFormatConversion.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/.umi/core/plugin.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | import * as Plugin_0 from '@@/core/exportStaticRuntimePlugin.ts';
5 | import * as Plugin_1 from '@@/core/helmet.ts';
6 | import { PluginManager } from 'umi';
7 |
8 | function __defaultExport (obj) {
9 | if (obj.default) {
10 | return typeof obj.default === 'function' ? obj.default() : obj.default
11 | }
12 | return obj;
13 | }
14 | export function getPlugins() {
15 | return [
16 | {
17 | apply: Plugin_0,
18 | path: process.env.NODE_ENV === 'production' ? void 0 : '@@/core/exportStaticRuntimePlugin.ts',
19 | },
20 | {
21 | apply: Plugin_1,
22 | path: process.env.NODE_ENV === 'production' ? void 0 : '@@/core/helmet.ts',
23 | },
24 | ];
25 | }
26 |
27 | export function getValidKeys() {
28 | return ['patchRoutes','patchClientRoutes','modifyContextOpts','modifyClientRenderOpts','rootContainer','innerProvider','i18nProvider','accessProvider','dataflowProvider','outerProvider','render','onRouteChange',];
29 | }
30 |
31 | let pluginManager = null;
32 |
33 | export function createPluginManager() {
34 | pluginManager = PluginManager.create({
35 | plugins: getPlugins(),
36 | validKeys: getValidKeys(),
37 | });
38 |
39 |
40 | return pluginManager;
41 | }
42 |
43 | export function getPluginManager() {
44 | return pluginManager;
45 | }
46 |
--------------------------------------------------------------------------------
/src/assets/img/logo.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/.umi/core/terminal.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | let count = 0;
5 | let groupLevel = 0;
6 | function send(type: string, message?: string) {
7 | if(process.env.NODE_ENV==='production'){
8 | return;
9 | }else{
10 | const encodedMessage = message ? `&m=${encodeURI(message)}` : '';
11 | fetch(`/__umi/api/terminal?type=${type}&t=${Date.now()}&c=${count++}&g=${groupLevel}${encodedMessage}`, { mode: 'no-cors' })
12 | }
13 | }
14 | function prettyPrint(obj: any) {
15 | return JSON.stringify(obj, null, 2);
16 | }
17 | function stringifyObjs(objs: any[]) {
18 | const obj = objs.length > 1 ? objs.map(stringify).join(' ') : objs[0];
19 | return typeof obj === 'object' ? `${prettyPrint(obj)}` : obj.toString();
20 | }
21 | function stringify(obj: any) {
22 | return typeof obj === 'object' ? `${JSON.stringify(obj)}` : obj.toString();
23 | }
24 | const terminal = {
25 | log(...objs: any[]) { send('log', stringifyObjs(objs)) },
26 | info(...objs: any[]) { send('info', stringifyObjs(objs)) },
27 | warn(...objs: any[]) { send('warn', stringifyObjs(objs)) },
28 | error(...objs: any[]) { send('error', stringifyObjs(objs)) },
29 | group() { groupLevel++ },
30 | groupCollapsed() { groupLevel++ },
31 | groupEnd() { groupLevel && --groupLevel },
32 | clear() { send('clear') },
33 | trace(...args: any[]) { console.trace(...args) },
34 | profile(...args: any[]) { console.profile(...args) },
35 | profileEnd(...args: any[]) { console.profileEnd(...args) },
36 | };
37 | export { terminal };
38 |
--------------------------------------------------------------------------------
/src/assets/img/logo1.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/pages/home.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Typography } from "@douyinfe/semi-ui";
2 | import { Col, Row } from "antd";
3 | import React from "react";
4 | import { history } from "umi";
5 | import { NAV_MAP } from "../layouts/menu";
6 |
7 | const { Title,Text } = Typography;
8 |
9 | const SIZE = "30px";
10 |
11 | const Home = () => {
12 | return (
13 |
14 |
15 | {NAV_MAP.filter((item) => item.itemKey !== "/home").map(
16 | (item, index) => (
17 | {
21 | history.push(item.itemKey);
22 | }}
23 | >
24 |
30 | {React.cloneElement(item.icon, {
31 | style: {
32 | fontSize: SIZE,
33 | height: SIZE,
34 | width: SIZE,
35 | marginRight: "8px",
36 | },
37 | })}
38 | {item.text}
39 |
40 | }
41 | style={{ height: "100%" }}
42 | bodyStyle={{ height: "100%" }}
43 | shadows="hover"
44 | >
45 | {item.des}
46 |
47 |
48 | )
49 | )}
50 |
51 |
52 | );
53 | };
54 |
55 | export default Home;
56 |
--------------------------------------------------------------------------------
/src/assets/img/imgScan.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/.umi/core/history.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | import { createHashHistory, createMemoryHistory, createBrowserHistory } from '/Users/xutaotao/Documents/s/XTools/node_modules/@umijs/renderer-react';
5 | import type { UmiHistory } from './historyIntelli';
6 |
7 | let history: UmiHistory;
8 | let basename: string = '/';
9 | export function createHistory(opts: any) {
10 | let h;
11 | if (opts.type === 'hash') {
12 | h = createHashHistory();
13 | } else if (opts.type === 'memory') {
14 | h = createMemoryHistory(opts);
15 | } else {
16 | h = createBrowserHistory();
17 | }
18 | if (opts.basename) {
19 | basename = opts.basename;
20 | }
21 |
22 |
23 | history = {
24 | ...h,
25 | push(to, state) {
26 | h.push(patchTo(to, h), state);
27 | },
28 | replace(to, state) {
29 | h.replace(patchTo(to, h), state);
30 | },
31 | get location() {
32 | return h.location;
33 | },
34 | get action() {
35 | return h.action;
36 | }
37 | }
38 |
39 | return h;
40 | }
41 |
42 | // Patch `to` to support basename
43 | // Refs:
44 | // https://github.com/remix-run/history/blob/3e9dab4/packages/history/index.ts#L484
45 | // https://github.com/remix-run/history/blob/dev/docs/api-reference.md#to
46 | function patchTo(to: any, h: History) {
47 | if (typeof to === 'string') {
48 | return `${stripLastSlash(basename)}${to}`;
49 | } else if (typeof to === 'object') {
50 |
51 | const currentPathname = h.location.pathname;
52 |
53 | return {
54 | ...to,
55 | pathname: to.pathname? `${stripLastSlash(basename)}${to.pathname}` : currentPathname,
56 | };
57 | } else {
58 | throw new Error(`Unexpected to: ${to}`);
59 | }
60 | }
61 |
62 | function stripLastSlash(path) {
63 | return path.slice(-1) === '/' ? path.slice(0, -1) : path;
64 | }
65 |
66 | export { history };
67 |
--------------------------------------------------------------------------------
/src/layouts/menu.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { IconImage, IconIntro } from "@douyinfe/semi-icons-lab";
3 | import ImageCompressSvg from "@/assets/img/imageCompress.svg";
4 | import JsonSvg from "@/assets/img/json.svg";
5 | import JsonDiffSvg from "@/assets/img/jsonDiff.svg";
6 | import imgScanSvg from "@/assets/img/imgScan.svg";
7 | import imgFormatConversion from "@/assets/img/imgFormatConversion.svg";
8 | import timeSvg from "@/assets/img/time.svg";
9 | import colorSvg from "@/assets/img/color.svg";
10 | import imgEdit from "@/assets/img/imgEdit.svg";
11 |
12 | export const NAV_MAP = [
13 | {
14 | itemKey: "/home",
15 | text: "首页",
16 | icon: ,
17 | },
18 | {
19 | itemKey: "/jsonFormatting",
20 | text: "JSON格式化",
21 | icon:
,
22 | des:'将JSON格式化为可读性更好的格式'
23 | },
24 | {
25 | itemKey: "/jsonDiff",
26 | text: "JSON对比",
27 | icon:
,
28 | des:'比较两个JSON对象,并展示差异'
29 | },
30 | {
31 | itemKey: "/chrono",
32 | text: "计时器",
33 | icon:
,
34 | des:'用于监控事物的持续时间'
35 | },
36 | {
37 | itemKey: "/colorConversion",
38 | text: "颜色转换器",
39 | icon:
,
40 | des:'在不同格式之间转换颜色'
41 | },
42 | {
43 | itemKey: "/imageSlicing",
44 | text: "图片分割",
45 | icon: ,
46 | des:'将图片切割成多张小图片'
47 | },
48 | {
49 | itemKey: "/imageCompress",
50 | text: "图片压缩",
51 | icon:
,
52 | des:'压缩图片大小,支持单个和批量压缩'
53 | },
54 | {
55 | itemKey: "/imgScan",
56 | text: "图片识别文字",
57 | icon:
,
58 | des:'识别图片中的文字'
59 | },
60 | {
61 | itemKey: "/imgFormatConversion",
62 | text: "图片格式转换",
63 | icon:
,
64 | des:'图片格式互相转换'
65 | },
66 | {
67 | itemKey: "/imgEditor",
68 | text: "图片编辑器",
69 | icon:
,
70 | des:'图片编辑器,支持裁剪、旋转、滤镜等'
71 | },
72 | ];
--------------------------------------------------------------------------------
/src/assets/img/XTools.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/src/pages/chrono.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef, useCallback } from 'react';
2 | import { Card, Space } from 'antd';
3 | import { Button, Typography } from '@douyinfe/semi-ui';
4 | import { formatMs } from '@/utils/index';
5 |
6 | const {Title} = Typography;
7 |
8 | const ChronoComponent = () => {
9 | const [isRunning, setIsRunning] = useState(false);
10 | const [counter, setCounter] = useState(0);
11 | const previousRafDateRef = useRef(Date.now());
12 | const requestIdRef:any = useRef(null);
13 |
14 | const rafCallback = useCallback(() => {
15 | const deltaMs = Date.now() - previousRafDateRef.current;
16 | previousRafDateRef.current = Date.now();
17 | setCounter((prevCounter) => prevCounter + deltaMs);
18 | requestIdRef.current = requestAnimationFrame(rafCallback);
19 | }, []);
20 |
21 | useEffect(() => {
22 | if (isRunning) {
23 | previousRafDateRef.current = Date.now();
24 | requestIdRef.current = requestAnimationFrame(rafCallback);
25 | } else {
26 | cancelAnimationFrame(requestIdRef.current);
27 | }
28 | }, [isRunning, rafCallback]);
29 |
30 | const resume = () => {
31 | setIsRunning(true);
32 | };
33 |
34 | const pause = () => {
35 | setIsRunning(false);
36 | };
37 |
38 | const reset = () => {
39 | setCounter(0);
40 | setIsRunning(false);
41 | cancelAnimationFrame(requestIdRef.current);
42 | };
43 |
44 | return (
45 |
46 |
47 | {formatMs(counter)}
48 |
49 |
50 | {!isRunning ? (
51 |
52 | ) : (
53 |
54 | )}
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default ChronoComponent;
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy site to Pages
2 |
3 | on:
4 | push:
5 | branches: [master]
6 |
7 | workflow_dispatch:
8 |
9 | permissions:
10 | contents: read
11 | pages: write
12 | id-token: write
13 |
14 | concurrency:
15 | group: pages
16 | cancel-in-progress: false
17 |
18 | jobs:
19 | build:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v3
24 | with:
25 | fetch-depth: 0 # Not needed if lastUpdated is not enabled
26 | # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm
27 | - name: Setup Node
28 | uses: actions/setup-node@v3
29 | with:
30 | node-version: 18
31 | cache: yarn # or pnpm / yarn
32 | - name: Setup Pages
33 | uses: actions/configure-pages@v3
34 | - name: Install dependencies
35 | run: yarn install # or pnpm install / yarn install
36 | - name: Build
37 | run: yarn build # or pnpm docs:build / yarn docs:build
38 | - name: Upload artifact
39 | uses: actions/upload-pages-artifact@v2
40 | with:
41 | path: dist
42 |
43 | # Deployment job
44 | deploy:
45 | environment:
46 | name: github-pages
47 | url: ${{ steps.deployment.outputs.page_url }}
48 | needs: build
49 | runs-on: ubuntu-latest
50 | name: Deploy
51 | steps:
52 | - name: Deploy to GitHub Pages
53 | id: deployment
54 | uses: actions/deploy-pages@v2
55 | - name: Download artifact
56 | uses: actions/download-artifact@v3
57 | with:
58 | name: github-pages
59 | path: artifact
60 | - name: Extract artifact
61 | run: tar -xf artifact/*.tar --directory artifact
62 |
63 | - name: Deploy to Server
64 | uses: easingthemes/ssh-deploy@v2.1.5
65 | env:
66 | SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
67 | ARGS: '-rltgoDzvO --delete'
68 | SOURCE: artifact/*
69 | REMOTE_HOST: ${{ secrets.SERVER_HOST }}
70 | REMOTE_USER: ${{ secrets.SERVER_USERNAME }}
71 | TARGET: /usr/share/nginx/XTools/
--------------------------------------------------------------------------------
/src/assets/img/imageCompress.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/.umi/umi.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | import './core/polyfill';
5 |
6 | import { renderClient } from '/Users/xutaotao/Documents/s/XTools/node_modules/@umijs/renderer-react';
7 | import { getRoutes } from './core/route';
8 | import { createPluginManager } from './core/plugin';
9 | import { createHistory } from './core/history';
10 | import Loading from '/Users/xutaotao/Documents/s/XTools/src/loading.tsx';
11 | import { ApplyPluginsType } from 'umi';
12 |
13 |
14 | const publicPath = "/XTools/";
15 | const runtimePublicPath = false;
16 |
17 | async function render() {
18 | const pluginManager = createPluginManager();
19 | const { routes, routeComponents } = await getRoutes(pluginManager);
20 |
21 | // allow user to extend routes
22 | await pluginManager.applyPlugins({
23 | key: 'patchRoutes',
24 | type: ApplyPluginsType.event,
25 | args: {
26 | routes,
27 | routeComponents,
28 | },
29 | });
30 |
31 | const contextOpts = pluginManager.applyPlugins({
32 | key: 'modifyContextOpts',
33 | type: ApplyPluginsType.modify,
34 | initialValue: {},
35 | });
36 |
37 | const basename = contextOpts.basename || '/XTools/';
38 | const historyType = contextOpts.historyType || 'browser';
39 |
40 | const history = createHistory({
41 | type: historyType,
42 | basename,
43 | ...contextOpts.historyOpts,
44 | });
45 |
46 | return (pluginManager.applyPlugins({
47 | key: 'render',
48 | type: ApplyPluginsType.compose,
49 | initialValue() {
50 | const context = {
51 | routes,
52 | routeComponents,
53 | pluginManager,
54 | rootElement: contextOpts.rootElement || document.getElementById('root'),
55 | loadingComponent: Loading,
56 | publicPath,
57 | runtimePublicPath,
58 | history,
59 | historyType,
60 | basename,
61 | callback: contextOpts.callback,
62 | };
63 | const modifiedContext = pluginManager.applyPlugins({
64 | key: 'modifyClientRenderOpts',
65 | type: ApplyPluginsType.modify,
66 | initialValue: context,
67 | });
68 | return renderClient(modifiedContext);
69 | },
70 | }))();
71 | }
72 |
73 |
74 | render();
75 |
76 | window.g_umi = {
77 | version: '4.1.10',
78 | };
79 |
--------------------------------------------------------------------------------
/src/assets/loading.css:
--------------------------------------------------------------------------------
1 | .three-body {
2 | --uib-size: 35px;
3 | --uib-speed: 0.8s;
4 | --uib-color: #1677ff;
5 | position: relative;
6 | display: inline-block;
7 | height: var(--uib-size);
8 | width: var(--uib-size);
9 | animation: spin78236 calc(var(--uib-speed) * 2.5) infinite linear;
10 | }
11 |
12 | .three-body__dot {
13 | position: absolute;
14 | height: 100%;
15 | width: 30%;
16 | }
17 |
18 | .three-body__dot:after {
19 | content: '';
20 | position: absolute;
21 | height: 0%;
22 | width: 100%;
23 | padding-bottom: 100%;
24 | background-color: var(--uib-color);
25 | border-radius: 50%;
26 | }
27 |
28 | .three-body__dot:nth-child(1) {
29 | bottom: 5%;
30 | left: 0;
31 | transform: rotate(60deg);
32 | transform-origin: 50% 85%;
33 | }
34 |
35 | .three-body__dot:nth-child(1)::after {
36 | bottom: 0;
37 | left: 0;
38 | animation: wobble1 var(--uib-speed) infinite ease-in-out;
39 | animation-delay: calc(var(--uib-speed) * -0.3);
40 | }
41 |
42 | .three-body__dot:nth-child(2) {
43 | bottom: 5%;
44 | right: 0;
45 | transform: rotate(-60deg);
46 | transform-origin: 50% 85%;
47 | }
48 |
49 | .three-body__dot:nth-child(2)::after {
50 | bottom: 0;
51 | left: 0;
52 | animation: wobble1 var(--uib-speed) infinite
53 | calc(var(--uib-speed) * -0.15) ease-in-out;
54 | }
55 |
56 | .three-body__dot:nth-child(3) {
57 | bottom: -5%;
58 | left: 0;
59 | transform: translateX(116.666%);
60 | }
61 |
62 | .three-body__dot:nth-child(3)::after {
63 | top: 0;
64 | left: 0;
65 | animation: wobble2 var(--uib-speed) infinite ease-in-out;
66 | }
67 |
68 | @keyframes spin78236 {
69 | 0% {
70 | transform: rotate(0deg);
71 | }
72 |
73 | 100% {
74 | transform: rotate(360deg);
75 | }
76 | }
77 |
78 | @keyframes wobble1 {
79 | 0%,
80 | 100% {
81 | transform: translateY(0%) scale(1);
82 | opacity: 1;
83 | }
84 |
85 | 50% {
86 | transform: translateY(-66%) scale(0.65);
87 | opacity: 0.8;
88 | }
89 | }
90 |
91 | @keyframes wobble2 {
92 | 0%,
93 | 100% {
94 | transform: translateY(0%) scale(1);
95 | opacity: 1;
96 | }
97 |
98 | 50% {
99 | transform: translateY(66%) scale(0.65);
100 | opacity: 0.8;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/.umi/testBrowser.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | import React, { useEffect, useState } from 'react';
5 | import { ApplyPluginsType } from 'umi';
6 | import { renderClient, RenderClientOpts } from '/Users/xutaotao/Documents/s/XTools/node_modules/@umijs/renderer-react';
7 | import { createHistory } from './core/history';
8 | import { createPluginManager } from './core/plugin';
9 | import { getRoutes } from './core/route';
10 | import type { Location } from 'history';
11 |
12 |
13 | const publicPath = '/';
14 | const runtimePublicPath = false;
15 |
16 | type TestBrowserProps = {
17 | location?: Partial;
18 | historyRef?: React.MutableRefObject;
19 | };
20 |
21 | export function TestBrowser(props: TestBrowserProps) {
22 | const pluginManager = createPluginManager();
23 | const [context, setContext] = useState(
24 | undefined
25 | );
26 | useEffect(() => {
27 | const genContext = async () => {
28 | const { routes, routeComponents } = await getRoutes(pluginManager);
29 | // allow user to extend routes
30 | await pluginManager.applyPlugins({
31 | key: 'patchRoutes',
32 | type: ApplyPluginsType.event,
33 | args: {
34 | routes,
35 | routeComponents,
36 | },
37 | });
38 | const contextOpts = pluginManager.applyPlugins({
39 | key: 'modifyContextOpts',
40 | type: ApplyPluginsType.modify,
41 | initialValue: {},
42 | });
43 | const basename = contextOpts.basename || '/';
44 | const history = createHistory({
45 | type: 'memory',
46 | basename,
47 | });
48 | const context = {
49 | routes,
50 | routeComponents,
51 | pluginManager,
52 | rootElement: contextOpts.rootElement || document.getElementById('root'),
53 | loadingComponent: Loading,
54 | publicPath,
55 | runtimePublicPath,
56 | history,
57 | basename,
58 | components: true,
59 | };
60 | const modifiedContext = pluginManager.applyPlugins({
61 | key: 'modifyClientRenderOpts',
62 | type: ApplyPluginsType.modify,
63 | initialValue: context,
64 | });
65 | return modifiedContext;
66 | };
67 | genContext().then((context) => {
68 | setContext(context);
69 | if (props.location) {
70 | context?.history?.push(props.location);
71 | }
72 | if (props.historyRef) {
73 | props.historyRef.current = context?.history;
74 | }
75 | });
76 | }, []);
77 |
78 | if (context === undefined) {
79 | return ;
80 | }
81 |
82 | const Children = renderClient(context);
83 | return (
84 |
85 |
86 |
87 | );
88 | }
89 |
--------------------------------------------------------------------------------
/src/.umi/typings.d.ts:
--------------------------------------------------------------------------------
1 | // This file is generated by Umi automatically
2 | // DO NOT CHANGE IT MANUALLY!
3 | type CSSModuleClasses = { readonly [key: string]: string }
4 | declare module '*.css' {
5 | const classes: CSSModuleClasses
6 | export default classes
7 | }
8 | declare module '*.scss' {
9 | const classes: CSSModuleClasses
10 | export default classes
11 | }
12 | declare module '*.sass' {
13 | const classes: CSSModuleClasses
14 | export default classes
15 | }
16 | declare module '*.less' {
17 | const classes: CSSModuleClasses
18 | export default classes
19 | }
20 | declare module '*.styl' {
21 | const classes: CSSModuleClasses
22 | export default classes
23 | }
24 | declare module '*.stylus' {
25 | const classes: CSSModuleClasses
26 | export default classes
27 | }
28 |
29 | // images
30 | declare module '*.jpg' {
31 | const src: string
32 | export default src
33 | }
34 | declare module '*.jpeg' {
35 | const src: string
36 | export default src
37 | }
38 | declare module '*.png' {
39 | const src: string
40 | export default src
41 | }
42 | declare module '*.gif' {
43 | const src: string
44 | export default src
45 | }
46 | declare module '*.svg' {
47 | import * as React from 'react';
48 | export const ReactComponent: React.FunctionComponent & { title?: string }>;
51 |
52 | const src: string
53 | export default src
54 | }
55 | declare module '*.ico' {
56 | const src: string
57 | export default src
58 | }
59 | declare module '*.webp' {
60 | const src: string
61 | export default src
62 | }
63 | declare module '*.avif' {
64 | const src: string
65 | export default src
66 | }
67 |
68 | // media
69 | declare module '*.mp4' {
70 | const src: string
71 | export default src
72 | }
73 | declare module '*.webm' {
74 | const src: string
75 | export default src
76 | }
77 | declare module '*.ogg' {
78 | const src: string
79 | export default src
80 | }
81 | declare module '*.mp3' {
82 | const src: string
83 | export default src
84 | }
85 | declare module '*.wav' {
86 | const src: string
87 | export default src
88 | }
89 | declare module '*.flac' {
90 | const src: string
91 | export default src
92 | }
93 | declare module '*.aac' {
94 | const src: string
95 | export default src
96 | }
97 |
98 | // fonts
99 | declare module '*.woff' {
100 | const src: string
101 | export default src
102 | }
103 | declare module '*.woff2' {
104 | const src: string
105 | export default src
106 | }
107 | declare module '*.eot' {
108 | const src: string
109 | export default src
110 | }
111 | declare module '*.ttf' {
112 | const src: string
113 | export default src
114 | }
115 | declare module '*.otf' {
116 | const src: string
117 | export default src
118 | }
119 |
120 | // other
121 | declare module '*.wasm' {
122 | const initWasm: (options: WebAssembly.Imports) => Promise
123 | export default initWasm
124 | }
125 | declare module '*.webmanifest' {
126 | const src: string
127 | export default src
128 | }
129 | declare module '*.pdf' {
130 | const src: string
131 | export default src
132 | }
133 | declare module '*.txt' {
134 | const src: string
135 | export default src
136 | }
137 |
--------------------------------------------------------------------------------
/src/pages/imgScan.tsx:
--------------------------------------------------------------------------------
1 | import { InboxOutlined } from "@ant-design/icons";
2 | import { Card, Col, Image, Row, Spin, Upload, UploadFile } from "antd";
3 | import { Button } from "@douyinfe/semi-ui";
4 | import { useState } from "react";
5 | import { createWorker } from "tesseract.js";
6 | import { UploadChangeParam } from "antd/es/upload";
7 | import { BODY_HEIGHT } from "@/utils/const";
8 |
9 | const { Dragger } = Upload;
10 |
11 | interface FileWithPreview extends UploadFile {
12 | preview?: string;
13 | }
14 |
15 | export default function ImgScan() {
16 | const [fileList, setFileList] = useState([]);
17 | const [currentImg, setCurrentImg] = useState("");
18 | const [extractedText, setExtractedText] = useState("");
19 | const [spinning, setSpinning] = useState(false);
20 |
21 | const filesChange = (info: UploadChangeParam) => {
22 | const newFile: FileWithPreview = {
23 | ...info.file,
24 | preview: "",
25 | };
26 | setFileList([newFile]);
27 | let reader = new FileReader();
28 | setSpinning(true);
29 | if (info.file.originFileObj) {
30 | reader.readAsDataURL(info.file.originFileObj);
31 | }
32 | reader.onload = async function (e: any) {
33 | setCurrentImg(e.target.result);
34 | const worker: any = await createWorker("chi_sim+eng");
35 | worker
36 | .recognize(e.target.result)
37 | .then((result: any) => {
38 | const extractedText = result.data.text;
39 | setExtractedText(extractedText);
40 | })
41 | .catch((error: Error) => {
42 | console.error("Error:", error);
43 | })
44 | .finally(() => {
45 | if (worker) worker.terminate();
46 | setSpinning(false);
47 | });
48 | };
49 | };
50 |
51 | return (
52 |
53 |
54 |
55 | {
65 | setFileList([]);
66 | setCurrentImg("");
67 | setExtractedText("");
68 | }}
69 | >
70 | 重新上传图片
71 |
72 | ) : null
73 | }
74 | >
75 | {currentImg ? (
76 |
77 | ) : (
78 | {}}
83 | showUploadList={false}
84 | >
85 |
86 |
87 |
88 | 点击或拖拽上传图片
89 |
90 | )}
91 |
92 |
93 |
94 |
100 |
101 | {extractedText}
102 |
103 |
104 |
105 |
106 |
107 | );
108 | }
--------------------------------------------------------------------------------
/src/assets/img/color.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/colorConversion.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Card, ColorPicker, Form, Input } from "antd";
2 | import { useState, useEffect, useMemo } from "react";
3 | import { colord,extend } from "colord";
4 | import type { Colord } from 'colord';
5 | import namesPlugin from "colord/plugins/names";
6 | import hwbPlugin from "colord/plugins/hwb";
7 | import lchPlugin from "colord/plugins/lch";
8 | import cmykPlugin from "colord/plugins/cmyk";
9 | import type { ColorPickerProps, GetProp } from 'antd';
10 |
11 | type Color = GetProp;
12 |
13 | extend([namesPlugin,hwbPlugin,lchPlugin,cmykPlugin]);
14 |
15 |
16 | export default function ColorConversion() {
17 | const [form] = Form.useForm();
18 | const [color, setColor] = useState("#1677ff");
19 |
20 | const parseColor = (color: any) => {
21 | const colorVal: Colord = colord(color);
22 | const colorValues = {
23 | colorName: colorVal.toName({ closest: true }),
24 | colorHex: colorVal.toHex(),
25 | colorRgb: colorVal.toRgbString(),
26 | colorHsl: colorVal.toHslString(),
27 | colorHwb: colorVal.toHwbString(),
28 | colorLch: colorVal.toLchString(),
29 | colorCmyk: colorVal.toCmykString()
30 | };
31 | return colorValues;
32 | }
33 |
34 | const handleColorChange = (newColor: any) => {
35 | setColor(newColor)
36 | const colorVal:Colord = colord(newColor.toHexString());
37 | form.setFieldsValue({
38 | ...parseColor(colorVal)
39 | })
40 | };
41 |
42 | const handleInputChange = (e: any) => {
43 | const { value } = e.target;
44 | try {
45 | const colorVal:Colord = colord(value)
46 | if (colorVal.isValid()){
47 | setColor(colorVal.toHex());
48 | form.setFieldsValue({
49 | ...parseColor(colorVal)
50 | })
51 | }
52 | } catch {
53 |
54 | }
55 |
56 | };
57 |
58 | const bgColor = useMemo(
59 | () => (typeof color === 'string' ? color : color!.toHexString()),
60 | [color],
61 | );
62 |
63 | const btnStyle: React.CSSProperties = {
64 | backgroundColor: bgColor,
65 | width: '100%'
66 | };
67 |
68 | useEffect(() => {
69 | form.setFieldsValue({
70 | ...parseColor(color)
71 | })
72 | },[])
73 |
74 | return (
75 |
76 |
77 |
85 |
86 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | );
115 | }
--------------------------------------------------------------------------------
/src/.umi/core/historyIntelli.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | import { getRoutes } from './route'
5 | import type { History } from '/Users/xutaotao/Documents/s/XTools/node_modules/@umijs/renderer-react'
6 |
7 | type Routes = Awaited>['routes']
8 | type AllRoute = Routes[keyof Routes]
9 | type IsRoot = 'parentId' extends keyof T ? false : true
10 |
11 | // show `/` in not `layout / wrapper` only
12 | type GetAllRouteWithoutLayout- = Item extends any
13 | ? 'isWrapper' extends keyof Item
14 | ? never
15 | : 'isLayout' extends keyof Item
16 | ? never
17 | : Item
18 | : never
19 | type AllRouteWithoutLayout = GetAllRouteWithoutLayout
20 | type IndexRoutePathname = '/' extends AllRouteWithoutLayout['path']
21 | ? '/'
22 | : never
23 |
24 | type GetChildrens = T extends any
25 | ? IsRoot extends true
26 | ? never
27 | : T
28 | : never
29 | type Childrens = GetChildrens
30 | type Root = Exclude
31 | type AllIds = AllRoute['id']
32 |
33 | type GetChildrensByParentId<
34 | Id extends AllIds,
35 | Item = AllRoute
36 | > = Item extends any
37 | ? 'parentId' extends keyof Item
38 | ? Item['parentId'] extends Id
39 | ? Item
40 | : never
41 | : never
42 | : never
43 |
44 | type RouteObject<
45 | Id extends AllIds,
46 | Item = GetChildrensByParentId
47 | > = IsNever
- extends true
48 | ? ''
49 | : Item extends AllRoute
50 | ? {
51 | [Key in Item['path'] as TrimSlash]: UnionMerge<
52 | RouteObject
-
53 | >
54 | }
55 | : never
56 |
57 | type GetRootRouteObject
- = Item extends Root
58 | ? {
59 | [K in Item['path'] as TrimSlash]: UnionMerge>
60 | }
61 | : never
62 | type MergedResult = UnionMerge>
63 |
64 | // --- patch history types ---
65 |
66 | type HistoryTo = Parameters['0']
67 | type HistoryPath = Exclude
68 |
69 | type UmiPathname = Path | (string & {})
70 | interface UmiPath extends HistoryPath {
71 | pathname: UmiPathname
72 | }
73 | type UmiTo = UmiPathname | UmiPath
74 |
75 | type UmiPush = (to: UmiTo, state?: any) => void
76 | type UmiReplace = (to: UmiTo, state?: any) => void
77 |
78 |
79 | export interface UmiHistory extends History {
80 | push: UmiPush
81 | replace: UmiReplace
82 | }
83 |
84 | // --- type utils ---
85 | type TrimLeftSlash = T extends `/${infer R}`
86 | ? TrimLeftSlash
87 | : T
88 | type TrimRightSlash = T extends `${infer R}/`
89 | ? TrimRightSlash
90 | : T
91 | type TrimSlash = TrimLeftSlash>
92 |
93 | type IsNever = [T] extends [never] ? true : false
94 | type IsEqual = (() => G extends A ? 1 : 2) extends () => G extends B
95 | ? 1
96 | : 2
97 | ? true
98 | : false
99 |
100 | type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (
101 | k: infer I
102 | ) => void
103 | ? I
104 | : never
105 | type UnionMerge = UnionToIntersection extends infer O
106 | ? { [K in keyof O]: O[K] }
107 | : never
108 |
109 | type ExcludeEmptyKey = IsEqual extends true ? never : T
110 |
111 | type PathConcat<
112 | TKey extends string,
113 | TValue,
114 | N = TrimSlash
115 | > = TValue extends string
116 | ? ExcludeEmptyKey
117 | :
118 | | ExcludeEmptyKey
119 | | `${N & string}${IsNever> extends true
120 | ? ''
121 | : '/'}${UnionPath}`
122 |
123 | type UnionPath = {
124 | [K in keyof T]-?: PathConcat
125 | }[keyof T]
126 |
127 | type MakeSureLeftSlash = T extends any
128 | ? `/${TrimRightSlash}`
129 | : never
130 |
131 | // exclude `/*`, because it always at the top of the IDE tip list
132 | type Path> = Exclude, '/*'> | IndexRoutePathname
133 |
--------------------------------------------------------------------------------
/src/.umi/core/route.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // This file is generated by Umi automatically
3 | // DO NOT CHANGE IT MANUALLY!
4 | import routeProps from './routeProps';
5 |
6 | if (process.env.NODE_ENV === 'development') {
7 | Object.entries(routeProps).forEach(([key, value]) => {
8 | const internalProps = ['path', 'id', 'parentId', 'isLayout', 'isWrapper', 'layout', 'clientLoader'];
9 | Object.keys(value).forEach((prop) => {
10 | if (internalProps.includes(prop)) {
11 | throw new Error(
12 | `[UmiJS] route '${key}' should not have '${prop}' prop, please remove this property in 'routeProps'.`
13 | )
14 | }
15 | })
16 | })
17 | }
18 |
19 | import React from 'react';
20 |
21 | export async function getRoutes() {
22 | 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"},"colorConversion":{"path":"colorConversion","id":"colorConversion","parentId":"@@/global-layout"},"imgEditor/index":{"path":"imgEditor","id":"imgEditor/index","parentId":"@@/global-layout"},"imgFilter/index":{"path":"imgFilter","id":"imgFilter/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"},"chrono":{"path":"chrono","id":"chrono","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;
23 | return {
24 | routes,
25 | routeComponents: {
26 | 'imageCompress/singleImageCompress': React.lazy(() => import(/* webpackChunkName: "src__pages__imageCompress__singleImageCompress" */'../../../src/pages/imageCompress/singleImageCompress.tsx')),
27 | 'imageCompress/batchImageCompress': React.lazy(() => import(/* webpackChunkName: "src__pages__imageCompress__batchImageCompress" */'../../../src/pages/imageCompress/batchImageCompress.tsx')),
28 | 'imageCompress/index': React.lazy(() => import(/* webpackChunkName: "src__pages__imageCompress__index" */'../../../src/pages/imageCompress/index.tsx')),
29 | 'imgFormatConversion': React.lazy(() => import(/* webpackChunkName: "src__pages__imgFormatConversion" */'../../../src/pages/imgFormatConversion.tsx')),
30 | 'colorConversion': React.lazy(() => import(/* webpackChunkName: "src__pages__colorConversion" */'../../../src/pages/colorConversion.tsx')),
31 | 'imgEditor/index': React.lazy(() => import(/* webpackChunkName: "src__pages__imgEditor__index" */'../../../src/pages/imgEditor/index.tsx')),
32 | 'imgFilter/index': React.lazy(() => import(/* webpackChunkName: "src__pages__imgFilter__index" */'../../../src/pages/imgFilter/index.tsx')),
33 | 'jsonFormatting': React.lazy(() => import(/* webpackChunkName: "src__pages__jsonFormatting" */'../../../src/pages/jsonFormatting.tsx')),
34 | 'imageSlicing': React.lazy(() => import(/* webpackChunkName: "src__pages__imageSlicing" */'../../../src/pages/imageSlicing.tsx')),
35 | 'jsonDiff': React.lazy(() => import(/* webpackChunkName: "src__pages__jsonDiff" */'../../../src/pages/jsonDiff.tsx')),
36 | 'imgScan': React.lazy(() => import(/* webpackChunkName: "src__pages__imgScan" */'../../../src/pages/imgScan.tsx')),
37 | 'chrono': React.lazy(() => import(/* webpackChunkName: "src__pages__chrono" */'../../../src/pages/chrono.tsx')),
38 | 'index': React.lazy(() => import(/* webpackChunkName: "src__pages__index" */'../../../src/pages/index.tsx')),
39 | 'home': React.lazy(() => import(/* webpackChunkName: "src__pages__home" */'../../../src/pages/home.tsx')),
40 | '@@/global-layout': React.lazy(() => import(/* webpackChunkName: "layouts__index" */'/Users/xutaotao/Documents/s/XTools/src/layouts/index.tsx')),
41 | },
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/src/pages/jsonDiff.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@douyinfe/semi-ui";
2 | import { Col, Input, Row, Space, message } from "antd";
3 | import { Differ, Viewer } from "json-diff-kit";
4 | import { useEffect, useState } from "react";
5 | import "json-diff-kit/dist/viewer.css";
6 |
7 |
8 | const { TextArea } = Input;
9 |
10 | export default function JsonDiff() {
11 | const [data,setData] = useState('');
12 | const [newData,setNewData] = useState('');
13 | const [jsonData, setJsonData] = useState({});
14 | const [newJsonData, setNewJsonData] = useState({});
15 | const [diff, setDiff] = useState([[], []]);
16 |
17 | const viewerProps = {
18 | detectCircular: true, // default `true`
19 | maxDepth: Infinity, // default `Infinity`
20 | showModifications: true, // default `true`
21 | arrayDiffMethod: "lcs",
22 | indent:4,
23 | lineNumbers: true,
24 | highlightInlineDiff: true,
25 | };
26 |
27 | useEffect(() => {
28 | const d = new Differ({
29 | detectCircular: true,
30 | maxDepth: 8,
31 | showModifications: true,
32 | arrayDiffMethod: "lcs",
33 | ignoreCase: false,
34 | ignoreCaseForKey: false,
35 | recursiveEqual: true,
36 | });
37 | const diff = d.diff(jsonData, newJsonData);
38 | setDiff(diff);
39 | }, [jsonData, newJsonData]);
40 |
41 | const handleJsonData = (e: any, type = "old") => {
42 | if (type === "old") {
43 | setData(e.target.value);
44 | }
45 | if (type === "new") {
46 | setNewData(e.target.value);
47 | }
48 | try {
49 | if (type === "old") {
50 | setJsonData(JSON.parse(e.target.value));
51 | }
52 | if (type === "new") {
53 | setNewJsonData(JSON.parse(e.target.value));
54 | }
55 | } catch {
56 | if (e.target.value) {
57 | message.error("请检查输入的JSON格式是否正确");
58 | }
59 | }
60 | };
61 | return (
62 |
114 |
115 | );
116 | }
117 |
--------------------------------------------------------------------------------
/src/assets/img/json.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/layouts/index.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet, history, useLocation } from "umi";
2 | import {
3 | IconCustomerSupport,
4 | IconGithubLogo,
5 | IconLikeHeart,
6 | } from "@douyinfe/semi-icons";
7 | import { Button, Layout, Nav, Popover,Image,Typography } from "@douyinfe/semi-ui";
8 | import Logo from "@/assets/img/XTools.svg";
9 | import "@/assets/normalize.css";
10 | import { NAV_MAP } from "./menu";
11 | const { Header, Sider, Content } = Layout;
12 |
13 | const { Text,Title } = Typography;
14 |
15 |
16 | const XLayout = () => {
17 | const location = useLocation();
18 | const handleImageError = (
19 | e: React.SyntheticEvent
20 | ) => {
21 | e.currentTarget.style.display = "none";
22 | };
23 | return (
24 |
25 |
26 |
27 |
}>
53 | }
56 | style={{
57 | color: "var(--semi-color-text-2)",
58 | marginRight: "12px",
59 | }}
60 | />
61 |
62 |
63 | }
66 | style={{
67 | color: "var(--semi-color-text-2)",
68 | marginRight: "12px",
69 | }}
70 | onClick={() => {window.open("https://github.com/xutaotaotao/XTools/issues")}}
71 | />
72 | }
75 | style={{
76 | color: "var(--semi-color-text-2)",
77 | marginRight: "12px",
78 | }}
79 | onClick={() => {window.open("https://github.com/xutaotaotao/XTools")}}
80 | />
81 |
82 |
83 |
84 |
85 | {location.pathname === "/" ? (
86 |