npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

keel-web-shared

v0.0.1

Published

Shared utilities, components, hooks, and tools for React + Ant Design + UmiJS projects

Readme

keel-web-shared

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

目录


功能概览

| 模块 | 导入路径 | 功能 | |------|---------|------| | Utils | keel-web-shared/utils | 格式化、验证、存储(带 TTL)、日志、剪贴板、HTML 过滤、权限、认证管理、树操作、地区数据、水印 | | Crypto | keel-web-shared/crypto | AES-256-CBC + HMAC-SHA256 端到端加密(Web Crypto API 原生实现) | | Request | keel-web-shared/request | UmiJS 请求配置工厂(认证拦截、加密拦截、错误处理)、CRUD Service 工厂、文件服务工厂 | | Components | keel-web-shared/components | StatusTag、ErrorBoundary、ActionColumn、RecycleBinModal、StatCard、DynamicFormModal、PageContainer 等 14 个 Ant Design 组件 | | Hooks | keel-web-shared/hooks | useTableData、useCrudList、useDetailPage、useFormModal、useFormSubmit、useModal、useFileUpload、useDownload 等 16 个 React Hooks | | Styles | keel-web-shared/styles | antd-style 样式工厂(通用样式、页面样式、响应式样式) | | Config | keel-web-shared/config | UmiJS 基础配置工厂、App Runtime 工厂(getInitialState + layout)、Biome/TSConfig 预设 | | Types | keel-web-shared/types | ApiResponse、PaginatedData、BaseUser、AuthToken、StatusMap 等通用类型定义 | | Constants | keel-web-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 keel-web-shared@link:../../keel-web-shared

# 或从 npm 安装
pnpm add keel-web-shared

2. TypeScript 路径映射

tsconfig.json 中配置路径别名(本地链接时需要):

{
  "extends": "keel-web-shared/config/tsconfig.base.json",
  "compilerOptions": {
    "paths": {
      "keel-web-shared": ["../../keel-web-shared/src/index"],
      "keel-web-shared/*": ["../../keel-web-shared/src/*/index"]
    }
  }
}

3. Biome 代码检查

biome.json 中继承预设:

{
  "extends": ["keel-web-shared/config/biome.json"]
}

4. UmiJS 配置

// config/config.ts
import { createUmiBaseConfig } from 'keel-web-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 'keel-web-shared/request';
import { createEncryption } from 'keel-web-shared/crypto';
import { getMessageApi } from 'keel-web-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 'keel-web-shared/config';
import { GlobalAntdHolder } from 'keel-web-shared/components';
import { initSanitizer, initFormatters, createUserWatermark } from 'keel-web-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 工具函数

keel-web-shared/utils 导入。包含 19 个子模块:

认证管理

import { createAuthManager, emitLoginRequired, LOGIN_REQUIRED_EVENT } from 'keel-web-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 'keel-web-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 'keel-web-shared/utils';

safeSetStorage('key', { foo: 'bar' });     // 安全写入 (自动 JSON.stringify)
const val = safeGetStorage<MyType>('key');  // 安全读取 (自动 JSON.parse)
safeRemoveStorage('key');                   // 安全删除
clearStorage();                             // 清除所有

API 辅助

import { isApiSuccess, executeApiAction } from 'keel-web-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 'keel-web-shared/utils';
import { copyToClipboard } from 'keel-web-shared/utils';
import { sanitizeHtml, initSanitizer } from 'keel-web-shared/utils';
import { openInNewTab } from 'keel-web-shared/utils';
import { validatePhone, validateEmail, validateFileSize, validateFileType } from 'keel-web-shared/utils';
import { createValueEnum, createSelectOptionsFromMap } from 'keel-web-shared/utils';
import { setRegionDataSource, getRegionData, getCityData } from 'keel-web-shared/utils';
import { createUserWatermark } from 'keel-web-shared/utils';
import { extractErrorMessage } from 'keel-web-shared/utils';
import { initPerformanceMonitoring } from 'keel-web-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 加密

keel-web-shared/crypto 导入。当前内置算法为 aes-cbc-hmac(Web Crypto API 原生实现)。

import {
  createEncryption,
  getProviderType,
  hashPassword,
  isUsingNativeProvider,
} from 'keel-web-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 'keel-web-shared/crypto';

加密方案说明

SDK 默认使用 Web Crypto API 原生实现:

| 运行条件 | 使用的实现 | 性能 | |------|-----------|------| | 支持 Web Crypto API | Web Crypto API 原生 | ⚡ 性能最优 |


3. Request 请求层

keel-web-shared/request 导入。基于 UmiJS request 插件。

一站式配置(createRequestConfig)

新项目接入 > 请求配置

CRUD Service 工厂

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

// src/services/user.ts
import { createCrudService } from 'keel-web-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 'keel-web-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 'keel-web-shared/request';

// ErrorShowType 枚举
ErrorShowType.SILENT;        // 0 - 不显示
ErrorShowType.WARN_MESSAGE;  // 1 - 警告
ErrorShowType.ERROR_MESSAGE; // 2 - 错误
ErrorShowType.NOTIFICATION;  // 3 - 通知框
ErrorShowType.REDIRECT;      // 9 - 跳转

4. Components 组件

keel-web-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 'keel-web-shared/components';
import type { StatusMap, ActionItem } from 'keel-web-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 'keel-web-shared/components';
import type { StatCardGridItem } from 'keel-web-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 'keel-web-shared/components';
import type { DynamicFormField } from 'keel-web-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

keel-web-shared/hooks 导入。共 16 个 React Hooks。

useTableData — ProTable 数据请求

import { useTableData } from 'keel-web-shared/hooks';

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

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

useCrudList — 完整 CRUD 列表管理

import { useCrudList } from 'keel-web-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 'keel-web-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 'keel-web-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 'keel-web-shared/hooks';

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

useDownload — 文件下载

import { useDownload } from 'keel-web-shared/hooks';

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

useFileUpload — 文件上传

import { useFileUpload } from 'keel-web-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 'keel-web-shared/hooks';      // 表单提交(防重复)
import { useDetailPageMode } from 'keel-web-shared/hooks';  // 详情页模式(查看/新建/编辑)
import { useListData } from 'keel-web-shared/hooks';        // 通用列表数据
import { useResponsive } from 'keel-web-shared/hooks';      // 响应式断点检测
import { useIntlText } from 'keel-web-shared/hooks';        // 国际化文本
import { useTableConfig } from 'keel-web-shared/hooks';     // 表格列/操作配置
import { useFileSelector } from 'keel-web-shared/hooks';    // 文件选择
import { useUserPreference } from 'keel-web-shared/hooks';  // 用户偏好(主题/语言)

Hook 开发约定(贡献者)

  • 不稳定回调/配置统一通过 useLatestRef 读取,避免闭包陈旧与依赖震荡。
  • 可取消异步请求统一复用 src/hooks/shared-async.tsstartAbortableRequest / runAbortableTask / handleAbortableRequestError)。
  • ProTable 分页请求统一复用 src/hooks/shared-request.ts,避免重复实现参数转换与响应适配。
  • Hook 的 useCallback 依赖仅放稳定引用,避免 .current 直接进入依赖数组导致误触发。

6. Styles 样式

keel-web-shared/styles 导入。基于 antd-style 的 CSS-in-JS 样式工厂。

import { createCommonStyles, createPageStyles, createResponsiveStyles } from 'keel-web-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 配置

keel-web-shared/config 导入。

UmiJS 基础配置

import { createUmiBaseConfig } from 'keel-web-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

共享预设文件

// tsconfig.json — 继承 TypeScript 基础配置
{ "extends": "keel-web-shared/config/tsconfig.base.json" }

// biome.json — 继承 Biome 检查规则
{ "extends": ["keel-web-shared/config/biome.json"] }

8. Types 类型

keel-web-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 'keel-web-shared/types';

9. Constants 常量

keel-web-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 'keel-web-shared/constants';

推荐使用模式

导入路径

始终使用子模块路径导入,而非主入口,以获得更好的 Tree-shaking:

// ✅ 推荐 — 按模块导入
import { formatDate, createLogger } from 'keel-web-shared/utils';
import { useTableData, useModal } from 'keel-web-shared/hooks';
import { StatusTag, ErrorBoundary } from 'keel-web-shared/components';
import type { ApiResponse, StatusMap } from 'keel-web-shared/types';
import { BREAKPOINTS, TABLE_DEFAULTS } from 'keel-web-shared/constants';

// ❌ 不推荐 — 主入口导入
import { formatDate, useTableData, StatusTag } from 'keel-web-shared';

项目级封装层

对于需要注入项目配置的工具(如 auth、logger、加密),建议创建项目级封装文件:

src/utils/
├── auth.ts       ← 基于 createAuthManager,注入项目存储键和过期策略
├── logger.ts     ← 基于 createLogger,注入项目前缀
├── encrypt.ts    ← 基于 createEncryption,注入项目密钥
└── index.ts      ← barrel 文件

封装文件模板:

// src/utils/auth.ts — 有业务封装,保留
import { createAuthManager } from 'keel-web-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 'keel-web-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 'keel-web-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 'keel-web-shared/components';
export type { StatusTagProps } from 'keel-web-shared/components';
export type { StatusMap, StatusMapItem } from 'keel-web-shared/types';

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

// src/components/StatusTag/index.tsx
export { StatusTag } from 'keel-web-shared/components';
export type { StatusTagProps } from 'keel-web-shared/components';
export type { StatusMap, StatusMapItem } from 'keel-web-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. 消费项目通过 keel-web-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

// src/services/[module]/[entity].ts
import { createCrudService } from 'keel-web-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 自动处理。

添加新的类型

  1. 判断类型归属:

    • API 响应相关 → types/api.ts
    • 认证相关 → types/auth.ts
    • 通用业务类型 → types/common.ts
  2. 在对应文件中添加 export interface

  3. 消费项目通过 keel-web-shared/types 导入

添加新的常量

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

  2. 消费项目通过 keel-web-shared/constants 导入


发布与开发

依赖引用策略

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

  • 日常开发:消费项目使用 "keel-web-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 beta

deploy.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