feat(pc): 页面
parent
d9029ec43b
commit
5261c290bd
|
|
@ -1,26 +1,23 @@
|
||||||
{
|
{
|
||||||
"name": "vdi-manager",
|
"name": "vdi-manager",
|
||||||
"version": "1.0.5",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "vdi-manager",
|
"name": "vdi-manager",
|
||||||
"version": "1.0.5",
|
"version": "1.0.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"@grpc/grpc-js": "^1.13.4",
|
|
||||||
"@grpc/proto-loader": "^0.8.0",
|
|
||||||
"antd": "^5.26.6",
|
"antd": "^5.26.6",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"google-protobuf": "^4.0.0",
|
"electron-updater": "^6.6.2",
|
||||||
"umi": "^4.0.42"
|
"umi": "^4.0.42"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node14": "^1.0.3",
|
"@tsconfig/node14": "^1.0.3",
|
||||||
"@types/google-protobuf": "^3.15.12",
|
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"@umijs/plugin-electron": "^0.2.0",
|
"@umijs/plugin-electron": "^0.2.0",
|
||||||
|
|
@ -3211,55 +3208,6 @@
|
||||||
"use-isomorphic-layout-effect": "^1.1.1"
|
"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": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||||
|
|
@ -3653,16 +3601,6 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@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": {
|
"node_modules/@loadable/component": {
|
||||||
"version": "5.15.2",
|
"version": "5.15.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@loadable/component/-/component-5.15.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@loadable/component/-/component-5.15.2.tgz",
|
||||||
|
|
@ -4216,70 +4154,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/@rc-component/async-validator": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
|
||||||
|
|
@ -4918,13 +4792,6 @@
|
||||||
"@types/node": "*"
|
"@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": {
|
"node_modules/@types/graceful-fs": {
|
||||||
"version": "4.1.9",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
||||||
|
|
@ -10251,9 +10118,7 @@
|
||||||
"version": "6.6.2",
|
"version": "6.6.2",
|
||||||
"resolved": "https://registry.npmmirror.com/electron-updater/-/electron-updater-6.6.2.tgz",
|
"resolved": "https://registry.npmmirror.com/electron-updater/-/electron-updater-6.6.2.tgz",
|
||||||
"integrity": "sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==",
|
"integrity": "sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"builder-util-runtime": "9.3.1",
|
"builder-util-runtime": "9.3.1",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
|
|
@ -10269,9 +10134,7 @@
|
||||||
"version": "9.3.1",
|
"version": "9.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.3.1.tgz",
|
||||||
"integrity": "sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==",
|
"integrity": "sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"sax": "^1.2.4"
|
"sax": "^1.2.4"
|
||||||
|
|
@ -10284,9 +10147,7 @@
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^6.0.1",
|
"jsonfile": "^6.0.1",
|
||||||
|
|
@ -10300,9 +10161,7 @@
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -10314,9 +10173,7 @@
|
||||||
"version": "7.7.2",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
|
||||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
|
|
@ -10328,9 +10185,7 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
|
||||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -12167,12 +12022,6 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"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": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
|
@ -13874,7 +13723,6 @@
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz",
|
"resolved": "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||||
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/less": {
|
"node_modules/less": {
|
||||||
|
|
@ -14242,12 +14090,6 @@
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
|
@ -14259,18 +14101,14 @@
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||||
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
||||||
"dev": true,
|
"license": "MIT"
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isequal": {
|
"node_modules/lodash.isequal": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||||
"dev": true,
|
"license": "MIT"
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
|
|
@ -14286,12 +14124,6 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"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": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
|
@ -16816,30 +16648,6 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"optional": true
|
"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": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
|
@ -18554,7 +18362,6 @@
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
|
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
|
|
@ -20062,9 +19869,7 @@
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
||||||
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
||||||
"dev": true,
|
"license": "MIT"
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/titleize": {
|
"node_modules/titleize": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "vdi-manager",
|
"name": "vdi-manager",
|
||||||
"version": "1.0.5",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "umi dev",
|
"dev": "umi dev",
|
||||||
"build": "umi build",
|
"build": "umi build",
|
||||||
|
|
@ -17,17 +17,14 @@
|
||||||
"homepage": "http://10.209.8.11/users/sign_in",
|
"homepage": "http://10.209.8.11/users/sign_in",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"@grpc/grpc-js": "^1.13.4",
|
|
||||||
"@grpc/proto-loader": "^0.8.0",
|
|
||||||
"antd": "^5.26.6",
|
"antd": "^5.26.6",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"google-protobuf": "^4.0.0",
|
"electron-updater": "^6.6.2",
|
||||||
"umi": "^4.0.42"
|
"umi": "^4.0.42"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node14": "^1.0.3",
|
"@tsconfig/node14": "^1.0.3",
|
||||||
"@types/google-protobuf": "^3.15.12",
|
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"@umijs/plugin-electron": "^0.2.0",
|
"@umijs/plugin-electron": "^0.2.0",
|
||||||
|
|
|
||||||
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";
|
import { BrowserWindowConstructorOptions } from "electron";
|
||||||
export default{
|
export default{
|
||||||
browserWindow:{
|
browserWindow:{
|
||||||
kiosk: true,
|
kiosk: true, // 全屏
|
||||||
frame: false,
|
frame: false, // 无边框
|
||||||
titleBarStyle: 'hidden',
|
titleBarStyle: 'hidden', // 隐藏标题栏
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true, // 隐藏菜单栏
|
||||||
// 禁止调整窗口大小
|
// 禁止调整窗口大小
|
||||||
resizable: false,
|
resizable: false,
|
||||||
// 禁止最大化和最小化按钮
|
// 禁止最大化和最小化按钮
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
minimizable: false,
|
minimizable: false,
|
||||||
closable: false,
|
// closable: false, // 禁止关闭窗口
|
||||||
} as BrowserWindowConstructorOptions,
|
} as BrowserWindowConstructorOptions,
|
||||||
}
|
}
|
||||||
|
|
@ -1,194 +1,194 @@
|
||||||
// src/grpc/BTGrpcClient.ts 调用后端服务
|
// // src/grpc/BTGrpcClient.ts 调用后端服务
|
||||||
import * as grpc from '@grpc/grpc-js';
|
// import * as grpc from '@grpc/grpc-js';
|
||||||
import * as protoLoader from '@grpc/proto-loader';
|
// import * as protoLoader from '@grpc/proto-loader';
|
||||||
import path from 'path';
|
// import path from 'path';
|
||||||
import { app } from 'electron';
|
// import { app } from 'electron';
|
||||||
|
|
||||||
export interface DownloadRequest {
|
// export interface DownloadRequest {
|
||||||
torrent_url: string;
|
// torrent_url: string;
|
||||||
item_name: string;
|
// item_name: string;
|
||||||
item_id?: string;
|
// item_id?: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface ProgressCallback {
|
// export interface ProgressCallback {
|
||||||
(progress: ProgressUpdate): void;
|
// (progress: ProgressUpdate): void;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface ProgressUpdate {
|
// export interface ProgressUpdate {
|
||||||
download_id: string;
|
// download_id: string;
|
||||||
progress: number;
|
// progress: number;
|
||||||
download_speed: number;
|
// download_speed: number;
|
||||||
upload_speed: number;
|
// upload_speed: number;
|
||||||
eta: number;
|
// eta: number;
|
||||||
total_size: number;
|
// total_size: number;
|
||||||
downloaded_size: number;
|
// downloaded_size: number;
|
||||||
state: string;
|
// state: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export class BTGrpcClient {
|
// export class BTGrpcClient {
|
||||||
private client: any;
|
// private client: any;
|
||||||
private progressCallbacks: Map<string, ProgressCallback[]> = new Map();
|
// private progressCallbacks: Map<string, ProgressCallback[]> = new Map();
|
||||||
private progressStream: any = null;
|
// private progressStream: any = null;
|
||||||
|
|
||||||
constructor() {
|
// constructor() {
|
||||||
this.initializeClient();
|
// this.initializeClient();
|
||||||
}
|
// }
|
||||||
|
|
||||||
private initializeClient() {
|
// private initializeClient() {
|
||||||
try {
|
// try {
|
||||||
const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
|
// const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
|
||||||
|
|
||||||
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
// const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
||||||
keepCase: true,
|
// keepCase: true,
|
||||||
longs: String,
|
// longs: String,
|
||||||
enums: String,
|
// enums: String,
|
||||||
defaults: true,
|
// defaults: true,
|
||||||
oneofs: true,
|
// oneofs: true,
|
||||||
});
|
// });
|
||||||
|
|
||||||
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
// const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
||||||
const bittorrent = protoDescriptor.bittorrent as any;
|
// const bittorrent = protoDescriptor.bittorrent as any;
|
||||||
|
|
||||||
// 连接到后端 Agent,假设运行在 localhost:50051
|
// // 连接到后端 Agent,假设运行在 localhost:50051
|
||||||
this.client = new bittorrent.BTDownloadService(
|
// this.client = new bittorrent.BTDownloadService(
|
||||||
'localhost:50051',
|
// 'localhost:50051',
|
||||||
grpc.credentials.createInsecure()
|
// grpc.credentials.createInsecure()
|
||||||
);
|
// );
|
||||||
|
|
||||||
console.log('gRPC客户端初始化成功');
|
// console.log('gRPC客户端初始化成功');
|
||||||
this.setupProgressStream();
|
// this.setupProgressStream();
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('gRPC客户端初始化失败:', error);
|
// console.error('gRPC客户端初始化失败:', error);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 设置进度流监听
|
// // 设置进度流监听
|
||||||
private setupProgressStream() {
|
// private setupProgressStream() {
|
||||||
try {
|
// try {
|
||||||
this.progressStream = this.client.SubscribeProgress({});
|
// this.progressStream = this.client.SubscribeProgress({});
|
||||||
// 注册数据事件监听器,接收进度更新
|
// // 注册数据事件监听器,接收进度更新
|
||||||
this.progressStream.on('data', (progress: ProgressUpdate) => {
|
// this.progressStream.on('data', (progress: ProgressUpdate) => {
|
||||||
console.log('收到进度数据:', progress); // 添加调试日志
|
// console.log('收到进度数据:', progress); // 添加调试日志
|
||||||
// BTGrpcClient 通过IPC将进度发送给主进程
|
// // BTGrpcClient 通过IPC将进度发送给主进程
|
||||||
this.progressCallbacks.forEach((callbacks, downloadId) => {
|
// this.progressCallbacks.forEach((callbacks, downloadId) => {
|
||||||
if (downloadId === 'all' || downloadId === progress.download_id) {
|
// if (downloadId === 'all' || downloadId === progress.download_id) {
|
||||||
callbacks.forEach(callback => callback(progress));
|
// callbacks.forEach(callback => callback(progress));
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
// 如果有针对特定下载ID的回调,也要通知
|
// // 如果有针对特定下载ID的回调,也要通知
|
||||||
const specificCallbacks = this.progressCallbacks.get(progress.download_id);
|
// const specificCallbacks = this.progressCallbacks.get(progress.download_id);
|
||||||
if (specificCallbacks) {
|
// if (specificCallbacks) {
|
||||||
specificCallbacks.forEach(callback => callback(progress));
|
// specificCallbacks.forEach(callback => callback(progress));
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
this.progressStream.on('error', (error: Error) => {
|
// this.progressStream.on('error', (error: Error) => {
|
||||||
console.error('进度流错误:', error);
|
// console.error('进度流错误:', error);
|
||||||
});
|
// });
|
||||||
|
|
||||||
this.progressStream.on('end', () => {
|
// this.progressStream.on('end', () => {
|
||||||
console.log('进度流结束');
|
// console.log('进度流结束');
|
||||||
});
|
// });
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('设置进度流失败:', error);
|
// console.error('设置进度流失败:', error);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 开始下载
|
// // 开始下载
|
||||||
startDownload(request: DownloadRequest): Promise<any> {
|
// startDownload(request: DownloadRequest): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
// return new Promise((resolve, reject) => {
|
||||||
// MockBTService 接收请求,创建下载任务
|
// // MockBTService 接收请求,创建下载任务
|
||||||
this.client.StartDownload(request, (error: grpc.ServiceError, response: any) => {
|
// this.client.StartDownload(request, (error: grpc.ServiceError, response: any) => {
|
||||||
if (error) {
|
// if (error) {
|
||||||
reject(error);
|
// reject(error);
|
||||||
} else {
|
// } else {
|
||||||
resolve(response);
|
// resolve(response);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 停止下载
|
// // 停止下载
|
||||||
stopDownload(downloadId: string): Promise<any> {
|
// stopDownload(downloadId: string): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
// return new Promise((resolve, reject) => {
|
||||||
this.client.StopDownload({ download_id: downloadId }, (error: grpc.ServiceError, response: any) => {
|
// this.client.StopDownload({ download_id: downloadId }, (error: grpc.ServiceError, response: any) => {
|
||||||
if (error) {
|
// if (error) {
|
||||||
reject(error);
|
// reject(error);
|
||||||
} else {
|
// } else {
|
||||||
resolve(response);
|
// resolve(response);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 注册进度回调
|
// // 注册进度回调
|
||||||
registerProgressCallback(downloadId: string, callback: ProgressCallback) {
|
// registerProgressCallback(downloadId: string, callback: ProgressCallback) {
|
||||||
if (!this.progressCallbacks.has(downloadId)) {
|
// if (!this.progressCallbacks.has(downloadId)) {
|
||||||
this.progressCallbacks.set(downloadId, []);
|
// this.progressCallbacks.set(downloadId, []);
|
||||||
}
|
// }
|
||||||
this.progressCallbacks.get(downloadId)!.push(callback);
|
// this.progressCallbacks.get(downloadId)!.push(callback);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 移除进度回调
|
// // 移除进度回调
|
||||||
removeProgressCallback(downloadId: string, callback: ProgressCallback) {
|
// removeProgressCallback(downloadId: string, callback: ProgressCallback) {
|
||||||
const callbacks = this.progressCallbacks.get(downloadId);
|
// const callbacks = this.progressCallbacks.get(downloadId);
|
||||||
if (callbacks) {
|
// if (callbacks) {
|
||||||
const index = callbacks.indexOf(callback);
|
// const index = callbacks.indexOf(callback);
|
||||||
if (index > -1) {
|
// if (index > -1) {
|
||||||
callbacks.splice(index, 1);
|
// callbacks.splice(index, 1);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 检查连接状态
|
// // 检查连接状态
|
||||||
isConnected(): boolean {
|
// isConnected(): boolean {
|
||||||
return this.client && this.client.getChannel().getConnectivityState(true) === grpc.connectivityState.READY;
|
// return this.client && this.client.getChannel().getConnectivityState(true) === grpc.connectivityState.READY;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 重新连接
|
// // 重新连接
|
||||||
reconnect() {
|
// reconnect() {
|
||||||
try {
|
// try {
|
||||||
this.client.close();
|
// this.client.close();
|
||||||
this.initializeClient();
|
// this.initializeClient();
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('重新连接失败:', error);
|
// console.error('重新连接失败:', error);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
getConnectionState(): string {
|
// getConnectionState(): string {
|
||||||
if (!this.client) return 'NOT_CREATED';
|
// if (!this.client) return 'NOT_CREATED';
|
||||||
|
|
||||||
const state = this.client.getChannel().getConnectivityState(false);
|
// const state = this.client.getChannel().getConnectivityState(false);
|
||||||
const stateNames = {
|
// const stateNames = {
|
||||||
[grpc.connectivityState.IDLE]: 'IDLE',
|
// [grpc.connectivityState.IDLE]: 'IDLE',
|
||||||
[grpc.connectivityState.CONNECTING]: 'CONNECTING',
|
// [grpc.connectivityState.CONNECTING]: 'CONNECTING',
|
||||||
[grpc.connectivityState.READY]: 'READY',
|
// [grpc.connectivityState.READY]: 'READY',
|
||||||
[grpc.connectivityState.TRANSIENT_FAILURE]: 'TRANSIENT_FAILURE',
|
// [grpc.connectivityState.TRANSIENT_FAILURE]: 'TRANSIENT_FAILURE',
|
||||||
[grpc.connectivityState.SHUTDOWN]: 'SHUTDOWN'
|
// [grpc.connectivityState.SHUTDOWN]: 'SHUTDOWN'
|
||||||
} as const;
|
// } as const;
|
||||||
|
|
||||||
// 使用类型断言确保 state 是合法的 key
|
// // 使用类型断言确保 state 是合法的 key
|
||||||
return stateNames[state as keyof typeof stateNames] || 'UNKNOWN';
|
// return stateNames[state as keyof typeof stateNames] || 'UNKNOWN';
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 添加测试方法
|
// // 添加测试方法
|
||||||
async testConnection(): Promise<boolean> {
|
// async testConnection(): Promise<boolean> {
|
||||||
try {
|
// try {
|
||||||
// 尝试调用一个简单的方法来测试连接
|
// // 尝试调用一个简单的方法来测试连接
|
||||||
await new Promise((resolve, reject) => {
|
// await new Promise((resolve, reject) => {
|
||||||
this.client.ListDownloads({}, (error: any, response: any) => {
|
// this.client.ListDownloads({}, (error: any, response: any) => {
|
||||||
if (error && error.code !== grpc.status.UNIMPLEMENTED) {
|
// if (error && error.code !== grpc.status.UNIMPLEMENTED) {
|
||||||
reject(error);
|
// reject(error);
|
||||||
} else {
|
// } else {
|
||||||
resolve(response);
|
// resolve(response);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
return true;
|
// return true;
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('连接测试失败:', error);
|
// console.error('连接测试失败:', error);
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
@ -1,323 +1,323 @@
|
||||||
// 本地测试用的 gRPC 服务器,用于模拟 BT 下载
|
// // 本地测试用的 gRPC 服务器,用于模拟 BT 下载
|
||||||
// src/grpc/MockBTService.ts
|
// // src/grpc/MockBTService.ts
|
||||||
import * as grpc from '@grpc/grpc-js';
|
// import * as grpc from '@grpc/grpc-js';
|
||||||
import * as protoLoader from '@grpc/proto-loader';
|
// import * as protoLoader from '@grpc/proto-loader';
|
||||||
import path from 'path';
|
// import path from 'path';
|
||||||
|
|
||||||
// 进度更新接口
|
// // 进度更新接口
|
||||||
interface ProgressUpdate {
|
// interface ProgressUpdate {
|
||||||
download_id: string;
|
// download_id: string;
|
||||||
progress: number;
|
// progress: number;
|
||||||
download_speed: number;
|
// download_speed: number;
|
||||||
item_name?: string; // 可选的文件名字段
|
// item_name?: string; // 可选的文件名字段
|
||||||
upload_speed: number;
|
// upload_speed: number;
|
||||||
eta: number;
|
// eta: number;
|
||||||
total_size: number;
|
// total_size: number;
|
||||||
downloaded_size: number;
|
// downloaded_size: number;
|
||||||
state: string;
|
// state: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 模拟的下载任务
|
// // 模拟的下载任务
|
||||||
interface MockDownloadTask {
|
// interface MockDownloadTask {
|
||||||
id: string;
|
// id: string;
|
||||||
itemName: string;
|
// itemName: string;
|
||||||
torrentUrl: string;
|
// torrentUrl: string;
|
||||||
progress: number;
|
// progress: number;
|
||||||
totalSize: number;
|
// totalSize: number;
|
||||||
downloadedSize: number;
|
// downloadedSize: number;
|
||||||
downloadSpeed: number;
|
// downloadSpeed: number;
|
||||||
state: 'downloading' | 'completed' | 'error' | 'paused';
|
// state: 'downloading' | 'completed' | 'error' | 'paused';
|
||||||
startTime: number;
|
// startTime: number;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export class MockBTService {
|
// export class MockBTService {
|
||||||
private server: grpc.Server;
|
// private server: grpc.Server;
|
||||||
private activeDownloads: Map<string, MockDownloadTask> = new Map();
|
// private activeDownloads: Map<string, MockDownloadTask> = new Map();
|
||||||
private progressIntervals: Map<string, NodeJS.Timeout> = new Map();
|
// private progressIntervals: Map<string, NodeJS.Timeout> = new Map();
|
||||||
// 存储所有的进度回调函数,发送信息
|
// // 存储所有的进度回调函数,发送信息
|
||||||
private progressCallbacks: ((progress: ProgressUpdate) => void)[] = [];
|
// private progressCallbacks: ((progress: ProgressUpdate) => void)[] = [];
|
||||||
|
|
||||||
constructor() {
|
// constructor() {
|
||||||
this.server = new grpc.Server();
|
// this.server = new grpc.Server();
|
||||||
this.setupService();
|
// this.setupService();
|
||||||
}
|
// }
|
||||||
|
|
||||||
private setupService() {
|
// private setupService() {
|
||||||
// 设置gRPC服务,加载 bittorrent.proto 定义,实现所有gRPC服务方法
|
// // 设置gRPC服务,加载 bittorrent.proto 定义,实现所有gRPC服务方法
|
||||||
const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
|
// const PROTO_PATH = path.join(__dirname, 'protos', 'bittorrent.proto');
|
||||||
|
|
||||||
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
// const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
||||||
keepCase: true,
|
// keepCase: true,
|
||||||
longs: String,
|
// longs: String,
|
||||||
enums: String,
|
// enums: String,
|
||||||
defaults: true,
|
// defaults: true,
|
||||||
oneofs: true,
|
// oneofs: true,
|
||||||
});
|
// });
|
||||||
|
|
||||||
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
// const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
||||||
const bittorrent = protoDescriptor.bittorrent as any;
|
// const bittorrent = protoDescriptor.bittorrent as any;
|
||||||
|
|
||||||
// 实现 gRPC 服务方法
|
// // 实现 gRPC 服务方法
|
||||||
this.server.addService(bittorrent.BTDownloadService.service, {
|
// this.server.addService(bittorrent.BTDownloadService.service, {
|
||||||
StartDownload: this.startDownload.bind(this),
|
// StartDownload: this.startDownload.bind(this),
|
||||||
StopDownload: this.stopDownload.bind(this),
|
// StopDownload: this.stopDownload.bind(this),
|
||||||
GetDownloadStatus: this.getDownloadStatus.bind(this),
|
// GetDownloadStatus: this.getDownloadStatus.bind(this),
|
||||||
ListDownloads: this.listDownloads.bind(this),
|
// ListDownloads: this.listDownloads.bind(this),
|
||||||
SubscribeProgress: this.subscribeProgress.bind(this),
|
// SubscribeProgress: this.subscribeProgress.bind(this),
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 开始下载
|
// // 开始下载
|
||||||
// 在 startDownload 方法中,可以添加根据文件名设置不同大小的逻辑
|
// // 在 startDownload 方法中,可以添加根据文件名设置不同大小的逻辑
|
||||||
private startDownload(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
// private startDownload(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||||
const { torrent_url, item_name, item_id } = call.request;
|
// const { torrent_url, item_name, item_id } = call.request;
|
||||||
const downloadId = item_id || `download-${Date.now()}`;
|
// const downloadId = item_id || `download-${Date.now()}`;
|
||||||
|
|
||||||
console.log(`模拟开始下载: ${item_name}, ID: ${downloadId}`);
|
// console.log(`模拟开始下载: ${item_name}, ID: ${downloadId}`);
|
||||||
|
|
||||||
// 根据文件名或ID设置不同大小的文件
|
// // 根据文件名或ID设置不同大小的文件
|
||||||
let fileSize = 1024 * 1024 * 100; // 默认100MB
|
// let fileSize = 1024 * 1024 * 100; // 默认100MB
|
||||||
|
|
||||||
if (item_name.includes('large') || item_name.includes('大文件')) {
|
// if (item_name.includes('large') || item_name.includes('大文件')) {
|
||||||
fileSize = 4 * 1024 * 1024 * 1024; // 4GB
|
// fileSize = 4 * 1024 * 1024 * 1024; // 4GB
|
||||||
} else if (item_name.includes('medium') || item_name.includes('中等')) {
|
// } else if (item_name.includes('medium') || item_name.includes('中等')) {
|
||||||
fileSize = 1024 * 1024 * 1024; // 1GB
|
// fileSize = 1024 * 1024 * 1024; // 1GB
|
||||||
} else if (item_name.includes('small') || item_name.includes('小文件')) {
|
// } else if (item_name.includes('small') || item_name.includes('小文件')) {
|
||||||
fileSize = 100 * 1024 * 1024; // 100MB
|
// fileSize = 100 * 1024 * 1024; // 100MB
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 创建模拟下载任务
|
// // 创建模拟下载任务
|
||||||
const task: MockDownloadTask = {
|
// const task: MockDownloadTask = {
|
||||||
id: downloadId,
|
// id: downloadId,
|
||||||
itemName: item_name,
|
// itemName: item_name,
|
||||||
torrentUrl: torrent_url,
|
// torrentUrl: torrent_url,
|
||||||
progress: 0,
|
// progress: 0,
|
||||||
totalSize: fileSize,
|
// totalSize: fileSize,
|
||||||
downloadedSize: 0,
|
// downloadedSize: 0,
|
||||||
downloadSpeed: 1024 * 1024 * 2, // 2MB/s
|
// downloadSpeed: 1024 * 1024 * 2, // 2MB/s
|
||||||
state: 'downloading',
|
// state: 'downloading',
|
||||||
startTime: Date.now(),
|
// startTime: Date.now(),
|
||||||
};
|
// };
|
||||||
|
|
||||||
this.activeDownloads.set(downloadId, task);
|
// this.activeDownloads.set(downloadId, task);
|
||||||
console.log(`已创建下载任务: ${downloadId}, 大小: ${fileSize} bytes`);
|
// console.log(`已创建下载任务: ${downloadId}, 大小: ${fileSize} bytes`);
|
||||||
|
|
||||||
// 启动进度模拟
|
// // 启动进度模拟
|
||||||
this.startProgressSimulation(downloadId);
|
// this.startProgressSimulation(downloadId);
|
||||||
|
|
||||||
callback(null, {
|
// callback(null, {
|
||||||
success: true,
|
// success: true,
|
||||||
message: '下载已开始',
|
// message: '下载已开始',
|
||||||
download_id: downloadId,
|
// download_id: downloadId,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 停止下载
|
// // 停止下载
|
||||||
private stopDownload(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
// private stopDownload(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||||
const { download_id } = call.request;
|
// const { download_id } = call.request;
|
||||||
|
|
||||||
if (this.activeDownloads.has(download_id)) {
|
// if (this.activeDownloads.has(download_id)) {
|
||||||
const task = this.activeDownloads.get(download_id)!;
|
// const task = this.activeDownloads.get(download_id)!;
|
||||||
task.state = 'paused';
|
// task.state = 'paused';
|
||||||
|
|
||||||
// 清除进度定时器
|
// // 清除进度定时器
|
||||||
if (this.progressIntervals.has(download_id)) {
|
// if (this.progressIntervals.has(download_id)) {
|
||||||
clearInterval(this.progressIntervals.get(download_id));
|
// clearInterval(this.progressIntervals.get(download_id));
|
||||||
this.progressIntervals.delete(download_id);
|
// this.progressIntervals.delete(download_id);
|
||||||
}
|
// }
|
||||||
|
|
||||||
console.log(`模拟停止下载: ${download_id}`);
|
// console.log(`模拟停止下载: ${download_id}`);
|
||||||
callback(null, { success: true, message: '下载已停止' });
|
// callback(null, { success: true, message: '下载已停止' });
|
||||||
} else {
|
// } else {
|
||||||
callback({
|
// callback({
|
||||||
code: grpc.status.NOT_FOUND,
|
// code: grpc.status.NOT_FOUND,
|
||||||
message: `下载任务不存在: ${download_id}`
|
// message: `下载任务不存在: ${download_id}`
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取下载状态
|
// // 获取下载状态
|
||||||
private getDownloadStatus(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
// private getDownloadStatus(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||||
const { download_id } = call.request;
|
// const { download_id } = call.request;
|
||||||
|
|
||||||
if (this.activeDownloads.has(download_id)) {
|
// if (this.activeDownloads.has(download_id)) {
|
||||||
const task = this.activeDownloads.get(download_id)!;
|
// const task = this.activeDownloads.get(download_id)!;
|
||||||
callback(null, {
|
// callback(null, {
|
||||||
download_id: task.id,
|
// download_id: task.id,
|
||||||
progress: task.progress,
|
// progress: task.progress,
|
||||||
download_speed: task.downloadSpeed,
|
// download_speed: task.downloadSpeed,
|
||||||
state: task.state,
|
// state: task.state,
|
||||||
total_size: task.totalSize,
|
// total_size: task.totalSize,
|
||||||
downloaded_size: task.downloadedSize,
|
// downloaded_size: task.downloadedSize,
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
callback({
|
// callback({
|
||||||
code: grpc.status.NOT_FOUND,
|
// code: grpc.status.NOT_FOUND,
|
||||||
message: `下载任务不存在: ${download_id}`
|
// message: `下载任务不存在: ${download_id}`
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 列出所有下载
|
// // 列出所有下载
|
||||||
private listDownloads(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
// private listDownloads(call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) {
|
||||||
const downloads = Array.from(this.activeDownloads.values()).map(task => ({
|
// const downloads = Array.from(this.activeDownloads.values()).map(task => ({
|
||||||
download_id: task.id,
|
// download_id: task.id,
|
||||||
item_name: task.itemName,
|
// item_name: task.itemName,
|
||||||
progress: task.progress,
|
// progress: task.progress,
|
||||||
download_speed: task.downloadSpeed,
|
// download_speed: task.downloadSpeed,
|
||||||
state: task.state,
|
// state: task.state,
|
||||||
total_size: task.totalSize,
|
// total_size: task.totalSize,
|
||||||
downloaded_size: task.downloadedSize,
|
// downloaded_size: task.downloadedSize,
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
callback(null, { downloads });
|
// callback(null, { downloads });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 订阅进度更新(流式响应)
|
// // 订阅进度更新(流式响应)
|
||||||
private subscribeProgress(call: grpc.ServerWritableStream<any, any>) {
|
// private subscribeProgress(call: grpc.ServerWritableStream<any, any>) {
|
||||||
console.log('客户端订阅了进度更新');
|
// console.log('客户端订阅了进度更新');
|
||||||
|
|
||||||
// 存储回调以便发送进度更新
|
// // 存储回调以便发送进度更新
|
||||||
const callback = (progress: ProgressUpdate) => {
|
// const callback = (progress: ProgressUpdate) => {
|
||||||
try {
|
// try {
|
||||||
call.write(progress);
|
// call.write(progress);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('发送进度更新失败:', error);
|
// console.error('发送进度更新失败:', error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
this.progressCallbacks.push(callback);
|
// this.progressCallbacks.push(callback);
|
||||||
|
|
||||||
// 当客户端断开连接时清理
|
// // 当客户端断开连接时清理
|
||||||
call.on('cancelled', () => {
|
// call.on('cancelled', () => {
|
||||||
console.log('客户端取消了进度订阅');
|
// console.log('客户端取消了进度订阅');
|
||||||
const index = this.progressCallbacks.indexOf(callback);
|
// const index = this.progressCallbacks.indexOf(callback);
|
||||||
if (index > -1) {
|
// if (index > -1) {
|
||||||
this.progressCallbacks.splice(index, 1);
|
// this.progressCallbacks.splice(index, 1);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
call.on('error', (error) => {
|
// call.on('error', (error) => {
|
||||||
console.error('进度流错误:', error);
|
// console.error('进度流错误:', error);
|
||||||
const index = this.progressCallbacks.indexOf(callback);
|
// const index = this.progressCallbacks.indexOf(callback);
|
||||||
if (index > -1) {
|
// if (index > -1) {
|
||||||
this.progressCallbacks.splice(index, 1);
|
// this.progressCallbacks.splice(index, 1);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
// 处理客户端断开连接
|
// // 处理客户端断开连接
|
||||||
call.on('end', () => {
|
// call.on('end', () => {
|
||||||
console.log('客户端断开连接');
|
// console.log('客户端断开连接');
|
||||||
const index = this.progressCallbacks.indexOf(callback);
|
// const index = this.progressCallbacks.indexOf(callback);
|
||||||
if (index > -1) {
|
// if (index > -1) {
|
||||||
this.progressCallbacks.splice(index, 1);
|
// this.progressCallbacks.splice(index, 1);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 启动进度模拟
|
// // 启动进度模拟
|
||||||
// 在 MockBTService.ts 中修改 startProgressSimulation 方法
|
// // 在 MockBTService.ts 中修改 startProgressSimulation 方法
|
||||||
private startProgressSimulation(downloadId: string) {
|
// private startProgressSimulation(downloadId: string) {
|
||||||
const interval = setInterval(() => {
|
// const interval = setInterval(() => {
|
||||||
if (this.activeDownloads.has(downloadId)) {
|
// if (this.activeDownloads.has(downloadId)) {
|
||||||
const task = this.activeDownloads.get(downloadId)!;
|
// const task = this.activeDownloads.get(downloadId)!;
|
||||||
|
|
||||||
if (task.state === 'downloading' && task.progress < 100) {
|
// if (task.state === 'downloading' && task.progress < 100) {
|
||||||
// // 更新进度,但确保不超过100%
|
// // // 更新进度,但确保不超过100%
|
||||||
// task.progress += Math.random() * 2;
|
// // task.progress += Math.random() * 2;
|
||||||
// 使用固定的进度增加而不是随机值,使下载更加稳定
|
// // 使用固定的进度增加而不是随机值,使下载更加稳定
|
||||||
task.progress += 1.5; // 每秒增加1.5%的进度
|
// task.progress += 1.5; // 每秒增加1.5%的进度
|
||||||
|
|
||||||
// 确保进度不超过100%
|
// // 确保进度不超过100%
|
||||||
if (task.progress >= 100) {
|
// if (task.progress >= 100) {
|
||||||
task.progress = 100;
|
// task.progress = 100;
|
||||||
task.state = 'completed';
|
// task.state = 'completed';
|
||||||
task.downloadedSize = task.totalSize; // 确保已完成时已下载大小等于总大小
|
// task.downloadedSize = task.totalSize; // 确保已完成时已下载大小等于总大小
|
||||||
console.log(`下载完成: ${downloadId}`);
|
// console.log(`下载完成: ${downloadId}`);
|
||||||
|
|
||||||
// 清除定时器
|
// // 清除定时器
|
||||||
clearInterval(interval);
|
// clearInterval(interval);
|
||||||
this.progressIntervals.delete(downloadId);
|
// this.progressIntervals.delete(downloadId);
|
||||||
} else {
|
// } else {
|
||||||
// 根据进度计算已下载大小
|
// // 根据进度计算已下载大小
|
||||||
task.downloadedSize = (task.totalSize * task.progress) / 100;
|
// task.downloadedSize = (task.totalSize * task.progress) / 100;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 计算剩余时间(秒)
|
// // 计算剩余时间(秒)
|
||||||
let eta = 0;
|
// let eta = 0;
|
||||||
if (task.progress < 100) {
|
// if (task.progress < 100) {
|
||||||
// 基于当前速度计算剩余时间
|
// // 基于当前速度计算剩余时间
|
||||||
const progressPerSecond = 1.5; // 每秒进度百分比
|
// const progressPerSecond = 1.5; // 每秒进度百分比
|
||||||
eta = Math.round(((100 - task.progress) / progressPerSecond));
|
// eta = Math.round(((100 - task.progress) / progressPerSecond));
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 发送进度更新
|
// // 发送进度更新
|
||||||
this.sendProgressUpdate({
|
// this.sendProgressUpdate({
|
||||||
download_id: task.id,
|
// download_id: task.id,
|
||||||
item_name: task.itemName,
|
// item_name: task.itemName,
|
||||||
progress: task.progress,
|
// progress: task.progress,
|
||||||
download_speed: task.downloadSpeed,
|
// download_speed: task.downloadSpeed,
|
||||||
upload_speed: 1024 * 512, // 512KB/s 上传速度
|
// upload_speed: 1024 * 512, // 512KB/s 上传速度
|
||||||
eta: task.progress >= 100 ? 0 : Math.round(((100 - task.progress) / 2) * 1000),
|
// eta: task.progress >= 100 ? 0 : Math.round(((100 - task.progress) / 2) * 1000),
|
||||||
total_size: task.totalSize,
|
// total_size: task.totalSize,
|
||||||
downloaded_size: task.downloadedSize,
|
// downloaded_size: task.downloadedSize,
|
||||||
state: task.state,
|
// state: task.state,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
clearInterval(interval);
|
// clearInterval(interval);
|
||||||
this.progressIntervals.delete(downloadId);
|
// this.progressIntervals.delete(downloadId);
|
||||||
}
|
// }
|
||||||
}, 1000); // 每秒更新一次进度
|
// }, 1000); // 每秒更新一次进度
|
||||||
|
|
||||||
this.progressIntervals.set(downloadId, interval);
|
// this.progressIntervals.set(downloadId, interval);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 发送进度更新给所有订阅者
|
// // 发送进度更新给所有订阅者
|
||||||
private sendProgressUpdate(progress: ProgressUpdate) {
|
// private sendProgressUpdate(progress: ProgressUpdate) {
|
||||||
console.log('发送进度更新:', progress);
|
// console.log('发送进度更新:', progress);
|
||||||
// 通过已注册的回调函数传递给 BTGrpcClient
|
// // 通过已注册的回调函数传递给 BTGrpcClient
|
||||||
this.progressCallbacks.forEach((callback, index) => {
|
// this.progressCallbacks.forEach((callback, index) => {
|
||||||
try {
|
// try {
|
||||||
callback(progress);
|
// callback(progress);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error(`发送进度更新给回调 ${index} 失败:`, error);
|
// console.error(`发送进度更新给回调 ${index} 失败:`, error);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 绑定端口并启动服务器
|
// // 绑定端口并启动服务器
|
||||||
start(port: number = 50051): Promise<void> {
|
// start(port: number = 50051): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
// return new Promise((resolve, reject) => {
|
||||||
this.server.bindAsync(
|
// this.server.bindAsync(
|
||||||
`0.0.0.0:${port}`,
|
// `0.0.0.0:${port}`,
|
||||||
grpc.ServerCredentials.createInsecure(),
|
// grpc.ServerCredentials.createInsecure(),
|
||||||
(error, port) => {
|
// (error, port) => {
|
||||||
if (error) {
|
// if (error) {
|
||||||
reject(error);
|
// reject(error);
|
||||||
} else {
|
// } else {
|
||||||
this.server.start();
|
// this.server.start();
|
||||||
console.log(`Mock gRPC 服务器运行在端口 ${port}`);
|
// console.log(`Mock gRPC 服务器运行在端口 ${port}`);
|
||||||
resolve();
|
// resolve();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 停止服务器
|
// // 停止服务器
|
||||||
stop(): Promise<void> {
|
// stop(): Promise<void> {
|
||||||
return new Promise((resolve) => {
|
// return new Promise((resolve) => {
|
||||||
// 清理所有定时器
|
// // 清理所有定时器
|
||||||
this.progressIntervals.forEach(interval => clearInterval(interval));
|
// this.progressIntervals.forEach(interval => clearInterval(interval));
|
||||||
this.progressIntervals.clear();
|
// this.progressIntervals.clear();
|
||||||
this.activeDownloads.clear();
|
// this.activeDownloads.clear();
|
||||||
this.progressCallbacks = [];
|
// this.progressCallbacks = [];
|
||||||
|
|
||||||
this.server.tryShutdown(() => {
|
// this.server.tryShutdown(() => {
|
||||||
console.log('Mock gRPC 服务器已停止');
|
// console.log('Mock gRPC 服务器已停止');
|
||||||
resolve();
|
// 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 { ipcMain,app } from 'electron';
|
||||||
import { getDeviceId, getWiredConnectionName,netmaskToCidr } from '../utils/utils';
|
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 { exec } = require('child_process');
|
||||||
const { promisify } = require('util');
|
const { promisify } = require('util');
|
||||||
const execAsync = promisify(exec);
|
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();
|
const window = getBrowserWindowRuntime();
|
||||||
|
|
||||||
// 初始化 gRPC 客户端
|
let currentServerIp: string | undefined;
|
||||||
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 || '重新连接失败'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// 监听渲染进程发送的消息
|
// 监听渲染进程发送的消息
|
||||||
ipcMain.handle('getPlatform', () => {
|
ipcMain.handle('getPlatform', () => {
|
||||||
|
|
@ -197,23 +23,31 @@ ipcMain.on('close-app', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('minimize-app', () => {
|
ipcMain.on('minimize-app', () => {
|
||||||
window?.minimize();
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.minimize();
|
||||||
|
}
|
||||||
|
// window?.minimize();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('exit-kiosk', () => {
|
ipcMain.on('exit-kiosk', () => {
|
||||||
if (window) {
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||||
window.setFullScreen(false);
|
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();
|
const deviceId = await getDeviceId();
|
||||||
console.log(`Using device ID: ${deviceId}`);
|
console.log(`Using device ID: ${deviceId}`);
|
||||||
// TODO:传给后端
|
// TODO:传给后端
|
||||||
})
|
})
|
||||||
|
|
||||||
/* IPC 处理应用有线网络配置 */
|
/* 1. 平台网络配置:IPC 处理应用有线网络配置 */
|
||||||
ipcMain.handle('apply-wired-config',async(event,config)=>{
|
ipcMain.handle('apply-wired-config',async(event,config)=>{
|
||||||
// return {
|
// return {
|
||||||
// success: true,
|
// success: true,
|
||||||
|
|
@ -234,7 +68,13 @@ ipcMain.handle('apply-wired-config',async(event,config)=>{
|
||||||
// 添加 IPv6 配置(如果存在 ipv6Gateway)????ipv6和长度需要吗?ui只写了ipv6网关
|
// 添加 IPv6 配置(如果存在 ipv6Gateway)????ipv6和长度需要吗?ui只写了ipv6网关
|
||||||
// ipv6PrefixLength 是 IPv6 地址的前缀长度,类似于 IPv4 中的子网掩码。????
|
// ipv6PrefixLength 是 IPv6 地址的前缀长度,类似于 IPv4 中的子网掩码。????
|
||||||
if (config.ipv6 && config.ipv6Gateway) {
|
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地址和网关');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行配置命令
|
// 执行配置命令
|
||||||
|
|
@ -267,3 +107,161 @@ ipcMain.handle('apply-wired-config',async(event,config)=>{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**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,19 +8,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
closeApp: () => ipcRenderer.send('close-app'),
|
closeApp: () => ipcRenderer.send('close-app'),
|
||||||
minimizeApp: () => ipcRenderer.send('minimize-app'),
|
minimizeApp: () => ipcRenderer.send('minimize-app'),
|
||||||
exitKiosk: () => ipcRenderer.send('exit-kiosk'),
|
exitKiosk: () => ipcRenderer.send('exit-kiosk'),
|
||||||
|
// 版本更新相关API
|
||||||
// 新增的 gRPC API
|
downloadAndUpdate: (url: string) => ipcRenderer.invoke('download-and-update', url),
|
||||||
grpcStartDownload: (config: any) => ipcRenderer.invoke('grpc-start-download', config),
|
// 服务器IP获取
|
||||||
grpcStopDownload: (downloadId: string) => ipcRenderer.invoke('grpc-stop-download', downloadId),
|
getCurrentServerIp: () => ipcRenderer.invoke('get-current-server-ip'),
|
||||||
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');
|
|
||||||
},
|
|
||||||
|
|
||||||
// 事件监听
|
// 事件监听
|
||||||
onMainProcessMessage: (callback: (data: string) => void) => {
|
onMainProcessMessage: (callback: (data: string) => void) => {
|
||||||
|
|
|
||||||
|
|
@ -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 { exec } = require('child_process');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const { promisify } = require('util');
|
const { promisify } = require('util');
|
||||||
|
|
@ -100,3 +102,99 @@ export function netmaskToCidr(netmask:string) {
|
||||||
};
|
};
|
||||||
return netmaskMap[netmask] || '24';
|
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 {
|
.button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 15px 0;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
gap: 40px;
|
gap: 40px;
|
||||||
|
margin-top: 60px;
|
||||||
|
margin-bottom: 80px;
|
||||||
|
|
||||||
.cancel-button {
|
.cancel-button {
|
||||||
width: 140px;
|
width: 140px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
background: transparent;
|
background: rgba(1, 90, 255, 0.1);
|
||||||
border: 1px solid rgba(134, 133, 158, 1);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-family: PingFang SC;
|
font-family: PingFang SC;
|
||||||
|
|
@ -23,23 +24,23 @@
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0%;
|
||||||
color: rgba(229, 229, 229, 1);
|
color: rgba(1, 90, 255, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(1, 90, 255, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-button {
|
.confirm-button {
|
||||||
height: 64px;
|
height: 64px;
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(1, 90, 255, 1);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 30px;
|
padding: 0 67px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-family: PingFang SC;
|
font-family: PingFang SC;
|
||||||
|
|
@ -60,7 +61,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.4);
|
background: rgba(1, 90, 255, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
.main-layout {
|
.main-layout {
|
||||||
background-color: rgba(0, 9, 51, 0.9);
|
background-image: url("../../../assets/bg.png");
|
||||||
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
// background-size: 100% 100%;
|
background-color: transparent;
|
||||||
background-color: rgba(0, 9, 51, 0.9);
|
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,12 @@ const MainLayout: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: 第一次来:判断是否配置ip/DHCP、服务ip绑定终端 绑定:直接到版本更新页面 未绑定:到配置ip/DHCP页面
|
// TODO: 第一次来:判断是否配置ip/DHCP、服务ip绑定终端 绑定:直接到版本更新页面 未绑定:到配置ip/DHCP页面
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
history.push('/grpc');
|
// history.push('/login');
|
||||||
|
history.push('/configSteps?tab=terminalGetImage');
|
||||||
},1000)
|
},1000)
|
||||||
// const fetchDeviceId = async () => {
|
// const fetchDeviceId = async () => {
|
||||||
// try {
|
// try {
|
||||||
// const res = await window.electronAPI.invoke('get-deviceid');
|
// const res = await window.electronAPI.invoke('get-device-id');
|
||||||
// console.log('获取设备ID:', res);
|
// console.log('获取设备ID:', res);
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('获取设备ID失败:', 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 {
|
.network-config {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.tab-container {
|
.tab-content-container-wrapper {
|
||||||
height: 70px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: linear-gradient(180deg, rgba(229, 229, 229, 0.05) 0%, rgba(229, 229, 229, 0.05) 100%);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
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: 1;
|
flex: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// background: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.dhcp-content {
|
.tab-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
z-index: 2; // 提高tab容器的层级
|
||||||
align-items: center;
|
position: relative;
|
||||||
justify-content: center;
|
flex-shrink: 0;
|
||||||
height: 100%;
|
.special-tab-bg-con{
|
||||||
|
position: absolute;
|
||||||
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 {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 56px;
|
height: 100%;
|
||||||
background: rgba(255, 255, 255, 0.1);
|
z-index: 0;
|
||||||
border-radius: 28px;
|
.tab-bg{
|
||||||
border: none;
|
height: 50%;
|
||||||
padding: 0 24px;
|
width: 100%;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
.tab-grey-bg{
|
||||||
&:focus {
|
background: rgba(231, 235, 242, 1);
|
||||||
outline: none;
|
}
|
||||||
background: rgba(255, 255, 255, 0.15);
|
.tab-white-bg{
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
top: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 覆盖 Ant Design 的默认样式
|
.tab-item {
|
||||||
.ant-input {
|
height: 80px;
|
||||||
background: rgba(255, 255, 255, 0.1);
|
flex: 1;
|
||||||
border-radius: 28px;
|
display: flex;
|
||||||
border: none;
|
align-items: center;
|
||||||
padding: 0 24px;
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
font-family: PingFang SC;
|
font-family: PingFang SC;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: Heavy;
|
font-style: Heavy;
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
line-height: 32px;
|
line-height: 30px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0%;
|
||||||
color: rgba(229, 229, 229, 1);
|
padding: 0 40px;
|
||||||
height: 56px;
|
z-index: 1;
|
||||||
|
|
||||||
&::placeholder {
|
&.dhcp-tab {
|
||||||
color: rgba(229, 229, 229, 0.5);
|
// 默认状态 - 左上角和左下角圆角
|
||||||
|
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 {
|
&.static-tab {
|
||||||
outline: none;
|
// 默认状态 - 右上角和右下角圆角
|
||||||
background: rgba(255, 255, 255, 0.15);
|
border-top-right-radius: 20px;
|
||||||
box-shadow: none;
|
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 cs from 'classnames';
|
||||||
import { Form, Input, message } from 'antd';
|
import { Form, Input, message } from 'antd';
|
||||||
import ButtonCom from '../../../components/ButtonCom';
|
import ButtonCom from '../../../components/ButtonCom';
|
||||||
|
import { useConfigStep } from '@/contexts/ConfigStepContext';
|
||||||
|
|
||||||
const staticIpFormFields: CONFIG_STEPS.StaticFormFieldConfig[] = [
|
const staticIpFormFields: CONFIG_STEPS.FormFieldConfig[] = [
|
||||||
{
|
{
|
||||||
name: "ipv4",
|
name: "ipv4",
|
||||||
label: "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",
|
name: "ipv6Gateway",
|
||||||
label: "IPv6网关",
|
label: "IPv6网关",
|
||||||
|
|
@ -86,6 +111,30 @@ const staticIpFormFields: CONFIG_STEPS.StaticFormFieldConfig[] = [
|
||||||
const NetworkConfig: React.FC = () => {
|
const NetworkConfig: React.FC = () => {
|
||||||
const [activeTab, setActiveTab] = useState<'dhcp' | 'static'>('dhcp');
|
const [activeTab, setActiveTab] = useState<'dhcp' | 'static'>('dhcp');
|
||||||
const [form] = Form.useForm();
|
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 () => {
|
const handleSubmit = async () => {
|
||||||
if (activeTab === 'dhcp') {
|
if (activeTab === 'dhcp') {
|
||||||
|
|
@ -95,6 +144,9 @@ const NetworkConfig: React.FC = () => {
|
||||||
console.log('网络配置返回信息成功:', res);
|
console.log('网络配置返回信息成功:', res);
|
||||||
if(res.success){
|
if(res.success){
|
||||||
message.success('网络配置成功');
|
message.success('网络配置成功');
|
||||||
|
setTimeout(() => {
|
||||||
|
goToNextTab();
|
||||||
|
}, 300);
|
||||||
}else{
|
}else{
|
||||||
message.error(res.message || '网络配置失败');
|
message.error(res.message || '网络配置失败');
|
||||||
}
|
}
|
||||||
|
|
@ -106,10 +158,26 @@ const NetworkConfig: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
console.log('表单提交数据:', values);
|
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 });
|
const res = await window.electronAPI.invoke('apply-wired-config',{ method: 'static', ...values });
|
||||||
console.log('网络配置返回信息成功:', res);
|
console.log('网络配置返回信息成功:', res);
|
||||||
if(res.success){
|
if(res.success){
|
||||||
message.success('网络配置成功');
|
message.success('网络配置成功');
|
||||||
|
setTimeout(() => {
|
||||||
|
goToNextTab();
|
||||||
|
}, 300);
|
||||||
}else{
|
}else{
|
||||||
message.error(res.message || '网络配置失败');
|
message.error(res.message || '网络配置失败');
|
||||||
}
|
}
|
||||||
|
|
@ -154,26 +222,35 @@ const NetworkConfig: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["network-config"]}>
|
<div className={styles["network-config"]}>
|
||||||
<div className={styles["tab-container"]}>
|
<div className={styles['tab-content-container-wrapper']}>
|
||||||
<div
|
<div className={styles["tab-container"]}>
|
||||||
className={cs(styles["tab-item"], { [styles.active]: activeTab === 'dhcp' })}
|
<div className={styles["special-tab-bg-con"]}>
|
||||||
onClick={() => setActiveTab('dhcp')}
|
<div className={cs(styles["tab-bg"],styles["tab-grey-bg"])}></div>
|
||||||
>
|
<div className={cs(styles["tab-bg"],styles["tab-white-bg"])}></div>
|
||||||
<span>DHCP</span>
|
</div>
|
||||||
{activeTab === 'dhcp' && <div className={styles["indicator"]}></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>
|
||||||
<div
|
|
||||||
className={cs(styles["tab-item"], { [styles.active]: activeTab === 'static' })}
|
<div className={styles["content-container"]}>
|
||||||
onClick={() => setActiveTab('static')}
|
{activeTab === 'dhcp' ? <DhcpComponent /> : <StaticIpComponent />}
|
||||||
>
|
|
||||||
<span>静态IP</span>
|
|
||||||
{activeTab === 'static' && <div className={styles["indicator"]}></div>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["content-container"]}>
|
|
||||||
{activeTab === 'dhcp' ? <DhcpComponent /> : <StaticIpComponent />}
|
|
||||||
</div>
|
|
||||||
<ButtonCom confirmText="确认并进入下一步" onConfirm={handleSubmit}/>
|
<ButtonCom confirmText="确认并进入下一步" onConfirm={handleSubmit}/>
|
||||||
</div>
|
</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 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 (
|
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>
|
</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 (
|
return (
|
||||||
<div>
|
<div className={styles.container}>
|
||||||
侦测管理平台
|
{renderContent()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Index;
|
export default WatchManagement;
|
||||||
|
|
@ -1,70 +1,97 @@
|
||||||
|
// src/pages/configSteps/index.less
|
||||||
.config-step-container {
|
.config-step-container {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding-top: 24px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.tabs-container {
|
.steps-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
height: 60px;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
height: 48px;
|
||||||
// background: rgba(229, 229, 229, 0.2);
|
margin-top: 80px;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
|
||||||
.tab-item {
|
.step-item {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
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;
|
font-size: 22px;
|
||||||
color: rgba(255, 255, 255, 0.6); // 未激活tab文字透明
|
line-height: 32px;
|
||||||
transition: color 0.3s ease;
|
letter-spacing: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-indicator {
|
.step-label-active {
|
||||||
position: absolute;
|
color: rgba(1, 90, 255, 1);
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
background: rgba(229, 229, 229, 0.2);
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
.step-label-inactive {
|
||||||
.tab-label {
|
color: rgba(139, 153, 173, 1);
|
||||||
color: white; // 激活tab文字为白色
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-indicator {
|
|
||||||
background: white; // 激活tab指示器为白色
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.tab-content-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
background: rgba(255, 255, 255, 0.1);
|
width: 1400px;
|
||||||
|
|
||||||
.tab-content {
|
.tab-content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100%;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.emptyBox{
|
.emptyBox {
|
||||||
height: 60px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,45 +1,94 @@
|
||||||
// src/pages/configSteps/index.tsx
|
// 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 styles from './index.less';
|
||||||
import cs from 'classnames';
|
import cs from 'classnames';
|
||||||
import NetworkConfig from './components/networkConfig';
|
import NetworkConfig from './components/networkConfig';
|
||||||
|
import ServerConfig from './components/serverConfig';
|
||||||
import WatchManagement from './components/watchManagement';
|
import WatchManagement from './components/watchManagement';
|
||||||
import TerminalGetImage from './components/terminalGetImage';
|
import TerminalGetImage from './components/terminalGetImage';
|
||||||
|
import { ConfigStepProvider, useConfigStep } from '@/contexts/ConfigStepContext';
|
||||||
|
|
||||||
const Index: React.FC = () => {
|
const tabs = [
|
||||||
const [activeTab, setActiveTab] = useState<string>("networkConfig");
|
{ key: "networkConfig", label: '平台网络配置', component: <NetworkConfig /> },
|
||||||
|
{ key: "serverConfig", label: '服务器配置', component: <ServerConfig /> },
|
||||||
|
{ key: "watchManagement", label: '侦测平台管理', component: <WatchManagement /> },
|
||||||
|
{ key: "terminalGetImage", label: '终端获取镜像信息', component: <TerminalGetImage /> },
|
||||||
|
];
|
||||||
|
|
||||||
const tabs = [
|
const ConfigStepsContent: React.FC = () => {
|
||||||
{ key: "networkConfig", label: '平台网络配置', component: <NetworkConfig /> },
|
const { activeTab, setActiveTab, tabs: contextTabs } = useConfigStep();
|
||||||
{ key: "watchManagement", label: '侦测管理平台', component: <WatchManagement /> },
|
const location = useLocation();
|
||||||
{ key: "terminalGetImage", label: '终端获取镜像信息', component: <TerminalGetImage /> },
|
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 (
|
return (
|
||||||
<div className={styles["config-step-container"]}>
|
<div className={styles["config-step-container"]}>
|
||||||
<div className={styles["tabs-container"]}>
|
<div className={styles["steps-container"]}>
|
||||||
{tabs.map((tab) => (
|
{contextTabs.map((tab, index) => (
|
||||||
<div
|
<React.Fragment key={tab.key}>
|
||||||
key={tab.key}
|
<div
|
||||||
className={cs(styles["tab-item"], {
|
className={cs(styles["step-item"], {
|
||||||
[styles.active]: activeTab === tab.key
|
[styles.active]: activeTab === tab.key
|
||||||
})}
|
})}
|
||||||
onClick={() => setActiveTab(tab.key)}
|
onClick={() => setActiveTab(tab.key)}
|
||||||
>
|
>
|
||||||
<span className={styles["tab-label"]}>{tab.label}</span>
|
<div className={cs(styles["step-number"], {
|
||||||
<div className={styles["tab-indicator"]} />
|
[styles["step-number-active"]]: index <= activeTabIndex,
|
||||||
</div>
|
[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>
|
||||||
|
|
||||||
<div className={styles["tab-content-container"]}>
|
<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>
|
||||||
<div className={styles.emptyBox}></div>
|
<div className={styles.emptyBox}></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Index: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<ConfigStepProvider tabs={tabs}>
|
||||||
|
<ConfigStepsContent />
|
||||||
|
</ConfigStepProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Index;
|
export default Index;
|
||||||
|
|
@ -8,73 +8,58 @@
|
||||||
.showTextCon{
|
.showTextCon{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-self: center;
|
// justify-self: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.name{
|
height: 155px;
|
||||||
height: 80px;
|
justify-content: space-between;
|
||||||
margin-bottom: 20px;
|
.imgIcon{
|
||||||
font-size: 36px;
|
|
||||||
color: #e5e5e5;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
.welcomeIcon{
|
.welcomeIcon{
|
||||||
height: 66px;
|
height: 90px;
|
||||||
width: 70px;
|
width: 350px;
|
||||||
margin-right: 20px;
|
|
||||||
}
|
}
|
||||||
.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;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
.text{
|
animation: loading 1.5s infinite ease-in-out;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
|
||||||
.nameText{
|
.dot:nth-child(1) {
|
||||||
font-size: 28px;
|
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{
|
25% {
|
||||||
width: 96px;
|
background: rgba(1, 90, 255, 0.5);
|
||||||
height: 12px;
|
// transform: scale(0.8);
|
||||||
display: flex;
|
}
|
||||||
justify-content: space-between;
|
50% {
|
||||||
align-items: center;
|
background: rgba(1, 90, 255, 1);
|
||||||
.dot {
|
// transform: scale(1);
|
||||||
width: 10px;
|
}
|
||||||
height: 10px;
|
75% {
|
||||||
border-radius: 50%;
|
background: rgba(1, 90, 255, 0.5);
|
||||||
background-color: rgba(255, 255, 255, 0.3);
|
// transform: scale(0.8);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,19 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import WelcomeIcon from '../../assets/welcome-icon.jpg'
|
import WelcomeIcon from '../../assets/welcome-icon.png'
|
||||||
|
|
||||||
const Welcome = () => {
|
const Welcome = () => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.welcomeCon} >
|
<div className={styles.welcomeCon} >
|
||||||
<div className={styles.showTextCon}>
|
<div className={styles.showTextCon}>
|
||||||
<div className={styles.name}>
|
<div className={styles.imgIcon}>
|
||||||
<img src={WelcomeIcon} className={styles.welcomeIcon} />
|
<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>
|
||||||
|
<div className={styles.loadingCon}>
|
||||||
|
<div className={styles.dot}></div>
|
||||||
|
<div className={styles.dot}></div>
|
||||||
|
<div className={styles.dot}></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
declare namespace CONFIG_STEPS {
|
declare namespace CONFIG_STEPS {
|
||||||
interface StaticFormFieldConfig {
|
// 平台网络配置>静态IP字段
|
||||||
|
interface FormFieldConfig {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: 'input' | 'select'; // 可扩展更多类型
|
type: 'input' | 'select'; // 可扩展更多类型
|
||||||
|
|
@ -8,4 +9,10 @@ declare namespace CONFIG_STEPS {
|
||||||
options?: { label: string; value: string }[]; // select 专用
|
options?: { label: string; value: string }[]; // select 专用
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
}
|
}
|
||||||
|
interface UpdateProgress {
|
||||||
|
percent: number;
|
||||||
|
transferred: number;
|
||||||
|
total: number;
|
||||||
|
bytesPerSecond: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue