feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
import { createError, defineEventHandler, getQuery } from 'h3'
import { db } from '~/drizzle/db'
import { users, gradeClass } from '~/drizzle/schema'
import { and, count, inArray, ne, notInArray } from 'drizzle-orm'
import { smartSort } from '~~/server/utils/grade-class-core'
import { ARCHIVED_USER_STATUSES, resolveArchivedFilter } from '~~/server/utils/user-archive'
export default defineEventHandler(async (event) => {
try {
// 检查用户是否为管理员
const user = event.context.user
if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({
statusCode: 403,
message: '只有系统管理员可以访问此选项'
})
}
// 归档筛选:archived=1 仅查已归档(graduate/withdrawn);archived=0 排除已归档;缺省不限制
const query = getQuery(event)
const archivedFilter = resolveArchivedFilter(query.archived)
// 用户树需要全量轻字段,避免用分页列表推导时统计不完整;排除待审核用户(未通过审核不计入组织树/筛选)
const treeConditions = [ne(users.status, 'pending')]
if (archivedFilter === 'archived') {
treeConditions.push(inArray(users.status, [...ARCHIVED_USER_STATUSES]))
} else if (archivedFilter === 'unarchived') {
treeConditions.push(notInArray(users.status, [...ARCHIVED_USER_STATUSES]))
}
const treeUsers = await db
.select({
id: users.id,
name: users.name,
username: users.username,
grade: users.grade,
class: users.class,
role: users.role,
status: users.status
})
.from(users)
.where(and(...treeConditions))
// 已归档用户总数(graduate/withdrawn 状态),供前端"已归档用户"入口徽标
const archivedCountResult = await db
.select({ count: count() })
.from(users)
.where(inArray(users.status, [...ARCHIVED_USER_STATUSES]))
const archivedCount = archivedCountResult[0]?.count || 0
// 年级班级选项:配置优先;未配置时回退到全部用户聚合(含 withdrawn/graduate 与仅有班级项),
// 与组织结构树口径一致,避免筛选下拉缺项
const configRows = await db
.select({ grade: gradeClass.grade, class: gradeClass.class })
.from(gradeClass)
let grades: string[]
let classes: { grade: string | null; class: string }[]
if (configRows.length > 0) {
const options = configRows
.filter((item) => Boolean(item.grade?.trim()) && Boolean(item.class?.trim()))
.sort((a, b) => {
const gradeResult = smartSort(a.grade, b.grade)
return gradeResult || smartSort(a.class, b.class)
})
grades = [...new Set(options.map((item) => item.grade))].sort(smartSort)
classes = options.map((item) => ({ grade: item.grade, class: item.class }))
} else {
const gradesSet = new Set<string>()
const classesMap = new Map<string, { grade: string | null; class: string }>()
for (const treeUser of treeUsers) {
const gradeValue = treeUser.grade?.trim() || ''
const classValue = treeUser.class?.trim() || ''
if (gradeValue) gradesSet.add(gradeValue)
if (classValue) {
const classKey = `${gradeValue}:${classValue}`
if (!classesMap.has(classKey)) {
classesMap.set(classKey, { grade: gradeValue || null, class: classValue })
}
}
}
grades = Array.from(gradesSet).sort(smartSort)
classes = Array.from(classesMap.values()).sort((a, b) =>
a.class.localeCompare(b.class, 'zh-CN', { numeric: true })
)
}
return {
success: true,
grades,
classes,
treeUsers,
archivedCount
}
} catch (error) {
console.error('获取用户筛选选项失败:', error)
throw createError({
statusCode: 500,
message: '获取选项失败'
})
}
})