21 KiB
FF 组件库
基于 React 的企业级组件库,提供完整的页面开发解决方案。
目录
核心模块
主入口 - ff
详细文档: ff-core/README.md
import ff, { http, cache, configure, func, route } from 'ff'
import { AppContext, AppGlobalParamsContext } from 'ff'
ff (default)
核心应用实例,管理用户认证、环境初始化、组件加载。
主要方法:
// 注册组件供应商
ff.setVendor(key, vendor) // => ff
// 动态加载组件
ff.getWidgetComponent(widgetPath) // => Promise<Component>
// 示例: ff.getWidgetComponent('@pkg/components/Button')
// 获取路由配置
ff.getRoutes() // => Promise<Map>
// 获取菜单配置
ff.getMenus() // => Promise<Array>
// 获取应用配置
ff.getConfigure() // => Promise<Object>
// 获取权限配置
ff.getWidgetOperationAuth() // => Promise<Array>
// 用户登录
ff.signin(username, password) // => Promise
// 用户登出
ff.signout() // => Promise
// 获取当前用户信息
ff.getUser() // => Object | null
// 初始化应用
ff.init(params) // => Promise
http
HTTP 请求封装,支持缓存、去重、Base62 编解码。
HttpResponse 类型:
HttpResponse 实现了 Promise A+ 规范,支持链式调用。
请求失败时,如果没有主动处理错误(即未使用
try-catch包裹,也未调用.msg()或.catch()方法),系统会通过unhandledrejection机制自动弹窗显示错误消息。
interface HttpResponse extends Promise<any> {
code: number // 0 或 1 表示成功,其他表示失败
message: string // 响应消息
data: any // 响应数据
url: string // 请求 URL
res?: string // 数据资源 UUID (用于 useSubscribeRequest 订阅更新)
// Promise A+ 方法
then(onFulfilled, onRejected): Promise<any>
catch(onRejected): Promise<any>
// 扩展方法
resp(callback): Promise<any> // 接收完整响应对象
msg(callback, isResp?): Promise<any> // 弹窗显示消息后执行回调, isResp = true,回调接收完整响应对象
}
API 方法:
// 基础请求 - 返回 HttpResponse 对象
http.request(config) // => HttpResponse
http.get(url, config) // => HttpResponse
http.post(url, data, config) // => HttpResponse
http.put(url, data, config) // => HttpResponse
http.delete(url, config) // => HttpResponse
// 列表请求 (自动处理 Base62 编码)
http.list(listCode, params) // => HttpResponse
// Base62 编解码
http.encode(data) // => string
http.decode(str) // => Object
// 缓存控制
http.cache(url, params) // => HttpResponse (永久缓存)
http.refreshCache(isSystem) // 清除缓存
使用示例:
// 1. 基础请求 - then() 自动解析为 data
const users = await http.get('/api/users')
// users 是 response.data
// 2. 获取完整响应对象 - 使用 .resp()
const response = await http.get('/api/users').resp(resp => {
console.log('状态码:', resp.code) // 0 或 1
console.log('消息:', resp.message) // '操作成功'
console.log('资源UUID:', resp.res) // 用于订阅
console.log('URL:', resp.url) // '/api/users'
return resp.data
})
// 3. 成功后弹窗提示 - 使用 .msg()
await http.post('/api/users', userData).msg(() => {
console.log('用户创建成功,消息已弹窗显示')
})
// 4. 错误处理
try {
const data = await http.get('/api/users')
} catch (error) {
console.error('请求失败:', error)
}
cache
缓存管理器。
// 设置缓存
cache.set(key, value, ttl) // ttl: 过期时间(秒)
// 获取缓存
cache.get(key) // => value | null
// 删除缓存
cache.delete(key)
// 清空所有缓存
cache.clear()
// 检查缓存是否存在
cache.has(key) // => boolean
func
函数执行器,支持 Web Worker 沙箱执行。
// 执行 JavaScript 代码字符串
func.exec(code, params, helpers) // => Promise<result>
// 示例
await func.exec(
'return value * 2',
{ value: 10 },
{ getFieldValue: (name) => form.getFieldValue(name) }
) // => 20
route
路由管理器。
// 路由跳转
route.push(path, params)
route.replace(path, params)
// 获取当前路由
route.getCurrentRoute()
Hooks - ff/hooks
React Hooks 工具集。
import {
useMergedState,
useUpdate,
usePrevious,
useStateWithCallback,
useDeepEqualEffect,
useOptions,
useSubscribeRequest
} from 'ff/hooks'
useMergedState(defaultValue, options)
合并状态管理,结合内部 state 和外部 props。
const [value, setValue] = useMergedState(defaultValue, {
value: props.value,
onChange: props.onChange
})
useUpdate()
强制更新组件。
const forceUpdate = useUpdate()
// 调用 forceUpdate() 强制组件重新渲染
usePrevious(state)
获取上一次的状态值。
const prevValue = usePrevious(value)
useStateWithCallback(initialState)
带回调的状态管理。
const [state, setState] = useStateWithCallback(initialState)
setState(newState, (currentState) => {
console.log('状态已更新:', currentState)
})
useDeepEqualEffect(callback, deps, compare)
深度对比的 useEffect,只在依赖真正变化时执行。
useDeepEqualEffect(() => {
// 只有当 deps 深度对比不相等时才执行
}, [complexObject])
useOptions(options, language, type, form, basicForm)
选项数据处理,支持静态数组或动态 JS 代码。
const options = useOptions(
fieldOptions, // 选项数据或 JS 代码
'javascript', // 'json' | 'javascript'
'string', // 值类型
form, // 表单实例
basicForm // 基础表单实例
)
useSubscribeRequest(param)
WebSocket 订阅请求。
const data = useSubscribeRequest({
uri: '/api/subscribe',
params: { id: 123 }
})
工具函数 - ff/utils
通用工具函数集合。
import {
toPrimitive,
getPrimitiveType,
uuid,
hashJSON,
replaceKeys,
deepSome,
getWidgetPropsData,
getPkgName,
getPkgCategory,
getPkgOwner,
makeUniqueIdGenerator,
hasUrlPrefix,
getUrlWithPrefix
} from 'ff/utils'
toPrimitive(data, type)
类型转换。
toPrimitive('123', 'number') // => 123
toPrimitive('true', 'boolean') // => true
toPrimitive('[1,2]', 'array') // => [1, 2]
toPrimitive('{"a":1}', 'json') // => { a: 1 }
getPrimitiveType(value)
获取原始类型。
getPrimitiveType(123) // => 'number'
getPrimitiveType('text') // => 'string'
getPrimitiveType([1, 2]) // => 'array'
uuid()
生成唯一标识符。
const id = uuid() // => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
hashJSON(obj, bits, algo)
计算 JSON 对象的哈希值。
const hash = hashJSON({ name: 'test' }, 128, 'md5')
replaceKeys(obj, keyMap)
批量替换对象键名。
replaceKeys(
{ oldKey: 'value' },
{ oldKey: 'newKey' }
) // => { newKey: 'value' }
数据转换器 - ff/data-converter
中间件模式的数据转换器。
import DataConverter from 'ff/data-converter'
使用方式:
const converter = new DataConverter([
[middleware1, options1],
[middleware2, options2]
])
// 转换为值
const value = await converter.toValue(rawData, context)
// 转换为渲染内容
const rendered = await converter.toRender(rawData, context, defaultValue)
WebSocket 订阅 - ff/res-ws
详细文档: ff-core/README.md
WebSocket 资源订阅管理器。
import ResWs from 'ff/res-ws'
// 创建订阅
const ws = new ResWs({
uri: '/api/subscribe',
params: { id: 123 },
onMessage: (data) => console.log(data)
})
// 发送消息
ws.send(data)
// 关闭连接
ws.close()
组件模块
按钮 - ff/button
详细文档: ff-button/README.md
import Button, { auth, useButton } from 'ff/button'
组件 Props:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | 'primary' | 'default' | 'danger' |
'default' |
按钮类型 |
| size | 'large' | 'middle' | 'small' |
'middle' |
按钮大小 |
| name | string |
- | 按钮文本 |
| icon | string |
- | 图标名称 |
| loading | boolean |
false |
加载状态 |
| disabled | boolean |
false |
禁用状态 |
| data | any |
- | 传递给点击事件的数据源 |
| widget | string | Component | Function |
- | 组件路径、组件或函数 |
| widgetType | 'destroy' | 'redirect' | 'func' | 'component' | 'grid-layout-form' | 'data-list' |
- | 组件类型 |
| widgetData | object |
- | 组件默认数据 |
| widgetProps | object |
- | 数据结构映射 |
| widgetSetting | object |
- | 组件设置 |
| widgetContainerProps | WidgetContainerProps |
- | 容器配置 |
| tooltip | TooltipConfig |
- | 提示配置 |
| confirm | ConfirmConfig |
- | 确认对话框配置 |
| onBeforeClick | (data) => void |
- | 点击前回调 |
| onAfterClick | (result) => void |
- | 点击后回调 |
TooltipConfig 类型:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string |
- | 提示内容(必填) |
| placement | string |
- | 位置 |
| enabled | boolean | number |
- | 是否启用 |
| getPopupContainer | Function |
- | 自定义容器 |
ConfirmConfig 类型:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string |
- | 确认内容(必填) |
| okText | string |
- | 确定按钮文本 |
| cancelText | string |
- | 取消按钮文本 |
| okType | string |
- | 确定按钮类型 |
| placement | string |
- | 位置 |
| enabled | boolean | number |
- | 是否启用 |
| getPopupContainer | Function |
- | 自定义容器 |
| arrow | boolean | object |
- | 箭头配置 |
WidgetContainerProps 类型:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string |
- | 标题 |
| placement | string |
- | 位置(top/left/right/bottom 等) |
| width | number |
260 |
宽度 |
| height | number |
- | 高度 |
| zIndex | number |
- | 层级 |
| arrow | boolean | object |
{ pointAtCenter: true } |
箭头配置 |
| align | object |
- | 对齐配置 |
| isPopupMountBodyContainer | boolean |
true |
是否挂载到 body |
| getPopupContainer | Function |
- | 自定义容器 |
| classNames | object |
- | 自定义类名(header/body/footer) |
| className | string |
- | 根元素类名 |
组件:
// 默认按钮
<Button type="primary" name="点击" />
// 链接按钮
<Button.Link name="链接" />
// 圆形按钮
<Button.Circle icon="icon-add" />
// 圆角按钮
<Button.Round name="圆角" />
// 虚线按钮
<Button.Dashed name="虚线" />
// 气泡卡片按钮
<Button.Popover content={<div>内容</div>}>
气泡按钮
</Button.Popover>
权限控制:
// 检查权限
auth.check(uuid) // => boolean
// 设置权限
auth.set(uuids) // uuids: Array<string>
Hook:
const buttonProps = useButton(props)
容器 - ff/container
详细文档: ff-container/README.md
import Container, { PageRender, PopupRender, Popup } from 'ff/container'
FFContainer Props:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| className | string |
- | 容器类名 |
| rootClassName | string |
- | 根容器类名 |
| style | object |
{} |
容器样式 |
| title | ReactNode |
- | 标题内容 |
| subTitle | ReactNode |
- | 副标题内容 |
| actions | ReactNode |
- | 操作按钮区域 |
| extras | ReactNode |
- | 扩展区域 |
| children | ReactNode |
- | 主体内容 |
页面容器:
<Container
title="页面标题"
subTitle="副标题"
actions={<Button>操作</Button>}
extras={<div>扩展区</div>}
>
页面内容
</Container>
Popup API:
// 打开模态框
Popup.modal(component, props, containerProps) // => Promise
// 通知消息
Popup.notification(content, options) // => Promise
// 成功提示
Popup.success(content, options) // => Promise
// 错误提示
Popup.error(content, options) // => Promise
// 确认对话框
Popup.confirm(content, options) // => Promise<boolean>
// 表单弹窗
Popup.form(fields, containerProps, formProps) // => Promise<object>
数据列表 - ff/data-list
详细文档: ff-data-list/README.md
import DataList, {
DataListHelper,
DataListFilter,
DataListToolbar,
DataListTable,
DataListFramework
} from 'ff/data-list'
import { useDataListHelper } from 'ff/data-list/utils'
DataList Props:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| listCode | string |
- | 列表资源代码(必填) |
| isItemGridLayout | boolean |
false |
是否使用网格布局 |
| theme | string |
- | 自定义主题组件路径 |
| themeProps | object |
- | 传递给主题组件的属性 |
| total | number |
0 |
数据总条数 |
| page | number |
0 |
当前页码 |
| pageSize | number |
30 |
每页条数 |
| tab | string |
- | 当前选中的 Tab |
| keyword | string |
- | 搜索关键字 |
| condition | object |
- | 筛选条件对象 |
| sider | string |
- | 侧边栏选中项 |
| payload | object |
{} |
额外的请求参数 |
| layouts | object | false |
- | 自定义布局组件配置 |
| classNames | object |
{} |
各部分的类名配置 |
| onReload | function |
- | 重新加载回调 |
| onClickCallback | function |
- | 操作按钮点击回调 |
| onPageChange | function |
- | 页码变化回调 |
| onPageSizeChange | function |
- | 每页条数变化回调 |
| onTabChange | function |
- | Tab 变化回调 |
| onKeywordChange | function |
- | 关键字变化回调 |
| onConditionChange | function |
- | 筛选条件变化回调 |
| onSiderChange | function |
- | 侧边栏变化回调 |
基础使用:
<DataList
listCode="user-list"
theme="@pkg/themes/TableTheme"
/>
使用 Hook:
const helper = useDataListHelper('user-list', {
page: 1,
pageSize: 20,
condition: {}
})
// helper 包含:
// - dataSource: 数据源
// - total: 总数
// - page, pageSize: 分页信息
// - onPageChange: 分页回调
// - onConditionChange: 筛选回调
// - onReload: 刷新方法
网格布局 - ff/grid-layout
详细文档: ff-grid-layout/README.md
import GridLayout, { GridLayoutWidget, GridLayoutFramework } from 'ff/grid-layout'
import { useStructure, useField, useFnRun } from 'ff/grid-layout/utils'
GridLayout Props:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | string |
- | 表单名称 |
| form | FormInstance |
null |
rc-field-form 实例 |
| basicForm | FormInstance |
null |
基础表单实例 |
| style | object |
{} |
容器样式 |
| className | string |
- | 容器类名 |
| cols | number |
12 |
网格列数 |
| rowHeight | number |
21 |
行高(px) |
| containerPadding | [number, number] |
[0, 0] |
容器内边距 [y, x] |
| itemMargin | [number, number] |
[4, 0] |
元素间距 [y, x] |
| formProps | object |
{} |
表单额外属性 |
| formFields | array |
[] |
表单字段配置 |
| fields | array |
[] |
布局字段配置 |
| data | object |
- | 表单数据 |
| theme | string |
- | 主题框架路径 |
| themeProps | object |
{} |
主题框架属性 |
| groups | array |
[{key:'default',label:'默认'}] |
分组配置 |
基础使用:
<GridLayout
cols={12}
rowHeight={21}
fields={fields}
data={data}
/>
使用 Hook:
// 获取远程配置
const structure = useStructure('layout-code')
// 字段值处理
const [displayValue, rawValue] = useField(name, fieldConfig)
// 执行动态函数
const [result, error] = useFnRun(jsCode, initialValue, params, helpers)
网格表单 - ff/grid-layout-form
import GridLayoutForm, { GridLayoutFormHelper } from 'ff/grid-layout-form'
import {
useFormAction,
useFormData,
useFormSubmit,
useRules
} from 'ff/grid-layout-form/utils'
GridLayoutForm Props:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | string |
- | 表单名称 |
| form | FormInstance |
null |
rc-field-form 实例 |
| basicForm | FormInstance |
null |
基础表单实例 |
| style | object |
{} |
自定义样式 |
| className | string |
- | 自定义类名 |
| cols | number |
24 |
网格列数 |
| rowHeight | number |
16 |
行高(px) |
| itemMargin | [number, number] |
[8, 16] |
字段间距 [x, y] |
| containerPadding | [number, number] |
[0, 0] |
容器内边距 [y, x] |
| fields | array |
[] |
字段配置数组 |
| hides | array |
[] |
隐藏字段数组 |
| primaryKey | number | string |
0 |
主键值(编辑模式) |
| formProps | object |
{} |
表单额外属性 |
| formFields | array |
[] |
表单额外字段 |
| listenChangeFields | array |
- | 监听变化的字段名数组 |
| listenChangeFieldsFunc | string |
- | 字段变化回调函数 |
| onValuesChange | function |
- | 表单值变化回调 |
| theme | string |
- | 主题框架组件路径 |
| themeProps | object |
{} |
主题框架属性 |
| groups | array |
[{key:'default',label:'默认'}] |
分组配置 |
基础使用:
const [form] = Form.useForm()
<GridLayoutForm
form={form}
formCode="user-form"
primaryKey={userId}
/>
使用 Hook:
// 表单操作 (获取数据 + 提交)
const submit = useFormAction(form, 'user-form', userId)
await submit({
serialize: (values) => values,
onSuccess: () => console.log('成功'),
onFail: (err) => console.error(err)
})
// 仅获取数据
const formData = useFormData('user-form', userId)
// 仅提交
const submit = useFormSubmit('user-form', userId)
await submit(form, options)
// 校验规则
const rules = useRules({
required: true,
type: 'string',
max: 100,
message: '必填且不超过100字符'
})
图标 - ff/iconfont
详细文档: ff-iconfont/README.md
import Iconfont from 'ff/iconfont'
Iconfont Props:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | string |
- | 图标类型(iconfont 图标名称,必填) |
| className | string |
- | 自定义类名 |
| style | object |
{} |
自定义样式 |
初始化:
// 应用启动时初始化
Iconfont.init([
'//at.alicdn.com/t/font_xxx.js',
'//at.alicdn.com/t/font_yyy.js'
])
使用:
<Iconfont
type="icon-home"
style={{ fontSize: '24px', color: '#1890ff' }}
/>
技术栈
- 框架: React 18.2+
- 构建: Vite 5.2+
- 表单: rc-field-form 1.44+
- UI: Ant Design 5.17+
- 样式: Less
Peer Dependencies
{
"react": "^18.2.0",
"react-dom": "^18.2.0",
"antd": "^5.17.0",
"rc-field-form": "^1.44.0",
"lodash": "^4.17.21",
"classnames": "^2.5.1"
}
完整示例
import React from 'react'
import ff, { http } from 'ff'
import { useDataListHelper } from 'ff/data-list/utils'
import Button from 'ff/button'
import Container from 'ff/container'
import DataList from 'ff/data-list'
import Iconfont from 'ff/iconfont'
// 初始化
Iconfont.init(['//at.alicdn.com/t/font_xxx.js'])
function App() {
const helper = useDataListHelper('users', { page: 1 })
const handleAdd = async () => {
await http.post('/api/users', { name: 'New User' })
helper.onReload()
}
return (
<Container
title="用户管理"
actions={<Button onClick={handleAdd}>添加</Button>}
>
<DataList
listCode="users"
{...helper}
/>
</Container>
)
}
许可证
作者: what-00@qq.com