From c4d3433cc95c649168298178dfc8baa4bf673a32 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:38:14 +0800 Subject: [PATCH] feat(shared): add UI components, hooks, form fields, and action utils - Add UI components: confirm-delete-dialog, empty-table-row, list-pagination, pagination, status-badge - Add form-fields directory for reusable form field components - Add hooks: use-action-mutation, use-action-query for server action integration - Add action-utils lib for action state helpers - Update a11y components, charts, global-search, onboarding-gate, question components - Update UI components: chip-nav, filter-bar, page-header, stat-card, stat-item, switch, table - Update hooks: use-action-with-toast, use-aria-live, use-debounce, use-local-storage, use-media-query, use-permission - Update lib: a11y, ai, audit-logger, auth-guard, bcrypt-utils, change-logger, download, excel, file-storage, http-utils, login-logger, password-policy, password-security-service, permissions, rate-limit, role-utils, search-params, session, storage-provider - Update types: action-state, permissions - Update i18n messages (en, zh-CN) for dashboard, diagnostic, grades, lesson-preparation, settings --- .../components/charts/chart-card-shell.tsx | 14 +- .../components/charts/simple-bar-chart.tsx | 4 + .../components/form-fields/select-field.tsx | 139 +++++++++++ .../components/form-fields/text-field.tsx | 117 +++++++++ .../components/form-fields/textarea-field.tsx | 97 ++++++++ .../components/ui/confirm-delete-dialog.tsx | 84 +++++++ src/shared/components/ui/empty-table-row.tsx | 37 +++ src/shared/components/ui/list-pagination.tsx | 138 +++++++++++ src/shared/components/ui/pagination.tsx | 81 +++++++ src/shared/components/ui/status-badge.tsx | 71 ++++++ src/shared/hooks/use-action-mutation.ts | 74 ++++++ src/shared/hooks/use-action-query.ts | 84 +++++++ src/shared/i18n/messages/en/dashboard.json | 8 +- src/shared/i18n/messages/en/diagnostic.json | 104 +++++++- src/shared/i18n/messages/en/grades.json | 225 +++++++++++++++++- .../i18n/messages/en/lesson-preparation.json | 33 ++- src/shared/i18n/messages/en/settings.json | 74 +++++- src/shared/i18n/messages/zh-CN/dashboard.json | 8 +- .../i18n/messages/zh-CN/diagnostic.json | 104 +++++++- src/shared/i18n/messages/zh-CN/grades.json | 225 +++++++++++++++++- .../messages/zh-CN/lesson-preparation.json | 33 ++- src/shared/i18n/messages/zh-CN/settings.json | 74 +++++- src/shared/lib/action-utils.ts | 171 +++++++++++++ src/shared/lib/permissions.ts | 7 + src/shared/types/permissions.ts | 8 + 25 files changed, 1986 insertions(+), 28 deletions(-) create mode 100644 src/shared/components/form-fields/select-field.tsx create mode 100644 src/shared/components/form-fields/text-field.tsx create mode 100644 src/shared/components/form-fields/textarea-field.tsx create mode 100644 src/shared/components/ui/confirm-delete-dialog.tsx create mode 100644 src/shared/components/ui/empty-table-row.tsx create mode 100644 src/shared/components/ui/list-pagination.tsx create mode 100644 src/shared/components/ui/pagination.tsx create mode 100644 src/shared/components/ui/status-badge.tsx create mode 100644 src/shared/hooks/use-action-mutation.ts create mode 100644 src/shared/hooks/use-action-query.ts create mode 100644 src/shared/lib/action-utils.ts diff --git a/src/shared/components/charts/chart-card-shell.tsx b/src/shared/components/charts/chart-card-shell.tsx index 6f404a8..0695655 100644 --- a/src/shared/components/charts/chart-card-shell.tsx +++ b/src/shared/components/charts/chart-card-shell.tsx @@ -46,6 +46,8 @@ interface ChartCardShellProps { className?: string /** CardContent 额外类名 */ contentClassName?: string + /** 卡片头部右侧操作区(如"查看全部"链接) */ + action?: ReactNode } export function ChartCardShell({ @@ -62,15 +64,19 @@ export function ChartCardShell({ children, className, contentClassName, + action, }: ChartCardShellProps) { const EmptyIcon = emptyIcon ?? Icon return ( - - {Icon ? : null} - {title} - +
+ + {Icon ? : null} + {title} + + {action} +
{description ? {description} : null}
diff --git a/src/shared/components/charts/simple-bar-chart.tsx b/src/shared/components/charts/simple-bar-chart.tsx index 230e6e7..7533c0c 100644 --- a/src/shared/components/charts/simple-bar-chart.tsx +++ b/src/shared/components/charts/simple-bar-chart.tsx @@ -66,6 +66,8 @@ interface SimpleBarChartProps { tooltipFormatter?: (payload: unknown) => ReactNode /** 按数据项着色的映射(key = xKey 值, value = 颜色);用于单 Bar 分桶着色 */ cellColors?: Record + /** 自定义 SVG defs(如 patterns、gradients),渲染在 BarChart 内部 */ + defs?: ReactNode /** 容器额外类名 */ className?: string } @@ -93,6 +95,7 @@ export function SimpleBarChart({ tooltipClassName = "w-[200px]", tooltipFormatter, cellColors, + defs, className, }: SimpleBarChartProps) { const chartConfig: ChartConfig = {} @@ -115,6 +118,7 @@ export function SimpleBarChart({ return ( + {defs} = FieldPath +> { + /** react-hook-form control 实例 */ + control: Control + /** 字段路径 */ + name: TName + /** 标签文本 */ + label: string + /** 占位符 */ + placeholder?: string + /** 描述文本 */ + description?: React.ReactNode + /** 选项列表 */ + options: SelectOption[] + /** 是否禁用 */ + disabled?: boolean + /** FormItem 的 className */ + itemClassName?: string + /** FormLabel 右侧的额外节点(如"新建"按钮) */ + labelSlot?: React.ReactNode + /** + * 自定义 value 转换:从 field.value → Select 的 value(string)。 + * 常用于 number 字段:`(v) => String(v)` + */ + toSelectValue?: (value: unknown) => string + /** + * 自定义 onChange 转换:从 Select 的 string value → field.onChange 的值。 + * 常用于 number 字段:`(val) => parseInt(val, 10)` + */ + fromSelectValue?: (val: string) => unknown +} + +/** + * 通用选择字段组件,封装 FormField + FormItem + FormLabel + Select + SelectContent + SelectItem + FormMessage。 + * + * 用于替代各表单中重复的 ` field.onChange(fromSelectValue(val)) : field.onChange} + disabled={disabled} + > + + + + + + + {options.map((opt) => ( + + {opt.label} + + ))} + + + {description ? {description} : null} + + + )} + /> + ) +} diff --git a/src/shared/components/form-fields/text-field.tsx b/src/shared/components/form-fields/text-field.tsx new file mode 100644 index 0000000..91fa044 --- /dev/null +++ b/src/shared/components/form-fields/text-field.tsx @@ -0,0 +1,117 @@ +"use client" + +import * as React from "react" +import { + type Control, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/shared/components/ui/form" +import { Input } from "@/shared/components/ui/input" + +export interface TextFieldProps< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> { + /** react-hook-form control 实例 */ + control: Control + /** 字段路径 */ + name: TName + /** 标签文本 */ + label: string + /** 占位符 */ + placeholder?: string + /** 描述文本(显示在标签下) */ + description?: React.ReactNode + /** Input 类型,默认 "text" */ + type?: "text" | "number" | "password" | "email" | "datetime-local" | "tel" | "url" + /** 是否禁用 */ + disabled?: boolean + /** FormItem 的 className(用于布局,如 "col-span-2") */ + itemClassName?: string + /** Input 的额外 className */ + inputClassName?: string + /** 透传到 Input 的其他 props(如 min/max/step) */ + inputProps?: Omit< + React.InputHTMLAttributes, + "name" | "value" | "onChange" | "onBlur" | "disabled" | "type" | "placeholder" | "className" + > + /** + * 自定义 value 转换:从 field.value → input value。 + * 常用于 number 字段处理 null/undefined → ""。 + */ + toInputValue?: (value: unknown) => string | number | readonly string[] + /** + * 自定义 onChange 转换:从 input event → field.onChange 的值。 + * 常用于 number 字段处理空字符串 → null。 + */ + fromInputValue?: (e: React.ChangeEvent) => unknown +} + +/** + * 通用文本字段组件,封装 FormField + FormItem + FormLabel + FormControl + Input + FormMessage。 + * + * 用于替代各表单中重复的 ` }>` 模式。 + * + * @example + * + * + * @example 数值字段 + * + */ +export function TextField< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + control, + name, + label, + placeholder, + description, + type = "text", + disabled, + itemClassName, + inputClassName, + inputProps, + toInputValue, + fromInputValue, +}: TextFieldProps) { + return ( + ( + + {label} + + + + {description ? {description} : null} + + + )} + /> + ) +} diff --git a/src/shared/components/form-fields/textarea-field.tsx b/src/shared/components/form-fields/textarea-field.tsx new file mode 100644 index 0000000..d2d8ab6 --- /dev/null +++ b/src/shared/components/form-fields/textarea-field.tsx @@ -0,0 +1,97 @@ +"use client" + +import * as React from "react" +import { + type Control, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/shared/components/ui/form" +import { Textarea } from "@/shared/components/ui/textarea" + +export interface TextareaFieldProps< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> { + /** react-hook-form control 实例 */ + control: Control + /** 字段路径 */ + name: TName + /** 标签文本 */ + label: string + /** 占位符 */ + placeholder?: string + /** 描述文本 */ + description?: React.ReactNode + /** 是否禁用 */ + disabled?: boolean + /** FormItem 的 className */ + itemClassName?: string + /** Textarea 的额外 className(如 "min-h-[200px]") */ + textareaClassName?: string + /** 透传到 Textarea 的其他 props(如 rows、maxLength) */ + textareaProps?: Omit< + React.TextareaHTMLAttributes, + "name" | "value" | "onChange" | "onBlur" | "disabled" | "placeholder" | "className" + > +} + +/** + * 通用多行文本字段组件,封装 FormField + FormItem + FormLabel + FormControl + Textarea + FormMessage。 + * + * 用于替代各表单中重复的 `