P0 修复: - 页面层 i18n 全量补齐(admin/teacher/parent/student × attendance/elective) - types.ts 状态标签常量迁移至 constants.ts(i18n key + Badge variant) - 修复 getTranslations 导入路径(next-intl → next-intl/server) P1 改进: - 解耦 parent 模块对 attendance 类型的直接依赖(本地 view-model 类型) - 导出纯函数(computeStats/buildWarnings/buildLotteryRankCase 等) - 统一空状态为 EmptyState 组件 - 清理死代码读 Action(attendance 5 个 + elective 3 个) - 预留监控埋点接口(trackEvent 13 个新事件名) - 补齐骨架屏 loading.tsx(8 个页面) - AlertDialog 替换 window.confirm(student-selection-view) - a11y 改进(aria-label/role/键盘导航) 修复: - AttendanceStatus 从 constants.ts 重导出,消除 types/constants 双源混乱 - buildWarnings 的 Translator 类型改用 ReturnType<typeof useTranslations>
93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
import "server-only"
|
||
|
||
/**
|
||
* 监控埋点接口(预留)
|
||
*
|
||
* 在关键 Server Action 中调用 trackEvent 记录业务事件,用于:
|
||
* - 公告阅读率、消息回复率等关键指标统计
|
||
* - 通知发送失败告警
|
||
* - 用户行为漏斗分析
|
||
*
|
||
* 当前实现:输出到 console.info,不阻塞主流程。
|
||
* 后续扩展:可接入外部监控服务(如 Sentry / PostHog / 自建埋点系统),
|
||
* 只需在 trackEventToSink 中替换实现即可,调用方无需改动。
|
||
*/
|
||
|
||
/** 事件名称(使用点号分隔的命名空间,如 "announcement.published") */
|
||
export type EventName =
|
||
| "announcement.created"
|
||
| "announcement.updated"
|
||
| "announcement.published"
|
||
| "announcement.archived"
|
||
| "announcement.deleted"
|
||
| "message.sent"
|
||
| "message.deleted"
|
||
| "message.marked_read"
|
||
| "notification.marked_read"
|
||
| "notification.marked_all_read"
|
||
| "notification.sent"
|
||
| "notification.send_failed"
|
||
| "attendance.recorded"
|
||
| "attendance.batch_recorded"
|
||
| "attendance.updated"
|
||
| "attendance.deleted"
|
||
| "attendance.rules_saved"
|
||
| "elective.course_created"
|
||
| "elective.course_updated"
|
||
| "elective.course_deleted"
|
||
| "elective.selection_opened"
|
||
| "elective.selection_closed"
|
||
| "elective.course_selected"
|
||
| "elective.course_dropped"
|
||
| "elective.lottery_completed"
|
||
|
||
/** 埋点事件负载 */
|
||
export interface TrackEventPayload {
|
||
/** 事件名称 */
|
||
event: EventName
|
||
/** 当前用户 ID(可选,未登录场景为 undefined) */
|
||
userId?: string
|
||
/** 目标对象 ID(如公告 ID、消息 ID) */
|
||
targetId?: string
|
||
/** 目标对象类型(如 "announcement"、"message") */
|
||
targetType?: string
|
||
/** 附加属性(如受众人数、渠道类型) */
|
||
properties?: Record<string, unknown>
|
||
}
|
||
|
||
/**
|
||
* 将事件发送到外部监控服务。
|
||
*
|
||
* 当前为占位实现:仅输出到 console.info。
|
||
* 接入真实服务时替换此函数体即可。
|
||
*/
|
||
function trackEventToSink(payload: TrackEventPayload): void {
|
||
console.info(
|
||
`[TrackEvent] ${payload.event} userId=${payload.userId ?? "-"} targetId=${payload.targetId ?? "-"}${payload.properties ? ` properties=${JSON.stringify(payload.properties)}` : ""}`
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 记录一个监控事件。
|
||
*
|
||
* 非阻塞:任何异常都被吞掉,确保不影响主业务流程。
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* await trackEvent({
|
||
* event: "announcement.published",
|
||
* userId: ctx.userId,
|
||
* targetId: id,
|
||
* targetType: "announcement",
|
||
* properties: { audienceSize: userIds.length },
|
||
* })
|
||
* ```
|
||
*/
|
||
export async function trackEvent(payload: TrackEventPayload): Promise<void> {
|
||
try {
|
||
trackEventToSink(payload)
|
||
} catch {
|
||
// 埋点失败不影响主流程
|
||
}
|
||
}
|