更新站点资讯界面

This commit is contained in:
李志强 2025-12-24 17:46:26 +08:00
parent 5fcd17f858
commit f97bd549da
32 changed files with 16521 additions and 123 deletions

2
frontend/.env Normal file
View File

@ -0,0 +1,2 @@
# API 基础地址
VITE_API_DOMAIN=http://localhost:8000

View File

@ -8,9 +8,9 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
"axios": "^1.13.2",
"element-plus": "^2.13.0",
"pinia": "^3.0.4",
"sass": "^1.97.1",
"unplugin-auto-import": "^20.3.0",
"unplugin-vue-components": "^30.0.0",
"vue": "^3.5.24",
@ -20,6 +20,8 @@
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"less": "^4.5.1",
"less-loader": "^12.3.0",
"typescript": "~5.9.3",
"vite": "^7.2.4",
"vue-tsc": "^3.1.4"
@ -605,9 +607,11 @@
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
@ -644,11 +648,13 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -664,11 +670,13 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -684,11 +692,13 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -704,11 +714,13 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -724,11 +736,13 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -744,11 +758,13 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -764,11 +780,13 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -784,11 +802,13 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -804,11 +824,13 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -824,11 +846,13 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -844,11 +868,13 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -864,11 +890,13 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -884,11 +912,13 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 10.0.0"
},
@ -1584,6 +1614,23 @@
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/birpc": {
"version": "2.9.0",
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
@ -1597,8 +1644,10 @@
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@ -1606,6 +1655,19 @@
"node": ">=8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
@ -1621,6 +1683,18 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/confbox": {
"version": "0.2.2",
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz",
@ -1671,12 +1745,23 @@
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"peer": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
@ -1684,6 +1769,20 @@
"node": ">=0.10"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/element-plus": {
"version": "2.13.0",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.0.tgz",
@ -1721,6 +1820,65 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz",
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"prr": "~1.0.1"
},
"bin": {
"errno": "cli.js"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.27.2",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz",
@ -1808,8 +1966,10 @@
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@ -1817,6 +1977,42 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
@ -1832,24 +2028,162 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"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.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"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.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
"dev": true,
"license": "MIT",
"optional": true,
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/immutable": {
"version": "5.1.4",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"license": "MIT"
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -1858,8 +2192,10 @@
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@ -1871,8 +2207,10 @@
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.12.0"
}
@ -1895,6 +2233,81 @@
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
"license": "MIT"
},
"node_modules/less": {
"version": "4.5.1",
"resolved": "https://registry.npmmirror.com/less/-/less-4.5.1.tgz",
"integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
"tslib": "^2.3.0"
},
"bin": {
"lessc": "bin/lessc"
},
"engines": {
"node": ">=14"
},
"optionalDependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"needle": "^3.1.0",
"source-map": "~0.6.0"
}
},
"node_modules/less-loader": {
"version": "12.3.0",
"resolved": "https://registry.npmmirror.com/less-loader/-/less-loader-12.3.0.tgz",
"integrity": "sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"@rspack/core": "0.x || 1.x",
"less": "^3.5.0 || ^4.0.0",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"@rspack/core": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/less/node_modules/copy-anything": {
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz",
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-what": "^3.14.1"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/less/node_modules/is-what": {
"version": "3.14.1",
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz",
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
"dev": true,
"license": "MIT"
},
"node_modules/local-pkg": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz",
@ -1944,6 +2357,30 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
@ -1954,8 +2391,10 @@
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@ -1968,8 +2407,10 @@
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=8.6"
},
@ -1977,6 +2418,41 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true,
"license": "MIT",
"optional": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
@ -2043,12 +2519,32 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/needle": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz",
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.3",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
"optional": true,
"peer": true
},
"node_modules/normalize-wheel-es": {
"version": "1.2.0",
@ -2056,6 +2552,16 @@
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
"node_modules/parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz",
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
@ -2093,6 +2599,17 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=6"
}
},
"node_modules/pinia": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz",
@ -2162,6 +2679,20 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz",
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
@ -2239,11 +2770,22 @@
"fsevents": "~2.3.2"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/sass": {
"version": "1.97.1",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.97.1.tgz",
"integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@ -2259,12 +2801,42 @@
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/sax": {
"version": "1.4.3",
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.3.tgz",
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"optional": true
},
"node_modules/scule": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
"license": "MIT"
},
"node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
@ -2327,8 +2899,10 @@
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"is-number": "^7.0.0"
},
@ -2336,6 +2910,13 @@
"node": ">=8.0"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",

View File

@ -9,9 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.13.2",
"element-plus": "^2.13.0",
"pinia": "^3.0.4",
"sass": "^1.97.1",
"unplugin-auto-import": "^20.3.0",
"unplugin-vue-components": "^30.0.0",
"vue": "^3.5.24",
@ -21,6 +21,8 @@
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"less": "^4.5.1",
"less-loader": "^12.3.0",
"typescript": "~5.9.3",
"vite": "^7.2.4",
"vue-tsc": "^3.1.4"

369
frontend/src/api/README.md Normal file
View File

@ -0,0 +1,369 @@
# API 接口管理使用说明
## 📁 文件结构
```
src/api/
├── axios.ts # 🚀 Axios 配置、拦截器和通用请求方法
├── api.ts # 📋 具体 API 接口定义和管理
├── status.ts # ⚠️ HTTP 状态码处理
└── README.md # 📖 使用说明(本文档)
```
## 🚀 快速开始
### 1. 环境变量配置
在项目根目录创建或编辑 `.env` 文件:
```env
# API 基础地址
VITE_API_DOMAIN=http://localhost:8080/api
```
### 2. 在组件中使用 API
```typescript
// 导入 API 服务
import { UserService, landRelevant } from '@/api/api'
// 在组件中使用
const handleLogin = async () => {
try {
const response = await UserService.login1({
username: 'admin',
password: '123456'
})
console.log('登录成功:', response.data)
} catch (error) {
console.error('登录失败:', error)
}
}
const getLandList = async () => {
try {
const response = await landRelevant.landList({
page: 1,
size: 10
})
console.log('土地列表:', response.data)
} catch (error) {
console.error('获取列表失败:', error)
}
}
```
## 📋 详细说明
### axios.ts - HTTP 请求核心配置
#### 🔧 主要功能:
- **超时配置**60秒超时
- **基础 URL**:通过环境变量配置
- **请求拦截器**:自动添加请求头和 token
- **响应拦截器**:统一错误处理
- **通用请求方法**:封装 GET/POST 请求
#### 📝 配置详情:
```typescript
// 超时时间
axios.defaults.timeout = 60000
// 基础 URL
axios.defaults.baseURL = import.meta.env.VITE_API_DOMAIN
// 请求头配置
config.headers = {
"Content-Type": "application/json;charset=UTF-8",
token: "80c483d59ca86ad0393cf8a98416e2a1"
}
```
#### 🎯 使用通用请求方法:
```typescript
import { request } from '@/api/axios'
// GET 请求
const getData = await request('/api/data', { id: 1 }, 'GET')
// POST 请求
const postData = await request('/api/submit', { name: 'test' }, 'POST')
```
### api.ts - 业务接口管理
#### 🏗️ 架构设计:
- **类化管理**:按业务模块划分服务类
- **静态方法**:所有接口方法都是静态方法
- **统一导入**:所有接口都通过 `request` 方法调用
#### 📚 当前接口模块:
##### UserService - 用户相关接口
```typescript
// 用户登录相关接口
UserService.login1(params) // 接口一
UserService.login2(params) // 接口二
UserService.login3(params) // 接口三
```
##### landRelevant - 土地相关接口
```typescript
// 获取土地列表
landRelevant.landList(params)
```
##### menu - 菜单相关接口
```typescript
// 获取头部菜单
menu.getHeaderMenu()
```
**返回数据结构:**
```json
{
"code": 0,
"msg": "获取主菜单成功",
"data": [
{
"id": 1,
"parent_id": 0,
"title": "站点资讯",
"src": "/articles",
"icon": "",
"sort": 0,
"children": []
}
]
}
```
#### 添加新的接口:
```typescript
// 1. 在现有类中添加方法
export class UserService {
// 添加新方法
static async getUserInfo(params) {
return request("/user/info", params, "get");
}
}
// 2. 创建新的服务类
export class ProductService {
// 产品相关接口
static async getProductList(params) {
return request("/product/list", params, "get");
}
static async createProduct(params) {
return request("/product/create", params, "post");
}
}
```
### status.ts - 状态码处理
#### 🎯 功能说明:
- **HTTP 状态码映射**:将状态码转换为用户友好的错误信息
- **统一错误提示**:所有 HTTP 错误都有对应的中文提示
#### 📊 支持的状态码:
| 状态码 | 说明 | 提示信息 |
|--------|------|----------|
| 400 | 请求错误 | 请求错误(400) |
| 401 | 未授权 | 未授权,请重新登录(401) |
| 403 | 拒绝访问 | 拒绝访问(403) |
| 404 | 请求出错 | 请求出错(404) |
| 408 | 请求超时 | 请求超时(408) |
| 500 | 服务器错误 | 服务器错误(500) |
| 501 | 服务未实现 | 服务未实现(501) |
| 502 | 网络错误 | 网络错误(502) |
| 503 | 服务不可用 | 服务不可用(503) |
| 504 | 网络超时 | 网络超时(504) |
| 505 | HTTP版本不受支持 | HTTP版本不受支持(505) |
## 💡 使用示例
### 在 Vue 组件中使用
```vue
<template>
<div>
<el-button @click="handleLogin" :loading="loading">
登录
</el-button>
<el-button @click="getLands">
获取土地列表
</el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { UserService, landRelevant } from '@/api/api'
import { ElMessage } from 'element-plus'
const loading = ref(false)
// 用户登录
const handleLogin = async () => {
loading.value = true
try {
const response = await UserService.login1({
username: 'admin',
password: '123456'
})
if (response.data.code === 200) {
ElMessage.success('登录成功')
// 处理登录成功逻辑
}
} catch (error) {
console.error('登录失败:', error)
ElMessage.error('登录失败,请重试')
} finally {
loading.value = false
}
}
// 获取土地列表
const getLands = async () => {
try {
const response = await landRelevant.landList({
page: 1,
size: 20
})
console.log('土地数据:', response.data)
// 处理数据展示逻辑
} catch (error) {
console.error('获取土地列表失败:', error)
}
}
// 获取菜单数据
const getMenus = async () => {
try {
const response = await menu.getHeaderMenu();
console.log('菜单数据:', response.data);
// 更新菜单状态
} catch (error) {
console.error('获取菜单失败:', error);
}
}
</script>
```
### 在组合式函数中使用
```typescript
// composables/useUser.ts
import { ref } from 'vue'
import { UserService } from '@/api/api'
export function useUser() {
const userInfo = ref(null)
const loading = ref(false)
const login = async (credentials) => {
loading.value = true
try {
const response = await UserService.login1(credentials)
userInfo.value = response.data.user
return response.data
} catch (error) {
throw error
} finally {
loading.value = false
}
}
const logout = () => {
userInfo.value = null
// 清除本地存储等
}
return {
userInfo,
loading,
login,
logout
}
}
```
## ⚠️ 注意事项
### 1. 错误处理
- 所有接口调用都需要 try-catch 包装
- 使用 Element Plus 的 ElMessage 显示错误信息
### 2. 环境变量
- 确保 `.env` 文件中配置了 `VITE_API_DOMAIN`
- 不同环境可以使用不同的配置文件
### 3. Token 管理
- 当前代码中 token 是硬编码的
- 实际项目中应该从 localStorage 或 Vuex/Pinia 中获取
### 4. 类型安全
- 建议为请求参数和响应数据添加 TypeScript 类型定义
- 可以创建 `types.ts` 文件定义接口类型
## 🔧 扩展指南
### 添加新的业务模块
```typescript
// 1. 在 api.ts 中添加新的服务类
export class OrderService {
static async getOrderList(params) {
return request("/order/list", params, "get");
}
static async createOrder(params) {
return request("/order/create", params, "post");
}
}
// 2. 在组件中导入使用
import { OrderService } from '@/api/api'
```
### 自定义请求配置
```typescript
// 如果需要特殊的请求配置,可以直接使用 axios
import axios from '@/api/axios'
// 自定义配置的请求
const customRequest = await axios({
method: 'POST',
url: '/custom/api',
data: params,
timeout: 30000,
headers: {
'Custom-Header': 'value'
}
})
```
## 🎯 最佳实践
1. **统一错误处理**:在组件中统一处理接口错误
2. **Loading 状态**:显示加载状态提升用户体验
3. **数据缓存**:对不变数据使用缓存策略
4. **接口文档**:为每个接口方法添加 JSDoc 注释
5. **模块化**:按业务领域划分接口模块
## 📞 技术支持
如有问题,请检查:
1. 环境变量配置是否正确
2. 网络连接是否正常
3. 后端接口是否可用
4. 控制台是否有详细错误信息

6
frontend/src/api/api.ts Normal file
View File

@ -0,0 +1,6 @@
//进行接口API的统一管理
import { request } from "./axios";
export class UserService {
}

70
frontend/src/api/axios.ts Normal file
View File

@ -0,0 +1,70 @@
//封装请求配置拦截器
import axios from "axios";
import { showMessage } from "./status"; // 引入状态码文件
import { ElMessage } from "element-plus";
// 设置接口超时时间
axios.defaults.timeout = 60000;
// @ts-ignore
axios.defaults.baseURL = import.meta.env.VITE_API_DOMAIN;
//http request 拦截器
axios.interceptors.request.use(
(config) => {
// 配置请求头
config.headers.set("Content-Type", "application/json;charset=UTF-8");
return config;
},
(error) => {
return Promise.reject(error);
}
);
//http response 拦截器
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
const { response } = error;
if (response) {
// 请求已发出但是不在2xx的范围
showMessage(response.status); // 传入响应码,匹配响应码对应信息
return Promise.reject(response.data);
} else {
ElMessage.warning("网络连接异常,请稍后再试!");
}
}
);
// 封装 GET POST 请求并导出
export function request(url = "", params = {}, type = "POST") {
//设置 url params type 的默认值
return new Promise((resolve, reject) => {
let promise;
if (type.toUpperCase() === "GET") {
promise = axios({
url,
params,
});
} else if (type.toUpperCase() === "POST") {
promise = axios({
method: "POST",
url,
data: params,
});
} else {
reject(new Error(`不支持的请求类型: ${type}`));
return;
}
//处理返回
promise
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
}

12
frontend/src/api/menu.ts Normal file
View File

@ -0,0 +1,12 @@
//进行接口API的统一管理
import { request } from "./axios";
export class menu {
/**
* @description header菜单
* @return {Promise}
*/
static async getHeaderMenu() {
return request("/index/index/getmainmenu", "get");
}
}

View File

@ -0,0 +1,21 @@
//进行接口API的统一管理
import { request } from "./axios";
export class siteInformation {
/**
* @description siteinformation分类
* @return {Promise}
*/
static async getSiteInformationCategory() {
return request("/index/articles/getSiteInformationCategory", "get");
}
/**
* @description siteinformation文章列表
* @param {string} cateid - ID
* @return {Promise}
*/
static async getSiteInformationLists(cateid: string) {
return request("/index/articles/getSiteInformationLists", { cateid }, "get");
}
}

View File

@ -0,0 +1,42 @@
//管理接口返回状态码
export const showMessage = (status: number | string): string => {
let message: string = "";
switch (status) {
case 400:
message = "请求错误(400)";
break;
case 401:
message = "未授权,请重新登录(401)";
break;
case 403:
message = "拒绝访问(403)";
break;
case 404:
message = "请求出错(404)";
break;
case 408:
message = "请求超时(408)";
break;
case 500:
message = "服务器错误(500)";
break;
case 501:
message = "服务未实现(501)";
break;
case 502:
message = "网络错误(502)";
break;
case 503:
message = "服务不可用(503)";
break;
case 504:
message = "网络超时(504)";
break;
case 505:
message = "HTTP版本不受支持(505)";
break;
default:
message = `连接出错(${status})!`;
}
return `${message},请检查网络或联系管理员!`;
};

13870
frontend/src/assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,126 @@
@import url(@/assets/css/bootstrap.min.css);
@import url(@/assets/less/main.less);
// 全局样式变量
:root {
--white:#fff;
--primary-color: #409eff;
--success-color: #67c23a;
--warning-color: #e6a23c;
--danger-color: #f56c6c;
--info-color: #909399;
--text-primary: #303133;
--text-regular: #606266;
--text-secondary: #909399;
--text-placeholder: #c0c4cc;
--border-color: #dcdfe6;
--border-color-light: #e4e7ed;
--border-color-lighter: #ebeef5;
--background-color: #f5f5f5;
--background-color-page: #f0f2f5;
}
// 全局重置样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a{
text-decoration: none;
}
html,
body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
font-size: 14px;
color: var(--text-primary);
background-color: var(--background-color-page);
}
#app {
min-height: 100vh;
}
// 工具类
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.d-flex {
display: flex;
}
.d-block {
display: block;
}
.d-none {
display: none;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.align-center {
align-items: center;
}
.w-100 {
width: 100%;
}
.h-100 {
height: 100%;
}
.m-0 {
margin: 0;
}
.mt-1 {
margin-top: 8px;
}
.mt-2 {
margin-top: 16px;
}
.mt-3 {
margin-top: 24px;
}
.mb-1 {
margin-bottom: 8px;
}
.mb-2 {
margin-bottom: 16px;
}
.mb-3 {
margin-bottom: 24px;
}
.p-1 {
padding: 8px;
}
.p-2 {
padding: 16px;
}
.p-3 {
padding: 24px;
}

26
frontend/src/components.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElPagination: typeof import('element-plus/es')['ElPagination']
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

View File

@ -1,5 +1,5 @@
import { createApp } from "vue";
import "./style.css";
import "@/assets/less/global.less";
import App from "./App.vue";
import router from "@/router";
import { createPinia } from 'pinia'

View File

@ -1,19 +1,43 @@
import { createRouter, createWebHistory } from "vue-router";
import type { RouteRecordRaw } from "vue-router";
const history = createWebHistory();
const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/index",
},
{
path: "/index",
name: "index",
component: () => import("@/views/index/index.vue"),
},
];
const router = createRouter({
history,
routes,
history: createWebHistory(),
routes: [
{
path: "/",
redirect: "/index",
},
{
path: "/index",
name: "index",
component: () => import("@/views/index/index.vue"),
},
{
path: "/siteInformation",
name: "siteInformation",
component: () => import("@/views/siteInformation/index.vue"),
},
{
path: "/technicalArticles",
name: "technicalArticles",
component: () => import("@/views/technicalArticles/index.vue"),
},
{
path: "/officeResources",
name: "officeResources",
component: () => import("@/views/officeResources/index.vue"),
},
{
path: "/downloadPrograms",
name: "downloadPrograms",
component: () => import("@/views/downloadPrograms/index.vue"),
},
{
path: "/downloadGames",
name: "downloadGames",
component: () => import("@/views/downloadGames/index.vue"),
},
],
});
export default router;

View File

@ -1,79 +0,0 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@ -0,0 +1,282 @@
<script setup lang="ts">
</script>
<template>
<div class="footer">
<div class="container">
<div class="row" style="width: 100%">
<div class="row-main">
<!-- Logo 和介绍 -->
<div class="mr-20">
<img src="@/assets/imgs/logo-light.png" alt="Logo" height="70" />
<p class="text-white-50 my-4 f18" style="width: 400px">
美天智能科技这里是介绍
</p>
</div>
<!-- 菜单区域 -->
<div
style="
display: flex;
justify-content: space-between;
width: 100%;
margin-right: 200px;
"
>
<!-- 关于我们 -->
<div>
<h4 class="text-white f-20 font-weight-normal mb-3">关于我们</h4>
<ul class="list-unstyled footer-sub-menu">
<li><a href="#" class="footer-link">概况</a></li>
<li><a href="#" class="footer-link">资讯</a></li>
<li><a href="#" class="footer-link">加入我们</a></li>
<li><a href="#" class="footer-link">联系我们</a></li>
</ul>
</div>
<!-- 商务合作 -->
<div>
<h4 class="text-white f-20 font-weight-normal mb-3">商务合作</h4>
<ul class="list-unstyled footer-sub-menu">
<li><a href="#" class="footer-link">商务合作</a></li>
</ul>
</div>
<!-- 服务支持 -->
<div>
<h4 class="text-white f-20 font-weight-normal mb-3">服务支持</h4>
<ul class="list-unstyled footer-sub-menu">
<li><a href="#" class="footer-link">常见问答</a></li>
<li><a href="#" class="footer-link">软件下载</a></li>
<li><a href="#" class="footer-link">服务政策</a></li>
<li><a href="#" class="footer-link">投诉建议</a></li>
</ul>
</div>
</div>
<!-- 微信二维码 -->
<div>
<div class="text-center">
<img
src="@/assets/imgs/wechat_qrcode.jpg"
alt="微信二维码"
class="img-fluid"
style="max-width: 150px"
/>
<p class="text-white-50 mt-2">微信公众号</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 版权信息 -->
<section class="copyright text-center">
<div class="container">
<p class="copyright__text">
Copyright <span class="dynamic-year">2025</span> | All Rights By
<a href="http://www.yunzer.cn">Yunzer</a>
</p>
</div>
<div class="container">
<a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow"
>苏ICP备2023006641号</a
>
</div>
<div class="tongji"></div>
</section>
</template>
<style lang="less" scoped>
.footer {
padding: 80px 0;
position: relative;
background-color: #2a254d;
margin-top: auto; // footer
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-image: url(@/assets/imgs/footer-bg-1.png);
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
.row {
.row-main {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 40px;
.mr-20 {
flex-shrink: 0;
margin-right: 20px;
img {
margin-bottom: 20px;
}
.text-white-50 {
color: rgba(255, 255, 255, 0.5) !important;
}
.my-4 {
margin-top: 1.5rem !important;
margin-bottom: 1.5rem !important;
}
.f18 {
font-size: 18px;
}
}
//
> div:nth-child(2) {
flex: 1;
display: flex;
justify-content: space-between;
> div {
h4 {
&.text-white {
color: #fff;
}
&.f-20 {
font-size: 20px;
}
&.font-weight-normal {
font-weight: 400;
}
&.mb-3 {
margin-bottom: 1rem;
}
}
.footer-sub-menu {
list-style: none;
padding: 0;
margin: 0;
li {
margin-bottom: 8px;
.footer-link {
color: rgba(255, 255, 255, 0.7);
text-decoration: none;
font-size: 14px;
transition: color 0.3s ease;
&:hover {
color: #fff;
}
}
}
}
}
}
//
> div:last-child {
flex-shrink: 0;
.text-center {
text-align: center;
.img-fluid {
max-width: 100%;
height: auto;
}
.mt-2 {
margin-top: 0.5rem;
}
.text-white-50 {
color: rgba(255, 255, 255, 0.5) !important;
}
}
}
}
}
}
//
@media (max-width: 992px) {
.footer {
.row-main {
flex-direction: column;
gap: 30px;
.mr-20 {
margin-right: 0;
text-align: center;
p {
width: 100% !important;
max-width: 400px;
margin: 0 auto;
}
}
> div:nth-child(2) {
margin-right: 0 !important;
flex-direction: column;
gap: 30px;
> div {
text-align: center;
}
}
> div:last-child {
text-align: center;
}
}
}
}
@media (max-width: 576px) {
.footer {
padding: 40px 0;
.row-main {
gap: 20px;
}
}
}
.copyright {
position: relative;
background-color: #1f1944;
padding: 27px 0 28px;
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
.copyright__text {
color: #697585;
margin: 0;
font-size: 16px;
line-height: 25px;
font-weight: 400;
}
.copyright__text a {
color: #f57005;
cursor: pointer;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
</style>

View File

@ -0,0 +1,291 @@
<script lang="ts" setup>
import { ref } from "vue";
import { Search, User, Setting, SwitchButton } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
//
const showSearch = ref(false);
const searchText = ref("");
const username = ref("管理员"); // store
//
interface MenuItem {
path: string;
label: string;
}
const staticMenuItems: MenuItem[] = [
{ path: "/", label: "首页" },
{ path: "/siteInformation", label: "站点资讯" },
{ path: "/technicalArticles", label: "技术文章" },
{ path: "/officeResources", label: "办公资源" },
{ path: "/downloadPrograms", label: "程序下载" },
{ path: "/downloadGames", label: "游戏下载" },
];
//
const handleSearch = () => {
if (searchText.value.trim()) {
console.log("搜索:", searchText.value);
// TODO:
//
showSearch.value = false;
searchText.value = "";
}
};
const handleCommand = (command: string) => {
switch (command) {
case "profile":
console.log("跳转到个人中心");
// TODO:
ElMessage.info("跳转到个人中心");
break;
case "account":
console.log("跳转到账号管理");
// TODO:
ElMessage.info("跳转到账号管理");
break;
case "logout":
console.log("退出登录");
// TODO: 退
ElMessage.success("退出登录成功");
break;
}
};
</script>
<template>
<div class="header">
<div class="header-content">
<!-- 左侧 Logo -->
<div class="header-left">
<div class="logo">
<img src="@/assets/imgs/logo.png" alt="Logo" />
</div>
</div>
<!-- 中间菜单 -->
<div class="header-center">
<nav class="nav-menu">
<ul class="menu-list">
<li
v-for="item in staticMenuItems"
:key="item.path"
class="menu-item"
>
<router-link :to="item.path" class="menu-link">{{
item.label
}}</router-link>
</li>
</ul>
</nav>
</div>
<!-- 右侧工具栏 -->
<div class="header-right">
<!-- 搜索按钮 -->
<el-button link class="search-btn" @click="showSearch = !showSearch">
<el-icon><Search /></el-icon>
</el-button>
<!-- 搜索框可展开 -->
<el-input
v-if="showSearch"
v-model="searchText"
placeholder="搜索..."
size="small"
class="search-input"
@keyup.enter="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<!-- 用户信息 -->
<div class="user-info">
<span class="username">{{ username }}</span>
<el-dropdown @command="handleCommand" class="user-dropdown">
<el-avatar :size="32" class="user-avatar">
{{ username.charAt(0).toUpperCase() }}
</el-avatar>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile">
<el-icon><User /></el-icon>
个人中心
</el-dropdown-item>
<el-dropdown-item command="account">
<el-icon><Setting /></el-icon>
账号管理
</el-dropdown-item>
<el-dropdown-item command="logout" divided>
<el-icon><SwitchButton /></el-icon>
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
.header-left {
.logo {
img {
height: auto;
width: 180px;
}
}
}
.header-center {
.nav-menu {
.menu-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 30px;
.menu-item {
.menu-link {
text-decoration: none;
color: #333;
font-weight: 500;
padding: 8px 16px;
border-radius: 4px;
transition: all 0.3s ease;
&:hover {
color: #409eff;
background: rgba(64, 158, 255, 0.1);
}
&.router-link-active {
color: #409eff;
background: rgba(64, 158, 255, 0.1);
}
}
}
}
}
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
.search-btn {
color: #666;
&:hover {
color: #409eff;
}
}
.search-input {
width: 200px;
margin-right: 16px;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
.username {
color: #333;
font-size: 14px;
font-weight: 500;
}
.user-dropdown {
.user-avatar {
background: #409eff;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: scale(1.05);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
}
}
}
}
}
}
}
//
@media (max-width: 768px) {
.header {
.header-content {
padding: 0 15px;
.header-center {
.nav-menu {
.menu-list {
gap: 15px;
.menu-item {
.menu-link {
padding: 6px 12px;
font-size: 14px;
}
}
}
}
}
.header-right {
gap: 8px;
.search-input {
width: 150px;
margin-right: 8px;
}
.username {
display: none; //
}
}
}
}
}
@media (max-width: 480px) {
.header {
.header-content {
.header-center {
display: none; //
}
}
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<Header />
<div class="download-games-page">
<div class="container">
<h1 class="page-title">游戏下载</h1>
<div class="content">
<p>这里是游戏下载页面内容</p>
<p>您可以在这里提供各种游戏的下载链接和介绍</p>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import Footer from "@/views/components/footer.vue";
import Header from "../components/header.vue";
</script>
<style scoped lang="less">
.download-games-page {
padding: 120px 20px 40px; // paddingheader
min-height: 100vh;
background: #f5f5f5;
.container {
max-width: 1200px;
margin: 0 auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 40px;
.page-title {
color: #333;
font-size: 28px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.content {
color: #666;
line-height: 1.6;
p {
margin-bottom: 16px;
}
}
}
}
@media (max-width: 768px) {
.download-games-page {
padding: 100px 15px 20px;
.container {
padding: 20px;
.page-title {
font-size: 24px;
}
}
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<Header />
<div class="download-programs-page">
<div class="container">
<h1 class="page-title">程序下载</h1>
<div class="content">
<p>这里是程序下载页面内容</p>
<p>您可以在这里提供各种软件程序的下载链接和介绍</p>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import Header from "@/views/components/header.vue";
import Footer from "@/views/components/footer.vue";
</script>
<style scoped lang="less">
.download-programs-page {
padding: 120px 20px 40px; // paddingheader
min-height: 100vh;
background: #f5f5f5;
.container {
max-width: 1200px;
margin: 0 auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 40px;
.page-title {
color: #333;
font-size: 28px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.content {
color: #666;
line-height: 1.6;
p {
margin-bottom: 16px;
}
}
}
}
@media (max-width: 768px) {
.download-programs-page {
padding: 100px 15px 20px;
.container {
padding: 20px;
.page-title {
font-size: 24px;
}
}
}
}
</style>

View File

@ -1,24 +1,64 @@
<template>
<div>hello world!</div>
<div>姓名{{username}}年龄{{age}}</div>
<el-button @click="showMessage" type="success">Success</el-button>
<div class="app-layout">
<Header />
<!-- 主内容区域 -->
<main class="main-content">
<div class="content-wrapper">
<div>hello world!</div>
<div>姓名{{ username }}年龄{{ age }}</div>
<el-button @click="showMessage" type="success">Success</el-button>
</div>
</main>
<Footer />
</div>
</template>
<script lang="ts" setup name="Home">
import storeUser from '@/store/user'
import { storeToRefs } from 'pinia'
<script lang="ts" setup>
import Header from "@/views/components/header.vue";
import Footer from "@/views/components/footer.vue";
import storeUser from "@/store/user";
import { storeToRefs } from "pinia";
//
import 'element-plus/es/components/message/style/css'
import { ElMessage } from 'element-plus'
// import "element-plus/es/components/message/style/css";
import { ElMessage } from "element-plus";
const showMessage = ()=>{
ElMessage.success('test')
}
const showMessage = () => {
ElMessage.success("test");
};
const userStore = storeUser()
const { username, age } = storeToRefs(userStore)
const userStore = storeUser();
const { username, age } = storeToRefs(userStore);
</script>
<style scoped>
<style scoped lang="less">
.app-layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
</style>
.main-content {
flex: 1;
display: flex;
flex-direction: column;
}
.content-wrapper {
flex: 1;
padding: 20px;
background-color: var(--background-color-page);
}
//
@media (max-width: 768px) {
.content-wrapper {
padding: 15px;
}
}
@media (max-width: 480px) {
.content-wrapper {
padding: 10px;
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<Header />
<div class="office-resources-page">
<div class="container">
<h1 class="page-title">办公资源</h1>
<div class="content">
<p>这里是办公资源页面内容</p>
<p>您可以在这里分享办公模板工具资源等内容</p>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import Header from "@/views/components/header.vue";
import Footer from "@/views/components/footer.vue";
</script>
<style scoped lang="less">
.office-resources-page {
padding: 120px 20px 40px; // paddingheader
min-height: 100vh;
background: #f5f5f5;
.container {
max-width: 1200px;
margin: 0 auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 40px;
.page-title {
color: #333;
font-size: 28px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.content {
color: #666;
line-height: 1.6;
p {
margin-bottom: 16px;
}
}
}
}
@media (max-width: 768px) {
.office-resources-page {
padding: 100px 15px 20px;
.container {
padding: 20px;
.page-title {
font-size: 24px;
}
}
}
}
</style>

View File

@ -0,0 +1,422 @@
<template>
<Header />
<div class="articles-page">
<div class="container">
<div class="top-content">
<h1 class="page-title">站点资讯</h1>
<div class="content flex flex-column align-items-center">
<p>最新的站点资讯公告等内容</p>
</div>
</div>
<div class="main-content">
<!-- 左侧分类导航 -->
<div class="left-menu">
<div class="menu-title">分类导航</div>
<ul class="category-list">
<li
v-for="category in categories"
:key="category.id"
class="category-item"
:class="{ active: selectedCategory === category.id }"
@click="selectCategory(category.id)"
>
{{ category.name }}
</li>
</ul>
</div>
<!-- 右侧内容区域 -->
<div class="contents">
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="articles.length === 0" class="no-data">暂无内容</div>
<div v-else class="articles-grid">
<div
v-for="article in articles"
:key="article.id"
class="article-card"
>
<div class="card-header">
<h3 class="article-title">{{ article.title }}</h3>
<div class="article-meta">
<span class="article-date">{{
formatDate(article.created_at)
}}</span>
<span class="article-category">{{
article.category_name
}}</span>
</div>
</div>
<div class="card-body">
<p class="article-summary">
{{
article.summary ||
article.content?.substring(0, 100) + "..."
}}
</p>
</div>
<div class="card-footer">
<router-link :to="`/article/${article.id}`" class="read-more">
阅读更多
</router-link>
</div>
</div>
</div>
<!-- 分页组件 -->
<div v-if="total > 0" class="pagination-wrapper">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import Header from "@/views/components/header.vue";
import Footer from "@/views/components/footer.vue";
import {
siteInformation,
getSiteInformationLists,
} from "@/api/siteInformation";
//
const categories = ref<any[]>([]); //
const articles = ref<any[]>([]); //
const selectedCategory = ref<string>(""); //
const loading = ref(false); //
const currentPage = ref(1); //
const pageSize = ref(10); //
const total = ref(0); //
//
const fetchCategories = async () => {
try {
const response: any = await siteInformation.getSiteInformationCategory();
if (
response.data &&
response.data.data &&
Array.isArray(response.data.data)
) {
categories.value = response.data.data;
//
if (categories.value.length > 0) {
selectedCategory.value = categories.value[0].id;
fetchArticles();
}
} else {
categories.value = [];
ElMessage.warning(response.data?.msg || response.msg || "获取分类失败");
}
} catch (error) {
console.error("获取分类失败:", error);
categories.value = [];
ElMessage.warning("获取分类失败");
}
};
//
const fetchArticles = async (page: number = currentPage.value) => {
loading.value = true;
try {
const response: any = await siteInformation.getSiteInformationLists(
selectedCategory.value,
page,
pageSize.value
);
const data = response.data?.data;
if (data?.articles && Array.isArray(data.articles)) {
articles.value = data.articles;
total.value = data.total || 0;
currentPage.value = data.page || 1;
pageSize.value = data.limit || 10;
} else {
articles.value = [];
total.value = 0;
ElMessage.warning(response.data?.msg || "获取文章失败");
}
} catch (error) {
console.error("获取文章失败:", error);
articles.value = [];
total.value = 0;
ElMessage.warning("获取文章失败");
} finally {
loading.value = false;
}
};
//
const selectCategory = (categoryId: string) => {
selectedCategory.value = categoryId;
currentPage.value = 1; //
fetchArticles(1);
};
//
const handlePageChange = (page: number) => {
currentPage.value = page;
fetchArticles(page);
};
//
const handlePageSizeChange = (size: number) => {
pageSize.value = size;
currentPage.value = 1; //
fetchArticles(1);
};
//
const formatDate = (dateString: string) => {
if (!dateString) return "";
const date = new Date(dateString);
return date.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
};
//
onMounted(() => {
fetchCategories();
});
</script>
<style scoped lang="less">
.articles-page {
padding-top: 100px;
min-height: 100vh;
background: #f5f5f5;
.top-content {
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
padding: 80px 40px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--white);
.page-title {
font-size: 28px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.content {
line-height: 1.6;
p {
margin-bottom: 16px;
}
}
}
.main-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
gap: 30px;
padding: 40px 0;
align-items: flex-start;
@media (max-width: 768px) {
flex-direction: column;
gap: 20px;
padding: 20px 0;
}
.left-menu {
background-color: var(--white);
padding: 20px;
border-radius: 8px;
width: 250px;
flex-shrink: 0;
align-self: flex-start;
@media (max-width: 768px) {
width: 100%;
}
.menu-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #1e9fff;
}
.category-list {
list-style: none;
padding: 0;
margin: 0;
.category-item {
padding: 12px 16px;
margin-bottom: 8px;
cursor: pointer;
border-radius: 6px;
transition: all 0.3s ease;
border-left: 3px solid transparent;
&:hover {
background: #f0f8ff;
border-left-color: #1e9fff;
}
&.active {
background: #e6f7ff;
border-left-color: #1e9fff;
color: #1e9fff;
font-weight: 500;
}
}
}
}
.contents {
background-color: var(--white);
// min-height: 400px;
border-radius: 8px;
padding: 20px;
flex: 1;
.pagination-wrapper {
margin-top: 30px;
display: flex;
justify-content: center;
padding: 20px 0;
border-top: 1px solid #e8e8e8;
.el-pagination {
--el-pagination-font-size: 14px;
--el-pagination-button-width: 40px;
--el-pagination-button-height: 40px;
}
}
.loading,
.no-data {
text-align: center;
padding: 60px 20px;
color: #666;
font-size: 16px;
}
.articles-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 20px;
@media (max-width: 1200px) {
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 992px) {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
@media (max-width: 480px) {
grid-template-columns: 1fr;
gap: 15px;
}
.article-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #e8e8e8;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.card-header {
padding: 16px 16px 12px;
.article-title {
font-size: 14px;
font-weight: 600;
color: #333;
margin: 0 0 8px 0;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.article-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #999;
.article-date,
.article-category {
flex: 1;
}
}
}
.card-body {
padding: 0 16px;
.article-summary {
font-size: 12px;
color: #666;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
margin: 0;
}
}
.card-footer {
padding: 12px 16px 16px;
text-align: right;
.read-more {
color: #1e9fff;
text-decoration: none;
font-size: 12px;
font-weight: 500;
transition: color 0.3s ease;
&:hover {
color: #0d8aff;
text-decoration: underline;
}
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<Header />
<div class="technical-articles-page">
<div class="container">
<h1 class="page-title">技术文章</h1>
<div class="content">
<p>这里是技术文章页面内容</p>
<p>您可以在这里展示技术博客教程开发经验等内容</p>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import Header from "@/views/components/header.vue";
import Footer from "@/views/components/footer.vue";
</script>
<style scoped lang="less">
.technical-articles-page {
padding: 120px 20px 40px; // paddingheader
min-height: 100vh;
background: #f5f5f5;
.container {
max-width: 1200px;
margin: 0 auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 40px;
.page-title {
color: #333;
font-size: 28px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.content {
color: #666;
line-height: 1.6;
p {
margin-bottom: 16px;
}
}
}
}
@media (max-width: 768px) {
.technical-articles-page {
padding: 100px 15px 20px;
.container {
padding: 20px;
.page-title {
font-size: 24px;
}
}
}
}
</style>

View File

@ -2,7 +2,7 @@
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client", "src/auto-imports.d.ts"],
"types": ["vite/client"],
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
@ -14,8 +14,7 @@
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"skipLibCheck": true
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@ -6,31 +6,59 @@ import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// https://vitejs.dev/config/
export default defineConfig({
// 开发服务器配置(可选,解决跨域等问题)
// server: {
// host: "0.0.0.0", // 允许局域网访问
// port: 5173, // 自定义端口
// open: true, // 启动后自动打开浏览器
// },
plugins: [
vue(),
// 自动导入 Vue/VueRouter/Element Plus API
AutoImport({
imports: ["vue", "vue-router"],
resolvers: [ElementPlusResolver()],
dts: "src/auto-imports.d.ts",
resolvers: [ElementPlusResolver({ importStyle: "css" })],
dts: path.resolve(__dirname, "src/auto-imports.d.ts"),
eslintrc: {
enabled: true,
filepath: path.resolve(__dirname, ".eslintrc-auto-import.json"),
},
}),
// 自动导入 Element Plus 组件
Components({
resolvers: [ElementPlusResolver()],
dirs: ["src/components"],
resolvers: [ElementPlusResolver({ importStyle: "css" })],
dts: path.resolve(__dirname, "src/components.d.ts"),
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
// 省略文件扩展名,导入时无需写 .vue/.scss/.js
extensions: [
".mjs",
".js",
".ts",
".jsx",
".tsx",
".json",
".vue",
".less",
],
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "./src/assets/scss/main.scss";',
less: {
// 全局注入:用函数判断,避免给 global.less 自身注入导致循环导入
additionalData: (content, filename) => {
if (!filename.includes("global.less")) {
return `@import "@/assets/less/global.less";${content}`;
}
return content;
},
charset: false,
},
},
},