Browse Source

docs: add Welcome page cheatsheet documentation (#11712)

* feat: rewrite Welcome page with two-column layout

Replace centered single-card layout with a two-column design:
left column (2/3 width) renders a Markdown cheatsheet via
@ant-design/x-markdown, right column (1/3 width) shows three
InfoCard links. Add locale-aware content switching using
useLocale from @umijs/max with ?raw markdown imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: improve Welcome cheatsheet with i18n, responsive layout, and build compatibility

- Use useIntl/formatMessage for InfoCard text (consistent with project convention)
- Add i18n keys to zh-CN/en-US locale files for InfoCard content
- Add responsive breakpoint (mobile stack, desktop side-by-side)
- Add aria-label to InfoCard links for accessibility
- Convert docs/*.md to docs/*.ts (utoopack doesn't support ?raw imports)
- Use @root alias for doc imports instead of relative paths
- Remove unnecessary useState/useEffect, derive content directly from locale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add cheatsheet design spec and implementation plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: ignore docs/superpowers from git tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use getLocale instead of non-existent useLocale

@umijs/max exports getLocale(), not useLocale() hook.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: import XMarkdown CSS for proper markdown styling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add comprehensive markdown typography styles for cheatsheet

XMarkdown's built-in CSS only provides skeleton layout (margins, word-break).
Add antd-style createStyles with full typography using antd design tokens:
- Heading sizes (h1-h6) with proper font-weight and margins
- Link colors (colorLink/colorLinkHover)
- Code blocks with background, border, and rounded corners
- Inline code with subtle background
- Blockquotes with left border and tinted background
- Tables with borders, header background, and hover rows
- Horizontal rules and strong text

All colors adapt to light/dark theme automatically via tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: replace antd-style with plain CSS for markdown typography

Use a dedicated Welcome.css with CSS custom properties for light/dark mode,
instead of antd-style createStyles. Simpler, no runtime overhead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use Card component and compact InfoCard layout

- Left markdown: replace div wrapper with antd Card component
- Right InfoCards: use Card with size=small and hoverable for white bg
- Reduce badge size (h-10/w-10) and text size for compact cards
- Reduce gap between cards from 6 to 4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add heading anchor links and dark code block background

- Add hash anchor links to h1-h4 headings via XMarkdown components prop
- Anchor (#) appears on hover, clicking scrolls to heading
- Code blocks use dark background (#1e1e2e) with light text
- Remove code block border for cleaner look

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add syntax highlighting for code blocks with highlight.js

- Use highlight.js via XMarkdown config (marked highlight option)
- Light theme: github-gist (white background, standard colors)
- Dark theme: custom GitHub Dark overrides (.dark selector)
- Keywords red, strings blue, comments gray, numbers blue, builtins orange

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add syntax highlighting and improve heading anchors

- Code blocks: override XMarkdown's code renderer to use highlight.js
  (XMarkdown's internal renderer escapes HTML, bypassing marked's
  highlight option)
- Heading anchors: replace inline <a> with CSS ::before pseudo-element,
  # only appears on hover via absolute positioning, no layout shift

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: heading anchor # shows inline on hover

Use inline <a> with opacity toggle instead of absolute positioned
::before which was clipped by Card overflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add simple mode explanation to Getting Started

Explain what npm run simple does (strips demo pages and deps,
irreversible) and warn to commit code first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: heading anchor styles not applied

- Merge incoming className instead of overriding it (ensures CSS
  selectors match)
- Fix id generation: React children from XMarkdown Renderer are
  React nodes not strings, extract textContent properly
- Remove unused domNode prop from type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: heading anchor # positioned absolutely, no layout shift

Use position: absolute with negative left offset so the #: does not
occupy character space in the heading text flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: explain full mode vs simple mode in Getting Started

Add section after project creation explaining the two project modes,
and recommend starting with full mode for learning then switching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: merge all class prop variants from XMarkdown Renderer

XMarkdown Renderer passes class as className, classname, or class
depending on the source. Merge all three and don't spread rest
(some props like domNode are not valid HTML attributes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: move heading anchor # closer to title text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: soften simple mode warning — git can revert, not truly irreversible

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add explicit bash commands for simple mode switch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: use git clone instead of create-umi for project setup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: apply GitHub Dark theme for code blocks in dark mode

Override highlight.js github-gist (white) theme with GitHub Dark color
scheme when .dark class is active. Fix white background on code blocks,
add syntax token colors, and style inline code for dark backgrounds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: dark mode code blocks and add banner image

- Detect dark mode via initialState.settings.navTheme and apply .dark
  class to wrapper div, enabling CSS dark overrides and Tailwind dark:
  utilities
- Configure Tailwind darkMode: 'class' strategy
- Fix TypeScript errors for XMarkdown non-standard class props
- Remove unused import and ...rest spread in Heading component
- Add banner image below markdown title in both locales

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: use raw markdown files with utoopack raw-loader

- Convert docs/welcome.*.ts to docs/cheatsheet.*.md (pure markdown)
- Configure utoopack module.rules to load .md files as raw strings
- Add .md module type declaration in src/typings.d.ts
- Update Welcome.tsx imports from .ts to .md
- Add utoopack/bundler info and utoo.land link in cheatsheet docs
- Add cheatsheet links in README.md and README.zh-CN.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add Jest mock for .md imports and update README links

- Add moduleNameMapper for .md files in jest.config.ts to return mock
  string, placed before umi aliases to ensure match priority
- Create tests/__mocks__/raw.js as the mock module for .md imports
- Replace pro.ant.design doc/faq links in READMEs with cheatsheet links

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: replace pro.ant.design links with local cheatsheet and GitHub releases

Pro.ant.design is being decommissioned. Replace Documentation and FAQ
links with local cheatsheet md files, ChangeLog with GitHub releases.
Remove Home Page links that pointed to pro.ant.design.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Delete pnpm-lock.yaml

* fix: revert utoopack raw-loader and use .ts wrappers for markdown content

utoopack does not support module.rules type:'raw' for .md files. Revert
to TypeScript wrapper files that export markdown as template literals.

Changes:
- Add docs/cheatsheet.zh-CN.ts and docs/cheatsheet.en-US.ts as .ts
  wrappers (markdown content with proper escaping)
- Revert utoopack module.rules config for .md files
- Remove .md type declaration from src/typings.d.ts
- Remove Jest moduleNameMapper for .md and tests/__mocks__/raw.js
- Update Welcome.tsx imports from .md back to .ts
- Fix openapi.json typo (was oneapi.json) in both docs
- Improve locale fallback: zh* locales fall back to zh-CN, others to en-US
- Refactor InfoCard rendering to data-driven map pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
pull/11718/head
Alex Zhu 5 months ago
committed by GitHub
parent
commit
4bb860ac2e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      .gitignore
  2. 7
      README.md
  3. 9
      README.zh-CN.md
  4. 473
      docs/cheatsheet.en-US.md
  5. 475
      docs/cheatsheet.en-US.ts
  6. 473
      docs/cheatsheet.zh-CN.md
  7. 475
      docs/cheatsheet.zh-CN.ts
  8. 22201
      pnpm-lock.yaml
  9. 9
      src/locales/en-US/pages.ts
  10. 9
      src/locales/zh-CN/pages.ts
  11. 68
      src/pages/Welcome-dark.css
  12. 169
      src/pages/Welcome.css
  13. 229
      src/pages/Welcome.tsx
  14. 1
      tailwind.config.js

3
.gitignore

@ -46,6 +46,9 @@ build
# worktrees
.worktrees
# superpowers docs
docs/superpowers
# cloudflare wrangler
.wrangler
requestRecord.mock.js

7
README.md

@ -18,10 +18,9 @@ Language: 🇺🇸 | [🇨🇳](./README.zh-CN.md)
</div>
- Preview: http://preview.pro.ant.design
- Home Page: http://pro.ant.design
- Documentation: http://pro.ant.design/docs/getting-started
- ChangeLog: http://pro.ant.design/docs/changelog
- FAQ: http://pro.ant.design/docs/faq
- Documentation: [docs/cheatsheet.en-US.md](./docs/cheatsheet.en-US.md)
- ChangeLog: https://github.com/ant-design/ant-design-pro/releases
- FAQ: [docs/cheatsheet.en-US.md#faq](./docs/cheatsheet.en-US.md#faq)
## Features

9
README.zh-CN.md

@ -17,10 +17,9 @@ Language : [🇺🇸](./README.md) | 🇨🇳
</div>
- 预览:http://preview.pro.ant.design
- 首页:http://pro.ant.design/index-cn
- 使用文档:http://pro.ant.design/docs/getting-started-cn
- 更新日志: http://pro.ant.design/docs/changelog-cn
- 常见问题:http://pro.ant.design/docs/faq-cn
- 使用文档:[docs/cheatsheet.zh-CN.md](./docs/cheatsheet.zh-CN.md)
- 更新日志: https://github.com/ant-design/ant-design-pro/releases
- 常见问题:[docs/cheatsheet.zh-CN.md#faq](./docs/cheatsheet.zh-CN.md#faq)
## 5.0 已经发布! 🎉🎉🎉
@ -121,7 +120,7 @@ npm run simple
npm run build
```
更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)。
更多信息请参考 [使用文档](./docs/cheatsheet.zh-CN.md)。
## 支持环境

473
docs/cheatsheet.en-US.md

@ -0,0 +1,473 @@
# 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/oneapi.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)

475
docs/cheatsheet.en-US.ts

@ -0,0 +1,475 @@
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;

473
docs/cheatsheet.zh-CN.md

@ -0,0 +1,473 @@
# 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/oneapi.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)

475
docs/cheatsheet.zh-CN.ts

@ -0,0 +1,475 @@
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-styleCSS-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 CSSv4** 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;

22201
pnpm-lock.yaml

File diff suppressed because it is too large

9
src/locales/en-US/pages.ts

@ -27,6 +27,15 @@ export default {
'pages.welcome.link': 'Welcome',
'pages.welcome.alertMessage':
'Faster and stronger heavy-duty components have been released.',
'pages.welcome.infoCard.umi.title': 'Learn umi',
'pages.welcome.infoCard.umi.desc':
'umi is an extensible enterprise-level frontend framework based on routing, supporting both config-based and convention-based routes.',
'pages.welcome.infoCard.antd.title': 'Learn Ant Design',
'pages.welcome.infoCard.antd.desc':
'antd is a React UI component library based on the Ant Design system, mainly for enterprise-level mid-end products.',
'pages.welcome.infoCard.procomponents.title': 'Learn Pro Components',
'pages.welcome.infoCard.procomponents.desc':
'ProComponents provides higher-abstraction template components on top of Ant Design, with one-component-one-page philosophy.',
'pages.404.subTitle': 'Sorry, the page you visited does not exist.',
'pages.404.buttonText': 'Back Home',
'pages.admin.subPage.title': 'This page can only be viewed by Admin',

9
src/locales/zh-CN/pages.ts

@ -26,6 +26,15 @@ export default {
'pages.login.registerAccount': '注册账户',
'pages.welcome.link': '欢迎使用',
'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。',
'pages.welcome.infoCard.umi.title': '了解 umi',
'pages.welcome.infoCard.umi.desc':
'umi 是一个可扩展的企业级前端应用框架,以路由为基础,支持配置式路由和约定式路由。',
'pages.welcome.infoCard.antd.title': '了解 Ant Design',
'pages.welcome.infoCard.antd.desc':
'antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。',
'pages.welcome.infoCard.procomponents.title': '了解 Pro Components',
'pages.welcome.infoCard.procomponents.desc':
'ProComponents 是基于 Ant Design 的高抽象模板组件,以一个组件就是一个页面为开发理念。',
'pages.404.subTitle': '抱歉,您访问的页面不存在。',
'pages.404.buttonText': '返回首页',
'pages.admin.subPage.title': ' 这个页面只有 admin 权限才能查看',

68
src/pages/Welcome-dark.css

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

169
src/pages/Welcome.css

@ -0,0 +1,169 @@
.welcome-markdown .x-markdown {
--md-heading-weight: 600;
--md-code-bg: rgba(0, 0, 0, 0.04);
--md-code-border: rgba(0, 0, 0, 0.06);
--md-pre-bg: #f6f8fa;
--md-pre-border: rgba(0, 0, 0, 0.06);
--md-blockquote-bg: rgba(0, 0, 0, 0.04);
--md-blockquote-border: #1677ff;
--md-blockquote-color: rgba(0, 0, 0, 0.45);
--md-link-color: #1677ff;
--md-link-hover: #4096ff;
--md-th-bg: rgba(0, 0, 0, 0.04);
--md-table-border: rgba(0, 0, 0, 0.06);
--md-tr-hover-bg: rgba(0, 0, 0, 0.02);
}
.dark .welcome-markdown .x-markdown {
--md-code-bg: rgba(255, 255, 255, 0.08);
--md-code-border: rgba(255, 255, 255, 0.1);
--md-pre-bg: #161b22;
--md-pre-border: rgba(255, 255, 255, 0.1);
--md-blockquote-bg: rgba(255, 255, 255, 0.06);
--md-blockquote-color: rgba(255, 255, 255, 0.45);
--md-th-bg: rgba(255, 255, 255, 0.06);
--md-table-border: rgba(255, 255, 255, 0.1);
--md-tr-hover-bg: rgba(255, 255, 255, 0.04);
}
.welcome-markdown .x-markdown h1 {
font-size: 30px;
font-weight: var(--md-heading-weight);
margin: 0 0 16px;
line-height: 1.3;
}
.welcome-markdown .x-markdown h2 {
font-size: 24px;
font-weight: var(--md-heading-weight);
margin: 32px 0 8px;
line-height: 1.35;
padding-bottom: 4px;
border-bottom: 1px solid var(--md-table-border);
}
.welcome-markdown .x-markdown h3 {
font-size: 20px;
font-weight: var(--md-heading-weight);
margin: 24px 0 4px;
line-height: 1.4;
}
.welcome-markdown .x-markdown h4 {
font-size: 16px;
font-weight: var(--md-heading-weight);
margin: 16px 0 4px;
}
.welcome-markdown .x-markdown h5,
.welcome-markdown .x-markdown h6 {
font-size: 14px;
font-weight: var(--md-heading-weight);
margin: 16px 0 4px;
}
.welcome-markdown .x-markdown a {
color: var(--md-link-color);
text-decoration: none;
}
.welcome-markdown .x-markdown a:hover {
color: var(--md-link-hover);
}
.welcome-markdown .x-markdown code {
background: var(--md-code-bg);
border: 1px solid var(--md-code-border);
border-radius: 4px;
}
.welcome-markdown .x-markdown pre {
background: var(--md-pre-bg);
border: 1px solid var(--md-pre-border);
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.welcome-markdown .x-markdown pre code {
background: none;
border: none;
padding: 0;
margin: 0;
font-size: 13px;
line-height: 1.6;
border-radius: 0;
}
.welcome-markdown .x-markdown blockquote {
margin: 16px 0;
padding: 8px 16px;
border-left: 4px solid var(--md-blockquote-border);
background: var(--md-blockquote-bg);
border-radius: 0 4px 4px 0;
color: var(--md-blockquote-color);
}
.welcome-markdown .x-markdown blockquote > p:first-child {
margin-top: 0;
}
.welcome-markdown .x-markdown blockquote > p:last-child {
margin-bottom: 0;
}
.welcome-markdown .x-markdown table:not(pre) {
border: 1px solid var(--md-table-border);
border-radius: 8px;
overflow: hidden;
}
.welcome-markdown .x-markdown th {
background: var(--md-th-bg);
border-bottom: 2px solid var(--md-table-border);
font-weight: var(--md-heading-weight);
}
.welcome-markdown .x-markdown th,
.welcome-markdown .x-markdown td {
border: 1px solid var(--md-table-border);
}
.welcome-markdown .x-markdown tr:hover td {
background: var(--md-tr-hover-bg);
}
.welcome-markdown .x-markdown hr {
border: none;
border-top: 1px solid var(--md-table-border);
}
.welcome-markdown .x-markdown strong {
font-weight: 600;
}
.welcome-markdown .x-markdown img {
border-radius: 8px;
}
/* Heading anchor links */
.welcome-markdown .x-markdown .heading-anchor {
position: relative;
scroll-margin-top: 24px;
}
.welcome-markdown .x-markdown .heading-anchor .anchor-link {
position: absolute;
left: -1.2em;
width: 1.2em;
text-align: center;
color: var(--md-link-color);
font-weight: normal;
opacity: 0;
transition: opacity 0.2s;
text-decoration: none;
}
.welcome-markdown .x-markdown .heading-anchor:hover .anchor-link {
opacity: 1;
}

229
src/pages/Welcome.tsx

@ -1,6 +1,17 @@
import { PageContainer } from '@ant-design/pro-components';
import XMarkdown from '@ant-design/x-markdown';
import '@ant-design/x-markdown/es/XMarkdown/index.css';
import enUS from '@root/docs/cheatsheet.en-US';
import zhCN from '@root/docs/cheatsheet.zh-CN';
import { getLocale, useIntl, useModel } from '@umijs/max';
import { Card } from 'antd';
import hljs from 'highlight.js';
import React from 'react';
import 'highlight.js/styles/github-gist.css';
import './Welcome.css';
import './Welcome-dark.css';
interface InfoCardProps {
title: string;
index: number;
@ -9,71 +20,169 @@ interface InfoCardProps {
}
const InfoCard: React.FC<InfoCardProps> = ({ title, index, desc, href }) => (
<a
href={href}
target="_blank"
rel="noreferrer"
className="block h-full rounded-lg border border-solid border-gray-200 p-5 transition-shadow hover:shadow-md dark:border-gray-700"
>
<div className="flex items-start gap-4">
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-lg bg-[#1677ff] text-2xl font-bold text-white">
{index}
</div>
<div className="min-w-0 flex-1">
<h4 className="mb-2 mt-0 text-base font-semibold">{title}</h4>
<p className="mb-0 line-clamp-2 text-sm text-gray-500 dark:text-gray-400">
{desc}
</p>
<a href={href} target="_blank" rel="noopener noreferrer" aria-label={title}>
<Card hoverable size="small">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[#1677ff] text-base font-bold text-white">
{index}
</div>
<div className="min-w-0 flex-1">
<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">
{desc}
</p>
</div>
</div>
</div>
</Card>
</a>
);
const Welcome: React.FC = () => (
<PageContainer>
<div className="rounded-lg border border-solid border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-[#141414]">
<div className="mb-10 text-center">
<h2 className="mb-4 text-2xl font-semibold">使 Ant Design Pro</h2>
<p className="mx-auto max-w-[600px] text-base text-gray-500 dark:text-gray-400">
Ant Design Pro umiAnt Design
ProComponents的脚手架方案//
</p>
</div>
const mdContent: Record<string, string> = {
'zh-CN': zhCN,
'en-US': enUS,
};
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
<InfoCard
index={1}
href="https://umijs.org/docs/introduce/introduce"
title="了解 umi"
desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
/>
<InfoCard
index={2}
title="了解 ant design"
href="https://ant.design"
desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
/>
<InfoCard
index={3}
title="了解 Pro Components"
href="https://procomponents.ant.design"
desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
/>
</div>
// XMarkdown Renderer passes class names via non-standard props
interface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
tag?: string;
domNode?: unknown;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
classname?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class?: any;
}
const Heading: React.FC<HeadingProps> = ({
tag: Tag = 'h1',
children,
className,
classname,
class: htmlClass,
}) => {
// Merge all possible class sources from XMarkdown Renderer
const allClasses = [className, classname, htmlClass]
.filter(Boolean)
.join(' ');
// Extract text content from children for id generation
const textContent = React.Children.toArray(children)
.map((child) => (typeof child === 'string' ? child : ''))
.join('');
const id = textContent
.replace(/[^\w\s一-鿿-]/g, '')
.trim()
.replace(/\s+/g, '-')
.toLowerCase();
const mergedClass = `heading-anchor ${allClasses}`.trim();
return (
// @ts-expect-error dynamic tag
<Tag id={id} className={mergedClass}>
<a href={`#${id}`} className="anchor-link">
#
</a>
{children}
</Tag>
);
};
<div className="mt-10 border-t border-gray-100 pt-6 text-center text-sm text-gray-500 dark:border-gray-700 dark:text-gray-400">
访{' '}
<a
href="https://pro.ant.design"
target="_blank"
rel="noreferrer"
className="text-[#1677ff] hover:text-[#4096ff]"
>
Pro
</a>
const mdComponents = {
h1: (props: HeadingProps) => <Heading tag="h1" {...props} />,
h2: (props: HeadingProps) => <Heading tag="h2" {...props} />,
h3: (props: HeadingProps) => <Heading tag="h3" {...props} />,
h4: (props: HeadingProps) => <Heading tag="h4" {...props} />,
};
const mdConfig = {
renderer: {
code({ text, lang }: { text: string; lang?: string }) {
const langString = (lang || '').trim();
let highlighted: string;
if (langString && hljs.getLanguage(langString)) {
highlighted = hljs.highlight(text.replace(/\n$/, ''), {
language: langString,
}).value;
} else {
highlighted = hljs.highlightAuto(text.replace(/\n$/, '')).value;
}
const classAttr = langString
? ` class="hljs language-${langString}"`
: ' class="hljs"';
return `<pre><code${classAttr}>${highlighted}\n</code></pre>\n`;
},
},
};
const infoCards = [
{
index: 1,
href: 'https://umijs.org/docs/introduce/introduce',
titleId: 'pages.welcome.infoCard.umi.title',
titleDefault: 'Learn umi',
descId: 'pages.welcome.infoCard.umi.desc',
descDefault:
'umi is an extensible enterprise-level frontend framework based on routing, supporting both config-based and convention-based routes.',
},
{
index: 2,
href: 'https://ant.design',
titleId: 'pages.welcome.infoCard.antd.title',
titleDefault: 'Learn Ant Design',
descId: 'pages.welcome.infoCard.antd.desc',
descDefault:
'antd is a React UI component library based on the Ant Design system, mainly for enterprise-level mid-end products.',
},
{
index: 3,
href: 'https://procomponents.ant.design',
titleId: 'pages.welcome.infoCard.procomponents.title',
titleDefault: 'Learn Pro Components',
descId: 'pages.welcome.infoCard.procomponents.desc',
descDefault:
'ProComponents provides higher-abstraction template components on top of Ant Design, with one-component-one-page philosophy.',
},
] as const;
const Welcome: React.FC = () => {
const intl = useIntl();
const locale = getLocale();
const normalizedLocale = locale.toLowerCase();
const content =
mdContent[locale] ??
(normalizedLocale.startsWith('zh') ? mdContent['zh-CN'] : mdContent['en-US']);
const { initialState } = useModel('@@initialState');
const isDark = initialState?.settings?.navTheme === 'realDark';
return (
<PageContainer>
<div
className={`flex flex-col gap-6 md:flex-row${isDark ? ' dark' : ''}`}
>
<div className="min-w-0 md:flex-[2] welcome-markdown">
<Card>
<XMarkdown components={mdComponents} config={mdConfig}>
{content}
</XMarkdown>
</Card>
</div>
<div className="flex flex-1 flex-col gap-4">
{infoCards.map((card) => (
<InfoCard
key={card.href}
index={card.index}
href={card.href}
title={intl.formatMessage({
id: card.titleId,
defaultMessage: card.titleDefault,
})}
desc={intl.formatMessage({
id: card.descId,
defaultMessage: card.descDefault,
})}
/>
))}
</div>
</div>
</div>
</PageContainer>
);
</PageContainer>
);
};
export default Welcome;
export default Welcome;

1
tailwind.config.js

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

Loading…
Cancel
Save