From 7204e011d9e36594b481e6d7dc3443c4756c6324 Mon Sep 17 00:00:00 2001 From: zhangtao <9480807882@qq.com> Date: Sun, 20 Jul 2025 15:39:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(role):=20=E6=96=B0=E5=A2=9E=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E6=9D=83=E9=99=90=E5=88=86=E9=85=8D=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E7=95=8C=E9=9D=A2=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(menu): 重构菜单类型枚举和权限数据结构 fix(log): 修复日志类型显示和查询问题 style(common): 重命名部门树格式化函数为通用树格式化函数 perf(store): 移除控制台日志输出 docs(tsconfig): 添加Vite客户端类型注释 chore(package): 调整依赖项顺序 --- .../app/api/v1/models/system/menu_model.py | 2 +- .../v1/models/system/operation_log_model.py | 1 + .../v1/params/system/operation_log_param.py | 2 + .../app/api/v1/schemas/system/menu_schema.py | 4 +- .../v1/schemas/system/operation_log_schema.py | 1 + .../services/system/operation_log_service.py | 3 + backend/app/core/router_class.py | 3 + backend/app/scripts/data/system_menu.json | 6 +- backend/dev_sql.db | Bin 241664 -> 253952 bytes frontend/package.json | 2 +- frontend/src/api/system/dept.ts | 2 +- frontend/src/api/system/log.ts | 2 + frontend/src/api/system/menu.ts | 6 +- frontend/src/api/system/role.ts | 26 +- frontend/src/enums/system/menu.enum.ts | 4 +- frontend/src/store/modules/notice.store.ts | 1 - .../src/store/modules/permission.store.ts | 2 +- frontend/src/utils/common.ts | 6 +- frontend/src/views/monitor/job/index.vue | 1150 ++++++++++------- frontend/src/views/system/config/index.vue | 6 +- frontend/src/views/system/dept/index.vue | 15 +- .../system/dict/components/DataDrawer.vue | 13 +- frontend/src/views/system/dict/index.vue | 6 +- frontend/src/views/system/log/index.vue | 32 +- frontend/src/views/system/menu/index.vue | 255 ++-- frontend/src/views/system/notice/index.vue | 204 +-- frontend/src/views/system/position/index.vue | 6 +- .../role/components/PermissonDrawer.vue | 305 +++++ frontend/src/views/system/role/index.vue | 258 +--- .../views/system/user/components/DeptTree.vue | 4 +- .../system/user/components/UserImport.vue | 199 +++ frontend/src/views/system/user/index.vue | 10 +- frontend/tsconfig.json | 5 +- 33 files changed, 1507 insertions(+), 1034 deletions(-) create mode 100644 frontend/src/views/system/role/components/PermissonDrawer.vue diff --git a/backend/app/api/v1/models/system/menu_model.py b/backend/app/api/v1/models/system/menu_model.py index 6f27e396..40c50aa0 100644 --- a/backend/app/api/v1/models/system/menu_model.py +++ b/backend/app/api/v1/models/system/menu_model.py @@ -39,7 +39,7 @@ class MenuModel(ModelBase): keep_alive = Column(Boolean, default=True, nullable=False, comment="是否缓存(True:是 False:否)") always_show = Column(Boolean, default=False, nullable=False, comment="是否始终显示(True:是 False:否)") title = Column(String(50), nullable=True, comment="菜单标题") - params = Column(String(200), nullable=True, comment="路由参数") + params = Column(Text, nullable=True, comment="路由参数(JSON数组: [{\"key\": \"\", \"value\": \"\"}])") affix = Column(Boolean, default=False, nullable=False, comment="是否固定标签页(True:是 False:否)") # 层级关系 diff --git a/backend/app/api/v1/models/system/operation_log_model.py b/backend/app/api/v1/models/system/operation_log_model.py index 95589481..26b2902a 100644 --- a/backend/app/api/v1/models/system/operation_log_model.py +++ b/backend/app/api/v1/models/system/operation_log_model.py @@ -13,6 +13,7 @@ class OperationLogModel(ModelBase): __table_args__ = ({'comment': '操作日志表'}) id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID') + type = Column(Integer, nullable=True, comment="日志类型(1登录日志 2操作日志)", index=True) request_path = Column(String(255), nullable=True, comment="请求路径", index=True) request_method = Column(String(10), nullable=True, comment="请求方式", index=True) request_payload = Column(Text, nullable=True, comment="请求体") diff --git a/backend/app/api/v1/params/system/operation_log_param.py b/backend/app/api/v1/params/system/operation_log_param.py index 2d4654d9..6ac667cf 100644 --- a/backend/app/api/v1/params/system/operation_log_param.py +++ b/backend/app/api/v1/params/system/operation_log_param.py @@ -11,6 +11,7 @@ class OperationLogQueryParams: def __init__( self, + type: Optional[int] = Query(None, description="日志类型(1:登录日志, 2:操作日志)"), request_path: Optional[str] = Query(None, description="请求路径"), request_method: Optional[str] = Query(None, description="请求方法"), request_ip: Optional[str] = Query(None, description="请求IP"), @@ -29,6 +30,7 @@ class OperationLogQueryParams: self.request_method = request_method self.request_ip = request_ip self.response_code = response_code + self.type = type # 时间范围查询 if start_time and end_time: diff --git a/backend/app/api/v1/schemas/system/menu_schema.py b/backend/app/api/v1/schemas/system/menu_schema.py index 52634d4f..f237ede4 100644 --- a/backend/app/api/v1/schemas/system/menu_schema.py +++ b/backend/app/api/v1/schemas/system/menu_schema.py @@ -10,7 +10,7 @@ from app.core.base_schema import BaseSchema class MenuCreateSchema(BaseModel): """菜单创建模型""" name: str = Field(..., max_length=50, description="菜单名称") - type: int = Field(..., ge=1, le=3, description="菜单类型(1:目录 2:菜单 3:按钮)") + type: int = Field(..., ge=1, le=4, description="菜单类型(1:目录 2:菜单 3:按钮 4:外链)") icon: Optional[str] = Field(default=None, max_length=100, description="菜单图标") order: int = Field(default=1, ge=0, description="显示顺序") permission: Optional[str] = Field(default=None, max_length=100, description="权限标识") @@ -23,7 +23,7 @@ class MenuCreateSchema(BaseModel): hidden: bool = Field(default=False, description="是否隐藏(True:是 False:否)") always_show: bool = Field(default=False, description="是否始终显示(True:是 False:否)") title: Optional[str] = Field(default=None, max_length=50, description="菜单标题") - params: Optional[str] = Field(default=None, max_length=200, description="路由参数") + params: Optional[list[dict[str, str]]] = Field(default=None, description="路由参数,格式为[{key: string, value: string}]") affix: bool = Field(default=False, description="是否固定标签页(True:是 False:否)") parent_id: Optional[int] = Field(default=None, ge=0, description="父菜单ID") description: Optional[str] = Field(default=None, max_length=500, description="备注说明") diff --git a/backend/app/api/v1/schemas/system/operation_log_schema.py b/backend/app/api/v1/schemas/system/operation_log_schema.py index fd421109..1044278b 100644 --- a/backend/app/api/v1/schemas/system/operation_log_schema.py +++ b/backend/app/api/v1/schemas/system/operation_log_schema.py @@ -8,6 +8,7 @@ from app.core.base_schema import BaseSchema class OperationLogCreateSchema(BaseModel): """日志创建模型""" + type: Optional[int] = Field(default=None, description="日志类型(1登录日志 2操作日志)") request_path: Optional[str] = Field(default=None, description="请求路径") request_method: Optional[str] = Field(default=None, description="请求方法") request_payload: Optional[str] = Field(default=None, description="请求负载") diff --git a/backend/app/api/v1/services/system/operation_log_service.py b/backend/app/api/v1/services/system/operation_log_service.py index e2fc1f7e..a79678f2 100644 --- a/backend/app/api/v1/services/system/operation_log_service.py +++ b/backend/app/api/v1/services/system/operation_log_service.py @@ -65,6 +65,7 @@ class OperationLogService: # 操作日志字段映射 mapping_dict = { 'id': '编号', + 'type': '日志类型', 'request_path': '请求URL', 'request_method': '请求方式', 'request_payload': '请求参数', @@ -87,5 +88,7 @@ class OperationLogService: for item in data: # 处理状态 item['response_code'] = '成功' if item.get('response_code') == 200 else '失败' + # 处理日志类型 + item['type'] = '操作日志' if item.get('type') == 1 else '登录日志' return ExcelUtil.export_list2excel(list_data=operation_log_list, mapping_dict=mapping_dict) diff --git a/backend/app/core/router_class.py b/backend/app/core/router_class.py index 838bcab1..37159b3e 100644 --- a/backend/app/core/router_class.py +++ b/backend/app/core/router_class.py @@ -60,9 +60,11 @@ class OperationLogRoute(APIRoute): process_time = time.time() - start_time # 获取当前用户ID,如果是登录接口则为空 + log_type = 1 # 1:登录日志 2:操作日志 current_user_id = None if "user_id" in request.scope: current_user_id = request.scope.get("user_id") + log_type = 2 request_ip = None x_forwarded_for = request.headers.get('X-Forwarded-For') @@ -81,6 +83,7 @@ class OperationLogRoute(APIRoute): auth = AuthSchema(db=session) await OperationLogService.create_log_service(data=OperationLogCreateSchema( + type = log_type, request_path = request.url.path, request_method = request.method, request_payload = payload, diff --git a/backend/app/scripts/data/system_menu.json b/backend/app/scripts/data/system_menu.json index 1e8f9d7f..d430a47c 100644 --- a/backend/app/scripts/data/system_menu.json +++ b/backend/app/scripts/data/system_menu.json @@ -1500,7 +1500,7 @@ "route_name": null, "route_path": null, "component_path": null, - "parent_id": 62, + "parent_id": 71, "status": true, "keep_alive": false, "hidden": false, @@ -1535,7 +1535,7 @@ { "id": 74, "name": "接口管理", - "type": 2, + "type": 4, "icon": "api", "order": 1, "permission": "common:docs:query", @@ -1556,7 +1556,7 @@ { "id": 75, "name": "文档管理", - "type": 2, + "type": 4, "icon": "el-icon-Document", "order": 2, "permission": "common:redoc:query", diff --git a/backend/dev_sql.db b/backend/dev_sql.db index 987cdf89f2c13e778d9c13b4e348a2d553aeece1..d05ed0728ca3e339dad26587286c58058f5d479d 100644 GIT binary patch delta 19527 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 delta 13102 zcmeHNeQ=aTnxB3<$t01?3u*`pBm@)>G#~Hx`%btuB$ID4lMGCV$>PEQAzvhfe2`>V z0wXShy2@79(dX$YuBYx+9e!nttU{2jy4$*PuiO<~@2-|}xx?eUTE*xppdh*KiNZTQ zZ&R^q|JXl56~)lc?|J&^=lON_^LF>1lj5E;o`VbKYA zWRG4JqqCMva*-kz>1u%`8>(z6lA`BJX1?srWhCg?g?BP&^9JZk-vVu-RoSd-kk=S#^##m6*}=EOch&Tr^ppf)13F+nlx8h&!f^+dpz0i$(zLkusb4tEey@jeSM8EcxOj}Eh zCf|(DM~GIaY$X#7=Y-O}6f|Bo$n@cy&=@+JGW*(~QqT{|S@$GIE>KlDnj%uwY?M;L zy2sa&(6);8@q#|5K|Aw}ctp@Yt9*3YXbXj_9%j(aD-E@6qPf)%Fla89 z6$AaMnzhZPBth@2xu3Dk<)NaZWvhM?pJ0WL)}nW&pplxnXjW|}(*>8Wii!@`?q$$i z#u0R_pS8_poT8vJ{wEmQe6)<74PXY%C6^+hmb%_WaSM)|kkZi$b&t&!*LwDO_IM(m zeovccm8X1kaqjaOc;Yu82~~H`0e6pnue(9Kne0xoS|nLTs!?ECx>q;Q`0n|3cT}>V z*w}j=?bR$rM#&@ZFlY{Yi5&|GyMM=^?Y3>hB53oKJqx@xG}%)*D>0RLQQ50K(?(n9 zKVP+{pIS%=V{9*vNzj4U?Chm&&NCw>LB=sVS!kQ{D5ubG99zry%}48~`uGTgcAgp3 zwuUB-+h;UwbDk102|7GJz}R-45>x2APwZyU&LpR31)Vz)VbIPbr)U`+cs&q@HoeX( zC`C)JoGg&-XkF%^sRT8CvW`JJGgc*N3!VPNzLwKA4o_m}&7W2~N06|t?Lw$EAH99G59 z?|jbs&0!Tm%c%H*eHW*>il9IE z7HAVSUa}XKWRw<{gO-8*`4SUBi_0oYN75Heb}TNd2>OXHSlfKGhTLB=jJ3ENv{W?w zWeJlVE~^Oo?3Z^lXfCTP1=U<$HEpzvPF!aF=CaC?kaUHKpvh&G=|xW@pt-Cf=+v(& z&DWS$zr`oaY3e7K39SqS1dOKfm*PbFB)265>mjhy7i z;5)KG4HFqo_NgKKf7uB`<-;|+CI`gvme}C1g-Y;=91yoKlSXH{5PB#I&R>+^3};=5 z!SNyxw@Iin+3}o?iKun?Xf8;SmU&!ny3)?17Q3&y$K7hu`J`RKX<>cFv5d@F12_HN z^(NQ{Tj*z_28*AupONOCf;x_Fq2#^;SczYdT<#=vlilv8le;{!rL1` z7QR{~c``d$2Tm5_~ls)SVhc8xF(*Hj4w_&ckFyV9e#&39)t5ls0t zj0YQ4!mapRm2d}DG1Koa{f;|J*TMe3!huyn$JoED65axb>V>4Sqz&L_Ira=2MjZF) z7ll!zO9h5RJ}Gqk?g;2+;CAk_@UGpUgMr)0%)pQC0cxBxlENz%sRXZk5^S?uCyjPz zZ1a{%@Mlkgdl|UhnRvK~BTps3Ia7sLH;x_#$+Odh7hUOBQkMuXjxL`4w2<~-54_n` zoEdGwua1ER$;ivy6z=KY)D~Xf*)m*|YlaQ4CCg#2wb84YUfI&ZhG}lnEzPhDuPNc; zUx5E8tm*J~v<@~!nmcP7%jM=qe}AB|L1|tixA`MYYHetBTV1F;QdbrnYEr5rejGdo z-uHw$Tm2!w;V)}7{E=qteGL?9+`+S*ak&~Ez5n}>Jk?X`U+ zrQwZLRr#iAw)dM`t1Z8VpLh-I`?I*fxWddq@cA>Z&&ePvaDzP3@@ z(NjI_%Wqk0tgh{>H?6SN)H@io@&~GeoA5sz2gSj6*1k(mcmMaXWB)V$H@10s?UR>$ z^{JM9g2zkLA2td%yGkJ5z|darhLx71xY|hvbfiVdB_OU8q{F4Xy2}t0W;V z+ca3WMs9EFDj%$E40JYkRLg-#TW4LUrn7l1u6h-mjm5aE-0+8*j6m618sj``xGuCw z@^_TTB+MoKTZ60XOSjxp;>)iIwpGc49VHE=L*dA3CDa%3wfoDvd;GO)f?Kv(OYmE# zpo|}W9puAtNoBuRs6IB26hUy^`s*(~pksy2N@AfH{O|J7a_UF~ldwz!I~y zH{9L7zJIuRaVmtEWZaA<3qmy zdkfo|JDP@?L#>0gp(Z^LswQy>4*G{lT$Bc-w$U&9D{I;U^xv|Ib~Y{@)g(5hvA7tq zxCBD}K@yj?zM5WNRdBuDxV_S^wEOziRdRmKjvWnkTgqGVjmE&R@1CB`jrH|@{P=5N z|DVMP!oECRA{o~Gky39#r*BAVt!v%l@A5@_jX`gJNx!_cS6N+ewQbe{!HrwjuJ(0y z4f(ZUKc(MY^~I-_4>|IWt{O^_EEOoKq$YeQT$gy$i8>U|xx`E2$KtusIeY$HT$Tzm zL4Dnt5d7wc;D(uLv}FqYoJ%|_J}3Uu=wj(_#U7ECG1MIGCR&EQG~rT;P=DPhH{_@l)~Q=$wP^i=HIt0x$`t0Rci++R+K{ zPKIrvR5V`2LM0ad>MO8(+He!sT}^;HS1i=JfuFt#?AHmj(LN8Zui7yQo=mjvT!Ku& zgOebS@!h!up>Pd9Hwo;g8MM*41fdf3=R#6$svrhj;`3sFlvi7Yj>oN$YiGy3V?0)!+0+`Wr$yXv zJOl&CQ9;3r1A-U-&QFwg1&G0% z05Lf3CkC4Xq>cP?<4Hd;_)I_ugVCgeUy8HS(lUrSD4utTm&MP;^Ms^lM0|dmFgG2F zAJWFp#1Bd1dm{dDw=j=1PEa~u5GP3Ee~a`SHDz8h1VA8sPD`6&{3jH0QT$|VPA+_3 z%p}s3Zzw<2pWmZk_hI32au$0qV@!ELI0c|T;~aLs6l*KX7&|vEALp;PHEO(uZe-c)_EK6L{Wlg@eQv`WjR8p5Wdae{Mc=8^vDtaONJl?wPrb znz@Z)?(6Bj*vxH|troUz6eKjU+bH{9Bg=`mQL$?j5=hy5s0Y3tkJg<(rU+WM@CQB6 zexpMhoj<0g;L+`HBV(NpH}R}qxG{dM)HyW*>DP#+N?7cLS-5T&OvXohp^wq!6bwXH zkERw<-PpB1F6o2z!xZW|r+A>M_(&fNGlSy8HEj07Va74;dZ(+=)V!%djSRpP{6#-3 zWpp`p1M!UL-gQmaelI}>#8*@B#{&srw|`N^@k^JZ?&bKUJ`#4~p&LKG1Fn?g5u8$m zI3Y#FWpiCi0l-fV!g=`iL1;g(BW`NWXD@UB5WM4NNaw4r*pJ`*!-Q1Al)Qu4c5j?9=pU%5B%5Kj2Hlkl7G5E}y9G5~{lp zM^%(LG6L;K zg~Sh)OC;5ce=`C%$1f8%fLnOoZn%?ybC*ihJh}&VlR_77i$eRs9Px>BPERE|PPq>* zC0cp+LHm8{jkPrV@_n!?-ZjqgJ~gD`%Du1x+>I9xK>Oi6m2sIvwBFhapJF=U!xjA4 z{qUy@oJ$u|#vMO|_PcKC43{AUp0N);9dF&>GK3u1{o6jcb-HlFi?==id*dgG!6lMm zMdy{^Z4ZzKV7dEY6Qjmu2vJL3IYrxF?q_Lh@YPIw=K=T#GZyapXBhau4#4dUoV)N5 z_|6C6er62ZMa$5mS$~a#J!I$;`^Z*P`w$FHTUU!_t(emN!$a^M#zXGHXQ+6?!*Dy( z3zvt68qM-f^>WLP;62k;Q=)S%Jh_t$?8uK`BjX&G#)gdZAAviVUbr+iq-d653*U#2 zu;J6WoTj?^l>>({ url: `/system/dept/list`, method: "get", diff --git a/frontend/src/api/system/log.ts b/frontend/src/api/system/log.ts index 178f3de1..5da00e5a 100644 --- a/frontend/src/api/system/log.ts +++ b/frontend/src/api/system/log.ts @@ -37,6 +37,7 @@ const LogAPI = { export default LogAPI; export interface LogPageQuery extends PageQuery { + type?: number; request_path?: string; creator_name?: string; /** 开始时间 */ @@ -47,6 +48,7 @@ export interface LogPageQuery extends PageQuery { export interface LogTable { id?: number; + type?: number; // 1 登录日志 2 操作日志 request_path?: string; request_method?: string; request_ip?: string; diff --git a/frontend/src/api/system/menu.ts b/frontend/src/api/system/menu.ts index 2e579865..138546df 100644 --- a/frontend/src/api/system/menu.ts +++ b/frontend/src/api/system/menu.ts @@ -1,7 +1,7 @@ import request from "@/utils/request"; const MenuAPI = { - getMenuList(query: MenuPageQuery) { + getMenuList(query?: MenuPageQuery) { return request>>({ url: `/system/menu/list`, method: "get", @@ -78,7 +78,7 @@ export interface MenuTable { hidden?: boolean; always_show?: boolean; title?: string; - params?: string; + params?: { key: string; value: string; }[]; affix?: boolean; status?: boolean; description?: string; @@ -103,7 +103,7 @@ export interface MenuForm { hidden?: boolean; always_show?: boolean; title?: string; - params?: string; + params?: { key: string; value: string; }[]; affix?: boolean; status?: boolean; description?: string; diff --git a/frontend/src/api/system/role.ts b/frontend/src/api/system/role.ts index 2be6dc6c..4efca3f9 100644 --- a/frontend/src/api/system/role.ts +++ b/frontend/src/api/system/role.ts @@ -106,26 +106,26 @@ export interface RoleForm { } export interface permissionDataType { - role_ids?: RoleTable["id"][]; - menu_ids?: permissionMenuType["id"][]; - data_scope?: number; - dept_ids?: number[]; + role_ids: RoleTable["id"][]; + menu_ids: permissionMenuType["id"][]; + data_scope: number; + dept_ids: permissionDeptType["id"][]; } export interface permissionDeptType { - id?: number; - name?: string; - parent_id?: number; - children?: permissionDeptType[]; + id: number; + name: string; + parent_id: number; + children: permissionDeptType[]; } export interface permissionMenuType { - id?: number; - name?: string; - type?: number; - permission?: string; + id: number; + name: string; + type: number; + permission: string; parent_id?: number; - status?: boolean; + status: boolean; description?: string; children?: permissionMenuType[]; } diff --git a/frontend/src/enums/system/menu.enum.ts b/frontend/src/enums/system/menu.enum.ts index cf246c3e..f561d8fc 100644 --- a/frontend/src/enums/system/menu.enum.ts +++ b/frontend/src/enums/system/menu.enum.ts @@ -1,7 +1,7 @@ // 核心枚举定义 export enum MenuTypeEnum { - CATALOG = 2, // 目录 - MENU = 1, // 菜单 + CATALOG = 1, // 目录 + MENU = 2, // 菜单 BUTTON = 3, // 按钮 EXTLINK = 4, // 外链 } diff --git a/frontend/src/store/modules/notice.store.ts b/frontend/src/store/modules/notice.store.ts index 6f9baf1e..f493d86e 100644 --- a/frontend/src/store/modules/notice.store.ts +++ b/frontend/src/store/modules/notice.store.ts @@ -11,7 +11,6 @@ export const useNoticeStore = defineStore("notice", { actions: { async getNotice() { const response = await NoticeAPI.getNoticeListAvailable(); - console.log('获取我的通知公告', response); this.noticeList = response.data.data.items; this.total = response.data.data.total; this.isNoticeLoaded = true; diff --git a/frontend/src/store/modules/permission.store.ts b/frontend/src/store/modules/permission.store.ts index 5da4561b..dd8967bb 100644 --- a/frontend/src/store/modules/permission.store.ts +++ b/frontend/src/store/modules/permission.store.ts @@ -22,7 +22,7 @@ export interface Meta { /** 排序 */ order?: number; /** 参数 */ - params?: string; + params?: { key: string; value: string; }[]; /** 是否固定路由 */ affix?: boolean; } diff --git a/frontend/src/utils/common.ts b/frontend/src/utils/common.ts index f27ee2a5..ccdd5d73 100644 --- a/frontend/src/utils/common.ts +++ b/frontend/src/utils/common.ts @@ -1,5 +1,3 @@ - - // 问候语:根据当前小时返回不同问候语 export function greetings () { // 当前时间(用于计算问候语) @@ -145,7 +143,7 @@ export function listToTree(list: any[]) { } // 加载部门选项 -export function formatDeptTree(nodes: any[]): any[] { +export function formatTree(nodes: any[]): any[] { return nodes.map(node => { const formattedNode = { value: node.id, @@ -153,7 +151,7 @@ export function formatDeptTree(nodes: any[]): any[] { }; if (node.children && node.children.length > 0) { - Object.assign(formattedNode, { children: formatDeptTree(node.children) }); + Object.assign(formattedNode, { children: formatTree(node.children) }); } return formattedNode; diff --git a/frontend/src/views/monitor/job/index.vue b/frontend/src/views/monitor/job/index.vue index 3b190f6d..e24354aa 100644 --- a/frontend/src/views/monitor/job/index.vue +++ b/frontend/src/views/monitor/job/index.vue @@ -1,280 +1,506 @@ - + + + diff --git a/frontend/src/views/system/dept/index.vue b/frontend/src/views/system/dept/index.vue index 7772b2ed..0a6eb2c1 100644 --- a/frontend/src/views/system/dept/index.vue +++ b/frontend/src/views/system/dept/index.vue @@ -181,7 +181,7 @@ defineOptions({ import DeptAPI, { DeptTable, DeptForm, DeptPageQuery } from "@/api/system/dept"; import { ElMessage, ElMessageBox } from "element-plus"; -import { listToTree, formatDeptTree } from "@/utils/common"; +import { listToTree, formatTree } from "@/utils/common"; const queryFormRef = ref(); const dataFormRef = ref(); @@ -246,13 +246,8 @@ const rules = reactive({ status: [{ required: true, message: "请选择状态", trigger: "blur" }], }); -// 刷新数据(防抖) -const handleRefresh = () => { - refreshTable(); -}; - -// 刷新列表数据 -async function refreshTable() { +// 列表刷新 +async function handleRefresh () { loading.value = true; try { const response = await DeptAPI.getDeptList(queryFormData); @@ -264,7 +259,7 @@ async function refreshTable() { } finally { loading.value = false; } -} +}; // 加载表格数据 async function loadingData() { @@ -275,7 +270,7 @@ async function loadingData() { pageTableData.value = treeData; total.value = response.data.data.total; // 加载部门选项 - deptOptions.value = formatDeptTree(treeData); + deptOptions.value = formatTree(treeData); } catch (error: any) { ElMessage.error(error.message); diff --git a/frontend/src/views/system/dict/components/DataDrawer.vue b/frontend/src/views/system/dict/components/DataDrawer.vue index 14fca18d..ffdf6dc3 100644 --- a/frontend/src/views/system/dict/components/DataDrawer.vue +++ b/frontend/src/views/system/dict/components/DataDrawer.vue @@ -1,6 +1,6 @@ - + + + - + + + diff --git a/frontend/src/views/system/log/index.vue b/frontend/src/views/system/log/index.vue index e48e8a12..c38c1850 100644 --- a/frontend/src/views/system/log/index.vue +++ b/frontend/src/views/system/log/index.vue @@ -10,6 +10,12 @@ + + + + + + @@ -82,7 +88,18 @@ - + + + + + + - + @@ -279,7 +285,7 @@ - + @@ -297,7 +303,7 @@ - + @@ -318,7 +324,7 @@ -
+
添加路由参数 @@ -342,10 +348,27 @@
- + + + - 显示 - 隐藏 + + @@ -382,12 +405,12 @@ - + - - + + @@ -395,8 +418,26 @@ - - + + + + + + + + + + + + + + 启用 + 禁用 + + + + + @@ -424,7 +465,7 @@ import { DeviceEnum } from "@/enums/settings/device.enum"; import MenuAPI, { MenuPageQuery, MenuForm, MenuTable } from "@/api/system/menu"; import { MenuTypeEnum } from "@/enums/system/menu.enum"; -import { listToTree } from "@/utils/common"; +import { formatTree, listToTree } from "@/utils/common"; const appStore = useAppStore(); @@ -459,18 +500,18 @@ const formData = reactive({ name: undefined, type: MenuTypeEnum.MENU, icon: undefined, - order: undefined, + order: 1, permission: '', route_name: '', route_path: '', component_path: '', redirect: '', parent_id: undefined, - keep_alive: undefined, - hidden: undefined, - always_show: undefined, + keep_alive: false, + hidden: false, + always_show: false, title: '', - params: '', + params: [] as { key: string; value: string }[], affix: false, status: true, description: undefined, @@ -508,19 +549,41 @@ const rules = reactive({ // 选择表格的行菜单ID const selectedMenuId = ref(); -// 刷新数据(防抖) -const handleRefresh = useDebounceFn(() => { - loadingData(); - ElMessage.success("刷新成功"); -}, 1000); +// 列表刷新 +async function handleRefresh () { + loading.value = true; + try { + const response = await MenuAPI.getMenuList(queryFormData); + const treeData = listToTree(response.data.data.items); + pageTableData.value = treeData; + total.value = response.data.data.total; + } catch (error: any) { + ElMessage.error(error.message); + } finally { + loading.value = false; + } +}; + +// 修改菜单选项过滤逻辑,添加递归过滤函数 +const filterMenuTypes = (nodes: MenuTable[]) => { + return nodes + .filter(node => node.type === MenuTypeEnum.CATALOG || node.type === MenuTypeEnum.MENU) + .map((node: any): any => ({ + ...node, + children: node.children ? filterMenuTypes(node.children) : [] + })); +}; // 加载表格数据 async function loadingData() { loading.value = true; try { const response = await MenuAPI.getMenuList(queryFormData); - pageTableData.value = listToTree(response.data.data.items); + const treeData = listToTree(response.data.data.items); + pageTableData.value = treeData; total.value = response.data.data.total; + // 加载部门选项,只显示目录、菜单 + menuOptions.value = formatTree(filterMenuTypes(treeData)); } catch (error: any) { ElMessage.error(error.message); @@ -566,10 +629,10 @@ async function handleCloseDialog() { } //打开弹窗 -async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) { +async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number, parentId?: number) { dialogVisible.type = type; if (id) { - const response = await MenuAPI.getMenuDetail({ id }); + const response = await MenuAPI.getMenuDetail(id); if (type === 'detail') { dialogVisible.title = "菜单详情"; Object.assign(detailFormData.value, response.data.data); @@ -580,6 +643,10 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe } else { dialogVisible.title = "新增菜单"; formData.id = undefined; + // 设置父级部门 + if (parentId) { + formData.parent_id = parentId; + } } dialogVisible.visible = true; } @@ -632,48 +699,44 @@ async function handleSubmit() { // 删除、批量删除 async function handleDelete(ids: number[]) { - if (!ids && !selectIds.value.length) { - ElMessageBox.confirm("确认删除该项数据?", "警告", { - confirmButtonText: "确定", - cancelButtonText: "取消", - type: "warning", - }).then(async () => { - try { - loading.value = true; - await DictAPI.deleteDictType({ ids }); - handleResetQuery(); - } catch (error: any) { - ElMessage.error(error.message); - } finally { - loading.value = false; - } - }).catch(() => { - ElMessage.info('已取消删除'); - }); - } + ElMessageBox.confirm("确认删除该项数据?", "警告", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(async () => { + try { + loading.value = true; + await MenuAPI.deleteMenu(ids); + handleResetQuery(); + } catch (error: any) { + ElMessage.error(error.message); + } finally { + loading.value = false; + } + }).catch(() => { + ElMessage.info('已取消删除'); + }); } // 批量启用/停用 async function handleMoreClick(status: boolean) { - if (selectIds.value.length) { - ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", { - confirmButtonText: "确定", - cancelButtonText: "取消", - type: "warning", - }).then(async () => { - try { - loading.value = true; - await MenuAPI.batchAvailableMenu({ ids: selectIds.value, status }); - handleResetQuery(); - } catch (error: any) { - ElMessage.error(error.message); - } finally { - loading.value = false; - } - }).catch(() => { - ElMessage.info('已取消批量操作'); - }); - } + ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(async () => { + try { + loading.value = true; + await MenuAPI.batchAvailableMenu({ ids: selectIds.value, status }); + handleResetQuery(); + } catch (error: any) { + ElMessage.error(error.message); + } finally { + loading.value = false; + } + }).catch(() => { + ElMessage.info('已取消批量操作'); + }); } onMounted(() => { diff --git a/frontend/src/views/system/notice/index.vue b/frontend/src/views/system/notice/index.vue index e7a4ba88..ae3b49af 100644 --- a/frontend/src/views/system/notice/index.vue +++ b/frontend/src/views/system/notice/index.vue @@ -107,7 +107,11 @@ - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 将文件拖到此处,或 - 点击上传 -
- -
-
-
-
- - -
@@ -580,129 +509,6 @@ async function handleMoreClick(status: boolean) { } } - -// // 导出 -// function handleExport() { -// const filename = exportsFormData.filename -// ? exportsFormData.filename -// : props.contentConfig.permPrefix || "export"; -// const sheetname = exportsFormData.sheetname ? exportsFormData.sheetname : "sheet"; -// const workbook = new ExcelJS.Workbook(); -// const worksheet = workbook.addWorksheet(sheetname); -// const columns: Partial[] = []; -// cols.value.forEach((col) => { -// if (col.label && col.prop && exportsFormData.fields.includes(col.prop)) { -// columns.push({ header: col.label, key: col.prop }); -// } -// }); -// worksheet.columns = columns; -// if (exportsFormData.origin === ExportsOriginEnum.REMOTE) { -// if (props.contentConfig.exportsAction) { -// props.contentConfig.exportsAction(lastFormData).then((res) => { -// worksheet.addRows(res); -// workbook.xlsx -// .writeBuffer() -// .then((buffer) => { -// saveXlsx(buffer, filename as string); -// }) -// .catch((error) => console.log(error)); -// }); -// } else { -// ElMessage.error("未配置exportsAction"); -// } -// } else { -// worksheet.addRows( -// exportsFormData.origin === ExportsOriginEnum.SELECTED ? selectionData.value : pageData.value -// ); -// workbook.xlsx -// .writeBuffer() -// .then((buffer) => { -// saveXlsx(buffer, filename as string); -// }) -// .catch((error) => console.log(error)); -// } -// } - -// // 下载导入模板 -// function handleDownloadTemplate() { -// const importTemplate = props.contentConfig.importTemplate; -// if (typeof importTemplate === "string") { -// window.open(importTemplate); -// } else if (typeof importTemplate === "function") { -// importTemplate().then((response) => { -// const fileData = response.data; -// const fileName = decodeURI( -// response.headers["content-disposition"].split(";")[1].split("=")[1] -// ); -// saveXlsx(fileData, fileName); -// }); -// } else { -// ElMessage.error("未配置importTemplate"); -// } -// } - -// // 导入 -// function handleImport() { -// const importsAction = props.contentConfig.importsAction; -// if (importsAction === undefined) { -// ElMessage.error("未配置importsAction"); -// return; -// } -// // 获取选择的文件 -// const file = importFormData.files[0].raw as File; -// // 创建Workbook实例 -// const workbook = new ExcelJS.Workbook(); -// // 使用FileReader对象来读取文件内容 -// const fileReader = new FileReader(); -// // 二进制字符串的形式加载文件 -// fileReader.readAsArrayBuffer(file); -// fileReader.onload = (ev) => { -// if (ev.target !== null && ev.target.result !== null) { -// const result = ev.target.result as ArrayBuffer; -// // 从 buffer中加载数据解析 -// workbook.xlsx -// .load(result) -// .then((workbook) => { -// // 解析后的数据 -// const data = []; -// // 获取第一个worksheet内容 -// const worksheet = workbook.getWorksheet(1); -// if (worksheet) { -// // 获取第一行的标题 -// const fields: any[] = []; -// worksheet.getRow(1).eachCell((cell) => { -// fields.push(cell.value); -// }); -// // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行) -// for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) { -// const rowData: IObject = {}; -// const row = worksheet.getRow(rowNumber); -// // 遍历当前行的每个单元格 -// row.eachCell((cell, colNumber) => { -// // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中 -// rowData[fields[colNumber - 1]] = cell.value; -// }); -// // 将当前行的数据对象添加到数组中 -// data.push(rowData); -// } -// } -// if (data.length === 0) { -// ElMessage.error("未解析到数据"); -// return; -// } -// importsAction(data).then(() => { -// ElMessage.success("导入数据成功"); -// handleCloseImportModal(); -// handleRefresh(true); -// }); -// }) -// .catch((error) => console.log(error)); -// } else { -// ElMessage.error("读取文件失败"); -// } -// }; -// } - onMounted(() => { loadingData(); }); diff --git a/frontend/src/views/system/position/index.vue b/frontend/src/views/system/position/index.vue index a3361e07..de84dff6 100644 --- a/frontend/src/views/system/position/index.vue +++ b/frontend/src/views/system/position/index.vue @@ -95,7 +95,11 @@ - + + + - + + +