Compare commits

..

No commits in common. "6751a5c2b2c742349303e053f173e2a3acc6b1a8" and "0545d68018bdc598f6eb673a5b6924e9aae9bc2c" have entirely different histories.

20 changed files with 1290 additions and 1349 deletions

View File

@ -44,9 +44,7 @@ async def create_permission(
name=permission_data.name, name=permission_data.name,
code=permission_data.code, code=permission_data.code,
parentid=permission_data.parentid, parentid=permission_data.parentid,
route=permission_data.route,
description=permission_data.description, description=permission_data.description,
sort=permission_data.sort,
) )
db.add(db_permission) db.add(db_permission)
@ -114,7 +112,6 @@ async def get_permission_tree(
"parentid": perm.parentid, "parentid": perm.parentid,
"route": perm.route, "route": perm.route,
"description": perm.description, "description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime, "createtime": perm.createtime,
"updatetime": perm.updatetime, "updatetime": perm.updatetime,
"children": [] "children": []
@ -131,16 +128,6 @@ async def get_permission_tree(
# 子节点,添加到父节点的 children # 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data) 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( await create_operation_log(
db, current_user.id, current_user.username, "search", "permission", db, current_user.id, current_user.username, "search", "permission",
@ -200,7 +187,6 @@ async def get_menu(
"parentid": perm.parentid, "parentid": perm.parentid,
"route": perm.route, "route": perm.route,
"description": perm.description, "description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime, "createtime": perm.createtime,
"updatetime": perm.updatetime, "updatetime": perm.updatetime,
"children": [] "children": []
@ -217,16 +203,6 @@ async def get_menu(
# 子节点,添加到父节点的 children # 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data) 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( await create_operation_log(
db, current_user.id, current_user.username, "search", "permission", db, current_user.id, current_user.username, "search", "permission",
@ -246,7 +222,7 @@ async def get_permission(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
): ):
"""获取单个权限""" """获取单个权限"""
permission = await permission_crud.get(db, id=permission_id, schema_to_select=PermissionResponse, return_as_model=True) permission = await permission_crud.get(db, id=permission_id)
if not permission: if not permission:
raise HTTPException(status_code=404, detail="权限不存在") raise HTTPException(status_code=404, detail="权限不存在")
@ -295,7 +271,7 @@ async def update_permission(
raise HTTPException(status_code=404, detail="权限不存在") raise HTTPException(status_code=404, detail="权限不存在")
# 记录操作日志 # 记录操作日志
changed = {} changed = {}
for field in ["name", "code", "parentid", "route", "sort", "description"]: for field in ["name", "code", "parentid", "description"]:
old_val = getattr(old_permission, field, None) old_val = getattr(old_permission, field, None)
new_val = update_data.get(field) new_val = update_data.get(field)
if old_val != new_val: if old_val != new_val:
@ -476,7 +452,6 @@ async def get_current_user_permissions(
"parentid": perm.parentid, "parentid": perm.parentid,
"route": perm.route, "route": perm.route,
"description": perm.description, "description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime, "createtime": perm.createtime,
"updatetime": perm.updatetime, "updatetime": perm.updatetime,
"children": [] "children": []
@ -493,16 +468,6 @@ async def get_current_user_permissions(
# 子节点,添加到父节点的 children # 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data) 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( await create_operation_log(
db, current_user.id, current_user.username, "search", "permission", db, current_user.id, current_user.username, "search", "permission",

View File

@ -1,451 +1,452 @@
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text from sqlalchemy import select, text
from models import Role, User, Permission, SystemSetting from models import Role, User, Permission, SystemSetting
from utils.password import get_password_hash from utils.password import get_password_hash
async def init_permissions(db: AsyncSession): async def init_permissions(db: AsyncSession):
"""初始化权限数据(树形结构)""" """初始化权限数据(树形结构)"""
# 定义需要初始化的权限(树形结构) # 定义需要初始化的权限(树形结构)
# parentid: 0 = 顶级模块, >0 = 子权限 # parentid: 0 = 顶级模块, >0 = 子权限
permission_tree = [ permission_tree = [
# ======================================== # ========================================
# 顶级模块 (DB ID: 1,2,3) # 顶级模块 (DB ID: 1,2,3)
# ======================================== # ========================================
{"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块", "sort": 1}, {"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块"},
{"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块", "sort": 2}, {"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块"},
{"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块", "sort": 3}, {"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块"},
# ======================================== # ========================================
# 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6) # 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6)
# ======================================== # ========================================
{"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块", "sort": 1}, {"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块"},
{"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块", "sort": 2}, {"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块"},
{"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块", "sort": 3}, {"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块"},
# ======================================== # ========================================
# 用户管理操作 (parentid=4, DB ID: 7-14) # 用户管理操作 (parentid=4, DB ID: 7-14)
# ======================================== # ========================================
{"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表", "sort": 1}, {"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表"},
{"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情", "sort": 2}, {"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情"},
{"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户", "sort": 3}, {"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户"},
{"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户", "sort": 4}, {"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户"},
{"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息", "sort": 5}, {"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息"},
{"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户", "sort": 6}, {"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户"},
{"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据", "sort": 7}, {"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据"},
{"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户", "sort": 8}, {"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户"},
# ======================================== # ========================================
# 角色管理操作 (parentid=5, DB ID: 15-22) # 角色管理操作 (parentid=5, DB ID: 15-22)
# ======================================== # ========================================
{"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表", "sort": 1}, {"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表"},
{"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情", "sort": 2}, {"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情"},
{"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色", "sort": 3}, {"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色"},
{"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色", "sort": 4}, {"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色"},
{"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息", "sort": 5}, {"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息"},
{"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色", "sort": 6}, {"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色"},
{"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据", "sort": 7}, {"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据"},
{"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限", "sort": 8}, {"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限"},
# ======================================== # ========================================
# 菜单管理操作 (parentid=6, DB ID: 23-29) # 菜单管理操作 (parentid=6, DB ID: 23-29)
# ======================================== # ========================================
{"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表", "sort": 1}, {"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表"},
{"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情", "sort": 2}, {"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情"},
{"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单", "sort": 3}, {"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单"},
{"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单", "sort": 4}, {"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单"},
{"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息", "sort": 5}, {"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息"},
{"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单", "sort": 6}, {"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单"},
{"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据", "sort": 7}, {"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据"},
# ======================================== # ========================================
# 系统设置 -> 子权限 (parentid=2, DB ID: 30) # 系统设置 -> 子权限 (parentid=2, DB ID: 30)
# ======================================== # ========================================
{"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置", "sort": 1}, {"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置"},
# ======================================== # ========================================
# 日志管理 -> 子模块 (parentid=3, DB ID: 31,32,37) # 日志管理 -> 子模块 (parentid=3, DB ID: 31,32)
# ======================================== # ========================================
{"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看", "sort": 1}, {"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看"},
{"name": "操作日志", "code": "log:operation", "parentid": 3, "route": "/admin/operation-logs", "description": "操作日志查看", "sort": 2}, {"name": "操作日志", "code": "log:operation", "parentid": 3, "route": "/admin/operation-logs", "description": "操作日志查看"},
{"name": "清理日志", "code": "log:clean", "parentid": 3, "description": "清理旧日志", "sort": 3},
# ========================================
# ======================================== # 登录日志操作 (parentid=31, DB ID: 33,34)
# 登录日志操作 (parentid=31, DB ID: 33,34) # ========================================
# ======================================== {"name": "查看列表", "code": "log:login:list", "parentid": 31, "description": "查看登录日志列表"},
{"name": "查看列表", "code": "log:login:list", "parentid": 31, "description": "查看登录日志列表", "sort": 1}, {"name": "搜索", "code": "log:login:search", "parentid": 31, "description": "搜索登录日志"},
{"name": "搜索", "code": "log:login:search", "parentid": 31, "description": "搜索登录日志", "sort": 2},
# ========================================
# ======================================== # 操作日志操作 (parentid=32, DB ID: 35,36)
# 操作日志操作 (parentid=32, DB ID: 35,36) # ========================================
# ======================================== {"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表"},
{"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表", "sort": 1}, {"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志"},
{"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志", "sort": 2},
] # ========================================
# 日志清理 (parentid=3, DB ID: 37)
# 检查现有权限 # ========================================
result = await db.execute(select(Permission)) {"name": "清理日志", "code": "log:clean", "parentid": 3, "description": "清理旧日志"},
existing_permissions = result.scalars().all() ]
existing_permission_codes = {perm.code: perm for perm in existing_permissions}
# 检查现有权限
# 创建或更新权限 result = await db.execute(select(Permission))
created_count = 0 existing_permissions = result.scalars().all()
updated_count = 0 existing_permission_codes = {perm.code: perm for perm in existing_permissions}
for perm_data in permission_tree: # 创建或更新权限
perm_code = perm_data["code"] created_count = 0
if perm_code not in existing_permission_codes: updated_count = 0
# 权限不存在,需要创建
db.add(Permission(**perm_data)) for perm_data in permission_tree:
created_count += 1 perm_code = perm_data["code"]
else: if perm_code not in existing_permission_codes:
# 权限存在,检查是否需要更新 # 权限不存在,需要创建
existing_perm = existing_permission_codes[perm_code] db.add(Permission(**perm_data))
update_needed = False created_count += 1
else:
if existing_perm.name != perm_data["name"]: # 权限存在,检查是否需要更新
existing_perm.name = perm_data["name"] existing_perm = existing_permission_codes[perm_code]
update_needed = True update_needed = False
if existing_perm.parentid != perm_data["parentid"]:
existing_perm.parentid = perm_data["parentid"] if existing_perm.name != perm_data["name"]:
update_needed = True existing_perm.name = perm_data["name"]
if existing_perm.route != perm_data.get("route"): update_needed = True
existing_perm.route = perm_data.get("route") if existing_perm.parentid != perm_data["parentid"]:
update_needed = True existing_perm.parentid = perm_data["parentid"]
if existing_perm.description != perm_data["description"]: update_needed = True
existing_perm.description = perm_data["description"] if existing_perm.route != perm_data.get("route"):
update_needed = True existing_perm.route = perm_data.get("route")
if existing_perm.sort != perm_data.get("sort", 0): update_needed = True
existing_perm.sort = perm_data.get("sort", 0) if existing_perm.description != perm_data["description"]:
update_needed = True existing_perm.description = perm_data["description"]
update_needed = True
if update_needed:
updated_count += 1 if update_needed:
updated_count += 1
if created_count > 0 or updated_count > 0:
await db.commit() if created_count > 0 or updated_count > 0:
print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限") await db.commit()
else: print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限")
print("权限数据已存在且无需更新,跳过初始化") else:
print("权限数据已存在且无需更新,跳过初始化")
print("权限数据初始化/更新完成")
print("权限数据初始化/更新完成")
async def init_role_permissions(db: AsyncSession):
"""初始化角色权限分配""" async def init_role_permissions(db: AsyncSession):
from sqlalchemy.orm import selectinload """初始化角色权限分配"""
from sqlalchemy.orm import selectinload
# 获取管理员角色(使用预加载避免懒加载问题)
result = await db.execute( # 获取管理员角色(使用预加载避免懒加载问题)
select(Role) result = await db.execute(
.filter(Role.name == "管理员") select(Role)
.options(selectinload(Role.permissions)) .filter(Role.name == "管理员")
) .options(selectinload(Role.permissions))
admin_role = result.scalar_one_or_none() )
if not admin_role: admin_role = result.scalar_one_or_none()
print("管理员角色不存在,请先初始化角色数据") if not admin_role:
return print("管理员角色不存在,请先初始化角色数据")
return
# 获取所有权限
result = await db.execute(select(Permission)) # 获取所有权限
all_permissions = result.scalars().all() result = await db.execute(select(Permission))
all_permissions = result.scalars().all()
if not all_permissions:
print("没有找到任何权限,请先初始化权限数据") if not all_permissions:
return print("没有找到任何权限,请先初始化权限数据")
return
# 获取管理员角色当前的权限(使用预加载的数据)
admin_permission_codes = set() # 获取管理员角色当前的权限(使用预加载的数据)
if admin_role.permissions: admin_permission_codes = set()
admin_permission_codes = {perm.code for perm in admin_role.permissions} if admin_role.permissions:
admin_permission_codes = {perm.code for perm in admin_role.permissions}
# 检查是否需要添加权限
permissions_to_add = [] # 检查是否需要添加权限
for perm in all_permissions: permissions_to_add = []
if perm.code not in admin_permission_codes: for perm in all_permissions:
permissions_to_add.append(perm) if perm.code not in admin_permission_codes:
permissions_to_add.append(perm)
if permissions_to_add:
# 直接设置权限(替换而非追加,确保权限完整) if permissions_to_add:
admin_role.permissions = all_permissions # 直接设置权限(替换而非追加,确保权限完整)
await db.commit() admin_role.permissions = all_permissions
print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限") await db.commit()
else: print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限")
print("管理员角色权限已完整,无需更新") else:
print("管理员角色权限已完整,无需更新")
print("角色权限初始化/更新完成")
print("角色权限初始化/更新完成")
async def init_roles(db: AsyncSession):
"""初始化角色数据""" async def init_roles(db: AsyncSession):
# 定义需要初始化的角色 """初始化角色数据"""
required_roles = [ # 定义需要初始化的角色
{"name": "管理员", "creator": "system"}, required_roles = [
{"name": "普通用户", "creator": "system"}, {"name": "管理员", "creator": "system"},
{"name": "访客", "creator": "system"}, {"name": "普通用户", "creator": "system"},
{"name": "会员", "creator": "system"}, {"name": "访客", "creator": "system"},
] {"name": "会员", "creator": "system"},
]
# 检查现有角色
result = await db.execute(select(Role)) # 检查现有角色
existing_roles = result.scalars().all() result = await db.execute(select(Role))
existing_role_names = {role.name for role in existing_roles} existing_roles = result.scalars().all()
existing_role_names = {role.name for role in existing_roles}
# 检查需要创建或更新的角色
roles_to_add = [] # 检查需要创建或更新的角色
roles_to_update = [] roles_to_add = []
roles_to_update = []
for role_data in required_roles:
role_name = role_data["name"] for role_data in required_roles:
if role_name not in existing_role_names: role_name = role_data["name"]
# 角色不存在,需要创建 if role_name not in existing_role_names:
roles_to_add.append(Role(**role_data)) # 角色不存在,需要创建
else: roles_to_add.append(Role(**role_data))
# 角色存在,检查是否需要更新 else:
existing_role = next( # 角色存在,检查是否需要更新
role for role in existing_roles if role.name == role_name existing_role = next(
) role for role in existing_roles if role.name == role_name
if existing_role.creator != role_data["creator"]: )
# 需要更新 if existing_role.creator != role_data["creator"]:
existing_role.creator = role_data["creator"] # 需要更新
roles_to_update.append(existing_role) existing_role.creator = role_data["creator"]
roles_to_update.append(existing_role)
# 执行创建和更新操作
if roles_to_add: # 执行创建和更新操作
for role in roles_to_add: if roles_to_add:
db.add(role) for role in roles_to_add:
await db.commit() db.add(role)
print(f"创建了 {len(roles_to_add)} 个角色") await db.commit()
print(f"创建了 {len(roles_to_add)} 个角色")
if roles_to_update:
await db.commit() if roles_to_update:
print(f"更新了 {len(roles_to_update)} 个角色") await db.commit()
print(f"更新了 {len(roles_to_update)} 个角色")
if not roles_to_add and not roles_to_update:
print("角色数据已存在且无需更新,跳过初始化") if not roles_to_add and not roles_to_update:
print("角色数据已存在且无需更新,跳过初始化")
print("角色数据初始化/更新完成")
print("角色数据初始化/更新完成")
async def init_admin_user(db: AsyncSession):
"""初始化管理员用户""" async def init_admin_user(db: AsyncSession):
# 获取管理员角色 """初始化管理员用户"""
result = await db.execute(select(Role).filter(Role.name == "管理员")) # 获取管理员角色
admin_role = result.scalar_one_or_none() result = await db.execute(select(Role).filter(Role.name == "管理员"))
if not admin_role: admin_role = result.scalar_one_or_none()
print("管理员角色不存在,请先初始化角色数据") if not admin_role:
return print("管理员角色不存在,请先初始化角色数据")
return
# 定义需要的管理员用户数据
admin_data = { # 定义需要的管理员用户数据
"username": "admin", admin_data = {
"nickname": "系统管理员", "username": "admin",
"email": "admin@example.com", "nickname": "系统管理员",
"phone": "13800138000", "email": "admin@example.com",
"password_hash": get_password_hash("admin"), "phone": "13800138000",
"role_id": admin_role.id, "password_hash": get_password_hash("admin"),
"avatar": "", "role_id": admin_role.id,
"wx_openid": "", "avatar": "",
} "wx_openid": "",
}
# 检查管理员用户是否存在
result = await db.execute(select(User).filter(User.username == "admin")) # 检查管理员用户是否存在
existing_admin = result.scalar_one_or_none() result = await db.execute(select(User).filter(User.username == "admin"))
existing_admin = result.scalar_one_or_none()
if not existing_admin:
# 管理员不存在,创建 if not existing_admin:
admin_user = User(**admin_data) # 管理员不存在,创建
db.add(admin_user) admin_user = User(**admin_data)
await db.commit() db.add(admin_user)
print("创建了管理员用户") await db.commit()
else: print("创建了管理员用户")
# 管理员存在,检查是否需要更新 else:
update_needed = False # 管理员存在,检查是否需要更新
update_needed = False
# 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
if existing_admin.nickname != admin_data["nickname"]: # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
existing_admin.nickname = admin_data["nickname"] if existing_admin.nickname != admin_data["nickname"]:
update_needed = True existing_admin.nickname = admin_data["nickname"]
if existing_admin.email != admin_data["email"]: update_needed = True
existing_admin.email = admin_data["email"] if existing_admin.email != admin_data["email"]:
update_needed = True existing_admin.email = admin_data["email"]
if existing_admin.phone != admin_data["phone"]: update_needed = True
existing_admin.phone = admin_data["phone"] if existing_admin.phone != admin_data["phone"]:
update_needed = True existing_admin.phone = admin_data["phone"]
if existing_admin.role_id != admin_data["role_id"]: update_needed = True
existing_admin.role_id = admin_data["role_id"] if existing_admin.role_id != admin_data["role_id"]:
update_needed = True existing_admin.role_id = admin_data["role_id"]
if existing_admin.avatar != admin_data["avatar"]: update_needed = True
existing_admin.avatar = admin_data["avatar"] if existing_admin.avatar != admin_data["avatar"]:
update_needed = True existing_admin.avatar = admin_data["avatar"]
if existing_admin.wx_openid != admin_data["wx_openid"]: update_needed = True
existing_admin.wx_openid = admin_data["wx_openid"] if existing_admin.wx_openid != admin_data["wx_openid"]:
update_needed = True existing_admin.wx_openid = admin_data["wx_openid"]
update_needed = True
if update_needed:
await db.commit() if update_needed:
print("更新了管理员用户") await db.commit()
else: print("更新了管理员用户")
print("管理员用户已存在且无需更新,跳过初始化") else:
print("管理员用户已存在且无需更新,跳过初始化")
print("管理员用户初始化/更新完成")
print("管理员用户初始化/更新完成")
async def init_test_user(db: AsyncSession):
"""初始化测试用户""" async def init_test_user(db: AsyncSession):
# 获取普通用户角色 """初始化测试用户"""
result = await db.execute(select(Role).filter(Role.name == "普通用户")) # 获取普通用户角色
user_role = result.scalar_one_or_none() result = await db.execute(select(Role).filter(Role.name == "普通用户"))
if not user_role: user_role = result.scalar_one_or_none()
print("普通用户角色不存在,请先初始化角色数据") if not user_role:
return print("普通用户角色不存在,请先初始化角色数据")
return
# 定义需要的测试用户数据
test_data = { # 定义需要的测试用户数据
"username": "test", test_data = {
"nickname": "测试用户", "username": "test",
"email": "test@example.com", "nickname": "测试用户",
"phone": "13800138001", "email": "test@example.com",
"password_hash": get_password_hash("test"), "phone": "13800138001",
"role_id": user_role.id, "password_hash": get_password_hash("test"),
"avatar": "", "role_id": user_role.id,
"wx_openid": "", "avatar": "",
} "wx_openid": "",
}
# 检查测试用户是否存在
result = await db.execute(select(User).filter(User.username == "test")) # 检查测试用户是否存在
existing_test_user = result.scalar_one_or_none() result = await db.execute(select(User).filter(User.username == "test"))
existing_test_user = result.scalar_one_or_none()
if not existing_test_user:
# 测试用户不存在,创建 if not existing_test_user:
test_user = User(**test_data) # 测试用户不存在,创建
db.add(test_user) test_user = User(**test_data)
await db.commit() db.add(test_user)
print("创建了测试用户") await db.commit()
else: print("创建了测试用户")
# 测试用户存在,检查是否需要更新 else:
update_needed = False # 测试用户存在,检查是否需要更新
update_needed = False
# 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
if existing_test_user.nickname != test_data["nickname"]: # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
existing_test_user.nickname = test_data["nickname"] if existing_test_user.nickname != test_data["nickname"]:
update_needed = True existing_test_user.nickname = test_data["nickname"]
if existing_test_user.email != test_data["email"]: update_needed = True
existing_test_user.email = test_data["email"] if existing_test_user.email != test_data["email"]:
update_needed = True existing_test_user.email = test_data["email"]
if existing_test_user.phone != test_data["phone"]: update_needed = True
existing_test_user.phone = test_data["phone"] if existing_test_user.phone != test_data["phone"]:
update_needed = True existing_test_user.phone = test_data["phone"]
if existing_test_user.role_id != test_data["role_id"]: update_needed = True
existing_test_user.role_id = test_data["role_id"] if existing_test_user.role_id != test_data["role_id"]:
update_needed = True existing_test_user.role_id = test_data["role_id"]
if existing_test_user.avatar != test_data["avatar"]: update_needed = True
existing_test_user.avatar = test_data["avatar"] if existing_test_user.avatar != test_data["avatar"]:
update_needed = True existing_test_user.avatar = test_data["avatar"]
if existing_test_user.wx_openid != test_data["wx_openid"]: update_needed = True
existing_test_user.wx_openid = test_data["wx_openid"] if existing_test_user.wx_openid != test_data["wx_openid"]:
update_needed = True existing_test_user.wx_openid = test_data["wx_openid"]
update_needed = True
if update_needed:
await db.commit() if update_needed:
print("更新了测试用户") await db.commit()
else: print("更新了测试用户")
print("测试用户已存在且无需更新,跳过初始化") else:
print("测试用户已存在且无需更新,跳过初始化")
print("测试用户初始化/更新完成")
print("测试用户初始化/更新完成")
async def init_system_settings(db: AsyncSession):
"""初始化系统设置数据""" async def init_system_settings(db: AsyncSession):
# 定义需要初始化的系统设置 """初始化系统设置数据"""
system_settings = [ # 定义需要初始化的系统设置
{ system_settings = [
"key": "系统标题", {
"value": "图书管理系统", "key": "系统标题",
"type": "string", "value": "图书管理系统",
"description": "系统显示标题" "type": "string",
}, "description": "系统显示标题"
{ },
"key": "页脚信息", {
"value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技", "key": "页脚信息",
"type": "string", "value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技",
"description": "页脚信息" "type": "string",
}, "description": "页脚信息"
{ },
"key": "ICP信息", {
"value": "京ICP备2023000000号", "key": "ICP信息",
"type": "string", "value": "京ICP备2023000000号",
"description": "ICP备案信息" "type": "string",
}, "description": "ICP备案信息"
{ },
"key": "启用手机短信注册", {
"value": "false", "key": "启用手机短信注册",
"type": "boolean", "value": "false",
"description": "是否允许通过手机短信验证码注册" "type": "boolean",
}, "description": "是否允许通过手机短信验证码注册"
{ },
"key": "启用邮箱注册", {
"value": "false", "key": "启用邮箱注册",
"type": "boolean", "value": "false",
"description": "是否允许通过邮箱验证链接注册" "type": "boolean",
}, "description": "是否允许通过邮箱验证链接注册"
{ },
"key": "系统Logo", {
"value": "", "key": "系统Logo",
"type": "image", "value": "",
"description": "系统Logo图片" "type": "image",
} "description": "系统Logo图片"
] }
]
# 检查现有系统设置
result = await db.execute(select(SystemSetting)) # 检查现有系统设置
existing_settings = result.scalars().all() result = await db.execute(select(SystemSetting))
existing_setting_keys = {setting.key: setting for setting in existing_settings} existing_settings = result.scalars().all()
existing_setting_keys = {setting.key: setting for setting in existing_settings}
# 创建或更新系统设置
created_count = 0 # 创建或更新系统设置
updated_count = 0 created_count = 0
updated_count = 0
for setting_data in system_settings:
setting_key = setting_data["key"] for setting_data in system_settings:
if setting_key not in existing_setting_keys: setting_key = setting_data["key"]
# 设置不存在,需要创建 if setting_key not in existing_setting_keys:
db.add(SystemSetting(**setting_data)) # 设置不存在,需要创建
created_count += 1 db.add(SystemSetting(**setting_data))
else: created_count += 1
# 设置存在,检查是否需要更新 else:
existing_setting = existing_setting_keys[setting_key] # 设置存在,检查是否需要更新
update_needed = False existing_setting = existing_setting_keys[setting_key]
update_needed = False
if existing_setting.value != setting_data["value"]:
existing_setting.value = setting_data["value"] if existing_setting.value != setting_data["value"]:
update_needed = True existing_setting.value = setting_data["value"]
if existing_setting.type != setting_data["type"]: update_needed = True
existing_setting.type = setting_data["type"] if existing_setting.type != setting_data["type"]:
update_needed = True existing_setting.type = setting_data["type"]
if existing_setting.description != setting_data.get("description"): update_needed = True
existing_setting.description = setting_data.get("description") if existing_setting.description != setting_data.get("description"):
update_needed = True existing_setting.description = setting_data.get("description")
update_needed = True
if update_needed:
updated_count += 1 if update_needed:
updated_count += 1
if created_count > 0 or updated_count > 0:
await db.commit() if created_count > 0 or updated_count > 0:
print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置") await db.commit()
else: print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置")
print("系统设置数据已存在且无需更新,跳过初始化") else:
print("系统设置数据已存在且无需更新,跳过初始化")
print("系统设置数据初始化/更新完成")
print("系统设置数据初始化/更新完成")
async def init_all_data(db: AsyncSession):
"""初始化所有数据""" async def init_all_data(db: AsyncSession):
print("开始初始化数据库数据...") """初始化所有数据"""
await init_roles(db) print("开始初始化数据库数据...")
await init_permissions(db) await init_roles(db)
await init_role_permissions(db) await init_permissions(db)
await init_admin_user(db) await init_role_permissions(db)
await init_test_user(db) await init_admin_user(db)
await init_system_settings(db) await init_test_user(db)
print("数据库数据初始化完成") await init_system_settings(db)
print("数据库数据初始化完成")

View File

@ -1,182 +1,172 @@
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, FileResponse from fastapi.responses import HTMLResponse, FileResponse
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from apps.urls import api_router from apps.urls import api_router
from apps.settings.urls import UPLOAD_DIR from apps.settings.urls import UPLOAD_DIR
from database import engine, get_db from database import engine, get_db
from models import Base from models import Base
from init_data import init_all_data from init_data import init_all_data
from pathlib import Path from pathlib import Path
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from sqlalchemy import text
@asynccontextmanager
@asynccontextmanager async def lifespan(app: FastAPI):
async def lifespan(app: FastAPI): # 启动时初始化数据库
# 启动时初始化数据库 async with engine.begin() as conn:
async with engine.begin() as conn: # 创建所有表
# 创建所有表 await conn.run_sync(Base.metadata.create_all)
await conn.run_sync(Base.metadata.create_all)
# 为已有表添加 sort 列(兼容 PostgreSQL 和 SQLite # 初始化数据
try: async for db in get_db():
await conn.execute(text("ALTER TABLE permission ADD COLUMN IF NOT EXISTS sort INTEGER DEFAULT 0")) await init_all_data(db)
except Exception: break
# SQLite 不支持 IF NOT EXISTS
try: yield
await conn.execute(text("ALTER TABLE permission ADD COLUMN sort INTEGER DEFAULT 0")) # 关闭时的清理工作
except Exception: await engine.dispose()
pass # 列已存在则忽略
# 初始化数据 # 实例化FastAPI
async for db in get_db(): app = FastAPI(
await init_all_data(db) title="图书系统授权服务API",
break description="""图书系统授权服务API是一套用于用户认证和授权的服务提供用户注册、登录、权限校验等功能。
同时实现了基于角色的访问控制RBAC支持自定义角色和权限还增加了日志记录功能方便监控和调试""",
yield version="1.0.0",
# 关闭时的清理工作 lifespan=lifespan,
await engine.dispose() docs_url=None,
redoc_url="/redoc",
)
# 实例化FastAPI
app = FastAPI( # 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins
title="图书系统授权服务API", app.add_middleware(
description="""图书系统授权服务API是一套用于用户认证和授权的服务提供用户注册、登录、权限校验等功能。 CORSMiddleware,
同时实现了基于角色的访问控制RBAC支持自定义角色和权限还增加了日志记录功能方便监控和调试""", allow_origins=[
version="1.0.0", "http://localhost:5173",
lifespan=lifespan, "http://127.0.0.1:5173",
docs_url=None, "http://localhost:3000",
redoc_url="/redoc", "http://127.0.0.1:3000",
) ],
allow_credentials=True,
# 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins allow_methods=["*"],
app.add_middleware( allow_headers=["*"],
CORSMiddleware, expose_headers=["X-Captcha-ID"],
allow_origins=[ )
"http://localhost:5173",
"http://127.0.0.1:5173", # 挂载swagger-ui静态文件目录
"http://localhost:3000", swagger_ui_path = Path(__file__).parent.parent / "swagger-ui"
"http://127.0.0.1:3000", app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static")
],
allow_credentials=True, # 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致)
allow_methods=["*"], @app.get("/uploads/{filename}")
allow_headers=["*"], async def serve_upload(filename: str):
expose_headers=["X-Captcha-ID"], """提供上传的图片文件"""
) file_path = UPLOAD_DIR / filename
if not file_path.exists():
# 挂载swagger-ui静态文件目录 raise HTTPException(status_code=404, detail="文件不存在")
swagger_ui_path = Path(__file__).parent.parent / "swagger-ui" return FileResponse(str(file_path))
app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static")
# 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致) # 自定义Swagger UI页面
@app.get("/uploads/{filename}") @app.get("/docs", include_in_schema=False)
async def serve_upload(filename: str): async def custom_swagger_ui_html():
"""提供上传的图片文件""" html_content = """
file_path = UPLOAD_DIR / filename <!DOCTYPE html>
if not file_path.exists(): <html lang="en">
raise HTTPException(status_code=404, detail="文件不存在") <head>
return FileResponse(str(file_path)) <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Book System API - Swagger UI</title>
# 自定义Swagger UI页面 <link rel="stylesheet" type="text/css" href="/static/swagger-ui.css">
@app.get("/docs", include_in_schema=False) <link rel="icon" type="image/png" href="/static/favicon-32x32.png" sizes="32x32"/>
async def custom_swagger_ui_html(): <style>
html_content = """ html {
<!DOCTYPE html> box-sizing: border-box;
<html lang="en"> overflow: -moz-scrollbars-vertical;
<head> overflow-y: scroll;
<meta charset="UTF-8"> }
<meta name="viewport" content="width=device-width, initial-scale=1.0"> *, *:before, *:after {
<title>Book System API - Swagger UI</title> box-sizing: inherit;
<link rel="stylesheet" type="text/css" href="/static/swagger-ui.css"> }
<link rel="icon" type="image/png" href="/static/favicon-32x32.png" sizes="32x32"/> body {
<style> margin: 0;
html { background: #fafafa;
box-sizing: border-box; }
overflow: -moz-scrollbars-vertical; </style>
overflow-y: scroll; </head>
} <body>
*, *:before, *:after { <div id="swagger-ui"></div>
box-sizing: inherit; <script src="/static/swagger-ui-bundle.js"></script>
} <script>
body { window.onload = function() {
margin: 0; const ui = SwaggerUIBundle({
background: #fafafa; url: "/openapi.json",
} dom_id: '#swagger-ui',
</style> deepLinking: true,
</head> presets: [
<body> SwaggerUIBundle.presets.apis,
<div id="swagger-ui"></div> SwaggerUIBundle.SwaggerUIStandalonePreset
<script src="/static/swagger-ui-bundle.js"></script> ],
<script> layout: "BaseLayout",
window.onload = function() { persistAuthorization: true,
const ui = SwaggerUIBundle({ docExpansion: "list"
url: "/openapi.json", });
dom_id: '#swagger-ui', window.ui = ui;
deepLinking: true, };
presets: [ </script>
SwaggerUIBundle.presets.apis, </body>
SwaggerUIBundle.SwaggerUIStandalonePreset </html>
], """
layout: "BaseLayout", return HTMLResponse(content=html_content)
persistAuthorization: true,
docExpansion: "list"
}); # 包含路由
window.ui = ui; app.include_router(api_router)
};
</script>
</body> # 声明装饰器方法和路径
</html> @app.get("/")
""" # 声明装饰器函数
return HTMLResponse(content=html_content) async def home():
return {"message": "Hello World!!!"}
# 包含路由
app.include_router(api_router) # 自定义验证错误处理器
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
# 声明装饰器方法和路径 errors = []
@app.get("/") for err in exc.errors():
# 声明装饰器函数 field = err.get("loc", ["unknown"])[-1]
async def home(): msg = err.get("msg", "验证失败")
return {"message": "Hello World!!!"}
if "min_length" in msg.lower():
if field == "username":
# 自定义验证错误处理器 msg = "用户名长度至少为3个字符"
@app.exception_handler(RequestValidationError) elif field == "password":
async def validation_exception_handler(request: Request, exc: RequestValidationError): msg = "密码长度至少为6个字符"
errors = [] elif "max_length" in msg.lower():
for err in exc.errors(): if field == "username":
field = err.get("loc", ["unknown"])[-1] msg = "用户名长度不能超过50个字符"
msg = err.get("msg", "验证失败") elif field == "password":
msg = "密码长度不能超过50个字符"
if "min_length" in msg.lower():
if field == "username": errors.append({"field": field, "message": msg})
msg = "用户名长度至少为3个字符"
elif field == "password": return JSONResponse(
msg = "密码长度至少为6个字符" status_code=422,
elif "max_length" in msg.lower(): content={"code": 422, "message": "验证失败", "errors": errors}
if field == "username": )
msg = "用户名长度不能超过50个字符"
elif field == "password":
msg = "密码长度不能超过50个字符" # 如果使用命令行启动使用uvicorn 文件名:app --reload启动即可下面命令就不用写
# 如果写下面的命令就不需要命令行启动了直接用IDE运行即可
errors.append({"field": field, "message": msg}) if __name__ == "__main__":
import uvicorn
return JSONResponse( import os
status_code=422,
content={"code": 422, "message": "验证失败", "errors": errors} name = f"{os.path.splitext(os.path.basename(os.path.abspath(__file__)))[0]}:app"
) uvicorn.run(name, host="0.0.0.0", port=8000, reload=True, reload_dirs=["_"])
# 如果使用命令行启动使用uvicorn 文件名:app --reload启动即可下面命令就不用写
# 如果写下面的命令就不需要命令行启动了直接用IDE运行即可
if __name__ == "__main__":
import uvicorn
import os
name = f"{os.path.splitext(os.path.basename(os.path.abspath(__file__)))[0]}:app"
uvicorn.run(name, host="0.0.0.0", port=8000, reload=True, reload_dirs=["_"])

View File

@ -1,141 +1,140 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func, Table, Index, Text from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func, Table, Index, Text
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime from datetime import datetime
Base = declarative_base() Base = declarative_base()
class Role(Base): class Role(Base):
"""角色数据库模型""" """角色数据库模型"""
__tablename__ = "role" __tablename__ = "role"
id = Column(Integer, primary_key=True, index=True, comment="角色ID") id = Column(Integer, primary_key=True, index=True, comment="角色ID")
name = Column(String(50), nullable=False, unique=True, comment="角色名称") name = Column(String(50), nullable=False, unique=True, comment="角色名称")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
updatetime = Column( updatetime = Column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
) )
creator = Column(String(50), nullable=True, comment="创建人") creator = Column(String(50), nullable=True, comment="创建人")
# 关联到User # 关联到User
users = relationship("User", back_populates="role_rel") users = relationship("User", back_populates="role_rel")
# 关联到Permission多对多 # 关联到Permission多对多
permissions = relationship( permissions = relationship(
"Permission", "Permission",
secondary="role_permission", secondary="role_permission",
back_populates="roles" back_populates="roles"
) )
class User(Base): class User(Base):
"""用户数据库模型""" """用户数据库模型"""
__tablename__ = "users" __tablename__ = "users"
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False, comment="登录名") username = Column(String(50), unique=True, nullable=False, comment="登录名")
nickname = Column(String(50), nullable=False, comment="昵称") nickname = Column(String(50), nullable=False, comment="昵称")
email = Column(String(50), nullable=True, comment="电子邮箱") email = Column(String(50), nullable=True, comment="电子邮箱")
phone = Column(String(11), nullable=True, comment="手机号码") phone = Column(String(11), nullable=True, comment="手机号码")
wx_openid = Column(String(100), nullable=True, comment="微信OpenID") wx_openid = Column(String(100), nullable=True, comment="微信OpenID")
password_hash = Column(String(128), nullable=False, comment="密码哈希值") password_hash = Column(String(128), nullable=False, comment="密码哈希值")
avatar = Column(String(255), nullable=True, comment="头像路径") avatar = Column(String(255), nullable=True, comment="头像路径")
role_id = Column(Integer, ForeignKey("role.id"), default=2, comment="角色ID") role_id = Column(Integer, ForeignKey("role.id"), default=2, comment="角色ID")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
updatetime = Column( updatetime = Column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
) )
lastlogintime = Column(DateTime, comment="最后登录时间") lastlogintime = Column(DateTime, comment="最后登录时间")
# 关联到Role # 关联到Role
role_rel = relationship("Role", back_populates="users") role_rel = relationship("Role", back_populates="users")
class Permission(Base): class Permission(Base):
"""权限数据库模型""" """权限数据库模型"""
__tablename__ = "permission" __tablename__ = "permission"
id = Column(Integer, primary_key=True, index=True, comment="权限ID") id = Column(Integer, primary_key=True, index=True, comment="权限ID")
name = Column(String(100), nullable=False, comment="权限名称") name = Column(String(100), nullable=False, comment="权限名称")
code = Column(String(100), nullable=False, unique=True, comment="权限编码") code = Column(String(100), nullable=False, unique=True, comment="权限编码")
parentid = Column(Integer, default=0, comment="父权限ID0表示顶级模块") parentid = Column(Integer, default=0, comment="父权限ID0表示顶级模块")
route = Column(String(255), nullable=True, comment="前端路由路径") route = Column(String(255), nullable=True, comment="前端路由路径")
description = Column(String(255), nullable=True, comment="权限描述") description = Column(String(255), nullable=True, comment="权限描述")
sort = Column(Integer, default=0, comment="排序号(升序排列)") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") updatetime = Column(
updatetime = Column( DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" )
)
# 关联到Role多对多
# 关联到Role多对多 roles = relationship(
roles = relationship( "Role",
"Role", secondary="role_permission",
secondary="role_permission", back_populates="permissions"
back_populates="permissions" )
)
# 角色权限关联表(多对多)
# 角色权限关联表(多对多) role_permission = Table(
role_permission = Table( "role_permission",
"role_permission", Base.metadata,
Base.metadata, Column("role_id", Integer, ForeignKey("role.id"), primary_key=True, comment="角色ID"),
Column("role_id", Integer, ForeignKey("role.id"), primary_key=True, comment="角色ID"), Column("permission_id", Integer, ForeignKey("permission.id"), primary_key=True, comment="权限ID")
Column("permission_id", Integer, ForeignKey("permission.id"), primary_key=True, comment="权限ID") )
)
class SystemSetting(Base):
class SystemSetting(Base): """系统设置数据库模型"""
"""系统设置数据库模型"""
__tablename__ = "system_setting"
__tablename__ = "system_setting" id = Column(Integer, primary_key=True, index=True, comment="设置ID")
id = Column(Integer, primary_key=True, index=True, comment="设置ID") key = Column(String(100), nullable=False, unique=True, comment="设置键名")
key = Column(String(100), nullable=False, unique=True, comment="设置键名") value = Column(String(2000), nullable=False, comment="设置值")
value = Column(String(2000), nullable=False, comment="设置值") type = Column(String(20), default="string", comment="类型string/image/select/boolean")
type = Column(String(20), default="string", comment="类型string/image/select/boolean") options = Column(String(500), nullable=True, comment="下拉选项(用|分隔)")
options = Column(String(500), nullable=True, comment="下拉选项(用|分隔)") description = Column(String(255), nullable=True, comment="设置描述")
description = Column(String(255), nullable=True, comment="设置描述") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") updatetime = Column(
updatetime = Column( DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" )
)
class LoginLog(Base):
class LoginLog(Base): """登录日志"""
"""登录日志"""
__tablename__ = "login_log"
__tablename__ = "login_log" id = Column(Integer, primary_key=True, index=True, comment="日志ID")
id = Column(Integer, primary_key=True, index=True, comment="日志ID") username = Column(String(50), nullable=False, comment="用户名")
username = Column(String(50), nullable=False, comment="用户名") login_time = Column(DateTime, default=datetime.utcnow, comment="登录时间")
login_time = Column(DateTime, default=datetime.utcnow, comment="登录时间") ip_address = Column(String(50), nullable=True, comment="IP地址")
ip_address = Column(String(50), nullable=True, comment="IP地址") user_agent = Column(String(500), nullable=True, comment="User-Agent")
user_agent = Column(String(500), nullable=True, comment="User-Agent") status = Column(String(20), nullable=False, comment="状态: success/failure")
status = Column(String(20), nullable=False, comment="状态: success/failure") failure_reason = Column(String(255), nullable=True, comment="失败原因")
failure_reason = Column(String(255), nullable=True, comment="失败原因")
Index("ix_login_log_login_time", LoginLog.login_time)
Index("ix_login_log_login_time", LoginLog.login_time) Index("ix_login_log_username", LoginLog.username)
Index("ix_login_log_username", LoginLog.username) Index("ix_login_log_status", LoginLog.status)
Index("ix_login_log_status", LoginLog.status)
class OperationLog(Base):
class OperationLog(Base): """操作日志"""
"""操作日志"""
__tablename__ = "operation_log"
__tablename__ = "operation_log" id = Column(Integer, primary_key=True, index=True, comment="日志ID")
id = Column(Integer, primary_key=True, index=True, comment="日志ID") user_id = Column(Integer, nullable=False, comment="用户ID")
user_id = Column(Integer, nullable=False, comment="用户ID") username = Column(String(50), nullable=False, comment="用户名")
username = Column(String(50), nullable=False, comment="用户名") action_type = Column(String(20), nullable=False, comment="操作类型: create/update/delete")
action_type = Column(String(20), nullable=False, comment="操作类型: create/update/delete") target_type = Column(String(50), nullable=False, comment="目标类型: user/role/permission/setting")
target_type = Column(String(50), nullable=False, comment="目标类型: user/role/permission/setting") target_id = Column(Integer, nullable=True, comment="目标ID")
target_id = Column(Integer, nullable=True, comment="目标ID") target_name = Column(String(100), nullable=True, comment="目标名称")
target_name = Column(String(100), nullable=True, comment="目标名称") detail = Column(Text, nullable=True, comment="变更详情(JSON)")
detail = Column(Text, nullable=True, comment="变更详情(JSON)") ip_address = Column(String(50), nullable=True, comment="IP地址")
ip_address = Column(String(50), nullable=True, comment="IP地址") user_agent = Column(String(500), nullable=True, comment="User-Agent")
user_agent = Column(String(500), nullable=True, comment="User-Agent") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
Index("ix_operation_log_createtime", OperationLog.createtime)
Index("ix_operation_log_createtime", OperationLog.createtime) Index("ix_operation_log_username", OperationLog.username)
Index("ix_operation_log_username", OperationLog.username) Index("ix_operation_log_action_type", OperationLog.action_type)
Index("ix_operation_log_action_type", OperationLog.action_type)
Index("ix_operation_log_target_type", OperationLog.target_type) Index("ix_operation_log_target_type", OperationLog.target_type)

View File

@ -1,337 +1,335 @@
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Optional from typing import Optional
from datetime import datetime from datetime import datetime
class UserBase(BaseModel): class UserBase(BaseModel):
"""用户基础模型""" """用户基础模型"""
username: str = Field( username: str = Field(
..., ...,
min_length=3, min_length=3,
max_length=50, max_length=50,
description="登录名", description="登录名",
json_schema_extra={ json_schema_extra={
"error_messages": { "error_messages": {
"min_length": "用户名长度至少为3个字符", "min_length": "用户名长度至少为3个字符",
"max_length": "用户名长度不能超过50个字符" "max_length": "用户名长度不能超过50个字符"
} }
} }
) )
nickname: str = Field(..., max_length=50, description="昵称") nickname: str = Field(..., max_length=50, description="昵称")
email: Optional[str] = Field(None, max_length=50, description="电子邮箱") email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
phone: Optional[str] = Field(None, max_length=11, description="手机号码") phone: Optional[str] = Field(None, max_length=11, description="手机号码")
wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID") wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
avatar: Optional[str] = Field(None, max_length=255, description="头像路径") avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
role_id: Optional[int] = Field(2, description="角色ID") role_id: Optional[int] = Field(2, description="角色ID")
model_config = {"extra": "ignore"} model_config = {"extra": "ignore"}
class UserCreateRequest(UserBase): class UserCreateRequest(UserBase):
"""用户创建请求模型""" """用户创建请求模型"""
password: str = Field( password: str = Field(
..., ...,
min_length=6, min_length=6,
max_length=50, max_length=50,
description="密码", description="密码",
json_schema_extra={ json_schema_extra={
"error_messages": { "error_messages": {
"min_length": "密码长度至少为6个字符", "min_length": "密码长度至少为6个字符",
"max_length": "密码长度不能超过50个字符" "max_length": "密码长度不能超过50个字符"
} }
} }
) )
class UserCreate(UserBase): class UserCreate(UserBase):
"""用户创建模型""" """用户创建模型"""
password_hash: str = Field( password_hash: str = Field(
..., min_length=6, max_length=128, description="密码哈希值" ..., min_length=6, max_length=128, description="密码哈希值"
) )
class UserUpdate(BaseModel): class UserUpdate(BaseModel):
"""用户更新模型""" """用户更新模型"""
username: str = Field(..., min_length=3, max_length=50, description="登录名") username: str = Field(..., min_length=3, max_length=50, description="登录名")
nickname: str = Field(..., max_length=50, description="昵称") nickname: str = Field(..., max_length=50, description="昵称")
email: Optional[str] = Field(None, max_length=50, description="电子邮箱") email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
phone: Optional[str] = Field(None, max_length=11, description="手机号码") phone: Optional[str] = Field(None, max_length=11, description="手机号码")
wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID") wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
avatar: Optional[str] = Field(None, max_length=255, description="头像路径") avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
role_id: Optional[int] = Field(2, description="角色ID") role_id: Optional[int] = Field(2, description="角色ID")
password: Optional[str] = Field(None, max_length=128, description="密码") password: Optional[str] = Field(None, max_length=128, description="密码")
class UserLogin(BaseModel): class UserLogin(BaseModel):
"""用户登录模型""" """用户登录模型"""
username: str = Field(..., description="用户名/邮箱/手机号") username: str = Field(..., description="用户名/邮箱/手机号")
password: str = Field(..., description="密码") password: str = Field(..., description="密码")
captcha_id: str = Field(..., description="验证码ID") captcha_id: str = Field(..., description="验证码ID")
captcha_code: str = Field(..., description="验证码") captcha_code: str = Field(..., description="验证码")
class UserResponse(BaseModel): class UserResponse(BaseModel):
"""用户响应模型""" """用户响应模型"""
id: int = Field(..., description="用户ID") id: int = Field(..., description="用户ID")
username: str = Field(..., description="登录名") username: str = Field(..., description="登录名")
nickname: str = Field(..., description="昵称") nickname: str = Field(..., description="昵称")
email: Optional[str] = Field(None, description="电子邮箱") email: Optional[str] = Field(None, description="电子邮箱")
phone: Optional[str] = Field(None, description="手机号码") phone: Optional[str] = Field(None, description="手机号码")
wx_openid: Optional[str] = Field(None, description="微信OpenID") wx_openid: Optional[str] = Field(None, description="微信OpenID")
avatar: Optional[str] = Field(None, description="头像路径") avatar: Optional[str] = Field(None, description="头像路径")
role_id: int = Field(..., description="角色ID") role_id: int = Field(..., description="角色ID")
createtime: datetime = Field(..., description="创建时间") createtime: datetime = Field(..., description="创建时间")
updatetime: datetime = Field(..., description="更新时间") updatetime: datetime = Field(..., description="更新时间")
lastlogintime: Optional[datetime] = Field(None, description="最后登录时间") lastlogintime: Optional[datetime] = Field(None, description="最后登录时间")
class Config: class Config:
from_attributes = True from_attributes = True
class Token(BaseModel): class Token(BaseModel):
"""令牌模型""" """令牌模型"""
access_token: str = Field(..., description="访问令牌") access_token: str = Field(..., description="访问令牌")
token_type: str = Field(..., description="令牌类型") token_type: str = Field(..., description="令牌类型")
class TokenData(BaseModel): class TokenData(BaseModel):
"""令牌数据模型""" """令牌数据模型"""
user_id: Optional[int] = None user_id: Optional[int] = None
email: Optional[str] = None email: Optional[str] = None
class RoleBase(BaseModel): class RoleBase(BaseModel):
"""角色基础模型""" """角色基础模型"""
name: str = Field(..., max_length=50, description="角色名称") name: str = Field(..., max_length=50, description="角色名称")
class RoleCreate(RoleBase): class RoleCreate(RoleBase):
"""角色创建模型""" """角色创建模型"""
creator: Optional[str] = Field(None, max_length=50, description="创建人") creator: Optional[str] = Field(None, max_length=50, description="创建人")
class RoleResponse(RoleBase): class RoleResponse(RoleBase):
"""角色响应模型""" """角色响应模型"""
id: int = Field(..., description="角色ID") id: int = Field(..., description="角色ID")
createtime: datetime = Field(..., description="创建时间") createtime: datetime = Field(..., description="创建时间")
updatetime: datetime = Field(..., description="更新时间") updatetime: datetime = Field(..., description="更新时间")
creator: Optional[str] = Field(None, description="创建人") creator: Optional[str] = Field(None, description="创建人")
class Config: class Config:
from_attributes = True from_attributes = True
# 权限相关模型 # 权限相关模型
class PermissionBase(BaseModel): class PermissionBase(BaseModel):
"""权限基础模型""" """权限基础模型"""
name: str = Field(..., max_length=100, description="权限名称") name: str = Field(..., max_length=100, description="权限名称")
code: str = Field(..., max_length=100, description="权限编码") code: str = Field(..., max_length=100, description="权限编码")
parentid: int = Field(0, description="父权限ID0表示顶级模块") parentid: int = Field(0, description="父权限ID0表示顶级模块")
route: Optional[str] = Field(None, max_length=255, description="前端路由路径") route: Optional[str] = Field(None, max_length=255, description="前端路由路径")
description: Optional[str] = Field(None, max_length=255, description="权限描述") description: Optional[str] = Field(None, max_length=255, description="权限描述")
sort: int = Field(0, description="排序号(升序排列)")
class PermissionCreate(PermissionBase):
class PermissionCreate(PermissionBase): """权限创建模型"""
"""权限创建模型""" pass
pass
class PermissionResponse(PermissionBase):
class PermissionResponse(PermissionBase): """权限响应模型"""
"""权限响应模型"""
id: int = Field(..., description="权限ID")
id: int = Field(..., description="权限ID") createtime: datetime = Field(..., description="创建时间")
createtime: datetime = Field(..., description="创建时间") updatetime: datetime = Field(..., description="更新时间")
updatetime: datetime = Field(..., description="更新时间")
class Config:
class Config: from_attributes = True
from_attributes = True
class PermissionTreeResponse(BaseModel):
class PermissionTreeResponse(BaseModel): """权限树响应模型"""
"""权限树响应模型"""
id: int = Field(..., description="权限ID")
id: int = Field(..., description="权限ID") name: str = Field(..., description="权限名称")
name: str = Field(..., description="权限名称") code: str = Field(..., description="权限编码")
code: str = Field(..., description="权限编码") parentid: int = Field(..., description="父权限ID")
parentid: int = Field(..., description="父权限ID") route: Optional[str] = Field(None, description="前端路由路径")
route: Optional[str] = Field(None, description="前端路由路径") createtime: Optional[datetime] = Field(None, description="创建时间")
createtime: Optional[datetime] = Field(None, description="创建时间") updatetime: Optional[datetime] = Field(None, description="更新时间")
updatetime: Optional[datetime] = Field(None, description="更新时间") description: Optional[str] = Field(None, description="权限描述")
description: Optional[str] = Field(None, description="权限描述") children: list = Field([], description="子权限列表")
sort: int = Field(0, description="排序号")
children: list = Field([], description="子权限列表")
# 角色权限关联相关模型
class RolePermissionAssign(BaseModel):
# 角色权限关联相关模型 """角色权限分配模型"""
class RolePermissionAssign(BaseModel):
"""角色权限分配模型""" role_id: int = Field(..., description="角色ID")
permission_ids: list[int] = Field(..., description="权限ID列表")
role_id: int = Field(..., description="角色ID")
permission_ids: list[int] = Field(..., description="权限ID列表")
class RolePermissionResponse(BaseModel):
"""角色权限响应模型"""
class RolePermissionResponse(BaseModel):
"""角色权限响应模型""" role_id: int = Field(..., description="角色ID")
role_name: str = Field(..., description="角色名称")
role_id: int = Field(..., description="角色ID") permissions: list[PermissionResponse] = Field(..., description="权限列表")
role_name: str = Field(..., description="角色名称") menu: list = Field([], description="菜单树形结构")
permissions: list[PermissionResponse] = Field(..., description="权限列表")
menu: list = Field([], description="菜单树形结构")
# 系统设置相关模型
class SystemSettingBase(BaseModel):
# 系统设置相关模型 """系统设置基础模型"""
class SystemSettingBase(BaseModel):
"""系统设置基础模型""" key: str = Field(..., max_length=100, description="设置键名")
value: str = Field(..., max_length=2000, description="设置值")
key: str = Field(..., max_length=100, description="设置键名") type: Optional[str] = Field("string", max_length=20, description="类型string/image/select/boolean")
value: str = Field(..., max_length=2000, description="设置值") options: Optional[str] = Field(None, max_length=500, description="下拉选项(用|分隔)")
type: Optional[str] = Field("string", max_length=20, description="类型string/image/select/boolean") description: Optional[str] = Field(None, max_length=255, description="设置描述")
options: Optional[str] = Field(None, max_length=500, description="下拉选项(用|分隔)")
description: Optional[str] = Field(None, max_length=255, description="设置描述") model_config = {"extra": "ignore"}
model_config = {"extra": "ignore"}
class SystemSettingCreate(SystemSettingBase):
"""系统设置创建模型"""
class SystemSettingCreate(SystemSettingBase): pass
"""系统设置创建模型"""
pass
class SystemSettingUpdate(SystemSettingBase):
"""系统设置更新模型"""
class SystemSettingUpdate(SystemSettingBase): key: Optional[str] = Field(None, max_length=100, description="设置键名")
"""系统设置更新模型"""
key: Optional[str] = Field(None, max_length=100, description="设置键名")
class SystemSettingResponse(BaseModel):
"""系统设置响应模型"""
class SystemSettingResponse(BaseModel):
"""系统设置响应模型""" id: int = Field(..., description="设置ID")
key: str = Field(..., description="设置键名")
id: int = Field(..., description="设置ID") value: str = Field(..., description="设置值")
key: str = Field(..., description="设置键名") type: str = Field(..., description="类型")
value: str = Field(..., description="设置值") options: Optional[str] = Field(None, description="下拉选项")
type: str = Field(..., description="类型") description: Optional[str] = Field(None, description="设置描述")
options: Optional[str] = Field(None, description="下拉选项") createtime: datetime = Field(..., description="创建时间")
description: Optional[str] = Field(None, description="设置描述") updatetime: datetime = Field(..., description="更新时间")
createtime: datetime = Field(..., description="创建时间")
updatetime: datetime = Field(..., description="更新时间") class Config:
from_attributes = True
class Config:
from_attributes = True
class LoginLogResponse(BaseModel):
"""登录日志响应模型"""
class LoginLogResponse(BaseModel):
"""登录日志响应模型""" id: int = Field(..., description="日志ID")
username: str = Field(..., description="用户名")
id: int = Field(..., description="日志ID") login_time: datetime = Field(..., description="登录时间")
username: str = Field(..., description="用户名") ip_address: Optional[str] = Field(None, description="IP地址")
login_time: datetime = Field(..., description="登录时间") user_agent: Optional[str] = Field(None, description="User-Agent")
ip_address: Optional[str] = Field(None, description="IP地址") status: str = Field(..., description="状态: success/failure")
user_agent: Optional[str] = Field(None, description="User-Agent") failure_reason: Optional[str] = Field(None, description="失败原因")
status: str = Field(..., description="状态: success/failure")
failure_reason: Optional[str] = Field(None, description="失败原因") class Config:
from_attributes = True
class Config:
from_attributes = True
class LoginLogSearch(BaseModel):
"""登录日志搜索模型"""
class LoginLogSearch(BaseModel):
"""登录日志搜索模型""" username: Optional[str] = Field(None, description="用户名(模糊搜索)")
status: Optional[str] = Field(None, description="状态")
username: Optional[str] = Field(None, description="用户名(模糊搜索)") start_time: Optional[datetime] = Field(None, description="开始时间")
status: Optional[str] = Field(None, description="状态") end_time: Optional[datetime] = Field(None, description="结束时间")
start_time: Optional[datetime] = Field(None, description="开始时间")
end_time: Optional[datetime] = Field(None, description="结束时间")
class LoginLogCleanRequest(BaseModel):
"""登录日志清理请求模型"""
class LoginLogCleanRequest(BaseModel):
"""登录日志清理请求模型""" before_date: datetime = Field(..., description="清理此日期之前的日志")
before_date: datetime = Field(..., description="清理此日期之前的日志")
class OperationLogResponse(BaseModel):
"""操作日志响应模型"""
class OperationLogResponse(BaseModel):
"""操作日志响应模型""" id: int = Field(..., description="日志ID")
user_id: int = Field(..., description="用户ID")
id: int = Field(..., description="日志ID") username: str = Field(..., description="用户名")
user_id: int = Field(..., description="用户ID") action_type: str = Field(..., description="操作类型: create/update/delete")
username: str = Field(..., description="用户名") target_type: str = Field(..., description="目标类型: user/role/permission/setting")
action_type: str = Field(..., description="操作类型: create/update/delete") target_id: Optional[int] = Field(None, description="目标ID")
target_type: str = Field(..., description="目标类型: user/role/permission/setting") target_name: Optional[str] = Field(None, description="目标名称")
target_id: Optional[int] = Field(None, description="目标ID") detail: Optional[str] = Field(None, description="变更详情(JSON)")
target_name: Optional[str] = Field(None, description="目标名称") ip_address: Optional[str] = Field(None, description="IP地址")
detail: Optional[str] = Field(None, description="变更详情(JSON)") user_agent: Optional[str] = Field(None, description="User-Agent")
ip_address: Optional[str] = Field(None, description="IP地址") createtime: datetime = Field(..., description="创建时间")
user_agent: Optional[str] = Field(None, description="User-Agent")
createtime: datetime = Field(..., description="创建时间") class Config:
from_attributes = True
class Config:
from_attributes = True
class OperationLogSearch(BaseModel):
"""操作日志搜索模型"""
class OperationLogSearch(BaseModel):
"""操作日志搜索模型""" username: Optional[str] = Field(None, description="用户名(模糊搜索)")
action_type: Optional[str] = Field(None, description="操作类型")
username: Optional[str] = Field(None, description="用户名(模糊搜索)") target_type: Optional[str] = Field(None, description="目标类型")
action_type: Optional[str] = Field(None, description="操作类型") start_time: Optional[datetime] = Field(None, description="开始时间")
target_type: Optional[str] = Field(None, description="目标类型") end_time: Optional[datetime] = Field(None, description="结束时间")
start_time: Optional[datetime] = Field(None, description="开始时间")
end_time: Optional[datetime] = Field(None, description="结束时间")
# ========================================
# 用户注册相关模型
# ======================================== # ========================================
# 用户注册相关模型
# ========================================
class PhoneSendCodeRequest(BaseModel):
"""发送短信验证码请求"""
class PhoneSendCodeRequest(BaseModel): phone: str = Field(..., pattern=r"^\d{11}$", description="手机号11位数字")
"""发送短信验证码请求"""
phone: str = Field(..., pattern=r"^\d{11}$", description="手机号11位数字")
class PhoneSendCodeResponse(BaseModel):
"""发送短信验证码响应"""
class PhoneSendCodeResponse(BaseModel): message: str = "验证码已发送"
"""发送短信验证码响应""" expire_seconds: int = 300
message: str = "验证码已发送"
expire_seconds: int = 300
class PhoneRegisterRequest(BaseModel):
"""手机号注册请求"""
class PhoneRegisterRequest(BaseModel): username: str = Field(..., min_length=3, max_length=50, description="用户名")
"""手机号注册请求""" phone: str = Field(..., pattern=r"^\d{11}$", description="手机号")
username: str = Field(..., min_length=3, max_length=50, description="用户名") code: str = Field(..., min_length=6, max_length=6, description="短信验证码")
phone: str = Field(..., pattern=r"^\d{11}$", description="手机号") password: str = Field(..., min_length=6, max_length=50, description="密码")
code: str = Field(..., min_length=6, max_length=6, description="短信验证码") nickname: str = Field(..., max_length=50, description="昵称")
password: str = Field(..., min_length=6, max_length=50, description="密码")
nickname: str = Field(..., max_length=50, description="昵称")
class EmailRegisterInitRequest(BaseModel):
"""邮箱注册初始化请求"""
class EmailRegisterInitRequest(BaseModel): username: str = Field(..., min_length=3, max_length=50, description="用户名")
"""邮箱注册初始化请求""" email: str = Field(..., max_length=50, description="电子邮箱")
username: str = Field(..., min_length=3, max_length=50, description="用户名") password: str = Field(..., min_length=6, max_length=50, description="密码")
email: str = Field(..., max_length=50, description="电子邮箱") nickname: str = Field(..., max_length=50, description="昵称")
password: str = Field(..., min_length=6, max_length=50, description="密码")
nickname: str = Field(..., max_length=50, description="昵称")
class EmailRegisterInitResponse(BaseModel):
"""邮箱注册初始化响应"""
class EmailRegisterInitResponse(BaseModel): message: str = "验证邮件已发送,请查收并点击链接完成注册"
"""邮箱注册初始化响应""" expire_minutes: int = 30
message: str = "验证邮件已发送,请查收并点击链接完成注册"
expire_minutes: int = 30
class RegisterSuccessResponse(BaseModel):
"""注册成功响应"""
class RegisterSuccessResponse(BaseModel): id: int
"""注册成功响应""" username: str
id: int nickname: str
username: str
nickname: str
message: str = "注册成功" message: str = "注册成功"

View File

@ -1 +1 @@
import{f as K,u as O,p as y,g as Q,E as X,q as d,w as a,k as Y,l as Z,h as f,r as o,i as p,s as ee,v as te,x as ae,d as c,a as n,c as _,y as b,t as l,z as I,F as k,A as N,B as w,b as S,C as T,D as se,o as s,_ as ne}from"./index-rb7Oshfn.js";const oe={class:"brand"},ce=["src"],ie={class:"brand-text"},le={class:"right"},re={class:"nick"},ue={class:"page-title-bar"},de={class:"page-title"},_e={class:"page-content"},fe={key:0,class:"footer"},pe={href:"https://beian.miit.gov.cn",target:"_blank"},me={key:0},ge=K({__name:"AdminLayout",setup(he){const m=Y(),x=Z(),i=O(),L=p(""),g=p(""),h=p(""),v=p(""),V=T(()=>m.path),$=T(()=>m.meta.title||""),R={perm:y(ae),system:y(te),log:y(ee)};function C(r){for(const e in R)if(r.startsWith(e))return R[e];return se}async function D(){let e=await f.get("/api/settings/public?setting_id=1");e.data&&(L.value=e.data.value)}async function E(){try{let e=await f.get("/api/settings/public?setting_id=2");e.data&&(h.value=e.data.value)}catch{}}async function M(){try{let e=await f.get("/api/settings/public?setting_id=3");e.data&&(v.value=e.data.value)}catch{}}async function P(){try{let e=await f.get("/api/settings/public?key=系统Logo");e.data&&e.data.value&&(g.value=e.data.value)}catch{}}Q(async()=>{try{await D(),await P(),await E(),await M(),i.user||await i.fetchProfile(),await i.fetchPermissions()}catch{X.error("获取用户信息失败,请重新登录"),await i.logout(),x.replace({name:"admin-login",query:{redirect:m.fullPath}})}});async function q(){await i.logout(),x.push({name:"admin-login"})}return(r,e)=>{const U=o("el-icon"),B=o("el-menu-item"),F=o("el-sub-menu"),z=o("el-menu"),W=o("el-aside"),j=o("el-button"),G=o("el-header"),H=o("router-view"),J=o("el-main"),A=o("el-container");return s(),d(A,{class:"admin-root"},{default:a(()=>[c(W,{width:"220px",class:"aside"},{default:a(()=>[n("div",oe,[g.value?(s(),_("img",{key:0,src:g.value,class:"brand-logo"},null,8,ce)):b("",!0),n("span",ie,l(L.value),1)]),c(z,{"default-active":V.value,router:"","background-color":"#0f172a","text-color":"#e2e8f0","active-text-color":"#38bdf8"},{default:a(()=>[(s(!0),_(k,null,I(w(i).menu,t=>(s(),_(k,{key:t.id},[t.children&&t.children.length>0?(s(),d(F,{key:0,index:t.route},{title:a(()=>[c(U,null,{default:a(()=>[(s(),d(N(C(t.code))))]),_:2},1024),n("span",null,l(t.name),1)]),default:a(()=>[(s(!0),_(k,null,I(t.children,u=>(s(),d(B,{key:u.id,index:u.route},{default:a(()=>[n("span",null,l(u.name),1)]),_:2},1032,["index"]))),128))]),_:2},1032,["index"])):(s(),d(B,{key:1,index:t.route},{default:a(()=>[c(U,null,{default:a(()=>[(s(),d(N(C(t.code))))]),_:2},1024),n("span",null,l(t.name),1)]),_:2},1032,["index"]))],64))),128))]),_:1},8,["default-active"])]),_:1}),c(A,null,{default:a(()=>[c(G,{class:"header"},{default:a(()=>{var t,u;return[n("div",le,[n("span",re,l(((t=w(i).user)==null?void 0:t.nickname)||((u=w(i).user)==null?void 0:u.username)),1),c(j,{type:"primary",link:"",onClick:q},{default:a(()=>[...e[0]||(e[0]=[S("退出",-1)])]),_:1})])]}),_:1}),c(J,{class:"main"},{default:a(()=>[n("div",ue,[n("h1",de,l($.value),1)]),n("div",_e,[c(H)]),h.value?(s(),_("div",fe,[S(l(h.value)+" ",1),n("a",pe,[v.value?(s(),_("span",me," | "+l(v.value),1)):b("",!0)])])):b("",!0)]),_:1})]),_:1})]),_:1})}}}),ye=ne(ge,[["__scopeId","data-v-627faa7f"]]);export{ye as default}; import{f as K,u as O,p as y,g as Q,E as X,q as d,w as a,k as Y,l as Z,h as f,r as o,i as p,s as ee,v as te,x as ae,d as c,a as n,c as _,y as b,t as l,z as I,F as k,A as N,B as w,b as S,C as T,D as se,o as s,_ as ne}from"./index-ChTrSGCb.js";const oe={class:"brand"},ce=["src"],ie={class:"brand-text"},le={class:"right"},re={class:"nick"},ue={class:"page-title-bar"},de={class:"page-title"},_e={class:"page-content"},fe={key:0,class:"footer"},pe={href:"https://beian.miit.gov.cn",target:"_blank"},me={key:0},ge=K({__name:"AdminLayout",setup(he){const m=Y(),x=Z(),i=O(),L=p(""),g=p(""),h=p(""),v=p(""),V=T(()=>m.path),$=T(()=>m.meta.title||""),R={perm:y(ae),system:y(te),log:y(ee)};function C(r){for(const e in R)if(r.startsWith(e))return R[e];return se}async function D(){let e=await f.get("/api/settings/public?setting_id=1");e.data&&(L.value=e.data.value)}async function E(){try{let e=await f.get("/api/settings/public?setting_id=2");e.data&&(h.value=e.data.value)}catch{}}async function M(){try{let e=await f.get("/api/settings/public?setting_id=3");e.data&&(v.value=e.data.value)}catch{}}async function P(){try{let e=await f.get("/api/settings/public?key=系统Logo");e.data&&e.data.value&&(g.value=e.data.value)}catch{}}Q(async()=>{try{await D(),await P(),await E(),await M(),i.user||await i.fetchProfile(),await i.fetchPermissions()}catch{X.error("获取用户信息失败,请重新登录"),await i.logout(),x.replace({name:"admin-login",query:{redirect:m.fullPath}})}});async function q(){await i.logout(),x.push({name:"admin-login"})}return(r,e)=>{const U=o("el-icon"),B=o("el-menu-item"),F=o("el-sub-menu"),z=o("el-menu"),W=o("el-aside"),j=o("el-button"),G=o("el-header"),H=o("router-view"),J=o("el-main"),A=o("el-container");return s(),d(A,{class:"admin-root"},{default:a(()=>[c(W,{width:"220px",class:"aside"},{default:a(()=>[n("div",oe,[g.value?(s(),_("img",{key:0,src:g.value,class:"brand-logo"},null,8,ce)):b("",!0),n("span",ie,l(L.value),1)]),c(z,{"default-active":V.value,router:"","background-color":"#0f172a","text-color":"#e2e8f0","active-text-color":"#38bdf8"},{default:a(()=>[(s(!0),_(k,null,I(w(i).menu,t=>(s(),_(k,{key:t.id},[t.children&&t.children.length>0?(s(),d(F,{key:0,index:t.route},{title:a(()=>[c(U,null,{default:a(()=>[(s(),d(N(C(t.code))))]),_:2},1024),n("span",null,l(t.name),1)]),default:a(()=>[(s(!0),_(k,null,I(t.children,u=>(s(),d(B,{key:u.id,index:u.route},{default:a(()=>[n("span",null,l(u.name),1)]),_:2},1032,["index"]))),128))]),_:2},1032,["index"])):(s(),d(B,{key:1,index:t.route},{default:a(()=>[c(U,null,{default:a(()=>[(s(),d(N(C(t.code))))]),_:2},1024),n("span",null,l(t.name),1)]),_:2},1032,["index"]))],64))),128))]),_:1},8,["default-active"])]),_:1}),c(A,null,{default:a(()=>[c(G,{class:"header"},{default:a(()=>{var t,u;return[n("div",le,[n("span",re,l(((t=w(i).user)==null?void 0:t.nickname)||((u=w(i).user)==null?void 0:u.username)),1),c(j,{type:"primary",link:"",onClick:q},{default:a(()=>[...e[0]||(e[0]=[S("退出",-1)])]),_:1})])]}),_:1}),c(J,{class:"main"},{default:a(()=>[n("div",ue,[n("h1",de,l($.value),1)]),n("div",_e,[c(H)]),h.value?(s(),_("div",fe,[S(l(h.value)+" ",1),n("a",pe,[v.value?(s(),_("span",me," | "+l(v.value),1)):b("",!0)])])):b("",!0)]),_:1})]),_:1})]),_:1})}}}),ye=ne(ge,[["__scopeId","data-v-627faa7f"]]);export{ye as default};

View File

@ -1 +1 @@
import{G as y,O as M,n as w,f as V,H as Y,g as D,c as H,d as n,w as l,F as O,i as p,r as s,I as R,B as E,b as _,E as m,P as L,o as I}from"./index-rb7Oshfn.js";function P(f){return()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},request:{pageRequest:async e=>{const{data:t}=await w.post("/login-logs/search",e);return t},transformQuery:({form:e})=>{const t={};return e!=null&&e.username&&(t.username=e.username),e!=null&&e.status&&(t.status=e.status),t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}}},rowHandle:{show:!1},toolbar:{buttons:{clean:{text:"清理旧日志",type:"warning",click:()=>f.onClean()}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},username:{title:"用户名",key:"username",type:"text",search:{show:!0}},status:{title:"状态",key:"status",type:"dict-select",search:{show:!0},dict:M({data:[{value:"success",label:"成功"},{value:"failure",label:"失败"}],value:"value",label:"label"}),column:{width:80}},failure_reason:{title:"失败原因",key:"failure_reason",type:"text",column:{width:160}},ip_address:{title:"IP地址",key:"ip_address",type:"text",column:{width:150}},user_agent:{title:"User-Agent",key:"user_agent",type:"text",column:{width:300}},login_time:{title:"登录时间",key:"login_time",type:"text",column:{width:170},form:{show:!1},valueBuilder({value:e,row:t,key:u}){!t||e==null||(t[u]=y(e).format("YYYY-MM-DD HH:mm:ss"))}}}}})}const S=V({__name:"LoginLogManage",setup(f){const{crudBinding:e,crudRef:t,crudExpose:u}=Y({createCrudOptions:P({onClean:()=>{o.value=!0}})}),o=p(!1),r=p(),d=p(!1);async function h(){if(!r.value){m.warning("请选择要清理的日期");return}try{await L.confirm(`确定要清理 ${y(r.value).format("YYYY-MM-DD HH:mm:ss")} 之前的所有登录日志吗?此操作不可撤销。`,"确认清理",{confirmButtonText:"确认清理",cancelButtonText:"取消",type:"warning"})}catch{return}d.value=!0;try{const{data:c}=await w.delete("/login-logs/clean",{data:{before_date:r.value.toISOString()}});m.success(c.message||"清理成功"),o.value=!1,u.doRefresh()}catch{m.error("清理失败")}finally{d.value=!1}}return D(()=>{u.doRefresh()}),(c,a)=>{const v=s("fs-crud"),x=s("fs-page"),k=s("el-date-picker"),b=s("el-form-item"),C=s("el-form"),g=s("el-button"),B=s("el-dialog");return I(),H(O,null,[n(x,null,{default:l(()=>[n(v,R({ref_key:"crudRef",ref:t},E(e)),null,16)]),_:1}),n(B,{modelValue:o.value,"onUpdate:modelValue":a[2]||(a[2]=i=>o.value=i),title:"清理旧日志",width:"420px"},{footer:l(()=>[n(g,{onClick:a[1]||(a[1]=i=>o.value=!1)},{default:l(()=>[...a[3]||(a[3]=[_("取消",-1)])]),_:1}),n(g,{type:"warning",loading:d.value,onClick:h},{default:l(()=>[...a[4]||(a[4]=[_("确认清理",-1)])]),_:1},8,["loading"])]),default:l(()=>[n(C,null,{default:l(()=>[n(b,{label:"清理此日期之前的日志"},{default:l(()=>[n(k,{modelValue:r.value,"onUpdate:modelValue":a[0]||(a[0]=i=>r.value=i),type:"datetime",placeholder:"选择日期时间",style:{width:"100%"}},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)}}});export{S as default}; import{G as y,O as M,n as w,f as V,H as Y,g as D,c as H,d as n,w as l,F as O,i as p,r as s,I as R,B as E,b as _,E as m,P as L,o as I}from"./index-ChTrSGCb.js";function P(f){return()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},request:{pageRequest:async e=>{const{data:t}=await w.post("/login-logs/search",e);return t},transformQuery:({form:e})=>{const t={};return e!=null&&e.username&&(t.username=e.username),e!=null&&e.status&&(t.status=e.status),t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}}},rowHandle:{show:!1},toolbar:{buttons:{clean:{text:"清理旧日志",type:"warning",click:()=>f.onClean()}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},username:{title:"用户名",key:"username",type:"text",search:{show:!0}},status:{title:"状态",key:"status",type:"dict-select",search:{show:!0},dict:M({data:[{value:"success",label:"成功"},{value:"failure",label:"失败"}],value:"value",label:"label"}),column:{width:80}},failure_reason:{title:"失败原因",key:"failure_reason",type:"text",column:{width:160}},ip_address:{title:"IP地址",key:"ip_address",type:"text",column:{width:150}},user_agent:{title:"User-Agent",key:"user_agent",type:"text",column:{width:300}},login_time:{title:"登录时间",key:"login_time",type:"text",column:{width:170},form:{show:!1},valueBuilder({value:e,row:t,key:u}){!t||e==null||(t[u]=y(e).format("YYYY-MM-DD HH:mm:ss"))}}}}})}const S=V({__name:"LoginLogManage",setup(f){const{crudBinding:e,crudRef:t,crudExpose:u}=Y({createCrudOptions:P({onClean:()=>{o.value=!0}})}),o=p(!1),r=p(),d=p(!1);async function h(){if(!r.value){m.warning("请选择要清理的日期");return}try{await L.confirm(`确定要清理 ${y(r.value).format("YYYY-MM-DD HH:mm:ss")} 之前的所有登录日志吗?此操作不可撤销。`,"确认清理",{confirmButtonText:"确认清理",cancelButtonText:"取消",type:"warning"})}catch{return}d.value=!0;try{const{data:c}=await w.delete("/login-logs/clean",{data:{before_date:r.value.toISOString()}});m.success(c.message||"清理成功"),o.value=!1,u.doRefresh()}catch{m.error("清理失败")}finally{d.value=!1}}return D(()=>{u.doRefresh()}),(c,a)=>{const v=s("fs-crud"),x=s("fs-page"),k=s("el-date-picker"),b=s("el-form-item"),C=s("el-form"),g=s("el-button"),B=s("el-dialog");return I(),H(O,null,[n(x,null,{default:l(()=>[n(v,R({ref_key:"crudRef",ref:t},E(e)),null,16)]),_:1}),n(B,{modelValue:o.value,"onUpdate:modelValue":a[2]||(a[2]=i=>o.value=i),title:"清理旧日志",width:"420px"},{footer:l(()=>[n(g,{onClick:a[1]||(a[1]=i=>o.value=!1)},{default:l(()=>[...a[3]||(a[3]=[_("取消",-1)])]),_:1}),n(g,{type:"warning",loading:d.value,onClick:h},{default:l(()=>[...a[4]||(a[4]=[_("确认清理",-1)])]),_:1},8,["loading"])]),default:l(()=>[n(C,null,{default:l(()=>[n(b,{label:"清理此日期之前的日志"},{default:l(()=>[n(k,{modelValue:r.value,"onUpdate:modelValue":a[0]||(a[0]=i=>r.value=i),type:"datetime",placeholder:"选择日期时间",style:{width:"100%"}},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)}}});export{S as default};

View File

@ -1 +1 @@
import{f as q,u as I,g as j,c as h,d as o,w as r,h as k,E as g,r as u,i,j as B,k as D,l as E,o as w,m as M,a as v,b as N,t as O,n as P,_ as $}from"./index-rb7Oshfn.js";const A={class:"login-wrap"},W={class:"card-title"},X={class:"captcha-row"},z=["src"],F={key:1,class:"captcha-placeholder"},G=q({__name:"LoginView",setup(H){const x=D(),R=E(),m=I(),b=i(),_=i(!1),l=i(""),p=i(""),y=i(""),V="/api",a=B({username:"",password:"",captcha_code:""}),U={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}],captcha_code:[{required:!0,message:"请输入验证码",trigger:"blur"}]};async function C(){let t=await k.get(`${V}/settings/public?setting_id=1`);t.data&&(y.value=t.data.value)}async function f(){l.value.startsWith("blob:")&&URL.revokeObjectURL(l.value),l.value="",p.value="";const t=await k.get(`${V}/users/captcha`,{responseType:"blob",headers:{}}),e=t.headers["x-captcha-id"]||t.headers["X-Captcha-ID"];if(!e){g.error("未获取到验证码 ID请检查后端 CORS expose_headers");return}p.value=e,l.value=URL.createObjectURL(t.data),a.captcha_code=""}async function L(){var e,n;if(!p.value){g.warning("请先加载验证码");return}const t=b.value;if(t){try{await t.validate()}catch{return}_.value=!0;try{const{data:s}=await P.post("/users/login",{username:a.username,password:a.password,captcha_id:p.value,captcha_code:a.captcha_code});m.setToken(s.access_token),await m.fetchProfile(),await m.fetchPermissions();const c=x.query.redirect||"/admin";await R.replace(c)}catch(s){const c=(n=(e=s==null?void 0:s.response)==null?void 0:e.data)==null?void 0:n.detail;g.error(typeof c=="string"?c:"登录失败"),await f()}finally{_.value=!1}}}return j(()=>{f(),C()}),(t,e)=>{const n=u("el-input"),s=u("el-form-item"),c=u("el-button"),S=u("el-form"),T=u("el-card");return w(),h("div",A,[o(T,{class:"card",shadow:"hover"},{header:r(()=>[v("span",W,O(y.value),1)]),default:r(()=>[o(S,{ref_key:"formRef",ref:b,model:a,rules:U,"label-width":"88px",onSubmit:e[3]||(e[3]=M(()=>{},["prevent"]))},{default:r(()=>[o(s,{label:"用户名",prop:"username"},{default:r(()=>[o(n,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=d=>a.username=d),autocomplete:"username"},null,8,["modelValue"])]),_:1}),o(s,{label:"密码",prop:"password"},{default:r(()=>[o(n,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=d=>a.password=d),type:"password","show-password":"",autocomplete:"current-password"},null,8,["modelValue"])]),_:1}),o(s,{label:"验证码",prop:"captcha_code"},{default:r(()=>[v("div",X,[o(n,{modelValue:a.captcha_code,"onUpdate:modelValue":e[2]||(e[2]=d=>a.captcha_code=d),maxlength:"8",placeholder:"请输入验证码"},null,8,["modelValue"]),v("div",{class:"captcha-img",onClick:f},[l.value?(w(),h("img",{key:0,src:l.value,alt:"captcha"},null,8,z)):(w(),h("span",F,"点击获取"))])])]),_:1}),o(s,null,{default:r(()=>[o(c,{type:"primary",loading:_.value,style:{width:"100%"},onClick:L},{default:r(()=>[...e[4]||(e[4]=[N(" 登录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"])]),_:1})])}}}),K=$(G,[["__scopeId","data-v-3d2542ad"]]);export{K as default}; import{f as q,u as I,g as j,c as h,d as o,w as r,h as k,E as g,r as u,i,j as B,k as D,l as E,o as w,m as M,a as v,b as N,t as O,n as P,_ as $}from"./index-ChTrSGCb.js";const A={class:"login-wrap"},W={class:"card-title"},X={class:"captcha-row"},z=["src"],F={key:1,class:"captcha-placeholder"},G=q({__name:"LoginView",setup(H){const x=D(),R=E(),m=I(),b=i(),_=i(!1),l=i(""),p=i(""),y=i(""),V="/api",a=B({username:"",password:"",captcha_code:""}),U={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}],captcha_code:[{required:!0,message:"请输入验证码",trigger:"blur"}]};async function C(){let t=await k.get(`${V}/settings/public?setting_id=1`);t.data&&(y.value=t.data.value)}async function f(){l.value.startsWith("blob:")&&URL.revokeObjectURL(l.value),l.value="",p.value="";const t=await k.get(`${V}/users/captcha`,{responseType:"blob",headers:{}}),e=t.headers["x-captcha-id"]||t.headers["X-Captcha-ID"];if(!e){g.error("未获取到验证码 ID请检查后端 CORS expose_headers");return}p.value=e,l.value=URL.createObjectURL(t.data),a.captcha_code=""}async function L(){var e,n;if(!p.value){g.warning("请先加载验证码");return}const t=b.value;if(t){try{await t.validate()}catch{return}_.value=!0;try{const{data:s}=await P.post("/users/login",{username:a.username,password:a.password,captcha_id:p.value,captcha_code:a.captcha_code});m.setToken(s.access_token),await m.fetchProfile(),await m.fetchPermissions();const c=x.query.redirect||"/admin";await R.replace(c)}catch(s){const c=(n=(e=s==null?void 0:s.response)==null?void 0:e.data)==null?void 0:n.detail;g.error(typeof c=="string"?c:"登录失败"),await f()}finally{_.value=!1}}}return j(()=>{f(),C()}),(t,e)=>{const n=u("el-input"),s=u("el-form-item"),c=u("el-button"),S=u("el-form"),T=u("el-card");return w(),h("div",A,[o(T,{class:"card",shadow:"hover"},{header:r(()=>[v("span",W,O(y.value),1)]),default:r(()=>[o(S,{ref_key:"formRef",ref:b,model:a,rules:U,"label-width":"88px",onSubmit:e[3]||(e[3]=M(()=>{},["prevent"]))},{default:r(()=>[o(s,{label:"用户名",prop:"username"},{default:r(()=>[o(n,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=d=>a.username=d),autocomplete:"username"},null,8,["modelValue"])]),_:1}),o(s,{label:"密码",prop:"password"},{default:r(()=>[o(n,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=d=>a.password=d),type:"password","show-password":"",autocomplete:"current-password"},null,8,["modelValue"])]),_:1}),o(s,{label:"验证码",prop:"captcha_code"},{default:r(()=>[v("div",X,[o(n,{modelValue:a.captcha_code,"onUpdate:modelValue":e[2]||(e[2]=d=>a.captcha_code=d),maxlength:"8",placeholder:"请输入验证码"},null,8,["modelValue"]),v("div",{class:"captcha-img",onClick:f},[l.value?(w(),h("img",{key:0,src:l.value,alt:"captcha"},null,8,z)):(w(),h("span",F,"点击获取"))])])]),_:1}),o(s,null,{default:r(()=>[o(c,{type:"primary",loading:_.value,style:{width:"100%"},onClick:L},{default:r(()=>[...e[4]||(e[4]=[N(" 登录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"])]),_:1})])}}}),K=$(G,[["__scopeId","data-v-3d2542ad"]]);export{K as default};

View File

@ -1 +1 @@
import{G as l,E as t,n as u,f as y,H as h,g as w,q as _,w as x,r as m,d as k,I as q,B as E,o as M}from"./index-rb7Oshfn.js";import{t as D}from"./list-query-4ZZvVtjn.js";const R=()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},tree:{enable:!0,childrenKey:"children",parentKey:"parentid",hasChildField:"children"},request:{transformQuery:({form:e})=>D(e,["name","code"]),pageRequest:async e=>{const{data:s}=await u.get("/permissions/tree");return s},transformRes:({res:e})=>{const s=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(s.length,1),total:s.length,records:s}},addRequest:async({form:e})=>{await u.post("/permissions/",e),t.success({message:"添加成功",duration:5e3})},editRequest:async({form:e,row:s})=>{s&&(await u.put(`/permissions/${s.id}`,e),t.success({message:"修改成功",duration:5e3}))},delRequest:async({row:e})=>{e&&(await u.delete(`/permissions/${e.id}`),t.success({message:"删除成功",duration:5e3}))},onSuccess:({action:e})=>{e==="add"?t.success({message:"添加成功",duration:5e3}):e==="edit"&&t.success({message:"修改成功",duration:5e3})},onError:e=>{var r;let s="操作失败";if((r=e.response)!=null&&r.data){const a=e.response.data;a.message?s=a.message:a.detail&&(s=typeof a.detail=="string"?a.detail:"操作失败")}t.error({message:s,duration:5e3})}},table:{remove:{showSuccessNotification:!1}},rowHandle:{buttons:{edit:{text:"编辑"},remove:{text:"删除"}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},name:{title:"名称",key:"name",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"必填"}]}},code:{title:"编码",key:"code",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"必填"}]}},parentid:{title:"父级ID",key:"parentid",type:"number",form:{value:0,rules:[{required:!0,message:"必填"}]}},description:{title:"描述",key:"description",type:"textarea",column:{show:!1}},route:{title:"路由",key:"route",type:"text",column:{width:200},form:{placeholder:"如:/admin/users"}},sort:{title:"排序",key:"sort",type:"number",column:{width:80,align:"center"},form:{value:0,rules:[{required:!0,message:"必填"}],helper:"数字越小,排序越靠前"}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:r}){!s||e==null||(s[r]=l(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:r}){!s||e==null||(s[r]=l(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),b=y({__name:"MenuManage",setup(e){const{crudBinding:s,crudRef:r,crudExpose:a}=h({createCrudOptions:R}),p=()=>{t.success({message:"添加成功",duration:5e3})},f=()=>{t.success({message:"修改成功",duration:5e3})},g=()=>{t.success({message:"删除成功",duration:5e3})},d=c=>{var i;let n="操作失败";if((i=c.response)!=null&&i.data){const o=c.response.data;o.message?n=o.message:o.detail&&(n=typeof o.detail=="string"?o.detail:"操作失败")}t.error({message:n,duration:5e3})};return w(()=>{a.doRefresh()}),(c,n)=>{const i=m("fs-crud"),o=m("fs-page");return M(),_(o,null,{default:x(()=>[k(i,q({ref_key:"crudRef",ref:r},E(s),{onAddSuccess:p,onEditSuccess:f,onDelSuccess:g,onAddError:d,onEditError:d,onDelError:d}),null,16)]),_:1})}}});export{b as default}; import{G as l,E as t,n as u,f as y,H as h,g as w,q as _,w as x,r as m,d as k,I as q,B as E,o as M}from"./index-ChTrSGCb.js";import{t as D}from"./list-query-4ZZvVtjn.js";const R=()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},tree:{enable:!0,childrenKey:"children",parentKey:"parentid",hasChildField:"children"},request:{transformQuery:({form:e})=>D(e,["name","code"]),pageRequest:async e=>{const{data:s}=await u.get("/permissions/tree");return s},transformRes:({res:e})=>{const s=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(s.length,1),total:s.length,records:s}},addRequest:async({form:e})=>{await u.post("/permissions/",e),t.success({message:"添加成功",duration:5e3})},editRequest:async({form:e,row:s})=>{s&&(await u.put(`/permissions/${s.id}`,e),t.success({message:"修改成功",duration:5e3}))},delRequest:async({row:e})=>{e&&(await u.delete(`/permissions/${e.id}`),t.success({message:"删除成功",duration:5e3}))},onSuccess:({action:e})=>{e==="add"?t.success({message:"添加成功",duration:5e3}):e==="edit"&&t.success({message:"修改成功",duration:5e3})},onError:e=>{var r;let s="操作失败";if((r=e.response)!=null&&r.data){const a=e.response.data;a.message?s=a.message:a.detail&&(s=typeof a.detail=="string"?a.detail:"操作失败")}t.error({message:s,duration:5e3})}},table:{remove:{showSuccessNotification:!1}},rowHandle:{buttons:{edit:{text:"编辑"},remove:{text:"删除"}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},name:{title:"名称",key:"name",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"必填"}]}},code:{title:"编码",key:"code",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"必填"}]}},parentid:{title:"父级ID",key:"parentid",type:"number",form:{value:0,rules:[{required:!0,message:"必填"}]}},description:{title:"描述",key:"description",type:"textarea",column:{show:!1}},route:{title:"路由",key:"route",type:"text",column:{width:200},form:{placeholder:"如:/admin/users"}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:r}){!s||e==null||(s[r]=l(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:r}){!s||e==null||(s[r]=l(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),B=y({__name:"MenuManage",setup(e){const{crudBinding:s,crudRef:r,crudExpose:a}=h({createCrudOptions:R}),p=()=>{t.success({message:"添加成功",duration:5e3})},f=()=>{t.success({message:"修改成功",duration:5e3})},g=()=>{t.success({message:"删除成功",duration:5e3})},d=c=>{var i;let n="操作失败";if((i=c.response)!=null&&i.data){const o=c.response.data;o.message?n=o.message:o.detail&&(n=typeof o.detail=="string"?o.detail:"操作失败")}t.error({message:n,duration:5e3})};return w(()=>{a.doRefresh()}),(c,n)=>{const i=m("fs-crud"),o=m("fs-page");return M(),_(o,null,{default:x(()=>[k(i,q({ref_key:"crudRef",ref:r},E(s),{onAddSuccess:p,onEditSuccess:f,onDelSuccess:g,onAddError:d,onEditError:d,onDelError:d}),null,16)]),_:1})}}});export{B as default};

View File

@ -1 +1 @@
import{G as w,O as f,n as h,f as M,H as V,g as O,c as Y,d as l,w as n,F as H,i as p,r as o,I,B as R,b as g,E as m,P as E,o as P}from"./index-rb7Oshfn.js";function A(_){return()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},request:{pageRequest:async e=>{const{data:t}=await h.post("/operation-logs/search",e);return t},transformQuery:({form:e})=>{const t={};return e!=null&&e.username&&(t.username=e.username),e!=null&&e.action_type&&(t.action_type=e.action_type),e!=null&&e.target_type&&(t.target_type=e.target_type),t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}}},rowHandle:{show:!1},toolbar:{buttons:{clean:{text:"清理旧日志",type:"warning",click:()=>_.onClean()}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},user_id:{title:"用户ID",key:"user_id",type:"number",column:{width:80},form:{show:!1}},username:{title:"操作人",key:"username",type:"text",search:{show:!0}},action_type:{title:"操作类型",key:"action_type",type:"dict-select",search:{show:!0},dict:f({data:[{value:"create",label:"创建"},{value:"update",label:"更新"},{value:"delete",label:"删除"}],value:"value",label:"label"}),column:{width:90}},target_type:{title:"目标类型",key:"target_type",type:"dict-select",search:{show:!0},dict:f({data:[{value:"user",label:"用户"},{value:"role",label:"角色"},{value:"permission",label:"菜单"},{value:"setting",label:"系统设置"}],value:"value",label:"label"}),column:{width:100}},target_name:{title:"目标名称",key:"target_name",type:"text",column:{width:140}},target_id:{title:"目标ID",key:"target_id",type:"number",column:{width:80},form:{show:!1}},detail:{title:"变更详情",key:"detail",type:"text",column:{width:200},form:{show:!1}},ip_address:{title:"IP地址",key:"ip_address",type:"text",column:{width:150}},user_agent:{title:"User-Agent",key:"user_agent",type:"text",column:{width:300}},createtime:{title:"操作时间",key:"createtime",type:"text",column:{width:170},form:{show:!1},valueBuilder({value:e,row:t,key:u}){!t||e==null||(t[u]=w(e).format("YYYY-MM-DD HH:mm:ss"))}}}}})}const S=M({__name:"OperationLogManage",setup(_){const{crudBinding:e,crudRef:t,crudExpose:u}=V({createCrudOptions:A({onClean:()=>{s.value=!0}})}),s=p(!1),r=p(),d=p(!1);async function v(){if(!r.value){m.warning("请选择要清理的日期");return}try{await E.confirm(`确定要清理 ${w(r.value).format("YYYY-MM-DD HH:mm:ss")} 之前的所有操作日志吗?此操作不可撤销。`,"确认清理",{confirmButtonText:"确认清理",cancelButtonText:"取消",type:"warning"})}catch{return}d.value=!0;try{const{data:c}=await h.delete("/operation-logs/clean",{data:{before_date:r.value.toISOString()}});m.success(c.message||"清理成功"),s.value=!1,u.doRefresh()}catch{m.error("清理失败")}finally{d.value=!1}}return O(()=>{u.doRefresh()}),(c,a)=>{const b=o("fs-crud"),k=o("fs-page"),x=o("el-date-picker"),C=o("el-form-item"),B=o("el-form"),y=o("el-button"),D=o("el-dialog");return P(),Y(H,null,[l(k,null,{default:n(()=>[l(b,I({ref_key:"crudRef",ref:t},R(e)),null,16)]),_:1}),l(D,{modelValue:s.value,"onUpdate:modelValue":a[2]||(a[2]=i=>s.value=i),title:"清理旧日志",width:"420px"},{footer:n(()=>[l(y,{onClick:a[1]||(a[1]=i=>s.value=!1)},{default:n(()=>[...a[3]||(a[3]=[g("取消",-1)])]),_:1}),l(y,{type:"warning",loading:d.value,onClick:v},{default:n(()=>[...a[4]||(a[4]=[g("确认清理",-1)])]),_:1},8,["loading"])]),default:n(()=>[l(B,null,{default:n(()=>[l(C,{label:"清理此日期之前的日志"},{default:n(()=>[l(x,{modelValue:r.value,"onUpdate:modelValue":a[0]||(a[0]=i=>r.value=i),type:"datetime",placeholder:"选择日期时间",style:{width:"100%"}},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)}}});export{S as default}; import{G as w,O as f,n as h,f as M,H as V,g as O,c as Y,d as l,w as n,F as H,i as p,r as o,I,B as R,b as g,E as m,P as E,o as P}from"./index-ChTrSGCb.js";function A(_){return()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},request:{pageRequest:async e=>{const{data:t}=await h.post("/operation-logs/search",e);return t},transformQuery:({form:e})=>{const t={};return e!=null&&e.username&&(t.username=e.username),e!=null&&e.action_type&&(t.action_type=e.action_type),e!=null&&e.target_type&&(t.target_type=e.target_type),t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}}},rowHandle:{show:!1},toolbar:{buttons:{clean:{text:"清理旧日志",type:"warning",click:()=>_.onClean()}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},user_id:{title:"用户ID",key:"user_id",type:"number",column:{width:80},form:{show:!1}},username:{title:"操作人",key:"username",type:"text",search:{show:!0}},action_type:{title:"操作类型",key:"action_type",type:"dict-select",search:{show:!0},dict:f({data:[{value:"create",label:"创建"},{value:"update",label:"更新"},{value:"delete",label:"删除"}],value:"value",label:"label"}),column:{width:90}},target_type:{title:"目标类型",key:"target_type",type:"dict-select",search:{show:!0},dict:f({data:[{value:"user",label:"用户"},{value:"role",label:"角色"},{value:"permission",label:"菜单"},{value:"setting",label:"系统设置"}],value:"value",label:"label"}),column:{width:100}},target_name:{title:"目标名称",key:"target_name",type:"text",column:{width:140}},target_id:{title:"目标ID",key:"target_id",type:"number",column:{width:80},form:{show:!1}},detail:{title:"变更详情",key:"detail",type:"text",column:{width:200},form:{show:!1}},ip_address:{title:"IP地址",key:"ip_address",type:"text",column:{width:150}},user_agent:{title:"User-Agent",key:"user_agent",type:"text",column:{width:300}},createtime:{title:"操作时间",key:"createtime",type:"text",column:{width:170},form:{show:!1},valueBuilder({value:e,row:t,key:u}){!t||e==null||(t[u]=w(e).format("YYYY-MM-DD HH:mm:ss"))}}}}})}const S=M({__name:"OperationLogManage",setup(_){const{crudBinding:e,crudRef:t,crudExpose:u}=V({createCrudOptions:A({onClean:()=>{s.value=!0}})}),s=p(!1),r=p(),d=p(!1);async function v(){if(!r.value){m.warning("请选择要清理的日期");return}try{await E.confirm(`确定要清理 ${w(r.value).format("YYYY-MM-DD HH:mm:ss")} 之前的所有操作日志吗?此操作不可撤销。`,"确认清理",{confirmButtonText:"确认清理",cancelButtonText:"取消",type:"warning"})}catch{return}d.value=!0;try{const{data:c}=await h.delete("/operation-logs/clean",{data:{before_date:r.value.toISOString()}});m.success(c.message||"清理成功"),s.value=!1,u.doRefresh()}catch{m.error("清理失败")}finally{d.value=!1}}return O(()=>{u.doRefresh()}),(c,a)=>{const b=o("fs-crud"),k=o("fs-page"),x=o("el-date-picker"),C=o("el-form-item"),B=o("el-form"),y=o("el-button"),D=o("el-dialog");return P(),Y(H,null,[l(k,null,{default:n(()=>[l(b,I({ref_key:"crudRef",ref:t},R(e)),null,16)]),_:1}),l(D,{modelValue:s.value,"onUpdate:modelValue":a[2]||(a[2]=i=>s.value=i),title:"清理旧日志",width:"420px"},{footer:n(()=>[l(y,{onClick:a[1]||(a[1]=i=>s.value=!1)},{default:n(()=>[...a[3]||(a[3]=[g("取消",-1)])]),_:1}),l(y,{type:"warning",loading:d.value,onClick:v},{default:n(()=>[...a[4]||(a[4]=[g("确认清理",-1)])]),_:1},8,["loading"])]),default:n(()=>[l(B,null,{default:n(()=>[l(C,{label:"清理此日期之前的日志"},{default:n(()=>[l(x,{modelValue:r.value,"onUpdate:modelValue":a[0]||(a[0]=i=>r.value=i),type:"datetime",placeholder:"选择日期时间",style:{width:"100%"}},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)}}});export{S as default};

View File

@ -1 +1 @@
import{_ as n,c as r,a as s,b as t,d as l,w as d,r as i,o as a}from"./index-rb7Oshfn.js";const p={},u={class:"public-wrap"},m={class:"hint"};function f(_,o){const e=i("router-link");return a(),r("div",u,[o[5]||(o[5]=s("h1",null,"BookSystem",-1)),s("p",m,[o[1]||(o[1]=t(" 此处预留为",-1)),o[2]||(o[2]=s("strong",null,"无需登录",-1)),o[3]||(o[3]=t("的站点首页(图书浏览、门户等)。管理功能请前往 ",-1)),l(e,{to:"/admin/login"},{default:d(()=>[...o[0]||(o[0]=[t("管理后台",-1)])]),_:1}),o[4]||(o[4]=t("。 ",-1))])])}const x=n(p,[["render",f],["__scopeId","data-v-f5876933"]]);export{x as default}; import{_ as n,c as r,a as s,b as t,d as l,w as d,r as i,o as a}from"./index-ChTrSGCb.js";const p={},u={class:"public-wrap"},m={class:"hint"};function f(_,o){const e=i("router-link");return a(),r("div",u,[o[5]||(o[5]=s("h1",null,"BookSystem",-1)),s("p",m,[o[1]||(o[1]=t(" 此处预留为",-1)),o[2]||(o[2]=s("strong",null,"无需登录",-1)),o[3]||(o[3]=t("的站点首页(图书浏览、门户等)。管理功能请前往 ",-1)),l(e,{to:"/admin/login"},{default:d(()=>[...o[0]||(o[0]=[t("管理后台",-1)])]),_:1}),o[4]||(o[4]=t("。 ",-1))])])}const x=n(p,[["render",f],["__scopeId","data-v-f5876933"]]);export{x as default};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.setting-container[data-v-a71db493]{padding:20px}.header[data-v-a71db493]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h2[data-v-a71db493]{margin:0;font-size:18px}.add-btn[data-v-a71db493]{background:#409eff;color:#fff;border:none;padding:8px 16px;border-radius:4px;cursor:pointer}.add-btn[data-v-a71db493]:hover{background:#66b1ff}.settings-table[data-v-a71db493]{width:100%;border-collapse:collapse}.settings-table th[data-v-a71db493],.settings-table td[data-v-a71db493]{border:1px solid #ebeef5;padding:12px;text-align:left}.settings-table th[data-v-a71db493]{background:#f5f7fa;font-weight:600}.value-cell[data-v-a71db493]{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cell-image[data-v-a71db493]{width:60px;height:60px;object-fit:contain;border-radius:4px;background:#f5f7fa}.actions[data-v-a71db493]{display:flex;gap:8px}.edit-btn[data-v-a71db493]{background:#67c23a;color:#fff;border:none;padding:4px 12px;border-radius:4px;cursor:pointer}.edit-btn[data-v-a71db493]:hover{background:#85ce61}.modal-overlay[data-v-a71db493]{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;justify-content:center;align-items:center;z-index:1000}.modal-content[data-v-a71db493]{background:#fff;padding:24px;border-radius:8px;width:480px}.modal-content h3[data-v-a71db493]{margin:0 0 20px;font-size:16px}.form-group[data-v-a71db493]{margin-bottom:16px}.form-group label[data-v-a71db493]{display:block;margin-bottom:8px;font-weight:500}.form-group input[data-v-a71db493],.form-group textarea[data-v-a71db493],.form-select[data-v-a71db493]{width:100%;padding:8px;border:1px solid #dcdfe6;border-radius:4px;box-sizing:border-box}.form-group textarea[data-v-a71db493]{resize:vertical}.form-select[data-v-a71db493]{height:36px}.image-upload-area[data-v-a71db493]{display:flex;flex-direction:column;align-items:center;gap:12px;padding:16px;border:1px dashed #dcdfe6;border-radius:4px;background:#fafafa}.preview-image[data-v-a71db493]{max-width:200px;max-height:120px;object-fit:contain;border-radius:4px}.no-image[data-v-a71db493]{color:#c0c4cc;font-size:13px;padding:12px}.upload-btn[data-v-a71db493]{background:#409eff;color:#fff;border:none;padding:6px 16px;border-radius:4px;cursor:pointer;font-size:13px}.upload-btn[data-v-a71db493]:hover{background:#66b1ff}.upload-hint[data-v-a71db493]{color:#909399;font-size:13px}.modal-footer[data-v-a71db493]{display:flex;justify-content:flex-end;gap:12px;margin-top:24px}.cancel-btn[data-v-a71db493]{background:#f5f7fa;color:#606266;border:none;padding:8px 16px;border-radius:4px;cursor:pointer}.cancel-btn[data-v-a71db493]:hover{background:#e4e7ed}.submit-btn[data-v-a71db493]{background:#409eff;color:#fff;border:none;padding:8px 16px;border-radius:4px;cursor:pointer}.submit-btn[data-v-a71db493]:hover{background:#66b1ff}

View File

@ -1 +1 @@
import{f as I,c as n,a as e,F as L,z as B,m as T,t as u,L as p,M as b,N as M,y as C,i as r,n as f,E as i,o,_ as R}from"./index-rb7Oshfn.js";const j={class:"setting-container"},z={class:"settings-table"},A={class:"value-cell"},H=["src"],Y={key:1},q={class:"actions"},G=["onClick"],J={class:"modal-content"},K={class:"form-group"},O={class:"form-group"},P={class:"form-group"},Q={key:0,class:"image-upload-area"},W=["src"],X={key:1,class:"no-image"},Z={key:2,class:"upload-hint"},ee={class:"form-group"},te={class:"modal-footer"},le=I({__name:"SettingManage",setup(ae){const D=s=>({string:"字符串",image:"图片",select:"下拉列表",boolean:"布尔值"})[s]||s,_=s=>{const t=new Date(s);if(isNaN(t.getTime()))return"-";const l=t.getFullYear(),c=String(t.getMonth()+1).padStart(2,"0"),y=String(t.getDate()).padStart(2,"0"),k=String(t.getHours()).padStart(2,"0"),F=String(t.getMinutes()).padStart(2,"0"),N=String(t.getSeconds()).padStart(2,"0");return`${l}-${c}-${y} ${k}:${F}:${N}`},h=r([]),v=r(!1),d=r(!1),g=r(!1),S=r(),a=r({id:0,key:"",value:"",type:"string",description:""}),w=async()=>{try{const{data:s}=await f.get("/settings/");h.value=s}catch{i.error("加载失败")}},$=()=>{d.value=!1,a.value={id:0,key:"",value:"",type:"string",description:""},v.value=!0},x=s=>{d.value=!0,a.value={id:s.id,key:s.key,value:s.value,type:s.type||"string",description:s.description},v.value=!0},m=()=>{v.value=!1,g.value=!1},V=()=>{var s;(s=S.value)==null||s.click()},U=async s=>{var c;const t=s.target,l=(c=t.files)==null?void 0:c[0];if(l){g.value=!0;try{const y=new FormData;y.append("file",l);const{data:k}=await f.post("/settings/upload",y);a.value.value=k.url,i.success("图片上传成功")}catch{i.error("图片上传失败")}finally{g.value=!1,t.value=""}}},E=async()=>{if(!a.value.key){i.warning("请输入设置键名");return}try{d.value?(await f.put(`/settings/${a.value.id}`,a.value),i.success("修改成功")):(await f.post("/settings/",a.value),i.success("添加成功")),m(),await w()}catch{i.error("操作失败")}};return w(),(s,t)=>(o(),n("div",j,[e("div",{class:"header"},[t[5]||(t[5]=e("h2",null,"系统设置管理",-1)),e("button",{class:"add-btn",onClick:$},"添加设置")]),e("table",z,[t[6]||(t[6]=e("thead",null,[e("tr",null,[e("th",null,"ID"),e("th",null,"设置键名"),e("th",null,"类型"),e("th",null,"设置值"),e("th",null,"描述"),e("th",null,"更新时间"),e("th",null,"创建时间"),e("th",null,"操作")])],-1)),e("tbody",null,[(o(!0),n(L,null,B(h.value,l=>(o(),n("tr",{key:l.id},[e("td",null,u(l.id),1),e("td",null,u(l.key),1),e("td",null,u(D(l.type)),1),e("td",A,[l.type==="image"&&l.value?(o(),n("img",{key:0,src:l.value,class:"cell-image"},null,8,H)):(o(),n("span",Y,u(l.value),1))]),e("td",null,u(l.description||"-"),1),e("td",null,u(_(l.updatetime)),1),e("td",null,u(_(l.createtime)),1),e("td",q,[e("button",{class:"edit-btn",onClick:c=>x(l)},"编辑",8,G)])]))),128))])]),v.value?(o(),n("div",{key:0,class:"modal-overlay",onClick:T(m,["self"])},[e("div",J,[e("h3",null,u(d.value?"编辑设置":"添加设置"),1),e("div",K,[t[7]||(t[7]=e("label",null,"设置键名 *",-1)),p(e("input",{"onUpdate:modelValue":t[0]||(t[0]=l=>a.value.key=l),type:"text",placeholder:"请输入设置键名"},null,512),[[b,a.value.key]])]),e("div",O,[t[9]||(t[9]=e("label",null,"类型",-1)),p(e("select",{"onUpdate:modelValue":t[1]||(t[1]=l=>a.value.type=l),class:"form-select"},[...t[8]||(t[8]=[e("option",{value:"string"},"字符串",-1),e("option",{value:"image"},"图片",-1),e("option",{value:"select"},"下拉列表",-1),e("option",{value:"boolean"},"布尔值",-1)])],512),[[M,a.value.type]])]),e("div",P,[t[11]||(t[11]=e("label",null,"设置值",-1)),a.value.type==="image"?(o(),n("div",Q,[a.value.value?(o(),n("img",{key:0,src:a.value.value,class:"preview-image"},null,8,W)):(o(),n("div",X,"暂无图片")),e("input",{ref_key:"fileInputRef",ref:S,type:"file",accept:"image/png,image/jpeg,image/gif,image/webp",style:{display:"none"},onChange:U},null,544),e("button",{class:"upload-btn",onClick:V},"选择图片"),g.value?(o(),n("span",Z,"上传中...")):C("",!0)])):a.value.type==="boolean"?p((o(),n("select",{key:1,"onUpdate:modelValue":t[2]||(t[2]=l=>a.value.value=l),class:"form-select"},[...t[10]||(t[10]=[e("option",{value:"true"},"是",-1),e("option",{value:"false"},"否",-1)])],512)),[[M,a.value.value]]):p((o(),n("textarea",{key:2,"onUpdate:modelValue":t[3]||(t[3]=l=>a.value.value=l),rows:"3",placeholder:"请输入设置值"},null,512)),[[b,a.value.value]])]),e("div",ee,[t[12]||(t[12]=e("label",null,"描述",-1)),p(e("textarea",{"onUpdate:modelValue":t[4]||(t[4]=l=>a.value.description=l),rows:"2",placeholder:"请输入描述"},null,512),[[b,a.value.description]])]),e("div",te,[e("button",{class:"cancel-btn",onClick:m},"取消"),e("button",{class:"submit-btn",onClick:E},u(d.value?"保存修改":"确认添加"),1)])])])):C("",!0)]))}}),ne=R(le,[["__scopeId","data-v-1321c610"]]);export{ne as default}; import{f as I,c as n,a as e,F as L,z as B,m as T,t as u,L as p,M as b,N as M,y as C,i as r,n as f,E as i,o,_ as R}from"./index-ChTrSGCb.js";const j={class:"setting-container"},z={class:"settings-table"},A={class:"value-cell"},H=["src"],Y={key:1},q={class:"actions"},G=["onClick"],J={class:"modal-content"},K={class:"form-group"},O={class:"form-group"},P={class:"form-group"},Q={key:0,class:"image-upload-area"},W=["src"],X={key:1,class:"no-image"},Z={key:2,class:"upload-hint"},ee={class:"form-group"},te={class:"modal-footer"},le=I({__name:"SettingManage",setup(ae){const D=s=>({string:"字符串",image:"图片",select:"下拉列表",boolean:"布尔值"})[s]||s,_=s=>{const t=new Date(s);if(isNaN(t.getTime()))return"-";const l=t.getFullYear(),c=String(t.getMonth()+1).padStart(2,"0"),y=String(t.getDate()).padStart(2,"0"),k=String(t.getHours()).padStart(2,"0"),F=String(t.getMinutes()).padStart(2,"0"),N=String(t.getSeconds()).padStart(2,"0");return`${l}-${c}-${y} ${k}:${F}:${N}`},h=r([]),v=r(!1),d=r(!1),g=r(!1),S=r(),a=r({id:0,key:"",value:"",type:"string",description:""}),w=async()=>{try{const{data:s}=await f.get("/settings/");h.value=s}catch{i.error("加载失败")}},$=()=>{d.value=!1,a.value={id:0,key:"",value:"",type:"string",description:""},v.value=!0},x=s=>{d.value=!0,a.value={id:s.id,key:s.key,value:s.value,type:s.type||"string",description:s.description},v.value=!0},m=()=>{v.value=!1,g.value=!1},V=()=>{var s;(s=S.value)==null||s.click()},U=async s=>{var c;const t=s.target,l=(c=t.files)==null?void 0:c[0];if(l){g.value=!0;try{const y=new FormData;y.append("file",l);const{data:k}=await f.post("/settings/upload",y);a.value.value=k.url,i.success("图片上传成功")}catch{i.error("图片上传失败")}finally{g.value=!1,t.value=""}}},E=async()=>{if(!a.value.key){i.warning("请输入设置键名");return}try{d.value?(await f.put(`/settings/${a.value.id}`,a.value),i.success("修改成功")):(await f.post("/settings/",a.value),i.success("添加成功")),m(),await w()}catch{i.error("操作失败")}};return w(),(s,t)=>(o(),n("div",j,[e("div",{class:"header"},[t[5]||(t[5]=e("h2",null,"系统设置管理",-1)),e("button",{class:"add-btn",onClick:$},"添加设置")]),e("table",z,[t[6]||(t[6]=e("thead",null,[e("tr",null,[e("th",null,"ID"),e("th",null,"设置键名"),e("th",null,"类型"),e("th",null,"设置值"),e("th",null,"描述"),e("th",null,"更新时间"),e("th",null,"创建时间"),e("th",null,"操作")])],-1)),e("tbody",null,[(o(!0),n(L,null,B(h.value,l=>(o(),n("tr",{key:l.id},[e("td",null,u(l.id),1),e("td",null,u(l.key),1),e("td",null,u(D(l.type)),1),e("td",A,[l.type==="image"&&l.value?(o(),n("img",{key:0,src:l.value,class:"cell-image"},null,8,H)):(o(),n("span",Y,u(l.value),1))]),e("td",null,u(l.description||"-"),1),e("td",null,u(_(l.updatetime)),1),e("td",null,u(_(l.createtime)),1),e("td",q,[e("button",{class:"edit-btn",onClick:c=>x(l)},"编辑",8,G)])]))),128))])]),v.value?(o(),n("div",{key:0,class:"modal-overlay",onClick:T(m,["self"])},[e("div",J,[e("h3",null,u(d.value?"编辑设置":"添加设置"),1),e("div",K,[t[7]||(t[7]=e("label",null,"设置键名 *",-1)),p(e("input",{"onUpdate:modelValue":t[0]||(t[0]=l=>a.value.key=l),type:"text",placeholder:"请输入设置键名"},null,512),[[b,a.value.key]])]),e("div",O,[t[9]||(t[9]=e("label",null,"类型",-1)),p(e("select",{"onUpdate:modelValue":t[1]||(t[1]=l=>a.value.type=l),class:"form-select"},[...t[8]||(t[8]=[e("option",{value:"string"},"字符串",-1),e("option",{value:"image"},"图片",-1),e("option",{value:"select"},"下拉列表",-1),e("option",{value:"boolean"},"布尔值",-1)])],512),[[M,a.value.type]])]),e("div",P,[t[11]||(t[11]=e("label",null,"设置值",-1)),a.value.type==="image"?(o(),n("div",Q,[a.value.value?(o(),n("img",{key:0,src:a.value.value,class:"preview-image"},null,8,W)):(o(),n("div",X,"暂无图片")),e("input",{ref_key:"fileInputRef",ref:S,type:"file",accept:"image/png,image/jpeg,image/gif,image/webp",style:{display:"none"},onChange:U},null,544),e("button",{class:"upload-btn",onClick:V},"选择图片"),g.value?(o(),n("span",Z,"上传中...")):C("",!0)])):a.value.type==="boolean"?p((o(),n("select",{key:1,"onUpdate:modelValue":t[2]||(t[2]=l=>a.value.value=l),class:"form-select"},[...t[10]||(t[10]=[e("option",{value:"true"},"是",-1),e("option",{value:"false"},"否",-1)])],512)),[[M,a.value.value]]):p((o(),n("textarea",{key:2,"onUpdate:modelValue":t[3]||(t[3]=l=>a.value.value=l),rows:"3",placeholder:"请输入设置值"},null,512)),[[b,a.value.value]])]),e("div",ee,[t[12]||(t[12]=e("label",null,"描述",-1)),p(e("textarea",{"onUpdate:modelValue":t[4]||(t[4]=l=>a.value.description=l),rows:"2",placeholder:"请输入描述"},null,512),[[b,a.value.description]])]),e("div",te,[e("button",{class:"cancel-btn",onClick:m},"取消"),e("button",{class:"submit-btn",onClick:E},u(d.value?"保存修改":"确认添加"),1)])])])):C("",!0)]))}}),ne=R(le,[["__scopeId","data-v-a71db493"]]);export{ne as default};

View File

@ -1 +0,0 @@
.setting-container[data-v-1321c610]{padding:20px}.header[data-v-1321c610]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h2[data-v-1321c610]{margin:0;font-size:18px}.add-btn[data-v-1321c610]{background:#409eff;color:#fff;border:none;padding:8px 16px;border-radius:4px;cursor:pointer}.add-btn[data-v-1321c610]:hover{background:#66b1ff}.settings-table[data-v-1321c610]{width:100%;border-collapse:collapse}.settings-table th[data-v-1321c610],.settings-table td[data-v-1321c610]{border:1px solid #ebeef5;padding:12px;text-align:left}.settings-table th[data-v-1321c610]{background:#f5f7fa;font-weight:600}.value-cell[data-v-1321c610]{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cell-image[data-v-1321c610]{width:60px;height:60px;object-fit:contain;border-radius:4px;background:#f5f7fa}.actions[data-v-1321c610]{display:flex;gap:8px}.edit-btn[data-v-1321c610]{background:#67c23a;color:#fff;border:none;padding:4px 12px;border-radius:4px;cursor:pointer}.edit-btn[data-v-1321c610]:hover{background:#85ce61}.modal-overlay[data-v-1321c610]{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;justify-content:center;align-items:center;z-index:1000}.modal-content[data-v-1321c610]{background:#fff;padding:24px;border-radius:8px;width:480px}.modal-content h3[data-v-1321c610]{margin:0 0 20px;font-size:16px}.form-group[data-v-1321c610]{margin-bottom:16px}.form-group label[data-v-1321c610]{display:block;margin-bottom:8px;font-weight:500}.form-group input[data-v-1321c610],.form-group textarea[data-v-1321c610],.form-select[data-v-1321c610]{width:100%;padding:8px;border:1px solid #dcdfe6;border-radius:4px;box-sizing:border-box}.form-group textarea[data-v-1321c610]{resize:vertical}.form-select[data-v-1321c610]{height:36px}.image-upload-area[data-v-1321c610]{display:flex;flex-direction:column;align-items:center;gap:12px;padding:16px;border:1px dashed #dcdfe6;border-radius:4px;background:#fafafa}.preview-image[data-v-1321c610]{max-width:200px;max-height:120px;object-fit:contain;border-radius:4px}.no-image[data-v-1321c610]{color:#c0c4cc;font-size:13px;padding:12px}.upload-btn[data-v-1321c610]{background:#409eff;color:#fff;border:none;padding:6px 16px;border-radius:4px;cursor:pointer;font-size:13px}.upload-btn[data-v-1321c610]:hover{background:#66b1ff}.upload-hint[data-v-1321c610]{color:#909399;font-size:13px}.modal-footer[data-v-1321c610]{display:flex;justify-content:flex-end;gap:12px;margin-top:24px}.cancel-btn[data-v-1321c610]{background:#f5f7fa;color:#606266;border:none;padding:8px 16px;border-radius:4px;cursor:pointer}.cancel-btn[data-v-1321c610]:hover{background:#e4e7ed}.submit-btn[data-v-1321c610]{background:#409eff;color:#fff;border:none;padding:8px 16px;border-radius:4px;cursor:pointer}.submit-btn[data-v-1321c610]:hover{background:#66b1ff}

View File

@ -1 +1 @@
import{G as n,O as c,n as i,E as o,f as d,H as p,g as f,q as h,w as g,r as u,d as y,I as w,B as x,o as _}from"./index-rb7Oshfn.js";import{t as k}from"./list-query-4ZZvVtjn.js";const Y=()=>({crudOptions:{pagination:{show:!1},search:{show:!0},searchFormOptions:{inline:!0,labelWidth:"80px",col:{span:0}},request:{transformQuery:({form:e})=>k(e,["username","nickname","email","phone"]),pageRequest:async e=>{const{data:s}=await i.post("/users/search",e);return s},transformRes:({res:e})=>{const s=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(s.length,1),total:s.length,records:s}},onError:e=>{var t;let s="操作失败";if((t=e.response)!=null&&t.data){const a=e.response.data;if(a.errors&&Array.isArray(a.errors))s=a.errors.map(r=>r.message).join("");else if(a.message)s=a.message;else if(a.detail){const r=a.detail;typeof r=="string"&&(r.includes("at least 6 characters")?s="密码长度至少为6个字符":s=r)}}else e.message&&(s=e.message);throw o.error({message:s,duration:5e3}),e},addRequest:async({form:e})=>{await i.post("/users/",e),o.success({message:"添加成功",duration:5e3})},editRequest:async({form:e,row:s})=>{if(!s)return;const t={...e};t.password||delete t.password,await i.put(`/users/${s.id}`,t),o.success({message:"修改成功",duration:5e3})},delRequest:async({row:e})=>{e&&(await i.delete(`/users/${e.id}`),o.success({message:"删除成功",duration:5e3}))},onSuccess:({action:e})=>{e==="add"?o.success({message:"添加成功",duration:5e3}):e==="edit"&&o.success({message:"修改成功",duration:5e3})}},table:{remove:{showSuccessNotification:!1}},rowHandle:{buttons:{edit:{text:"编辑"},remove:{text:"删除",show:!0}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},username:{title:"登录名",key:"username",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"请输入登录名",trigger:"blur"},{min:3,max:50,message:"用户名长度必须在3-50个字符之间",trigger:"blur"}]}},nickname:{title:"昵称",key:"nickname",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"必填"}]}},password:{title:"密码",key:"password",type:"password",column:{show:!1},form:{show:!1},addForm:{show:!0,rules:[{required:!0,message:"请输入密码",trigger:"blur"},{min:6,max:50,message:"密码长度必须在6-50个字符之间",trigger:"blur"}]},editForm:{show:!0,component:{placeholder:"不修改请留空"},rules:[{min:6,max:50,message:"密码长度必须在6-50个字符之间",trigger:"blur"}]}},email:{title:"邮箱",key:"email",type:"text",search:{show:!0}},phone:{title:"手机",key:"phone",type:"text",search:{show:!0}},wx_openid:{title:"微信OpenID",key:"wx_openid",type:"text",column:{show:!1}},avatar:{title:"头像",key:"avatar",type:"text",column:{show:!1}},role_id:{title:"角色",key:"role_id",type:"dict-select",dict:c({getData:async()=>{const{data:e}=await i.post("/roles/search",{});return e},value:"id",label:"name"}),column:{width:120,dict:!0},form:{rules:[{required:!0,message:"必选"}],value:2}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:t}){!s||e==null||(s[t]=n(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:t}){!s||e==null||(s[t]=n(e).format("YYYY-MM-DD HH:mm:ss"))}},lastlogintime:{title:"最后登录",key:"lastlogintime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:t}){!s||e==null||(s[t]=n(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),D=d({__name:"UserManage",setup(e){const{crudBinding:s,crudRef:t,crudExpose:a}=p({createCrudOptions:Y});return f(()=>{a.doRefresh()}),(r,b)=>{const l=u("fs-crud"),m=u("fs-page");return _(),h(m,null,{default:g(()=>[y(l,w({ref_key:"crudRef",ref:t},x(s)),null,16)]),_:1})}}});export{D as default}; import{G as n,O as c,n as i,E as o,f as d,H as p,g as f,q as h,w as g,r as u,d as y,I as w,B as x,o as _}from"./index-ChTrSGCb.js";import{t as k}from"./list-query-4ZZvVtjn.js";const Y=()=>({crudOptions:{pagination:{show:!1},search:{show:!0},searchFormOptions:{inline:!0,labelWidth:"80px",col:{span:0}},request:{transformQuery:({form:e})=>k(e,["username","nickname","email","phone"]),pageRequest:async e=>{const{data:s}=await i.post("/users/search",e);return s},transformRes:({res:e})=>{const s=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(s.length,1),total:s.length,records:s}},onError:e=>{var t;let s="操作失败";if((t=e.response)!=null&&t.data){const a=e.response.data;if(a.errors&&Array.isArray(a.errors))s=a.errors.map(r=>r.message).join("");else if(a.message)s=a.message;else if(a.detail){const r=a.detail;typeof r=="string"&&(r.includes("at least 6 characters")?s="密码长度至少为6个字符":s=r)}}else e.message&&(s=e.message);throw o.error({message:s,duration:5e3}),e},addRequest:async({form:e})=>{await i.post("/users/",e),o.success({message:"添加成功",duration:5e3})},editRequest:async({form:e,row:s})=>{if(!s)return;const t={...e};t.password||delete t.password,await i.put(`/users/${s.id}`,t),o.success({message:"修改成功",duration:5e3})},delRequest:async({row:e})=>{e&&(await i.delete(`/users/${e.id}`),o.success({message:"删除成功",duration:5e3}))},onSuccess:({action:e})=>{e==="add"?o.success({message:"添加成功",duration:5e3}):e==="edit"&&o.success({message:"修改成功",duration:5e3})}},table:{remove:{showSuccessNotification:!1}},rowHandle:{buttons:{edit:{text:"编辑"},remove:{text:"删除",show:!0}}},columns:{id:{title:"ID",key:"id",type:"number",column:{width:70},form:{show:!1}},username:{title:"登录名",key:"username",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"请输入登录名",trigger:"blur"},{min:3,max:50,message:"用户名长度必须在3-50个字符之间",trigger:"blur"}]}},nickname:{title:"昵称",key:"nickname",type:"text",search:{show:!0},form:{rules:[{required:!0,message:"必填"}]}},password:{title:"密码",key:"password",type:"password",column:{show:!1},form:{show:!1},addForm:{show:!0,rules:[{required:!0,message:"请输入密码",trigger:"blur"},{min:6,max:50,message:"密码长度必须在6-50个字符之间",trigger:"blur"}]},editForm:{show:!0,component:{placeholder:"不修改请留空"},rules:[{min:6,max:50,message:"密码长度必须在6-50个字符之间",trigger:"blur"}]}},email:{title:"邮箱",key:"email",type:"text",search:{show:!0}},phone:{title:"手机",key:"phone",type:"text",search:{show:!0}},wx_openid:{title:"微信OpenID",key:"wx_openid",type:"text",column:{show:!1}},avatar:{title:"头像",key:"avatar",type:"text",column:{show:!1}},role_id:{title:"角色",key:"role_id",type:"dict-select",dict:c({getData:async()=>{const{data:e}=await i.post("/roles/search",{});return e},value:"id",label:"name"}),column:{width:120,dict:!0},form:{rules:[{required:!0,message:"必选"}],value:2}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:t}){!s||e==null||(s[t]=n(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:t}){!s||e==null||(s[t]=n(e).format("YYYY-MM-DD HH:mm:ss"))}},lastlogintime:{title:"最后登录",key:"lastlogintime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:s,key:t}){!s||e==null||(s[t]=n(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),D=d({__name:"UserManage",setup(e){const{crudBinding:s,crudRef:t,crudExpose:a}=p({createCrudOptions:Y});return f(()=>{a.doRefresh()}),(r,b)=>{const l=u("fs-crud"),m=u("fs-page");return _(),h(m,null,{default:g(()=>[y(l,w({ref_key:"crudRef",ref:t},x(s)),null,16)]),_:1})}}});export{D as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BookSystem</title> <title>BookSystem</title>
<script type="module" crossorigin src="/assets/index-rb7Oshfn.js"></script> <script type="module" crossorigin src="/assets/index-ChTrSGCb.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cn04Rt7y.css"> <link rel="stylesheet" crossorigin href="/assets/index-Cn04Rt7y.css">
</head> </head>
<body> <body>

View File

@ -1,188 +1,176 @@
import type { CreateCrudOptions } from "@fast-crud/fast-crud"; import type { CreateCrudOptions } from "@fast-crud/fast-crud";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { http } from "@/modules/admin/api/http"; import { http } from "@/modules/admin/api/http";
import { toSearchParams } from "@/modules/admin/utils/list-query"; import { toSearchParams } from "@/modules/admin/utils/list-query";
export type PermissionRow = { export type PermissionRow = {
id: number; id: number;
name: string; name: string;
code: string; code: string;
parentid: number; parentid: number;
route?: string | null; route?: string | null;
description?: string | null; description?: string | null;
sort: number; createtime: string;
createtime: string; updatetime: string;
updatetime: string; children?: PermissionRow[];
children?: PermissionRow[]; };
};
export const createPermissionCrudOptions: CreateCrudOptions<PermissionRow> = () => {
export const createPermissionCrudOptions: CreateCrudOptions<PermissionRow> = () => { return {
return { crudOptions: {
crudOptions: { pagination: { show: false },
pagination: { show: false }, search: {
search: { show: true,
show: true, options: {
options: { layout: 'inline',
layout: 'inline', },
}, expand: false,
expand: false, },
}, tree: {
tree: { enable: true,
enable: true, childrenKey: "children",
childrenKey: "children", parentKey: "parentid",
parentKey: "parentid", hasChildField: "children",
hasChildField: "children", },
}, request: {
request: { transformQuery: ({ form }) =>
transformQuery: ({ form }) => toSearchParams(form as Record<string, unknown>, ["name", "code"]),
toSearchParams(form as Record<string, unknown>, ["name", "code"]), pageRequest: async (_query) => {
pageRequest: async (_query) => { const { data } = await http.get<PermissionRow[]>("/permissions/tree");
const { data } = await http.get<PermissionRow[]>("/permissions/tree"); return data;
return data; },
}, transformRes: ({ res }) => {
transformRes: ({ res }) => { const records = Array.isArray(res) ? res : [];
const records = Array.isArray(res) ? res : []; return {
return { currentPage: 1,
currentPage: 1, pageSize: Math.max(records.length, 1),
pageSize: Math.max(records.length, 1), total: records.length,
total: records.length, records,
records, };
}; },
}, addRequest: async ({ form }) => {
addRequest: async ({ form }) => { await http.post("/permissions/", form);
await http.post("/permissions/", form); ElMessage.success({ message: "添加成功", duration: 5000 });
ElMessage.success({ message: "添加成功", duration: 5000 }); },
}, editRequest: async ({ form, row }) => {
editRequest: async ({ form, row }) => { if (!row) return;
if (!row) return; await http.put(`/permissions/${row.id}`, form);
await http.put(`/permissions/${row.id}`, form); ElMessage.success({ message: "修改成功", duration: 5000 });
ElMessage.success({ message: "修改成功", duration: 5000 }); },
}, delRequest: async ({ row }) => {
delRequest: async ({ row }) => { if (!row) return;
if (!row) return; await http.delete(`/permissions/${row.id}`);
await http.delete(`/permissions/${row.id}`); ElMessage.success({ message: "删除成功", duration: 5000 });
ElMessage.success({ message: "删除成功", duration: 5000 }); },
}, onSuccess: ({ action }: { action: string }) => {
onSuccess: ({ action }: { action: string }) => { if (action === "add") {
if (action === "add") { ElMessage.success({ message: "添加成功", duration: 5000 });
ElMessage.success({ message: "添加成功", duration: 5000 }); } else if (action === "edit") {
} else if (action === "edit") { ElMessage.success({ message: "修改成功", duration: 5000 });
ElMessage.success({ message: "修改成功", duration: 5000 }); }
} },
}, onError: (error: any) => {
onError: (error: any) => { let message = "操作失败";
let message = "操作失败"; if (error.response?.data) {
if (error.response?.data) { const data = error.response.data;
const data = error.response.data; if (data.message) {
if (data.message) { message = data.message;
message = data.message; } else if (data.detail) {
} else if (data.detail) { message = typeof data.detail === "string" ? data.detail : "操作失败";
message = typeof data.detail === "string" ? data.detail : "操作失败"; }
} }
} ElMessage.error({ message, duration: 5000 });
ElMessage.error({ message, duration: 5000 }); },
}, },
}, table: {
table: { remove: {
remove: { showSuccessNotification: false,
showSuccessNotification: false, },
}, },
}, rowHandle: {
rowHandle: { buttons: {
buttons: { edit: { text: "编辑" },
edit: { text: "编辑" }, remove: { text: "删除" },
remove: { text: "删除" }, },
}, },
}, columns: {
columns: { id: {
id: { title: "ID",
title: "ID", key: "id",
key: "id", type: "number",
type: "number", column: { width: 70 },
column: { width: 70 }, form: { show: false },
form: { show: false }, },
}, name: {
name: { title: "名称",
title: "名称", key: "name",
key: "name", type: "text",
type: "text", search: { show: true },
search: { show: true }, form: {
form: { rules: [{ required: true, message: "必填" }],
rules: [{ required: true, message: "必填" }], },
}, },
}, code: {
code: { title: "编码",
title: "编码", key: "code",
key: "code", type: "text",
type: "text", search: { show: true },
search: { show: true }, form: {
form: { rules: [{ required: true, message: "必填" }],
rules: [{ required: true, message: "必填" }], },
}, },
}, parentid: {
parentid: { title: "父级ID",
title: "父级ID", key: "parentid",
key: "parentid", type: "number",
type: "number", form: {
form: { value: 0,
value: 0, rules: [{ required: true, message: "必填" }],
rules: [{ required: true, message: "必填" }], },
}, },
}, description: {
description: { title: "描述",
title: "描述", key: "description",
key: "description", type: "textarea",
type: "textarea", column: { show: false },
column: { show: false }, },
}, route: {
route: { title: "路由",
title: "路由", key: "route",
key: "route", type: "text",
type: "text", column: { width: 200 },
column: { width: 200 }, form: {
form: { placeholder: "如:/admin/users",
placeholder: "如:/admin/users", },
}, },
}, createtime: {
sort: { title: "创建时间",
title: "排序", key: "createtime",
key: "sort", type: "text",
type: "number", form: { show: false },
column: { width: 80, align: "center" }, column: { width: 170 },
form: { valueBuilder({ value, row, key }) {
value: 0, if (!row || value == null) return;
rules: [{ required: true, message: "必填" }], (row as Record<string, unknown>)[key as string] = dayjs(value).format(
helper: "数字越小,排序越靠前", "YYYY-MM-DD HH:mm:ss",
}, );
}, },
createtime: { },
title: "创建时间", updatetime: {
key: "createtime", title: "更新时间",
type: "text", key: "updatetime",
form: { show: false }, type: "text",
column: { width: 170 }, form: { show: false },
valueBuilder({ value, row, key }) { column: { width: 170 },
if (!row || value == null) return; valueBuilder({ value, row, key }) {
(row as Record<string, unknown>)[key as string] = dayjs(value).format( if (!row || value == null) return;
"YYYY-MM-DD HH:mm:ss", (row as Record<string, unknown>)[key as string] = dayjs(value).format(
); "YYYY-MM-DD HH:mm:ss",
}, );
}, },
updatetime: { },
title: "更新时间", },
key: "updatetime", },
type: "text", };
form: { show: false }, };
column: { width: 170 },
valueBuilder({ value, row, key }) {
if (!row || value == null) return;
(row as Record<string, unknown>)[key as string] = dayjs(value).format(
"YYYY-MM-DD HH:mm:ss",
);
},
},
},
},
};
};