fix:bug
This commit is contained in:
parent
9f09e8ba61
commit
ffabc1ccbe
64
__unconfig_vite.config.ts
Normal file
64
__unconfig_vite.config.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
let __unconfig_data;
|
||||||
|
let __unconfig_stub = function (data = {}) { __unconfig_data = data };
|
||||||
|
__unconfig_stub.default = (data = {}) => { __unconfig_data = data };
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import svgr from 'vite-plugin-svgr'
|
||||||
|
import { CodeInspectorPlugin } from 'code-inspector-plugin'
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// const host = process.env.TAURI_DEV_HOST;
|
||||||
|
const host = '127.0.0.1'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
const __unconfig_default = defineConfig(async () => ({
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
AutoImport({
|
||||||
|
dts: './auto-imports.d.ts', //此文件配置保存后系统自动生成
|
||||||
|
imports: [
|
||||||
|
'react', // 自动导入 React
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
svgr({ include: '**/*.svg?react' }),
|
||||||
|
CodeInspectorPlugin({
|
||||||
|
bundler: 'vite',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
'package.json': path.resolve(__dirname, './package.json'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
|
//
|
||||||
|
// 1. prevent vite from obscuring rust errors
|
||||||
|
clearScreen: false,
|
||||||
|
// 2. tauri expects a fixed port, fail if that port is not available
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
host: host,
|
||||||
|
hmr: host
|
||||||
|
? {
|
||||||
|
protocol: 'ws',
|
||||||
|
host,
|
||||||
|
port: 1421,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
watch: {
|
||||||
|
// 3. tell vite to ignore watching `src-tauri`
|
||||||
|
ignored: ['**/src-tauri/**'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (typeof __unconfig_default === "function") __unconfig_default(...[{"command":"serve","mode":"development"}]);export default __unconfig_data;
|
||||||
@ -48,6 +48,7 @@
|
|||||||
"i18next": "^24.2.0",
|
"i18next": "^24.2.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
|
|||||||
27
pnpm-lock.yaml
generated
27
pnpm-lock.yaml
generated
@ -119,6 +119,9 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.469.0
|
specifier: ^0.469.0
|
||||||
version: 0.469.0(react@18.3.1)
|
version: 0.469.0(react@18.3.1)
|
||||||
|
mitt:
|
||||||
|
specifier: ^3.0.1
|
||||||
|
version: 3.0.1
|
||||||
react:
|
react:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
@ -569,42 +572,36 @@ packages:
|
|||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.0':
|
'@parcel/watcher-linux-arm-musl@2.5.0':
|
||||||
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
|
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.0':
|
'@parcel/watcher-linux-arm64-glibc@2.5.0':
|
||||||
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
|
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.0':
|
'@parcel/watcher-linux-arm64-musl@2.5.0':
|
||||||
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
|
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.0':
|
'@parcel/watcher-linux-x64-glibc@2.5.0':
|
||||||
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
|
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.0':
|
'@parcel/watcher-linux-x64-musl@2.5.0':
|
||||||
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
|
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.0':
|
'@parcel/watcher-win32-arm64@2.5.0':
|
||||||
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
|
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
|
||||||
@ -1320,55 +1317,46 @@ packages:
|
|||||||
resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
|
resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.27.4':
|
'@rollup/rollup-linux-arm-musleabihf@4.27.4':
|
||||||
resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
|
resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.27.4':
|
'@rollup/rollup-linux-arm64-gnu@4.27.4':
|
||||||
resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
|
resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.27.4':
|
'@rollup/rollup-linux-arm64-musl@4.27.4':
|
||||||
resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
|
resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
|
||||||
resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
|
resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.27.4':
|
'@rollup/rollup-linux-riscv64-gnu@4.27.4':
|
||||||
resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
|
resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.27.4':
|
'@rollup/rollup-linux-s390x-gnu@4.27.4':
|
||||||
resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
|
resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.27.4':
|
'@rollup/rollup-linux-x64-gnu@4.27.4':
|
||||||
resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
|
resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.27.4':
|
'@rollup/rollup-linux-x64-musl@4.27.4':
|
||||||
resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
|
resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.27.4':
|
'@rollup/rollup-win32-arm64-msvc@4.27.4':
|
||||||
resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
|
resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
|
||||||
@ -1490,28 +1478,24 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-musl@2.1.0':
|
'@tauri-apps/cli-linux-arm64-musl@2.1.0':
|
||||||
resolution: {integrity: sha512-NzwqjUCilhnhJzusz3d/0i0F1GFrwCQbkwR6yAHUxItESbsGYkZRJk0yMEWkg3PzFnyK4cWTlQJMEU52TjhEzA==}
|
resolution: {integrity: sha512-NzwqjUCilhnhJzusz3d/0i0F1GFrwCQbkwR6yAHUxItESbsGYkZRJk0yMEWkg3PzFnyK4cWTlQJMEU52TjhEzA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-gnu@2.1.0':
|
'@tauri-apps/cli-linux-x64-gnu@2.1.0':
|
||||||
resolution: {integrity: sha512-TyiIpMEtZxNOQmuFyfJwaaYbg3movSthpBJLIdPlKxSAB2BW0VWLY3/ZfIxm/G2YGHyREkjJvimzYE0i37PnMA==}
|
resolution: {integrity: sha512-TyiIpMEtZxNOQmuFyfJwaaYbg3movSthpBJLIdPlKxSAB2BW0VWLY3/ZfIxm/G2YGHyREkjJvimzYE0i37PnMA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-musl@2.1.0':
|
'@tauri-apps/cli-linux-x64-musl@2.1.0':
|
||||||
resolution: {integrity: sha512-/dQd0TlaxBdJACrR72DhynWftzHDaX32eBtS5WBrNJ+nnNb+znM3gON6nJ9tSE9jgDa6n1v2BkI/oIDtypfUXw==}
|
resolution: {integrity: sha512-/dQd0TlaxBdJACrR72DhynWftzHDaX32eBtS5WBrNJ+nnNb+znM3gON6nJ9tSE9jgDa6n1v2BkI/oIDtypfUXw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-arm64-msvc@2.1.0':
|
'@tauri-apps/cli-win32-arm64-msvc@2.1.0':
|
||||||
resolution: {integrity: sha512-NdQJO7SmdYqOcE+JPU7bwg7+odfZMWO6g8xF9SXYCMdUzvM2Gv/AQfikNXz5yS7ralRhNFuW32i5dcHlxh4pDg==}
|
resolution: {integrity: sha512-NdQJO7SmdYqOcE+JPU7bwg7+odfZMWO6g8xF9SXYCMdUzvM2Gv/AQfikNXz5yS7ralRhNFuW32i5dcHlxh4pDg==}
|
||||||
@ -2097,6 +2081,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
mitt@3.0.1:
|
||||||
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
mkdirp@0.5.6:
|
mkdirp@0.5.6:
|
||||||
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
|
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -4782,6 +4769,8 @@ snapshots:
|
|||||||
|
|
||||||
minipass@7.1.2: {}
|
minipass@7.1.2: {}
|
||||||
|
|
||||||
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
mkdirp@0.5.6:
|
mkdirp@0.5.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
|
|||||||
111
src/App.tsx
111
src/App.tsx
@ -2,14 +2,23 @@ import { useStartupCheck } from "@/hooks/useStartupCheck";
|
|||||||
import { usePreventDefault } from "@/hooks/usePreventDefaultEventListener";
|
import { usePreventDefault } from "@/hooks/usePreventDefaultEventListener";
|
||||||
import { useGlobalShortcut } from "@/hooks/useGlobalShortcut";
|
import { useGlobalShortcut } from "@/hooks/useGlobalShortcut";
|
||||||
import { useRecreateTheCircuit } from "@/hooks/useRecreateTheCircuit";
|
import { useRecreateTheCircuit } from "@/hooks/useRecreateTheCircuit";
|
||||||
import { useCoreConfig } from "./hooks/useCoreConfig";
|
import { useCoreConfig } from "@/hooks/useCoreConfig";
|
||||||
// import { commands } from '@/bindings'
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import Titlebar from "@/components/Titlebar";
|
import {
|
||||||
|
setProxyInfoProxies,
|
||||||
|
setProxiesList1,
|
||||||
|
setProxiesList2,
|
||||||
|
setProxiesLine,
|
||||||
|
} from "@/store/web3Slice";
|
||||||
|
import type { AppDispatch, RootState } from "@/store";
|
||||||
|
|
||||||
|
import eventBus, { eventTypes } from "@/utils/eventBus";
|
||||||
|
import { WebSocketClient } from "@/utils/webSocketClient";
|
||||||
|
|
||||||
|
import Titlebar from "@/components/Titlebar";
|
||||||
import Layout from "@/layout";
|
import Layout from "@/layout";
|
||||||
import Tray from "@/components/Tray";
|
import Tray from "@/components/Tray";
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// 执行启动自检
|
// 执行启动自检
|
||||||
useStartupCheck();
|
useStartupCheck();
|
||||||
@ -22,7 +31,101 @@ function App() {
|
|||||||
// 读取配置,若文件不存在则持久化到本地 `%APPDATA%/com.paw.paw-gui`
|
// 读取配置,若文件不存在则持久化到本地 `%APPDATA%/com.paw.paw-gui`
|
||||||
const { loadCoreConfig } = useCoreConfig();
|
const { loadCoreConfig } = useCoreConfig();
|
||||||
loadCoreConfig();
|
loadCoreConfig();
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const { web3List, web3List2, proxy_info, path_list } = useSelector(
|
||||||
|
(state: RootState) => state.web3Reducer
|
||||||
|
);
|
||||||
|
const webSocketClient = useRef<WebSocketClient | null>();
|
||||||
|
|
||||||
|
const openWsTraffic = async () => {
|
||||||
|
if (webSocketClient.current) return;
|
||||||
|
const { api_port } = await loadCoreConfig();
|
||||||
|
// todo! 后面会把二级制文件启动的参数作为配置项,这里暂时写死
|
||||||
|
webSocketClient.current = new WebSocketClient(
|
||||||
|
`ws://127.0.0.1:${api_port}/traffic`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer secret",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 执行 WebSocket 操作
|
||||||
|
await webSocketClient.current.connect();
|
||||||
|
webSocketClient.current.addListener((msg: any) => {
|
||||||
|
if (msg.code === 0) {
|
||||||
|
switch (msg.event) {
|
||||||
|
case eventTypes.NODE_UP:
|
||||||
|
console.log("节点上线");
|
||||||
|
break;
|
||||||
|
case eventTypes.NODE_DOWN:
|
||||||
|
// 添加下线节点到store 里面
|
||||||
|
console.log("节点下线");
|
||||||
|
break;
|
||||||
|
case eventTypes.MALICIOUS_NODE:
|
||||||
|
// 添加恶意节点到store 里面
|
||||||
|
console.log("检测到恶意节点");
|
||||||
|
break;
|
||||||
|
case eventTypes.NODE_INIT_COMPLATE:
|
||||||
|
console.log("节点预配置完成");
|
||||||
|
break;
|
||||||
|
case eventTypes.NODE_REMOVE:
|
||||||
|
console.log("节点清除");
|
||||||
|
break;
|
||||||
|
case eventTypes.NODE_ADD:
|
||||||
|
console.log("添加节点");
|
||||||
|
break;
|
||||||
|
case eventTypes.NODE_INIT:
|
||||||
|
console.log("节点预配置");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createdSoketEventBus = async () => {
|
||||||
|
eventBus.on(eventTypes.NODE_INIT_COMPLATE, (data: any) => {
|
||||||
|
console.log("节点预配置完成", data);
|
||||||
|
webSocketClient.current?.sendMessage(data);
|
||||||
|
});
|
||||||
|
eventBus.on(eventTypes.NODE_REMOVE, () => {
|
||||||
|
console.log("节点清除");
|
||||||
|
webSocketClient.current?.sendMessage({
|
||||||
|
code: 0,
|
||||||
|
event: eventTypes.NODE_REMOVE,
|
||||||
|
data: {
|
||||||
|
name: "proxy-xxx",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeWsTraffic = async () => {
|
||||||
|
if (webSocketClient.current) {
|
||||||
|
await webSocketClient.current.disconnect();
|
||||||
|
webSocketClient.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeSoketEventBus = () => {
|
||||||
|
eventBus.off(eventTypes.NODE_INIT_COMPLATE);
|
||||||
|
eventBus.off(eventTypes.NODE_REMOVE);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initWebsocketsAndEventBus = async () => {
|
||||||
|
await openWsTraffic();
|
||||||
|
await createdSoketEventBus();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// initWebsocketsAndEventBus();
|
||||||
|
// return () => {
|
||||||
|
// closeWsTraffic();
|
||||||
|
// removeSoketEventBus();
|
||||||
|
// };
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
26
src/hooks/useEventBus.ts
Normal file
26
src/hooks/useEventBus.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// src/hooks/useEventBus.ts
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import eventBus from '@/utils/eventBus';
|
||||||
|
|
||||||
|
|
||||||
|
export function useEventBus(
|
||||||
|
event:any,
|
||||||
|
handler:any,
|
||||||
|
) {
|
||||||
|
useEffect(() => {
|
||||||
|
// 添加事件监听器
|
||||||
|
eventBus.on(event, handler as any);
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => {
|
||||||
|
eventBus.off(event, handler as any);
|
||||||
|
};
|
||||||
|
}, [event, handler]); // 依赖项包含 event 和 handler
|
||||||
|
|
||||||
|
// 返回发布事件的函数
|
||||||
|
return {
|
||||||
|
emit: (eventName:any, data: any) => {
|
||||||
|
eventBus.emit(eventName, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import { app } from '@tauri-apps/api'
|
|||||||
import { createCircuit } from '@/store/circuitSlice'
|
import { createCircuit } from '@/store/circuitSlice'
|
||||||
import { setServiceStatus, getProxy, getVersion } from '@/store/serviceSlice'
|
import { setServiceStatus, getProxy, getVersion } from '@/store/serviceSlice'
|
||||||
import { setNodes, setNodesError, clearNodes } from '@/store/nodesSlice'
|
import { setNodes, setNodesError, clearNodes } from '@/store/nodesSlice'
|
||||||
|
// import {} from '@/store/web3Slice'
|
||||||
|
|
||||||
// import { clearCircuitState } from '@/store/circuitSlice';
|
// import { clearCircuitState } from '@/store/circuitSlice';
|
||||||
import { AppDispatch, RootState } from '@/store'
|
import { AppDispatch, RootState } from '@/store'
|
||||||
|
|||||||
@ -37,11 +37,11 @@ export default function Layout() {
|
|||||||
title: "面向溯源对抗的数据转发",
|
title: "面向溯源对抗的数据转发",
|
||||||
icon: <PoolSvg className="w-5 h-5" />,
|
icon: <PoolSvg className="w-5 h-5" />,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// id: 'proxies',
|
id: 'proxies',
|
||||||
// title: '节点池',
|
title: '节点池',
|
||||||
// icon: <PoolSvg className="w-5 h-5" />,
|
icon: <PoolSvg className="w-5 h-5" />,
|
||||||
// },
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleClickMenu = (index: number) => {
|
const handleClickMenu = (index: number) => {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { FormInstance } from "antd";
|
|||||||
import { FormDialog } from "@/components/FormDialog";
|
import { FormDialog } from "@/components/FormDialog";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import { nodeList,getRandomNodes } from "@/store/datas";
|
import { nodeList, getRandomNodes } from "@/store/datas";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { EllipsisTooltip } from "@/components/Encapsulation";
|
import { EllipsisTooltip } from "@/components/Encapsulation";
|
||||||
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
|
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
|
||||||
@ -11,170 +11,133 @@ import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
|
|||||||
import { NODEDIALOGTYPE } from "../../index";
|
import { NODEDIALOGTYPE } from "../../index";
|
||||||
import { cn, getUrl } from "@/lib/utils";
|
import { cn, getUrl } from "@/lib/utils";
|
||||||
|
|
||||||
import {
|
import { } from "@/store/web3Slice";
|
||||||
setClearFailTimer,
|
|
||||||
setClearWarningTimer,
|
|
||||||
} from "@/store/web3Slice";
|
|
||||||
import { AppDispatch, RootState } from "@/store";
|
import { AppDispatch, RootState } from "@/store";
|
||||||
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
|
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
|
||||||
|
|
||||||
export interface DialogConfig {
|
export interface DialogConfig {
|
||||||
title: string;
|
title: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
successText: string;
|
successText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
|
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
|
||||||
props
|
props
|
||||||
) => {
|
) => {
|
||||||
const { name, code, exit = false } = props.proxyInfo;
|
const { name, code, exit = false } = props.proxyInfo;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
|
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
|
||||||
exit && "hover:bg-[#EFF6FF]",
|
exit && "hover:bg-[#EFF6FF]",
|
||||||
props.clasName
|
props.clasName
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex-1 flex items-center justify-end w-full h-7">
|
<div className="flex-1 flex items-center justify-end w-full h-7">
|
||||||
<div className="flex-1 flex space-x-3 items-center">
|
<div className="flex-1 flex space-x-3 items-center">
|
||||||
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
|
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
|
||||||
<img
|
<img
|
||||||
className={cn(
|
className={cn("w-full h-full object-cover rounded-sm")}
|
||||||
"w-full h-full object-cover rounded-sm"
|
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
|
||||||
)}
|
/>
|
||||||
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
|
</div>
|
||||||
/>
|
<EllipsisTooltip
|
||||||
</div>
|
className="text-lg flex-1 font-semibold"
|
||||||
<EllipsisTooltip
|
text={name}
|
||||||
className="text-lg flex-1 font-semibold"
|
/>
|
||||||
text={name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClearNodeDialog = ({
|
export const ClearNodeDialog = ({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
successHandle,
|
successHandle,
|
||||||
dialogLoading,
|
dialogLoading,
|
||||||
form,
|
form,
|
||||||
type,
|
type,
|
||||||
canSubmit,
|
canSubmit,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
successHandle: () => void;
|
successHandle: () => void;
|
||||||
dialogLoading: boolean;
|
dialogLoading: boolean;
|
||||||
canSubmit: boolean;
|
canSubmit: boolean;
|
||||||
form: FormInstance;
|
form: FormInstance;
|
||||||
type: DialogConfig;
|
type: DialogConfig;
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const { clearWarningTimer, clearFailTimer } = useSelector(
|
const { } = useSelector(
|
||||||
(state: RootState) => state.web3Reducer
|
(state: RootState) => state.web3Reducer
|
||||||
);
|
);
|
||||||
const [isClear, setIsClear] = useState(false);
|
const [isClear, setIsClear] = useState(false);
|
||||||
const showDialog = (open: boolean) => {
|
const showDialog = (open: boolean) => {
|
||||||
setOpen(open);
|
setOpen(open);
|
||||||
};
|
};
|
||||||
const onSuccessHandle = () => {
|
const onSuccessHandle = () => {
|
||||||
successHandle();
|
successHandle();
|
||||||
setIsClear(true);
|
setIsClear(true);
|
||||||
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
||||||
dispatch(setClearFailTimer(Date.now()));
|
|
||||||
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
||||||
dispatch(setClearWarningTimer(Date.now()));
|
|
||||||
}
|
}
|
||||||
// showDialog(false);
|
// showDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const proxyList = useMemo(() => {
|
const proxyList = useMemo(() => {
|
||||||
const newData = getRandomNodes(nodeList);
|
const newData = getRandomNodes(nodeList);
|
||||||
if (open) {
|
|
||||||
if (isClear) return [];
|
|
||||||
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
|
||||||
if (clearFailTimer) {
|
|
||||||
const clear =
|
|
||||||
isTimestampPlusTenMinutesBeforeNow(clearFailTimer);
|
|
||||||
if (clear) {
|
|
||||||
return newData
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
|
||||||
if (clearWarningTimer) {
|
|
||||||
const clear =
|
|
||||||
isTimestampPlusTenMinutesBeforeNow(clearWarningTimer);
|
|
||||||
console.log(clear,'clear')
|
|
||||||
if (clear) {
|
|
||||||
return newData
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newData;
|
return newData;
|
||||||
}
|
}, [nodeList, open, isClear, type]);
|
||||||
// 随机 2-9条 nodelist里面的数据
|
|
||||||
return [];
|
|
||||||
}, [nodeList, open, isClear, clearFailTimer, clearWarningTimer, type]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setIsClear(false);
|
setIsClear(false);
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormDialog
|
<FormDialog
|
||||||
open={open}
|
open={open}
|
||||||
openChange={showDialog}
|
openChange={showDialog}
|
||||||
title={type.title}
|
title={type.title}
|
||||||
describe={type.desc}
|
describe={type.desc}
|
||||||
successText={type.successText}
|
successText={type.successText}
|
||||||
successHandle={onSuccessHandle}
|
successHandle={onSuccessHandle}
|
||||||
submitLoading={dialogLoading}
|
submitLoading={dialogLoading}
|
||||||
form={form}
|
form={form}
|
||||||
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
||||||
successStyle={
|
successStyle={
|
||||||
canSubmit
|
canSubmit
|
||||||
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
|
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
|
||||||
: "opacity-50"
|
: "opacity-50"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{proxyList.length > 0 ? (
|
{proxyList.length > 0 ? (
|
||||||
proxyList.map((item) => {
|
proxyList.map((item) => {
|
||||||
return <ProxyItem proxyInfo={item} key={item.name} />;
|
return <ProxyItem proxyInfo={item} key={item.name} />;
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-[382px] flex flex-col items-center justify-center">
|
<div className="w-full h-[382px] flex flex-col items-center justify-center">
|
||||||
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
|
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
|
||||||
<NotFailNodeIcon />
|
<NotFailNodeIcon />
|
||||||
) : (
|
) : (
|
||||||
<NotWarningNodeIcon />
|
<NotWarningNodeIcon />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
|
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
|
||||||
{type.title === NODEDIALOGTYPE.ClearFailNode.title
|
{type.title === NODEDIALOGTYPE.ClearFailNode.title
|
||||||
? "暂无掉线节点"
|
? "暂无掉线节点"
|
||||||
: "暂无恶意节点"}
|
: "暂无恶意节点"}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</FormDialog>
|
</div>
|
||||||
);
|
)}
|
||||||
|
</div>
|
||||||
|
</FormDialog>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -702,7 +702,8 @@ export const WorldGeo = memo(
|
|||||||
show: true, // 是否显示
|
show: true, // 是否显示
|
||||||
period: 4, // 特效动画时间
|
period: 4, // 特效动画时间
|
||||||
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
||||||
symbol: planePathImg, // 特效图形标记
|
color: pathColor, // 特效颜色
|
||||||
|
// symbol: planePathImg, // 特效图形标记
|
||||||
symbolSize: [10, 20],
|
symbolSize: [10, 20],
|
||||||
},
|
},
|
||||||
// 线条样式
|
// 线条样式
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { FormInstance } from "antd";
|
|||||||
import { FormDialog } from "@/components/FormDialog";
|
import { FormDialog } from "@/components/FormDialog";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import { nodeList,getRandomNodes } from "@/store/datas";
|
import { nodeList, getRandomNodes } from "@/store/datas";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { EllipsisTooltip } from "@/components/Encapsulation";
|
import { EllipsisTooltip } from "@/components/Encapsulation";
|
||||||
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
|
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
|
||||||
@ -11,170 +11,127 @@ import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
|
|||||||
import { NODEDIALOGTYPE } from "../../index";
|
import { NODEDIALOGTYPE } from "../../index";
|
||||||
import { cn, getUrl } from "@/lib/utils";
|
import { cn, getUrl } from "@/lib/utils";
|
||||||
|
|
||||||
import {
|
|
||||||
setClearFailTimer,
|
|
||||||
setClearWarningTimer,
|
|
||||||
} from "@/store/web3Slice";
|
|
||||||
import { AppDispatch, RootState } from "@/store";
|
import { AppDispatch, RootState } from "@/store";
|
||||||
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
|
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
|
||||||
|
|
||||||
export interface DialogConfig {
|
export interface DialogConfig {
|
||||||
title: string;
|
title: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
successText: string;
|
successText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
|
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
|
||||||
props
|
props
|
||||||
) => {
|
) => {
|
||||||
const { name, code, exit = false } = props.proxyInfo;
|
const { name, code, exit = false } = props.proxyInfo;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
|
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
|
||||||
exit && "hover:bg-[#EFF6FF]",
|
exit && "hover:bg-[#EFF6FF]",
|
||||||
props.clasName
|
props.clasName
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex-1 flex items-center justify-end w-full h-7">
|
<div className="flex-1 flex items-center justify-end w-full h-7">
|
||||||
<div className="flex-1 flex space-x-3 items-center">
|
<div className="flex-1 flex space-x-3 items-center">
|
||||||
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
|
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
|
||||||
<img
|
<img
|
||||||
className={cn(
|
className={cn("w-full h-full object-cover rounded-sm")}
|
||||||
"w-full h-full object-cover rounded-sm"
|
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
|
||||||
)}
|
/>
|
||||||
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
|
</div>
|
||||||
/>
|
<EllipsisTooltip
|
||||||
</div>
|
className="text-lg flex-1 font-semibold"
|
||||||
<EllipsisTooltip
|
text={name}
|
||||||
className="text-lg flex-1 font-semibold"
|
/>
|
||||||
text={name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClearNodeDialog = ({
|
export const ClearNodeDialog = ({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
successHandle,
|
successHandle,
|
||||||
dialogLoading,
|
dialogLoading,
|
||||||
form,
|
form,
|
||||||
type,
|
type,
|
||||||
canSubmit,
|
canSubmit,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
successHandle: () => void;
|
successHandle: () => void;
|
||||||
dialogLoading: boolean;
|
dialogLoading: boolean;
|
||||||
canSubmit: boolean;
|
canSubmit: boolean;
|
||||||
form: FormInstance;
|
form: FormInstance;
|
||||||
type: DialogConfig;
|
type: DialogConfig;
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const { clearWarningTimer, clearFailTimer } = useSelector(
|
const {} = useSelector((state: RootState) => state.web3Reducer);
|
||||||
(state: RootState) => state.web3Reducer
|
const [isClear, setIsClear] = useState(false);
|
||||||
);
|
const showDialog = (open: boolean) => {
|
||||||
const [isClear, setIsClear] = useState(false);
|
setOpen(open);
|
||||||
const showDialog = (open: boolean) => {
|
};
|
||||||
setOpen(open);
|
const onSuccessHandle = () => {
|
||||||
};
|
successHandle();
|
||||||
const onSuccessHandle = () => {
|
setIsClear(true);
|
||||||
successHandle();
|
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
||||||
setIsClear(true);
|
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
||||||
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
}
|
||||||
dispatch(setClearFailTimer(Date.now()));
|
// showDialog(false);
|
||||||
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
};
|
||||||
dispatch(setClearWarningTimer(Date.now()));
|
|
||||||
}
|
|
||||||
// showDialog(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const proxyList = useMemo(() => {
|
const proxyList = useMemo(() => {
|
||||||
const newData = getRandomNodes(nodeList);
|
// 随机 2-9条 nodelist里面的数据
|
||||||
if (open) {
|
return [];
|
||||||
if (isClear) return [];
|
}, [nodeList, open, isClear, type]);
|
||||||
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
|
||||||
if (clearFailTimer) {
|
|
||||||
const clear =
|
|
||||||
isTimestampPlusTenMinutesBeforeNow(clearFailTimer);
|
|
||||||
if (clear) {
|
|
||||||
return newData
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
|
||||||
if (clearWarningTimer) {
|
|
||||||
const clear =
|
|
||||||
isTimestampPlusTenMinutesBeforeNow(clearWarningTimer);
|
|
||||||
console.log(clear,'clear')
|
|
||||||
if (clear) {
|
|
||||||
return newData
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newData;
|
useEffect(() => {
|
||||||
}
|
if (!open) {
|
||||||
// 随机 2-9条 nodelist里面的数据
|
setIsClear(false);
|
||||||
return [];
|
}
|
||||||
}, [nodeList, open, isClear, clearFailTimer, clearWarningTimer, type]);
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
if (!open) {
|
<FormDialog
|
||||||
setIsClear(false);
|
open={open}
|
||||||
}
|
openChange={showDialog}
|
||||||
}, [open]);
|
title={type.title}
|
||||||
|
describe={type.desc}
|
||||||
|
successText={type.successText}
|
||||||
|
successHandle={onSuccessHandle}
|
||||||
|
submitLoading={dialogLoading}
|
||||||
|
form={form}
|
||||||
|
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
||||||
|
successStyle={
|
||||||
|
canSubmit
|
||||||
|
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
|
||||||
|
: "opacity-50"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{proxyList.length > 0 ? (
|
||||||
|
proxyList.map((item) => {
|
||||||
|
return <ProxyItem proxyInfo={item} key={item.name} />;
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-[382px] flex flex-col items-center justify-center">
|
||||||
|
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
|
||||||
|
<NotFailNodeIcon />
|
||||||
|
) : (
|
||||||
|
<NotWarningNodeIcon />
|
||||||
|
)}
|
||||||
|
|
||||||
return (
|
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
|
||||||
<FormDialog
|
{type.title === NODEDIALOGTYPE.ClearFailNode.title
|
||||||
open={open}
|
? "暂无掉线节点"
|
||||||
openChange={showDialog}
|
: "暂无恶意节点"}
|
||||||
title={type.title}
|
|
||||||
describe={type.desc}
|
|
||||||
successText={type.successText}
|
|
||||||
successHandle={onSuccessHandle}
|
|
||||||
submitLoading={dialogLoading}
|
|
||||||
form={form}
|
|
||||||
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
|
||||||
successStyle={
|
|
||||||
canSubmit
|
|
||||||
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
|
|
||||||
: "opacity-50"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
{proxyList.length > 0 ? (
|
|
||||||
proxyList.map((item) => {
|
|
||||||
return <ProxyItem proxyInfo={item} key={item.name} />;
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div className="w-full h-[382px] flex flex-col items-center justify-center">
|
|
||||||
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
|
|
||||||
<NotFailNodeIcon />
|
|
||||||
) : (
|
|
||||||
<NotWarningNodeIcon />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
|
|
||||||
{type.title === NODEDIALOGTYPE.ClearFailNode.title
|
|
||||||
? "暂无掉线节点"
|
|
||||||
: "暂无恶意节点"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</FormDialog>
|
</div>
|
||||||
);
|
)}
|
||||||
|
</div>
|
||||||
|
</FormDialog>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,217 +8,237 @@ import { DIALOGTYPE } from "../../index";
|
|||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { DefaultLink } from "./pathChoose";
|
import { DefaultLink } from "./pathChoose";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
export interface DialogConfig {
|
export interface DialogConfig {
|
||||||
title: string;
|
title: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
successText: string;
|
successText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PledgeAmount = ({
|
const PledgeAmount = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (data: string) => void;
|
onChange?: (data: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Input
|
<Input
|
||||||
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
|
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
|
||||||
placeholder="请输质押金额*"
|
placeholder="请输质押金额*"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChange?.(e.target.value);
|
onChange?.(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span>SOL</span>
|
<span>SOL</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const IpPortInput = ({
|
const IpPortInput = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
value?: { port: string; ip: string };
|
value?: { port: string; ip: string };
|
||||||
onChange?: (data: { port: string; ip: string }) => void;
|
onChange?: (data: { port: string; ip: string }) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [ipAndPort, setIpAndPort] = useState<{
|
const [ipAndPort, setIpAndPort] = useState<{
|
||||||
port: string;
|
port: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
}>(value ? value : { port: "", ip: "" });
|
}>(value ? value : { port: "", ip: "" });
|
||||||
const handleChagne = ({
|
const handleChagne = ({ key, val }: { key: "port" | "ip"; val: string }) => {
|
||||||
key,
|
const newValue = value ? { ...value } : { port: "", ip: "" };
|
||||||
val,
|
newValue[key] = val;
|
||||||
}: {
|
onChange?.(newValue);
|
||||||
key: "port" | "ip";
|
};
|
||||||
val: string;
|
useEffect(() => {
|
||||||
}) => {
|
if (value) {
|
||||||
const newValue = value ? { ...value } : { port: "", ip: "" };
|
setIpAndPort(value);
|
||||||
newValue[key] = val;
|
}
|
||||||
onChange?.(newValue);
|
}, [value]);
|
||||||
};
|
return (
|
||||||
useEffect(() => {
|
<div className="flex items-center gap-1.5">
|
||||||
if (value) {
|
<Input
|
||||||
setIpAndPort(value);
|
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
|
||||||
}
|
placeholder="请输入IP"
|
||||||
}, [value]);
|
value={ipAndPort?.ip}
|
||||||
return (
|
onChange={(e) => {
|
||||||
<div className="flex items-center gap-1.5">
|
handleChagne?.({ key: "ip", val: e.target.value });
|
||||||
<Input
|
}}
|
||||||
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
|
/>
|
||||||
placeholder="请输入IP*"
|
<Input
|
||||||
value={ipAndPort?.ip}
|
className="data-[state=checked]:bg-[#1E3A8A] "
|
||||||
onChange={(e) => {
|
placeholder="请输入端口"
|
||||||
handleChagne?.({ key: "ip", val: e.target.value });
|
type="number"
|
||||||
}}
|
min={0}
|
||||||
/>
|
max={65535}
|
||||||
<Input
|
value={ipAndPort?.port}
|
||||||
className="data-[state=checked]:bg-[#1E3A8A] "
|
onChange={(e) => {
|
||||||
placeholder="请输入端口"
|
handleChagne?.({ key: "port", val: e.target.value });
|
||||||
type="number"
|
}}
|
||||||
min={0}
|
/>
|
||||||
max={65535}
|
</div>
|
||||||
value={ipAndPort?.port}
|
);
|
||||||
onChange={(e) => {
|
};
|
||||||
handleChagne?.({ key: "port", val: e.target.value });
|
|
||||||
}}
|
const SwitchComponent = ({
|
||||||
/>
|
value,
|
||||||
</div>
|
onChange,
|
||||||
);
|
}: {
|
||||||
|
value?: boolean;
|
||||||
|
onChange?: (data: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const [checked, setChecked] = useState(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Switch
|
||||||
|
className="data-[state=checked]:bg-[#1E3A8A]"
|
||||||
|
checked={checked}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
setChecked(e);
|
||||||
|
onChange?.(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="ml-2 text-zinc-900 text-sm font-medium leading-tight">
|
||||||
|
是否为出口节点
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NodeForm = ({ form }: { form: FormInstance }) => {
|
const NodeForm = ({ form }: { form: FormInstance }) => {
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
className="-mt-1"
|
className="-mt-1"
|
||||||
form={form}
|
form={form}
|
||||||
name="dynamic_form_nest_item"
|
name="dynamic_form_nest_item"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
{/* <Form.Item name="uid" className="hidden">
|
{/* <Form.Item name="uid" className="hidden">
|
||||||
<div className="hidden">uid</div>
|
<div className="hidden">uid</div>
|
||||||
</Form.Item> */}
|
</Form.Item> */}
|
||||||
<Form.Item name="public_key" label="节点身份公钥">
|
<Form.Item name="name" label="节点名称" rules={[{ required: true, message: '请输入节点名称' }]}>
|
||||||
<Input
|
<Input
|
||||||
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
|
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
|
||||||
placeholder="节点身份公钥*"
|
placeholder="节点名称"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="private_key" label="节点元数据">
|
<Form.Item name="private_key" label="私钥" rules={[{ required: true, message: '请输入私钥' }]}>
|
||||||
<Input
|
<Input
|
||||||
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
|
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
|
||||||
placeholder="请输入TLS Pubkey*"
|
placeholder="私钥"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="ipAndPort" label="IP+端口">
|
<Form.Item name="public_key" label="公钥" rules={[{ required: true, message: '请输入公钥' }]}>
|
||||||
<IpPortInput />
|
<Input
|
||||||
</Form.Item>
|
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
|
||||||
<Form.Item name="pledgeAmount" label="质押金额">
|
placeholder="公钥"
|
||||||
<PledgeAmount />
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="walletAddress" label="钱包地址">
|
<Form.Item name="ipAndPort" label="IP+端口" rules={[{ required: true, message: '请输入IP+端口' }]}>
|
||||||
<Input
|
<IpPortInput />
|
||||||
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
|
</Form.Item>
|
||||||
placeholder="钱包地址*"
|
<Form.Item name="exit">
|
||||||
/>
|
<SwitchComponent />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NetworkForm = ({ form }: { form: FormInstance }) => {
|
const NetworkForm = ({ form }: { form: FormInstance }) => {
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
className="-mt-1"
|
className="-mt-1"
|
||||||
form={form}
|
form={form}
|
||||||
name="dynamic_form_nest_item"
|
name="dynamic_form_nest_item"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="inbound"
|
name="inbound"
|
||||||
label="入口节点"
|
label="入口节点"
|
||||||
rules={[{ required: true, message: "请选择入口节点" }]}
|
rules={[{ required: true, message: "请选择入口节点" }]}
|
||||||
>
|
>
|
||||||
<DefaultLink
|
<DefaultLink
|
||||||
des="入口节点"
|
des="入口节点"
|
||||||
type="entry"
|
type="entry"
|
||||||
countries={Object.keys(countryCodeMap)
|
countries={Object.keys(countryCodeMap)
|
||||||
// .splice(0, 50)
|
// .splice(0, 50)
|
||||||
.map((key) => key)}
|
.map((key) => key)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="outbound"
|
name="outbound"
|
||||||
label="出口节点"
|
label="出口节点"
|
||||||
rules={[{ required: true, message: "请选择出口节点" }]}
|
rules={[{ required: true, message: "请选择出口节点" }]}
|
||||||
>
|
>
|
||||||
<DefaultLink
|
<DefaultLink
|
||||||
des="出口节点"
|
des="出口节点"
|
||||||
type="exit"
|
type="exit"
|
||||||
countries={Object.keys(countryCodeMap)
|
countries={Object.keys(countryCodeMap)
|
||||||
// .splice(0, 50)
|
// .splice(0, 50)
|
||||||
.map((key) => key)}
|
.map((key) => key)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FormAlertDialog = ({
|
export const FormAlertDialog = ({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
successHandle,
|
successHandle,
|
||||||
dialogLoading,
|
dialogLoading,
|
||||||
form,
|
form,
|
||||||
type,
|
type,
|
||||||
canSubmit,
|
canSubmit,
|
||||||
handleSelectFile,
|
handleSelectFile,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
successHandle: () => void;
|
successHandle: () => void;
|
||||||
dialogLoading: boolean;
|
dialogLoading: boolean;
|
||||||
canSubmit: boolean;
|
canSubmit: boolean;
|
||||||
form: FormInstance;
|
form: FormInstance;
|
||||||
type: DialogConfig;
|
type: DialogConfig;
|
||||||
handleSelectFile: () => void;
|
handleSelectFile: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const showDialog = (open: boolean) => {
|
const showDialog = (open: boolean) => {
|
||||||
setOpen(open);
|
setOpen(open);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
return (
|
<FormDialog
|
||||||
<FormDialog
|
open={open}
|
||||||
open={open}
|
openChange={showDialog}
|
||||||
openChange={showDialog}
|
title={type.title}
|
||||||
title={type.title}
|
describe={type.desc}
|
||||||
describe={type.desc}
|
successText={type.successText}
|
||||||
successText={type.successText}
|
successHandle={successHandle}
|
||||||
successHandle={successHandle}
|
submitLoading={dialogLoading}
|
||||||
submitLoading={dialogLoading}
|
form={form}
|
||||||
form={form}
|
contentClass="w-[850px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
||||||
contentClass="w-[850px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
successStyle={
|
||||||
successStyle={
|
canSubmit
|
||||||
canSubmit
|
? "bg-[#1E3A8A] hover:bg-[#1D4ED8] active:bg-[#1E40AF]"
|
||||||
? "bg-[#1E3A8A] hover:bg-[#1D4ED8] active:bg-[#1E40AF]"
|
: "bg-[#1E3A8A] hover:bg-[#1E3A8A] opacity-50"
|
||||||
: "bg-[#1E3A8A] hover:bg-[#1E3A8A] opacity-50"
|
}
|
||||||
}
|
>
|
||||||
>
|
<Button
|
||||||
<Button
|
className="absolute top-3 right-12 bg-transparent text-zinc-900 border border-zinc-200"
|
||||||
className="absolute top-3 right-12 bg-transparent text-zinc-900 border border-zinc-200"
|
onClick={() => handleSelectFile()}
|
||||||
onClick={() => handleSelectFile()}
|
>
|
||||||
>
|
批量导入
|
||||||
批量导入
|
</Button>
|
||||||
</Button>
|
{open && type.title === DIALOGTYPE.ADDNode.title ? (
|
||||||
{open && type.title === DIALOGTYPE.ADDNode.title ? (
|
<NodeForm form={form} />
|
||||||
<NodeForm form={form} />
|
) : (
|
||||||
) : (
|
<NetworkForm form={form} />
|
||||||
<NetworkForm form={form} />
|
)}
|
||||||
)}
|
</FormDialog>
|
||||||
</FormDialog>
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -91,7 +91,6 @@ export const WorldGeo = memo(({ screenData }: { screenData: any }) => {
|
|||||||
const mainToData = useMemo(() => {
|
const mainToData = useMemo(() => {
|
||||||
// 使用新的数据结构
|
// 使用新的数据结构
|
||||||
const proxiesList = screenData?.proxy_info?.proxies ?? [{data:[{country_code: 'AI', ingress_country_code: 'AE'}], isLine: true}];
|
const proxiesList = screenData?.proxy_info?.proxies ?? [{data:[{country_code: 'AI', ingress_country_code: 'AE'}], isLine: true}];
|
||||||
console.log(proxiesList,'proxiesList')
|
|
||||||
// 初始化数据数组 - 不再包含 startCountry
|
// 初始化数据数组 - 不再包含 startCountry
|
||||||
const data: any = [];
|
const data: any = [];
|
||||||
|
|
||||||
@ -418,7 +417,8 @@ export const WorldGeo = memo(({ screenData }: { screenData: any }) => {
|
|||||||
show: true, // 是否显示
|
show: true, // 是否显示
|
||||||
period: 4, // 特效动画时间
|
period: 4, // 特效动画时间
|
||||||
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
||||||
symbol: planePathImg, // 特效图形标记
|
// symbol: planePathImg, // 特效图形标记
|
||||||
|
color:"#0ea5e9",
|
||||||
symbolSize: [10, 20],
|
symbolSize: [10, 20],
|
||||||
},
|
},
|
||||||
// 线条样式
|
// 线条样式
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { FormInstance } from "antd";
|
|||||||
import { FormDialog } from "@/components/FormDialog";
|
import { FormDialog } from "@/components/FormDialog";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import { nodeList,getRandomNodes } from "@/store/datas";
|
import { nodeList, getRandomNodes } from "@/store/datas";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { EllipsisTooltip } from "@/components/Encapsulation";
|
import { EllipsisTooltip } from "@/components/Encapsulation";
|
||||||
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
|
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
|
||||||
@ -11,170 +11,128 @@ import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
|
|||||||
import { NODEDIALOGTYPE } from "../../index";
|
import { NODEDIALOGTYPE } from "../../index";
|
||||||
import { cn, getUrl } from "@/lib/utils";
|
import { cn, getUrl } from "@/lib/utils";
|
||||||
|
|
||||||
import {
|
|
||||||
setClearFailTimer,
|
|
||||||
setClearWarningTimer,
|
|
||||||
} from "@/store/web3Slice";
|
|
||||||
import { AppDispatch, RootState } from "@/store";
|
import { AppDispatch, RootState } from "@/store";
|
||||||
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
|
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
|
||||||
|
|
||||||
export interface DialogConfig {
|
export interface DialogConfig {
|
||||||
title: string;
|
title: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
successText: string;
|
successText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
|
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
|
||||||
props
|
props
|
||||||
) => {
|
) => {
|
||||||
const { name, code, exit = false } = props.proxyInfo;
|
const { name, code, exit = false } = props.proxyInfo;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
|
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
|
||||||
exit && "hover:bg-[#EFF6FF]",
|
exit && "hover:bg-[#EFF6FF]",
|
||||||
props.clasName
|
props.clasName
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex-1 flex items-center justify-end w-full h-7">
|
<div className="flex-1 flex items-center justify-end w-full h-7">
|
||||||
<div className="flex-1 flex space-x-3 items-center">
|
<div className="flex-1 flex space-x-3 items-center">
|
||||||
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
|
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
|
||||||
<img
|
<img
|
||||||
className={cn(
|
className={cn("w-full h-full object-cover rounded-sm")}
|
||||||
"w-full h-full object-cover rounded-sm"
|
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
|
||||||
)}
|
/>
|
||||||
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
|
</div>
|
||||||
/>
|
<EllipsisTooltip
|
||||||
</div>
|
className="text-lg flex-1 font-semibold"
|
||||||
<EllipsisTooltip
|
text={name}
|
||||||
className="text-lg flex-1 font-semibold"
|
/>
|
||||||
text={name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClearNodeDialog = ({
|
export const ClearNodeDialog = ({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
successHandle,
|
successHandle,
|
||||||
dialogLoading,
|
dialogLoading,
|
||||||
form,
|
form,
|
||||||
type,
|
type,
|
||||||
canSubmit,
|
canSubmit,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
successHandle: () => void;
|
successHandle: () => void;
|
||||||
dialogLoading: boolean;
|
dialogLoading: boolean;
|
||||||
canSubmit: boolean;
|
canSubmit: boolean;
|
||||||
form: FormInstance;
|
form: FormInstance;
|
||||||
type: DialogConfig;
|
type: DialogConfig;
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const { clearWarningTimer, clearFailTimer } = useSelector(
|
const {} = useSelector((state: RootState) => state.web3Reducer);
|
||||||
(state: RootState) => state.web3Reducer
|
const [isClear, setIsClear] = useState(false);
|
||||||
);
|
const showDialog = (open: boolean) => {
|
||||||
const [isClear, setIsClear] = useState(false);
|
setOpen(open);
|
||||||
const showDialog = (open: boolean) => {
|
};
|
||||||
setOpen(open);
|
const onSuccessHandle = () => {
|
||||||
};
|
successHandle();
|
||||||
const onSuccessHandle = () => {
|
setIsClear(true);
|
||||||
successHandle();
|
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
||||||
setIsClear(true);
|
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
||||||
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
}
|
||||||
dispatch(setClearFailTimer(Date.now()));
|
// showDialog(false);
|
||||||
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
};
|
||||||
dispatch(setClearWarningTimer(Date.now()));
|
|
||||||
}
|
|
||||||
// showDialog(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const proxyList = useMemo(() => {
|
const proxyList = useMemo(() => {
|
||||||
const newData = getRandomNodes(nodeList);
|
const newData = getRandomNodes(nodeList);
|
||||||
if (open) {
|
|
||||||
if (isClear) return [];
|
|
||||||
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
|
|
||||||
if (clearFailTimer) {
|
|
||||||
const clear =
|
|
||||||
isTimestampPlusTenMinutesBeforeNow(clearFailTimer);
|
|
||||||
if (clear) {
|
|
||||||
return newData
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
|
|
||||||
if (clearWarningTimer) {
|
|
||||||
const clear =
|
|
||||||
isTimestampPlusTenMinutesBeforeNow(clearWarningTimer);
|
|
||||||
console.log(clear,'clear')
|
|
||||||
if (clear) {
|
|
||||||
return newData
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newData;
|
return [];
|
||||||
}
|
}, [nodeList, open, isClear, type]);
|
||||||
// 随机 2-9条 nodelist里面的数据
|
|
||||||
return [];
|
|
||||||
}, [nodeList, open, isClear, clearFailTimer, clearWarningTimer, type]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setIsClear(false);
|
setIsClear(false);
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormDialog
|
<FormDialog
|
||||||
open={open}
|
open={open}
|
||||||
openChange={showDialog}
|
openChange={showDialog}
|
||||||
title={type.title}
|
title={type.title}
|
||||||
describe={type.desc}
|
describe={type.desc}
|
||||||
successText={type.successText}
|
successText={type.successText}
|
||||||
successHandle={onSuccessHandle}
|
successHandle={onSuccessHandle}
|
||||||
submitLoading={dialogLoading}
|
submitLoading={dialogLoading}
|
||||||
form={form}
|
form={form}
|
||||||
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
|
||||||
successStyle={
|
successStyle={
|
||||||
canSubmit
|
canSubmit
|
||||||
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
|
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
|
||||||
: "opacity-50"
|
: "opacity-50"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{proxyList.length > 0 ? (
|
{proxyList.length > 0 ? (
|
||||||
proxyList.map((item) => {
|
proxyList.map((item) => {
|
||||||
return <ProxyItem proxyInfo={item} key={item.name} />;
|
return <ProxyItem proxyInfo={item} key={item.name} />;
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-[382px] flex flex-col items-center justify-center">
|
<div className="w-full h-[382px] flex flex-col items-center justify-center">
|
||||||
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
|
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
|
||||||
<NotFailNodeIcon />
|
<NotFailNodeIcon />
|
||||||
) : (
|
) : (
|
||||||
<NotWarningNodeIcon />
|
<NotWarningNodeIcon />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
|
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
|
||||||
{type.title === NODEDIALOGTYPE.ClearFailNode.title
|
{type.title === NODEDIALOGTYPE.ClearFailNode.title
|
||||||
? "暂无掉线节点"
|
? "暂无掉线节点"
|
||||||
: "暂无恶意节点"}
|
: "暂无恶意节点"}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</FormDialog>
|
</div>
|
||||||
);
|
)}
|
||||||
|
</div>
|
||||||
|
</FormDialog>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export const WorldGeo = memo(
|
|||||||
const labelsRef = useRef<HTMLDivElement[]>([]);
|
const labelsRef = useRef<HTMLDivElement[]>([]);
|
||||||
const mainToData = useMemo(() => {
|
const mainToData = useMemo(() => {
|
||||||
const newList = [
|
const newList = [
|
||||||
...dataInfo.passAuthentication.data,
|
dataInfo.passAuthentication,
|
||||||
...dataInfo.trafficObfuscation,
|
...dataInfo.trafficObfuscation,
|
||||||
...dataInfo.nestedEncryption,
|
...dataInfo.nestedEncryption,
|
||||||
...dataInfo.dynamicRouteGeneration,
|
...dataInfo.dynamicRouteGeneration,
|
||||||
@ -239,7 +239,8 @@ export const WorldGeo = memo(
|
|||||||
const positionCustomTooltip2 = () => {
|
const positionCustomTooltip2 = () => {
|
||||||
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
|
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
|
||||||
// 找到US点
|
// 找到US点
|
||||||
const coords = geoCoordMap[dataInfo.trafficObfuscation?.[0]?.code ?? "ZA"];
|
const coords =
|
||||||
|
geoCoordMap[dataInfo.trafficObfuscation?.[0]?.code ?? "ZA"];
|
||||||
if (!coords) return;
|
if (!coords) return;
|
||||||
try {
|
try {
|
||||||
// 将地理坐标转换为屏幕坐标
|
// 将地理坐标转换为屏幕坐标
|
||||||
@ -261,27 +262,7 @@ export const WorldGeo = memo(
|
|||||||
console.error("Error positioning tooltip:", error);
|
console.error("Error positioning tooltip:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 主线每个节点tip竖线的经纬度,修改tip 竖线的高度也可以用这个
|
|
||||||
const mianLineData = (data: typeof mainToData) => {
|
|
||||||
return (
|
|
||||||
data
|
|
||||||
.map((item: any) => {
|
|
||||||
const countryCode = item.country_code.toUpperCase();
|
|
||||||
if (!(["RU", "FR"].includes(countryCode) && item.type === "start"))
|
|
||||||
return null;
|
|
||||||
const coords = geoCoordMap[countryCode] as
|
|
||||||
| [number, number]
|
|
||||||
| undefined;
|
|
||||||
if (!coords) return null;
|
|
||||||
return {
|
|
||||||
name: countryCodeMap[countryCode],
|
|
||||||
coords: [coords, [coords[0], coords[1] + 4]],
|
|
||||||
value: countryCode,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((v: any) => !!v) ?? []
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const getLineItem = (
|
const getLineItem = (
|
||||||
preCode: string,
|
preCode: string,
|
||||||
nextCode: string
|
nextCode: string
|
||||||
@ -746,7 +727,8 @@ export const WorldGeo = memo(
|
|||||||
show: true, // 是否显示
|
show: true, // 是否显示
|
||||||
period: 4, // 特效动画时间
|
period: 4, // 特效动画时间
|
||||||
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
||||||
symbol: planePathImg, // 特效图形标记
|
color: lastExitColor, // 特效颜色
|
||||||
|
// symbol: planePathImg, // 特效图形标记
|
||||||
symbolSize: [10, 20],
|
symbolSize: [10, 20],
|
||||||
},
|
},
|
||||||
// 线条样式
|
// 线条样式
|
||||||
@ -772,7 +754,6 @@ export const WorldGeo = memo(
|
|||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
||||||
const lastExitColor =
|
const lastExitColor =
|
||||||
(item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
|
(item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
|
||||||
|
|
||||||
// 添加虚线
|
// 添加虚线
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: item[0],
|
||||||
@ -807,7 +788,7 @@ export const WorldGeo = memo(
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建A点和B点,并添加飞线和标签
|
// 创建A点和B点,并添加飞线和标签
|
||||||
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
||||||
// 定义点A和点B的坐标
|
// 定义点A和点B的坐标
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createBrowserRouter, Navigate } from 'react-router-dom'
|
import { createBrowserRouter, Navigate } from 'react-router-dom'
|
||||||
// import HomePage from '@/pages/home'
|
import HomePage from '@/pages/home'
|
||||||
import NewHomePage from '@/pages/new-home'
|
import NewHomePage from '@/pages/new-home'
|
||||||
import DecentralizedElasticNetworkPage from '@/pages/decentralized-lastic-network'
|
import DecentralizedElasticNetworkPage from '@/pages/decentralized-lastic-network'
|
||||||
import AntiForensicsForwardingPage from '@/pages/anti-forensics-forwarding'
|
import AntiForensicsForwardingPage from '@/pages/anti-forensics-forwarding'
|
||||||
@ -35,7 +35,7 @@ export const router = createBrowserRouter([
|
|||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
path: '/proxies',
|
path: '/proxies',
|
||||||
element: <LazyLoader component={ProxiesPage} />,
|
element: <LazyLoader component={HomePage} />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -23,8 +23,6 @@ interface Iweb3Slice {
|
|||||||
newHomeProxies: any[];
|
newHomeProxies: any[];
|
||||||
path_list: any;
|
path_list: any;
|
||||||
proxy_info: any;
|
proxy_info: any;
|
||||||
clearWarningTimer: number | null;
|
|
||||||
clearFailTimer: number | null;
|
|
||||||
isLine: boolean;
|
isLine: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +102,6 @@ const initialState: Iweb3Slice = {
|
|||||||
change_at: 0,
|
change_at: 0,
|
||||||
proxies: [],
|
proxies: [],
|
||||||
},
|
},
|
||||||
clearWarningTimer: null,
|
|
||||||
clearFailTimer: null,
|
|
||||||
newHomeProxies: [
|
newHomeProxies: [
|
||||||
{
|
{
|
||||||
authenticationPoint: [
|
authenticationPoint: [
|
||||||
@ -187,7 +183,6 @@ export const appSlice = createSlice({
|
|||||||
|
|
||||||
// 进一步优化的代码
|
// 进一步优化的代码
|
||||||
if (state.proxy_info.proxies.length === 0) return;
|
if (state.proxy_info.proxies.length === 0) return;
|
||||||
|
|
||||||
// 标记所有代理为在线 - 这个操作仍然需要
|
// 标记所有代理为在线 - 这个操作仍然需要
|
||||||
state.proxy_info.proxies = state.proxy_info.proxies.map((item: any) => {
|
state.proxy_info.proxies = state.proxy_info.proxies.map((item: any) => {
|
||||||
item.isLine = true;
|
item.isLine = true;
|
||||||
@ -244,12 +239,6 @@ export const appSlice = createSlice({
|
|||||||
setIsLine: (state, action) => {
|
setIsLine: (state, action) => {
|
||||||
state.isLine = action.payload;
|
state.isLine = action.payload;
|
||||||
},
|
},
|
||||||
setClearWarningTimer: (state, action) => {
|
|
||||||
state.clearWarningTimer = action.payload;
|
|
||||||
},
|
|
||||||
setClearFailTimer: (state, action) => {
|
|
||||||
state.clearFailTimer = action.payload;
|
|
||||||
},
|
|
||||||
setWeb3List: (state, action) => {
|
setWeb3List: (state, action) => {
|
||||||
state.web3List = action.payload;
|
state.web3List = action.payload;
|
||||||
},
|
},
|
||||||
@ -281,8 +270,6 @@ export const {
|
|||||||
setProxyInfoProxies,
|
setProxyInfoProxies,
|
||||||
randomUpdateWeb3List,
|
randomUpdateWeb3List,
|
||||||
randomUpdateWeb3List2,
|
randomUpdateWeb3List2,
|
||||||
setClearWarningTimer,
|
|
||||||
setClearFailTimer,
|
|
||||||
reset,
|
reset,
|
||||||
setIsLine,
|
setIsLine,
|
||||||
setProxiesList1,
|
setProxiesList1,
|
||||||
|
|||||||
23
src/utils/eventBus.ts
Normal file
23
src/utils/eventBus.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import mitt from "mitt";
|
||||||
|
|
||||||
|
const eventBus = mitt();
|
||||||
|
|
||||||
|
// 事件类型
|
||||||
|
export const eventTypes = {
|
||||||
|
// 节点上线
|
||||||
|
NODE_UP: "node_up",
|
||||||
|
// 节点下线
|
||||||
|
NODE_DOWN: "node_down",
|
||||||
|
// 检测到恶意节点
|
||||||
|
MALICIOUS_NODE: "malicious_node",
|
||||||
|
// 节点预配置完成
|
||||||
|
NODE_INIT_COMPLATE: "node_init_complate",
|
||||||
|
// 节点清除
|
||||||
|
NODE_REMOVE: "node_remove",
|
||||||
|
// 添加节点
|
||||||
|
NODE_ADD: "node_add",
|
||||||
|
// 节点预配置
|
||||||
|
NODE_INIT: "node_init",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default eventBus;
|
||||||
@ -27,7 +27,7 @@ export class WebSocketClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
async sendMessage(message: string): Promise<void> {
|
async sendMessage(message: any): Promise<void> {
|
||||||
if (!this.ws) {
|
if (!this.ws) {
|
||||||
console.error('WebSocket is not connected')
|
console.error('WebSocket is not connected')
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user