feat(pc): 页面
parent
d9029ec43b
commit
5261c290bd
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 597 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
|
|
@ -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: <NetworkConfig /> },
|
||||
* { key: "systemConfig", label: '系统配置', component: <SystemConfig /> },
|
||||
* ];
|
||||
*
|
||||
* <ConfigStepProvider tabs={tabs}>
|
||||
* <YourComponent />
|
||||
* </ConfigStepProvider>
|
||||
*
|
||||
* @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 (
|
||||
* <div>
|
||||
* <p>当前Tab: {activeTab}</p>
|
||||
* <button onClick={handleNext}>下一步</button>
|
||||
* <button onClick={handleJump}>跳转到系统配置</button>
|
||||
* </div>
|
||||
* );
|
||||
* };
|
||||
*/
|
||||
|
||||
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<ConfigStepContextType | undefined>(undefined);
|
||||
|
||||
interface ConfigStepProviderProps {
|
||||
children: ReactNode;
|
||||
initialTab?: string;
|
||||
tabs: { key: string; label: string; component: React.ReactNode }[];
|
||||
}
|
||||
|
||||
export const ConfigStepProvider: React.FC<ConfigStepProviderProps> = ({
|
||||
children,
|
||||
initialTab = "networkConfig",
|
||||
tabs
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<string>(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 (
|
||||
<ConfigStepContext.Provider
|
||||
value={{
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
goToNextTab,
|
||||
goToPreviousTab,
|
||||
tabs
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ConfigStepContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useConfigStep = () => {
|
||||
const context = useContext(ConfigStepContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useConfigStep must be used within a ConfigStepProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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<string, ProgressCallback[]> = new Map();
|
||||
private progressStream: any = null;
|
||||
// export class BTGrpcClient {
|
||||
// private client: any;
|
||||
// private progressCallbacks: Map<string, ProgressCallback[]> = 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<any> {
|
||||
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<any> {
|
||||
// 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<any> {
|
||||
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<any> {
|
||||
// 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<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// // 添加测试方法
|
||||
// async testConnection(): Promise<boolean> {
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -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<string, MockDownloadTask> = new Map();
|
||||
private progressIntervals: Map<string, NodeJS.Timeout> = new Map();
|
||||
// 存储所有的进度回调函数,发送信息
|
||||
private progressCallbacks: ((progress: ProgressUpdate) => void)[] = [];
|
||||
// export class MockBTService {
|
||||
// private server: grpc.Server;
|
||||
// private activeDownloads: Map<string, MockDownloadTask> = new Map();
|
||||
// private progressIntervals: Map<string, NodeJS.Timeout> = 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<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
const { torrent_url, item_name, item_id } = call.request;
|
||||
const downloadId = item_id || `download-${Date.now()}`;
|
||||
// // 开始下载
|
||||
// // 在 startDownload 方法中,可以添加根据文件名设置不同大小的逻辑
|
||||
// private startDownload(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
// 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<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
const { download_id } = call.request;
|
||||
// // 停止下载
|
||||
// private stopDownload(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
// 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<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
const { download_id } = call.request;
|
||||
// // 获取下载状态
|
||||
// private getDownloadStatus(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
// 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<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
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<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||
// 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<any, any>) {
|
||||
console.log('客户端订阅了进度更新');
|
||||
// // 订阅进度更新(流式响应)
|
||||
// private subscribeProgress(call: grpc.ServerWritableStream<any, any>) {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
return new Promise((resolve) => {
|
||||
// 清理所有定时器
|
||||
this.progressIntervals.forEach(interval => clearInterval(interval));
|
||||
this.progressIntervals.clear();
|
||||
this.activeDownloads.clear();
|
||||
this.progressCallbacks = [];
|
||||
// // 停止服务器
|
||||
// stop(): Promise<void> {
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
// this.server.tryShutdown(() => {
|
||||
// console.log('Mock gRPC 服务器已停止');
|
||||
// resolve();
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
|
@ -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 || '重新连接失败'
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
|
@ -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 || '未知错误')}`
|
||||
};
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**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))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -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<typeof ipcRenderer.on>) {
|
||||
const [channel, listener] = args
|
||||
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
|
||||
|
|
|
|||
|
|
@ -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<string, string>;
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
|
@ -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)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ProgressBarProps> = ({ percent, text = '处理中...' }) => {
|
||||
const clampedPercent = Math.min(100, Math.max(0, percent));
|
||||
return (
|
||||
<div className={styles.progressBarContainer}>
|
||||
<div className={styles.progressLine}>
|
||||
<div className={styles.progressContainer}>
|
||||
<div
|
||||
className={styles.progressBar}
|
||||
style={{ width: `${clampedPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.progressPercent}>{clampedPercent}%</div>
|
||||
</div>
|
||||
<div className={styles.progressText}>{text}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressBar;
|
||||
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className={styles["network-config"]}>
|
||||
<div className={styles["tab-container"]}>
|
||||
<div
|
||||
className={cs(styles["tab-item"], { [styles.active]: activeTab === 'dhcp' })}
|
||||
onClick={() => setActiveTab('dhcp')}
|
||||
>
|
||||
<span>DHCP</span>
|
||||
{activeTab === 'dhcp' && <div className={styles["indicator"]}></div>}
|
||||
<div className={styles['tab-content-container-wrapper']}>
|
||||
<div className={styles["tab-container"]}>
|
||||
<div className={styles["special-tab-bg-con"]}>
|
||||
<div className={cs(styles["tab-bg"],styles["tab-grey-bg"])}></div>
|
||||
<div className={cs(styles["tab-bg"],styles["tab-white-bg"])}></div>
|
||||
</div>
|
||||
<div
|
||||
className={cs(styles["tab-item"], styles["dhcp-tab"], {
|
||||
[styles.active]: activeTab === 'dhcp'
|
||||
})}
|
||||
onClick={() => setActiveTab('dhcp')}
|
||||
>
|
||||
<span>DHCP</span>
|
||||
</div>
|
||||
<div
|
||||
className={cs(styles["tab-item"], styles["static-tab"], {
|
||||
[styles.active]: activeTab === 'static'
|
||||
})}
|
||||
onClick={() => setActiveTab('static')}
|
||||
>
|
||||
<span>静态IP</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cs(styles["tab-item"], { [styles.active]: activeTab === 'static' })}
|
||||
onClick={() => setActiveTab('static')}
|
||||
>
|
||||
<span>静态IP</span>
|
||||
{activeTab === 'static' && <div className={styles["indicator"]}></div>}
|
||||
|
||||
<div className={styles["content-container"]}>
|
||||
{activeTab === 'dhcp' ? <DhcpComponent /> : <StaticIpComponent />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles["content-container"]}>
|
||||
{activeTab === 'dhcp' ? <DhcpComponent /> : <StaticIpComponent />}
|
||||
</div>
|
||||
<ButtonCom confirmText="确认并进入下一步" onConfirm={handleSubmit}/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = () => (
|
||||
<div className={styles["server-container"]}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
className={styles["form-container"]}
|
||||
>
|
||||
{serverIpFormFields.map(field => (
|
||||
<Form.Item
|
||||
key={field.name}
|
||||
name={field.name}
|
||||
label={<div className={styles["label"]}>{field.label}</div>}
|
||||
rules={field.rules}
|
||||
>
|
||||
<Input
|
||||
className={styles["input-field"]}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className={styles["server-config"]}>
|
||||
<div className={styles["content-container"]}>
|
||||
<ServerComponent />
|
||||
</div>
|
||||
|
||||
<ButtonCom confirmText="确认并进入下一步" onConfirm={handleSubmit}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = () => (
|
||||
<div className={styles["image-loading-container"]}>
|
||||
<div className={styles["loading-wrapper"]}>
|
||||
<div className={styles["spinner"] + ' ' + styles["rotating"]}>
|
||||
{[...Array(8)].map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={styles["spinner-item"]}
|
||||
style={{
|
||||
transform: `rotate(${-45 * index - 90}deg) translate(60px)`, // 添加 -90 度偏移
|
||||
width: `${12 - index * 1.5}px`,
|
||||
height: `${12 - index * 1.5}px`
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles["loading-text"]}>正在获取镜像数据</div>
|
||||
<div className={styles["loading-subtext"]}>请耐心等待...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SuccessComponent = () => (
|
||||
<div className={styles["success-container"]}>
|
||||
<div className={styles["success-box"]}>
|
||||
{/* 这里可以显示实际的镜像信息 */}
|
||||
<div>镜像信息展示区域</div>
|
||||
</div>
|
||||
<div className={styles["ready-icon"]}>
|
||||
<img src={TrueIcon} />
|
||||
<div className={styles["ready-text"]}>客户端及镜像已就绪</div>
|
||||
</div>
|
||||
<div className={styles["ready-subtext"]}>点击确认并立即使用吧~</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
history.push('/login');
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
终端获取镜像信息
|
||||
<div className={styles["terminal-config"]}>
|
||||
{imageStatus === 'loading' && <ImageLoadingComponent />}
|
||||
{imageStatus === 'success' && <SuccessComponent />}
|
||||
{imageStatus === 'loading' && <div style={{height:'204px'}}></div>}
|
||||
{imageStatus === 'success' && <ButtonCom confirmText="确认并立即使用" onConfirm={handleSubmit} showCancel={false} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className={styles.content}>
|
||||
<ProgressBar percent={30} text="稍等片刻,正在侦测管理平台" />
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ButtonCom
|
||||
cancelText="取消"
|
||||
onCancel={handleCancel}
|
||||
showConfirm={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'latest':
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.latestContainer}>
|
||||
<div className={styles.iconText}>
|
||||
<span className={styles.icon}>✓</span>
|
||||
<span>已是最新版本</span>
|
||||
</div>
|
||||
<p className={styles.description}>当前客户端版本与管理平台版本一致</p>
|
||||
</div>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ButtonCom
|
||||
confirmText="确认并进入下一步"
|
||||
onConfirm={goToNextTab}
|
||||
showCancel={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'outdated':
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.outdatedContainer}>
|
||||
<div className={styles.warningBox}>
|
||||
<img src={WaringIcon} className={styles.icon}></img>
|
||||
<span>客户端更新</span>
|
||||
</div>
|
||||
<p className={styles.title}>当前客户端版本过低,需要升级同步</p>
|
||||
<p className={styles.description}>客户端版本需与管理平台版本一致</p>
|
||||
</div>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ButtonCom
|
||||
cancelText="取消"
|
||||
onCancel={handleCancel}
|
||||
confirmText="确认并更新客户端"
|
||||
onConfirm={handleConfirmUpdate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'updating':
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<ProgressBar
|
||||
percent={progress}
|
||||
text={progress < 100 ? "更新中..." : "更新成功"}
|
||||
/>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ButtonCom
|
||||
cancelText="取消"
|
||||
onCancel={handleCancel}
|
||||
showConfirm={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'downloaded':
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<ProgressBar percent={100} text="下载完成,准备安装更新" />
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ButtonCom
|
||||
confirmText="立即安装"
|
||||
onConfirm={handleRestart}
|
||||
cancelText="稍后安装"
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'updated':
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<ProgressBar percent={100} text={`更新成功,${countdown}秒后自动重启`} />
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ButtonCom
|
||||
confirmText="确认重启"
|
||||
onConfirm={handleRestart}
|
||||
showCancel={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const Index = () => {
|
||||
return (
|
||||
<div>
|
||||
侦测管理平台
|
||||
<div className={styles.container}>
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Index;
|
||||
export default WatchManagement;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string>("networkConfig");
|
||||
const tabs = [
|
||||
{ key: "networkConfig", label: '平台网络配置', component: <NetworkConfig /> },
|
||||
{ key: "serverConfig", label: '服务器配置', component: <ServerConfig /> },
|
||||
{ key: "watchManagement", label: '侦测平台管理', component: <WatchManagement /> },
|
||||
{ key: "terminalGetImage", label: '终端获取镜像信息', component: <TerminalGetImage /> },
|
||||
];
|
||||
|
||||
const tabs = [
|
||||
{ key: "networkConfig", label: '平台网络配置', component: <NetworkConfig /> },
|
||||
{ key: "watchManagement", label: '侦测管理平台', component: <WatchManagement /> },
|
||||
{ key: "terminalGetImage", label: '终端获取镜像信息', component: <TerminalGetImage /> },
|
||||
];
|
||||
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 (
|
||||
<div className={styles["config-step-container"]}>
|
||||
<div className={styles["tabs-container"]}>
|
||||
{tabs.map((tab) => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cs(styles["tab-item"], {
|
||||
[styles.active]: activeTab === tab.key
|
||||
})}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
>
|
||||
<span className={styles["tab-label"]}>{tab.label}</span>
|
||||
<div className={styles["tab-indicator"]} />
|
||||
</div>
|
||||
<div className={styles["steps-container"]}>
|
||||
{contextTabs.map((tab, index) => (
|
||||
<React.Fragment key={tab.key}>
|
||||
<div
|
||||
className={cs(styles["step-item"], {
|
||||
[styles.active]: activeTab === tab.key
|
||||
})}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
>
|
||||
<div className={cs(styles["step-number"], {
|
||||
[styles["step-number-active"]]: index <= activeTabIndex,
|
||||
[styles["step-number-inactive"]]: index > activeTabIndex
|
||||
})}>
|
||||
{index + 1}
|
||||
</div>
|
||||
<div className={cs(styles["step-label"], {
|
||||
[styles["step-label-active"]]: index <= activeTabIndex,
|
||||
[styles["step-label-inactive"]]: index > activeTabIndex
|
||||
})}>
|
||||
{tab.label}
|
||||
</div>
|
||||
</div>
|
||||
{index < contextTabs.length - 1 && (
|
||||
<div className={cs(styles["step-divider"], {
|
||||
[styles["step-divider-active"]]: index < activeTabIndex,
|
||||
[styles["step-divider-inactive"]]: index >= activeTabIndex
|
||||
})}>
|
||||
>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles["tab-content-container"]}>
|
||||
{activeTabItem?.component || <div>未找到对应内容</div>}
|
||||
<div className={styles["tab-content"]} style={{ opacity: contentOpacity }}>
|
||||
{activeTabItem?.component || <div>未找到对应内容</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.emptyBox}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Index: React.FC = () => {
|
||||
return (
|
||||
<ConfigStepProvider tabs={tabs}>
|
||||
<ConfigStepsContent />
|
||||
</ConfigStepProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className={styles.welcomeCon} >
|
||||
<div className={styles.showTextCon}>
|
||||
<div className={styles.name}>
|
||||
<div className={styles.imgIcon}>
|
||||
<img src={WelcomeIcon} className={styles.welcomeIcon} />
|
||||
<div className={styles.textCon}>
|
||||
<div className={styles.text}>
|
||||
<span>紫光汇智</span>
|
||||
<span className={styles.nameText}>UNISSENSE</span>
|
||||
</div>
|
||||
<div className={styles.loadingCon}>
|
||||
<div className={styles.dot}></div>
|
||||
<div className={styles.dot}></div>
|
||||
<div className={styles.dot}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.loadingCon}>
|
||||
<div className={styles.dot}></div>
|
||||
<div className={styles.dot}></div>
|
||||
<div className={styles.dot}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue