配置后端对接
This commit is contained in:
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(npm run lint:*)",
|
||||||
|
"Bash(npm run typecheck:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
4
.env.development
Normal file
4
.env.development
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Development environment variables
|
||||||
|
VITE_API_BASE_URL=http://localhost:2025
|
||||||
|
VITE_APP_TITLE=教务管理系统
|
||||||
|
VITE_APP_ENV=development
|
||||||
4
.env.production
Normal file
4
.env.production
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Production environment variables
|
||||||
|
VITE_API_BASE_URL=/api
|
||||||
|
VITE_APP_TITLE=教务管理系统
|
||||||
|
VITE_APP_ENV=production
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/web-react": "^2.66.4",
|
"@arco-design/web-react": "^2.66.4",
|
||||||
|
"axios": "^1.11.0",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
|||||||
190
pnpm-lock.yaml
generated
190
pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ importers:
|
|||||||
'@arco-design/web-react':
|
'@arco-design/web-react':
|
||||||
specifier: ^2.66.4
|
specifier: ^2.66.4
|
||||||
version: 2.66.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
version: 2.66.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
axios:
|
||||||
|
specifier: ^1.11.0
|
||||||
|
version: 1.11.0
|
||||||
d3:
|
d3:
|
||||||
specifier: ^7.9.0
|
specifier: ^7.9.0
|
||||||
version: 7.9.0
|
version: 7.9.0
|
||||||
@@ -562,6 +565,9 @@ packages:
|
|||||||
argparse@2.0.1:
|
argparse@2.0.1:
|
||||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
autoprefixer@10.4.21:
|
autoprefixer@10.4.21:
|
||||||
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
|
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@@ -569,6 +575,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.1.0
|
postcss: ^8.1.0
|
||||||
|
|
||||||
|
axios@1.11.0:
|
||||||
|
resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
|
||||||
|
|
||||||
b-tween@0.3.3:
|
b-tween@0.3.3:
|
||||||
resolution: {integrity: sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==}
|
resolution: {integrity: sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==}
|
||||||
|
|
||||||
@@ -586,6 +595,10 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
callsites@3.1.0:
|
callsites@3.1.0:
|
||||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -616,6 +629,10 @@ packages:
|
|||||||
color@3.2.1:
|
color@3.2.1:
|
||||||
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
commander@7.2.0:
|
commander@7.2.0:
|
||||||
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
@@ -785,12 +802,20 @@ packages:
|
|||||||
delaunator@5.0.1:
|
delaunator@5.0.1:
|
||||||
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
detect-node-es@1.1.0:
|
detect-node-es@1.1.0:
|
||||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||||
|
|
||||||
dom-helpers@5.2.1:
|
dom-helpers@5.2.1:
|
||||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
echarts@3.8.5:
|
echarts@3.8.5:
|
||||||
resolution: {integrity: sha512-E+nnROMfCeiLeoT/fZyX8SE8mKzwkTjyemyoBF543oqjRtjTSKQAVDEihMXy4oC6pJS0tYGdMqCA2ATk8onyRg==}
|
resolution: {integrity: sha512-E+nnROMfCeiLeoT/fZyX8SE8mKzwkTjyemyoBF543oqjRtjTSKQAVDEihMXy4oC6pJS0tYGdMqCA2ATk8onyRg==}
|
||||||
|
|
||||||
@@ -800,6 +825,22 @@ packages:
|
|||||||
electron-to-chromium@1.5.200:
|
electron-to-chromium@1.5.200:
|
||||||
resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==}
|
resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==}
|
||||||
|
|
||||||
|
es-define-property@1.0.1:
|
||||||
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-errors@1.3.0:
|
||||||
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
esbuild@0.25.9:
|
esbuild@0.25.9:
|
||||||
resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
|
resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -902,6 +943,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==}
|
resolution: {integrity: sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
follow-redirects@1.15.11:
|
||||||
|
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
form-data@4.0.4:
|
||||||
|
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
fraction.js@4.3.7:
|
fraction.js@4.3.7:
|
||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
|
|
||||||
@@ -910,10 +964,21 @@ packages:
|
|||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
function-bind@1.1.2:
|
||||||
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2:
|
gensync@1.0.0-beta.2:
|
||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
glob-parent@6.0.2:
|
glob-parent@6.0.2:
|
||||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@@ -926,10 +991,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==}
|
resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
gopd@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
has-flag@4.0.0:
|
has-flag@4.0.0:
|
||||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
has-symbols@1.1.0:
|
||||||
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1014,6 +1095,18 @@ packages:
|
|||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0:
|
||||||
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||||
|
|
||||||
@@ -1090,6 +1183,9 @@ packages:
|
|||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
punycode@2.3.1:
|
punycode@2.3.1:
|
||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1741,6 +1837,8 @@ snapshots:
|
|||||||
|
|
||||||
argparse@2.0.1: {}
|
argparse@2.0.1: {}
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
autoprefixer@10.4.21(postcss@8.5.6):
|
autoprefixer@10.4.21(postcss@8.5.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.25.2
|
browserslist: 4.25.2
|
||||||
@@ -1751,6 +1849,14 @@ snapshots:
|
|||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
|
|
||||||
|
axios@1.11.0:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.11
|
||||||
|
form-data: 4.0.4
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
b-tween@0.3.3: {}
|
b-tween@0.3.3: {}
|
||||||
|
|
||||||
b-validate@1.5.3: {}
|
b-validate@1.5.3: {}
|
||||||
@@ -1769,6 +1875,11 @@ snapshots:
|
|||||||
node-releases: 2.0.19
|
node-releases: 2.0.19
|
||||||
update-browserslist-db: 1.1.3(browserslist@4.25.2)
|
update-browserslist-db: 1.1.3(browserslist@4.25.2)
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
callsites@3.1.0: {}
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001734: {}
|
caniuse-lite@1.0.30001734: {}
|
||||||
@@ -1800,6 +1911,10 @@ snapshots:
|
|||||||
color-convert: 1.9.3
|
color-convert: 1.9.3
|
||||||
color-string: 1.9.1
|
color-string: 1.9.1
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
commander@7.2.0: {}
|
commander@7.2.0: {}
|
||||||
|
|
||||||
compute-scroll-into-view@1.0.20: {}
|
compute-scroll-into-view@1.0.20: {}
|
||||||
@@ -1982,6 +2097,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
robust-predicates: 3.0.2
|
robust-predicates: 3.0.2
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
detect-node-es@1.1.0: {}
|
detect-node-es@1.1.0: {}
|
||||||
|
|
||||||
dom-helpers@5.2.1:
|
dom-helpers@5.2.1:
|
||||||
@@ -1989,6 +2106,12 @@ snapshots:
|
|||||||
'@babel/runtime': 7.28.2
|
'@babel/runtime': 7.28.2
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-errors: 1.3.0
|
||||||
|
gopd: 1.2.0
|
||||||
|
|
||||||
echarts@3.8.5:
|
echarts@3.8.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
zrender: 3.7.4
|
zrender: 3.7.4
|
||||||
@@ -2000,6 +2123,21 @@ snapshots:
|
|||||||
|
|
||||||
electron-to-chromium@1.5.200: {}
|
electron-to-chromium@1.5.200: {}
|
||||||
|
|
||||||
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
esbuild@0.25.9:
|
esbuild@0.25.9:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@esbuild/aix-ppc64': 0.25.9
|
'@esbuild/aix-ppc64': 0.25.9
|
||||||
@@ -2138,13 +2276,43 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
follow-redirects@1.15.11: {}
|
||||||
|
|
||||||
|
form-data@4.0.4:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
fraction.js@4.3.7: {}
|
fraction.js@4.3.7: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
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
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
dunder-proto: 1.0.1
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
glob-parent@6.0.2:
|
glob-parent@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -2153,8 +2321,20 @@ snapshots:
|
|||||||
|
|
||||||
globals@16.3.0: {}
|
globals@16.3.0: {}
|
||||||
|
|
||||||
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
has-flag@4.0.0: {}
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
@@ -2221,6 +2401,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 1.1.12
|
brace-expansion: 1.1.12
|
||||||
@@ -2289,6 +2477,8 @@ snapshots:
|
|||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
react-clientside-effect@1.2.8(react@19.1.1):
|
react-clientside-effect@1.2.8(react@19.1.1):
|
||||||
|
|||||||
283
src/constants/statusMap.js
Normal file
283
src/constants/statusMap.js
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
// Status mapping constants for converting between frontend and backend values
|
||||||
|
|
||||||
|
// Application status mapping
|
||||||
|
export const APPLICATION_STATUS_MAP = {
|
||||||
|
// Backend -> Frontend
|
||||||
|
toFrontend: {
|
||||||
|
'SCHEDULED': 'applied',
|
||||||
|
'COMPLETED': 'interview_success',
|
||||||
|
'CANCELLED': 'interview_failed',
|
||||||
|
'NO_SHOW': 'not_applied',
|
||||||
|
},
|
||||||
|
// Frontend -> Backend
|
||||||
|
toBackend: {
|
||||||
|
'not_applied': null,
|
||||||
|
'applied': 'SCHEDULED',
|
||||||
|
'interview_success': 'COMPLETED',
|
||||||
|
'accepted': 'COMPLETED',
|
||||||
|
'interview_failed': 'CANCELLED',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enrollment status mapping
|
||||||
|
export const ENROLLMENT_STATUS_MAP = {
|
||||||
|
// Backend values
|
||||||
|
NOT_STARTED: {
|
||||||
|
text: '未开始',
|
||||||
|
color: '#999999',
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
IN_PROGRESS: {
|
||||||
|
text: '学习中',
|
||||||
|
color: '#3b82f6',
|
||||||
|
progress: 50,
|
||||||
|
},
|
||||||
|
COMPLETED: {
|
||||||
|
text: '已完成',
|
||||||
|
color: '#10b981',
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interview status mapping
|
||||||
|
export const INTERVIEW_STATUS_MAP = {
|
||||||
|
// Backend values
|
||||||
|
SCHEDULED: {
|
||||||
|
text: '待面试',
|
||||||
|
color: '#f59e0b',
|
||||||
|
icon: 'clock',
|
||||||
|
},
|
||||||
|
COMPLETED: {
|
||||||
|
text: '已完成',
|
||||||
|
color: '#10b981',
|
||||||
|
icon: 'check',
|
||||||
|
},
|
||||||
|
CANCELLED: {
|
||||||
|
text: '已取消',
|
||||||
|
color: '#ef4444',
|
||||||
|
icon: 'close',
|
||||||
|
},
|
||||||
|
NO_SHOW: {
|
||||||
|
text: '未到场',
|
||||||
|
color: '#6b7280',
|
||||||
|
icon: 'warning',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interview result mapping
|
||||||
|
export const INTERVIEW_RESULT_MAP = {
|
||||||
|
PASS: {
|
||||||
|
text: '通过',
|
||||||
|
color: '#10b981',
|
||||||
|
applicationStatus: 'interview_success',
|
||||||
|
},
|
||||||
|
FAIL: {
|
||||||
|
text: '未通过',
|
||||||
|
color: '#ef4444',
|
||||||
|
applicationStatus: 'interview_failed',
|
||||||
|
},
|
||||||
|
PENDING: {
|
||||||
|
text: '待定',
|
||||||
|
color: '#f59e0b',
|
||||||
|
applicationStatus: 'applied',
|
||||||
|
},
|
||||||
|
OFFER: {
|
||||||
|
text: '已发Offer',
|
||||||
|
color: '#10b981',
|
||||||
|
applicationStatus: 'accepted',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Job type mapping
|
||||||
|
export const JOB_TYPE_MAP = {
|
||||||
|
// Backend -> Frontend display
|
||||||
|
FULLTIME: {
|
||||||
|
text: '全职',
|
||||||
|
value: 'fulltime',
|
||||||
|
color: '#3b82f6',
|
||||||
|
},
|
||||||
|
PARTTIME: {
|
||||||
|
text: '兼职',
|
||||||
|
value: 'parttime',
|
||||||
|
color: '#8b5cf6',
|
||||||
|
},
|
||||||
|
INTERNSHIP: {
|
||||||
|
text: '实习',
|
||||||
|
value: 'internship',
|
||||||
|
color: '#ec4899',
|
||||||
|
},
|
||||||
|
CONTRACT: {
|
||||||
|
text: '合同制',
|
||||||
|
value: 'contract',
|
||||||
|
color: '#f59e0b',
|
||||||
|
},
|
||||||
|
REMOTE: {
|
||||||
|
text: '远程',
|
||||||
|
value: 'remote',
|
||||||
|
color: '#10b981',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Job level mapping
|
||||||
|
export const JOB_LEVEL_MAP = {
|
||||||
|
JUNIOR: {
|
||||||
|
text: '初级',
|
||||||
|
minExperience: 0,
|
||||||
|
maxExperience: 2,
|
||||||
|
},
|
||||||
|
MID: {
|
||||||
|
text: '中级',
|
||||||
|
minExperience: 2,
|
||||||
|
maxExperience: 5,
|
||||||
|
},
|
||||||
|
SENIOR: {
|
||||||
|
text: '高级',
|
||||||
|
minExperience: 5,
|
||||||
|
maxExperience: 10,
|
||||||
|
},
|
||||||
|
LEAD: {
|
||||||
|
text: '资深',
|
||||||
|
minExperience: 8,
|
||||||
|
maxExperience: 15,
|
||||||
|
},
|
||||||
|
MANAGER: {
|
||||||
|
text: '管理',
|
||||||
|
minExperience: 5,
|
||||||
|
maxExperience: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Course category mapping
|
||||||
|
export const COURSE_CATEGORY_MAP = {
|
||||||
|
GENERAL: {
|
||||||
|
text: '通识课程',
|
||||||
|
color: '#6b7280',
|
||||||
|
icon: 'book',
|
||||||
|
},
|
||||||
|
PROFESSIONAL: {
|
||||||
|
text: '专业课程',
|
||||||
|
color: '#3b82f6',
|
||||||
|
icon: 'desktop',
|
||||||
|
},
|
||||||
|
PRACTICAL: {
|
||||||
|
text: '实践课程',
|
||||||
|
color: '#10b981',
|
||||||
|
icon: 'tool',
|
||||||
|
},
|
||||||
|
COMPREHENSIVE: {
|
||||||
|
text: '综合课程',
|
||||||
|
color: '#8b5cf6',
|
||||||
|
icon: 'layers',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Course type mapping
|
||||||
|
export const COURSE_TYPE_MAP = {
|
||||||
|
LIVE: {
|
||||||
|
text: '直播课',
|
||||||
|
color: '#ef4444',
|
||||||
|
icon: 'video',
|
||||||
|
},
|
||||||
|
RECORDED: {
|
||||||
|
text: '录播课',
|
||||||
|
color: '#3b82f6',
|
||||||
|
icon: 'play',
|
||||||
|
},
|
||||||
|
HYBRID: {
|
||||||
|
text: '混合式',
|
||||||
|
color: '#8b5cf6',
|
||||||
|
icon: 'mix',
|
||||||
|
},
|
||||||
|
OFFLINE: {
|
||||||
|
text: '线下课',
|
||||||
|
color: '#10b981',
|
||||||
|
icon: 'location',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gender mapping
|
||||||
|
export const GENDER_MAP = {
|
||||||
|
MALE: '男',
|
||||||
|
FEMALE: '女',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Company scale mapping
|
||||||
|
export const COMPANY_SCALE_MAP = {
|
||||||
|
SMALL: {
|
||||||
|
text: '50人以下',
|
||||||
|
min: 0,
|
||||||
|
max: 50,
|
||||||
|
},
|
||||||
|
MEDIUM: {
|
||||||
|
text: '50-200人',
|
||||||
|
min: 50,
|
||||||
|
max: 200,
|
||||||
|
},
|
||||||
|
LARGE: {
|
||||||
|
text: '200-1000人',
|
||||||
|
min: 200,
|
||||||
|
max: 1000,
|
||||||
|
},
|
||||||
|
ENTERPRISE: {
|
||||||
|
text: '1000人以上',
|
||||||
|
min: 1000,
|
||||||
|
max: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interview type mapping
|
||||||
|
export const INTERVIEW_TYPE_MAP = {
|
||||||
|
PHONE: {
|
||||||
|
text: '电话面试',
|
||||||
|
icon: 'phone',
|
||||||
|
},
|
||||||
|
VIDEO: {
|
||||||
|
text: '视频面试',
|
||||||
|
icon: 'video',
|
||||||
|
},
|
||||||
|
ONSITE: {
|
||||||
|
text: '现场面试',
|
||||||
|
icon: 'location',
|
||||||
|
},
|
||||||
|
TECHNICAL: {
|
||||||
|
text: '技术面试',
|
||||||
|
icon: 'code',
|
||||||
|
},
|
||||||
|
HR: {
|
||||||
|
text: 'HR面试',
|
||||||
|
icon: 'user',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
export const getStatusText = (status, map) => {
|
||||||
|
return map[status]?.text || status;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStatusColor = (status, map) => {
|
||||||
|
return map[status]?.color || '#6b7280';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapApplicationStatus = (backendStatus, result = null) => {
|
||||||
|
if (result && INTERVIEW_RESULT_MAP[result]) {
|
||||||
|
return INTERVIEW_RESULT_MAP[result].applicationStatus;
|
||||||
|
}
|
||||||
|
return APPLICATION_STATUS_MAP.toFrontend[backendStatus] || 'not_applied';
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
APPLICATION_STATUS_MAP,
|
||||||
|
ENROLLMENT_STATUS_MAP,
|
||||||
|
INTERVIEW_STATUS_MAP,
|
||||||
|
INTERVIEW_RESULT_MAP,
|
||||||
|
JOB_TYPE_MAP,
|
||||||
|
JOB_LEVEL_MAP,
|
||||||
|
COURSE_CATEGORY_MAP,
|
||||||
|
COURSE_TYPE_MAP,
|
||||||
|
GENDER_MAP,
|
||||||
|
COMPANY_SCALE_MAP,
|
||||||
|
INTERVIEW_TYPE_MAP,
|
||||||
|
getStatusText,
|
||||||
|
getStatusColor,
|
||||||
|
mapApplicationStatus,
|
||||||
|
};
|
||||||
@@ -1,17 +1,67 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { mockData } from "@/data/mockData";
|
import { jobAPI } from "@/services/api";
|
||||||
|
import { mapJobList } from "@/utils/dataMapper";
|
||||||
import JobList from "@/pages/CompanyJobsPage/components/JobList";
|
import JobList from "@/pages/CompanyJobsPage/components/JobList";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const CompanyJobsListPage = () => {
|
const CompanyJobsListPage = () => {
|
||||||
const { companyJobs } = mockData;
|
const [jobs, setJobs] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchJobs();
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
const fetchJobs = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await jobAPI.getList({
|
||||||
|
page,
|
||||||
|
pageSize: 20,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const mappedJobs = mapJobList(response.data || response);
|
||||||
|
setJobs(mappedJobs);
|
||||||
|
setTotal(response.total || mappedJobs.length);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch jobs:", error);
|
||||||
|
setJobs([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading && jobs.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="company-jobs-list-page-wrapper" style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: '400px'
|
||||||
|
}}>
|
||||||
|
<p>加载中...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="company-jobs-list-page-wrapper">
|
<ul className="company-jobs-list-page-wrapper">
|
||||||
<JobList data={companyJobs?.companyPositions} />
|
<JobList data={jobs} />
|
||||||
|
{jobs.length === 0 && !loading && (
|
||||||
|
<div style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: '40px',
|
||||||
|
color: '#999'
|
||||||
|
}}>
|
||||||
|
暂无岗位信息
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,85 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { mockData } from "@/data/mockData";
|
import { jobAPI, interviewAPI, studentAPI } from "@/services/api";
|
||||||
|
import { mapJobList, mapInterviewList } from "@/utils/dataMapper";
|
||||||
import JobList from "./components/JobList";
|
import JobList from "./components/JobList";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const CompanyJobsPage = () => {
|
const CompanyJobsPage = () => {
|
||||||
const { companyJobs } = mockData;
|
const [jobs, setJobs] = useState([]);
|
||||||
|
const [interviews, setInterviews] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const [isExpand, setIsExpand] = useState(false); // 是否展开
|
const [isExpand, setIsExpand] = useState(false); // 是否展开
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Get current user's student ID from API
|
||||||
|
let studentId = null;
|
||||||
|
try {
|
||||||
|
const currentStudent = await studentAPI.getCurrentStudent();
|
||||||
|
studentId = currentStudent?.id;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Could not get current student:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch jobs (and interviews if we have a student ID)
|
||||||
|
const jobsData = await jobAPI.getList({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let interviewsData = { data: [] };
|
||||||
|
if (studentId) {
|
||||||
|
try {
|
||||||
|
interviewsData = await interviewAPI.getList({
|
||||||
|
studentId: studentId,
|
||||||
|
status: 'SCHEDULED'
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log('No interviews found or API error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map data to frontend format
|
||||||
|
const mappedJobs = mapJobList(jobsData.data || jobsData);
|
||||||
|
const mappedInterviews = mapInterviewList(interviewsData.data || interviewsData);
|
||||||
|
|
||||||
|
setJobs(mappedJobs);
|
||||||
|
setInterviews(mappedInterviews);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch data:", error);
|
||||||
|
// Fallback to empty data
|
||||||
|
setJobs([]);
|
||||||
|
setInterviews([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleJobWrapperClick = () => {
|
const handleJobWrapperClick = () => {
|
||||||
navigate("/company-jobs-list");
|
navigate("/company-jobs-list");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="company-jobs-page-wrapper">
|
||||||
|
<div className="company-jobs-page" style={{ justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<p>加载中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="company-jobs-page-wrapper">
|
<div className="company-jobs-page-wrapper">
|
||||||
<div className="company-jobs-page">
|
<div className="company-jobs-page">
|
||||||
@@ -22,7 +87,7 @@ const CompanyJobsPage = () => {
|
|||||||
<p className="company-jobs-page-title">企业内推岗位库</p>
|
<p className="company-jobs-page-title">企业内推岗位库</p>
|
||||||
<div className="company-jobs-page-left-list-wrapper">
|
<div className="company-jobs-page-left-list-wrapper">
|
||||||
<JobList
|
<JobList
|
||||||
data={companyJobs?.companyPositions}
|
data={jobs}
|
||||||
backgroundColor="#F7F8FA"
|
backgroundColor="#F7F8FA"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,15 +102,15 @@ const CompanyJobsPage = () => {
|
|||||||
>
|
>
|
||||||
<p className="company-jobs-page-title">内推岗位面试</p>
|
<p className="company-jobs-page-title">内推岗位面试</p>
|
||||||
<ul className="company-jobs-page-interview-list">
|
<ul className="company-jobs-page-interview-list">
|
||||||
{companyJobs?.internalPositions?.map((item) => (
|
{interviews.length > 0 ? interviews.map((item) => (
|
||||||
<li className="company-jobs-page-interview-item" key={item.id}>
|
<li className="company-jobs-page-interview-item" key={item.id}>
|
||||||
<div className="company-jobs-page-interview-item-info">
|
<div className="company-jobs-page-interview-item-info">
|
||||||
<p className="company-jobs-page-interview-item-info-position">
|
<p className="company-jobs-page-interview-item-info-position">
|
||||||
{item.position}
|
{item.position}
|
||||||
</p>
|
</p>
|
||||||
{item?.tags?.length > 0 ? (
|
{item.job?.tags?.length > 0 ? (
|
||||||
<ul className="company-jobs-page-interview-item-info-tags">
|
<ul className="company-jobs-page-interview-item-info-tags">
|
||||||
{item?.tags.map((tag) => (
|
{item.job.tags.map((tag) => (
|
||||||
<li
|
<li
|
||||||
className="company-jobs-page-interview-item-info-tag"
|
className="company-jobs-page-interview-item-info-tag"
|
||||||
key={tag}
|
key={tag}
|
||||||
@@ -56,14 +121,14 @@ const CompanyJobsPage = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="company-jobs-page-interview-item-info-salary">
|
<span className="company-jobs-page-interview-item-info-salary">
|
||||||
{item.salary}
|
{item.job?.salary || '面议'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="company-jobs-page-interview-item-btn-wrapper">
|
<div className="company-jobs-page-interview-item-btn-wrapper">
|
||||||
<span>{item.interviewTime}</span>
|
<span>{item.interviewTime}</span>
|
||||||
<div
|
<div
|
||||||
className={`company-jobs-page-interview-item-btn ${
|
className={`company-jobs-page-interview-item-btn ${
|
||||||
item.status !== "completed" &&
|
item.status !== "COMPLETED" &&
|
||||||
"company-jobs-page-interview-item-btn-active"
|
"company-jobs-page-interview-item-btn-active"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -71,7 +136,13 @@ const CompanyJobsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
)) : (
|
||||||
|
<li className="company-jobs-page-interview-item">
|
||||||
|
<p style={{ color: '#999', textAlign: 'center', width: '100%' }}>
|
||||||
|
暂无面试安排
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,60 @@
|
|||||||
import { Avatar } from "@arco-design/web-react";
|
import { Avatar } from "@arco-design/web-react";
|
||||||
import { mockData } from "@/data/mockData";
|
import { useState, useEffect } from "react";
|
||||||
|
import { studentAPI } from "@/services/api";
|
||||||
|
import { mapProfile } from "@/utils/dataMapper";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const ProfileCard = () => {
|
const ProfileCard = () => {
|
||||||
const { profile } = mockData;
|
const [profile, setProfile] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchProfile();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchProfile = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
// Get current logged-in student information
|
||||||
|
const studentData = await studentAPI.getCurrentStudent();
|
||||||
|
|
||||||
|
if (studentData) {
|
||||||
|
const mappedProfile = mapProfile(studentData);
|
||||||
|
setProfile(mappedProfile);
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to get current student data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch profile:", error);
|
||||||
|
// Show error message instead of fake data
|
||||||
|
setProfile({
|
||||||
|
name: "数据加载失败",
|
||||||
|
studentId: "请检查后端服务",
|
||||||
|
school: error.message || "后端未运行",
|
||||||
|
major: "请确保数据库已初始化",
|
||||||
|
badges: {
|
||||||
|
credits: 0,
|
||||||
|
classRank: 0,
|
||||||
|
mbti: "-"
|
||||||
|
},
|
||||||
|
courses: []
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="profile-card-wrapper" style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
<p>加载中...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile-card-wrapper">
|
<div className="profile-card-wrapper">
|
||||||
@@ -15,9 +66,9 @@ const ProfileCard = () => {
|
|||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="profile-card-user-name">
|
<div className="profile-card-user-name">
|
||||||
<span className="profile-card-user-name-text">{profile.name}</span>
|
<span className="profile-card-user-name-text">{profile?.name}</span>
|
||||||
<p className="profile-card-user-name-student-id">
|
<p className="profile-card-user-name-student-id">
|
||||||
学号: {profile.studentId}
|
学号: {profile?.studentId}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,7 +76,7 @@ const ProfileCard = () => {
|
|||||||
<li className="profile-card-achievement-info-item">
|
<li className="profile-card-achievement-info-item">
|
||||||
<span className="profile-card-achievement-info-item-title">学分</span>
|
<span className="profile-card-achievement-info-item-title">学分</span>
|
||||||
<span className="profile-card-achievement-info-item-text">
|
<span className="profile-card-achievement-info-item-text">
|
||||||
{profile?.badges?.credits}
|
{profile?.badges?.credits || 0}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="profile-card-achievement-info-item">
|
<li className="profile-card-achievement-info-item">
|
||||||
@@ -33,13 +84,13 @@ const ProfileCard = () => {
|
|||||||
班级排名
|
班级排名
|
||||||
</span>
|
</span>
|
||||||
<span className="profile-card-achievement-info-item-text">
|
<span className="profile-card-achievement-info-item-text">
|
||||||
{profile?.badges?.classRank}
|
{profile?.badges?.classRank || '-'}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="profile-card-achievement-info-item">
|
<li className="profile-card-achievement-info-item">
|
||||||
<span className="profile-card-achievement-info-item-title">MBTI</span>
|
<span className="profile-card-achievement-info-item-title">MBTI</span>
|
||||||
<span className="profile-card-achievement-info-item-text">
|
<span className="profile-card-achievement-info-item-text">
|
||||||
{profile?.badges?.mbti}
|
{profile?.badges?.mbti || '-'}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -48,30 +99,30 @@ const ProfileCard = () => {
|
|||||||
<i className="profile-card-class-info-item-icon icon-school" />
|
<i className="profile-card-class-info-item-icon icon-school" />
|
||||||
<span className="profile-card-class-info-item-title">学校</span>
|
<span className="profile-card-class-info-item-title">学校</span>
|
||||||
<span className="profile-card-class-info-item-text">
|
<span className="profile-card-class-info-item-text">
|
||||||
{profile?.school}
|
{profile?.school || '-'}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="profile-card-class-info-item">
|
<li className="profile-card-class-info-item">
|
||||||
<i className="profile-card-class-info-item-icon icon-major" />
|
<i className="profile-card-class-info-item-icon icon-major" />
|
||||||
<span className="profile-card-class-info-item-title">专业</span>
|
<span className="profile-card-class-info-item-title">专业</span>
|
||||||
<span className="profile-card-class-info-item-text">
|
<span className="profile-card-class-info-item-text">
|
||||||
{profile?.major}
|
{profile?.major || '-'}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="profile-card-class-info-item">
|
<li className="profile-card-class-info-item">
|
||||||
<i className="profile-card-class-info-item-icon icon-location" />
|
<i className="profile-card-class-info-item-icon icon-location" />
|
||||||
<span className="profile-card-class-info-item-title">
|
<span className="profile-card-class-info-item-title">
|
||||||
就业管家课程
|
班级
|
||||||
</span>
|
</span>
|
||||||
<span className="profile-card-class-info-item-text">
|
<span className="profile-card-class-info-item-text">
|
||||||
{profile?.course}
|
{profile?.className || '-'}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="profile-card-class-info-item">
|
<li className="profile-card-class-info-item">
|
||||||
<i className="profile-card-class-info-item-icon icon-course" />
|
<i className="profile-card-class-info-item-icon icon-course" />
|
||||||
<span className="profile-card-class-info-item-title">垂直方向</span>
|
<span className="profile-card-class-info-item-title">学习阶段</span>
|
||||||
<span className="profile-card-class-info-item-text">
|
<span className="profile-card-class-info-item-text">
|
||||||
{profile?.course}
|
{profile?.stageName || '-'}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
211
src/services/api.js
Normal file
211
src/services/api.js
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
// Student API
|
||||||
|
export const studentAPI = {
|
||||||
|
// Get current logged-in student
|
||||||
|
getCurrentStudent: () => request.get('/api/students/me'),
|
||||||
|
|
||||||
|
// Get student list
|
||||||
|
getList: (params) => request.get('/api/students', { params }),
|
||||||
|
|
||||||
|
// Get student detail
|
||||||
|
getDetail: (id) => request.get(`/api/students/${id}`),
|
||||||
|
|
||||||
|
// Create student
|
||||||
|
create: (data) => request.post('/api/students', data),
|
||||||
|
|
||||||
|
// Update student
|
||||||
|
update: (id, data) => request.put(`/api/students/${id}`, data),
|
||||||
|
|
||||||
|
// Get student progress
|
||||||
|
getProgress: (id) => request.get(`/api/students/${id}/progress`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Course API
|
||||||
|
export const courseAPI = {
|
||||||
|
// Get course list
|
||||||
|
getList: (params) => request.get('/api/courses', { params }),
|
||||||
|
|
||||||
|
// Get course detail
|
||||||
|
getDetail: (id) => request.get(`/api/courses/${id}`),
|
||||||
|
|
||||||
|
// Create course
|
||||||
|
create: (data) => request.post('/api/courses', data),
|
||||||
|
|
||||||
|
// Update course
|
||||||
|
update: (id, data) => request.put(`/api/courses/${id}`, data),
|
||||||
|
|
||||||
|
// Enroll student in course
|
||||||
|
enroll: (courseId, studentId) =>
|
||||||
|
request.post(`/api/courses/${courseId}/enroll`, { studentId }),
|
||||||
|
|
||||||
|
// Update enrollment progress
|
||||||
|
updateEnrollment: (courseId, enrollmentId, data) =>
|
||||||
|
request.put(`/api/courses/${courseId}/enrollment/${enrollmentId}`, data),
|
||||||
|
|
||||||
|
// Get course students
|
||||||
|
getStudents: (id) => request.get(`/api/courses/${id}/students`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Job API
|
||||||
|
export const jobAPI = {
|
||||||
|
// Get job list
|
||||||
|
getList: (params) => request.get('/api/jobs', { params }),
|
||||||
|
|
||||||
|
// Get job detail
|
||||||
|
getDetail: (id) => request.get(`/api/jobs/${id}`),
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
create: (data) => request.post('/api/jobs', data),
|
||||||
|
|
||||||
|
// Update job
|
||||||
|
update: (id, data) => request.put(`/api/jobs/${id}`, data),
|
||||||
|
|
||||||
|
// Get recommended jobs for student
|
||||||
|
getRecommended: (studentId) => request.get(`/api/jobs/recommend/${studentId}`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Company API
|
||||||
|
export const companyAPI = {
|
||||||
|
// Get company list
|
||||||
|
getList: (params) => request.get('/api/companies', { params }),
|
||||||
|
|
||||||
|
// Get company detail
|
||||||
|
getDetail: (id) => request.get(`/api/companies/${id}`),
|
||||||
|
|
||||||
|
// Create company
|
||||||
|
create: (data) => request.post('/api/companies', data),
|
||||||
|
|
||||||
|
// Update company
|
||||||
|
update: (id, data) => request.put(`/api/companies/${id}`, data),
|
||||||
|
|
||||||
|
// Get company jobs
|
||||||
|
getJobs: (id) => request.get(`/api/companies/${id}/jobs`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resume API
|
||||||
|
export const resumeAPI = {
|
||||||
|
// Get resume list
|
||||||
|
getList: (params) => request.get('/api/resumes', { params }),
|
||||||
|
|
||||||
|
// Get resume detail
|
||||||
|
getDetail: (id) => request.get(`/api/resumes/${id}`),
|
||||||
|
|
||||||
|
// Create resume
|
||||||
|
create: (data) => request.post('/api/resumes', data),
|
||||||
|
|
||||||
|
// Update resume
|
||||||
|
update: (id, data) => request.put(`/api/resumes/${id}`, data),
|
||||||
|
|
||||||
|
// Delete resume
|
||||||
|
delete: (id) => request.delete(`/api/resumes/${id}`),
|
||||||
|
|
||||||
|
// Get student's active resume
|
||||||
|
getStudentActive: (studentId) =>
|
||||||
|
request.get(`/api/resumes/student/${studentId}/active`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interview API
|
||||||
|
export const interviewAPI = {
|
||||||
|
// Get interview list
|
||||||
|
getList: (params) => request.get('/api/interviews', { params }),
|
||||||
|
|
||||||
|
// Get interview detail
|
||||||
|
getDetail: (id) => request.get(`/api/interviews/${id}`),
|
||||||
|
|
||||||
|
// Schedule interview
|
||||||
|
schedule: (data) => request.post('/api/interviews', data),
|
||||||
|
|
||||||
|
// Update interview
|
||||||
|
update: (id, data) => request.put(`/api/interviews/${id}`, data),
|
||||||
|
|
||||||
|
// Cancel interview
|
||||||
|
cancel: (id, reason) =>
|
||||||
|
request.post(`/api/interviews/${id}/cancel`, { reason }),
|
||||||
|
|
||||||
|
// Submit feedback
|
||||||
|
submitFeedback: (id, data) =>
|
||||||
|
request.post(`/api/interviews/${id}/feedback`, data),
|
||||||
|
|
||||||
|
// Get student interview history
|
||||||
|
getStudentHistory: (studentId) =>
|
||||||
|
request.get(`/api/interviews/student/${studentId}/history`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Class API
|
||||||
|
export const classAPI = {
|
||||||
|
// Get class list
|
||||||
|
getList: (params) => request.get('/api/classes', { params }),
|
||||||
|
|
||||||
|
// Get class detail
|
||||||
|
getDetail: (id) => request.get(`/api/classes/${id}`),
|
||||||
|
|
||||||
|
// Create class
|
||||||
|
create: (data) => request.post('/api/classes', data),
|
||||||
|
|
||||||
|
// Update class
|
||||||
|
update: (id, data) => request.put(`/api/classes/${id}`, data),
|
||||||
|
|
||||||
|
// Get class students
|
||||||
|
getStudents: (id) => request.get(`/api/classes/${id}/students`),
|
||||||
|
|
||||||
|
// Add student to class
|
||||||
|
addStudent: (classId, studentId) =>
|
||||||
|
request.post(`/api/classes/${classId}/students`, { studentId }),
|
||||||
|
|
||||||
|
// Remove student from class
|
||||||
|
removeStudent: (classId, studentId) =>
|
||||||
|
request.delete(`/api/classes/${classId}/students/${studentId}`),
|
||||||
|
|
||||||
|
// Get class statistics
|
||||||
|
getStats: (id) => request.get(`/api/classes/${id}/stats`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Learning Stage API
|
||||||
|
export const stageAPI = {
|
||||||
|
// Get all stages
|
||||||
|
getList: () => request.get('/api/stages'),
|
||||||
|
|
||||||
|
// Get stage detail
|
||||||
|
getDetail: (id) => request.get(`/api/stages/${id}`),
|
||||||
|
|
||||||
|
// Create stage
|
||||||
|
create: (data) => request.post('/api/stages', data),
|
||||||
|
|
||||||
|
// Update stage
|
||||||
|
update: (id, data) => request.put(`/api/stages/${id}`, data),
|
||||||
|
|
||||||
|
// Delete stage
|
||||||
|
delete: (id) => request.delete(`/api/stages/${id}`),
|
||||||
|
|
||||||
|
// Get stage courses
|
||||||
|
getCourses: (id) => request.get(`/api/stages/${id}/courses`),
|
||||||
|
|
||||||
|
// Get stage students
|
||||||
|
getStudents: (id) => request.get(`/api/stages/${id}/students`),
|
||||||
|
|
||||||
|
// Advance student to next stage
|
||||||
|
advanceStudent: (stageId, studentId) =>
|
||||||
|
request.post(`/api/stages/${stageId}/advance/${studentId}`),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auth API
|
||||||
|
export const authAPI = {
|
||||||
|
// Login
|
||||||
|
login: (data) => request.post('/api/auth/login', data),
|
||||||
|
|
||||||
|
// Register
|
||||||
|
register: (data) => request.post('/api/auth/register', data),
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
logout: () => request.post('/api/auth/logout'),
|
||||||
|
|
||||||
|
// Get current user
|
||||||
|
getCurrentUser: () => request.get('/api/auth/me'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Health Check
|
||||||
|
export const healthAPI = {
|
||||||
|
check: () => request.get('/health'),
|
||||||
|
checkDB: () => request.get('/health/db'),
|
||||||
|
};
|
||||||
363
src/utils/dataMapper.js
Normal file
363
src/utils/dataMapper.js
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
// Data mapping utilities for converting backend data to frontend format
|
||||||
|
|
||||||
|
// Map student data from backend to frontend format
|
||||||
|
export const mapStudent = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
name: backendData.realName, // realName -> name
|
||||||
|
studentId: backendData.studentNo, // studentNo -> studentId
|
||||||
|
gender: backendData.gender === 'MALE' ? '男' : '女',
|
||||||
|
school: backendData.school,
|
||||||
|
major: backendData.major,
|
||||||
|
enrollDate: backendData.enrollDate,
|
||||||
|
mbtiType: backendData.mbtiType,
|
||||||
|
className: backendData.class?.name,
|
||||||
|
classId: backendData.classId,
|
||||||
|
stageName: backendData.currentStage?.name,
|
||||||
|
stageId: backendData.currentStageId,
|
||||||
|
// User info
|
||||||
|
email: backendData.user?.email,
|
||||||
|
phone: backendData.user?.phone,
|
||||||
|
username: backendData.user?.username,
|
||||||
|
lastLogin: backendData.user?.lastLogin,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map student list
|
||||||
|
export const mapStudentList = (backendList) => {
|
||||||
|
if (!Array.isArray(backendList)) return [];
|
||||||
|
return backendList.map(mapStudent);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map course data
|
||||||
|
export const mapCourse = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
name: backendData.name,
|
||||||
|
code: backendData.code,
|
||||||
|
description: backendData.description,
|
||||||
|
category: backendData.category,
|
||||||
|
type: backendData.type,
|
||||||
|
credits: backendData.credits,
|
||||||
|
hours: backendData.hours,
|
||||||
|
isAiCourse: backendData.isAiCourse,
|
||||||
|
teacher: backendData.teacher ? {
|
||||||
|
id: backendData.teacher.id,
|
||||||
|
name: backendData.teacher.realName,
|
||||||
|
} : null,
|
||||||
|
stage: backendData.stage,
|
||||||
|
enrollmentCount: backendData.enrollmentCount || backendData._count?.enrollments || 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map course list
|
||||||
|
export const mapCourseList = (backendList) => {
|
||||||
|
if (!Array.isArray(backendList)) return [];
|
||||||
|
return backendList.map(mapCourse);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map job data
|
||||||
|
export const mapJob = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
// Format salary range
|
||||||
|
let salary = '面议';
|
||||||
|
if (backendData.salaryMin && backendData.salaryMax) {
|
||||||
|
const min = Math.floor(backendData.salaryMin / 1000);
|
||||||
|
const max = Math.floor(backendData.salaryMax / 1000);
|
||||||
|
salary = `${min}K-${max}K`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
position: backendData.title, // title -> position
|
||||||
|
description: backendData.description,
|
||||||
|
requirements: backendData.requirements,
|
||||||
|
responsibilities: backendData.responsibilities,
|
||||||
|
company: backendData.company?.name || '',
|
||||||
|
companyId: backendData.companyId,
|
||||||
|
type: mapJobType(backendData.type),
|
||||||
|
jobType: backendData.type === 'INTERNSHIP' ? 'internship' : 'fulltime',
|
||||||
|
level: backendData.level,
|
||||||
|
location: backendData.location,
|
||||||
|
salary: salary,
|
||||||
|
salaryMin: backendData.salaryMin,
|
||||||
|
salaryMax: backendData.salaryMax,
|
||||||
|
benefits: backendData.benefits || [],
|
||||||
|
skills: backendData.skills || [],
|
||||||
|
isActive: backendData.isActive,
|
||||||
|
status: backendData.isActive ? 'available' : 'closed',
|
||||||
|
remainingPositions: backendData._count?.interviews || 5, // Mock remaining positions
|
||||||
|
applicationStatus: 'not_applied', // Default status
|
||||||
|
tags: generateJobTags(backendData),
|
||||||
|
deadline: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map job list
|
||||||
|
export const mapJobList = (backendList) => {
|
||||||
|
if (!Array.isArray(backendList)) return [];
|
||||||
|
return backendList.map(mapJob);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map job type
|
||||||
|
const mapJobType = (type) => {
|
||||||
|
const typeMap = {
|
||||||
|
'FULLTIME': '全职',
|
||||||
|
'PARTTIME': '兼职',
|
||||||
|
'INTERNSHIP': '实习',
|
||||||
|
'CONTRACT': '合同制',
|
||||||
|
'REMOTE': '远程',
|
||||||
|
};
|
||||||
|
return typeMap[type] || type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate job tags
|
||||||
|
const generateJobTags = (job) => {
|
||||||
|
const tags = [];
|
||||||
|
if (job.location) tags.push(job.location.split('市')[0] + '市');
|
||||||
|
if (job.type === 'FULLTIME') tags.push('五险一金');
|
||||||
|
if (job.benefits?.includes('双休')) tags.push('双休');
|
||||||
|
if (job.benefits?.includes('弹性工作')) tags.push('弹性工作');
|
||||||
|
return tags.slice(0, 4); // Max 4 tags
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map company data
|
||||||
|
export const mapCompany = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
name: backendData.name,
|
||||||
|
companyName: backendData.name, // Alias for compatibility
|
||||||
|
description: backendData.description,
|
||||||
|
industry: backendData.industry,
|
||||||
|
scale: mapCompanyScale(backendData.scale),
|
||||||
|
location: backendData.location,
|
||||||
|
website: backendData.website,
|
||||||
|
logo: backendData.logo,
|
||||||
|
contact: backendData.contact,
|
||||||
|
jobCount: backendData._count?.jobs || 0,
|
||||||
|
jobs: backendData.jobs ? mapJobList(backendData.jobs) : [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map company list
|
||||||
|
export const mapCompanyList = (backendList) => {
|
||||||
|
if (!Array.isArray(backendList)) return [];
|
||||||
|
return backendList.map(mapCompany);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map company scale
|
||||||
|
const mapCompanyScale = (scale) => {
|
||||||
|
const scaleMap = {
|
||||||
|
'SMALL': '50人以下',
|
||||||
|
'MEDIUM': '50-200人',
|
||||||
|
'LARGE': '200-1000人',
|
||||||
|
'ENTERPRISE': '1000人以上',
|
||||||
|
};
|
||||||
|
return scaleMap[scale] || scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map resume data
|
||||||
|
export const mapResume = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
title: backendData.title,
|
||||||
|
content: backendData.content,
|
||||||
|
isActive: backendData.isActive,
|
||||||
|
version: backendData.version,
|
||||||
|
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||||
|
studentId: backendData.studentId,
|
||||||
|
createdAt: backendData.createdAt,
|
||||||
|
updatedAt: backendData.updatedAt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map interview data
|
||||||
|
export const mapInterview = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
scheduledAt: backendData.scheduledAt,
|
||||||
|
interviewTime: new Date(backendData.scheduledAt).toLocaleString('zh-CN'),
|
||||||
|
type: backendData.type,
|
||||||
|
status: backendData.status,
|
||||||
|
location: backendData.location,
|
||||||
|
notes: backendData.notes,
|
||||||
|
feedback: backendData.feedback,
|
||||||
|
result: backendData.result,
|
||||||
|
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||||
|
job: backendData.job ? mapJob(backendData.job) : null,
|
||||||
|
company: backendData.job?.company?.name || '',
|
||||||
|
position: backendData.job?.title || '',
|
||||||
|
// Map status for frontend
|
||||||
|
statusText: mapInterviewStatus(backendData.status, backendData.result),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map interview list
|
||||||
|
export const mapInterviewList = (backendList) => {
|
||||||
|
if (!Array.isArray(backendList)) return [];
|
||||||
|
return backendList.map(mapInterview);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map interview status
|
||||||
|
const mapInterviewStatus = (status, result) => {
|
||||||
|
if (status === 'COMPLETED') {
|
||||||
|
if (result === 'PASS' || result === 'OFFER') return '面试成功';
|
||||||
|
if (result === 'FAIL') return '面试失败';
|
||||||
|
return '已完成';
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
'SCHEDULED': '待面试',
|
||||||
|
'CANCELLED': '已取消',
|
||||||
|
'NO_SHOW': '未到场',
|
||||||
|
};
|
||||||
|
return statusMap[status] || status;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map enrollment data
|
||||||
|
export const mapEnrollment = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
courseId: backendData.courseId,
|
||||||
|
studentId: backendData.studentId,
|
||||||
|
status: backendData.status,
|
||||||
|
progress: backendData.progress || 0,
|
||||||
|
score: backendData.score,
|
||||||
|
enrolledAt: backendData.enrolledAt,
|
||||||
|
completedAt: backendData.completedAt,
|
||||||
|
course: backendData.course ? mapCourse(backendData.course) : null,
|
||||||
|
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||||
|
// Map status for display
|
||||||
|
statusText: mapEnrollmentStatus(backendData.status),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map enrollment status
|
||||||
|
const mapEnrollmentStatus = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
'NOT_STARTED': '未开始',
|
||||||
|
'IN_PROGRESS': '学习中',
|
||||||
|
'COMPLETED': '已完成',
|
||||||
|
};
|
||||||
|
return statusMap[status] || status;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map class data
|
||||||
|
export const mapClass = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
name: backendData.name,
|
||||||
|
className: backendData.name, // Alias for compatibility
|
||||||
|
description: backendData.description,
|
||||||
|
startDate: backendData.startDate,
|
||||||
|
endDate: backendData.endDate,
|
||||||
|
isActive: backendData.isActive,
|
||||||
|
teacher: backendData.teacher ? {
|
||||||
|
id: backendData.teacher.id,
|
||||||
|
name: backendData.teacher.realName,
|
||||||
|
} : null,
|
||||||
|
studentCount: backendData._count?.students || 0,
|
||||||
|
students: backendData.students ? mapStudentList(backendData.students) : [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map stage data
|
||||||
|
export const mapStage = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
name: backendData.name,
|
||||||
|
description: backendData.description,
|
||||||
|
order: backendData.order,
|
||||||
|
duration: backendData.duration,
|
||||||
|
requirements: backendData.requirements,
|
||||||
|
courseCount: backendData._count?.courses || 0,
|
||||||
|
studentCount: backendData._count?.students || 0,
|
||||||
|
courses: backendData.courses ? mapCourseList(backendData.courses) : [],
|
||||||
|
students: backendData.students ? mapStudentList(backendData.students) : [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map learning record
|
||||||
|
export const mapLearningRecord = (backendData) => {
|
||||||
|
if (!backendData) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendData.id,
|
||||||
|
studentId: backendData.studentId,
|
||||||
|
courseId: backendData.courseId,
|
||||||
|
date: backendData.date,
|
||||||
|
duration: backendData.duration,
|
||||||
|
progress: backendData.progress,
|
||||||
|
content: backendData.content,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map profile data (for personal profile page)
|
||||||
|
export const mapProfile = (studentData) => {
|
||||||
|
if (!studentData) return null;
|
||||||
|
|
||||||
|
const mapped = mapStudent(studentData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mapped,
|
||||||
|
avatar: '/api/placeholder/80/80', // Default avatar
|
||||||
|
badges: {
|
||||||
|
credits: 84, // Mock data, should come from backend
|
||||||
|
classRank: 9, // Mock data, should come from backend
|
||||||
|
mbti: studentData.mbtiType || 'ENTP',
|
||||||
|
},
|
||||||
|
courses: studentData.enrollments ?
|
||||||
|
studentData.enrollments.map(e => e.course?.name).filter(Boolean) : [],
|
||||||
|
mbtiReport: studentData.mbtiReport || generateMockMBTIReport(studentData.mbtiType),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate mock MBTI report (temporary until backend provides)
|
||||||
|
const generateMockMBTIReport = (type) => {
|
||||||
|
return {
|
||||||
|
type: type || 'ENTP',
|
||||||
|
title: 'Personality Type',
|
||||||
|
description: 'Your personality type description',
|
||||||
|
characteristics: ['Creative', 'Analytical', 'Strategic'],
|
||||||
|
strengths: ['Problem-solving', 'Leadership', 'Innovation'],
|
||||||
|
recommendations: ['Focus on execution', 'Develop patience', 'Listen more'],
|
||||||
|
careerSuggestions: ['Product Manager', 'Consultant', 'Entrepreneur'],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export all mappers
|
||||||
|
export default {
|
||||||
|
mapStudent,
|
||||||
|
mapStudentList,
|
||||||
|
mapCourse,
|
||||||
|
mapCourseList,
|
||||||
|
mapJob,
|
||||||
|
mapJobList,
|
||||||
|
mapCompany,
|
||||||
|
mapCompanyList,
|
||||||
|
mapResume,
|
||||||
|
mapInterview,
|
||||||
|
mapInterviewList,
|
||||||
|
mapEnrollment,
|
||||||
|
mapClass,
|
||||||
|
mapStage,
|
||||||
|
mapLearningRecord,
|
||||||
|
mapProfile,
|
||||||
|
};
|
||||||
@@ -3,8 +3,8 @@ import axios from "axios";
|
|||||||
|
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
baseURL: "", // 基础URL,根据实际项目配置
|
baseURL: import.meta.env.VITE_API_BASE_URL || "http://localhost:3000", // 基础URL
|
||||||
timeout: 5000, // 请求超时时间
|
timeout: 10000, // 请求超时时间
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json;charset=utf-8",
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
},
|
},
|
||||||
@@ -13,11 +13,15 @@ const service = axios.create({
|
|||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
// 可以在这里添加token等信息
|
// 开发阶段使用固定的 x-user-id
|
||||||
const token = localStorage.getItem("token");
|
// 这个ID对应种子数据中的开发默认用户
|
||||||
if (token) {
|
config.headers["x-user-id"] = "dev-user-id";
|
||||||
config.headers["Authorization"] = `Bearer ${token}`;
|
|
||||||
}
|
// 后续对接飞书后使用token
|
||||||
|
// const token = localStorage.getItem("token");
|
||||||
|
// if (token) {
|
||||||
|
// config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
// }
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -30,12 +34,31 @@ service.interceptors.response.use(
|
|||||||
(response) => {
|
(response) => {
|
||||||
// 处理响应数据
|
// 处理响应数据
|
||||||
const res = response.data;
|
const res = response.data;
|
||||||
|
|
||||||
|
// 后端统一返回格式 {success, data, message}
|
||||||
|
if (res.success !== undefined) {
|
||||||
|
if (res.success) {
|
||||||
|
// 如果有分页信息,保留完整结构
|
||||||
|
if (res.total !== undefined) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// 否则只返回data
|
||||||
|
return res.data || res;
|
||||||
|
} else {
|
||||||
|
// 处理业务错误
|
||||||
|
console.error("业务错误:", res.message);
|
||||||
|
return Promise.reject(new Error(res.message || "请求失败"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容直接返回数据的情况
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
// 处理响应错误
|
// 处理响应错误
|
||||||
console.error("请求错误:", error);
|
console.error("请求错误:", error);
|
||||||
return Promise.reject(error);
|
const message = error.response?.data?.message || error.message || "网络错误";
|
||||||
|
return Promise.reject(new Error(message));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,17 @@ export default defineConfig({
|
|||||||
// 开发服务器配置
|
// 开发服务器配置
|
||||||
server: {
|
server: {
|
||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
port: 3000,
|
port: 5173, // Frontend port, different from backend
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
// API代理配置
|
// API代理配置
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": {
|
"/api": {
|
||||||
target: "http://localhost:3000",
|
target: "http://localhost:2025", // Backend server
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path,
|
||||||
|
},
|
||||||
|
"/health": {
|
||||||
|
target: "http://localhost:2025",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user