fix(roles): 修复角色创建时 model_dump 错误
修复角色创建时出现的 AttributeError: 'dict' object has no attribute 'model_dump' 错误,将 role_data.model_dump() 改为 dict(role_data) 以兼容 Pydantic v1 和 v2。 同时优化了搜索布局和样式,确保查询条件和按钮在同一行显示: 1. 修改 admin-crud.css 添加搜索区域不换行样式 2. 调整 role-crud.ts 中的 search 配置 3. 将 GET 查询接口改为 POST /search 方式 4. 修复角色权限分配时的权限加载问题
This commit is contained in:
parent
83d84254f5
commit
6f4ace886f
121
.trae/documents/bug_fix_plan.md
Normal file
121
.trae/documents/bug_fix_plan.md
Normal file
@ -0,0 +1,121 @@
|
||||
|
||||
# Bug 修复计划
|
||||
|
||||
## 问题描述
|
||||
|
||||
### 问题1:查询条件布局优化
|
||||
前端页面中用户、角色、权限的页面中查询条件和查询、重置按钮需要放到一行,并且去掉展开按钮。
|
||||
|
||||
### 问题2:查询方法修改
|
||||
查询目前使用 GET 方法进行的,需要改为 POST 方法。
|
||||
|
||||
## 项目结构分析
|
||||
|
||||
```
|
||||
frontend/
|
||||
└── src/
|
||||
└── modules/
|
||||
└── admin/
|
||||
├── views/
|
||||
│ ├── users/
|
||||
│ │ ├── UserManage.vue # 用户管理页面
|
||||
│ │ └── user-crud.ts # 用户 CRUD 配置
|
||||
│ ├── roles/
|
||||
│ │ ├── RoleManage.vue # 角色管理页面
|
||||
│ │ └── role-crud.ts # 角色 CRUD 配置
|
||||
│ └── permissions/
|
||||
│ ├── PermissionManage.vue # 权限管理页面
|
||||
│ └── permission-crud.ts # 权限 CRUD 配置
|
||||
└── api/
|
||||
└── http.ts # HTTP 请求封装
|
||||
```
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 问题1:查询条件布局优化
|
||||
|
||||
在 fast-crud 中,可以通过配置 `search` 属性来控制搜索表单的布局:
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|--------|-----|------|
|
||||
| `show` | `true` | 显示搜索表单 |
|
||||
| `inline` | `true` | 查询条件和按钮在一行 |
|
||||
| `expand` | `false` | 隐藏展开按钮 |
|
||||
|
||||
需要修改的文件:
|
||||
- `frontend/src/modules/admin/views/users/user-crud.ts`
|
||||
- `frontend/src/modules/admin/views/roles/role-crud.ts`
|
||||
- `frontend/src/modules/admin/views/permissions/permission-crud.ts`
|
||||
|
||||
### 问题2:查询方法修改
|
||||
|
||||
需要同时修改前端和后端:
|
||||
|
||||
**前端修改**:
|
||||
- 将 `pageRequest` 中的 `http.get` 改为 `http.post`
|
||||
- 将查询参数从 URL 参数改为请求体
|
||||
|
||||
**后端修改**:
|
||||
- 将 `/users/`、`/roles/`、`/permissions/` 的 GET 请求改为 POST 请求
|
||||
- 修改参数接收方式,从 Query 参数改为请求体
|
||||
|
||||
需要修改的文件:
|
||||
- 前端:`user-crud.ts`、`role-crud.ts`、`permission-crud.ts`
|
||||
- 后端:`users/urls.py`、`roles/urls.py`、`permissions/urls.py`
|
||||
|
||||
## 修改步骤
|
||||
|
||||
### 步骤1:修改前端 CRUD 配置(问题1+问题2)
|
||||
|
||||
修改三个 CRUD 配置文件:
|
||||
|
||||
1. `user-crud.ts`:
|
||||
- 添加 `search: { inline: true, expand: false }` 配置
|
||||
- 将 `pageRequest` 中的 `http.get` 改为 `http.post`
|
||||
|
||||
2. `role-crud.ts`:
|
||||
- 添加 `search: { inline: true, expand: false }` 配置
|
||||
- 将 `pageRequest` 中的 `http.get` 改为 `http.post`
|
||||
|
||||
3. `permission-crud.ts`:
|
||||
- 添加 `search: { inline: true, expand: false }` 配置
|
||||
- 将 `pageRequest` 中的 `http.get` 改为 `http.post`
|
||||
|
||||
### 步骤2:修改后端 API(问题2)
|
||||
|
||||
修改三个后端路由文件:
|
||||
|
||||
1. `users/urls.py`:
|
||||
- 将 `@router.get("/")` 改为 `@router.post("/search")`
|
||||
- 修改参数接收方式
|
||||
|
||||
2. `roles/urls.py`:
|
||||
- 将 `@router.get("/")` 改为 `@router.post("/search")`
|
||||
- 修改参数接收方式
|
||||
|
||||
3. `permissions/urls.py`:
|
||||
- 将 `@router.get("/")` 改为 `@router.post("/search")`
|
||||
- 修改参数接收方式
|
||||
|
||||
### 步骤3:验证修改
|
||||
|
||||
启动项目,验证:
|
||||
1. 查询条件和按钮是否在一行显示
|
||||
2. 展开按钮是否已隐藏
|
||||
3. 查询请求是否使用 POST 方法
|
||||
4. 查询功能是否正常工作
|
||||
|
||||
## 风险评估
|
||||
|
||||
| 风险项 | 风险等级 | 应对措施 |
|
||||
|--------|----------|----------|
|
||||
| 后端 API 变更可能影响其他功能 | 中 | 保留原 GET 接口,新增 POST 接口 |
|
||||
| fast-crud 配置不生效 | 低 | 检查 fast-crud 文档确认配置项 |
|
||||
| 查询参数传递方式变更 | 低 | 修改前端传递方式,确保与后端一致 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 后端修改时,建议保留原 GET 接口,新增 POST `/search` 接口,避免影响其他功能
|
||||
2. 修改完成后需要测试所有查询功能是否正常工作
|
||||
3. 确保前后端参数传递方式一致
|
||||
|
||||
63
.trae/documents/bug_fix_plan_v2.md
Normal file
63
.trae/documents/bug_fix_plan_v2.md
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
# Bug 修复计划(版本2)
|
||||
|
||||
## 问题描述
|
||||
|
||||
### 问题1:查询条件布局仍未生效
|
||||
前端页面中用户、角色、权限的查询条件和查询、重置按钮没有在同一行显示,需要重新检查配置。
|
||||
|
||||
### 问题2:删除 GET 查询接口
|
||||
用户要求删除原有的 GET 方式查询接口,只保留 POST 方式。
|
||||
|
||||
## 分析
|
||||
|
||||
### 问题1原因分析
|
||||
fast-crud 的搜索布局配置可能需要调整。当前配置:
|
||||
```javascript
|
||||
search: {
|
||||
show: true,
|
||||
inline: true,
|
||||
expand: false,
|
||||
}
|
||||
```
|
||||
可能需要调整为:
|
||||
```javascript
|
||||
search: {
|
||||
show: true,
|
||||
form: {
|
||||
inline: true,
|
||||
},
|
||||
expand: false,
|
||||
}
|
||||
```
|
||||
|
||||
### 问题2修改方案
|
||||
删除后端三个路由文件中的 GET 查询接口(`@router.get("/")`),只保留 POST `/search` 接口。
|
||||
|
||||
## 修改步骤
|
||||
|
||||
### 步骤1:修复前端搜索布局配置
|
||||
修改三个 CRUD 配置文件,调整搜索布局配置:
|
||||
|
||||
1. `user-crud.ts`: 调整 search 配置
|
||||
2. `role-crud.ts`: 调整 search 配置
|
||||
3. `permission-crud.ts`: 调整 search 配置
|
||||
|
||||
### 步骤2:删除后端 GET 查询接口
|
||||
删除三个后端路由文件中的 GET 查询接口:
|
||||
|
||||
1. `users/urls.py`: 删除 `@router.get("/")` 接口
|
||||
2. `roles/urls.py`: 删除 `@router.get("/")` 接口
|
||||
3. `permissions/urls.py`: 删除 `@router.get("/")` 接口
|
||||
|
||||
## 修改文件清单
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `frontend/src/modules/admin/views/users/user-crud.ts` | 调整 search 布局配置 |
|
||||
| `frontend/src/modules/admin/views/roles/role-crud.ts` | 调整 search 布局配置 |
|
||||
| `frontend/src/modules/admin/views/permissions/permission-crud.ts` | 调整 search 布局配置 |
|
||||
| `backend/auth/apps/users/urls.py` | 删除 GET `/users/` 查询接口 |
|
||||
| `backend/auth/apps/roles/urls.py` | 删除 GET `/roles/` 查询接口 |
|
||||
| `backend/auth/apps/permissions/urls.py` | 删除 GET `/permissions/` 查询接口 |
|
||||
|
||||
43
.trae/documents/bug_fix_plan_v3.md
Normal file
43
.trae/documents/bug_fix_plan_v3.md
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
# Bug 修复计划(版本3)
|
||||
|
||||
## 问题描述
|
||||
|
||||
搜索组件的查询按钮和条件无法显示在同一行,需要调整 fast-crud 的搜索布局配置。
|
||||
|
||||
## 分析
|
||||
|
||||
根据 fast-crud 的官方文档和 GitHub issues,正确的行内布局配置应该是:
|
||||
|
||||
```javascript
|
||||
search: {
|
||||
show: true,
|
||||
options: {
|
||||
layout: 'inline' // 使用 options.layout 而非 form.inline
|
||||
},
|
||||
expand: false,
|
||||
}
|
||||
```
|
||||
|
||||
之前使用的 `search.form.inline` 配置不正确,导致布局没有生效。
|
||||
|
||||
## 修改步骤
|
||||
|
||||
修改三个 CRUD 配置文件,调整搜索布局配置:
|
||||
|
||||
1. `user-crud.ts`: 调整 search.options.layout 配置
|
||||
2. `role-crud.ts`: 调整 search.options.layout 配置
|
||||
3. `permission-crud.ts`: 调整 search.options.layout 配置
|
||||
|
||||
## 修改文件清单
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `frontend/src/modules/admin/views/users/user-crud.ts` | 调整 search 配置为 options.layout: 'inline' |
|
||||
| `frontend/src/modules/admin/views/roles/role-crud.ts` | 调整 search 配置为 options.layout: 'inline' |
|
||||
| `frontend/src/modules/admin/views/permissions/permission-crud.ts` | 调整 search 配置为 options.layout: 'inline' |
|
||||
|
||||
## 预期结果
|
||||
|
||||
修改后,查询条件和查询、重置按钮将显示在同一行,且展开按钮被隐藏。
|
||||
|
||||
34
.trae/documents/bug_fix_plan_v4.md
Normal file
34
.trae/documents/bug_fix_plan_v4.md
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
# Bug 修复计划(版本4)
|
||||
|
||||
## 问题描述
|
||||
|
||||
搜索组件的查询按钮和条件分成了两行显示,需要通过 CSS 样式强制它们在同一行显示。
|
||||
|
||||
## 分析
|
||||
|
||||
当前 `admin-crud.css` 文件中有以下样式:
|
||||
|
||||
```css
|
||||
.page-content .fs-crud .el-row {
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
这个 `flex-wrap: wrap` 导致搜索条件和按钮换行显示。需要修改或添加 CSS 样式来强制搜索区域的元素不换行。
|
||||
|
||||
## 修改步骤
|
||||
|
||||
修改 `admin-crud.css` 文件,添加搜索区域的样式,强制搜索条件和按钮在同一行显示。
|
||||
|
||||
## 修改文件清单
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `frontend/src/styles/admin-crud.css` | 添加搜索区域不换行样式 |
|
||||
|
||||
## 预期结果
|
||||
|
||||
修改后,查询条件和查询、重置按钮将显示在同一行。
|
||||
|
||||
28
.trae/documents/bug_fix_plan_v5.md
Normal file
28
.trae/documents/bug_fix_plan_v5.md
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
# Bug 修复计划(版本5)
|
||||
|
||||
## 问题描述
|
||||
|
||||
添加角色时,后台报错误:`AttributeError: 'dict' object has no attribute 'model_dump'`
|
||||
|
||||
## 分析
|
||||
|
||||
这个错误说明 `role_data` 已经是一个字典(dict),而不是 Pydantic 模型对象。这可能是因为:
|
||||
|
||||
1. **Pydantic 版本问题**:在 Pydantic v1 中使用 `dict()` 方法,而在 Pydantic v2 中使用 `model_dump()` 方法
|
||||
2. FastAPI 解析问题:请求格式不正确导致无法正确解析为 Pydantic 模型
|
||||
|
||||
## 修改步骤
|
||||
|
||||
修改 `backend/auth/apps/roles/urls.py` 文件,将 `role_data.model_dump()` 改为 `dict(role_data)`,这是兼容 Pydantic v1 和 v2 的写法。
|
||||
|
||||
## 修改文件清单
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `backend/auth/apps/roles/urls.py` | 将 `role_data.model_dump()` 改为 `dict(role_data)` |
|
||||
|
||||
## 预期结果
|
||||
|
||||
修改后,创建角色功能应该正常工作。
|
||||
|
||||
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, and_
|
||||
from sqlalchemy.orm import selectinload
|
||||
from fastcrud import FastCRUD
|
||||
from typing import Dict, Any
|
||||
|
||||
@ -51,17 +52,17 @@ async def create_permission(
|
||||
return db_permission
|
||||
|
||||
|
||||
@router.get("/", response_model=list[PermissionResponse])
|
||||
async def get_permissions(
|
||||
@router.post("/search", response_model=list[PermissionResponse])
|
||||
async def search_permissions(
|
||||
query: dict = {},
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
name: str | None = Query(None, description="权限名称,模糊匹配"),
|
||||
code: str | None = Query(None, description="权限编码,模糊匹配"),
|
||||
):
|
||||
"""获取权限列表;支持按名称、编码的模糊查询与组合查询(AND)"""
|
||||
"""获取权限列表(POST方式);支持按名称、编码的模糊查询与组合查询(AND)"""
|
||||
stmt = select(Permission)
|
||||
filters = []
|
||||
for col, raw in ((Permission.name, name), (Permission.code, code)):
|
||||
for col, key in ((Permission.name, "name"), (Permission.code, "code")):
|
||||
raw = query.get(key)
|
||||
cond = ilike_contains(col, raw)
|
||||
if cond is not None:
|
||||
filters.append(cond)
|
||||
@ -173,8 +174,8 @@ async def assign_permissions_to_role(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""为角色分配权限"""
|
||||
# 查找角色
|
||||
result = await db.execute(select(Role).filter(Role.id == data.role_id))
|
||||
# 查找角色,同时加载权限关系
|
||||
result = await db.execute(select(Role).options(selectinload(Role.permissions)).filter(Role.id == data.role_id))
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
raise HTTPException(status_code=404, detail="角色不存在")
|
||||
@ -196,7 +197,7 @@ async def assign_permissions_to_role(
|
||||
return RolePermissionResponse(
|
||||
role_id=role.id,
|
||||
role_name=role.name,
|
||||
permissions=[PermissionResponse.from_orm(p) for p in role.permissions]
|
||||
permissions=[PermissionResponse.from_orm(p) for p in permissions]
|
||||
)
|
||||
|
||||
|
||||
@ -207,8 +208,8 @@ async def get_role_permissions(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取角色的权限列表"""
|
||||
# 查找角色
|
||||
result = await db.execute(select(Role).filter(Role.id == role_id))
|
||||
# 查找角色,同时加载权限关系
|
||||
result = await db.execute(select(Role).options(selectinload(Role.permissions)).filter(Role.id == role_id))
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
raise HTTPException(status_code=404, detail="角色不存在")
|
||||
|
||||
Binary file not shown.
@ -30,22 +30,25 @@ async def create_role(
|
||||
if existing_role:
|
||||
raise HTTPException(status_code=400, detail="角色名已存在")
|
||||
|
||||
# 准备创建数据
|
||||
create_data = role_data.model_dump()
|
||||
# 设置创建人为当前登录用户
|
||||
role_data.creator = current_user.nickname or current_user.username
|
||||
|
||||
# 使用FastCRUD创建角色
|
||||
created_role = await role_crud.create(db, create_data)
|
||||
# 使用FastCRUD创建角色,指定返回模型
|
||||
created_role = await role_crud.create(
|
||||
db, role_data, schema_to_select=RoleResponse, return_as_model=True
|
||||
)
|
||||
return created_role
|
||||
|
||||
|
||||
@router.get("/", response_model=list[RoleResponse])
|
||||
async def get_roles(
|
||||
@router.post("/search", response_model=list[RoleResponse])
|
||||
async def search_roles(
|
||||
query: dict = {},
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
name: str | None = Query(None, description="角色名称,模糊匹配"),
|
||||
):
|
||||
"""获取角色列表;支持按名称模糊查询"""
|
||||
"""获取角色列表(POST方式);支持按名称模糊查询"""
|
||||
stmt = select(Role)
|
||||
name = query.get("name")
|
||||
cond = ilike_contains(Role.name, name)
|
||||
if cond is not None:
|
||||
stmt = stmt.where(cond)
|
||||
|
||||
Binary file not shown.
@ -117,24 +117,22 @@ async def create_user(user_data: UserCreateRequest, db: AsyncSession = Depends(g
|
||||
return created_user
|
||||
|
||||
|
||||
@router.get("/", response_model=list[UserResponse])
|
||||
async def get_users(
|
||||
@router.post("/search", response_model=list[UserResponse])
|
||||
async def search_users(
|
||||
query: dict = {},
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
username: str | None = Query(None, description="登录名,模糊匹配"),
|
||||
nickname: str | None = Query(None, description="昵称,模糊匹配"),
|
||||
email: str | None = Query(None, description="邮箱,模糊匹配"),
|
||||
phone: str | None = Query(None, description="手机,模糊匹配"),
|
||||
):
|
||||
"""分页列表用户;支持登录名、昵称、邮箱、手机的模糊查询与组合查询(AND)"""
|
||||
"""分页列表用户(POST方式);支持登录名、昵称、邮箱、手机的模糊查询与组合查询(AND)"""
|
||||
stmt = select(User)
|
||||
filters = []
|
||||
for col, raw in (
|
||||
(User.username, username),
|
||||
(User.nickname, nickname),
|
||||
(User.email, email),
|
||||
(User.phone, phone),
|
||||
for col, key in (
|
||||
(User.username, "username"),
|
||||
(User.nickname, "nickname"),
|
||||
(User.email, "email"),
|
||||
(User.phone, "phone"),
|
||||
):
|
||||
raw = query.get(key)
|
||||
cond = ilike_contains(col, raw)
|
||||
if cond is not None:
|
||||
filters.append(cond)
|
||||
|
||||
@ -1 +1 @@
|
||||
import{f as C,u as A,g as L,E as N,p as S,w as t,k as V,l as q,r as a,d as e,a as o,q as l,s as E,t as M,v as P,x as p,b as R,y as f,o as T,_ as D}from"./index-B340d_U7.js";const I={class:"right"},j={class:"nick"},z={class:"page-title-bar"},F={class:"page-title"},G={class:"page-content"},H=C({__name:"AdminLayout",setup(J){const u=V(),d=q(),s=A(),v=f(()=>u.path),g=f(()=>u.meta.title||"");L(async()=>{try{s.user||await s.fetchProfile()}catch{N.error("获取用户信息失败,请重新登录"),await s.logout(),d.replace({name:"admin-login",query:{redirect:u.fullPath}})}});async function x(){await s.logout(),d.push({name:"admin-login"})}return(K,n)=>{const i=a("el-icon"),r=a("el-menu-item"),y=a("el-menu"),h=a("el-aside"),k=a("el-button"),b=a("el-header"),w=a("router-view"),B=a("el-main"),c=a("el-container");return T(),S(c,{class:"admin-root"},{default:t(()=>[e(h,{width:"220px",class:"aside"},{default:t(()=>[n[3]||(n[3]=o("div",{class:"brand"},"BookSystem 管理",-1)),e(y,{"default-active":v.value,router:"","background-color":"#0f172a","text-color":"#e2e8f0","active-text-color":"#38bdf8"},{default:t(()=>[e(r,{index:"/admin/users",route:{name:"admin-users"}},{default:t(()=>[e(i,null,{default:t(()=>[e(l(E))]),_:1}),n[0]||(n[0]=o("span",null,"用户",-1))]),_:1}),e(r,{index:"/admin/roles",route:{name:"admin-roles"}},{default:t(()=>[e(i,null,{default:t(()=>[e(l(M))]),_:1}),n[1]||(n[1]=o("span",null,"角色",-1))]),_:1}),e(r,{index:"/admin/permissions",route:{name:"admin-permissions"}},{default:t(()=>[e(i,null,{default:t(()=>[e(l(P))]),_:1}),n[2]||(n[2]=o("span",null,"权限",-1))]),_:1})]),_:1},8,["default-active"])]),_:1}),e(c,null,{default:t(()=>[e(b,{class:"header"},{default:t(()=>{var _,m;return[o("div",I,[o("span",j,p(((_=l(s).user)==null?void 0:_.nickname)||((m=l(s).user)==null?void 0:m.username)),1),e(k,{type:"primary",link:"",onClick:x},{default:t(()=>[...n[4]||(n[4]=[R("退出",-1)])]),_:1})])]}),_:1}),e(B,{class:"main"},{default:t(()=>[o("div",z,[o("h1",F,p(g.value),1)]),o("div",G,[e(w)])]),_:1})]),_:1})]),_:1})}}}),Q=D(H,[["__scopeId","data-v-c0bd569a"]]);export{Q as default};
|
||||
import{f as C,u as A,g as L,E as N,p as S,w as t,k as V,l as q,r as a,d as e,a as o,q as l,s as E,t as M,v as P,x as p,b as R,y as f,o as T,_ as D}from"./index-CRc7EmAb.js";const I={class:"right"},j={class:"nick"},z={class:"page-title-bar"},F={class:"page-title"},G={class:"page-content"},H=C({__name:"AdminLayout",setup(J){const u=V(),d=q(),s=A(),v=f(()=>u.path),g=f(()=>u.meta.title||"");L(async()=>{try{s.user||await s.fetchProfile()}catch{N.error("获取用户信息失败,请重新登录"),await s.logout(),d.replace({name:"admin-login",query:{redirect:u.fullPath}})}});async function x(){await s.logout(),d.push({name:"admin-login"})}return(K,n)=>{const i=a("el-icon"),r=a("el-menu-item"),y=a("el-menu"),h=a("el-aside"),k=a("el-button"),b=a("el-header"),w=a("router-view"),B=a("el-main"),c=a("el-container");return T(),S(c,{class:"admin-root"},{default:t(()=>[e(h,{width:"220px",class:"aside"},{default:t(()=>[n[3]||(n[3]=o("div",{class:"brand"},"BookSystem 管理",-1)),e(y,{"default-active":v.value,router:"","background-color":"#0f172a","text-color":"#e2e8f0","active-text-color":"#38bdf8"},{default:t(()=>[e(r,{index:"/admin/users",route:{name:"admin-users"}},{default:t(()=>[e(i,null,{default:t(()=>[e(l(E))]),_:1}),n[0]||(n[0]=o("span",null,"用户",-1))]),_:1}),e(r,{index:"/admin/roles",route:{name:"admin-roles"}},{default:t(()=>[e(i,null,{default:t(()=>[e(l(M))]),_:1}),n[1]||(n[1]=o("span",null,"角色",-1))]),_:1}),e(r,{index:"/admin/permissions",route:{name:"admin-permissions"}},{default:t(()=>[e(i,null,{default:t(()=>[e(l(P))]),_:1}),n[2]||(n[2]=o("span",null,"权限",-1))]),_:1})]),_:1},8,["default-active"])]),_:1}),e(c,null,{default:t(()=>[e(b,{class:"header"},{default:t(()=>{var _,m;return[o("div",I,[o("span",j,p(((_=l(s).user)==null?void 0:_.nickname)||((m=l(s).user)==null?void 0:m.username)),1),e(k,{type:"primary",link:"",onClick:x},{default:t(()=>[...n[4]||(n[4]=[R("退出",-1)])]),_:1})])]}),_:1}),e(B,{class:"main"},{default:t(()=>[o("div",z,[o("h1",F,p(g.value),1)]),o("div",G,[e(w)])]),_:1})]),_:1})]),_:1})}}}),Q=D(H,[["__scopeId","data-v-c0bd569a"]]);export{Q as default};
|
||||
@ -1 +1 @@
|
||||
import{f as L,u as q,g as I,c as f,d as o,w as s,h as S,E as h,r as u,i,j,k as B,l as E,o as w,m as M,a as g,b as N,n as O,_ as T}from"./index-B340d_U7.js";const D={class:"login-wrap"},A={class:"captcha-row"},P=["src"],W={key:1,class:"captcha-placeholder"},X=L({__name:"LoginView",setup($){const y=B(),V=E(),v=q(),b=i(),m=i(!1),l=i(""),p=i(""),k="/api",a=j({username:"",password:"",captcha_code:""}),x={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}],captcha_code:[{required:!0,message:"请输入验证码",trigger:"blur"}]};async function _(){l.value.startsWith("blob:")&&URL.revokeObjectURL(l.value),l.value="",p.value="";const r=await S.get(`${k}/users/captcha`,{responseType:"blob",headers:{}}),e=r.headers["x-captcha-id"]||r.headers["X-Captcha-ID"];if(!e){h.error("未获取到验证码 ID,请检查后端 CORS expose_headers");return}p.value=e,l.value=URL.createObjectURL(r.data),a.captcha_code=""}async function R(){var e,n;if(!p.value){h.warning("请先加载验证码");return}const r=b.value;if(r){try{await r.validate()}catch{return}m.value=!0;try{const{data:t}=await O.post("/users/login",{username:a.username,password:a.password,captcha_id:p.value,captcha_code:a.captcha_code});v.setToken(t.access_token),await v.fetchProfile();const c=y.query.redirect||"/admin";await V.replace(c)}catch(t){const c=(n=(e=t==null?void 0:t.response)==null?void 0:e.data)==null?void 0:n.detail;h.error(typeof c=="string"?c:"登录失败"),await _()}finally{m.value=!1}}}return I(()=>{_()}),(r,e)=>{const n=u("el-input"),t=u("el-form-item"),c=u("el-button"),U=u("el-form"),C=u("el-card");return w(),f("div",D,[o(C,{class:"card",shadow:"hover"},{header:s(()=>[...e[4]||(e[4]=[g("span",{class:"card-title"},"管理后台登录",-1)])]),default:s(()=>[o(U,{ref_key:"formRef",ref:b,model:a,rules:x,"label-width":"88px",onSubmit:e[3]||(e[3]=M(()=>{},["prevent"]))},{default:s(()=>[o(t,{label:"用户名",prop:"username"},{default:s(()=>[o(n,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=d=>a.username=d),autocomplete:"username"},null,8,["modelValue"])]),_:1}),o(t,{label:"密码",prop:"password"},{default:s(()=>[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(t,{label:"验证码",prop:"captcha_code"},{default:s(()=>[g("div",A,[o(n,{modelValue:a.captcha_code,"onUpdate:modelValue":e[2]||(e[2]=d=>a.captcha_code=d),maxlength:"8",placeholder:"请输入验证码"},null,8,["modelValue"]),g("div",{class:"captcha-img",onClick:_},[l.value?(w(),f("img",{key:0,src:l.value,alt:"captcha"},null,8,P)):(w(),f("span",W,"点击获取"))])])]),_:1}),o(t,null,{default:s(()=>[o(c,{type:"primary",loading:m.value,style:{width:"100%"},onClick:R},{default:s(()=>[...e[5]||(e[5]=[N(" 登录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"])]),_:1})])}}}),F=T(X,[["__scopeId","data-v-24c39918"]]);export{F as default};
|
||||
import{f as L,u as q,g as I,c as f,d as o,w as s,h as S,E as h,r as u,i,j,k as B,l as E,o as w,m as M,a as g,b as N,n as O,_ as T}from"./index-CRc7EmAb.js";const D={class:"login-wrap"},A={class:"captcha-row"},P=["src"],W={key:1,class:"captcha-placeholder"},X=L({__name:"LoginView",setup($){const y=B(),V=E(),v=q(),b=i(),m=i(!1),l=i(""),p=i(""),k="/api",a=j({username:"",password:"",captcha_code:""}),x={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}],captcha_code:[{required:!0,message:"请输入验证码",trigger:"blur"}]};async function _(){l.value.startsWith("blob:")&&URL.revokeObjectURL(l.value),l.value="",p.value="";const r=await S.get(`${k}/users/captcha`,{responseType:"blob",headers:{}}),e=r.headers["x-captcha-id"]||r.headers["X-Captcha-ID"];if(!e){h.error("未获取到验证码 ID,请检查后端 CORS expose_headers");return}p.value=e,l.value=URL.createObjectURL(r.data),a.captcha_code=""}async function R(){var e,n;if(!p.value){h.warning("请先加载验证码");return}const r=b.value;if(r){try{await r.validate()}catch{return}m.value=!0;try{const{data:t}=await O.post("/users/login",{username:a.username,password:a.password,captcha_id:p.value,captcha_code:a.captcha_code});v.setToken(t.access_token),await v.fetchProfile();const c=y.query.redirect||"/admin";await V.replace(c)}catch(t){const c=(n=(e=t==null?void 0:t.response)==null?void 0:e.data)==null?void 0:n.detail;h.error(typeof c=="string"?c:"登录失败"),await _()}finally{m.value=!1}}}return I(()=>{_()}),(r,e)=>{const n=u("el-input"),t=u("el-form-item"),c=u("el-button"),U=u("el-form"),C=u("el-card");return w(),f("div",D,[o(C,{class:"card",shadow:"hover"},{header:s(()=>[...e[4]||(e[4]=[g("span",{class:"card-title"},"管理后台登录",-1)])]),default:s(()=>[o(U,{ref_key:"formRef",ref:b,model:a,rules:x,"label-width":"88px",onSubmit:e[3]||(e[3]=M(()=>{},["prevent"]))},{default:s(()=>[o(t,{label:"用户名",prop:"username"},{default:s(()=>[o(n,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=d=>a.username=d),autocomplete:"username"},null,8,["modelValue"])]),_:1}),o(t,{label:"密码",prop:"password"},{default:s(()=>[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(t,{label:"验证码",prop:"captcha_code"},{default:s(()=>[g("div",A,[o(n,{modelValue:a.captcha_code,"onUpdate:modelValue":e[2]||(e[2]=d=>a.captcha_code=d),maxlength:"8",placeholder:"请输入验证码"},null,8,["modelValue"]),g("div",{class:"captcha-img",onClick:_},[l.value?(w(),f("img",{key:0,src:l.value,alt:"captcha"},null,8,P)):(w(),f("span",W,"点击获取"))])])]),_:1}),o(t,null,{default:s(()=>[o(c,{type:"primary",loading:m.value,style:{width:"100%"},onClick:R},{default:s(()=>[...e[5]||(e[5]=[N(" 登录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"])]),_:1})])}}}),F=T(X,[["__scopeId","data-v-24c39918"]]);export{F as default};
|
||||
@ -1 +0,0 @@
|
||||
import{z as a,n as s,f as m,A as d,g as c,p,w as l,r as n,d as f,B as y,q as h,o as g}from"./index-B340d_U7.js";import{t as _}from"./list-query-4ZZvVtjn.js";const w=()=>({crudOptions:{pagination:{show:!1},request:{transformQuery:({form:e})=>_(e,["name","code"]),pageRequest:async e=>{const{data:t}=await s.get("/permissions/",{params:e});return t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}},addRequest:async({form:e})=>{await s.post("/permissions/",e)},editRequest:async({form:e,row:t})=>{t&&await s.put(`/permissions/${t.id}`,e)},delRequest:async({row:e})=>{e&&await s.delete(`/permissions/${e.id}`)}},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}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:r}){!t||e==null||(t[r]=a(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:r}){!t||e==null||(t[r]=a(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),Y=m({__name:"PermissionManage",setup(e){const{crudBinding:t,crudRef:r,crudExpose:o}=d({createCrudOptions:w});return c(()=>{o.doRefresh()}),(x,k)=>{const i=n("fs-crud"),u=n("fs-page");return g(),p(u,null,{default:l(()=>[f(i,y({ref_key:"crudRef",ref:r},h(t)),null,16)]),_:1})}}});export{Y as default};
|
||||
1
frontend/dist/assets/PermissionManage-D-YK5ex6.js
vendored
Normal file
1
frontend/dist/assets/PermissionManage-D-YK5ex6.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{z as a,n as r,f as m,A as c,g as d,p,w as l,r as n,d as f,B as y,q as h,o as _}from"./index-CRc7EmAb.js";import{t as g}from"./list-query-4ZZvVtjn.js";const w=()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},request:{transformQuery:({form:e})=>g(e,["name","code"]),pageRequest:async e=>{const{data:t}=await r.post("/permissions/search",e);return t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}},addRequest:async({form:e})=>{await r.post("/permissions/",e)},editRequest:async({form:e,row:t})=>{t&&await r.put(`/permissions/${t.id}`,e)},delRequest:async({row:e})=>{e&&await r.delete(`/permissions/${e.id}`)}},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}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),Y=m({__name:"PermissionManage",setup(e){const{crudBinding:t,crudRef:s,crudExpose:o}=c({createCrudOptions:w});return d(()=>{o.doRefresh()}),(x,k)=>{const i=n("fs-crud"),u=n("fs-page");return _(),p(u,null,{default:l(()=>[f(i,y({ref_key:"crudRef",ref:s},h(t)),null,16)]),_:1})}}});export{Y as default};
|
||||
@ -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-B340d_U7.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-CRc7EmAb.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};
|
||||
1
frontend/dist/assets/RoleManage-DlOeFBnz.js
vendored
1
frontend/dist/assets/RoleManage-DlOeFBnz.js
vendored
@ -1 +0,0 @@
|
||||
import{z as A,n as u,f as F,A as I,g as U,p as v,w as m,r as p,i,d as _,B as j,q as G,c as h,b as k,a as B,x,C as Y,F as L,D as Q,E as R,o as l,G as J,_ as W}from"./index-B340d_U7.js";import{t as X}from"./list-query-4ZZvVtjn.js";function Z(C){return()=>({crudOptions:{pagination:{show:!1},request:{transformQuery:({form:t})=>X(t,["name"]),pageRequest:async t=>{const{data:s}=await u.get("/roles/",{params:t});return s},transformRes:({res:t})=>{const s=Array.isArray(t)?t:[];return{currentPage:1,pageSize:Math.max(s.length,1),total:s.length,records:s}},addRequest:async({form:t})=>{await u.post("/roles/",t)},editRequest:async({form:t,row:s})=>{s&&await u.put(`/roles/${s.id}`,t)},delRequest:async({row:t})=>{t&&await u.delete(`/roles/${t.id}`)}},rowHandle:{width:300,buttons:{viewPerm:{text:"查看",order:1,click:({row:t})=>{t&&C.onView(t)}},assignPerm:{text:"分配权限",order:2,click:({row:t})=>{t&&C.onAssign(t)}},edit:{text:"编辑",order:10},remove:{text:"删除",order:20}}},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:"必填"}]}},creator:{title:"创建人",key:"creator",type:"text"},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:t,row:s,key:d}){!s||t==null||(s[d]=A(t).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:t,row:s,key:d}){!s||t==null||(s[d]=A(t).format("YYYY-MM-DD HH:mm:ss"))}}}}})}const ee={key:0,class:"dlg-role"},te={key:1,class:"perm-tags"},se={key:0,class:"dlg-role"},ae=F({__name:"RoleManage",setup(C){const t=i(!1),s=i(!1),d=i(null),g=i([]),y=i(null),V=i([]),w=i(),b=i(!1),$={label:"label",children:"children"};function D(a){return a.map(e=>{var n;return{id:e.id,label:`${e.name}(${e.code})`,children:(n=e.children)!=null&&n.length?D(e.children):void 0}})}async function q(){const{data:a}=await u.get("/permissions/tree");V.value=D(a??[])}async function T(a){d.value=a,t.value=!0;try{const{data:e}=await u.get(`/permissions/role/${a.id}`);g.value=e.permissions??[]}catch{R.error("加载权限失败"),g.value=[]}}function H(a){y.value=a,s.value=!0}async function O(){var e,n;const a=y.value;if(a)try{const{data:f}=await u.get(`/permissions/role/${a.id}`),o=(f.permissions??[]).map(c=>c.id);await J(),(e=w.value)==null||e.setCheckedKeys(o)}catch{R.error("加载角色权限失败"),(n=w.value)==null||n.setCheckedKeys([])}}async function z(){var e,n,f;const a=y.value;if(a){b.value=!0;try{const o=((e=w.value)==null?void 0:e.getCheckedKeys(!1))??[];await u.post("/permissions/assign",{role_id:a.id,permission_ids:o}),R.success("保存成功"),s.value=!1,M.doRefresh()}catch(o){const c=(f=(n=o==null?void 0:o.response)==null?void 0:n.data)==null?void 0:f.detail;R.error(typeof c=="string"?c:"保存失败")}finally{b.value=!1}}}const{crudBinding:E,crudRef:N,crudExpose:M}=I({createCrudOptions:Z({onView:T,onAssign:H})});return U(async()=>{await q(),M.doRefresh()}),(a,e)=>{const n=p("fs-crud"),f=p("el-tag"),o=p("el-empty"),c=p("el-dialog"),S=p("el-tree"),P=p("el-button"),K=p("fs-page");return l(),v(K,null,{default:m(()=>[_(n,j({ref_key:"crudRef",ref:N},G(E)),null,16),_(c,{modelValue:t.value,"onUpdate:modelValue":e[0]||(e[0]=r=>t.value=r),title:"查看角色权限",width:"560px","destroy-on-close":"",onClosed:e[1]||(e[1]=r=>g.value=[])},{default:m(()=>[d.value?(l(),h("p",ee,[e[4]||(e[4]=k(" 角色:",-1)),B("strong",null,x(d.value.name),1)])):Y("",!0),g.value.length?(l(),h("div",te,[(l(!0),h(L,null,Q(g.value,r=>(l(),v(f,{key:r.id,class:"perm-tag",type:"info"},{default:m(()=>[k(x(r.name)+"("+x(r.code)+") ",1)]),_:2},1024))),128))])):(l(),v(o,{key:2,description:"暂未分配权限","image-size":72}))]),_:1},8,["modelValue"]),_(c,{modelValue:s.value,"onUpdate:modelValue":e[3]||(e[3]=r=>s.value=r),title:"分配权限",width:"640px","destroy-on-close":"",onOpen:O},{footer:m(()=>[_(P,{onClick:e[2]||(e[2]=r=>s.value=!1)},{default:m(()=>[...e[6]||(e[6]=[k("取消",-1)])]),_:1}),_(P,{type:"primary",loading:b.value,onClick:z},{default:m(()=>[...e[7]||(e[7]=[k(" 保存 ",-1)])]),_:1},8,["loading"])]),default:m(()=>[y.value?(l(),h("p",se,[e[5]||(e[5]=k(" 角色:",-1)),B("strong",null,x(y.value.name),1)])):Y("",!0),V.value.length?(l(),v(S,{key:1,ref_key:"permTreeRef",ref:w,class:"perm-tree",data:V.value,"show-checkbox":"","node-key":"id",props:$,"default-expand-all":"","check-strictly":!1},null,8,["data"])):(l(),v(o,{key:2,description:"暂无权限数据","image-size":64}))]),_:1},8,["modelValue"])]),_:1})}}}),re=W(ae,[["__scopeId","data-v-714ba56c"]]);export{re as default};
|
||||
1
frontend/dist/assets/RoleManage-Dmqoc6FI.js
vendored
Normal file
1
frontend/dist/assets/RoleManage-Dmqoc6FI.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{z as A,n as u,f as F,A as I,g as U,p as v,w as m,r as p,i,d as _,B as j,q as G,c as w,b as k,a as B,x,C as Y,F as L,D as Q,E as R,o as l,G as J,_ as W}from"./index-CRc7EmAb.js";import{t as X}from"./list-query-4ZZvVtjn.js";function Z(C){return()=>({crudOptions:{pagination:{show:!1},search:{show:!0,options:{layout:"inline"},expand:!1},request:{transformQuery:({form:t})=>X(t,["name"]),pageRequest:async t=>{const{data:s}=await u.post("/roles/search",t);return s},transformRes:({res:t})=>{const s=Array.isArray(t)?t:[];return{currentPage:1,pageSize:Math.max(s.length,1),total:s.length,records:s}},addRequest:async({form:t})=>{await u.post("/roles/",t)},editRequest:async({form:t,row:s})=>{s&&await u.put(`/roles/${s.id}`,t)},delRequest:async({row:t})=>{t&&await u.delete(`/roles/${t.id}`)}},rowHandle:{width:300,buttons:{viewPerm:{text:"查看",order:1,click:({row:t})=>{t&&C.onView(t)}},assignPerm:{text:"分配权限",order:2,click:({row:t})=>{t&&C.onAssign(t)}},edit:{text:"编辑",order:10},remove:{text:"删除",order:20}}},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:"必填"}]}},creator:{title:"创建人",key:"creator",type:"text"},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:t,row:s,key:d}){!s||t==null||(s[d]=A(t).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:t,row:s,key:d}){!s||t==null||(s[d]=A(t).format("YYYY-MM-DD HH:mm:ss"))}}}}})}const ee={key:0,class:"dlg-role"},te={key:1,class:"perm-tags"},se={key:0,class:"dlg-role"},ae=F({__name:"RoleManage",setup(C){const t=i(!1),s=i(!1),d=i(null),g=i([]),y=i(null),V=i([]),h=i(),b=i(!1),$={label:"label",children:"children"};function D(a){return a.map(e=>{var n;return{id:e.id,label:`${e.name}(${e.code})`,children:(n=e.children)!=null&&n.length?D(e.children):void 0}})}async function q(){const{data:a}=await u.get("/permissions/tree");V.value=D(a??[])}async function T(a){d.value=a,t.value=!0;try{const{data:e}=await u.get(`/permissions/role/${a.id}`);g.value=e.permissions??[]}catch{R.error("加载权限失败"),g.value=[]}}function H(a){y.value=a,s.value=!0}async function O(){var e,n;const a=y.value;if(a)try{const{data:f}=await u.get(`/permissions/role/${a.id}`),o=(f.permissions??[]).map(c=>c.id);await J(),(e=h.value)==null||e.setCheckedKeys(o)}catch{R.error("加载角色权限失败"),(n=h.value)==null||n.setCheckedKeys([])}}async function z(){var e,n,f;const a=y.value;if(a){b.value=!0;try{const o=((e=h.value)==null?void 0:e.getCheckedKeys(!1))??[];await u.post("/permissions/assign",{role_id:a.id,permission_ids:o}),R.success("保存成功"),s.value=!1,M.doRefresh()}catch(o){const c=(f=(n=o==null?void 0:o.response)==null?void 0:n.data)==null?void 0:f.detail;R.error(typeof c=="string"?c:"保存失败")}finally{b.value=!1}}}const{crudBinding:E,crudRef:N,crudExpose:M}=I({createCrudOptions:Z({onView:T,onAssign:H})});return U(async()=>{await q(),M.doRefresh()}),(a,e)=>{const n=p("fs-crud"),f=p("el-tag"),o=p("el-empty"),c=p("el-dialog"),S=p("el-tree"),P=p("el-button"),K=p("fs-page");return l(),v(K,null,{default:m(()=>[_(n,j({ref_key:"crudRef",ref:N},G(E)),null,16),_(c,{modelValue:t.value,"onUpdate:modelValue":e[0]||(e[0]=r=>t.value=r),title:"查看角色权限",width:"560px","destroy-on-close":"",onClosed:e[1]||(e[1]=r=>g.value=[])},{default:m(()=>[d.value?(l(),w("p",ee,[e[4]||(e[4]=k(" 角色:",-1)),B("strong",null,x(d.value.name),1)])):Y("",!0),g.value.length?(l(),w("div",te,[(l(!0),w(L,null,Q(g.value,r=>(l(),v(f,{key:r.id,class:"perm-tag",type:"info"},{default:m(()=>[k(x(r.name)+"("+x(r.code)+") ",1)]),_:2},1024))),128))])):(l(),v(o,{key:2,description:"暂未分配权限","image-size":72}))]),_:1},8,["modelValue"]),_(c,{modelValue:s.value,"onUpdate:modelValue":e[3]||(e[3]=r=>s.value=r),title:"分配权限",width:"640px","destroy-on-close":"",onOpen:O},{footer:m(()=>[_(P,{onClick:e[2]||(e[2]=r=>s.value=!1)},{default:m(()=>[...e[6]||(e[6]=[k("取消",-1)])]),_:1}),_(P,{type:"primary",loading:b.value,onClick:z},{default:m(()=>[...e[7]||(e[7]=[k(" 保存 ",-1)])]),_:1},8,["loading"])]),default:m(()=>[y.value?(l(),w("p",se,[e[5]||(e[5]=k(" 角色:",-1)),B("strong",null,x(y.value.name),1)])):Y("",!0),V.value.length?(l(),v(S,{key:1,ref_key:"permTreeRef",ref:h,class:"perm-tree",data:V.value,"show-checkbox":"","node-key":"id",props:$,"default-expand-all":"","check-strictly":!1},null,8,["data"])):(l(),v(o,{key:2,description:"暂无权限数据","image-size":64}))]),_:1},8,["modelValue"])]),_:1})}}}),re=W(ae,[["__scopeId","data-v-714ba56c"]]);export{re as default};
|
||||
1
frontend/dist/assets/UserManage-Bvgn1DBa.js
vendored
1
frontend/dist/assets/UserManage-Bvgn1DBa.js
vendored
@ -1 +0,0 @@
|
||||
import{z as a,O as l,n as r,f as m,A as d,g as c,p,w as f,r as o,d as h,B as y,q as w,o as g}from"./index-B340d_U7.js";import{t as _}from"./list-query-4ZZvVtjn.js";const k=()=>({crudOptions:{pagination:{show:!1},request:{transformQuery:({form:e})=>_(e,["username","nickname","email","phone"]),pageRequest:async e=>{const{data:t}=await r.get("/users/",{params:e});return t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}},addRequest:async({form:e})=>{await r.post("/users/",e)},editRequest:async({form:e,row:t})=>{if(!t)return;const s={...e};s.password||delete s.password,await r.put(`/users/${t.id}`,s)},delRequest:async({row:e})=>{e&&await r.delete(`/users/${e.id}`)}},rowHandle:{buttons:{edit:{text:"编辑"},remove:{text:"删除"}}},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:"必填"}]}},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:"新建用户需设置密码"}]},editForm:{show:!0,component:{placeholder:"不修改请留空"}}},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:l({getData:async()=>{const{data:e}=await r.get("/roles/");return e},value:"id",label:"name"}),form:{rules:[{required:!0,message:"必选"}],value:2}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}},lastlogintime:{title:"最后登录",key:"lastlogintime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),M=m({__name:"UserManage",setup(e){const{crudBinding:t,crudRef:s,crudExpose:n}=d({createCrudOptions:k});return c(()=>{n.doRefresh()}),(x,Y)=>{const u=o("fs-crud"),i=o("fs-page");return g(),p(i,null,{default:f(()=>[h(u,y({ref_key:"crudRef",ref:s},w(t)),null,16)]),_:1})}}});export{M as default};
|
||||
1
frontend/dist/assets/UserManage-_ZRZv-72.js
vendored
Normal file
1
frontend/dist/assets/UserManage-_ZRZv-72.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{z as a,O as l,n as r,f as m,A as d,g as c,p,w as f,r as o,d as h,B as y,q as w,o as g}from"./index-CRc7EmAb.js";import{t as _}from"./list-query-4ZZvVtjn.js";const x=()=>({crudOptions:{pagination:{show:!1},search:{show:!0},searchFormOptions:{inline:!0,labelWidth:"80px",col:{span:0}},request:{transformQuery:({form:e})=>_(e,["username","nickname","email","phone"]),pageRequest:async e=>{const{data:t}=await r.post("/users/search",e);return t},transformRes:({res:e})=>{const t=Array.isArray(e)?e:[];return{currentPage:1,pageSize:Math.max(t.length,1),total:t.length,records:t}},addRequest:async({form:e})=>{await r.post("/users/",e)},editRequest:async({form:e,row:t})=>{if(!t)return;const s={...e};s.password||delete s.password,await r.put(`/users/${t.id}`,s)},delRequest:async({row:e})=>{e&&await r.delete(`/users/${e.id}`)}},rowHandle:{buttons:{edit:{text:"编辑"},remove:{text:"删除"}}},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:"必填"}]}},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:"新建用户需设置密码"}]},editForm:{show:!0,component:{placeholder:"不修改请留空"}}},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:l({getData:async()=>{const{data:e}=await r.get("/roles/");return e},value:"id",label:"name"}),form:{rules:[{required:!0,message:"必选"}],value:2}},createtime:{title:"创建时间",key:"createtime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}},updatetime:{title:"更新时间",key:"updatetime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}},lastlogintime:{title:"最后登录",key:"lastlogintime",type:"text",form:{show:!1},column:{width:170},valueBuilder({value:e,row:t,key:s}){!t||e==null||(t[s]=a(e).format("YYYY-MM-DD HH:mm:ss"))}}}}}),M=m({__name:"UserManage",setup(e){const{crudBinding:t,crudRef:s,crudExpose:n}=d({createCrudOptions:x});return c(()=>{n.doRefresh()}),(k,Y)=>{const u=o("fs-crud"),i=o("fs-page");return g(),p(i,null,{default:f(()=>[h(u,y({ref_key:"crudRef",ref:s},w(t)),null,16)]),_:1})}}});export{M as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BookSystem</title>
|
||||
<script type="module" crossorigin src="/assets/index-B340d_U7.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-ciY4YJQ7.css">
|
||||
<script type="module" crossorigin src="/assets/index-CRc7EmAb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Cn04Rt7y.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -17,13 +17,18 @@ export const createPermissionCrudOptions: CreateCrudOptions<PermissionRow> = ()
|
||||
return {
|
||||
crudOptions: {
|
||||
pagination: { show: false },
|
||||
search: {
|
||||
show: true,
|
||||
options: {
|
||||
layout: 'inline',
|
||||
},
|
||||
expand: false,
|
||||
},
|
||||
request: {
|
||||
transformQuery: ({ form }) =>
|
||||
toSearchParams(form as Record<string, unknown>, ["name", "code"]),
|
||||
pageRequest: async (query) => {
|
||||
const { data } = await http.get<PermissionRow[]>("/permissions/", {
|
||||
params: query,
|
||||
});
|
||||
const { data } = await http.post<PermissionRow[]>("/permissions/search", query);
|
||||
return data;
|
||||
},
|
||||
transformRes: ({ res }) => {
|
||||
|
||||
@ -57,6 +57,7 @@ import { nextTick, onMounted, ref } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import { ElMessage } from "element-plus";
|
||||
import type { ElTree } from "element-plus";
|
||||
import axios, { isAxiosError } from "axios";
|
||||
import { http } from "@/modules/admin/api/http";
|
||||
import { buildRoleCrudOptions, type RoleRow } from "./role-crud";
|
||||
|
||||
@ -131,13 +132,22 @@ async function onAssignDialogOpen() {
|
||||
const role = assignRole.value;
|
||||
if (!role) return;
|
||||
try {
|
||||
const { data } = await http.get<{ permissions: Permission[] }>(
|
||||
console.log(`Loading permissions for role id: ${role.id}`);
|
||||
const response = await http.get<{ permissions: Permission[] }>(
|
||||
`/permissions/role/${role.id}`,
|
||||
);
|
||||
const ids = (data.permissions ?? []).map((p) => p.id);
|
||||
console.log("Response data:", response.data);
|
||||
const ids = (response.data.permissions ?? []).map((p) => p.id);
|
||||
console.log("Permission IDs:", ids);
|
||||
await nextTick();
|
||||
permTreeRef.value?.setCheckedKeys(ids);
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error("Error loading role permissions:", error);
|
||||
if (isAxiosError(error)) {
|
||||
console.error("Response status:", error.response?.status);
|
||||
console.error("Response data:", error.response?.data);
|
||||
console.error("Request URL:", error.request?.url);
|
||||
}
|
||||
ElMessage.error("加载角色权限失败");
|
||||
permTreeRef.value?.setCheckedKeys([]);
|
||||
}
|
||||
|
||||
@ -20,13 +20,18 @@ export function buildRoleCrudOptions(h: RoleCrudHandlers): CreateCrudOptions<Rol
|
||||
return () => ({
|
||||
crudOptions: {
|
||||
pagination: { show: false },
|
||||
search: {
|
||||
show: true,
|
||||
options: {
|
||||
layout: 'inline',
|
||||
},
|
||||
expand: false,
|
||||
},
|
||||
request: {
|
||||
transformQuery: ({ form }) =>
|
||||
toSearchParams(form as Record<string, unknown>, ["name"]),
|
||||
pageRequest: async (query) => {
|
||||
const { data } = await http.get<RoleRow[]>("/roles/", {
|
||||
params: query,
|
||||
});
|
||||
const { data } = await http.post<RoleRow[]>("/roles/search", query);
|
||||
return data;
|
||||
},
|
||||
transformRes: ({ res }) => {
|
||||
|
||||
@ -22,6 +22,12 @@ export const createUserCrudOptions: CreateCrudOptions<UserRow> = () => {
|
||||
return {
|
||||
crudOptions: {
|
||||
pagination: { show: false },
|
||||
search: { show: true},
|
||||
searchFormOptions: {
|
||||
inline: true,
|
||||
labelWidth: "80px",
|
||||
col: { span: 0 },
|
||||
},
|
||||
request: {
|
||||
transformQuery: ({ form }) =>
|
||||
toSearchParams(form as Record<string, unknown>, [
|
||||
@ -31,9 +37,7 @@ export const createUserCrudOptions: CreateCrudOptions<UserRow> = () => {
|
||||
"phone",
|
||||
]),
|
||||
pageRequest: async (query) => {
|
||||
const { data } = await http.get<UserRow[]>("/users/", {
|
||||
params: query,
|
||||
});
|
||||
const { data } = await http.post<UserRow[]>("/users/search", query);
|
||||
return data;
|
||||
},
|
||||
transformRes: ({ res }) => {
|
||||
|
||||
@ -40,3 +40,36 @@
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 搜索区域不换行,强制查询条件和按钮在同一行 */
|
||||
.page-content .fs-crud .fs-search {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-content .fs-crud .fs-search .el-form {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.page-content .fs-crud .fs-search .el-form-item {
|
||||
margin-bottom: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-content .fs-crud .fs-search .el-form-item__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-content .fs-crud .fs-search .fs-search-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user