feat(dashboard,diagnostic,elective): add widgets, layout, parent dashboard, role-config, services, elective components

dashboard:

- Add comparison-badge, dashboard-notification-widget, dashboard-responsive-layout, dashboard-time-range-filter

- Add parent-dashboard components directory

- Add config, hooks, and services directories

diagnostic:

- Add role-config and services directory

elective:

- Add elective-course-detail, elective-stats-cards, parent-selection-view components

- Add data-access-settings and data-access-stats
This commit is contained in:
SpecialX
2026-07-03 10:25:46 +08:00
parent dfffb61e94
commit 138b6f1b00
58 changed files with 3313 additions and 695 deletions

View File

@@ -0,0 +1,97 @@
import "server-only"
import { cache } from "react"
import { eq, and } from "drizzle-orm"
import { db } from "@/shared/db"
import { systemSettings } from "@/shared/db/schema"
/**
* 选课模块配置化设置P2-4 新增)。
*
* 设计原则:
* - 复用全局 `system_settings` 表category="elective"),避免新增独立表
* - 支持按年级覆盖key=`creditLimit:grade:<gradeId>`fallback 到全局key=`creditLimit:default`
* - 用 React `cache()` 包装,单次请求内去重
* - 配置缺失时使用默认值(向后兼容)
*
* 配置项:
* - `creditLimit:default` / `creditLimit:grade:<gradeId>`:学期学分上限(默认 10
* - `capacityNotifyThreshold`:容量阈值通知比例 0-1默认 0.9
*/
const SETTINGS_CATEGORY = "elective"
/** 默认学期学分上限K12 选修课) */
const DEFAULT_MAX_CREDIT_PER_TERM = 10
/** 默认容量阈值通知比例90% */
const DEFAULT_CAPACITY_NOTIFY_THRESHOLD = 0.9
/**
* 读取 system_settings 中指定 key 的值。
* 失败或未配置时返回 null不抛错保证向后兼容
*/
async function readSettingValue(key: string): Promise<string | null> {
const [row] = await db
.select({ value: systemSettings.value, valueType: systemSettings.valueType })
.from(systemSettings)
.where(
and(
eq(systemSettings.category, SETTINGS_CATEGORY),
eq(systemSettings.key, key)
)
)
.limit(1)
return row?.value ?? null
}
/**
* 获取学期学分上限P2-4 新增)。
*
* 查询顺序:
* 1. 若传入 gradeId先查 `creditLimit:grade:<gradeId>`
* 2. 若未配置或未传入 gradeIdfallback 到 `creditLimit:default`
* 3. 都未配置则返回默认值 10
*
* @param gradeId 学生所在年级 ID可选
*/
export const getElectiveCreditLimit = cache(
async (gradeId?: string | null): Promise<number> => {
if (gradeId) {
const gradeValue = await readSettingValue(`creditLimit:grade:${gradeId}`)
if (gradeValue !== null) {
const parsed = Number(gradeValue)
if (!Number.isNaN(parsed) && parsed > 0) return parsed
}
}
const defaultValue = await readSettingValue("creditLimit:default")
if (defaultValue !== null) {
const parsed = Number(defaultValue)
if (!Number.isNaN(parsed) && parsed > 0) return parsed
}
return DEFAULT_MAX_CREDIT_PER_TERM
}
)
/**
* 获取容量阈值通知比例P2-4 新增)。
*
* 当课程 `enrolledCount >= capacity * threshold` 时触发管理员通知。
* 默认 0.990%)。
*/
export const getCapacityNotifyThreshold = cache(
async (): Promise<number> => {
const value = await readSettingValue("capacityNotifyThreshold")
if (value !== null) {
const parsed = Number(value)
if (!Number.isNaN(parsed) && parsed > 0 && parsed <= 1) return parsed
}
return DEFAULT_CAPACITY_NOTIFY_THRESHOLD
}
)
/** 导出默认值常量(供测试与文档引用) */
export const ELECTIVE_DEFAULTS = {
MAX_CREDIT_PER_TERM: DEFAULT_MAX_CREDIT_PER_TERM,
CAPACITY_NOTIFY_THRESHOLD: DEFAULT_CAPACITY_NOTIFY_THRESHOLD,
} as const