A:增加前端WebSocket连接使用
This commit is contained in:
1 parent
6d9a6ac3ee
commit
83b7ca5971
8 files changed
+2015
No files matched your search
Generated
+739
@@ -20,6 +20,7 @@
|
||||
"@types/node": "^22.15.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
||||
"@typescript-eslint/parser": "^8.31.1",
|
||||
"@unocss/preset-mini": "^66.1.2",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||
"@vitest/eslint-plugin": "^1.1.39",
|
||||
@@ -36,6 +37,7 @@
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "~5.8.0",
|
||||
"unocss": "^66.1.2",
|
||||
"unplugin-auto-import": "^19.2.0",
|
||||
"unplugin-vue-components": "^28.5.0",
|
||||
"vite": "^6.2.4",
|
||||
@@ -58,6 +60,27 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/install-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
|
||||
"integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"package-manager-detector": "^1.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/install-pkg/node_modules/tinyexec": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.1.tgz",
|
||||
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@antfu/utils": {
|
||||
"version": "0.7.10",
|
||||
"resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz",
|
||||
@@ -1399,6 +1422,53 @@
|
||||
"url": "https://github.com/sponsors/nzakas"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/types": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz",
|
||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@iconify/utils": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-2.3.0.tgz",
|
||||
"integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@antfu/install-pkg": "^1.0.0",
|
||||
"@antfu/utils": "^8.1.0",
|
||||
"@iconify/types": "^2.0.0",
|
||||
"debug": "^4.4.0",
|
||||
"globals": "^15.14.0",
|
||||
"kolorist": "^1.8.0",
|
||||
"local-pkg": "^1.0.0",
|
||||
"mlly": "^1.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/utils/node_modules/@antfu/utils": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-8.1.1.tgz",
|
||||
"integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/utils/node_modules/globals": {
|
||||
"version": "15.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/globals/-/globals-15.15.0.tgz",
|
||||
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@@ -1902,6 +1972,22 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@quansync/fs": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@quansync/fs/-/fs-0.1.3.tgz",
|
||||
"integrity": "sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"quansync": "^0.2.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
|
||||
@@ -2502,6 +2588,486 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/astro": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/astro/-/astro-66.1.2.tgz",
|
||||
"integrity": "sha512-QBcvrPp0F2jqe2Y/S/FQDmEmNlAhGjeWN5fkUGj02N7mXRrg0/VJxSpOJH6XHRWkMoFPoNNyEjHk563ODbjtHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/reset": "66.1.2",
|
||||
"@unocss/vite": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/cli": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/cli/-/cli-66.1.2.tgz",
|
||||
"integrity": "sha512-bYCRpkGMu0QwC6Ktq3S/HwtcIW8Famy0dXOu1RIAM1IT60lq+4S5UTEBPdwryoFgDBoVMB7KLUhPYiGQ3pmSTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
"@unocss/config": "66.1.2",
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/preset-uno": "66.1.2",
|
||||
"cac": "^6.7.14",
|
||||
"chokidar": "^3.6.0",
|
||||
"colorette": "^2.0.20",
|
||||
"consola": "^3.4.2",
|
||||
"magic-string": "^0.30.17",
|
||||
"pathe": "^2.0.3",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"tinyglobby": "^0.2.13",
|
||||
"unplugin-utils": "^0.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"unocss": "bin/unocss.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/cli/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/cli/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/cli/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/config": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/config/-/config-66.1.2.tgz",
|
||||
"integrity": "sha512-2sQXj+Qaq4RVDELVTPoXMggZ30g1WKHeCuur396I12Ab0HgAR6bTc/DIrNtqKVHFI3mmlvP1oM1ynhKWSKPsTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"unconfig": "^7.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/core": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/core/-/core-66.1.2.tgz",
|
||||
"integrity": "sha512-mN9h1hHEuhDcdbI4z74o7UnxlBZYVsJpYcdC1YLWBKROcLYTkuyZ7hgBzpo1FBNox2Bt3JnrSinVDmc44Bxjow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/extractor-arbitrary-variants": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/extractor-arbitrary-variants/-/extractor-arbitrary-variants-66.1.2.tgz",
|
||||
"integrity": "sha512-F570wH9VYeFTb4r8qgcbN5QpEVIAvFC1zOnrAPUr6B6kbU2YChMXxHP7PHK0AzLHnEr458Pwpzl6hmP6bzxZ8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/inspector": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/inspector/-/inspector-66.1.2.tgz",
|
||||
"integrity": "sha512-ftdZzFP5DAKDzgBI078xDDZbNNVq1RV/yhpNkviBvWCUsgRWc6o3G8swqJPIvFaphmUms0RIYH9shmXilVXFtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/rule-utils": "66.1.2",
|
||||
"colorette": "^2.0.20",
|
||||
"gzip-size": "^6.0.0",
|
||||
"sirv": "^3.0.1",
|
||||
"vue-flow-layout": "^0.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/postcss": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/postcss/-/postcss-66.1.2.tgz",
|
||||
"integrity": "sha512-RCA3or1qBdRVduNW73xdeiFDCEb8cvcGKsHSN66rL66RrlzNnunE4NE55vbI+yoArTRZ7RdUnxq1KuXKjrJbYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/config": "66.1.2",
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/rule-utils": "66.1.2",
|
||||
"css-tree": "^3.1.0",
|
||||
"postcss": "^8.5.3",
|
||||
"tinyglobby": "^0.2.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-attributify": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-attributify/-/preset-attributify-66.1.2.tgz",
|
||||
"integrity": "sha512-i7+LRtpxbtSzS+gHdc+aW99mGLYeR8hUnEWqFNnr+MiiyzbD8yFimye/u8TySSBLzPKGbLCb4YWVV684BuZgxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-icons": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-icons/-/preset-icons-66.1.2.tgz",
|
||||
"integrity": "sha512-14390jFBJ2anuKvjX9TeRCm7adNjR/mey0bh0+S/k/5W3VugIY2y0E+OH3m+sx5d/5ZUYbYkUGsmtuKbVNwwxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/utils": "^2.3.0",
|
||||
"@unocss/core": "66.1.2",
|
||||
"ofetch": "^1.4.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-mini": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-mini/-/preset-mini-66.1.2.tgz",
|
||||
"integrity": "sha512-oiDe+VhwZ8B5Z0UGfggtOwgpRZMLtH1RTDFvmJmJEXYYX5BPWknS6wYcQzxy0i/y9ym0xp2QnEaTpGmR7LKdkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/extractor-arbitrary-variants": "66.1.2",
|
||||
"@unocss/rule-utils": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-tagify": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-tagify/-/preset-tagify-66.1.2.tgz",
|
||||
"integrity": "sha512-Xw5sFJGuzmGnfAXMI0kAiWDBh4DT3cOyphcyY9grBxbmxgqQDxRFHOV3Eg85lWK6X5cScOv3DhO0ndGv5ND8YA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-typography": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-typography/-/preset-typography-66.1.2.tgz",
|
||||
"integrity": "sha512-+k9zp27Ak8rB6LPFDwq9fcwd3+ivFeSvXFQ2d4fBCwGGOAKHIA7qHLg3etxRaMhGd3YUPv/6d7FWpBbQgUVYZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/preset-mini": "66.1.2",
|
||||
"@unocss/rule-utils": "66.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-uno": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-uno/-/preset-uno-66.1.2.tgz",
|
||||
"integrity": "sha512-JL9YkDwluu1YGhzBaxO60XkKtZBagL13z3K6dsjsghbs+dKVlh35rhlIm5TZ+NdLAzcLM8PHhXm2ausjSd54Bg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/preset-wind3": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-web-fonts": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-web-fonts/-/preset-web-fonts-66.1.2.tgz",
|
||||
"integrity": "sha512-2ru+6jaac72oUx0kOBgNzbbkVe6oWKjqGmx24uK94fAcrP9eQyd+r7xiFpqXegrQ8+kONI66+HxAClvF2JHqdw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"ofetch": "^1.4.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-wind": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-wind/-/preset-wind-66.1.2.tgz",
|
||||
"integrity": "sha512-O3nIfbTbX/YRMFj7jNb7nHBDV47G79qOmyid4WPFZrPV3BbFAo94d/54kSoDVuc8jAt06YYQH9XC4ZeD59Sr3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/preset-wind3": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-wind3": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-wind3/-/preset-wind3-66.1.2.tgz",
|
||||
"integrity": "sha512-S09imGOngAAOXCBCHb3JAtxD1/L7nDWrgEeX6NT0ElDp3X1T6XxUXYJlpjCfcqV/klMoXyYouKvp0YuG9QSgVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/preset-mini": "66.1.2",
|
||||
"@unocss/rule-utils": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/preset-wind4": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/preset-wind4/-/preset-wind4-66.1.2.tgz",
|
||||
"integrity": "sha512-03p4rpBAWzz58BzAiKsUuG+6YO7IG6mJMGQAtPzuhd+nVBJLIRa3eBIVXOPmAVz1rNx5XPRTAr6PMC7ycdMFRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/extractor-arbitrary-variants": "66.1.2",
|
||||
"@unocss/rule-utils": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/reset": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/reset/-/reset-66.1.2.tgz",
|
||||
"integrity": "sha512-njNy/QCpuPKBFeEvhYGwwCe3t8R8JTxONsyUB9NsFOamkF13DSlEB4Yy/QLQfIinbbmx0F/wiej/JGOJk1ecDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/rule-utils": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/rule-utils/-/rule-utils-66.1.2.tgz",
|
||||
"integrity": "sha512-nn0ehvDh7yyWq2mcBDLVpmMAivjRATUroZ8ETinyN1rmfsGesm71R0d1gV3K+Z6YC7a3+dMLc+/qzI7VK3AG/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "^66.1.2",
|
||||
"magic-string": "^0.30.17"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/transformer-attributify-jsx": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/transformer-attributify-jsx/-/transformer-attributify-jsx-66.1.2.tgz",
|
||||
"integrity": "sha512-PNwxpsQlBlTAyw1apIMyioeAKrLAf7axLDjZ4BW20WH7ql0GUwvMhuO/qzsWDpYWdtSlFnnAdWI2aCxyvhzdCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/transformer-compile-class": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/transformer-compile-class/-/transformer-compile-class-66.1.2.tgz",
|
||||
"integrity": "sha512-viJetYFncLf9llxYQ7DKf5PuSJw08B7qhp0IXv/7ZG7agU09J1mlussC6ff+00iRoMxvG+5uXiYlTzL2vfikwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/transformer-directives": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/transformer-directives/-/transformer-directives-66.1.2.tgz",
|
||||
"integrity": "sha512-A41/cPMB+BUEgnhz5kFiTYgSuCAziJy6hSlLYBDcrFbARUsvmhZFou0P2fRr3wDOFxD3BuApHjsefybKTh1UeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/rule-utils": "66.1.2",
|
||||
"css-tree": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/transformer-variant-group": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/transformer-variant-group/-/transformer-variant-group-66.1.2.tgz",
|
||||
"integrity": "sha512-RfqJmeic4kAwS5OhSk/D00hqla+xXIw8AJH93jYqHfyDhJR5vddEAJi5RBMOL7y6vDQqRlUCEDQvfp3zSmi6iw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/core": "66.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/vite": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@unocss/vite/-/vite-66.1.2.tgz",
|
||||
"integrity": "sha512-ZJHN8+HKSrclVjT/+S7Vh2t59DK8J44d5nLZPG1Goua7uNK8yYJeOLK2sCGX7aackRer1ZynmglFFzxNFVt+IA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
"@unocss/config": "66.1.2",
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/inspector": "66.1.2",
|
||||
"chokidar": "^3.6.0",
|
||||
"magic-string": "^0.30.17",
|
||||
"pathe": "^2.0.3",
|
||||
"tinyglobby": "^0.2.13",
|
||||
"unplugin-utils": "^0.2.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/vite/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/vite/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/vite/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz",
|
||||
@@ -3566,6 +4132,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz",
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz",
|
||||
@@ -3601,6 +4174,16 @@
|
||||
"proto-list": "~1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz",
|
||||
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||
@@ -3695,6 +4278,20 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-3.1.0.tgz",
|
||||
"integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.12.2",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -3840,6 +4437,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
|
||||
@@ -3850,6 +4454,13 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/destr/-/destr-2.0.5.tgz",
|
||||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
@@ -3880,6 +4491,13 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz",
|
||||
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@@ -4912,6 +5530,22 @@
|
||||
"integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==",
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
|
||||
},
|
||||
"node_modules/gzip-size": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz",
|
||||
"integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"duplexer": "^0.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
|
||||
@@ -5623,6 +6257,13 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.12.2.tgz",
|
||||
"integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz",
|
||||
@@ -5840,6 +6481,13 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/node-fetch-native": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-1.6.6.tgz",
|
||||
"integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
|
||||
@@ -6028,6 +6676,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ofetch": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/ofetch/-/ofetch-1.4.1.tgz",
|
||||
"integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"destr": "^2.0.3",
|
||||
"node-fetch-native": "^1.6.4",
|
||||
"ufo": "^1.5.4"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -6127,6 +6787,13 @@
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/package-manager-detector": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.3.0.tgz",
|
||||
"integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@@ -7492,6 +8159,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unconfig": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/unconfig/-/unconfig-7.3.2.tgz",
|
||||
"integrity": "sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@quansync/fs": "^0.1.1",
|
||||
"defu": "^6.1.4",
|
||||
"jiti": "^2.4.2",
|
||||
"quansync": "^0.2.8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
|
||||
@@ -7584,6 +8267,52 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unocss": {
|
||||
"version": "66.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/unocss/-/unocss-66.1.2.tgz",
|
||||
"integrity": "sha512-mVwuXzIZ5Ex83F4w3XVJyp9DSbh5KhDzglyvMLktX8oU0QxQtaSpa5lE1twl3wgM0pVL9gmzD4a0FoYWZuJIDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/astro": "66.1.2",
|
||||
"@unocss/cli": "66.1.2",
|
||||
"@unocss/core": "66.1.2",
|
||||
"@unocss/postcss": "66.1.2",
|
||||
"@unocss/preset-attributify": "66.1.2",
|
||||
"@unocss/preset-icons": "66.1.2",
|
||||
"@unocss/preset-mini": "66.1.2",
|
||||
"@unocss/preset-tagify": "66.1.2",
|
||||
"@unocss/preset-typography": "66.1.2",
|
||||
"@unocss/preset-uno": "66.1.2",
|
||||
"@unocss/preset-web-fonts": "66.1.2",
|
||||
"@unocss/preset-wind": "66.1.2",
|
||||
"@unocss/preset-wind3": "66.1.2",
|
||||
"@unocss/preset-wind4": "66.1.2",
|
||||
"@unocss/transformer-attributify-jsx": "66.1.2",
|
||||
"@unocss/transformer-compile-class": "66.1.2",
|
||||
"@unocss/transformer-directives": "66.1.2",
|
||||
"@unocss/transformer-variant-group": "66.1.2",
|
||||
"@unocss/vite": "66.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@unocss/webpack": "66.1.2",
|
||||
"vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@unocss/webpack": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@@ -8201,6 +8930,16 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-flow-layout": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue-flow-layout/-/vue-flow-layout-0.1.1.tgz",
|
||||
"integrity": "sha512-JdgRRUVrN0Y2GosA0M68DEbKlXMqJ7FQgsK8CjQD2vxvNSqAU6PZEpi4cfcTVtfM2GVOMjHo7GKKLbXxOBqDqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Time:2025/5/25 19:16 09
|
||||
* Name:useFirewallRules.ts
|
||||
* Path:src/composables
|
||||
* ProjectName:ClientApp
|
||||
* Author:charlatans
|
||||
*
|
||||
* Il n'ya qu'un héroïsme au monde :
|
||||
* c'est de voir le monde tel qu'il est et de l'aimer.
|
||||
*/
|
||||
// composables/useFirewallRules.ts - 防火墙规则管理
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useWebSocket } from './useWebSocket'
|
||||
import type { FirewallRuleMessage } from '@/types/websocket'
|
||||
|
||||
interface FirewallRule {
|
||||
name: string
|
||||
action: string
|
||||
protocol: string
|
||||
port: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export function useFirewallRules() {
|
||||
const { sendCommand, onMessage, isConnected } = useWebSocket()
|
||||
|
||||
const firewallRules = ref<FirewallRule[]>([])
|
||||
const loading = ref(false)
|
||||
const cleanupFunctions: (() => void)[] = []
|
||||
|
||||
// 获取防火墙规则
|
||||
const fetchFirewallRules = async () => {
|
||||
if (!isConnected.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await sendCommand('getFirewallRules')
|
||||
} catch (error) {
|
||||
console.error('获取防火墙规则失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 添加防火墙规则
|
||||
const addFirewallRule = async (rule: Omit<FirewallRule, 'enabled'>) => {
|
||||
if (!isConnected.value) return false
|
||||
|
||||
const ruleData: FirewallRuleMessage = {
|
||||
Type: 'firewallRule',
|
||||
RuleName: rule.name,
|
||||
Action: rule.action,
|
||||
Protocol: rule.protocol,
|
||||
Port: rule.port,
|
||||
Enabled: true,
|
||||
Timestamp: new Date().toISOString()
|
||||
}
|
||||
|
||||
return await sendCommand('addFirewallRule', ruleData)
|
||||
}
|
||||
|
||||
// 处理防火墙规则响应
|
||||
const handleFirewallRulesResponse = (message: any) => {
|
||||
if (message.Success && message.Data && Array.isArray(message.Data)) {
|
||||
firewallRules.value = message.Data.map((item: FirewallRuleMessage) => ({
|
||||
name: item.RuleName,
|
||||
action: item.Action,
|
||||
protocol: item.Protocol,
|
||||
port: item.Port,
|
||||
enabled: item.Enabled
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 监听防火墙规则响应
|
||||
cleanupFunctions.push(
|
||||
onMessage('firewallRules', handleFirewallRulesResponse)
|
||||
)
|
||||
|
||||
// 监听防火墙规则更新
|
||||
cleanupFunctions.push(
|
||||
onMessage('firewallRulesUpdate', handleFirewallRulesResponse)
|
||||
)
|
||||
|
||||
// 监听添加规则响应
|
||||
cleanupFunctions.push(
|
||||
onMessage('addFirewallRuleResponse', (message) => {
|
||||
if (message.Success) {
|
||||
console.log('防火墙规则添加成功')
|
||||
// 规则会通过广播更新自动刷新
|
||||
} else {
|
||||
console.error('防火墙规则添加失败:', message.Message)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 连接成功后获取规则
|
||||
cleanupFunctions.push(
|
||||
onMessage('welcome', () => {
|
||||
fetchFirewallRules()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanupFunctions.forEach(cleanup => cleanup())
|
||||
})
|
||||
|
||||
return {
|
||||
firewallRules,
|
||||
loading,
|
||||
fetchFirewallRules,
|
||||
addFirewallRule
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
|
||||
// composables/useWebSocket.ts
|
||||
import { onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useWebSocketStore } from '@/stores/websocket'
|
||||
import type { ConnectionState } from '@/types/websocket'
|
||||
|
||||
export interface UseWebSocketOptions {
|
||||
url: string
|
||||
autoConnect?: boolean
|
||||
autoRefresh?: boolean
|
||||
refreshInterval?: number
|
||||
onConnected?: () => void
|
||||
onDisconnected?: () => void
|
||||
onError?: (error: string) => void
|
||||
onStateChange?: (state: ConnectionState) => void
|
||||
}
|
||||
|
||||
export const useWebSocket = (options: UseWebSocketOptions) => {
|
||||
const store = useWebSocketStore()
|
||||
let refreshTimer: NodeJS.Timeout | null = null
|
||||
|
||||
const {
|
||||
url,
|
||||
autoConnect = true,
|
||||
autoRefresh = true,
|
||||
refreshInterval = 5000,
|
||||
onConnected,
|
||||
onDisconnected,
|
||||
onError,
|
||||
onStateChange
|
||||
} = options
|
||||
|
||||
// 启动自动刷新
|
||||
const startAutoRefresh = (): void => {
|
||||
if (!autoRefresh || refreshTimer) return
|
||||
|
||||
refreshTimer = setInterval(async () => {
|
||||
if (store.isConnected) {
|
||||
try {
|
||||
await store.fetchSystemInfo()
|
||||
} catch (error) {
|
||||
console.error('Auto refresh failed:', error)
|
||||
}
|
||||
}
|
||||
}, refreshInterval)
|
||||
}
|
||||
|
||||
// 停止自动刷新
|
||||
const stopAutoRefresh = (): void => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 手动连接
|
||||
const connect = async (): Promise<void> => {
|
||||
try {
|
||||
await store.initialize(url)
|
||||
if (autoRefresh) {
|
||||
startAutoRefresh()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Connection failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 手动断开连接
|
||||
const disconnect = (): void => {
|
||||
stopAutoRefresh()
|
||||
store.disconnect()
|
||||
}
|
||||
|
||||
// 监听连接状态变化
|
||||
watch(
|
||||
() => store.connectionState,
|
||||
(newState, oldState) => {
|
||||
onStateChange?.(newState)
|
||||
|
||||
if (newState === 'connected' && oldState !== 'connected') {
|
||||
onConnected?.()
|
||||
if (autoRefresh) {
|
||||
startAutoRefresh()
|
||||
}
|
||||
} else if (newState === 'disconnected' && oldState === 'connected') {
|
||||
stopAutoRefresh()
|
||||
onDisconnected?.()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 监听错误
|
||||
watch(
|
||||
() => store.lastError,
|
||||
(error) => {
|
||||
if (error) {
|
||||
onError?.(error)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (autoConnect) {
|
||||
connect().catch(console.error)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
disconnect()
|
||||
})
|
||||
|
||||
return {
|
||||
// 状态
|
||||
connectionState: store.connectionState,
|
||||
isConnected: store.isConnected,
|
||||
systemInfo: store.systemInfo,
|
||||
networkStatus: store.networkStatus,
|
||||
firewallRules: store.firewallRules,
|
||||
lastError: store.lastError,
|
||||
isLoading: store.isLoading,
|
||||
connectionCount: store.connectionCount,
|
||||
activeNetworkInterfaces: store.activeNetworkInterfaces,
|
||||
|
||||
// 方法
|
||||
connect,
|
||||
disconnect,
|
||||
fetchSystemInfo: store.fetchSystemInfo,
|
||||
fetchNetworkStatus: store.fetchNetworkStatus,
|
||||
fetchFirewallRules: store.fetchFirewallRules,
|
||||
addFirewallRule: store.addFirewallRule,
|
||||
updateFirewallRule: store.updateFirewallRule,
|
||||
deleteFirewallRule: store.deleteFirewallRule,
|
||||
toggleFirewallRule: store.toggleFirewallRule,
|
||||
clearError: store.clearError,
|
||||
startAutoRefresh,
|
||||
stopAutoRefresh
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* Time:2025/5/25 20:42 29
|
||||
* Name:useWebSocketEnhanced.ts
|
||||
* Path:src/composables
|
||||
* ProjectName:ClientApp
|
||||
* Author:charlatans
|
||||
*
|
||||
* Il n'ya qu'un héroïsme au monde :
|
||||
* c'est de voir le monde tel qu'il est et de l'aimer.
|
||||
*/
|
||||
|
||||
// composables/useWebSocketEnhanced.ts - 增强的WebSocket管理
|
||||
import { ref, onUnmounted, watch } from 'vue';
|
||||
import { useWebSocketStore } from '@/stores/websocketStore';
|
||||
import { WebSocketRetryManager } from '@/utils/websocketHelpers';
|
||||
import type { NetworkStatus, ConnectionInfo, FirewallRule, WebSocketMessage, WebSocketCommand } from '@/types/websocket';
|
||||
|
||||
export class EnhancedWebSocketManager {
|
||||
private ws: WebSocket | null = null;
|
||||
private retryManager = new WebSocketRetryManager();
|
||||
private pingTimer: number | null = null;
|
||||
private dataRefreshTimer: number | null = null;
|
||||
private messageHandlers = new Map<string, Function[]>();
|
||||
private pendingCommands = new Map<string, { resolve: Function; reject: Function; timeout: number }>();
|
||||
private store = useWebSocketStore();
|
||||
|
||||
constructor(private url: string = 'ws://localhost:8080') {
|
||||
this.setupEventHandlers();
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) return;
|
||||
|
||||
this.store.updateConnectionState(false, true);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.ws = new WebSocket(this.url);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket connected successfully');
|
||||
this.store.updateConnectionState(true, false, 'excellent');
|
||||
this.retryManager.reset();
|
||||
this.startPing();
|
||||
this.startDataRefresh();
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
this.handleMessage(event.data);
|
||||
};
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
console.log('WebSocket closed:', event.code, event.reason);
|
||||
this.store.updateConnectionState(false, false);
|
||||
this.stopPing();
|
||||
this.stopDataRefresh();
|
||||
|
||||
if (event.code !== 1000 && this.retryManager.shouldRetry()) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
this.store.setError('WebSocket连接错误');
|
||||
this.store.updateConnectionState(false, false);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.store.updateConnectionState(false, false);
|
||||
this.store.setError('无法创建WebSocket连接');
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.stopPing();
|
||||
this.stopDataRefresh();
|
||||
this.retryManager.reset();
|
||||
|
||||
// 清理待处理的命令
|
||||
this.pendingCommands.forEach(({ reject, timeout }) => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('Connection closed'));
|
||||
});
|
||||
this.pendingCommands.clear();
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close(1000, 'User initiated disconnect');
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
this.store.updateConnectionState(false, false, 'disconnected');
|
||||
}
|
||||
|
||||
async sendCommand<T = unknown>(action: WebSocketCommand['action'], data?: unknown): Promise<T> {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||
throw new Error('WebSocket not connected');
|
||||
}
|
||||
|
||||
const commandId = this.generateId();
|
||||
const command: WebSocketCommand = { action, data, id: commandId };
|
||||
const message: WebSocketMessage = {
|
||||
type: 'command',
|
||||
data: command,
|
||||
timestamp: Date.now(),
|
||||
id: commandId
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 设置超时
|
||||
const timeout = setTimeout(() => {
|
||||
this.pendingCommands.delete(commandId);
|
||||
reject(new Error(`Command timeout: ${action}`));
|
||||
}, 15000);
|
||||
|
||||
// 存储待处理命令
|
||||
this.pendingCommands.set(commandId, { resolve, reject, timeout });
|
||||
|
||||
// 发送命令
|
||||
try {
|
||||
this.ws!.send(JSON.stringify(message));
|
||||
} catch (error) {
|
||||
this.pendingCommands.delete(commandId);
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleMessage(data: string) {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(data);
|
||||
|
||||
switch (message.type) {
|
||||
case 'network_status':
|
||||
this.store.updateNetworkStatus(message.data);
|
||||
this.emit('network_status', message.data);
|
||||
break;
|
||||
|
||||
case 'connection_info':
|
||||
this.store.updateConnections(message.data);
|
||||
this.emit('connection_info', message.data);
|
||||
break;
|
||||
|
||||
case 'firewall_rules':
|
||||
this.store.updateFirewallRules(message.data);
|
||||
this.emit('firewall_rules', message.data);
|
||||
break;
|
||||
|
||||
case 'response':
|
||||
this.handleCommandResponse(message);
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.store.setError(message.data.message || '服务器错误');
|
||||
this.emit('error', message.data);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.emit('message', message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private handleCommandResponse(message: WebSocketMessage) {
|
||||
if (!message.id) return;
|
||||
|
||||
const pending = this.pendingCommands.get(message.id);
|
||||
if (pending) {
|
||||
clearTimeout(pending.timeout);
|
||||
this.pendingCommands.delete(message.id);
|
||||
|
||||
if (message.data.success) {
|
||||
pending.resolve(message.data.data);
|
||||
} else {
|
||||
pending.reject(new Error(message.data.error || 'Command failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleReconnect() {
|
||||
const delay = this.retryManager.getNextDelay();
|
||||
console.log(`Reconnecting in ${delay}ms (attempt ${this.retryManager.getCurrentAttempt()})`);
|
||||
|
||||
setTimeout(() => {
|
||||
this.connect().catch(console.error);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
private startPing() {
|
||||
this.pingTimer = setInterval(() => {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.sendPing().catch(() => {
|
||||
this.store.updateConnectionState(true, false, 'poor');
|
||||
});
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
private stopPing() {
|
||||
if (this.pingTimer) {
|
||||
clearInterval(this.pingTimer);
|
||||
this.pingTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private startDataRefresh() {
|
||||
// 定期刷新数据
|
||||
this.dataRefreshTimer = setInterval(() => {
|
||||
this.refreshAllData().catch(console.error);
|
||||
}, 5000); // 每5秒刷新一次
|
||||
}
|
||||
|
||||
private stopDataRefresh() {
|
||||
if (this.dataRefreshTimer) {
|
||||
clearInterval(this.dataRefreshTimer);
|
||||
this.dataRefreshTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async sendPing() {
|
||||
const message: WebSocketMessage = {
|
||||
type: 'command',
|
||||
data: { action: 'ping' },
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshAllData() {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
this.sendCommand('get_network_status'),
|
||||
this.sendCommand('get_connections'),
|
||||
this.sendCommand('get_firewall_rules')
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private setupEventHandlers() {
|
||||
// 监听store状态变化
|
||||
watch(() => this.store.isConnected, (connected) => {
|
||||
if (connected) {
|
||||
this.refreshAllData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 事件系统
|
||||
on(event: string, handler: Function) {
|
||||
if (!this.messageHandlers.has(event)) {
|
||||
this.messageHandlers.set(event, []);
|
||||
}
|
||||
this.messageHandlers.get(event)!.push(handler);
|
||||
}
|
||||
|
||||
off(event: string, handler: Function) {
|
||||
const handlers = this.messageHandlers.get(event);
|
||||
if (handlers) {
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index > -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private emit(event: string, data?: unknown) {
|
||||
const handlers = this.messageHandlers.get(event);
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => handler(data));
|
||||
}
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
}
|
||||
|
||||
// API方法
|
||||
async getNetworkStatus(): Promise<NetworkStatus[]> {
|
||||
return this.sendCommand('get_network_status');
|
||||
}
|
||||
|
||||
async getConnections(): Promise<ConnectionInfo[]> {
|
||||
return this.sendCommand('get_connections');
|
||||
}
|
||||
|
||||
async getFirewallRules(): Promise<FirewallRule[]> {
|
||||
return this.sendCommand('get_firewall_rules');
|
||||
}
|
||||
|
||||
async addFirewallRule(rule: Omit<FirewallRule, 'id'>): Promise<void> {
|
||||
return this.sendCommand('add_firewall_rule', rule);
|
||||
}
|
||||
|
||||
async removeFirewallRule(ruleId: string): Promise<void> {
|
||||
return this.sendCommand('remove_firewall_rule', { id: ruleId });
|
||||
}
|
||||
|
||||
async toggleFirewallRule(ruleId: string): Promise<void> {
|
||||
return this.sendCommand('toggle_firewall_rule', { id: ruleId });
|
||||
}
|
||||
}
|
||||
|
||||
// 全局WebSocket管理器实例
|
||||
let wsManager: EnhancedWebSocketManager | null = null;
|
||||
|
||||
export function useWebSocketEnhanced(url?: string) {
|
||||
if (!wsManager) {
|
||||
wsManager = new EnhancedWebSocketManager(url);
|
||||
}
|
||||
|
||||
const store = useWebSocketStore();
|
||||
|
||||
// 自动连接
|
||||
if (!store.isConnected && !store.isConnecting) {
|
||||
wsManager.connect().catch(console.error);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
// 组件卸载时不断开连接,因为是单例
|
||||
});
|
||||
|
||||
return {
|
||||
// 从store获取状态
|
||||
...store,
|
||||
|
||||
// WebSocketManager方法
|
||||
connect: () => wsManager!.connect(),
|
||||
disconnect: () => wsManager!.disconnect(),
|
||||
sendCommand: (action: WebSocketCommand['action'], data?: unknown) => wsManager!.sendCommand(action, data),
|
||||
|
||||
// API方法
|
||||
getNetworkStatus: () => wsManager!.getNetworkStatus(),
|
||||
getConnections: () => wsManager!.getConnections(),
|
||||
getFirewallRules: () => wsManager!.getFirewallRules(),
|
||||
addFirewallRule: (rule: Omit<FirewallRule, 'id'>) => wsManager!.addFirewallRule(rule),
|
||||
removeFirewallRule: (ruleId: string) => wsManager!.removeFirewallRule(ruleId),
|
||||
toggleFirewallRule: (ruleId: string) => wsManager!.toggleFirewallRule(ruleId),
|
||||
|
||||
// 事件监听
|
||||
on: (event: string, handler: Function) => wsManager!.on(event, handler),
|
||||
off: (event: string, handler: Function) => wsManager!.off(event, handler),
|
||||
};
|
||||
}
|
||||
|
||||
// plugins/websocket.ts - Vue插件
|
||||
import type { App } from 'vue';
|
||||
import { EnhancedWebSocketManager } from '@/composables/useWebSocketEnhanced';
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
$ws: EnhancedWebSocketManager;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
install(app: App, options: { url?: string } = {}) {
|
||||
const wsManager = new EnhancedWebSocketManager(options.url);
|
||||
|
||||
app.config.globalProperties.$ws = wsManager;
|
||||
app.provide('websocket', wsManager);
|
||||
|
||||
// 应用关闭时断开连接
|
||||
window.addEventListener('beforeunload', () => {
|
||||
wsManager.disconnect();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,232 @@
|
||||
|
||||
// services/WebSocketManager.ts
|
||||
import { EventEmitter } from 'events'
|
||||
import type {
|
||||
WebSocketMessage,
|
||||
WebSocketConfig,
|
||||
ConnectionState,
|
||||
MessageType
|
||||
} from '@/types/websocket'
|
||||
|
||||
export class WebSocketManager extends EventEmitter {
|
||||
private ws: WebSocket | null = null
|
||||
private config: WebSocketConfig
|
||||
private reconnectTimer: NodeJS.Timeout | null = null
|
||||
private heartbeatTimer: NodeJS.Timeout | null = null
|
||||
/**
|
||||
* 重新连接尝试
|
||||
* @private
|
||||
*/
|
||||
private reconnectAttempts = 0
|
||||
private state: ConnectionState = ConnectionState.DISCONNECTED
|
||||
/**
|
||||
* 待处理请求
|
||||
* @private
|
||||
*/
|
||||
private pendingRequests = new Map<string, {
|
||||
resolve: (value: unknown) => void
|
||||
reject: (reason: unknown) => void
|
||||
timeout: NodeJS.Timeout
|
||||
}>()
|
||||
|
||||
constructor(config: WebSocketConfig) {
|
||||
super()
|
||||
this.config = config
|
||||
}
|
||||
|
||||
public connect(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.state === ConnectionState.CONNECTED) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
this.setState(ConnectionState.CONNECTING)
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(this.config.url)
|
||||
this.setupEventListeners(resolve, reject)
|
||||
} catch (error) {
|
||||
this.setState(ConnectionState.ERROR)
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
this.clearTimers()
|
||||
this.setState(ConnectionState.DISCONNECTED)
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close()
|
||||
this.ws = null
|
||||
}
|
||||
|
||||
this.rejectPendingRequests('Connection closed')
|
||||
}
|
||||
|
||||
public send<T>(type: MessageType, data?: T): void {
|
||||
if (!this.isConnected()) {
|
||||
throw new Error('WebSocket is not connected')
|
||||
}
|
||||
|
||||
const message: WebSocketMessage<T> = {
|
||||
type,
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
this.ws!.send(JSON.stringify(message))
|
||||
}
|
||||
|
||||
public sendRequest<T, R>(type: MessageType, data?: T): Promise<R> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isConnected()) {
|
||||
reject(new Error('WebSocket is not connected'))
|
||||
return
|
||||
}
|
||||
|
||||
const requestId = this.generateRequestId()
|
||||
const message: WebSocketMessage<T> = {
|
||||
type,
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
requestId
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
this.pendingRequests.delete(requestId)
|
||||
reject(new Error('Request timeout'))
|
||||
}, this.config.timeout)
|
||||
|
||||
this.pendingRequests.set(requestId, { resolve, reject, timeout })
|
||||
this.ws!.send(JSON.stringify(message))
|
||||
})
|
||||
}
|
||||
|
||||
public getState(): ConnectionState {
|
||||
return this.state
|
||||
}
|
||||
|
||||
public isConnected(): boolean {
|
||||
return this.state === ConnectionState.CONNECTED && this.ws?.readyState === WebSocket.OPEN
|
||||
}
|
||||
|
||||
private setupEventListeners(
|
||||
connectResolve: () => void,
|
||||
connectReject: (error: unknown) => void
|
||||
): void {
|
||||
if (!this.ws) return
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.setState(ConnectionState.CONNECTED)
|
||||
this.reconnectAttempts = 0
|
||||
this.startHeartbeat()
|
||||
connectResolve()
|
||||
this.emit('connected')
|
||||
}
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
this.setState(ConnectionState.DISCONNECTED)
|
||||
this.stopHeartbeat()
|
||||
this.emit('disconnected', event)
|
||||
|
||||
if (!event.wasClean && this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
||||
this.scheduleReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
this.setState(ConnectionState.ERROR)
|
||||
this.emit('error', error)
|
||||
connectReject(error)
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(event.data)
|
||||
this.handleMessage(message)
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleMessage(message: WebSocketMessage): void {
|
||||
// 处理响应消息
|
||||
if (message.requestId && this.pendingRequests.has(message.requestId)) {
|
||||
const request = this.pendingRequests.get(message.requestId)!
|
||||
clearTimeout(request.timeout)
|
||||
this.pendingRequests.delete(message.requestId)
|
||||
|
||||
if (message.type === MessageType.ERROR) {
|
||||
request.reject(new Error(message.data))
|
||||
} else {
|
||||
request.resolve(message.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 触发消息事件
|
||||
this.emit('message', message)
|
||||
this.emit(message.type, message.data)
|
||||
}
|
||||
|
||||
private scheduleReconnect(): void {
|
||||
if (this.reconnectTimer) return
|
||||
|
||||
this.setState(ConnectionState.RECONNECTING)
|
||||
this.reconnectAttempts++
|
||||
|
||||
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 30000)
|
||||
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.reconnectTimer = null
|
||||
this.connect().catch(() => {
|
||||
// 重连失败,会自动触发下一次重连
|
||||
})
|
||||
}, delay)
|
||||
}
|
||||
|
||||
private startHeartbeat(): void {
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.isConnected()) {
|
||||
this.send(MessageType.GET_SYSTEM_INFO)
|
||||
}
|
||||
}, this.config.heartbeatInterval)
|
||||
}
|
||||
|
||||
private stopHeartbeat(): void {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer)
|
||||
this.heartbeatTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
private clearTimers(): void {
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer)
|
||||
this.reconnectTimer = null
|
||||
}
|
||||
this.stopHeartbeat()
|
||||
}
|
||||
|
||||
private setState(state: ConnectionState): void {
|
||||
if (this.state !== state) {
|
||||
this.state = state
|
||||
this.emit('stateChange', state)
|
||||
}
|
||||
}
|
||||
|
||||
private generateRequestId(): string {
|
||||
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
}
|
||||
|
||||
private rejectPendingRequests(reason: string): void {
|
||||
this.pendingRequests.forEach(({ reject, timeout }) => {
|
||||
clearTimeout(timeout)
|
||||
reject(new Error(reason))
|
||||
})
|
||||
this.pendingRequests.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
|
||||
// stores/websocket.ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { WebSocketManager } from '@/services/WebSocketManager'
|
||||
import type {
|
||||
SystemInfo,
|
||||
NetworkStatus,
|
||||
FirewallRule,
|
||||
ConnectionState,
|
||||
MessageType
|
||||
} from '@/types/websocket'
|
||||
|
||||
export const useWebSocketStore = defineStore('websocket', () => {
|
||||
// 状态
|
||||
const wsManager = ref<WebSocketManager | null>(null)
|
||||
const connectionState = ref<ConnectionState>('disconnected' as ConnectionState)
|
||||
const systemInfo = ref<SystemInfo | null>(null)
|
||||
const networkStatus = ref<NetworkStatus[]>([])
|
||||
const firewallRules = ref<FirewallRule[]>([])
|
||||
const lastError = ref<string | null>(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
// 计算属性
|
||||
const isConnected = computed(() => connectionState.value === 'connected')
|
||||
const connectionCount = computed(() => systemInfo.value?.connectionCount ?? 0)
|
||||
const activeNetworkInterfaces = computed(() =>
|
||||
networkStatus.value.filter(status => status.isConnected)
|
||||
)
|
||||
|
||||
// 初始化WebSocket连接
|
||||
const initialize = async (url: string): Promise<void> => {
|
||||
if (wsManager.value) {
|
||||
wsManager.value.disconnect()
|
||||
}
|
||||
|
||||
wsManager.value = new WebSocketManager({
|
||||
url,
|
||||
reconnectInterval: 5000,
|
||||
maxReconnectAttempts: 10,
|
||||
heartbeatInterval: 30000,
|
||||
timeout: 10000
|
||||
})
|
||||
|
||||
setupEventListeners()
|
||||
|
||||
try {
|
||||
await wsManager.value.connect()
|
||||
} catch (error) {
|
||||
lastError.value = error instanceof Error ? error.message : 'Connection failed'
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
const setupEventListeners = (): void => {
|
||||
if (!wsManager.value) return
|
||||
|
||||
wsManager.value.on('stateChange', (state: ConnectionState) => {
|
||||
connectionState.value = state
|
||||
})
|
||||
|
||||
wsManager.value.on('error', (error: Error) => {
|
||||
lastError.value = error.message
|
||||
})
|
||||
|
||||
// 监听数据更新事件
|
||||
wsManager.value.on('network_status_response', (data: NetworkStatus[]) => {
|
||||
networkStatus.value = data
|
||||
})
|
||||
|
||||
wsManager.value.on('firewall_rules_response', (data: FirewallRule[]) => {
|
||||
firewallRules.value = data
|
||||
})
|
||||
|
||||
wsManager.value.on('system_info_response', (data: SystemInfo) => {
|
||||
systemInfo.value = data
|
||||
networkStatus.value = data.networkInterfaces
|
||||
firewallRules.value = data.firewallRules
|
||||
})
|
||||
|
||||
// 监听实时更新事件
|
||||
wsManager.value.on('network_status_changed', (data: NetworkStatus[]) => {
|
||||
networkStatus.value = data
|
||||
})
|
||||
|
||||
wsManager.value.on('firewall_rule_changed', (data: FirewallRule[]) => {
|
||||
firewallRules.value = data
|
||||
})
|
||||
|
||||
wsManager.value.on('system_alert', (data: { message: string; level: string }) => {
|
||||
console.warn('System Alert:', data)
|
||||
})
|
||||
}
|
||||
|
||||
// 数据获取方法
|
||||
const fetchSystemInfo = async (): Promise<SystemInfo> => {
|
||||
if (!wsManager.value || !isConnected.value) {
|
||||
throw new Error('WebSocket not connected')
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await wsManager.value.sendRequest<void, SystemInfo>(
|
||||
'get_system_info' as MessageType
|
||||
)
|
||||
systemInfo.value = data
|
||||
return data
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchNetworkStatus = async (): Promise<NetworkStatus[]> => {
|
||||
if (!wsManager.value || !isConnected.value) {
|
||||
throw new Error('WebSocket not connected')
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await wsManager.value.sendRequest<void, NetworkStatus[]>(
|
||||
'get_network_status' as MessageType
|
||||
)
|
||||
networkStatus.value = data
|
||||
return data
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchFirewallRules = async (): Promise<FirewallRule[]> => {
|
||||
if (!wsManager.value || !isConnected.value) {
|
||||
throw new Error('WebSocket not connected')
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await wsManager.value.sendRequest<void, FirewallRule[]>(
|
||||
'get_firewall_rules' as MessageType
|
||||
)
|
||||
firewallRules.value = data
|
||||
return data
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 防火墙操作方法
|
||||
const addFirewallRule = async (rule: Omit<FirewallRule, 'id'>): Promise<void> => {
|
||||
if (!wsManager.value || !isConnected.value) {
|
||||
throw new Error('WebSocket not connected')
|
||||
}
|
||||
|
||||
await wsManager.value.sendRequest<Omit<FirewallRule, 'id'>, void>(
|
||||
'add_firewall_rule' as MessageType,
|
||||
rule
|
||||
)
|
||||
}
|
||||
|
||||
const updateFirewallRule = async (rule: FirewallRule): Promise<void> => {
|
||||
if (!wsManager.value || !isConnected.value) {
|
||||
throw new Error('WebSocket not connected')
|
||||
}
|
||||
|
||||
await wsManager.value.sendRequest<FirewallRule, void>(
|
||||
'update_firewall_rule' as MessageType,
|
||||
rule
|
||||
)
|
||||
}
|
||||
|
||||
const deleteFirewallRule = async (ruleId: string): Promise<void> => {
|
||||
if (!wsManager.value || !isConnected.value) {
|
||||
throw new Error('WebSocket not connected')
|
||||
}
|
||||
|
||||
await wsManager.value.sendRequest<{ id: string }, void>(
|
||||
'delete_firewall_rule' as MessageType,
|
||||
{ id: ruleId }
|
||||
)
|
||||
}
|
||||
|
||||
const toggleFirewallRule = async (ruleId: string): Promise<void> => {
|
||||
if (!wsManager.value || !isConnected.value) {
|
||||
throw new Error('WebSocket not connected')
|
||||
}
|
||||
|
||||
await wsManager.value.sendRequest<{ id: string }, void>(
|
||||
'toggle_firewall_rule' as MessageType,
|
||||
{ id: ruleId }
|
||||
)
|
||||
}
|
||||
|
||||
// 断开连接
|
||||
const disconnect = (): void => {
|
||||
if (wsManager.value) {
|
||||
wsManager.value.disconnect()
|
||||
wsManager.value = null
|
||||
}
|
||||
connectionState.value = 'disconnected' as ConnectionState
|
||||
}
|
||||
|
||||
// 清除错误
|
||||
const clearError = (): void => {
|
||||
lastError.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
connectionState,
|
||||
systemInfo,
|
||||
networkStatus,
|
||||
firewallRules,
|
||||
lastError,
|
||||
isLoading,
|
||||
|
||||
// 计算属性
|
||||
isConnected,
|
||||
connectionCount,
|
||||
activeNetworkInterfaces,
|
||||
|
||||
// 方法
|
||||
initialize,
|
||||
fetchSystemInfo,
|
||||
fetchNetworkStatus,
|
||||
fetchFirewallRules,
|
||||
addFirewallRule,
|
||||
updateFirewallRule,
|
||||
deleteFirewallRule,
|
||||
toggleFirewallRule,
|
||||
disconnect,
|
||||
clearError
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
// types/websocket.ts
|
||||
export interface WebSocketMessage<T = unknown> {
|
||||
type: string
|
||||
data: T
|
||||
timestamp: number
|
||||
requestId?: string
|
||||
}
|
||||
|
||||
export interface NetworkStatus {
|
||||
interfaceName: string
|
||||
isConnected: boolean
|
||||
ipAddress: string
|
||||
macAddress: string
|
||||
speed: number
|
||||
bytesReceived: number
|
||||
bytesSent: number
|
||||
}
|
||||
|
||||
export interface FirewallRule {
|
||||
id: string
|
||||
name: string
|
||||
direction: 'inbound' | 'outbound'
|
||||
action: 'allow' | 'block'
|
||||
protocol: 'tcp' | 'udp' | 'icmp' | 'any'
|
||||
localPort?: string
|
||||
remotePort?: string
|
||||
localAddress?: string
|
||||
remoteAddress?: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface SystemInfo {
|
||||
cpuUsage: number
|
||||
memoryUsage: number
|
||||
networkInterfaces: NetworkStatus[]
|
||||
firewallRules: FirewallRule[]
|
||||
connectionCount: number
|
||||
}
|
||||
|
||||
export enum MessageType {
|
||||
// 数据获取相关
|
||||
GET_NETWORK_STATUS = 'get_network_status',
|
||||
GET_FIREWALL_RULES = 'get_firewall_rules',
|
||||
GET_SYSTEM_INFO = 'get_system_info',
|
||||
|
||||
// 数据响应相关
|
||||
NETWORK_STATUS_RESPONSE = 'network_status_response',
|
||||
FIREWALL_RULES_RESPONSE = 'firewall_rules_response',
|
||||
SYSTEM_INFO_RESPONSE = 'system_info_response',
|
||||
|
||||
// 操作相关
|
||||
ADD_FIREWALL_RULE = 'add_firewall_rule',
|
||||
UPDATE_FIREWALL_RULE = 'update_firewall_rule',
|
||||
DELETE_FIREWALL_RULE = 'delete_firewall_rule',
|
||||
TOGGLE_FIREWALL_RULE = 'toggle_firewall_rule',
|
||||
|
||||
// 通知相关
|
||||
NETWORK_STATUS_CHANGED = 'network_status_changed',
|
||||
FIREWALL_RULE_CHANGED = 'firewall_rule_changed',
|
||||
SYSTEM_ALERT = 'system_alert',
|
||||
ERROR = 'error',
|
||||
SUCCESS = 'success'
|
||||
}
|
||||
|
||||
export interface WebSocketConfig {
|
||||
url: string
|
||||
/**
|
||||
* 重新连接间隔
|
||||
*/
|
||||
reconnectInterval: number
|
||||
/**
|
||||
* 最大重新连接尝试次数
|
||||
*/
|
||||
maxReconnectAttempts: number
|
||||
/**
|
||||
* 心跳间隔
|
||||
*/
|
||||
heartbeatInterval: number
|
||||
timeout: number
|
||||
}
|
||||
|
||||
export enum ConnectionState {
|
||||
CONNECTING = 'connecting',
|
||||
CONNECTED = 'connected',
|
||||
DISCONNECTED = 'disconnected',
|
||||
RECONNECTING = 'reconnecting',
|
||||
ERROR = 'error'
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Time:2025/5/25 20:41 47
|
||||
* Name:websocketHelpers.ts
|
||||
* Path:src/utils
|
||||
* ProjectName:ClientApp
|
||||
* Author:charlatans
|
||||
*
|
||||
* Il n'ya qu'un héroïsme au monde :
|
||||
* c'est de voir le monde tel qu'il est et de l'aimer.
|
||||
*/
|
||||
|
||||
|
||||
// utils/websocketHelpers.ts - WebSocket辅助工具
|
||||
export class WebSocketRetryManager {
|
||||
private retryAttempts = 0;
|
||||
private maxRetries: number;
|
||||
private baseDelay: number;
|
||||
private maxDelay: number;
|
||||
|
||||
constructor(maxRetries = 5, baseDelay = 1000, maxDelay = 30000) {
|
||||
this.maxRetries = maxRetries;
|
||||
this.baseDelay = baseDelay;
|
||||
this.maxDelay = maxDelay;
|
||||
}
|
||||
|
||||
shouldRetry(): boolean {
|
||||
return this.retryAttempts < this.maxRetries;
|
||||
}
|
||||
|
||||
getNextDelay(): number {
|
||||
const delay = Math.min(this.baseDelay * Math.pow(2, this.retryAttempts), this.maxDelay);
|
||||
this.retryAttempts++;
|
||||
return delay;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.retryAttempts = 0;
|
||||
}
|
||||
|
||||
getCurrentAttempt(): number {
|
||||
return this.retryAttempts;
|
||||
}
|
||||
}
|
||||
|
||||
export class WebSocketMessageQueue {
|
||||
private queue: Array<{ message: unknown; resolve: Function; reject: Function }> = [];
|
||||
private isProcessing = false;
|
||||
|
||||
async enqueue(message: unknown): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queue.push({ message, resolve, reject });
|
||||
this.processQueue();
|
||||
});
|
||||
}
|
||||
|
||||
private async processQueue() {
|
||||
if (this.isProcessing || this.queue.length === 0) return;
|
||||
|
||||
this.isProcessing = true;
|
||||
|
||||
while (this.queue.length > 0) {
|
||||
const item = this.queue.shift();
|
||||
if (item) {
|
||||
try {
|
||||
// 这里应该调用实际的发送方法
|
||||
await this.sendMessage(item.message);
|
||||
item.resolve();
|
||||
} catch (error) {
|
||||
item.reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
private async sendMessage(message: unknown): Promise<void> {
|
||||
// 由WebSocketManager实现
|
||||
throw new Error('sendMessage method should be implemented by WebSocketManager');
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.queue.forEach(item => item.reject(new Error('Queue cleared')));
|
||||
this.queue = [];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user