feat: 增强用户、角色和权限管理功能

- 在用户、角色和权限的API中添加模糊查询支持
- 更新CRUD选项以支持查询参数的转换
- 优化前端管理页面,提升用户体验
- 删除不再使用的旧资产文件以减小包体积
This commit is contained in:
jayhgq 2026-05-09 13:13:45 +08:00
parent 5a3fdfe295
commit 83d84254f5
25 changed files with 131 additions and 33 deletions

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select, and_
from fastcrud import FastCRUD from fastcrud import FastCRUD
from typing import Dict, Any from typing import Dict, Any
@ -15,6 +15,7 @@ from schemas import (
) )
from middleware import get_current_user from middleware import get_current_user
from models import User from models import User
from utils.like_filter import ilike_contains
router = APIRouter(prefix="/permissions", tags=["permissions"]) router = APIRouter(prefix="/permissions", tags=["permissions"])
@ -54,11 +55,21 @@ async def create_permission(
async def get_permissions( async def get_permissions(
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
name: str | None = Query(None, description="权限名称,模糊匹配"),
code: str | None = Query(None, description="权限编码,模糊匹配"),
): ):
"""获取所有权限""" """获取权限列表支持按名称、编码的模糊查询与组合查询AND"""
result = await db.execute(select(Permission)) stmt = select(Permission)
filters = []
for col, raw in ((Permission.name, name), (Permission.code, code)):
cond = ilike_contains(col, raw)
if cond is not None:
filters.append(cond)
if filters:
stmt = stmt.where(and_(*filters))
result = await db.execute(stmt)
permissions = result.scalars().all() permissions = result.scalars().all()
return permissions return [PermissionResponse.model_validate(p) for p in permissions]
@router.get("/tree", response_model=list[PermissionTreeResponse]) @router.get("/tree", response_model=list[PermissionTreeResponse])

View File

@ -1,11 +1,13 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from fastcrud import FastCRUD from fastcrud import FastCRUD
from database import get_db from database import get_db
from models import Role, User from models import Role, User
from schemas import RoleCreate, RoleResponse from schemas import RoleCreate, RoleResponse
from middleware import get_current_user from middleware import get_current_user
from utils.like_filter import ilike_contains
router = APIRouter(prefix="/roles", tags=["roles"]) router = APIRouter(prefix="/roles", tags=["roles"])
@ -38,11 +40,18 @@ async def create_role(
@router.get("/", response_model=list[RoleResponse]) @router.get("/", response_model=list[RoleResponse])
async def get_roles( async def get_roles(
db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
name: str | None = Query(None, description="角色名称,模糊匹配"),
): ):
"""获取所有角色""" """获取角色列表;支持按名称模糊查询"""
result = await role_crud.get_multi(db) stmt = select(Role)
return result["data"] cond = ilike_contains(Role.name, name)
if cond is not None:
stmt = stmt.where(cond)
result = await db.execute(stmt)
rows = result.scalars().all()
return [RoleResponse.model_validate(r) for r in rows]
@router.get("/{role_id}", response_model=RoleResponse) @router.get("/{role_id}", response_model=RoleResponse)

View File

@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi.responses import Response from fastapi.responses import Response
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select, and_
from fastcrud import FastCRUD from fastcrud import FastCRUD
from datetime import timedelta, datetime from datetime import timedelta, datetime
import redis.asyncio as redis import redis.asyncio as redis
@ -22,6 +22,7 @@ from config import app_settings
from utils.redis_client import get_redis, set_token_in_redis from utils.redis_client import get_redis, set_token_in_redis
from utils.captcha import create_captcha, validate_captcha from utils.captcha import create_captcha, validate_captcha
from middleware import get_current_user from middleware import get_current_user
from utils.like_filter import ilike_contains
router = APIRouter(prefix="/users", tags=["users"]) router = APIRouter(prefix="/users", tags=["users"])
@ -118,11 +119,30 @@ async def create_user(user_data: UserCreateRequest, db: AsyncSession = Depends(g
@router.get("/", response_model=list[UserResponse]) @router.get("/", response_model=list[UserResponse])
async def get_users( async def get_users(
db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) 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"""
result = await user_crud.get_multi(db) stmt = select(User)
return result["data"] filters = []
for col, raw in (
(User.username, username),
(User.nickname, nickname),
(User.email, email),
(User.phone, phone),
):
cond = ilike_contains(col, raw)
if cond is not None:
filters.append(cond)
if filters:
stmt = stmt.where(and_(*filters))
result = await db.execute(stmt)
rows = result.scalars().all()
return [UserResponse.model_validate(u) for u in rows]
@router.get("/me", response_model=UserResponse) @router.get("/me", response_model=UserResponse)

View File

@ -0,0 +1,20 @@
"""LIKE / ILIKE 模糊查询:转义 %、_、\\,避免通配符注入。"""
from typing import Any
from sqlalchemy import ColumnElement
def ilike_contains(column: ColumnElement[Any], value: str | None) -> ColumnElement[bool] | None:
"""非空时对 column 做 ILIKE '%value%'PostgreSQL 下不区分大小写)。"""
if value is None:
return None
s = value.strip()
if not s:
return None
escaped = (
s.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_")
)
return column.ilike(f"%{escaped}%", escape="\\")

View File

@ -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-CG0WJko8.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-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};

View File

@ -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-CG0WJko8.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-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};

View File

@ -0,0 +1 @@
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};

View File

@ -1 +0,0 @@
import{z as a,n as r,f as d,A as m,g as c,p as l,w as p,r as n,d as f,B as y,q as h,o as g}from"./index-CG0WJko8.js";const _=()=>({crudOptions:{pagination:{show:!1},request:{pageRequest:async()=>{const{data:e}=await r.get("/permissions/");return e},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"))}}}}}),q=d({__name:"PermissionManage",setup(e){const{crudBinding:t,crudRef:s,crudExpose:o}=m({createCrudOptions:_});return c(()=>{o.doRefresh()}),(w,x)=>{const i=n("fs-crud"),u=n("fs-page");return g(),l(u,null,{default:p(()=>[f(i,y({ref_key:"crudRef",ref:s},h(t)),null,16)]),_:1})}}});export{q 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-CG0WJko8.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-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};

View File

@ -1 +0,0 @@
import{z as B,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 P,x,C as Y,F as L,D as J,E as R,o as r,G as Q,_ as W}from"./index-CG0WJko8.js";function X(C){return()=>({crudOptions:{pagination:{show:!1},request:{pageRequest:async()=>{const{data:t}=await u.get("/roles/");return t},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]=B(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]=B(t).format("YYYY-MM-DD HH:mm:ss"))}}}}})}const Z={key:0,class:"dlg-role"},ee={key:1,class:"perm-tags"},te={key:0,class:"dlg-role"},se=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 Q(),(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:X({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"),K=p("el-tree"),A=p("el-button"),S=p("fs-page");return r(),v(S,null,{default:m(()=>[_(n,j({ref_key:"crudRef",ref:N},G(E)),null,16),_(c,{modelValue:t.value,"onUpdate:modelValue":e[0]||(e[0]=l=>t.value=l),title:"查看角色权限",width:"560px","destroy-on-close":"",onClosed:e[1]||(e[1]=l=>g.value=[])},{default:m(()=>[d.value?(r(),h("p",Z,[e[4]||(e[4]=k(" 角色:",-1)),P("strong",null,x(d.value.name),1)])):Y("",!0),g.value.length?(r(),h("div",ee,[(r(!0),h(L,null,J(g.value,l=>(r(),v(f,{key:l.id,class:"perm-tag",type:"info"},{default:m(()=>[k(x(l.name)+""+x(l.code)+" ",1)]),_:2},1024))),128))])):(r(),v(o,{key:2,description:"暂未分配权限","image-size":72}))]),_:1},8,["modelValue"]),_(c,{modelValue:s.value,"onUpdate:modelValue":e[3]||(e[3]=l=>s.value=l),title:"分配权限",width:"640px","destroy-on-close":"",onOpen:O},{footer:m(()=>[_(A,{onClick:e[2]||(e[2]=l=>s.value=!1)},{default:m(()=>[...e[6]||(e[6]=[k("取消",-1)])]),_:1}),_(A,{type:"primary",loading:b.value,onClick:z},{default:m(()=>[...e[7]||(e[7]=[k(" 保存 ",-1)])]),_:1},8,["loading"])]),default:m(()=>[y.value?(r(),h("p",te,[e[5]||(e[5]=k(" 角色:",-1)),P("strong",null,x(y.value.name),1)])):Y("",!0),V.value.length?(r(),v(K,{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"])):(r(),v(o,{key:2,description:"暂无权限数据","image-size":64}))]),_:1},8,["modelValue"])]),_:1})}}}),ne=W(se,[["__scopeId","data-v-714ba56c"]]);export{ne as default};

View 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 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};

View 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-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};

View File

@ -1 +0,0 @@
import{z as a,O as i,n as r,f as d,A as m,g as c,p,w as f,r as o,d as y,B as h,q as w,o as g}from"./index-CG0WJko8.js";const _=()=>({crudOptions:{pagination:{show:!1},request:{pageRequest:async()=>{const{data:e}=await r.get("/users/");return e},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",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"},phone:{title:"手机",key:"phone",type:"text"},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:i({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"))}}}}}),q=d({__name:"UserManage",setup(e){const{crudBinding:t,crudRef:s,crudExpose:n}=m({createCrudOptions:_});return c(()=>{n.doRefresh()}),(x,k)=>{const u=o("fs-crud"),l=o("fs-page");return g(),p(l,null,{default:f(()=>[y(u,h({ref_key:"crudRef",ref:s},w(t)),null,16)]),_:1})}}});export{q 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

@ -0,0 +1 @@
function c(o,i){const r={};for(const n of i){const t=o[n];t!=null&&String(t).trim()!==""&&(r[n]=String(t).trim())}return r}export{c as t};

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-CG0WJko8.js"></script> <script type="module" crossorigin src="/assets/index-B340d_U7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ciY4YJQ7.css"> <link rel="stylesheet" crossorigin href="/assets/index-ciY4YJQ7.css">
</head> </head>
<body> <body>

View File

@ -0,0 +1,14 @@
/** 将 CRUD 查询表单中非空字段转为 GET query组合查询 */
export function toSearchParams(
form: Record<string, unknown>,
keys: readonly string[],
): Record<string, string> {
const q: Record<string, string> = {};
for (const k of keys) {
const v = form[k];
if (v !== undefined && v !== null && String(v).trim() !== "") {
q[k] = String(v).trim();
}
}
return q;
}

View File

@ -1,6 +1,7 @@
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 { http } from "@/modules/admin/api/http"; import { http } from "@/modules/admin/api/http";
import { toSearchParams } from "@/modules/admin/utils/list-query";
export type PermissionRow = { export type PermissionRow = {
id: number; id: number;
@ -17,8 +18,12 @@ export const createPermissionCrudOptions: CreateCrudOptions<PermissionRow> = ()
crudOptions: { crudOptions: {
pagination: { show: false }, pagination: { show: false },
request: { request: {
pageRequest: async () => { transformQuery: ({ form }) =>
const { data } = await http.get<PermissionRow[]>("/permissions/"); toSearchParams(form as Record<string, unknown>, ["name", "code"]),
pageRequest: async (query) => {
const { data } = await http.get<PermissionRow[]>("/permissions/", {
params: query,
});
return data; return data;
}, },
transformRes: ({ res }) => { transformRes: ({ res }) => {

View File

@ -1,6 +1,7 @@
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 { http } from "@/modules/admin/api/http"; import { http } from "@/modules/admin/api/http";
import { toSearchParams } from "@/modules/admin/utils/list-query";
export type RoleRow = { export type RoleRow = {
id: number; id: number;
@ -20,8 +21,12 @@ export function buildRoleCrudOptions(h: RoleCrudHandlers): CreateCrudOptions<Rol
crudOptions: { crudOptions: {
pagination: { show: false }, pagination: { show: false },
request: { request: {
pageRequest: async () => { transformQuery: ({ form }) =>
const { data } = await http.get<RoleRow[]>("/roles/"); toSearchParams(form as Record<string, unknown>, ["name"]),
pageRequest: async (query) => {
const { data } = await http.get<RoleRow[]>("/roles/", {
params: query,
});
return data; return data;
}, },
transformRes: ({ res }) => { transformRes: ({ res }) => {

View File

@ -2,6 +2,7 @@ import type { CreateCrudOptions } from "@fast-crud/fast-crud";
import { dict } from "@fast-crud/fast-crud"; import { dict } from "@fast-crud/fast-crud";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { http } from "@/modules/admin/api/http"; import { http } from "@/modules/admin/api/http";
import { toSearchParams } from "@/modules/admin/utils/list-query";
export type UserRow = { export type UserRow = {
id: number; id: number;
@ -22,8 +23,17 @@ export const createUserCrudOptions: CreateCrudOptions<UserRow> = () => {
crudOptions: { crudOptions: {
pagination: { show: false }, pagination: { show: false },
request: { request: {
pageRequest: async () => { transformQuery: ({ form }) =>
const { data } = await http.get<UserRow[]>("/users/"); toSearchParams(form as Record<string, unknown>, [
"username",
"nickname",
"email",
"phone",
]),
pageRequest: async (query) => {
const { data } = await http.get<UserRow[]>("/users/", {
params: query,
});
return data; return data;
}, },
transformRes: ({ res }) => { transformRes: ({ res }) => {
@ -76,6 +86,7 @@ export const createUserCrudOptions: CreateCrudOptions<UserRow> = () => {
title: "昵称", title: "昵称",
key: "nickname", key: "nickname",
type: "text", type: "text",
search: { show: true },
form: { form: {
rules: [{ required: true, message: "必填" }], rules: [{ required: true, message: "必填" }],
}, },
@ -99,11 +110,13 @@ export const createUserCrudOptions: CreateCrudOptions<UserRow> = () => {
title: "邮箱", title: "邮箱",
key: "email", key: "email",
type: "text", type: "text",
search: { show: true },
}, },
phone: { phone: {
title: "手机", title: "手机",
key: "phone", key: "phone",
type: "text", type: "text",
search: { show: true },
}, },
wx_openid: { wx_openid: {
title: "微信OpenID", title: "微信OpenID",