feat(dashboard): 添加首页地图展示功能
- 在 Dashboard 组件中集成锡林郭勒盟区域地图 - 实现地图数据接口和区域详情接口 - 添加地图交互功能,支持点击和悬停事件 - 更新开发计划和需求文档,增加地图展示功能
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24087,3 +24087,4 @@
|
||||
/frontend/dashboard/node_modules/zrender/README.md
|
||||
/frontend/dashboard/node_modules/.package-lock.json
|
||||
/frontend/dashboard/node_modules/
|
||||
/backend/api/node_modules/
|
||||
|
||||
831
backend/api/package-lock.json
generated
Normal file
831
backend/api/package-lock.json
generated
Normal file
@@ -0,0 +1,831 @@
|
||||
{
|
||||
"name": "xlxumu-api",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "xlxumu-api",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.21.2",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"helmet": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"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.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
||||
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.3.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"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.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.0.1.tgz",
|
||||
"integrity": "sha512-aZVCnybn7TVmxO4BtlmnvX+nuz8qHW124KKJ8dumsBsmv5ZLxE0pYu7S2nwyRBGHHCAzdmnGyrc5U/rksSPO7Q==",
|
||||
"dependencies": {
|
||||
"ip-address": "10.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/express-rate-limit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": ">= 4.11"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"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.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/helmet": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
|
||||
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
|
||||
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||
"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"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.21.2",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"helmet": "^8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,117 @@ app.get('/health', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 大屏可视化地图数据接口
|
||||
app.get('/api/v1/dashboard/map/regions', (req, res) => {
|
||||
// 模拟锡林郭勒盟各区域数据
|
||||
const regions = [
|
||||
{
|
||||
id: 'xlg',
|
||||
name: '锡林浩特市',
|
||||
coordinates: [116.093, 43.946],
|
||||
cattle_count: 25600,
|
||||
farm_count: 120,
|
||||
output_value: 650000000
|
||||
},
|
||||
{
|
||||
id: 'dwq',
|
||||
name: '东乌旗',
|
||||
coordinates: [116.980, 45.514],
|
||||
cattle_count: 18500,
|
||||
farm_count: 95,
|
||||
output_value: 480000000
|
||||
},
|
||||
{
|
||||
id: 'xwq',
|
||||
name: '西乌旗',
|
||||
coordinates: [117.615, 44.587],
|
||||
cattle_count: 21200,
|
||||
farm_count: 108,
|
||||
output_value: 520000000
|
||||
},
|
||||
{
|
||||
id: 'abg',
|
||||
name: '阿巴嘎旗',
|
||||
coordinates: [114.971, 44.022],
|
||||
cattle_count: 16800,
|
||||
farm_count: 86,
|
||||
output_value: 420000000
|
||||
},
|
||||
{
|
||||
id: 'snz',
|
||||
name: '苏尼特左旗',
|
||||
coordinates: [113.653, 43.859],
|
||||
cattle_count: 12400,
|
||||
farm_count: 65,
|
||||
output_value: 310000000
|
||||
}
|
||||
];
|
||||
|
||||
res.json({ regions });
|
||||
});
|
||||
|
||||
app.get('/api/v1/dashboard/map/region/:regionId', (req, res) => {
|
||||
const { regionId } = req.params;
|
||||
|
||||
// 模拟各区域详细数据
|
||||
const regionDetails = {
|
||||
'xlg': {
|
||||
region: {
|
||||
id: 'xlg',
|
||||
name: '锡林浩特市',
|
||||
coordinates: [116.093, 43.946],
|
||||
cattle_count: 25600,
|
||||
farm_count: 120,
|
||||
output_value: 650000000,
|
||||
trend: 'up'
|
||||
},
|
||||
farms: [
|
||||
{
|
||||
id: 'FARM001',
|
||||
name: '锡林浩特市第一牧场',
|
||||
coordinates: [116.120, 43.950],
|
||||
cattle_count: 2450,
|
||||
output_value: 62000000
|
||||
},
|
||||
{
|
||||
id: 'FARM002',
|
||||
name: '锡林浩特市第二牧场',
|
||||
coordinates: [116.080, 43.930],
|
||||
cattle_count: 2100,
|
||||
output_value: 53000000
|
||||
}
|
||||
]
|
||||
},
|
||||
'dwq': {
|
||||
region: {
|
||||
id: 'dwq',
|
||||
name: '东乌旗',
|
||||
coordinates: [116.980, 45.514],
|
||||
cattle_count: 18500,
|
||||
farm_count: 95,
|
||||
output_value: 480000000,
|
||||
trend: 'up'
|
||||
},
|
||||
farms: [
|
||||
{
|
||||
id: 'FARM003',
|
||||
name: '东乌旗牧场A',
|
||||
coordinates: [116.990, 45.520],
|
||||
cattle_count: 1950,
|
||||
output_value: 49000000
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const detail = regionDetails[regionId];
|
||||
if (detail) {
|
||||
res.json(detail);
|
||||
} else {
|
||||
res.status(404).json({ error: '区域未找到' });
|
||||
}
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
app.listen(PORT, () => {
|
||||
console.log(`API服务器正在端口 ${PORT} 上运行`);
|
||||
|
||||
@@ -108,7 +108,97 @@ GET /api/v1/dashboard/history
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 配置接口
|
||||
### 4.3 首页地图数据接口
|
||||
|
||||
#### 获取锡林郭勒盟区域地图数据
|
||||
```
|
||||
GET /api/v1/dashboard/map/regions
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
- 无
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"regions": [
|
||||
{
|
||||
"id": "xlg",
|
||||
"name": "锡林浩特市",
|
||||
"coordinates": [116.093, 43.946],
|
||||
"cattle_count": 25600,
|
||||
"farm_count": 120,
|
||||
"output_value": 650000000
|
||||
},
|
||||
{
|
||||
"id": "dwq",
|
||||
"name": "东乌旗",
|
||||
"coordinates": [116.980, 45.514],
|
||||
"cattle_count": 18500,
|
||||
"farm_count": 95,
|
||||
"output_value": 480000000
|
||||
},
|
||||
{
|
||||
"id": "xwq",
|
||||
"name": "西乌旗",
|
||||
"coordinates": [117.615, 44.587],
|
||||
"cattle_count": 21200,
|
||||
"farm_count": 108,
|
||||
"output_value": 520000000
|
||||
},
|
||||
{
|
||||
"id": "abg",
|
||||
"name": "阿巴嘎旗",
|
||||
"coordinates": [114.971, 44.022],
|
||||
"cattle_count": 16800,
|
||||
"farm_count": 86,
|
||||
"output_value": 420000000
|
||||
},
|
||||
{
|
||||
"id": "snz",
|
||||
"name": "苏尼特左旗",
|
||||
"coordinates": [113.653, 43.859],
|
||||
"cattle_count": 12400,
|
||||
"farm_count": 65,
|
||||
"output_value": 310000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取指定区域详细数据
|
||||
```
|
||||
GET /api/v1/dashboard/map/region/{regionId}
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
- `regionId` (string, required): 区域ID
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"region": {
|
||||
"id": "xlg",
|
||||
"name": "锡林浩特市",
|
||||
"coordinates": [116.093, 43.946],
|
||||
"cattle_count": 25600,
|
||||
"farm_count": 120,
|
||||
"output_value": 650000000,
|
||||
"trend": "up"
|
||||
},
|
||||
"farms": [
|
||||
{
|
||||
"id": "FARM001",
|
||||
"name": "锡林浩特市第一牧场",
|
||||
"coordinates": [116.120, 43.950],
|
||||
"cattle_count": 2450,
|
||||
"output_value": 62000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 配置接口
|
||||
|
||||
#### 获取可视化配置
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
## 3. 功能模块详细计划
|
||||
|
||||
### 3.1 产业概览模块 (3天)
|
||||
### 3.1 产业概览模块 (4天)
|
||||
- 第1天:
|
||||
- 整体产业规模展示(牛只总数、牧场数量等关键指标)
|
||||
- 产值和增长率关键指标(年度产值、增长率趋势图)
|
||||
@@ -27,6 +27,8 @@
|
||||
- 第3天:
|
||||
- 数据钻取功能实现(点击图表可查看详细数据)
|
||||
- 多维度数据展示(按时间、区域、品种等维度筛选)
|
||||
- 第4天:
|
||||
- 首页地图展示功能开发(集成锡林郭勒盟区域地图,展示各区域牛只分布、牧场位置)
|
||||
|
||||
### 3.2 养殖监控模块 (3天)
|
||||
- 第1天:
|
||||
@@ -98,10 +100,11 @@
|
||||
- 结合 DataV 图表实现丰富的数据可视化
|
||||
- 使用自适应容器确保不同分辨率下的正常显示
|
||||
- 添加窗口大小改变时的重绘功能
|
||||
- 集成地图组件展示锡林郭勒盟区域数据分布
|
||||
|
||||
## 5. 里程碑
|
||||
|
||||
- **里程碑1**:完成产业概览模块和养殖监控模块(6天)
|
||||
- **里程碑1**:完成产业概览模块和养殖监控模块(7天)
|
||||
- **里程碑2**:完成金融服务模块和交易统计模块(4天)
|
||||
- **里程碑3**:完成运输跟踪模块和风险预警模块(4天)
|
||||
- **里程碑4**:完成生态指标模块和政府监管模块(4天)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
- **实时数据更新机制**:通过 WebSocket 实现数据实时更新(`ws://<host>/api/v1/dashboard/realtime`)
|
||||
- **数据钻取功能**:支持点击图表查看详细数据(弹窗展示,含数据导出按钮)
|
||||
- **多维度数据筛选**:支持按时间、区域、品种等维度筛选(交互:下拉选择器 + 确认按钮)
|
||||
- **首页地图展示**:在首页集成锡林郭勒盟区域地图,展示各区域牛只分布、牧场位置、产业热点等信息(交互:点击区域查看详细数据)
|
||||
|
||||
### 2.2 养殖监控模块
|
||||
- **各牧场养殖情况展示**:通过 DataV 地图组件展示各牧场位置和规模(数据来源:`/api/v1/dashboard/farms`,数据库表:`farm_locations`)
|
||||
|
||||
2
frontend/dashboard/node_modules/.vite/deps/echarts.js
generated
vendored
2
frontend/dashboard/node_modules/.vite/deps/echarts.js
generated
vendored
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
__export
|
||||
} from "./chunk-2GTGKKMZ.js";
|
||||
} from "./chunk-5WWUZCGV.js";
|
||||
|
||||
// node_modules/tslib/tslib.es6.js
|
||||
var extendStatics = function(d, b) {
|
||||
|
||||
247
frontend/dashboard/src/components/map/RegionDetail.vue
Normal file
247
frontend/dashboard/src/components/map/RegionDetail.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div class="region-detail-overlay" v-if="visible" @click="closeOverlay">
|
||||
<div class="region-detail-card" @click.stop>
|
||||
<div class="card-header">
|
||||
<h2>{{ regionData.region?.name }} 详情</h2>
|
||||
<button class="close-button" @click="closeOverlay">×</button>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="region-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">牛只数量</div>
|
||||
<div class="stat-value">{{ regionData.region?.cattle_count || 0 }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">牧场数量</div>
|
||||
<div class="stat-value">{{ regionData.region?.farm_count || 0 }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">产值</div>
|
||||
<div class="stat-value">¥{{ formatNumber(regionData.region?.output_value || 0) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">趋势</div>
|
||||
<div class="stat-value" :class="regionData.region?.trend">
|
||||
{{ regionData.region?.trend === 'up' ? '↑ 上升' : '↓ 下降' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="farms-section" v-if="regionData.farms && regionData.farms.length > 0">
|
||||
<h3>牧场列表</h3>
|
||||
<div class="farms-list">
|
||||
<div
|
||||
class="farm-item"
|
||||
v-for="farm in regionData.farms"
|
||||
:key="farm.id"
|
||||
>
|
||||
<div class="farm-name">{{ farm.name }}</div>
|
||||
<div class="farm-details">
|
||||
<span>牛只: {{ farm.cattle_count }}</span>
|
||||
<span>产值: ¥{{ formatNumber(farm.output_value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-farms" v-else>
|
||||
<p>暂无牧场数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, defineProps, defineEmits } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'RegionDetail',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
regionData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
setup(props, { emit }) {
|
||||
const closeOverlay = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const formatNumber = (num) => {
|
||||
if (num >= 100000000) {
|
||||
return (num / 100000000).toFixed(2) + '亿';
|
||||
}
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(2) + '万';
|
||||
}
|
||||
return num.toLocaleString();
|
||||
};
|
||||
|
||||
return {
|
||||
closeOverlay,
|
||||
formatNumber
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.region-detail-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.region-detail-card {
|
||||
background: linear-gradient(135deg, #0f2027, #20555d);
|
||||
border-radius: 12px;
|
||||
width: 600px;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
max-height: calc(90vh - 100px);
|
||||
}
|
||||
|
||||
.region-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #ccc;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.stat-value.up {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.stat-value.down {
|
||||
color: #F44336;
|
||||
}
|
||||
|
||||
.farms-section h3 {
|
||||
color: #4CAF50;
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.farms-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.farm-item {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.farm-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.farm-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.no-farms {
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
padding: 30px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.region-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.farm-details {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,18 +5,27 @@
|
||||
class="map-canvas"
|
||||
:style="{ width: '100%', height: height + 'px' }"
|
||||
></div>
|
||||
|
||||
<RegionDetail
|
||||
:visible="showRegionDetail"
|
||||
:region-data="selectedRegionData"
|
||||
@close="closeRegionDetail"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
import * as turf from '@turf/turf';
|
||||
import RegionDetail from './RegionDetail.vue';
|
||||
import { fetchRegionDetail } from '@/services/dashboard.js';
|
||||
|
||||
export default {
|
||||
name: 'ThreeDMap',
|
||||
components: {
|
||||
RegionDetail
|
||||
},
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
@@ -35,8 +44,12 @@ export default {
|
||||
},
|
||||
setup(props) {
|
||||
const mapContainer = ref(null);
|
||||
const showRegionDetail = ref(false);
|
||||
const selectedRegionData = ref({});
|
||||
let scene, camera, renderer, controls;
|
||||
let animationId = null;
|
||||
let landmarks = [];
|
||||
let raycaster, mouse;
|
||||
|
||||
// 初始化3D场景
|
||||
const initScene = () => {
|
||||
@@ -78,6 +91,14 @@ export default {
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.05;
|
||||
|
||||
// 初始化射线检测器
|
||||
raycaster = new THREE.Raycaster();
|
||||
mouse = new THREE.Vector2();
|
||||
|
||||
// 添加事件监听
|
||||
renderer.domElement.addEventListener('click', onMouseClick, false);
|
||||
renderer.domElement.addEventListener('mousemove', onMouseMove, false);
|
||||
|
||||
// 创建地形和地标
|
||||
createTerrain();
|
||||
createLandmarks();
|
||||
@@ -91,7 +112,7 @@ export default {
|
||||
// 创建一个简单的平面作为地形基础
|
||||
const geometry = new THREE.PlaneGeometry(2000, 2000, 50, 50);
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: 0x4CAF50,
|
||||
color: 0x2c5364,
|
||||
wireframe: false,
|
||||
transparent: true,
|
||||
opacity: 0.7
|
||||
@@ -99,7 +120,7 @@ export default {
|
||||
|
||||
const terrain = new THREE.Mesh(geometry, material);
|
||||
terrain.rotation.x = -Math.PI / 2;
|
||||
terrain.position.y = -10;
|
||||
terrain.position.y = -20;
|
||||
terrain.receiveShadow = true;
|
||||
scene.add(terrain);
|
||||
|
||||
@@ -115,53 +136,155 @@ export default {
|
||||
|
||||
// 创建地标
|
||||
const createLandmarks = () => {
|
||||
// 创建锡林郭勒盟主要旗县的地标
|
||||
const locations = [
|
||||
{ name: '锡林浩特市', position: [0, 0, 0], color: 0x2196F3 },
|
||||
{ name: '东乌珠穆沁旗', position: [-200, 0, 100], color: 0xFF9800 },
|
||||
{ name: '西乌珠穆沁旗', position: [200, 0, 100], color: 0xF44336 },
|
||||
{ name: '镶黄旗', position: [-100, 0, -150], color: 0x9C27B0 },
|
||||
{ name: '正镶白旗', position: [100, 0, -150], color: 0x4CAF50 }
|
||||
];
|
||||
// 清除现有的地标
|
||||
landmarks.forEach(landmark => {
|
||||
scene.remove(landmark.mesh);
|
||||
scene.remove(landmark.label);
|
||||
});
|
||||
landmarks = [];
|
||||
|
||||
locations.forEach(location => {
|
||||
// 根据传入的地图数据创建地标
|
||||
props.mapData.forEach((location, index) => {
|
||||
// 计算位置(基于锡林浩特市为中心点)
|
||||
const x = (location.coordinates[0] - props.center[0]) * 5000;
|
||||
const z = (props.center[1] - location.coordinates[1]) * 5000;
|
||||
|
||||
// 根据牛只数量确定地标大小
|
||||
const size = Math.max(20, Math.min(50, (location.cattle_count || location.cattleCount) / 1000));
|
||||
const height = Math.max(30, Math.min(100, (location.cattle_count || location.cattleCount) / 500));
|
||||
|
||||
// 创建地标圆柱体
|
||||
const geometry = new THREE.CylinderGeometry(20, 20, 50, 32);
|
||||
const geometry = new THREE.CylinderGeometry(size, size, height, 32);
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: location.color,
|
||||
color: getColorByCattleCount(location.cattle_count || location.cattleCount),
|
||||
transparent: true,
|
||||
opacity: 0.8
|
||||
});
|
||||
const cylinder = new THREE.Mesh(geometry, material);
|
||||
cylinder.position.set(...location.position);
|
||||
cylinder.position.y = 25;
|
||||
cylinder.position.set(x, height/2 - 20, z);
|
||||
cylinder.castShadow = true;
|
||||
cylinder.userData = { location }; // 保存位置信息用于点击检测
|
||||
scene.add(cylinder);
|
||||
|
||||
// 添加地标名称
|
||||
addLabel(location.name, location.position, location.color);
|
||||
// 添加地标名称和数据
|
||||
const label = addLabel(
|
||||
`${location.name}\n牛只: ${location.cattle_count || location.cattleCount}\n牧场: ${location.farm_count || location.farmCount}`,
|
||||
[x, height + 20, z],
|
||||
getColorByCattleCount(location.cattle_count || location.cattleCount)
|
||||
);
|
||||
|
||||
landmarks.push({
|
||||
mesh: cylinder,
|
||||
label: label,
|
||||
data: location
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 根据牛只数量获取颜色
|
||||
const getColorByCattleCount = (count) => {
|
||||
if (count > 20000) return 0x4CAF50; // 绿色 - 数量多
|
||||
if (count > 15000) return 0xFFEB3B; // 黄色 - 数量中等
|
||||
return 0xF44336; // 红色 - 数量较少
|
||||
};
|
||||
|
||||
// 添加标签
|
||||
const addLabel = (text, position, color) => {
|
||||
// 创建简单的文本标签(在实际项目中可以使用更复杂的文本渲染)
|
||||
// 创建简单的文本标签
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.width = 256;
|
||||
canvas.height = 128;
|
||||
canvas.width = 512;
|
||||
canvas.height = 256;
|
||||
|
||||
context.fillStyle = `#${color.toString(16).padStart(6, '0')}`;
|
||||
context.font = '24px Arial';
|
||||
context.fillStyle = `#${new THREE.Color(color).getHexString()}`;
|
||||
context.font = '24px Microsoft YaHei';
|
||||
context.textAlign = 'center';
|
||||
context.fillText(text, 128, 64);
|
||||
context.textBaseline = 'middle';
|
||||
|
||||
// 支持多行文本
|
||||
const lines = text.split('\n');
|
||||
lines.forEach((line, i) => {
|
||||
context.fillText(line, 256, 128 + (i - (lines.length-1)/2) * 30);
|
||||
});
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
const material = new THREE.SpriteMaterial({ map: texture });
|
||||
const material = new THREE.SpriteMaterial({ map: texture, transparent: true });
|
||||
const sprite = new THREE.Sprite(material);
|
||||
sprite.position.set(position[0], 80, position[2]);
|
||||
sprite.scale.set(100, 50, 1);
|
||||
sprite.position.set(position[0], position[1], position[2]);
|
||||
sprite.scale.set(200, 100, 1);
|
||||
scene.add(sprite);
|
||||
return sprite;
|
||||
};
|
||||
|
||||
// 鼠标点击事件处理
|
||||
const onMouseClick = async (event) => {
|
||||
// 计算鼠标位置标准化设备坐标 (-1 到 +1)
|
||||
const rect = renderer.domElement.getBoundingClientRect();
|
||||
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
// 通过摄像机和鼠标位置更新射线
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
|
||||
// 计算物体和射线的交点
|
||||
const intersects = raycaster.intersectObjects(scene.children);
|
||||
|
||||
for (let i = 0; i < intersects.length; i++) {
|
||||
if (intersects[i].object.userData.location) {
|
||||
const region = intersects[i].object.userData.location;
|
||||
selectedRegionData.value = await fetchRegionDetail(region.id);
|
||||
showRegionDetail.value = true;
|
||||
highlightRegion(intersects[i].object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 鼠标移动事件处理(悬停效果)
|
||||
const onMouseMove = (event) => {
|
||||
// 计算鼠标位置标准化设备坐标 (-1 到 +1)
|
||||
const rect = renderer.domElement.getBoundingClientRect();
|
||||
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
// 通过摄像机和鼠标位置更新射线
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
|
||||
// 计算物体和射线的交点
|
||||
const intersects = raycaster.intersectObjects(scene.children);
|
||||
|
||||
// 重置所有地标的颜色
|
||||
resetRegionColors();
|
||||
|
||||
// 高亮显示悬停的地標
|
||||
for (let i = 0; i < intersects.length; i++) {
|
||||
if (intersects[i].object.userData.location) {
|
||||
intersects[i].object.material.emissive = new THREE.Color(0x222222);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 高亮选中区域
|
||||
const highlightRegion = (mesh) => {
|
||||
resetRegionColors();
|
||||
mesh.material.emissive = new THREE.Color(0x333333);
|
||||
};
|
||||
|
||||
// 重置区域颜色
|
||||
const resetRegionColors = () => {
|
||||
scene.children.forEach(child => {
|
||||
if (child.isMesh && child.userData.location) {
|
||||
child.material.emissive = new THREE.Color(0x000000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭区域详情
|
||||
const closeRegionDetail = () => {
|
||||
showRegionDetail.value = false;
|
||||
selectedRegionData.value = {};
|
||||
resetRegionColors();
|
||||
};
|
||||
|
||||
// 动画循环
|
||||
@@ -187,6 +310,13 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
// 监听地图数据变化
|
||||
watch(() => props.mapData, () => {
|
||||
if (scene) {
|
||||
createLandmarks();
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
// 清理资源
|
||||
const cleanup = () => {
|
||||
if (animationId) {
|
||||
@@ -194,6 +324,8 @@ export default {
|
||||
}
|
||||
|
||||
if (renderer) {
|
||||
renderer.domElement.removeEventListener('click', onMouseClick, false);
|
||||
renderer.domElement.removeEventListener('mousemove', onMouseMove, false);
|
||||
mapContainer.value.removeChild(renderer.domElement);
|
||||
renderer.dispose();
|
||||
}
|
||||
@@ -221,7 +353,10 @@ export default {
|
||||
});
|
||||
|
||||
return {
|
||||
mapContainer
|
||||
mapContainer,
|
||||
showRegionDetail,
|
||||
selectedRegionData,
|
||||
closeRegionDetail
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -231,6 +366,7 @@ export default {
|
||||
.three-d-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.map-canvas {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3000/api/v1/dashboard';
|
||||
const API_BASE_URL = 'http://localhost:8000/api/v1/dashboard';
|
||||
|
||||
export const fetchOverviewData = async () => {
|
||||
try {
|
||||
@@ -50,4 +50,24 @@ export const fetchFinanceData = async (type) => {
|
||||
console.error('Error fetching finance data:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchMapData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/map/regions`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching map data:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchRegionDetail = async (regionId) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/map/region/${regionId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching region detail:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
@@ -53,25 +53,16 @@
|
||||
|
||||
<!-- 中间区域 -->
|
||||
<div class="center-section">
|
||||
<!-- 产业概览 -->
|
||||
<!-- 产业概览地图 -->
|
||||
<div class="center-top">
|
||||
<div class="center-border card">
|
||||
<h2>产业概览</h2>
|
||||
<div class="center-content">
|
||||
<div class="total-count">
|
||||
<div class="count-title">牛只总数</div>
|
||||
<div class="count-value">128,456</div>
|
||||
<div class="count-unit">头</div>
|
||||
</div>
|
||||
<div class="total-count">
|
||||
<div class="count-title">牧场数量</div>
|
||||
<div class="count-value">1,245</div>
|
||||
<div class="count-unit">个</div>
|
||||
</div>
|
||||
<div class="growth-rate">
|
||||
<div class="rate-title">同比增长</div>
|
||||
<div class="rate-value positive">5.2%</div>
|
||||
</div>
|
||||
<h2>锡林郭勒盟产业分布</h2>
|
||||
<div class="map-container">
|
||||
<ThreeDMap
|
||||
:height="300"
|
||||
:map-data="mapData"
|
||||
ref="threeDMap"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,15 +117,21 @@
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import ThreeDMap from '@/components/map/ThreeDMap.vue'
|
||||
import { fetchMapData } from '@/services/dashboard.js'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
ThreeDMap
|
||||
},
|
||||
setup() {
|
||||
const currentTime = ref(new Date().toLocaleString())
|
||||
const breedingChart = ref(null)
|
||||
const transactionChart = ref(null)
|
||||
const regionChart = ref(null)
|
||||
const riskRadarChart = ref(null)
|
||||
const threeDMap = ref(null)
|
||||
|
||||
let breedingChartInstance = null
|
||||
let transactionChartInstance = null
|
||||
@@ -158,6 +155,9 @@ export default {
|
||||
{ time: '08-18 16:15', type: '运输风险', desc: '运输路线受阻', status: '已处理' }
|
||||
])
|
||||
|
||||
// 地图数据
|
||||
const mapData = ref([])
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = () => {
|
||||
// 确保 DOM 元素已正确绑定
|
||||
@@ -255,6 +255,35 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取地图数据
|
||||
const loadMapData = async () => {
|
||||
try {
|
||||
const data = await fetchMapData()
|
||||
if (data.regions && data.regions.length > 0) {
|
||||
mapData.value = data.regions
|
||||
} else {
|
||||
// 使用默认数据
|
||||
mapData.value = [
|
||||
{ id: 'xlg', name: '锡林浩特市', cattle_count: 25600, farm_count: 120, coordinates: [116.093, 43.946] },
|
||||
{ id: 'dwq', name: '东乌旗', cattle_count: 18500, farm_count: 95, coordinates: [116.980, 45.514] },
|
||||
{ id: 'xwq', name: '西乌旗', cattle_count: 21200, farm_count: 108, coordinates: [117.615, 44.587] },
|
||||
{ id: 'abg', name: '阿巴嘎旗', cattle_count: 16800, farm_count: 86, coordinates: [114.971, 44.022] },
|
||||
{ id: 'snz', name: '苏尼特左旗', cattle_count: 12400, farm_count: 65, coordinates: [113.653, 43.859] }
|
||||
]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取地图数据失败:', error)
|
||||
// 使用默认数据
|
||||
mapData.value = [
|
||||
{ id: 'xlg', name: '锡林浩特市', cattle_count: 25600, farm_count: 120, coordinates: [116.093, 43.946] },
|
||||
{ id: 'dwq', name: '东乌旗', cattle_count: 18500, farm_count: 95, coordinates: [116.980, 45.514] },
|
||||
{ id: 'xwq', name: '西乌旗', cattle_count: 21200, farm_count: 108, coordinates: [117.615, 44.587] },
|
||||
{ id: 'abg', name: '阿巴嘎旗', cattle_count: 16800, farm_count: 86, coordinates: [114.971, 44.022] },
|
||||
{ id: 'snz', name: '苏尼特左旗', cattle_count: 12400, farm_count: 65, coordinates: [113.653, 43.859] }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// 更新时间
|
||||
const updateTime = () => {
|
||||
currentTime.value = new Date().toLocaleString()
|
||||
@@ -270,6 +299,7 @@ export default {
|
||||
|
||||
onMounted(() => {
|
||||
initCharts()
|
||||
loadMapData()
|
||||
timer = setInterval(updateTime, 1000)
|
||||
window.addEventListener('resize', resizeCharts)
|
||||
})
|
||||
@@ -287,10 +317,12 @@ export default {
|
||||
currentTime,
|
||||
keyMetrics,
|
||||
riskData,
|
||||
mapData,
|
||||
breedingChart,
|
||||
transactionChart,
|
||||
regionChart,
|
||||
riskRadarChart
|
||||
riskRadarChart,
|
||||
threeDMap
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,15 +457,15 @@ export default {
|
||||
}
|
||||
|
||||
.center-top {
|
||||
height: 20%;
|
||||
height: 40%;
|
||||
}
|
||||
|
||||
.center-middle {
|
||||
height: 50%;
|
||||
height: 35%;
|
||||
}
|
||||
|
||||
.center-bottom {
|
||||
height: 30%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.center-border, .center-chart-border {
|
||||
@@ -442,6 +474,10 @@ export default {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.center-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user