Files
ff-dist/README.md
2026-02-06 10:19:13 +08:00

905 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FF 组件库
基于 React 的企业级组件库,提供完整的页面开发解决方案。
## 目录
- [核心模块](#核心模块)
- [主入口 - `ff`](#主入口---ff)
- [ff (default)](#ff-default)
- [http](#http)
- [cache](#cache)
- [func](#func)
- [route](#route)
- [Hooks - `ff/hooks`](#hooks---ffhooks)
- [工具函数 - `ff/utils`](#工具函数---ffutils)
- [数据转换器 - `ff/data-converter`](#数据转换器---ffdata-converter)
- [WebSocket 订阅 - `ff/res-ws`](#websocket-订阅---ffres-ws)
- [组件模块](#组件模块)
- [按钮 - `ff/button`](#按钮---ffbutton)
- [容器 - `ff/container`](#容器---ffcontainer)
- [数据列表 - `ff/data-list`](#数据列表---ffdata-list)
- [网格布局 - `ff/grid-layout`](#网格布局---ffgrid-layout)
- [网格表单 - `ff/grid-layout-form`](#网格表单---ffgrid-layout-form)
- [图标 - `ff/iconfont`](#图标---fficonfont)
- [技术栈](#技术栈)
- [Peer Dependencies](#peer-dependencies)
- [完整示例](#完整示例)
- [许可证](#许可证)
## 核心模块
### 主入口 - `ff`
> **详细文档:** [ff-core/README.md](src/ff-core/README.md)
```javascript
import ff, { http, cache, configure, func, route } from 'ff'
import { AppContext, AppGlobalParamsContext } from 'ff'
```
#### ff (default)
核心应用实例,管理用户认证、环境初始化、组件加载。
**主要方法:**
```javascript
// 注册组件供应商
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` 机制自动弹窗显示错误消息。
```typescript
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 方法:**
```javascript
// 基础请求 - 返回 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) // 清除缓存
```
**使用示例:**
```javascript
// 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
缓存管理器。
```javascript
// 设置缓存
cache.set(key, value, ttl) // ttl: 过期时间(秒)
// 获取缓存
cache.get(key) // => value | null
// 删除缓存
cache.delete(key)
// 清空所有缓存
cache.clear()
// 检查缓存是否存在
cache.has(key) // => boolean
```
#### func
函数执行器,支持 Web Worker 沙箱执行。
```javascript
// 执行 JavaScript 代码字符串
func.exec(code, params, helpers) // => Promise<result>
// 示例
await func.exec(
'return value * 2',
{ value: 10 },
{ getFieldValue: (name) => form.getFieldValue(name) }
) // => 20
```
#### route
路由管理器。
```javascript
// 路由跳转
route.push(path, params)
route.replace(path, params)
// 获取当前路由
route.getCurrentRoute()
```
---
### Hooks - `ff/hooks`
> **详细文档:** [ff-core/README.md#hooks-模块-hooksjs](src/ff-core/README.md#hooks-模块-hooksjs)
React Hooks 工具集。
```javascript
import {
useMergedState,
useUpdate,
usePrevious,
useStateWithCallback,
useDeepEqualEffect,
useOptions,
useSubscribeRequest
} from 'ff/hooks'
```
#### useMergedState(defaultValue, options)
合并状态管理,结合内部 state 和外部 props。
```javascript
const [value, setValue] = useMergedState(defaultValue, {
value: props.value,
onChange: props.onChange
})
```
#### useUpdate()
强制更新组件。
```javascript
const forceUpdate = useUpdate()
// 调用 forceUpdate() 强制组件重新渲染
```
#### usePrevious(state)
获取上一次的状态值。
```javascript
const prevValue = usePrevious(value)
```
#### useStateWithCallback(initialState)
带回调的状态管理。
```javascript
const [state, setState] = useStateWithCallback(initialState)
setState(newState, (currentState) => {
console.log('状态已更新:', currentState)
})
```
#### useDeepEqualEffect(callback, deps, compare)
深度对比的 useEffect,只在依赖真正变化时执行。
```javascript
useDeepEqualEffect(() => {
// 只有当 deps 深度对比不相等时才执行
}, [complexObject])
```
#### useOptions(options, language, type, form, basicForm)
选项数据处理,支持静态数组或动态 JS 代码。
```javascript
const options = useOptions(
fieldOptions, // 选项数据或 JS 代码
'javascript', // 'json' | 'javascript'
'string', // 值类型
form, // 表单实例
basicForm // 基础表单实例
)
```
#### useSubscribeRequest(param)
WebSocket 订阅请求。
```javascript
const data = useSubscribeRequest({
uri: '/api/subscribe',
params: { id: 123 }
})
```
---
### 工具函数 - `ff/utils`
> **详细文档:** [ff-core/README.md#工具函数模块-utilsjs](src/ff-core/README.md#工具函数模块-utilsjs)
通用工具函数集合。
```javascript
import {
toPrimitive,
getPrimitiveType,
uuid,
hashJSON,
replaceKeys,
deepSome,
getWidgetPropsData,
getPkgName,
getPkgCategory,
getPkgOwner,
makeUniqueIdGenerator,
hasUrlPrefix,
getUrlWithPrefix
} from 'ff/utils'
```
#### toPrimitive(data, type)
类型转换。
```javascript
toPrimitive('123', 'number') // => 123
toPrimitive('true', 'boolean') // => true
toPrimitive('[1,2]', 'array') // => [1, 2]
toPrimitive('{"a":1}', 'json') // => { a: 1 }
```
#### getPrimitiveType(value)
获取原始类型。
```javascript
getPrimitiveType(123) // => 'number'
getPrimitiveType('text') // => 'string'
getPrimitiveType([1, 2]) // => 'array'
```
#### uuid()
生成唯一标识符。
```javascript
const id = uuid() // => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
```
#### hashJSON(obj, bits, algo)
计算 JSON 对象的哈希值。
```javascript
const hash = hashJSON({ name: 'test' }, 128, 'md5')
```
#### replaceKeys(obj, keyMap)
批量替换对象键名。
```javascript
replaceKeys(
{ oldKey: 'value' },
{ oldKey: 'newKey' }
) // => { newKey: 'value' }
```
---
### 数据转换器 - `ff/data-converter`
> **详细文档:** [ff-core/README.md#数据转换器-data-converterjs](src/ff-core/README.md#数据转换器-data-converterjs)
中间件模式的数据转换器。
```javascript
import DataConverter from 'ff/data-converter'
```
**使用方式:**
```javascript
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](src/ff-core/README.md)
WebSocket 资源订阅管理器。
```javascript
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](src/ff-button/README.md)
```javascript
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` | - | 根元素类名 |
**组件:**
```jsx
// 默认按钮
<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>
```
**权限控制:**
```javascript
// 检查权限
auth.check(uuid) // => boolean
// 设置权限
auth.set(uuids) // uuids: Array<string>
```
**Hook:**
```javascript
const buttonProps = useButton(props)
```
---
### 容器 - `ff/container`
> **详细文档:** [ff-container/README.md](src/ff-container/README.md)
```javascript
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` | - | 主体内容 |
**页面容器:**
```jsx
<Container
title="页面标题"
subTitle="副标题"
actions={<Button>操作</Button>}
extras={<div>扩展区</div>}
>
页面内容
</Container>
```
**Popup API:**
```javascript
// 打开模态框
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](src/ff-data-list/README.md)
```javascript
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` | - | 侧边栏变化回调 |
**基础使用:**
```jsx
<DataList
listCode="user-list"
theme="@pkg/themes/TableTheme"
/>
```
**使用 Hook:**
```javascript
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](src/ff-grid-layout/README.md)
```javascript
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:'默认'}]` | 分组配置 |
**基础使用:**
```jsx
<GridLayout
cols={12}
rowHeight={21}
fields={fields}
data={data}
/>
```
**使用 Hook:**
```javascript
// 获取远程配置
const structure = useStructure('layout-code')
// 字段值处理
const [displayValue, rawValue] = useField(name, fieldConfig)
// 执行动态函数
const [result, error] = useFnRun(jsCode, initialValue, params, helpers)
```
---
### 网格表单 - `ff/grid-layout-form`
> **详细文档:** [ff-grid-layout-form/README.md](src/ff-grid-layout-form/README.md)
```javascript
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:'默认'}]` | 分组配置 |
**基础使用:**
```jsx
const [form] = Form.useForm()
<GridLayoutForm
form={form}
formCode="user-form"
primaryKey={userId}
/>
```
**使用 Hook:**
```javascript
// 表单操作 (获取数据 + 提交)
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](src/ff-iconfont/README.md)
```javascript
import Iconfont from 'ff/iconfont'
```
**Iconfont Props:**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| type | `string` | - | 图标类型(iconfont 图标名称,必填) |
| className | `string` | - | 自定义类名 |
| style | `object` | `{}` | 自定义样式 |
**初始化:**
```javascript
// 应用启动时初始化
Iconfont.init([
'//at.alicdn.com/t/font_xxx.js',
'//at.alicdn.com/t/font_yyy.js'
])
```
**使用:**
```jsx
<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
```json
{
"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"
}
```
## 完整示例
```jsx
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