diff --git a/pc-fe/package-lock.json b/pc-fe/package-lock.json
index 5880101..85e5d90 100644
--- a/pc-fe/package-lock.json
+++ b/pc-fe/package-lock.json
@@ -1,26 +1,23 @@
{
"name": "vdi-manager",
- "version": "1.0.5",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vdi-manager",
- "version": "1.0.5",
+ "version": "1.0.0",
"hasInstallScript": true,
"dependencies": {
"@ant-design/icons": "^6.0.0",
- "@grpc/grpc-js": "^1.13.4",
- "@grpc/proto-loader": "^0.8.0",
"antd": "^5.26.6",
"axios": "^1.11.0",
"classnames": "^2.5.1",
- "google-protobuf": "^4.0.0",
+ "electron-updater": "^6.6.2",
"umi": "^4.0.42"
},
"devDependencies": {
"@tsconfig/node14": "^1.0.3",
- "@types/google-protobuf": "^3.15.12",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@umijs/plugin-electron": "^0.2.0",
@@ -3211,55 +3208,6 @@
"use-isomorphic-layout-effect": "^1.1.1"
}
},
- "node_modules/@grpc/grpc-js": {
- "version": "1.13.4",
- "resolved": "https://registry.npmmirror.com/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
- "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@grpc/proto-loader": "^0.7.13",
- "@js-sdsl/ordered-map": "^4.4.2"
- },
- "engines": {
- "node": ">=12.10.0"
- }
- },
- "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
- "version": "0.7.15",
- "resolved": "https://registry.npmmirror.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
- "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "lodash.camelcase": "^4.3.0",
- "long": "^5.0.0",
- "protobufjs": "^7.2.5",
- "yargs": "^17.7.2"
- },
- "bin": {
- "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@grpc/proto-loader": {
- "version": "0.8.0",
- "resolved": "https://registry.npmmirror.com/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
- "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "lodash.camelcase": "^4.3.0",
- "long": "^5.0.0",
- "protobufjs": "^7.5.3",
- "yargs": "^17.7.2"
- },
- "bin": {
- "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -3653,16 +3601,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@js-sdsl/ordered-map": {
- "version": "4.4.2",
- "resolved": "https://registry.npmmirror.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
- "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/js-sdsl"
- }
- },
"node_modules/@loadable/component": {
"version": "5.15.2",
"resolved": "https://registry.npmmirror.com/@loadable/component/-/component-5.15.2.tgz",
@@ -4216,70 +4154,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmmirror.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmmirror.com/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/codegen": {
- "version": "2.0.4",
- "resolved": "https://registry.npmmirror.com/@protobufjs/codegen/-/codegen-2.0.4.tgz",
- "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/fetch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/@protobufjs/fetch/-/fetch-1.1.0.tgz",
- "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.1",
- "@protobufjs/inquire": "^1.1.0"
- }
- },
- "node_modules/@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmmirror.com/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/inquire": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/@protobufjs/inquire/-/inquire-1.1.0.tgz",
- "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmmirror.com/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/utf8": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/@protobufjs/utf8/-/utf8-1.1.0.tgz",
- "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
- "license": "BSD-3-Clause"
- },
"node_modules/@rc-component/async-validator": {
"version": "5.0.4",
"resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
@@ -4918,13 +4792,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/google-protobuf": {
- "version": "3.15.12",
- "resolved": "https://registry.npmmirror.com/@types/google-protobuf/-/google-protobuf-3.15.12.tgz",
- "integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -10251,9 +10118,7 @@
"version": "6.6.2",
"resolved": "https://registry.npmmirror.com/electron-updater/-/electron-updater-6.6.2.tgz",
"integrity": "sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==",
- "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"builder-util-runtime": "9.3.1",
"fs-extra": "^10.1.0",
@@ -10269,9 +10134,7 @@
"version": "9.3.1",
"resolved": "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.3.1.tgz",
"integrity": "sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==",
- "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
@@ -10284,9 +10147,7 @@
"version": "10.1.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
- "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -10300,9 +10161,7 @@
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
- "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"universalify": "^2.0.0"
},
@@ -10314,9 +10173,7 @@
"version": "7.7.2",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "dev": true,
"license": "ISC",
- "peer": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -10328,9 +10185,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">= 10.0.0"
}
@@ -12167,12 +12022,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/google-protobuf": {
- "version": "4.0.0",
- "resolved": "https://registry.npmmirror.com/google-protobuf/-/google-protobuf-4.0.0.tgz",
- "integrity": "sha512-b8wmenhUMf2WNL+xIJ/slvD/hEE6V3nRnG86O2bzkBrMweM9gnqZE1dfXlDjibY3aXJXDNbAHepevYyQ7qWKsQ==",
- "license": "(BSD-3-Clause AND Apache-2.0)"
- },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
@@ -13874,7 +13723,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/less": {
@@ -14242,12 +14090,6 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
- "node_modules/lodash.camelcase": {
- "version": "4.3.0",
- "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
- "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
- "license": "MIT"
- },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -14259,18 +14101,14 @@
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
- "dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
- "dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -14286,12 +14124,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/long": {
- "version": "5.3.2",
- "resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz",
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
- "license": "Apache-2.0"
- },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -16816,30 +16648,6 @@
"license": "ISC",
"optional": true
},
- "node_modules/protobufjs": {
- "version": "7.5.4",
- "resolved": "https://registry.npmmirror.com/protobufjs/-/protobufjs-7.5.4.tgz",
- "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
- "hasInstallScript": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.4",
- "@protobufjs/eventemitter": "^1.1.0",
- "@protobufjs/fetch": "^1.1.0",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.0",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.0",
- "@types/node": ">=13.7.0",
- "long": "^5.0.0"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -18554,7 +18362,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
- "devOptional": true,
"license": "ISC"
},
"node_modules/scheduler": {
@@ -20062,9 +19869,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
- "dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/titleize": {
"version": "3.0.0",
diff --git a/pc-fe/package.json b/pc-fe/package.json
index d4559e9..cd7af02 100644
--- a/pc-fe/package.json
+++ b/pc-fe/package.json
@@ -1,6 +1,6 @@
{
"name": "vdi-manager",
- "version": "1.0.5",
+ "version": "1.0.0",
"scripts": {
"dev": "umi dev",
"build": "umi build",
@@ -17,17 +17,14 @@
"homepage": "http://10.209.8.11/users/sign_in",
"dependencies": {
"@ant-design/icons": "^6.0.0",
- "@grpc/grpc-js": "^1.13.4",
- "@grpc/proto-loader": "^0.8.0",
"antd": "^5.26.6",
"axios": "^1.11.0",
"classnames": "^2.5.1",
- "google-protobuf": "^4.0.0",
+ "electron-updater": "^6.6.2",
"umi": "^4.0.42"
},
"devDependencies": {
"@tsconfig/node14": "^1.0.3",
- "@types/google-protobuf": "^3.15.12",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@umijs/plugin-electron": "^0.2.0",
@@ -61,4 +58,4 @@
"icon": "src/assets/unis.png"
}
}
-}
+}
\ No newline at end of file
diff --git a/pc-fe/src/assets/bg.png b/pc-fe/src/assets/bg.png
new file mode 100644
index 0000000..910c794
Binary files /dev/null and b/pc-fe/src/assets/bg.png differ
diff --git a/pc-fe/src/assets/true.png b/pc-fe/src/assets/true.png
new file mode 100644
index 0000000..d18ff59
Binary files /dev/null and b/pc-fe/src/assets/true.png differ
diff --git a/pc-fe/src/assets/waringIcon.png b/pc-fe/src/assets/waringIcon.png
new file mode 100644
index 0000000..9e80fee
Binary files /dev/null and b/pc-fe/src/assets/waringIcon.png differ
diff --git a/pc-fe/src/assets/welcome-icon.jpg b/pc-fe/src/assets/welcome-icon.jpg
deleted file mode 100644
index 234edef..0000000
Binary files a/pc-fe/src/assets/welcome-icon.jpg and /dev/null differ
diff --git a/pc-fe/src/assets/welcome-icon.png b/pc-fe/src/assets/welcome-icon.png
new file mode 100644
index 0000000..ad3dde6
Binary files /dev/null and b/pc-fe/src/assets/welcome-icon.png differ
diff --git a/pc-fe/src/contexts/ConfigStepContext.tsx b/pc-fe/src/contexts/ConfigStepContext.tsx
new file mode 100644
index 0000000..a8a7a41
--- /dev/null
+++ b/pc-fe/src/contexts/ConfigStepContext.tsx
@@ -0,0 +1,113 @@
+// src/contexts/ConfigStepContext.tsx
+/**
+ * 配置步骤Tab状态管理上下文
+ *
+ * 用于在配置步骤页面中管理tab的激活状态和切换逻辑
+ * 提供了统一的状态管理和便捷的tab切换方法
+ *
+ * @example
+ * // 1. 在顶层组件包装Provider
+ * import { ConfigStepProvider } from '@/contexts/ConfigStepContext';
+ *
+ * const tabs = [
+ * { key: "networkConfig", label: '网络配置', component: },
+ * { key: "systemConfig", label: '系统配置', component: },
+ * ];
+ *
+ *
+ *
+ *
+ *
+ * @example
+ * // 2. 在子组件中使用hook访问和控制tab状态
+ * import { useConfigStep } from '@/contexts/ConfigStepContext';
+ *
+ * const MyComponent = () => {
+ * const {
+ * activeTab, // 当前激活的tab key
+ * setActiveTab, // 设置特定tab为激活状态
+ * goToNextTab, // 切换到下一个tab
+ * goToPreviousTab // 切换到上一个tab
+ * } = useConfigStep();
+ *
+ * const handleNext = () => {
+ * // 执行某些操作后切换到下一个tab
+ * setTimeout(() => {
+ * goToNextTab();
+ * }, 300);
+ * };
+ *
+ * const handleJump = () => {
+ * // 直接跳转到指定tab
+ * setActiveTab('systemConfig');
+ * };
+ *
+ * return (
+ *
+ *
当前Tab: {activeTab}
+ *
+ *
+ *
+ * );
+ * };
+ */
+
+import React, { createContext, useContext, useState, ReactNode } from 'react';
+
+interface ConfigStepContextType {
+ activeTab: string;
+ setActiveTab: (tab: string) => void;
+ goToNextTab: () => void;
+ goToPreviousTab: () => void;
+ tabs: { key: string; label: string; component: React.ReactNode }[];
+}
+
+const ConfigStepContext = createContext(undefined);
+
+interface ConfigStepProviderProps {
+ children: ReactNode;
+ initialTab?: string;
+ tabs: { key: string; label: string; component: React.ReactNode }[];
+}
+
+export const ConfigStepProvider: React.FC = ({
+ children,
+ initialTab = "networkConfig",
+ tabs
+}) => {
+ const [activeTab, setActiveTab] = useState(initialTab);
+
+ const goToNextTab = () => {
+ const currentIndex = tabs.findIndex(tab => tab.key === activeTab);
+ const nextIndex = (currentIndex + 1) % tabs.length;
+ setActiveTab(tabs[nextIndex].key);
+ };
+
+ const goToPreviousTab = () => {
+ const currentIndex = tabs.findIndex(tab => tab.key === activeTab);
+ const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
+ setActiveTab(tabs[prevIndex].key);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useConfigStep = () => {
+ const context = useContext(ConfigStepContext);
+ if (context === undefined) {
+ throw new Error('useConfigStep must be used within a ConfigStepProvider');
+ }
+ return context;
+};
\ No newline at end of file
diff --git a/pc-fe/src/main/config.ts b/pc-fe/src/main/config.ts
index 73e270b..8cbbf7a 100644
--- a/pc-fe/src/main/config.ts
+++ b/pc-fe/src/main/config.ts
@@ -1,15 +1,15 @@
import { BrowserWindowConstructorOptions } from "electron";
export default{
browserWindow:{
- kiosk: true,
- frame: false,
- titleBarStyle: 'hidden',
- autoHideMenuBar: true,
+ kiosk: true, // 全屏
+ frame: false, // 无边框
+ titleBarStyle: 'hidden', // 隐藏标题栏
+ autoHideMenuBar: true, // 隐藏菜单栏
// 禁止调整窗口大小
resizable: false,
// 禁止最大化和最小化按钮
maximizable: false,
minimizable: false,
- closable: false,
+ // closable: false, // 禁止关闭窗口
} as BrowserWindowConstructorOptions,
}
\ No newline at end of file
diff --git a/pc-fe/src/main/grpc/BTGrpcClient.ts b/pc-fe/src/main/grpc/BTGrpcClient.ts
index e746c2a..0f3020b 100644
--- a/pc-fe/src/main/grpc/BTGrpcClient.ts
+++ b/pc-fe/src/main/grpc/BTGrpcClient.ts
@@ -1,194 +1,194 @@
-// src/grpc/BTGrpcClient.ts 调用后端服务
-import * as grpc from '@grpc/grpc-js';
-import * as protoLoader from '@grpc/proto-loader';
-import path from 'path';
-import { app } from 'electron';
+// // src/grpc/BTGrpcClient.ts 调用后端服务
+// import * as grpc from '@grpc/grpc-js';
+// import * as protoLoader from '@grpc/proto-loader';
+// import path from 'path';
+// import { app } from 'electron';
-export interface DownloadRequest {
- torrent_url: string;
- item_name: string;
- item_id?: string;
-}
+// export interface DownloadRequest {
+// torrent_url: string;
+// item_name: string;
+// item_id?: string;
+// }
-export interface ProgressCallback {
- (progress: ProgressUpdate): void;
-}
+// export interface ProgressCallback {
+// (progress: ProgressUpdate): void;
+// }
-export interface ProgressUpdate {
- download_id: string;
- progress: number;
- download_speed: number;
- upload_speed: number;
- eta: number;
- total_size: number;
- downloaded_size: number;
- state: string;
-}
+// export interface ProgressUpdate {
+// download_id: string;
+// progress: number;
+// download_speed: number;
+// upload_speed: number;
+// eta: number;
+// total_size: number;
+// downloaded_size: number;
+// state: string;
+// }
-export class BTGrpcClient {
- private client: any;
- private progressCallbacks: Map = new Map();
- private progressStream: any = null;
+// export class BTGrpcClient {
+// private client: any;
+// private progressCallbacks: Map = new Map();
+// private progressStream: any = null;
- constructor() {
- this.initializeClient();
- }
+// constructor() {
+// this.initializeClient();
+// }
- private initializeClient() {
- try {
- const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
+// private initializeClient() {
+// try {
+// const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
- const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
- keepCase: true,
- longs: String,
- enums: String,
- defaults: true,
- oneofs: true,
- });
+// const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
+// keepCase: true,
+// longs: String,
+// enums: String,
+// defaults: true,
+// oneofs: true,
+// });
- const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
- const bittorrent = protoDescriptor.bittorrent as any;
+// const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
+// const bittorrent = protoDescriptor.bittorrent as any;
- // 连接到后端 Agent,假设运行在 localhost:50051
- this.client = new bittorrent.BTDownloadService(
- 'localhost:50051',
- grpc.credentials.createInsecure()
- );
+// // 连接到后端 Agent,假设运行在 localhost:50051
+// this.client = new bittorrent.BTDownloadService(
+// 'localhost:50051',
+// grpc.credentials.createInsecure()
+// );
- console.log('gRPC客户端初始化成功');
- this.setupProgressStream();
- } catch (error) {
- console.error('gRPC客户端初始化失败:', error);
- }
- }
+// console.log('gRPC客户端初始化成功');
+// this.setupProgressStream();
+// } catch (error) {
+// console.error('gRPC客户端初始化失败:', error);
+// }
+// }
- // 设置进度流监听
- private setupProgressStream() {
- try {
- this.progressStream = this.client.SubscribeProgress({});
- // 注册数据事件监听器,接收进度更新
- this.progressStream.on('data', (progress: ProgressUpdate) => {
- console.log('收到进度数据:', progress); // 添加调试日志
- // BTGrpcClient 通过IPC将进度发送给主进程
- this.progressCallbacks.forEach((callbacks, downloadId) => {
- if (downloadId === 'all' || downloadId === progress.download_id) {
- callbacks.forEach(callback => callback(progress));
- }
- });
+// // 设置进度流监听
+// private setupProgressStream() {
+// try {
+// this.progressStream = this.client.SubscribeProgress({});
+// // 注册数据事件监听器,接收进度更新
+// this.progressStream.on('data', (progress: ProgressUpdate) => {
+// console.log('收到进度数据:', progress); // 添加调试日志
+// // BTGrpcClient 通过IPC将进度发送给主进程
+// this.progressCallbacks.forEach((callbacks, downloadId) => {
+// if (downloadId === 'all' || downloadId === progress.download_id) {
+// callbacks.forEach(callback => callback(progress));
+// }
+// });
- // 如果有针对特定下载ID的回调,也要通知
- const specificCallbacks = this.progressCallbacks.get(progress.download_id);
- if (specificCallbacks) {
- specificCallbacks.forEach(callback => callback(progress));
- }
- });
+// // 如果有针对特定下载ID的回调,也要通知
+// const specificCallbacks = this.progressCallbacks.get(progress.download_id);
+// if (specificCallbacks) {
+// specificCallbacks.forEach(callback => callback(progress));
+// }
+// });
- this.progressStream.on('error', (error: Error) => {
- console.error('进度流错误:', error);
- });
+// this.progressStream.on('error', (error: Error) => {
+// console.error('进度流错误:', error);
+// });
- this.progressStream.on('end', () => {
- console.log('进度流结束');
- });
- } catch (error) {
- console.error('设置进度流失败:', error);
- }
- }
+// this.progressStream.on('end', () => {
+// console.log('进度流结束');
+// });
+// } catch (error) {
+// console.error('设置进度流失败:', error);
+// }
+// }
- // 开始下载
- startDownload(request: DownloadRequest): Promise {
- return new Promise((resolve, reject) => {
- // MockBTService 接收请求,创建下载任务
- this.client.StartDownload(request, (error: grpc.ServiceError, response: any) => {
- if (error) {
- reject(error);
- } else {
- resolve(response);
- }
- });
- });
- }
+// // 开始下载
+// startDownload(request: DownloadRequest): Promise {
+// return new Promise((resolve, reject) => {
+// // MockBTService 接收请求,创建下载任务
+// this.client.StartDownload(request, (error: grpc.ServiceError, response: any) => {
+// if (error) {
+// reject(error);
+// } else {
+// resolve(response);
+// }
+// });
+// });
+// }
- // 停止下载
- stopDownload(downloadId: string): Promise {
- return new Promise((resolve, reject) => {
- this.client.StopDownload({ download_id: downloadId }, (error: grpc.ServiceError, response: any) => {
- if (error) {
- reject(error);
- } else {
- resolve(response);
- }
- });
- });
- }
+// // 停止下载
+// stopDownload(downloadId: string): Promise {
+// return new Promise((resolve, reject) => {
+// this.client.StopDownload({ download_id: downloadId }, (error: grpc.ServiceError, response: any) => {
+// if (error) {
+// reject(error);
+// } else {
+// resolve(response);
+// }
+// });
+// });
+// }
- // 注册进度回调
- registerProgressCallback(downloadId: string, callback: ProgressCallback) {
- if (!this.progressCallbacks.has(downloadId)) {
- this.progressCallbacks.set(downloadId, []);
- }
- this.progressCallbacks.get(downloadId)!.push(callback);
- }
+// // 注册进度回调
+// registerProgressCallback(downloadId: string, callback: ProgressCallback) {
+// if (!this.progressCallbacks.has(downloadId)) {
+// this.progressCallbacks.set(downloadId, []);
+// }
+// this.progressCallbacks.get(downloadId)!.push(callback);
+// }
- // 移除进度回调
- removeProgressCallback(downloadId: string, callback: ProgressCallback) {
- const callbacks = this.progressCallbacks.get(downloadId);
- if (callbacks) {
- const index = callbacks.indexOf(callback);
- if (index > -1) {
- callbacks.splice(index, 1);
- }
- }
- }
+// // 移除进度回调
+// removeProgressCallback(downloadId: string, callback: ProgressCallback) {
+// const callbacks = this.progressCallbacks.get(downloadId);
+// if (callbacks) {
+// const index = callbacks.indexOf(callback);
+// if (index > -1) {
+// callbacks.splice(index, 1);
+// }
+// }
+// }
- // 检查连接状态
- isConnected(): boolean {
- return this.client && this.client.getChannel().getConnectivityState(true) === grpc.connectivityState.READY;
- }
+// // 检查连接状态
+// isConnected(): boolean {
+// return this.client && this.client.getChannel().getConnectivityState(true) === grpc.connectivityState.READY;
+// }
- // 重新连接
- reconnect() {
- try {
- this.client.close();
- this.initializeClient();
- } catch (error) {
- console.error('重新连接失败:', error);
- }
- }
+// // 重新连接
+// reconnect() {
+// try {
+// this.client.close();
+// this.initializeClient();
+// } catch (error) {
+// console.error('重新连接失败:', error);
+// }
+// }
- getConnectionState(): string {
- if (!this.client) return 'NOT_CREATED';
+// getConnectionState(): string {
+// if (!this.client) return 'NOT_CREATED';
- const state = this.client.getChannel().getConnectivityState(false);
- const stateNames = {
- [grpc.connectivityState.IDLE]: 'IDLE',
- [grpc.connectivityState.CONNECTING]: 'CONNECTING',
- [grpc.connectivityState.READY]: 'READY',
- [grpc.connectivityState.TRANSIENT_FAILURE]: 'TRANSIENT_FAILURE',
- [grpc.connectivityState.SHUTDOWN]: 'SHUTDOWN'
- } as const;
+// const state = this.client.getChannel().getConnectivityState(false);
+// const stateNames = {
+// [grpc.connectivityState.IDLE]: 'IDLE',
+// [grpc.connectivityState.CONNECTING]: 'CONNECTING',
+// [grpc.connectivityState.READY]: 'READY',
+// [grpc.connectivityState.TRANSIENT_FAILURE]: 'TRANSIENT_FAILURE',
+// [grpc.connectivityState.SHUTDOWN]: 'SHUTDOWN'
+// } as const;
- // 使用类型断言确保 state 是合法的 key
- return stateNames[state as keyof typeof stateNames] || 'UNKNOWN';
- }
+// // 使用类型断言确保 state 是合法的 key
+// return stateNames[state as keyof typeof stateNames] || 'UNKNOWN';
+// }
-// 添加测试方法
- async testConnection(): Promise {
- try {
- // 尝试调用一个简单的方法来测试连接
- await new Promise((resolve, reject) => {
- this.client.ListDownloads({}, (error: any, response: any) => {
- if (error && error.code !== grpc.status.UNIMPLEMENTED) {
- reject(error);
- } else {
- resolve(response);
- }
- });
- });
- return true;
- } catch (error) {
- console.error('连接测试失败:', error);
- return false;
- }
- }
-}
\ No newline at end of file
+// // 添加测试方法
+// async testConnection(): Promise {
+// try {
+// // 尝试调用一个简单的方法来测试连接
+// await new Promise((resolve, reject) => {
+// this.client.ListDownloads({}, (error: any, response: any) => {
+// if (error && error.code !== grpc.status.UNIMPLEMENTED) {
+// reject(error);
+// } else {
+// resolve(response);
+// }
+// });
+// });
+// return true;
+// } catch (error) {
+// console.error('连接测试失败:', error);
+// return false;
+// }
+// }
+// }
\ No newline at end of file
diff --git a/pc-fe/src/main/grpc/MockBTService.ts b/pc-fe/src/main/grpc/MockBTService.ts
index d06afd9..92f11d3 100644
--- a/pc-fe/src/main/grpc/MockBTService.ts
+++ b/pc-fe/src/main/grpc/MockBTService.ts
@@ -1,323 +1,323 @@
-// 本地测试用的 gRPC 服务器,用于模拟 BT 下载
-// src/grpc/MockBTService.ts
-import * as grpc from '@grpc/grpc-js';
-import * as protoLoader from '@grpc/proto-loader';
-import path from 'path';
+// // 本地测试用的 gRPC 服务器,用于模拟 BT 下载
+// // src/grpc/MockBTService.ts
+// import * as grpc from '@grpc/grpc-js';
+// import * as protoLoader from '@grpc/proto-loader';
+// import path from 'path';
-// 进度更新接口
-interface ProgressUpdate {
- download_id: string;
- progress: number;
- download_speed: number;
- item_name?: string; // 可选的文件名字段
- upload_speed: number;
- eta: number;
- total_size: number;
- downloaded_size: number;
- state: string;
-}
+// // 进度更新接口
+// interface ProgressUpdate {
+// download_id: string;
+// progress: number;
+// download_speed: number;
+// item_name?: string; // 可选的文件名字段
+// upload_speed: number;
+// eta: number;
+// total_size: number;
+// downloaded_size: number;
+// state: string;
+// }
-// 模拟的下载任务
-interface MockDownloadTask {
- id: string;
- itemName: string;
- torrentUrl: string;
- progress: number;
- totalSize: number;
- downloadedSize: number;
- downloadSpeed: number;
- state: 'downloading' | 'completed' | 'error' | 'paused';
- startTime: number;
-}
+// // 模拟的下载任务
+// interface MockDownloadTask {
+// id: string;
+// itemName: string;
+// torrentUrl: string;
+// progress: number;
+// totalSize: number;
+// downloadedSize: number;
+// downloadSpeed: number;
+// state: 'downloading' | 'completed' | 'error' | 'paused';
+// startTime: number;
+// }
-export class MockBTService {
- private server: grpc.Server;
- private activeDownloads: Map = new Map();
- private progressIntervals: Map = new Map();
- // 存储所有的进度回调函数,发送信息
- private progressCallbacks: ((progress: ProgressUpdate) => void)[] = [];
+// export class MockBTService {
+// private server: grpc.Server;
+// private activeDownloads: Map = new Map();
+// private progressIntervals: Map = new Map();
+// // 存储所有的进度回调函数,发送信息
+// private progressCallbacks: ((progress: ProgressUpdate) => void)[] = [];
- constructor() {
- this.server = new grpc.Server();
- this.setupService();
- }
+// constructor() {
+// this.server = new grpc.Server();
+// this.setupService();
+// }
- private setupService() {
- // 设置gRPC服务,加载 bittorrent.proto 定义,实现所有gRPC服务方法
- const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
+// private setupService() {
+// // 设置gRPC服务,加载 bittorrent.proto 定义,实现所有gRPC服务方法
+// const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
- const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
- keepCase: true,
- longs: String,
- enums: String,
- defaults: true,
- oneofs: true,
- });
+// const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
+// keepCase: true,
+// longs: String,
+// enums: String,
+// defaults: true,
+// oneofs: true,
+// });
- const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
- const bittorrent = protoDescriptor.bittorrent as any;
+// const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
+// const bittorrent = protoDescriptor.bittorrent as any;
- // 实现 gRPC 服务方法
- this.server.addService(bittorrent.BTDownloadService.service, {
- StartDownload: this.startDownload.bind(this),
- StopDownload: this.stopDownload.bind(this),
- GetDownloadStatus: this.getDownloadStatus.bind(this),
- ListDownloads: this.listDownloads.bind(this),
- SubscribeProgress: this.subscribeProgress.bind(this),
- });
- }
+// // 实现 gRPC 服务方法
+// this.server.addService(bittorrent.BTDownloadService.service, {
+// StartDownload: this.startDownload.bind(this),
+// StopDownload: this.stopDownload.bind(this),
+// GetDownloadStatus: this.getDownloadStatus.bind(this),
+// ListDownloads: this.listDownloads.bind(this),
+// SubscribeProgress: this.subscribeProgress.bind(this),
+// });
+// }
- // 开始下载
- // 在 startDownload 方法中,可以添加根据文件名设置不同大小的逻辑
- private startDownload(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
- const { torrent_url, item_name, item_id } = call.request;
- const downloadId = item_id || `download-${Date.now()}`;
+// // 开始下载
+// // 在 startDownload 方法中,可以添加根据文件名设置不同大小的逻辑
+// private startDownload(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
+// const { torrent_url, item_name, item_id } = call.request;
+// const downloadId = item_id || `download-${Date.now()}`;
- console.log(`模拟开始下载: ${item_name}, ID: ${downloadId}`);
+// console.log(`模拟开始下载: ${item_name}, ID: ${downloadId}`);
- // 根据文件名或ID设置不同大小的文件
- let fileSize = 1024 * 1024 * 100; // 默认100MB
+// // 根据文件名或ID设置不同大小的文件
+// let fileSize = 1024 * 1024 * 100; // 默认100MB
- if (item_name.includes('large') || item_name.includes('大文件')) {
- fileSize = 4 * 1024 * 1024 * 1024; // 4GB
- } else if (item_name.includes('medium') || item_name.includes('中等')) {
- fileSize = 1024 * 1024 * 1024; // 1GB
- } else if (item_name.includes('small') || item_name.includes('小文件')) {
- fileSize = 100 * 1024 * 1024; // 100MB
- }
+// if (item_name.includes('large') || item_name.includes('大文件')) {
+// fileSize = 4 * 1024 * 1024 * 1024; // 4GB
+// } else if (item_name.includes('medium') || item_name.includes('中等')) {
+// fileSize = 1024 * 1024 * 1024; // 1GB
+// } else if (item_name.includes('small') || item_name.includes('小文件')) {
+// fileSize = 100 * 1024 * 1024; // 100MB
+// }
- // 创建模拟下载任务
- const task: MockDownloadTask = {
- id: downloadId,
- itemName: item_name,
- torrentUrl: torrent_url,
- progress: 0,
- totalSize: fileSize,
- downloadedSize: 0,
- downloadSpeed: 1024 * 1024 * 2, // 2MB/s
- state: 'downloading',
- startTime: Date.now(),
- };
+// // 创建模拟下载任务
+// const task: MockDownloadTask = {
+// id: downloadId,
+// itemName: item_name,
+// torrentUrl: torrent_url,
+// progress: 0,
+// totalSize: fileSize,
+// downloadedSize: 0,
+// downloadSpeed: 1024 * 1024 * 2, // 2MB/s
+// state: 'downloading',
+// startTime: Date.now(),
+// };
- this.activeDownloads.set(downloadId, task);
- console.log(`已创建下载任务: ${downloadId}, 大小: ${fileSize} bytes`);
+// this.activeDownloads.set(downloadId, task);
+// console.log(`已创建下载任务: ${downloadId}, 大小: ${fileSize} bytes`);
- // 启动进度模拟
- this.startProgressSimulation(downloadId);
+// // 启动进度模拟
+// this.startProgressSimulation(downloadId);
- callback(null, {
- success: true,
- message: '下载已开始',
- download_id: downloadId,
- });
- }
+// callback(null, {
+// success: true,
+// message: '下载已开始',
+// download_id: downloadId,
+// });
+// }
- // 停止下载
- private stopDownload(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
- const { download_id } = call.request;
+// // 停止下载
+// private stopDownload(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
+// const { download_id } = call.request;
- if (this.activeDownloads.has(download_id)) {
- const task = this.activeDownloads.get(download_id)!;
- task.state = 'paused';
+// if (this.activeDownloads.has(download_id)) {
+// const task = this.activeDownloads.get(download_id)!;
+// task.state = 'paused';
- // 清除进度定时器
- if (this.progressIntervals.has(download_id)) {
- clearInterval(this.progressIntervals.get(download_id));
- this.progressIntervals.delete(download_id);
- }
+// // 清除进度定时器
+// if (this.progressIntervals.has(download_id)) {
+// clearInterval(this.progressIntervals.get(download_id));
+// this.progressIntervals.delete(download_id);
+// }
- console.log(`模拟停止下载: ${download_id}`);
- callback(null, { success: true, message: '下载已停止' });
- } else {
- callback({
- code: grpc.status.NOT_FOUND,
- message: `下载任务不存在: ${download_id}`
- });
- }
- }
+// console.log(`模拟停止下载: ${download_id}`);
+// callback(null, { success: true, message: '下载已停止' });
+// } else {
+// callback({
+// code: grpc.status.NOT_FOUND,
+// message: `下载任务不存在: ${download_id}`
+// });
+// }
+// }
- // 获取下载状态
- private getDownloadStatus(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
- const { download_id } = call.request;
+// // 获取下载状态
+// private getDownloadStatus(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
+// const { download_id } = call.request;
- if (this.activeDownloads.has(download_id)) {
- const task = this.activeDownloads.get(download_id)!;
- callback(null, {
- download_id: task.id,
- progress: task.progress,
- download_speed: task.downloadSpeed,
- state: task.state,
- total_size: task.totalSize,
- downloaded_size: task.downloadedSize,
- });
- } else {
- callback({
- code: grpc.status.NOT_FOUND,
- message: `下载任务不存在: ${download_id}`
- });
- }
- }
+// if (this.activeDownloads.has(download_id)) {
+// const task = this.activeDownloads.get(download_id)!;
+// callback(null, {
+// download_id: task.id,
+// progress: task.progress,
+// download_speed: task.downloadSpeed,
+// state: task.state,
+// total_size: task.totalSize,
+// downloaded_size: task.downloadedSize,
+// });
+// } else {
+// callback({
+// code: grpc.status.NOT_FOUND,
+// message: `下载任务不存在: ${download_id}`
+// });
+// }
+// }
- // 列出所有下载
- private listDownloads(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
- const downloads = Array.from(this.activeDownloads.values()).map(task => ({
- download_id: task.id,
- item_name: task.itemName,
- progress: task.progress,
- download_speed: task.downloadSpeed,
- state: task.state,
- total_size: task.totalSize,
- downloaded_size: task.downloadedSize,
- }));
+// // 列出所有下载
+// private listDownloads(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) {
+// const downloads = Array.from(this.activeDownloads.values()).map(task => ({
+// download_id: task.id,
+// item_name: task.itemName,
+// progress: task.progress,
+// download_speed: task.downloadSpeed,
+// state: task.state,
+// total_size: task.totalSize,
+// downloaded_size: task.downloadedSize,
+// }));
- callback(null, { downloads });
- }
+// callback(null, { downloads });
+// }
- // 订阅进度更新(流式响应)
- private subscribeProgress(call: grpc.ServerWritableStream) {
- console.log('客户端订阅了进度更新');
+// // 订阅进度更新(流式响应)
+// private subscribeProgress(call: grpc.ServerWritableStream) {
+// console.log('客户端订阅了进度更新');
- // 存储回调以便发送进度更新
- const callback = (progress: ProgressUpdate) => {
- try {
- call.write(progress);
- } catch (error) {
- console.error('发送进度更新失败:', error);
- }
- };
+// // 存储回调以便发送进度更新
+// const callback = (progress: ProgressUpdate) => {
+// try {
+// call.write(progress);
+// } catch (error) {
+// console.error('发送进度更新失败:', error);
+// }
+// };
- this.progressCallbacks.push(callback);
+// this.progressCallbacks.push(callback);
- // 当客户端断开连接时清理
- call.on('cancelled', () => {
- console.log('客户端取消了进度订阅');
- const index = this.progressCallbacks.indexOf(callback);
- if (index > -1) {
- this.progressCallbacks.splice(index, 1);
- }
- });
+// // 当客户端断开连接时清理
+// call.on('cancelled', () => {
+// console.log('客户端取消了进度订阅');
+// const index = this.progressCallbacks.indexOf(callback);
+// if (index > -1) {
+// this.progressCallbacks.splice(index, 1);
+// }
+// });
- call.on('error', (error) => {
- console.error('进度流错误:', error);
- const index = this.progressCallbacks.indexOf(callback);
- if (index > -1) {
- this.progressCallbacks.splice(index, 1);
- }
- });
+// call.on('error', (error) => {
+// console.error('进度流错误:', error);
+// const index = this.progressCallbacks.indexOf(callback);
+// if (index > -1) {
+// this.progressCallbacks.splice(index, 1);
+// }
+// });
- // 处理客户端断开连接
- call.on('end', () => {
- console.log('客户端断开连接');
- const index = this.progressCallbacks.indexOf(callback);
- if (index > -1) {
- this.progressCallbacks.splice(index, 1);
- }
- });
- }
+// // 处理客户端断开连接
+// call.on('end', () => {
+// console.log('客户端断开连接');
+// const index = this.progressCallbacks.indexOf(callback);
+// if (index > -1) {
+// this.progressCallbacks.splice(index, 1);
+// }
+// });
+// }
- // 启动进度模拟
- // 在 MockBTService.ts 中修改 startProgressSimulation 方法
-private startProgressSimulation(downloadId: string) {
- const interval = setInterval(() => {
- if (this.activeDownloads.has(downloadId)) {
- const task = this.activeDownloads.get(downloadId)!;
+// // 启动进度模拟
+// // 在 MockBTService.ts 中修改 startProgressSimulation 方法
+// private startProgressSimulation(downloadId: string) {
+// const interval = setInterval(() => {
+// if (this.activeDownloads.has(downloadId)) {
+// const task = this.activeDownloads.get(downloadId)!;
- if (task.state === 'downloading' && task.progress < 100) {
- // // 更新进度,但确保不超过100%
- // task.progress += Math.random() * 2;
- // 使用固定的进度增加而不是随机值,使下载更加稳定
- task.progress += 1.5; // 每秒增加1.5%的进度
+// if (task.state === 'downloading' && task.progress < 100) {
+// // // 更新进度,但确保不超过100%
+// // task.progress += Math.random() * 2;
+// // 使用固定的进度增加而不是随机值,使下载更加稳定
+// task.progress += 1.5; // 每秒增加1.5%的进度
- // 确保进度不超过100%
- if (task.progress >= 100) {
- task.progress = 100;
- task.state = 'completed';
- task.downloadedSize = task.totalSize; // 确保已完成时已下载大小等于总大小
- console.log(`下载完成: ${downloadId}`);
+// // 确保进度不超过100%
+// if (task.progress >= 100) {
+// task.progress = 100;
+// task.state = 'completed';
+// task.downloadedSize = task.totalSize; // 确保已完成时已下载大小等于总大小
+// console.log(`下载完成: ${downloadId}`);
- // 清除定时器
- clearInterval(interval);
- this.progressIntervals.delete(downloadId);
- } else {
- // 根据进度计算已下载大小
- task.downloadedSize = (task.totalSize * task.progress) / 100;
- }
+// // 清除定时器
+// clearInterval(interval);
+// this.progressIntervals.delete(downloadId);
+// } else {
+// // 根据进度计算已下载大小
+// task.downloadedSize = (task.totalSize * task.progress) / 100;
+// }
- // 计算剩余时间(秒)
- let eta = 0;
- if (task.progress < 100) {
- // 基于当前速度计算剩余时间
- const progressPerSecond = 1.5; // 每秒进度百分比
- eta = Math.round(((100 - task.progress) / progressPerSecond));
- }
+// // 计算剩余时间(秒)
+// let eta = 0;
+// if (task.progress < 100) {
+// // 基于当前速度计算剩余时间
+// const progressPerSecond = 1.5; // 每秒进度百分比
+// eta = Math.round(((100 - task.progress) / progressPerSecond));
+// }
- // 发送进度更新
- this.sendProgressUpdate({
- download_id: task.id,
- item_name: task.itemName,
- progress: task.progress,
- download_speed: task.downloadSpeed,
- upload_speed: 1024 * 512, // 512KB/s 上传速度
- eta: task.progress >= 100 ? 0 : Math.round(((100 - task.progress) / 2) * 1000),
- total_size: task.totalSize,
- downloaded_size: task.downloadedSize,
- state: task.state,
- });
- }
- } else {
- clearInterval(interval);
- this.progressIntervals.delete(downloadId);
- }
- }, 1000); // 每秒更新一次进度
+// // 发送进度更新
+// this.sendProgressUpdate({
+// download_id: task.id,
+// item_name: task.itemName,
+// progress: task.progress,
+// download_speed: task.downloadSpeed,
+// upload_speed: 1024 * 512, // 512KB/s 上传速度
+// eta: task.progress >= 100 ? 0 : Math.round(((100 - task.progress) / 2) * 1000),
+// total_size: task.totalSize,
+// downloaded_size: task.downloadedSize,
+// state: task.state,
+// });
+// }
+// } else {
+// clearInterval(interval);
+// this.progressIntervals.delete(downloadId);
+// }
+// }, 1000); // 每秒更新一次进度
- this.progressIntervals.set(downloadId, interval);
-}
+// this.progressIntervals.set(downloadId, interval);
+// }
- // 发送进度更新给所有订阅者
- private sendProgressUpdate(progress: ProgressUpdate) {
- console.log('发送进度更新:', progress);
- // 通过已注册的回调函数传递给 BTGrpcClient
- this.progressCallbacks.forEach((callback, index) => {
- try {
- callback(progress);
- } catch (error) {
- console.error(`发送进度更新给回调 ${index} 失败:`, error);
- }
- });
- }
+// // 发送进度更新给所有订阅者
+// private sendProgressUpdate(progress: ProgressUpdate) {
+// console.log('发送进度更新:', progress);
+// // 通过已注册的回调函数传递给 BTGrpcClient
+// this.progressCallbacks.forEach((callback, index) => {
+// try {
+// callback(progress);
+// } catch (error) {
+// console.error(`发送进度更新给回调 ${index} 失败:`, error);
+// }
+// });
+// }
- // 绑定端口并启动服务器
- start(port: number = 50051): Promise {
- return new Promise((resolve, reject) => {
- this.server.bindAsync(
- `0.0.0.0:${port}`,
- grpc.ServerCredentials.createInsecure(),
- (error, port) => {
- if (error) {
- reject(error);
- } else {
- this.server.start();
- console.log(`Mock gRPC 服务器运行在端口 ${port}`);
- resolve();
- }
- }
- );
- });
- }
+// // 绑定端口并启动服务器
+// start(port: number = 50051): Promise {
+// return new Promise((resolve, reject) => {
+// this.server.bindAsync(
+// `0.0.0.0:${port}`,
+// grpc.ServerCredentials.createInsecure(),
+// (error, port) => {
+// if (error) {
+// reject(error);
+// } else {
+// this.server.start();
+// console.log(`Mock gRPC 服务器运行在端口 ${port}`);
+// resolve();
+// }
+// }
+// );
+// });
+// }
- // 停止服务器
- stop(): Promise {
- return new Promise((resolve) => {
- // 清理所有定时器
- this.progressIntervals.forEach(interval => clearInterval(interval));
- this.progressIntervals.clear();
- this.activeDownloads.clear();
- this.progressCallbacks = [];
+// // 停止服务器
+// stop(): Promise {
+// return new Promise((resolve) => {
+// // 清理所有定时器
+// this.progressIntervals.forEach(interval => clearInterval(interval));
+// this.progressIntervals.clear();
+// this.activeDownloads.clear();
+// this.progressCallbacks = [];
- this.server.tryShutdown(() => {
- console.log('Mock gRPC 服务器已停止');
- resolve();
- });
- });
- }
-}
\ No newline at end of file
+// this.server.tryShutdown(() => {
+// console.log('Mock gRPC 服务器已停止');
+// resolve();
+// });
+// });
+// }
+// }
\ No newline at end of file
diff --git a/pc-fe/src/main/ipc/grpc.ts b/pc-fe/src/main/ipc/grpc.ts
new file mode 100644
index 0000000..e463d5e
--- /dev/null
+++ b/pc-fe/src/main/ipc/grpc.ts
@@ -0,0 +1,181 @@
+// import { ipcMain,app,BrowserWindow } from 'electron';
+
+// // 模拟grpc服务端
+// import { BTGrpcClient } from '../grpc/BTGrpcClient';
+// import { MockBTService } from '../grpc/MockBTService';
+
+// // 客户端和服务端 通信
+// const IS_TEST_MODE = true; // 设置为 false 时连接真实后端
+// const GRPC_SERVER_PORT = 50051;
+// // 先声明变量,但不立即初始化
+// let btGrpcClient: BTGrpcClient | null = null;
+// let mockServer: MockBTService | null = null;
+// let healthCheckInterval: NodeJS.Timeout | null = null; // 声明一个变量来存储定时器
+
+
+// // 初始化 gRPC 客户端
+// async function initializeGrpc() {
+// try {
+// if (IS_TEST_MODE) {
+// console.log('启动模拟 gRPC 服务器...');
+// mockServer = new MockBTService();
+// await mockServer.start(GRPC_SERVER_PORT);
+
+// // 给一点时间让服务器启动
+// await new Promise(resolve => setTimeout(resolve, 1000));
+// }
+
+// // 创建 BTGrpcClient 实例连接到gRPC服务
+// btGrpcClient = new BTGrpcClient();
+
+// // 注册进度回调函数,用于接收下载进度更新
+// btGrpcClient.registerProgressCallback('all', (progress) => {
+// const mainWindow = BrowserWindow.getFocusedWindow();
+// if (mainWindow) {
+// mainWindow.webContents.send('grpc-progress-update', progress);
+// }
+// });
+
+// // 测试连接
+// setTimeout(async () => {
+// try {
+// const connected = await btGrpcClient!.testConnection();
+// console.log('gRPC 连接状态:', connected ? '已连接' : '未连接');
+
+// const mainWindow = BrowserWindow.getFocusedWindow();
+// if (mainWindow) {
+// mainWindow.webContents.send('grpc-connection-status', {
+// connected,
+// isMock: IS_TEST_MODE
+// });
+// }
+// } catch (error) {
+// console.error('连接测试失败:', error);
+// }
+// }, 2000);
+
+// // 启动健康检查
+// startHealthCheck();
+
+// } catch (error) {
+// console.error('gRPC 初始化失败:', error);
+// }
+// }
+
+// // 启动健康检查
+// function startHealthCheck() {
+// if (healthCheckInterval) {
+// clearInterval(healthCheckInterval);
+// }
+
+// healthCheckInterval = setInterval(async () => {
+// try {
+// if (!btGrpcClient) return;
+
+// const isConnected = await btGrpcClient.testConnection();
+// const mainWindow = BrowserWindow.getFocusedWindow();
+// if (mainWindow) {
+// mainWindow.webContents.send('grpc-connection-status', {
+// connected: isConnected,
+// state: btGrpcClient.getConnectionState(),
+// isMock: IS_TEST_MODE
+// });
+// }
+// } catch (error) {
+// console.error('健康检查失败:', error);
+// }
+// }, 5000); // 每5秒检查一次
+// }
+
+
+// // 应用启动时初始化
+// app.whenReady().then(() => {
+// initializeGrpc();
+// });
+
+// // 应用退出时清理
+// app.on('before-quit', async () => {
+// if (healthCheckInterval) {
+// clearInterval(healthCheckInterval);
+// }
+
+// if (mockServer) {
+// await mockServer.stop();
+// }
+// });
+
+// // 添加 gRPC 相关的 IPC 处理程序
+// ipcMain.handle('grpc-start-download', async (event, config) => {
+// try {
+// if (!btGrpcClient) {
+// return {
+// success: false,
+// error: 'gRPC客户端未初始化'
+// };
+// }
+
+// console.log('开始gRPC下载:', config);
+// // gRPC客户端向服务端发起 StartDownload 调用
+// const result = await btGrpcClient.startDownload({
+// torrent_url: config.torrentUrl,
+// item_name: config.itemName,
+// item_id: config.itemId
+// });
+// return { success: true, data: result };
+// } catch (error: any) {
+// console.error('gRPC下载失败:', error);
+// return {
+// success: false,
+// error: error.message || '未知错误',
+// details: error.details
+// };
+// }
+// });
+
+// ipcMain.handle('grpc-stop-download', async (event, downloadId) => {
+// try {
+// if (!btGrpcClient) {
+// return {
+// success: false,
+// error: 'gRPC客户端未初始化'
+// };
+// }
+
+// const result = await btGrpcClient.stopDownload(downloadId);
+// return { success: true, data: result };
+// } catch (error: any) {
+// return {
+// success: false,
+// error: error.message || '未知错误'
+// };
+// }
+// });
+
+// ipcMain.handle('grpc-check-connection', async () => {
+// if (!btGrpcClient) {
+// return { connected: false, error: 'gRPC客户端未初始化' };
+// }
+
+// return {
+// connected: btGrpcClient.isConnected(),
+// state: btGrpcClient.getConnectionState()
+// };
+// });
+
+// // 重新连接 gRPC
+// ipcMain.handle('grpc-reconnect', async () => {
+// try {
+// if (btGrpcClient) {
+// btGrpcClient.reconnect();
+// return { success: true, message: '正在重新连接' };
+// } else {
+// await initializeGrpc();
+// return { success: true, message: '正在初始化连接' };
+// }
+// } catch (error: any) {
+// return {
+// success: false,
+// error: error.message || '重新连接失败'
+// };
+// }
+// });
\ No newline at end of file
diff --git a/pc-fe/src/main/ipc/platform.ts b/pc-fe/src/main/ipc/platform.ts
index d1654cf..5cc52cb 100644
--- a/pc-fe/src/main/ipc/platform.ts
+++ b/pc-fe/src/main/ipc/platform.ts
@@ -1,190 +1,16 @@
-import { ipcMain,app,BrowserWindow } from 'electron';
-import { getDeviceId, getWiredConnectionName,netmaskToCidr } from '../utils/utils';
+import { ipcMain,app } from 'electron';
+import { getDeviceId, getWiredConnectionName, netmaskToCidr,simulateUpdate,performRealUpdate } from '../utils/utils';
+import { BrowserWindow } from 'electron';
+import { autoUpdater } from 'electron-updater';
+import request from '../utils/request';
+
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
-// 模拟grpc服务端
-import { BTGrpcClient } from '../grpc/BTGrpcClient';
-import { MockBTService } from '../grpc/MockBTService';
-
-// 客户端和服务端 通信
-const IS_TEST_MODE = true; // 设置为 false 时连接真实后端
-const GRPC_SERVER_PORT = 50051;
-// 先声明变量,但不立即初始化
-let btGrpcClient: BTGrpcClient | null = null;
-let mockServer: MockBTService | null = null;
-let healthCheckInterval: NodeJS.Timeout | null = null; // 声明一个变量来存储定时器
-
const window = getBrowserWindowRuntime();
-// 初始化 gRPC 客户端
-async function initializeGrpc() {
- try {
- if (IS_TEST_MODE) {
- console.log('启动模拟 gRPC 服务器...');
- mockServer = new MockBTService();
- await mockServer.start(GRPC_SERVER_PORT);
-
- // 给一点时间让服务器启动
- await new Promise(resolve => setTimeout(resolve, 1000));
- }
-
- // 创建 BTGrpcClient 实例连接到gRPC服务
- btGrpcClient = new BTGrpcClient();
-
- // 注册进度回调函数,用于接收下载进度更新
- btGrpcClient.registerProgressCallback('all', (progress) => {
- const mainWindow = BrowserWindow.getFocusedWindow();
- if (mainWindow) {
- mainWindow.webContents.send('grpc-progress-update', progress);
- }
- });
-
- // 测试连接
- setTimeout(async () => {
- try {
- const connected = await btGrpcClient!.testConnection();
- console.log('gRPC 连接状态:', connected ? '已连接' : '未连接');
-
- const mainWindow = BrowserWindow.getFocusedWindow();
- if (mainWindow) {
- mainWindow.webContents.send('grpc-connection-status', {
- connected,
- isMock: IS_TEST_MODE
- });
- }
- } catch (error) {
- console.error('连接测试失败:', error);
- }
- }, 2000);
-
- // 启动健康检查
- startHealthCheck();
-
- } catch (error) {
- console.error('gRPC 初始化失败:', error);
- }
-}
-
-// 启动健康检查
-function startHealthCheck() {
- if (healthCheckInterval) {
- clearInterval(healthCheckInterval);
- }
-
- healthCheckInterval = setInterval(async () => {
- try {
- if (!btGrpcClient) return;
-
- const isConnected = await btGrpcClient.testConnection();
- const mainWindow = BrowserWindow.getFocusedWindow();
- if (mainWindow) {
- mainWindow.webContents.send('grpc-connection-status', {
- connected: isConnected,
- state: btGrpcClient.getConnectionState(),
- isMock: IS_TEST_MODE
- });
- }
- } catch (error) {
- console.error('健康检查失败:', error);
- }
- }, 5000); // 每5秒检查一次
-}
-
-
-// 应用启动时初始化
-app.whenReady().then(() => {
- initializeGrpc();
-});
-
-// 应用退出时清理
-app.on('before-quit', async () => {
- if (healthCheckInterval) {
- clearInterval(healthCheckInterval);
- }
-
- if (mockServer) {
- await mockServer.stop();
- }
-});
-
-// 添加 gRPC 相关的 IPC 处理程序
-ipcMain.handle('grpc-start-download', async (event, config) => {
- try {
- if (!btGrpcClient) {
- return {
- success: false,
- error: 'gRPC客户端未初始化'
- };
- }
-
- console.log('开始gRPC下载:', config);
- // gRPC客户端向服务端发起 StartDownload 调用
- const result = await btGrpcClient.startDownload({
- torrent_url: config.torrentUrl,
- item_name: config.itemName,
- item_id: config.itemId
- });
- return { success: true, data: result };
- } catch (error: any) {
- console.error('gRPC下载失败:', error);
- return {
- success: false,
- error: error.message || '未知错误',
- details: error.details
- };
- }
-});
-
-ipcMain.handle('grpc-stop-download', async (event, downloadId) => {
- try {
- if (!btGrpcClient) {
- return {
- success: false,
- error: 'gRPC客户端未初始化'
- };
- }
-
- const result = await btGrpcClient.stopDownload(downloadId);
- return { success: true, data: result };
- } catch (error: any) {
- return {
- success: false,
- error: error.message || '未知错误'
- };
- }
-});
-
-ipcMain.handle('grpc-check-connection', async () => {
- if (!btGrpcClient) {
- return { connected: false, error: 'gRPC客户端未初始化' };
- }
-
- return {
- connected: btGrpcClient.isConnected(),
- state: btGrpcClient.getConnectionState()
- };
-});
-
-// 重新连接 gRPC
-ipcMain.handle('grpc-reconnect', async () => {
- try {
- if (btGrpcClient) {
- btGrpcClient.reconnect();
- return { success: true, message: '正在重新连接' };
- } else {
- await initializeGrpc();
- return { success: true, message: '正在初始化连接' };
- }
- } catch (error: any) {
- return {
- success: false,
- error: error.message || '重新连接失败'
- };
- }
-});
-
+let currentServerIp: string | undefined;
// 监听渲染进程发送的消息
ipcMain.handle('getPlatform', () => {
@@ -197,23 +23,31 @@ ipcMain.on('close-app', () => {
});
ipcMain.on('minimize-app', () => {
- window?.minimize();
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+ if (focusedWindow) {
+ focusedWindow.minimize();
+ }
+ // window?.minimize();
});
ipcMain.on('exit-kiosk', () => {
- if (window) {
- window.setFullScreen(false);
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+ if (focusedWindow) {
+ focusedWindow.setFullScreen(false);
}
+ // if (window) {
+ // window.setFullScreen(false);
+ // }
});
-ipcMain.handle('get-deviceid',async()=>{
+ipcMain.handle('get-device-id',async()=>{
const deviceId = await getDeviceId();
console.log(`Using device ID: ${deviceId}`);
// TODO:传给后端
})
-/* IPC 处理应用有线网络配置 */
+/* 1. 平台网络配置:IPC 处理应用有线网络配置 */
ipcMain.handle('apply-wired-config',async(event,config)=>{
// return {
// success: true,
@@ -234,7 +68,13 @@ ipcMain.handle('apply-wired-config',async(event,config)=>{
// 添加 IPv6 配置(如果存在 ipv6Gateway)????ipv6和长度需要吗?ui只写了ipv6网关
// ipv6PrefixLength 是 IPv6 地址的前缀长度,类似于 IPv4 中的子网掩码。????
if (config.ipv6 && config.ipv6Gateway) {
- modifyCmd += ` ipv6.method manual ipv6.addresses "${config.ipv6}/${config.ipv6PrefixLength || 64}" ipv6.gateway "${config.ipv6Gateway}"`;
+ const ipv6PrefixLength = config.ipv6PrefixLength &&
+ config.ipv6PrefixLength >= 0 &&
+ config.ipv6PrefixLength <= 128 ?
+ config.ipv6PrefixLength : 64; // 默认使用64
+ modifyCmd += ` ipv6.method manual ipv6.addresses "${config.ipv6}/${ipv6PrefixLength}" ipv6.gateway "${config.ipv6Gateway}"`;
+ }else if (config.ipv6 || config.ipv6Gateway) {
+ console.warn('IPv6配置不完整:需要同时提供IPv6地址和网关');
}
// 执行配置命令
@@ -266,4 +106,162 @@ ipcMain.handle('apply-wired-config',async(event,config)=>{
message: `配置失败: ${error instanceof Error ? error.message : String(error || '未知错误')}`
};
}
-})
\ No newline at end of file
+})
+
+/**2. 服务器配置 */
+ipcMain.handle('connect-server', async (event, { serverIp }) => {
+ console.log(`Connecting to server: ${serverIp}`);
+
+ try {
+ // 获取设备ID
+ const deviceId = await getDeviceId();
+ console.log(`Using device ID: ${deviceId}`);
+
+ // 构建新的API地址,使用POST请求
+ const apiUrl = `http://${serverIp}:8113/api/nex/v1/client/authentication`;
+ console.log(`Testing API endpoint: ${apiUrl}`);
+
+ // 调用Authentication接口进行连接验证(POST请求)
+ const response = await request(apiUrl, {
+ method: 'POST',
+ timeout: 10000, // 10秒超时
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ data: JSON.stringify({ device_id: deviceId })
+ });
+
+ console.log('API response received:', response);
+
+ // 解析响应数据
+ let responseData;
+ try {
+ responseData = JSON.parse((response as any).data);
+ } catch (parseError) {
+ console.error('Failed to parse authentication response:', parseError);
+ return { success: false, message: '服务器响应格式错误' };
+ }
+
+ console.log('Parsed authentication response:', responseData);
+
+ // 检查认证结果 - 只有code等于200时才算成功
+ if (responseData.code === '200' || responseData.code === 200) {
+ // 认证成功,存储服务器IP
+ currentServerIp = serverIp;
+ console.log('Authentication successful, server IP stored:', serverIp);
+ return { success: true, message: `已连接到服务器 ${serverIp}`, serverIp: serverIp };
+ } else {
+ // 认证失败,返回服务器提供的错误信息
+ const errorMessage = responseData.message || responseData.msg || responseData.error || '服务器认证失败';
+ console.log('Authentication failed:', errorMessage);
+ return { success: false, message: errorMessage };
+ }
+
+ } catch (error:any) {
+ console.error('Server connection error details:', {
+ message: error.message,
+ code: error.code,
+ errno: error.errno,
+ syscall: error.syscall,
+ address: error.address,
+ port: error.port
+ });
+
+ // 根据具体错误提供更准确的提示
+ let errorMessage = '请输入正确服务器ip或者联系管理员';
+
+ if (error.message.includes('ECONNREFUSED')) {
+ errorMessage = `无法连接到服务器 ${serverIp}:8113,请检查服务器是否运行`;
+ } else if (error.message.includes('ENOTFOUND')) {
+ errorMessage = `无法解析服务器地址 ${serverIp},请检查IP地址是否正确`;
+ } else if (error.message.includes('请求超时')) {
+ errorMessage = `连接服务器 ${serverIp}:8113 超时,请检查网络连接`;
+ } else if (error.message.includes('ENETUNREACH')) {
+ errorMessage = `网络不可达,无法连接到服务器 ${serverIp}`;
+ } else if (error.message.includes('HTTP 404')) {
+ errorMessage = `服务器返回404错误,Authentication接口路径可能不正确`;
+ } else if (error.message.includes('HTTP')) {
+ errorMessage = `服务器返回错误: ${error.message}`;
+ }
+
+ return {
+ success: false,
+ message: errorMessage
+ };
+ }
+});
+// 服务器IP获取
+ipcMain.handle('get-current-server-ip', () => {
+ return currentServerIp;
+});
+
+/**3. 侦测管理平台 */
+// 下载并更新客户端
+let updateDownloadedInfo: any = null;
+
+// 修改 download-and-update 处理器中的事件监听器
+ipcMain.handle('download-and-update', async (event, url) => {
+ console.log('下载并更新客户端:', url);
+
+ // 重置更新状态
+ updateDownloadedInfo = null;
+
+ if (process.env.NODE_ENV === 'development') {
+ // 开发环境下使用模拟更新
+ return simulateUpdate(event, url);
+ } else {
+ // 生产环境下使用真实更新
+ // 清除之前的事件监听器,避免重复注册
+ autoUpdater.removeAllListeners('update-downloaded');
+
+ // 监听更新下载完成事件
+ autoUpdater.on('update-downloaded', (info) => {
+ console.log('更新已下载=====>', info);
+ updateDownloadedInfo = info;
+ });
+
+ return performRealUpdate(event, url);
+ }
+});
+
+// 安装更新并重启应用
+ipcMain.on('install-update-and-restart', () => {
+ console.log('安装更新并重启应用...');
+
+ if (process.env.NODE_ENV === 'development') {
+ // 开发环境下使用模拟更新
+ console.log('开发环境:模拟重启应用');
+ // 直接调用,添加小延迟
+
+ app.quit();
+ } else {
+ // 生产环境下使用真实更新
+ if (!updateDownloadedInfo) {
+ console.warn('没有已完成下载的更新');
+ // 可以选择不执行重启或提示用户
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+ if (focusedWindow) {
+ focusedWindow.webContents.send('update-not-available', {
+ message: '没有已完成下载的更新,请先下载更新'
+ });
+ }
+ return;
+ }
+
+ console.log('生产环境:安装更新并重启');
+
+ try {
+ autoUpdater.quitAndInstall(false, true);
+ } catch (installError) {
+ console.error('自动更新安装失败,降级到手动重启:', installError);
+ // app.relaunch();
+ // app.quit();
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+ if (focusedWindow) {
+ focusedWindow.webContents.send('update-install-error', {
+ message: '更新安装失败: ' + (installError instanceof Error ? installError.message : String(installError))
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/pc-fe/src/main/preload.ts b/pc-fe/src/main/preload.ts
index d13be4b..abaca82 100644
--- a/pc-fe/src/main/preload.ts
+++ b/pc-fe/src/main/preload.ts
@@ -8,25 +8,16 @@ contextBridge.exposeInMainWorld('electronAPI', {
closeApp: () => ipcRenderer.send('close-app'),
minimizeApp: () => ipcRenderer.send('minimize-app'),
exitKiosk: () => ipcRenderer.send('exit-kiosk'),
-
- // 新增的 gRPC API
- grpcStartDownload: (config: any) => ipcRenderer.invoke('grpc-start-download', config),
- grpcStopDownload: (downloadId: string) => ipcRenderer.invoke('grpc-stop-download', downloadId),
- grpcCheckConnection: () => ipcRenderer.invoke('grpc-check-connection'),
- // gRPC 进度监听
- onGrpcProgress: (callback: (progress: any) => void) => {
- ipcRenderer.on('grpc-progress-update', (_, progress) => callback(progress));
- },
- // 移除监听器
- removeAllGrpcProgressListeners: () => {
- ipcRenderer.removeAllListeners('grpc-progress-update');
- },
+ // 版本更新相关API
+ downloadAndUpdate: (url: string) => ipcRenderer.invoke('download-and-update', url),
+ // 服务器IP获取
+ getCurrentServerIp: () => ipcRenderer.invoke('get-current-server-ip'),
// 事件监听
onMainProcessMessage: (callback: (data: string) => void) => {
ipcRenderer.on('main-process-message', (_, data) => callback(data));
},
-
+
on(...args: Parameters) {
const [channel, listener] = args
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
diff --git a/pc-fe/src/main/utils/request.ts b/pc-fe/src/main/utils/request.ts
new file mode 100644
index 0000000..100cbb2
--- /dev/null
+++ b/pc-fe/src/main/utils/request.ts
@@ -0,0 +1,132 @@
+// src/main/utils/request.ts
+/**
+ * // 使用方式1: 直接调用,类似 axios
+const queryAlarmHandleReq = async (params = {}) => {
+ const res = await request({
+ url: `/api/biz-scene/v1/alarm/base/info/statistics`,
+ data: params,
+ method: 'POST',
+ });
+ return res;
+};
+
+// 使用方式2: 使用便捷方法
+const queryAlarmHandleReq2 = async (params = {}) => {
+ const res = await post(`/api/biz-scene/v1/alarm/base/info/statistics`, params);
+ return res;
+};
+
+// 使用方式3: 使用默认导出
+import axiosLikeRequest from './request';
+
+const queryAlarmHandleReq3 = async (params = {}) => {
+ const res = await axiosLikeRequest({
+ url: `/api/biz-scene/v1/alarm/base/info/statistics`,
+ data: params,
+ method: 'POST',
+ });
+ return res;
+};
+ */
+const https = require('https');
+const http = require('http');
+
+interface RequestOptions {
+ method?: string;
+ headers?: Record;
+ data?: any;
+ timeout?: number;
+}
+
+export default function request(url:string, options: RequestOptions = {}) {
+ return new Promise((resolve, reject) => {
+ console.log(`\n=== HTTP REQUEST START ===`);
+ console.log(`URL: ${url}`);
+ console.log(`Method: ${options.method || 'GET'}`);
+
+ const urlObj = new URL(url);
+ const isHttps = urlObj.protocol === 'https:';
+ const httpModule = isHttps ? https : http;
+
+ // 确保端口号正确解析
+ let port: number;
+ if (!urlObj.port) {
+ port = isHttps ? 443 : 80;
+ } else {
+ port = parseInt(urlObj.port, 10);
+ }
+
+ const requestOptions = {
+ hostname: urlObj.hostname,
+ port: port,
+ path: urlObj.pathname + urlObj.search,
+ method: options.method || 'GET',
+ timeout: options.timeout || 5000,
+ headers: {
+ 'User-Agent': 'NexOS/1.0',
+ 'Accept': 'application/json, text/plain, */*',
+ ...options.headers
+ }
+ };
+
+ console.log('Request Headers:', JSON.stringify(requestOptions.headers, null, 2));
+ if (options.data) {
+ console.log('Request Body:', options.data);
+ }
+ console.log('Request Options:', {
+ hostname: requestOptions.hostname,
+ port: requestOptions.port,
+ path: requestOptions.path,
+ method: requestOptions.method,
+ timeout: requestOptions.timeout
+ });
+
+ const req = httpModule.request(requestOptions, (res:any) => {
+ let data = '';
+
+ res.on('data', (chunk:any) => {
+ data += chunk;
+ });
+
+ res.on('end', () => {
+ try {
+ const result = {
+ statusCode: res.statusCode,
+ headers: res.headers,
+ data: data
+ };
+
+ console.log(`\n=== HTTP RESPONSE ===`);
+ console.log(`Status Code: ${res.statusCode}`);
+ console.log(`Response Headers:`, JSON.stringify(res.headers, null, 2));
+ console.log(`Response Body:`, data);
+ console.log(`=== HTTP REQUEST END ===\n`);
+
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ resolve(result);
+ } else {
+ reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
+ }
+ } catch (parseError) {
+ reject(parseError);
+ }
+ });
+ });
+
+ req.on('timeout', () => {
+ req.destroy();
+ reject(new Error('请求超时'));
+ });
+
+ req.on('error', (error:any) => {
+ console.error('HTTP request error:', error);
+ reject(error);
+ });
+
+ if (options.data) {
+ req.write(options.data);
+ }
+
+ req.end();
+ });
+}
\ No newline at end of file
diff --git a/pc-fe/src/main/utils/utils.ts b/pc-fe/src/main/utils/utils.ts
index bc7b818..2075112 100644
--- a/pc-fe/src/main/utils/utils.ts
+++ b/pc-fe/src/main/utils/utils.ts
@@ -1,4 +1,6 @@
+import { BrowserWindow } from 'electron';
+import { autoUpdater } from 'electron-updater';
const { exec } = require('child_process');
const os = require('os');
const { promisify } = require('util');
@@ -99,4 +101,100 @@ export function netmaskToCidr(netmask:string) {
'255.255.255.252': '30'
};
return netmaskMap[netmask] || '24';
+}
+
+/**模拟更新客户端 */
+export async function simulateUpdate(event: any, url: any) {
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+
+ return new Promise((resolve) => {
+ let progress = 0;
+ const interval = setInterval(() => {
+ progress += 10;
+ if (focusedWindow) {
+ focusedWindow.webContents.send('update-progress', {
+ percent: progress,
+ transferred: progress,
+ total: 100,
+ bytesPerSecond: 1024
+ });
+ }
+
+ if (progress >= 100) {
+ clearInterval(interval);
+
+ // 关键:发送更新下载完成的通知
+ if (focusedWindow) {
+ focusedWindow.webContents.send('update-downloaded', {
+ message: '模拟更新已下载完成'
+ });
+ }
+
+ resolve({ success: true, message: '模拟更新完成' });
+ }
+ }, 500);
+ });
+}
+/**真实更新 */
+export async function performRealUpdate(event: any, url: string) {
+ try {
+ console.log('开始检查更新...');
+
+ // 设置更新URL
+ if (url) {
+ autoUpdater.setFeedURL(url);
+ }
+
+ // 获取当前窗口用于发送进度更新
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+
+ return new Promise((resolve, reject) => {
+ // 创建一次性监听器函数
+ const handleDownloadProgress = (progress: any) => {
+ console.log('下载进度:', progress);
+ if (focusedWindow) {
+ focusedWindow.webContents.send('update-progress', progress);
+ }
+ };
+
+ const handleUpdateDownloaded = (info: any) => {
+ console.log('更新下载完成:', info);
+ // 通知前端更新已下载完成
+ if (focusedWindow) {
+ focusedWindow.webContents.send('update-downloaded', info);
+ }
+
+ // 清理监听器
+ autoUpdater.removeListener('download-progress', handleDownloadProgress);
+ autoUpdater.removeListener('update-downloaded', handleUpdateDownloaded);
+ autoUpdater.removeListener('error', handleError);
+
+ resolve({ success: true, message: '更新已下载完成' });
+ };
+
+ const handleError = (error: Error) => {
+ console.error('更新错误:', error);
+ // 清理监听器
+ autoUpdater.removeListener('download-progress', handleDownloadProgress);
+ autoUpdater.removeListener('update-downloaded', handleUpdateDownloaded);
+ autoUpdater.removeListener('error', handleError);
+
+ reject(new Error(`更新失败: ${error.message}`));
+ };
+
+ // 注册监听器
+ autoUpdater.on('download-progress', handleDownloadProgress);
+ autoUpdater.on('update-downloaded', handleUpdateDownloaded);
+ autoUpdater.on('error', handleError);
+
+ // 开始检查更新
+ autoUpdater.checkForUpdates();
+ });
+ } catch (error) {
+ console.error('更新过程出错:', error);
+ return {
+ success: false,
+ error: `更新失败: ${error instanceof Error ? error.message : String(error)}`
+ };
+ }
}
\ No newline at end of file
diff --git a/pc-fe/src/pages/components/ButtonCom/index.less b/pc-fe/src/pages/components/ButtonCom/index.less
index 8a9dde7..72c9faf 100644
--- a/pc-fe/src/pages/components/ButtonCom/index.less
+++ b/pc-fe/src/pages/components/ButtonCom/index.less
@@ -1,20 +1,21 @@
.button-container {
display: flex;
justify-content: center;
- padding: 15px 0;
flex-shrink: 0;
gap: 40px;
+ margin-top: 60px;
+ margin-bottom: 80px;
.cancel-button {
width: 140px;
height: 64px;
border-radius: 32px;
- background: transparent;
- border: 1px solid rgba(134, 133, 158, 1);
+ background: rgba(1, 90, 255, 0.1);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
+ border: none;
span {
font-family: PingFang SC;
@@ -23,23 +24,23 @@
font-size: 20px;
line-height: 32px;
letter-spacing: 0%;
- color: rgba(229, 229, 229, 1);
+ color: rgba(1, 90, 255, 1);
}
&:hover {
- background: rgba(255, 255, 255, 0.05);
+ background: rgba(1, 90, 255, 0.2);
}
}
.confirm-button {
height: 64px;
border-radius: 32px;
- background: rgba(255, 255, 255, 0.3);
+ background: rgba(1, 90, 255, 1);
border: none;
cursor: pointer;
display: flex;
align-items: center;
- padding: 0 30px;
+ padding: 0 67px;
span {
font-family: PingFang SC;
@@ -60,7 +61,7 @@
}
&:hover {
- background: rgba(255, 255, 255, 0.4);
+ background: rgba(1, 90, 255, 1);
}
}
}
\ No newline at end of file
diff --git a/pc-fe/src/pages/components/Layout/index.less b/pc-fe/src/pages/components/Layout/index.less
index 3320c8f..823bb22 100644
--- a/pc-fe/src/pages/components/Layout/index.less
+++ b/pc-fe/src/pages/components/Layout/index.less
@@ -1,11 +1,11 @@
.main-layout {
- background-color: rgba(0, 9, 51, 0.9);
+ background-image: url("../../../assets/bg.png");
+ background-size: 100% 100%;
}
.main-content {
- // background-size: 100% 100%;
- background-color: rgba(0, 9, 51, 0.9);
+ background-color: transparent;
width: 100vw;
height: 100vh;
}
diff --git a/pc-fe/src/pages/components/Layout/index.tsx b/pc-fe/src/pages/components/Layout/index.tsx
index a065a38..a75bc5b 100644
--- a/pc-fe/src/pages/components/Layout/index.tsx
+++ b/pc-fe/src/pages/components/Layout/index.tsx
@@ -26,11 +26,12 @@ const MainLayout: React.FC = () => {
useEffect(() => {
// TODO: 第一次来:判断是否配置ip/DHCP、服务ip绑定终端 绑定:直接到版本更新页面 未绑定:到配置ip/DHCP页面
setTimeout(() => {
- history.push('/grpc');
+ // history.push('/login');
+ history.push('/configSteps?tab=terminalGetImage');
},1000)
// const fetchDeviceId = async () => {
// try {
- // const res = await window.electronAPI.invoke('get-deviceid');
+ // const res = await window.electronAPI.invoke('get-device-id');
// console.log('获取设备ID:', res);
// } catch (error) {
// console.error('获取设备ID失败:', error);
diff --git a/pc-fe/src/pages/components/ProgressBar/index.less b/pc-fe/src/pages/components/ProgressBar/index.less
new file mode 100644
index 0000000..0513220
--- /dev/null
+++ b/pc-fe/src/pages/components/ProgressBar/index.less
@@ -0,0 +1,55 @@
+// src/pages/components/ProgressBar/index.less
+.progressBarContainer {
+ width: 620px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+ .progressLine{
+ display: flex;
+ align-items: center;
+ width: 100%;
+ margin-bottom: 20px;
+ .progressContainer {
+ width: 560px;
+ height: 10px;
+ border-radius: 5px;
+ background: rgba(255, 255, 255, 0.1);
+ overflow: hidden;
+ color: #BBBBBB;
+
+ .progressBar {
+ height: 100%;
+ border-radius: 5px;
+ background: #E5E5E5;
+ transition: width 0.3s ease;
+ }
+ }
+ .progressPercent {
+ color: #BBBBBB;
+ margin-left: 10px;
+ min-width: 40px;
+ text-align: right;
+ font-family: PingFang SC;
+ font-weight: 500;
+ font-style: Medium;
+ font-size: 22px;
+ leading-trim: NONE;
+ line-height: 100%;
+ letter-spacing: -1.17px;
+ vertical-align: middle;
+ }
+ }
+
+ .progressText {
+ color: #BBBBBB;
+ font-family: PingFang SC;
+ font-weight: 500;
+ font-style: Medium;
+ font-size: 22px;
+ line-height: 100%;
+ letter-spacing: -1.17px;
+ vertical-align: middle;
+ }
+}
\ No newline at end of file
diff --git a/pc-fe/src/pages/components/ProgressBar/index.tsx b/pc-fe/src/pages/components/ProgressBar/index.tsx
new file mode 100644
index 0000000..1263047
--- /dev/null
+++ b/pc-fe/src/pages/components/ProgressBar/index.tsx
@@ -0,0 +1,28 @@
+// src/pages/components/ProgressBar/index.tsx
+import React from 'react';
+import styles from './index.less';
+
+interface ProgressBarProps {
+ percent: number;
+ text?: string;
+}
+
+const ProgressBar: React.FC = ({ percent, text = '处理中...' }) => {
+ const clampedPercent = Math.min(100, Math.max(0, percent));
+ return (
+
+ );
+};
+
+export default ProgressBar;
\ No newline at end of file
diff --git a/pc-fe/src/pages/configSteps/components/networkConfig/index.less b/pc-fe/src/pages/configSteps/components/networkConfig/index.less
index 33891d0..0f5dff7 100644
--- a/pc-fe/src/pages/configSteps/components/networkConfig/index.less
+++ b/pc-fe/src/pages/configSteps/components/networkConfig/index.less
@@ -1,178 +1,202 @@
+// src/pages/configSteps/components/networkConfig/index.less
.network-config {
- width: 100%;
- height: 100%;
display: flex;
flex-direction: column;
+ height: 100%;
+ box-sizing: border-box;
- .tab-container {
- height: 70px;
- flex-shrink: 0;
- background: linear-gradient(180deg, rgba(229, 229, 229, 0.05) 0%, rgba(229, 229, 229, 0.05) 100%);
+ .tab-content-container-wrapper {
display: flex;
- justify-content: center;
- align-items: center;
-
- .tab-item {
- font-family: PingFang SC;
- font-weight: 400;
- font-style: Heavy;
- top: -5px;
- font-size: 20px;
- line-height: 30px;
- letter-spacing: 0%;
- padding: 0 30px;
- cursor: pointer;
- position: relative;
- color: rgba(229, 229, 229, 0.5);
-
- &.active {
- color: rgba(229, 229, 229, 1);
- }
-
- .indicator {
- position: absolute;
- bottom: -15px;
- left: 50%;
- transform: translateX(-50%);
- width: 8px;
- height: 8px;
- background: rgba(229, 229, 229, 1);
- border-radius: 50%;
- }
- }
- }
-
- .content-container {
+ flex-direction: column;
flex: 1;
+ box-sizing: border-box;
+ // background: rgba(255, 255, 255, 1);
+ border-radius: 20px;
overflow: hidden;
- }
-
- .dhcp-content {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 100%;
-
- h2 {
- font-family: PingFang SC;
- font-weight: 400;
- font-style: Heavy;
- font-size: 24px;
- color: rgba(229, 229, 229, 1);
- margin-bottom: 20px;
- }
-
- p {
- font-family: PingFang SC;
- font-size: 18px;
- color: rgba(229, 229, 229, 0.8);
- }
- }
-
- .static-ip-container {
- display: flex;
- flex-direction: column;
- height: 100%;
-
- .form-container {
- width: 500px;
- margin: 0 auto;
- flex: 1;
- overflow-y: auto;
- padding: 20px 0px;
- padding-right: 60px;
-
- // 滚动条样式
- &::-webkit-scrollbar {
- width: 10px;
- }
-
- &::-webkit-scrollbar-track {
- background: rgba(255, 255, 255, 0.05);
- border-radius: 28px;
- margin: 10px 0;
- }
-
- &::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);
- border-radius: 28px;
- min-height: 30px;
- }
-
- // 表单项样式
- .ant-form-item {
- margin-bottom: 24px;
- }
-
- .ant-form-item-label {
- padding: 0 0 12px 0;
- }
-
- .label {
- font-family: PingFang SC;
- font-weight: 400;
- font-style: Heavy;
- font-size: 18px;
- line-height: 32px;
- letter-spacing: 0%;
- color: rgba(229, 229, 229, 1);
- margin-bottom: 12px;
- }
-
- .input-field {
+ z-index: 1;
+ position: relative;
+
+ .tab-container {
+ display: flex;
+ z-index: 2; // 提高tab容器的层级
+ position: relative;
+ flex-shrink: 0;
+ .special-tab-bg-con{
+ position: absolute;
width: 100%;
- height: 56px;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 28px;
- border: none;
- padding: 0 24px;
- box-sizing: border-box;
- font-family: PingFang SC;
- font-weight: 400;
- font-style: Heavy;
- font-size: 18px;
- line-height: 32px;
- letter-spacing: 0%;
- color: rgba(229, 229, 229, 1);
-
- &::placeholder {
- color: rgba(229, 229, 229, 0.5);
+ height: 100%;
+ z-index: 0;
+ .tab-bg{
+ height: 50%;
+ width: 100%;
}
-
- &:focus {
- outline: none;
- background: rgba(255, 255, 255, 0.15);
+ .tab-grey-bg{
+ background: rgba(231, 235, 242, 1);
+ }
+ .tab-white-bg{
+ background: rgba(255, 255, 255, 1);
+ top: 50%;
}
}
- // 覆盖 Ant Design 的默认样式
- .ant-input {
- background: rgba(255, 255, 255, 0.1);
- border-radius: 28px;
- border: none;
- padding: 0 24px;
- box-sizing: border-box;
+ .tab-item {
+ height: 80px;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: all 0.3s ease;
font-family: PingFang SC;
font-weight: 400;
font-style: Heavy;
- font-size: 18px;
- line-height: 32px;
+ font-size: 20px;
+ line-height: 30px;
letter-spacing: 0%;
- color: rgba(229, 229, 229, 1);
- height: 56px;
+ padding: 0 40px;
+ z-index: 1;
- &::placeholder {
- color: rgba(229, 229, 229, 0.5);
+ &.dhcp-tab {
+ // 默认状态 - 左上角和左下角圆角
+ border-top-left-radius: 20px;
+ border-bottom-left-radius: 20px;
+
+ &.active {
+ background: rgba(255, 255, 255, 1);
+ color: rgba(51, 51, 51, 1);
+ height: 80px;
+ // 激活状态 - 左上角和右上角圆角
+ border-radius: 20px 20px 0 0;
+ margin-bottom: -20px;
+ position: relative;
+ z-index: 3;
+ // 移除底部圆角
+ border-bottom-left-radius: 0;
+ }
+
+ &:not(.active) {
+ background: rgba(231, 235, 242, 1);
+ color: rgba(153, 153, 153, 1);
+ // 未激活状态 - 左下角和右下角圆角
+ border-radius: 0 0 20px 0;
+ // 确保顶部没有圆角
+ border-top-left-radius: 0;
+ }
}
- &:focus {
- outline: none;
- background: rgba(255, 255, 255, 0.15);
- box-shadow: none;
+ &.static-tab {
+ // 默认状态 - 右上角和右下角圆角
+ border-top-right-radius: 20px;
+ border-bottom-right-radius: 20px;
+
+ &.active {
+ background: rgba(255, 255, 255, 1);
+ color: rgba(51, 51, 51, 1);
+ height: 80px;
+ // 激活状态 - 左上角和右上角圆角
+ border-radius: 20px 20px 0 0;
+ margin-bottom: -20px;
+ position: relative;
+ z-index: 3;
+ // 移除底部圆角
+ border-bottom-right-radius: 0;
+ }
+
+ &:not(.active) {
+ background: rgba(231, 235, 242, 1);
+ color: rgba(153, 153, 153, 1);
+ // 未激活状态 - 左下角和右下角圆角
+ border-radius: 0 0 0 20px;
+ // 确保顶部没有圆角
+ border-top-right-radius: 0;
+ }
}
}
}
+ .content-container {
+ flex: 1;
+ padding: 40px 0;
+ box-sizing: border-box;
+ overflow-y: hidden;
+ min-height: 0;
+ display: flex;
+ background: rgba(255, 255, 255, 1);
+ justify-content: center;
+ z-index: 1;
+
+ .dhcp-content,
+ .static-ip-container {
+ height: 100%;
+ overflow-y: auto;
+ padding-right: 60px;
+ width: 568px;
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: rgba(245, 245, 245, 1);
+ border-radius: 4px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(224, 229, 236, 1);
+ border-radius: 4px;
+ }
+ }
+
+ .dhcp-content {
+ h2 {
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 18px;
+ line-height: 32px;
+ margin-bottom: 10px;
+ }
+
+ p {
+ color: #666;
+ }
+ }
+
+ .static-ip-container {
+ .form-container {
+ width: 500px;
+ margin: 0 auto;
+
+ .label {
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 18px;
+ line-height: 32px;
+ letter-spacing: 0%;
+ color: rgba(102, 102, 102, 1);
+ }
+
+ .input-field {
+ background: rgba(245, 245, 245, 1);
+ height: 56px;
+ border-radius: 28px;
+ padding-left: 20px;
+ border: none;
+
+ &::placeholder {
+ color: rgba(187, 187, 187, 1);
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 18px;
+ leading-trim: NONE;
+ line-height: 32px;
+ letter-spacing: 0%;
+ }
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/pc-fe/src/pages/configSteps/components/networkConfig/index.tsx b/pc-fe/src/pages/configSteps/components/networkConfig/index.tsx
index 1626580..9448696 100644
--- a/pc-fe/src/pages/configSteps/components/networkConfig/index.tsx
+++ b/pc-fe/src/pages/configSteps/components/networkConfig/index.tsx
@@ -3,8 +3,9 @@ import styles from './index.less';
import cs from 'classnames';
import { Form, Input, message } from 'antd';
import ButtonCom from '../../../components/ButtonCom';
+import { useConfigStep } from '@/contexts/ConfigStepContext';
-const staticIpFormFields: CONFIG_STEPS.StaticFormFieldConfig[] = [
+const staticIpFormFields: CONFIG_STEPS.FormFieldConfig[] = [
{
name: "ipv4",
label: "IPv4",
@@ -44,6 +45,30 @@ const staticIpFormFields: CONFIG_STEPS.StaticFormFieldConfig[] = [
}
]
},
+ {
+ name: "ipv6",
+ label: "IPv6地址",
+ type: "input",
+ placeholder: "请输入",
+ rules: [
+ {
+ pattern: /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:)*::([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4}$|^::$/,
+ message: '请输入正确的IPv6地址格式'
+ }
+ ]
+ },
+ {
+ name: "ipv6PrefixLength",
+ label: "IPv6前缀长度",
+ type: "input",
+ placeholder: "请输入前缀长度,默认64",
+ rules: [
+ {
+ pattern: /^(?:[0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/,
+ message: '请输入0-128之间的数字,默认为64'
+ }
+ ]
+ },
{
name: "ipv6Gateway",
label: "IPv6网关",
@@ -86,6 +111,30 @@ const staticIpFormFields: CONFIG_STEPS.StaticFormFieldConfig[] = [
const NetworkConfig: React.FC = () => {
const [activeTab, setActiveTab] = useState<'dhcp' | 'static'>('dhcp');
const [form] = Form.useForm();
+ const { goToNextTab } = useConfigStep();
+
+ const validateIPv6Config = (values: any) => {
+ // 如果提供了IPv6网关但没有IPv6地址,给出提示
+ if (values.ipv6Gateway && !values.ipv6) {
+ message.warning('提供了IPv6网关但未提供IPv6地址,IPv6配置可能不完整');
+ }
+
+ // 如果提供了IPv6地址但没有前缀长度,使用默认值
+ if (values.ipv6 && !values.ipv6PrefixLength) {
+ message.info('未提供IPv6前缀长度,将使用默认值64');
+ }
+
+ // 验证前缀长度范围
+ if (values.ipv6PrefixLength) {
+ const prefixLength = parseInt(values.ipv6PrefixLength, 10);
+ if (prefixLength < 0 || prefixLength > 128) {
+ message.error('IPv6前缀长度必须在0-128之间');
+ return false;
+ }
+ }
+
+ return true;
+ };
const handleSubmit = async () => {
if (activeTab === 'dhcp') {
@@ -95,6 +144,9 @@ const NetworkConfig: React.FC = () => {
console.log('网络配置返回信息成功:', res);
if(res.success){
message.success('网络配置成功');
+ setTimeout(() => {
+ goToNextTab();
+ }, 300);
}else{
message.error(res.message || '网络配置失败');
}
@@ -106,10 +158,26 @@ const NetworkConfig: React.FC = () => {
try {
const values = await form.validateFields();
console.log('表单提交数据:', values);
+
+ // 验证IPv6配置
+ if (!validateIPv6Config(values)) {
+ return;
+ }
+
+ // 处理IPv6前缀长度默认值
+ if (values.ipv6 && !values.ipv6PrefixLength) {
+ values.ipv6PrefixLength = 64; // 默认前缀长度
+ } else if (values.ipv6PrefixLength) {
+ values.ipv6PrefixLength = parseInt(values.ipv6PrefixLength, 10);
+ }
+
const res = await window.electronAPI.invoke('apply-wired-config',{ method: 'static', ...values });
console.log('网络配置返回信息成功:', res);
if(res.success){
message.success('网络配置成功');
+ setTimeout(() => {
+ goToNextTab();
+ }, 300);
}else{
message.error(res.message || '网络配置失败');
}
@@ -154,26 +222,35 @@ const NetworkConfig: React.FC = () => {
return (
-
-
setActiveTab('dhcp')}
- >
-
DHCP
- {activeTab === 'dhcp' &&
}
+
+
+
+
setActiveTab('dhcp')}
+ >
+ DHCP
+
+
setActiveTab('static')}
+ >
+ 静态IP
+
-
setActiveTab('static')}
- >
-
静态IP
- {activeTab === 'static' &&
}
+
+
+ {activeTab === 'dhcp' ? : }
-
- {activeTab === 'dhcp' ? : }
-
);
diff --git a/pc-fe/src/pages/configSteps/components/serverConfig/index.less b/pc-fe/src/pages/configSteps/components/serverConfig/index.less
new file mode 100644
index 0000000..18cabd1
--- /dev/null
+++ b/pc-fe/src/pages/configSteps/components/serverConfig/index.less
@@ -0,0 +1,54 @@
+// src/pages/configSteps/components/networkConfig/index.less
+.server-config {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ box-sizing: border-box;
+
+ .content-container {
+ flex: 1;
+ padding: 40px 0;
+ box-sizing: border-box;
+ overflow-y: hidden;
+ min-height: 0;
+ display: flex;
+ background: rgba(255, 255, 255, 1);
+ justify-content: center;
+ z-index: 1;
+ .server-container {
+ .form-container {
+ width: 500px;
+ margin: 0 auto;
+
+ .label {
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 18px;
+ line-height: 32px;
+ letter-spacing: 0%;
+ color: rgba(102, 102, 102, 1);
+ }
+
+ .input-field {
+ background: rgba(245, 245, 245, 1);
+ height: 56px;
+ border-radius: 28px;
+ padding-left: 20px;
+ border: none;
+
+ &::placeholder {
+ color: rgba(187, 187, 187, 1);
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 18px;
+ leading-trim: NONE;
+ line-height: 32px;
+ letter-spacing: 0%;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/pc-fe/src/pages/configSteps/components/serverConfig/index.tsx b/pc-fe/src/pages/configSteps/components/serverConfig/index.tsx
new file mode 100644
index 0000000..1d8c132
--- /dev/null
+++ b/pc-fe/src/pages/configSteps/components/serverConfig/index.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import ButtonCom from '../../../components/ButtonCom';
+import { useConfigStep } from '@/contexts/ConfigStepContext';
+import styles from './index.less';
+import { Form, Input, message } from 'antd';
+
+
+const serverIpFormFields: CONFIG_STEPS.FormFieldConfig[] = [
+ {
+ name: "serverIp",
+ label: "服务器IP地址",
+ type: "input",
+ placeholder: "请输入",
+ rules: [
+ { required: true, message: '请输入服务器IP地址' },
+ {
+ pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
+ message: '请输入正确的服务器IP地址格式'
+ }
+ ]
+ }
+];
+
+const Index = () => {
+ const [form] = Form.useForm();
+ const { goToNextTab,setActiveTab } = useConfigStep();
+
+ const ServerComponent = () => (
+
+
{field.label}}
+ rules={field.rules}
+ >
+
+
+ ))}
+
+
+ );
+
+ const handleSubmit= async () => {
+ const values = await form.validateFields();
+ console.log('表单提交数据:', values);
+ const { serverIp } = values;
+ const result = await window.electronAPI.invoke('connect-server', { serverIp });
+ console.log('连接结果:', result);
+ if (result.success) {
+ // 连接成功,保存服务器IP到本地存储
+ localStorage.setItem('connected-server-ip', serverIp);
+ console.log('Connected server IP saved to localStorage:', serverIp);
+ // TODO:跳转到版本更新
+ // setActiveTab("watchManagement")
+ setActiveTab("terminalGetImage")
+ // await ipcRenderer.invoke('show-login-window');
+ } else {
+ message.error(result.message || '连接服务器失败');
+ }
+ }
+
+ return (
+
+ );
+}
+
+export default Index;
diff --git a/pc-fe/src/pages/configSteps/components/terminalGetImage/index.less b/pc-fe/src/pages/configSteps/components/terminalGetImage/index.less
index e69de29..f5d4080 100644
--- a/pc-fe/src/pages/configSteps/components/terminalGetImage/index.less
+++ b/pc-fe/src/pages/configSteps/components/terminalGetImage/index.less
@@ -0,0 +1,115 @@
+// src/pages/configSteps/components/networkConfig/index.less
+.terminal-config {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ box-sizing: border-box;
+
+ .image-loading-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex: 1;
+ width: 100%;
+ background: white;
+ border-radius: 20px;
+
+ .loading-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .spinner {
+ width: 141px;
+ height: 140px;
+ position: relative;
+ // 动画1:旋转
+ &.rotating {
+ animation: rotate 1.5s linear infinite;
+ }
+
+ .spinner-item {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ background: rgba(171, 184, 204, 1);
+ border-radius: 50%;
+ transform-origin: 0 0;
+ }
+ }
+
+ @keyframes rotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+ }
+
+ .loading-text {
+ font-family: "Microsoft YaHei UI";
+ font-weight: 700;
+ font-size: 34px;
+ line-height: 200%;
+ text-align: center;
+ color: rgba(51, 51, 51, 1);
+ margin-top: 70px;
+ }
+
+ .loading-subtext {
+ font-family: "PingFang SC";
+ font-weight: 500;
+ font-size: 22px;
+ line-height: 100%;
+ letter-spacing: -1.17px;
+ color: rgba(153, 153, 153, 1);
+ margin-top: 30px;
+ }
+ }
+ }
+
+ .success-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ flex: 1;
+ background: white;
+
+ .success-box {
+ width: 381px;
+ height: 260px;
+ border: 1px solid #ccc;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 100px;
+ }
+
+ .ready-icon {
+ display: flex;
+ align-items: center;
+ margin-top: 50px;
+ gap: 9px;
+ .ready-text {
+ font-family: "Microsoft YaHei UI";
+ font-weight: 700;
+ font-size: 34px;
+ line-height: 200%;
+ text-align: center;
+ color: rgba(51, 51, 51, 1);
+ }
+ }
+
+ .ready-subtext {
+ font-family: "PingFang SC";
+ font-weight: 500;
+ font-size: 22px;
+ line-height: 100%;
+ letter-spacing: -1.17px;
+ color: rgba(153, 153, 153, 1);
+ margin-top: 30px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/pc-fe/src/pages/configSteps/components/terminalGetImage/index.tsx b/pc-fe/src/pages/configSteps/components/terminalGetImage/index.tsx
index 0cd9718..67a2380 100644
--- a/pc-fe/src/pages/configSteps/components/terminalGetImage/index.tsx
+++ b/pc-fe/src/pages/configSteps/components/terminalGetImage/index.tsx
@@ -1,9 +1,89 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
+import ButtonCom from '../../../components/ButtonCom';
+import { useConfigStep } from '@/contexts/ConfigStepContext';
+import styles from './index.less';
+import { message } from 'antd';
+import { history } from 'umi';
+import TrueIcon from '@assets/true.png'
+
const Index = () => {
+ const { goToNextTab,setActiveTab } = useConfigStep();
+ const [imageStatus, setImageStatus] = useState<'loading' | 'success' | 'error'>('loading');
+
+ // 模拟获取镜像数据的API调用
+ useEffect(() => {
+ const fetchImageData = async () => {
+ try {
+ // 模拟API请求
+ await new Promise(resolve => setTimeout(resolve, 3000));
+ // 模拟随机成功或失败
+ const isSuccess = Math.random() > 0.3;
+ setImageStatus(isSuccess ? 'success' : 'loading');
+
+ // 如果失败,继续轮询检查状态
+ if (!isSuccess) {
+ const checkStatus = async () => {
+ // 模拟WebSocket或轮询检查
+ await new Promise(resolve => setTimeout(resolve, 5000));
+ setImageStatus('success'); // 模拟收到通知
+ };
+ checkStatus();
+ }
+ } catch (error) {
+ message.error('获取镜像数据失败');
+ }
+ };
+
+ fetchImageData();
+ }, []);
+
+ const ImageLoadingComponent = () => (
+
+
+
+ {[...Array(8)].map((_, index) => (
+
+ ))}
+
+
正在获取镜像数据
+
请耐心等待...
+
+
+ );
+
+ const SuccessComponent = () => (
+
+
+ {/* 这里可以显示实际的镜像信息 */}
+
镜像信息展示区域
+
+
+

+
客户端及镜像已就绪
+
+
点击确认并立即使用吧~
+
+ );
+
+ const handleSubmit = () => {
+ history.push('/login');
+ }
+
return (
-
- 终端获取镜像信息
+
+ {imageStatus === 'loading' &&
}
+ {imageStatus === 'success' &&
}
+ {imageStatus === 'loading' &&
}
+ {imageStatus === 'success' &&
}
);
}
diff --git a/pc-fe/src/pages/configSteps/components/watchManagement/index.less b/pc-fe/src/pages/configSteps/components/watchManagement/index.less
index e69de29..9aff3f8 100644
--- a/pc-fe/src/pages/configSteps/components/watchManagement/index.less
+++ b/pc-fe/src/pages/configSteps/components/watchManagement/index.less
@@ -0,0 +1,122 @@
+// src/pages/configSteps/components/watchManagement/index.less
+.container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ .content {
+ flex: 1;
+ width: 80%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ .buttonWrapper {
+ align-self: center;
+ }
+
+ .latestContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 40px 0;
+
+ .iconText {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 28px;
+ background: #FFFFFF1A;
+ backdrop-filter: blur(100px);
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 22px;
+ line-height: 32px;
+ letter-spacing: 0%;
+ padding: 20px 40px;
+ margin-bottom: 30px;
+
+ .icon {
+ margin-right: 10px;
+ font-size: 24px;
+ font-weight: bold;
+ }
+ }
+
+ .description {
+ font-family: PingFang SC;
+ font-weight: 500;
+ font-style: Medium;
+ font-size: 22px;
+ line-height: 100%;
+ letter-spacing: -1.17px;
+ vertical-align: middle;
+ color: #BBBBBB;
+ text-align: center;
+ }
+ }
+
+ .outdatedContainer {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 40px 0;
+
+ .warningBox {
+ width: 200px;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 28px;
+ background: #FFFFFF1A;
+ backdrop-filter: blur(100px);
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 22px;
+ line-height: 32px;
+ letter-spacing: 0%;
+ padding: 20px 20px;
+ margin-bottom: 8px;
+ color: #E5E5E5;
+
+ .icon {
+ margin-right: 10px;
+ }
+ }
+
+ .title {
+ font-family: Microsoft YaHei UI;
+ font-weight: 700;
+ font-style: Bold;
+ font-size: 40px;
+ line-height: 200%;
+ letter-spacing: 0%;
+ text-align: center;
+ color: #fff;
+ margin: 20px 0;
+ }
+
+ .description {
+ font-family: PingFang SC;
+ font-weight: 500;
+ font-style: Medium;
+ font-size: 22px;
+ line-height: 100%;
+ letter-spacing: -1.17px;
+ vertical-align: middle;
+ color: #BBBBBB;
+ text-align: center;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/pc-fe/src/pages/configSteps/components/watchManagement/index.tsx b/pc-fe/src/pages/configSteps/components/watchManagement/index.tsx
index f38f4ce..78c486a 100644
--- a/pc-fe/src/pages/configSteps/components/watchManagement/index.tsx
+++ b/pc-fe/src/pages/configSteps/components/watchManagement/index.tsx
@@ -1,11 +1,261 @@
-import React from 'react';
+// src/pages/configSteps/components/watchManagement/index.tsx
+import React, { useState, useEffect } from 'react';
+import styles from './index.less';
+import ProgressBar from '@/pages/components/ProgressBar';
+import ButtonCom from '@/pages/components/ButtonCom';
+import { useConfigStep } from '@/contexts/ConfigStepContext';
+import WaringIcon from '@/assets/waringIcon.png';
+import { message } from 'antd';
+
+const WatchManagement: React.FC = () => {
+ const { goToNextTab } = useConfigStep();
+ // 添加 'downloaded' 状态
+ const [status, setStatus] = useState<'checking' | 'latest' | 'outdated' | 'updating' | 'downloaded' | 'updated'>('checking');
+ const [progress, setProgress] = useState(0);
+ const [countdown, setCountdown] = useState(6);
+
+ // 模拟当前版本
+ const currentVersion:string = '1.0.0';
+ // 模拟最新版本
+ const latestVersion:string = '1.2.0';
+ // 模拟更新URL(实际应该从后端获取)
+ const updateUrl = 'http://localhost:3000/updates/linux';
+
+ useEffect(() => {
+ // 模拟检测版本
+ // const timer = setTimeout(() => {
+ // if (currentVersion === latestVersion) {
+ // setStatus('latest');
+ // } else {
+ // setStatus('outdated');
+ // }
+ // }, 2000);
+
+ // return () => clearTimeout(timer);
+ }, []);
+
+ // 监听更新进度和下载完成事件
+ useEffect(() => {
+ const handleUpdateProgress = (event: any, progressData: any) => {
+ console.log('收到更新进度:', progressData);
+ if (status === 'updating') {
+ setProgress(progressData.percent);
+ }
+ };
+
+ const handleUpdateDownloaded = (event: any, info: any) => {
+ console.log('更新已下载完成:', info);
+ setStatus('downloaded');
+ setProgress(100);
+ };
+
+ const handleUpdateError = (event: any, error: any) => {
+ console.error('更新出错:', error);
+ message.error('更新出错,请稍后重试');
+ setStatus('outdated'); // 出错时回到初始状态
+ };
+
+ const handleUpdateNotAvailable = (event:any, info:any) => {
+ // 显示提示信息给用户
+ console.log(info.message || '没有可安装的更新');
+ message.info('没有可安装的更新');
+ setStatus('latest');
+ }
+
+ // 监听更新进度事件
+ if (window.electronAPI) {
+ window.electronAPI.on('update-progress', handleUpdateProgress);
+ window.electronAPI.on('update-downloaded', handleUpdateDownloaded);
+ window.electronAPI.on('update-not-available', handleUpdateNotAvailable);
+ window.electronAPI.on('update-install-error', handleUpdateError);
+ }
+
+ // 清理监听器
+ return () => {
+ if (window.electronAPI) {
+ window.electronAPI.off('update-progress', handleUpdateProgress);
+ window.electronAPI.off('update-downloaded', handleUpdateDownloaded);
+ window.electronAPI.off('update-not-available', handleUpdateNotAvailable);
+ window.electronAPI.off('update-install-error', handleUpdateError);
+ }
+ };
+ }, [status]);
+
+ // useEffect(() => {
+ // let timer: NodeJS.Timeout;
+ // if (status === 'updated') {
+ // // 倒计时6秒后自动重启
+ // timer = setInterval(() => {
+ // setCountdown(prev => {
+ // if (prev <= 1) {
+ // clearInterval(timer);
+ // handleRestart();
+ // return 0;
+ // }
+ // return prev - 1;
+ // });
+ // }, 1000);
+ // }
+
+ // return () => {
+ // if (timer) clearInterval(timer);
+ // };
+ // }, [status]);
+
+ const handleCancel = () => {
+ console.log('取消操作');
+ };
+
+ const handleConfirmUpdate = () => {
+ // 确认更新客户端
+ setStatus('updating');
+ startUpdateProcess(updateUrl);
+ };
+
+ const handleGetImage = () => {
+ goToNextTab();
+ };
+
+ const handleRestart = () => {
+ // 安装更新并重启客户端
+ if (window.electronAPI) {
+ window.electronAPI.send('install-update-and-restart');
+ }
+ };
+
+ const startUpdateProcess = (url: string) => {
+ // 调用electron API下载并安装更新
+ if (window.electronAPI) {
+ window.electronAPI.downloadAndUpdate(url)
+ .then((result:any) => {
+ console.log('更新完成:', result);
+ if (result.success) {
+ // 不再直接设置为 updated,等待 update-downloaded 事件
+ // setStatus('updated');
+ // setProgress(100);
+ }
+ })
+ .catch((error: any) => {
+ console.error('更新失败:', error);
+ setStatus('outdated'); // 回到初始状态
+ });
+ }
+ };
+
+ const renderContent = () => {
+ switch (status) {
+ case 'checking':
+ return (
+
+ );
+
+ case 'latest':
+ return (
+
+
+
+ ✓
+ 已是最新版本
+
+
当前客户端版本与管理平台版本一致
+
+
+
+
+
+ );
+
+ case 'outdated':
+ return (
+
+
+
+

+
客户端更新
+
+
当前客户端版本过低,需要升级同步
+
客户端版本需与管理平台版本一致
+
+
+
+
+
+ );
+
+ case 'updating':
+ return (
+
+ );
+
+ case 'downloaded':
+ return (
+
+ );
+
+ case 'updated':
+ return (
+
+ );
+
+ default:
+ return null;
+ }
+ };
-const Index = () => {
return (
-
- 侦测管理平台
+
+ {renderContent()}
);
-}
+};
-export default Index;
+export default WatchManagement;
\ No newline at end of file
diff --git a/pc-fe/src/pages/configSteps/index.less b/pc-fe/src/pages/configSteps/index.less
index b6df9d7..4c5e904 100644
--- a/pc-fe/src/pages/configSteps/index.less
+++ b/pc-fe/src/pages/configSteps/index.less
@@ -1,70 +1,97 @@
+// src/pages/configSteps/index.less
.config-step-container {
- width: 100%;
- height: 100%;
- padding-top: 24px;
display: flex;
flex-direction: column;
+ height: 100%;
+ align-items: center;
- .tabs-container {
+ .steps-container {
display: flex;
- width: 100%;
- height: 60px;
- flex-shrink: 0;
- // background: rgba(229, 229, 229, 0.2);
+ align-items: center;
+ justify-content: center;
+ height: 48px;
+ margin-top: 80px;
+ margin-bottom: 60px;
- .tab-item {
- flex: 1;
+ .step-item {
display: flex;
- flex-direction: column;
align-items: center;
- justify-content: center;
cursor: pointer;
- position: relative;
- .tab-label {
+ .step-number {
+ width: 48px;
+ height: 48px;
+ border-radius: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 26px;
+ line-height: 36px;
+ letter-spacing: 0%;
+ }
+
+ .step-number-active {
+ background: rgba(1, 90, 255, 1);
+ color: rgba(255, 255, 255, 1);
+ }
+
+ .step-number-inactive {
+ background: rgba(171, 184, 204, 1);
+ color: rgba(255, 255, 255, 1);
+ }
+
+ .step-label {
+ margin-left: 10px;
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
font-size: 22px;
- color: rgba(255, 255, 255, 0.6); // 未激活tab文字透明
- transition: color 0.3s ease;
+ line-height: 32px;
+ letter-spacing: 0%;
}
- .tab-indicator {
- position: absolute;
- bottom: 0;
- width: 100%;
- height: 4px;
- background: rgba(229, 229, 229, 0.2);
- transition: background-color 0.3s ease;
+ .step-label-active {
+ color: rgba(1, 90, 255, 1);
}
- &.active {
- .tab-label {
- color: white; // 激活tab文字为白色
- }
-
- .tab-indicator {
- background: white; // 激活tab指示器为白色
- }
+ .step-label-inactive {
+ color: rgba(139, 153, 173, 1);
}
}
+
+ .step-divider {
+ margin: 0 20px;
+ font-family: PingFang SC;
+ font-weight: 400;
+ font-style: Heavy;
+ font-size: 22px;
+ line-height: 32px;
+ letter-spacing: 0%;
+ }
+
+ .step-divider-active {
+ color: rgba(1, 90, 255, 1);
+ }
+
+ .step-divider-inactive {
+ color: rgba(139, 153, 173, 1);
+ }
}
.tab-content-container {
flex: 1;
- overflow: hidden;
- background: rgba(255, 255, 255, 0.1);
-
+ overflow: auto;
+ width: 1400px;
+
.tab-content {
height: 100%;
- max-height: 100%;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 4px;
- padding: 20px;
- box-sizing: border-box;
}
}
-
- .emptyBox{
- height: 60px;
+
+ .emptyBox {
flex-shrink: 0;
}
}
\ No newline at end of file
diff --git a/pc-fe/src/pages/configSteps/index.tsx b/pc-fe/src/pages/configSteps/index.tsx
index 7f2e697..d400d9a 100644
--- a/pc-fe/src/pages/configSteps/index.tsx
+++ b/pc-fe/src/pages/configSteps/index.tsx
@@ -1,45 +1,94 @@
// src/pages/configSteps/index.tsx
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
+import { useLocation } from 'umi';
import styles from './index.less';
import cs from 'classnames';
import NetworkConfig from './components/networkConfig';
+import ServerConfig from './components/serverConfig';
import WatchManagement from './components/watchManagement';
import TerminalGetImage from './components/terminalGetImage';
+import { ConfigStepProvider, useConfigStep } from '@/contexts/ConfigStepContext';
-const Index: React.FC = () => {
- const [activeTab, setActiveTab] = useState
("networkConfig");
+const tabs = [
+ { key: "networkConfig", label: '平台网络配置', component: },
+ { key: "serverConfig", label: '服务器配置', component: },
+ { key: "watchManagement", label: '侦测平台管理', component: },
+ { key: "terminalGetImage", label: '终端获取镜像信息', component: },
+];
- const tabs = [
- { key: "networkConfig", label: '平台网络配置', component: },
- { key: "watchManagement", label: '侦测管理平台', component: },
- { key: "terminalGetImage", label: '终端获取镜像信息', component: },
- ];
+const ConfigStepsContent: React.FC = () => {
+ const { activeTab, setActiveTab, tabs: contextTabs } = useConfigStep();
+ const location = useLocation();
+ const [contentOpacity, setContentOpacity] = useState(1);
- const activeTabItem = tabs.find(tab => tab.key === activeTab);
+ // 根据路由参数设置初始tab
+ useEffect(() => {
+ const params = new URLSearchParams(location.search);
+ const tabParam = params.get('tab');
+
+ const hash = location.hash.replace('#', '');
+ const targetTab = tabParam || hash;
+
+ if (targetTab && contextTabs.some(tab => tab.key === targetTab)) {
+ setActiveTab(targetTab);
+ }
+ }, [location.search, location.hash, setActiveTab, contextTabs]);
+
+ const activeTabItem = contextTabs.find(tab => tab.key === activeTab);
+ const activeTabIndex = contextTabs.findIndex(tab => tab.key === activeTab);
return (
-
- {tabs.map((tab) => (
-
setActiveTab(tab.key)}
- >
-
{tab.label}
-
-
+
+ {contextTabs.map((tab, index) => (
+
+ setActiveTab(tab.key)}
+ >
+
activeTabIndex
+ })}>
+ {index + 1}
+
+
activeTabIndex
+ })}>
+ {tab.label}
+
+
+ {index < contextTabs.length - 1 && (
+ = activeTabIndex
+ })}>
+ >
+
+ )}
+
))}
- {activeTabItem?.component ||
未找到对应内容
}
+
+ {activeTabItem?.component ||
未找到对应内容
}
+
);
};
+const Index: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
export default Index;
\ No newline at end of file
diff --git a/pc-fe/src/pages/welcome/index.less b/pc-fe/src/pages/welcome/index.less
index 4dc5d7b..be1e141 100644
--- a/pc-fe/src/pages/welcome/index.less
+++ b/pc-fe/src/pages/welcome/index.less
@@ -8,73 +8,58 @@
.showTextCon{
display: flex;
flex-direction: column;
- justify-self: center;
+ // justify-self: center;
align-items: center;
- .name{
- height: 80px;
- margin-bottom: 20px;
- font-size: 36px;
- color: #e5e5e5;
+ height: 155px;
+ justify-content: space-between;
+ .imgIcon{
display: flex;
+ flex-direction: column;
.welcomeIcon{
- height: 66px;
- width: 70px;
- margin-right: 20px;
+ height: 90px;
+ width: 350px;
}
- .textCon{
- height: 120px;
+ }
+ .loadingCon{
+ width: 96px;
+ height: 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ .dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: rgba(1, 90, 255, 0.2);
display: flex;
- flex-direction: column;
justify-content: space-between;
- .text{
- display: flex;
- flex-direction: column;
- .nameText{
- font-size: 28px;
- }
+ animation: loading 1.5s infinite ease-in-out;
+ }
+
+ .dot:nth-child(1) {
+ animation-delay: -0.32s;
+ }
+
+ .dot:nth-child(2) {
+ animation-delay: -0.16s;
+ }
+
+ @keyframes loading {
+ 0%, 100% {
+ background: rgba(1, 90, 255, 0.2);
+ // transform: scale(0.6);
}
- .loadingCon{
- width: 96px;
- height: 12px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- .dot {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.3);
- display: flex;
- justify-content: space-between;
- animation: loading 1.5s infinite ease-in-out;
- }
-
- .dot:nth-child(1) {
- animation-delay: -0.32s;
- }
-
- .dot:nth-child(2) {
- animation-delay: -0.16s;
- }
-
- @keyframes loading {
- 0%, 100% {
- background-color: rgba(255, 255, 255, 0.3);
- transform: scale(0.6);
- }
- 25% {
- background-color: rgba(255, 255, 255, 0.6);
- transform: scale(0.8);
- }
- 50% {
- background-color: rgba(255, 255, 255, 1);
- transform: scale(1);
- }
- 75% {
- background-color: rgba(255, 255, 255, 0.6);
- transform: scale(0.8);
- }
- }
+ 25% {
+ background: rgba(1, 90, 255, 0.5);
+ // transform: scale(0.8);
+ }
+ 50% {
+ background: rgba(1, 90, 255, 1);
+ // transform: scale(1);
+ }
+ 75% {
+ background: rgba(1, 90, 255, 0.5);
+ // transform: scale(0.8);
}
}
}
diff --git a/pc-fe/src/pages/welcome/index.tsx b/pc-fe/src/pages/welcome/index.tsx
index 998967a..eb09fb0 100644
--- a/pc-fe/src/pages/welcome/index.tsx
+++ b/pc-fe/src/pages/welcome/index.tsx
@@ -1,26 +1,19 @@
import React from 'react';
import styles from './index.less';
-import WelcomeIcon from '../../assets/welcome-icon.jpg'
+import WelcomeIcon from '../../assets/welcome-icon.png'
const Welcome = () => {
return (
-
+

-
-
- 紫光汇智
- UNISSENSE
-
-
-
-
+
);
diff --git a/pc-fe/src/types/configSteps.d.ts b/pc-fe/src/types/configSteps.d.ts
index f702bbf..d60bda8 100644
--- a/pc-fe/src/types/configSteps.d.ts
+++ b/pc-fe/src/types/configSteps.d.ts
@@ -1,5 +1,6 @@
declare namespace CONFIG_STEPS {
- interface StaticFormFieldConfig {
+ // 平台网络配置>静态IP字段
+ interface FormFieldConfig {
name: string;
label: string;
type: 'input' | 'select'; // 可扩展更多类型
@@ -8,4 +9,10 @@ declare namespace CONFIG_STEPS {
options?: { label: string; value: string }[]; // select 专用
required?: boolean;
}
+ interface UpdateProgress {
+ percent: number;
+ transferred: number;
+ total: number;
+ bytesPerSecond: number;
+ }
}