DictFields 字典表单字段
基于字典代码自动加载选项数据的表单字段组件族,包含 DictSelectField、DictRadioField 和 DictCheckboxField 三种组件。
特性
- ✅ 基于 TanStack Query 实现全局缓存,相同字典代码只请求一次
- ✅ 支持在 DictionaryProvider 中配置全局 queryFn
- ✅ 支持字段级别覆盖 queryFn
- ✅ 自动显示加载状态和空数据提示
- ✅ 支持字段映射、过滤和排序
- ✅ 支持禁用选项
- ✅ 完整的 TypeScript 类型支持
- ✅ 与 @tanstack/react-form 无缝集成
导入
import { DictionaryProvider, Form } from '@xcloud/ui-core'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'示例
基础用法
1. 配置 DictionaryProvider
首先,在表单外层包裹 QueryClientProvider 和 DictionaryProvider 并提供全局 queryFn:
import { DictionaryProvider } from '@xcloud/ui-core'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
// 创建 QueryClient 实例
const queryClient = new QueryClient()
// 定义字典查询函数
const fetchDictionary = async (dictCode: string) => {
const response = await fetch(`/api/dict/${dictCode}`)
const data = await response.json()
return {
data, // 字典项数组
total: data.length
}
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<DictionaryProvider value={{ queryFn: fetchDictionary }}>
<MyForm />
</DictionaryProvider>
</QueryClientProvider>
)
}2. 使用字典表单字段
在 DictionaryProvider 内部使用字典表单字段组件:
import { DictionaryProvider, Form } from '@xcloud/ui-core'
function MyForm() {
const form = Form.useAppForm({
defaultValues: {
gender: '',
hobbies: []
}
})
return (
<form onSubmit={form.handleSubmit}>
{/* 下拉选择 */}
<form.AppField name="gender">
{(field) => (
<field.DictSelectField
dictCode="gender"
label="性别"
required
/>
)}
</form.AppField>
{/* 多选框 */}
<form.AppField name="hobbies">
{(field) => (
<field.DictCheckboxField
dictCode="hobby"
label="兴趣爱好"
/>
)}
</form.AppField>
</form>
)
}组件类型
DictSelectField - 下拉选择
基于字典的下拉选择框,适合选项较多的场景:
<form.AppField name="city">
{(field) => (
<field.DictSelectField
dictCode="city"
label="城市"
placeholder="请选择城市"
hint="选择您所在的城市"
required
/>
)}
</form.AppField>DictRadioField - 单选框
基于字典的单选框组,适合选项较少且需要全部可见的场景:
<form.AppField name="gender">
{(field) => (
<field.DictRadioField
dictCode="gender"
label="性别"
hint="请选择您的性别"
required
/>
)}
</form.AppField>DictCheckboxField - 多选框
基于字典的复选框组,用于多选场景:
<form.AppField name="hobbies">
{(field) => (
<field.DictCheckboxField
dictCode="hobby"
label="兴趣爱好"
hint="选择您的兴趣爱好(可多选)"
/>
)}
</form.AppField>与 @xcloud/request 集成
推荐使用 @xcloud/request 包来管理字典请求,它提供了统一的请求配置、拦截器和错误处理。
快速开始
1. 设置 RequestProvider
在应用根部提供 RequestProvider:
// App.tsx
import { RequestProvider } from '@xcloud/request'
import { requestClient } from '@/config/request'
function App() {
return (
<RequestProvider client={requestClient}>
<YourApp />
</RequestProvider>
)
}2. 使用 Hook 版本适配器(推荐)
使用 useDictionaryAdapter 自动从 RequestProvider 获取 client:
import { useDictionaryAdapter, DictSelectField } from '@xcloud/ui-core'
function UserForm() {
// 自动从 RequestProvider 获取 client
const queryFn = useDictionaryAdapter({
url: '/api/dict/{dictCode}',
})
return (
<form.Provider>
<form.AppField name="status">
{(field) => (
<field.DictSelectField
dictCode="user_status"
queryFn={queryFn}
label="用户状态"
/>
)}
</form.AppField>
</form.Provider>
)
}集成方式
方式 1: Hook 版本(推荐)
自动从 RequestProvider 获取 client,无需手动传递:
import { useDictionaryAdapter } from '@xcloud/ui-core'
function MyForm() {
const queryFn = useDictionaryAdapter({
url: '/api/dict/{dictCode}',
method: 'GET', // 可选,默认 GET
})
return (
<form.AppField name="role">
{(field) => (
<field.DictSelectField
dictCode="user_role"
queryFn={queryFn}
label="角色"
/>
)}
</form.AppField>
)
}方式 2: 工厂函数版本
手动创建 client 和适配器:
import { createClient } from '@xcloud/request'
import { createDictionaryAdapter } from '@xcloud/ui-core'
const client = createClient({ baseURL: '/api' })
const queryFn = createDictionaryAdapter({
url: '/dict/{dictCode}',
client,
})
function MyForm() {
return (
<form.AppField name="status">
{(field) => (
<field.DictSelectField
dictCode="user_status"
queryFn={queryFn}
label="状态"
/>
)}
</form.AppField>
)
}高级配置
自定义响应转换
如果后端返回格式与标准格式不同,可以自定义转换:
const queryFn = useDictionaryAdapter({
url: '/api/dict/{dictCode}',
transformResponse: (response) => ({
data: response.items.map(item => ({
value: item.id,
label: item.name,
disabled: !item.enabled,
})),
total: response.count,
}),
})POST 方法
const queryFn = useDictionaryAdapter({
url: '/api/dict/query',
method: 'POST',
params: {
category: 'system',
status: 'active',
},
})批量获取字典
import { useBatchDictionaryAdapter } from '@xcloud/ui-core'
function MyForm() {
const batchQueryFn = useBatchDictionaryAdapter({
url: '/api/dict/batch',
})
React.useEffect(() => {
async function loadDicts() {
const dictionaries = await batchQueryFn(['user_status', 'user_role'])
// dictionaries = {
// user_status: { data: [...], total: 10 },
// user_role: { data: [...], total: 5 }
// }
}
loadDicts()
}, [])
}创建可复用的适配器
在 API 层统一配置字典适配器:
// api/dictionaries.ts
import { useDictionaryAdapter } from '@xcloud/ui-core'
export function useDictApi() {
return useDictionaryAdapter({
url: '/api/system/dict/data/type/{dictCode}',
method: 'GET',
})
}
// 在组件中使用
import { useDictApi } from '@/api/dictionaries'
function UserForm() {
const queryFn = useDictApi()
return (
<form.AppField name="status">
{(field) => (
<field.DictSelectField
dictCode="user_status"
queryFn={queryFn}
label="状态"
/>
)}
</form.AppField>
)
}支持的响应格式
适配器自动识别以下后端响应格式:
// 格式 1: 直接返回数组
[{ value: '1', label: '选项1' }]
// 格式 2: 包含 data 字段
{ data: [...], total: 10 }
// 格式 3: 包含 list 字段
{ list: [...], total: 10 }
// 格式 4: 嵌套 data.data
{ data: { data: [...], total: 10 } }所有格式都会自动转换为标准的 DictResponse 格式。
高级用法
字段级别覆盖 queryFn
可以在字段级别覆盖全局 queryFn:
const fetchSpecialDictionary = async (dictCode: string) => {
// 自定义查询逻辑
return { data: [...], total: 0 }
}
<form.AppField name="special">
{(field) => (
<field.DictSelectField
dictCode="special"
queryFn={fetchSpecialDictionary}
label="特殊字典"
/>
)}
</form.AppField>字段映射
当后端返回的字段名与组件期望的不同时,使用 fieldNames 进行映射:
<form.AppField name="status">
{(field) => (
<field.DictSelectField
dictCode="status"
fieldNames={{
value: 'id', // 将 id 映射为 value
label: 'name', // 将 name 映射为 label
disabled: 'inactive' // 将 inactive 映射为 disabled
}}
label="状态"
/>
)}
</form.AppField>过滤和排序
使用 filter 和 sort 对选项进行过滤和排序:
<form.AppField name="user">
{(field) => (
<field.DictSelectField
dictCode="user"
// 只显示激活的用户
filter={(item) => item.active === true}
// 按名称排序
sort={(a, b) => a.name.localeCompare(b.name)}
label="用户"
/>
)}
</form.AppField>自定义缓存时间
通过 staleTime 自定义缓存时间(毫秒):
<form.AppField name="config">
{(field) => (
<field.DictSelectField
dictCode="config"
staleTime={10 * 60 * 1000} // 10分钟
label="配置项"
/>
)}
</form.AppField>自定义加载和空数据提示
<form.AppField name="options">
{(field) => (
<field.DictSelectField
dictCode="options"
showLoading={true}
loadingText="数据加载中..."
emptyText="暂无可选项"
label="选项"
/>
)}
</form.AppField>字典数据格式
标准格式
字典查询函数应返回以下格式的数据:
interface DictResponse<T = any> {
data: T[] // 字典项数组
total?: number // 总数(可选)
}
interface DictItem {
value: string | number // 选项值
label: string // 显示文本
disabled?: boolean // 是否禁用
[key: string]: any // 扩展字段
}示例数据
{
data: [
{ value: 'male', label: '男' },
{ value: 'female', label: '女' },
{ value: 'other', label: '其他', disabled: true }
],
total: 3
}组件 API
DictSelectField Props
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
dictCode | string | - | 必填。字典代码 |
queryFn | (dictCode: string) => Promise<DictResponse> | - | 查询函数,未提供时使用 Provider 中的配置 |
label | string | - | 表单标签 |
hint | string | - | 提示信息 |
hintIcon | React.ComponentType | - | 提示图标组件 |
errorIcon | React.ComponentType | - | 错误图标组件 |
required | boolean | false | 是否必填 |
disabled | boolean | false | 是否禁用 |
placeholder | string | '请选择' | 占位符 |
className | string | - | 自定义样式类名 |
showLoading | boolean | true | 是否显示加载状态 |
loadingText | string | '加载中...' | 加载文本 |
emptyText | string | '暂无数据' | 空数据文本 |
fieldNames | FieldNames | - | 字段名称映射 |
filter | (item: T) => boolean | - | 过滤函数 |
sort | (a: T, b: T) => number | - | 排序函数 |
enabled | boolean | true | 是否启用查询 |
staleTime | number | 300000 | 缓存时间(毫秒) |
DictRadioField Props
继承 DictSelectField 的所有属性,但不包括 placeholder。
DictCheckboxField Props
继承 DictSelectField 的所有属性,但不包括 placeholder。
注意:DictCheckboxField 的字段值类型为 Array<string | number>。
FieldNames
interface FieldNames {
value?: string // 值字段名,默认 'value'
label?: string // 标签字段名,默认 'label'
disabled?: string // 禁用字段名,默认 'disabled'
}DictionaryConfig
interface DictionaryConfig {
queryFn?: (dictCode: string) => Promise<DictResponse>
staleTime?: number // 默认 300000 (5分钟)
}最佳实践
1. 配置 QueryClient 和 Provider
推荐在应用根部配置 QueryClientProvider 和 DictionaryProvider,让所有字典字段共享配置:
// App.tsx
import { DictionaryProvider } from '@xcloud/ui-core'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fetchDictionary } from './api/dictionary'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟
refetchOnWindowFocus: false,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<DictionaryProvider value={{ queryFn: fetchDictionary }}>
<Router />
</DictionaryProvider>
</QueryClientProvider>
)
}2. 统一字典查询接口
将字典查询逻辑封装到统一的 API 函数中:
// api/dictionary.ts
export async function fetchDictionary(dictCode: string) {
const response = await fetch(`/api/v1/dictionaries/${dictCode}`)
if (!response.ok) {
throw new Error('Failed to fetch dictionary')
}
const data = await response.json()
return {
data: data.items,
total: data.total
}
}3. 类型安全
为字典项定义具体类型以获得更好的类型推断:
interface GenderDictItem {
value: 'male' | 'female' | 'other'
label: string
}
<form.AppField name="gender">
{(field) => (
<field.DictSelectField<GenderDictItem>
dictCode="gender"
label="性别"
/>
)}
</form.AppField>4. 错误处理
在 queryFn 中添加适当的错误处理:
const fetchDictionary = async (dictCode: string) => {
try {
const response = await fetch(`/api/dict/${dictCode}`)
if (!response.ok) throw new Error('Network error')
return await response.json()
} catch (error) {
console.error('Dictionary fetch failed:', error)
return { data: [], total: 0 }
}
}5. 预加载常用字典
对于频繁使用的字典,可以在应用启动时预加载:
import { useQuery } from '@tanstack/react-query'
function App() {
// 预加载常用字典
useQuery({
queryKey: ['dictionary', 'gender'],
queryFn: () => fetchDictionary('gender'),
staleTime: 5 * 60 * 1000
})
return <YourApp />
}缓存机制
组件使用 TanStack Query 实现全局缓存:
- 自动去重:相同
dictCode的多个字段只会发起一次请求 - 缓存时间:默认缓存 5 分钟,可通过
staleTime自定义 - 缓存键:
['dictionary', dictCode] - 自动更新:缓存过期后自动重新请求
常见问题
如何刷新字典数据?
使用 useDictionary hook 返回的 refetch 方法:
const { refetch } = useDictionary({
dictCode: 'status',
queryFn: fetchDictionary
})
<button onClick={() => refetch()}>刷新</button>如何禁用某些选项?
在字典数据中设置 disabled: true:
{
data: [
{ value: '1', label: '选项1' },
{ value: '2', label: '选项2', disabled: true }
]
}如何处理大量选项?
对于选项很多的场景,推荐使用 DictSelectField 配合搜索功能(需要自定义实现)。
字典数据为空时如何处理?
组件会自动显示空数据提示,可通过 emptyText 自定义提示文本。