This commit is contained in:
2026-02-06 10:19:13 +08:00
parent c7c24c6fd6
commit 06c4e4300f
22 changed files with 2383 additions and 1213 deletions

912
README.md
View File

@@ -1,12 +1,904 @@
### ff, 集成以下项目的开发环境
# 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/button
@ff/container
@ff/core
@ff/data-list
@ff/grid-layout
@ff/grid-layout-form
@ff/iconfont
@ff/pages
#### 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
```
### 最终打包到 [ff](https://git.fsdpf.net/npm/ff-dist.git).
#### 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