├── src ├── types │ ├── index.d.ts │ ├── less.d.ts │ └── assets.d.ts ├── styles │ ├── global.less │ ├── theme.less │ └── normalize.less ├── pages │ └── Home │ │ ├── Cookie │ │ ├── index.less │ │ └── index.tsx │ │ ├── Header │ │ ├── index.less │ │ └── index.tsx │ │ ├── components │ │ ├── ButtonGroup │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── Group │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── BaseInput │ │ │ └── index.tsx │ │ ├── AutoComplete │ │ │ ├── index.tsx │ │ │ └── HEADER_OPTIONS.ts │ │ ├── ExportModal │ │ │ └── index.tsx │ │ ├── Replacer │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── GroupModal │ │ │ └── index.tsx │ │ ├── ItemModal │ │ │ └── index.tsx │ │ ├── ItemDataModal │ │ │ └── index.tsx │ │ ├── ItemHeaderModal │ │ │ └── index.tsx │ │ ├── ImportModal │ │ │ └── index.tsx │ │ ├── DataDetail │ │ │ └── index.tsx │ │ └── GroupDetail │ │ │ └── index.tsx │ │ ├── Source │ │ ├── index.less │ │ └── index.tsx │ │ ├── index.tsx │ │ └── index.less ├── assets │ ├── jietu.png │ ├── packet proxy.png │ ├── packet proxy small.png │ ├── content.js │ ├── metadata │ │ ├── computed_hashes.json │ │ └── verified_contents.json │ ├── manifest.json │ └── request.js ├── services │ └── index.ts ├── index.tsx ├── index.html └── utils │ └── request.ts ├── packet-proxy.crx ├── resources ├── code_proxy.png ├── data_proxy.png ├── cookie_proxy.png ├── header_proxy.png ├── packet_proxy.png └── source_proxy.png ├── .prettierignore ├── .editorconfig ├── .prettierrc ├── .ols.config.ts ├── .gitignore ├── .stylelintrc.json ├── README.md ├── tsconfig.json ├── package.json └── .eslintrc.js /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare let chrome: any; 2 | -------------------------------------------------------------------------------- /src/styles/global.less: -------------------------------------------------------------------------------- 1 | @import './normalize.less'; 2 | -------------------------------------------------------------------------------- /packet-proxy.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/packet-proxy.crx -------------------------------------------------------------------------------- /src/pages/Home/Cookie/index.less: -------------------------------------------------------------------------------- 1 | .cookie-table-container { 2 | margin-top: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/Home/Header/index.less: -------------------------------------------------------------------------------- 1 | .header-table-container { 2 | margin-top: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/jietu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/src/assets/jietu.png -------------------------------------------------------------------------------- /resources/code_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/resources/code_proxy.png -------------------------------------------------------------------------------- /resources/data_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/resources/data_proxy.png -------------------------------------------------------------------------------- /resources/cookie_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/resources/cookie_proxy.png -------------------------------------------------------------------------------- /resources/header_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/resources/header_proxy.png -------------------------------------------------------------------------------- /resources/packet_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/resources/packet_proxy.png -------------------------------------------------------------------------------- /resources/source_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/resources/source_proxy.png -------------------------------------------------------------------------------- /src/assets/packet proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/src/assets/packet proxy.png -------------------------------------------------------------------------------- /src/assets/packet proxy small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/packet-proxy/main/src/assets/packet proxy small.png -------------------------------------------------------------------------------- /src/pages/Home/components/ButtonGroup/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | button + button { 3 | margin-left: 20px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/types/less.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.less' { 2 | const resource: { [key: string]: string }; 3 | export = resource; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/assets.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.jpg'; 3 | declare module '*.jpeg'; 4 | declare module '*.gif'; 5 | -------------------------------------------------------------------------------- /src/assets/content.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一定要先发送一条消息(随便啥消息),这样在background中才能监听得到当前页面的url 3 | */ 4 | chrome.runtime.sendMessage({ greeting: "hello, world" }, function (response) {}); 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | dist/ 3 | yarn.lock 4 | yarn-error.log 5 | *.sh 6 | .gitignore 7 | .prettierignore 8 | .DS_Store 9 | .editorconfig 10 | .eslintignore 11 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | // TODO: 请求接口格式 4 | export const getTestService = (params) => 5 | request({ 6 | url: `/api/v1/test`, 7 | params, 8 | method: 'get', 9 | }); 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # 🎨 editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = tab 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "jsxBracketSameLint": false, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { 9 | "parser": "json" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/Home/Source/index.less: -------------------------------------------------------------------------------- 1 | .source-right-plane { 2 | position: relative; 3 | 4 | &-header { 5 | margin-top: 10px; 6 | } 7 | 8 | .source-table-header { 9 | position: absolute; 10 | top: 10px; 11 | right: 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/metadata/computed_hashes.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_hashes":[ 3 | { 4 | "block_hashes":[ 5 | "tUkUY2Vk4ak7/8FJsfe3UwxzwN/1x1TBEZU6T4OgpMw=" 6 | ], 7 | "block_size":4096, 8 | "path":"request.js" 9 | }, 10 | { 11 | "block_hashes":[ 12 | "YwMeR/Eqb9iEGeKHlbtOil2LSOYy55k5eob3104Dpbw=" 13 | ], 14 | "block_size":4096, 15 | "path":"index.html" 16 | } 17 | ], 18 | "version":2 19 | } 20 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { ConfigProvider } from 'antd'; 4 | import zhCN from 'antd/es/locale/zh_CN'; 5 | import Home from '@/pages/Home'; 6 | 7 | import 'antd/lib/form/style/css'; 8 | import '@/styles/global.less'; 9 | 10 | const App: FC = () => { 11 | return ( 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | ReactDOM.render(, document.getElementById('app')); 19 | -------------------------------------------------------------------------------- /src/pages/Home/components/Group/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .group-container { 4 | width: 240px; 5 | height: 100%; 6 | overflow-y: auto; 7 | transform: translateX(-10px); 8 | 9 | &-item { 10 | &-label { 11 | display: inline-block; 12 | width: calc(100% - 50px); 13 | margin-left: 10px; 14 | overflow: hidden; 15 | white-space: nowrap; 16 | text-overflow: ellipsis; 17 | vertical-align: middle; 18 | } 19 | 20 | &-operator { 21 | transform: translateX(12px); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.ols.config.ts: -------------------------------------------------------------------------------- 1 | const CopyPlugin = require("copy-webpack-plugin"); 2 | 3 | module.exports = { 4 | // 类型:项目 | 组件 5 | type: 'project', 6 | // webpack配置,可以是一个对象,或者一个函数 7 | configureWebpack(config, merge) { 8 | return merge(config, { 9 | plugins: [ 10 | new CopyPlugin({ 11 | patterns: [ 12 | { from: "./src/assets" }, 13 | ], 14 | }), 15 | ] 16 | }); 17 | }, 18 | // 链式操作webpack配置 19 | chainWebpack(chian, config) { 20 | }, 21 | // 插件,是一个个函数组成的数组Array<(ctx) => void>,插件默认透出ctx上下文,用户可以通过上下文完成一些操作 22 | plugins: [] 23 | }; 24 | -------------------------------------------------------------------------------- /src/pages/Home/components/BaseInput/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useCallback } from 'react' 2 | import { Input } from 'antd' 3 | import { InputProps } from 'antd/lib/input' 4 | 5 | interface IProps extends InputProps { 6 | onChange: (v: any) => void; 7 | } 8 | 9 | const BaseInput: FC = ({ onChange, ...otherProps }) => { 10 | const onInputChange = useCallback( 11 | (e) => { 12 | onChange(e.target.value) 13 | }, 14 | [onChange], 15 | ) 16 | 17 | return 18 | } 19 | 20 | export default BaseInput 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | dist 3 | build 4 | node_modules 5 | 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | *.lock 38 | 39 | # zip 40 | *.zip 41 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <%= htmlWebpackPlugin.options.title %> 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/styles/theme.less: -------------------------------------------------------------------------------- 1 | /* 主题变量 */ 2 | 3 | @primary-color: #1890ff; // 全局主色 4 | @link-color: #1890ff; // 链接色 5 | @success-color: #52c41a; // 成功色 6 | @warning-color: #faad14; // 警告色 7 | @error-color: #f5222d; // 错误色 8 | @font-size-base: 14px; // 主字号 9 | @heading-color: rgba(0, 0, 0, 0.85); // 标题色 10 | @text-color: rgba(0, 0, 0, 0.65); // 主文本色 11 | @text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色 12 | @disabled-color: rgba(0, 0, 0, 0.25); // 失效色 13 | @border-radius-base: 4px; // 组件/浮层圆角 14 | @border-color-base: #d9d9d9; // 边框色 15 | @box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 浮层阴影 16 | @white-color: #fff; 17 | -------------------------------------------------------------------------------- /src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { Tabs } from 'antd' 3 | import Source from './Source' 4 | import Cookie from './Cookie' 5 | import Header from './Header' 6 | import './index.less' 7 | 8 | const { TabPane } = Tabs 9 | 10 | const Home: FC = () => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | ) 24 | } 25 | 26 | export default Home 27 | -------------------------------------------------------------------------------- /src/pages/Home/components/AutoComplete/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react' 2 | import { AutoComplete } from 'antd' 3 | import { AutoCompleteProps } from 'antd/lib/auto-complete' 4 | import HEADER_OPTIONS from './HEADER_OPTIONS' 5 | 6 | const AutoComplete2: FC = ({ ...otherProps }) => { 7 | const [options, setOptions] = useState([]) 8 | const onSearch = (searchText: string) => { 9 | const filter = HEADER_OPTIONS.filter((item) => 10 | item.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()), 11 | ) 12 | setOptions(filter.map((item) => ({ value: item }))) 13 | } 14 | 15 | return 16 | } 17 | 18 | export default AutoComplete2 19 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-rational-order", 5 | "stylelint-config-prettier" 6 | ], 7 | "plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"], 8 | "rules": { 9 | "comment-empty-line-before": null, 10 | "function-name-case": ["lower", { "ignoreFunctions": ["/colorPalette/"] }], 11 | "no-invalid-double-slash-comments": null, 12 | "no-descending-specificity": null, 13 | "declaration-empty-line-before": null, 14 | "selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["global"] }], 15 | "block-opening-brace-space-before": "always", 16 | "font-family-no-missing-generic-family-keyword": null 17 | }, 18 | "ignoreFiles": ["node_modules/**", "**/*.json"] 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/Home/index.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: sans-serif; 4 | 5 | #app { 6 | width: 800px; 7 | height: 600px; 8 | padding: 16px; 9 | overflow: hidden; 10 | background: #f1f1f1; 11 | } 12 | } 13 | 14 | .tab-container { 15 | height: 100%; 16 | 17 | > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-card > div > .ant-tabs-nav .ant-tabs-tab { 18 | background: #f1f1f1; 19 | &.ant-tabs-tab-active { 20 | background: #fff; 21 | } 22 | } 23 | 24 | .ant-tabs-nav { 25 | margin: 0; 26 | .ant-tabs-nav-wrap .ant-tabs-nav-list .ant-tabs-tab { 27 | margin-right: 0; 28 | border: none; 29 | } 30 | } 31 | .ant-tabs-content-holder { 32 | padding: 10px; 33 | background-color: #fff; 34 | } 35 | 36 | .ant-tabs-content { 37 | height: 100%; 38 | overflow: hidden; 39 | } 40 | 41 | .ant-tabs-tabpane { 42 | height: 100%; 43 | overflow-y: auto; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## packet-proxy 2 | 3 | 一款具有静态资源代理、接口转发、Cookie 代理等功能的 Chrome 浏览器插件 4 | 5 | ### 功能 6 | 7 | - 支持静态资源代理 8 | - 支持 Cookie 代理 9 | - 支持资源分组 10 | - 支持 JSON 导入导出 11 | 12 | ### 如何安装 13 | 14 | 打开chrome应用市场,搜索并安装,或者也可以直接下载目录下的packet-proxy.crx文件,并拖拽进扩展程序中 15 | 16 | ![packet-proxy](./resources/packet_proxy.png) 17 | 18 | ### 如何使用 19 | 20 | #### 资源代理 21 | 22 | 1. 新建组,并启用 23 | 2. 添加代理路径(源地址 + 代理地址);可以支持正则 24 | 3. 勾选复选框启用 25 | 4. 刷新相关的页面 26 | 27 | ![资源代理](./resources/source_proxy.png) 28 | 29 | #### Cookie 代理 30 | 31 | 1. 添加代理路径(源地址 + 代理地址);其中源地址必须是一个合法且带协议的 https?地址 32 | 2. 勾选复选框启用 33 | 3. 打开或刷新源网址,会自动读取到源地址的 cookie 34 | 4. 打开代理的网址,会自动把源地址的 cookie 塞到代理网址下 35 | 36 | ![Cookie代理](./resources/cookie_proxy.png) 37 | 38 | #### header添加 39 | 40 | ![header添加](./resources/header_proxy.png) 41 | 42 | #### 数据拦截 43 | 44 | ![数据拦截](./resources/data_proxy.png) 45 | 46 | #### 代码模式 47 | 48 | ![代码模式](./resources/code_proxy.png) 49 | -------------------------------------------------------------------------------- /src/pages/Home/components/ExportModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useRef } from 'react' 2 | import { Modal, Input } from 'antd' 3 | 4 | const ExportModal = ({ visible, onCancel, value }) => { 5 | const textareaRef = useRef(null) 6 | 7 | const onModalOk = useCallback(() => { 8 | const textAreaDom = textareaRef.current.resizableTextArea.textArea 9 | textAreaDom.select() 10 | document.execCommand('Copy') 11 | }, []) 12 | 13 | return ( 14 | 23 | 29 | 30 | ) 31 | } 32 | 33 | export default ExportModal 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "esnext", 5 | "lib": ["dom", "es2017"], 6 | "allowJs": true, 7 | "checkJs": true, 8 | "jsx": "preserve", 9 | "declaration": false, 10 | "sourceMap": true, 11 | "noImplicitAny": false, 12 | "strictNullChecks": false, 13 | "noImplicitThis": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "baseUrl": "./", 17 | "moduleResolution": "node", 18 | "paths": { 19 | "*": ["*"], 20 | "@/assets/*": ["src/assets/*"], 21 | "@/components/*": ["src/components/*"], 22 | "@/pages/*": ["src/pages/*"], 23 | "@/styles/*": ["src/styles/*"], 24 | "@/services/*": ["src/services/*"], 25 | "@/store/*": ["src/store/*"], 26 | "@/utils/*": ["src/utils/*"], 27 | "@/layouts/*": ["src/layouts/*"] 28 | }, 29 | "esModuleInterop": true, 30 | "experimentalDecorators": true, 31 | "skipLibCheck": true 32 | }, 33 | "include": ["src", "src/types"] 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/Home/components/ButtonGroup/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Button组 3 | */ 4 | 5 | import React, { FC, Fragment } from 'react' 6 | import { Button } from 'antd' 7 | import { ButtonProps } from 'antd/lib/button' 8 | import cn from 'classnames' 9 | 10 | import styles from './index.less' 11 | 12 | interface BProps extends ButtonProps { 13 | component?: React.ReactElement; 14 | } 15 | interface IProps { 16 | align?: 'left' | 'right' | 'center'; 17 | buttons?: BProps[]; 18 | style?: Record; 19 | className?: string; 20 | } 21 | 22 | const ButtonGroup: FC = ({ align = 'left', style = {}, className = '', buttons = [] }) => { 23 | return ( 24 |
28 | {buttons.map(({ children, component, ...otherProps }, index) => 29 | component ? ( 30 | {component} 31 | ) : ( 32 | 35 | ), 36 | )} 37 |
38 | ) 39 | } 40 | 41 | export default ButtonGroup 42 | -------------------------------------------------------------------------------- /src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background": { 3 | "persistent": true, 4 | "scripts": ["request.js"] 5 | }, 6 | "browser_action": { 7 | "default_icon": "packet proxy.png", 8 | "default_popup": "index.html", 9 | "default_title": "Choose your proxy." 10 | }, 11 | "content_scripts": [ 12 | { 13 | "js": ["/content.js"], 14 | "matches": ["\u003Call_urls>"], 15 | "run_at": "document_start", 16 | "all_frames": true 17 | } 18 | ], 19 | "web_accessible_resources": ["script/main.js"], 20 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", 21 | "commands": { 22 | "_execute_browser_action": { 23 | "suggested_key": { 24 | "chromeos": "Ctrl+Shift+U", 25 | "linux": "Ctrl+Shift+J", 26 | "mac": "Command+Shift+Y", 27 | "windows": "Ctrl+Shift+Y" 28 | } 29 | } 30 | }, 31 | "description": "A quick proxy plugin for requests, cookies and headers", 32 | "manifest_version": 2, 33 | "name": "Packet Proxy", 34 | "permissions": [ 35 | "storage", 36 | "webRequest", 37 | "webRequestBlocking", 38 | "\u003Call_urls>", 39 | "tabs", 40 | "cookies" 41 | ], 42 | "update_url": "http://clients2.google.com/service/update2/crx", 43 | "version": "0.0.7" 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/Home/components/Replacer/index.less: -------------------------------------------------------------------------------- 1 | .replace-with { 2 | margin-bottom: 6px; 3 | font-weight: 500; 4 | text-align: left; 5 | } 6 | .overrideTxt { 7 | position: relative; 8 | display: inline-block; 9 | box-sizing: border-box; 10 | width: 100%; 11 | height: 100px; 12 | margin: 0; 13 | padding: 4px 11px; 14 | color: rgba(0, 0, 0, 0.65); 15 | font-size: 14px; 16 | font-variant: tabular-nums; 17 | line-height: 1.5; 18 | list-style: none; 19 | background-color: #fff; 20 | background-image: none; 21 | border: 1px solid #d9d9d9; 22 | border-radius: 4px; 23 | resize: none; 24 | font-feature-settings: 'tnum'; 25 | } 26 | 27 | .errorTxt { 28 | border: 1px solid red !important; 29 | } 30 | 31 | .errorTip { 32 | color: red; 33 | } 34 | 35 | .overrideTxt::-webkit-input-placeholder { 36 | color: hsv(0, 0, 75%); 37 | } 38 | 39 | .overrideTxt:focus { 40 | border-color: #40a9ff; 41 | border-right-width: 1px !important; 42 | outline: 0; 43 | -webkit-box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); 44 | box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); 45 | } 46 | .overrideTxt:hover { 47 | border-color: #40a9ff; 48 | border-right-width: 1px !important; 49 | } 50 | .JsonEditor { 51 | width: calc(100% + 10px); 52 | // height: 300px; 53 | // margin-top: 15px; 54 | overflow: auto; 55 | text-align: left; 56 | } 57 | .invalid { 58 | color: rgb(168, 168, 168); 59 | font-size: 12px; 60 | text-align: center; 61 | } -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { notification } from 'antd'; 3 | 4 | const axiosInstance = axios.create({ 5 | timeout: 10000, 6 | }); 7 | 8 | // 过滤掉请求参数为 null | undefined 的属性;去除字符串两端的特殊字符 9 | const filterParams = (params) => { 10 | const newPrams = {}; 11 | for (const key in params) { 12 | const paramsValue = params[key]; 13 | if (typeof paramsValue === 'string' && paramsValue !== '') { 14 | newPrams[key] = paramsValue.trim(); 15 | } else if (paramsValue !== null && typeof paramsValue !== 'undefined') { 16 | newPrams[key] = paramsValue; 17 | } 18 | } 19 | return newPrams; 20 | }; 21 | 22 | const errHandler = (description: string = '') => { 23 | notification.error({ 24 | message: '接口失败', 25 | description 26 | }); 27 | } 28 | 29 | const request = ({ method, url, params = {}, data = {}, restConfig = {} }) => { 30 | return new Promise((resolve, reject) => { 31 | const config = { 32 | method, 33 | url, 34 | data: filterParams(data), 35 | params: filterParams(params), 36 | ...restConfig, 37 | }; 38 | axiosInstance(config).then( 39 | (res: any) => { 40 | // 根据服务端的接口定义判断请求成功的条件 41 | // 例如接口的状态码为 200 为成功条件 42 | if (res.data && res.data.code === 200) { 43 | resolve(res.data); 44 | } else { 45 | reject(res.data); 46 | errHandler(res.message || '服务错误'); 47 | } 48 | }, 49 | (err) => { 50 | reject(err); 51 | errHandler(String(err)); 52 | } 53 | ) 54 | }) 55 | }; 56 | 57 | export default request; 58 | -------------------------------------------------------------------------------- /src/pages/Home/components/GroupModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react' 2 | import { Modal } from 'antd' 3 | import { SchemaForm, createFormActions } from '@formily/antd' 4 | import BaseInput from '../BaseInput' 5 | 6 | const actions = createFormActions() 7 | 8 | const GroupModal = ({ visible, onOk, onCancel, mode, data }) => { 9 | const [formValue, setFormValue] = useState({}) 10 | 11 | const onModalOk = useCallback(() => { 12 | actions.validate().then(() => { 13 | onOk(formValue) 14 | }) 15 | }, [onOk, formValue]) 16 | 17 | useEffect(() => { 18 | visible && setFormValue(data) 19 | }, [visible]) 20 | 21 | return ( 22 | 29 | 50 | 51 | ) 52 | } 53 | 54 | export default GroupModal 55 | -------------------------------------------------------------------------------- /src/pages/Home/components/ItemModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState, useEffect } from 'react' 2 | import { Modal } from 'antd' 3 | import { SchemaForm, createFormActions } from '@formily/antd' 4 | import BaseInput from '../BaseInput' 5 | 6 | const actions = createFormActions() 7 | 8 | const ItemModal = ({ visible, onOk, onCancel, mode, data }) => { 9 | const [formValue, setFormValue] = useState({}) 10 | 11 | const onModalOk = useCallback(() => { 12 | actions.validate().then(() => { 13 | onOk(formValue) 14 | }) 15 | }, [onOk, formValue]) 16 | 17 | useEffect(() => { 18 | visible && setFormValue(data) 19 | }, [visible]) 20 | 21 | return ( 22 | 29 | 61 | 62 | ) 63 | } 64 | 65 | export default ItemModal 66 | -------------------------------------------------------------------------------- /src/pages/Home/components/ItemDataModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState, useEffect } from 'react' 2 | import { Modal } from 'antd' 3 | import { SchemaForm, createFormActions } from '@formily/antd' 4 | import BaseInput from '../BaseInput' 5 | import Replacer from '../Replacer' 6 | 7 | const actions = createFormActions() 8 | 9 | const ItemModal = ({ visible, onOk, onCancel, mode, data }) => { 10 | const [formValue, setFormValue] = useState({}) 11 | 12 | const onModalOk = useCallback(() => { 13 | actions.validate().then(() => { 14 | onOk(formValue) 15 | }) 16 | }, [onOk, formValue]) 17 | 18 | useEffect(() => { 19 | visible && setFormValue(data) 20 | }, [visible]) 21 | 22 | return ( 23 | 30 | 60 | 61 | ) 62 | } 63 | 64 | export default ItemModal 65 | -------------------------------------------------------------------------------- /src/pages/Home/components/Replacer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useCallback, useState, useEffect } from 'react' 2 | import { Input } from 'antd' 3 | import { InputProps } from 'antd/lib/input' 4 | import ReactJson from 'react-json-view' 5 | 6 | import './index.less' 7 | 8 | const { TextArea } = Input 9 | 10 | interface IProps extends InputProps { 11 | onChange: (v: any) => void; 12 | } 13 | 14 | const Replacer: FC = ({ onChange, value, placeholder }) => { 15 | const [formatValue, setFormatValue] = useState(null) 16 | const onInputChange = useCallback( 17 | (e) => { 18 | onChange(e.target.value) 19 | }, 20 | [onChange], 21 | ) 22 | 23 | useEffect(() => { 24 | try { 25 | const newFormat = JSON.parse(value as string) 26 | setFormatValue(newFormat) 27 | } catch { 28 | setFormatValue(null) 29 | } 30 | }, [value, setFormatValue]) 31 | 32 | const handleJSONEditorChange = useCallback( 33 | // eslint-disable-next-line camelcase, @typescript-eslint/camelcase 34 | ({ updated_src }) => { 35 | const txt = JSON.stringify(updated_src) 36 | setFormatValue(updated_src) 37 | onChange(txt) 38 | }, 39 | [onChange, setFormatValue], 40 | ) 41 | 42 | return ( 43 | <> 44 |