mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 04:46:26 +00:00
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:
@@ -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:
|
||||
"""
|
||||
批量设置部门可用状态
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user