前端服务共享

This commit is contained in:
2025-12-11 14:21:36 +08:00
parent fa3dbe0496
commit 5ee9770747
46 changed files with 3732 additions and 1782 deletions

View File

@@ -0,0 +1,192 @@
// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68
import {loadShare} from "@module-federation/runtime";
const importMap = {
"@element-plus/icons-vue": async () => {
let pkg = await import("__mf__virtual/shared__prebuild___mf_0_element_mf_2_plus_mf_1_icons_mf_2_vue__prebuild__.js");
return pkg;
}
,
"axios": async () => {
let pkg = await import("__mf__virtual/shared__prebuild__axios__prebuild__.js");
return pkg;
}
,
"element-plus": async () => {
let pkg = await import("__mf__virtual/shared__prebuild__element_mf_2_plus__prebuild__.js");
return pkg;
}
,
"vue": async () => {
let pkg = await import("__mf__virtual/shared__prebuild__vue__prebuild__.js");
return pkg;
}
,
"vue-router": async () => {
let pkg = await import("__mf__virtual/shared__prebuild__vue_mf_2_router__prebuild__.js");
return pkg;
}
}
const usedShared = {
"@element-plus/icons-vue": {
name: "@element-plus/icons-vue",
version: "2.3.2",
scope: ["default"],
loaded: false,
from: "shared",
async get () {
if (false) {
throw new Error(`Shared module '${"@element-plus/icons-vue"}' must be provided by host`);
}
usedShared["@element-plus/icons-vue"].loaded = true
const {"@element-plus/icons-vue": pkgDynamicImport} = importMap
const res = await pkgDynamicImport()
const exportModule = {...res}
// All npm packages pre-built by vite will be converted to esm
Object.defineProperty(exportModule, "__esModule", {
value: true,
enumerable: false
})
return function () {
return exportModule
}
},
shareConfig: {
singleton: false,
requiredVersion: "^2.3.2",
}
}
,
"axios": {
name: "axios",
version: "1.13.2",
scope: ["default"],
loaded: false,
from: "shared",
async get () {
if (false) {
throw new Error(`Shared module '${"axios"}' must be provided by host`);
}
usedShared["axios"].loaded = true
const {"axios": pkgDynamicImport} = importMap
const res = await pkgDynamicImport()
const exportModule = {...res}
// All npm packages pre-built by vite will be converted to esm
Object.defineProperty(exportModule, "__esModule", {
value: true,
enumerable: false
})
return function () {
return exportModule
}
},
shareConfig: {
singleton: false,
requiredVersion: "^1.13.2",
}
}
,
"element-plus": {
name: "element-plus",
version: "2.12.0",
scope: ["default"],
loaded: false,
from: "shared",
async get () {
if (false) {
throw new Error(`Shared module '${"element-plus"}' must be provided by host`);
}
usedShared["element-plus"].loaded = true
const {"element-plus": pkgDynamicImport} = importMap
const res = await pkgDynamicImport()
const exportModule = {...res}
// All npm packages pre-built by vite will be converted to esm
Object.defineProperty(exportModule, "__esModule", {
value: true,
enumerable: false
})
return function () {
return exportModule
}
},
shareConfig: {
singleton: false,
requiredVersion: "^2.12.0",
}
}
,
"vue": {
name: "vue",
version: "3.5.25",
scope: ["default"],
loaded: false,
from: "shared",
async get () {
if (false) {
throw new Error(`Shared module '${"vue"}' must be provided by host`);
}
usedShared["vue"].loaded = true
const {"vue": pkgDynamicImport} = importMap
const res = await pkgDynamicImport()
const exportModule = {...res}
// All npm packages pre-built by vite will be converted to esm
Object.defineProperty(exportModule, "__esModule", {
value: true,
enumerable: false
})
return function () {
return exportModule
}
},
shareConfig: {
singleton: false,
requiredVersion: "^3.5.25",
}
}
,
"vue-router": {
name: "vue-router",
version: "4.6.3",
scope: ["default"],
loaded: false,
from: "shared",
async get () {
if (false) {
throw new Error(`Shared module '${"vue-router"}' must be provided by host`);
}
usedShared["vue-router"].loaded = true
const {"vue-router": pkgDynamicImport} = importMap
const res = await pkgDynamicImport()
const exportModule = {...res}
// All npm packages pre-built by vite will be converted to esm
Object.defineProperty(exportModule, "__esModule", {
value: true,
enumerable: false
})
return function () {
return exportModule
}
},
shareConfig: {
singleton: false,
requiredVersion: "^4.6.3",
}
}
}
const usedRemotes = [
]
export {
usedShared,
usedRemotes
}

View File

@@ -5,24 +5,24 @@
"private": true,
"scripts": {
"dev": "vite",
"dev:demo": "vite",
"build": "run-p build:*",
"build:esm": "vite build --mode esm",
"build:federation": "vite build --mode federation",
"preview": "vite preview"
"build": "vite build",
"preview": "vite preview",
"serve": "node server.js"
},
"dependencies": {
"vue": "^3.5.13",
"ofetch": "^1.4.1",
"vue-router": "^4.5.0",
"@element-plus/icons-vue": "^2.3.2",
"cors": "^2.8.5",
"element-plus": "^2.12.0",
"@element-plus/icons-vue": "^2.3.2"
"express": "^4.18.2",
"ofetch": "^1.4.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@module-federation/vite": "^1.9.3",
"@types/node": "^20.10.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@originjs/vite-plugin-federation": "^1.3.6",
"npm-run-all": "^4.1.5",
"sass": "^1.80.6",
"sass-embedded": "^1.80.6",
@@ -30,7 +30,7 @@
"vite": "^6.0.3"
},
"peerDependencies": {
"vue": "^3.5.13",
"typescript": ">=5.0.0"
"typescript": ">=5.0.0",
"vue": "^3.5.13"
}
}
}

View File

@@ -8,9 +8,15 @@ dependencies:
'@element-plus/icons-vue':
specifier: ^2.3.2
version: 2.3.2(vue@3.5.25)
cors:
specifier: ^2.8.5
version: 2.8.5
element-plus:
specifier: ^2.12.0
version: 2.12.0(vue@3.5.25)
express:
specifier: ^4.18.2
version: 4.22.1
ofetch:
specifier: ^1.4.1
version: 1.5.1
@@ -22,9 +28,9 @@ dependencies:
version: 4.6.3(vue@3.5.25)
devDependencies:
'@originjs/vite-plugin-federation':
specifier: ^1.3.6
version: 1.4.1
'@module-federation/vite':
specifier: ^1.9.3
version: 1.9.3
'@types/node':
specifier: ^20.10.0
version: 20.19.25
@@ -603,12 +609,41 @@ packages:
'@jridgewell/sourcemap-codec': 1.5.5
dev: true
/@originjs/vite-plugin-federation@1.4.1:
resolution: {integrity: sha512-Uo08jW5pj1t58OUKuZNkmzcfTN2pqeVuAWCCiKf/75/oll4Efq4cHOqSE1FXMlvwZNGDziNdDyBbQ5IANem3CQ==}
engines: {node: '>=14.0.0', pnpm: '>=7.0.1'}
/@module-federation/error-codes@0.21.6:
resolution: {integrity: sha512-MLJUCQ05KnoVl8xd6xs9a5g2/8U+eWmVxg7xiBMeR0+7OjdWUbHwcwgVFatRIwSZvFgKHfWEiI7wsU1q1XbTRQ==}
dev: true
/@module-federation/runtime-core@0.21.6:
resolution: {integrity: sha512-5Hd1Y5qp5lU/aTiK66lidMlM/4ji2gr3EXAtJdreJzkY+bKcI5+21GRcliZ4RAkICmvdxQU5PHPL71XmNc7Lsw==}
dependencies:
estree-walker: 3.0.3
magic-string: 0.27.0
'@module-federation/error-codes': 0.21.6
'@module-federation/sdk': 0.21.6
dev: true
/@module-federation/runtime@0.21.6:
resolution: {integrity: sha512-+caXwaQqwTNh+CQqyb4mZmXq7iEemRDrTZQGD+zyeH454JAYnJ3s/3oDFizdH6245pk+NiqDyOOkHzzFQorKhQ==}
dependencies:
'@module-federation/error-codes': 0.21.6
'@module-federation/runtime-core': 0.21.6
'@module-federation/sdk': 0.21.6
dev: true
/@module-federation/sdk@0.21.6:
resolution: {integrity: sha512-x6hARETb8iqHVhEsQBysuWpznNZViUh84qV2yE7AD+g7uIzHKiYdoWqj10posbo5XKf/147qgWDzKZoKoEP2dw==}
dev: true
/@module-federation/vite@1.9.3:
resolution: {integrity: sha512-MV6XI3FX6okEMJ7FdmvFmYuu7DygRoLljKT8atrBwFhlttsgBbswpqMj4P4Fs/X+pFmbIi/ntFzVhsrG0qQnGQ==}
dependencies:
'@module-federation/runtime': 0.21.6
'@module-federation/sdk': 0.21.6
'@rollup/pluginutils': 5.3.0
defu: 6.1.4
estree-walker: 2.0.2
magic-string: 0.30.21
pathe: 1.1.2
transitivePeerDependencies:
- rollup
dev: true
/@parcel/watcher-android-arm64@2.5.1:
@@ -764,6 +799,20 @@ packages:
resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
dev: true
/@rollup/pluginutils@5.3.0:
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.3
dev: true
/@rollup/rollup-android-arm-eabi@4.53.3:
resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
cpu: [arm]
@@ -1142,6 +1191,14 @@ packages:
- vue
dev: false
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
dev: false
/ansi-styles@3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
@@ -1157,6 +1214,10 @@ packages:
is-array-buffer: 3.0.5
dev: true
/array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
dev: false
/arraybuffer.prototype.slice@1.0.4:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
@@ -1195,6 +1256,26 @@ packages:
hasBin: true
dev: true
/body-parser@1.20.4:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.1
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.14.0
raw-body: 2.5.3
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
dependencies:
@@ -1227,13 +1308,17 @@ packages:
resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==}
dev: true
/bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
dev: false
/call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
dev: true
/call-bind@1.0.8:
resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
@@ -1251,7 +1336,6 @@ packages:
dependencies:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0
dev: true
/caniuse-lite@1.0.30001759:
resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==}
@@ -1291,10 +1375,39 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
dependencies:
safe-buffer: 5.2.1
dev: false
/content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
dev: false
/convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: true
/cookie-signature@1.0.7:
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
dev: false
/cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
dev: false
/cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
dependencies:
object-assign: 4.1.1
vary: 1.1.2
dev: false
/cross-spawn@6.0.6:
resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
engines: {node: '>=4.8'}
@@ -1340,6 +1453,17 @@ packages:
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
dev: false
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.0.0
dev: false
/debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -1370,10 +1494,24 @@ packages:
object-keys: 1.1.1
dev: true
/defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
dev: true
/depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dev: false
/destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
dev: false
/destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dev: false
/detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
@@ -1389,7 +1527,10 @@ packages:
call-bind-apply-helpers: 1.0.2
es-errors: 1.3.0
gopd: 1.2.0
dev: true
/ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
/electron-to-chromium@1.5.266:
resolution: {integrity: sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==}
@@ -1419,6 +1560,16 @@ packages:
- '@vue/composition-api'
dev: false
/encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
dev: false
/encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
dev: false
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@@ -1492,19 +1643,16 @@ packages:
/es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
dev: true
/es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
dev: true
/es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
dependencies:
es-errors: 1.3.0
dev: true
/es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
@@ -1564,6 +1712,10 @@ packages:
engines: {node: '>=6'}
dev: true
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@@ -1572,11 +1724,49 @@ packages:
/estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
/estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
/etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
dev: false
/express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
engines: {node: '>= 0.10.0'}
dependencies:
'@types/estree': 1.0.8
dev: true
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.4
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.0.7
debug: 2.6.9
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.3.2
fresh: 0.5.2
http-errors: 2.0.1
merge-descriptors: 1.0.3
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
path-to-regexp: 0.1.12
proxy-addr: 2.0.7
qs: 6.14.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.19.1
serve-static: 1.16.2
setprototypeof: 1.2.0
statuses: 2.0.2
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
dev: false
/fdir@6.5.0(picomatch@4.0.3):
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
@@ -1599,6 +1789,21 @@ packages:
dev: true
optional: true
/finalhandler@1.3.2:
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
engines: {node: '>= 0.8'}
dependencies:
debug: 2.6.9
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.2
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
@@ -1606,6 +1811,16 @@ packages:
is-callable: 1.2.7
dev: true
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
dev: false
/fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
dev: false
/fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -1616,7 +1831,6 @@ packages:
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
dev: true
/function.prototype.name@1.1.8:
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
@@ -1658,7 +1872,6 @@ packages:
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
dev: true
/get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
@@ -1666,7 +1879,6 @@ packages:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
dev: true
/get-symbol-description@1.1.0:
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
@@ -1688,7 +1900,6 @@ packages:
/gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
dev: true
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -1725,7 +1936,6 @@ packages:
/has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
dev: true
/has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
@@ -1739,16 +1949,48 @@ packages:
engines: {node: '>= 0.4'}
dependencies:
function-bind: 1.1.2
dev: true
/hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
/http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.1
toidentifier: 1.0.1
dev: false
/http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.2
toidentifier: 1.0.1
dev: false
/iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/immutable@5.1.4:
resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
dev: true
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: false
/internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
@@ -1758,6 +2000,11 @@ packages:
side-channel: 1.1.0
dev: true
/ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
dev: false
/is-array-buffer@3.0.5:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
@@ -2015,13 +2262,6 @@ packages:
yallist: 3.1.1
dev: true
/magic-string@0.27.0:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
dev: true
/magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
dependencies:
@@ -2030,7 +2270,11 @@ packages:
/math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
dev: true
/media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
dev: false
/memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
@@ -2041,6 +2285,15 @@ packages:
engines: {node: '>= 0.10.0'}
dev: true
/merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
dev: false
/methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
dev: false
/micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -2051,21 +2304,47 @@ packages:
dev: true
optional: true
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
dev: false
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.12
dev: true
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
/ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
/negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
dev: false
/nice-try@1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
dev: true
@@ -2113,10 +2392,14 @@ packages:
string.prototype.padend: 3.1.6
dev: true
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
dev: false
/object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
dev: true
/object-keys@1.1.1:
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
@@ -2143,6 +2426,13 @@ packages:
ufo: 1.6.1
dev: false
/on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
dependencies:
ee-first: 1.1.1
dev: false
/own-keys@1.0.1:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
@@ -2160,6 +2450,11 @@ packages:
json-parse-better-errors: 1.0.2
dev: true
/parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
dev: false
/path-key@2.0.1:
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
engines: {node: '>=4'}
@@ -2169,6 +2464,10 @@ packages:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
dev: false
/path-type@3.0.0:
resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
engines: {node: '>=4'}
@@ -2176,6 +2475,10 @@ packages:
pify: 3.0.0
dev: true
/pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
dev: true
/picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2215,6 +2518,36 @@ packages:
picocolors: 1.1.1
source-map-js: 1.2.1
/proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
dev: false
/qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
dependencies:
side-channel: 1.1.0
dev: false
/range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
dev: false
/raw-body@2.5.3:
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
engines: {node: '>= 0.8'}
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
iconv-lite: 0.4.24
unpipe: 1.0.0
dev: false
/read-pkg@3.0.0:
resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
engines: {node: '>=4'}
@@ -2314,6 +2647,10 @@ packages:
isarray: 2.0.5
dev: true
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'}
@@ -2331,6 +2668,10 @@ packages:
is-regex: 1.2.1
dev: true
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
/sass-embedded-all-unknown@1.93.3:
resolution: {integrity: sha512-3okGgnE41eg+CPLtAPletu6nQ4N0ij7AeW+Sl5Km4j29XcmqZQeFwYjHe1AlKTEgLi/UAONk1O8i8/lupeKMbw==}
cpu: ['!arm', '!arm64', '!riscv64', '!x64']
@@ -2571,6 +2912,60 @@ packages:
hasBin: true
dev: true
/send@0.19.0:
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'}
dependencies:
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
encodeurl: 1.0.2
escape-html: 1.0.3
etag: 1.8.1
fresh: 0.5.2
http-errors: 2.0.0
mime: 1.6.0
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.1
transitivePeerDependencies:
- supports-color
dev: false
/send@0.19.1:
resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==}
engines: {node: '>= 0.8.0'}
dependencies:
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 0.5.2
http-errors: 2.0.0
mime: 1.6.0
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.1
transitivePeerDependencies:
- supports-color
dev: false
/serve-static@1.16.2:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 0.19.0
transitivePeerDependencies:
- supports-color
dev: false
/set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -2602,6 +2997,10 @@ packages:
es-object-atoms: 1.1.1
dev: true
/setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
dev: false
/shebang-command@1.2.0:
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
engines: {node: '>=0.10.0'}
@@ -2625,7 +3024,6 @@ packages:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
dev: true
/side-channel-map@1.0.1:
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
@@ -2635,7 +3033,6 @@ packages:
es-errors: 1.3.0
get-intrinsic: 1.3.0
object-inspect: 1.13.4
dev: true
/side-channel-weakmap@1.0.2:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
@@ -2646,7 +3043,6 @@ packages:
get-intrinsic: 1.3.0
object-inspect: 1.13.4
side-channel-map: 1.0.1
dev: true
/side-channel@1.1.0:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
@@ -2657,7 +3053,6 @@ packages:
side-channel-list: 1.0.0
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
dev: true
/source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
@@ -2685,6 +3080,16 @@ packages:
resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==}
dev: true
/statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
dev: false
/statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
dev: false
/stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@@ -2788,10 +3193,23 @@ packages:
dev: true
optional: true
/toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
dev: false
/tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
dev: true
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
dependencies:
media-typer: 0.3.0
mime-types: 2.1.35
dev: false
/typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -2860,6 +3278,11 @@ packages:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
dev: true
/unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
dev: false
/update-browserslist-db@1.2.2(browserslist@4.28.1):
resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==}
hasBin: true
@@ -2871,6 +3294,11 @@ packages:
picocolors: 1.1.1
dev: true
/utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
dev: false
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies:
@@ -2882,6 +3310,11 @@ packages:
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
dev: true
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: false
/vite@6.4.1(@types/node@20.19.25)(sass-embedded@1.93.3)(sass@1.94.2):
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}

View File

@@ -0,0 +1,73 @@
/**
* Shared 模块静态文件服务器
* 提供构建后的 ES Module 文件供其他应用使用
*
* 使用方式:
* 1. npm run build:esm # 先构建
* 2. node server.js # 启动服务器
*/
import express from 'express'
import cors from 'cors'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const app = express()
const PORT = 5000
// 启用 CORS
app.use(cors())
// 静态文件服务:/shared/* -> dist/esm/*
app.use('/shared', express.static(path.join(__dirname, 'dist/esm'), {
setHeaders: (res, filepath) => {
// 设置正确的 MIME 类型
if (filepath.endsWith('.js')) {
res.setHeader('Content-Type', 'application/javascript; charset=utf-8')
}
// 允许跨域
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')
}
}))
// 健康检查
app.get('/health', (req, res) => {
res.json({
status: 'ok',
port: PORT,
modules: ['components', 'utils', 'api', 'composables', 'types']
})
})
// 模块列表
app.get('/modules', (req, res) => {
res.json({
modules: {
components: '/shared/components.js',
utils: '/shared/utils.js',
api: '/shared/api.js',
composables: '/shared/composables.js',
types: '/shared/types.js'
}
})
})
// 启动服务器
app.listen(PORT, () => {
console.log(`\n🚀 Shared 模块服务器已启动!`)
console.log(``)
console.log(`📦 提供以下模块:`)
console.log(` - http://localhost:${PORT}/shared/components.js`)
console.log(` - http://localhost:${PORT}/shared/utils.js`)
console.log(` - http://localhost:${PORT}/shared/api.js`)
console.log(` - http://localhost:${PORT}/shared/composables.js`)
console.log(` - http://localhost:${PORT}/shared/types.js`)
console.log(``)
console.log(`🔍 健康检查http://localhost:${PORT}/health`)
console.log(`📋 模块列表http://localhost:${PORT}/modules`)
console.log(``)
})

View File

@@ -1,6 +1,92 @@
import { api } from '@/api/index'
import type { LoginParam, LoginDomain } from '@/types'
/**
* 认证 API
* 通过 Gateway (8180) 访问 Auth Service (8181)
* 路由规则:/urban-lifeline/auth/** → auth-service/urban-lifeline/auth/**
*/
export const authAPI = {
baseUrl: "/auth",
baseUrl: "/urban-lifeline/auth",
/**
* 用户登录
* @param loginParam 登录参数
* @returns 登录结果(包含 token 和用户信息)
*/
login(loginParam: LoginParam) {
return api.post<LoginDomain>(`${this.baseUrl}/login`, loginParam)
},
/**
* 用户登出
* @returns 登出结果
*/
logout() {
return api.post<LoginDomain>(`${this.baseUrl}/logout`)
},
/**
* 获取验证码(统一接口)
* @param loginParam 登录参数(包含验证码类型)
* @returns 验证码结果
*/
getCaptcha(loginParam: LoginParam) {
return api.post<LoginDomain>(`${this.baseUrl}/captcha`, loginParam)
},
/**
* 刷新 Token
* @returns 新的登录信息
*/
refreshToken() {
return api.post<LoginDomain>(`${this.baseUrl}/refresh`)
},
/**
* 发送邮箱验证码
* @param email 邮箱地址
* @returns 发送结果
*/
sendEmailCode(email: string) {
return api.post<LoginDomain>(`${this.baseUrl}/send-email-code`, { email })
},
/**
* 发送短信验证码
* @param phone 手机号
* @returns 发送结果
*/
sendSmsCode(phone: string) {
return api.post<LoginDomain>(`${this.baseUrl}/send-sms-code`, { phone })
},
/**
* 用户注册
* @param registerData 注册数据
* @returns 注册结果(成功后自动登录,返回 token
*/
register(registerData: {
registerType: 'username' | 'phone' | 'email'
username?: string
phone?: string
email?: string
password: string
confirmPassword: string
smsCode?: string
emailCode?: string
smsSessionId?: string
emailSessionId?: string
studentId?: string
}) {
return api.post<LoginDomain>(`${this.baseUrl}/register`, registerData)
},
/**
* 健康检查
* @returns 健康状态
*/
health() {
return api.get<string>(`${this.baseUrl}/health`)
}
}

View File

@@ -0,0 +1,274 @@
# 加密工具
## AES-256-GCM 加密
### 概述
前端 AES 加密工具,与后端 `AesEncryptUtil` 保持一致,用于敏感信息传输加密。
### 使用场景
1. **密码传输**:登录时加密密码
2. **敏感信息**:加密手机号、身份证号等
### 快速开始
#### 1. 初始化(应用启动时)
```typescript
import { initAesEncrypt } from '@/utils/crypto'
// 在 main.ts 中初始化
const AES_SECRET_KEY = '1234567890qwer' // 与后端配置保持一致
await initAesEncrypt(AES_SECRET_KEY)
```
#### 2. 使用加密
```typescript
import { getAesInstance } from '@/utils/crypto'
// 获取加密实例
const aes = getAesInstance()
// 加密密码
const encryptedPassword = await aes.encryptPassword('myPassword123')
// 加密手机号
const encryptedPhone = await aes.encryptPhone('13812345678')
```
### 完整示例
#### 登录时加密密码
```typescript
import { authAPI } from '@/api/auth'
import { getAesInstance } from '@/utils/crypto'
async function login(username: string, password: string) {
try {
// 加密密码
const aes = getAesInstance()
const encryptedPassword = await aes.encryptPassword(password)
// 发送登录请求
const response = await authAPI.login({
username,
password: encryptedPassword,
loginType: 'password'
})
return response.data
} catch (error) {
console.error('登录失败:', error)
throw error
}
}
```
#### 手机号注册
```typescript
import { authAPI } from '@/api/auth'
import { getAesInstance } from '@/utils/crypto'
async function register(phone: string, password: string, smsCode: string) {
try {
const aes = getAesInstance()
// 加密密码
const encryptedPassword = await aes.encryptPassword(password)
// 加密手机号
const encryptedPhone = await aes.encryptPhone(phone)
// 发送注册请求
const response = await authAPI.register({
registerType: 'phone',
phone: encryptedPhone,
password: encryptedPassword,
confirmPassword: encryptedPassword,
smsCode,
smsSessionId: 'session-id-from-captcha'
})
return response.data
} catch (error) {
console.error('注册失败:', error)
throw error
}
}
```
### 工具函数
#### 数据脱敏
```typescript
import { AesUtils } from '@/utils/crypto'
// 脱敏手机号
const masked = AesUtils.maskPhone('13812345678')
// 输出138****5678
// 脱敏身份证号
const maskedId = AesUtils.maskIdCard('110101199001011234')
// 输出110101********1234
// 脱敏邮箱
const maskedEmail = AesUtils.maskEmail('test@example.com')
// 输出t***@example.com
```
### API 参考
#### AesEncryptUtil 类
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `init()` | - | `Promise<void>` | 初始化密钥(必须先调用) |
| `encrypt(plaintext)` | `string` | `Promise<string>` | 加密字符串 |
| `decrypt(ciphertext)` | `string` | `Promise<string>` | 解密字符串 |
| `encryptPassword(password)` | `string` | `Promise<string>` | 加密密码 |
| `encryptPhone(phone)` | `string` | `Promise<string>` | 加密手机号 |
| `decryptPhone(encrypted)` | `string` | `Promise<string>` | 解密手机号 |
| `encryptIdCard(idCard)` | `string` | `Promise<string>` | 加密身份证号 |
| `decryptIdCard(encrypted)` | `string` | `Promise<string>` | 解密身份证号 |
#### AesUtils 静态方法
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `maskPhone(phone)` | `string` | `string` | 脱敏手机号 |
| `maskIdCard(idCard)` | `string` | `string` | 脱敏身份证号 |
| `maskEmail(email)` | `string` | `string` | 脱敏邮箱 |
#### 全局函数
| 函数 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `initAesEncrypt(secretKey)` | `string` | `Promise<void>` | 初始化 AES 加密(应用启动时调用) |
| `getAesInstance()` | - | `AesEncryptUtil` | 获取 AES 加密实例 |
| `createAesEncrypt(secretKey)` | `string` | `Promise<AesEncryptUtil>` | 创建新的 AES 实例 |
### 配置说明
#### 密钥配置
前端密钥必须与后端配置保持一致:
**后端配置application.yml**
```yaml
security:
aes:
secret-key: 1234567890qwer
```
**前端配置**
```typescript
const AES_SECRET_KEY = '1234567890qwer' // 与后端保持一致
await initAesEncrypt(AES_SECRET_KEY)
```
#### 算法参数
| 参数 | 值 | 说明 |
|------|-----|------|
| 算法 | AES-256-GCM | 高强度加密算法 |
| 密钥长度 | 256 bits | AES-256 |
| IV 长度 | 12 bytes | GCM 推荐长度 |
| Tag 长度 | 128 bits | GCM 认证标签 |
| 编码 | Base64 | 密文编码格式 |
### 安全注意事项
1. **密钥管理**
- 密钥不要硬编码在代码中
- 生产环境从配置中心或环境变量获取
- 定期轮换密钥
2. **HTTPS 传输**
- 生产环境必须使用 HTTPS
- 加密只是额外保障,不能替代 HTTPS
3. **密码安全**
- 密码传输前加密
- 后端再次使用 BCrypt 等算法加密存储
- 前端加密防止明文传输被截获
4. **错误处理**
- 捕获加密失败异常
- 不要在错误信息中暴露敏感信息
### 浏览器兼容性
使用 Web Crypto API支持以下浏览器
- Chrome 37+
- Firefox 34+
- Safari 11+
- Edge 79+
不支持 IE 浏览器。
### 与后端对接
#### 数据流程
```
前端 ----[加密数据]----> Gateway -----> Auth Service
(AES-256-GCM) (AES-256-GCM)
[解密] → [BCrypt] → 数据库
```
#### 示例对比
**前端加密**
```typescript
const encrypted = await aes.encrypt('13812345678')
// 输出Base64([IV(12字节)][密文])
```
**后端解密**
```java
String decrypted = aesEncryptUtil.decrypt(encrypted)
// 输出:'13812345678'
```
### 故障排查
#### 1. "AES 密钥未初始化"
**原因**:未调用 `initAesEncrypt()`
**解决**:在 `main.ts` 中初始化
```typescript
await initAesEncrypt(AES_SECRET_KEY)
```
#### 2. "AES 解密失败"
**原因**:前后端密钥不一致
**解决**:检查前后端密钥配置是否相同
#### 3. 类型错误
**原因**TypeScript 类型问题
**解决**:确保使用最新版本的工具类
### 性能优化
1. **密钥复用**:使用单例模式,避免重复初始化
2. **异步处理**:加密操作是异步的,注意使用 `await`
3. **批量加密**:如需加密多个字段,可并行处理
```typescript
// 并行加密
const [encryptedPhone, encryptedPassword] = await Promise.all([
aes.encryptPhone(phone),
aes.encryptPassword(password)
])
```

View File

@@ -0,0 +1,322 @@
/**
* AES-256-GCM 加密工具(兼容 H5/小程序/App
* 与后端 AesEncryptUtil 保持一致
*
* 使用场景:
* - 密码传输前加密
* - 敏感信息(手机号、身份证号)加密
*
* @author yslg
* @since 2025-12-10
*/
/**
* AES 加密配置
*/
interface AesConfig {
algorithm: string
keySize: number
ivLength: number
tagLength: number
}
const AES_CONFIG: AesConfig = {
algorithm: 'AES-GCM',
keySize: 256,
ivLength: 12, // GCM 推荐 IV 长度
tagLength: 128 // GCM 认证标签长度
}
const getCrypto = (): Crypto => {
return window.crypto
}
/**
* AES 加密工具类
*/
export class AesEncryptUtil {
private secretKey: CryptoKey | null = null
private secretKeyString: string
/**
* 构造函数
* @param secretKeyString Base64 编码的密钥32字节AES-256
*/
constructor(secretKeyString: string) {
this.secretKeyString = secretKeyString
}
/**
* 初始化密钥(异步)
*/
async init(): Promise<void> {
if (!this.secretKeyString) {
throw new Error('AES 密钥未配置')
}
try {
// Base64 解码密钥
const keyData = this.base64ToArrayBuffer(this.secretKeyString)
// 校验密钥长度AES-256 必须是 32 字节)
if (keyData.byteLength !== 32) {
throw new Error(`AES 密钥长度错误需32字节实际${keyData.byteLength}字节`)
}
// 导入密钥(跨端兼容)
this.secretKey = await getCrypto().subtle.importKey(
'raw',
keyData,
{ name: AES_CONFIG.algorithm },
false,
['encrypt', 'decrypt']
)
} catch (error) {
throw new Error(`AES 密钥初始化失败: ${error}`)
}
}
/**
* 加密字符串
* @param plaintext 明文
* @returns Base64 编码的密文(包含 IV
*/
async encrypt(plaintext: string): Promise<string> {
if (!plaintext) {
return plaintext
}
if (!this.secretKey) {
throw new Error('AES 密钥未初始化,请先调用 init()')
}
try {
// 生成随机 IV跨端兼容
const iv = getCrypto().getRandomValues(new Uint8Array(AES_CONFIG.ivLength))
// 将明文转为 ArrayBuffer
const encoder = new TextEncoder()
const data = encoder.encode(plaintext)
// 加密
const ciphertext = await getCrypto().subtle.encrypt(
{
name: AES_CONFIG.algorithm,
iv: iv,
tagLength: AES_CONFIG.tagLength
},
this.secretKey,
data
)
// 将 IV 和密文组合:[IV(12字节)][密文]
const combined = new Uint8Array(iv.length + ciphertext.byteLength)
combined.set(iv, 0)
combined.set(new Uint8Array(ciphertext), iv.length)
// Base64 编码
return this.arrayBufferToBase64(combined)
} catch (error) {
throw new Error(`AES 加密失败: ${error}`)
}
}
/**
* 解密字符串
* @param ciphertext Base64 编码的密文(包含 IV
* @returns 明文
*/
async decrypt(ciphertext: string): Promise<string> {
if (!ciphertext) {
return ciphertext
}
if (!this.secretKey) {
throw new Error('AES 密钥未初始化,请先调用 init()')
}
try {
// Base64 解码
const combinedBuffer = this.base64ToArrayBuffer(ciphertext)
const combined = new Uint8Array(combinedBuffer)
// 校验 IV 长度
if (combined.length < AES_CONFIG.ivLength) {
throw new Error('密文格式错误IV 长度不足')
}
// 提取 IV 和密文
const iv = combined.slice(0, AES_CONFIG.ivLength)
const data = combined.slice(AES_CONFIG.ivLength)
// 解密
const plaintext = await getCrypto().subtle.decrypt(
{
name: AES_CONFIG.algorithm,
iv: iv,
tagLength: AES_CONFIG.tagLength
},
this.secretKey,
data
)
// 将 ArrayBuffer 转为字符串
const decoder = new TextDecoder()
return decoder.decode(plaintext)
} catch (error) {
throw new Error(`AES 解密失败: ${error}`)
}
}
/**
* 加密密码(用于登录等场景)
*/
async encryptPassword(password: string): Promise<string> {
return this.encrypt(password)
}
/**
* 加密手机号
*/
async encryptPhone(phone: string): Promise<string> {
return this.encrypt(phone)
}
/**
* 解密手机号
*/
async decryptPhone(encryptedPhone: string): Promise<string> {
return this.decrypt(encryptedPhone)
}
/**
* 加密身份证号
*/
async encryptIdCard(idCard: string): Promise<string> {
return this.encrypt(idCard)
}
/**
* 解密身份证号
*/
async decryptIdCard(encryptedIdCard: string): Promise<string> {
return this.decrypt(encryptedIdCard)
}
// ============ 工具方法 ============
/**
* Base64 转 ArrayBuffer
*/
private base64ToArrayBuffer(base64: string): ArrayBuffer {
// 处理 Base64 填充字符
base64 = base64.replace(/-/g, '+').replace(/_/g, '/')
const padLength = (4 - (base64.length % 4)) % 4
base64 += '='.repeat(padLength)
const binaryString = atob(base64)
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes.buffer
}
/**
* ArrayBuffer 转 Base64
*/
private arrayBufferToBase64(buffer: Uint8Array): string {
let binary = ''
const len = buffer.byteLength
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(buffer[i])
}
return btoa(binary)
}
}
/**
* 静态工具方法
*/
export class AesUtils {
/**
* 脱敏显示手机号
* 例如13812345678 -> 138****5678
*/
static maskPhone(phone: string): string {
if (!phone || phone.length < 11) {
return phone
}
return phone.substring(0, 3) + '****' + phone.substring(7)
}
/**
* 脱敏显示身份证号
* 例如110101199001011234 -> 110101********1234
*/
static maskIdCard(idCard: string): string {
if (!idCard || idCard.length < 18) {
return idCard
}
return idCard.substring(0, 6) + '********' + idCard.substring(14)
}
/**
* 脱敏显示邮箱
* 例如test@example.com -> t***@example.com
*/
static maskEmail(email: string): string {
if (!email || !email.includes('@')) {
return email
}
const [username, domain] = email.split('@')
if (username.length <= 1) {
return email
}
return username[0] + '***@' + domain
}
/**
* 生成 AES-256 Base64 密钥(工具方法,用于后端/测试)
*/
static generateAes256KeyBase64(): string {
const crypto = getCrypto()
const keyBytes = crypto.getRandomValues(new Uint8Array(32)) // 32字节=256位
let binary = ''
for (let i = 0; i < keyBytes.length; i++) {
binary += String.fromCharCode(keyBytes[i])
}
return btoa(binary)
}
}
/**
* 创建 AES 加密实例的工厂函数
* @param secretKey Base64 编码的密钥32字节
*/
export async function createAesEncrypt(secretKey: string): Promise<AesEncryptUtil> {
const aes = new AesEncryptUtil(secretKey)
await aes.init()
return aes
}
// 导出单例(需要在应用启动时初始化)
let aesInstance: AesEncryptUtil | null = null
/**
* 获取 AES 加密实例
*/
export function getAesInstance(): AesEncryptUtil {
if (!aesInstance) {
throw new Error('AES 加密工具未初始化,请先调用 initAesEncrypt()')
}
return aesInstance
}
/**
* 初始化 AES 加密工具(在应用启动时调用)
* @param secretKey Base64 编码的密钥32字节与后端配置保持一致
*/
export async function initAesEncrypt(secretKey: string): Promise<void> {
aesInstance = await createAesEncrypt(secretKey)
}

View File

@@ -0,0 +1 @@
export * from './aes'

View File

@@ -3,3 +3,4 @@
*/
export * from './file'
export * from './crypto'

View File

@@ -1,108 +1,111 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { federation } from '@module-federation/vite'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
// ES 模块中获取 __dirname
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
/**
* ES Module 构建配置
* 用于 Import Maps 方案
* Module Federation 构建配置(@module-federation/vite
* 官方维护版本,支持 Vite 6 + 开发模式热更新
*
* 策略:将 Vue、Element Plus 等依赖打包进共享模块
* 业务应用只需引入 @shared/*,无需关心底层依赖
* 优势:
* - ✅ 完整支持 Vite 开发模式
* - ✅ dev 模式能生成 remoteEntry.js
* - ✅ 自动处理内部路径别名 (@/)
* - ✅ 真正的生产可用版本
*/
export default defineConfig({
plugins: [vue(), vueJsx()],
define: {
__VUE_PROD_DEVTOOLS__: true,
// __VUE_OPTIONS_API__: true, // 确保启用 Options API
// __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
},
resolve: {
alias: [
{ find: '@', replacement: resolve(__dirname, 'src') }
plugins: [
vue({
script: {
defineModel: true,
propsDestructure: true
}
}),
vueJsx(),
federation({
name: 'shared',
filename: 'remoteEntry.js',
// 暴露的模块
exposes: {
// 通用组件
'./FileUpload': './src/components/fileupload/FileUpload.vue',
'./DynamicFormItem': './src/components/dynamicFormItem/DynamicFormItem.vue',
// API 模块
'./api': './src/api/index.ts',
'./authAPI': './src/api/auth/auth.ts',
'./fileAPI': './src/api/file/file.ts',
// Utils 模块
'./utils': './src/utils/index.ts',
// Types 模块
'./types': './src/types/index.ts',
// 整体导出
'./components': './src/components/index.ts'
},
// 共享依赖(重要:避免重复加载)
shared: {
vue: {},
'vue-router': {},
'element-plus': {},
'@element-plus/icons-vue': {},
axios: {}
}
})
],
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
build: {
lib: {
entry: {
components: resolve(__dirname, 'src/components/index.ts'),
utils: resolve(__dirname, 'src/utils/index.ts'),
api: resolve(__dirname, 'src/api/index.ts'),
composables: resolve(__dirname, 'src/composables/index.ts'),
types: resolve(__dirname, 'src/types/index.ts')
},
formats: ['es'], // 仅构建 ES Module
fileName: (format, entryName) => `${entryName}.js`
define: {
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: true,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
},
rollupOptions: {
// ⚠️ 不外部化依赖,将它们打包进共享模块
// 这样业务应用只需引入 @shared/* 即可
external: [],
output: {
// 保持 ES Module 格式
format: 'es',
// 导出命名导出
exports: 'named',
// 生成 sourcemap
resolve: {
alias: {
'@': resolve(__dirname, 'src')
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
build: {
target: 'esnext',
minify: false,
cssCodeSplit: false,
sourcemap: true,
// 分块策略:将大的依赖分离出来
manualChunks(id) {
// Vue 核心
if (id.includes('node_modules/vue/') ||
id.includes('node_modules/@vue/')) {
return 'vue-core'
}
// Vue Router
if (id.includes('node_modules/vue-router/')) {
return 'vue-router'
}
// Pinia
if (id.includes('node_modules/pinia/')) {
return 'pinia'
}
// Element Plus
if (id.includes('node_modules/element-plus/')) {
return 'element-plus'
}
// VueUse
if (id.includes('node_modules/@vueuse/')) {
return 'vueuse'
}
rollupOptions: {
output: {
format: 'es'
}
}
}
},
// 输出目录
outDir: 'dist/esm',
emptyOutDir: true,
server: {
port: 5000,
strictPort: true,
host: true,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
},
// 目标浏览器
target: 'esnext',
// 不压缩(开发环境)
minify: false,
// 启用代码分割
cssCodeSplit: true
},
// 开发服务器配置
server: {
port: 5000,
host: true,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
// 'Content-Type': 'application/javascript; charset=utf-8'
preview: {
port: 5000,
host: true,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
}
}
})