diff --git a/src/shared/components/locale-switcher.tsx b/src/shared/components/locale-switcher.tsx index 62cc5d7..8e95dd4 100644 --- a/src/shared/components/locale-switcher.tsx +++ b/src/shared/components/locale-switcher.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { useTransition } from "react" import { useLocale, useTranslations } from "next-intl" import { Check, Globe } from "lucide-react" -import { toast } from "sonner" +import { notify } from "@/shared/lib/notify" import { Button } from "@/shared/components/ui/button" import { @@ -35,9 +35,9 @@ export function LocaleSwitcher({ compact = false }: { compact?: boolean }) { startTransition(async () => { const result = await setLocaleAction(next) if (result.success) { - toast.success(t("switch")) + notify.success(t("switch")) } else { - toast.error(t("switch")) + notify.error(t("switch")) } }) } diff --git a/src/shared/hooks/use-action-with-toast.ts b/src/shared/hooks/use-action-with-toast.ts index 8a12d6b..9a7acf1 100644 --- a/src/shared/hooks/use-action-with-toast.ts +++ b/src/shared/hooks/use-action-with-toast.ts @@ -1,7 +1,7 @@ "use client" import { useTransition } from "react" -import { toast } from "sonner" +import { notify } from "@/shared/lib/notify" import type { ActionState } from "@/shared/types/action-state" export function useActionWithToast() { @@ -11,9 +11,9 @@ export function useActionWithToast() { startTransition(async () => { const result = await action() if (result.success) { - toast.success(result.message || "操作成功") + notify.success(result.message || "操作成功") } else { - toast.error(result.message || "操作失败") + notify.error(result.message || "操作失败") } }) } diff --git a/src/shared/i18n/messages/en/rbac.json b/src/shared/i18n/messages/en/rbac.json index 787232e..7065af5 100644 --- a/src/shared/i18n/messages/en/rbac.json +++ b/src/shared/i18n/messages/en/rbac.json @@ -173,7 +173,11 @@ }, "audit": { "read": "Read Audit Logs", - "readDesc": "Allows viewing system audit logs" + "readDesc": "Allows viewing system audit logs", + "purge": "Purge Audit Logs", + "purgeDesc": "Physically delete expired audit logs (destructive, admin only)", + "retentionManage": "Manage Audit Retention", + "retentionManageDesc": "Configure audit log retention days and auto-cleanup rules" }, "announcement": { "manage": "Manage Announcements", diff --git a/src/shared/i18n/messages/zh-CN/rbac.json b/src/shared/i18n/messages/zh-CN/rbac.json index 6baed8a..c95d255 100644 --- a/src/shared/i18n/messages/zh-CN/rbac.json +++ b/src/shared/i18n/messages/zh-CN/rbac.json @@ -173,7 +173,11 @@ }, "audit": { "read": "查看审计日志", - "readDesc": "允许查看系统审计日志" + "readDesc": "允许查看系统审计日志", + "purge": "清理审计日志", + "purgeDesc": "物理删除过期审计日志(破坏性操作,仅管理员)", + "retentionManage": "管理审计保留策略", + "retentionManageDesc": "配置审计日志保留天数与自动清理规则" }, "announcement": { "manage": "管理公告", diff --git a/src/shared/lib/permission-bitmap.ts b/src/shared/lib/permission-bitmap.ts index 8e56109..fd5e092 100644 --- a/src/shared/lib/permission-bitmap.ts +++ b/src/shared/lib/permission-bitmap.ts @@ -91,6 +91,8 @@ export const PERMISSION_BITMAP_ORDER: readonly Permission[] = [ Permissions.SETTINGS_ADMIN, // Audit Permissions.AUDIT_LOG_READ, + Permissions.AUDIT_LOG_PURGE, + Permissions.AUDIT_RETENTION_MANAGE, // Announcement Permissions.ANNOUNCEMENT_MANAGE, Permissions.ANNOUNCEMENT_READ, diff --git a/src/shared/lib/permissions.ts b/src/shared/lib/permissions.ts index f766d47..80eb5c8 100644 --- a/src/shared/lib/permissions.ts +++ b/src/shared/lib/permissions.ts @@ -48,6 +48,8 @@ export const ROLE_PERMISSIONS_SEED: Record = { Permissions.AI_CONFIGURE, Permissions.SETTINGS_ADMIN, Permissions.AUDIT_LOG_READ, + Permissions.AUDIT_LOG_PURGE, + Permissions.AUDIT_RETENTION_MANAGE, Permissions.ANNOUNCEMENT_MANAGE, Permissions.ANNOUNCEMENT_READ, Permissions.GRADE_RECORD_MANAGE, diff --git a/src/shared/lib/resolve-action-error.ts b/src/shared/lib/resolve-action-error.ts index 8f607bc..9a26da2 100644 --- a/src/shared/lib/resolve-action-error.ts +++ b/src/shared/lib/resolve-action-error.ts @@ -15,7 +15,7 @@ import type { ActionState } from "@/shared/types/action-state" * ```tsx * const t = useTranslations("attendance") * const msg = resolveActionError(result, t, t("errors.unexpected")) - * toast.error(msg) + * notify.error(msg) * ``` */ export function resolveActionError( diff --git a/src/shared/types/permissions.ts b/src/shared/types/permissions.ts index d0983d2..0c00b98 100644 --- a/src/shared/types/permissions.ts +++ b/src/shared/types/permissions.ts @@ -110,6 +110,10 @@ export const Permissions = { // Audit AUDIT_LOG_READ: "audit_log:read", + /** 审计日志清理(物理删除)—— 破坏性操作,仅 admin */ + AUDIT_LOG_PURGE: "audit_log:purge", + /** 审计日志保留策略管理(配置保留天数、自动清理规则) */ + AUDIT_RETENTION_MANAGE: "audit_retention:manage", // Announcement ANNOUNCEMENT_MANAGE: "announcement:manage", @@ -210,6 +214,17 @@ export const Permissions = { export type Permission = (typeof Permissions)[keyof typeof Permissions] +/** 所有合法的权限值列表(用于运行时校验) */ +const PERMISSION_VALUES = Object.values(Permissions) as readonly string[] + +/** + * 类型守卫:判断值是否为合法的 Permission。 + * 用于从 string[] 安全收窄到 Permission[],替代 `as Permission[]` 断言。 + */ +export function isPermission(value: unknown): value is Permission { + return typeof value === "string" && PERMISSION_VALUES.includes(value) +} + /** * Data scope for row-level security. *