save code

This commit is contained in:
xsl
2026-01-27 17:28:07 +08:00
parent 989a0227c7
commit 17c458035b
11 changed files with 676 additions and 85 deletions
+270 -32
View File
@@ -7,37 +7,275 @@
@app.on_event("startup") @app.on_event("startup")
INFO: Will watch for changes in these directories: ['/home/xsl/code/ocean_project_manager/backend'] INFO: Will watch for changes in these directories: ['/home/xsl/code/ocean_project_manager/backend']
INFO: Uvicorn running on http://0.0.0.0:8188 (Press CTRL+C to quit) INFO: Uvicorn running on http://0.0.0.0:8188 (Press CTRL+C to quit)
INFO: Started reloader process [6520] using WatchFiles INFO: Started reloader process [4044] using WatchFiles
INFO: Started server process [6522] INFO: Started server process [4143]
INFO: Waiting for application startup. INFO: Waiting for application startup.
INFO: Application startup complete. INFO: Application startup complete.
INFO: 127.0.0.1:38698 - "POST /api/v1/auth/login HTTP/1.1" 200 OK INFO: 127.0.0.1:58538 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:57886 - "POST /api/v1/auth/login HTTP/1.1" 200 OK INFO: 127.0.0.1:59360 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57900 - "POST /api/v1/auth/login HTTP/1.1" 200 OK INFO: 127.0.0.1:59362 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:43792 - "GET /health HTTP/1.1" 200 OK INFO: 127.0.0.1:56816 - "GET /api/v1/projects/statistics?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:47906 - "GET / HTTP/1.1" 200 OK INFO: 127.0.0.1:56828 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:47906 - "GET /favicon.ico HTTP/1.1" 404 Not Found INFO: 127.0.0.1:56840 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:57272 - "GET / HTTP/1.1" 200 OK INFO: 127.0.0.1:56842 - "GET /api/v1/projects/statistics?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:57288 - "GET /favicon.ico HTTP/1.1" 404 Not Found INFO: 127.0.0.1:56854 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:60220 - "GET / HTTP/1.1" 200 OK INFO: 127.0.0.1:56862 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:60230 - "GET /favicon.ico HTTP/1.1" 404 Not Found WARNING: WatchFiles detected changes in 'config/import-excel-data-v2.py'. Reloading...
INFO: 127.0.0.1:49220 - "GET / HTTP/1.1" 200 OK INFO: Shutting down
INFO: 127.0.0.1:49232 - "GET /favicon.ico HTTP/1.1" 404 Not Found INFO: Waiting for application shutdown.
INFO: 127.0.0.1:43984 - "POST /api/v1/auth/login HTTP/1.1" 200 OK INFO: Application shutdown complete.
INFO: 127.0.0.1:43998 - "POST /api/v1/auth/login HTTP/1.1" 200 OK INFO: Finished server process [4143]
INFO: 127.0.0.1:35788 - "GET / HTTP/1.1" 200 OK INFO: Started server process [8844]
INFO: 127.0.0.1:35788 - "GET /favicon.ico HTTP/1.1" 404 Not Found INFO: Waiting for application startup.
INFO: 127.0.0.1:56510 - "POST /api/v1/auth/login HTTP/1.1" 200 OK INFO: Application startup complete.
INFO: 127.0.0.1:59614 - "POST /api/v1/auth/login HTTP/1.1" 200 OK WARNING: WatchFiles detected changes in 'config/import-excel-data-v2.py'. Reloading...
INFO: 127.0.0.1:39442 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK INFO: Shutting down
INFO: 127.0.0.1:39456 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK INFO: Waiting for application shutdown.
INFO: 127.0.0.1:39462 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK INFO: Application shutdown complete.
INFO: 127.0.0.1:39476 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK INFO: Finished server process [8844]
INFO: 127.0.0.1:39482 - "GET /api/v1/projects/statistics?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity INFO: Started server process [9834]
INFO: 127.0.0.1:39486 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK INFO: Waiting for application startup.
INFO: 127.0.0.1:39502 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity INFO: Application startup complete.
INFO: 127.0.0.1:39506 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK WARNING: WatchFiles detected changes in 'config/import-excel-data-v2.py'. Reloading...
INFO: 127.0.0.1:39522 - "GET /api/v1/projects/statistics?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity INFO: Shutting down
INFO: 127.0.0.1:39530 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity INFO: Waiting for application shutdown.
INFO: 127.0.0.1:33492 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK INFO: Application shutdown complete.
INFO: 127.0.0.1:33496 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK INFO: Finished server process [9834]
INFO: Started server process [9977]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 127.0.0.1:52704 - "GET /api/v1/projects/statistics?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:52712 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:52714 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:52728 - "GET /api/v1/projects/statistics?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:52730 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:52744 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:55242 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55248 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55252 - "POST /api/v1/auth/logout HTTP/1.1" 200 OK
INFO: 127.0.0.1:40794 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:40804 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:40806 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:49642 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:49646 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:49662 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:36846 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:36848 - "GET /api/v1/projects/statistics HTTP/1.1" 401 Unauthorized
INFO: 127.0.0.1:57848 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:54960 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:54970 - "GET /api/v1/projects/statistics HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:42982 - "GET /api/v1/projects?limit=1 HTTP/1.1" 401 Unauthorized
INFO: 127.0.0.1:48152 - "GET /api/v1/projects?limit=1 HTTP/1.1" 200 OK
INFO: 127.0.0.1:36282 - "GET /api/v1/projects/statistics HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:36266 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53870 - "GET /api/v1/projects?page=1&page_size=1 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46572 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:46582 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58766 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:53146 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:53150 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53166 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:53176 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57068 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:57054 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57084 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:57092 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57096 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57108 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50094 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:50104 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50120 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:50122 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50138 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50140 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50152 - "GET /api/v1/projects/statistics/basic?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50164 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50180 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:50182 - "GET /api/v1/projects/statistics/basic?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50192 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50202 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:47624 - "POST /api/v1/auth/logout HTTP/1.1" 200 OK
INFO: 127.0.0.1:47640 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:47654 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:47668 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:47662 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:47676 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:40950 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:42386 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:42398 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46604 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:40432 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:40418 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:40436 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:40440 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:40442 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:40450 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:40480 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:40464 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:40486 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:40502 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54970 - "POST /api/v1/auth/logout HTTP/1.1" 200 OK
INFO: 127.0.0.1:54980 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:54984 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:54992 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55000 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:54998 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56704 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:55252 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:55260 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:39804 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:39806 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:39816 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:39822 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56744 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:56760 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:46382 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46398 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50542 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50560 - "GET /api/v1/projects/statistics/basic?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50550 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:50570 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50584 - "GET /api/v1/projects/statistics/basic?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:50592 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:34382 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:34398 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:34394 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:34410 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:43320 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:43324 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55352 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:55364 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55374 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:55384 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55400 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:55390 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59952 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:59962 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59966 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:59974 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46372 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46384 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46778 - "GET /api/v1/projects/518 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56132 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56136 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:34614 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:34620 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:41252 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:41266 - "GET /api/v1/projects/514 HTTP/1.1" 200 OK
INFO: 127.0.0.1:44226 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:44218 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:44234 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:44246 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:45244 - "GET /api/v1/projects/517 HTTP/1.1" 200 OK
INFO: 127.0.0.1:45248 - "GET /api/v1/projects/517 HTTP/1.1" 200 OK
INFO: 127.0.0.1:48548 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:48546 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:48556 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:41330 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:41336 - "GET /api/v1/projects/statistics/basic?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:41350 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:41360 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:41368 - "GET /api/v1/projects/statistics/basic?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:41374 - "GET /api/v1/projects/statistics/timeline?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 422 Unprocessable Entity
INFO: 127.0.0.1:41380 - "GET /api/v1/projects/statistics/group?group_by=engineering_type&time_field=signing_date&group_by_time=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:52902 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:53544 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:35154 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:35164 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:35166 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:55172 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:55178 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:55194 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:59502 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:59504 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:51184 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:35166 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:35174 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:35156 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:35186 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:35184 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:35198 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:50886 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:50902 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50914 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:50926 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50932 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50940 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:51086 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:51100 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:51116 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:51132 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:51140 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:51146 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:58160 - "GET /api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month HTTP/1.1" 200 OK
INFO: 127.0.0.1:58176 - "GET /api/v1/projects/statistics/group?group_by=engineering_type HTTP/1.1" 200 OK
INFO: 127.0.0.1:58192 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:51614 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:51620 - "GET /api/v1/projects/statistics/basic HTTP/1.1" 200 OK
INFO: 127.0.0.1:51618 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:51624 - "GET /api/v1/projects?page=1&page_size=5 HTTP/1.1" 200 OK
INFO: 127.0.0.1:35502 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:35518 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:48470 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:45706 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60248 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:48410 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53666 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:38252 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:38854 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:38862 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53224 - "GET /api/v1/projects/518 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53234 - "GET /api/v1/projects/518 HTTP/1.1" 200 OK
INFO: 127.0.0.1:41768 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:41770 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:41772 - "GET /api/v1/projects/518 HTTP/1.1" 200 OK
INFO: 127.0.0.1:41784 - "GET /api/v1/projects/518 HTTP/1.1" 200 OK
INFO: 127.0.0.1:44564 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:44578 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60338 - "GET /api/v1/projects/515 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60344 - "GET /api/v1/projects/515 HTTP/1.1" 200 OK
INFO: 127.0.0.1:51570 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:51578 - "GET /api/v1/projects?page=1&page_size=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:47272 - "GET /api/v1/projects?page=1&page_size=10&keyword=3 HTTP/1.1" 200 OK
INFO: 127.0.0.1:47278 - "GET /api/v1/projects?page=1&page_size=10&keyword=31 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46604 - "GET /api/v1/projects?page=1&page_size=10&keyword=317 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46616 - "GET /api/v1/projects?page=1&page_size=10&keyword=3175 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46622 - "GET /api/v1/projects?page=1&page_size=10&keyword=317 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46634 - "GET /api/v1/projects?page=1&page_size=10&keyword=31 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46642 - "GET /api/v1/projects?page=1&page_size=10&keyword=3 HTTP/1.1" 200 OK
INFO: 127.0.0.1:46654 - "GET /api/v1/projects?page=1&page_size=10&keyword=3 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59518 - "GET /api/v1/projects?page=1&page_size=10&keyword=2 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59524 - "GET /api/v1/projects?page=1&page_size=10&keyword=22 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59534 - "GET /api/v1/projects?page=1&page_size=10&keyword=225 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59548 - "GET /api/v1/projects?page=1&page_size=10&keyword=2253 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59564 - "GET /api/v1/projects?page=1&page_size=10&keyword=225 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59570 - "GET /api/v1/projects?page=1&page_size=10&keyword=22 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59574 - "GET /api/v1/projects?page=1&page_size=10&keyword=2 HTTP/1.1" 200 OK
INFO: 127.0.0.1:59590 - "GET /api/v1/projects?page=1&page_size=10&keyword=2 HTTP/1.1" 200 OK
INFO: 127.0.0.1:47084 - "GET /api/v1/projects?page=1&page_size=10&keyword=2 HTTP/1.1" 200 OK
INFO: 127.0.0.1:47096 - "GET /api/v1/projects?page=1&page_size=10&keyword=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:47112 - "GET /api/v1/projects?page=1&page_size=10&keyword=202 HTTP/1.1" 200 OK
INFO: 127.0.0.1:47118 - "GET /api/v1/projects?page=1&page_size=10&keyword=2022 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50184 - "GET /api/v1/projects?page=1&page_size=10&keyword=20225 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50196 - "GET /api/v1/projects?page=1&page_size=10&keyword=202252 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50204 - "GET /api/v1/projects?page=1&page_size=10&keyword=20225 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50206 - "GET /api/v1/projects?page=1&page_size=10&keyword=2022 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50220 - "GET /api/v1/projects?page=1&page_size=10&keyword=202 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50222 - "GET /api/v1/projects?page=1&page_size=10&keyword=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50228 - "GET /api/v1/projects?page=1&page_size=10&keyword=2 HTTP/1.1" 200 OK
INFO: 127.0.0.1:50240 - "GET /api/v1/projects?page=1&page_size=10&keyword=2 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53746 - "GET /api/v1/projects?page=1&page_size=10&keyword=t HTTP/1.1" 200 OK
INFO: 127.0.0.1:53756 - "GET /api/v1/projects?page=1&page_size=10&keyword=ti HTTP/1.1" 200 OK
INFO: 127.0.0.1:53764 - "GET /api/v1/projects?page=1&page_size=10&keyword=ti%27a HTTP/1.1" 200 OK
INFO: 127.0.0.1:53758 - "GET /api/v1/projects?page=1&page_size=10&keyword=tiao HTTP/1.1" 200 OK
INFO: 127.0.0.1:53780 - "GET /api/v1/projects?page=1&page_size=10&keyword=tiao%27s HTTP/1.1" 200 OK
INFO: 127.0.0.1:53786 - "GET /api/v1/projects?page=1&page_size=10&keyword=tiao%27sh HTTP/1.1" 200 OK
INFO: 127.0.0.1:53798 - "GET /api/v1/projects?page=1&page_size=10&keyword=tiao%27shi HTTP/1.1" 200 OK
INFO: 127.0.0.1:53814 - "GET /api/v1/projects?page=1&page_size=10&keyword=%E8%B0%83%E8%AF%95 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53830 - "GET /api/v1/projects?page=1&page_size=10&keyword=%E8%B0%83 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53842 - "GET /api/v1/projects?page=1&page_size=10&keyword=%E8%B0%83 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53846 - "GET /api/v1/projects?page=1&page_size=10&keyword=l HTTP/1.1" 200 OK
INFO: 127.0.0.1:53858 - "GET /api/v1/projects?page=1&page_size=10&keyword=la HTTP/1.1" 200 OK
INFO: 127.0.0.1:53870 - "GET /api/v1/projects?page=1&page_size=10&keyword=lan HTTP/1.1" 200 OK
INFO: 127.0.0.1:53882 - "GET /api/v1/projects?page=1&page_size=10&keyword=%E7%83%82 HTTP/1.1" 200 OK
INFO: 127.0.0.1:39108 - "GET /api/v1/projects?page=1&page_size=10&keyword=%E7%83%82 HTTP/1.1" 200 OK
+2 -2
View File
@@ -20,7 +20,7 @@ from sqlalchemy.orm import declarative_base
from sqlalchemy import text from sqlalchemy import text
# 配置 # 配置
EXCEL_FILE = '../docs/example.xls' EXCEL_FILE = '/home/xsl/code/ocean_project_manager/docs/example.xls'
DATABASE_URL = "mysql+aiomysql://root:rootpassword@localhost:3306/project_manager?charset=utf8mb4" DATABASE_URL = "mysql+aiomysql://root:rootpassword@localhost:3306/project_manager?charset=utf8mb4"
# 创建引擎 # 创建引擎
@@ -195,7 +195,7 @@ async def import_projects_from_sheet(sheet, sheet_name, admin_id):
'profit': parse_decimal_value(safe_get(39)), 'profit': parse_decimal_value(safe_get(39)),
'actual_profit': parse_decimal_value(safe_get(40)), 'actual_profit': parse_decimal_value(safe_get(40)),
'cost_settlement_amount': parse_decimal_value(safe_get(41)), 'cost_settlement_amount': parse_decimal_value(safe_get(41)),
'cumulative_progress': parse_decimal_value(safe_get(42)), 'cumulative_progress': (lambda x: x if x is None else min(x, Decimal('999.99')))(parse_decimal_value(safe_get(42))),
'receivable_amount': parse_decimal_value(safe_get(43)), 'receivable_amount': parse_decimal_value(safe_get(43)),
'invoice_amount': parse_decimal_value(safe_get(44)), 'invoice_amount': parse_decimal_value(safe_get(44)),
'actual_receipt_amount': parse_decimal_value(safe_get(45)), 'actual_receipt_amount': parse_decimal_value(safe_get(45)),
+1 -1
View File
@@ -3,7 +3,7 @@
> vite > vite
VITE v5.4.21 ready in 131 ms VITE v5.4.21 ready in 145 ms
➜ Local: http://localhost:8189/ ➜ Local: http://localhost:8189/
➜ Network: use --host to expose ➜ Network: use --host to expose
+45
View File
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>API测试</title>
</head>
<body>
<h1>API测试页面</h1>
<button onclick="testStatistics()">测试统计API</button>
<div id="result"></div>
<script>
const API_BASE = '/api/v1';
async function testStatistics() {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '加载中...';
try {
// 先登录
const loginResponse = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'admin', password: 'admin123' })
});
const loginData = await loginResponse.json();
const token = loginData.data.token;
// 调用统计API
const statsResponse = await fetch(`${API_BASE}/projects/statistics/basic`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const statsData = await statsResponse.json();
resultDiv.innerHTML = `
<h2>统计结果:</h2>
<pre>${JSON.stringify(statsData, null, 2)}</pre>
<h3>项目总数:${statsData.data.total_count}</h3>
`;
} catch (error) {
resultDiv.innerHTML = `<p style="color: red;">错误:${error.message}</p>`;
}
}
</script>
</body>
</html>
+9
View File
@@ -5,6 +5,7 @@ import Layout from './components/Layout/Layout';
import Login from './pages/Login/Login'; import Login from './pages/Login/Login';
import Dashboard from './pages/Dashboard/Dashboard'; import Dashboard from './pages/Dashboard/Dashboard';
import ProjectList from './pages/Projects/ProjectList'; import ProjectList from './pages/Projects/ProjectList';
import ProjectDetail from './pages/Projects/ProjectDetail';
import UserList from './pages/Users/UserList'; import UserList from './pages/Users/UserList';
import Statistics from './pages/Statistics/Statistics'; import Statistics from './pages/Statistics/Statistics';
@@ -48,6 +49,14 @@ function App() {
</PrivateRoute> </PrivateRoute>
} }
/> />
<Route
path="/projects/:id"
element={
<PrivateRoute>
<ProjectDetail />
</PrivateRoute>
}
/>
<Route <Route
path="/projects/create" path="/projects/create"
element={ element={
+19 -14
View File
@@ -1,7 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { projectAPI } from '../../services/project'; import { projectAPI } from '../../services/project';
const Dashboard = () => { const Dashboard = () => {
const navigate = useNavigate();
const [statistics, setStatistics] = useState(null); const [statistics, setStatistics] = useState(null);
const [recentProjects, setRecentProjects] = useState([]); const [recentProjects, setRecentProjects] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -14,13 +16,18 @@ const Dashboard = () => {
try { try {
setLoading(true); setLoading(true);
console.log('开始获取仪表盘数据...');
const [statsResponse, projectsResponse] = await Promise.all([ const [statsResponse, projectsResponse] = await Promise.all([
authAPI.getStatistics(), projectAPI.getStatistics(),
projectAPI.getList({ page: 1, page_size: 5 }), projectAPI.getList({ page: 1, page_size: 5 }),
]); ]);
setStatistics(statsResponse); console.log('统计API响应:', statsResponse);
setRecentProjects(projectsResponse.items || []); console.log('项目列表API响应:', projectsResponse);
setStatistics(statsResponse.data || statsResponse);
setRecentProjects(projectsResponse.data?.items || projectsResponse.items || []);
} catch (error) { } catch (error) {
console.error('Failed to fetch dashboard data:', error); console.error('Failed to fetch dashboard data:', error);
} finally { } finally {
@@ -44,17 +51,9 @@ const Dashboard = () => {
<div className="stat-value">{statistics?.total_count || 0}</div> <div className="stat-value">{statistics?.total_count || 0}</div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-label">进行中</div> <div className="stat-label">平均进度%</div>
<div className="stat-value"> <div className="stat-value">
{(statistics?.total_count || 0) - {statistics?.avg_cumulative_progress?.toFixed(1) || '0.0'}
(statistics?.completed_count || 0) -
(statistics?.cancelled_count || 0) || 0}
</div>
</div>
<div className="stat-card">
<div className="stat-label">已完成</div>
<div className="stat-value">
{statistics?.completed_count || 0}
</div> </div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
@@ -66,6 +65,12 @@ const Dashboard = () => {
})} })}
</div> </div>
</div> </div>
<div className="stat-card">
<div className="stat-label">回款完成率%</div>
<div className="stat-value">
{statistics?.avg_receipt_completion_rate?.toFixed(1) || '0.0'}
</div>
</div>
</div> </div>
<div className="card"> <div className="card">
@@ -93,7 +98,7 @@ const Dashboard = () => {
onMouseLeave={(e) => { onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent'; e.currentTarget.style.background = 'transparent';
}} }}
onClick={() => window.location.href = `/projects/${project.id}`} onClick={() => navigate(`/projects/${project.id}`)}
> >
<div <div
style={{ style={{
@@ -0,0 +1,212 @@
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { projectAPI } from '../../services/project';
const ProjectDetail = () => {
const { id } = useParams();
const navigate = useNavigate();
const [project, setProject] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchProjectDetail();
}, [id]);
const fetchProjectDetail = async () => {
try {
setLoading(true);
const result = await projectAPI.getDetail(id);
setProject(result.data || result);
} catch (error) {
console.error('Failed to fetch project detail:', error);
alert('获取项目详情失败');
} finally {
setLoading(false);
}
};
if (loading) {
return <div>加载中...</div>;
}
if (!project) {
return <div>项目不存在</div>;
}
return (
<div>
<div style={{ marginBottom: '24px' }}>
<button
className="btn btn-default"
onClick={() => navigate('/projects')}
>
返回列表
</button>
</div>
<div className="card">
<div className="card-header">
项目详情
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '24px' }}>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>项目编号</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.project_no || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>项目名称</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.name || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>工程类别</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.engineering_type || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>所属部门</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.project_department || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>合同金额万元</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.contract_amount ? Number(project.contract_amount).toLocaleString('zh-CN', { minimumFractionDigits: 2 }) : '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>项目负责人</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.project_leader || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>建设单位</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.owner_unit || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>建设单位联系人</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.owner_contact || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>合同签订日期</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.signing_date || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>开始日期</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.start_date || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>计划结束日期</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.planned_end_date || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>实际结束日期</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.actual_end_date || '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>累计进度%</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.cumulative_progress ? Number(project.cumulative_progress).toFixed(2) : '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>实际收款金额万元</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.actual_receipt_amount ? Number(project.actual_receipt_amount).toLocaleString('zh-CN', { minimumFractionDigits: 2 }) : '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>回款完成率%</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.receipt_completion_rate ? Number(project.receipt_completion_rate).toFixed(2) : '-'}
</div>
</div>
<div>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>状态</label>
<div style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: '4px' }}>
{project.status || '-'}
</div>
</div>
</div>
{project.payment_method && (
<div style={{ marginTop: '24px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>付款方式</label>
<div style={{ padding: '12px', background: '#f5f5f5', borderRadius: '4px', whiteSpace: 'pre-wrap' }}>
{project.payment_method}
</div>
</div>
)}
{project.problems && (
<div style={{ marginTop: '24px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500, color: '#ff4d4f' }}>存在问题</label>
<div style={{ padding: '12px', background: '#fff2f0', borderRadius: '4px', border: '1px solid #ffccc7', whiteSpace: 'pre-wrap' }}>
{project.problems}
</div>
</div>
)}
{project.suggestions && (
<div style={{ marginTop: '24px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500, color: '#1890ff' }}>建议</label>
<div style={{ padding: '12px', background: '#e6f7ff', borderRadius: '4px', border: '1px solid #91d5ff', whiteSpace: 'pre-wrap' }}>
{project.suggestions}
</div>
</div>
)}
{project.remarks && (
<div style={{ marginTop: '24px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>备注</label>
<div style={{ padding: '12px', background: '#f5f5f5', borderRadius: '4px', whiteSpace: 'pre-wrap' }}>
{project.remarks}
</div>
</div>
)}
<div style={{ marginTop: '24px', padding: '12px', background: '#fafafa', borderRadius: '4px' }}>
<div style={{ fontSize: '12px', color: '#999' }}>
创建时间{project.created_at} | 创建人{project.created_by_name || '-'}
</div>
</div>
</div>
</div>
);
};
export default ProjectDetail;
+94 -6
View File
@@ -41,8 +41,9 @@ const ProjectList = ({ mode = 'list' }) => {
...filters, ...filters,
...params, ...params,
}); });
setList(result.items || []); const data = result.data || result;
setTotal(result.total || 0); setList(data.items || []);
setTotal(data.total || 0);
} catch (error) { } catch (error) {
console.error('Failed to fetch projects:', error); console.error('Failed to fetch projects:', error);
alert('获取项目列表失败'); alert('获取项目列表失败');
@@ -72,7 +73,8 @@ const ProjectList = ({ mode = 'list' }) => {
const handleEdit = async (id) => { const handleEdit = async (id) => {
try { try {
const project = await projectAPI.getDetail(id); const result = await projectAPI.getDetail(id);
const project = result.data || result;
setForm(project); setForm(project);
setEditingId(id); setEditingId(id);
setModalVisible(true); setModalVisible(true);
@@ -143,6 +145,23 @@ const ProjectList = ({ mode = 'list' }) => {
}); });
}; };
const handleChange = (e) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};
const handleFilterChange = (e) => {
const { name, value } = e.target;
const newFilters = { ...filters };
if (value) {
newFilters[name] = value;
} else {
delete newFilters[name];
}
setFilters(newFilters);
fetchProjects(newFilters);
};
const canEdit = (createdBy) => { const canEdit = (createdBy) => {
return user.role === 'admin' || (user.role === 'market' && createdBy === user?.id); return user.role === 'admin' || (user.role === 'market' && createdBy === user?.id);
}; };
@@ -157,10 +176,13 @@ const ProjectList = ({ mode = 'list' }) => {
{mode === 'create' ? '新建项目' : '项目列表'} {mode === 'create' ? '新建项目' : '项目列表'}
</h1> </h1>
<div style={{ marginBottom: '16px', display: 'flex', gap: '12px' }}> <div style={{ marginBottom: '16px', display: 'flex', gap: '12px', flexWrap: 'wrap', alignItems: 'center' }}>
<input <input
type="text" type="text"
name="keyword"
placeholder="搜索项目名称、合同编号..." placeholder="搜索项目名称、合同编号..."
value={filters.keyword || ''}
onChange={handleFilterChange}
style={{ style={{
padding: '8px 12px', padding: '8px 12px',
border: '1px solid #d9d9d9', border: '1px solid #d9d9d9',
@@ -170,6 +192,9 @@ const ProjectList = ({ mode = 'list' }) => {
}} }}
/> />
<select <select
name="engineering_type"
value={filters.engineering_type || ''}
onChange={handleFilterChange}
style={{ style={{
padding: '8px 12px', padding: '8px 12px',
border: '1px solid #d9d9d9', border: '1px solid #d9d9d9',
@@ -184,6 +209,69 @@ const ProjectList = ({ mode = 'list' }) => {
<option value="营销">营销</option> <option value="营销">营销</option>
<option value="检修">检修</option> <option value="检修">检修</option>
</select> </select>
<input
type="date"
name="signing_date_start"
value={filters.signing_date_start || ''}
onChange={handleFilterChange}
style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
fontSize: '14px',
}}
placeholder="签订日期起"
/>
<input
type="date"
name="signing_date_end"
value={filters.signing_date_end || ''}
onChange={handleFilterChange}
style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
fontSize: '14px',
}}
placeholder="签订日期止"
/>
<input
type="number"
name="contract_amount_min"
value={filters.contract_amount_min || ''}
onChange={handleFilterChange}
style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
fontSize: '14px',
width: '120px',
}}
placeholder="最小金额"
/>
<input
type="number"
name="contract_amount_max"
value={filters.contract_amount_max || ''}
onChange={handleFilterChange}
style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
fontSize: '14px',
width: '120px',
}}
placeholder="最大金额"
/>
<button
className="btn btn-default"
onClick={() => {
setFilters({});
fetchProjects({});
}}
>
重置筛选
</button>
{user?.role === 'admin' && ( {user?.role === 'admin' && (
<button <button
className="btn btn-primary" className="btn btn-primary"
@@ -247,8 +335,8 @@ const ProjectList = ({ mode = 'list' }) => {
<td style={{ padding: '12px 16px', fontSize: '14px', color: '#666' }}> <td style={{ padding: '12px 16px', fontSize: '14px', color: '#666' }}>
{project.engineering_type} {project.engineering_type}
</td> </td>
<td style={{ padding: '12px 16px', fontSize: '14px', color: '#666', textAlign: 'right' }}> <td style={{ padding: '12px 16px', fontSize: '14px', color: '#666', textAlign: 'right' }}>
{project.contract_amount?.toLocaleString('zh-CN')} {project.contract_amount ? Number(project.contract_amount).toLocaleString('zh-CN', { minimumFractionDigits: 2 }) : '-'}
</td> </td>
<td style={{ padding: '12px 16px', fontSize: '14px', textAlign: 'center' }}> <td style={{ padding: '12px 16px', fontSize: '14px', textAlign: 'center' }}>
<button <button
+22 -28
View File
@@ -21,14 +21,14 @@ const Statistics = () => {
setLoading(true); setLoading(true);
const [statsResponse, groupResponse, timelineResponse] = await Promise.all([ const [statsResponse, groupResponse, timelineResponse] = await Promise.all([
projectAPI.getStatistics(filters), projectAPI.getStatistics(),
projectAPI.getGroupStatistics(filters), projectAPI.getGroupStatistics({ group_by: filters.group_by }),
projectAPI.getTimelineStatistics(filters), projectAPI.getTimelineStatistics({ time_field: filters.time_field, group_by: filters.group_by_time }),
]); ]);
setStatistics(statsResponse); setStatistics(statsResponse.data || statsResponse);
setGroupStatistics(groupResponse || []); setGroupStatistics(groupResponse.data || groupResponse || []);
setTimelineStatistics(timelineResponse || []); setTimelineStatistics(timelineResponse.data || timelineResponse || []);
} catch (error) { } catch (error) {
console.error('Failed to fetch statistics data:', error); console.error('Failed to fetch statistics data:', error);
alert('获取统计数据失败'); alert('获取统计数据失败');
@@ -155,36 +155,30 @@ const Statistics = () => {
</div> </div>
</div> </div>
<div className="stat-grid"> <div className="stat-grid">
<div className="stat-card"> <div className="stat-card">
<div className="stat-label">项目总数</div> <div className="stat-label">项目总数</div>
<div className="stat-value">{statistics?.total_count || 0}</div> <div className="stat-value">{statistics?.total_count || 0}</div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-label">总投资金额万元</div> <div className="stat-label">平均进度%</div>
<div className="stat-value"> <div className="stat-value">
{(statistics?.total_investment || 0).toLocaleString('zh-CN', { {Number(statistics?.avg_cumulative_progress || 0).toFixed(1)}
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</div> </div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-label">总合同金额万元</div> <div className="stat-label">总合同金额万元</div>
<div className="stat-value"> <div className="stat-value">
{(statistics?.total_contract_amount || 0).toLocaleString('zh-CN', { {Number(statistics?.total_contract_amount || 0).toLocaleString('zh-CN', {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
})} })}
</div> </div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-label">总收款金额万元</div> <div className="stat-label">回款完成率%</div>
<div className="stat-value"> <div className="stat-value">
{(statistics?.total_receipt_amount || 0).toLocaleString('zh-CN', { {Number(statistics?.avg_receipt_completion_rate || 0).toFixed(1)}
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</div> </div>
</div> </div>
</div> </div>
@@ -336,16 +330,16 @@ const Statistics = () => {
borderBottom: '1px solid #f0f0f0', borderBottom: '1px solid #f0f0f0',
}} }}
> >
<td <td
style={{ style={{
padding: '12px 16px', padding: '12px 16px',
fontSize: '14px', fontSize: '14px',
color: '#666', color: '#666',
textAlign: 'right', textAlign: 'left',
}} }}
> >
{item.month} {item.month || item.day || item.year}
</td> </td>
<td <td
style={{ style={{
padding: '12px 16px', padding: '12px 16px',
+1 -1
View File
@@ -11,7 +11,7 @@ export const projectAPI = {
delete: (id) => api.delete(`/projects/${id}`), delete: (id) => api.delete(`/projects/${id}`),
getStatistics: (params) => api.get('/projects/statistics', { params }), getStatistics: (params) => api.get('/projects/statistics/basic', { params }),
getGroupStatistics: (params) => api.get('/projects/statistics/group', { params }), getGroupStatistics: (params) => api.get('/projects/statistics/group', { params }),
+1 -1
View File
@@ -48,7 +48,7 @@ body {
background: #ffffff; background: #ffffff;
border-right: 1px solid #e8e8e8; border-right: 1px solid #e8e8e8;
position: fixed; position: fixed;
left: 64px; left: 0;
top: 64px; top: 64px;
bottom: 0; bottom: 0;
overflow-y: auto; overflow-y: auto;