P1-3: empty-state 默认按钮 variant 改为 outline 并新增 variant prop;button.tsx 导出 ButtonProps;统一 5 个详情页返回路径为 ghost+ArrowLeft+文字标签;course-plan-detail raw a 改为 Link。P2-1: formatLongDate 默认 locale 改为 zh-CN,weekday 改为 short;返回按钮文案中文化;course-plan-detail 全量中文化;grades/analytics 标题中文化。验证:tsc 0 错误,lint 0 错误,架构图 004/005 已同步。
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
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}`
|
||
}
|