import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export function formatDate(date: string | Date, locale: string = "zh-CN") { return new Intl.DateTimeFormat(locale, { year: "numeric", month: "short", day: "numeric", }).format(new Date(date)) } /** * 格式化日期+时间(含小时、分钟)。 * 用于替代各处重复的 `new Date(x).toLocaleString("zh-CN", {...})` 调用。 */ export function formatDateTime(date: string | Date, locale: string = "zh-CN") { return new Intl.DateTimeFormat(locale, { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }).format(new Date(date)) } /** * 格式化为长日期(含星期、完整月份名)。 * 用于替代 `new Date(x).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })`。 * 默认使用中文 locale,输出形如「2026年6月20日 周一」。 */ export function formatLongDate(date: string | Date, locale: string = "zh-CN") { return new Intl.DateTimeFormat(locale, { weekday: "short", year: "numeric", month: "long", day: "numeric", }).format(new Date(date)) } /** Next.js App Router 搜索参数类型 */ export type SearchParams = { [key: string]: string | string[] | undefined } /** 从 SearchParams 中安全提取单个字符串值 */ export function getSearchParam(params: SearchParams, key: string): string | undefined { const v = params[key] if (typeof v === "string") return v if (Array.isArray(v)) return v[0] return undefined } /** 格式化数字,null/undefined/非有限数返回 "-" */ export function formatNumber(v: number | null | undefined, digits = 1): string { if (typeof v !== "number" || !Number.isFinite(v)) return "-" return v.toFixed(digits) } /** * 从姓名生成头像占位用的首字母(最多 2 个字符)。 * 用于 AvatarFallback 组件。 * - 含空格的姓名:取各单词首字母拼接(如 "John Doe" -> "JD") * - 无空格的姓名:取前 2 个字符(如 "张三" -> "张三") * - 空值:返回 "U"(User 通用占位) */ export function getInitials(name: string | null | undefined): string { if (!name) return "U" const trimmed = name.trim() if (!trimmed) return "U" if (trimmed.includes(" ")) { return trimmed .split(/\s+/) .map((n) => n[0]) .join("") .toUpperCase() .slice(0, 2) } return trimmed.slice(0, 2).toUpperCase() } /** * 格式化日期为文件名安全的 YYYY-MM-DD 格式。 * 用于导出文件名(如 `grades_export_2026-06-20.xlsx`)。 * @param d 日期对象,默认为当前时间 */ export function formatDateForFile(d: Date = new Date()): string { const y = d.getFullYear() const m = String(d.getMonth() + 1).padStart(2, "0") const day = String(d.getDate()).padStart(2, "0") return `${y}-${m}-${day}` }