diff --git a/backend/app/api/v1/controllers/monitor/job_controller.py b/backend/app/api/v1/controllers/monitor/job_controller.py index 240d6ba9..b49a3004 100644 --- a/backend/app/api/v1/controllers/monitor/job_controller.py +++ b/backend/app/api/v1/controllers/monitor/job_controller.py @@ -25,7 +25,7 @@ router = APIRouter(route_class=OperationLogRoute) @router.get("/detail/{id}", summary="获取定时任务详情", description="获取定时任务详情") async def get_obj_detail_controller( id: int = Path(..., description="定时任务ID"), - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:query"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"])) ) -> JSONResponse: result_dict = await JobService.get_job_detail_service(id=id, auth=auth) logger.info(f"获取定时任务详情成功 {id}") @@ -35,7 +35,7 @@ async def get_obj_detail_controller( async def get_obj_list_controller( page: PaginationQueryParams = Depends(), search: JobQueryParams = Depends(), - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:query"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"])) ) -> JSONResponse: result_dict_list = await JobService.get_job_list_service(auth=auth, search=search, order_by=page.order_by) result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size) @@ -45,7 +45,7 @@ async def get_obj_list_controller( @router.post("/create", summary="创建定时任务", description="创建定时任务") async def create_obj_controller( data: JobCreateSchema, - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:create"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:create"])) ) -> JSONResponse: result_dict = await JobService.create_job_service(auth=auth, data=data) logger.info(f"创建定时任务成功: {result_dict}") @@ -54,7 +54,7 @@ async def create_obj_controller( @router.put("/update", summary="修改定时任务", description="修改定时任务") async def update_obj_controller( data: JobUpdateSchema, - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:update"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:update"])) ) -> JSONResponse: result_dict = await JobService.update_job_service(auth=auth, data=data) logger.info(f"修改定时任务成功: {result_dict}") @@ -63,7 +63,7 @@ async def update_obj_controller( @router.delete("/delete", summary="删除定时任务", description="删除定时任务") async def delete_obj_controller( ids: list[int] = Body(..., description="ID列表"), - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:delete"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:delete"])) ) -> JSONResponse: await JobService.delete_job_service(auth=auth, ids=ids) logger.info(f"删除定时任务成功: {id}") @@ -72,7 +72,7 @@ async def delete_obj_controller( @router.post('/export', summary="导出定时任务", description="导出定时任务") async def export_obj_list_controller( search: JobQueryParams = Depends(), - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:export"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:export"])) ) -> StreamingResponse: # 获取全量数据 result_dict_list = await JobService.get_job_list_service(search=search, auth=auth) @@ -89,7 +89,7 @@ async def export_obj_list_controller( @router.delete("/clear", summary="清空定时任务日志", description="清空定时任务日志") async def clear_obj_log_controller( - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:delete"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:delete"])) ) -> JSONResponse: await JobService.clear_job_service(auth=auth) logger.info(f"清空定时任务成功") @@ -99,13 +99,13 @@ async def clear_obj_log_controller( async def option_obj_controller( id: int = Body(..., description="定时任务ID"), option: int = Body(..., description="操作类型 1: 暂停 2: 恢复 3: 重启"), - auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:update"])) + auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:update"])) ) -> JSONResponse: await JobService.option_job_service(auth=auth, id=id, option=option) logger.info(f"操作定时任务成功: {id}") return SuccessResponse(msg="操作定时任务成功") -@router.get("/log", summary="获取定时任务日志", description="获取定时任务日志", dependencies=[Depends(AuthPermission(permissions=["system:job:query"]))]) +@router.get("/log", summary="获取定时任务日志", description="获取定时任务日志", dependencies=[Depends(AuthPermission(permissions=["monitor:job:query"]))]) async def get_job_log_controller(): data = [ { diff --git a/backend/app/api/v1/models/system/config_model.py b/backend/app/api/v1/models/system/config_model.py index f2a4293c..04d7614c 100644 --- a/backend/app/api/v1/models/system/config_model.py +++ b/backend/app/api/v1/models/system/config_model.py @@ -35,7 +35,7 @@ class ConfigModel(ModelBase): creator = relationship( "UserModel", foreign_keys=creator_id, - lazy="joined", + lazy="selectin", post_update=True, uselist=False ) diff --git a/backend/app/api/v1/models/system/user_model.py b/backend/app/api/v1/models/system/user_model.py index 97537cba..a9556d90 100644 --- a/backend/app/api/v1/models/system/user_model.py +++ b/backend/app/api/v1/models/system/user_model.py @@ -3,7 +3,6 @@ from datetime import datetime from sqlalchemy import Boolean, Column, String, Integer, DateTime, ForeignKey, Text from sqlalchemy.orm import relationship -from typing import Optional, List from app.core.base_model import ModelBase diff --git a/backend/app/api/v1/schemas/system/position_schema.py b/backend/app/api/v1/schemas/system/position_schema.py index eb5786f1..3cf04c31 100644 --- a/backend/app/api/v1/schemas/system/position_schema.py +++ b/backend/app/api/v1/schemas/system/position_schema.py @@ -4,7 +4,7 @@ from typing import Optional from pydantic import BaseModel, ConfigDict, Field from app.core.base_schema import BaseSchema - +from app.core.validator import DateTimeStr class PositionCreateSchema(BaseModel): """岗位创建模型""" @@ -22,3 +22,11 @@ class PositionUpdateSchema(PositionCreateSchema): class PositionOutSchema(PositionCreateSchema, BaseSchema): """岗位信息响应模型""" model_config = ConfigDict(from_attributes=True) + + +class PositionOptionsOut(PositionCreateSchema): + model_config = ConfigDict(from_attributes=True) + + id: int = Field(description="主键ID") + created_at: DateTimeStr = Field(description="创建时间") + updated_at: DateTimeStr = Field(description="更新时间") \ No newline at end of file diff --git a/backend/app/api/v1/schemas/system/role_schema.py b/backend/app/api/v1/schemas/system/role_schema.py index c75d1d9c..7928fc16 100644 --- a/backend/app/api/v1/schemas/system/role_schema.py +++ b/backend/app/api/v1/schemas/system/role_schema.py @@ -7,7 +7,7 @@ from app.api.v1.schemas.system.dept_schema import DeptOutSchema from app.api.v1.schemas.system.menu_schema import MenuOutSchema from app.core.base_schema import BaseSchema from app.core.validator import role_permission_request_validator - +from app.core.validator import DateTimeStr class RoleCreateSchema(BaseModel): """角色创建模型""" @@ -43,3 +43,13 @@ class RoleOutSchema(RoleCreateSchema, BaseSchema): menus: List[MenuOutSchema] = Field(default_factory=list, description='角色菜单列表') depts: List[DeptOutSchema] = Field(default_factory=list, description='角色部门列表') + + +class RoleOptionsOut(RoleCreateSchema): + model_config = ConfigDict(from_attributes=True) + + id: int = Field(description="主键ID") + created_at: DateTimeStr = Field(description="创建时间") + updated_at: DateTimeStr = Field(description="更新时间") + menus: List[MenuOutSchema] = Field(default_factory=list, description='角色菜单列表') + depts: List[DeptOutSchema] = Field(default_factory=list, description='角色部门列表') \ No newline at end of file diff --git a/backend/app/api/v1/schemas/system/user_schema.py b/backend/app/api/v1/schemas/system/user_schema.py index 8822e127..3fbbd688 100644 --- a/backend/app/api/v1/schemas/system/user_schema.py +++ b/backend/app/api/v1/schemas/system/user_schema.py @@ -3,8 +3,8 @@ from typing import Optional, List from pydantic import BaseModel, ConfigDict, Field, EmailStr, field_validator -from app.api.v1.schemas.system.position_schema import PositionOutSchema -from app.api.v1.schemas.system.role_schema import RoleOutSchema +from app.api.v1.schemas.system.position_schema import PositionOptionsOut +from app.api.v1.schemas.system.role_schema import RoleOptionsOut from app.api.v1.schemas.system.dept_schema import DeptOutSchema from app.core.validator import DateTimeStr, mobile_validator from app.core.base_schema import BaseSchema @@ -76,6 +76,7 @@ class UserCreateSchema(CurrentUserUpdateSchema): class UserUpdateSchema(UserCreateSchema): """更新""" + model_config = ConfigDict(from_attributes=True, exclude={"password"}) id: int = Field(..., description="主键ID") @@ -86,5 +87,5 @@ class UserOutSchema(UserCreateSchema, BaseSchema): last_login: Optional[DateTimeStr] = Field(default=None, description="最后登录时间") dept_name: Optional[str] = Field(default=None, description='部门名称') dept: Optional[DeptOutSchema] = Field(default=None, description='部门') - roles: List[RoleOutSchema] = Field(default=[], description='角色') - positions: List[PositionOutSchema] = Field(default=[], description='岗位') + roles: Optional[List[RoleOptionsOut]] = Field(default=[], description='角色') + positions: Optional[List[PositionOptionsOut]] = Field(default=[], description='岗位') diff --git a/backend/app/api/v1/services/monitor/cache_service.py b/backend/app/api/v1/services/monitor/cache_service.py index 9b9261c9..fa89947b 100644 --- a/backend/app/api/v1/services/monitor/cache_service.py +++ b/backend/app/api/v1/services/monitor/cache_service.py @@ -116,8 +116,11 @@ class CacheService: :param redis: Redis对象 :return: 操作缓存响应信息 """ - cache_keys = await RedisCURD(redis).get_keys + cache_keys = await RedisCURD(redis).get_keys() if cache_keys: await RedisCURD(redis).delete(*cache_keys) return True + + # 避免清除所有的缓存,而采用上面的方式,只清除本系统内指定的所有缓存 + # return await RedisCURD(redis).clear() diff --git a/backend/app/api/v1/services/monitor/online_service.py b/backend/app/api/v1/services/monitor/online_service.py index 54ba804b..f7eb67bb 100644 --- a/backend/app/api/v1/services/monitor/online_service.py +++ b/backend/app/api/v1/services/monitor/online_service.py @@ -57,8 +57,8 @@ class OnlineService: async def clear_online_service(cls, redis: Redis) -> bool: """强制下线在线用户""" # 删除 token - await RedisCURD(redis).delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*") - await RedisCURD(redis).delete(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:*") + await RedisCURD(redis).clear(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*") + await RedisCURD(redis).clear(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:*") logger.info(f"清除所有在线用户会话成功") return True diff --git a/backend/app/api/v1/services/system/user_service.py b/backend/app/api/v1/services/system/user_service.py index d5661a79..8573f508 100644 --- a/backend/app/api/v1/services/system/user_service.py +++ b/backend/app/api/v1/services/system/user_service.py @@ -176,7 +176,7 @@ class UserService: raise CustomException(msg="用户不存在") # 获取部门名称 if user.dept_id: - dept = await DeptCRUD(auth).get_by_id_crud(id=user.dept_id) + dept = await DeptCRUD(auth).get_by_id_crud(id=auth.user.dept_id) user.dept_name = dept.name if dept else None user_dict = UserOutSchema.model_validate(user).model_dump() @@ -189,7 +189,7 @@ class UserService: MenuOutSchema.model_validate(menu).model_dump() for role in auth.user.roles for menu in role.menus - if menu.status and menu.type in [1, 2] + if menu.status and menu.type in [1, 2, 4] ] user_dict["menus"] = menus return user_dict @@ -259,7 +259,7 @@ class UserService: # 更新密码 new_password_hash = PwdUtil.set_password_hash(password=data.password) - new_user = await UserCRUD(auth).change_password_crud(id=user.id, password_hash=new_password_hash) + new_user = await UserCRUD(auth).change_password_crud(id=data.id, password_hash=new_password_hash) return UserOutSchema.model_validate(new_user).model_dump() @classmethod diff --git a/backend/app/config/setting.py b/backend/app/config/setting.py index 99373ba8..3cd08dfa 100755 --- a/backend/app/config/setting.py +++ b/backend/app/config/setting.py @@ -192,7 +192,7 @@ class Settings(BaseSettings): UPLOAD_MACHINE: str = 'A' # 上传机器标识 ALLOWED_EXTENSIONS: list[str] = [ # 允许的文件类型 # 图片 - '.bmp', '.gif', '.jpg', '.jpeg', '.png', '.ico', + '.bmp', '.gif', '.jpg', '.jpeg', '.png', '.ico', '.svg', # 文档 '.csv', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.html', '.htm', '.txt', '.pdf', # 压缩包 diff --git a/backend/app/core/base_crud.py b/backend/app/core/base_crud.py index 33929298..742ecd99 100644 --- a/backend/app/core/base_crud.py +++ b/backend/app/core/base_crud.py @@ -55,8 +55,13 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): if hasattr(self.model, "creator"): sql = sql.options(selectinload(self.model.creator)) + # sql = await self.__filter_permissions(sql) + result: Result = await self.db.execute(sql) obj = result.scalars().unique().first() + # if not obj: + # raise CustomException(msg="该信息不存在") + return obj except Exception as e: raise CustomException(msg=f"获取查询失败: {str(e)}") @@ -82,6 +87,9 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): .where(*conditions) .order_by(*self.__order_by(order)) .distinct()) + # 预加载creator关系 + if hasattr(self.model, "creator"): + sql = sql.options(selectinload(self.model.creator)) sql = await self.__filter_permissions(sql) result: Result = await self.db.execute(sql) return result.scalars().unique().all() @@ -219,14 +227,18 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): async def __filter_permissions(self, sql: Select[Any]) -> Select[Any]: """过滤数据权限""" - # 1. 如果模型没有creator字段,则不需要过滤 + # 如果不需要检查数据权限,则直接返回 + if not self.current_user or not self.auth.check_data_scope: + return sql + + # 1. 如果模型没有创建人creator字段,则不需要权限判断 if not hasattr(self.model, "creator"): return sql sql = sql.options(selectinload(self.model.creator)) # 2. 超级管理员可以查看所有数据 - if not self.current_user or self.current_user.is_superuser: + if self.current_user.is_superuser: return sql # 3. 如果用户没有部门或角色,则只能查看自己的数据 @@ -243,24 +255,22 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): # 3: 本部门及以下数据权限 # 4: 全部数据权限 # 5: 自定义数据权限 - # 5. 处理各种数据权限范围 + + # 获取当前用户所绑定角色的数据权限范围 for role in self.current_user.roles: - # 如果有全部数据权限,直接返回所有数据 - if role.data_scope == 4: - return sql - + for dept in role.depts: + dept_ids.add(dept.id) + data_scopes.add(role.data_scope) - # 如果是自定义权限,添加自定义部门 - if role.data_scope == 5: - dept_ids.update({dept.id for dept in role.depts}) - conditions = [] - - # 5. 处理各种数据权限范围 + if 4 in data_scopes: + # 4、全部数据权限 + return sql + if 1 in data_scopes: # 1、仅本人数据 - conditions.append(self.model.creator_id == self.current_user.id) - + return sql.where(self.model.creator_id == self.current_user.id) + if 2 in data_scopes: # 2、本部门数据 dept_ids.add(self.current_user.dept_id) @@ -270,16 +280,11 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): dept_objs = await CRUDBase(DeptModel, self.auth).list() id_map = get_child_id_map(dept_objs) dept_child_ids = get_child_recursion(id=self.current_user.dept_id, id_map=id_map) - dept_ids.update(dept_child_ids) - - if dept_ids: - conditions.append(self.model.creator.has(UserModel.dept_id.in_(list(dept_ids)))) - - # 6. 组合所有条件 - if conditions: - return sql.where(and_(*conditions)) - - return sql.where(self.model.creator_id == self.current_user.id) + for child_id in dept_child_ids: + dept_ids.add(child_id) + + # 5、自定义权限 + return sql.where(self.model.creator.has(UserModel.dept_id.in_(list(dept_ids)))) def __order_by(self, order_by: List[Dict[str, str]]) -> List[ColumnElement]: """ diff --git a/frontend/src/api/system/menu.ts b/frontend/src/api/system/menu.ts index 138546df..084b4833 100644 --- a/frontend/src/api/system/menu.ts +++ b/frontend/src/api/system/menu.ts @@ -103,8 +103,13 @@ export interface MenuForm { hidden?: boolean; always_show?: boolean; title?: string; - params?: { key: string; value: string; }[]; + params?: KeyValue[]; affix?: boolean; status?: boolean; description?: string; } + +export interface KeyValue { + key: string; + value: string; +} \ No newline at end of file diff --git a/frontend/src/api/system/user.ts b/frontend/src/api/system/user.ts index bd9eb9ee..7ef0ecb4 100644 --- a/frontend/src/api/system/user.ts +++ b/frontend/src/api/system/user.ts @@ -67,7 +67,7 @@ export const UserAPI = { }, getUserDetail(query: number) { - return request({ + return request>({ url: `/system/user/detail/${query}`, method: "get", }); @@ -246,9 +246,9 @@ export interface UserForm { dept_id?: number; dept_name?: string; role_ids?: number[]; - roleNames?: string; + roleNames?: string[]; position_ids?: number[]; - positionNames?: string; + positionNames?: string[]; password?: string; gender?: number; email?: string; diff --git a/frontend/src/store/modules/user.store.ts b/frontend/src/store/modules/user.store.ts index 631f7a6f..a61c8b88 100644 --- a/frontend/src/store/modules/user.store.ts +++ b/frontend/src/store/modules/user.store.ts @@ -26,7 +26,7 @@ export const useUserStore = defineStore("user", { // 获取用户信息 async getUserInfo() { const response = await UserAPI.getCurrentUserInfo(); - const routers = response.data.data.menus; + const routers: MenuTable[] = response.data.data.menus || []; delete response.data.data.menus; this.setRoute(routers); this.basicInfo = { ...this.basicInfo, ...response.data.data }; @@ -37,7 +37,8 @@ export const useUserStore = defineStore("user", { this.basicInfo = info; }, - setRoute(routers: any) { + // 设置路由 + setRoute(routers: MenuTable[]) { this.routeList = routers; this.hasGetRoute = true; }, diff --git a/frontend/src/views/current/profile.vue b/frontend/src/views/current/profile.vue index 5e27f363..9e4ad08c 100644 --- a/frontend/src/views/current/profile.vue +++ b/frontend/src/views/current/profile.vue @@ -122,18 +122,18 @@ 基本设置
- + - + - + - + @@ -159,9 +159,9 @@ 安全设置
- + - + @@ -169,7 +169,7 @@ - + @@ -177,7 +177,7 @@ - + @@ -507,4 +507,13 @@ onMounted(async () => { } } } + +/* 修复表单输入框清除按钮导致的宽度变化问题 */ +.el-input { + transition: none !important; +} + +.el-input__wrapper { + transition: none !important; +} diff --git a/frontend/src/views/monitor/job/index.vue b/frontend/src/views/monitor/job/index.vue index 423b416f..cf0003ff 100644 --- a/frontend/src/views/monitor/job/index.vue +++ b/frontend/src/views/monitor/job/index.vue @@ -879,7 +879,6 @@ const handleClear = () => { try { loading.value = true; await JobAPI.clearJob(); - ElMessage.success("清空成功"); handleResetQuery(); } catch (error: any) { console.error(error); diff --git a/frontend/src/views/system/auth/components/Login.vue b/frontend/src/views/system/auth/components/Login.vue index 78650e43..e24840be 100644 --- a/frontend/src/views/system/auth/components/Login.vue +++ b/frontend/src/views/system/auth/components/Login.vue @@ -101,10 +101,10 @@