feat(shared,tests): add error boundaries, lib utils, i18n messages, and integration tests
Some checks failed
CI / scheduled-backup (push) Has been skipped
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled

shared:

- Add class-filter, error-state, route-error, section-error-boundary, widget-boundary components

- Add ui/alert component

- Add constants directory

- Add breached-password, export-utils, permission-bitmap, rate-limit, resolve-action-error, route-permissions, route-resolver, type-guards lib

- Add i18n messages (en, zh-CN) for invitation-codes, parent, questions, rbac

tests:

- Add integration tests for elective

- Add tests/setup/empty-stub

scripts:

- Add update-md.cjs, tmp_append_en.ps1, tmp_merge_en.ps1 utilities
This commit is contained in:
SpecialX
2026-07-03 10:26:38 +08:00
parent 21142f9b99
commit 0e63c24ed9
97 changed files with 9753 additions and 819 deletions

View File

@@ -0,0 +1,148 @@
"use client"
import type { JSX } from "react"
import { Component, type ErrorInfo, type ReactNode } from "react"
import { useTranslations } from "next-intl"
import { AlertCircle, RefreshCw } from "lucide-react"
import { Button } from "@/shared/components/ui/button"
interface BoundaryProps {
children: ReactNode
/** 自定义降级 UI函数形式优先级最高。若提供则忽略 title/description/retryLabel */
fallback?: (error: Error, reset: () => void) => ReactNode
/** 渲染失败时的标题(已国际化,默认取 {namespace}.error.boundaryTitle */
title?: string
/** 渲染失败时的描述(已国际化,默认取 {namespace}.error.boundaryDescription */
description?: string
/** 重试按钮文案(已国际化,默认取 {namespace}.error.retry */
retryLabel?: string
/** 错误回调(用于埋点/监控) */
onError?: (error: Error, info: ErrorInfo) => void
}
interface State {
hasError: boolean
error: Error | null
}
/**
* 组件级 Error Boundary类组件实现承载错误捕获能力
* 不直接使用,请使用默认导出的函数式 `SectionErrorBoundary` 包装器以获得 i18n 支持。
*
* 支持两种降级形式:
* 1. `fallback`(函数 `(error, reset) => ReactNode`)— 完全自定义降级 UI优先级最高
* 2. `title` / `description` / `retryLabel` — 默认降级 UI + i18n 文案
*/
class SectionErrorBoundaryBase extends Component<BoundaryProps, State> {
constructor(props: BoundaryProps) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.error("[SectionErrorBoundary]", error, errorInfo)
this.props.onError?.(error, errorInfo)
}
private handleRetry = (): void => {
this.setState({ hasError: false, error: null })
}
render(): ReactNode {
if (this.state.hasError && this.state.error) {
// 优先使用自定义 fallback
if (this.props.fallback) {
return this.props.fallback(this.state.error, this.handleRetry)
}
// 默认降级 UI
return (
<div
role="alert"
aria-live="assertive"
className="flex flex-col items-center justify-center space-y-3 rounded-lg border border-dashed p-8 text-center"
>
<AlertCircle className="h-8 w-8 text-muted-foreground" aria-hidden="true" />
<div className="space-y-1">
<p className="text-sm font-medium">
{this.props.title ?? "加载失败"}
</p>
<p className="text-xs text-muted-foreground">
{this.props.description ?? "数据加载时发生错误,请重试。"}
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={this.handleRetry}
aria-label={this.props.retryLabel ?? "重试"}
>
<RefreshCw className="mr-1.5 h-3.5 w-3.5" aria-hidden="true" />
{this.props.retryLabel ?? "重试"}
</Button>
</div>
)
}
return this.props.children
}
}
interface SectionErrorBoundaryProps {
children: ReactNode
/** i18n namespace默认 "common"。需保证该 namespace 下存在 error.boundaryTitle / error.boundaryDescription / error.retry 键 */
namespace?: string
/** 自定义降级 UI函数形式优先级最高。若提供则忽略 title/description/retryLabel */
fallback?: (error: Error, reset: () => void) => ReactNode
/** 显式覆盖标题(优先级高于 i18n 默认) */
title?: string
/** 显式覆盖描述 */
description?: string
/** 显式覆盖重试按钮文案 */
retryLabel?: string
/** 错误回调(用于埋点/监控) */
onError?: (error: Error, info: ErrorInfo) => void
}
/**
* 组件级 Error Boundary包裹独立数据区块防止单区块错误导致整页崩溃。
* 自动使用 next-intl 提供本地化文案;如需完全自定义可传入 `fallback` 函数。
*
* @example
* ```tsx
* // 默认 i18n 文案
* <SectionErrorBoundary namespace="coursePlans">
* <CoursePlanProgress plan={plan} />
* </SectionErrorBoundary>
*
* // 完全自定义降级 UI
* <SectionErrorBoundary fallback={(error, reset) => <CustomUI error={error} onRetry={reset} />}>
* <CoursePlanProgress plan={plan} />
* </SectionErrorBoundary>
* ```
*/
export function SectionErrorBoundary({
children,
namespace = "common",
fallback,
title,
description,
retryLabel,
onError,
}: SectionErrorBoundaryProps): JSX.Element {
const t = useTranslations(namespace)
return (
<SectionErrorBoundaryBase
fallback={fallback}
title={title ?? t("error.boundaryTitle")}
description={description ?? t("error.boundaryDescription")}
retryLabel={retryLabel ?? t("error.retry")}
onError={onError}
>
{children}
</SectionErrorBoundaryBase>
)
}