diff --git a/backend/auth/apps/permissions/urls.py b/backend/auth/apps/permissions/urls.py index f9fec4e..30acde6 100644 --- a/backend/auth/apps/permissions/urls.py +++ b/backend/auth/apps/permissions/urls.py @@ -44,7 +44,9 @@ async def create_permission( name=permission_data.name, code=permission_data.code, parentid=permission_data.parentid, + route=permission_data.route, description=permission_data.description, + sort=permission_data.sort, ) db.add(db_permission) @@ -112,6 +114,7 @@ async def get_permission_tree( "parentid": perm.parentid, "route": perm.route, "description": perm.description, + "sort": perm.sort or 0, "createtime": perm.createtime, "updatetime": perm.updatetime, "children": [] @@ -128,6 +131,16 @@ async def get_permission_tree( # 子节点,添加到父节点的 children perm_dict[parentid]["children"].append(perm_data) + # 递归排序:按 sort 字段升序排列 + def sort_tree(nodes): + nodes.sort(key=lambda n: n.get("sort", 0)) + for node in nodes: + if node.get("children"): + sort_tree(node["children"]) + return nodes + + tree = sort_tree(tree) + # 记录查询日志 await create_operation_log( db, current_user.id, current_user.username, "search", "permission", @@ -187,6 +200,7 @@ async def get_menu( "parentid": perm.parentid, "route": perm.route, "description": perm.description, + "sort": perm.sort or 0, "createtime": perm.createtime, "updatetime": perm.updatetime, "children": [] @@ -203,6 +217,16 @@ async def get_menu( # 子节点,添加到父节点的 children perm_dict[parentid]["children"].append(perm_data) + # 递归排序:按 sort 字段升序排列 + def sort_tree(nodes): + nodes.sort(key=lambda n: n.get("sort", 0)) + for node in nodes: + if node.get("children"): + sort_tree(node["children"]) + return nodes + + tree = sort_tree(tree) + # 记录查询日志 await create_operation_log( db, current_user.id, current_user.username, "search", "permission", @@ -222,7 +246,7 @@ async def get_permission( current_user: User = Depends(get_current_user), ): """获取单个权限""" - permission = await permission_crud.get(db, id=permission_id) + permission = await permission_crud.get(db, id=permission_id, schema_to_select=PermissionResponse, return_as_model=True) if not permission: raise HTTPException(status_code=404, detail="权限不存在") @@ -271,7 +295,7 @@ async def update_permission( raise HTTPException(status_code=404, detail="权限不存在") # 记录操作日志 changed = {} - for field in ["name", "code", "parentid", "description"]: + for field in ["name", "code", "parentid", "route", "sort", "description"]: old_val = getattr(old_permission, field, None) new_val = update_data.get(field) if old_val != new_val: @@ -452,6 +476,7 @@ async def get_current_user_permissions( "parentid": perm.parentid, "route": perm.route, "description": perm.description, + "sort": perm.sort or 0, "createtime": perm.createtime, "updatetime": perm.updatetime, "children": [] @@ -468,6 +493,16 @@ async def get_current_user_permissions( # 子节点,添加到父节点的 children perm_dict[parentid]["children"].append(perm_data) + # 递归排序:按 sort 字段升序排列 + def sort_tree(nodes): + nodes.sort(key=lambda n: n.get("sort", 0)) + for node in nodes: + if node.get("children"): + sort_tree(node["children"]) + return nodes + + menu_tree = sort_tree(menu_tree) + # 记录查询日志 await create_operation_log( db, current_user.id, current_user.username, "search", "permission", diff --git a/backend/auth/init_data.py b/backend/auth/init_data.py index a6db11e..85a0096 100644 --- a/backend/auth/init_data.py +++ b/backend/auth/init_data.py @@ -1,452 +1,451 @@ -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, text -from models import Role, User, Permission, SystemSetting -from utils.password import get_password_hash - - -async def init_permissions(db: AsyncSession): - """初始化权限数据(树形结构)""" - # 定义需要初始化的权限(树形结构) - # parentid: 0 = 顶级模块, >0 = 子权限 - permission_tree = [ - # ======================================== - # 顶级模块 (DB ID: 1,2,3) - # ======================================== - {"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块"}, - {"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块"}, - {"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块"}, - - # ======================================== - # 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6) - # ======================================== - {"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块"}, - {"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块"}, - {"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块"}, - - # ======================================== - # 用户管理操作 (parentid=4, DB ID: 7-14) - # ======================================== - {"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表"}, - {"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情"}, - {"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户"}, - {"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户"}, - {"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息"}, - {"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户"}, - {"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据"}, - {"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户"}, - - # ======================================== - # 角色管理操作 (parentid=5, DB ID: 15-22) - # ======================================== - {"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表"}, - {"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情"}, - {"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色"}, - {"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色"}, - {"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息"}, - {"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色"}, - {"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据"}, - {"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限"}, - - # ======================================== - # 菜单管理操作 (parentid=6, DB ID: 23-29) - # ======================================== - {"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表"}, - {"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情"}, - {"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单"}, - {"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单"}, - {"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息"}, - {"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单"}, - {"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据"}, - - # ======================================== - # 系统设置 -> 子权限 (parentid=2, DB ID: 30) - # ======================================== - {"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置"}, - - # ======================================== - # 日志管理 -> 子模块 (parentid=3, DB ID: 31,32) - # ======================================== - {"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看"}, - {"name": "操作日志", "code": "log:operation", "parentid": 3, "route": "/admin/operation-logs", "description": "操作日志查看"}, - - # ======================================== - # 登录日志操作 (parentid=31, DB ID: 33,34) - # ======================================== - {"name": "查看列表", "code": "log:login:list", "parentid": 31, "description": "查看登录日志列表"}, - {"name": "搜索", "code": "log:login:search", "parentid": 31, "description": "搜索登录日志"}, - - # ======================================== - # 操作日志操作 (parentid=32, DB ID: 35,36) - # ======================================== - {"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表"}, - {"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志"}, - - # ======================================== - # 日志清理 (parentid=3, DB ID: 37) - # ======================================== - {"name": "清理日志", "code": "log:clean", "parentid": 3, "description": "清理旧日志"}, - ] - - # 检查现有权限 - result = await db.execute(select(Permission)) - existing_permissions = result.scalars().all() - existing_permission_codes = {perm.code: perm for perm in existing_permissions} - - # 创建或更新权限 - created_count = 0 - updated_count = 0 - - for perm_data in permission_tree: - perm_code = perm_data["code"] - if perm_code not in existing_permission_codes: - # 权限不存在,需要创建 - db.add(Permission(**perm_data)) - created_count += 1 - else: - # 权限存在,检查是否需要更新 - existing_perm = existing_permission_codes[perm_code] - update_needed = False - - if existing_perm.name != perm_data["name"]: - existing_perm.name = perm_data["name"] - update_needed = True - if existing_perm.parentid != perm_data["parentid"]: - existing_perm.parentid = perm_data["parentid"] - update_needed = True - if existing_perm.route != perm_data.get("route"): - existing_perm.route = perm_data.get("route") - update_needed = True - if existing_perm.description != perm_data["description"]: - existing_perm.description = perm_data["description"] - update_needed = True - - if update_needed: - updated_count += 1 - - if created_count > 0 or updated_count > 0: - await db.commit() - print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限") - else: - print("权限数据已存在且无需更新,跳过初始化") - - print("权限数据初始化/更新完成") - - -async def init_role_permissions(db: AsyncSession): - """初始化角色权限分配""" - from sqlalchemy.orm import selectinload - - # 获取管理员角色(使用预加载避免懒加载问题) - result = await db.execute( - select(Role) - .filter(Role.name == "管理员") - .options(selectinload(Role.permissions)) - ) - admin_role = result.scalar_one_or_none() - if not admin_role: - print("管理员角色不存在,请先初始化角色数据") - return - - # 获取所有权限 - result = await db.execute(select(Permission)) - all_permissions = result.scalars().all() - - if not all_permissions: - print("没有找到任何权限,请先初始化权限数据") - return - - # 获取管理员角色当前的权限(使用预加载的数据) - admin_permission_codes = set() - if admin_role.permissions: - admin_permission_codes = {perm.code for perm in admin_role.permissions} - - # 检查是否需要添加权限 - permissions_to_add = [] - for perm in all_permissions: - if perm.code not in admin_permission_codes: - permissions_to_add.append(perm) - - if permissions_to_add: - # 直接设置权限(替换而非追加,确保权限完整) - admin_role.permissions = all_permissions - await db.commit() - print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限") - else: - print("管理员角色权限已完整,无需更新") - - print("角色权限初始化/更新完成") - - -async def init_roles(db: AsyncSession): - """初始化角色数据""" - # 定义需要初始化的角色 - required_roles = [ - {"name": "管理员", "creator": "system"}, - {"name": "普通用户", "creator": "system"}, - {"name": "访客", "creator": "system"}, - {"name": "会员", "creator": "system"}, - ] - - # 检查现有角色 - result = await db.execute(select(Role)) - existing_roles = result.scalars().all() - existing_role_names = {role.name for role in existing_roles} - - # 检查需要创建或更新的角色 - roles_to_add = [] - roles_to_update = [] - - for role_data in required_roles: - role_name = role_data["name"] - if role_name not in existing_role_names: - # 角色不存在,需要创建 - roles_to_add.append(Role(**role_data)) - else: - # 角色存在,检查是否需要更新 - existing_role = next( - role for role in existing_roles if role.name == role_name - ) - if existing_role.creator != role_data["creator"]: - # 需要更新 - existing_role.creator = role_data["creator"] - roles_to_update.append(existing_role) - - # 执行创建和更新操作 - if roles_to_add: - for role in roles_to_add: - db.add(role) - await db.commit() - print(f"创建了 {len(roles_to_add)} 个角色") - - if roles_to_update: - await db.commit() - print(f"更新了 {len(roles_to_update)} 个角色") - - if not roles_to_add and not roles_to_update: - print("角色数据已存在且无需更新,跳过初始化") - - print("角色数据初始化/更新完成") - - -async def init_admin_user(db: AsyncSession): - """初始化管理员用户""" - # 获取管理员角色 - result = await db.execute(select(Role).filter(Role.name == "管理员")) - admin_role = result.scalar_one_or_none() - if not admin_role: - print("管理员角色不存在,请先初始化角色数据") - return - - # 定义需要的管理员用户数据 - admin_data = { - "username": "admin", - "nickname": "系统管理员", - "email": "admin@example.com", - "phone": "13800138000", - "password_hash": get_password_hash("admin"), - "role_id": admin_role.id, - "avatar": "", - "wx_openid": "", - } - - # 检查管理员用户是否存在 - result = await db.execute(select(User).filter(User.username == "admin")) - existing_admin = result.scalar_one_or_none() - - if not existing_admin: - # 管理员不存在,创建 - admin_user = User(**admin_data) - db.add(admin_user) - await db.commit() - print("创建了管理员用户") - else: - # 管理员存在,检查是否需要更新 - update_needed = False - - # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) - if existing_admin.nickname != admin_data["nickname"]: - existing_admin.nickname = admin_data["nickname"] - update_needed = True - if existing_admin.email != admin_data["email"]: - existing_admin.email = admin_data["email"] - update_needed = True - if existing_admin.phone != admin_data["phone"]: - existing_admin.phone = admin_data["phone"] - update_needed = True - if existing_admin.role_id != admin_data["role_id"]: - existing_admin.role_id = admin_data["role_id"] - update_needed = True - if existing_admin.avatar != admin_data["avatar"]: - existing_admin.avatar = admin_data["avatar"] - update_needed = True - if existing_admin.wx_openid != admin_data["wx_openid"]: - existing_admin.wx_openid = admin_data["wx_openid"] - update_needed = True - - if update_needed: - await db.commit() - print("更新了管理员用户") - else: - print("管理员用户已存在且无需更新,跳过初始化") - - print("管理员用户初始化/更新完成") - - -async def init_test_user(db: AsyncSession): - """初始化测试用户""" - # 获取普通用户角色 - result = await db.execute(select(Role).filter(Role.name == "普通用户")) - user_role = result.scalar_one_or_none() - if not user_role: - print("普通用户角色不存在,请先初始化角色数据") - return - - # 定义需要的测试用户数据 - test_data = { - "username": "test", - "nickname": "测试用户", - "email": "test@example.com", - "phone": "13800138001", - "password_hash": get_password_hash("test"), - "role_id": user_role.id, - "avatar": "", - "wx_openid": "", - } - - # 检查测试用户是否存在 - result = await db.execute(select(User).filter(User.username == "test")) - existing_test_user = result.scalar_one_or_none() - - if not existing_test_user: - # 测试用户不存在,创建 - test_user = User(**test_data) - db.add(test_user) - await db.commit() - print("创建了测试用户") - else: - # 测试用户存在,检查是否需要更新 - update_needed = False - - # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) - if existing_test_user.nickname != test_data["nickname"]: - existing_test_user.nickname = test_data["nickname"] - update_needed = True - if existing_test_user.email != test_data["email"]: - existing_test_user.email = test_data["email"] - update_needed = True - if existing_test_user.phone != test_data["phone"]: - existing_test_user.phone = test_data["phone"] - update_needed = True - if existing_test_user.role_id != test_data["role_id"]: - existing_test_user.role_id = test_data["role_id"] - update_needed = True - if existing_test_user.avatar != test_data["avatar"]: - existing_test_user.avatar = test_data["avatar"] - update_needed = True - if existing_test_user.wx_openid != test_data["wx_openid"]: - existing_test_user.wx_openid = test_data["wx_openid"] - update_needed = True - - if update_needed: - await db.commit() - print("更新了测试用户") - else: - print("测试用户已存在且无需更新,跳过初始化") - - print("测试用户初始化/更新完成") - - -async def init_system_settings(db: AsyncSession): - """初始化系统设置数据""" - # 定义需要初始化的系统设置 - system_settings = [ - { - "key": "系统标题", - "value": "图书管理系统", - "type": "string", - "description": "系统显示标题" - }, - { - "key": "页脚信息", - "value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技", - "type": "string", - "description": "页脚信息" - }, - { - "key": "ICP信息", - "value": "京ICP备2023000000号", - "type": "string", - "description": "ICP备案信息" - }, - { - "key": "启用手机短信注册", - "value": "false", - "type": "boolean", - "description": "是否允许通过手机短信验证码注册" - }, - { - "key": "启用邮箱注册", - "value": "false", - "type": "boolean", - "description": "是否允许通过邮箱验证链接注册" - }, - { - "key": "系统Logo", - "value": "", - "type": "image", - "description": "系统Logo图片" - } - ] - - # 检查现有系统设置 - result = await db.execute(select(SystemSetting)) - existing_settings = result.scalars().all() - existing_setting_keys = {setting.key: setting for setting in existing_settings} - - # 创建或更新系统设置 - created_count = 0 - updated_count = 0 - - for setting_data in system_settings: - setting_key = setting_data["key"] - if setting_key not in existing_setting_keys: - # 设置不存在,需要创建 - db.add(SystemSetting(**setting_data)) - created_count += 1 - else: - # 设置存在,检查是否需要更新 - existing_setting = existing_setting_keys[setting_key] - update_needed = False - - if existing_setting.value != setting_data["value"]: - existing_setting.value = setting_data["value"] - update_needed = True - if existing_setting.type != setting_data["type"]: - existing_setting.type = setting_data["type"] - update_needed = True - if existing_setting.description != setting_data.get("description"): - existing_setting.description = setting_data.get("description") - update_needed = True - - if update_needed: - updated_count += 1 - - if created_count > 0 or updated_count > 0: - await db.commit() - print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置") - else: - print("系统设置数据已存在且无需更新,跳过初始化") - - print("系统设置数据初始化/更新完成") - - -async def init_all_data(db: AsyncSession): - """初始化所有数据""" - print("开始初始化数据库数据...") - await init_roles(db) - await init_permissions(db) - await init_role_permissions(db) - await init_admin_user(db) - await init_test_user(db) - await init_system_settings(db) - print("数据库数据初始化完成") +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, text +from models import Role, User, Permission, SystemSetting +from utils.password import get_password_hash + + +async def init_permissions(db: AsyncSession): + """初始化权限数据(树形结构)""" + # 定义需要初始化的权限(树形结构) + # parentid: 0 = 顶级模块, >0 = 子权限 + permission_tree = [ + # ======================================== + # 顶级模块 (DB ID: 1,2,3) + # ======================================== + {"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块", "sort": 1}, + {"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块", "sort": 2}, + {"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块", "sort": 3}, + + # ======================================== + # 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6) + # ======================================== + {"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块", "sort": 1}, + {"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块", "sort": 2}, + {"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块", "sort": 3}, + + # ======================================== + # 用户管理操作 (parentid=4, DB ID: 7-14) + # ======================================== + {"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表", "sort": 1}, + {"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情", "sort": 2}, + {"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户", "sort": 3}, + {"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户", "sort": 4}, + {"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息", "sort": 5}, + {"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户", "sort": 6}, + {"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据", "sort": 7}, + {"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户", "sort": 8}, + + # ======================================== + # 角色管理操作 (parentid=5, DB ID: 15-22) + # ======================================== + {"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表", "sort": 1}, + {"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情", "sort": 2}, + {"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色", "sort": 3}, + {"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色", "sort": 4}, + {"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息", "sort": 5}, + {"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色", "sort": 6}, + {"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据", "sort": 7}, + {"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限", "sort": 8}, + + # ======================================== + # 菜单管理操作 (parentid=6, DB ID: 23-29) + # ======================================== + {"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表", "sort": 1}, + {"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情", "sort": 2}, + {"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单", "sort": 3}, + {"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单", "sort": 4}, + {"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息", "sort": 5}, + {"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单", "sort": 6}, + {"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据", "sort": 7}, + + # ======================================== + # 系统设置 -> 子权限 (parentid=2, DB ID: 30) + # ======================================== + {"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置", "sort": 1}, + + # ======================================== + # 日志管理 -> 子模块 (parentid=3, DB ID: 31,32,37) + # ======================================== + {"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看", "sort": 1}, + {"name": "操作日志", "code": "log:operation", "parentid": 3, "route": "/admin/operation-logs", "description": "操作日志查看", "sort": 2}, + {"name": "清理日志", "code": "log:clean", "parentid": 3, "description": "清理旧日志", "sort": 3}, + + # ======================================== + # 登录日志操作 (parentid=31, DB ID: 33,34) + # ======================================== + {"name": "查看列表", "code": "log:login:list", "parentid": 31, "description": "查看登录日志列表", "sort": 1}, + {"name": "搜索", "code": "log:login:search", "parentid": 31, "description": "搜索登录日志", "sort": 2}, + + # ======================================== + # 操作日志操作 (parentid=32, DB ID: 35,36) + # ======================================== + {"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表", "sort": 1}, + {"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志", "sort": 2}, + ] + + # 检查现有权限 + result = await db.execute(select(Permission)) + existing_permissions = result.scalars().all() + existing_permission_codes = {perm.code: perm for perm in existing_permissions} + + # 创建或更新权限 + created_count = 0 + updated_count = 0 + + for perm_data in permission_tree: + perm_code = perm_data["code"] + if perm_code not in existing_permission_codes: + # 权限不存在,需要创建 + db.add(Permission(**perm_data)) + created_count += 1 + else: + # 权限存在,检查是否需要更新 + existing_perm = existing_permission_codes[perm_code] + update_needed = False + + if existing_perm.name != perm_data["name"]: + existing_perm.name = perm_data["name"] + update_needed = True + if existing_perm.parentid != perm_data["parentid"]: + existing_perm.parentid = perm_data["parentid"] + update_needed = True + if existing_perm.route != perm_data.get("route"): + existing_perm.route = perm_data.get("route") + update_needed = True + if existing_perm.description != perm_data["description"]: + existing_perm.description = perm_data["description"] + update_needed = True + if existing_perm.sort != perm_data.get("sort", 0): + existing_perm.sort = perm_data.get("sort", 0) + update_needed = True + + if update_needed: + updated_count += 1 + + if created_count > 0 or updated_count > 0: + await db.commit() + print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限") + else: + print("权限数据已存在且无需更新,跳过初始化") + + print("权限数据初始化/更新完成") + + +async def init_role_permissions(db: AsyncSession): + """初始化角色权限分配""" + from sqlalchemy.orm import selectinload + + # 获取管理员角色(使用预加载避免懒加载问题) + result = await db.execute( + select(Role) + .filter(Role.name == "管理员") + .options(selectinload(Role.permissions)) + ) + admin_role = result.scalar_one_or_none() + if not admin_role: + print("管理员角色不存在,请先初始化角色数据") + return + + # 获取所有权限 + result = await db.execute(select(Permission)) + all_permissions = result.scalars().all() + + if not all_permissions: + print("没有找到任何权限,请先初始化权限数据") + return + + # 获取管理员角色当前的权限(使用预加载的数据) + admin_permission_codes = set() + if admin_role.permissions: + admin_permission_codes = {perm.code for perm in admin_role.permissions} + + # 检查是否需要添加权限 + permissions_to_add = [] + for perm in all_permissions: + if perm.code not in admin_permission_codes: + permissions_to_add.append(perm) + + if permissions_to_add: + # 直接设置权限(替换而非追加,确保权限完整) + admin_role.permissions = all_permissions + await db.commit() + print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限") + else: + print("管理员角色权限已完整,无需更新") + + print("角色权限初始化/更新完成") + + +async def init_roles(db: AsyncSession): + """初始化角色数据""" + # 定义需要初始化的角色 + required_roles = [ + {"name": "管理员", "creator": "system"}, + {"name": "普通用户", "creator": "system"}, + {"name": "访客", "creator": "system"}, + {"name": "会员", "creator": "system"}, + ] + + # 检查现有角色 + result = await db.execute(select(Role)) + existing_roles = result.scalars().all() + existing_role_names = {role.name for role in existing_roles} + + # 检查需要创建或更新的角色 + roles_to_add = [] + roles_to_update = [] + + for role_data in required_roles: + role_name = role_data["name"] + if role_name not in existing_role_names: + # 角色不存在,需要创建 + roles_to_add.append(Role(**role_data)) + else: + # 角色存在,检查是否需要更新 + existing_role = next( + role for role in existing_roles if role.name == role_name + ) + if existing_role.creator != role_data["creator"]: + # 需要更新 + existing_role.creator = role_data["creator"] + roles_to_update.append(existing_role) + + # 执行创建和更新操作 + if roles_to_add: + for role in roles_to_add: + db.add(role) + await db.commit() + print(f"创建了 {len(roles_to_add)} 个角色") + + if roles_to_update: + await db.commit() + print(f"更新了 {len(roles_to_update)} 个角色") + + if not roles_to_add and not roles_to_update: + print("角色数据已存在且无需更新,跳过初始化") + + print("角色数据初始化/更新完成") + + +async def init_admin_user(db: AsyncSession): + """初始化管理员用户""" + # 获取管理员角色 + result = await db.execute(select(Role).filter(Role.name == "管理员")) + admin_role = result.scalar_one_or_none() + if not admin_role: + print("管理员角色不存在,请先初始化角色数据") + return + + # 定义需要的管理员用户数据 + admin_data = { + "username": "admin", + "nickname": "系统管理员", + "email": "admin@example.com", + "phone": "13800138000", + "password_hash": get_password_hash("admin"), + "role_id": admin_role.id, + "avatar": "", + "wx_openid": "", + } + + # 检查管理员用户是否存在 + result = await db.execute(select(User).filter(User.username == "admin")) + existing_admin = result.scalar_one_or_none() + + if not existing_admin: + # 管理员不存在,创建 + admin_user = User(**admin_data) + db.add(admin_user) + await db.commit() + print("创建了管理员用户") + else: + # 管理员存在,检查是否需要更新 + update_needed = False + + # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) + if existing_admin.nickname != admin_data["nickname"]: + existing_admin.nickname = admin_data["nickname"] + update_needed = True + if existing_admin.email != admin_data["email"]: + existing_admin.email = admin_data["email"] + update_needed = True + if existing_admin.phone != admin_data["phone"]: + existing_admin.phone = admin_data["phone"] + update_needed = True + if existing_admin.role_id != admin_data["role_id"]: + existing_admin.role_id = admin_data["role_id"] + update_needed = True + if existing_admin.avatar != admin_data["avatar"]: + existing_admin.avatar = admin_data["avatar"] + update_needed = True + if existing_admin.wx_openid != admin_data["wx_openid"]: + existing_admin.wx_openid = admin_data["wx_openid"] + update_needed = True + + if update_needed: + await db.commit() + print("更新了管理员用户") + else: + print("管理员用户已存在且无需更新,跳过初始化") + + print("管理员用户初始化/更新完成") + + +async def init_test_user(db: AsyncSession): + """初始化测试用户""" + # 获取普通用户角色 + result = await db.execute(select(Role).filter(Role.name == "普通用户")) + user_role = result.scalar_one_or_none() + if not user_role: + print("普通用户角色不存在,请先初始化角色数据") + return + + # 定义需要的测试用户数据 + test_data = { + "username": "test", + "nickname": "测试用户", + "email": "test@example.com", + "phone": "13800138001", + "password_hash": get_password_hash("test"), + "role_id": user_role.id, + "avatar": "", + "wx_openid": "", + } + + # 检查测试用户是否存在 + result = await db.execute(select(User).filter(User.username == "test")) + existing_test_user = result.scalar_one_or_none() + + if not existing_test_user: + # 测试用户不存在,创建 + test_user = User(**test_data) + db.add(test_user) + await db.commit() + print("创建了测试用户") + else: + # 测试用户存在,检查是否需要更新 + update_needed = False + + # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) + if existing_test_user.nickname != test_data["nickname"]: + existing_test_user.nickname = test_data["nickname"] + update_needed = True + if existing_test_user.email != test_data["email"]: + existing_test_user.email = test_data["email"] + update_needed = True + if existing_test_user.phone != test_data["phone"]: + existing_test_user.phone = test_data["phone"] + update_needed = True + if existing_test_user.role_id != test_data["role_id"]: + existing_test_user.role_id = test_data["role_id"] + update_needed = True + if existing_test_user.avatar != test_data["avatar"]: + existing_test_user.avatar = test_data["avatar"] + update_needed = True + if existing_test_user.wx_openid != test_data["wx_openid"]: + existing_test_user.wx_openid = test_data["wx_openid"] + update_needed = True + + if update_needed: + await db.commit() + print("更新了测试用户") + else: + print("测试用户已存在且无需更新,跳过初始化") + + print("测试用户初始化/更新完成") + + +async def init_system_settings(db: AsyncSession): + """初始化系统设置数据""" + # 定义需要初始化的系统设置 + system_settings = [ + { + "key": "系统标题", + "value": "图书管理系统", + "type": "string", + "description": "系统显示标题" + }, + { + "key": "页脚信息", + "value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技", + "type": "string", + "description": "页脚信息" + }, + { + "key": "ICP信息", + "value": "京ICP备2023000000号", + "type": "string", + "description": "ICP备案信息" + }, + { + "key": "启用手机短信注册", + "value": "false", + "type": "boolean", + "description": "是否允许通过手机短信验证码注册" + }, + { + "key": "启用邮箱注册", + "value": "false", + "type": "boolean", + "description": "是否允许通过邮箱验证链接注册" + }, + { + "key": "系统Logo", + "value": "", + "type": "image", + "description": "系统Logo图片" + } + ] + + # 检查现有系统设置 + result = await db.execute(select(SystemSetting)) + existing_settings = result.scalars().all() + existing_setting_keys = {setting.key: setting for setting in existing_settings} + + # 创建或更新系统设置 + created_count = 0 + updated_count = 0 + + for setting_data in system_settings: + setting_key = setting_data["key"] + if setting_key not in existing_setting_keys: + # 设置不存在,需要创建 + db.add(SystemSetting(**setting_data)) + created_count += 1 + else: + # 设置存在,检查是否需要更新 + existing_setting = existing_setting_keys[setting_key] + update_needed = False + + if existing_setting.value != setting_data["value"]: + existing_setting.value = setting_data["value"] + update_needed = True + if existing_setting.type != setting_data["type"]: + existing_setting.type = setting_data["type"] + update_needed = True + if existing_setting.description != setting_data.get("description"): + existing_setting.description = setting_data.get("description") + update_needed = True + + if update_needed: + updated_count += 1 + + if created_count > 0 or updated_count > 0: + await db.commit() + print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置") + else: + print("系统设置数据已存在且无需更新,跳过初始化") + + print("系统设置数据初始化/更新完成") + + +async def init_all_data(db: AsyncSession): + """初始化所有数据""" + print("开始初始化数据库数据...") + await init_roles(db) + await init_permissions(db) + await init_role_permissions(db) + await init_admin_user(db) + await init_test_user(db) + await init_system_settings(db) + print("数据库数据初始化完成") diff --git a/backend/auth/main.py b/backend/auth/main.py index 4180034..e6f03d7 100644 --- a/backend/auth/main.py +++ b/backend/auth/main.py @@ -1,172 +1,182 @@ -from fastapi import FastAPI, HTTPException -from fastapi.middleware.cors import CORSMiddleware -from fastapi.staticfiles import StaticFiles -from fastapi.responses import HTMLResponse, FileResponse -from fastapi.exceptions import RequestValidationError -from contextlib import asynccontextmanager -from apps.urls import api_router -from apps.settings.urls import UPLOAD_DIR -from database import engine, get_db -from models import Base -from init_data import init_all_data -from pathlib import Path -from starlette.requests import Request -from starlette.responses import JSONResponse - - -@asynccontextmanager -async def lifespan(app: FastAPI): - # 启动时初始化数据库 - async with engine.begin() as conn: - # 创建所有表 - await conn.run_sync(Base.metadata.create_all) - - # 初始化数据 - async for db in get_db(): - await init_all_data(db) - break - - yield - # 关闭时的清理工作 - await engine.dispose() - - -# 实例化FastAPI -app = FastAPI( - title="图书系统授权服务API", - description="""图书系统授权服务API是一套用于用户认证和授权的服务,提供用户注册、登录、权限校验等功能。 - 同时实现了基于角色的访问控制(RBAC),支持自定义角色和权限。还增加了日志记录功能,方便监控和调试。""", - version="1.0.0", - lifespan=lifespan, - docs_url=None, - redoc_url="/redoc", -) - -# 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins) -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "http://localhost:5173", - "http://127.0.0.1:5173", - "http://localhost:3000", - "http://127.0.0.1:3000", - ], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - expose_headers=["X-Captcha-ID"], -) - -# 挂载swagger-ui静态文件目录 -swagger_ui_path = Path(__file__).parent.parent / "swagger-ui" -app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static") - -# 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致) -@app.get("/uploads/{filename}") -async def serve_upload(filename: str): - """提供上传的图片文件""" - file_path = UPLOAD_DIR / filename - if not file_path.exists(): - raise HTTPException(status_code=404, detail="文件不存在") - return FileResponse(str(file_path)) - - -# 自定义Swagger UI页面 -@app.get("/docs", include_in_schema=False) -async def custom_swagger_ui_html(): - html_content = """ - - -
- - -{if(!a._listeners)return;let r=a._listeners.length;for(;r-- >0;)a._listeners[r](o);a._listeners=null}),this.promise.then=o=>{let r;const l=new Promise(s=>{a.subscribe(s),r=s}).then(o);return l.cancel=function(){a.unsubscribe(r)},l},t(function(r,l,s){a.reason||(a.reason=new Bf(r,l,s),n(a.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const t=new AbortController,n=a=>{t.abort(a)};return this.subscribe(n),t.signal.unsubscribe=()=>this.unsubscribe(n),t.signal}static source(){let t;return{token:new AT(function(o){t=o}),cancel:t}}};function Q5e(e){return function(n){return e.apply(null,n)}}function e_e(e){return Ee.isObject(e)&&e.isAxiosError===!0}const Vg={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Vg).forEach(([e,t])=>{Vg[t]=e});function $T(e){const t=new Ci(e),n=fT(Ci.prototype.request,t);return Ee.extend(n,Ci.prototype,t,{allOwnKeys:!0}),Ee.extend(n,t,null,{allOwnKeys:!0}),n.create=function(o){return $T(Ni(e,o))},n}const aa=$T(Df);aa.Axios=Ci;aa.CanceledError=Bf;aa.CancelToken=Z5e;aa.isCancel=kT;aa.VERSION=Cb;aa.toFormData=um;aa.AxiosError=Ot;aa.Cancel=aa.CanceledError;aa.all=function(t){return Promise.all(t)};aa.spread=Q5e;aa.isAxiosError=e_e;aa.mergeConfig=Ni;aa.AxiosHeaders=eo;aa.formToJSON=e=>xT(Ee.isHTMLForm(e)?new FormData(e):e);aa.getAdapter=RT.getAdapter;aa.HttpStatusCode=Vg;aa.default=aa;const{Axios:u_e,AxiosError:c_e,CanceledError:d_e,isCancel:f_e,CancelToken:p_e,VERSION:h_e,all:m_e,Cancel:v_e,isAxiosError:g_e,spread:b_e,toFormData:y_e,AxiosHeaders:w_e,HttpStatusCode:C_e,formToJSON:__e,getAdapter:S_e,mergeConfig:x_e,create:k_e}=aa,t_e="/api",Cd=aa.create({baseURL:t_e,timeout:3e4});Cd.interceptors.request.use(e=>{const t=localStorage.getItem("access_token");return t&&(e.headers.Authorization=`Bearer ${t}`),e});Cd.interceptors.response.use(e=>e,e=>{var t;if(((t=e.response)==null?void 0:t.status)===401){localStorage.removeItem("access_token");const n=window.location.pathname;n.startsWith("/admin/login")||(window.location.href=`/admin/login?redirect=${encodeURIComponent(n+window.location.search)}`)}return Promise.reject(e)});const n_e=pR("auth",()=>{const e=F(localStorage.getItem("access_token")||""),t=F(null),n=F([]),a=F([]),o=x(()=>!!e.value),r=x(()=>n.value.map(m=>m.code));function l(m){e.value=m,m?localStorage.setItem("access_token",m):localStorage.removeItem("access_token")}async function s(){const{data:m}=await Cd.get("/users/me");return t.value=m,m}async function i(){const{data:m}=await Cd.get("/permissions/user/me");return n.value=m.permissions||[],a.value=m.menu||[],{permissions:n.value,menu:a.value}}async function u(){await s(),await i()}function c(m){return r.value.includes(m)}function f(m){return m.some(h=>r.value.includes(h))}async function p(){try{await Cd.post("/users/logout")}catch{}t.value=null,n.value=[],a.value=[],l("")}return{token:e,user:t,permissions:n,menu:a,permissionCodes:r,isAuthenticated:o,setToken:l,fetchProfile:s,fetchPermissions:i,loginAndFetchData:u,hasPermission:c,hasAnyPermission:f,logout:p}}),PT=y1e({history:Jge("/"),routes:[{path:"/",name:"public-home",meta:{title:"首页"},component:()=>Ar(()=>import("./PublicHome-BE6848HM.js"),__vite__mapDeps([0,1]))},{path:"/admin/login",name:"admin-login",meta:{title:"管理后台登录",guestOnly:!0},component:()=>Ar(()=>import("./LoginView-CL6FqBfI.js"),__vite__mapDeps([2,3]))},{path:"/admin",component:()=>Ar(()=>import("./AdminLayout-C8hRi9x-.js"),__vite__mapDeps([4,5])),meta:{requiresAuth:!0},children:[{path:"",redirect:{name:"admin-users"}},{path:"users",name:"admin-users",meta:{title:"用户管理",permission:"perm:user"},component:()=>Ar(()=>import("./UserManage-DExq8OEE.js"),__vite__mapDeps([6,7]))},{path:"roles",name:"admin-roles",meta:{title:"角色管理",permission:"perm:role"},component:()=>Ar(()=>import("./RoleManage-C7cZ0SSm.js"),__vite__mapDeps([8,7,9]))},{path:"menu",name:"admin-menu",meta:{title:"菜单管理",permission:"perm:menu"},component:()=>Ar(()=>import("./MenuManage-J689-0Rz.js"),__vite__mapDeps([10,7]))},{path:"settings",name:"admin-settings",meta:{title:"系统设置",permission:"system:"},component:()=>Ar(()=>import("./SettingManage-DbOShH1j.js"),__vite__mapDeps([11,12]))},{path:"login-logs",name:"admin-login-logs",meta:{title:"登录日志",permission:"log:"},component:()=>Ar(()=>import("./LoginLogManage-CQqltd1Y.js"),[])},{path:"operation-logs",name:"admin-operation-logs",meta:{title:"操作日志",permission:"log:"},component:()=>Ar(()=>import("./OperationLogManage-CrJsIKz-.js"),[])}]}]});PT.beforeEach(async(e,t,n)=>{const a=n_e(),o=e.meta.title||"BookSystem";if(document.title=`${o} · BookSystem`,e.meta.requiresAuth&&!a.isAuthenticated){n({name:"admin-login",query:{redirect:e.fullPath}});return}if(e.meta.guestOnly&&a.isAuthenticated){if(!a.user||a.permissions.length===0)try{await a.fetchProfile(),await a.fetchPermissions()}catch{a.logout(),n({name:"admin-login"});return}n({name:"admin-users"});return}const r=e.meta.permission;if(r&&a.isAuthenticated&&a.permissions.length>0&&!a.permissions.some(s=>s.code.startsWith(r))){n({name:"admin-users"});return}n()});const vc=o5(pCe);vc.use(sR());vc.use(i2e,{locale:u2e});vc.use(dT);vc.use(oCe,{ui:dT.set(),commonOptions:()=>({search:{container:{layout:"multi-line"}}})});vc.use(PT);vc.mount("#app");export{Me as A,d as B,x as C,RK as D,AE as E,Ae as F,tt as G,r_e as H,Re as I,Oe as J,g_e as K,wt as L,QC as M,a_e as N,xbe as O,$E as P,tm as Z,cCe as _,M as a,Ct as b,A as c,J as d,nm as e,K as f,gt as g,aa as h,F as i,Nt as j,s7 as k,o_e as l,nt as m,Cd as n,C as o,jo as p,ae as q,He as r,HS as s,ye as t,n_e as u,VY as v,te as w,LW as x,le as y,it as z}; +`+r)}}catch{}}throw a}}_request(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=Ni(this.defaults,n);const{transitional:a,paramsSerializer:o,headers:r}=n;a!==void 0&&ah.assertOptions(a,{silentJSONParsing:Po.transitional(Po.boolean),forcedJSONParsing:Po.transitional(Po.boolean),clarifyTimeoutError:Po.transitional(Po.boolean),legacyInterceptorReqResOrdering:Po.transitional(Po.boolean)},!1),o!=null&&(Ee.isFunction(o)?n.paramsSerializer={serialize:o}:ah.assertOptions(o,{encode:Po.function,serialize:Po.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),ah.assertOptions(n,{baseUrl:Po.spelling("baseURL"),withXsrfToken:Po.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();let l=r&&Ee.merge(r.common,r[n.method]);r&&Ee.forEach(["delete","get","head","post","put","patch","query","common"],h=>{delete r[h]}),n.headers=eo.concat(l,r);const s=[];let i=!0;this.interceptors.request.forEach(function(v){if(typeof v.runWhen=="function"&&v.runWhen(n)===!1)return;i=i&&v.synchronous;const g=n.transitional||yb;g&&g.legacyInterceptorReqResOrdering?s.unshift(v.fulfilled,v.rejected):s.push(v.fulfilled,v.rejected)});const u=[];this.interceptors.response.forEach(function(v){u.push(v.fulfilled,v.rejected)});let c,f=0,p;if(!i){const h=[Pw.bind(this),void 0];for(h.unshift(...s),h.push(...u),p=h.length,c=Promise.resolve(n);f
{if(!a._listeners)return;let r=a._listeners.length;for(;r-- >0;)a._listeners[r](o);a._listeners=null}),this.promise.then=o=>{let r;const l=new Promise(s=>{a.subscribe(s),r=s}).then(o);return l.cancel=function(){a.unsubscribe(r)},l},t(function(r,l,s){a.reason||(a.reason=new Bf(r,l,s),n(a.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const t=new AbortController,n=a=>{t.abort(a)};return this.subscribe(n),t.signal.unsubscribe=()=>this.unsubscribe(n),t.signal}static source(){let t;return{token:new AT(function(o){t=o}),cancel:t}}};function Q5e(e){return function(n){return e.apply(null,n)}}function e_e(e){return Ee.isObject(e)&&e.isAxiosError===!0}const Vg={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Vg).forEach(([e,t])=>{Vg[t]=e});function $T(e){const t=new Ci(e),n=fT(Ci.prototype.request,t);return Ee.extend(n,Ci.prototype,t,{allOwnKeys:!0}),Ee.extend(n,t,null,{allOwnKeys:!0}),n.create=function(o){return $T(Ni(e,o))},n}const aa=$T(Df);aa.Axios=Ci;aa.CanceledError=Bf;aa.CancelToken=Z5e;aa.isCancel=kT;aa.VERSION=Cb;aa.toFormData=um;aa.AxiosError=Ot;aa.Cancel=aa.CanceledError;aa.all=function(t){return Promise.all(t)};aa.spread=Q5e;aa.isAxiosError=e_e;aa.mergeConfig=Ni;aa.AxiosHeaders=eo;aa.formToJSON=e=>xT(Ee.isHTMLForm(e)?new FormData(e):e);aa.getAdapter=RT.getAdapter;aa.HttpStatusCode=Vg;aa.default=aa;const{Axios:u_e,AxiosError:c_e,CanceledError:d_e,isCancel:f_e,CancelToken:p_e,VERSION:h_e,all:m_e,Cancel:v_e,isAxiosError:g_e,spread:b_e,toFormData:y_e,AxiosHeaders:w_e,HttpStatusCode:C_e,formToJSON:__e,getAdapter:S_e,mergeConfig:x_e,create:k_e}=aa,t_e="/api",Cd=aa.create({baseURL:t_e,timeout:3e4});Cd.interceptors.request.use(e=>{const t=localStorage.getItem("access_token");return t&&(e.headers.Authorization=`Bearer ${t}`),e});Cd.interceptors.response.use(e=>e,e=>{var t;if(((t=e.response)==null?void 0:t.status)===401){localStorage.removeItem("access_token");const n=window.location.pathname;n.startsWith("/admin/login")||(window.location.href=`/admin/login?redirect=${encodeURIComponent(n+window.location.search)}`)}return Promise.reject(e)});const n_e=pR("auth",()=>{const e=F(localStorage.getItem("access_token")||""),t=F(null),n=F([]),a=F([]),o=x(()=>!!e.value),r=x(()=>n.value.map(m=>m.code));function l(m){e.value=m,m?localStorage.setItem("access_token",m):localStorage.removeItem("access_token")}async function s(){const{data:m}=await Cd.get("/users/me");return t.value=m,m}async function i(){const{data:m}=await Cd.get("/permissions/user/me");return n.value=m.permissions||[],a.value=m.menu||[],{permissions:n.value,menu:a.value}}async function u(){await s(),await i()}function c(m){return r.value.includes(m)}function f(m){return m.some(h=>r.value.includes(h))}async function p(){try{await Cd.post("/users/logout")}catch{}t.value=null,n.value=[],a.value=[],l("")}return{token:e,user:t,permissions:n,menu:a,permissionCodes:r,isAuthenticated:o,setToken:l,fetchProfile:s,fetchPermissions:i,loginAndFetchData:u,hasPermission:c,hasAnyPermission:f,logout:p}}),PT=y1e({history:Jge("/"),routes:[{path:"/",name:"public-home",meta:{title:"首页"},component:()=>Ar(()=>import("./PublicHome-4L60krmd.js"),__vite__mapDeps([0,1]))},{path:"/admin/login",name:"admin-login",meta:{title:"管理后台登录",guestOnly:!0},component:()=>Ar(()=>import("./LoginView-CdDWJNab.js"),__vite__mapDeps([2,3]))},{path:"/admin",component:()=>Ar(()=>import("./AdminLayout-ltF-ui71.js"),__vite__mapDeps([4,5])),meta:{requiresAuth:!0},children:[{path:"",redirect:{name:"admin-users"}},{path:"users",name:"admin-users",meta:{title:"用户管理",permission:"perm:user"},component:()=>Ar(()=>import("./UserManage-DDgTVhjY.js"),__vite__mapDeps([6,7]))},{path:"roles",name:"admin-roles",meta:{title:"角色管理",permission:"perm:role"},component:()=>Ar(()=>import("./RoleManage-7Ni_fgq0.js"),__vite__mapDeps([8,7,9]))},{path:"menu",name:"admin-menu",meta:{title:"菜单管理",permission:"perm:menu"},component:()=>Ar(()=>import("./MenuManage-BEleckEE.js"),__vite__mapDeps([10,7]))},{path:"settings",name:"admin-settings",meta:{title:"系统设置",permission:"system:"},component:()=>Ar(()=>import("./SettingManage-D3eSU6jo.js"),__vite__mapDeps([11,12]))},{path:"login-logs",name:"admin-login-logs",meta:{title:"登录日志",permission:"log:"},component:()=>Ar(()=>import("./LoginLogManage-COZ7cyEH.js"),[])},{path:"operation-logs",name:"admin-operation-logs",meta:{title:"操作日志",permission:"log:"},component:()=>Ar(()=>import("./OperationLogManage-BIONNQB8.js"),[])}]}]});PT.beforeEach(async(e,t,n)=>{const a=n_e(),o=e.meta.title||"BookSystem";if(document.title=`${o} · BookSystem`,e.meta.requiresAuth&&!a.isAuthenticated){n({name:"admin-login",query:{redirect:e.fullPath}});return}if(e.meta.guestOnly&&a.isAuthenticated){if(!a.user||a.permissions.length===0)try{await a.fetchProfile(),await a.fetchPermissions()}catch{a.logout(),n({name:"admin-login"});return}n({name:"admin-users"});return}const r=e.meta.permission;if(r&&a.isAuthenticated&&a.permissions.length>0&&!a.permissions.some(s=>s.code.startsWith(r))){n({name:"admin-users"});return}n()});const vc=o5(pCe);vc.use(sR());vc.use(i2e,{locale:u2e});vc.use(dT);vc.use(oCe,{ui:dT.set(),commonOptions:()=>({search:{container:{layout:"multi-line"}}})});vc.use(PT);vc.mount("#app");export{Me as A,d as B,x as C,RK as D,AE as E,Ae as F,tt as G,r_e as H,Re as I,Oe as J,g_e as K,wt as L,QC as M,a_e as N,xbe as O,$E as P,tm as Z,cCe as _,M as a,Ct as b,A as c,J as d,nm as e,K as f,gt as g,aa as h,F as i,Nt as j,s7 as k,o_e as l,nt as m,Cd as n,C as o,jo as p,ae as q,He as r,HS as s,ye as t,n_e as u,VY as v,te as w,LW as x,le as y,it as z}; diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 28a2433..e96c187 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -4,7 +4,7 @@