npm package: @kaa413/shared
Description: Shared utilities, components, hooks, and tools for React + Ant Design + UmiJS projects
Latest version: 0.0.1

# @aspect/shared

> React + Ant Design + UmiJS 项目共享工具库。提供工具函数、加密、请求层、组件、Hooks、样式、配置、类型和常量，统一多项目技术标准。

## 目录

- [功能概览](#功能概览)
- [目录结构](#目录结构)
- [新项目接入](#新项目接入)
- [模块使用指南](#模块使用指南)
  - [Utils 工具函数](#1-utils-工具函数)
  - [Crypto 加密](#2-crypto-加密)
  - [Request 请求层](#3-request-请求层)
  - [Components 组件](#4-components-组件)
  - [Hooks](#5-hooks)
  - [Styles 样式](#6-styles-样式)
  - [Config 配置](#7-config-配置)
  - [Types 类型](#8-types-类型)
  - [Constants 常量](#9-constants-常量)
- [推荐使用模式](#推荐使用模式)
- [常规操作步骤](#常规操作步骤)
- [发布与开发](#发布与开发)
- [Peer Dependencies](#peer-dependencies)

---

## 功能概览

| 模块 | 导入路径 | 功能 |
|------|---------|------|
| **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. 安装依赖

```bash
# 本地链接（monorepo 开发）
pnpm add @aspect/shared@link:../../keel-web-shared

# 或从 npm 安装
pnpm add @aspect/shared
```

### 2. TypeScript 路径映射

在 `tsconfig.json` 中配置路径别名（本地链接时需要）：

```jsonc
{
  "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` 中继承预设：

```json
{
  "extends": ["@aspect/shared/config/biome.json"]
}
```

### 4. UmiJS 配置

```typescript
// 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. 请求配置

```typescript
// 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

```tsx
// 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 个子模块：

#### 认证管理

```typescript
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
```

#### 格式化

```typescript
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%'
```

#### 存储

```typescript
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 辅助

```typescript
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),
  },
);
```

#### 其他工具

```typescript
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 原生实现）。

```typescript
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');
```

底层能力也可单独使用：

```typescript
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）

见 [新项目接入 > 请求配置](#5-请求配置)。

#### CRUD Service 工厂

消除手写重复 service 代码。为业务实体自动生成 `list` / `detail` / `create` / `update` / `remove` / `toggleStatus` / `save` 方法。

```typescript
// 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` |

#### 文件服务工厂

```typescript
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');
```

#### 请求常量和错误类型

```typescript
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） | — |

#### 基本用法

```tsx
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

```tsx
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

```tsx
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 数据请求

```tsx
import { useTableData } from '@aspect/shared/hooks';

const { request, actionRef } = useTableData<User>({
  fetchApi: (params) => userService.list(params),
});

<ProTable request={request} actionRef={actionRef} />
```

#### useCrudList — 完整 CRUD 列表管理

```tsx
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 — 表单弹窗

```tsx
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 — 详情页

```tsx
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 — 弹窗状态

```tsx
import { useModal } from '@aspect/shared/hooks';

const modal = useModal<User>();  // { open, data, show(data), close() }
modal.show(user);    // 打开并传入数据
modal.close();       // 关闭
```

#### useDownload — 文件下载

```tsx
import { useDownload } from '@aspect/shared/hooks';

const { downloading, downloadFromUrl, downloadJSON } = useDownload();
await downloadFromUrl('/api/export', '报表.xlsx');
downloadJSON(data, '数据.json');
```

#### useFileUpload — 文件上传

```tsx
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

```tsx
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 样式工厂。

```tsx
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 基础配置

```typescript
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 配置

见 [新项目接入 > App Runtime](#6-app-runtime)。

#### 共享预设文件

```typescript
// tsconfig.json — 继承 TypeScript 基础配置
{ "extends": "@aspect/shared/config/tsconfig.base.json" }

// biome.json — 继承 Biome 检查规则
{ "extends": ["@aspect/shared/config/biome.json"] }
```

---

### 8. Types 类型

从 `@aspect/shared/types` 导入。仅类型定义，零运行时代码。

```typescript
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` 导入。

```typescript
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：

```typescript
// ✅ 推荐 — 按模块导入
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 文件
```

**封装文件模板：**

```typescript
// 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;
```

```typescript
// src/utils/logger.ts — 极薄封装，注入前缀
import { createLogger } from '@aspect/shared/utils';
export const logger = createLogger({ prefix: 'MyApp' });
```

### Hooks 重导出

**推荐在项目中创建 `hooks/index.ts` barrel 文件**，统一导出来自 shared 的 hooks 和项目特有的 hooks：

```typescript
// 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` 的导入一致性：

```tsx
// 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';
```

如需扩展，可在同文件添加项目特有的状态映射常量：

```tsx
// 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 签名（返回值转换、参数默认值） | 保留，确保项目内接口一致性 |
| 扩展功能（添加业务方法、错误提示集成） | 保留，合理的功能增强 |

---

## 常规操作步骤

### 添加新的工具函数

1. 判断功能是否通用（3 个项目都可能用到）
   - **通用** → 添加到 `keel-web-shared/src/utils/` 对应文件
   - **项目特有** → 添加到项目本地 `src/utils/`

2. 在 `keel-web-shared/src/utils/index.ts` 中添加 `export`

3. 运行 `pnpm build` 重新构建

4. 消费项目通过 `@aspect/shared/utils` 导入

### 添加新的 Hook

1. 在 `keel-web-shared/src/hooks/` 下创建 `useXxx.ts`

2. 在 `keel-web-shared/src/hooks/index.ts` 中添加导出

3. 在消费项目的 `hooks/index.ts` barrel 文件中添加重导出

### 添加新的组件

1. 在 `keel-web-shared/src/components/` 下创建 `XxxComponent/index.tsx`

2. 在 `keel-web-shared/src/components/index.ts` 中添加导出

3. 在消费项目中创建代理文件 `src/components/XxxComponent/index.tsx`

### 添加新的 CRUD Service

```typescript
// 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 模式即可实时联调：

```bash
# 终端 1：启动 watch 模式
cd keel-web-shared
pnpm dev

# 终端 2：启动消费项目（改动自动热更新）
cd keelcoud/keel-cloud-web
pnpm dev
```

> 注意：不要手动将 `link:` 改为版本号，发布时由 `deploy.py` 自动处理。

### 添加新的类型

1. 判断类型归属：
   - API 响应相关 → `types/api.ts`
   - 认证相关 → `types/auth.ts`
   - 通用业务类型 → `types/common.ts`

2. 在对应文件中添加 `export interface`

3. 消费项目通过 `@aspect/shared/types` 导入

### 添加新的常量

1. 在 `keel-web-shared/src/constants/ui.ts` 中添加

2. 消费项目通过 `@aspect/shared/constants` 导入

---

## 发布与开发

### 依赖引用策略

本项目采用 **link 开发 + 版本发布** 双模式：

- **日常开发**：消费项目使用 `"@aspect/shared": "link:../../keel-web-shared"` 本地链接，配合 `pnpm dev` (watch) 实时生效
- **生产发布**：通过 `deploy.py` 脚本一键发布到 npm，脚本会自动处理 link ↔ 版本号的切换

### 一键发布（推荐）

```bash
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 beta
```

> deploy.py 会自动：① 运行测试 → ② 构建 dist → ③ 将消费项目 link: 切为 ^x.y.z → ④ 发布到 npm → ⑤ 恢复消费项目为 link: 。无需手动操作。

### 本地开发联调

消费项目 package.json 中默认已配置 `link:`，配合 watch 模式即可：

```bash
# 终端 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