feat(admin): 补全 admin 模块核心功能与产品体验优化
修复 v4 报告中的 13 个产品体验问题:新增用户管理列表页和系统设置页,重组导航菜单并补充缺失入口,增加角色切换机制,Dashboard 增加快捷操作和 recharts 趋势图表,考勤增加统计概览,排课增加课表网格视图,统一 Toast 操作反馈,同步更新架构文档
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import "server-only"
|
||||
|
||||
import { cache } from "react"
|
||||
import { and, count, desc, eq, gt, inArray } from "drizzle-orm"
|
||||
import { and, count, desc, eq, gt, ilike, inArray, or } from "drizzle-orm"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
import { db } from "@/shared/db"
|
||||
@@ -304,3 +304,97 @@ export const getUserIdsByGradeId = cache(
|
||||
return rows.map((r) => r.id)
|
||||
}
|
||||
)
|
||||
|
||||
/** Returns all user IDs (used for school-wide announcement notifications). */
|
||||
export const getAllUserIds = cache(async (): Promise<string[]> => {
|
||||
const rows = await db.select({ id: users.id }).from(users)
|
||||
return rows.map((r) => r.id)
|
||||
})
|
||||
|
||||
export type AdminUserListItem = {
|
||||
id: string
|
||||
name: string | null
|
||||
email: string
|
||||
roles: string[]
|
||||
phone: string | null
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
export type AdminUserListResult = {
|
||||
items: AdminUserListItem[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
totalPages: number
|
||||
}
|
||||
|
||||
export async function getAdminUsers(params: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
search?: string
|
||||
role?: string
|
||||
}): Promise<AdminUserListResult> {
|
||||
const page = Math.max(1, params.page ?? 1)
|
||||
const pageSize = Math.min(100, Math.max(1, params.pageSize ?? 20))
|
||||
const offset = (page - 1) * pageSize
|
||||
|
||||
const conditions = []
|
||||
if (params.search) {
|
||||
const search = `%${params.search}%`
|
||||
conditions.push(
|
||||
or(ilike(users.name, search), ilike(users.email, search))
|
||||
)
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? and(...conditions) : undefined
|
||||
|
||||
const [userRows, countRow] = await Promise.all([
|
||||
db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(whereClause)
|
||||
.orderBy(desc(users.createdAt))
|
||||
.limit(pageSize)
|
||||
.offset(offset),
|
||||
db.select({ value: count() }).from(users).where(whereClause),
|
||||
])
|
||||
|
||||
const userIds = userRows.map((u) => u.id)
|
||||
const roleRows = userIds.length
|
||||
? await db
|
||||
.select({ userId: usersToRoles.userId, roleName: roles.name })
|
||||
.from(usersToRoles)
|
||||
.innerJoin(roles, eq(usersToRoles.roleId, roles.id))
|
||||
.where(inArray(usersToRoles.userId, userIds))
|
||||
: []
|
||||
|
||||
const rolesByUserId = new Map<string, string[]>()
|
||||
for (const row of roleRows) {
|
||||
const list = rolesByUserId.get(row.userId) ?? []
|
||||
list.push(row.roleName)
|
||||
rolesByUserId.set(row.userId, list)
|
||||
}
|
||||
|
||||
const items = userRows.map((u) => ({
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
email: u.email,
|
||||
roles: rolesByUserId.get(u.id) ?? [],
|
||||
phone: u.phone,
|
||||
createdAt: u.createdAt,
|
||||
}))
|
||||
|
||||
const total = Number(countRow[0]?.value ?? 0)
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAdminUserRoles(): Promise<string[]> {
|
||||
const rows = await db.select({ name: roles.name }).from(roles)
|
||||
return rows.map((r) => r.name)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user