- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
/**
|
||
* 部门工具函数
|
||
*
|
||
* 提供部门层级结构处理、权限范围检查等功能
|
||
* 参考 flnxi_sucai 项目的实现逻辑
|
||
*/
|
||
|
||
/**
|
||
* 标准化部门名称(去除空白字符)
|
||
*/
|
||
const normalizeName = (name?: string | null): string => {
|
||
return (name || '').trim()
|
||
}
|
||
|
||
/**
|
||
* 构建部门后裔映射
|
||
* 返回一个Map,key是部门名称,value是该部门的所有后裔部门名称集合
|
||
*
|
||
* @param departments 部门列表
|
||
* @returns Map<string, Set<string>> 部门名称到其后裔部门名称集合的映射
|
||
*/
|
||
export function buildDepartmentDescendants(
|
||
departments: Array<{ id: string; name: string; parentId?: string | null }>
|
||
): Map<string, Set<string>> {
|
||
// 构建部门ID到部门的映射
|
||
const deptMap = new Map<string, { id: string; name: string; parentId?: string | null }>()
|
||
departments.forEach(dept => {
|
||
deptMap.set(dept.id, dept)
|
||
})
|
||
|
||
// 构建部门名称到部门的映射
|
||
const nameToDeptMap = new Map<string, { id: string; name: string; parentId?: string | null }>()
|
||
departments.forEach(dept => {
|
||
const normalizedName = normalizeName(dept.name)
|
||
if (normalizedName) {
|
||
nameToDeptMap.set(normalizedName, dept)
|
||
}
|
||
})
|
||
|
||
// 构建子部门映射(parentId -> children)
|
||
const childrenMap = new Map<string, string[]>()
|
||
departments.forEach(dept => {
|
||
if (dept.parentId && deptMap.has(dept.parentId)) {
|
||
const parent = deptMap.get(dept.parentId)!
|
||
const parentName = normalizeName(parent.name)
|
||
if (parentName) {
|
||
if (!childrenMap.has(parentName)) {
|
||
childrenMap.set(parentName, [])
|
||
}
|
||
childrenMap.get(parentName)!.push(normalizeName(dept.name))
|
||
}
|
||
}
|
||
})
|
||
|
||
// 构建后裔映射(递归收集所有后裔)
|
||
const descendantMap = new Map<string, Set<string>>()
|
||
|
||
const visit = (deptName: string): Set<string> => {
|
||
if (descendantMap.has(deptName)) {
|
||
return descendantMap.get(deptName)!
|
||
}
|
||
|
||
const descendants = new Set<string>([deptName])
|
||
const children = childrenMap.get(deptName) || []
|
||
|
||
children.forEach(childName => {
|
||
visit(childName).forEach(desc => descendants.add(desc))
|
||
})
|
||
|
||
descendantMap.set(deptName, descendants)
|
||
return descendants
|
||
}
|
||
|
||
// 遍历所有部门,构建后裔映射
|
||
departments.forEach(dept => {
|
||
const deptName = normalizeName(dept.name)
|
||
if (deptName) {
|
||
visit(deptName)
|
||
}
|
||
})
|
||
|
||
return descendantMap
|
||
}
|
||
|
||
/**
|
||
* 检查目标部门是否在权限范围内
|
||
*
|
||
* @param scope 权限范围部门名称(当前用户的部门或管理范围)
|
||
* @param target 目标部门名称(要检查的部门)
|
||
* @param descendantMap 部门后裔映射(可选,如果不提供则使用简单的字符串匹配)
|
||
* @returns boolean 目标部门是否在权限范围内
|
||
*/
|
||
export function isDepartmentWithinScope(
|
||
scope: string | null | undefined,
|
||
target: string | null | undefined,
|
||
descendantMap?: Map<string, Set<string>>
|
||
): boolean {
|
||
const normalizedScope = normalizeName(scope)
|
||
const normalizedTarget = normalizeName(target)
|
||
|
||
if (!normalizedScope || !normalizedTarget) {
|
||
return false
|
||
}
|
||
|
||
// 完全匹配
|
||
if (normalizedScope === normalizedTarget) {
|
||
return true
|
||
}
|
||
|
||
// 如果有后裔映射,检查目标是否是范围部门的后裔
|
||
if (descendantMap) {
|
||
const scopedDescendants = descendantMap.get(normalizedScope)
|
||
if (scopedDescendants?.has(normalizedTarget)) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
// 回退到简单的层级匹配(如果使用名称层级结构,如:父部门-子部门)
|
||
// 注意:当前项目使用 parentId,这个匹配可能不准确,但作为备用方案
|
||
return normalizedTarget.startsWith(`${normalizedScope}-`)
|
||
}
|
||
|
||
/**
|
||
* 获取部门排序比较器(中文拼音排序)
|
||
*/
|
||
export function getDepartmentCollator(): Intl.Collator {
|
||
return new Intl.Collator('zh-Hans-CN-u-co-pinyin', {
|
||
numeric: true,
|
||
sensitivity: 'base',
|
||
})
|
||
}
|
||
|