├── .eslintrc.json ├── .github └── workflows │ └── nextjs.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.js ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── commitlint.config.js ├── declare.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── public ├── Mono.ttf ├── cnzz.js ├── favicon.ico ├── pkg │ ├── cidr_aggregator.js │ └── cidr_aggregator_bg.wasm └── wasm │ ├── crackzip.wasm │ ├── fastcoll.wasm │ ├── file.wasm │ ├── pycdas.wasm │ ├── pycdc.wasm │ └── sqlite.wasm ├── src ├── asset │ ├── img │ │ ├── copy_icon.webp │ │ ├── default_avatar.png │ │ ├── edit_icon.webp │ │ ├── emblem.webp │ │ ├── logo.svg │ │ └── pixel-img.jpeg │ ├── json2go │ │ └── index.js │ ├── tag │ │ ├── dev.svg │ │ ├── dev_check.svg │ │ ├── encode.svg │ │ ├── encode_check.svg │ │ ├── encryption.svg │ │ ├── encryption_check.svg │ │ ├── hot.svg │ │ ├── hot_check.svg │ │ ├── hover_red_like.svg │ │ ├── image.svg │ │ ├── image_check.svg │ │ ├── json.svg │ │ ├── json_check.svg │ │ ├── like.svg │ │ ├── like_check.svg │ │ ├── no_like.png │ │ ├── other.svg │ │ ├── other_check.svg │ │ ├── red_like.png │ │ ├── red_like.svg │ │ ├── red_like_check.png │ │ ├── text.svg │ │ └── text_check.svg │ ├── tools │ │ ├── cloud_bg.png │ │ ├── dot_black.png │ │ ├── dot_blue.png │ │ ├── dot_yellow.png │ │ └── sunglasses.png │ └── wasm │ │ ├── crackzip.js │ │ ├── fastcoll.js │ │ ├── file.js │ │ ├── pycdas.js │ │ └── pycdc.js ├── components │ ├── Alert │ │ └── index.tsx │ ├── Button │ │ └── index.tsx │ ├── CIDR │ │ ├── InputEditor.tsx │ │ ├── OptionsControl.tsx │ │ ├── OutputEditor.tsx │ │ ├── OutputStatusLine.tsx │ │ └── WarningFab.tsx │ ├── CusTabs │ │ └── index.tsx │ ├── Dynamic │ │ ├── AudioFmt.tsx │ │ ├── Frame.tsx │ │ ├── Video2Gif.tsx │ │ └── VideoFmt.tsx │ ├── FormItem │ │ └── index.tsx │ ├── Formater │ │ └── index.tsx │ ├── Header │ │ ├── components.tsx │ │ ├── index.tsx │ │ ├── loggedInView.tsx │ │ ├── profilePanel.tsx │ │ └── toolkit.ts │ ├── ImageDownload │ │ └── index.tsx │ ├── LikeIcon │ │ └── index.tsx │ ├── MainContent │ │ └── index.tsx │ ├── Text │ │ └── index.tsx │ ├── TextFieldWithClean │ │ └── index.tsx │ ├── TextFieldWithCopy │ │ └── index.tsx │ ├── ToolCard │ │ └── index.tsx │ ├── Tools │ │ └── index.tsx │ └── Watermark │ │ ├── context.ts │ │ ├── index.tsx │ │ ├── useClips.ts │ │ ├── useWatermark.tsx │ │ └── utils.ts ├── constant │ ├── color.ts │ ├── index.tsx │ └── style.ts ├── hooks │ ├── index.ts │ ├── useAnchor.tsx │ ├── useCSV.ts │ ├── useDebounce.ts │ ├── useLikeList.tsx │ ├── useMobileView.ts │ ├── useNavigationWithPramas.ts │ ├── usePath.ts │ └── useWasm.ts ├── icon │ ├── ArrowDown.tsx │ ├── Check.tsx │ ├── CheckCircleFilled.tsx │ ├── CheckCircleOutlined.tsx │ ├── Copy.tsx │ ├── DashedArrow.tsx │ ├── Delete.tsx │ ├── Exclamation.tsx │ ├── Home.tsx │ ├── ImageIcon.tsx │ ├── Logo.tsx │ ├── LogoWhite.tsx │ ├── RightLined.tsx │ ├── Sort.tsx │ ├── WeChat.tsx │ ├── index.tsx │ └── menu │ │ └── index.tsx ├── layouts │ ├── Header │ │ └── index.tsx │ └── SideBar │ │ └── index.tsx ├── pages │ ├── 3des.tsx │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── aes.tsx │ ├── ascii.tsx │ ├── audiofmt.tsx │ ├── base64.tsx │ ├── camelcase.tsx │ ├── case_convert.tsx │ ├── chart_line.tsx │ ├── chart_nightingale.tsx │ ├── chart_radar.tsx │ ├── cidr.tsx │ ├── cidr_aggregator.tsx │ ├── cn_space_en.tsx │ ├── color_convert.tsx │ ├── color_picker.tsx │ ├── countdown.tsx │ ├── css_minifier.tsx │ ├── cssfmt.tsx │ ├── cvencode.tsx │ ├── de_qrcode.tsx │ ├── des.tsx │ ├── diff.tsx │ ├── dir_tree.tsx │ ├── docker_run_to_docker_compose.tsx │ ├── eatwhat.tsx │ ├── excalidraw.tsx │ ├── figlet.tsx │ ├── file.tsx │ ├── generate_qrcode.tsx │ ├── getvideoaudio.tsx │ ├── git.tsx │ ├── hash.tsx │ ├── hex.tsx │ ├── hexeditor.tsx │ ├── home.tsx │ ├── htmlentity.tsx │ ├── htmlfmt.tsx │ ├── htpasswd.tsx │ ├── img2base64.tsx │ ├── img_conversion.tsx │ ├── img_radius.tsx │ ├── img_sharp.tsx │ ├── img_split.tsx │ ├── jsfmt.tsx │ ├── jsfuck.tsx │ ├── json2go.tsx │ ├── json2js.tsx │ ├── json2xml.tsx │ ├── json2yaml.tsx │ ├── jsonfmt.tsx │ ├── jsontocsv.tsx │ ├── leet_convert.tsx │ ├── less2css.tsx │ ├── lifecount.tsx │ ├── line_number.tsx │ ├── md2html.tsx │ ├── md5fastcollision.tsx │ ├── morse.tsx │ ├── ocr.tsx │ ├── pixel_img.tsx │ ├── pyc2asm.tsx │ ├── pyc2py.tsx │ ├── radix_convert.tsx │ ├── random.tsx │ ├── random_email.tsx │ ├── random_ip.tsx │ ├── random_ua.tsx │ ├── rsa.tsx │ ├── shuffle_text_generator.tsx │ ├── sqlfmt.tsx │ ├── sqlite.tsx │ ├── tsfmt.tsx │ ├── uncolor.tsx │ ├── unix.tsx │ ├── urlencoder.tsx │ ├── uuid_gen.tsx │ ├── video2gif.tsx │ ├── videofmt.tsx │ ├── videoframe.tsx │ ├── watermark.tsx │ ├── word_count.tsx │ ├── xmlfmt.tsx │ ├── xssvector.tsx │ ├── yamlfmt.tsx │ └── zipcrack.tsx ├── styles │ ├── colors.ts │ ├── global.css │ ├── static │ │ ├── effect-creative.min.css │ │ ├── navigation.min.css │ │ ├── slick-theme.css │ │ ├── slick.css │ │ ├── swiper-customize.css │ │ └── swiper.css │ └── theme.ts ├── types │ ├── app.ts │ ├── auth.ts │ ├── data.ts │ ├── declare.d.ts │ ├── general.ts │ ├── index.ts │ ├── navbar.ts │ ├── notifications.ts │ ├── orders.ts │ ├── org.ts │ ├── ticket.ts │ └── user.ts └── utils │ ├── bufferToWave.ts │ ├── color.ts │ ├── core-values-encoder.ts │ ├── download.ts │ ├── emotionCache.ts │ ├── enterApp.ts │ ├── getDefaultOrg.ts │ ├── index.ts │ ├── meta.ts │ ├── tags.ts │ ├── tools.ts │ └── url.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"], 3 | "plugins": ["@typescript-eslint", "unused-imports"], 4 | "rules": { 5 | "unused-imports/no-unused-imports": "error" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/nextjs.yml: -------------------------------------------------------------------------------- 1 | name: Build XTools 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | tags: 7 | - 'v*.*.*' 8 | pull_request: 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | # Build job 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Detect package manager 23 | id: detect-package-manager 24 | run: | 25 | echo "manager=npm" >> $GITHUB_OUTPUT 26 | echo "command=install" >> $GITHUB_OUTPUT 27 | echo "runner=npx --no-install" >> $GITHUB_OUTPUT 28 | exit 0 29 | - name: Setup Node 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: '20' 33 | cache: ${{ steps.detect-package-manager.outputs.manager }} 34 | - name: Restore cache 35 | uses: actions/cache@v3 36 | with: 37 | path: | 38 | .next/cache 39 | # Generate a new cache whenever packages or source files change. 40 | key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} 41 | # If source files changed but packages didn't, rebuild from a prior cache. 42 | restore-keys: | 43 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- 44 | - name: Install dependencies 45 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} 46 | - name: Build with Next.js 47 | run: ${{ steps.detect-package-manager.outputs.runner }} npm run build 48 | - name: 'Tar files' 49 | run: tar -cvzf out.tgz ./out 50 | - name: Upload artifact 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: xtools 54 | path: out.tgz 55 | - name: Release 56 | uses: softprops/action-gh-release@v1 57 | if: startsWith(github.ref, 'refs/tags/') 58 | with: 59 | files: out.tgz 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .idea/ 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | next-env.d.ts 36 | *.tsbuildinfo 37 | .yalc 38 | yalc 39 | yalc.lock -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{ts,tsx}': ['npm run lint --fix', 'git add'], 3 | '*.{html,json,yaml,yml}': ['prettier --write', 'git add'], 4 | }; 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | registry=https://registry.npmmirror.com 3 | 4 | sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli 5 | ENTRYCLI_CDNURL=https://cdn.npm.taobao.org/dist/sentry-cli 6 | 7 | strict-ssl=false 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | 3 | **/src/asset/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": true, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "jsxSingleQuote": true 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Chaitin Tech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /declare.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'css2less'; 2 | declare module 'clean-css' { 3 | export type Options = any; 4 | export default any; 5 | } 6 | declare module 'composerize' { 7 | export default any; 8 | } 9 | declare module 'figlet/importable-fonts/Standard.js'; 10 | declare module 'figlet/importable-fonts/1Row.js'; 11 | declare module 'figlet/importable-fonts/3D-ASCII'; 12 | declare module 'figlet/importable-fonts/ANSI Regular'; 13 | declare module 'figlet/importable-fonts/Alligator'; 14 | declare module 'figlet/importable-fonts/Whimsy'; 15 | declare module 'figlet/importable-fonts/Wow'; 16 | declare module 'figlet/importable-fonts/Weird'; 17 | declare module 'figlet/importable-fonts/Wavy'; 18 | declare module 'figlet/importable-fonts/3D Diagonal'; 19 | declare module 'figlet/importable-fonts/Banner'; 20 | declare module 'figlet/importable-fonts/Block'; 21 | declare module 'figlet/importable-fonts/Bear'; 22 | declare module 'figlet/importable-fonts/Big Money-ne'; 23 | declare module 'figlet/importable-fonts/Delta Corps Priest 1'; 24 | declare module 'figlet/importable-fonts/Doh'; 25 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const withPlugins = require('next-compose-plugins'); 3 | const withTM = require('next-transpile-modules')([ 4 | 'react-syntax-highlighter', 5 | 'shrinkpng', 6 | 'figlet', 7 | ]); 8 | 9 | const nextConfig = withPlugins([withTM], { 10 | reactStrictMode: false, 11 | output: 'export', 12 | images: { 13 | unoptimized: true, 14 | }, 15 | basePath: process.env.NODE_ENV === 'production' ? '/tools' : '', 16 | redirects: process.env.NODE_ENV === 'development' ? async () => { 17 | return [ 18 | { 19 | source: '/', 20 | destination: '/home', 21 | permanent: true, 22 | }, 23 | ]; 24 | } : undefined, 25 | webpack: (config) => { 26 | config.resolve.fallback = { fs: false }; 27 | config.experiments = { 28 | asyncWebAssembly: true, 29 | }; 30 | return config; 31 | }, 32 | }); 33 | 34 | module.exports = nextConfig; 35 | -------------------------------------------------------------------------------- /public/Mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/Mono.ttf -------------------------------------------------------------------------------- /public/cnzz.js: -------------------------------------------------------------------------------- 1 | if ( 2 | [ 3 | 'rivers.chaitin.cn', 4 | 'dev.rivers.ctopt.cn', 5 | // '127.0.0.1', 6 | // 'localhost', 7 | ].includes(document.domain) 8 | ) { 9 | window.is_river = true; 10 | var _czc = _czc || []; 11 | var cnzzTag = document.createElement('script'); 12 | cnzzTag.src = 'https://s4.cnzz.com/z.js?id=1281132544&async=1'; 13 | document.head.append(cnzzTag); 14 | } 15 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/favicon.ico -------------------------------------------------------------------------------- /public/pkg/cidr_aggregator_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/pkg/cidr_aggregator_bg.wasm -------------------------------------------------------------------------------- /public/wasm/crackzip.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/wasm/crackzip.wasm -------------------------------------------------------------------------------- /public/wasm/fastcoll.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/wasm/fastcoll.wasm -------------------------------------------------------------------------------- /public/wasm/file.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/wasm/file.wasm -------------------------------------------------------------------------------- /public/wasm/pycdas.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/wasm/pycdas.wasm -------------------------------------------------------------------------------- /public/wasm/pycdc.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/wasm/pycdc.wasm -------------------------------------------------------------------------------- /public/wasm/sqlite.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/public/wasm/sqlite.wasm -------------------------------------------------------------------------------- /src/asset/img/copy_icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/img/copy_icon.webp -------------------------------------------------------------------------------- /src/asset/img/default_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/img/default_avatar.png -------------------------------------------------------------------------------- /src/asset/img/edit_icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/img/edit_icon.webp -------------------------------------------------------------------------------- /src/asset/img/emblem.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/img/emblem.webp -------------------------------------------------------------------------------- /src/asset/img/pixel-img.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/img/pixel-img.jpeg -------------------------------------------------------------------------------- /src/asset/tag/encode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 编码解码(未选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/asset/tag/encode_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 编码解码(选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/asset/tag/encryption.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 加密解密(未选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/asset/tag/encryption_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 加密解密(选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/asset/tag/hot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 热门工具(未选中) 2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/asset/tag/hot_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 热门工具(选中) 2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/asset/tag/hover_red_like.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/asset/tag/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 图像处理(未选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/asset/tag/image_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 图像处理(选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/asset/tag/like.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 我的收藏(未选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/asset/tag/no_like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tag/no_like.png -------------------------------------------------------------------------------- /src/asset/tag/other.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 杂项(未选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/asset/tag/other_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 杂项(选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/asset/tag/red_like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tag/red_like.png -------------------------------------------------------------------------------- /src/asset/tag/red_like.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/asset/tag/red_like_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tag/red_like_check.png -------------------------------------------------------------------------------- /src/asset/tag/text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 文字处理(未选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/asset/tag/text_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 文字处理(选中) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/asset/tools/cloud_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tools/cloud_bg.png -------------------------------------------------------------------------------- /src/asset/tools/dot_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tools/dot_black.png -------------------------------------------------------------------------------- /src/asset/tools/dot_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tools/dot_blue.png -------------------------------------------------------------------------------- /src/asset/tools/dot_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tools/dot_yellow.png -------------------------------------------------------------------------------- /src/asset/tools/sunglasses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/xtools/5338f18e5a4ebe527951f44f7242397c7d74a169/src/asset/tools/sunglasses.png -------------------------------------------------------------------------------- /src/components/Alert/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { success } from '@/styles/colors'; 3 | import Snackbar from '@mui/material/Snackbar'; 4 | import MuiAlert, { AlertProps, AlertColor } from '@mui/material/Alert'; 5 | import { createRoot } from 'react-dom/client'; 6 | import { useTheme } from '@mui/material/styles'; 7 | 8 | const Alert = React.forwardRef( 9 | function Alert(props, ref) { 10 | return ; 11 | } 12 | ); 13 | 14 | export interface WarningProps { 15 | content: string; 16 | color: AlertColor; 17 | } 18 | 19 | function WarningBar(props: WarningProps) { 20 | const theme = useTheme(); 21 | const [open, setOpen] = useState(true); 22 | 23 | useEffect(() => { 24 | setTimeout(() => { 25 | setOpen(false); 26 | }, 2000); 27 | }, []); 28 | return ( 29 | setOpen(false)} 33 | key={'top-center-warning'} 34 | sx={{ zIndex: theme.zIndex.snackbar + 100 }} 35 | > 36 | 44 | {props?.content ?? ''} 45 | 46 | 47 | ); 48 | } 49 | 50 | export function callAlert(props: WarningProps) { 51 | const warningDom = document.createElement('div'); 52 | document.body.appendChild(warningDom); 53 | warningDom.id = 'warning-window'; 54 | warningDom.style.zIndex = '-2'; 55 | const warningRoot = createRoot(warningDom); 56 | warningRoot.render(); 57 | setTimeout(() => { 58 | warningDom.remove(); 59 | }, 3000); 60 | } 61 | 62 | const alertActions = { 63 | success: (content: string) => callAlert({ color: 'success', content }), 64 | warning: (content: string) => callAlert({ color: 'warning', content }), 65 | error: (content: string) => callAlert({ color: 'error', content }), 66 | }; 67 | 68 | export default alertActions; 69 | -------------------------------------------------------------------------------- /src/components/CIDR/InputEditor.tsx: -------------------------------------------------------------------------------- 1 | import { countLines } from '@/utils'; 2 | import { Box, TextField, Typography } from '@mui/material'; 3 | import { useMemo } from 'react'; 4 | import WarningFab from './WarningFab'; 5 | 6 | export default function InputEditor({ 7 | input, 8 | setInput, 9 | output, 10 | }: { 11 | input: string; 12 | output: any; 13 | setInput: (value: string) => void; 14 | }) { 15 | return ( 16 | 17 | setInput(event.target.value)} 28 | /> 29 | 30 | 31 | 行数: {useMemo(() => countLines(input), [input])}{' '} 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/CIDR/OptionsControl.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Grid } from '@mui/material'; 2 | import { ForwardedRef, forwardRef } from 'react'; 3 | 4 | function OptionsControl( 5 | { 6 | ipKind, 7 | toggleIpv4, 8 | toggleIpv6, 9 | bogonFilter, 10 | toggleReservedFilter, 11 | handleAggregate, 12 | }: { 13 | ipKind: string; 14 | toggleIpv4: () => void; 15 | toggleIpv6: () => void; 16 | bogonFilter?: string; 17 | toggleReservedFilter: () => void; 18 | handleAggregate: (reverse?: boolean) => void; 19 | }, 20 | ref: ForwardedRef 21 | ) { 22 | return ( 23 | 24 | 25 | handleAggregate()} 30 | > 31 | 聚合 32 | 33 | 34 | 35 | ); 36 | } 37 | 38 | export default forwardRef(OptionsControl); 39 | -------------------------------------------------------------------------------- /src/components/CIDR/OutputEditor.tsx: -------------------------------------------------------------------------------- 1 | import { Box, TextField } from '@mui/material'; 2 | import OutputStatusLine from './OutputStatusLine'; 3 | 4 | export default function OutputEditor({ 5 | ipKind, 6 | output, 7 | }: { 8 | ipKind: string; 9 | output: any; 10 | }) { 11 | return ( 12 | 13 | {' '} 14 | v) 27 | .join('\n')} 28 | /> 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/components/CIDR/OutputStatusLine.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Typography } from '@mui/material'; 2 | 3 | function Partial({ name, status }: { name: string; status: any }) { 4 | return ( 5 | 6 | {name}: {status?.line_count_before ?? 0} 7 | 行 / {status?.address_count_before ?? '0'} 个 8 | ➟ 9 | {status?.line_count_after ?? 0} 10 | 行 /{' '} 11 | {status?.address_count_after ?? '0'} 个 12 | 13 | ); 14 | } 15 | 16 | export default function OutputStatusLine({ output }: { output: any }) { 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/CIDR/WarningFab.tsx: -------------------------------------------------------------------------------- 1 | import { countLines } from '@/utils'; 2 | import WarningIcon from '@mui/icons-material/Warning'; 3 | import { 4 | Badge, 5 | Box, 6 | Fab, 7 | Paper, 8 | Popover, 9 | Tooltip, 10 | Typography, 11 | } from '@mui/material'; 12 | import React, { MouseEvent, useMemo } from 'react'; 13 | 14 | export default function WarningFab({ invalidLines }: { invalidLines: string }) { 15 | const [anchorEl, setAnchorEl] = React.useState(null as any); 16 | const handleOpen = (event: MouseEvent) => { 17 | setAnchorEl(event.currentTarget); 18 | }; 19 | const handleClose = () => { 20 | setAnchorEl(null); 21 | }; 22 | const open = Boolean(anchorEl); 23 | const id = open ? 'invalid-lines-popover' : undefined; 24 | 25 | const invalidCount = useMemo(() => countLines(invalidLines), [invalidLines]); 26 | 27 | return invalidCount > 0 ? ( 28 | <> 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 55 | 56 | 57 | 无效的 CIDR 58 | 59 | 64 | 65 | {invalidLines} 66 | 67 | 68 | 69 | 70 | > 71 | ) : ( 72 | <>> 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/components/CusTabs/index.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, useState, useEffect } from 'react'; 2 | 3 | import { Tabs, Tab, type SxProps } from '@mui/material'; 4 | 5 | interface ListItem { 6 | label: string | React.ReactNode; 7 | value: string | number; 8 | disabled?: boolean; 9 | } 10 | 11 | interface RadioButtonProps { 12 | list: ListItem[]; 13 | defatValue?: ListItem['value']; 14 | onChange?(value: ListItem['value']): void; 15 | size?: 'small' | 'medium' | 'large'; 16 | sx?: SxProps; 17 | 18 | // 希望父组件控制 19 | change?(value: ListItem['value']): void; 20 | value?: ListItem['value']; 21 | } 22 | 23 | const CusTabs: FC = ({ 24 | list, 25 | defatValue, 26 | onChange, 27 | change, 28 | sx, 29 | value: v, 30 | }) => { 31 | const [value, setValue] = useState( 32 | v || defatValue || list[0].value 33 | ); 34 | const handleChange = ( 35 | event: React.SyntheticEvent, 36 | id: any 37 | ) => { 38 | if (id !== null) { 39 | setValue(id); 40 | onChange?.(id); 41 | } 42 | }; 43 | 44 | useEffect(() => { 45 | if (v) setValue(v); 46 | }, [v]); 47 | 48 | return ( 49 | , value: any) => 54 | change(value) 55 | : handleChange 56 | } 57 | sx={{ 58 | p: '5px', 59 | border: '1px solid', 60 | borderColor: 'divider', 61 | minHeight: 36, 62 | height: 36, 63 | backgroundColor: 'background.paper', 64 | borderRadius: '4px', 65 | 66 | '.MuiTabs-indicator': { 67 | top: 0, 68 | bottom: 0, 69 | height: 'auto', 70 | borderRadius: '4px', 71 | }, 72 | ...sx, 73 | }} 74 | > 75 | {list.map((item) => ( 76 | 92 | ))} 93 | 94 | ); 95 | }; 96 | 97 | export default CusTabs; 98 | -------------------------------------------------------------------------------- /src/components/FormItem/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormControl, InputLabel, Stack } from '@mui/material'; 2 | import React from 'react'; 3 | 4 | const FormItem: React.FC<{ 5 | label: string | React.ReactNode; 6 | children: React.ReactNode; 7 | singleLine?: boolean; 8 | }> = (props) => { 9 | const { singleLine = false } = props; 10 | return ( 11 | 12 | 16 | {props.label} 17 | {props.children} 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default FormItem; 24 | -------------------------------------------------------------------------------- /src/components/Header/loggedInView.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip, Box } from '@mui/material'; 2 | import { Avatar } from './components'; 3 | import React from 'react'; 4 | import ProfilePanel from './profilePanel'; 5 | 6 | export interface LoggedInProps { 7 | user: any | null; 8 | verified: boolean; 9 | } 10 | 11 | const LoggedInView = ({ user, promotionInfo }: any) => { 12 | return ( 13 | 38 | } 39 | > 40 | 41 | {user.head_img_url ? ( 42 | 52 | ) : ( 53 | 54 | )} 55 | 56 | 57 | ); 58 | }; 59 | 60 | export default LoggedInView; 61 | -------------------------------------------------------------------------------- /src/components/ImageDownload/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps } from '@mui/material'; 2 | import React, { useCallback } from 'react'; 3 | 4 | interface Props { 5 | src: string; 6 | sx?: SxProps; 7 | } 8 | 9 | const Module: React.FC = (props) => { 10 | const handleClick = useCallback( 11 | (event: React.MouseEvent) => { 12 | if (props.src) { 13 | const extName = /^data:[a-zA-Z0-9]+\/([a-zA-Z0-9]+)[^;]*;base64,/.exec( 14 | props.src 15 | ); 16 | if (extName && extName[1]) { 17 | const aTag = document.createElement('a'); 18 | aTag.style.display = 'none'; 19 | aTag.href = props.src; 20 | aTag.download = '保存图片.' + extName[1]; 21 | document.body.appendChild(aTag); 22 | aTag.click(); 23 | } 24 | } 25 | }, 26 | [props.src] 27 | ); 28 | 29 | return ( 30 | 42 | {props.src ? ( 43 | 44 | ) : ( 45 | <>> 46 | )} 47 | 68 | {' '} 69 | 点击下载图片{' '} 70 | 71 | 72 | ); 73 | }; 74 | 75 | export default Module; 76 | -------------------------------------------------------------------------------- /src/components/LikeIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import RedTag from '@/asset/tag/red_like.svg'; 2 | import RedTagCheck from '@/asset/tag/red_like_check.png'; 3 | import HoverRedTagCheck from '@/asset/tag/hover_red_like.svg'; 4 | import { LikeContext } from '@/hooks/useLikeList'; 5 | import React, { useContext, useMemo } from 'react'; 6 | import { Box } from '@mui/material'; 7 | 8 | const LikeIcon: React.FC<{ path: string; style: React.CSSProperties }> = ( 9 | props 10 | ) => { 11 | const { path, style = {} } = props; 12 | const { likeList, updateLikeList } = useContext(LikeContext); 13 | 14 | const hanldeLike = ( 15 | event: React.MouseEvent, 16 | path: string 17 | ) => { 18 | event.preventDefault(); 19 | if (typeof window !== 'undefined' && window.localStorage) { 20 | try { 21 | let newLike = []; 22 | if (likeList?.includes(path)) { 23 | newLike = likeList.filter((item) => item !== path); 24 | } else { 25 | newLike = [...(likeList || []), path]; 26 | } 27 | updateLikeList(newLike); 28 | } finally { 29 | console.log('Get like list error'); 30 | } 31 | } 32 | }; 33 | const isCheck = useMemo(() => { 34 | return likeList?.includes(path); 35 | }, [likeList, path]); 36 | 37 | return ( 38 | hanldeLike(event, path)} 57 | /> 58 | ); 59 | }; 60 | 61 | export default LikeIcon; 62 | -------------------------------------------------------------------------------- /src/components/Text/index.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { defaultText } from '@/styles/colors'; 3 | 4 | type sizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; 5 | type weightType = 'bold' | 'thin' | 'regular'; 6 | type langType = 'zh' | 'en' | 'num'; 7 | export interface TitleTextProps { 8 | size?: sizeType; 9 | weight?: weightType; 10 | lang?: langType; 11 | hover?: 'true' | 'false'; 12 | } 13 | 14 | const sizeChart = { 15 | xs: { fontSize: '14px', lineHeight: '22px' }, 16 | sm: { fontSize: '16px', lineHeight: '24px' }, 17 | md: { fontSize: '24px', lineHeight: '32px' }, 18 | lg: { fontSize: '32px', lineHeight: '40px' }, 19 | xl: { fontSize: '44px', lineHeight: '48px' }, 20 | xxl: { fontSize: '60px', lineHeight: '68px' }, 21 | }; 22 | const weightChart = { 23 | bold: 'AlibabaPuHuiTiBold', 24 | thin: 'AlibabaPuHuiTiThin', 25 | regular: 'AlibabaPuHuiTiRegular', 26 | }; 27 | 28 | const langFont = (weight: weightType) => ({ 29 | zh: `${weightChart[weight]}, "PingFang SC"`, 30 | en: `"Helvetica Neue", Gilroy`, 31 | num: `DIN`, 32 | }); 33 | 34 | const Text = styled('div', { 35 | shouldForwardProp: (prop) => 36 | prop !== 'size' && prop !== 'weight' && prop !== 'lang', 37 | })( 38 | ({ size = 'sm', weight = 'regular', lang = 'zh', theme }) => ({ 39 | fontFamily: langFont(weight)[lang], 40 | ...sizeChart[size], 41 | color: defaultText, 42 | '& > a': { 43 | textDecoration: 'none', 44 | color: 'inherit', 45 | fontFamily: langFont(weight)[lang], 46 | }, 47 | [theme.breakpoints.down('sm')]: { 48 | fontFamily: `"PingFang SC"`, 49 | fontWeight: weight, 50 | }, 51 | }) 52 | ); 53 | 54 | export const TextH1 = styled('h1', { 55 | shouldForwardProp: (prop) => 56 | prop !== 'size' && prop !== 'weight' && prop !== 'lang', 57 | })( 58 | ({ size = 'sm', weight = 'regular', lang = 'zh', theme }) => ({ 59 | fontFamily: langFont(weight)[lang], 60 | ...sizeChart[size], 61 | color: defaultText, 62 | '& > a': { 63 | textDecoration: 'none', 64 | color: 'inherit', 65 | fontFamily: langFont(weight)[lang], 66 | }, 67 | [theme.breakpoints.down('sm')]: { 68 | fontFamily: `"PingFang SC"`, 69 | fontWeight: weight, 70 | }, 71 | }) 72 | ); 73 | 74 | export default Text; 75 | -------------------------------------------------------------------------------- /src/components/TextFieldWithClean/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box, TextField, OutlinedTextFieldProps } from '@mui/material'; 2 | import React from 'react'; 3 | import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; 4 | 5 | interface CustomTextFieldProps extends OutlinedTextFieldProps { 6 | onClean: React.MouseEventHandler; 7 | } 8 | 9 | const TextFieldWithClean: React.FC = (props) => { 10 | const { onClean, ...otherProps } = props; 11 | 12 | return ( 13 | 14 | 19 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default TextFieldWithClean; 47 | -------------------------------------------------------------------------------- /src/components/TextFieldWithCopy/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box, TextField, OutlinedTextFieldProps } from '@mui/material'; 2 | import React, { useCallback } from 'react'; 3 | import CopyToClipboard from 'react-copy-to-clipboard'; 4 | import ContentCopyIcon from '@mui/icons-material/ContentCopy'; 5 | import alert from '@/components/Alert'; 6 | 7 | const TextFieldWithCopy: React.FC = (props) => { 8 | const { ...otherProps } = props; 9 | 10 | const handleCopyClick = useCallback(() => { 11 | alert.success('已复制到剪切板'); 12 | }, []); 13 | 14 | return ( 15 | 16 | 21 | 40 | 44 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | export default TextFieldWithCopy; 52 | -------------------------------------------------------------------------------- /src/components/ToolCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from '@/utils/tags'; 2 | import { Tool } from '@/utils/tools'; 3 | import { Avatar, CardHeader, Typography } from '@mui/material'; 4 | import LikeIcon from '../LikeIcon'; 5 | import { grayText2 } from '@/constant'; 6 | 7 | export const ToolCard = (props: { 8 | tool: Tool; 9 | tag?: Tag; 10 | showStar?: boolean; 11 | }) => { 12 | const { tool, tag, showStar = true } = props; 13 | return ( 14 | <> 15 | 35 | {tool.label[0]} 36 | 37 | } 38 | action={ 39 | 47 | } 48 | title={tool.label} 49 | /> 50 | 62 | {tool.subTitle} 63 | 64 | > 65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /src/components/Tools/index.tsx: -------------------------------------------------------------------------------- 1 | import { defaultTextClick, primary } from '@/constant'; 2 | import { Box, Stack, Typography } from '@mui/material'; 3 | import { styled } from '@mui/material/styles'; 4 | 5 | type sizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; 6 | type weightType = 'bold' | 'thin' | 'regular'; 7 | type langType = 'zh' | 'en' | 'num'; 8 | 9 | export const SecondUnitBtn = styled(Typography)(() => ({ 10 | cursor: 'pointer', 11 | minWidth: '30px', 12 | textAlign: 'right', 13 | '&:hover': { 14 | color: primary, 15 | }, 16 | })); 17 | export const ToolsForm = styled(Box)(() => ({ 18 | pt: '10px', 19 | display: 'flex', 20 | flexDirection: 'column', 21 | 22 | mx: 'auto', 23 | gap: 16, 24 | '& .MuiOutlinedInput-root': { 25 | flexShrink: 0, 26 | flexGrow: 1, 27 | }, 28 | '& .MuiOutlinedInput-input': { 29 | paddingTop: '4px', 30 | paddingBottom: '4px', 31 | }, 32 | '& .MuiInputLabel-root': { 33 | position: 'relative', 34 | transform: 'unset', 35 | width: '155px', 36 | color: defaultTextClick, 37 | fontSize: '14px', 38 | }, 39 | '& .MuiOutlinedInput-notchedOutline': { 40 | borderColor: '#E3E8EF', 41 | }, 42 | '& .MuiFormHelperText-root': { 43 | position: 'absolute', 44 | bottom: '-20px', 45 | }, 46 | '& .MuiInputAdornment-positionEnd > p': { 47 | fontSize: '14px!important', 48 | }, 49 | '& input': { 50 | fontSize: '14px!important', 51 | }, 52 | '& .MuiInputLabel-root.Mui-focused': { 53 | color: 'unset', 54 | }, 55 | })); 56 | 57 | export const UnixStartBtn = styled(Stack)(() => ({ 58 | cursor: 'pointer', 59 | alignItems: 'center', 60 | fontSize: '14px', 61 | borderRadius: '4px', 62 | })); 63 | 64 | export const UnixInputWrap = styled(Stack)(() => ({ 65 | '& .MuiOutlinedInput-root': { 66 | '@media(min-width: 1520px)': { 67 | width: '360px', 68 | }, 69 | }, 70 | })); 71 | -------------------------------------------------------------------------------- /src/components/Watermark/context.ts: -------------------------------------------------------------------------------- 1 | import { useEvent } from 'rc-util'; 2 | import * as React from 'react'; 3 | 4 | export interface WatermarkContextProps { 5 | add: (ele: HTMLElement) => void; 6 | remove: (ele: HTMLElement) => void; 7 | } 8 | 9 | function voidFunc() {} 10 | 11 | const WatermarkContext = React.createContext({ 12 | add: voidFunc, 13 | remove: voidFunc, 14 | }); 15 | 16 | export function usePanelRef(panelSelector?: string) { 17 | const watermark = React.useContext(WatermarkContext); 18 | 19 | const panelEleRef = React.useRef(); 20 | const panelRef = useEvent((ele: HTMLElement | null) => { 21 | if (ele) { 22 | const innerContentEle = panelSelector 23 | ? ele.querySelector(panelSelector)! 24 | : ele; 25 | watermark.add(innerContentEle); 26 | panelEleRef.current = innerContentEle; 27 | } else { 28 | watermark.remove(panelEleRef.current!); 29 | } 30 | }); 31 | 32 | return panelRef; 33 | } 34 | 35 | export default WatermarkContext; 36 | -------------------------------------------------------------------------------- /src/components/Watermark/useWatermark.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { getStyleStr } from './utils'; 4 | 5 | /** 6 | * Base size of the canvas, 1 for parallel layout and 2 for alternate layout 7 | * Only alternate layout is currently supported 8 | */ 9 | export const BaseSize = 2; 10 | export const FontGap = 3; 11 | 12 | // Prevent external hidden elements from adding accent styles 13 | const EmphasizedStyles = { 14 | visibility: 'visible !important', 15 | }; 16 | 17 | export type AppendWatermark = ( 18 | base64Url: string, 19 | markWidth: number, 20 | container: HTMLElement 21 | ) => void; 22 | 23 | export default function useWatermark( 24 | markStyle: React.CSSProperties 25 | ): [ 26 | appendWatermark: AppendWatermark, 27 | removeWatermark: (container: HTMLElement) => void, 28 | isWatermarkEle: (ele: Node) => boolean, 29 | ] { 30 | const [watermarkMap] = React.useState( 31 | () => new Map() 32 | ); 33 | 34 | const appendWatermark = ( 35 | base64Url: string, 36 | markWidth: number, 37 | container: HTMLElement 38 | ) => { 39 | if (container) { 40 | if (!watermarkMap.get(container)) { 41 | const newWatermarkEle = document.createElement('div'); 42 | watermarkMap.set(container, newWatermarkEle); 43 | } 44 | 45 | const watermarkEle = watermarkMap.get(container)!; 46 | 47 | watermarkEle.setAttribute( 48 | 'style', 49 | getStyleStr({ 50 | ...markStyle, 51 | backgroundImage: `url('${base64Url}')`, 52 | backgroundSize: `${Math.floor(markWidth)}px`, 53 | ...(EmphasizedStyles as React.CSSProperties), 54 | }) 55 | ); 56 | // Prevents using the browser `Hide Element` to hide watermarks 57 | watermarkEle.removeAttribute('class'); 58 | 59 | container.append(watermarkEle); 60 | } 61 | }; 62 | 63 | const removeWatermark = (container: HTMLElement) => { 64 | const watermarkEle = watermarkMap.get(container); 65 | 66 | if (watermarkEle && container) { 67 | container.removeChild(watermarkEle); 68 | } 69 | 70 | watermarkMap.delete(container); 71 | }; 72 | 73 | const isWatermarkEle = (ele: any) => 74 | Array.from(watermarkMap.values()).includes(ele); 75 | 76 | return [appendWatermark, removeWatermark, isWatermarkEle]; 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Watermark/utils.ts: -------------------------------------------------------------------------------- 1 | /** converting camel-cased strings to be lowercase and link it with Separato */ 2 | export function toLowercaseSeparator(key: string) { 3 | return key.replace(/([A-Z])/g, '-$1').toLowerCase(); 4 | } 5 | 6 | export function getStyleStr(style: React.CSSProperties): string { 7 | return Object.keys(style) 8 | .map( 9 | // @ts-ignore 10 | (key: keyof React.CSSProperties) => 11 | `${toLowercaseSeparator(key)}: ${style[key]};` 12 | ) 13 | .join(' '); 14 | } 15 | 16 | /** Returns the ratio of the device's physical pixel resolution to the css pixel resolution */ 17 | export function getPixelRatio() { 18 | return window.devicePixelRatio || 1; 19 | } 20 | 21 | /** Whether to re-render the watermark */ 22 | export const reRendering = ( 23 | mutation: MutationRecord, 24 | isWatermarkEle: (ele: any) => boolean 25 | ) => { 26 | let flag = false; 27 | // Whether to delete the watermark node 28 | if (mutation.removedNodes.length) { 29 | flag = Array.from(mutation.removedNodes).some((node) => 30 | isWatermarkEle(node) 31 | ); 32 | } 33 | // Whether the watermark dom property value has been modified 34 | if (mutation.type === 'attributes' && isWatermarkEle(mutation.target)) { 35 | flag = true; 36 | } 37 | return flag; 38 | }; 39 | 40 | export const downloadFile = ( 41 | base64Data: string, 42 | name: string = 'image.png' 43 | ) => { 44 | const b64encoded = base64Data.replace(/^data:image\/\w+;base64,/, ''); 45 | const rawData = atob(b64encoded); 46 | const arrayBuffer = Uint8Array.from(rawData, (c) => c.charCodeAt(0)); 47 | const blob = new Blob([arrayBuffer], { type: 'image/png' }); 48 | const url = URL.createObjectURL(blob); 49 | const a = document.createElement('a'); 50 | a.href = url; 51 | a.download = name; 52 | a.click(); 53 | }; 54 | -------------------------------------------------------------------------------- /src/constant/color.ts: -------------------------------------------------------------------------------- 1 | export const primary = 'rgba(52, 90, 255, 1)'; 2 | export const primaryHover = '#73D13C'; 3 | export const primaryClick = '#389E0E'; 4 | 5 | export const primaryLight = '#DDF4D2'; 6 | export const primaryLightGradient = 7 | 'linear-gradient(202deg, rgba(166,216,73,0.1) 0%, rgba(116,193,89,0.1) 100%);'; 8 | 9 | export const secondary = '#ECF9E6'; 10 | export const secondaryHover = '#F3FDEE'; 11 | export const secondaryClick = '#DAE8D4'; 12 | 13 | export const special = 14 | 'linear-gradient(270deg, #11AF60 0%, rgba(52, 90, 255, 1) 100%)'; 15 | export const dangerHover = 'linear-gradient(225deg, #FF1F1F 0%, #F78900 100%)'; 16 | 17 | export const disabled = '#F7F7F7'; 18 | export const shadowColor = '#041B0F'; 19 | 20 | export const defaultText = '#041B0F'; 21 | export const defaultTextHover = '#0E2919'; 22 | export const defaultTextClick = '#000000'; 23 | 24 | export const secondaryText = '#041B0F99'; 25 | export const secondaryTextHover = '#737373'; 26 | export const secondaryTextClick = '#404040'; 27 | 28 | export const leastText = '#041B0F66'; 29 | export const leastTextHover = '#A6A6A6'; 30 | export const leastTextClick = '#737373'; 31 | 32 | export const disabledText = '#BFBFBF'; 33 | export const specialText = '#737971'; 34 | 35 | export const success = 'rgba(52, 90, 255, 1)'; 36 | export const warning = '#FFBF00'; 37 | export const error = '#FF1F1F'; 38 | 39 | export const grayText = '#0B2562'; 40 | export const grayText2 = 'rgba(11,37,98,0.5)'; 41 | export const errorText = '#FF1F1F'; 42 | export const infoText = '#2F7CE9'; 43 | 44 | export const gray = '#DADCE0'; 45 | export const grayLight = '#F8F8F8'; 46 | export const grayLabel = '#717579'; 47 | export const hover = 'rgba(82, 196, 26, 0.8)'; 48 | export const hoverLight = 'rgba(82, 196, 26, 0.11)'; 49 | 50 | export const darkBg = '#072120'; 51 | export const grayBg = '#F3F4F7'; 52 | export const grayBg2 = '#F7F8FA'; 53 | 54 | export const grayBorder = '#E7EAEF'; 55 | 56 | export const caption2 = 'rgba(0, 0, 0, 0.50)'; 57 | 58 | export const notificationsBg = 'rgba(255, 24, 68, 1)'; 59 | -------------------------------------------------------------------------------- /src/constant/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './color'; 2 | export * from './style'; 3 | -------------------------------------------------------------------------------- /src/constant/style.ts: -------------------------------------------------------------------------------- 1 | export const Banner_Animation_Time = 0.5; 2 | 3 | export const Side_Margin = '8%'; 4 | 5 | export const boxShadow = '0 4px 14px 0 #1A041B0F'; 6 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useMobileView } from './useMobileView'; 2 | export { useNavigateParams } from './useNavigationWithPramas'; 3 | export { useDebounce } from './useDebounce'; 4 | export { usePath } from './usePath'; 5 | export { useCSV } from './useCSV'; 6 | -------------------------------------------------------------------------------- /src/hooks/useAnchor.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from 'react'; 2 | 3 | export interface Anchor { 4 | anchor: string; 5 | updateAnchor: (value: string) => void; 6 | } 7 | 8 | export const AnchorContext = createContext(null as any); 9 | 10 | export const AnchorContextProvider = ({ children }: any) => { 11 | const [anchor, setAnchor] = useState(''); 12 | const updateAnchor = (newValue: string) => { 13 | setAnchor(newValue); 14 | }; 15 | const contextValue = { 16 | anchor, 17 | updateAnchor, 18 | }; 19 | 20 | return ( 21 | 22 | {children} 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/hooks/useCSV.ts: -------------------------------------------------------------------------------- 1 | import { setCellWidthInExcel } from '@/utils'; 2 | import { useCallback, useEffect, useState } from 'react'; 3 | import { utils, writeFile } from 'xlsx'; 4 | 5 | export function useCSV( 6 | value: T[] 7 | ): [T[], number[], React.Dispatch>, () => void] { 8 | const [pres, setPres] = useState(value); 9 | const [widths, setWidths] = useState([]); 10 | const [rcWidths, setRcWidths] = useState([]); 11 | 12 | useEffect(() => { 13 | const [wchs, tcwchs] = setCellWidthInExcel(pres); 14 | setWidths(wchs); 15 | setRcWidths(tcwchs); 16 | }, [pres]); 17 | 18 | const exportFile = useCallback(() => { 19 | const ws = utils.json_to_sheet(pres); 20 | const wb = utils.book_new(); 21 | utils.book_append_sheet(wb, ws, 'jsontoexcel'); 22 | 23 | ws['!cols'] = widths.map((it) => ({ wch: it })); 24 | 25 | writeFile(wb, 'jsontoexcel.xlsx'); 26 | }, [pres, widths]); 27 | 28 | return [pres, rcWidths, setPres, exportFile]; 29 | } 30 | -------------------------------------------------------------------------------- /src/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useDebounce(value: T, delay?: number): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500); 8 | 9 | return () => { 10 | clearTimeout(timer); 11 | }; 12 | }, [value, delay]); 13 | 14 | return debouncedValue; 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/useLikeList.tsx: -------------------------------------------------------------------------------- 1 | import { useLocalStorageState } from 'ahooks'; 2 | import { createContext, useState } from 'react'; 3 | 4 | export interface Like { 5 | likeList: string[]; 6 | updateLikeList: (value: string[]) => void; 7 | } 8 | 9 | export const LikeContext = createContext(null as any); 10 | 11 | export const LikeContextProvider = ({ children }: any) => { 12 | const [localList = [], setLocalList] = useLocalStorageState( 13 | 'like_list', 14 | { defaultValue: [] } 15 | ); 16 | const [likeList, setValue] = useState(localList); 17 | const updateLikeList = (newValue: string[]) => { 18 | setValue(newValue); 19 | setLocalList(newValue); 20 | }; 21 | const contextValue = { 22 | likeList, 23 | updateLikeList, 24 | }; 25 | 26 | return ( 27 | {children} 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/hooks/useMobileView.ts: -------------------------------------------------------------------------------- 1 | import { useTheme, useMediaQuery } from '@mui/material'; 2 | 3 | export const useMobileView = () => { 4 | const theme = useTheme(); 5 | const isMobile = useMediaQuery(theme.breakpoints.down('sm')); 6 | return isMobile; 7 | }; 8 | -------------------------------------------------------------------------------- /src/hooks/useNavigationWithPramas.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const useNavigateParams = () => { 4 | const router = useRouter(); 5 | 6 | return (path: string) => { 7 | router.push({ pathname: path, query: router.query }); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/hooks/usePath.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const usePath = (): { 4 | root: string; 5 | path: string; 6 | getResolvedPath: (path: string) => string; 7 | } => { 8 | const router = useRouter(); 9 | const currentPath = router.pathname; 10 | const paths = currentPath.split('/')?.filter((path) => path !== ''); 11 | const currentPathRoot = paths?.[0] ?? '/'; 12 | const getResolvedPath = (path: string): string => { 13 | let resolvedPath = location.pathname; 14 | if (path) { 15 | paths.push(path); 16 | paths.unshift(''); 17 | resolvedPath = paths.join('/'); 18 | } 19 | return resolvedPath; 20 | }; 21 | 22 | return { root: currentPathRoot, path: currentPath, getResolvedPath }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/hooks/useWasm.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import __wbg_init, { aggregate } from '../../public/pkg/cidr_aggregator.js'; 3 | 4 | interface WasmModule { 5 | aggregate: ( 6 | cidrs: string, 7 | reverse: boolean, 8 | exclude_reserved: boolean 9 | ) => any; 10 | } 11 | 12 | export const useWasm = () => { 13 | const [wasm, setWasm] = useState(null); 14 | 15 | useEffect(() => { 16 | async function loadWasm() { 17 | try { 18 | const wasmModuleUrl = new URL( 19 | '../../public/pkg/cidr_aggregator_bg.wasm', 20 | import.meta.url 21 | ); 22 | const wasmModule = await fetch(wasmModuleUrl).then((response) => 23 | response.arrayBuffer() 24 | ); 25 | await __wbg_init(wasmModule); 26 | setWasm({ 27 | aggregate: (cidrs, reverse, exclude_reserved) => 28 | aggregate(cidrs, reverse, exclude_reserved), 29 | }); 30 | } catch (err) { 31 | console.error('Failed to load WebAssembly module:', err); 32 | } 33 | } 34 | 35 | loadWasm(); 36 | }, []); 37 | 38 | return wasm; 39 | }; 40 | -------------------------------------------------------------------------------- /src/icon/ArrowDown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const ArrowDown = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/Check.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const Check = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/CheckCircleFilled.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const CheckCircleFilled = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/CheckCircleOutlined.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const CheckCircleOutlined = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/Copy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const Copy = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 20 | 21 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/icon/DashedArrow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | import dashed_arrow from '@/asset/svgs/onboarding_arrow.svg'; 4 | 5 | export const DashedArrow = (props: SvgIconProps) => { 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /src/icon/Delete.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const Delete = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 15 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/icon/Exclamation.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const Exclamation = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const Home = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/ImageIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { styled, SxProps } from '@mui/material/styles'; 3 | import Image, { StaticImageData } from 'next/image'; 4 | 5 | export const IconWrapper = styled('div')(() => ({ 6 | display: 'inline-block', 7 | transition: 'all 0.2s linear', 8 | })); 9 | 10 | // export const Image = styled("img")(() => ({ 11 | // display: "block", 12 | // width: "100%", 13 | // verticalAlign: "middle", 14 | // // transform: "translateX(-50%)", 15 | // // objectFit: "contain", 16 | // })); 17 | 18 | export interface ImageIconProps { 19 | src: string | StaticImageData; 20 | className?: string; 21 | sx?: SxProps; 22 | id?: string; 23 | onClick?: (event: React.MouseEvent) => void; 24 | } 25 | 26 | export const ImageIcon: React.FC = (props) => { 27 | const { src, className, sx, id, onClick } = props; 28 | return ( 29 | 30 | 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/icon/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const Logo = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 15 | 16 | 17 | 18 | 26 | 30 | 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/icon/LogoWhite.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const LogoWhite = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/icon/RightLined.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const RightLined = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/Sort.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const Sort = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/WeChat.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgIcon, SvgIconProps } from '@mui/material'; 3 | 4 | export const WeChat = (props: SvgIconProps) => { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/icon/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Logo'; 2 | export * from './LogoWhite'; 3 | export * from './WeChat'; 4 | export * from './ArrowDown'; 5 | export * from './Exclamation'; 6 | export * from './Check'; 7 | export * from './CheckCircleOutlined'; 8 | export * from './CheckCircleFilled'; 9 | export * from './ImageIcon'; 10 | export * from './Home'; 11 | export * from './RightLined'; 12 | export * from './Sort'; 13 | export * from './Delete'; 14 | export * from './Copy'; 15 | export * from './DashedArrow'; 16 | -------------------------------------------------------------------------------- /src/layouts/SideBar/index.tsx: -------------------------------------------------------------------------------- 1 | import { grayText } from '@/constant/color'; 2 | import { AnchorContext } from '@/hooks/useAnchor'; 3 | import { allTags } from '@/utils/tags'; 4 | import { Button, Paper, Stack, Typography } from '@mui/material'; 5 | import Image from 'next/image'; 6 | import React, { useContext, useEffect, useState } from 'react'; 7 | import Link from 'next/link'; 8 | import { useLocalStorageState } from 'ahooks'; 9 | 10 | const SideBar: React.FC<{}> = () => { 11 | const { anchor } = useContext(AnchorContext); 12 | const [linkAnchor, setLinkAnchor] = useState(false); 13 | const [checkAnchor, setCheckAnchor] = useState(''); 14 | const [, setScrollTop] = useLocalStorageState('home_scrollTop', { 15 | defaultValue: 0, 16 | }); 17 | 18 | useEffect(() => { 19 | if (linkAnchor) setLinkAnchor(false); 20 | else setCheckAnchor(anchor); 21 | // eslint-disable-next-line react-hooks/exhaustive-deps 22 | }, [anchor]); 23 | return ( 24 | 35 | 36 | {allTags.map((item) => ( 37 | { 42 | setCheckAnchor(item.name); 43 | setLinkAnchor(true); 44 | setScrollTop(undefined); 45 | }} 46 | href={'/home/#' + item.name} 47 | style={{ alignSelf: 'stretch' }} 48 | className='custom-link' 49 | prefetch 50 | > 51 | 61 | 65 | 74 | {item.label} 75 | 76 | 77 | 78 | ))} 79 | 80 | 81 | ); 82 | }; 83 | 84 | export default SideBar; 85 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Stack } from '@mui/material'; 4 | import Text from '@/components/Text'; 5 | import Button from '@/components/Button'; 6 | import Link from 'next/link'; 7 | 8 | export const NotFound = () => { 9 | return ( 10 | 18 | 404 NOT FOUND 19 | a': { color: 'inherit', textDecoration: 'none' }, 23 | marginBottom: 'auto', 24 | }} 25 | > 26 | 返回 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default NotFound; 33 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '@/styles/global.css'; 2 | import '@/styles/static/slick.css'; 3 | import '@/styles/static/effect-creative.min.css'; 4 | import '@/styles/static/navigation.min.css'; 5 | import '@/styles/static/swiper-customize.css'; 6 | import '@/styles/static/swiper.css'; 7 | import theme from '@/styles/theme'; 8 | import createEmotionCache from '@/utils/emotionCache'; 9 | import { CacheProvider } from '@emotion/react'; 10 | import { Stack, ThemeProvider } from '@mui/material'; 11 | import CssBaseline from '@mui/material/CssBaseline'; 12 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 13 | 14 | import PropTypes from 'prop-types'; 15 | import Header from '@/layouts/Header'; 16 | import SideBar from '@/layouts/SideBar'; 17 | import { LikeContextProvider } from '@/hooks/useLikeList'; 18 | import { AnchorContextProvider } from '@/hooks/useAnchor'; 19 | import { usePath } from '@/hooks'; 20 | import { useMemo } from 'react'; 21 | import { allTools } from '@/utils/tools'; 22 | import Head from 'next/head'; 23 | import RiverHeader from '../components/Header'; 24 | import '@chaitin_rivers/excalidraw/index.css'; 25 | 26 | const clientSideEmotionCache = createEmotionCache(); 27 | 28 | const queryClient = new QueryClient({ 29 | defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } }, 30 | }); 31 | 32 | export default function App({ 33 | Component, 34 | emotionCache = clientSideEmotionCache, 35 | }: any) { 36 | const { path } = usePath(); 37 | const isProduction = process.env.NODE_ENV === 'production'; 38 | const currentItem = useMemo(() => { 39 | return allTools.find((item) => item.path === path); 40 | }, [path]); 41 | return ( 42 | <> 43 | 44 | 48 | 49 | {currentItem?.label 50 | ? currentItem?.label + ' - 百川在线工具箱' 51 | : '百川云常用工具'} 52 | 53 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {isProduction ? : null} 68 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | > 91 | ); 92 | } 93 | 94 | App.propTypes = { 95 | Component: PropTypes.elementType.isRequired, 96 | emotionCache: PropTypes.object, 97 | }; 98 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GetServerSidePropsContext } from 'next'; 3 | import Document, { 4 | Html, 5 | Head, 6 | Main, 7 | NextScript, 8 | DocumentProps, 9 | DocumentContext, 10 | } from 'next/document'; 11 | import createEmotionServer from '@emotion/server/create-instance'; 12 | import createEmotionCache from '@/utils/emotionCache'; 13 | 14 | interface MyDocumentProps extends DocumentProps { 15 | emotionStyleTags: JSX.Element[]; 16 | } 17 | 18 | export default function MyDocument({ emotionStyleTags }: MyDocumentProps) { 19 | return ( 20 | 21 | 22 | 23 | {emotionStyleTags} 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | 33 | MyDocument.getInitialProps = async ( 34 | context: GetServerSidePropsContext & DocumentContext 35 | ) => { 36 | const originalRenderPage = context.renderPage; 37 | 38 | const cache = createEmotionCache(); 39 | const { extractCriticalToChunks } = createEmotionServer(cache); 40 | 41 | context.renderPage = () => 42 | originalRenderPage({ 43 | enhanceApp: (App: any) => 44 | function EnhanceApp(props: JSX.IntrinsicAttributes) { 45 | return ; 46 | }, 47 | }); 48 | 49 | const initialProps = (await Document.getInitialProps(context)) as { 50 | html: string; 51 | }; 52 | 53 | const emotionStyles = extractCriticalToChunks(initialProps.html); 54 | const emotionStyleTags = emotionStyles.styles.map((style, index) => ( 55 | 60 | )); 61 | return { 62 | ...initialProps, 63 | emotionStyleTags, // will be passed to the page component as props 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /src/pages/ascii.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import { 3 | Grid, 4 | Table, 5 | TableBody, 6 | TableCell, 7 | TableContainer, 8 | TableHead, 9 | TableRow, 10 | styled, 11 | tableCellClasses, 12 | } from '@mui/material'; 13 | import Paper from '@mui/material/Paper'; 14 | import React from 'react'; 15 | 16 | const Hash: React.FC = () => { 17 | const createData = (dec: number, hex: string, char: string) => { 18 | char = 19 | { 20 | '0x00': '{NUL}', 21 | '0x01': '{SOH}', 22 | '0x02': '{STX}', 23 | '0x03': '{ETX}', 24 | '0x04': '{EOT}', 25 | '0x05': '{ENQ}', 26 | '0x06': '{ACK}', 27 | '0x07': '{BEL}', 28 | '0x08': '{BS}', 29 | '0x09': '{HT}', 30 | '0x0a': '{NL}', 31 | '0x0b': '{VT}', 32 | '0x0c': '{NP}', 33 | '0x0d': '{CR}', 34 | '0x0e': '{SO}', 35 | '0x0f': '{SI}', 36 | '0x10': '{DLE}', 37 | '0x11': '{DC1}', 38 | '0x12': '{DC2}', 39 | '0x13': '{DC3}', 40 | '0x14': '{DC4}', 41 | '0x15': '{NAK}', 42 | '0x16': '{SYN}', 43 | '0x17': '{ETB}', 44 | '0x18': '{CAN}', 45 | '0x19': '{EM}', 46 | '0x1a': '{SUB}', 47 | '0x1b': '{ESC}', 48 | '0x1c': '{FS}', 49 | '0x1d': '{GS}', 50 | '0x1e': '{RS}', 51 | '0x1f': '{US}', 52 | '0x20': '{SP}', 53 | '0x7f': '{DEL}', 54 | }[hex] || char; 55 | return { hex, dec, char }; 56 | }; 57 | 58 | const rows = Array(128) 59 | .fill(0) 60 | .map((_, x) => { 61 | return createData( 62 | x, 63 | '0x' + x.toString(16).padStart(2, '0'), 64 | String.fromCharCode(x) 65 | ); 66 | }); 67 | 68 | const StyledTableCell = styled(TableCell)(({ theme }) => ({ 69 | [`&.${tableCellClasses.head}`]: { 70 | backgroundColor: theme.palette.common.black, 71 | color: theme.palette.common.white, 72 | }, 73 | })); 74 | 75 | const tables = Array(4) 76 | .fill(0) 77 | .map((_, x) => { 78 | return ( 79 | 80 | 81 | 82 | 83 | 84 | DEC 85 | HEX 86 | CHAR 87 | 88 | 89 | 90 | {rows.slice(x * 0x20, x * 0x20 + 0x20).map((row) => ( 91 | 92 | {row.dec} 93 | {row.hex} 94 | 95 | {row.char} 96 | 97 | 98 | ))} 99 | 100 | 101 | 102 | 103 | ); 104 | }); 105 | 106 | return ( 107 | 108 | 109 | {tables} 110 | 111 | 112 | ); 113 | }; 114 | 115 | export default Hash; 116 | -------------------------------------------------------------------------------- /src/pages/audiofmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import dynamic from 'next/dynamic'; 3 | 4 | const AudioFMT = () => { 5 | const AudioFmt = dynamic(() => import('@/components/Dynamic/AudioFmt'), { 6 | ssr: false, 7 | }); 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default AudioFMT; 16 | -------------------------------------------------------------------------------- /src/pages/base64.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import TabContext from '@mui/lab/TabContext'; 3 | import TabList from '@mui/lab/TabList'; 4 | import { Box, Tab } from '@mui/material'; 5 | import { Buffer } from 'buffer'; 6 | import React, { useCallback, useMemo, useState } from 'react'; 7 | import TextFieldWithCopy from '@/components/TextFieldWithCopy'; 8 | import TextFieldWithClean from '@/components/TextFieldWithClean'; 9 | 10 | const Base64: React.FC = () => { 11 | const [method, setMethod] = React.useState('encode'); 12 | const [input, setInput] = useState(''); 13 | const [output, setOutput] = useState(''); 14 | 15 | const funcMap = useMemo(() => { 16 | const decode = (str: string): string => 17 | Buffer.from(str, 'base64').toString('utf-8'); 18 | const encode = (str: string): string => 19 | Buffer.from(str, 'utf-8').toString('base64'); 20 | const m = new Map(); 21 | m.set('encode', encode); 22 | m.set('decode', decode); 23 | return m; 24 | }, []); 25 | 26 | const handleChange = useCallback( 27 | (event: React.SyntheticEvent, method: string) => { 28 | const newInput = output; 29 | setInput(newInput); 30 | setMethod(method); 31 | var fn = funcMap.get(method); 32 | if (fn) { 33 | setOutput(fn(newInput)); 34 | } 35 | }, 36 | [input, output] 37 | ); 38 | 39 | const handleInputChanged = useCallback( 40 | (event: React.ChangeEvent) => { 41 | var value = event.target.value; 42 | setInput(value); 43 | var fn = funcMap.get(method); 44 | if (fn) { 45 | setOutput(fn(value)); 46 | } 47 | }, 48 | [funcMap, method] 49 | ); 50 | 51 | const handleCleanClick = useCallback(() => { 52 | setInput(''); 53 | setOutput(''); 54 | }, []); 55 | 56 | return ( 57 | 58 | <> 59 | 60 | 61 | 62 | 67 | 72 | 73 | 74 | 75 | 90 | 106 | > 107 | 108 | ); 109 | }; 110 | 111 | export default Base64; 112 | -------------------------------------------------------------------------------- /src/pages/case_convert.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import MainContent from '@/components/MainContent'; 3 | import { Box, Stack, Typography } from '@mui/material'; 4 | import TextField from '@mui/material/TextField'; 5 | 6 | const CaseConvert: React.FC = () => { 7 | let [value, setValue] = useState(); 8 | 9 | return ( 10 | 11 | <> 12 | 13 | 输入 14 | 15 | { 21 | setValue(event.target.value); 22 | }} 23 | sx={{ textarea: { fontSize: '14px', fontFamily: 'Mono' } }} 24 | /> 25 | 26 | 27 | 28 | 大写 29 | 30 | 40 | 41 | 42 | 43 | 小写 44 | 45 | 55 | 56 | 57 | > 58 | 59 | ); 60 | }; 61 | 62 | export default CaseConvert; 63 | -------------------------------------------------------------------------------- /src/pages/cssfmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import Formater, { Mode } from '@/components/Formater'; 3 | import React from 'react'; 4 | 5 | const mock = 6 | '.a{position:absolute;box-sizing:border-box;min-width:100%;contain:style size layout;font-variant-ligatures:no-common-ligatures;}.a:focus{box-shadow:inset 0 0 0 2px #5E9ED6;outline:none;}'; 7 | 8 | const CSSFmt: React.FC = () => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default CSSFmt; 17 | -------------------------------------------------------------------------------- /src/pages/diff.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import { 3 | Box, 4 | ToggleButtonGroup, 5 | ToggleButton, 6 | OutlinedInput, 7 | Stack, 8 | Typography, 9 | } from '@mui/material'; 10 | import { useCallback, useState } from 'react'; 11 | import ReactDiffViewer from 'react-diff-viewer-continued'; 12 | 13 | const _C = () => { 14 | const [raw, setRaw] = useState(); 15 | const [modified, setModified] = useState(); 16 | const [alignment, setAlignment] = useState('1'); 17 | 18 | const handleAlignment = ( 19 | event: React.MouseEvent, 20 | newAlignment: string 21 | ) => { 22 | setAlignment(newAlignment); 23 | console.log(alignment); 24 | }; 25 | 26 | const handleRawChanged = useCallback( 27 | (event: React.ChangeEvent) => { 28 | setRaw(event.target.value); 29 | }, 30 | [] 31 | ); 32 | 33 | const handleModifiedChanged = useCallback( 34 | (event: React.ChangeEvent) => { 35 | setModified(event.target.value); 36 | }, 37 | [] 38 | ); 39 | var myStyle = { 40 | line: { 41 | 'word-break': 'break-word', 42 | }, 43 | }; 44 | 45 | return ( 46 | 47 | 48 | 49 | 原文本 50 | 对比文本 51 | 52 | 53 | 63 | 73 | 74 | 75 | 81 | 上下对比 82 | 左右对比 83 | 84 | 85 | 92 | 93 | 94 | ); 95 | }; 96 | 97 | export default _C; 98 | -------------------------------------------------------------------------------- /src/pages/docker_run_to_docker_compose.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import TextFieldWithClean from '@/components/TextFieldWithClean'; 3 | import { ToolsForm } from '@/components/Tools'; 4 | import { saveFile } from '@/utils/download'; 5 | import { Button, MenuItem, Select, Stack, TextField } from '@mui/material'; 6 | import Composerize from 'composerize'; 7 | 8 | import React, { useState } from 'react'; 9 | 10 | const DockerRunToDockerCompose: React.FC = () => { 11 | const [dockerRun, setDockerRun] = useState( 12 | 'docker run -p 8080:80 -d --name myapp nginx' 13 | ); 14 | const [version, setVersion] = useState<'v2x' | 'v3x'>('v2x'); 15 | const [dockerCompose, setDockerCompose] = useState(''); 16 | const conversionResult = () => { 17 | if (!dockerRun.trim()) return; 18 | const res = Composerize(dockerRun.trim(), null, version); 19 | setDockerCompose(res); 20 | }; 21 | const handleSaveFile = () => { 22 | saveFile(dockerCompose, 'docker-compose.yml'); 23 | }; 24 | return ( 25 | 26 | 27 | setDockerRun(event.target.value)} 32 | placeholder='请输入docker run命令' 33 | onClean={() => { 34 | setDockerRun(''); 35 | setDockerCompose(''); 36 | }} 37 | variant='outlined' 38 | /> 39 | 40 | 53 | 转换 54 | 55 | 68 | 导出 YAML 69 | 70 | setVersion(v.target.value as 'v2x' | 'v3x')} 75 | > 76 | 77 | v2x 78 | 79 | 80 | v3x 81 | 82 | 83 | 84 | 85 | 86 | 87 | ); 88 | }; 89 | 90 | export default DockerRunToDockerCompose; 91 | -------------------------------------------------------------------------------- /src/pages/excalidraw.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import { Box } from '@mui/material'; 3 | 4 | import React, { useState, useEffect } from 'react'; 5 | 6 | const Board: React.FC = () => { 7 | const [Excalidraw, setExcalidraw] = useState(null); 8 | useEffect(() => { 9 | import('@chaitin_rivers/excalidraw').then((comp: any) => 10 | setExcalidraw(comp.Excalidraw) 11 | ); 12 | }, []); 13 | 14 | return ( 15 | 27 | div': { 33 | border: '1px solid rgba(0,0,0,0.2)', 34 | borderRadius: '4px', 35 | }, 36 | }} 37 | > 38 | {Excalidraw && } 39 | 40 | 41 | ); 42 | }; 43 | 44 | export default Board; 45 | -------------------------------------------------------------------------------- /src/pages/hex.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import TextFieldWithClean from '@/components/TextFieldWithClean'; 3 | import TextFieldWithCopy from '@/components/TextFieldWithCopy'; 4 | import TabContext from '@mui/lab/TabContext'; 5 | import TabList from '@mui/lab/TabList'; 6 | import { Box, Tab } from '@mui/material'; 7 | import { Buffer } from 'buffer'; 8 | import React, { useCallback, useMemo, useState } from 'react'; 9 | 10 | const Hex: React.FC = () => { 11 | const [method, setMethod] = React.useState('encode'); 12 | const [input, setInput] = useState(''); 13 | const [output, setOutput] = useState(''); 14 | 15 | const funcMap = useMemo(() => { 16 | const decode = (str: string): string => 17 | Buffer.from(str, 'hex').toString('utf-8'); 18 | const encode = (str: string): string => 19 | Buffer.from(str, 'utf-8').toString('hex'); 20 | const m = new Map(); 21 | m.set('encode', encode); 22 | m.set('decode', decode); 23 | return m; 24 | }, []); 25 | 26 | const handleChange = (event: React.SyntheticEvent, method: string) => { 27 | const newInput = output; 28 | setInput(newInput); 29 | setMethod(method); 30 | var fn = funcMap.get(method); 31 | if (fn) { 32 | setOutput(fn(newInput)); 33 | } 34 | }; 35 | 36 | const handleInputChanged = useCallback( 37 | (event: React.ChangeEvent) => { 38 | var value = event.target.value; 39 | setInput(value); 40 | var fn = funcMap.get(method); 41 | if (fn) { 42 | setOutput(fn(value)); 43 | } 44 | }, 45 | [funcMap, method] 46 | ); 47 | 48 | const handleCleanClick = useCallback(() => { 49 | setInput(''); 50 | setOutput(''); 51 | }, []); 52 | 53 | return ( 54 | 55 | <> 56 | 57 | 58 | 59 | 64 | 69 | 70 | 71 | 72 | 73 | 88 | 104 | > 105 | 106 | ); 107 | }; 108 | 109 | export default Hex; 110 | -------------------------------------------------------------------------------- /src/pages/htmlentity.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import TabContext from '@mui/lab/TabContext'; 3 | import TabList from '@mui/lab/TabList'; 4 | import { Box, Tab } from '@mui/material'; 5 | import React, { useCallback, useMemo, useState } from 'react'; 6 | import { encode, decode } from 'html-entities'; 7 | import TextFieldWithCopy from '@/components/TextFieldWithCopy'; 8 | import TextFieldWithClean from '@/components/TextFieldWithClean'; 9 | 10 | const Hex: React.FC = () => { 11 | const [method, setMethod] = React.useState('encode'); 12 | const [input, setInput] = useState(''); 13 | const [output, setOutput] = useState(''); 14 | 15 | const funcMap = useMemo(() => { 16 | const m = new Map(); 17 | m.set('encode', encode); 18 | m.set('decode', decode); 19 | return m; 20 | }, []); 21 | 22 | const handleChange = (event: React.SyntheticEvent, method: string) => { 23 | setMethod(method); 24 | var fn = funcMap.get(method); 25 | if (fn) { 26 | setOutput(fn(input)); 27 | } 28 | }; 29 | 30 | const handleInputChanged = useCallback( 31 | (event: React.ChangeEvent) => { 32 | var value = event.target.value; 33 | setInput(value); 34 | var fn = funcMap.get(method); 35 | if (fn) { 36 | setOutput(fn(value)); 37 | } 38 | }, 39 | [funcMap, method] 40 | ); 41 | 42 | const handleCleanClick = useCallback(() => { 43 | setInput(''); 44 | setOutput(''); 45 | }, []); 46 | 47 | return ( 48 | 49 | <> 50 | 51 | 52 | 53 | 58 | 63 | 64 | 65 | 66 | 81 | 97 | > 98 | 99 | ); 100 | }; 101 | 102 | export default Hex; 103 | -------------------------------------------------------------------------------- /src/pages/htmlfmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import Formater, { Mode } from '@/components/Formater'; 3 | import React from 'react'; 4 | 5 | const mock = 'Hello Chaitin'; 6 | 7 | const HTMLFmt: React.FC = () => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default HTMLFmt; 16 | -------------------------------------------------------------------------------- /src/pages/jsfmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import Formater, { Mode } from '@/components/Formater'; 3 | import React from 'react'; 4 | 5 | const mock = 6 | 'function fibonacci(n){if(n<=1){return n}else{return fibonacci(n-1)+fibonacci(n-2)}}for(let i=0;i<10;i++){console.log(fibonacci(i))}'; 7 | 8 | const JSFmt: React.FC = () => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default JSFmt; 17 | -------------------------------------------------------------------------------- /src/pages/jsonfmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import Formater, { Mode } from '@/components/Formater'; 3 | import React from 'react'; 4 | 5 | const mock = '{"id": 1, "name": "test"}'; 6 | 7 | const JsonFmt: React.FC = () => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default JsonFmt; 16 | -------------------------------------------------------------------------------- /src/pages/morse.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Container, Grid, TextField, Button, Box } from '@mui/material'; 3 | import SendIcon from '@mui/icons-material/Send'; 4 | import TranslateIcon from '@mui/icons-material/Translate'; 5 | import { encode, decode } from 'xmorse'; 6 | import MainContent from '@/components/MainContent'; 7 | import alertActions from '@/components/Alert'; 8 | 9 | const MorseCodeTranslator = () => { 10 | const [text, setText] = useState(''); 11 | const [morse, setMorse] = useState(''); 12 | 13 | const onTextChange = (event: React.ChangeEvent) => { 14 | setText(event.target.value); 15 | }; 16 | 17 | const onMorseChange = (event: React.ChangeEvent) => { 18 | setMorse(event.target.value); 19 | }; 20 | 21 | const encodeToMorse = () => { 22 | try { 23 | const encoded = encode(text, { space: ' ' }); 24 | setMorse(encoded); 25 | } catch (error) { 26 | const err = error as Error; 27 | console.log(err); 28 | alertActions.error('加密失败!'); 29 | } 30 | }; 31 | 32 | const decodeFromMorse = () => { 33 | try { 34 | const decoded = decode(morse, { space: ' ' }); 35 | setText(decoded); 36 | } catch (error) { 37 | const err = error as Error; 38 | console.log(err); 39 | alertActions.error('解密失败!'); 40 | } 41 | }; 42 | 43 | return ( 44 | 45 | <> 46 | 47 | 48 | 49 | 50 | 63 | 64 | 73 | } 77 | sx={{ mb: 2 }} 78 | > 79 | 加密 80 | 81 | } 85 | > 86 | 解密 87 | 88 | 89 | 90 | 103 | 104 | 105 | 106 | 107 | > 108 | 109 | ); 110 | }; 111 | 112 | export default MorseCodeTranslator; 113 | -------------------------------------------------------------------------------- /src/pages/ocr.tsx: -------------------------------------------------------------------------------- 1 | import alertActions from '@/components/Alert'; 2 | import MainContent from '@/components/MainContent'; 3 | import { Box, Button } from '@mui/material'; 4 | import { useCallback, useState } from 'react'; 5 | import CopyToClipboard from 'react-copy-to-clipboard'; 6 | import { createWorker } from 'tesseract.js'; 7 | 8 | const sxWrap = { 9 | fontSize: '12px', 10 | p: 2, 11 | mt: 2, 12 | borderRadius: '4px', 13 | backgroundColor: 'rgba(0, 0, 0, 0.05)', 14 | color: 'rgba(0, 0, 0, 0.5)', 15 | }; 16 | 17 | const _C = () => { 18 | const [text, setText] = useState(''); 19 | const [error, setError] = useState(''); 20 | const [status, setStatus] = useState(''); 21 | 22 | const upload = () => { 23 | const fileInput = document.getElementById('fileInput'); 24 | fileInput?.click(); 25 | }; 26 | 27 | const copy = useCallback(() => { 28 | alertActions.success('复制成功'); 29 | }, []); 30 | 31 | const convert = async (e: React.ChangeEvent) => { 32 | const file = e.target.files?.[0]; 33 | if (file) { 34 | setText(''); 35 | setError(''); 36 | try { 37 | const imgUrl = URL.createObjectURL(file); 38 | 39 | const worker = await createWorker( 40 | 'eng+chi_sim+rus+deu+fra+jpn+kor', 41 | 1, 42 | { 43 | logger: (m) => { 44 | setStatus( 45 | JSON.stringify({ 46 | status: m.status, 47 | progress: (m.progress * 100).toFixed(2) + '%', 48 | }) 49 | ); 50 | }, 51 | } 52 | ); 53 | const res: any = await worker.recognize(imgUrl); 54 | setText(res.data.text); 55 | } catch (error) { 56 | setError(String(error)); 57 | } 58 | } 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 70 | 上传图片 71 | 72 | 80 | 81 | {text 82 | ? '识别成功' 83 | : status || '点击上方按钮,上传图片即可识别图片中的文字!'} 84 | 85 | {text && ( 86 | 87 | 88 | {text} 89 | 90 | 91 | 101 | 复制 102 | 103 | 104 | 105 | )} 106 | {error && {error}} 107 | 108 | 109 | ); 110 | }; 111 | 112 | export default _C; 113 | -------------------------------------------------------------------------------- /src/pages/pyc2asm.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import { Box, Button } from '@mui/material'; 3 | import TextFieldWithCopy from '@/components/TextFieldWithCopy'; 4 | import React, { useCallback, useEffect, useState } from 'react'; 5 | import { styled } from '@mui/material/styles'; 6 | 7 | import wasm from '@/asset/wasm/pycdas.js'; 8 | 9 | const VisuallyHiddenInput = styled('input')({ 10 | clip: 'rect(0 0 0 0)', 11 | clipPath: 'inset(50%)', 12 | height: 1, 13 | overflow: 'hidden', 14 | position: 'absolute', 15 | bottom: 0, 16 | left: 0, 17 | whiteSpace: 'nowrap', 18 | width: 1, 19 | }); 20 | 21 | const Pyc2py: React.FC = () => { 22 | useEffect(() => { 23 | console.log(wasm); 24 | }, []); 25 | const [fileName, setFileName] = useState(''); 26 | const [result, setResult] = useState('未加载文件'); 27 | 28 | const matchContent = (filename: string, arr: ArrayBuffer) => { 29 | var result = wasm.ccall( 30 | 'decompile', 31 | 'string', 32 | ['string', 'array', 'number'], 33 | [filename, new Uint8Array(arr, 0, arr.byteLength), arr.byteLength] 34 | ); 35 | return result; 36 | }; 37 | 38 | const handleSelectFile = useCallback( 39 | (event: React.ChangeEvent) => { 40 | const files = event.target.files || []; 41 | if (files.length == 0) { 42 | return; 43 | } 44 | const file = files[0]; 45 | const reader = new FileReader(); 46 | reader.onload = function (e) { 47 | if (e.target !== null) { 48 | setResult(matchContent(file.name, e.target.result as ArrayBuffer)); 49 | } 50 | }; 51 | setFileName(file.name); 52 | reader.readAsArrayBuffer(file); 53 | }, 54 | [] 55 | ); 56 | 57 | return ( 58 | 59 | 60 | 65 | {fileName ? '已加载文件 ' + fileName : '加载文件'} 66 | 67 | 68 | 69 | {fileName ? ( 70 | 84 | ) : ( 85 | <>> 86 | )} 87 | 88 | 89 | ); 90 | }; 91 | 92 | export default Pyc2py; 93 | -------------------------------------------------------------------------------- /src/pages/pyc2py.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import { Box, Button } from '@mui/material'; 3 | import TextFieldWithCopy from '@/components/TextFieldWithCopy'; 4 | import React, { useCallback, useEffect, useState } from 'react'; 5 | import { styled } from '@mui/material/styles'; 6 | 7 | import wasm from '@/asset/wasm/pycdc.js'; 8 | 9 | const VisuallyHiddenInput = styled('input')({ 10 | clip: 'rect(0 0 0 0)', 11 | clipPath: 'inset(50%)', 12 | height: 1, 13 | overflow: 'hidden', 14 | position: 'absolute', 15 | bottom: 0, 16 | left: 0, 17 | whiteSpace: 'nowrap', 18 | width: 1, 19 | }); 20 | 21 | const Pyc2py: React.FC = () => { 22 | useEffect(() => { 23 | console.log(wasm); 24 | }, []); 25 | const [fileName, setFileName] = useState(''); 26 | const [result, setResult] = useState('未加载文件'); 27 | 28 | const matchContent = (filename: string, arr: ArrayBuffer) => { 29 | var result = wasm.ccall( 30 | 'decompile', 31 | 'string', 32 | ['string', 'array', 'number'], 33 | [filename, new Uint8Array(arr, 0, arr.byteLength), arr.byteLength] 34 | ); 35 | return result; 36 | }; 37 | 38 | const handleSelectFile = useCallback( 39 | (event: React.ChangeEvent) => { 40 | const files = event.target.files || []; 41 | if (files.length == 0) { 42 | return; 43 | } 44 | const file = files[0]; 45 | const reader = new FileReader(); 46 | reader.onload = function (e) { 47 | if (e.target !== null) { 48 | setResult(matchContent(file.name, e.target.result as ArrayBuffer)); 49 | } 50 | }; 51 | setFileName(file.name); 52 | reader.readAsArrayBuffer(file); 53 | }, 54 | [] 55 | ); 56 | 57 | return ( 58 | 59 | 60 | 65 | {fileName ? '已加载文件 ' + fileName : '加载文件'} 66 | 67 | 68 | 69 | {fileName ? ( 70 | 84 | ) : ( 85 | <>> 86 | )} 87 | 88 | 89 | ); 90 | }; 91 | 92 | export default Pyc2py; 93 | -------------------------------------------------------------------------------- /src/pages/random_ip.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import { 3 | Box, 4 | Grid, 5 | Slider, 6 | Paper, 7 | Stack, 8 | Button, 9 | Typography, 10 | } from '@mui/material'; 11 | 12 | import React, { useCallback, useEffect, useState } from 'react'; 13 | 14 | function generateRandomIP() { 15 | const part1 = Math.floor(Math.random() * 256); 16 | const part2 = Math.floor(Math.random() * 256); 17 | const part3 = Math.floor(Math.random() * 256); 18 | const part4 = Math.floor(Math.random() * 256); 19 | 20 | return `${part1}.${part2}.${part3}.${part4}`; 21 | } 22 | 23 | const RandomIP: React.FC = () => { 24 | const [ipCount, setIPCount] = useState(44); 25 | const [ipList, setIPList] = useState>([]); 26 | 27 | const generateIPs = (count: number) => { 28 | const newIPList = []; 29 | for (let i = 0; i < count; i++) { 30 | newIPList.push(generateRandomIP()); 31 | } 32 | return newIPList; 33 | }; 34 | 35 | const handleCountChange = useCallback( 36 | (event: Event, newValue: number | number[]) => { 37 | const count = newValue as number; 38 | setIPCount(count); 39 | setIPList(generateIPs(count)); 40 | }, 41 | [] 42 | ); 43 | 44 | const copyToClipboard = () => { 45 | const textToCopy = ipList.join('\n'); 46 | navigator.clipboard.writeText(textToCopy); 47 | }; 48 | 49 | // Generate default IPs on component mount 50 | useEffect(() => { 51 | setIPList(generateIPs(ipCount)); 52 | }, []); 53 | 54 | return ( 55 | 56 | 57 | 64 | 65 | 73 | 74 | IP 数量 75 | 76 | 77 | 85 | 86 | 87 | 88 | 一键复制 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {ipList.map((ip, index) => ( 97 | 98 | 102 | {ip} 103 | 104 | 105 | ))} 106 | 107 | 108 | 109 | 110 | ); 111 | }; 112 | 113 | export default RandomIP; 114 | -------------------------------------------------------------------------------- /src/pages/random_ua.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useEffect } from 'react'; 2 | import MainContent from '@/components/MainContent'; 3 | 4 | import { Box, Typography, Slider, Button, Grid } from '@mui/material'; 5 | import alert from '@/components/Alert'; 6 | 7 | const Browsers = [ 8 | 'Mozilla/5.0', 9 | 'AppleWebKit/537.36', 10 | 'Chrome/90.0.4430.85', 11 | 'Safari/537.36', 12 | ]; 13 | const OSes = [ 14 | 'Windows NT 10.0; Win64; x64', 15 | 'Macintosh; Intel Mac OS X 10_15_7', 16 | 'X11; Linux x86_64', 17 | ]; 18 | const Versions = ['rv:11.0', 'KHTML, like Gecko', 'Edge/17.17134']; 19 | 20 | const UserAgentGenerator: React.FC = () => { 21 | const [agentsCount, setAgentsCount] = useState(26); 22 | const [userAgents, setUserAgents] = useState([]); 23 | 24 | useEffect(() => { 25 | generateAgents(); 26 | }, [agentsCount]); 27 | 28 | const changeAgentCount = useCallback( 29 | (event: Event, newValue: number | number[]) => { 30 | setAgentsCount(newValue as number); 31 | }, 32 | [] 33 | ); 34 | 35 | const generateAgents = useCallback(() => { 36 | let agents = []; 37 | for (let i = 0; i < agentsCount; i++) { 38 | const Browser = Browsers[Math.floor(Math.random() * Browsers.length)]; 39 | const OS = OSes[Math.floor(Math.random() * OSes.length)]; 40 | const Version = Versions[Math.floor(Math.random() * Versions.length)]; 41 | const userAgentString = `${Browser} (compatible; ${OS}) ${Version}`; 42 | agents.push(userAgentString); 43 | } 44 | setUserAgents(agents); 45 | }, [agentsCount]); 46 | 47 | const copyToClipboard = useCallback(() => { 48 | const element = document.createElement('textarea'); 49 | element.value = userAgents.join('\n'); 50 | document.body.appendChild(element); 51 | element.select(); 52 | document.execCommand('copy'); 53 | document.body.removeChild(element); 54 | alert.success('已复制到剪切板'); 55 | }, [userAgents]); 56 | 57 | return ( 58 | 59 | 60 | 61 | 62 | 70 | 71 | 72 | 共 {agentsCount} 个 73 | 74 | 75 | 76 | 88 | 95 | 一键复制 96 | 97 | {userAgents.map((agent, index) => ( 98 | {agent} 99 | ))} 100 | 101 | 102 | 103 | ); 104 | }; 105 | 106 | export default UserAgentGenerator; 107 | -------------------------------------------------------------------------------- /src/pages/shuffle_text_generator.tsx: -------------------------------------------------------------------------------- 1 | import alert from '@/components/Alert'; 2 | import MainContent from '@/components/MainContent'; 3 | import TextFieldWithClean from '@/components/TextFieldWithClean'; 4 | import { Button, Stack } from '@mui/material'; 5 | import React, { useState } from 'react'; 6 | import CopyToClipboard from 'react-copy-to-clipboard'; 7 | import ContentCopyIcon from '@mui/icons-material/ContentCopy'; 8 | 9 | const ShuffleTextGenerator: React.FC = () => { 10 | const [text, setText] = useState(''); 11 | const [randomText, setRandomText] = useState(''); 12 | 13 | const handleRandomText = () => { 14 | const arr = text.split('\n'); 15 | const randomArr = arr.map((item) => 16 | Array.from(item) 17 | .sort(() => 0.5 - Math.random()) 18 | .join('') 19 | ); 20 | setRandomText(randomArr.join('\n')); 21 | }; 22 | const handleRandomParagraph = () => { 23 | const arr = text.split('\n'); 24 | const randomArr = arr.sort(() => 0.5 - Math.random()); 25 | setRandomText(randomArr.join('\n')); 26 | }; 27 | const handleCopyClick = () => { 28 | alert.success('已复制到剪切板'); 29 | }; 30 | return ( 31 | 32 | 33 | { 41 | setText(event.target.value); 42 | }} 43 | onClean={() => { 44 | setText(''); 45 | }} 46 | sx={{ input: { fontSize: '14px', fontFamily: 'Mono' } }} 47 | /> 48 | 49 | 54 | 文字乱序 55 | 56 | 61 | 段落乱序 62 | 63 | 64 | 65 | 结果: 66 | 67 | {randomText} 68 | 69 | 74 | 75 | 76 | 77 | 78 | ); 79 | }; 80 | 81 | export default ShuffleTextGenerator; 82 | -------------------------------------------------------------------------------- /src/pages/tsfmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import Formater, { Mode } from '@/components/Formater'; 3 | import React from 'react'; 4 | 5 | const mock = 6 | "interface MyInterface {\n foo(): string,\n bar: Array,\n}\n\nexport abstract class Foo implements MyInterface {\n foo() {\n // TODO: return an actual value here\n return 'hello'\n }\n get bar() {\n return [ 1,\n\n 2, 3,\n ]\n }\n}\n\ntype RequestType = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'OPTIONS' | 'CONNECT' | 'DELETE' | 'TRACE'\n"; 7 | 8 | const TSFmt: React.FC = () => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default TSFmt; 17 | -------------------------------------------------------------------------------- /src/pages/uncolor.tsx: -------------------------------------------------------------------------------- 1 | import ImageDownload from '@/components/ImageDownload'; 2 | import MainContent from '@/components/MainContent'; 3 | import { Box, Button, Stack } from '@mui/material'; 4 | 5 | import { styled } from '@mui/material/styles'; 6 | import React, { useCallback, useState } from 'react'; 7 | 8 | const VisuallyHiddenInput = styled('input')({ 9 | clip: 'rect(0 0 0 0)', 10 | clipPath: 'inset(50%)', 11 | height: 1, 12 | overflow: 'hidden', 13 | position: 'absolute', 14 | bottom: 0, 15 | left: 0, 16 | whiteSpace: 'nowrap', 17 | width: 1, 18 | }); 19 | 20 | const UploadImg = styled('img')({ 21 | maxHeight: '100%', 22 | maxWidth: '100%', 23 | }); 24 | 25 | const MySpan = styled('span')({}); 26 | 27 | const ImgBase64: React.FC = () => { 28 | const [imageIn, setimageIn] = useState(''); 29 | const [uncolorOut, setuncolorOut] = useState(''); 30 | 31 | const handleSelectFile = useCallback( 32 | (event: React.ChangeEvent) => { 33 | const files = event.target.files || []; 34 | if (files.length == 0) { 35 | return; 36 | } 37 | const reader = new FileReader(); 38 | reader.onload = function (e) { 39 | if (e.target == null) { 40 | return; 41 | } 42 | setimageIn(e.target.result as string); 43 | // 获取加载的图像数据 44 | const imgData = e.target.result; 45 | if (typeof imgData !== 'string') { 46 | return; 47 | } 48 | const img = new Image(); 49 | img.onload = function () { 50 | const canvas = document.createElement('canvas'); 51 | const ctx = canvas.getContext('2d'); 52 | if (ctx == null) { 53 | return; 54 | } 55 | canvas.width = img.width; 56 | canvas.height = img.height; 57 | ctx.drawImage(img, 0, 0, img.width, img.height); 58 | // 将图像转换为黑白 59 | const imgArrData = ctx.getImageData(0, 0, img.width, img.height); 60 | for (let i = 0; i < imgArrData.data.length; i += 4) { 61 | let r = imgArrData.data[i], 62 | g = imgArrData.data[i + 1], 63 | b = imgArrData.data[i + 2]; 64 | const avg = (r + g + b) / 3; 65 | imgArrData.data[i] = 66 | imgArrData.data[i + 1] = 67 | imgArrData.data[i + 2] = 68 | avg; 69 | } 70 | console.log(imgArrData); 71 | ctx.putImageData(imgArrData, 0, 0); 72 | const blackAndWhiteDataURL = canvas.toDataURL(); 73 | console.log('Black and white image:', blackAndWhiteDataURL); 74 | setuncolorOut(blackAndWhiteDataURL); 75 | }; 76 | img.src = imgData; 77 | }; 78 | reader.readAsDataURL(files[0]); 79 | }, 80 | [] 81 | ); 82 | 83 | return ( 84 | 85 | 86 | 87 | 92 | {imageIn ? : 选择图片} 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ); 101 | }; 102 | 103 | export default ImgBase64; 104 | -------------------------------------------------------------------------------- /src/pages/urlencoder.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import TextFieldWithClean from '@/components/TextFieldWithClean'; 3 | import TextFieldWithCopy from '@/components/TextFieldWithCopy'; 4 | import TabContext from '@mui/lab/TabContext'; 5 | import TabList from '@mui/lab/TabList'; 6 | import { Box, Tab } from '@mui/material'; 7 | import React, { useCallback, useMemo, useState } from 'react'; 8 | 9 | const URLEncoder: React.FC = () => { 10 | const [method, setMethod] = React.useState('encode'); 11 | const [input, setInput] = useState(''); 12 | const [output, setOutput] = useState(''); 13 | 14 | const funcMap = useMemo(() => { 15 | const m = new Map(); 16 | m.set('encode', encodeURIComponent); 17 | m.set('decode', decodeURIComponent); 18 | return m; 19 | }, []); 20 | 21 | const handleChange = (event: React.SyntheticEvent, method: string) => { 22 | const newInput = output; 23 | setInput(newInput); 24 | setMethod(method); 25 | var fn = funcMap.get(method); 26 | if (fn) { 27 | try { 28 | setOutput(fn(newInput)); 29 | } catch (e) { 30 | setOutput('输入有误, 解码失败'); 31 | } 32 | } 33 | }; 34 | 35 | const handleInputChanged = useCallback( 36 | (event: React.ChangeEvent) => { 37 | var value = event.target.value; 38 | setInput(value); 39 | var fn = funcMap.get(method); 40 | if (fn) { 41 | try { 42 | setOutput(fn(value)); 43 | } catch (e) { 44 | setOutput('输入有误, 解码失败'); 45 | } 46 | } 47 | }, 48 | [funcMap, method] 49 | ); 50 | 51 | const handleCleanClick = useCallback(() => { 52 | setInput(''); 53 | setOutput(''); 54 | }, []); 55 | 56 | return ( 57 | 58 | <> 59 | 60 | 61 | 62 | 67 | 72 | 73 | 74 | 75 | 90 | 106 | > 107 | 108 | ); 109 | }; 110 | 111 | export default URLEncoder; 112 | -------------------------------------------------------------------------------- /src/pages/video2gif.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import dynamic from 'next/dynamic'; 3 | 4 | const _C = () => { 5 | const Video2Gif = dynamic(() => import('@/components/Dynamic/Video2Gif'), { 6 | ssr: false, 7 | }); 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default _C; 16 | -------------------------------------------------------------------------------- /src/pages/videofmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import dynamic from 'next/dynamic'; 3 | 4 | const VideoFMT = () => { 5 | const VideoFmt = dynamic(() => import('@/components/Dynamic/VideoFmt'), { 6 | ssr: false, 7 | }); 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default VideoFMT; 16 | -------------------------------------------------------------------------------- /src/pages/videoframe.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import dynamic from 'next/dynamic'; 3 | 4 | const VideoFrame = () => { 5 | const Frame = dynamic(() => import('@/components/Dynamic/Frame'), { 6 | ssr: false, 7 | }); 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default VideoFrame; 16 | -------------------------------------------------------------------------------- /src/pages/xmlfmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import Formater, { Mode } from '@/components/Formater'; 3 | import React from 'react'; 4 | 5 | const mock = 'Chaitin'; 6 | 7 | const XMLFmt: React.FC = () => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default XMLFmt; 16 | -------------------------------------------------------------------------------- /src/pages/yamlfmt.tsx: -------------------------------------------------------------------------------- 1 | import MainContent from '@/components/MainContent'; 2 | import Formater, { Mode } from '@/components/Formater'; 3 | import React from 'react'; 4 | 5 | const mock = 'a : test\nb:\n- b1\n- b2'; 6 | 7 | const YamlFmt: React.FC = () => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default YamlFmt; 16 | -------------------------------------------------------------------------------- /src/styles/colors.ts: -------------------------------------------------------------------------------- 1 | export const primary = 'rgba(52, 90, 255, 1)'; 2 | export const primaryHover = '#73D13C'; 3 | export const primaryClick = '#389E0E'; 4 | 5 | export const primaryLight = '#DDF4D2'; 6 | export const primaryLightGradient = 7 | 'linear-gradient(202deg, rgba(166,216,73,0.1) 0%, rgba(116,193,89,0.1) 100%);'; 8 | 9 | export const secondary = '#ECF9E6'; 10 | export const secondaryHover = '#F3FDEE'; 11 | export const secondaryClick = '#DAE8D4'; 12 | 13 | export const special = 14 | 'linear-gradient(270deg, #11AF60 0%, rgba(52, 90, 255, 1) 100%)'; 15 | export const dangerHover = 'linear-gradient(225deg, #FF1F1F 0%, #F78900 100%)'; 16 | 17 | export const disabled = '#F7F7F7'; 18 | export const shadowColor = '#041B0F'; 19 | 20 | export const defaultText = '#041B0F'; 21 | export const defaultTextHover = '#0E2919'; 22 | export const defaultTextClick = '#000000'; 23 | 24 | export const secondaryText = '#041B0F99'; 25 | export const secondaryTextHover = '#737373'; 26 | export const secondaryTextClick = '#404040'; 27 | 28 | export const leastText = '#041B0F66'; 29 | export const leastTextHover = '#A6A6A6'; 30 | export const leastTextClick = '#737373'; 31 | 32 | export const disabledText = '#BFBFBF'; 33 | export const specialText = '#737971'; 34 | 35 | export const success = '#389E0E'; 36 | export const warning = '#FFBF00'; 37 | export const error = '#FF1F1F'; 38 | 39 | export const grayText = '#0B310440'; 40 | export const grayText2 = '#97A4B0'; 41 | export const errorText = '#FF1F1F'; 42 | export const infoText = '#2F7CE9'; 43 | 44 | export const gray = '#DADCE0'; 45 | export const grayLight = '#F8F8F8'; 46 | export const grayLabel = '#717579'; 47 | export const hover = 'rgba(82, 196, 26, 0.8)'; 48 | export const hoverLight = 'rgba(82, 196, 26, 0.11)'; 49 | 50 | export const darkBg = '#072120'; 51 | export const grayBg = '#F3F4F7'; 52 | 53 | export const grayBorder = '#E7EAEF'; 54 | 55 | export const fallbackBg = '#041B0F14'; 56 | 57 | //之后替换掉 58 | export const Side_Margin = '8%'; 59 | export const Banner_Animation_Time = 0.5; 60 | export const boxShadow = '0 4px 14px 0 #1A041B0F'; 61 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | #cnzz_stat_icon_1281132544 { 2 | display: none; 3 | } 4 | 5 | #SM_BTN_1 { 6 | width: auto !important; 7 | background-color: rgba(52, 90, 255, 1) !important; 8 | } 9 | 10 | .sm-txt { 11 | color: #fff !important; 12 | } 13 | 14 | .impowerBox .qrcode { 15 | width: 180px !important; 16 | } 17 | 18 | .impowerBox .title { 19 | display: none !important; 20 | } 21 | #__next { 22 | height: 100%; 23 | } 24 | 25 | .custom-link { 26 | text-decoration: none; /* 去除下划线 */ 27 | color: unset; 28 | } 29 | 30 | .custom-link:hover { 31 | text-decoration: unset; /* 设置鼠标悬停时链接文本的下划线 */ 32 | color: unset; 33 | } 34 | 35 | #root, 36 | body, 37 | html { 38 | overflow: auto !important; 39 | background: rgba(247, 249, 252, 1); 40 | } 41 | 42 | @font-face { 43 | font-family: 'Mono'; 44 | font-weight: 250; 45 | src: url('/tools/Mono.ttf') format('truetype'); 46 | font-display: swap; 47 | } 48 | -------------------------------------------------------------------------------- /src/styles/static/effect-creative.min.css: -------------------------------------------------------------------------------- 1 | .swiper-creative .swiper-slide { 2 | -webkit-backface-visibility: hidden; 3 | backface-visibility: hidden; 4 | overflow: hidden; 5 | transition-property: transform, opacity, height; 6 | } 7 | -------------------------------------------------------------------------------- /src/styles/static/navigation.min.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --swiper-navigation-size: 44px; 3 | } 4 | .swiper-button-next, 5 | .swiper-button-prev { 6 | position: absolute; 7 | top: var(--swiper-navigation-top-offset, 50%); 8 | width: calc(var(--swiper-navigation-size) / 44 * 27); 9 | height: var(--swiper-navigation-size); 10 | margin-top: calc(0px - (var(--swiper-navigation-size) / 2)); 11 | z-index: 10; 12 | cursor: pointer; 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | color: var(--swiper-navigation-color, var(--swiper-theme-color)); 17 | } 18 | .swiper-button-next.swiper-button-disabled, 19 | .swiper-button-prev.swiper-button-disabled { 20 | opacity: 0.35; 21 | cursor: auto; 22 | pointer-events: none; 23 | } 24 | .swiper-button-next.swiper-button-hidden, 25 | .swiper-button-prev.swiper-button-hidden { 26 | opacity: 0; 27 | cursor: auto; 28 | pointer-events: none; 29 | } 30 | .swiper-navigation-disabled .swiper-button-next, 31 | .swiper-navigation-disabled .swiper-button-prev { 32 | display: none !important; 33 | } 34 | .swiper-button-next:after, 35 | .swiper-button-prev:after { 36 | font-family: swiper-icons; 37 | font-size: var(--swiper-navigation-size); 38 | text-transform: none !important; 39 | letter-spacing: 0; 40 | font-variant: initial; 41 | line-height: 1; 42 | } 43 | .swiper-button-prev, 44 | .swiper-rtl .swiper-button-next { 45 | left: var(--swiper-navigation-sides-offset, 10px); 46 | right: auto; 47 | } 48 | .swiper-button-prev:after, 49 | .swiper-rtl .swiper-button-next:after { 50 | content: 'prev'; 51 | } 52 | .swiper-button-next, 53 | .swiper-rtl .swiper-button-prev { 54 | right: var(--swiper-navigation-sides-offset, 10px); 55 | left: auto; 56 | } 57 | .swiper-button-next:after, 58 | .swiper-rtl .swiper-button-prev:after { 59 | content: 'next'; 60 | } 61 | .swiper-button-lock { 62 | display: none; 63 | } 64 | -------------------------------------------------------------------------------- /src/styles/static/slick.css: -------------------------------------------------------------------------------- 1 | /* Slider */ 2 | .slick-slider { 3 | position: relative; 4 | 5 | display: block; 6 | box-sizing: border-box; 7 | 8 | -webkit-user-select: none; 9 | -moz-user-select: none; 10 | -ms-user-select: none; 11 | user-select: none; 12 | 13 | -webkit-touch-callout: none; 14 | -khtml-user-select: none; 15 | -ms-touch-action: pan-y; 16 | touch-action: pan-y; 17 | -webkit-tap-highlight-color: transparent; 18 | } 19 | 20 | .slick-list { 21 | position: relative; 22 | 23 | display: block; 24 | overflow: hidden; 25 | 26 | margin: 0; 27 | padding: 0; 28 | } 29 | .slick-list:focus { 30 | outline: none; 31 | } 32 | .slick-list.dragging { 33 | cursor: pointer; 34 | cursor: hand; 35 | } 36 | 37 | .slick-slider .slick-track, 38 | .slick-slider .slick-list { 39 | -webkit-transform: translate3d(0, 0, 0); 40 | -moz-transform: translate3d(0, 0, 0); 41 | -ms-transform: translate3d(0, 0, 0); 42 | -o-transform: translate3d(0, 0, 0); 43 | transform: translate3d(0, 0, 0); 44 | } 45 | 46 | .slick-track { 47 | position: relative; 48 | top: 0; 49 | left: 0; 50 | 51 | display: flex; 52 | margin-left: auto; 53 | margin-right: auto; 54 | } 55 | .slick-track:before, 56 | .slick-track:after { 57 | display: table; 58 | 59 | content: ''; 60 | } 61 | .slick-track:after { 62 | clear: both; 63 | } 64 | .slick-loading .slick-track { 65 | visibility: hidden; 66 | } 67 | 68 | .slick-slide { 69 | display: none; 70 | float: left; 71 | 72 | height: 100%; 73 | min-height: 1px; 74 | } 75 | [dir='rtl'] .slick-slide { 76 | float: right; 77 | } 78 | .slick-slide img { 79 | display: block; 80 | } 81 | .slick-slide.slick-loading img { 82 | display: none; 83 | } 84 | .slick-slide.dragging img { 85 | pointer-events: none; 86 | } 87 | .slick-initialized .slick-slide { 88 | display: block; 89 | } 90 | .slick-loading .slick-slide { 91 | visibility: hidden; 92 | } 93 | .slick-vertical .slick-slide { 94 | display: block; 95 | 96 | height: auto; 97 | 98 | border: 1px solid transparent; 99 | } 100 | .slick-arrow.slick-hidden { 101 | display: none; 102 | } 103 | -------------------------------------------------------------------------------- /src/styles/static/swiper-customize.css: -------------------------------------------------------------------------------- 1 | .swiper-center { 2 | /* margin-left: 25%; 3 | padding:0 48px; */ 4 | justify-content: center; 5 | display: flex; 6 | } 7 | -------------------------------------------------------------------------------- /src/types/app.ts: -------------------------------------------------------------------------------- 1 | import { SortType } from './general'; 2 | 3 | export type QueryInfo = { 4 | page?: number; 5 | size?: number; 6 | role?: string; 7 | permission?: string; 8 | nickname?: string; 9 | mail?: string; 10 | name?: string; 11 | }; 12 | 13 | export type GetApplications = { 14 | org_id: number; 15 | query?: { order_by_latest?: SortType; order_by_hot?: SortType }; 16 | }; 17 | 18 | export type ApplicationInfo = { 19 | id: number; 20 | category: string; 21 | scope: string; 22 | purchase_type: string; 23 | url_prefix: string; 24 | name: string; 25 | description: string; 26 | latest_news: string[]; 27 | screenshots: string[]; 28 | expired_at: string; 29 | logo_url: string; 30 | landing_page: string; 31 | slogan: string; 32 | labels: string[]; 33 | status: number; //0:下架,1: 已上线 ,2: 未上线 34 | url?: string; 35 | cnt?: number; 36 | modules: AppModuleProps[]; 37 | doc_url: string; 38 | }; 39 | export type AppModuleProps = { 40 | module_id: number; 41 | temp_id: 1 | 2 | 3 | 4; 42 | module_name: string; 43 | urls: string[]; 44 | categories: AppModuleCategoriyProps[]; 45 | }; 46 | 47 | export type AppModuleCategoriyProps = { 48 | category_id: number; 49 | category_name: string; 50 | description: string; 51 | promotional_urls: string[]; 52 | detail_url: string; 53 | }; 54 | 55 | export type SubscriptionInfo = { 56 | id: number; // app id 57 | org_id: number; 58 | package_id: number; 59 | }; 60 | 61 | export type ApplicationShareInfo = { 62 | app_id: number; 63 | code?: string; 64 | app_name: string; 65 | logo_url: string; 66 | hit_count: number; 67 | register_count: number; 68 | hit_count_history: number; 69 | register_count_history: number; 70 | url: string; 71 | remark: string; 72 | }; 73 | 74 | export type GenerateShareCodeInfo = { 75 | app_id: number; 76 | remark: string; 77 | }; 78 | -------------------------------------------------------------------------------- /src/types/auth.ts: -------------------------------------------------------------------------------- 1 | export type UserLogin = { 2 | nickname: string; 3 | password: string; 4 | mail: string; 5 | code: number; 6 | }; 7 | 8 | export type WeChatAuth = { 9 | code: string; 10 | state: string; 11 | }; 12 | 13 | export type WeChatInfo = { 14 | appId: string; 15 | redirectUrl: string; 16 | state: string; 17 | }; 18 | -------------------------------------------------------------------------------- /src/types/data.ts: -------------------------------------------------------------------------------- 1 | export interface NavMenuProps { 2 | title: string; 3 | subTitle?: string; 4 | expand?: boolean; 5 | link?: string; 6 | items?: NavMenuProps[]; 7 | } 8 | 9 | export type ButtonActions = 'hover' | 'click'; 10 | 11 | export interface ButtonProps { 12 | text: string; 13 | link?: string; 14 | action?: ButtonActions; 15 | color?: string; 16 | hoverColor?: string; 17 | } 18 | 19 | export interface BannerDataProps { 20 | title: string; 21 | subTitle?: string; 22 | buttons?: ButtonProps[]; 23 | bannerUrl?: string; 24 | link?: string; 25 | } 26 | 27 | export interface KeywordDataProps { 28 | title: string; 29 | subTitle: string; 30 | link: string; 31 | } 32 | 33 | export interface ProductItemProps { 34 | title: string; 35 | subTitle: string; 36 | description?: string; 37 | keywords?: string; 38 | buttons?: ButtonProps[]; 39 | } 40 | 41 | export interface ProductsDataProps { 42 | title: string; 43 | subTitle?: string; 44 | items?: ProductItemProps[]; 45 | } 46 | 47 | export interface OpenApiDataItemProps extends KeywordDataProps { 48 | button: ButtonProps; 49 | } 50 | 51 | export interface OpenApiDataProps { 52 | title: string; 53 | subTitle?: string; 54 | items?: OpenApiDataItemProps[]; 55 | } 56 | 57 | export interface FooterDataProps { 58 | title: string; 59 | buttons?: ButtonProps[]; 60 | items?: Partial[]; 61 | } 62 | -------------------------------------------------------------------------------- /src/types/declare.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' { 2 | const value: string; 3 | export default value; 4 | } 5 | 6 | declare module '*.webp' { 7 | const value: string; 8 | export default value; 9 | } 10 | 11 | declare module '*.svg' { 12 | import React from 'react'; 13 | const SVG: React.FC>; 14 | export default SVG; 15 | } 16 | 17 | declare module '*.gif' { 18 | const value: string; 19 | export default value; 20 | } 21 | 22 | declare module '*.json' { 23 | const value: unknown; 24 | export default value; 25 | } 26 | 27 | declare module '*.ttf' { 28 | const value: string; 29 | export default value; 30 | } 31 | 32 | declare module '*.otf' { 33 | const value: string; 34 | export default value; 35 | } 36 | 37 | declare module '*.woff' { 38 | const value: string; 39 | export default value; 40 | } 41 | 42 | declare module '*.woff2' { 43 | const value: string; 44 | export default value; 45 | } 46 | 47 | declare const PARTNER: string; 48 | 49 | type SocketClient = { 50 | ids: number; 51 | on: (event: string, data?: unknown) => void; 52 | emit: (event: string, data?: unknown) => void; 53 | connected: boolean; 54 | disconnect: () => void; 55 | }; 56 | 57 | declare module 'nextPage/nextpage'; 58 | declare module '@chaitin_rivers/excalidraw'; 59 | declare module 'gofmt.js'; 60 | declare module 'uuid'; 61 | declare module 'apache-crypt'; 62 | declare module 'bcryptjs'; 63 | declare module 'jsfuck'; 64 | -------------------------------------------------------------------------------- /src/types/general.ts: -------------------------------------------------------------------------------- 1 | import type { SxProps } from '@mui/material/styles'; 2 | 3 | export type ListResponse = { 4 | total: number; 5 | count?: number; 6 | data: Array; 7 | }; 8 | 9 | export type AppListResponse = { 10 | total: number; 11 | apps: Array; 12 | }; 13 | 14 | export type OrderListResponse = { 15 | total_page: number; 16 | total: number; 17 | detail: Array; 18 | }; 19 | 20 | export type SortType = 'asc' | 'desc'; 21 | 22 | export type BasicPaginationParams = { 23 | page: number; 24 | size: number; 25 | }; 26 | 27 | export type BaseComponentProps = { 28 | children: React.ReactNode; 29 | sx?: SxProps; 30 | id?: string; 31 | }; 32 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './navbar'; 2 | export * from './auth'; 3 | export * from './user'; 4 | export * from './org'; 5 | export * from './general'; 6 | export * from './app'; 7 | export * from './ticket'; 8 | export * from './orders'; 9 | export * from './notifications'; 10 | -------------------------------------------------------------------------------- /src/types/navbar.ts: -------------------------------------------------------------------------------- 1 | export interface NavMenuProps { 2 | title: string; 3 | subTitle?: string; 4 | expand?: boolean; 5 | link?: string; 6 | items?: NavMenuProps[]; 7 | } 8 | 9 | export type ButtonActions = 'hover' | 'click'; 10 | 11 | export interface ButtonProps { 12 | text: string; 13 | link?: string; 14 | action?: ButtonActions; 15 | color?: string; 16 | hoverColor?: string; 17 | clickColor?: string; 18 | } 19 | 20 | export interface BannerDataProps { 21 | title: string; 22 | subTitle?: string; 23 | buttons?: ButtonProps[]; 24 | bannerUrl?: string; 25 | link?: string; 26 | promotionTitleIcon?: string; 27 | } 28 | 29 | export interface KeywordDataProps { 30 | title: string; 31 | subTitle: string; 32 | link?: string; 33 | } 34 | 35 | export interface ProductItemProps { 36 | title: string; 37 | subTitle: string; 38 | description?: string; 39 | keywords?: string; 40 | buttons?: ButtonProps[]; 41 | } 42 | 43 | export interface ProductsDataProps { 44 | title: string; 45 | subTitle?: string; 46 | items?: ProductItemProps[]; 47 | } 48 | 49 | export interface OpenApiDataItemProps extends KeywordDataProps { 50 | button: ButtonProps; 51 | } 52 | 53 | export interface OpenApiDataProps { 54 | title: string; 55 | subTitle?: string; 56 | items?: OpenApiDataItemProps[]; 57 | } 58 | 59 | export interface FooterDataProps { 60 | title: string; 61 | buttons?: ButtonProps[]; 62 | items?: Partial[]; 63 | } 64 | -------------------------------------------------------------------------------- /src/types/notifications.ts: -------------------------------------------------------------------------------- 1 | import { BasicPaginationParams } from './general'; 2 | export type NotificationLevel = 'high' | 'medium' | 'low'; 3 | 4 | export type NotificationType = 'app' | 'platform'; 5 | 6 | export type NotificationDetail = { 7 | id: number; 8 | content_id?: number; 9 | simple_text: string; 10 | send_id: number; 11 | send_name: string; 12 | tags: { id: number; name: string }[]; 13 | level: NotificationLevel; 14 | created_at: string; 15 | read_status: number; // 1未读 2已读 3已删除 16 | full_text?: string; 17 | org_name?: string; 18 | org_id?: number; 19 | logo_url?: string; 20 | }; 21 | 22 | export type NotificationsParams = BasicPaginationParams & { 23 | origin?: NotificationType; 24 | read_status?: number; // 1未读 2已读 3已删除 25 | level?: NotificationLevel; 26 | org_id?: number | string; 27 | send_id?: number | string; 28 | is_recently?: boolean; 29 | keywords?: string; 30 | }; 31 | 32 | export type NotificationListResponse = { 33 | total: number; 34 | count?: number; 35 | detail: Array; 36 | }; 37 | 38 | export type WebhookProps = { 39 | id?: string; 40 | address: string; 41 | sign?: string; 42 | }; 43 | 44 | export type MessageChannelTypes = 'email' | 'dingtalk' | 'lark' | 'wecom'; 45 | 46 | // 1:邮箱;2:钉钉;3:企业微信;4:飞书 47 | export enum MessageChannelEnum { 48 | email = 'email', 49 | dingtalk = 'dingtalk', 50 | wecom = 'wecom', 51 | lark = 'lark', 52 | wechat = 5, 53 | } 54 | 55 | export enum MessageLevelsEnum { 56 | low = '低', 57 | medium = '中', 58 | high = '高', 59 | } 60 | 61 | export type ForwardingConfigDetails = { 62 | id?: number; 63 | type?: ForwardingConfigType; 64 | desc: string; 65 | enabled?: 1 | 0; //0:off 1:on 66 | channels: ForwardingConfigChannelProps[]; 67 | created_at?: string; 68 | updated_at?: string; 69 | }; 70 | 71 | export type ForwardingConfigType = 'wechat' | 'normal'; 72 | 73 | export type ForwardingConfigChannelProps = { 74 | channel: string; 75 | addresses: WebhookProps[]; 76 | }; 77 | 78 | export type UnReadNotify = { 79 | total_number: number; 80 | app_total_number: number; 81 | plat_total_number: number; 82 | }; 83 | 84 | export type SocketMesageType = 'unread' | 'invite' | 'remove'; 85 | 86 | export type SocketMessage = { 87 | type: SocketMesageType; 88 | content: SocketMessageContent; 89 | }; 90 | 91 | export type SocketMessageContent = 92 | T extends 'unread' 93 | ? UnReadNotify 94 | : NotificationDetail & { sender_id: number }; 95 | 96 | export type SocketMessageData = UnReadNotify & { connected: boolean }; 97 | -------------------------------------------------------------------------------- /src/types/orders.ts: -------------------------------------------------------------------------------- 1 | import { BasicPaginationParams } from './index'; 2 | export type OrderDetails = { 3 | order_code: string; 4 | app_name: string; 5 | logo_url?: string; 6 | total_amount: number; 7 | real_amount: number; 8 | discount_amount: number; 9 | goods_detail: string; 10 | expire_time: string; 11 | expire_time_seconds: number; 12 | gmt_create: string; 13 | status: number; // 1:未支付;2:已支付;3:已退款;4:已取消 5:超时关闭 14 | pay_type: number; // 1:支付宝 15 | creator: number; 16 | creator_name: string; 17 | orgId: number; 18 | url?: string; //付款地址 19 | created_at: string; 20 | updated_at: string; 21 | deleted_at: string; 22 | redeem_code: string; 23 | }; 24 | 25 | export enum PayTypeEnum { 26 | default = 0, 27 | alipay = 1, 28 | wechat = 2, 29 | voucher = 3, 30 | } 31 | 32 | export enum PaymentStatusEnum { 33 | unpaid = 1, 34 | paid = 2, 35 | refunded = 3, 36 | canceled = 4, 37 | expaired = 5, 38 | } 39 | 40 | export type OrderGoodsDetail = { 41 | package_name: string; 42 | duration: number; 43 | purchase_duration_unit: 'D' | 'M' | 'Y'; 44 | items: { name: string; value: string }[]; 45 | }; 46 | 47 | export type GetOrderParams = BasicPaginationParams & { 48 | order_code?: string; 49 | status?: number; 50 | org_id: number; 51 | }; 52 | -------------------------------------------------------------------------------- /src/types/org.ts: -------------------------------------------------------------------------------- 1 | import { QueryInfo } from './index'; 2 | 3 | export type CreateOrg = { 4 | name: string; 5 | description?: string | undefined; 6 | }; 7 | 8 | export type Organizationinfo = { 9 | id: number; 10 | description: string; 11 | name: string; 12 | user_count: number; 13 | app_count: number; 14 | order_count: number; 15 | api_token_count: number; 16 | unpaid_count: number; 17 | }; 18 | 19 | export type MemberInfo = { 20 | id: string | number; 21 | u_id: string; 22 | name: string; 23 | nickname: string; 24 | roles: { id: number; name: string }[]; 25 | created_at: string; 26 | last_logined_at: string; 27 | confirm_state: string; 28 | description: string; 29 | mail: string; 30 | head_img_url?: string; 31 | subscribe_oa?: boolean; 32 | }; 33 | 34 | export type GetOrgMembers = { 35 | id: number; 36 | query: QueryInfo; 37 | }; 38 | 39 | export type DeleteOrgMember = { 40 | id: number; //org id 41 | query: { id: string }; 42 | }; 43 | 44 | export type InvitationInfo = { 45 | mail?: string; 46 | user_id: string; 47 | role_ids: number[]; 48 | }; 49 | 50 | export type InviteMembers = { 51 | id: number; // org id 52 | users: InvitationInfo[]; 53 | }; 54 | 55 | export type RoleType = 'builtin' | 'manual'; 56 | 57 | export type RoleInfo = { 58 | id: number; 59 | name: string; 60 | desc: string; 61 | create_user: string; 62 | update_at: string; 63 | perms: { 64 | id: number; 65 | name: string; 66 | }[]; 67 | type: RoleType; 68 | }; 69 | 70 | export type RoleCreateInfo = { 71 | name: string; 72 | perms: number[]; 73 | }; 74 | 75 | export type GetRoles = { 76 | id: number; //org id 77 | query: QueryInfo; 78 | }; 79 | 80 | export type DeleteRole = { 81 | id: number; // org id 82 | role_ids: number[]; 83 | }; 84 | 85 | export type UpdateUserRole = { 86 | id: number; // org id 87 | user_id: number; // user id 88 | query: Pick; 89 | }; 90 | 91 | export type PermissionInfo = { 92 | id: number; 93 | name: string; 94 | scope: string; 95 | description: string; 96 | }; 97 | 98 | export type UpdateRolePermission = { 99 | id: number; // org id 100 | role_id: number; 101 | perms: number[]; 102 | }; 103 | 104 | export type CreateRoles = { 105 | id: number; // org id 106 | roles: RoleCreateInfo[]; 107 | }; 108 | 109 | export type APITokenInfo = { 110 | id: number; 111 | name: string; 112 | token: string; 113 | perms: string[]; 114 | creator_name: string; 115 | creator_mail: string; 116 | expired_at: string; 117 | }; 118 | 119 | export type CrateAPITokenInfo = { 120 | name: string; 121 | perms: number[]; 122 | expired_at: string; 123 | }; 124 | 125 | export type UpdateOrganizationinfo = { 126 | id: number; //组织id 127 | info: { name: string; description: string }; 128 | }; 129 | -------------------------------------------------------------------------------- /src/types/ticket.ts: -------------------------------------------------------------------------------- 1 | export type TicketQueryInfo = { 2 | page?: number; 3 | size?: number; 4 | title?: string; 5 | state?: string; 6 | }; 7 | 8 | export type TicketType = '完成' | '等待处理' | '正在处理' | '关闭'; 9 | 10 | export type TicketInfo = { 11 | title: string; 12 | id: number; 13 | state: TicketType; 14 | type: string; 15 | app_name: string; 16 | created_at: string; 17 | updated_at: string; 18 | }; 19 | 20 | export type TicketStatus = { 21 | title: string; 22 | actions: { action: string; created_at: string }[]; 23 | }; 24 | 25 | export type DialogType = 'query' | 'reply'; //query 用户,reply 客服 26 | 27 | export type DialogInfo = { 28 | content: string; 29 | created_at: string; 30 | creator: number; // creator id 31 | images: string[]; 32 | type: DialogType; 33 | }; 34 | 35 | export type DialogReplyInfo = { 36 | id: number; // dialog id 37 | content: string; 38 | type: DialogType; 39 | images: string[]; 40 | }; 41 | 42 | export type CreateTicket = { 43 | title: string; 44 | content: string; 45 | ticket_type: 1 | 2 | 3; //1:故障;2:建议;3:其他 46 | app_id: number; 47 | images: string[]; 48 | }; 49 | -------------------------------------------------------------------------------- /src/types/user.ts: -------------------------------------------------------------------------------- 1 | export type UserInfo = { 2 | id: number; 3 | nickname: string; 4 | password: string; 5 | mail: string; 6 | passport: string; 7 | created_at: string; 8 | updated_at: string; 9 | wechat_nickname: string; 10 | head_img_url: string; 11 | is_partner: boolean; 12 | is_certified: number; // 1:已认证, 2:认证中,3:未认证 13 | phone_number: string; 14 | org_id: number; 15 | }; 16 | 17 | export type UpdateUserInfo = { 18 | mail?: string; 19 | old_password?: string; 20 | new_password?: string; 21 | confirm_password?: string; 22 | }; 23 | 24 | export type UserVerification = { 25 | cert_type: IdentificationType | ''; 26 | cert_no: string; 27 | cert_name: string; 28 | }; 29 | 30 | export type UserVerificationInfo = UserVerification & { 31 | create_at: string; 32 | certify_status: number; // 1:已认证, 2:认证中,3:未认证 33 | }; 34 | 35 | export enum IdentificationType { 36 | 'IDENTITY_CARD' = '中华人民共和国居民身份证', 37 | 'HOME_VISIT_PERMIT_HK_MC' = '港澳通行证', 38 | 'HOME_VISIT_PERMIT_TAIWAN' = '台湾通行证', 39 | 'RESIDENCE_PERMIT_HK_MC' = '港澳居住证', 40 | 'RESIDENCE_PERMIT_TAIWAN' = '台湾居住证', 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/bufferToWave.ts: -------------------------------------------------------------------------------- 1 | // Convert AudioBuffer to a Blob using WAVE representation 2 | export default function bufferToWave(abuffer: AudioBuffer, len: number) { 3 | var numOfChan = abuffer.numberOfChannels, 4 | length = len * numOfChan * 2 + 44, 5 | buffer = new ArrayBuffer(length), 6 | view = new DataView(buffer), 7 | channels = [], 8 | i, 9 | sample, 10 | offset = 0, 11 | pos = 0; 12 | 13 | // write WAVE header 14 | // "RIFF" 15 | setUint32(0x46464952); 16 | // file length - 8 17 | setUint32(length - 8); 18 | // "WAVE" 19 | setUint32(0x45564157); 20 | // "fmt " chunk 21 | setUint32(0x20746d66); 22 | // length = 16 23 | setUint32(16); 24 | // PCM (uncompressed) 25 | setUint16(1); 26 | setUint16(numOfChan); 27 | setUint32(abuffer.sampleRate); 28 | // avg. bytes/sec 29 | setUint32(abuffer.sampleRate * 2 * numOfChan); 30 | // block-align 31 | setUint16(numOfChan * 2); 32 | // 16-bit (hardcoded in this demo) 33 | setUint16(16); 34 | // "data" - chunk 35 | setUint32(0x61746164); 36 | // chunk length 37 | setUint32(length - pos - 4); 38 | 39 | // write interleaved data 40 | for (i = 0; i < abuffer.numberOfChannels; i++) 41 | channels.push(abuffer.getChannelData(i)); 42 | 43 | while (pos < length) { 44 | // interleave channels 45 | for (i = 0; i < numOfChan; i++) { 46 | // clamp 47 | sample = Math.max(-1, Math.min(1, channels[i][offset])); 48 | // scale to 16-bit signed int 49 | sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; 50 | // write 16-bit sample 51 | view.setInt16(pos, sample, true); 52 | pos += 2; 53 | } 54 | // next source sample 55 | offset++; 56 | } 57 | 58 | // create Blob 59 | return new Blob([buffer], { type: 'audio/wav' }); 60 | 61 | function setUint16(data: number) { 62 | view.setUint16(pos, data, true); 63 | pos += 2; 64 | } 65 | 66 | function setUint32(data: number) { 67 | view.setUint32(pos, data, true); 68 | pos += 4; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/utils/color.ts: -------------------------------------------------------------------------------- 1 | type RGBColor = { 2 | r: number; 3 | g: number; 4 | b: number; 5 | }; 6 | 7 | type HexColor = string; 8 | 9 | type HSLColor = { 10 | h: number; 11 | s: number; 12 | l: number; 13 | }; 14 | 15 | type HWBColor = { 16 | h: number; 17 | w: number; 18 | b: number; 19 | }; 20 | 21 | function rgbToHex(r: number, g: number, b: number): HexColor { 22 | const toHex = (value: number): string => { 23 | const hex = Math.round(Math.min(255, Math.max(0, value))).toString(16); 24 | return hex.length === 1 ? '0' + hex : hex; 25 | }; 26 | 27 | const hexR = toHex(r); 28 | const hexG = toHex(g); 29 | const hexB = toHex(b); 30 | 31 | return `#${hexR}${hexG}${hexB}`; 32 | } 33 | 34 | export function hexToRgb(hex: string): number[] { 35 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 36 | return result 37 | ? [ 38 | parseInt(result[1], 16), 39 | parseInt(result[2], 16), 40 | parseInt(result[3], 16), 41 | ] 42 | : []; 43 | } 44 | function rgbToHsl(r: number, g: number, b: number): HSLColor { 45 | r /= 255; 46 | g /= 255; 47 | b /= 255; 48 | const max = Math.max(r, g, b); 49 | const min = Math.min(r, g, b); 50 | const delta = max - min; 51 | 52 | let h = 0; 53 | let s = 0; 54 | let l = (max + min) / 2; 55 | 56 | if (delta !== 0) { 57 | s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min); 58 | 59 | switch (max) { 60 | case r: 61 | h = (g - b) / delta + (g < b ? 6 : 0); 62 | break; 63 | case g: 64 | h = (b - r) / delta + 2; 65 | break; 66 | case b: 67 | h = (r - g) / delta + 4; 68 | break; 69 | } 70 | 71 | h *= 60; 72 | } 73 | 74 | return { h, s, l }; 75 | } 76 | 77 | function rgbToHwb(r: number, g: number, b: number): HWBColor { 78 | const max = Math.max(r, g, b); 79 | const min = Math.min(r, g, b); 80 | const h = rgbToHsl(r, g, b).h; 81 | 82 | const w = (1 / 255) * min; 83 | const bVal = 1 - max / 255; 84 | 85 | return { h, w, b: bVal }; 86 | } 87 | 88 | export function rgbConvert(r: number, g: number, b: number): any { 89 | const hsl = rgbToHsl(r, g, b); 90 | const hwb = rgbToHwb(r, g, b); 91 | return { 92 | rgb: `rgb(${r}, ${g}, ${b})`, 93 | hsl: `hsl(${hsl.h.toFixed(2)}, ${(hsl.s * 100).toFixed(2)}%, ${( 94 | hsl.l * 100 95 | ).toFixed(2)}%)`, 96 | hwb: `hwb(${hwb.h.toFixed(2)}, ${(hwb.w * 100).toFixed(2)}%, ${( 97 | hwb.b * 100 98 | ).toFixed(2)}%)`, 99 | hex: rgbToHex(r, g, b), 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /src/utils/core-values-encoder.ts: -------------------------------------------------------------------------------- 1 | function assert(...express: any[]) { 2 | const l = express.length; 3 | const msg = 4 | typeof express[l - 1] === 'string' ? express[l - 1] : 'Assert Error'; 5 | for (let b of express) { 6 | if (!b) { 7 | throw new Error(msg); 8 | } 9 | } 10 | } 11 | 12 | function randBin(): boolean { 13 | return Math.random() >= 0.5; 14 | } 15 | 16 | const values = '富强民主文明和谐自由平等公正法治爱国敬业诚信友善'; 17 | 18 | function str2utf8(str: string) { 19 | // return in hex 20 | 21 | const notEncoded = /[A-Za-z0-9-_.!~*'()]/g; 22 | const str1 = str.replace(notEncoded, (c: any) => 23 | c.codePointAt(0).toString(16) 24 | ); 25 | let str2 = encodeURIComponent(str1); 26 | const concated = str2.replace(/%/g, '').toUpperCase(); 27 | return concated; 28 | } 29 | 30 | function utf82str(utfs: string) { 31 | assert(typeof utfs === 'string', 'utfs Error'); 32 | 33 | const l = utfs.length; 34 | 35 | assert((l & 1) === 0); 36 | 37 | const splited = []; 38 | 39 | for (let i = 0; i < l; i++) { 40 | if ((i & 1) === 0) { 41 | splited.push('%'); 42 | } 43 | splited.push(utfs[i]); 44 | } 45 | 46 | return decodeURIComponent(splited.join('')); 47 | } 48 | 49 | function hex2duo(hexs: string) { 50 | // duodecimal in array of number 51 | 52 | // '0'.. '9' -> 0.. 9 53 | // 'A'.. 'F' -> 10, c - 10 a2fFlag = 10 54 | // or 11, c - 6 a2fFlag = 11 55 | assert(typeof hexs === 'string'); 56 | 57 | const duo = []; 58 | 59 | for (let c of hexs) { 60 | const n = Number.parseInt(c, 16); 61 | if (n < 10) { 62 | duo.push(n); 63 | } else { 64 | if (randBin()) { 65 | duo.push(10); 66 | duo.push(n - 10); 67 | } else { 68 | duo.push(11); 69 | duo.push(n - 6); 70 | } 71 | } 72 | } 73 | return duo; 74 | } 75 | 76 | function duo2hex(duo: number[]) { 77 | assert(duo instanceof Array); 78 | 79 | const hex = []; 80 | 81 | const l = duo.length; 82 | 83 | let i = 0; 84 | 85 | while (i < l) { 86 | if (duo[i] < 10) { 87 | hex.push(duo[i]); 88 | } else { 89 | if (duo[i] === 10) { 90 | i++; 91 | hex.push(duo[i] + 10); 92 | } else { 93 | i++; 94 | hex.push(duo[i] + 6); 95 | } 96 | } 97 | i++; 98 | } 99 | return hex.map((v) => v.toString(16).toUpperCase()).join(''); 100 | } 101 | 102 | function duo2values(duo: number[]) { 103 | return duo.map((d) => values[2 * d] + values[2 * d + 1]).join(''); 104 | } 105 | 106 | function valuesDecode(encoded: string) { 107 | const duo = []; 108 | 109 | for (let c of encoded) { 110 | const i = values.indexOf(c); 111 | if (i === -1) { 112 | continue; 113 | } else if (i & 1) { 114 | continue; 115 | } else { 116 | // i is even 117 | duo.push(i >> 1); 118 | } 119 | } 120 | 121 | const hexs = duo2hex(duo); 122 | 123 | assert((hexs.length & 1) === 0); 124 | 125 | let str; 126 | str = utf82str(hexs); 127 | return str; 128 | } 129 | 130 | function valuesEncode(str: string) { 131 | return duo2values(hex2duo(str2utf8(str))); 132 | } 133 | 134 | export default { 135 | encode(val: string) { 136 | return valuesEncode(val); 137 | }, 138 | decode(val: string) { 139 | return valuesDecode(val); 140 | }, 141 | }; 142 | -------------------------------------------------------------------------------- /src/utils/download.ts: -------------------------------------------------------------------------------- 1 | export const saveFile = (text: string, name: string) => { 2 | const url = URL.createObjectURL( 3 | new Blob([text], { type: 'application/octet-stream' }) 4 | ); 5 | const aTag = document.createElement('a'); 6 | aTag.style.display = 'none'; 7 | aTag.href = url; 8 | aTag.download = name; 9 | document.body.appendChild(aTag); 10 | aTag.click(); 11 | URL.revokeObjectURL(url); 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/emotionCache.ts: -------------------------------------------------------------------------------- 1 | import createCache from '@emotion/cache'; 2 | 3 | const isBrowser = typeof document !== 'undefined'; 4 | 5 | function getMeta(metaName: string) { 6 | if (isBrowser) { 7 | const metas = document.getElementsByTagName('meta'); 8 | 9 | for (let i = 0; i < metas.length; i++) { 10 | if (metas[i].getAttribute('name') === metaName) { 11 | return metas[i] as HTMLElement; 12 | } 13 | } 14 | } 15 | 16 | return undefined; 17 | } 18 | 19 | export default function createEmotionCache() { 20 | return createCache({ 21 | key: 'css', 22 | // insertionPoint: getMeta("emotion-insertion-point"), 23 | prepend: true, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/enterApp.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationInfo } from '@/types'; 2 | export const encodeUrl = (url: string) => { 3 | const d = new URL(url); 4 | return d.pathname + d.search + d.hash; 5 | }; 6 | export const enterApp = (app: ApplicationInfo) => { 7 | const matchKey = app.url_prefix.split('.')[0].match(/^https?:\/\/(.+)/); 8 | // const target = `${app.url_prefix}?${qs.stringify({ app_id: app.id })}`; 9 | window.open( 10 | `/app/${matchKey ? matchKey[1] : ''}${encodeUrl(app.url_prefix)}`, 11 | '_blank' 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/getDefaultOrg.ts: -------------------------------------------------------------------------------- 1 | export const getCurrentOrgId = () => { 2 | if (typeof window === 'object') { 3 | const curOrgID = document?.cookie?.replace( 4 | /(?:(?:^|.*;\s*)heap-org-id\s*=\s*([^;]*).*$)|^.*$/, 5 | '$1' 6 | ); 7 | 8 | return curOrgID ? parseInt(curOrgID, 10) : null; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | // 计算导出后的 excel 单元格宽度 2 | export function getExcelCellWidth(value: string = '') { 3 | if (!value) return 0; 4 | if (/.*[\u4e00-\u9fa5]+.*$/.test(value)) { 5 | // @ts-ignore 6 | const cLen = value!.match(/[\u4e00-\u9fa5]/g).length; 7 | const otherLen = value.length - cLen; 8 | const length = cLen * 2.5 + otherLen * 1.1; 9 | return length < 15 ? 15 : length; 10 | } 11 | const length = value.toString().length * 1.1; 12 | return length < 15 ? 15 : length; 13 | } 14 | // 计算表格列宽 15 | export function getRCTableCellWidth(value: string = '') { 16 | if (!value) return 0; 17 | if (/.*[\u4e00-\u9fa5]+.*$/.test(value)) { 18 | // @ts-ignore 19 | const cLen = value!.match(/[\u4e00-\u9fa5]/g).length; 20 | const otherLen = value.length - cLen; 21 | const length = cLen * 27 + otherLen * 9 + 10; 22 | return length < 40 ? 40 : length; 23 | } 24 | ``; 25 | const length = value.toString().length * 9 + 10; 26 | return length < 40 ? 40 : length; 27 | } 28 | // 计算单元格宽度 29 | export function setCellWidthInExcel(data: any[]) { 30 | const wchs: number[] = Object.keys(data[0] || {}).map((it) => 31 | getExcelCellWidth(it) 32 | ); 33 | data.map((it) => { 34 | const values: string[] = Object.values(it || {}).map((it) => 35 | JSON.stringify(it) 36 | ); 37 | for (let i = 0; i < values.length; i++) { 38 | wchs[i] = Math.max(getExcelCellWidth(values[i]), wchs[i]); 39 | } 40 | }); 41 | const tcwchs: number[] = Object.keys(data[0] || {}).map((it) => 42 | getRCTableCellWidth(it) 43 | ); 44 | data.map((it) => { 45 | const values: string[] = Object.values(it || {}).map((it) => 46 | JSON.stringify(it) 47 | ); 48 | for (let i = 0; i < values.length; i++) { 49 | tcwchs[i] = Math.max(getRCTableCellWidth(values[i]), tcwchs[i]); 50 | } 51 | }); 52 | return [wchs, tcwchs]; 53 | } 54 | // 转化图片 55 | export function getConversionUrlByImageFile( 56 | file: File, 57 | type: string 58 | ): Promise { 59 | return new Promise((resolve, reject) => { 60 | const imageFileReader = new FileReader(); 61 | imageFileReader.onload = (e) => { 62 | const image = new Image(); 63 | image.src = e.target?.result as string; 64 | image.onload = () => { 65 | const canvas = document.createElement('canvas'); 66 | canvas.width = image.width; 67 | canvas.height = image.height; 68 | canvas.getContext('2d')?.drawImage(image, 0, 0); 69 | const conversion = canvas.toDataURL(type); 70 | resolve(conversion); 71 | }; 72 | }; 73 | imageFileReader.readAsDataURL(file); 74 | }); 75 | } 76 | export function countLines(s: string): number { 77 | if (!s) { 78 | return 0; 79 | } 80 | return s 81 | .trim() 82 | .split('\n') 83 | .filter((v) => v).length; 84 | } 85 | -------------------------------------------------------------------------------- /src/utils/meta.ts: -------------------------------------------------------------------------------- 1 | export const metaData = [ 2 | // { 3 | // name: "viewport", 4 | // content: 5 | // "width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no", 6 | // }, 7 | { 8 | name: 'description', 9 | content: 10 | '百川云——长亭对外开放的SaaS产品服务平台,基于长亭多年一线安全攻防经验以及广泛应用的安全产品技术沉淀,面向开发者、安全人员、运维人员和企业提供在线一站式安全解决方案,助力企业用户治理云安全问题,让云上业务更安全,管理更轻松', 11 | }, 12 | { 13 | name: 'keywords', 14 | content: 15 | '长亭,长亭百川云,百川云,云原生,云安全,容器安全,问脉,雷池,牧云,WAF,网站监测,云监测,防火墙,主机管理,主机安全,DDOS防护,漏洞扫描,云上合规,安全评估,应急响应,渗透测试,Web应用防火墙', 16 | }, 17 | { 18 | name: 'google-site-verification', 19 | content: 'Rep2zPqssSI5TNhaMm__gnjePLcCfy0yetKbQPoagdU', 20 | }, 21 | { 22 | name: 'baidu-site-verification', 23 | content: 'code-ZyezwgzXJP', 24 | }, 25 | { 26 | name: 'msvalidate.01', 27 | content: 'B254BE3DA57BDF4C94406478BB982E2E', 28 | }, 29 | { 30 | name: '360-site-verification', 31 | content: 'bdf97b7fe20f7b2b9ab04114e4747b61', 32 | }, 33 | { 34 | name: 'sogou_site_verification', 35 | content: 'h4H8DbZDxv', 36 | }, 37 | { 38 | name: 'shenma-site-verification', 39 | content: 'ddb4549aa03799578443866ff52561b0_1683542950', 40 | }, 41 | { 42 | name: 'bytedance-verification-code', 43 | content: 'lqMJj5kMgHithHARU+73', 44 | }, 45 | ]; 46 | -------------------------------------------------------------------------------- /src/utils/url.ts: -------------------------------------------------------------------------------- 1 | export function parseUrlSearch(search: string) { 2 | const res: { [key: string]: string } = {}; 3 | search 4 | .replace('?', '') 5 | .split('&') 6 | .filter((s) => s !== '') 7 | .reduce((pre, cur) => { 8 | const [key, value] = cur.split('='); 9 | pre[key] = decodeURIComponent(decodeURI(value)); 10 | return pre; 11 | }, res); 12 | return res; 13 | } 14 | 15 | /** 16 | * 17 | * @param app_key guanshan 18 | * @return path /record?aa=bb#ccc 19 | */ 20 | export const getChildPath = (app_key: string) => { 21 | const match = location.href.match(/\/app(.+)$/); 22 | return match ? match[1].replace(`/${app_key}`, '') : ''; 23 | }; 24 | export const decodeUrl = (app_key: string, data: string) => { 25 | return `${location.protocol}//${app_key}.${location.hostname}${data}`; 26 | }; 27 | 28 | export const encodeUrl = (url: string) => { 29 | const d = new URL(url); 30 | return d.pathname + d.search + d.hash; 31 | }; 32 | 33 | export const isValidUrl = (url: string) => { 34 | const regex = /^(https?):\/\/[^\s/$.?#].[^\s]*$/i; 35 | return regex.test(url); 36 | }; 37 | 38 | export const isPublicPage = (path: string) => { 39 | const rootPath = path.split('/')[0]; 40 | return ['landing', 'tools'].includes(rootPath); 41 | }; 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "paths": { 18 | "@/*": ["./src/*"] 19 | } 20 | }, 21 | "include": ["next-env.d.ts", "*.d.ts", "**/*.ts", "**/*.tsx", "imgLoader.js"], 22 | "exclude": ["node_modules"] 23 | } 24 | --------------------------------------------------------------------------------
65 | {invalidLines} 66 |
{invalidLines}
65 | 结果: 66 | 67 | {randomText} 68 | 69 | 74 | 75 |