feat(shared): update permissions system, i18n, hooks, and components

- Update permission-bitmap.ts, permissions.ts, resolve-action-error.ts

- Update types/permissions.ts

- Update rbac i18n messages (en, zh-CN)

- Update locale-switcher.tsx

- Update use-action-with-toast.ts hook
This commit is contained in:
SpecialX
2026-07-07 16:17:53 +08:00
parent b333cda8c5
commit 224740ad98
8 changed files with 36 additions and 9 deletions

View File

@@ -4,7 +4,7 @@ import * as React from "react"
import { useTransition } from "react" import { useTransition } from "react"
import { useLocale, useTranslations } from "next-intl" import { useLocale, useTranslations } from "next-intl"
import { Check, Globe } from "lucide-react" import { Check, Globe } from "lucide-react"
import { toast } from "sonner" import { notify } from "@/shared/lib/notify"
import { Button } from "@/shared/components/ui/button" import { Button } from "@/shared/components/ui/button"
import { import {
@@ -35,9 +35,9 @@ export function LocaleSwitcher({ compact = false }: { compact?: boolean }) {
startTransition(async () => { startTransition(async () => {
const result = await setLocaleAction(next) const result = await setLocaleAction(next)
if (result.success) { if (result.success) {
toast.success(t("switch")) notify.success(t("switch"))
} else { } else {
toast.error(t("switch")) notify.error(t("switch"))
} }
}) })
} }

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import { useTransition } from "react" import { useTransition } from "react"
import { toast } from "sonner" import { notify } from "@/shared/lib/notify"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
export function useActionWithToast<T>() { export function useActionWithToast<T>() {
@@ -11,9 +11,9 @@ export function useActionWithToast<T>() {
startTransition(async () => { startTransition(async () => {
const result = await action() const result = await action()
if (result.success) { if (result.success) {
toast.success(result.message || "操作成功") notify.success(result.message || "操作成功")
} else { } else {
toast.error(result.message || "操作失败") notify.error(result.message || "操作失败")
} }
}) })
} }

View File

@@ -173,7 +173,11 @@
}, },
"audit": { "audit": {
"read": "Read Audit Logs", "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": { "announcement": {
"manage": "Manage Announcements", "manage": "Manage Announcements",

View File

@@ -173,7 +173,11 @@
}, },
"audit": { "audit": {
"read": "查看审计日志", "read": "查看审计日志",
"readDesc": "允许查看系统审计日志" "readDesc": "允许查看系统审计日志",
"purge": "清理审计日志",
"purgeDesc": "物理删除过期审计日志(破坏性操作,仅管理员)",
"retentionManage": "管理审计保留策略",
"retentionManageDesc": "配置审计日志保留天数与自动清理规则"
}, },
"announcement": { "announcement": {
"manage": "管理公告", "manage": "管理公告",

View File

@@ -91,6 +91,8 @@ export const PERMISSION_BITMAP_ORDER: readonly Permission[] = [
Permissions.SETTINGS_ADMIN, Permissions.SETTINGS_ADMIN,
// Audit // Audit
Permissions.AUDIT_LOG_READ, Permissions.AUDIT_LOG_READ,
Permissions.AUDIT_LOG_PURGE,
Permissions.AUDIT_RETENTION_MANAGE,
// Announcement // Announcement
Permissions.ANNOUNCEMENT_MANAGE, Permissions.ANNOUNCEMENT_MANAGE,
Permissions.ANNOUNCEMENT_READ, Permissions.ANNOUNCEMENT_READ,

View File

@@ -48,6 +48,8 @@ export const ROLE_PERMISSIONS_SEED: Record<BuiltinRole, Permission[]> = {
Permissions.AI_CONFIGURE, Permissions.AI_CONFIGURE,
Permissions.SETTINGS_ADMIN, Permissions.SETTINGS_ADMIN,
Permissions.AUDIT_LOG_READ, Permissions.AUDIT_LOG_READ,
Permissions.AUDIT_LOG_PURGE,
Permissions.AUDIT_RETENTION_MANAGE,
Permissions.ANNOUNCEMENT_MANAGE, Permissions.ANNOUNCEMENT_MANAGE,
Permissions.ANNOUNCEMENT_READ, Permissions.ANNOUNCEMENT_READ,
Permissions.GRADE_RECORD_MANAGE, Permissions.GRADE_RECORD_MANAGE,

View File

@@ -15,7 +15,7 @@ import type { ActionState } from "@/shared/types/action-state"
* ```tsx * ```tsx
* const t = useTranslations("attendance") * const t = useTranslations("attendance")
* const msg = resolveActionError(result, t, t("errors.unexpected")) * const msg = resolveActionError(result, t, t("errors.unexpected"))
* toast.error(msg) * notify.error(msg)
* ``` * ```
*/ */
export function resolveActionError<T>( export function resolveActionError<T>(

View File

@@ -110,6 +110,10 @@ export const Permissions = {
// Audit // Audit
AUDIT_LOG_READ: "audit_log:read", AUDIT_LOG_READ: "audit_log:read",
/** 审计日志清理(物理删除)—— 破坏性操作,仅 admin */
AUDIT_LOG_PURGE: "audit_log:purge",
/** 审计日志保留策略管理(配置保留天数、自动清理规则) */
AUDIT_RETENTION_MANAGE: "audit_retention:manage",
// Announcement // Announcement
ANNOUNCEMENT_MANAGE: "announcement:manage", ANNOUNCEMENT_MANAGE: "announcement:manage",
@@ -210,6 +214,17 @@ export const Permissions = {
export type Permission = (typeof Permissions)[keyof typeof 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. * Data scope for row-level security.
* *