@flow97/react-toolkit
v0.1.13
Published
一套面向企业级应用的前端工具集,基于 React、Ant Design 与 TanStack Query。开箱即用的应用壳、权限/菜单、通用列表(分页/无限滚动)、抽屉/弹框表单与国际化支持,助你快速搭建一致、可维护的中后台应用。
Readme
@flow97/react-toolkit
一套面向企业级应用的前端工具集,基于 React、Ant Design 与 TanStack Query。开箱即用的应用壳、权限/菜单、通用列表(分页/无限滚动)、抽屉/弹框表单与国际化支持,助你快速搭建一致、可维护的中后台应用。
亮点特性
- 应用壳与导航:
Layout、侧边菜单、面包屑、登录页、404 - 列表范式:
QueryList(分页)、InfiniteList(无限滚动) - 表单弹层:
useFormModal、useFormDrawer - 权限体系:
RequireAuth、AuthButton,以及权限/菜单路由片段 - 数据获取:服务层 hooks(如
useAuth、useMenuList、useGames) - 子路径导出按域组织,Tree Shaking 友好
安装与要求
pnpm add @flow97/react-toolkit对等依赖(需由你的应用提供):
- react、react-dom:^19
- antd:^6
- react-router:^7
- @tanstack/react-query:^5
样式:
import '@flow97/react-toolkit/style.css'快速上手
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import 'react-toolkits/style.css'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router'
import { ToolkitProvider } from 'react-toolkits/components'
import { AuthMode } from 'react-toolkits/constants'
import router from './router'
// 创建 QueryClient 实例
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
},
mutations: {
retry: false,
},
},
})
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(
<QueryClientProvider client={queryClient}>
<ToolkitProvider collapsible gameApiV2 auth={{ mode: AuthMode.GROUP_BASED }} loginPath="/sign_in" mainPagePath="/">
<RouterProvider router={router} />
</ToolkitProvider>
</QueryClientProvider>,
)重要提示:QueryClientProvider 必须包裹 ToolkitProvider,因为 react-toolkits 内部使用了 @tanstack/react-query 的 hooks(如 useQueryClient、useQuery、useMutation)。
配置项(ToolkitProvider)
- loginPath: 登录页路径(未鉴权时会跳转)
- mainPagePath: 登录后主页路径(鉴权通过时进入)
- auth.mode: 认证模式,建议使用
AuthMode.GROUP_BASED - collapsible: 侧边栏是否可折叠
- gameApiV2: 是否启用游戏相关 V2 接口适配(如不涉及可忽略)
- 其余高级配置见类型定义
react-toolkits/components内导出的 Provider Props
子路径导出(推荐)
react-toolkits/components:组件与 Hooks(如Layout、ToolkitProvider、InfiniteList)react-toolkits/hooks:表单弹层 Hooks(如useFormDrawer、useFormModal)react-toolkits/pages:内置页面与路由片段(如permissionRoutes、menuRoutes、SignIn)react-toolkits/services:请求相关 Hooks(如useAuth、useMenuList、useGames)react-toolkits/constants:常量与枚举(如APP_ID_HEADER、AuthMode)react-toolkits/types:公共类型react-toolkits/utils:工具函数react-toolkits/locale与react-toolkits/locale/*:国际化资源与工具
示例:
import { Layout } from 'react-toolkits/components'
import { useFormDrawer } from 'react-toolkits/hooks'
import { permissionRoutes } from 'react-toolkits/pages'
import { useAuth } from 'react-toolkits/services'
import { APP_ID_HEADER } from 'react-toolkits/constants'
import type { NavMenuItem } from 'react-toolkits/types'权限与菜单集成示例
import { createBrowserRouter } from 'react-router'
import { Layout } from 'react-toolkits/components'
import { permissionRoutes, menuRoutes, SignIn } from 'react-toolkits/pages'
const router = createBrowserRouter([
{
path: '/',
element: <Layout />, // 自动接入面包屑、侧边栏与鉴权
children: [
// 权限/菜单内置片段(可按需选择)
...permissionRoutes,
...menuRoutes,
],
},
{ path: '/sign_in', element: <SignIn /> },
])
export default router国际化(Locale)
内置基础语言包与上下文工具,也可与第三方 i18n 方案并存:
import { useTranslation } from 'react-toolkits/locale'
import zhCN from 'react-toolkits/locale/zh_CN'
import enGB from 'react-toolkits/locale/en_GB'
const { t } = useTranslation()
// t('FilterFormWrapper.confirmText') → "查询"如果你已使用 react-i18next 等,可以仅复用本包的页面/菜单能力。
自定义菜单文案
- 使用
react-toolkits/locale的useTranslation配合内置 key,或在你的 i18n 方案中映射同名 key - 可按需引入
react-toolkits/locale/zh_CN、en_GB等作为基础词条
目录结构(概览)
src/
components/ // 组件与 Hooks(以 react-toolkits/components 导出)
hooks/ // 表单弹层 Hooks(以 react-toolkits/hooks 导出)
features/ // 特性模块(权限/菜单等:hooks/组件/服务聚合)
pages/ // 内置页面与路由片段
services/ // 数据服务 hooks(依赖 libs/ky 与全局上下文)
constants/ // 常量与枚举
utils/ // 工具函数
locale/ // 国际化上下文与语言包常用用法示例
列表(分页 QueryList)
import { QueryList } from 'react-toolkits/components'
export default function UserTable() {
return (
<QueryList
queryKey={['users']}
queryFn={async ({ page, pageSize }) => {
// 返回 { list: T[]; total: number }
const res = await fetch(`/api/users?page=${page}&pageSize=${pageSize}`)
return res.json()
}}
columns={[
{ title: 'ID', dataIndex: 'id' },
{ title: 'Name', dataIndex: 'name' },
]}
/>
)
}列表(无限滚动 InfiniteList)
import { InfiniteList } from 'react-toolkits/components'
export default function LogList() {
return (
<InfiniteList
queryKey={['logs']}
queryFn={async ({ pageParam = 1 }) => {
// 返回 { list: T[]; nextPage?: number }
const res = await fetch(`/api/logs?page=${pageParam}`)
return res.json()
}}
itemRender={item => <div>{item.message}</div>}
/>
)
}表单弹层(useFormDrawer)
import { Button } from 'antd'
import { useFormDrawer } from 'react-toolkits/hooks'
export default function CreateUser() {
const { show, drawer } = useFormDrawer({
title: '新建用户',
onConfirm: async values => {
await fetch('/api/users', { method: 'POST', body: JSON.stringify(values) })
},
content: (
<>
<Form.Item name="name" label="姓名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="role" label="角色" rules={[{ required: true }]}>
<Select
options={[
{ value: 'admin', label: '管理员' },
{ value: 'user', label: '用户' },
]}
/>
</Form.Item>
</>
),
})
return (
<>
<Button onClick={show}>新建</Button>
{drawer}
</>
)
}表单弹层(useFormModal)
import { Button, Form, Input, Select } from 'antd'
import { useFormModal } from 'react-toolkits/hooks'
export default function EditUser() {
const { show, modal } = useFormModal({
title: '编辑用户',
onConfirm: async values => {
await fetch('/api/users', { method: 'PUT', body: JSON.stringify(values) })
},
content: (
<>
<Form.Item name="name" label="姓名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="role" label="角色" rules={[{ required: true }]}>
<Select
options={[
{ value: 'admin', label: '管理员' },
{ value: 'user', label: '用户' },
]}
/>
</Form.Item>
</>
),
})
return (
<>
<Button onClick={() => show({ initialValues: { name: 'John', role: 'user' } })}>编辑</Button>
{modal}
</>
)
}服务层 Hooks(react-toolkits/services)
import { useAuth, useMenuList } from 'react-toolkits/services'
export function UseDataExample() {
const { data: permissions } = useAuth()
const { data: menus } = useMenuList()
// 根据你的业务渲染
return null
}Ky 与请求拦截(useKy)
- 内置
useKy封装了鉴权与APP_ID_HEADER注入逻辑,结合 Provider 的上下文使用 - 你也可以直接使用
fetch,但推荐统一通过services或useKy
import { useEffect } from 'react'
import { useKy } from 'react-toolkits/components'
import { APP_ID_HEADER } from 'react-toolkits/constants'
export default function FetchWithKy() {
const ky = useKy({
headers: {
[APP_ID_HEADER]: 'my-app-id',
},
})
useEffect(() => {
ky.get('/api/ping').json()
}, [ky])
return null
}提示:若你的应用已有全局请求层,可仅复用本包页面/组件,将头与鉴权逻辑放到你的层中保持一致性。
迁移指南(从根导入 → 子路径导出)
过去:
import { ToolkitProvider, Layout, useFormDrawer } from '@flow97/react-toolkit'现在(推荐):
import { ToolkitProvider, Layout } from 'react-toolkits/components'
import { useFormDrawer, useFormModal } from 'react-toolkits/hooks'
import { permissionRoutes } from 'react-toolkits/pages'
import { useAuth } from 'react-toolkits/services'收益:边界清晰、按需打包更友好(Tree Shaking)。
权限版本兼容
- 历史上存在不同权限数据结构,
AuthMode用于适配后端变体 - 新项目建议使用
V3;旧项目如为V2,只需在ToolkitProvider中切换版本
常见问题(FAQ)
- 如何引入样式?
- 在应用入口一次性引入:
import 'react-toolkits/style.css'
- 在应用入口一次性引入:
- Provider 放哪?
- 在应用根部使用
ToolkitProvider,传入登录路径、主页路径、权限版本等配置。
- 在应用根部使用
- 如何自定义菜单?
- 通过
Layout的items或参考示例应用的menu-items.tsx。
- 通过
- 如何统一请求与拦截?
- 使用
useKy或services中的 hooks(自动注入鉴权与 App-ID 头)。 - 若需要自定义拦截,传入
useKy的配置或在应用层包裹一层。
- 使用
- 列表的数据结构需要什么格式?
- 分页:
{ list: T[]; total: number };无限滚动:{ list: T[]; nextPage?: number } - 可以只用页面和路由,不用 Provider 吗?
- 不建议。多数功能依赖上下文(鉴权、国际化、请求)。若必须,请自行在应用层提供等价上下文。
本地开发与构建
# 构建
pnpm -C packages/react-toolkits build
# 开发调试(监听)
pnpm -C packages/react-toolkits dev构建将产出多入口:lib/index.js、lib/components.js、lib/hooks.js、lib/pages.js、lib/services.js、lib/types.js、lib/utils.js,以及 locale/*。
提示:发布前请确保示例应用验证通过,并更新 CHANGELOG.md 描述变更与迁移说明。
变更与发布
- 更新日志见
CHANGELOG.md - 遵循 semver;涉及子路径导出/目录变更会在次版本提供迁移说明
许可证
MIT
