Browse Source

refactor: remove duplicate cheatsheet .ts wrappers, import .md directly (#11717)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: zoomdong <1344492820@qq.com>
pull/11718/head
Alex Zhu 5 months ago
committed by GitHub
parent
commit
8212d72a90
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      config/config.ts
  2. 4
      config/md-raw-loader.cjs
  3. 475
      docs/cheatsheet.en-US.ts
  4. 475
      docs/cheatsheet.zh-CN.ts
  5. 1
      jest.config.ts
  6. 58
      src/pages/Welcome-dark.css
  7. 2
      src/pages/Welcome.css
  8. 17
      src/pages/Welcome.tsx
  9. 4
      src/typings.d.ts
  10. 1
      tailwind.config.js
  11. 1
      tests/__mocks__/raw.js

11
config/config.ts

@ -193,7 +193,16 @@ export default defineConfig({
include: ['src/pages/**/_mock.ts'], include: ['src/pages/**/_mock.ts'],
exclude: ['mock/requestRecord.mock.js'], exclude: ['mock/requestRecord.mock.js'],
}, },
utoopack: {}, utoopack: {
module: {
rules: {
'*.md': {
loaders: [{ loader: join(__dirname, 'md-raw-loader.cjs') }],
as: '*.js',
},
},
},
},
requestRecord: {}, requestRecord: {},
exportStatic: {}, exportStatic: {},
define: { define: {

4
config/md-raw-loader.cjs

@ -0,0 +1,4 @@
module.exports = function mdRawLoader(source) {
this.cacheable?.();
return `export default ${JSON.stringify(String(source))};`;
};

475
docs/cheatsheet.en-US.ts

@ -1,475 +0,0 @@
const content = `# Ant Design Pro Cheatsheet
![Ant Design Pro](https://mdn.alipayobjects.com/huamei_fkc4p0/afts/img/A*EX3ISYC2ghEAAAAAddAAAAgAeobDAQ/original)
## Getting Started
**Requirements:** Node.js >= 20
**Create a project:**
\`\`\`bash
git clone --depth 1 https://github.com/ant-design/ant-design-pro.git my-project
cd my-project
npm install
\`\`\`
The project offers two modes:
- **Full mode**: Includes all demo pages (Dashboard, Forms, Lists, Access, etc.), great for reference and learning
- **Simple mode**: Only keeps login page and basic layout, ideal for starting from scratch
Switch to simple mode:
\`\`\`bash
git add -A && git commit -m "chore: save before simple" # Commit first to allow revert
npm run simple # Remove demo pages and unused deps
npm install # Update dependencies
\`\`\`
> 💡 Start with full mode to learn the project structure, then switch to simple mode for development.
**Directory structure:**
\`\`\`
├── config/ # Configuration (routes, proxy, theme)
│ ├── config.ts # Main config
│ ├── routes.ts # Route definitions
│ ├── defaultSettings.ts # Layout & theme settings
│ └── proxy.ts # Dev proxy config
├── mock/ # Mock data
├── src/
│ ├── components/ # Shared components
│ ├── locales/ # i18n resources
│ ├── models/ # Global data models
│ ├── services/ # API service layer
│ ├── utils/ # Utility functions
│ ├── access.ts # Permission definitions
│ └── app.tsx # Runtime configuration
├── docs/ # Project documentation
└── types/ # Type declarations
\`\`\`
**Common commands:**
| Command | Description |
|---------|-------------|
| \`npm start\` | Start dev server (UMI_ENV=dev, with Mock) |
| \`npm run dev\` | Start dev server (no Mock) |
| \`npm run start:dev\` | Same as dev, UMI_ENV=dev, no Mock |
| \`npm run start:no-mock\` | Start without Mock |
| \`npm run start:pre\` | Pre-production environment |
| \`npm run start:test\` | Test environment |
| \`npm run build\` | Build for production |
| \`npm run preview\` | Build and preview locally (port 8000) |
| \`npm run analyze\` | Analyze bundle size |
| \`npm run lint\` | Lint (Biome + TypeScript) |
| \`npm run biome\` | Auto-fix with Biome |
| \`npm test\` | Run tests |
| \`npm run test:coverage\` | Test with coverage |
| \`npm run openapi\` | Generate API code from OpenAPI schema |
| \`npm run simple\` | Strip demo pages and unused deps |
> 💡 \`UMI_ENV\` switches environment configs, mapping to different proxy rules in \`config/proxy.ts\`.
> 💡 \`npm run simple\` removes demo pages (dashboard, form, list etc.) and unused dependencies (plots, etc.), replacing with minimal routes. Ideal for starting from scratch. **Commit your code first so you can revert if needed.**
**Build tool:** This project uses [utoopack](https://github.com/utooland/utoo) (a next-gen bundler powered by Turbopack) as the default build tool, configured via the \`utoopack\` field in \`config/config.ts\`. utoopack is Webpack-compatible and supports \`module.rules\` for custom loaders.
→ See [umi Getting Started](https://umijs.org/docs/guides/getting-started), [utoo Docs](https://utoo.land)
## Routes & Menu
**Route config** is in \`config/routes.ts\`:
\`\`\`ts
export default [
{
path: '/welcome',
name: 'welcome', // maps to menu.welcome i18n key
icon: 'home',
component: './Welcome',
},
{
path: '/admin',
name: 'admin',
icon: 'crown',
access: 'canAdmin', // route-level access control
routes: [...],
},
{ path: '/', redirect: '/dashboard/analysis' },
{ component: '404', path: './*' },
];
\`\`\`
**Route navigation:**
\`\`\`tsx
import { useNavigate, useParams, useLocation } from '@umijs/max';
const navigate = useNavigate();
navigate('/dashboard'); // navigate
navigate(-1); // go back
const { id } = useParams(); // dynamic param /user/:id
const location = useLocation(); // current route info
\`\`\`
**Menu & access:** The \`access\` field in route config controls menu visibility — unauthorized routes won't appear in the menu.
> 💡 The \`name\` field is automatically mapped to \`menu.xxx\` i18n keys. Configure translations in \`src/locales/\`.
→ See [umi Routes](https://umijs.org/docs/guides/routes), [Umi Max Layout & Menu](https://umijs.org/docs/max/layout-menu)
## Layout
**ProLayout config** is in \`config/defaultSettings.ts\`:
\`\`\`ts
export default {
navTheme: 'light', // nav theme: light / dark
colorPrimary: '#1890ff', // primary color
layout: 'mix', // layout mode: side / top / mix
contentWidth: 'Fluid', // content width: Fluid / Fixed
fixSiderbar: true, // fixed sidebar
};
\`\`\`
**Layout modes:**
- \`side\` — Side navigation
- \`top\` — Top navigation
- \`mix\` — Top + side mixed navigation
**Page container:**
\`\`\`tsx
import { PageContainer } from '@ant-design/pro-components';
const Page = () => (
<PageContainer
header={{ title: 'Page Title' }}
content="Page description"
>
{/* Page content */}
</PageContainer>
);
\`\`\`
**Custom areas:** Top-right \`src/components/RightContent\`, footer \`src/components/Footer\`.
→ See [Umi Max Layout & Menu](https://umijs.org/docs/max/layout-menu)
## Data Flow
**useModel — lightweight global state:** Create a file in \`src/models/\` to auto-register:
\`\`\`ts
// src/models/counter.ts
import { useState } from 'react';
export default function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return { count, increment };
}
\`\`\`
\`\`\`tsx
// Use in any component
import { useModel } from '@umijs/max';
const { count, increment } = useModel('counter');
\`\`\`
**useRequest — data fetching:**
\`\`\`tsx
import { useRequest } from '@umijs/max';
const { data, loading, error } = useRequest(getUserInfo);
\`\`\`
**React Query — server state management:**
\`\`\`tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Query
const { data, isLoading } = useQuery({
queryKey: ['user', id],
queryFn: () => getUser(id),
});
// Mutation
const mutation = useMutation({
mutationFn: updateUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
},
});
\`\`\`
**Initial state — getInitialState:** Define in \`src/app.tsx\`, accessible globally:
\`\`\`tsx
// src/app.tsx
export async function getInitialState() {
const currentUser = await fetchUserInfo();
return { currentUser };
}
// Use in components
import { useModel } from '@umijs/max';
const { initialState } = useModel('@@initialState');
\`\`\`
> 💡 \`getInitialState\` runs once on app startup, ideal for fetching global info (user identity, permissions).
→ See [Umi Max Data Flow](https://umijs.org/docs/max/data-flow)
## Request
**Request config** is in \`src/app.tsx\`:
\`\`\`ts
export const request: RequestConfig = {
baseURL: 'https://api.example.com',
timeout: 10000,
requestInterceptors: [], // request interceptors
responseInterceptors: [], // response interceptors
};
\`\`\`
**Error handling** is in \`src/requestErrorConfig.ts\`, customize error code mapping and notification logic.
**Using request:**
\`\`\`tsx
import { request } from '@umijs/max';
// GET
const data = await request('/api/users', { params: { page: 1 } });
// POST
await request('/api/users', { method: 'POST', data: { name: 'test' } });
\`\`\`
**OpenAPI code generation:**
\`\`\`bash
npm run openapi
\`\`\`
Auto-generates API calling code under \`src/services/\` based on \`config/openapi.json\`.
> 💡 Generated code uses \`import { request } from '@umijs/max'\` directly — no manual wrapping needed.
→ See [Umi Max Request](https://umijs.org/docs/max/request)
## Access Control
**Define permissions** in \`src/access.ts\`:
\`\`\`ts
export default function access(initialState: { currentUser?: API.CurrentUser }) {
const { currentUser } = initialState;
return {
canAdmin: currentUser?.access === 'admin',
canUser: !!currentUser,
};
}
\`\`\`
**Route-level access:** Add \`access\` field in route config:
\`\`\`ts
{ path: '/admin', access: 'canAdmin' }
\`\`\`
**Component-level access:**
\`\`\`tsx
import { Access, useAccess } from '@umijs/max';
// Declarative
<Access accessible={access.canAdmin}>
<AdminPanel />
</Access>
// Imperative
const access = useAccess();
if (access.canAdmin) { /* ... */ }
\`\`\`
→ See [Umi Max Access](https://umijs.org/docs/max/access)
## Internationalization
**Config** in \`config/config.ts\`:
\`\`\`ts
locale: {
default: 'zh-CN',
antd: true, // sync antd component locale
baseNavigator: true, // follow browser language
},
\`\`\`
**File structure:**
\`\`\`
src/locales/
├── zh-CN.ts # Chinese entry
├── zh-CN/
│ ├── menu.ts # Menu translations
│ ├── pages.ts # Page translations
│ └── ...
├── en-US.ts # English entry
└── en-US/
└── ...
\`\`\`
**Usage:**
\`\`\`tsx
import { useIntl, FormattedMessage } from '@umijs/max';
// Hook
const intl = useIntl();
intl.formatMessage({ id: 'menu.welcome' });
// Component
<FormattedMessage id="menu.welcome" />
\`\`\`
**Switch locale:**
\`\`\`tsx
import { setLocale } from '@umijs/max';
setLocale('en-US', false); // false = no page reload
\`\`\`
→ See [Umi Max i18n](https://umijs.org/docs/max/i18n)
## Styling
**CSS Modules:** Name files \`*.module.less\` or \`*.module.css\`:
\`\`\`css
/* example.module.less */
.container { padding: 24px; }
.title { font-size: 16px; }
\`\`\`
\`\`\`tsx
import styles from './example.module.less';
<div className={styles.container} />
\`\`\`
**antd-style (CSS-in-JS):**
\`\`\`tsx
import { createStyles } from 'antd-style';
const useStyles = createStyles(({ token, css }) => ({
card: css\`
background: \${token.colorBgContainer};
border-radius: \${token.borderRadiusLG}px;
\`,
}));
const { styles } = useStyles();
<div className={styles.card} />
\`\`\`
**Tailwind CSS (v4):** Use directly in className:
\`\`\`tsx
<div className="flex items-center gap-4 p-6 rounded-lg bg-white dark:bg-[#141414]" />
\`\`\`
**Dynamic theme:** Set in \`config/config.ts\` \`antd\` config:
\`\`\`ts
antd: {
configProvider: {
theme: {
token: {
colorPrimary: '#1890ff',
borderRadius: 6,
},
},
},
},
\`\`\`
Use SettingDrawer in dev mode to switch themes in real-time.
> 💡 Three styling approaches can coexist: Tailwind for layout, CSS Modules for component styles, antd-style when consuming theme tokens.
→ See [umi Styling](https://umijs.org/docs/guides/styling), [Umi Max antd Dynamic Theme](https://umijs.org/docs/max/antd#动态主题)
## Testing & Debugging
**Jest testing:**
\`\`\`bash
npm test # Run all tests
npm run test:coverage # With coverage report
npm run test:update # Update snapshots
\`\`\`
Test files go next to the component, named \`*.test.ts(x)\`.
**Mock data:** Create files in \`mock/\`:
\`\`\`ts
// mock/user.ts
export default {
'GET /api/currentUser': { name: 'Serati Ma', access: 'admin' },
'POST /api/login': (req, res) => { res.end('ok'); },
};
\`\`\`
Umi auto-registers mocks, active in dev mode.
**Proxy config** is in \`config/proxy.ts\`:
\`\`\`ts
export default {
dev: {
'/api/': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
};
\`\`\`
> 💡 Use \`MOCK=none\` to skip mock and proxy to backend: \`npm run start:no-mock\`.
→ See [umi Testing](https://umijs.org/docs/guides/testing), [umi Mock](https://umijs.org/docs/guides/mock)
## FAQ
**Q: How to disable Mock?**
\`npm run start:no-mock\` or \`cross-env MOCK=none max dev\`
**Q: How to change the primary color?**
Edit \`colorPrimary\` in \`config/defaultSettings.ts\`. Use SettingDrawer for live preview in dev mode.
**Q: How to add a new page?**
1. Create component in \`src/pages/\` 2. Add route in \`config/routes.ts\` 3. Add menu translation in \`src/locales/\` (if needed)
**Q: How to add global state?**
Create a file in \`src/models/\` exporting a custom Hook, then use \`useModel('filename')\` in components.
**Q: How to deploy?**
\`npm run build\` generates \`dist/\`. Deploy to any static file server. Set \`publicPath\` for non-root deployments.
**Q: How to use OpenAPI code generation?**
1. Configure \`openAPI\` in \`config/config.ts\` 2. Run \`npm run openapi\` 3. Code is auto-generated under \`src/services/\`
→ See [umi FAQ](https://umijs.org/docs/guides/faq)`;
export default content;

475
docs/cheatsheet.zh-CN.ts

@ -1,475 +0,0 @@
const content = `# Ant Design Pro Cheatsheet
![Ant Design Pro](https://mdn.alipayobjects.com/huamei_fkc4p0/afts/img/A*EX3ISYC2ghEAAAAAddAAAAgAeobDAQ/original)
## 快速开始
**环境要求:** Node.js >= 20
**创建项目:**
\`\`\`bash
git clone --depth 1 https://github.com/ant-design/ant-design-pro.git my-project
cd my-project
npm install
\`\`\`
项目提供两种模式:
- **完整模式**:包含所有示例页面(Dashboard、表单、列表、权限等),适合参考学习
- **精简模式**:仅保留登录页和基础布局,适合从零开发
切换精简模式:
\`\`\`bash
git add -A && git commit -m "chore: save before simple" # 先提交,以便回退
npm run simple # 删除示例页面和多余依赖
npm install # 更新依赖
\`\`\`
> 💡 建议先用完整模式熟悉项目结构,再切换精简模式开始开发。
**目录结构:**
\`\`\`
├── config/ # 配置文件(路由、代理、主题)
│ ├── config.ts # 主配置
│ ├── routes.ts # 路由配置
│ ├── defaultSettings.ts # 布局主题设置
│ └── proxy.ts # 开发代理配置
├── mock/ # Mock 数据
├── src/
│ ├── components/ # 公共组件
│ ├── locales/ # 国际化资源
│ ├── models/ # 全局数据模型
│ ├── services/ # API 服务层
│ ├── utils/ # 工具函数
│ ├── access.ts # 权限定义
│ └── app.tsx # 运行时配置
├── docs/ # 项目文档
└── types/ # 类型声明
\`\`\`
**常用命令:**
| 命令 | 说明 |
|------|------|
| \`npm start\` | 启动开发服务器(UMI_ENV=dev,带 Mock) |
| \`npm run dev\` | 启动开发服务器(无 Mock) |
| \`npm run start:dev\` | 同 dev,UMI_ENV=dev,无 Mock |
| \`npm run start:no-mock\` | 无 Mock 启动 |
| \`npm run start:pre\` | 预发布环境启动 |
| \`npm run start:test\` | 测试环境启动 |
| \`npm run build\` | 构建生产产物 |
| \`npm run preview\` | 构建并本地预览(端口 8000) |
| \`npm run analyze\` | 构建产物体积分析 |
| \`npm run lint\` | 代码检查(Biome + TypeScript) |
| \`npm run biome\` | Biome 自动修复 |
| \`npm test\` | 运行测试 |
| \`npm run test:coverage\` | 测试覆盖率 |
| \`npm run openapi\` | 根据 OpenAPI 生成 API 代码 |
| \`npm run simple\` | 精简模式(删除示例页面和多余依赖) |
> 💡 \`UMI_ENV\` 用于切换环境配置,对应 \`config/proxy.ts\` 中的不同代理规则。
> 💡 \`npm run simple\` 会删除示例页面(dashboard、form、list 等)和多余依赖(plots 等),替换为精简路由,适合从零开始开发。**建议先提交代码,以便需要时回退。**
**构建工具:** 本项目使用 [utoopack](https://github.com/utooland/utoo)(基于 Turbopack 的新一代打包器)作为默认构建工具,通过 \`config/config.ts\` 中的 \`utoopack\` 字段配置。utoopack 兼容 Webpack 配置格式,支持 \`module.rules\` 配置自定义加载器。
→ 更多内容见 [umi 入门指南](https://umijs.org/docs/guides/getting-started)、[utoo 文档](https://utoo.land)
## 路由与菜单
**路由配置** 位于 \`config/routes.ts\`:
\`\`\`ts
export default [
{
path: '/welcome',
name: 'welcome', // 对应 menu.welcome 国际化 key
icon: 'home',
component: './Welcome',
},
{
path: '/admin',
name: 'admin',
icon: 'crown',
access: 'canAdmin', // 路由级权限控制
routes: [...],
},
{ path: '/', redirect: '/dashboard/analysis' },
{ component: '404', path: './*' },
];
\`\`\`
**路由导航:**
\`\`\`tsx
import { useNavigate, useParams, useLocation } from '@umijs/max';
const navigate = useNavigate();
navigate('/dashboard'); // 跳转
navigate(-1); // 后退
const { id } = useParams(); // 获取动态参数 /user/:id
const location = useLocation(); // 当前路由信息
\`\`\`
**菜单与权限联动:** 路由配置中 \`access\` 字段控制菜单可见性,未授权路由不会出现在菜单中。
> 💡 \`name\` 字段自动映射为 \`menu.xxx\` 国际化 key,在 \`src/locales/\` 中配置翻译。
→ 更多内容见 [umi 路由文档](https://umijs.org/docs/guides/routes)、[Umi Max 布局与菜单](https://umijs.org/docs/max/layout-menu)
## 布局
**ProLayout 配置** 位于 \`config/defaultSettings.ts\`:
\`\`\`ts
export default {
navTheme: 'light', // 导航主题:light / dark
colorPrimary: '#1890ff', // 主题色
layout: 'mix', // 布局模式:side / top / mix
contentWidth: 'Fluid', // 内容宽度:Fluid / Fixed
fixSiderbar: true, // 固定侧边栏
};
\`\`\`
**布局模式:**
- \`side\` — 左侧导航
- \`top\` — 顶部导航
- \`mix\` — 顶部 + 侧边混合导航
**页面容器:**
\`\`\`tsx
import { PageContainer } from '@ant-design/pro-components';
const Page = () => (
<PageContainer
header={{ title: '页面标题' }}
content="页面描述"
>
{/* 页面内容 */}
</PageContainer>
);
\`\`\`
**自定义区域:** 右上角 \`src/components/RightContent\`,底部 \`src/components/Footer\`。
→ 更多内容见 [Umi Max 布局与菜单](https://umijs.org/docs/max/layout-menu)
## 数据流
**useModel — 轻量全局状态:** 在 \`src/models/\` 下创建文件即自动注册:
\`\`\`ts
// src/models/counter.ts
import { useState } from 'react';
export default function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return { count, increment };
}
\`\`\`
\`\`\`tsx
// 任意组件中使用
import { useModel } from '@umijs/max';
const { count, increment } = useModel('counter');
\`\`\`
**useRequest — 数据请求:**
\`\`\`tsx
import { useRequest } from '@umijs/max';
const { data, loading, error } = useRequest(getUserInfo);
\`\`\`
**React Query — 服务端状态管理:**
\`\`\`tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// 查询
const { data, isLoading } = useQuery({
queryKey: ['user', id],
queryFn: () => getUser(id),
});
// 变更
const mutation = useMutation({
mutationFn: updateUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
},
});
\`\`\`
**初始状态 — getInitialState:** 在 \`src/app.tsx\` 中定义,全局可访问:
\`\`\`tsx
// src/app.tsx
export async function getInitialState() {
const currentUser = await fetchUserInfo();
return { currentUser };
}
// 组件中使用
import { useModel } from '@umijs/max';
const { initialState } = useModel('@@initialState');
\`\`\`
> 💡 \`getInitialState\` 在应用启动时执行一次,适合获取全局信息(如用户身份、权限)。
→ 更多内容见 [Umi Max 数据流](https://umijs.org/docs/max/data-flow)
## 请求
**请求配置** 位于 \`src/app.tsx\`:
\`\`\`ts
export const request: RequestConfig = {
baseURL: 'https://api.example.com',
timeout: 10000,
requestInterceptors: [], // 请求拦截器
responseInterceptors: [], // 响应拦截器
};
\`\`\`
**错误处理** 位于 \`src/requestErrorConfig.ts\`,可自定义错误码映射和提示逻辑。
**使用请求:**
\`\`\`tsx
import { request } from '@umijs/max';
// GET
const data = await request('/api/users', { params: { page: 1 } });
// POST
await request('/api/users', { method: 'POST', data: { name: 'test' } });
\`\`\`
**OpenAPI 代码生成:**
\`\`\`bash
npm run openapi
\`\`\`
根据 \`config/openapi.json\` 自动生成 \`src/services/\` 下的 API 调用代码。
> 💡 生成后的代码直接用 \`import { request } from '@umijs/max'\` 发起请求,无需手动封装。
→ 更多内容见 [Umi Max 请求](https://umijs.org/docs/max/request)
## 权限
**定义权限** 在 \`src/access.ts\`:
\`\`\`ts
export default function access(initialState: { currentUser?: API.CurrentUser }) {
const { currentUser } = initialState;
return {
canAdmin: currentUser?.access === 'admin',
canUser: !!currentUser,
};
}
\`\`\`
**路由级权限:** 在路由配置中添加 \`access\` 字段:
\`\`\`ts
{ path: '/admin', access: 'canAdmin' }
\`\`\`
**组件级权限:**
\`\`\`tsx
import { Access, useAccess } from '@umijs/max';
// 声明式
<Access accessible={access.canAdmin}>
<AdminPanel />
</Access>
// 命令式
const access = useAccess();
if (access.canAdmin) { /* ... */ }
\`\`\`
→ 更多内容见 [Umi Max 权限](https://umijs.org/docs/max/access)
## 国际化
**配置** 在 \`config/config.ts\`:
\`\`\`ts
locale: {
default: 'zh-CN',
antd: true, // 同步 antd 组件语言
baseNavigator: true, // 跟随浏览器语言
},
\`\`\`
**文件结构:**
\`\`\`
src/locales/
├── zh-CN.ts # 中文入口
├── zh-CN/
│ ├── menu.ts # 菜单翻译
│ ├── pages.ts # 页面翻译
│ └── ...
├── en-US.ts # 英文入口
└── en-US/
└── ...
\`\`\`
**使用方式:**
\`\`\`tsx
import { useIntl, FormattedMessage } from '@umijs/max';
// Hook 方式
const intl = useIntl();
intl.formatMessage({ id: 'menu.welcome' });
// 组件方式
<FormattedMessage id="menu.welcome" />
\`\`\`
**切换语言:**
\`\`\`tsx
import { setLocale } from '@umijs/max';
setLocale('en-US', false); // false = 不刷新页面
\`\`\`
→ 更多内容见 [Umi Max 国际化](https://umijs.org/docs/max/i18n)
## 样式
**CSS Modules:** 文件命名为 \`*.module.less\` 或 \`*.module.css\`:
\`\`\`css
/* example.module.less */
.container { padding: 24px; }
.title { font-size: 16px; }
\`\`\`
\`\`\`tsx
import styles from './example.module.less';
<div className={styles.container} />
\`\`\`
**antd-style(CSS-in-JS):**
\`\`\`tsx
import { createStyles } from 'antd-style';
const useStyles = createStyles(({ token, css }) => ({
card: css\`
background: \${token.colorBgContainer};
border-radius: \${token.borderRadiusLG}px;
\`,
}));
const { styles } = useStyles();
<div className={styles.card} />
\`\`\`
**Tailwind CSS(v4):** 直接在 className 中使用:
\`\`\`tsx
<div className="flex items-center gap-4 p-6 rounded-lg bg-white dark:bg-[#141414]" />
\`\`\`
**动态主题:** 在 \`config/config.ts\` 的 \`antd\` 配置中设置:
\`\`\`ts
antd: {
configProvider: {
theme: {
token: {
colorPrimary: '#1890ff',
borderRadius: 6,
},
},
},
},
\`\`\`
开发环境可通过右下角 SettingDrawer 实时切换主题。
> 💡 三种样式方案可以共存:Tailwind 适合布局、CSS Modules 适合组件样式、antd-style 适合需要消费主题 token 的场景。
→ 更多内容见 [umi 样式文档](https://umijs.org/docs/guides/styling)、[Umi Max antd 动态主题](https://umijs.org/docs/max/antd#动态主题)
## 测试 & 调试
**Jest 测试:**
\`\`\`bash
npm test # 运行所有测试
npm run test:coverage # 带覆盖率报告
npm run test:update # 更新快照
\`\`\`
测试文件放在对应组件目录下,命名为 \`*.test.ts(x)\`。
**Mock 数据:** 在 \`mock/\` 目录下创建文件:
\`\`\`ts
// mock/user.ts
export default {
'GET /api/currentUser': { name: 'Serati Ma', access: 'admin' },
'POST /api/login': (req, res) => { res.end('ok'); },
};
\`\`\`
Umi 自动注册 mock,开发模式下生效。
**代理配置** 位于 \`config/proxy.ts\`:
\`\`\`ts
export default {
dev: {
'/api/': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
};
\`\`\`
> 💡 用 \`MOCK=none\` 启动可跳过 Mock,直接代理到后端:\`npm run start:no-mock\`。
→ 更多内容见 [umi 测试](https://umijs.org/docs/guides/testing)、[umi Mock](https://umijs.org/docs/guides/mock)
## FAQ
**Q: 如何关闭 Mock?**
\`npm run start:no-mock\` 或 \`cross-env MOCK=none max dev\`
**Q: 如何修改主题色?**
修改 \`config/defaultSettings.ts\` 的 \`colorPrimary\`,开发时可用 SettingDrawer 实时调整。
**Q: 如何添加新页面?**
1. 在 \`src/pages/\` 下创建组件 2. 在 \`config/routes.ts\` 添加路由 3. 在 \`src/locales/\` 添加菜单翻译(如需)
**Q: 如何添加全局状态?**
在 \`src/models/\` 下创建文件,导出自定义 Hook,组件中通过 \`useModel('文件名')\` 使用。
**Q: 如何部署?**
\`npm run build\` 生成 \`dist/\` 目录,部署到任意静态服务器。配置 \`publicPath\` 处理非根目录部署。
**Q: 如何使用 OpenAPI 代码生成?**
1. 在 \`config/config.ts\` 配置 \`openAPI\` 2. 运行 \`npm run openapi\` 3. 自动生成 \`src/services/\` 下的代码
→ 更多内容见 [umi FAQ](https://umijs.org/docs/guides/faq)`;
export default content;

1
jest.config.ts

@ -10,6 +10,7 @@ export default async (): Promise<any> => {
...config, ...config,
testPathIgnorePatterns: ['/node_modules/', '/.worktrees/'], testPathIgnorePatterns: ['/node_modules/', '/.worktrees/'],
moduleNameMapper: { moduleNameMapper: {
'\\.md$': '<rootDir>/tests/__mocks__/raw.js',
...(config.moduleNameMapper || {}), ...(config.moduleNameMapper || {}),
'^mermaid$': '<rootDir>/tests/__mocks__/mermaid.js', '^mermaid$': '<rootDir>/tests/__mocks__/mermaid.js',
}, },

58
src/pages/Welcome-dark.css

@ -1,67 +1,67 @@
/* Dark mode: override highlight.js github-gist (white) with GitHub Dark theme */ /* Dark mode: override highlight.js github-gist (white) with GitHub Dark theme */
.dark .welcome-markdown .x-markdown pre { [data-theme="dark"] .welcome-markdown .x-markdown pre {
background: var(--md-pre-bg); background: var(--md-pre-bg);
border-color: var(--md-pre-border); border-color: var(--md-pre-border);
} }
.dark .welcome-markdown .x-markdown pre code.hljs { [data-theme="dark"] .welcome-markdown .x-markdown pre code.hljs {
background: transparent; background: transparent;
color: #e6edf3; color: #e6edf3;
} }
/* Keywords: if, const, import, return, etc. */ /* Keywords: if, const, import, return, etc. */
.dark .welcome-markdown .x-markdown .hljs-keyword, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-keyword,
.dark .welcome-markdown .x-markdown .hljs-selector-tag, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-selector-tag,
.dark .welcome-markdown .x-markdown .hljs-literal, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-literal,
.dark .welcome-markdown .x-markdown .hljs-section, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-section,
.dark .welcome-markdown .x-markdown .hljs-link { [data-theme="dark"] .welcome-markdown .x-markdown .hljs-link {
color: #ff7b72; color: #ff7b72;
} }
/* Strings, types, attributes */ /* Strings, types, attributes */
.dark .welcome-markdown .x-markdown .hljs-string, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-string,
.dark .welcome-markdown .x-markdown .hljs-title, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-title,
.dark .welcome-markdown .x-markdown .hljs-name, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-name,
.dark .welcome-markdown .x-markdown .hljs-type, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-type,
.dark .welcome-markdown .x-markdown .hljs-attribute, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-attribute,
.dark .welcome-markdown .x-markdown .hljs-symbol, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-symbol,
.dark .welcome-markdown .x-markdown .hljs-bullet, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-bullet,
.dark .welcome-markdown .x-markdown .hljs-addition, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-addition,
.dark .welcome-markdown .x-markdown .hljs-variable, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-variable,
.dark .welcome-markdown .x-markdown .hljs-template-tag, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-template-tag,
.dark .welcome-markdown .x-markdown .hljs-template-variable { [data-theme="dark"] .welcome-markdown .x-markdown .hljs-template-variable {
color: #a5d6ff; color: #a5d6ff;
} }
/* Comments, quotes, deletions */ /* Comments, quotes, deletions */
.dark .welcome-markdown .x-markdown .hljs-comment, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-comment,
.dark .welcome-markdown .x-markdown .hljs-quote, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-quote,
.dark .welcome-markdown .x-markdown .hljs-deletion, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-deletion,
.dark .welcome-markdown .x-markdown .hljs-meta { [data-theme="dark"] .welcome-markdown .x-markdown .hljs-meta {
color: #8b949e; color: #8b949e;
} }
/* Numbers */ /* Numbers */
.dark .welcome-markdown .x-markdown .hljs-number { [data-theme="dark"] .welcome-markdown .x-markdown .hljs-number {
color: #79c0ff; color: #79c0ff;
} }
/* Built-in types, class names */ /* Built-in types, class names */
.dark .welcome-markdown .x-markdown .hljs-built_in, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-built_in,
.dark .welcome-markdown .x-markdown .hljs-class .hljs-title, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-class .hljs-title,
.dark .welcome-markdown .x-markdown .hljs-title.class_ { [data-theme="dark"] .welcome-markdown .x-markdown .hljs-title.class_ {
color: #ffa657; color: #ffa657;
} }
/* RegExp, emphasis */ /* RegExp, emphasis */
.dark .welcome-markdown .x-markdown .hljs-regexp, [data-theme="dark"] .welcome-markdown .x-markdown .hljs-regexp,
.dark .welcome-markdown .x-markdown .hljs-emphasis { [data-theme="dark"] .welcome-markdown .x-markdown .hljs-emphasis {
color: #7ee787; color: #7ee787;
} }
/* Inline code */ /* Inline code */
.dark .welcome-markdown .x-markdown code { [data-theme="dark"] .welcome-markdown .x-markdown code {
background: var(--md-code-bg); background: var(--md-code-bg);
border-color: var(--md-code-border); border-color: var(--md-code-border);
color: #e6edf3; color: #e6edf3;

2
src/pages/Welcome.css

@ -14,7 +14,7 @@
--md-tr-hover-bg: rgba(0, 0, 0, 0.02); --md-tr-hover-bg: rgba(0, 0, 0, 0.02);
} }
.dark .welcome-markdown .x-markdown { [data-theme="dark"] .welcome-markdown .x-markdown {
--md-code-bg: rgba(255, 255, 255, 0.08); --md-code-bg: rgba(255, 255, 255, 0.08);
--md-code-border: rgba(255, 255, 255, 0.1); --md-code-border: rgba(255, 255, 255, 0.1);
--md-pre-bg: #161b22; --md-pre-bg: #161b22;

17
src/pages/Welcome.tsx

@ -1,8 +1,8 @@
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import XMarkdown from '@ant-design/x-markdown'; import XMarkdown from '@ant-design/x-markdown';
import '@ant-design/x-markdown/es/XMarkdown/index.css'; import '@ant-design/x-markdown/es/XMarkdown/index.css';
import enUS from '@root/docs/cheatsheet.en-US'; import enUS from '@root/docs/cheatsheet.en-US.md';
import zhCN from '@root/docs/cheatsheet.zh-CN'; import zhCN from '@root/docs/cheatsheet.zh-CN.md';
import { getLocale, useIntl, useModel } from '@umijs/max'; import { getLocale, useIntl, useModel } from '@umijs/max';
import { Card } from 'antd'; import { Card } from 'antd';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
@ -28,9 +28,7 @@ const InfoCard: React.FC<InfoCardProps> = ({ title, index, desc, href }) => (
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h4 className="mb-1 mt-0 text-sm font-semibold">{title}</h4> <h4 className="mb-1 mt-0 text-sm font-semibold">{title}</h4>
<p className="mb-0 line-clamp-2 text-xs text-gray-500 dark:text-gray-400"> <p className="mb-0 line-clamp-2 text-xs text-gray-500">{desc}</p>
{desc}
</p>
</div> </div>
</div> </div>
</Card> </Card>
@ -147,14 +145,17 @@ const Welcome: React.FC = () => {
const normalizedLocale = locale.toLowerCase(); const normalizedLocale = locale.toLowerCase();
const content = const content =
mdContent[locale] ?? mdContent[locale] ??
(normalizedLocale.startsWith('zh') ? mdContent['zh-CN'] : mdContent['en-US']); (normalizedLocale.startsWith('zh')
? mdContent['zh-CN']
: mdContent['en-US']);
const { initialState } = useModel('@@initialState'); const { initialState } = useModel('@@initialState');
const isDark = initialState?.settings?.navTheme === 'realDark'; const isDark = initialState?.settings?.navTheme === 'realDark';
return ( return (
<PageContainer> <PageContainer>
<div <div
className={`flex flex-col gap-6 md:flex-row${isDark ? ' dark' : ''}`} data-theme={isDark ? 'dark' : 'light'}
className="flex flex-col gap-6 md:flex-row"
> >
<div className="min-w-0 md:flex-[2] welcome-markdown"> <div className="min-w-0 md:flex-[2] welcome-markdown">
<Card> <Card>
@ -185,4 +186,4 @@ const Welcome: React.FC = () => {
); );
}; };
export default Welcome; export default Welcome;

4
src/typings.d.ts

@ -10,6 +10,10 @@ declare module '*.jpeg';
declare module '*.gif'; declare module '*.gif';
declare module '*.bmp'; declare module '*.bmp';
declare module '*.tiff'; declare module '*.tiff';
declare module '*.md' {
const content: string;
export default content;
}
declare module 'omit.js'; declare module 'omit.js';
declare module 'mockjs'; declare module 'mockjs';

1
tailwind.config.js

@ -1,4 +1,3 @@
module.exports = { module.exports = {
content: ['./src/**/*.tsx'], content: ['./src/**/*.tsx'],
darkMode: 'class',
}; };

1
tests/__mocks__/raw.js

@ -0,0 +1 @@
module.exports = 'mock-md-content';
Loading…
Cancel
Save