From abe314fdc8e4d6d11ebaab23345f4e129b8d5314 Mon Sep 17 00:00:00 2001 From: pony <1356137040@qq.com> Date: Tue, 9 Dec 2025 14:46:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 13 +- .gitignore | 3 + data/db.sqlite3 | Bin 1302528 -> 1302528 bytes generate_menu.py | 4 +- hertz_server_diango_ui/.env | 2 +- hertz_server_diango_ui/.env.development | 3 +- hertz_server_diango_ui/.env.production | 3 +- hertz_server_diango_ui/README.md | 71 +- hertz_server_diango_ui/components.d.ts | 1 + hertz_server_diango_ui/package-lock.json | 19 - hertz_server_diango_ui/package.json | 3 +- .../scripts/prune-modules.mjs | 392 +++ hertz_server_diango_ui/src/api/index.ts | 1 + hertz_server_diango_ui/src/api/kb.ts | 131 + hertz_server_diango_ui/src/api/user.ts | 6 + hertz_server_diango_ui/src/api/yolo.ts | 217 +- hertz_server_diango_ui/src/assets/vue.svg | 1 - .../src/components/HelloWorld.vue | 55 - .../src/config/hertz_modules.ts | 85 + .../src/outer_src/api/ai.ts | 69 - .../src/outer_src/router/user_menu_ai.ts | 231 -- .../src/outer_src/views/user_pages/AiChat.vue | 261 -- .../src/public/img/logo-蓝.png | Bin 12610 -> 0 bytes .../src/router/admin_menu.ts | 47 +- hertz_server_diango_ui/src/router/index.ts | 20 + .../src/router/user_menu_ai.ts | 41 +- .../src/stores/hertz_user.ts | 25 +- hertz_server_diango_ui/src/style.css | 79 - .../src/views/ModuleSetup.vue | 149 + ...seManagement.vue => ArticleManagement.vue} | 581 ++-- .../src/views/admin_page/Dashboard.vue | 6 +- .../views/admin_page/DatasetManagement.vue | 1404 +++++++++ .../views/admin_page/DepartmentManagement.vue | 25 +- .../src/views/admin_page/LogManagement.vue | 2 +- .../src/views/admin_page/MenuManagement.vue | 57 +- .../admin_page/NotificationManagement.vue | 90 +- .../src/views/admin_page/UserManagement.vue | 16 +- .../views/admin_page/YoloTrainManagement.vue | 1305 +++++++++ .../src/views/admin_page/index.vue | 78 +- hertz_server_diango_ui/src/views/index.vue | 1 - hertz_server_diango_ui/src/views/register.vue | 24 +- .../src/views/user_pages/AiChat.vue | 193 +- ...{KnowledgeCenter.vue => ArticleCenter.vue} | 6 +- ...{KnowledgeDetail.vue => ArticleDetail.vue} | 0 .../src/views/user_pages/KbCenter.vue | 2499 +++++++++++++++++ .../src/views/user_pages/Profile.vue | 281 +- .../src/views/user_pages/YoloDetection.vue | 210 +- .../src/views/user_pages/index.vue | 12 +- hertz_server_django/asgi.py | 29 +- hertz_server_django/settings.py | 36 +- hertz_server_django/urls.py | 21 +- .../yolo/convert_paths_to_relative.py | 146 + requirements.txt | 3 +- start_server.py | 99 +- static/models/yolov12/BoxF1_curve.png | Bin 241179 -> 0 bytes static/models/yolov12/BoxPR_curve.png | Bin 130739 -> 0 bytes static/models/yolov12/BoxP_curve.png | Bin 162401 -> 0 bytes static/models/yolov12/BoxR_curve.png | Bin 217424 -> 0 bytes static/models/yolov12/args.yaml | 106 - static/models/yolov12/confusion_matrix.png | Bin 182413 -> 0 bytes .../yolov12/confusion_matrix_normalized.png | Bin 208392 -> 0 bytes static/models/yolov12/labels.jpg | Bin 201121 -> 0 bytes static/models/yolov12/results.csv | 101 - static/models/yolov12/results.png | Bin 254988 -> 0 bytes static/models/yolov12/train_batch0.jpg | Bin 690107 -> 0 bytes static/models/yolov12/train_batch1.jpg | Bin 506570 -> 0 bytes static/models/yolov12/train_batch2.jpg | Bin 524602 -> 0 bytes static/models/yolov12/val_batch0_labels.jpg | Bin 721644 -> 0 bytes static/models/yolov12/val_batch0_pred.jpg | Bin 729614 -> 0 bytes static/models/yolov12/val_batch1_labels.jpg | Bin 747775 -> 0 bytes static/models/yolov12/val_batch1_pred.jpg | Bin 758841 -> 0 bytes static/models/yolov12/val_batch2_labels.jpg | Bin 734314 -> 0 bytes static/models/yolov12/val_batch2_pred.jpg | Bin 754556 -> 0 bytes static/models/yolov12/weights/best.pt | Bin 5561860 -> 0 bytes static/models/yolov12/weights/last.pt | Bin 5561860 -> 0 bytes 后端部署教程.md | 5 +- 76 files changed, 7601 insertions(+), 1667 deletions(-) create mode 100644 hertz_server_diango_ui/scripts/prune-modules.mjs create mode 100644 hertz_server_diango_ui/src/api/kb.ts delete mode 100644 hertz_server_diango_ui/src/assets/vue.svg delete mode 100644 hertz_server_diango_ui/src/components/HelloWorld.vue create mode 100644 hertz_server_diango_ui/src/config/hertz_modules.ts delete mode 100644 hertz_server_diango_ui/src/outer_src/api/ai.ts delete mode 100644 hertz_server_diango_ui/src/outer_src/router/user_menu_ai.ts delete mode 100644 hertz_server_diango_ui/src/outer_src/views/user_pages/AiChat.vue delete mode 100644 hertz_server_diango_ui/src/public/img/logo-蓝.png delete mode 100644 hertz_server_diango_ui/src/style.css create mode 100644 hertz_server_diango_ui/src/views/ModuleSetup.vue rename hertz_server_diango_ui/src/views/admin_page/{KnowledgeBaseManagement.vue => ArticleManagement.vue} (78%) create mode 100644 hertz_server_diango_ui/src/views/admin_page/DatasetManagement.vue create mode 100644 hertz_server_diango_ui/src/views/admin_page/YoloTrainManagement.vue delete mode 100644 hertz_server_diango_ui/src/views/index.vue rename hertz_server_diango_ui/src/views/user_pages/{KnowledgeCenter.vue => ArticleCenter.vue} (99%) rename hertz_server_diango_ui/src/views/user_pages/{KnowledgeDetail.vue => ArticleDetail.vue} (100%) create mode 100644 hertz_server_diango_ui/src/views/user_pages/KbCenter.vue create mode 100644 hertz_studio_django_utils/yolo/convert_paths_to_relative.py delete mode 100644 static/models/yolov12/BoxF1_curve.png delete mode 100644 static/models/yolov12/BoxPR_curve.png delete mode 100644 static/models/yolov12/BoxP_curve.png delete mode 100644 static/models/yolov12/BoxR_curve.png delete mode 100644 static/models/yolov12/args.yaml delete mode 100644 static/models/yolov12/confusion_matrix.png delete mode 100644 static/models/yolov12/confusion_matrix_normalized.png delete mode 100644 static/models/yolov12/labels.jpg delete mode 100644 static/models/yolov12/results.csv delete mode 100644 static/models/yolov12/results.png delete mode 100644 static/models/yolov12/train_batch0.jpg delete mode 100644 static/models/yolov12/train_batch1.jpg delete mode 100644 static/models/yolov12/train_batch2.jpg delete mode 100644 static/models/yolov12/val_batch0_labels.jpg delete mode 100644 static/models/yolov12/val_batch0_pred.jpg delete mode 100644 static/models/yolov12/val_batch1_labels.jpg delete mode 100644 static/models/yolov12/val_batch1_pred.jpg delete mode 100644 static/models/yolov12/val_batch2_labels.jpg delete mode 100644 static/models/yolov12/val_batch2_pred.jpg delete mode 100644 static/models/yolov12/weights/best.pt delete mode 100644 static/models/yolov12/weights/last.pt diff --git a/.env b/.env index 6118cfa..4f2da5e 100644 --- a/.env +++ b/.env @@ -3,13 +3,14 @@ SECRET_KEY=django-insecure-0a1bx*8!97l^4z#ml#ufn_*9ut*)zlso$*k-g^h&(2=p@^51md DEBUG=True #ALLOWED_HOSTS=localhost,127.0.0.1,django-host,192.168.1.22 ALLOWED_HOSTS=* -# Database Configuration +# 切换数据源,支持sqlite/mysql +DB_ENGINE=sqlite USE_REDIS_AS_DB=True # MySQL Configuration (when USE_REDIS_AS_DB=False) DB_NAME=hertz_server DB_USER=root -DB_PASSWORD=root +DB_PASSWORD=123456 DB_HOST=localhost DB_PORT=3306 @@ -27,9 +28,9 @@ EMAIL_HOST=smtp.qq.com EMAIL_PORT=465 EMAIL_USE_SSL=True EMAIL_USE_TLS=False -EMAIL_HOST_USER=your_email@example.com -EMAIL_HOST_PASSWORD=your_email_password_or_app_key -DEFAULT_FROM_EMAIL=your_email@example.com +EMAIL_HOST_USER=your_email@qq.com +EMAIL_HOST_PASSWORD=your_email_password +DEFAULT_FROM_EMAIL=your_email@qq.com # 注册邮箱验证码开关(0=关闭,1=开启) REGISTER_EMAIL_VERIFICATION=0 @@ -48,4 +49,4 @@ HERTZ_CAPTCHA_REDIS_KEY_PREFIX=hertz_captcha: # Auth Middleware Configuration - 不需要登录验证的URL模式(支持正则表达式) # 格式:使用逗号分隔的正则表达式模式 # 示例:/api/demo 表示demo接口,/api/.* 表示/api路径下的所有 -NO_AUTH_PATTERNS=^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$,^/yolo/.*$, \ No newline at end of file +NO_AUTH_PATTERNS=^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$,^/yolo/.*$, diff --git a/.gitignore b/.gitignore index 9c52905..85f7b13 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# python envs +venv/ + # Distribution / packaging .Python build/ diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 045100f173f79a096c479c1d9ed9aa81e8a1acea..d59a5eec807657fe5c0455f6864d6a70eb27ffb3 100644 GIT binary patch delta 18006 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 delta 12647 zcmeHNd3;sHy+3o#x#yg_oHI9M!LWoIARt*Tci+O2fCAY_5Rpni5=f8;fj~f(3h|tBfOHejjl+9KpD*erzdv9(+5}^OR&qu=f z&YbxzXJ+ni<~Pgl{=?<>A1>cEIB5^Zp$Qzv&4T9;>J0&)a* z;(y@__$)q+kK_07AEl4>Zz{v&NE4DIpg$DOZG8_{T6wjqHDEhnK?hqt$<32c#t5Mh zkqe*6gIWsnGNk;WnZiUwp6QU&(Z-ghylTX^9M4Nd`&vHDt3%^ks_p5d?R&z2WFEiG zsr>SW+LhI->Xr}DgnXW4{)8XMOJyxTwXZ?xEsn7Zq%i{xf>&vA7*bkZ9Gis-Tiza< zZO*P))@Xt%jWM>g$d0U5kE2~_sj?B#E*6bwSrJSn=iiqNE$5RAt!=^SX~G!eqb+j9 z%+?vUfKkdDBXUI}NajU3UFW!Sx=+|=QCj6~MF?1aYHC&Xd@Y`&A3by zIFv*|qhO|BqF|&TQ;;YaD5w;y6a)&qAaF+d10j5w*5oX{YF8V<1m*x)C=7dUNVa}78=R+@mi)O=(0O~ z0lU-XvHQu2iCQIck!Kd7RPtZ>T8_<`=W^N{{*Vj)JMA8?Bj|C2i<FHUt2F}IErY7`++fOp<&>X!hVl8lo+^&$zVRtxv9=`{A!GCoxT!#{;Bjj+~{XrLW zCbYqq*oL9B8?IA_%MR7>2c4k9o2VnZZ}~F)=X8f?9pdtnn?13P2ro$j%Rr^&Q_E-F ze&(fpXI>7Ue(bqZ4@XY#JaBT$&z*gCQD#iDBN+0!>@F{`I{EOVW_L{W_4cvD7jiqG zdjj5o3;NjgAM`Q&N?vMeE|1R*KU8V6GqHcJua9*dVDfIi!|MYbjzk^(^l{YOyW~AS zCpDI_H`)+~4f@g%2zmYXKp^Py(;wQNSc&UsNL+`{9)OYSrQ7#1@uVmOWAREAR+ z-pcS6hLagiVpzzqfZ;@j6Bv$XIF4aH!w|zD!vI6SVLJW1kG*&$0r9-#VJ~imE{0Bq z4u)eH+8O3C%w?FvFuQFV603?wnkzYc3?IZ#;`?wV4&ij|Gwsh>o7Sk^ra3jMdQSbF z`a`u@U7{ANV^qa*+VY0wzby}17Fs4*GA*3>nE9ajN%MW?%E;EU=xjnJb5@r7!u7M55=>GixabHW@j+qkZ#o_GD_mKvJzo%(_~ica=5&C&X_0i zvN_oAbUQs>X;N>u;uUIo!L>H)PlV%f{rVOSVXi5z&DFS1Gqw{y6iSP%2ViR~~iMO=sZ ze`vtaO4^U`9uVy0U>DFHv9$9a;_Vd@76l_pr%P?R2E~_&wSx~4TL*|1ckoY9-2-5H zLMT03m9}x3;UH%?s1a?f<&x#wmJ#Ly<{75bYNcs|`YThi@p)sBSW5rUZdhm6qsZq{CaPK%W1czeil0l){nlWj=#=FZ0vH#hU)wE-p4j36aK5{w5?YlHmQ3FZkbz zi`AIOhA;UtvgtBERPoiUuCHI`^pT`ae(-4M=%C#PON^i^*1JwObr9YJ$|8Zh+Q|=- z-AvTYB**weo`5~*bo#vh9+C-^@C-hc?EZ@1LE7e_+?%^EnxO9<=eXpuF(Xn{W2V9t z78(`B$a%e9^3^2uo`F0Xg`13qss;0bt~F8Yrdq$95@=Z4j}8+k|xf<5@p+V{2h^gsA;twvfhgu)>M1-JfTr@x?f zZFPOa(puUrNwl1t&k%A&ma`2LCW&c+6Ol#3glxU+v0*@EWv>qt3W`UXUgxxCtyVo^ zc0+^P@OHcuPsPKvFSU2HR;@rAto}=VL48QAQuEa`%LU7CEzepuS>{;+mSpqC<~Pmz z%uVJwX3g|rWV%f_Y$9`Vgu%MYbLR-9;z%*IkUWqhWRf>2;ZTk+fxMF=Sh4QkD4D;v zu67NfKF-+ns{6V4d(5izT~~Ks*B4Vaf7dTl%;)uBiq5nCke8_fqho%r2UpAiclUk$ zGR6E~KTOmIHql+BG_EhMgl(gg-w#ulFT5V6;3zS@kY_ucrrQ&{y{B93RasBZ*bh@| zpC$SFVroj*jY;0FdiAJQcR$%TTQ_If>o4V zajq^8+JmbHQ`C$0%GA@7_QOPdWfR?0NIBQw(mg7jL`NEra+X7;XH6STvrM@r!FUw^ zA55X^a48<9UDST1J)td8&#SMgJJd;Ps^yI3QA;h%BWCm8%=^s`nrEA{O<$VYwOh2I z>TUL=9fvSl%_hDP6;`7fN*Oby0c_xSo{Q&Rxo3lp%@x@z`2b zA6YdV3AHCri&PW{D~;qrkuaK+g#?A>?xK)M8o=`P+|$Lvcs=*KVnKkQ?l*GeE2@W;8WD<`6C10NuZz83;1$U!#H`@4jt}@yllmw&g zg%UMdN=feW5OJd=`=W&|G%Ea z7wD^aeeR8&Q>n0~9w5EJ;U?`1Ev#|s8p|h^W#*IS8q;ypBI8NpT;*M5ru>#%B)tJ= zL%Sp)%xxqMJC&KFV5cIJ9}#5~-?V`=OgEO1u}>(f7;pyMq~Hl9P0#(FDBFav@J%vt zwQ@U|uv0OK9ymT`92(8#*Voq6t*#Gq>q*BtWd>s*hn`Tfgs`-ZI3EQms+UaLsbuj@ zYf1aPN(E&y8XQ5VJ4h9!YAhSov~CTVu|_G5^7tJ=ALB7NSa#7)Wh>vbTCecLU5X1n zvx@wFtumXux(k&0d=8(R?BAuNVwMd>t0y$-Ezp@fL9Z*yM6)p_Zl&G^oyqO=2BJ(f z8)K4I=&gc}tD@84b21*yN3&6$rUt#4pvNS_btS7%8p}oQ*`*i|-&D_9uw#{CAleE= zA-~zJWJOSia%C8~a!xX#@4->dPCTZPH*#4{#aLL#o%+eQ&%FHj$%i+e-hZH5{*gz{ z9D0Sk5)@Mn0gpG}VOh+wbYaL@IS!u#btmy)dZv^p%TxPd4BLTW3l7%v7{14G!#JeC zH&K-P)Q&Buf3R1V)bl4dKXme?pEayru(Ymmaczwy`hCCK8DQC@0L@zYcnvyf>n>n+ zdEZqU$$Uk7S8c7b-Na*h`k5e*K$0kl=fC@k?OAJwV%>+V+bd)^A%z1$Vkt1pymdNc zOW`SjrvjdGc*@`@g5%av@Qj9hF+4NjxdWcr@XUp$8lDC4dr{rHFN#JjY0`JRzr$S09%hQ1@F=w6CvgNMOo{G0k05TX*;3RgH5O)HbfNH>^tF8GRMc(rU)@z<_|p zYNj{~ACU4%{Z9G2WXRL<8R&&i^t(prg}rhIJx?D(FXa$o$WbfiwX+<)Pdlse8V|O7 zQH}5iWu6DwPg$6WHb0^rmQUb8S_N1WJ&lK`5TlXWCy)P059|L)tnIQtGPzy;0nUYD zLq&y14R0MU3dp!Z6-IJK1kofvM1pECot4%LM~!7BJ3j|jrr$A#Cxr!hVEKF$UP$gb zC#R9OK9IF+{ zTb{}tNE%PbVeO&p@E}I{Rvl0xr6=S@1L^!go?*7U$8D1bE-6CcO0w~Nc_b;gC{HFY zUy##j&OpY0ARmkz`cOWHvY{E-P^)tO+hak==HzgS{+7%>wzziXs`c~rI!0dlhuq0O z3+7~fbemY?(3*9_`#W6j9>s6N)$ad5OwNz-i+CU2gCE1qaO%AQ-;0;wMffg!2cC|n z;E6bZ9r$K!!#80oRxr{o!=cXKwfD87+S}S&+H2Y`v>$6PYWuW3+GAQX808jo+^47FH}zCluc!PvNkm4JzZMb&<*#9-A^BcCl~&WV355)+^a8O_Plc6w z%CBJJp#~-%TFy#?@-jV@>h;vHR8PevdMezbr+gig4lZWW!Mj;$KwhM$QmvjEX4dE# zaiN|H3-pw)X5xV=CLWm2O8xR(dMeG+Q^Q<66)W{rm_t+Y3RZ>>VI@Ap?RqNC)KlR$ z#_=&$(v;Y!gVFSe8reNmna^u7r^zn2iw6QhQsAW8kmF$i`rLkVSW+D=_#!P2F_CvDN|XZDxBZU4MuB{$+t!* zLrJn-8QFE{ziry7~aS5n+(@8T*q)N!!;B`_cC0~a23NwhASDaVA#NL zImO^IhV=}WLihp{En#>M!#akG8Qx7Xu!vzT!y1MQ87^R0&9I8$e2V_N7|vrjmtiHt zISlV)_zi}$4VfU|yMw;?W-+{-;Y^0NQH)$1t6=Lec`RoXAAl=E%PEVdJSqRL^pN4t z28Z~7v`1377od~3MqW5Czl-8igjF1FE1ym-9OTnze4lB@MOjQ?_fvH>!^d!(@ilp1 zQ4zlt4pt+4I%6U4rzNGZ9M63R7C7dvDv0tIUP`TVros_Z%%JH@oS~d5 ze*oqsaPlN+uOt}eh%bqPrA_|KeB87Ze!DvcZBfH+d5Z!tu(p&uk45V?GiR|!y5vT2BHCpHLiCELoC zGVSO(vBps}znCz-KpAk|UqWLnucst0Or$O<8N?hcfw1YG3lR;x$bs z&zCBy85WvWs*c~36qm`Bqbq>O@e*a2sQ2oYD~}lAF~MVo#}c{nh>G_1wUC6+fP%-6 zI4TK2Fv3V}wz|a$W(0Hp!`t&t8A5QFx-oTJNd2XKK(lG-iFfLWQL7nbQ71~Ff~7YqxP=KWZ7$ITZzM{VfUK78bvYrVGrX|5 z7(yP0!UP0$E@5@fUx0@86`Yz%+83Zvx(3+AL1-&P>K6S6b=V#fpy5lKr`@=0G-MGr z<-w3MXb-v|+K@*1P6buh8|6z*!}l$4HZz3^U0+x)d81gDxiwLy?>Jt{Z?)1(FR0ut zH&VHga5+XRH#zY`ENrl^-z9_0jLJ+(l{ti@3OjL0_t%zQO&`UmO#e{CHNSj(Q>%^gFPK96Yb0Eg+k$6WE{zmh7st9(p z(a7;H7ozk&e*zk~8IL}9+w~ij>)o~Xzo)$x6*jgAZSJtPbtH-BGgMPGhqqv(_KY?{ kC928txMiIAPv(2f*wkjKhRJH8X-oCd0g>vX$xj>p4f{g78~^|S diff --git a/generate_menu.py b/generate_menu.py index 34c9776..a55e725 100644 --- a/generate_menu.py +++ b/generate_menu.py @@ -58,7 +58,7 @@ def generate_crud_menu(args): print("请重启服务器以同步菜单到数据库") -def main(): +def menu_generator_main(): parser = argparse.ArgumentParser(description='菜单生成器') subparsers = parser.add_subparsers(dest='command', help='可用命令') @@ -81,4 +81,4 @@ def main(): if __name__ == "__main__": - main() + menu_generator_main() diff --git a/hertz_server_diango_ui/.env b/hertz_server_diango_ui/.env index 407ac53..1c234cc 100644 --- a/hertz_server_diango_ui/.env +++ b/hertz_server_diango_ui/.env @@ -1,5 +1,5 @@ # API 基础地址 -VITE_API_BASE_URL=http://127.0.0.1:8000 +VITE_API_BASE_URL=http://192.168.124.23:8000 # 应用配置 VITE_APP_TITLE=Hertz Admin diff --git a/hertz_server_diango_ui/.env.development b/hertz_server_diango_ui/.env.development index 6c9bbfd..638a3b8 100644 --- a/hertz_server_diango_ui/.env.development +++ b/hertz_server_diango_ui/.env.development @@ -1 +1,2 @@ -VITE_API_BASE_URL=http://127.0.0.1:8000 +VITE_API_BASE_URL=http://localhost:8000 +VITE_TEMPLATE_SETUP_MODE=true \ No newline at end of file diff --git a/hertz_server_diango_ui/.env.production b/hertz_server_diango_ui/.env.production index 6c9bbfd..7cb2aca 100644 --- a/hertz_server_diango_ui/.env.production +++ b/hertz_server_diango_ui/.env.production @@ -1 +1,2 @@ -VITE_API_BASE_URL=http://127.0.0.1:8000 +VITE_API_BASE_URL=http://192.168.124.40:8002 +VITE_TEMPLATE_SETUP_MODE=true \ No newline at end of file diff --git a/hertz_server_diango_ui/README.md b/hertz_server_diango_ui/README.md index 366c161..7a240ea 100644 --- a/hertz_server_diango_ui/README.md +++ b/hertz_server_diango_ui/README.md @@ -17,7 +17,7 @@ - **工程化完善**:TS 强类型、模块化 API、统一请求封装、权限化菜单/路由 - **设计统一**:全局“超现代风格”主题,卡片 / 弹窗 / 按钮 / 输入 / 分页风格一致 - **业务可复用**: - - 知识库管理:分类树 + 列表搜索 + 编辑/发布 + - 文章管理:分类树 + 列表搜索 + 编辑/发布 - YOLO 模型:模型管理、模型类别管理、告警处理中心、检测历史管理 - AI 助手:多会话列表 + 消息记录 + 多布局对话界面(含错误调试信息) - 认证体系:登录/注册、验证码 @@ -246,6 +246,72 @@ npm run dev - 修改 `src/styles/variables.scss` 中的主色、背景色、圆角、阴影 - 如需大改导航栏、卡片风格,优先在全局样式里做统一,而不是每页重新写 +## 🧩 模块选择与模板模式 + +- **模块配置文件** + - 路径:`src/config/hertz_modules.ts` + - 内容: + - 使用 `HERTZ_MODULES` 统一管理“管理端 / 用户端”各功能模块 + - 每个模块包含:`key`(模块标识)、`label`(展示名称)、`group`(admin/user)、`defaultEnabled`(是否默认启用) + - 运行时通过 `isModuleEnabled` / `getEnabledModuleKeys` 控制路由和菜单是否展示对应模块。 + +- **模块选择页面(功能 DIY)** + - 页面:`src/views/ModuleSetup.vue` + - 路由:`/template/modules` + - 说明: + 1. 勾选需要启用的模块,未勾选的模块在菜单和路由中隐藏(仅运行时屏蔽,不改动源码)。 + 2. 点击“保存配置并刷新”可多次预览效果;点击“保存并跳转登录”会在保存后跳转到登录页。 + 3. 选择结果会以 `hertz_enabled_modules` 的形式保存在浏览器 Local Storage 中。 + +- **模板模式开关** + - 通过环境变量控制:`VITE_TEMPLATE_SETUP_MODE` + - 建议在开发环境 (`.env.development`) 中开启: + + ```bash + VITE_TEMPLATE_SETUP_MODE=true + ``` + + - 当模板模式开启且浏览器中 **没有** `hertz_enabled_modules` 记录时,路由守卫会在首次进入时自动重定向到 `/template/modules`,强制先完成模块选择。 + - 如果已经配置过模块,下次 `npm run dev` 将直接进入系统。如需重新进入模块选择页: + 1. 打开浏览器开发者工具 → Application → Local Storage + 2. 选择当前站点,删除键 `hertz_enabled_modules` + 3. 刷新页面即可再次进入模块选择流程。 + +## ✂️ 一键裁剪(npm run prune) + +> 适用于已经确定“哪些功能模块不再需要”的场景,用于真正瘦身前端代码体积。建议在执行前先提交一次 Git。 + +- **脚本位置与命令** + - 脚本:`scripts/prune-modules.mjs` + - 命令: + + ```bash + npm run prune + ``` + +- **推荐使用流程** + 1. 启动开发环境:`npm run dev`。 + 2. 打开 `/template/modules`,通过勾选确认“需要保留的模块”,用“保存配置并刷新”反复调试菜单/路由效果。 + 3. 确认无误后,关闭开发服务器。 + 4. 在终端执行 `npm run prune`,按照 CLI 提示: + - 选择要“裁剪掉”的模块(通常是你在模块选择页面中未勾选的模块)。 + - 选择裁剪模式: + - **模式 1:仅屏蔽** + - 修改 `admin_menu.ts` / `user_menu_ai.ts` 中对应模块的 `moduleKey`,加上 `__pruned__` 前缀 + - 注释组件映射行,使这些模块在菜单和路由中完全隐藏 + - **不删除任何 `.vue` 文件**,方便后续恢复 + - **模式 2:删除** + - 在模式 1 的基础上,额外删除对应模块的视图文件,如 `src/views/admin_page/UserManagement.vue` 等 + - 这是不可逆操作,建议先在模式 1 下验证,再使用模式 2 做最终瘦身 + +- **影响范围(前端)** + - 管理端: + - `src/router/admin_menu.ts` 中对应模块的菜单配置和组件映射 + - `src/views/admin_page/*.vue` 中不需要的页面(仅在删除模式下移除) + - 用户端: + - `src/router/user_menu_ai.ts` 中对应模块配置 + - `src/views/user_pages/*.vue` 中不需要的页面(仅在删除模式下移除) + ## 📜 NPM 脚本 ```json @@ -253,7 +319,8 @@ npm run dev "scripts": { "dev": "vite", "build": "vue-tsc -b && vite build", - "preview": "vite preview" + "preview": "vite preview", + "prune": "node scripts/prune-modules.mjs" } } ``` diff --git a/hertz_server_diango_ui/components.d.ts b/hertz_server_diango_ui/components.d.ts index bedf380..8a3f626 100644 --- a/hertz_server_diango_ui/components.d.ts +++ b/hertz_server_diango_ui/components.d.ts @@ -16,6 +16,7 @@ declare module 'vue' { AButton: typeof import('ant-design-vue/es')['Button'] ACard: typeof import('ant-design-vue/es')['Card'] ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] ACol: typeof import('ant-design-vue/es')['Col'] ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] diff --git a/hertz_server_diango_ui/package-lock.json b/hertz_server_diango_ui/package-lock.json index 277b2fb..cd5d47b 100644 --- a/hertz_server_diango_ui/package-lock.json +++ b/hertz_server_diango_ui/package-lock.json @@ -25,12 +25,10 @@ "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", "autoprefixer": "^10.4.21", - "daisyui": "^5.1.13", "eslint": "^9.36.0", "eslint-plugin-vue": "^10.4.0", "postcss": "^8.5.6", "sass-embedded": "^1.93.0", - "tailwindcss": "^4.1.13", "typescript": "~5.8.3", "typescript-eslint": "^8.44.0", "unplugin-vue-components": "^29.1.0", @@ -2663,16 +2661,6 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, - "node_modules/daisyui": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.1.13.tgz", - "integrity": "sha512-KWPF/4R+EHTJRqKZFNmSDPfAZ5xeS6YWB/2kS7Y6wGKg+atscUi2DOp6HoDD/OgGML0PJTtTpgwpTfeHVfjk7w==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/saadeghi/daisyui?sponsor=1" - } - }, "node_modules/dayjs": { "version": "1.11.18", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", @@ -5141,13 +5129,6 @@ "node": ">=16.0.0" } }, - "node_modules/tailwindcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", - "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/hertz_server_diango_ui/package.json b/hertz_server_diango_ui/package.json index 2e4d7e1..a260e42 100644 --- a/hertz_server_diango_ui/package.json +++ b/hertz_server_diango_ui/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite", "build": "vue-tsc -b && vite build", - "preview": "vite preview" + "preview": "vite preview", + "prune": "node scripts/prune-modules.mjs" }, "dependencies": { "@types/node": "^24.5.2", diff --git a/hertz_server_diango_ui/scripts/prune-modules.mjs b/hertz_server_diango_ui/scripts/prune-modules.mjs new file mode 100644 index 0000000..ca50d94 --- /dev/null +++ b/hertz_server_diango_ui/scripts/prune-modules.mjs @@ -0,0 +1,392 @@ +#!/usr/bin/env node + +// 一键裁剪脚本:根据功能模块删除或屏蔽对应的菜单配置和页面文件 +// 设计原则: +// - 先通过运行时模块开关/页面确认要保留哪些模块 +// - 然后运行本脚本,选择要“裁剪掉”的模块,以及裁剪模式: +// 1) 仅屏蔽(修改 moduleKey,使其永远不会被启用,保留页面文件) +// 2) 删除(在 1 的基础上,再删除对应 .vue 页面文件) +// - 脚本只操作前端代码,不影响后端 + +import fs from 'fs' +import path from 'path' +import readline from 'readline' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const projectRoot = path.resolve(__dirname, '..') + +/** 模块定义(与 src/config/hertz_modules.ts 保持一致) */ +const MODULES = [ + { key: 'admin.user-management', label: '管理端 · 用户管理', group: 'admin' }, + { key: 'admin.department-management', label: '管理端 · 部门管理', group: 'admin' }, + { key: 'admin.menu-management', label: '管理端 · 菜单管理', group: 'admin' }, + { key: 'admin.role-management', label: '管理端 · 角色管理', group: 'admin' }, + { key: 'admin.notification-management', label: '管理端 · 通知管理', group: 'admin' }, + { key: 'admin.log-management', label: '管理端 · 日志管理', group: 'admin' }, + { key: 'admin.knowledge-base', label: '管理端 · 文章管理', group: 'admin' }, + { key: 'admin.yolo-model', label: '管理端 · YOLO 模型相关', group: 'admin' }, + + { key: 'user.system-monitor', label: '用户端 · 系统监控', group: 'user' }, + { key: 'user.ai-chat', label: '用户端 · AI 助手', group: 'user' }, + { key: 'user.yolo-detection', label: '用户端 · YOLO 检测', group: 'user' }, + { key: 'user.live-detection', label: '用户端 · 实时检测', group: 'user' }, + { key: 'user.detection-history', label: '用户端 · 检测历史', group: 'user' }, + { key: 'user.alert-center', label: '用户端 · 告警中心', group: 'user' }, + { key: 'user.notice-center', label: '用户端 · 通知中心', group: 'user' }, + { key: 'user.knowledge-center', label: '用户端 · 知识库中心', group: 'user' }, +] + +/** + * 每个模块对应的裁剪配置: + * - adminModuleKey / userModuleKey: 在路由配置文件中的 moduleKey 值 + * - adminComponentNames / userComponentNames: 在组件映射对象中的组件名(*.vue) + * - viewFiles: 可以安全删除的页面文件(相对项目根路径) + */ +const PRUNE_CONFIG = { + 'admin.user-management': { + adminModuleKey: 'admin.user-management', + adminComponentNames: ['UserManagement.vue'], + userModuleKey: null, + userComponentNames: [], + viewFiles: ['src/views/admin_page/UserManagement.vue'], + }, + 'admin.department-management': { + adminModuleKey: 'admin.department-management', + adminComponentNames: ['DepartmentManagement.vue'], + userModuleKey: null, + userComponentNames: [], + viewFiles: ['src/views/admin_page/DepartmentManagement.vue'], + }, + 'admin.menu-management': { + adminModuleKey: 'admin.menu-management', + adminComponentNames: ['MenuManagement.vue'], + userModuleKey: null, + userComponentNames: [], + viewFiles: ['src/views/admin_page/MenuManagement.vue'], + }, + 'admin.role-management': { + adminModuleKey: 'admin.role-management', + adminComponentNames: ['Role.vue'], + userModuleKey: null, + userComponentNames: [], + viewFiles: ['src/views/admin_page/Role.vue'], + }, + 'admin.notification-management': { + adminModuleKey: 'admin.notification-management', + adminComponentNames: ['NotificationManagement.vue'], + userModuleKey: null, + userComponentNames: [], + viewFiles: ['src/views/admin_page/NotificationManagement.vue'], + }, + 'admin.log-management': { + adminModuleKey: 'admin.log-management', + adminComponentNames: ['LogManagement.vue'], + userModuleKey: null, + userComponentNames: [], + viewFiles: ['src/views/admin_page/LogManagement.vue'], + }, + 'admin.knowledge-base': { + adminModuleKey: 'admin.knowledge-base', + adminComponentNames: ['KnowledgeBaseManagement.vue'], + userModuleKey: null, + userComponentNames: [], + viewFiles: ['src/views/admin_page/KnowledgeBaseManagement.vue'], + }, + 'admin.yolo-model': { + adminModuleKey: 'admin.yolo-model', + adminComponentNames: [ + 'ModelManagement.vue', + 'AlertLevelManagement.vue', + 'AlertProcessingCenter.vue', + 'DetectionHistoryManagement.vue', + ], + userModuleKey: null, + userComponentNames: [], + viewFiles: [ + 'src/views/admin_page/ModelManagement.vue', + 'src/views/admin_page/AlertLevelManagement.vue', + 'src/views/admin_page/AlertProcessingCenter.vue', + 'src/views/admin_page/DetectionHistoryManagement.vue', + ], + }, + 'user.system-monitor': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.system-monitor', + userComponentNames: ['SystemMonitor.vue'], + viewFiles: ['src/views/user_pages/SystemMonitor.vue'], + }, + 'user.ai-chat': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.ai-chat', + userComponentNames: ['AiChat.vue'], + viewFiles: ['src/views/user_pages/AiChat.vue'], + }, + 'user.yolo-detection': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.yolo-detection', + userComponentNames: ['YoloDetection.vue'], + viewFiles: ['src/views/user_pages/YoloDetection.vue'], + }, + 'user.live-detection': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.live-detection', + userComponentNames: ['LiveDetection.vue'], + viewFiles: ['src/views/user_pages/LiveDetection.vue'], + }, + 'user.detection-history': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.detection-history', + userComponentNames: ['DetectionHistory.vue'], + viewFiles: ['src/views/user_pages/DetectionHistory.vue'], + }, + 'user.alert-center': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.alert-center', + userComponentNames: ['AlertCenter.vue'], + viewFiles: ['src/views/user_pages/AlertCenter.vue'], + }, + 'user.notice-center': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.notice-center', + userComponentNames: ['NoticeCenter.vue'], + viewFiles: ['src/views/user_pages/NoticeCenter.vue'], + }, + 'user.knowledge-center': { + adminModuleKey: null, + adminComponentNames: [], + userModuleKey: 'user.knowledge-center', + userComponentNames: ['KnowledgeCenter.vue'], + // 注意:这里只删除 KnowledgeCenter.vue,保留 KnowledgeDetail.vue,避免复杂路由修改 + viewFiles: ['src/views/user_pages/KnowledgeCenter.vue'], + }, +} + +const ADMIN_MENU_FILE = 'src/router/admin_menu.ts' +const USER_MENU_FILE = 'src/router/user_menu_ai.ts' + +/** + * 简单的 CLI 交互封装 + */ +const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) + +function ask(question) { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()) + }) + }) +} + +function resolvePath(relativePath) { + return path.resolve(projectRoot, relativePath) +} + +function readTextFile(relativePath) { + const full = resolvePath(relativePath) + if (!fs.existsSync(full)) { + return null + } + return fs.readFileSync(full, 'utf8') +} + +function writeTextFile(relativePath, content) { + const full = resolvePath(relativePath) + fs.writeFileSync(full, content, 'utf8') +} + +function commentComponentLines(content, componentNames) { + if (!componentNames || componentNames.length === 0) return content + const lines = content.split('\n') + const nameSet = new Set(componentNames) + + const updated = lines.map((line) => { + const trimmed = line.trim() + if (trimmed.startsWith('//')) return line + + for (const name of nameSet) { + if (line.includes(`'${name}'`)) { + return `// ${line}` + } + } + return line + }) + + return updated.join('\n') +} + +function updateModuleKey(content, originalKey) { + if (!originalKey) return content + const patterns = [ + `moduleKey: '${originalKey}'`, + `moduleKey: "${originalKey}"`, + ] + const pruned = `moduleKey: '__pruned__${originalKey}'` + + if (content.includes(pruned)) { + return content + } + + let updated = content + let found = false + + for (const p of patterns) { + if (updated.includes(p)) { + updated = updated.replace(p, pruned) + found = true + } + } + + if (!found) { + console.warn(`⚠️ 未在文件中找到 moduleKey: ${originalKey}`) + } + + return updated +} + +function collectViewFilesForModules(selectedKeys) { + const files = new Set() + for (const key of selectedKeys) { + const cfg = PRUNE_CONFIG[key] + if (!cfg) continue + for (const f of cfg.viewFiles) { + files.add(f) + } + } + return Array.from(files) +} + +async function main() { + console.log('===== Hertz 模板 · 一键裁剪脚本 =====') + console.log('说明:') + console.log('1. 建议先在浏览器里通过“模板模式 + 模块选择页”确认要保留的模块') + console.log('2. 然后关闭 dev 服务器,运行本脚本选择要裁剪掉的模块') + console.log('3. 先可选择“仅屏蔽”,确认无误后,再选择“删除”彻底缩减代码体积') + console.log('') + + console.log('当前可裁剪模块:') + MODULES.forEach((m, index) => { + console.log(`${index + 1}. [${m.group}] ${m.label} (${m.key})`) + }) + console.log('') + + const indexAnswer = await ask('请输入要“裁剪掉”的模块序号(多个用逗号分隔,例如 2,4,7),或直接回车取消:') + if (!indexAnswer) { + console.log('未选择任何模块,退出。') + rl.close() + return + } + + const indexes = indexAnswer + .split(',') + .map((s) => parseInt(s.trim(), 10)) + .filter((n) => !Number.isNaN(n) && n >= 1 && n <= MODULES.length) + + if (indexes.length === 0) { + console.log('未解析出有效的序号,退出。') + rl.close() + return + } + + const selectedModules = Array.from(new Set(indexes.map((i) => MODULES[i - 1]))) + console.log('\n将要裁剪的模块:') + selectedModules.forEach((m) => { + console.log(`- [${m.group}] ${m.label} (${m.key})`) + }) + + console.log('\n裁剪模式:') + console.log('1) 仅屏蔽模块:') + console.log(' - 修改 router 配置中的 moduleKey 为 __pruned__...') + console.log(' - 生成的菜单和路由中将完全隐藏这些模块') + console.log(' - 不删除任何 .vue 页面文件(可随时恢复)') + console.log('2) 删除模块:') + console.log(' - 在 1 的基础上,额外删除对应的 .vue 页面文件') + console.log(' - 删除操作不可逆,请确保已经提交或备份代码\n') + + const modeAnswer = await ask('请选择裁剪模式(1 = 仅屏蔽,2 = 删除):') + const mode = modeAnswer === '2' ? 'delete' : 'comment' + + const viewFiles = collectViewFilesForModules(selectedModules.map((m) => m.key)) + + console.log('\n即将进行如下修改:') + console.log('- 修改文件: src/router/admin_menu.ts(按需)') + console.log('- 修改文件: src/router/user_menu_ai.ts(按需)') + if (mode === 'delete') { + console.log('- 删除页面文件:') + viewFiles.forEach((f) => console.log(` · ${f}`)) + } else { + console.log('- 不删除任何页面文件,仅屏蔽模块') + } + + const confirm = await ask('\n确认执行这些修改吗?(y/N): ') + if (confirm.toLowerCase() !== 'y') { + console.log('已取消操作。') + rl.close() + return + } + + // 1) 修改 admin_menu.ts + let adminMenuContent = readTextFile(ADMIN_MENU_FILE) + if (adminMenuContent) { + const adminKeys = selectedModules.map((m) => m.key).filter((k) => PRUNE_CONFIG[k]?.adminModuleKey) + if (adminKeys.length > 0) { + for (const key of adminKeys) { + const cfg = PRUNE_CONFIG[key] + adminMenuContent = updateModuleKey(adminMenuContent, cfg.adminModuleKey) + adminMenuContent = commentComponentLines(adminMenuContent, cfg.adminComponentNames) + } + writeTextFile(ADMIN_MENU_FILE, adminMenuContent) + console.log('✅ 已更新 src/router/admin_menu.ts') + } + } + + // 2) 修改 user_menu_ai.ts + let userMenuContent = readTextFile(USER_MENU_FILE) + if (userMenuContent) { + const userKeys = selectedModules.map((m) => m.key).filter((k) => PRUNE_CONFIG[k]?.userModuleKey) + if (userKeys.length > 0) { + for (const key of userKeys) { + const cfg = PRUNE_CONFIG[key] + userMenuContent = updateModuleKey(userMenuContent, cfg.userModuleKey) + userMenuContent = commentComponentLines(userMenuContent, cfg.userComponentNames) + } + writeTextFile(USER_MENU_FILE, userMenuContent) + console.log('✅ 已更新 src/router/user_menu_ai.ts') + } + } + + // 3) 删除 .vue 页面文件(仅在 delete 模式下) + if (mode === 'delete') { + console.log('\n开始删除页面文件...') + for (const relative of viewFiles) { + const full = resolvePath(relative) + if (fs.existsSync(full)) { + fs.rmSync(full) + console.log(`🗑️ 已删除: ${relative}`) + } else { + console.log(`⚠️ 文件不存在,跳过: ${relative}`) + } + } + } + + console.log('\n🎉 裁剪完成。建议执行以下操作检查:') + console.log('- 重新运行: npm run dev') + console.log('- 在浏览器中确认菜单和路由是否符合预期') + console.log('- 如需恢复,请使用 Git 回退或重新拷贝模板') + + rl.close() +} + +main().catch((err) => { + console.error('执行过程中发生错误:', err) + rl.close() + process.exit(1) +}) diff --git a/hertz_server_diango_ui/src/api/index.ts b/hertz_server_diango_ui/src/api/index.ts index 5190e44..7909944 100644 --- a/hertz_server_diango_ui/src/api/index.ts +++ b/hertz_server_diango_ui/src/api/index.ts @@ -14,3 +14,4 @@ export * from './ai' // export * from './admin' export * from './log' export * from './knowledge' +export * from './kb' diff --git a/hertz_server_diango_ui/src/api/kb.ts b/hertz_server_diango_ui/src/api/kb.ts new file mode 100644 index 0000000..ee1b721 --- /dev/null +++ b/hertz_server_diango_ui/src/api/kb.ts @@ -0,0 +1,131 @@ +import { request } from '@/utils/hertz_request' + +// 通用响应结构(与后端 HertzResponse 对齐) +export interface KbApiResponse { + success: boolean + code: number + message: string + data: T +} + +// 知识库条目 +export interface KbItem { + id: number + title: string + modality: 'text' | 'code' | 'image' | 'audio' | 'video' | string + source_type: 'text' | 'file' | 'url' | string + chunk_count?: number + created_at?: string + updated_at?: string + created_chunk_count?: number + // 允许后端扩展字段 + [key: string]: any +} + +export interface KbItemListParams { + query?: string + page?: number + page_size?: number +} + +export interface KbItemListData { + total: number + page: number + page_size: number + list: KbItem[] +} + +// 语义搜索 +export interface KbSearchParams { + q: string + k?: number +} + +// 问答(RAG) +export interface KbQaPayload { + question: string + k?: number +} + +export interface KbQaData { + answer: string + [key: string]: any +} + +// 图谱查询参数(实体 / 关系) +export interface KbGraphListParams { + query?: string + page?: number + page_size?: number + // 关系检索可选参数 + source?: number + target?: number + relation_type?: string +} + +export const kbApi = { + // 知识库条目:列表 + listItems(params?: KbItemListParams): Promise> { + return request.get('/api/kb/items/list/', { params }) + }, + + // 语义搜索 + search(params: KbSearchParams): Promise> { + return request.get('/api/kb/search/', { params }) + }, + + // 问答(RAG) + qa(payload: KbQaPayload): Promise> { + return request.post('/api/kb/qa/', payload) + }, + + // 图谱:实体列表 + listEntities(params?: KbGraphListParams): Promise> { + return request.get('/api/kb/graph/entities/', { params }) + }, + + // 图谱:关系列表 + listRelations(params?: KbGraphListParams): Promise> { + return request.get('/api/kb/graph/relations/', { params }) + }, + + // 知识库条目:创建(JSON 文本) + createItemJson(payload: { title: string; modality?: string; source_type?: string; content?: string; metadata?: any }): Promise> { + return request.post('/api/kb/items/create/', payload) + }, + + // 知识库条目:创建(文件上传) + createItemFile(formData: FormData): Promise> { + return request.post('/api/kb/items/create/', formData) + }, + + // 图谱:创建实体 + createEntity(payload: { name: string; type: string; properties?: any }): Promise> { + return request.post('/api/kb/graph/entities/', payload) + }, + + // 图谱:更新实体 + updateEntity(id: number, payload: { name?: string; type?: string; properties?: any }): Promise> { + return request.put(`/api/kb/graph/entities/${id}/`, payload) + }, + + // 图谱:删除实体 + deleteEntity(id: number): Promise> { + return request.delete(`/api/kb/graph/entities/${id}/`) + }, + + // 图谱:创建关系 + createRelation(payload: { source: number; target: number; relation_type: string; properties?: any; source_chunk?: number }): Promise> { + return request.post('/api/kb/graph/relations/', payload) + }, + + // 图谱:删除关系 + deleteRelation(id: number): Promise> { + return request.delete(`/api/kb/graph/relations/${id}/`) + }, + + // 图谱:自动抽取实体与关系 + extractGraph(payload: { text?: string; item_id?: number }): Promise> { + return request.post('/api/kb/graph/extract/', payload) + }, +} diff --git a/hertz_server_diango_ui/src/api/user.ts b/hertz_server_diango_ui/src/api/user.ts index 8d0c178..9ddf0f2 100644 --- a/hertz_server_diango_ui/src/api/user.ts +++ b/hertz_server_diango_ui/src/api/user.ts @@ -103,6 +103,12 @@ export const userApi = { return request.put('/api/auth/user/info/update/', data) }, + uploadAvatar: (file: File): Promise> => { + const formData = new FormData() + formData.append('avatar', file) + return request.upload('/api/auth/user/avatar/upload/', formData) + }, + // 分配用户角色 assignRoles: (data: AssignRolesParams): Promise> => { return request.post('/api/users/assign-roles/', data) diff --git a/hertz_server_diango_ui/src/api/yolo.ts b/hertz_server_diango_ui/src/api/yolo.ts index 3cde52b..d74139e 100644 --- a/hertz_server_diango_ui/src/api/yolo.ts +++ b/hertz_server_diango_ui/src/api/yolo.ts @@ -67,6 +67,114 @@ export interface YoloModelListResponse { } } +// 数据集管理相关类型 +export interface YoloDatasetSummary { + id: number + name: string + version?: string + root_folder_path: string + data_yaml_path: string + nc?: number + description?: string + created_at?: string +} + +export interface YoloDatasetDetail extends YoloDatasetSummary { + names?: string[] + train_images_count?: number + train_labels_count?: number + val_images_count?: number + val_labels_count?: number + test_images_count?: number + test_labels_count?: number +} + +export interface YoloDatasetSampleItem { + image: string + image_size?: number + label?: string + filename: string +} + +// YOLO 训练任务相关类型 +export type YoloTrainStatus = + | 'queued' + | 'running' + | 'canceling' + | 'completed' + | 'failed' + | 'canceled' + +export interface YoloTrainDatasetOption { + id: number + name: string + version?: string + yaml: string +} + +export interface YoloTrainVersionOption { + family: 'v8' | '11' | '12' + config_path: string + sizes: string[] +} + +export interface YoloTrainOptionsResponse { + success: boolean + code?: number + message?: string + data?: { + datasets: YoloTrainDatasetOption[] + versions: YoloTrainVersionOption[] + } +} + +export interface YoloTrainingJob { + id: number + dataset: number + dataset_name: string + model_family: 'v8' | '11' | '12' + model_size?: 'n' | 's' | 'm' | 'l' | 'x' + weight_path?: string + config_path?: string + status: YoloTrainStatus + logs_path?: string + runs_path?: string + best_model_path?: string + last_model_path?: string + progress: number + epochs: number + imgsz: number + batch: number + device: string + optimizer: 'SGD' | 'Adam' | 'AdamW' | 'RMSProp' + error_message?: string + created_at: string + started_at?: string | null + finished_at?: string | null +} + +export interface StartTrainingPayload { + dataset_id: number + model_family: 'v8' | '11' | '12' + model_size?: 'n' | 's' | 'm' | 'l' | 'x' + epochs?: number + imgsz?: number + batch?: number + device?: string + optimizer?: 'SGD' | 'Adam' | 'AdamW' | 'RMSProp' +} + +export interface YoloTrainLogsResponse { + success: boolean + code?: number + message?: string + data?: { + content: string + next_offset: number + finished: boolean + } +} + // YOLO检测API export const yoloApi = { // 执行YOLO检测 @@ -111,7 +219,8 @@ export const yoloApi = { // 获取当前启用的YOLO模型信息 async getCurrentEnabledModel(): Promise<{ success: boolean; data?: YoloModel; message?: string }> { - return request.get('/api/yolo/models/enabled/') + // 关闭全局错误提示,由调用方(如 YOLO 检测页面)自行处理“未启用模型”等业务文案 + return request.get('/api/yolo/models/enabled/', { showError: false }) }, // 获取模型详情 @@ -196,6 +305,112 @@ export const yoloApi = { return request.get('/api/yolo/stats/') }, + // 数据集管理相关接口 + // 上传数据集 + async uploadDataset(formData: FormData): Promise<{ success: boolean; data?: YoloDatasetDetail; message?: string }> { + return request.upload('/api/yolo/datasets/upload/', formData) + }, + + // 获取数据集列表 + async getDatasets(): Promise<{ success: boolean; data?: YoloDatasetSummary[]; message?: string }> { + return request.get('/api/yolo/datasets/') + }, + + // 获取数据集详情 + async getDatasetDetail(datasetId: number): Promise<{ success: boolean; data?: YoloDatasetDetail; message?: string }> { + return request.get(`/api/yolo/datasets/${datasetId}/`) + }, + + // 删除数据集 + async deleteDataset(datasetId: number): Promise<{ success: boolean; message?: string }> { + return request.post(`/api/yolo/datasets/${datasetId}/delete/`) + }, + + // 获取数据集样本 + async getDatasetSamples( + datasetId: number, + params: { split?: 'train' | 'val' | 'test'; limit?: number; offset?: number } = {} + ): Promise<{ + success: boolean + data?: { items: YoloDatasetSampleItem[]; total: number } + message?: string + }> { + return request.get(`/api/yolo/datasets/${datasetId}/samples/`, { params }) + }, + + // YOLO 训练任务相关接口 + // 获取训练选项(可用数据集与模型版本) + async getTrainOptions(): Promise { + return request.get('/api/yolo/train/options/') + }, + + // 获取训练任务列表 + async getTrainJobs(): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob[] + }> { + return request.get('/api/yolo/train/jobs/') + }, + + // 创建并启动训练任务 + async startTrainJob(payload: StartTrainingPayload): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob + }> { + return request.post('/api/yolo/train/jobs/start/', payload) + }, + + // 获取训练任务详情 + async getTrainJobDetail(id: number): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob + }> { + return request.get(`/api/yolo/train/jobs/${id}/`) + }, + + // 获取训练任务日志(分页读取) + async getTrainJobLogs( + id: number, + params: { offset?: number; max?: number } = {} + ): Promise { + return request.get(`/api/yolo/train/jobs/${id}/logs/`, { params }) + }, + + // 取消训练任务 + async cancelTrainJob(id: number): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob + }> { + return request.post(`/api/yolo/train/jobs/${id}/cancel/`) + }, + + // 下载训练结果(ZIP) + async downloadTrainJobResult(id: number): Promise<{ + success: boolean + code?: number + message?: string + data?: { url: string; size: number } + }> { + return request.get(`/api/yolo/train/jobs/${id}/download/`) + }, + + // 删除训练任务 + async deleteTrainJob(id: number): Promise<{ + success: boolean + code?: number + message?: string + }> { + return request.post(`/api/yolo/train/jobs/${id}/delete/`) + }, + // 警告等级管理相关接口 // 获取警告等级列表 async getAlertLevels(): Promise<{ success: boolean; data?: AlertLevel[]; message?: string }> { diff --git a/hertz_server_diango_ui/src/assets/vue.svg b/hertz_server_diango_ui/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/hertz_server_diango_ui/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/hertz_server_diango_ui/src/components/HelloWorld.vue b/hertz_server_diango_ui/src/components/HelloWorld.vue deleted file mode 100644 index fca8a68..0000000 --- a/hertz_server_diango_ui/src/components/HelloWorld.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - - - \ No newline at end of file diff --git a/hertz_server_diango_ui/src/config/hertz_modules.ts b/hertz_server_diango_ui/src/config/hertz_modules.ts new file mode 100644 index 0000000..1d1ed92 --- /dev/null +++ b/hertz_server_diango_ui/src/config/hertz_modules.ts @@ -0,0 +1,85 @@ +export type HertzModuleGroup = 'admin' | 'user' + +export interface HertzModule { + key: string + label: string + group: HertzModuleGroup + description?: string + defaultEnabled: boolean +} + +export const HERTZ_MODULES: HertzModule[] = [ + { key: 'admin.user-management', label: '管理端 · 用户管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.department-management', label: '管理端 · 部门管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.menu-management', label: '管理端 · 菜单管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.role-management', label: '管理端 · 角色管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.notification-management', label: '管理端 · 通知管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.log-management', label: '管理端 · 日志管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.knowledge-base', label: '管理端 · 文章管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.yolo-model', label: '管理端 · YOLO 模型相关', group: 'admin', defaultEnabled: true }, + + { key: 'user.system-monitor', label: '用户端 · 系统监控', group: 'user', defaultEnabled: true }, + { key: 'user.ai-chat', label: '用户端 · AI 助手', group: 'user', defaultEnabled: true }, + { key: 'user.yolo-detection', label: '用户端 · YOLO 检测', group: 'user', defaultEnabled: true }, + { key: 'user.live-detection', label: '用户端 · 实时检测', group: 'user', defaultEnabled: true }, + { key: 'user.detection-history', label: '用户端 · 检测历史', group: 'user', defaultEnabled: true }, + { key: 'user.alert-center', label: '用户端 · 告警中心', group: 'user', defaultEnabled: true }, + { key: 'user.notice-center', label: '用户端 · 通知中心', group: 'user', defaultEnabled: true }, + { key: 'user.knowledge-center', label: '用户端 · 文章中心', group: 'user', defaultEnabled: true }, + { key: 'user.kb-center', label: '用户端 · 知识库中心', group: 'user', defaultEnabled: true }, +] + +const LOCAL_STORAGE_KEY = 'hertz_enabled_modules' + +export function getEnabledModuleKeys(): string[] { + const fallback = HERTZ_MODULES.filter(m => m.defaultEnabled).map(m => m.key) + + if (typeof window === 'undefined') { + return fallback + } + + try { + const stored = window.localStorage.getItem(LOCAL_STORAGE_KEY) + if (!stored) return fallback + const parsed = JSON.parse(stored) + if (Array.isArray(parsed)) { + const valid = parsed.filter((k): k is string => typeof k === 'string') + // 自动合并新增的默认启用模块,避免新模块在已有选择下被永久隐藏 + const missingDefaults = HERTZ_MODULES + .filter(m => m.defaultEnabled && !valid.includes(m.key)) + .map(m => m.key) + return [...valid, ...missingDefaults] + } + return fallback + } catch { + return fallback + } +} + +export function setEnabledModuleKeys(keys: string[]): void { + if (typeof window === 'undefined') return + try { + window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(keys)) + } catch { + // ignore + } +} + +export function isModuleEnabled(moduleKey?: string, enabledKeys?: string[]): boolean { + if (!moduleKey) return true + const keys = enabledKeys ?? getEnabledModuleKeys() + return keys.indexOf(moduleKey) !== -1 +} + +export function getModulesByGroup(group: HertzModuleGroup): HertzModule[] { + return HERTZ_MODULES.filter(m => m.group === group) +} + +export function hasModuleSelection(): boolean { + if (typeof window === 'undefined') return false + try { + return window.localStorage.getItem(LOCAL_STORAGE_KEY) !== null + } catch { + return false + } +} diff --git a/hertz_server_diango_ui/src/outer_src/api/ai.ts b/hertz_server_diango_ui/src/outer_src/api/ai.ts deleted file mode 100644 index b6faa29..0000000 --- a/hertz_server_diango_ui/src/outer_src/api/ai.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { request } from '@/utils/hertz_request' - -// 通用响应类型 -export interface ApiResponse { - success: boolean - code: number - message: string - data: T -} - -// 会话与消息类型 -export interface AIChatItem { - id: number - title: string - created_at: string - updated_at: string - latest_message?: string -} - -export interface AIChatDetail { - id: number - title: string - created_at: string - updated_at: string -} - -export interface AIChatMessage { - id: number - role: 'user' | 'assistant' | 'system' - content: string - created_at: string -} - -export interface ChatListData { - total: number - page: number - page_size: number - chats: AIChatItem[] -} - -export interface ChatDetailData { - chat: AIChatDetail - messages: AIChatMessage[] -} - -export interface SendMessageData { - user_message: AIChatMessage - ai_message: AIChatMessage -} - -export const aiApi = { - listChats: (params?: { query?: string; page?: number; page_size?: number }): Promise> => - request.get('/api/ai/chats/', { params, showError: false }), - - createChat: (body?: { title?: string }): Promise> => - request.post('/api/ai/chats/create/', body || { title: '新对话' }), - - getChatDetail: (chatId: number): Promise> => - request.get(`/api/ai/chats/${chatId}/`), - - updateChat: (chatId: number, body: { title: string }): Promise> => - request.put(`/api/ai/chats/${chatId}/update/`, body), - - deleteChats: (chatIds: number[]): Promise> => - request.post('/api/ai/chats/delete/', { chat_ids: chatIds }), - - sendMessage: (chatId: number, body: { content: string }): Promise> => - request.post(`/api/ai/chats/${chatId}/send/`, body), -} \ No newline at end of file diff --git a/hertz_server_diango_ui/src/outer_src/router/user_menu_ai.ts b/hertz_server_diango_ui/src/outer_src/router/user_menu_ai.ts deleted file mode 100644 index be27394..0000000 --- a/hertz_server_diango_ui/src/outer_src/router/user_menu_ai.ts +++ /dev/null @@ -1,231 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router' -import { defineAsyncComponent } from 'vue' - -// 统一的菜单项配置接口 -export interface UserMenuConfig { - key: string - label: string - icon?: string - path: string - component: string // 组件路径,相对于 @/views/user_pages/ - children?: UserMenuConfig[] - disabled?: boolean - meta?: { - title?: string - requiresAuth?: boolean - roles?: string[] - [key: string]: any - } -} - -// 菜单项接口定义(用于前端显示) -export interface MenuItem { - key: string - label: string - icon?: string - path?: string - children?: MenuItem[] - disabled?: boolean -} - -// 统一配置 - 同时用于菜单和路由 -export const userMenuConfigs: UserMenuConfig[] = [ - { - key: 'dashboard', - label: '首页', - icon: 'DashboardOutlined', - path: '/dashboard', - component: 'index.vue', - meta: { title: '用户首页', requiresAuth: true } - }, - { - key: 'profile', - label: '个人信息', - icon: 'UserOutlined', - path: '/user/profile', - component: 'Profile.vue', - meta: { title: '个人信息', requiresAuth: true } - }, - { - key: 'documents', - label: '文档管理', - icon: 'FileTextOutlined', - path: '/user/documents', - component: 'Documents.vue', - meta: { title: '文档管理', requiresAuth: true } - }, - { - key: 'messages', - label: '消息中心', - icon: 'MessageOutlined', - path: '/user/messages', - component: 'Messages.vue', - meta: { title: '消息中心', requiresAuth: true } - }, - { - key: 'system-monitor', - label: '系统监控', - icon: 'DashboardOutlined', - path: '/user/system-monitor', - component: 'SystemMonitor.vue', - meta: { title: '系统监控', requiresAuth: true } - }, - { - key: 'ai-chat', - label: 'AI助手', - icon: 'MessageOutlined', - path: '/user/ai-chat', - component: 'AiChat.vue', - meta: { title: 'AI助手', requiresAuth: true } - }, -] - -// 显式组件映射 - 避免Vite动态导入限制 -const explicitComponentMap: Record = { - 'index.vue': defineAsyncComponent(() => import('@/views/user_pages/index.vue')), - 'Profile.vue': defineAsyncComponent(() => import('@/views/user_pages/Profile.vue')), - 'Documents.vue': defineAsyncComponent(() => import('@/views/user_pages/Documents.vue')), - 'Messages.vue': defineAsyncComponent(() => import('@/views/user_pages/Messages.vue')), - 'SystemMonitor.vue': defineAsyncComponent(() => import('@/views/user_pages/SystemMonitor.vue')), - 'AiChat.vue': defineAsyncComponent(() => import('@/views/user_pages/AiChat.vue')), -} - -// 自动生成菜单项(用于前端显示) -export const userMenuItems: MenuItem[] = userMenuConfigs.map(config => ({ - key: config.key, - label: config.label, - icon: config.icon, - path: config.path, - disabled: config.disabled, - children: config.children?.map(child => ({ - key: child.key, - label: child.label, - icon: child.icon, - path: child.path, - disabled: child.disabled - })) -})) - -// 组件映射表 - 用于解决Vite动态导入限制 -const componentMap: Record Promise> = { - 'index.vue': () => import('@/views/user_pages/index.vue'), - 'Profile.vue': () => import('@/views/user_pages/Profile.vue'), - 'Documents.vue': () => import('@/views/user_pages/Documents.vue'), - 'Messages.vue': () => import('@/views/user_pages/Messages.vue'), - 'SystemMonitor.vue': () => import('@/views/user_pages/SystemMonitor.vue'), - 'AiChat.vue': () => import('@/views/user_pages/AiChat.vue'), -} - -// 自动生成路由配置 -export const userRoutes: RouteRecordRaw[] = userMenuConfigs.map(config => { - const route: RouteRecordRaw = { - path: config.path, - name: `User${config.key.charAt(0).toUpperCase() + config.key.slice(1)}`, - component: componentMap[config.component] || (() => import('@/views/NotFound.vue')), - meta: { - title: config.meta?.title || config.label, - requiresAuth: config.meta?.requiresAuth ?? true, - ...config.meta - } - } - - if (config.children && config.children.length > 0) { - route.children = config.children.map(child => ({ - path: child.path, - name: `User${child.key.charAt(0).toUpperCase() + child.key.slice(1)}`, - component: componentMap[child.component] || (() => import('@/views/NotFound.vue')), - meta: { - title: child.meta?.title || child.label, - requiresAuth: child.meta?.requiresAuth ?? true, - ...child.meta - } - })) - } - - return route -}) - -// 根据菜单项生成路由路径 -export function getMenuPath(menuKey: string): string { - const findPath = (items: MenuItem[], key: string): string | null => { - for (const item of items) { - if (item.key === key && item.path) return item.path - if (item.children) { - const childPath = findPath(item.children, key) - if (childPath) return childPath - } - } - return null - } - return findPath(userMenuItems, menuKey) || '/dashboard' -} - -// 获取菜单的面包屑路径 -export function getMenuBreadcrumb(menuKey: string): string[] { - const findBreadcrumb = (items: MenuItem[], key: string, path: string[] = []): string[] | null => { - for (const item of items) { - const currentPath = [...path, item.label] - if (item.key === menuKey) return currentPath - if (item.children) { - const childPath = findBreadcrumb(item.children, key, currentPath) - if (childPath) return childPath - } - } - return null - } - return findBreadcrumb(userMenuItems, menuKey) || ['仪表盘'] -} - -// 自动生成组件映射(基于配置和显式映射) -export const generateComponentMap = () => { - const map: Record = {} - const processConfigs = (configs: UserMenuConfig[]) => { - configs.forEach(config => { - if (explicitComponentMap[config.component]) { - map[config.key] = explicitComponentMap[config.component] - } else { - map[config.key] = defineAsyncComponent(() => import('@/views/NotFound.vue')) - } - if (config.children) processConfigs(config.children) - }) - } - processConfigs(userMenuConfigs) - return map -} - -// 导出自动生成的组件映射 -export const userComponentMap = generateComponentMap() - -// 根据用户权限过滤菜单项 -export const getFilteredUserMenuItems = (userRoles: string[], userPermissions: string[]): MenuItem[] => { - return userMenuConfigs - .filter(config => { - if (!config.meta?.roles || config.meta.roles.length === 0) return true - return config.meta.roles.some(requiredRole => userRoles.includes(requiredRole)) - }) - .map(config => ({ - key: config.key, - label: config.label, - icon: config.icon, - path: config.path, - disabled: config.disabled, - children: config.children?.filter(child => { - if (!child.meta?.roles || child.meta.roles.length === 0) return true - return child.meta.roles.some(requiredRole => userRoles.includes(requiredRole)) - }).map(child => ({ - key: child.key, - label: child.label, - icon: child.icon, - path: child.path, - disabled: child.disabled - })) - })) -} - -// 检查用户是否有访问特定菜单的权限 -export const hasUserMenuPermission = (menuKey: string, userRoles: string[]): boolean => { - const menuConfig = userMenuConfigs.find(config => config.key === menuKey) - if (!menuConfig) return false - if (!menuConfig.meta?.roles || menuConfig.meta.roles.length === 0) return true - return menuConfig.meta.roles.some(requiredRole => userRoles.includes(requiredRole)) -} \ No newline at end of file diff --git a/hertz_server_diango_ui/src/outer_src/views/user_pages/AiChat.vue b/hertz_server_diango_ui/src/outer_src/views/user_pages/AiChat.vue deleted file mode 100644 index 6ef6299..0000000 --- a/hertz_server_diango_ui/src/outer_src/views/user_pages/AiChat.vue +++ /dev/null @@ -1,261 +0,0 @@ - - - - - \ No newline at end of file diff --git a/hertz_server_diango_ui/src/public/img/logo-蓝.png b/hertz_server_diango_ui/src/public/img/logo-蓝.png deleted file mode 100644 index 2040f6c77f79ae7d5ffbae96b792238c58f464ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12610 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 diff --git a/hertz_server_diango_ui/src/router/admin_menu.ts b/hertz_server_diango_ui/src/router/admin_menu.ts index cca8462..e9235aa 100644 --- a/hertz_server_diango_ui/src/router/admin_menu.ts +++ b/hertz_server_diango_ui/src/router/admin_menu.ts @@ -1,4 +1,5 @@ import type { RouteRecordRaw } from "vue-router"; +import { getEnabledModuleKeys, isModuleEnabled } from "@/config/hertz_modules"; // 角色权限枚举 export enum UserRole { @@ -19,6 +20,7 @@ export interface AdminMenuItem { roles?: UserRole[]; // 允许访问的角色,不设置则使用默认管理员角色 permission?: string; // 所需权限标识符 children?: AdminMenuItem[]; // 子菜单 + moduleKey?: string; } // 🎯 统一配置中心 - 只需要在这里修改菜单配置 @@ -38,6 +40,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/user-management", component: "UserManagement.vue", permission: "system:user:list", // 需要用户列表权限 + moduleKey: "admin.user-management", }, { key: "department-management", @@ -45,7 +48,8 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ icon: "SettingOutlined", path: "/admin/department-management", component: "DepartmentManagement.vue", - permission: "system:dept:list", // 需要部门列表权限 + permission: "system:dept:list", // 需要部门列表权限 + moduleKey: "admin.department-management", }, { key: "menu-management", @@ -54,6 +58,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/menu-management", component: "MenuManagement.vue", permission: "system:menu:list", // 需要菜单列表权限 + moduleKey: "admin.menu-management", }, { key: "teacher", @@ -62,6 +67,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/teacher", component: "Role.vue", permission: "system:role:list", // 需要角色列表权限 + moduleKey: "admin.role-management", }, { key: "notification-management", @@ -70,6 +76,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/notification-management", component: "NotificationManagement.vue", permission: "studio:notice:list", // 需要通知列表权限 + moduleKey: "admin.notification-management", }, { key: "log-management", @@ -78,15 +85,17 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/log-management", component: "LogManagement.vue", permission: "log.view_operationlog", // 查看操作日志权限 + moduleKey: "admin.log-management", }, { key: "knowledge-base", - title: "知识库管理", + title: "文章管理", icon: "DatabaseOutlined", - path: "/admin/knowledge-base", - component: "KnowledgeBaseManagement.vue", + path: "/admin/article-management", + component: "ArticleManagement.vue", // 菜单访问权限:需要具备文章列表权限 permission: "system:knowledge:article:list", + moduleKey: "admin.knowledge-base", }, { key: "yolo-model", @@ -95,6 +104,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/yolo-model", component: "ModelManagement.vue", // 默认显示模型管理页面 // 父菜单不设置权限,由子菜单的权限决定是否显示 + moduleKey: "admin.yolo-model", children: [ { key: "model-management", @@ -104,6 +114,20 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ component: "ModelManagement.vue", permission: "system:yolo:model:list", }, + { + key: "dataset-management", + title: "数据集管理", + icon: "DatabaseOutlined", + path: "/admin/dataset-management", + component: "DatasetManagement.vue", + }, + { + key: "yolo-train-management", + title: "YOLO训练", + icon: "HistoryOutlined", + path: "/admin/yolo-train", + component: "YoloTrainManagement.vue", + }, { key: "alert-level-management", title: "模型类别管理", @@ -144,8 +168,10 @@ const COMPONENT_MAP: { [key: string]: () => Promise } = { 'MenuManagement.vue': () => import("@/views/admin_page/MenuManagement.vue"), 'NotificationManagement.vue': () => import("@/views/admin_page/NotificationManagement.vue"), 'LogManagement.vue': () => import("@/views/admin_page/LogManagement.vue"), - 'KnowledgeBaseManagement.vue': () => import("@/views/admin_page/KnowledgeBaseManagement.vue"), + 'ArticleManagement.vue': () => import("@/views/admin_page/ArticleManagement.vue"), 'ModelManagement.vue': () => import("@/views/admin_page/ModelManagement.vue"), + 'DatasetManagement.vue': () => import("@/views/admin_page/DatasetManagement.vue"), + 'YoloTrainManagement.vue': () => import("@/views/admin_page/YoloTrainManagement.vue"), 'AlertLevelManagement.vue': () => import("@/views/admin_page/AlertLevelManagement.vue"), 'AlertProcessingCenter.vue': () => import("@/views/admin_page/AlertProcessingCenter.vue"), 'DetectionHistoryManagement.vue': () => import("@/views/admin_page/DetectionHistoryManagement.vue"), @@ -154,8 +180,12 @@ const COMPONENT_MAP: { [key: string]: () => Promise } = { // 🚀 自动生成路由配置 function generateAdminRoutes(): RouteRecordRaw { const children: RouteRecordRaw[] = []; + const enabledModuleKeys = getEnabledModuleKeys(); ADMIN_MENU_CONFIG.forEach(item => { + if (!isModuleEnabled(item.moduleKey, enabledModuleKeys)) { + return; + } // 如果有子菜单,将子菜单作为独立的路由项 if (item.children && item.children.length > 0) { // 为每个子菜单创建独立的路由 @@ -334,8 +364,13 @@ export const getFilteredMenuConfig = (userRoles: string[], userPermissions: stri // 对 super_admin / system_admin 开放所有管理菜单(忽略权限字符串过滤) const isPrivilegedAdmin = userRoles.includes('super_admin') || userRoles.includes('system_admin'); - // 过滤菜单项 - 基于权限字符串检查 + const enabledModuleKeys = getEnabledModuleKeys(); + + // 过滤菜单项 - 基于模块开关和权限字符串检查 const filteredMenus = ADMIN_MENU_CONFIG.filter(menuItem => { + if (!isModuleEnabled(menuItem.moduleKey, enabledModuleKeys)) { + return false; + } console.log(`🔍 检查菜单项: ${menuItem.title} (${menuItem.key})`, { hasPermission: !!menuItem.permission, permission: menuItem.permission, diff --git a/hertz_server_diango_ui/src/router/index.ts b/hertz_server_diango_ui/src/router/index.ts index 38a307b..b9f8549 100644 --- a/hertz_server_diango_ui/src/router/index.ts +++ b/hertz_server_diango_ui/src/router/index.ts @@ -3,6 +3,7 @@ import type { RouteRecordRaw } from "vue-router"; import { useUserStore } from "@/stores/hertz_user"; import { adminMenuRoutes, UserRole } from "./admin_menu"; import { userRoutes } from "./user_menu_ai"; +import { hasModuleSelection } from "@/config/hertz_modules"; // 固定路由配置 const fixedRoutes: RouteRecordRaw[] = [ @@ -25,6 +26,15 @@ const fixedRoutes: RouteRecordRaw[] = [ requiresAuth: false, }, }, + { + path: "/template/modules", + name: "ModuleSetup", + component: () => import("@/views/ModuleSetup.vue"), + meta: { + title: "模块配置", + requiresAuth: false, + }, + }, { path: "/register", name: "Register", @@ -160,6 +170,16 @@ router.beforeEach((to, _from, next) => { console.log('📋 用户信息:', userStore.userInfo); console.log('🔄 重定向计数:', redirectCount); + // 模板模式:首次必须先完成模块选择 + const isTemplateMode = import.meta.env.VITE_TEMPLATE_SETUP_MODE === 'true'; + if (isTemplateMode && to.name !== "ModuleSetup") { + if (!hasModuleSelection()) { + console.log('🧩 模板模式开启,尚未选择模块,重定向到模块配置页'); + next({ name: "ModuleSetup", query: { redirect: to.fullPath } }); + return; + } + } + // 设置页面标题 if (to.meta.title) { document.title = `${to.meta.title} - 管理系统`; diff --git a/hertz_server_diango_ui/src/router/user_menu_ai.ts b/hertz_server_diango_ui/src/router/user_menu_ai.ts index 8ba59cf..e228d4e 100644 --- a/hertz_server_diango_ui/src/router/user_menu_ai.ts +++ b/hertz_server_diango_ui/src/router/user_menu_ai.ts @@ -1,5 +1,6 @@ import type { RouteRecordRaw } from 'vue-router' import { defineAsyncComponent } from 'vue' +import { getEnabledModuleKeys, isModuleEnabled } from '@/config/hertz_modules' export interface UserMenuConfig { key: string @@ -15,6 +16,7 @@ export interface UserMenuConfig { roles?: string[] [key: string]: any } + moduleKey?: string } export interface MenuItem { @@ -30,16 +32,23 @@ export const userMenuConfigs: UserMenuConfig[] = [ { key: 'dashboard', label: '首页', icon: 'DashboardOutlined', path: '/dashboard', component: 'index.vue', meta: { title: '用户首页', requiresAuth: true } }, { key: 'profile', label: '个人信息', icon: 'UserOutlined', path: '/user/profile', component: 'Profile.vue', meta: { title: '个人信息', requiresAuth: true, hideInMenu: true } }, // { key: 'documents', label: '文档管理', icon: 'FileTextOutlined', path: '/user/documents', component: 'Documents.vue', meta: { title: '文档管理', requiresAuth: true } }, - { key: 'system-monitor', label: '系统监控', icon: 'DashboardOutlined', path: '/user/system-monitor', component: 'SystemMonitor.vue', meta: { title: '系统监控', requiresAuth: true } }, - { key: 'ai-chat', label: 'AI助手', icon: 'MessageOutlined', path: '/user/ai-chat', component: 'AiChat.vue', meta: { title: 'AI助手', requiresAuth: true } }, - { key: 'yolo-detection', label: 'YOLO检测', icon: 'ScanOutlined', path: '/user/yolo-detection', component: 'YoloDetection.vue', meta: { title: 'YOLO检测中心', requiresAuth: true } }, - { key: 'live-detection', label: '实时检测', icon: 'VideoCameraOutlined', path: '/user/live-detection', component: 'LiveDetection.vue', meta: { title: '实时检测', requiresAuth: true } }, - { key: 'detection-history', label: '检测历史', icon: 'HistoryOutlined', path: '/user/detection-history', component: 'DetectionHistory.vue', meta: { title: '检测历史记录', requiresAuth: true } }, - { key: 'alert-center', label: '告警中心', icon: 'ExclamationCircleOutlined', path: '/user/alert-center', component: 'AlertCenter.vue', meta: { title: '告警中心', requiresAuth: true } }, - { key: 'notice-center', label: '通知中心', icon: 'BellOutlined', path: '/user/notice', component: 'NoticeCenter.vue', meta: { title: '通知中心', requiresAuth: true } }, - { key: 'knowledge-center', label: '知识库中心', icon: 'DatabaseOutlined', path: '/user/knowledge', component: 'KnowledgeCenter.vue', meta: { title: '知识库中心', requiresAuth: true } }, + { key: 'system-monitor', label: '系统监控', icon: 'DashboardOutlined', path: '/user/system-monitor', component: 'SystemMonitor.vue', meta: { title: '系统监控', requiresAuth: true }, moduleKey: 'user.system-monitor' }, + { key: 'ai-chat', label: 'AI助手', icon: 'MessageOutlined', path: '/user/ai-chat', component: 'AiChat.vue', meta: { title: 'AI助手', requiresAuth: true }, moduleKey: 'user.ai-chat' }, + { key: 'yolo-detection', label: 'YOLO检测', icon: 'ScanOutlined', path: '/user/yolo-detection', component: 'YoloDetection.vue', meta: { title: 'YOLO检测中心', requiresAuth: true }, moduleKey: 'user.yolo-detection' }, + { key: 'live-detection', label: '实时检测', icon: 'VideoCameraOutlined', path: '/user/live-detection', component: 'LiveDetection.vue', meta: { title: '实时检测', requiresAuth: true }, moduleKey: 'user.live-detection' }, + { key: 'detection-history', label: '检测历史', icon: 'HistoryOutlined', path: '/user/detection-history', component: 'DetectionHistory.vue', meta: { title: '检测历史记录', requiresAuth: true }, moduleKey: 'user.detection-history' }, + { key: 'alert-center', label: '告警中心', icon: 'ExclamationCircleOutlined', path: '/user/alert-center', component: 'AlertCenter.vue', meta: { title: '告警中心', requiresAuth: true }, moduleKey: 'user.alert-center' }, + { key: 'notice-center', label: '通知中心', icon: 'BellOutlined', path: '/user/notice', component: 'NoticeCenter.vue', meta: { title: '通知中心', requiresAuth: true }, moduleKey: 'user.notice-center' }, + { key: 'knowledge-center', label: '文章中心', icon: 'DatabaseOutlined', path: '/user/knowledge', component: 'ArticleCenter.vue', meta: { title: '文章中心', requiresAuth: true }, moduleKey: 'user.knowledge-center' }, + { key: 'kb-center', label: '知识库中心', icon: 'DatabaseOutlined', path: '/user/kb-center', component: 'KbCenter.vue', meta: { title: '知识库中心', requiresAuth: true }, moduleKey: 'user.kb-center' }, ] +const enabledModuleKeys = getEnabledModuleKeys() + +const effectiveUserMenuConfigs: UserMenuConfig[] = userMenuConfigs.filter(config => + isModuleEnabled(config.moduleKey, enabledModuleKeys) +) + const explicitComponentMap: Record = { 'index.vue': defineAsyncComponent(() => import('@/views/user_pages/index.vue')), 'Profile.vue': defineAsyncComponent(() => import('@/views/user_pages/Profile.vue')), @@ -52,10 +61,11 @@ const explicitComponentMap: Record = { 'DetectionHistory.vue': defineAsyncComponent(() => import('@/views/user_pages/DetectionHistory.vue')), 'AlertCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/AlertCenter.vue')), 'NoticeCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/NoticeCenter.vue')), - 'KnowledgeCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/KnowledgeCenter.vue')), + 'ArticleCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/ArticleCenter.vue')), + 'KbCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/KbCenter.vue')), } -export const userMenuItems: MenuItem[] = userMenuConfigs.map(config => ({ +export const userMenuItems: MenuItem[] = effectiveUserMenuConfigs.map(config => ({ key: config.key, label: config.label, icon: config.icon, @@ -76,10 +86,11 @@ const componentMap: Record Promise> = { 'DetectionHistory.vue': () => import('@/views/user_pages/DetectionHistory.vue'), 'AlertCenter.vue': () => import('@/views/user_pages/AlertCenter.vue'), 'NoticeCenter.vue': () => import('@/views/user_pages/NoticeCenter.vue'), - 'KnowledgeCenter.vue': () => import('@/views/user_pages/KnowledgeCenter.vue'), + 'ArticleCenter.vue': () => import('@/views/user_pages/ArticleCenter.vue'), + 'KbCenter.vue': () => import('@/views/user_pages/KbCenter.vue'), } -const baseRoutes: RouteRecordRaw[] = userMenuConfigs.map(config => { +const baseRoutes: RouteRecordRaw[] = effectiveUserMenuConfigs.map(config => { const route: RouteRecordRaw = { path: config.path, name: `User${config.key.charAt(0).toUpperCase() + config.key.slice(1)}`, @@ -101,7 +112,7 @@ const baseRoutes: RouteRecordRaw[] = userMenuConfigs.map(config => { const knowledgeDetailRoute: RouteRecordRaw = { path: '/user/knowledge/:id', name: 'UserKnowledgeDetail', - component: () => import('@/views/user_pages/KnowledgeDetail.vue'), + component: () => import('@/views/user_pages/ArticleDetail.vue'), meta: { title: '文章详情', requiresAuth: true, hideInMenu: true } } @@ -148,14 +159,14 @@ export const generateComponentMap = () => { if (config.children) processConfigs(config.children) }) } - processConfigs(userMenuConfigs) + processConfigs(effectiveUserMenuConfigs) return map } export const userComponentMap = generateComponentMap() export const getFilteredUserMenuItems = (userRoles: string[], userPermissions: string[]): MenuItem[] => { - return userMenuConfigs + return effectiveUserMenuConfigs .filter(config => { // 隐藏菜单中不显示的项(如个人信息,只在用户下拉菜单中显示) if (config.meta?.hideInMenu) return false diff --git a/hertz_server_diango_ui/src/stores/hertz_user.ts b/hertz_server_diango_ui/src/stores/hertz_user.ts index f0cef2c..1cc8d41 100644 --- a/hertz_server_diango_ui/src/stores/hertz_user.ts +++ b/hertz_server_diango_ui/src/stores/hertz_user.ts @@ -6,6 +6,7 @@ import type { ChangePasswordParams } from '@/api/password' import { roleApi } from '@/api/role' import { initializeMenuMapping } from '@/utils/menu_mapping' import { logoutUser } from '@/api/auth' +import { hasModuleSelection } from '@/config/hertz_modules' // 用户信息接口 interface UserInfo { @@ -69,8 +70,11 @@ export const useUserStore = defineStore('user', () => { localStorage.setItem('userInfo', JSON.stringify(response.user_info)) } - // 获取用户菜单权限 - await fetchUserMenuPermissions() + // 获取用户菜单权限(模板模式首次运行时跳过) + const isTemplateMode = import.meta.env.VITE_TEMPLATE_SETUP_MODE === 'true' + if (!isTemplateMode || hasModuleSelection()) { + await fetchUserMenuPermissions() + } return response } catch (error) { @@ -142,9 +146,12 @@ export const useUserStore = defineStore('user', () => { console.log('✅ 用户状态恢复成功') console.log('👤 恢复的用户信息:', parsedUserInfo) console.log('🔐 登录状态:', isLoggedIn.value) - - // 获取用户菜单权限 - await fetchUserMenuPermissions() + + // 获取用户菜单权限(模板模式首次运行时跳过) + const isTemplateMode = import.meta.env.VITE_TEMPLATE_SETUP_MODE === 'true' + if (!isTemplateMode || hasModuleSelection()) { + await fetchUserMenuPermissions() + } } catch (error) { console.error('❌ 解析用户信息失败:', error) clearAuth() @@ -181,6 +188,14 @@ export const useUserStore = defineStore('user', () => { } try { + const adminRoleCodes = ['admin', 'system_admin', 'super_admin'] + const hasAdminRole = userInfo.value.roles.some(role => adminRoleCodes.includes(role.role_code)) + + if (!hasAdminRole) { + userMenuPermissions.value = [] + return [] + } + // 获取用户所有角色的菜单权限 const allMenuPermissions = new Set() diff --git a/hertz_server_diango_ui/src/style.css b/hertz_server_diango_ui/src/style.css deleted file mode 100644 index f691315..0000000 --- a/hertz_server_diango_ui/src/style.css +++ /dev/null @@ -1,79 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -.card { - padding: 2em; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/hertz_server_diango_ui/src/views/ModuleSetup.vue b/hertz_server_diango_ui/src/views/ModuleSetup.vue new file mode 100644 index 0000000..a540684 --- /dev/null +++ b/hertz_server_diango_ui/src/views/ModuleSetup.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/hertz_server_diango_ui/src/views/admin_page/KnowledgeBaseManagement.vue b/hertz_server_diango_ui/src/views/admin_page/ArticleManagement.vue similarity index 78% rename from hertz_server_diango_ui/src/views/admin_page/KnowledgeBaseManagement.vue rename to hertz_server_diango_ui/src/views/admin_page/ArticleManagement.vue index d44c17a..4c34ffa 100644 --- a/hertz_server_diango_ui/src/views/admin_page/KnowledgeBaseManagement.vue +++ b/hertz_server_diango_ui/src/views/admin_page/ArticleManagement.vue @@ -7,8 +7,8 @@
-

知识库管理

-

集中管理内部知识文档,支持分类、标签和搜索

+

文章管理

+

集中管理文章内容,支持分类、标签和搜索

@@ -60,9 +60,9 @@
- + - +
- +
- +
@@ -144,7 +154,13 @@ - + @@ -162,93 +178,112 @@ - - - - - - - -
- - - - 新建分类 - - - - - -
- - - - - - - - - - - - - - - - - - - - - - + -
+ + + +
+ + + + 新建分类 + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
- \ No newline at end of file + diff --git a/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue b/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue index 05d26a0..4a5f1a5 100644 --- a/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue +++ b/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue @@ -506,7 +506,7 @@ const quickActions = [ { key: 'role', label: '角色管理', icon: DatabaseOutlined, gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' }, { key: 'notification', label: '通知管理', icon: BellOutlined, gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' }, { key: 'log', label: '日志管理', icon: FileSearchOutlined, gradient: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)' }, - { key: 'knowledge', label: '知识库管理', icon: BookOutlined, gradient: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }, + { key: 'knowledge', label: '文章管理', icon: BookOutlined, gradient: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }, { key: 'model-management', label: '模型管理', icon: RobotOutlined, gradient: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)' }, { key: 'alert-level-management', label: '类别管理', icon: WarningOutlined, gradient: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' }, { key: 'alert-processing-center', label: '告警中心', icon: BellOutlined, gradient: 'linear-gradient(135deg, #ff6e7f 0%, #bfe9ff 100%)' }, @@ -594,8 +594,8 @@ const handleQuickAction = (action: string) => { message.success('跳转到日志管理页面') break case 'knowledge': - router.push('/admin/knowledge-base') - message.success('跳转到知识库管理页面') + router.push('/admin/article-management') + message.success('跳转到文章管理页面') break case 'model-management': router.push('/admin/model-management') diff --git a/hertz_server_diango_ui/src/views/admin_page/DatasetManagement.vue b/hertz_server_diango_ui/src/views/admin_page/DatasetManagement.vue new file mode 100644 index 0000000..1e1c1cb --- /dev/null +++ b/hertz_server_diango_ui/src/views/admin_page/DatasetManagement.vue @@ -0,0 +1,1404 @@ + + + + + diff --git a/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue b/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue index f3939ec..da2caa9 100644 --- a/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue +++ b/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue @@ -47,7 +47,6 @@ @change="handleTableChange" row-key="dept_id" size="middle" - :scroll="{ x: 1200 }" >