feat: Add role code to system_role.json and update system_users.json creator_id to null

fix: Refactor initialize.py to handle nested children data during initialization

feat: Implement tree structure traversal functions in common_util.py

chore: Update requirements.txt to specify sqlalchemy-crud-plus version and add rich

refactor: Change API endpoints in dept.ts and menu.ts to return tree structure

feat: Add code field to role, dept, and menu interfaces in respective TypeScript files

fix: Update dept and role Vue components to display and handle code field

docs: Add comprehensive project documentation for FastAPI Vue3 Admin
This commit is contained in:
zhangtao
2025-09-15 00:19:11 +08:00
parent 1f7c934e41
commit e159b31e62
47 changed files with 2543 additions and 2206 deletions
@@ -9,10 +9,10 @@ from app.core.base_schema import BaseSchema
class ApplicationCreateSchema(BaseModel):
"""应用创建模型"""
name: str = Field(..., max_length=64, description='应用名称')
access_url: str = Field(..., max_length=500, description="访问地址")
access_url: str = Field(..., max_length=255, description="访问地址")
icon_url: Optional[str] = Field(None, max_length=300, description="应用图标URL")
status: bool = Field(True, description="是否启用(True:启用 False:禁用)")
description: Optional[str] = Field(None, max_length=1000, description="应用描述")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
class ApplicationUpdateSchema(ApplicationCreateSchema):
@@ -10,7 +10,7 @@ class DemoCreateSchema(BaseModel):
"""新增模型"""
name: str = Field(..., max_length=50, description='名称')
status: bool = Field(True, description="是否启用(True:启用 False:禁用)")
description: Optional[str] = Field(None, max_length=255, description="描述")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
class DemoUpdateSchema(DemoCreateSchema):
@@ -22,7 +22,7 @@ class JobCreateSchema(BaseModel):
trigger_args: Optional[str] = Field(default=None, description='触发器参数')
start_date: Optional[str] = Field(default=None, description='开始时间')
end_date: Optional[str] = Field(default=None, description='结束时间')
description: Optional[str] = Field(default=None, description='备注说明')
description: Optional[str] = Field(default=None, max_length=255, description='描述')
status: Optional[bool] = Field(default=False, description='任务状态:启动,停止')
@@ -8,11 +8,11 @@ from app.core.base_schema import BaseSchema
class ConfigCreateSchema(BaseModel):
"""配置创建模型"""
config_name: str = Field(..., max_length=500, description="参数名称")
config_key: str = Field(..., max_length=500, description="参数键名")
config_value: str = Field(..., max_length=500, description="参数键值")
config_name: str = Field(..., max_length=255, description="参数名称")
config_key: str = Field(..., max_length=255, description="参数键名")
config_value: str = Field(..., max_length=255, description="参数键值")
config_type: bool = Field(..., description="系统内置((True:是 False:否))")
description: Optional[str] = Field(None, max_length=500, description="备注说明")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
class ConfigUpdateSchema(ConfigCreateSchema):
@@ -161,26 +161,27 @@ class ConfigService:
@classmethod
async def init_config_service(cls, redis: Redis) -> bool:
async with AsyncSessionLocal() as session:
auth = AuthSchema(db=session)
config_obj = await ConfigCRUD(auth).get_obj_list_crud()
if not config_obj:
raise CustomException(msg="系统配置不存在")
try:
# 保存到Redis并设置过期时间
for config in config_obj:
redis_key = (f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{config.config_key}")
config_obj_dict = ConfigOutSchema.model_validate(config).model_dump()
value = json.dumps(config_obj_dict, ensure_ascii=False)
result = await RedisCURD(redis).set(
key=redis_key,
value=value,
)
if not result:
logger.error(f"初始化系统配置失败: {config_obj_dict}")
raise CustomException(msg="初始化系统配置失败")
except Exception as e:
logger.error(f"初始化系统配置失败: {e}")
raise CustomException(msg="初始化系统配置失败")
async with session.begin():
auth = AuthSchema(db=session)
config_obj = await ConfigCRUD(auth).get_obj_list_crud()
if not config_obj:
raise CustomException(msg="系统配置不存在")
try:
# 保存到Redis并设置过期时间
for config in config_obj:
redis_key = (f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{config.config_key}")
config_obj_dict = ConfigOutSchema.model_validate(config).model_dump()
value = json.dumps(config_obj_dict, ensure_ascii=False)
result = await RedisCURD(redis).set(
key=redis_key,
value=value,
)
if not result:
logger.error(f"初始化系统配置失败: {config_obj_dict}")
raise CustomException(msg="初始化系统配置失败")
except Exception as e:
logger.error(f"初始化系统配置失败: {e}")
raise CustomException(msg="初始化系统配置失败")
@classmethod
async def get_init_config_service(cls, redis: Redis) -> Dict:
@@ -22,16 +22,14 @@ from .schema import (
DeptRouter = APIRouter(route_class=OperationLogRoute, prefix="/dept", tags=["部门管理"])
@DeptRouter.get("/list", summary="查询部门", description="查询部门")
async def get_obj_list_controller(
page: PaginationQueryParams = Depends(),
@DeptRouter.get("/tree", summary="查询部门", description="查询部门")
async def get_dept_tree_controller(
search: DeptQueryParams = Depends(),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:dept:query"]))
) -> JSONResponse:
result_dict_list = await DeptService.get_dept_list_service(search=search, auth=auth, 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)
logger.info(f"查询部门成功")
return SuccessResponse(data=result_dict, msg="查询部门成功")
result_dict_list = await DeptService.get_dept_tree_service(search=search, auth=auth)
logger.info(f"查询部门树成功")
return SuccessResponse(data=result_dict_list, msg="查询部门成功")
@DeptRouter.get("/detail/{id}", summary="查询部门详情", description="查询部门详情")
@@ -51,6 +51,16 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]):
obj.parent_name = parent_map.get(obj.parent_id)
return obj_list
async def get_tree_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[DeptModel]:
"""
获取部门树形列表
:param search: 搜索条件
:param order_by: 排序字段
:return: 部门树形列表
"""
return await self.get_tree_list(search=search, order_by=order_by, children_attr='children')
async def set_available_crud(self, ids: List[int], status: bool) -> None:
"""
批量设置部门可用状态
+7 -11
View File
@@ -8,26 +8,22 @@ from app.core.base_model import ModelMixin
class DeptModel(ModelMixin):
"""
部门表 - 用于存储组织架构中的部门信息 - SQLAlchemy 2.0 语法
支持层级结构,兼容 MySQL 和 PostgreSQL
部门表
"""
__tablename__ = "system_dept"
__table_args__ = ({'comment': '部门表'})
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
# 基础字段
name: Mapped[str] = mapped_column(String(40),nullable=False,unique=True,comment="部门名称")
order: Mapped[int] = mapped_column(Integer,nullable=False,default=999,comment="显示排序")
code: Mapped[Optional[str]] = mapped_column(String(20),nullable=True,unique=True,comment="部门编码")
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("system_dept.id", ondelete="SET NULL", onupdate="CASCADE"), default=None, index=True, comment="父级部门ID")
# parent: Mapped[Optional["DeptModel"]] = relationship("DeptModel", cascade="all, delete-orphan", uselist=False)
parent: Mapped[Optional['DeptModel']] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[Optional[list['DeptModel']]] = relationship(init=False, back_populates='parent')
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("system_dept.id", ondelete="SET NULL", onupdate="CASCADE"), default=None, index=True, comment="父级部门ID")
parent: Mapped[Optional['DeptModel']] = relationship(back_populates='children', remote_side=[id],uselist=False)
children: Mapped[Optional[List['DeptModel']]] = relationship(back_populates='parent')
# 角色关联关系
roles: Mapped[List["RoleModel"]] = relationship(secondary="system_role_depts", back_populates="depts", lazy="selectin")
# 用户关联关系
users: Mapped[List["UserModel"]] = relationship(back_populates="dept", lazy="selectin")
# code: Mapped[Optional[str]] = mapped_column(String(20),nullable=True,unique=True,comment="部门编码")
# leader_id: Mapped[Optional[int]] = mapped_column(Integer,nullable=True,comment="负责人ID")
users: Mapped[List["UserModel"]] = relationship(back_populates="dept", lazy="selectin")
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Optional
from typing import Optional, List
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.base_schema import BaseSchema
@@ -10,9 +10,10 @@ class DeptCreateSchema(BaseModel):
"""部门创建模型"""
name: str = Field(..., max_length=40, description="部门名称")
order: int = Field(default=1, ge=0, description="显示顺序")
code: Optional[str] = Field(default=None, max_length=60, description="部门编码")
status: bool = Field(default=True, description="是否启用(True:启用 False:禁用)")
parent_id: Optional[int] = Field(default=None, ge=0, description="父部门ID")
description: Optional[str] = Field(default=None, max_length=500, description="备注说明")
description: Optional[str] = Field(default=None, max_length=255, description="备注说明")
@field_validator('name')
@classmethod
@@ -8,7 +8,8 @@ from app.utils.common_util import (
get_parent_id_map,
get_parent_recursion,
get_child_id_map,
get_child_recursion
get_child_recursion,
traversal_to_tree
)
from ..auth.schema import AuthSchema
from .crud import DeptCRUD
@@ -38,21 +39,25 @@ class DeptService:
return DeptOutSchema.model_validate(dept).model_dump()
@classmethod
async def get_dept_list_service(cls, auth: AuthSchema, search: DeptQueryParams, order_by: List[Dict] = None) -> List[Dict]:
async def get_dept_tree_service(cls, auth: AuthSchema, search: DeptQueryParams, order_by: List[Dict] = None) -> List[Dict]:
"""
获取部门列表service
获取部门树形列表service
:param auth: 认证对象
:param search: 查询参数对象
:param order_by: 排序参数
:return: 部门列表对象
:return: 部门树形列表对象
"""
if order_by:
order_by = eval(order_by)
else:
order_by = [{"order": "asc"}]
dept_list = await DeptCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
return [DeptOutSchema.model_validate(dept).model_dump() for dept in dept_list]
# 使用树形结构查询,预加载children关系
dept_list = await DeptCRUD(auth).get_tree_list_crud(search=search.__dict__, order_by=order_by)
# 转换为字典列表
dept_dict_list = [DeptOutSchema.model_validate(dept).model_dump() for dept in dept_list]
# 使用traversal_to_tree构建树形结构
return traversal_to_tree(dept_dict_list)
@classmethod
async def create_dept_service(cls, auth: AuthSchema, data: DeptCreateSchema) -> Dict:
@@ -129,4 +134,4 @@ class DeptService:
disable_ids = get_child_recursion(id=dept_id, id_map=id_map)
total_ids.extend(disable_ids)
await DeptCRUD(auth).set_available_crud(ids=total_ids, status=data.status)
await DeptCRUD(auth).set_available_crud(ids=total_ids, status=data.status)
@@ -13,7 +13,7 @@ class DictTypeCreateSchema(BaseModel):
dict_name: str = Field(..., min_length=1, max_length=100, description='字典名称')
dict_type: str = Field(..., min_length=1, max_length=100, description='字典类型')
status: Optional[bool] = Field(default=None, description='状态(1正常 0停用)')
description: Optional[str] = Field(None, max_length=255, description="描述")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
@field_validator('dict_name')
def validate_dict_name(cls, value: str):
@@ -186,35 +186,36 @@ class DictDataService:
return [DictDataOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def init_dict_service(cls, redis: Redis, db: AsyncSession):
async def init_dict_service(cls, redis: Redis):
"""应用初始化: 获取所有字典类型对应的字典数据信息并缓存service"""
async with AsyncSessionLocal() as session:
auth = AuthSchema(db=session)
obj_list = await DictTypeCRUD(auth).get_obj_list_crud()
if not obj_list:
logger.warning("未找到任何字典类型数据")
return
for obj in obj_list:
dict_type = obj.dict_type
dict_data_list = await DictDataCRUD(auth).get_obj_list_crud(search={'dict_type': dict_type})
if not dict_data_list:
logger.warning(f"字典类型 {dict_type} 未找到对应的字典数据")
continue
dict_data = [DictDataOutSchema.model_validate(row).model_dump() for row in dict_data_list if row]
# 保存到Redis并设置过期时间
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{dict_type}"
try:
value = json.dumps(dict_data, ensure_ascii=False)
await RedisCURD(redis).set(
key=redis_key,
value=value,
)
except Exception as e:
logger.error(f"初始化字典数据失败: {e}")
raise CustomException(msg=f"初始化字典数据失败 {e}")
async with session.begin():
auth = AuthSchema(db=session)
obj_list = await DictTypeCRUD(auth).get_obj_list_crud()
if not obj_list:
logger.warning("未找到任何字典类型数据")
return
for obj in obj_list:
dict_type = obj.dict_type
dict_data_list = await DictDataCRUD(auth).get_obj_list_crud(search={'dict_type': dict_type})
if not dict_data_list:
logger.warning(f"字典类型 {dict_type} 未找到对应的字典数据")
continue
dict_data = [DictDataOutSchema.model_validate(row).model_dump() for row in dict_data_list if row]
# 保存到Redis并设置过期时间
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{dict_type}"
try:
value = json.dumps(dict_data, ensure_ascii=False)
await RedisCURD(redis).set(
key=redis_key,
value=value,
)
except Exception as e:
logger.error(f"初始化字典数据失败: {e}")
raise CustomException(msg=f"初始化字典数据失败 {e}")
@classmethod
async def get_init_dict_service(cls, redis: Redis, dict_type: str)->List[Dict]:
@@ -19,7 +19,7 @@ class OperationLogCreateSchema(BaseModel):
response_code: Optional[int] = Field(default=None, description="响应状态码")
response_json: Optional[str] = Field(default=None, description="响应 JSON 数据")
process_time: Optional[str] = Field(default=None, description="处理时间")
description: Optional[str] = Field(default=None, max_length=255, description="备注")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
creator_id: Optional[int] = Field(default=None, description="创建人ID")
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Body, Depends, Path, Query
from fastapi import APIRouter, Body, Depends, Path
from fastapi.responses import JSONResponse
from app.common.response import SuccessResponse
from app.common.request import PaginationService
from app.core.dependencies import AuthPermission
from app.core.base_schema import BatchSetAvailable
from app.core.base_params import PaginationQueryParams
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
from ..auth.schema import AuthSchema
@@ -21,17 +19,14 @@ from .schema import (
MenuRouter = APIRouter(route_class=OperationLogRoute, prefix="/menu", tags=["菜单管理"])
@MenuRouter.get("/list", summary="查询菜单", description="查询菜单")
async def get_obj_list_controller(
page: PaginationQueryParams = Depends(),
@MenuRouter.get("/tree", summary="查询菜单", description="查询菜单")
async def get_menu_tree_controller(
search: MenuQueryParams = Depends(),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:menu:query"]))
) -> JSONResponse:
result_dict_list = await MenuService.get_menu_list_service(search=search, auth=auth)
menu_items = await MenuService.convert_to_menu(result_dict_list)
result_dict = await PaginationService.get_page_obj(data_list=menu_items, page_no=page.page_no, page_size=page.page_size)
logger.info(f"查询菜单成功")
return SuccessResponse(data=result_dict, msg="查询菜单成功")
result_dict_list = await MenuService.get_menu_tree_service(search=search, auth=auth)
logger.info(f"查询菜单树成功")
return SuccessResponse(data=result_dict_list, msg="查询菜单树成功")
@MenuRouter.get("/detail/{id}", summary="查询菜单详情", description="查询菜单详情")
@@ -82,4 +77,4 @@ async def batch_set_available_obj_controller(
) -> JSONResponse:
await MenuService.set_menu_available_service(data=data, auth=auth)
logger.info(f"批量修改菜单状态成功: {data.ids}")
return SuccessResponse(msg="批量修改菜单状态成功")
return SuccessResponse(msg="批量修改菜单状态成功")
@@ -51,6 +51,16 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]):
obj.parent_name = parent_map.get(obj.parent_id)
return obj_list
async def get_tree_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[MenuModel]:
"""
获取菜单树形列表
:param search: 搜索条件
:param order_by: 排序字段
:return: 菜单树形列表
"""
return await self.get_tree_list(search=search, order_by=order_by, children_attr='children')
async def set_available_crud(self, ids: List[int], status: bool) -> None:
"""
批量设置菜单可用状态
@@ -24,7 +24,7 @@ class MenuModel(ModelMixin):
"""
__tablename__ = "system_menu"
__table_args__ = ({'comment': '菜单表'})
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
name: Mapped[str] = mapped_column(String(50), nullable=False, comment='菜单名称', unique=True)
type: Mapped[int] = mapped_column(Integer, nullable=False, default=2, comment='菜单类型(1:目录 2:菜单 3:按钮/权限 4:链接)')
order: Mapped[int] = mapped_column(Integer, nullable=False, default=999, comment='显示排序')
@@ -42,12 +42,11 @@ class MenuModel(ModelMixin):
affix: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, comment='是否固定标签页(True:是 False:否)')
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('system_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID')
# parent: Mapped[Optional['MenuModel']] = relationship(cascade='all, delete-orphan', primaryjoin="MenuModel.parent_id == MenuModel.id", uselist=False)
parent: Mapped[Optional['MenuModel']] = relationship(back_populates='children', remote_side=[id], uselist=False)
children: Mapped[Optional[List['MenuModel']]] = relationship(back_populates='parent')
# 角色关联关系
roles: Mapped[List["RoleModel"]] = relationship(secondary="system_role_menus", back_populates="menus", lazy="selectin")
# link: Mapped[Optional[str]] = mapped_column(String(255), comment='外链地址')
# iframe: Mapped[Optional[str]] = mapped_column(String(255), comment='内嵌iframe地址')
parent: Mapped[Optional['MenuModel']] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[Optional[list['MenuModel']]] = relationship(init=False, back_populates='parent')
# iframe: Mapped[Optional[str]] = mapped_column(String(255), comment='内嵌iframe地址')
@@ -26,7 +26,7 @@ class MenuCreateSchema(BaseModel):
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=1, description="父菜单ID")
description: Optional[str] = Field(default=None, max_length=500, description="备注说明")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
@model_validator(mode='after')
def validate_fields(self):
@@ -41,5 +41,5 @@ class MenuUpdateSchema(MenuCreateSchema):
class MenuOutSchema(MenuCreateSchema, BaseSchema):
"""菜单响应模型"""
model_config = ConfigDict(from_attributes=True)
parent_name: Optional[str] = Field(default=None, max_length=50, description="父菜单名称")
@@ -8,7 +8,8 @@ from app.utils.common_util import (
get_parent_id_map,
get_parent_recursion,
get_child_id_map,
get_child_recursion
get_child_recursion,
traversal_to_tree
)
from ..auth.schema import AuthSchema
from .param import MenuQueryParams
@@ -32,35 +33,25 @@ class MenuService:
return menu_dict
@classmethod
async def get_menu_list_service(cls, auth: AuthSchema, search: MenuQueryParams, order_by: List[Dict] = None) -> List[Dict]:
async def get_menu_tree_service(cls, auth: AuthSchema, search: MenuQueryParams, order_by: List[Dict] = None) -> List[Dict]:
"""
获取菜单树形列表service
:param auth: 认证对象
:param search: 查询参数对象
:param order_by: 排序参数
:return: 菜单树形列表对象
"""
if order_by:
order_by = eval(order_by)
else:
order_by = [{"order": "asc"}]
menu_list = await MenuCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
# 使用树形结构查询,预加载children关系
menu_list = await MenuCRUD(auth).get_tree_list_crud(search=search.__dict__, order_by=order_by)
# 转换为字典列表
menu_dict_list = [MenuOutSchema.model_validate(menu).model_dump() for menu in menu_list]
return menu_dict_list
@staticmethod
async def convert_to_menu(menu_list):
menu_dict = {}
result = []
for menu in menu_list:
if isinstance(menu, dict):
menu_dict[menu['id']] = menu
menu['key']=menu['id']
for menu in menu_list:
if isinstance(menu, dict):
parent_id = menu.get('parent_id')
if parent_id is None or parent_id not in menu_dict:
result.append(menu)
else:
parent_menu = menu_dict[parent_id]
if 'children' not in parent_menu:
parent_menu['children'] = []
parent_menu['children'].append(menu)
return result
# 使用traversal_to_tree构建树形结构
return traversal_to_tree(menu_dict_list)
@classmethod
async def create_menu_service(cls, auth: AuthSchema, data: MenuCreateSchema) -> Dict:
@@ -122,4 +113,4 @@ class MenuService:
disable_ids = get_child_recursion(id=menu_id, id_map=id_map)
total_ids.extend(disable_ids)
await MenuCRUD(auth).set_available_crud(ids=total_ids, status=data.status)
await MenuCRUD(auth).set_available_crud(ids=total_ids, status=data.status)
@@ -12,7 +12,7 @@ class NoticeCreateSchema(BaseModel):
notice_type: str = Field(..., description='公告类型(1通知 2公告)')
notice_content: str = Field(..., description='公告内容')
status: bool = Field(default=True, description="是否启用(True:启用 False:禁用)")
description: Optional[str] = Field(default=None, max_length=255, description="公告描述")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
class NoticeUpdateSchema(NoticeCreateSchema):
@@ -11,7 +11,7 @@ class PositionCreateSchema(BaseModel):
name: str = Field(..., max_length=40, description="岗位名称")
order: Optional[int] = Field(default=1, ge=1, description='显示排序')
status: bool = Field(default=True, description="是否启用(True:启用 False:禁用)")
description: Optional[str] = Field(default=None, description="备注说明")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
class PositionUpdateSchema(PositionCreateSchema):
@@ -10,8 +10,6 @@ from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column
from app.core.base_model import MappedBase, CreatorMixin
from app.api.v1.module_system.dept.model import DeptModel
from app.api.v1.module_system.menu.model import MenuModel
class RoleMenusModel(MappedBase):
@@ -13,10 +13,11 @@ from ..menu.schema import MenuOutSchema
class RoleCreateSchema(BaseModel):
"""角色创建模型"""
name: str = Field(..., max_length=40, description="角色名称")
code: Optional[str] = Field(default=None, max_length=40, description="角色编码")
order: Optional[int] = Field(default=1, ge=1, description='显示排序')
data_scope: Optional[int] = Field(default=1, ge=1, le=5, description='数据权限范围')
status: bool = Field(default=True, description="是否启用")
description: Optional[str] = Field(None, max_length=255, description="角色描述")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
class RolePermissionSettingSchema(BaseModel):
@@ -29,7 +29,7 @@ class UserRegisterSchema(BaseModel):
password: str = Field(..., max_length=128, description="密码哈希值")
role_ids: Optional[List[int]] = Field(default=[2], description='角色ID')
creator_id: Optional[int] = Field(default=1, description='创建人ID')
description: Optional[str] = Field(default=f'注册用户{username}', max_length=255, description="备注")
description: Optional[str] = Field(default=None, max_length=255, description="备注")
@field_validator("mobile")
@classmethod
@@ -69,7 +69,7 @@ class UserCreateSchema(CurrentUserUpdateSchema):
password: str = Field(..., max_length=128, description="密码哈希值")
status: bool = Field(default=True, description="是否可用")
is_superuser: bool = Field(default=False, description="是否超管")
description: Optional[str] = Field(None, max_length=255, description="备注")
description: Optional[str] = Field(default=None, max_length=255, description="备注")
dept_id: Optional[int] = Field(default=None, description='部门ID')
role_ids: Optional[List[int]] = Field(default=[], description='角色ID')
@@ -9,6 +9,7 @@ from app.core.exceptions import CustomException
from app.core.hash_bcrpy import PwdUtil
from app.core.base_schema import BatchSetAvailable, UploadResponseSchema
from app.core.logger import logger
from app.utils.common_util import traversal_to_tree
from app.utils.excel_util import ExcelUtil
from app.utils.upload_util import UploadUtil
from ..position.crud import PositionCRUD
@@ -184,16 +185,25 @@ class UserService:
# 获取菜单权限
if auth.user.is_superuser:
menu_all = await MenuCRUD(auth).get_list_crud(search={'type': ('in', [1, 2, 4]), 'status': True})
# 使用树形结构查询,预加载children关系
menu_all = await MenuCRUD(auth).get_tree_list_crud(search={'type': ('in', [1, 2, 4]), 'status': True})
menus = [MenuOutSchema.model_validate(menu).model_dump() for menu in menu_all]
else:
menus = [
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, 4]
]
user_dict["menus"] = menus
# 收集用户所有角色的菜单ID
menu_ids = []
for role in auth.user.roles:
for menu in role.menus:
if menu.status and menu.type in [1, 2, 4]:
menu_ids.append(menu.id)
# 使用树形结构查询,预加载children关系
if menu_ids:
menu_all = await MenuCRUD(auth).get_tree_list_crud(search={'id': ('in', menu_ids)})
menus = [MenuOutSchema.model_validate(menu).model_dump() for menu in menu_all]
else:
menus = []
user_dict["menus"] = traversal_to_tree(menus)
return user_dict
@classmethod