@kaa413/shared
v0.0.1
Published
Shared utilities, components, hooks, and tools for React + Ant Design + UmiJS projects
Maintainers
Readme
@aspect/shared
React + Ant Design + UmiJS 项目共享工具库。提供工具函数、加密、请求层、组件、Hooks、样式、配置、类型和常量,统一多项目技术标准。
目录
功能概览
| 模块 | 导入路径 | 功能 |
|------|---------|------|
| Utils | @aspect/shared/utils | 格式化、验证、存储(带 TTL)、日志、剪贴板、HTML 过滤、权限、认证管理、树操作、地区数据、水印 |
| Crypto | @aspect/shared/crypto | AES-256-CBC + HMAC-SHA256 端到端加密(Web Crypto API 原生实现) |
| Request | @aspect/shared/request | UmiJS 请求配置工厂(认证拦截、加密拦截、错误处理)、CRUD Service 工厂、文件服务工厂 |
| Components | @aspect/shared/components | StatusTag、ErrorBoundary、ActionColumn、RecycleBinModal、StatCard、DynamicFormModal、PageContainer 等 14 个 Ant Design 组件 |
| Hooks | @aspect/shared/hooks | useTableData、useCrudList、useDetailPage、useFormModal、useFormSubmit、useModal、useFileUpload、useDownload 等 16 个 React Hooks |
| Styles | @aspect/shared/styles | antd-style 样式工厂(通用样式、页面样式、响应式样式) |
| Config | @aspect/shared/config | UmiJS 基础配置工厂、App Runtime 工厂(getInitialState + layout)、Biome/TSConfig 预设 |
| Types | @aspect/shared/types | ApiResponse、PaginatedData、BaseUser、AuthToken、StatusMap 等通用类型定义 |
| Constants | @aspect/shared/constants | BREAKPOINTS、TABLE_DEFAULTS、DEBOUNCE_DELAY、ErrorShowType 等 UI 常量 |
目录结构
keel-web-shared/
├── src/
│ ├── components/ # React + Ant Design 组件
│ │ ├── ActionColumn/ # 操作列(查看/编辑/删除/切换状态)
│ │ ├── DataTableCard/ # 数据表格卡片
│ │ ├── DetailPageLayout/# 详情页布局
│ │ ├── DynamicFormModal/# 动态表单弹窗
│ │ ├── ErrorBoundary/ # 错误边界
│ │ ├── GlobalAntdHolder/# Antd 静态方法持有器
│ │ ├── PageContainer/ # 页面容器(带 Tab)
│ │ ├── RecycleBinModal/ # 回收站弹窗
│ │ ├── RouteGuard/ # 认证/访客路由守卫
│ │ ├── SearchFilterBar/ # 搜索筛选栏
│ │ ├── StatCard/ # 统计卡片 + 网格
│ │ ├── StatusShow/ # 状态展示(空/加载/错误)
│ │ ├── StatusTag/ # 状态标签
│ │ ├── UserAvatar/ # 用户头像
│ │ └── index.ts
│ ├── config/ # 配置工厂
│ │ ├── app-runtime.ts # App Runtime(getInitialState + layout)
│ │ ├── umi-base.ts # UmiJS 基础配置
│ │ ├── biome.json # Biome 代码检查预设
│ │ └── tsconfig.base.json # TypeScript 基础配置
│ ├── constants/ # 统一常量
│ │ └── ui.ts # BREAKPOINTS, TABLE_DEFAULTS, ErrorShowType 等
│ ├── crypto/ # 加密模块
│ │ ├── factory.ts # createEncryption 工厂
│ │ ├── aes-cbc-hmac.ts # AES-CBC + HMAC 策略(环境自动检测)
│ │ ├── crypto-native.ts # Web Crypto API 原生实现
│ │ ├── crypto-provider-factory.ts # 加密器工厂(环境自动检测)
│ │ ├── hash.ts # SHA-256 哈希
│ │ ├── path-matcher.ts # 路径白名单匹配
│ │ └── replay-guard.ts # 重放防护
│ ├── hooks/ # React Hooks
│ │ ├── useCrudList.ts # CRUD 列表管理
│ │ ├── useDetailPage.ts # 详情页数据加载
│ │ ├── useDetailPageMode.ts # 详情页模式(查看/新建/编辑)
│ │ ├── useDownload.ts # 文件下载
│ │ ├── useFileSelector.ts # 文件选择
│ │ ├── useFileUpload.ts # 文件上传
│ │ ├── useFormModal.ts # 表单弹窗
│ │ ├── useFormSubmit.ts # 表单提交
│ │ ├── useIntlText.ts # 国际化文本
│ │ ├── useListData.ts # 列表数据管理
│ │ ├── useModal.ts # 弹窗状态
│ │ ├── useResponsive.ts # 响应式断点
│ │ ├── useTableConfig.tsx # 表格配置(列/操作)
│ │ ├── useTableData.ts # ProTable 数据请求
│ │ ├── useUserPreference.ts # 用户偏好(主题/语言)
│ │ ├── shared-request.ts # ProTable request 适配
│ │ └── index.ts
│ ├── request/ # 请求层
│ │ ├── create-request-config.ts # 一站式请求配置工厂
│ │ ├── crud-service.ts # CRUD Service 工厂
│ │ ├── file-service.ts # 文件服务工厂
│ │ ├── interceptors.ts # 请求/响应拦截器
│ │ ├── error-handler.ts # 错误处理器
│ │ ├── constants.ts # HTTP_STATUS, API_SUCCESS_CODE
│ │ └── types.ts # 请求层类型定义
│ ├── styles/ # antd-style 样式工厂
│ │ ├── common.ts # 通用布局样式
│ │ ├── common-layout.ts # 布局辅助样式
│ │ ├── common-typography.ts # 排版样式
│ │ ├── common-utilities.ts # 工具样式
│ │ ├── page.ts # 页面级样式
│ │ └── responsive.ts # 响应式样式
│ ├── types/ # 通用类型定义
│ │ ├── api.ts # ApiResponse, PaginatedData, ProTableResponse
│ │ ├── auth.ts # BaseUser, AuthToken, LoginParams
│ │ └── common.ts # StatusMapItem, StatusMap, SelectOption, TreeNode
│ ├── utils/ # 工具函数
│ │ ├── api-error.ts # 错误信息提取
│ │ ├── api-helpers.ts # isApiSuccess, executeApiAction
│ │ ├── auth.ts # createAuthManager, emitLoginRequired
│ │ ├── clipboard.ts # copyToClipboard
│ │ ├── formatters.ts # 日期/数字/货币/文件大小格式化
│ │ ├── logger.ts # createLogger 日志工厂
│ │ ├── navigation.ts # openInNewTab
│ │ ├── performance.ts # 性能监控工具
│ │ ├── permission.ts # 权限检查
│ │ ├── region.ts # 地区数据(省市区)
│ │ ├── sanitize.ts # HTML 过滤(基于 DOMPurify)
│ │ ├── storage.ts # localStorage 安全封装(带 TTL)
│ │ ├── theme.ts # 主题工具
│ │ ├── tree.ts # 树结构转换
│ │ ├── url.ts # URL 工具
│ │ ├── validators.ts # 手机号/邮箱/文件类型验证
│ │ ├── value-enum.ts # createValueEnum 工厂
│ │ └── watermark.ts # 用户水印
│ └── index.ts # 主入口(聚合所有模块)
├── package.json
├── tsup.config.ts
└── tsconfig.json新项目接入
1. 安装依赖
# 本地链接(monorepo 开发)
pnpm add @aspect/shared@link:../../keel-web-shared
# 或从 npm 安装
pnpm add @aspect/shared2. TypeScript 路径映射
在 tsconfig.json 中配置路径别名(本地链接时需要):
{
"extends": "@aspect/shared/config/tsconfig.base.json",
"compilerOptions": {
"paths": {
"@aspect/shared": ["../../keel-web-shared/src/index"],
"@aspect/shared/*": ["../../keel-web-shared/src/*/index"]
}
}
}3. Biome 代码检查
在 biome.json 中继承预设:
{
"extends": ["@aspect/shared/config/biome.json"]
}4. UmiJS 配置
// config/config.ts
import { createUmiBaseConfig } from '@aspect/shared/config';
import routes from './routes';
export default createUmiBaseConfig({
routes,
locale: { default: 'zh-CN', antd: true },
title: '我的应用',
proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } },
});5. 请求配置
// src/requestErrorConfig.ts
import { createRequestConfig } from '@aspect/shared/request';
import { createEncryption } from '@aspect/shared/crypto';
import { getMessageApi } from '@aspect/shared/components';
import { history } from '@umijs/max';
export const request = createRequestConfig({
timeout: 10000,
auth: {
tokenKey: 'my_auth_token',
headerName: 'Authorization',
headerPrefix: 'Bearer ',
},
encryption: {
enabled: true,
strategy: createEncryption({
algorithm: 'aes-cbc-hmac',
keys: {
requestKeyHex: import.meta.env.VITE_AES_REQUEST_KEY_HEX,
responseKeyHex: import.meta.env.VITE_AES_RESPONSE_KEY_HEX,
},
}),
methods: ['POST', 'PUT'],
excludePaths: ['/api/upload', '/api/download'],
},
errorConfig: {
onUnauthorized: () => history.push('/login'),
onError: (msg) => getMessageApi()?.error(msg),
},
});6. App Runtime
// src/app.tsx
import { createAppRuntime, createPageChangeGuard } from '@aspect/shared/config';
import { GlobalAntdHolder } from '@aspect/shared/components';
import { initSanitizer, initFormatters, createUserWatermark } from '@aspect/shared/utils';
import { history } from '@umijs/max';
// 初始化工具
initSanitizer();
initFormatters({ locale: 'zh-CN' });
const appRuntime = createAppRuntime({
auth: {
fetchUserInfo: async () => {
const res = await fetchCurrentUser();
return isApiSuccess(res) ? res.data : undefined;
},
loginPath: '/login',
guestPaths: ['/login', '/register'],
isAuthenticated: () => !!localStorage.getItem('my_auth_token'),
},
layout: {
title: '我的应用',
logo: '/logo.svg',
showAvatarName: true,
avatarMenuItems: [
{ key: 'settings', label: '个人设置', onClick: () => history.push('/settings') },
{ key: 'logout', label: '退出登录', danger: true, onClick: handleLogout },
],
onPageChange: createPageChangeGuard({
loginPath: '/login',
guestPaths: ['/login', '/register'],
}),
childrenRender: (children, initialState) => (
<>
{children}
<GlobalAntdHolder />
</>
),
},
navigate: (path) => history.push(path),
});
export const getInitialState = appRuntime.getInitialState;
export const layout = appRuntime.layout;模块使用指南
1. Utils 工具函数
从 @aspect/shared/utils 导入。包含 19 个子模块:
认证管理
import { createAuthManager, emitLoginRequired, LOGIN_REQUIRED_EVENT } from '@aspect/shared/utils';
// 创建认证管理器(每个项目配置不同的存储键和过期策略)
const auth = createAuthManager({
tokenKey: 'my_token',
expireKey: 'my_token_expire',
ttlMs: 30 * 24 * 60 * 60 * 1000, // 30 天
extraStorageKeys: ['userRoles', 'userMenus'],
onUnauthorized: () => history.push('/login'),
});
auth.setToken('abc123');
auth.getToken(); // 'abc123'(自动检查过期)
auth.isAuthenticated(); // true
auth.clearAuth(); // 清除 token + 所有 extraStorageKeys格式化
import { formatDate, formatDateTime, formatRelativeTime, formatNumber, formatPercentage } from '@aspect/shared/utils';
formatDate('2024-01-15'); // '2024-01-15'
formatDateTime('2024-01-15 10:30'); // '2024-01-15 10:30:00'
formatRelativeTime('2024-01-14'); // '1 天前'
formatNumber(12345.6); // '12,345.6'
formatPercentage(0.856); // '85.6%'存储
import { safeGetStorage, safeSetStorage, safeRemoveStorage, clearStorage } from '@aspect/shared/utils';
safeSetStorage('key', { foo: 'bar' }); // 安全写入 (自动 JSON.stringify)
const val = safeGetStorage<MyType>('key'); // 安全读取 (自动 JSON.parse)
safeRemoveStorage('key'); // 安全删除
clearStorage(); // 清除所有API 辅助
import { isApiSuccess, executeApiAction } from '@aspect/shared/utils';
// 判断 API 是否成功
if (isApiSuccess(response)) {
console.log(response.data);
}
// 安全执行 API 调用 + 自动错误处理
const data = await executeApiAction(
() => userService.remove(id),
{
successMsg: '删除成功',
onSuccess: (data, msg) => {
message.success(msg);
actionRef.current?.reload();
},
onError: (msg) => message.error(msg),
},
);其他工具
import { createLogger } from '@aspect/shared/utils';
import { copyToClipboard } from '@aspect/shared/utils';
import { sanitizeHtml, initSanitizer } from '@aspect/shared/utils';
import { openInNewTab } from '@aspect/shared/utils';
import { validatePhone, validateEmail, validateFileSize, validateFileType } from '@aspect/shared/utils';
import { createValueEnum, createSelectOptionsFromMap } from '@aspect/shared/utils';
import { setRegionDataSource, getRegionData, getCityData } from '@aspect/shared/utils';
import { createUserWatermark } from '@aspect/shared/utils';
import { extractErrorMessage } from '@aspect/shared/utils';
import { initPerformanceMonitoring } from '@aspect/shared/utils';
// 日志
const logger = createLogger({ prefix: 'MyApp' });
logger.info('应用启动');
logger.warn('配置缺失');
// 剪贴板
await copyToClipboard('复制内容');
// HTML 过滤
initSanitizer(); // 全局初始化一次
const clean = sanitizeHtml('<script>alert(1)</script><p>安全内容</p>');
// 值枚举
const statusEnum = createValueEnum([
{ value: 'active', label: '启用', status: 'Success' },
{ value: 'inactive', label: '禁用', status: 'Error' },
]);2. Crypto 加密
从 @aspect/shared/crypto 导入。当前内置算法为 aes-cbc-hmac(Web Crypto API 原生实现)。
import {
createEncryption,
getProviderType,
hashPassword,
isUsingNativeProvider,
} from '@aspect/shared/crypto';
// 检测当前加密器类型
const providerType = getProviderType(); // 'native'
if (isUsingNativeProvider()) {
console.log('使用 Web Crypto API 原生加密(性能最优)');
}
// 创建加密实例
const crypto = createEncryption({
algorithm: 'aes-cbc-hmac',
keys: {
requestKeyHex: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
responseKeyHex: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789',
},
});
// 加密/解密
const timestamp = Date.now();
const encrypted = await crypto.encrypt({ username: 'test' }, timestamp);
const decrypted = await crypto.decrypt(encrypted, timestamp);
// 密码哈希(SHA-256)
const hashed = await hashPassword('password123');底层能力也可单独使用:
import {
AESCBCHMACStrategy,
encryptRequestData,
decryptResponseData,
isExcludedPath,
validateTimestamp,
generateRandomHex,
encryptAESOptimized,
decryptAESOptimized,
} from '@aspect/shared/crypto';加密方案说明
SDK 默认使用 Web Crypto API 原生实现:
| 运行条件 | 使用的实现 | 性能 | |------|-----------|------| | 支持 Web Crypto API | Web Crypto API 原生 | ⚡ 性能最优 |
3. Request 请求层
从 @aspect/shared/request 导入。基于 UmiJS request 插件。
一站式配置(createRequestConfig)
见 新项目接入 > 请求配置。
CRUD Service 工厂
消除手写重复 service 代码。为业务实体自动生成 list / detail / create / update / remove / toggleStatus / save 方法。
// src/services/user.ts
import { createCrudService } from '@aspect/shared/request';
import { request } from '@umijs/max';
// 完整配置
const userService = createCrudService<User, CreateUserDTO, UpdateUserDTO>('/api/users', {
request,
list: { method: 'POST', path: '/page' },
detail: { method: 'GET', path: '/:id' },
create: { method: 'POST', path: '' },
update: { method: 'PUT', path: '' },
delete: { method: 'DELETE', path: '/:id' },
toggleStatus: { method: 'PUT', path: '/:id/status' },
});
// 默认配置已覆盖常见 RESTful 约定,可简写:
const userService = createCrudService<User>('/api/users', { request });
// 禁用不需要的操作
const readonlyService = createCrudService<Article>('/api/articles', {
request,
create: false,
update: false,
delete: false,
toggleStatus: false,
});
// 使用
const res = await userService.list({ pageNo: 1, pageSize: 10, keyword: '张三' });
const user = await userService.detail(1);
await userService.create({ name: 'test' });
await userService.save({ id: 1, name: 'updated' }); // 有 id 自动走 update
await userService.remove(1);
await userService.toggleStatus(1, 'inactive');默认操作路径约定:
| 操作 | 方法 | 路径 |
|------|------|------|
| list | POST | {basePath}/page |
| detail | GET | {basePath}/:id |
| create | POST | {basePath} |
| update | PUT | {basePath} |
| delete | DELETE | {basePath}/:id |
| toggleStatus | PUT | {basePath}/:id/status |
文件服务工厂
import { createFileService } from '@aspect/shared/request';
import { request } from '@umijs/max';
const fileService = createFileService('/api/files', {
request,
upload: { path: '/upload', requestOptions: { skipEncrypt: true } },
delete: { path: '/', paramName: 'fileUrl' },
download: { path: '/download/:id' },
});
// 上传
const res = await fileService.upload({
file: selectedFile,
moduleType: 'avatar',
businessId: userId,
});
// 删除
await fileService.remove('https://cdn.example.com/files/abc.jpg');
// 下载
const blob = await fileService.download('file-123');请求常量和错误类型
import { API_SUCCESS_CODE, HTTP_STATUS, ErrorShowType } from '@aspect/shared/request';
// ErrorShowType 枚举
ErrorShowType.SILENT; // 0 - 不显示
ErrorShowType.WARN_MESSAGE; // 1 - 警告
ErrorShowType.ERROR_MESSAGE; // 2 - 错误
ErrorShowType.NOTIFICATION; // 3 - 通知框
ErrorShowType.REDIRECT; // 9 - 跳转4. Components 组件
从 @aspect/shared/components 导入。基于 Ant Design + antd-style。
组件清单
| 组件 | 用途 | 关键 Props |
|------|------|-----------|
| StatusTag | 状态标签,根据 statusMap 显示颜色和文本 | status, statusMap |
| ErrorBoundary | React 错误边界 | fallback, onError |
| ActionColumn | ProTable 操作列(查看/编辑/删除/切换状态) | actions |
| RecycleBinModal | 回收站弹窗(已删除数据恢复/彻底删除) | api, columns |
| StatCard / StatCardGrid | 统计卡片,支持网格布局 | items, colorPresets |
| StatusShow | 状态展示(空、加载、错误) | type |
| UserAvatar | 用户头像 | src, name, size |
| AuthGuard / GuestGuard | 认证/访客路由守卫 | fallback |
| DataTableCard | 数据表格 + 卡片包装 | title, columns |
| DetailPageLayout | 详情页面板布局 | mode, breadcrumbs |
| SearchFilterBar | 搜索筛选栏 | fields |
| DynamicFormModal | 动态表单弹窗(字段配置驱动) | fields, mode |
| PageContainer | 带 Tab 的页面容器 | tabs |
| GlobalAntdHolder | Antd 静态 API 持有器(message/notification/modal) | — |
基本用法
import { StatusTag, ErrorBoundary, ActionColumn, GlobalAntdHolder, getMessageApi } from '@aspect/shared/components';
import type { StatusMap, ActionItem } from '@aspect/shared/types';
// StatusTag
const STATUS_MAP: StatusMap<'active' | 'inactive'> = {
active: { text: '启用', color: 'green' },
inactive: { text: '禁用', color: 'red' },
};
<StatusTag status="active" statusMap={STATUS_MAP} />
// ActionColumn
const actions: ActionItem[] = [
createViewAction(() => handleView(record)),
createEditAction(() => handleEdit(record)),
createDeleteAction(() => handleDelete(record.id)),
];
<ActionColumn actions={actions} />
// ErrorBoundary
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
// GlobalAntdHolder(在 app.tsx 或根组件中挂载一次)
<GlobalAntdHolder />
// 之后在任意位置使用静态 API
getMessageApi()?.success('操作成功');
getNotificationApi()?.info({ message: '通知', description: '详情' });
getModalApi()?.confirm({ title: '确认删除?' });StatCardGrid
import { StatCardGrid } from '@aspect/shared/components';
import type { StatCardGridItem } from '@aspect/shared/components';
const items: StatCardGridItem[] = [
{ title: '总用户', value: 1234, color: 'blue' },
{ title: '活跃用户', value: 567, color: 'green', trend: 12.5 },
{ title: '收入', value: '¥89,000', color: 'gold' },
];
<StatCardGrid items={items} />DynamicFormModal
import { DynamicFormModal } from '@aspect/shared/components';
import type { DynamicFormField } from '@aspect/shared/components';
const fields: DynamicFormField[] = [
{ name: 'name', label: '名称', type: 'text', required: true },
{ name: 'status', label: '状态', type: 'select', options: [
{ label: '启用', value: 'active' },
{ label: '禁用', value: 'inactive' },
]},
{ name: 'description', label: '描述', type: 'textarea' },
];
<DynamicFormModal
open={open}
mode="create"
fields={fields}
onSubmit={handleSubmit}
onCancel={() => setOpen(false)}
/>5. Hooks
从 @aspect/shared/hooks 导入。共 16 个 React Hooks。
useTableData — ProTable 数据请求
import { useTableData } from '@aspect/shared/hooks';
const { request, actionRef } = useTableData<User>({
fetchApi: (params) => userService.list(params),
});
<ProTable request={request} actionRef={actionRef} />useCrudList — 完整 CRUD 列表管理
import { useCrudList } from '@aspect/shared/hooks';
import { Modal, message } from 'antd';
import { history } from '@umijs/max';
const {
request,
actionRef,
goCreate,
goEdit,
goDetail,
handleDelete,
handleBatchDelete,
} = useCrudList<User>({
fetchApi: userService.list,
deleteApi: userService.remove,
batchDeleteApi: userService.batchRemove,
basePath: '/system/user',
navigate: history.push,
showMessage: (content, type) => message[type](content),
showConfirm: ({ title, content, onOk }) =>
Modal.confirm({ title, content, onOk }),
});useFormModal — 表单弹窗
import { useFormModal } from '@aspect/shared/hooks';
import { Form, Modal } from 'antd';
const [form] = Form.useForm<UserForm>();
const { modalProps, openCreate, openEdit, submit } = useFormModal<
UserForm,
UserRecord
>({
form,
createApi: (data) => userService.create(data),
updateApi: (data) => userService.update(data),
messages: { createSuccess: '创建成功', updateSuccess: '更新成功' },
onSuccess: () => actionRef.current?.reload(),
});
<Modal {...modalProps}>
<Form form={form} layout="vertical" onFinish={submit}>
{/* ... */}
</Form>
</Modal>useDetailPage — 详情页
import { useDetailPage } from '@aspect/shared/hooks';
import { useParams } from '@umijs/max';
const params = useParams<{ id: string }>();
const { data, loading, error, refresh } = useDetailPage<User>({
fetchApi: (id) => userService.detail(id),
id: params.id,
});useModal — 弹窗状态
import { useModal } from '@aspect/shared/hooks';
const modal = useModal<User>(); // { open, data, show(data), close() }
modal.show(user); // 打开并传入数据
modal.close(); // 关闭useDownload — 文件下载
import { useDownload } from '@aspect/shared/hooks';
const { downloading, downloadFromUrl, downloadJSON } = useDownload();
await downloadFromUrl('/api/export', '报表.xlsx');
downloadJSON(data, '数据.json');useFileUpload — 文件上传
import { useFileUpload } from '@aspect/shared/hooks';
const { uploading, uploadFile, fileList } = useFileUpload({
uploadApi: (file) => fileService.upload({ file }),
maxSize: 10 * 1024 * 1024, // 10MB
accept: '.jpg,.png,.pdf',
});其他 Hooks
import { useFormSubmit } from '@aspect/shared/hooks'; // 表单提交(防重复)
import { useDetailPageMode } from '@aspect/shared/hooks'; // 详情页模式(查看/新建/编辑)
import { useListData } from '@aspect/shared/hooks'; // 通用列表数据
import { useResponsive } from '@aspect/shared/hooks'; // 响应式断点检测
import { useIntlText } from '@aspect/shared/hooks'; // 国际化文本
import { useTableConfig } from '@aspect/shared/hooks'; // 表格列/操作配置
import { useFileSelector } from '@aspect/shared/hooks'; // 文件选择
import { useUserPreference } from '@aspect/shared/hooks'; // 用户偏好(主题/语言)Hook 开发约定(贡献者)
- 不稳定回调/配置统一通过
useLatestRef读取,避免闭包陈旧与依赖震荡。 - 可取消异步请求统一复用
src/hooks/shared-async.ts(startAbortableRequest/runAbortableTask/handleAbortableRequestError)。 - ProTable 分页请求统一复用
src/hooks/shared-request.ts,避免重复实现参数转换与响应适配。 - Hook 的
useCallback依赖仅放稳定引用,避免.current直接进入依赖数组导致误触发。
6. Styles 样式
从 @aspect/shared/styles 导入。基于 antd-style 的 CSS-in-JS 样式工厂。
import { createCommonStyles, createPageStyles, createResponsiveStyles } from '@aspect/shared/styles';
// 通用样式
const useCommonStyles = createCommonStyles();
const { styles } = useCommonStyles();
<div className={styles.flexCenter}>居中内容</div>
// 页面级样式
const usePageStyles = createPageStyles();
const { styles } = usePageStyles();
<div className={styles.pageContainer}>页面内容</div>
// 响应式样式
const useResponsiveStyles = createResponsiveStyles();7. Config 配置
从 @aspect/shared/config 导入。
UmiJS 基础配置
import { createUmiBaseConfig } from '@aspect/shared/config';
export default createUmiBaseConfig({
routes,
locale: { default: 'zh-CN', antd: true },
title: '应用标题',
publicPath: '/',
alias: { '@': './src' },
define: { 'process.env.API_BASE_URL': '/api' },
proxy: { '/api': { target: 'http://localhost:8080' } },
useProLayout: true, // 启用 ProLayout
codeSplitting: true, // 启用代码分割
antdTheme: { token: { colorPrimary: '#1890ff' } },
});内置默认值:
- hash 路由
- model + initialState + access 插件
- mako 构建
- fastRefresh 热更新
- moment → dayjs
- antd CSS 变量模式
- esbuildMinifyIIFE
App Runtime 配置
共享预设文件
// tsconfig.json — 继承 TypeScript 基础配置
{ "extends": "@aspect/shared/config/tsconfig.base.json" }
// biome.json — 继承 Biome 检查规则
{ "extends": ["@aspect/shared/config/biome.json"] }8. Types 类型
从 @aspect/shared/types 导入。仅类型定义,零运行时代码。
import type {
// API 相关
ApiResponse, // { code, msg, data }
PaginatedData, // { list, total, pageNo?, pageNum?, pageSize? }
PaginatedResponse, // ApiResponse<PaginatedData<T>>
PaginationParams, // { current?, pageSize? }
ProTableRequestParams,// PaginationParams + sort + filter
ProTableResponse, // { data, success, total }
// 认证相关
BaseUser, // { id, username?, nickname?, avatar?, roles?, permissions? }
AuthToken, // { token, tokenType?, expiresAt?, refreshToken? }
SessionConfig, // { tokenKey, ttlDays?, expireKey? }
LoginParams, // { username, password, rememberMe?, captcha? }
LoginResult, // { success, token?, user?, message? }
// 通用业务
StatusMapItem, // { text, color, icon? }
StatusMap, // Record<T, StatusMapItem>
SelectOption, // { label, value, disabled?, children? }
TreeNode, // { key, title, data?, children?, isLeaf? }
KeyValue, // { key, value }
SortOrder, // 'ascend' | 'descend' | null
SortInfo, // { field, order }
} from '@aspect/shared/types';9. Constants 常量
从 @aspect/shared/constants 导入。
import {
BREAKPOINTS, // { xs: 480, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1600 }
TABLE_DEFAULTS, // { PAGE_SIZE: 10, PAGE_SIZE_OPTIONS, SCROLL_X, ROW_KEY, ... }
DEBOUNCE_DELAY, // { SEARCH: 300, RESIZE: 200, SCROLL: 100, VALIDATE: 500 }
ANIMATION_DURATION, // { FAST: 150, NORMAL: 300, SLOW: 500 }
ICON_SIZE, // { SMALL: 14, MEDIUM: 16, LARGE: 20, XLARGE: 24 }
SPACING, // { XS: 4, SM: 8, MD: 16, LG: 24, XL: 32, PAGE: 24 }
Z_INDEX, // { DROPDOWN, FIXED, MODAL, MESSAGE, TOOLTIP }
ErrorShowType, // enum { SILENT, WARN_MESSAGE, ERROR_MESSAGE, NOTIFICATION, REDIRECT }
} from '@aspect/shared/constants';推荐使用模式
导入路径
始终使用子模块路径导入,而非主入口,以获得更好的 Tree-shaking:
// ✅ 推荐 — 按模块导入
import { formatDate, createLogger } from '@aspect/shared/utils';
import { useTableData, useModal } from '@aspect/shared/hooks';
import { StatusTag, ErrorBoundary } from '@aspect/shared/components';
import type { ApiResponse, StatusMap } from '@aspect/shared/types';
import { BREAKPOINTS, TABLE_DEFAULTS } from '@aspect/shared/constants';
// ❌ 不推荐 — 主入口导入
import { formatDate, useTableData, StatusTag } from '@aspect/shared';项目级封装层
对于需要注入项目配置的工具(如 auth、logger、加密),建议创建项目级封装文件:
src/utils/
├── auth.ts ← 基于 createAuthManager,注入项目存储键和过期策略
├── logger.ts ← 基于 createLogger,注入项目前缀
├── encrypt.ts ← 基于 createEncryption,注入项目密钥
└── index.ts ← barrel 文件封装文件模板:
// src/utils/auth.ts — 有业务封装,保留
import { createAuthManager } from '@aspect/shared/utils';
export const auth = createAuthManager({
tokenKey: 'my_session_id',
expireKey: 'my_session_expire',
ttlMs: 30 * 24 * 60 * 60 * 1000,
extraStorageKeys: ['userRoles'],
onUnauthorized: () => history.push('/login'),
});
export const { getToken, setToken, clearAuth } = auth;// src/utils/logger.ts — 极薄封装,注入前缀
import { createLogger } from '@aspect/shared/utils';
export const logger = createLogger({ prefix: 'MyApp' });Hooks 重导出
推荐在项目中创建 hooks/index.ts barrel 文件,统一导出来自 shared 的 hooks 和项目特有的 hooks:
// src/hooks/index.ts
// ===== shared hooks =====
export {
useCrudList,
useDetailPage,
useDetailPageMode,
useDownload,
useFileSelector,
useFileUpload,
useFormModal,
useFormSubmit,
useIntlText,
useListData,
useModal,
useResponsive,
useTableConfig,
useTableData,
useUserPreference,
} from '@aspect/shared/hooks';
// ===== 项目特有 hooks =====
export { usePermission } from './usePermission';
export { useTopicDrawer } from './useTopicDrawer';消费方统一从 @/hooks 导入,后续如需在 shared 和项目级实现间切换,只需修改 barrel 文件。
组件重导出
推荐为每个 shared 组件创建本地代理文件,保持 @/components/XXX 的导入一致性:
// src/components/StatusTag/index.tsx
export { StatusTag } from '@aspect/shared/components';
export type { StatusTagProps } from '@aspect/shared/components';
export type { StatusMap, StatusMapItem } from '@aspect/shared/types';如需扩展,可在同文件添加项目特有的状态映射常量:
// src/components/StatusTag/index.tsx
export { StatusTag } from '@aspect/shared/components';
export type { StatusTagProps } from '@aspect/shared/components';
export type { StatusMap, StatusMapItem } from '@aspect/shared/types';
// ===== 项目特有状态映射 =====
export const USER_STATUS_MAP = {
active: { text: '正常', color: 'green' },
disabled: { text: '已禁用', color: 'red' },
};什么时候保留中间文件
| 场景 | 建议 | |------|------| | 仅转发、无任何业务逻辑 | 保留为 barrel/代理层,提供项目级 API 边界 | | 注入项目配置(存储键、前缀、密钥等) | 保留,这是必要的业务封装 | | 适配 API 签名(返回值转换、参数默认值) | 保留,确保项目内接口一致性 | | 扩展功能(添加业务方法、错误提示集成) | 保留,合理的功能增强 |
常规操作步骤
添加新的工具函数
判断功能是否通用(3 个项目都可能用到)
- 通用 → 添加到
keel-web-shared/src/utils/对应文件 - 项目特有 → 添加到项目本地
src/utils/
- 通用 → 添加到
在
keel-web-shared/src/utils/index.ts中添加export运行
pnpm build重新构建消费项目通过
@aspect/shared/utils导入
添加新的 Hook
在
keel-web-shared/src/hooks/下创建useXxx.ts在
keel-web-shared/src/hooks/index.ts中添加导出在消费项目的
hooks/index.tsbarrel 文件中添加重导出
添加新的组件
在
keel-web-shared/src/components/下创建XxxComponent/index.tsx在
keel-web-shared/src/components/index.ts中添加导出在消费项目中创建代理文件
src/components/XxxComponent/index.tsx
添加新的 CRUD Service
// src/services/[module]/[entity].ts
import { createCrudService } from '@aspect/shared/request';
import { request } from '@umijs/max';
export interface MyEntity {
id: number;
name: string;
status: string;
}
export const myEntityService = createCrudService<MyEntity>('/api/my-entity', {
request,
// 使用默认配置,或按需覆盖
// list: { method: 'POST', path: '/list' },
});修改共享库代码(联调开发)
消费项目默认使用 link: 本地引用,配合 watch 模式即可实时联调:
# 终端 1:启动 watch 模式
cd keel-web-shared
pnpm dev
# 终端 2:启动消费项目(改动自动热更新)
cd keelcoud/keel-cloud-web
pnpm dev注意:不要手动将
link:改为版本号,发布时由deploy.py自动处理。
添加新的类型
判断类型归属:
- API 响应相关 →
types/api.ts - 认证相关 →
types/auth.ts - 通用业务类型 →
types/common.ts
- API 响应相关 →
在对应文件中添加
export interface消费项目通过
@aspect/shared/types导入
添加新的常量
在
keel-web-shared/src/constants/ui.ts中添加消费项目通过
@aspect/shared/constants导入
发布与开发
依赖引用策略
本项目采用 link 开发 + 版本发布 双模式:
- 日常开发:消费项目使用
"@aspect/shared": "link:../../keel-web-shared"本地链接,配合pnpm dev(watch) 实时生效 - 生产发布:通过
deploy.py脚本一键发布到 npm,脚本会自动处理 link ↔ 版本号的切换
一键发布(推荐)
cd keel-web-shared
# 自动 patch +1 并发布(包含 test + build + publish)
python deploy.py patch
# 自动 minor +1 并发布
python deploy.py minor
# 指定版本号发布
python deploy.py 1.0.0
# 模拟发布(不实际执行)
python deploy.py patch --dry-run
# 跳过构建(已有最新 dist)
python deploy.py patch --skip-build
# 跳过测试
python deploy.py patch --skip-test
# 发布 beta 版本
python deploy.py patch --tag betadeploy.py 会自动:① 运行测试 → ② 构建 dist → ③ 将消费项目 link: 切为 ^x.y.z → ④ 发布到 npm → ⑤ 恢复消费项目为 link: 。无需手动操作。
本地开发联调
消费项目 package.json 中默认已配置 link:,配合 watch 模式即可:
# 终端 1:启动本包 watch 模式
cd keel-web-shared
pnpm dev
# 终端 2:启动消费项目(改动自动热更新)
cd keelcoud/keel-cloud-web
pnpm dev消费此包的项目
| 项目 | 路径 | 依赖方式 |
|------|------|---------|
| keel-cloud-web | keelcoud/keel-cloud-web | link:../../keel-web-shared(开发) / ^x.y.z(生产) |
| ruiying-web | ruiying/ruiying-web | link:../../keel-web-shared(开发) / ^x.y.z(生产) |
| mc-revenue-guide-web | mc-guide/mc-revenue-guide-web | link:../../keel-web-shared(开发) / ^x.y.z(生产) |
可用脚本
| 命令 | 说明 |
|------|------|
| pnpm build | 使用 tsup 构建到 dist/(ESM + CJS + 类型声明) |
| pnpm dev | watch 模式,源码改动自动重编译 |
| pnpm test | 运行 vitest 测试 |
| pnpm test:watch | watch 模式运行测试 |
| pnpm lint | 使用 Biome 检查代码 |
| pnpm typecheck | TypeScript 类型检查 |
| python deploy.py | 一键发布到 npm(见上方说明) |
Peer Dependencies
| 依赖 | 版本 | 必需模块 |
|------|------|---------|
| dayjs | ^1.11.0 | 所有(日期格式化) |
| react | >=18.0.0 | components, hooks, styles(可选) |
| react-dom | >=18.0.0 | components(可选) |
| antd | ^6.0.0 | components, styles(可选) |
| antd-style | ^4.0.0 | styles(可选) |
| @umijs/max | ^4.0.0 | config, request(可选) |
| dompurify | ^3.0.0 | utils/sanitize(可选) |
| china-division | ^2.0.0 | utils/region(可选) |
仅使用 utils/crypto 模块时,只需安装 dayjs,其余均为可选。
License
MIT