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 ( +
+
+
+
+
+
{clampedPercent}%
+
+
{text}
+
+ ); +}; + +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 = () => ( +
+
+ {serverIpFormFields.map(field => ( + {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; + } }