diff --git a/config/config.ts b/config/config.ts index 40d383ad..d5b0ee52 100644 --- a/config/config.ts +++ b/config/config.ts @@ -193,7 +193,16 @@ export default defineConfig({ include: ['src/pages/**/_mock.ts'], exclude: ['mock/requestRecord.mock.js'], }, - utoopack: {}, + utoopack: { + module: { + rules: { + '*.md': { + loaders: [{ loader: join(__dirname, 'md-raw-loader.cjs') }], + as: '*.js', + }, + }, + }, + }, requestRecord: {}, exportStatic: {}, define: { diff --git a/config/md-raw-loader.cjs b/config/md-raw-loader.cjs new file mode 100644 index 00000000..abb9bbc3 --- /dev/null +++ b/config/md-raw-loader.cjs @@ -0,0 +1,4 @@ +module.exports = function mdRawLoader(source) { + this.cacheable?.(); + return `export default ${JSON.stringify(String(source))};`; +}; \ No newline at end of file diff --git a/docs/cheatsheet.en-US.ts b/docs/cheatsheet.en-US.ts deleted file mode 100644 index b87c1f2d..00000000 --- a/docs/cheatsheet.en-US.ts +++ /dev/null @@ -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 = () => ( - - {/* Page content */} - -); -\`\`\` - -**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 - - - - -// 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 - -\`\`\` - -**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'; -
-\`\`\` - -**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(); -
-\`\`\` - -**Tailwind CSS (v4):** Use directly in className: - -\`\`\`tsx -
-\`\`\` - -**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; diff --git a/docs/cheatsheet.zh-CN.ts b/docs/cheatsheet.zh-CN.ts deleted file mode 100644 index c59a46f2..00000000 --- a/docs/cheatsheet.zh-CN.ts +++ /dev/null @@ -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 = () => ( - - {/* 页面内容 */} - -); -\`\`\` - -**自定义区域:** 右上角 \`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'; - -// 声明式 - - - - -// 命令式 -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' }); - -// 组件方式 - -\`\`\` - -**切换语言:** - -\`\`\`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'; -
-\`\`\` - -**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(); -
-\`\`\` - -**Tailwind CSS(v4):** 直接在 className 中使用: - -\`\`\`tsx -
-\`\`\` - -**动态主题:** 在 \`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; diff --git a/jest.config.ts b/jest.config.ts index f61808c0..9a36fc1b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -10,6 +10,7 @@ export default async (): Promise => { ...config, testPathIgnorePatterns: ['/node_modules/', '/.worktrees/'], moduleNameMapper: { + '\\.md$': '/tests/__mocks__/raw.js', ...(config.moduleNameMapper || {}), '^mermaid$': '/tests/__mocks__/mermaid.js', }, diff --git a/src/pages/Welcome-dark.css b/src/pages/Welcome-dark.css index 2a5c2b42..78a9a751 100644 --- a/src/pages/Welcome-dark.css +++ b/src/pages/Welcome-dark.css @@ -1,67 +1,67 @@ /* 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); 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; 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 { +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-keyword, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-selector-tag, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-literal, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-section, +[data-theme="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 { +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-string, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-title, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-name, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-type, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-attribute, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-symbol, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-bullet, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-addition, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-variable, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-template-tag, +[data-theme="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 { +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-comment, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-quote, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-deletion, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-meta { color: #8b949e; } /* Numbers */ -.dark .welcome-markdown .x-markdown .hljs-number { +[data-theme="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_ { +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-built_in, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-class .hljs-title, +[data-theme="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 { +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-regexp, +[data-theme="dark"] .welcome-markdown .x-markdown .hljs-emphasis { color: #7ee787; } /* Inline code */ -.dark .welcome-markdown .x-markdown code { +[data-theme="dark"] .welcome-markdown .x-markdown code { background: var(--md-code-bg); border-color: var(--md-code-border); color: #e6edf3; diff --git a/src/pages/Welcome.css b/src/pages/Welcome.css index daf470ea..0a513451 100644 --- a/src/pages/Welcome.css +++ b/src/pages/Welcome.css @@ -14,7 +14,7 @@ --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-border: rgba(255, 255, 255, 0.1); --md-pre-bg: #161b22; diff --git a/src/pages/Welcome.tsx b/src/pages/Welcome.tsx index d205d61c..87d1c1a1 100644 --- a/src/pages/Welcome.tsx +++ b/src/pages/Welcome.tsx @@ -1,8 +1,8 @@ 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 enUS from '@root/docs/cheatsheet.en-US.md'; +import zhCN from '@root/docs/cheatsheet.zh-CN.md'; import { getLocale, useIntl, useModel } from '@umijs/max'; import { Card } from 'antd'; import hljs from 'highlight.js'; @@ -28,9 +28,7 @@ const InfoCard: React.FC = ({ title, index, desc, href }) => (

{title}

-

- {desc} -

+

{desc}

@@ -147,14 +145,17 @@ const Welcome: React.FC = () => { const normalizedLocale = locale.toLowerCase(); const content = 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 isDark = initialState?.settings?.navTheme === 'realDark'; return (
@@ -185,4 +186,4 @@ const Welcome: React.FC = () => { ); }; -export default Welcome; \ No newline at end of file +export default Welcome; diff --git a/src/typings.d.ts b/src/typings.d.ts index 793a45e5..6191ba4b 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -10,6 +10,10 @@ declare module '*.jpeg'; declare module '*.gif'; declare module '*.bmp'; declare module '*.tiff'; +declare module '*.md' { + const content: string; + export default content; +} declare module 'omit.js'; declare module 'mockjs'; diff --git a/tailwind.config.js b/tailwind.config.js index a9b5d194..a403104a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,4 +1,3 @@ module.exports = { content: ['./src/**/*.tsx'], - darkMode: 'class', }; diff --git a/tests/__mocks__/raw.js b/tests/__mocks__/raw.js new file mode 100644 index 00000000..9f0aea47 --- /dev/null +++ b/tests/__mocks__/raw.js @@ -0,0 +1 @@ +module.exports = 'mock-md-content';