From 458d63c020551a6c354e742083bfb328e7e4dc9f Mon Sep 17 00:00:00 2001 From: zuiidea Date: Tue, 14 Apr 2026 17:16:40 +0800 Subject: [PATCH] Reinitialize the project --- .eslintignore | 10 - .eslintrc.cjs | 61 -- .github/workflows/ci.yml | 76 --- .gitignore | 14 - .npmrc | 2 - .prettierignore | 9 - .prettierrc | 8 - .umirc.ts | 15 - README-zh_CN.md | 109 ---- README.md | 114 ---- docs/.nojekyll | 0 docs/API-configuration.md | 36 -- docs/README.md | 57 -- docs/_media/favicon.ico | Bin 67646 -> 0 bytes docs/_media/logo.svg | 24 - docs/_media/term_build.svg | 69 --- docs/_media/term_i18n.svg | 69 --- docs/_media/term_js_build.svg | 200 ------ docs/_media/term_js_i18n.svg | 200 ------ docs/_sidebar.md | 12 - docs/change-log.md | 26 - docs/configuration.md | 33 - docs/deploy.md | 113 ---- docs/faq.md | 9 - docs/getting-started.md | 75 --- docs/i18n.md | 121 ---- docs/index.html | 55 -- docs/layout.md | 60 -- docs/request.md | 33 - docs/sw.js | 83 --- docs/zh-cn/API-configuration.md | 35 -- docs/zh-cn/README.md | 53 -- docs/zh-cn/_sidebar.md | 11 - docs/zh-cn/change-log.md | 52 -- docs/zh-cn/configuration.md | 33 - docs/zh-cn/deploy.md | 114 ---- docs/zh-cn/faq.md | 7 - docs/zh-cn/getting-started.md | 69 --- docs/zh-cn/i18n.md | 123 ---- docs/zh-cn/layout.md | 60 -- docs/zh-cn/request.md | 33 - docs/zh-cn/router.md | 5 - jest.config.ts | 15 - llm/skills/admin.skill.ts | 53 -- llm/skills/dashboard.skill.ts | 17 - llm/skills/index.ts | 32 - llm/skills/menu.skill.ts | 35 -- llm/skills/user.skill.ts | 39 -- llms.txt | 36 -- manifest.json | 36 -- mock/index.js | 373 ------------ package.json | 56 -- public/favicon.ico | Bin 67646 -> 0 bytes public/logo.svg | 24 - public/logo/logo@128.png | Bin 3184 -> 0 bytes public/logo/logo@144.png | Bin 3604 -> 0 bytes public/logo/logo@152.png | Bin 4038 -> 0 bytes public/logo/logo@192.png | Bin 4785 -> 0 bytes public/logo/logo@384.png | Bin 9968 -> 0 bytes public/logo/logo@512.png | Bin 13256 -> 0 bytes public/logo/logo@72.png | Bin 2138 -> 0 bytes public/logo/logo@96.png | Bin 2449 -> 0 bytes src/app.tsx | 82 --- src/components/AuthGate.tsx | 63 -- src/components/DropOption.test.tsx | 67 -- src/components/DropOption.tsx | 48 -- src/components/GlobalFooter.tsx | 34 -- src/components/IconMap.tsx | 33 - src/components/TopbarControls.tsx | 138 ----- src/hooks/useGlobalSync.ts | 17 - src/hooks/useIsMounted.ts | 13 - src/layouts/BaseLayout.tsx | 31 - src/layouts/PrimaryLayout.tsx | 577 ------------------ src/layouts/PublicLayout.tsx | 38 -- src/layouts/index.less | 235 -------- src/layouts/index.tsx | 23 - src/locales/en-US.json | 16 - src/locales/zh-CN.json | 16 - src/pages/404.tsx | 20 - src/pages/admin/$id.tsx | 143 ----- src/pages/admin/components/Filter.tsx | 17 - src/pages/admin/components/List.tsx | 67 -- src/pages/admin/components/Modal.tsx | 94 --- src/pages/admin/list.tsx | 108 ---- src/pages/ai/aiConfig.ts | 116 ---- src/pages/ai/chat.tsx | 637 -------------------- src/pages/ai/components/Modal.tsx | 143 ----- src/pages/ai/components/mockData.tsx | 38 -- src/pages/dashboard.tsx | 72 --- src/pages/guide.tsx | 192 ------ src/pages/index.tsx | 13 - src/pages/login/index.module.less | 83 --- src/pages/login/index.tsx | 109 ---- src/pages/sys/menu/components/Filter.tsx | 29 - src/pages/sys/menu/components/List.tsx | 80 --- src/pages/sys/menu/components/Modal.tsx | 127 ---- src/pages/sys/menu/components/SortModal.tsx | 212 ------- src/pages/sys/menu/index.tsx | 249 -------- src/pages/users/$id.tsx | 163 ----- src/pages/users/list.tsx | 126 ---- src/services/admin.ts | 76 --- src/services/dashboard.ts | 13 - src/services/menu.ts | 34 -- src/services/user.ts | 49 -- src/setupTests.ts | 1 - src/store/useGlobalStore.ts | 78 --- src/types/admin.ts | 16 - src/types/menu.ts | 25 - src/types/user.ts | 19 - src/utils/config.ts | 11 - src/utils/request.test.ts | 62 -- src/utils/request.ts | 127 ---- tsconfig.json | 16 - typings.d.ts | 1 - 114 files changed, 7801 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.cjs delete mode 100644 .github/workflows/ci.yml delete mode 100644 .gitignore delete mode 100644 .npmrc delete mode 100644 .prettierignore delete mode 100644 .prettierrc delete mode 100644 .umirc.ts delete mode 100644 README-zh_CN.md delete mode 100644 README.md delete mode 100644 docs/.nojekyll delete mode 100644 docs/API-configuration.md delete mode 100644 docs/README.md delete mode 100644 docs/_media/favicon.ico delete mode 100644 docs/_media/logo.svg delete mode 100644 docs/_media/term_build.svg delete mode 100644 docs/_media/term_i18n.svg delete mode 100644 docs/_media/term_js_build.svg delete mode 100644 docs/_media/term_js_i18n.svg delete mode 100644 docs/_sidebar.md delete mode 100644 docs/change-log.md delete mode 100644 docs/configuration.md delete mode 100644 docs/deploy.md delete mode 100644 docs/faq.md delete mode 100644 docs/getting-started.md delete mode 100644 docs/i18n.md delete mode 100644 docs/index.html delete mode 100644 docs/layout.md delete mode 100644 docs/request.md delete mode 100644 docs/sw.js delete mode 100644 docs/zh-cn/API-configuration.md delete mode 100644 docs/zh-cn/README.md delete mode 100644 docs/zh-cn/_sidebar.md delete mode 100644 docs/zh-cn/change-log.md delete mode 100644 docs/zh-cn/configuration.md delete mode 100644 docs/zh-cn/deploy.md delete mode 100644 docs/zh-cn/faq.md delete mode 100644 docs/zh-cn/getting-started.md delete mode 100644 docs/zh-cn/i18n.md delete mode 100644 docs/zh-cn/layout.md delete mode 100644 docs/zh-cn/request.md delete mode 100644 docs/zh-cn/router.md delete mode 100644 jest.config.ts delete mode 100644 llm/skills/admin.skill.ts delete mode 100644 llm/skills/dashboard.skill.ts delete mode 100644 llm/skills/index.ts delete mode 100644 llm/skills/menu.skill.ts delete mode 100644 llm/skills/user.skill.ts delete mode 100644 llms.txt delete mode 100644 manifest.json delete mode 100644 mock/index.js delete mode 100644 package.json delete mode 100644 public/favicon.ico delete mode 100644 public/logo.svg delete mode 100755 public/logo/logo@128.png delete mode 100755 public/logo/logo@144.png delete mode 100755 public/logo/logo@152.png delete mode 100755 public/logo/logo@192.png delete mode 100755 public/logo/logo@384.png delete mode 100755 public/logo/logo@512.png delete mode 100755 public/logo/logo@72.png delete mode 100755 public/logo/logo@96.png delete mode 100644 src/app.tsx delete mode 100644 src/components/AuthGate.tsx delete mode 100644 src/components/DropOption.test.tsx delete mode 100644 src/components/DropOption.tsx delete mode 100644 src/components/GlobalFooter.tsx delete mode 100644 src/components/IconMap.tsx delete mode 100644 src/components/TopbarControls.tsx delete mode 100644 src/hooks/useGlobalSync.ts delete mode 100644 src/hooks/useIsMounted.ts delete mode 100644 src/layouts/BaseLayout.tsx delete mode 100644 src/layouts/PrimaryLayout.tsx delete mode 100644 src/layouts/PublicLayout.tsx delete mode 100644 src/layouts/index.less delete mode 100644 src/layouts/index.tsx delete mode 100644 src/locales/en-US.json delete mode 100644 src/locales/zh-CN.json delete mode 100644 src/pages/404.tsx delete mode 100644 src/pages/admin/$id.tsx delete mode 100644 src/pages/admin/components/Filter.tsx delete mode 100644 src/pages/admin/components/List.tsx delete mode 100644 src/pages/admin/components/Modal.tsx delete mode 100644 src/pages/admin/list.tsx delete mode 100644 src/pages/ai/aiConfig.ts delete mode 100644 src/pages/ai/chat.tsx delete mode 100644 src/pages/ai/components/Modal.tsx delete mode 100644 src/pages/ai/components/mockData.tsx delete mode 100644 src/pages/dashboard.tsx delete mode 100644 src/pages/guide.tsx delete mode 100644 src/pages/index.tsx delete mode 100644 src/pages/login/index.module.less delete mode 100644 src/pages/login/index.tsx delete mode 100644 src/pages/sys/menu/components/Filter.tsx delete mode 100644 src/pages/sys/menu/components/List.tsx delete mode 100644 src/pages/sys/menu/components/Modal.tsx delete mode 100644 src/pages/sys/menu/components/SortModal.tsx delete mode 100644 src/pages/sys/menu/index.tsx delete mode 100644 src/pages/users/$id.tsx delete mode 100644 src/pages/users/list.tsx delete mode 100644 src/services/admin.ts delete mode 100644 src/services/dashboard.ts delete mode 100644 src/services/menu.ts delete mode 100644 src/services/user.ts delete mode 100644 src/setupTests.ts delete mode 100644 src/store/useGlobalStore.ts delete mode 100644 src/types/admin.ts delete mode 100644 src/types/menu.ts delete mode 100644 src/types/user.ts delete mode 100644 src/utils/config.ts delete mode 100644 src/utils/request.test.ts delete mode 100644 src/utils/request.ts delete mode 100644 tsconfig.json delete mode 100644 typings.d.ts diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 5f3a4c3..0000000 --- a/.eslintignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -dist -.umi -build -public -coverage -*.config.js - -# ignore umi generated files under src -src/.umi diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 625454a..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,61 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - ecmaFeatures: { jsx: true }, - }, - plugins: [ - '@typescript-eslint', - 'react', - 'react-hooks', - 'unused-imports', - 'prettier', - 'import', - 'jsx-a11y', - ], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', - 'plugin:prettier/recommended', - ], - settings: { - react: { version: 'detect' }, - 'import/resolver': { - typescript: {}, - }, - }, - env: { - browser: true, - node: true, - es6: true, - }, - rules: { - 'prettier/prettier': 'error', - 'react/react-in-jsx-scope': 'off', - '@typescript-eslint/no-unused-vars': [ - 'warn', - { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, - ], - '@typescript-eslint/no-explicit-any': 'off', - 'unused-imports/no-unused-imports': 'warn', - 'no-console': [ - 'warn', - { - allow: ['warn', 'error', 'info'], - }, - ], - 'import/order': 'off', - }, - overrides: [ - { - files: ['*.ts', '*.tsx'], - rules: { - '@typescript-eslint/explicit-module-boundary-types': 'off', - }, - }, - ], -}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index fd3a62f..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,76 +0,0 @@ -++ name: CI - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18, 20] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'pnpm' - - - name: Enable corepack (pnpm) - run: | - corepack enable - corepack prepare pnpm@latest --activate - - - name: Cache pnpm store - uses: actions/cache@v4 - with: - path: | - ~/.pnpm-store - ~/.cache/pnpm - key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm- - - - name: Install dependencies - run: pnpm install - - - name: Test (if present) - run: pnpm test --if-present - - - name: Lint - run: pnpm run lint - - - name: Build - run: pnpm run build - - - name: Upload build (artifact) - if: success() - uses: actions/upload-artifact@v4 - with: - name: dist - path: ./dist - - publish: - name: Publish to GitHub Pages - runs-on: ubuntu-latest - needs: build - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: dist - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - publish_dir: ./dist - publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8ec33e4..0000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -/node_modules -/.env.local -/.umirc.local.ts -/config/config.local.ts -/src/.umi -/src/.umi-production -/src/.umi-test -/dist -.swc -.turbopack - -pnpm-lock.yaml -yarn.lock -npm-lock.json \ No newline at end of file diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 0b44d45..0000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -registry=https://registry.npmjs.com/ - diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index f511fed..0000000 --- a/.prettierignore +++ /dev/null @@ -1,9 +0,0 @@ -node_modules -dist -.umi -build -coverage -public -node_modules -.umi -.umi-production diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 70767cd..0000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "printWidth": 80, - "singleQuote": true, - "trailingComma": "all", - "proseWrap": "never", - "overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }], - "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"] -} diff --git a/.umirc.ts b/.umirc.ts deleted file mode 100644 index 6c474f2..0000000 --- a/.umirc.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig } from 'umi'; - -export default defineConfig({ - plugins: ['@umijs/plugins/dist/antd', '@umijs/plugins/dist/locale'], - esbuildMinifyIIFE: true, - locale: { - antd: true, - default: 'zh-CN', - baseNavigator: true, - useLocalStorage: true, - baseSeparator: '-', - title: true, - }, - npmClient: 'pnpm', -}); diff --git a/README-zh_CN.md b/README-zh_CN.md deleted file mode 100644 index 147021e..0000000 --- a/README-zh_CN.md +++ /dev/null @@ -1,109 +0,0 @@ -

- - antd-admin-logo - -

- -

AntD Admin

- -
- -基于 Ant Design 6、Umi 4 与 TypeScript 构建的企业级前端解决方案。 - -
- ---- - -## 📖 项目简介 - -**AntD Admin** 是一个轻量、高性能的管理后台模板,旨在为构建企业级应用提供干净、可扩展的起点。项目使用 **Ant Design 6**、**Umi 4** 与 **TypeScript** 的最新特性,减轻样板代码,提升开发体验。 - -无论您要构建仪表盘、管理系统,还是集成 AI 功能的工具,本项目都能提供必要的基础。 - -## ✨ 主要特性 - -- 🚀 **现代栈**:基于 **Ant Design 6** + **Umi 4** + **TypeScript**。 -- 🎨 **清晰架构**:使用 `zustand` 进行轻量状态管理。 -- 🛠 **良好的开发体验**:内置本地 mock 服务器(`mock/index.js`),无需后端即可开发前端功能。 -- 📦 **丰富示例页面**:包含可直接使用的页面示例: - - 📊 **仪表盘**:数据可视化概览。 - - 👥 **用户管理**:CRUD 操作示例。 - - 🧩 **菜单管理**:动态路由配置示例。 - - 🤖 **AI 聊天示例**:大模型(LLM)接口集成示例。 -- ✅ **CI/CD 支持**:预配置的 GitHub Actions 工作流,用于 lint 和构建。 - -## 🚀 快速开始 - -建议使用 **pnpm** 进行包管理。 - -```bash -# 1. 克隆仓库 -git clone https://github.com/zuiidea/antd-admin.git -b master --depth=1 -cd antd-admin - -# 2. 安装依赖 -pnpm install - -# 3. 启动本地开发(含 mock 数据) -npm run dev - -# 4. 构建生产包 -npm run build - -# 5. 代码质量检查 -npm run lint -npm run format -``` - -🔌 开发说明 - -#### Mock 服务 - -项目包含位于 `mock/index.js` 的内置 mock 服务,方便在没有后端的情况下独立开发前端功能。 - -如需关闭 mock 或切换到真实 API,请修改项目的代理配置(例如 Umi 项目可能的配置文件 `config/proxy.ts`、`.umirc.ts` 或 `config/config.ts`),以匹配您的实际项目结构。 - -#### 常用路由 - -| 路由 | 说明 | -| --- | --- | -| `/` 或 `/dashboard` | 主仪表盘 | -| `/users` | 用户列表与管理 | -| `/sys/menu` | 系统菜单配置 | -| `/ai/chat` | AI 聊天示例界面 | - -#### 定制化 - -- **API 集成:** 在 `src/services` 中更新 API 接口以对接真实后端。 -- **组件扩展:** 在 `src/components` 中复用或扩展现有组件(例如 `DropOption`、`StandardTable`)。 -- **主题:** 在主题配置中自定义 Ant Design 变量以匹配品牌视觉。 - -#### GitHub Actions(CI) - -仓库包含预配置的 GitHub Actions 工作流(见 `.github/workflows/ci.yml`),用于保证代码质量: - -- 使用 `pnpm` 安装依赖。 -- 运行 lint 检查。 -- 构建生产包。 - -- **工作流位置:** `.github/workflows/ci.yml` -- **提示:** 若要在 CI 中加入自动化测试,请在 `package.json` 中添加测试脚本并更新 workflow YAML。 - -## 🤝 贡献 - -欢迎各类贡献:修复 bug、添加新示例页面或组件、完善类型定义与 lint 规则、改进国际化等。 - -常见贡献流程: - -1. Fork 仓库。 -2. 新建分支:`git checkout -b feature/AmazingFeature`。 -3. 提交变更:`git commit -m 'Add some AmazingFeature'`。 -4. 推送分支:`git push origin feature/AmazingFeature`。 -5. 提交 Pull Request。 - -## 📄 许可证 - -本项目采用 MIT 许可证。 - -由 zuiidea 与贡献者共同维护,Made with ❤️。 - diff --git a/README.md b/README.md deleted file mode 100644 index 856f5d3..0000000 --- a/README.md +++ /dev/null @@ -1,114 +0,0 @@ -

- - antd-admin-logo - -

- -

AntD Admin

- -
- -An excellent enterprise-grade front-end solution built with Ant Design 6, Umi 4, and TypeScript. - -[![antd](https://img.shields.io/badge/AntD-^6.0.0-1890ff?style=flat-square&logo=ant-design)](https://github.com/ant-design/ant-design) -[![umi](https://img.shields.io/badge/Umi-^4.0.0-ff9900?style=flat-square&logo=umijs)](https://github.com/umijs/umi) -[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178c6?style=flat-square&logo=typescript)](https://www.typescriptlang.org/) -[![GitHub issues](https://img.shields.io/github/issues/zuiidea/antd-admin?style=flat-square)](https://github.com/zuiidea/antd-admin/issues) -[![GitHub stars](https://img.shields.io/github/stars/zuiidea/antd-admin?style=flat-square)](https://github.com/zuiidea/antd-admin/stargazers) -[![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](http://opensource.org/licenses/MIT) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](https://github.com/zuiidea/antd-admin/pulls) - -
- ---- - -## 📖 Introduction - -**AntD Admin** is a lightweight, high-performance admin template designed to provide a clean and extensible starting point for building enterprise applications. It leverages the latest features of **Ant Design 6**, **Umi 4**, and **TypeScript**, offering a modern development experience with minimal boilerplate. - -Whether you are building a dashboard, a management system, or an AI-powered tool, this project provides the essential foundation you need. - -## ✨ Key Features - -- 🚀 **Modern Stack**: Built on **Ant Design 6** + **Umi 4** + **TypeScript**. -- 🎨 **Clean Architecture**: Lightweight state management using `zustand`. -- 🛠 **Dev Experience**: Integrated local mock server (`mock/index.js`) for seamless frontend development without backend dependency. -- 📦 **Rich Examples**: Includes ready-to-use pages: - - 📊 **Dashboard**: Data visualization overview. - - 👥 **User Management**: CRUD operations example. - - 🧩 **Menu Management**: Dynamic routing configuration. - - 🤖 **AI Chat Demo**: Integration example for LLM interfaces. -- ✅ **CI/CD Ready**: Pre-configured GitHub Actions for linting and building. - -## 🚀 Quick Start - -We recommend using **pnpm** for package management. - -```bash -# 1. Clone the repository -git clone https://github.com/zuiidea/antd-admin.git -b master --depth=1 -cd antd-admin - -# 2. Install dependencies -pnpm install - -# 3. Start local development server (with mock data) -npm run dev - -# 4. Build for production -npm run build - -# 5. Code quality checks -npm run lint -npm run format -``` - -🔌 **Development Notes** - -#### Mock Server - -The project includes a built-in mock server defined in `mock/index.js`. This allows you to develop frontend features independently of the backend. - -To disable mocks or switch to a real API, modify the proxy settings in your configuration file (for example, `config/proxy.ts` or `.umirc.ts`). - -#### Common Routes - -| Route | Description | -| --- | --- | -| `/` or `/dashboard` | Main Dashboard | -| `/users` | User List & Management | -| `/sys/menu` | System Menu Configuration | -| `/ai/chat` | AI Chat Interface Demo | - -#### Customization - -- **API Integration:** Update API endpoints in `src/services` to connect to your real backend. -- **Component Extension:** Reuse or extend existing components in `src/components` (for example, `DropOption`, `StandardTable`). -- **Theme:** Customize Ant Design tokens in the theme configuration to match your brand identity. - -#### GitHub Actions CI - -This repository includes a pre-configured GitHub Actions workflow to ensure code quality: - -- Installs dependencies using `pnpm`. -- Runs linting checks. -- Builds the project for production. - -- **Workflow location:** `.github/workflows/ci.yml` -- **Tip:** To add automated tests to the CI pipeline, add a test script to `package.json` and update the workflow YAML accordingly. -🤝 Contributing -Contributions are always welcome! Whether it's fixing a bug, adding a new feature, or improving documentation, please feel free to submit an Issue or a Pull Request. -Typical contributions include: -🐛 Bug fixes -🧩 New example pages or components -📝 Type definitions and linting improvements -🌍 Internationalization (i18n) updates -How to contribute: -Fork the repository. -Create your feature branch (git checkout -b feature/AmazingFeature). -Commit your changes (git commit -m 'Add some AmazingFeature'). -Push to the branch (git push origin feature/AmazingFeature). -Open a Pull Request. -📄 License -This project is licensed under the MIT License. -Made with ❤️ by zuiidea and contributors. diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/docs/API-configuration.md b/docs/API-configuration.md deleted file mode 100644 index c40a250..0000000 --- a/docs/API-configuration.md +++ /dev/null @@ -1,36 +0,0 @@ -# API configuration - -This project uses explicit per-feature service modules (TypeScript) under `src/services/` that call a shared HTTP helper `src/utils/request.ts` (exported as the default `api` function). - -Why this pattern? -- Clear function boundaries per domain (users, admin, menu, dashboard). -- Type-safe signatures in TypeScript for inputs/outputs (`src/types`). -- Centralized request handling (query params, JSON body, auth header, unified response format) in `src/utils/request.ts`. - -Example service (from `src/services/user.ts`): - -```ts -import type { PagedResponse, User } from '@/types/user'; -import api from '../utils/request'; - -export async function listUsers( - params?: Record, -): Promise> { - return api>('/users', { method: 'GET', params }); -} - -export async function createUser(values: Record): Promise { - return api('/user', { method: 'POST', body: values }); -} -``` - -Usage: -- Import named functions from the service module: `import { listUsers } from '@/services/user'`. -- The helper `api(path, options)` returns the parsed `data` payload from responses that follow the `{ success, message, data }` convention. - -Notes about `src/utils/request.ts`: -- Combines `config.apiPrefix` with the provided `path`. -- Supports `params` (query), `body` (JSON or FormData), and sets `Authorization` header from local storage when available. -- Throws errors and shows UI messages for non-success responses. - -If you are migrating older code that used an API mapping, prefer creating small service modules with clear function names and types as shown above. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 52b2616..0000000 --- a/docs/README.md +++ /dev/null @@ -1,57 +0,0 @@ -

- - antd-admin - -

- -

AntD Admin

- -
- -An enterprise-grade front-end starter built with Ant Design 6, Umi 4 and TypeScript. - -[![antd](https://img.shields.io/badge/AntD-^6.0.0-1890ff?style=flat-square&logo=ant-design)](https://github.com/ant-design/ant-design) -[![umi](https://img.shields.io/badge/Umi-^4.0.0-ff9900?style=flat-square&logo=umijs)](https://github.com/umijs/umi) -[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178c6?style=flat-square&logo=typescript)](https://www.typescriptlang.org/) -[![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](http://opensource.org/licenses/MIT) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](https://github.com/zuiidea/antd-admin/pulls) - -
- -- Documentation source for this project is under `docs/`. - -## Quick start - -This repository uses `pnpm` in examples and CI; `npm` or `yarn` also work but `pnpm` is recommended. - -```bash -# clone -git clone https://github.com/zuiidea/antd-admin.git -cd antd-admin - -# install -pnpm install - -# start dev server (includes local mock server) -npm run dev - -# build -npm run build - -# lint & format -npm run lint -npm run format -``` - -Notes: - -- Mock data live in `mock/index.js` and the project can run without a backend during development. -- CI workflow is available at `.github/workflows/ci.yml` (uses pnpm, lint, build). -- Update API endpoints under `src/services` to integrate a real backend. - -For detailed documentation see the other pages in this `docs/` folder. - -## Contributing - -Contributions welcome — file issues or open a PR as usual on GitHub. - diff --git a/docs/_media/favicon.ico b/docs/_media/favicon.ico deleted file mode 100644 index 7ca2170ea0f4e0472c56924a402807e10ddf0220..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67646 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 - - Created with Sketch. - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_media/term_build.svg b/docs/_media/term_build.svg deleted file mode 100644 index 5b1a6ea..0000000 --- a/docs/_media/term_build.svg +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - ➜ antd-admin git:(master) ✗ ➜ antd-admin git:(master) ✗ n ➜ antd-admin git:(master) ✗ np ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm r ➜ antd-admin git:(master) ✗ npm ru ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run b ➜ antd-admin git:(master) ✗ npm run bu ➜ antd-admin git:(master) ✗ npm run bui ➜ antd-admin git:(master) ✗ npm run buil ➜ antd-admin git:(master) ✗ npm run build Compiling● webpack █████████████████████████ compiling (0%) Compiling ● webpack █████████████████████████ building modules (10%) 2/2 modules 0 active Compiling ● webpack █████████████████████████ building modules (11%) 11/13 modules 2 active node_modules/umi/lib/createHistory.js ● webpack █████████████████████████ building modules (12%) 18/21 modules 3 activ node_modules/webpack/buildin/global.js ● webpack █████████████████████████ building modules (23%) 110/112 modules 2 active node_modules/react-dom/cjs/react-dom.production.min.js ● webpack █████████████████████████ building modules (23%) 115/118 modules 3 act node_modules/util/support/isBufferBrowser.js ● webpack █████████████████████████ building modules (23%) 116/141 modules 25 active node_modules/core-js/modules/_global.js ● webpack █████████████████████████ building modules (25%) 130/142 modules 12 ac node_modules/path-browserify/index.js ● webpack █████████████████████████ building modules (27%) 147/191 modules 44 ac node_modules/core-js/modules/_bind.js ● webpack █████████████████████████ building modules (35%) 215/256 modules 41 ac node_modules/core-js/modules/_inherit-if-required.js ● webpack █████████████████████████ building modules (39%) 249/266 modules 17 ac node_modules/core-js/modules/web.dom.iterable.js ● webpack █████████████████████████ building modules (42%) 267/307 modules 40 ac ...4m › postcss-loader › less-loader › src/pages/404.less ● webpack █████████████████████████ building modules (47%) 313/320 modules 7 act node_modules/prop-types/index.js ● webpack █████████████████████████ building modules (49%) 328/351 modules 23 ac node_modules/react-router-dom/es/index.js ● webpack █████████████████████████ building modules (51%) 343/354 modules 11 ac babel-loader › src/pages/chart/highCharts/HighChartsComponent.js ● webpack █████████████████████████ building modules (52%) 354/372 modules 18 ac src/locales/en/messages.json ● webpack █████████████████████████ building modules (54%) 372/394 modules 22 ac babel-loader › src/utils/theme.js ● webpack █████████████████████████ building modules (57%) 395/422 modules 27 ac node_modules/@babel/runtime/helpers/arrayWithoutHoles.js ● webpack █████████████████████████ building modules (60%) 424/455 modules 31 ac node_modules/antd/es/card/Meta.js ● webpack █████████████████████████ building modules (61%) 427/463 modules 36 ac node_modules/antd/es/_util/throttleByAnimationFrame.js ● webpack █████████████████████████ building modules (64%) 458/477 modules 19 ac node_modules/antd/es/radio/group.js ● webpack █████████████████████████ building modules (67%) 476/497 modules 21 ac babel-loader › src/pages/chart/highCharts/mapdata/europe.js ● webpack █████████████████████████ building modules (66%) 488/514 modules 26 ac node_modules/react-router-dom/node_modules/warning/warning.js ● webpack █████████████████████████ building modules (67%) 493/518 modules 25 ac ...der › less-loader › src/pages/chart/Recharts/Container.less ● webpack █████████████████████████ building modules (67%) 505/525 modules 20 ac node_modules/omit.js/es/index.js ● webpack █████████████████████████ building modules (67%) 532/559 modules 27 ac node_modules/highcharts-exporting/exporting.js ● webpack █████████████████████████ building modules (67%) 574/598 modules 24 ac node_modules/antd/es/date-picker/locale/en_US.js ● webpack █████████████████████████ building modules (68%) 589/608 modules 19 ac node_modules/draft-js/lib/ContentState.js ● webpack █████████████████████████ building modules (67%) 616/641 modules 25 ac node_modules/react-router/es/matchPath.js ● webpack █████████████████████████ building modules (67%) 656/679 modules 23 ac node_modules/draft-js/lib/DraftEntityInstance.js ● webpack █████████████████████████ building modules (67%) 665/690 modules 25 ac node_modules/axios/lib/utils.js ● webpack █████████████████████████ building modules (67%) 685/712 modules 27 ac node_modules/add-dom-event-listener/lib/index.js ● webpack █████████████████████████ building modules (68%) 735/756 modules 21 ac node_modules/lodash/_objectToString.js ● webpack █████████████████████████ building modules (68%) 737/758 modules 21 ac node_modules/lodash/_getRawTag.js ● webpack █████████████████████████ building modules (67%) 756/787 modules 31 ac node_modules/add-dom-event-listener/lib/EventObject.js ● webpack █████████████████████████ building modules (67%) 771/800 modules 29 ac node_modules/core-js/library/modules/_add-to-unscopables.js ● webpack █████████████████████████ building modules (67%) 781/808 modules 27 ac node_modules/core-js/library/modules/_object-dp.js ● webpack █████████████████████████ building modules (68%) 817/836 modules 19 ac node_modules/size-sensor/lib/index.js ● webpack █████████████████████████ building modules (68%) 845/870 modules 25 ac node_modules/is-buffer/index.js ● webpack █████████████████████████ building modules (68%) 892/913 modules 21 ac node_modules/echarts/lib/util/model.js ● webpack █████████████████████████ building modules (68%) 933/963 modules 30 ac node_modules/echarts-liquidfill/node_modules/echarts/lib/model/Series.js ● webpack █████████████████████████ building modules (68%) 978/996 modules 18 ac node_modules/lodash/_baseFor.js ● webpack █████████████████████████ building modules (68%) 1000/1029 modules 29 active node_modules/zrender/lib/contain/text.js ● webpack █████████████████████████ building modules (68%) 1029/1056 modules 27 ...e_modules/echarts-liquidfill/node_modules/echarts/lib/model/mixin/dataFormat.js ● webpack █████████████████████████ building modules (68%) 1059/1077 modules 18 node_modules/lodash/_createBaseFor.js ● webpack █████████████████████████ building modules (68%) 1101/1133 modules 32 node_modules/zrender/lib/graphic/helper/image.js ● webpack █████████████████████████ building modules (68%) 1155/1175 modules 20 node_modules/lodash/_baseGetAllKeys.js ● webpack █████████████████████████ building modules (69%) 1212/1231 modules 19 node_modules/size-sensor/lib/sensors/resizeObserver.js ● webpack █████████████████████████ building modules (68%) 1266/1296 modules 30 node_modules/d3-shape/src/curve/cardinalClosed.js ● webpack █████████████████████████ building modules (68%) 1329/1357 modules 28 ...ules/echarts-liquidfill/node_modules/echarts/lib/data/helper/dataStackHelper.● webpack █████████████████████████ building modules (69%) 1392/1409 modules 17 node_modules/d3-collection/src/set.js ● webpack █████████████████████████ building modules (69%) 1449/1467 modules 18 node_modules/component-classes/index.js ● webpack █████████████████████████ building modules (68%) 1481/1507 modules 26 node_modules/d3-time/src/second.js ● webpack █████████████████████████ building modules (69%) 1534/1551 modules 17 node_modules/echarts-liquidfill/src/liquidFillView.js ● webpack █████████████████████████ building modules (69%) 1594/1607 modules 13 node_modules/rc-animate/es/CSSMotion.js ● webpack █████████████████████████ building modules (68%) 1651/1691 modules 40 node_modules/echarts/lib/component/gridSimple.js ● webpack █████████████████████████ building modules (69%) 1700/1713 modules 13 node_modules/echarts/lib/coord/calendar/prepareCustom.js ● webpack █████████████████████████ building modules (68%) 1739/1771 modules 32 node_modules/echarts/lib/component/tooltip/TooltipContent.js ● webpack █████████████████████████ building modules (69%) 1780/1807 modules 27 node_modules/echarts/lib/chart/scatter/ScatterView.js ● webpack █████████████████████████ building modules (69%) 1781/1808 modules 27 node_modules/echarts/lib/chart/scatter/ScatterSeries.js ● webpack █████████████████████████ building modules (68%) 1819/1852 modules 33 node_modules/echarts/lib/chart/graph/categoryFilter.js ● webpack █████████████████████████ building modules (69%) 1867/1880 modules 13 node_modules/echarts/lib/chart/sankey/SankeySeries.js ● webpack █████████████████████████ building modules (69%) 1916/1944 modules 28 node_modules/echarts/lib/component/dataZoom/DataZoomView.js ● webpack █████████████████████████ building modules (69%) 1966/1993 modules 27 node_modules/echarts/lib/chart/sunburst/SunburstPiece.js ● webpack █████████████████████████ building modules (69%) 2002/2030 modules 28 node_modules/draft-js/lib/CompositeDraftDecorator.js ● webpack █████████████████████████ building modules (69%) 2010/2034 modules 24 node_modules/echarts/lib/component/marker/MarkerModel.js ● webpack █████████████████████████ building modules (69%) 2031/2047 modules 16 babel-loader › src/pages/dashboard/components/browser.js ● webpack █████████████████████████ building modules (69%) 2081/2100 modules 19 node_modules/array-tree-filter/lib/index.js ● webpack █████████████████████████ building modules (69%) 2105/2122 modules 17 node_modules/echarts/lib/component/dataZoom/SelectZoomModel.js ● webpack █████████████████████████ building modules (69%) 2108/2123 modules 15 ● webpack █████████████████████████ building modules (69%) 2117/2127 modules 10 node_modules/react-router-redux/es/middleware.js ● webpack █████████████████████████ building modules (69%) 2122/2130 modules 8 active node_modules/react-router-redux/es/actions.js ● webpack █████████████████████████ building modules (69%) 2143/2167 modules 24 node_modules/react-redux/es/connect/connect.js ● webpack █████████████████████████ building modules (69%) 2185/2203 modules 18 node_modules/rc-dialog/es/DialogWrap.js ● webpack █████████████████████████ building modules (69%) 2237/2265 modules 28 node_modules/antd/es/tag/index.js ● webpack █████████████████████████ building modules (69%) 2283/2301 modules 18 babel-loader › src/pages/user/components/Filter.js ● webpack █████████████████████████ building modules (69%) 2327/2338 modules 11 node_modules/rc-util/es/getScrollBarSize.js ● webpack █████████████████████████ building modules (69%) 2339/2357 modules 18 node_modules/async-validator/es/index.js ● webpack █████████████████████████ building modules (69%) 2347/2363 modules 16 node_modules/lodash/_createCurry.js ● webpack █████████████████████████ building modules (69%) 2384/2400 modules 16 node_modules/rmc-feedback/es/index.js ● webpack █████████████████████████ building modules (69%) 2402/2422 modules 20 node_modules/dom-align/es/getAlignOffset.js ● webpack █████████████████████████ building modules (69%) 2426/2438 modules 12 node_modules/dva-core/lib/prefixType.js ● webpack █████████████████████████ building modules (69%) 2430/2438 modules 8 a● webpack █████████████████████████ building modules (69%) 2451/2471 modules 20 node_modules/antd/es/badge/index.js ● webpack █████████████████████████ building modules (69%) 2485/2494 modules 9 a node_modules/fbjs/lib/emptyObject.js ● webpack █████████████████████████ building modules (69%) 2490/2520 modules 30 node_modules/antd/es/date-picker/WeekPicker.js ● webpack █████████████████████████ building modules (69%) 2512/2524 modules 12 node_modules/react-side-effect/lib/index.js ● webpack █████████████████████████ building modules (69%) 2519/2529 modules 10 node_modules/redux-saga/lib/internal/runSaga.js ● webpack █████████████████████████ building modules (69%) 2524/2532 modules 8 a node_modules/antd/es/layout/Sider.js ● webpack █████████████████████████ building modules (69%) 2540/2549 modules 9 a node_modules/antd/es/_util/getDataOrAriaProps.js ● webpack █████████████████████████ building modules (69%) 2554/2561 modules 7 a node_modules/redux-saga/lib/internal/sagaHelpers/index.js ● webpack █████████████████████████ building modules (69%) 2577/2581 modules 4 a node_modules/rc-switch/es/index.js ● webpack █████████████████████████ building modules (69%) 2581/2589 modules 8 a node_modules/rc-drawer/es/index.js ● webpack █████████████████████████ building modules (69%) 2604/2610 modules 6 a node_modules/rc-calendar/es/date/DateInput.js ● webpack █████████████████████████ finish module graph (70%) ● webpack █████████████████████████ optimizing (73%) ● webpack █████████████████████████ advanced chunk modules optimization (79%) ● webpack █████████████████████████ after hashing (88%) ● webpack █████████████████████████ additional chunk assets processing (90%) ● webpack █████████████████████████ chunk asset optimization (92%) UglifyJSPlugin ● webpack █████████████████████████ after chunk asset optimization (92%) ➜ antd-admin git:(master) ✗ npm run build > antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin> umi build[12:40:22] webpack compiled in 15s 56ms DONE Compiled successfully in 15062ms 12:40:22File sizes after gzip: 887 B dist/p__dashboard__model.js.async.js 877 B dist/precache-manifest.6d2a703c437c4f28b5f6e072817bbe6e.js 786 B dist/p__UIElement__editor__index.async.js 764 B dist/i18n-0.async.js 652 B dist/p__login__index.chunk.css 614 B dist/p__user__$id__models__detail.js.async.js 599 B dist/p__post__model.js.async.js 589 B dist/p__user__$id__index.async.js 569 B dist/service-worker.js 530 B dist/p__request__index.chunk.css 515 B dist/p__login__model.js.async.js 419 B dist/p__index.async.js 245 B dist/p__chart__ECharts__index.chunk.css 230 B dist/p__chart__Recharts__index.chunk.css 138 B dist/p__user__$id__index.chunk.css 74 B dist/p__chart__highCharts__index.chunk.css 66 B dist/p__user__index.chunk.css 66 B dist/p__post__index.chunk.cssThe bundle size is significantly larger than recommended.Consider reducing it with code splitting: https://goo.gl/9VhYWBYou can also analyze the project dependencies: https://goo.gl/LeUzfb➜ antd-admin git:(master) ✗ - \ No newline at end of file diff --git a/docs/_media/term_i18n.svg b/docs/_media/term_i18n.svg deleted file mode 100644 index a287916..0000000 --- a/docs/_media/term_i18n.svg +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - ➜ antd-admin git:(master) ✗ ➜ antd-admin git:(master) ✗ n ➜ antd-admin git:(master) ✗ np ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm r ➜ antd-admin git:(master) ✗ npm ru ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run a ➜ antd-admin git:(master) ✗ npm run ad ➜ antd-admin git:(master) ✗ npm run add ➜ antd-admin git:(master) ✗ npm run add- ➜ antd-admin git:(master) ✗ npm run add-l ➜ antd-admin git:(master) ✗ npm run add-lo ➜ antd-admin git:(master) ✗ npm run add-loc ➜ antd-admin git:(master) ✗ npm run add-loca ➜ antd-admin git:(master) ✗ npm run add-local ➜ antd-admin git:(master) ✗ npm run add-locale ➜ antd-admin git:(master) ✗ npm run add-locale ➜ antd-admin git:(master) ✗ npm run add-locale j ➜ antd-admin git:(master) ✗ npm run add-locale ja ➜ antd-admin git:(master) ✗ npm run e ➜ antd-admin git:(master) ✗ npm run ex ➜ antd-admin git:(master) ✗ npm run ext ➜ antd-admin git:(master) ✗ npm run extr ➜ antd-admin git:(master) ✗ npm run extra ➜ antd-admin git:(master) ✗ npm run extrac ➜ antd-admin git:(master) ✗ npm run extract ➜ antd-admin git:(master) ✗ npm run add-locale ja > antd-admin@5.0.0-beta add-locale /Users/zuiidea/web/antd-admin> lingui add-locale "ja"Added locale ja.(use "lingui extract" to extract messages)➜ antd-admin git:(master) ✗ npm run extract > antd-admin@5.0.0-beta extract /Users/zuiidea/web/antd-admin> lingui extract➜ antd-admin git:(master) ✗ npm run t ➜ antd-admin git:(master) ✗ npm run tr ➜ antd-admin git:(master) ✗ npm run tra ➜ antd-admin git:(master) ✗ npm run tran ➜ antd-admin git:(master) ✗ npm run trans ➜ antd-admin git:(master) ✗ npm run trans: ➜ antd-admin git:(master) ✗ npm run trans:o ➜ antd-admin git:(master) ✗ npm run trans:on ➜ antd-admin git:(master) ✗ npm run trans:onl Catalog statistics:┌─────────────┬─────────────┬─────────┐│ Language │ Total count │ Missing │├─────────────┼─────────────┼─────────┤│ en (source) │ 52 │ - ││ ja │ 52 │ 52 ││ zh │ 52 │ 0 │└─────────────┴─────────────┴─────────┘(use "lingui add-locale <language>" to add more locales)(use "lingui extract" to update catalogs with new messages)(use "lingui compile" to compile catalogs for production)➜ antd-admin git:(master) ✗ npm run trans:only ➜ antd-admin git:(master) ✗ npm run trans:only > antd-admin@5.0.0-beta trans:only /Users/zuiidea/web/antd-admin> node ./scripts/translate.jsadd to skip: Publish Dateadd to skip: Resetadd to skip: Searchadd to skip: Search Nameadd to skip: Sendadd to skip: Sign inadd to skip: Sign outadd to skip: Switch Themeadd to skip: Tagsadd to skip: The input is not valid E-mail!add to skip: The input is not valid phone!add to skip: Titleadd to skip: Total {total} Itemsadd to skip: Unpublishedadd to skip: Updateadd to skip: Update Useradd to skip: Usernameadd to skip: Viewsadd to skip: Visibilityadd to skip: You have viewed all notifications.completed: en -> zhstart: en -> jalink: en -> ja: /dashboard -> /ja/dashboardyoudao: en -> ja: Add Param -> Param の追加youdao: en -> ja: Address -> アドレスyoudao: en -> ja: Age -> 年齢youdao: en -> ja: Are you sure delete this record? -> このレコードを削除してもよろしいですか?youdao: en -> ja: Author -> 著者youdao: en -> ja: Avatar -> アバターyoudao: en -> ja: Categories -> カテゴリyoudao: en -> ja: Clear notifications -> 通知のクリアyoudao: en -> ja: Comments -> コメントyoudao: en -> ja: Create -> 作成youdao: en -> ja: Create User -> ユーザーの作成same: en -> ja: CreateTimeyoudao: en -> ja: Dark -> 暗いyoudao: en -> ja: Delete -> 削除youdao: en -> ja: Email -> 電子メールyoudao: en -> ja: Female -> 女性youdao: en -> ja: Gender -> 性別youdao: en -> ja: Hi, -> こんにちはyoudao: en -> ja: Image -> イメージyoudao: en -> ja: Light -> 光youdao: en -> ja: Male -> 男性youdao: en -> ja: Name -> 名前youdao: en -> ja: NickName -> ニックネームyoudao: en -> ja: Not Found -> 見つからないyoudao: en -> ja: Operation -> 操作same: en -> ja: Paramsyoudao: en -> ja: Password -> パスワードyoudao: en -> ja: Phone -> 電話youdao: en -> ja: Pick an address -> 住所の選択youdao: en -> ja: Please pick an address -> 住所を選択してくださいsame: en -> ja: Publisedyoudao: en -> ja: Publish Date -> 発行日youdao: en -> ja: Reset -> リセットyoudao: en -> ja: Search -> 検索youdao: en -> ja: Search Name -> 検索名youdao: en -> ja: Send -> 送信youdao: en -> ja: Sign in -> サインインyoudao: en -> ja: Sign out -> サインアウトyoudao: en -> ja: Switch Theme -> テーマを切り替えるyoudao: en -> ja: Tags -> タグyoudao: en -> ja: The input is not valid E-mail! -> 入力が有効な電子メールではありません!youdao: en -> ja: The input is not valid phone! -> 入力は有効な電話ではありません!youdao: en -> ja: Title -> タイトルyoudao: en -> ja: Total {total} Items -> 合計{total}項目youdao: en -> ja: Unpublished -> 未発表youdao: en -> ja: Update -> 更新youdao: en -> ja: Update User -> ユーザーの更新youdao: en -> ja: Username -> 名youdao: en -> ja: Views -> 表示モードyoudao: en -> ja: Visibility -> 可視 性youdao: en -> ja: You have viewed all notifications. -> すべての通知を表示しました。completed: en -> jaAll translations have been completed.➜ antd-admin git:(master) ✗ - \ No newline at end of file diff --git a/docs/_media/term_js_build.svg b/docs/_media/term_js_build.svg deleted file mode 100644 index ff7489f..0000000 --- a/docs/_media/term_js_build.svg +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - ➜ antd-admin git:(master) ✗ ➜ antd-admin git:(master) ✗ n ➜ antd-admin git:(master) ✗ np ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm r ➜ antd-admin git:(master) ✗ npm ru ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run b ➜ antd-admin git:(master) ✗ npm run bu ➜ antd-admin git:(master) ✗ npm run bui ➜ antd-admin git:(master) ✗ npm run buil ➜ antd-admin git:(master) ✗ npm run build Compiling● webpack █████████████████████████ compiling (0%) Compiling ● webpack █████████████████████████ building modules (10%) 2/2 modules 0 active Compiling ● webpack █████████████████████████ building modules (11%) 11/13 modules 2 active node_modules/umi/lib/createHistory.js ● webpack █████████████████████████ building modules (12%) 18/21 modules 3 activ node_modules/webpack/buildin/global.js ● webpack █████████████████████████ building modules (23%) 110/112 modules 2 active node_modules/react-dom/cjs/react-dom.production.min.js ● webpack █████████████████████████ building modules (23%) 115/118 modules 3 act node_modules/util/support/isBufferBrowser.js ● webpack █████████████████████████ building modules (23%) 116/141 modules 25 active node_modules/core-js/modules/_global.js ● webpack █████████████████████████ building modules (25%) 130/142 modules 12 ac node_modules/path-browserify/index.js ● webpack █████████████████████████ building modules (27%) 147/191 modules 44 ac node_modules/core-js/modules/_bind.js ● webpack █████████████████████████ building modules (35%) 215/256 modules 41 ac node_modules/core-js/modules/_inherit-if-required.js ● webpack █████████████████████████ building modules (39%) 249/266 modules 17 ac node_modules/core-js/modules/web.dom.iterable.js ● webpack █████████████████████████ building modules (42%) 267/307 modules 40 ac ...4m › postcss-loader › less-loader › src/pages/404.less ● webpack █████████████████████████ building modules (47%) 313/320 modules 7 act node_modules/prop-types/index.js ● webpack █████████████████████████ building modules (49%) 328/351 modules 23 ac node_modules/react-router-dom/es/index.js ● webpack █████████████████████████ building modules (51%) 343/354 modules 11 ac babel-loader › src/pages/chart/highCharts/HighChartsComponent.js ● webpack █████████████████████████ building modules (52%) 354/372 modules 18 ac src/locales/en/messages.json ● webpack █████████████████████████ building modules (54%) 372/394 modules 22 ac babel-loader › src/utils/theme.js ● webpack █████████████████████████ building modules (57%) 395/422 modules 27 ac node_modules/@babel/runtime/helpers/arrayWithoutHoles.js ● webpack █████████████████████████ building modules (60%) 424/455 modules 31 ac node_modules/antd/es/card/Meta.js ● webpack █████████████████████████ building modules (61%) 427/463 modules 36 ac node_modules/antd/es/_util/throttleByAnimationFrame.js ● webpack █████████████████████████ building modules (64%) 458/477 modules 19 ac node_modules/antd/es/radio/group.js ● webpack █████████████████████████ building modules (67%) 476/497 modules 21 ac babel-loader › src/pages/chart/highCharts/mapdata/europe.js ● webpack █████████████████████████ building modules (66%) 488/514 modules 26 ac node_modules/react-router-dom/node_modules/warning/warning.js ● webpack █████████████████████████ building modules (67%) 493/518 modules 25 ac ...der › less-loader › src/pages/chart/Recharts/Container.less ● webpack █████████████████████████ building modules (67%) 505/525 modules 20 ac node_modules/omit.js/es/index.js ● webpack █████████████████████████ building modules (67%) 532/559 modules 27 ac node_modules/highcharts-exporting/exporting.js ● webpack █████████████████████████ building modules (67%) 574/598 modules 24 ac node_modules/antd/es/date-picker/locale/en_US.js ● webpack █████████████████████████ building modules (68%) 589/608 modules 19 ac node_modules/draft-js/lib/ContentState.js ● webpack █████████████████████████ building modules (67%) 616/641 modules 25 ac node_modules/react-router/es/matchPath.js ● webpack █████████████████████████ building modules (67%) 656/679 modules 23 ac node_modules/draft-js/lib/DraftEntityInstance.js ● webpack █████████████████████████ building modules (67%) 665/690 modules 25 ac node_modules/axios/lib/utils.js ● webpack █████████████████████████ building modules (67%) 685/712 modules 27 ac node_modules/add-dom-event-listener/lib/index.js ● webpack █████████████████████████ building modules (68%) 735/756 modules 21 ac node_modules/lodash/_objectToString.js ● webpack █████████████████████████ building modules (68%) 737/758 modules 21 ac node_modules/lodash/_getRawTag.js ● webpack █████████████████████████ building modules (67%) 756/787 modules 31 ac node_modules/add-dom-event-listener/lib/EventObject.js ● webpack █████████████████████████ building modules (67%) 771/800 modules 29 ac node_modules/core-js/library/modules/_add-to-unscopables.js ● webpack █████████████████████████ building modules (67%) 781/808 modules 27 ac node_modules/core-js/library/modules/_object-dp.js ● webpack █████████████████████████ building modules (68%) 817/836 modules 19 ac node_modules/size-sensor/lib/index.js ● webpack █████████████████████████ building modules (68%) 845/870 modules 25 ac node_modules/is-buffer/index.js ● webpack █████████████████████████ building modules (68%) 892/913 modules 21 ac node_modules/echarts/lib/util/model.js ● webpack █████████████████████████ building modules (68%) 933/963 modules 30 ac node_modules/echarts-liquidfill/node_modules/echarts/lib/model/Series.js ● webpack █████████████████████████ building modules (68%) 978/996 modules 18 ac node_modules/lodash/_baseFor.js ● webpack █████████████████████████ building modules (68%) 1000/1029 modules 29 active node_modules/zrender/lib/contain/text.js ● webpack █████████████████████████ building modules (68%) 1029/1056 modules 27 ...e_modules/echarts-liquidfill/node_modules/echarts/lib/model/mixin/dataFormat.js ● webpack █████████████████████████ building modules (68%) 1059/1077 modules 18 node_modules/lodash/_createBaseFor.js ● webpack █████████████████████████ building modules (68%) 1101/1133 modules 32 node_modules/zrender/lib/graphic/helper/image.js ● webpack █████████████████████████ building modules (68%) 1155/1175 modules 20 node_modules/lodash/_baseGetAllKeys.js ● webpack █████████████████████████ building modules (69%) 1212/1231 modules 19 node_modules/size-sensor/lib/sensors/resizeObserver.js ● webpack █████████████████████████ building modules (68%) 1266/1296 modules 30 node_modules/d3-shape/src/curve/cardinalClosed.js ● webpack █████████████████████████ building modules (68%) 1329/1357 modules 28 ...ules/echarts-liquidfill/node_modules/echarts/lib/data/helper/dataStackHelper.● webpack █████████████████████████ building modules (69%) 1392/1409 modules 17 node_modules/d3-collection/src/set.js ● webpack █████████████████████████ building modules (69%) 1449/1467 modules 18 node_modules/component-classes/index.js ● webpack █████████████████████████ building modules (68%) 1481/1507 modules 26 node_modules/d3-time/src/second.js ● webpack █████████████████████████ building modules (69%) 1534/1551 modules 17 node_modules/echarts-liquidfill/src/liquidFillView.js ● webpack █████████████████████████ building modules (69%) 1594/1607 modules 13 node_modules/rc-animate/es/CSSMotion.js ● webpack █████████████████████████ building modules (68%) 1651/1691 modules 40 node_modules/echarts/lib/component/gridSimple.js ● webpack █████████████████████████ building modules (69%) 1700/1713 modules 13 node_modules/echarts/lib/coord/calendar/prepareCustom.js ● webpack █████████████████████████ building modules (68%) 1739/1771 modules 32 node_modules/echarts/lib/component/tooltip/TooltipContent.js ● webpack █████████████████████████ building modules (69%) 1780/1807 modules 27 node_modules/echarts/lib/chart/scatter/ScatterView.js ● webpack █████████████████████████ building modules (69%) 1781/1808 modules 27 node_modules/echarts/lib/chart/scatter/ScatterSeries.js ● webpack █████████████████████████ building modules (68%) 1819/1852 modules 33 node_modules/echarts/lib/chart/graph/categoryFilter.js ● webpack █████████████████████████ building modules (69%) 1867/1880 modules 13 node_modules/echarts/lib/chart/sankey/SankeySeries.js ● webpack █████████████████████████ building modules (69%) 1916/1944 modules 28 node_modules/echarts/lib/component/dataZoom/DataZoomView.js ● webpack █████████████████████████ building modules (69%) 1966/1993 modules 27 node_modules/echarts/lib/chart/sunburst/SunburstPiece.js ● webpack █████████████████████████ building modules (69%) 2002/2030 modules 28 node_modules/draft-js/lib/CompositeDraftDecorator.js ● webpack █████████████████████████ building modules (69%) 2010/2034 modules 24 node_modules/echarts/lib/component/marker/MarkerModel.js ● webpack █████████████████████████ building modules (69%) 2031/2047 modules 16 babel-loader › src/pages/dashboard/components/browser.js ● webpack █████████████████████████ building modules (69%) 2081/2100 modules 19 node_modules/array-tree-filter/lib/index.js ● webpack █████████████████████████ building modules (69%) 2105/2122 modules 17 node_modules/echarts/lib/component/dataZoom/SelectZoomModel.js ● webpack █████████████████████████ building modules (69%) 2108/2123 modules 15 ● webpack █████████████████████████ building modules (69%) 2117/2127 modules 10 node_modules/react-router-redux/es/middleware.js ● webpack █████████████████████████ building modules (69%) 2122/2130 modules 8 active node_modules/react-router-redux/es/actions.js ● webpack █████████████████████████ building modules (69%) 2143/2167 modules 24 node_modules/react-redux/es/connect/connect.js ● webpack █████████████████████████ building modules (69%) 2185/2203 modules 18 node_modules/rc-dialog/es/DialogWrap.js ● webpack █████████████████████████ building modules (69%) 2237/2265 modules 28 node_modules/antd/es/tag/index.js ● webpack █████████████████████████ building modules (69%) 2283/2301 modules 18 babel-loader › src/pages/user/components/Filter.js ● webpack █████████████████████████ building modules (69%) 2327/2338 modules 11 node_modules/rc-util/es/getScrollBarSize.js ● webpack █████████████████████████ building modules (69%) 2339/2357 modules 18 node_modules/async-validator/es/index.js ● webpack █████████████████████████ building modules (69%) 2347/2363 modules 16 node_modules/lodash/_createCurry.js ● webpack █████████████████████████ building modules (69%) 2384/2400 modules 16 node_modules/rmc-feedback/es/index.js ● webpack █████████████████████████ building modules (69%) 2402/2422 modules 20 node_modules/dom-align/es/getAlignOffset.js ● webpack █████████████████████████ building modules (69%) 2426/2438 modules 12 node_modules/dva-core/lib/prefixType.js ● webpack █████████████████████████ building modules (69%) 2430/2438 modules 8 a● webpack █████████████████████████ building modules (69%) 2451/2471 modules 20 node_modules/antd/es/badge/index.js ● webpack █████████████████████████ building modules (69%) 2485/2494 modules 9 a node_modules/fbjs/lib/emptyObject.js ● webpack █████████████████████████ building modules (69%) 2490/2520 modules 30 node_modules/antd/es/date-picker/WeekPicker.js ● webpack █████████████████████████ building modules (69%) 2512/2524 modules 12 node_modules/react-side-effect/lib/index.js ● webpack █████████████████████████ building modules (69%) 2519/2529 modules 10 node_modules/redux-saga/lib/internal/runSaga.js ● webpack █████████████████████████ building modules (69%) 2524/2532 modules 8 a node_modules/antd/es/layout/Sider.js ● webpack █████████████████████████ building modules (69%) 2540/2549 modules 9 a node_modules/antd/es/_util/getDataOrAriaProps.js ● webpack █████████████████████████ building modules (69%) 2554/2561 modules 7 a node_modules/redux-saga/lib/internal/sagaHelpers/index.js ● webpack █████████████████████████ building modules (69%) 2577/2581 modules 4 a node_modules/rc-switch/es/index.js ● webpack █████████████████████████ building modules (69%) 2581/2589 modules 8 a node_modules/rc-drawer/es/index.js ● webpack █████████████████████████ building modules (69%) 2604/2610 modules 6 a node_modules/rc-calendar/es/date/DateInput.js ● webpack █████████████████████████ finish module graph (70%) ● webpack █████████████████████████ optimizing (73%) ● webpack █████████████████████████ advanced chunk modules optimization (79%) ● webpack █████████████████████████ after hashing (88%) ● webpack █████████████████████████ additional chunk assets processing (90%) ● webpack █████████████████████████ chunk asset optimization (92%) UglifyJSPlugin ● webpack █████████████████████████ after chunk asset optimization (92%) ➜ antd-admin git:(master) ✗ npm run build > antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin> umi build[12:40:22] webpack compiled in 15s 56ms DONE Compiled successfully in 15062ms 12:40:22File sizes after gzip: 887 B dist/p__dashboard__model.js.async.js 877 B dist/precache-manifest.6d2a703c437c4f28b5f6e072817bbe6e.js 786 B dist/p__UIElement__editor__index.async.js 764 B dist/i18n-0.async.js 652 B dist/p__login__index.chunk.css 614 B dist/p__user__$id__models__detail.js.async.js 599 B dist/p__post__model.js.async.js 589 B dist/p__user__$id__index.async.js 569 B dist/service-worker.js 530 B dist/p__request__index.chunk.css 515 B dist/p__login__model.js.async.js 419 B dist/p__index.async.js 245 B dist/p__chart__ECharts__index.chunk.css 230 B dist/p__chart__Recharts__index.chunk.css 138 B dist/p__user__$id__index.chunk.css 74 B dist/p__chart__highCharts__index.chunk.css 66 B dist/p__user__index.chunk.css 66 B dist/p__post__index.chunk.cssThe bundle size is significantly larger than recommended.Consider reducing it with code splitting: https://goo.gl/9VhYWBYou can also analyze the project dependencies: https://goo.gl/LeUzfb➜ antd-admin git:(master) ✗ - 0:00/0:00 - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_media/term_js_i18n.svg b/docs/_media/term_js_i18n.svg deleted file mode 100644 index f985604..0000000 --- a/docs/_media/term_js_i18n.svg +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - ➜ antd-admin git:(master) ✗ ➜ antd-admin git:(master) ✗ n ➜ antd-admin git:(master) ✗ np ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm ➜ antd-admin git:(master) ✗ npm r ➜ antd-admin git:(master) ✗ npm ru ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run ➜ antd-admin git:(master) ✗ npm run a ➜ antd-admin git:(master) ✗ npm run ad ➜ antd-admin git:(master) ✗ npm run add ➜ antd-admin git:(master) ✗ npm run add- ➜ antd-admin git:(master) ✗ npm run add-l ➜ antd-admin git:(master) ✗ npm run add-lo ➜ antd-admin git:(master) ✗ npm run add-loc ➜ antd-admin git:(master) ✗ npm run add-loca ➜ antd-admin git:(master) ✗ npm run add-local ➜ antd-admin git:(master) ✗ npm run add-locale ➜ antd-admin git:(master) ✗ npm run add-locale ➜ antd-admin git:(master) ✗ npm run add-locale j ➜ antd-admin git:(master) ✗ npm run add-locale ja ➜ antd-admin git:(master) ✗ npm run e ➜ antd-admin git:(master) ✗ npm run ex ➜ antd-admin git:(master) ✗ npm run ext ➜ antd-admin git:(master) ✗ npm run extr ➜ antd-admin git:(master) ✗ npm run extra ➜ antd-admin git:(master) ✗ npm run extrac ➜ antd-admin git:(master) ✗ npm run extract ➜ antd-admin git:(master) ✗ npm run add-locale ja > antd-admin@5.0.0-beta add-locale /Users/zuiidea/web/antd-admin> lingui add-locale "ja"Added locale ja.(use "lingui extract" to extract messages)➜ antd-admin git:(master) ✗ npm run extract > antd-admin@5.0.0-beta extract /Users/zuiidea/web/antd-admin> lingui extract➜ antd-admin git:(master) ✗ npm run t ➜ antd-admin git:(master) ✗ npm run tr ➜ antd-admin git:(master) ✗ npm run tra ➜ antd-admin git:(master) ✗ npm run tran ➜ antd-admin git:(master) ✗ npm run trans ➜ antd-admin git:(master) ✗ npm run trans: ➜ antd-admin git:(master) ✗ npm run trans:o ➜ antd-admin git:(master) ✗ npm run trans:on ➜ antd-admin git:(master) ✗ npm run trans:onl Catalog statistics:┌─────────────┬─────────────┬─────────┐│ Language │ Total count │ Missing │├─────────────┼─────────────┼─────────┤│ en (source) │ 52 │ - ││ ja │ 52 │ 52 ││ zh │ 52 │ 0 │└─────────────┴─────────────┴─────────┘(use "lingui add-locale <language>" to add more locales)(use "lingui extract" to update catalogs with new messages)(use "lingui compile" to compile catalogs for production)➜ antd-admin git:(master) ✗ npm run trans:only ➜ antd-admin git:(master) ✗ npm run trans:only > antd-admin@5.0.0-beta trans:only /Users/zuiidea/web/antd-admin> node ./scripts/translate.jsadd to skip: Publish Dateadd to skip: Resetadd to skip: Searchadd to skip: Search Nameadd to skip: Sendadd to skip: Sign inadd to skip: Sign outadd to skip: Switch Themeadd to skip: Tagsadd to skip: The input is not valid E-mail!add to skip: The input is not valid phone!add to skip: Titleadd to skip: Total {total} Itemsadd to skip: Unpublishedadd to skip: Updateadd to skip: Update Useradd to skip: Usernameadd to skip: Viewsadd to skip: Visibilityadd to skip: You have viewed all notifications.completed: en -> zhstart: en -> jalink: en -> ja: /dashboard -> /ja/dashboardyoudao: en -> ja: Add Param -> Param の追加youdao: en -> ja: Address -> アドレスyoudao: en -> ja: Age -> 年齢youdao: en -> ja: Are you sure delete this record? -> このレコードを削除してもよろしいですか?youdao: en -> ja: Author -> 著者youdao: en -> ja: Avatar -> アバターyoudao: en -> ja: Categories -> カテゴリyoudao: en -> ja: Clear notifications -> 通知のクリアyoudao: en -> ja: Comments -> コメントyoudao: en -> ja: Create -> 作成youdao: en -> ja: Create User -> ユーザーの作成same: en -> ja: CreateTimeyoudao: en -> ja: Dark -> 暗いyoudao: en -> ja: Delete -> 削除youdao: en -> ja: Email -> 電子メールyoudao: en -> ja: Female -> 女性youdao: en -> ja: Gender -> 性別youdao: en -> ja: Hi, -> こんにちはyoudao: en -> ja: Image -> イメージyoudao: en -> ja: Light -> 光youdao: en -> ja: Male -> 男性youdao: en -> ja: Name -> 名前youdao: en -> ja: NickName -> ニックネームyoudao: en -> ja: Not Found -> 見つからないyoudao: en -> ja: Operation -> 操作same: en -> ja: Paramsyoudao: en -> ja: Password -> パスワードyoudao: en -> ja: Phone -> 電話youdao: en -> ja: Pick an address -> 住所の選択youdao: en -> ja: Please pick an address -> 住所を選択してくださいsame: en -> ja: Publisedyoudao: en -> ja: Publish Date -> 発行日youdao: en -> ja: Reset -> リセットyoudao: en -> ja: Search -> 検索youdao: en -> ja: Search Name -> 検索名youdao: en -> ja: Send -> 送信youdao: en -> ja: Sign in -> サインインyoudao: en -> ja: Sign out -> サインアウトyoudao: en -> ja: Switch Theme -> テーマを切り替えるyoudao: en -> ja: Tags -> タグyoudao: en -> ja: The input is not valid E-mail! -> 入力が有効な電子メールではありません!youdao: en -> ja: The input is not valid phone! -> 入力は有効な電話ではありません!youdao: en -> ja: Title -> タイトルyoudao: en -> ja: Total {total} Items -> 合計{total}項目youdao: en -> ja: Unpublished -> 未発表youdao: en -> ja: Update -> 更新youdao: en -> ja: Update User -> ユーザーの更新youdao: en -> ja: Username -> 名youdao: en -> ja: Views -> 表示モードyoudao: en -> ja: Visibility -> 可視 性youdao: en -> ja: You have viewed all notifications. -> すべての通知を表示しました。completed: en -> jaAll translations have been completed.➜ antd-admin git:(master) ✗ - 0:00/0:00 - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_sidebar.md b/docs/_sidebar.md deleted file mode 100644 index 3d6d176..0000000 --- a/docs/_sidebar.md +++ /dev/null @@ -1,12 +0,0 @@ -- Getting started - - [Quick Start](getting-started.md) -- Customization - - [Configuration](configuration.md) - - [API Configuration](API-configuration.md) - - [I18n](i18n.md) - - [Layout](layout.md) - - [Request](request.md) -- Guide - - [Deploy](deploy.md) -- [Change Log](change-log.md) -- [FAQ](faq.md) diff --git a/docs/change-log.md b/docs/change-log.md deleted file mode 100644 index 7e8fa95..0000000 --- a/docs/change-log.md +++ /dev/null @@ -1,26 +0,0 @@ -## 6.0.0 - -This project was modernized and migrated to a modern front-end stack. Key highlights of the current version: - -### Modern stack -- Upgraded to Ant Design 6, Umi 4 and TypeScript 5. -- React has been updated to the current major line used in the repo (see `package.json`). - -### Architecture & DX -- Services are implemented as TypeScript modules under `src/services/` and use a centralized HTTP helper `src/utils/request.ts`. -- Lightweight local state management using `zustand` replacing older `dva` patterns. -- Local mock server using `mock/index.js` with `mockjs` for frontend development without backend dependency. - -### Tooling & CI -- Dev workflows use `pnpm` by default; scripts are defined in `package.json` (e.g. `pnpm run dev`, `pnpm run build`). -- Tests use `jest` and related tooling; CI is implemented with GitHub Actions (`.github/workflows/ci.yml`) which installs via `pnpm`, lints and builds the project. - -### Quality and types -- TypeScript types are added/expanded under `src/types/` for safer APIs and components. -- Prettier / ESLint configuration added to enforce code style. - -### Other -- Documentation is maintained under `docs/` (this site) and updated to reflect the new stack and commands. -- Removed old custom components that duplicate Ant Design functionality and simplified the codebase accordingly. - -If you want a more detailed, commit-level changelog for each migration step, I can generate one from git history or help draft a migration guide. \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index 08cb299..0000000 --- a/docs/configuration.md +++ /dev/null @@ -1,33 +0,0 @@ -# Configuration - -You can do some custom configuration in `/src/utils/config.js`: - -## siteName - -- Type `String` - - Configure the site name, apply it to the login box, and display the title text at the top of the sidebar. - -## copyright - -- Type: `String` - - Configure the copyright notice to apply to the login page, at the bottom of the `Primary` layout. - -## logoPath - -- Type: `String` - - Configure the site logo to apply to the login box and the Logo display at the top of the sidebar. - -## apiPrefix - -- Type: `String` - - Configure the prefix of the interface in the project. The interface related documents can be viewed [API configuration](API-configuration.md) - -## fixedHeader - -- Type: `String` - - Under the `Primary` layout, whether the top of the page is fixed when scrolling。 \ No newline at end of file diff --git a/docs/deploy.md b/docs/deploy.md deleted file mode 100644 index 3cc157f..0000000 --- a/docs/deploy.md +++ /dev/null @@ -1,113 +0,0 @@ -# Deploy - -After the development is completed and verified in the development environment, it needs to be deployed to our users. - -![i18n](./_media/term_build.svg) - -## Build - -First execute the following command, - -```bash -npm run build -``` - -After a few seconds, the output should look like this: - -```bash -> antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin -> umi build - -[21:13:17] webpack compiled in 43s 868ms - DONE Compiled successfully in 43877ms 21:13:17 - -File sizes after gzip: - - 1.3 MB dist/vendors.async.js - 308.21 KB dist/umi.js - 45.49 KB dist/vendors.chunk.css - 36.08 KB dist/p__chart__highCharts__index.async.js - 33.53 KB dist/p__user__index.async.js - 22.36 KB dist/p__chart__ECharts__index.async.js - 4.21 KB dist/p__dashboard__index.async.js - 4.06 KB dist/umi.css - ... -``` - -The `build` command will package all resources, including JavaScript, CSS, web fonts, images, html, and more. You can find these files in the `dist/` directory. - -> If you have requirements for using HashHistory , deploying html to non-root directories, statics, etc., check out [Umi Deployment] (https://umijs.org/en/guide/deploy.html). - -## Local verification - - -Local verification can be done via `serve` before publishing. - -``` -$ yarn global add serve -$ serve ./dist - -Serving! - -- Local: http://localhost:5000 -- On Your Network: http://{Your IP}:5000 - -Copied local address to clipboard! - -``` - -Access [http://localhost:5000](http://localhost:5000), under normal circumstances, it should be consistent with `npm start` (The API may not get the correct data). - - -## Deploy - -Next, we can upload the static file to the server. If you use Nginx as the Web server, you can configure it in `ngnix.conf`: -``` -server - { - listen 80; - # Specify an accessible domain name - server_name antd-admin.zuiidea.com; - # The directory where the compiled files are stored - root /home/www/antd-admin/dist; - - # Proxy server interface to avoid cross-domain - location /api { - proxy_pass http://localhost:7000/api; - } - - Because the front end uses BrowserHistory, it will route backback to index.html - location / { - index index.html; - try_files $uri $uri/ /index.html; - } - } -``` - -Restart the web server and access [http://antd-admin.zuiidea.com](http://antd-admin.zuiidea.com) , You will see the correct page. - -```bash -nginx -s reload -``` - -Similarly, if you use Caddy as a web server, you can do this in `Caddyfile`: - -``` -antd-admin.zuiidea.com { - gzip - root /home/www/antd-admin/dist - proxy /api http://localhost:7000 - - rewrite { - if {path} not_match ^/api - to {path} {path}/ / - } -} - - -antd-admin.zuiidea.com/public { - gzip - root /home/www/antd-admin/dist/static/public -} - -``` diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 2c6409d..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,9 +0,0 @@ -# FAQ - -Most asked - -## create new page - - 1. just copy a page in /src/pages (route here auto generated by [umi](https://umijs.org/guide/router.html#basic-routing)) - 2. modify namespace/pathToRegexp in model.js - 3. modify mock route.js to add a route \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 24ea6a8..0000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,75 +0,0 @@ -# Quick Start - -> Before delving into Ant Design React, a good knowledge base of [React](http://facebook.github.io/react/) 、 [ES2015+](http://es6.ruanyifeng.com/) 、 [Antd Design](https://ant.design/docs/react/introduce-cn). Learn about [UmiJS](https://umijs.org/). Ensure you have [Node.js](https://nodejs.org/) (v18+ recommended) and [Git](https://git-scm.com/) installed. - -## Installation - -```bash -git clone https://github.com/zuiidea/antd-admin.git my-project -cd my-project -``` - -## Scaffolding - -The project layout is as follows: - -```bash -├── dist/ # Default build output directory -├── mock/ # Mock files -├── public/ # Static resource -├── src/ # Source code -│ ├── components/ # Components -│ ├── e2e/ # Integrated Test Case -│ ├── layouts/ # Common Layouts -│ ├── locales/ # i18n resources -│ ├── models/ # Global dva Model -│ ├── pages/ # Sub-pages and templates -│ ├── services/ # Backend Services -│ │ ├── api.js # API configuration -│ │ └── index.js # API export -│ ├── themes/ # Themes -│ │ ├── default.less # Less variable -│ │ ├── index.less # Global style -│ │ ├── mixin.less # Less mixin -│ │ └── vars.less # Less variable and mixin -│ ├── utils/ # Utility -│ │ ├── config.js # Application configuration -│ │ ├── constant.js # Static constant -│ │ ├── index.js # Utility methods -│ │ ├── request.js # Request function(axios) -│ │ └── theme.js # Style variables used in js -├── .editorconfig -├── .env -├── .eslintrc -├── .gitignore -├── .prettierignore -├── .prettierrc -├── .stylelintrc.json -├── .travis.yml -└── .umirc.js -└── package.json -``` - -## Development - -1. Install dependencies (recommended: `pnpm`). - -```bash -pnpm install -``` - -Or with other package managers: - -```bash -npm install -# or -yarn install -``` - -2. Start local development server. - -```bash -npm run dev -``` - -3. After startup, open the app in a browser (see dev server output for the actual URL/port). diff --git a/docs/i18n.md b/docs/i18n.md deleted file mode 100644 index 17384e2..0000000 --- a/docs/i18n.md +++ /dev/null @@ -1,121 +0,0 @@ -# Internationalization (i18n) Guide - -This project uses Umi's `plugin-locale` (based on `react-intl`) to manage localization. Translation resources live in `src/locales` (for example `en-US.json`, `zh-CN.json`). Below are instructions for: adding a language, adding a translation entry, switching languages, and disabling i18n. Examples use Japanese `ja-JP`. - -## 1. Add a new language (persistent) - -1. Create a new translation file under `src/locales/` with the language id as the file name, for example: `src/locales/ja-JP.json`. - -2. The file should contain a JSON object of translation keys and values (nesting allowed): - -```json -{ - "welcome": "ようこそ", - "login": { - "username": "ユーザー名", - "password": "パスワード", - "submit": "ログイン" - } -} -``` - -3. If you want Ant Design components to use the same locale (dates, component text), add the AntD locale import and mapping in `src/app.tsx`'s `pick` function, e.g.: - -```ts -import jaJP from 'antd/locale/ja_JP'; -// ... -if (l === 'ja-JP') return jaJP; -``` - -4. Add the language to the language selector (for example in `src/components/TopbarControls.tsx`): - -```ts -{ key: 'ja-JP', label: '日本語' } -``` - -5. Restart the dev server: - -```bash -pnpm run dev -``` - -Umi collects files in `src/locales` into `localeInfo` at build/dev time so the new language becomes available. If you prefer not to restart, see the runtime registration option below. - -## 2. Add a translation entry - -1. Add the same key to each language JSON (keep `en-US.json` and `zh-CN.json` up to date alongside the new language): - -`src/locales/en-US.json` - -```json -"my.feature.greeting": "Hello from feature" -``` - -`src/locales/zh-CN.json` - -```json -"my.feature.greeting": "来自功能的问候" -``` - -2. In a component use `useIntl()` and `formatMessage`: - -```ts -import { useIntl } from 'umi'; -const intl = useIntl(); -const text = intl.formatMessage({ id: 'my.feature.greeting', defaultMessage: 'Hello' }); -``` - -Use `useIntl()` (or `injectIntl`) so the component updates automatically when the locale changes. - -## 3. Runtime switching and registering - -- Switch locale (writes `umi_locale` to localStorage and either reloads or triggers an update): - -```ts -import { setLocale } from 'umi'; -// switch and reload -setLocale('ja-JP', true); -// switch without reload (emit update event) -setLocale('zh-CN', false); -``` - -- Register a locale at runtime (no server restart required): - -```ts -import { addLocale, setLocale } from 'umi'; -import jaMessages from '@/locales/ja-JP.json'; -import jaAntd from 'antd/locale/ja_JP'; - -addLocale('ja-JP', jaMessages, { antd: jaAntd, momentLocale: 'ja' }); -setLocale('ja-JP', false); -``` - -`addLocale` merges messages into the internal `localeInfo`. You can pass extra data (like AntD locale and moment locale) so components using AntD/moment behave correctly. - -## 4. Disable i18n (turn off multi-language) - -To permanently disable multi-language support: - -1. In `src/app.tsx`'s `LocaleWrapper`, return a fixed AntD locale (ignore `localStorage` `umi_locale`): - -```ts -const pick = (l: string | null) => { - return zhCN; // force Chinese -}; -``` - -2. Remove or hide the language switcher (for example, remove language menu items in `src/components/TopbarControls.tsx`). - -3. Optionally remove other JSON files in `src/locales` except the single target language to reduce build size. - -If you only want to temporarily prevent user switching, hide the switcher (step 2) and keep files intact for easy re-enable. - -## 5. Debugging & common issues - -- New language not appearing: restart dev server or use `addLocale`; ensure AntD locale mapping exists in `app.tsx`. -- Missing translations: check all `src/locales/*.json` contain the key, and the `formatMessage` id is correct. -- Want automated extraction of message ids: this repo does not include extraction scripts; consider integrating `lingui`, `babel-plugin-react-intl`, or other tooling and adding scripts to `package.json`. - -If you want, I can: -- create an initial `src/locales/ja-JP.json` and update `TopbarControls`/`app.tsx` mappings; or -- add a small example component that demonstrates runtime registration using `addLocale`. Tell me which one you prefer. diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index e981d4c..0000000 --- a/docs/index.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - antd-admin - An admin dashboard application demo built upon Ant Design and UmiJS - - - - - - - - - -
Loading...
- - - - - - - - - \ No newline at end of file diff --git a/docs/layout.md b/docs/layout.md deleted file mode 100644 index 6c415d0..0000000 --- a/docs/layout.md +++ /dev/null @@ -1,60 +0,0 @@ -# Layout - -## Add a new layout - -Take a new layout named `secondary` as an example to make the route starting with `secondary` use this layout. - -1. Add related configuration in `src/utils/config.js`. For details, please refer to [layouts](/configuration?id=layouts). - -```javascript -   layouts: [ -           { -               name: 'primary', -               include: [/.*/], -               exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/secondary\/(.*)/], -           }, -           { -               name: 'secondary', -               include: [/(\/(en|zh))*\/secondary\/(.*)/], -           }, -   ], -``` - -2. Add the `secondary` layout component to the `src/layouts/BaseLayout.js` file. - -```javascript -   import SecondaryLayout from './SecondaryLayout' - -   const LayoutMap = { -     primary: PrimaryLayout, -     public: PublicLayout, -     secondary: SecondaryLayout, -   } -``` - -3. Add the `SecondaryLayout.js` file to the `src/layouts/` directory. - -```javascript -   import React from 'react' - -   export default ({ children }) => { -     return ( -       
-         

Secondary

-         {children} -       
-     ) -   } -``` - -4. Add a `secondary/index.js` file to the `src/pages/` directory. - -```javascript -   import React from 'react' - -   export default ({ children }) => { -     Return
Secondary page Content
-   } -``` - -5. Finally, start the development mode with `npm run dev`, then open the layout page in your browser (see dev server output for the actual URL), e.g. `/secondary/`. diff --git a/docs/request.md b/docs/request.md deleted file mode 100644 index f41dbd3..0000000 --- a/docs/request.md +++ /dev/null @@ -1,33 +0,0 @@ -# HTTP requests - -This project uses a small fetch-based HTTP helper implemented at `src/utils/request.ts` (export default `api`). The helper centralizes: - -- Prefixing paths with `config.apiPrefix`. -- Handling `params` (query string) and `body` (JSON or FormData). -- Injecting an `Authorization` header from local storage when available. -- Unified response parsing and error handling (expects backend responses in the form `{ success, message, data }`). - -Key behaviors - -- `api(path, options)` merges `config.apiPrefix` + `path` to build the URL. -- If `options.params` is provided it's serialized and appended as query string. -- `options.body` handling: - - FormData is sent as-is (do not set Content-Type manually). - - Strings and Blobs are sent directly. - - Plain objects are JSON.stringified and Content-Type defaults to `application/json`. -- If a token exists in local storage (key `app-auth-token`), the helper injects an `Authorization: Bearer ` header unless one is already provided. -- Responses are expected to be JSON with `{ success: boolean, message?: string, data: T }`. Non-success responses throw an Error and show a UI message (uses `antd`'s `message`). - -Usage (TypeScript-friendly): - -```ts -import api from '@/utils/request'; - -// GET with query params -const page = await api('/users', { method: 'GET', params: { page: 1, pageSize: 10 } }); - -// POST with JSON body -const created = await api('/user', { method: 'POST', body: { name: 'Alice' } }); -``` - -The helper returns the `data` field from successful responses. Check `src/utils/request.ts` for exact typings and behavior. \ No newline at end of file diff --git a/docs/sw.js b/docs/sw.js deleted file mode 100644 index cf6295c..0000000 --- a/docs/sw.js +++ /dev/null @@ -1,83 +0,0 @@ -/* =========================================================== - * docsify sw.js - * =========================================================== - * Copyright 2016 @huxpro - * Licensed under Apache 2.0 - * Register service worker. - * ========================================================== */ - -const RUNTIME = 'docsify' -const HOSTNAME_WHITELIST = [ - self.location.hostname, - 'fonts.gstatic.com', - 'fonts.googleapis.com', - 'unpkg.com' -] - -// The Util Function to hack URLs of intercepted requests -const getFixedUrl = (req) => { - var now = Date.now() - var url = new URL(req.url) - - // 1. fixed http URL - // Just keep syncing with location.protocol - // fetch(httpURL) belongs to active mixed content. - // And fetch(httpRequest) is not supported yet. - url.protocol = self.location.protocol - - // 2. add query for caching-busting. - // Github Pages served with Cache-Control: max-age=600 - // max-age on mutable content is error-prone, with SW life of bugs can even extend. - // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. - // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 - if (url.hostname === self.location.hostname) { - url.search += (url.search ? '&' : '?') + 'cache-bust=' + now - } - return url.href -} - -/** - * @Lifecycle Activate - * New one activated when old isnt being used. - * - * waitUntil(): activating ====> activated - */ -self.addEventListener('activate', event => { - event.waitUntil(self.clients.claim()) -}) - -/** - * @Functional Fetch - * All network requests are being intercepted here. - * - * void respondWith(Promise r) - */ -self.addEventListener('fetch', event => { - // Skip some of cross-origin requests, like those for Google Analytics. - if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) { - // Stale-while-revalidate - // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale - // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1 - const cached = caches.match(event.request) - const fixedUrl = getFixedUrl(event.request) - const fetched = fetch(fixedUrl, { cache: 'no-store' }) - const fetchedCopy = fetched.then(resp => resp.clone()) - - // Call respondWith() with whatever we get first. - // If the fetch fails (e.g disconnected), wait for the cache. - // If there’s nothing in cache, wait for the fetch. - // If neither yields a response, return offline pages. - event.respondWith( - Promise.race([fetched.catch(_ => cached), cached]) - .then(resp => resp || fetched) - .catch(_ => { /* eat any errors */ }) - ) - - // Update the cache with the version we fetched (only for ok status) - event.waitUntil( - Promise.all([fetchedCopy, caches.open(RUNTIME)]) - .then(([response, cache]) => response.ok && cache.put(event.request, response)) - .catch(_ => { /* eat any errors */ }) - ) - } -}) \ No newline at end of file diff --git a/docs/zh-cn/API-configuration.md b/docs/zh-cn/API-configuration.md deleted file mode 100644 index 59fbf1b..0000000 --- a/docs/zh-cn/API-configuration.md +++ /dev/null @@ -1,35 +0,0 @@ -# 接口配置 - -本项目使用基于 TypeScript 的按功能分割服务模块,位于 `src/services/`,所有服务通过共享的 HTTP 封装 `src/utils/request.ts`(默认导出为 `api` 函数)发起请求。 - -为什么采用此模式? -- 每个领域(user/admin/menu/dashboard)有独立函数,职责清晰。 -- 使用 TypeScript 提供输入/输出类型定义,便于维护(参见 `src/types`)。 -- 请求处理集中化(查询参数、JSON body、鉴权 header、统一响应格式等)在 `src/utils/request.ts`。 - -示例(来自 `src/services/user.ts`): - -```ts -import type { PagedResponse, User } from '@/types/user'; -import api from '../utils/request'; - -export async function listUsers( - params?: Record, -): Promise> { - return api>('/users', { method: 'GET', params }); -} - -export async function createUser(values: Record): Promise { - return api('/user', { method: 'POST', body: values }); -} -``` - -使用方式: -- 通过 `import { listUsers } from '@/services/user'` 导入命名函数。 -- `api(path, options)` 返回后端约定格式 `{ success, message, data }` 中的 `data` 字段。 - -关于 `src/utils/request.ts`: -- 会将 `config.apiPrefix` 与 `path` 拼接;支持 `params`(查询)、`body`(JSON 或 FormData);若存在本地 token 则自动注入 `Authorization`。 -- 对非成功响应会抛出异常并在界面上显示消息(使用 `antd` 的 `message`)。 - -如果你在迁移旧的 API 映射实现,建议按当前结构将功能拆分为小型服务模块并补充类型定义。 \ No newline at end of file diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md deleted file mode 100644 index 8910de9..0000000 --- a/docs/zh-cn/README.md +++ /dev/null @@ -1,53 +0,0 @@ -

- - antd-admin - -

- -

AntD Admin

- -
- -基于 Ant Design 6、Umi 4 与 TypeScript 的企业级前端起手模版。 - -[![antd](https://img.shields.io/badge/AntD-^6.0.0-1890ff?style=flat-square&logo=ant-design)](https://github.com/ant-design/ant-design) -[![umi](https://img.shields.io/badge/Umi-^4.0.0-ff9900?style=flat-square&logo=umijs)](https://github.com/umijs/umi) - -
- -- 文档源文件位于 `docs/` 目录。 - -## 快速开始 - -本仓库示例与 CI 使用 `pnpm`,你也可以使用 `npm` 或 `yarn`,但推荐 `pnpm`。 - -```bash -# 克隆 -git clone https://github.com/zuiidea/antd-admin.git -cd antd-admin - -# 安装依赖 -pnpm install - -# 启动开发(含本地 mock) -npm run dev - -# 构建 -npm run build - -# lint 与格式化 -npm run lint -npm run format -``` - -说明: - -- 本地 mock 实现在 `mock/index.js`,便于在无后端时开发。 -- CI 工作流位于 `.github/workflows/ci.yml`(使用 pnpm、lint、build)。 -- 若需对接真实后端,请修改 `src/services` 下的 API。 - -更多详细文档请查看 `docs/` 目录下的其他页面。 - -## 参与贡献 - -欢迎提交 Issue 或 PR 改进项目文档或代码。 diff --git a/docs/zh-cn/_sidebar.md b/docs/zh-cn/_sidebar.md deleted file mode 100644 index 68dc92e..0000000 --- a/docs/zh-cn/_sidebar.md +++ /dev/null @@ -1,11 +0,0 @@ -- 入门 - - [快速上手](zh-cn/getting-started.md) -- 定制化 - - [配置项](zh-cn/configuration.md) - - [接口配置](zh-cn/API-configuration.md) - - [国际化](zh-cn/i18n.md) - - [布局](zh-cn/layout.md) - - [http 请求](zh-cn/request.md) -- 指南 - - [部署](zh-cn/deploy.md) -- [更新日志](zh-cn/change-log.md) diff --git a/docs/zh-cn/change-log.md b/docs/zh-cn/change-log.md deleted file mode 100644 index 54b0ec6..0000000 --- a/docs/zh-cn/change-log.md +++ /dev/null @@ -1,52 +0,0 @@ -## 5.0.0 - -#### 优化 - -- 尽量使用修饰器,简化代码编写,提高代码可读性。 - -- API 配置化,简化获取数据方式。 - -- `utils` 内文件拆分,各司其职。 - -- 简化`utils/request`文件,不做特殊处理。 - -#### 规范 - -- 函数添加描述、参数、返回值等注释,含糊不清的代码增加注释,规范参考 [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html#appendices-jsdoc-tag-reference)。 - -- 语义化版本号,规范参加 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/)。 - -- 静态代码检查,统一代码风格,代码提交前将会使用 `prettier`、`stylelint`、`eslint` 规范代码。 - -- Git 提交信息规范化,[git-commit-emoji-cn](https://github.com/liuchengxu/git-commit-emoji-cn)。 - -- 基于 `Umi` 的约定式路由,无需再写路由配置文件。 - -- 使用 `React 16` 新特性,如 `Fragment`、`Context`、 `PureComponent`等。 - -#### 功能 - -- 支持国际化,源码中抽离翻译字段,按需加载语言包,自动在线翻译。 - -- 支持按需引入 `lodash` 函数。 - -- 支持多布局,可根据规则规定哪些路由使用哪种布局。 - -- 支持 Antd Admin 在 Travis 上自动编译和部署。 - -- 使用 `Docsify` 生成文档网站。 - - -#### 样式 - -- 新增 Antd Admin 独立 Logo。 - -- 重写整体布局组件,优化菜单、面包屑导航自动高亮,菜单自动展开等逻辑。 - -- 移动端菜单更改为抽屉式。 - -#### 其他 - -- 废弃 `IconFont`、 `Search`、`DataTable`等组件,因为在 `Antd` 中有很好的支持和可替代的。 - - diff --git a/docs/zh-cn/configuration.md b/docs/zh-cn/configuration.md deleted file mode 100644 index 4adcb07..0000000 --- a/docs/zh-cn/configuration.md +++ /dev/null @@ -1,33 +0,0 @@ -# 配置项 - -你可以在 `/src/utils/config.js` 里做一些自定义配置: - -## siteName - -- 类型: `String` - - 配置站点名称,应用到登录框,侧边栏顶部的标题文字显示。 - -## copyright - -- 类型: `String` - - 配置版权声明,应用到登录页、`Primay`布局底部。 - -## logoPath - -- 类型: `String` - - 配置站点 Logo,应用到登录框,侧边栏顶部的 Logo 显示。 - -## apiPrefix - -- 类型: `String` - - 配置项目中接口的前缀,接口相关文档可查看 [接口配置](API-configuration.md) - -## fixedHeader - -- 类型: `String` - - 在`Primary`布局下,页面滚动时是否固定顶部。 \ No newline at end of file diff --git a/docs/zh-cn/deploy.md b/docs/zh-cn/deploy.md deleted file mode 100644 index cdbac06..0000000 --- a/docs/zh-cn/deploy.md +++ /dev/null @@ -1,114 +0,0 @@ -# 部署 - -完成开发并且在开发环境验证之后,就需要部署给我们的用户了。 - -![i18n](../_media/term_build.svg) - -## 构建 - -先执行下面的命令, - -```bash -npm run build -``` - -几秒后,输出应该如下: - -```bash -> antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin -> umi build - -[21:13:17] webpack compiled in 43s 868ms - DONE Compiled successfully in 43877ms 21:13:17 - -File sizes after gzip: - - 1.3 MB dist/vendors.async.js - 308.21 KB dist/umi.js - 45.49 KB dist/vendors.chunk.css - 36.08 KB dist/p__chart__highCharts__index.async.js - 33.53 KB dist/p__user__index.async.js - 22.36 KB dist/p__chart__ECharts__index.async.js - 4.21 KB dist/p__dashboard__index.async.js - 4.06 KB dist/umi.css - ... -``` - -`build` 命令会打包所有的资源,包含 JavaScript, CSS, web fonts, images, html 等。你可以在 `dist/` 目录下找到这些文件。 - -> 如果有使用 HashHistory 、 部署 html 到非根目录、静态化等需求,请查看[Umi 部署](https://umijs.org/zh/guide/deploy.html)。 - -## 本地验证 - - -发布之前,可以通过 `serve` 做本地验证, - -``` -$ yarn global add serve -$ serve ./dist - -Serving! - -- Local: http://localhost:5000 -- On Your Network: http://{Your IP}:5000 - -Copied local address to clipboard! - -``` - -访问 [http://localhost:5000](http://localhost:5000),正常情况下法应该是和 `npm start` 一致的(接口可能无法获取到正确数据)。 - - -## 部署 - -接下来,我们可以把静态文件上传到服务器,如果使用 Nginx 作为 Web server,你可以在 `ngnix.conf` 中这样配置: - -``` -server - { - listen 80; - # 指定可访问的域名 - server_name antd-admin.zuiidea.com; - # 编译后的文件存放的目录 - root /home/www/antd-admin/dist; - - # 代理服务端接口,避免跨域 - location /api { - proxy_pass http://localhost:7000/api; - } - - # 因为前端使用了BrowserHistory,所以将路由 fallback 到 index.html - location / { - index index.html; - try_files $uri $uri/ /index.html; - } - } -``` - -重启 Web server,访问 [http://antd-admin.zuiidea.com](http://antd-admin.zuiidea.com) ,你将看到正确的页面。 - -```bash -nginx -s reload -``` - -类似的,如果你使用 Caddy 作为 Web server,你可以在 `Caddyfile` 中这样配置: - -``` -antd-admin.zuiidea.com { - gzip - root /home/www/antd-admin/dist - proxy /api http://localhost:7000 - - rewrite { - if {path} not_match ^/api - to {path} {path}/ / - } -} - - -antd-admin.zuiidea.com/public { - gzip - root /home/www/antd-admin/dist/static/public -} - -``` diff --git a/docs/zh-cn/faq.md b/docs/zh-cn/faq.md deleted file mode 100644 index bf5f1bd..0000000 --- a/docs/zh-cn/faq.md +++ /dev/null @@ -1,7 +0,0 @@ -# 问题集锦 - -## 新建页面 - - 1. 直接从/src/pages复制一个page (会自动创建路由[umi](https://umijs.org/zh/guide/router.html#%E7%BA%A6%E5%AE%9A%E5%BC%8F%E8%B7%AF%E7%94%B1)) - 2. 修改 namespace/pathToRegexp 在 model.js - 3. 修改 mock中route.js增加一条route \ No newline at end of file diff --git a/docs/zh-cn/getting-started.md b/docs/zh-cn/getting-started.md deleted file mode 100644 index 9f53ea1..0000000 --- a/docs/zh-cn/getting-started.md +++ /dev/null @@ -1,69 +0,0 @@ -# 快速上手 - -> 在开始之前,推荐先学习 [React](http://facebook.github.io/react/) 、 [ES2015+](http://es6.ruanyifeng.com/) 、 [Antd Design](https://ant.design/docs/react/introduce-cn) , 了解 [UmiJS](https://umijs.org/) 、[Dva](http://github.com/dvajs/dva) ,并正确安装和配置了 [Node.js](https://nodejs.org/) v8 或以上 、[Git](https://git-scm.com/)。提前了解和学习这些知识会非常有帮助。 - -## 安装 - -```bash -git clone https://github.com/zuiidea/antd-admin.git my-project -cd my-project -``` - -1. 克隆项目并安装依赖(推荐使用 `pnpm`)。 - -```bash -git clone https://github.com/zuiidea/antd-admin.git my-project -cd my-project -pnpm install -``` - -或使用其它包管理器: - -```bash -npm install -# 或 -yarn install -``` - -2. 启动开发模式: - -```bash -npm run dev -``` - -3. 启动完成后在浏览器打开项目(查看开发服务器输出以获取实际 URL/端口)。 -│ │ ├── index.js # 工具函数 -│ │ ├── request.js # 异步请求函数(axios) -│ │ └── theme.js # 项目需要在js中使用到样式变量 -├── .editorconfig # 编辑器配置 -├── .env # 环境变量 -├── .eslintrc # ESlint配置 -├── .gitignore # Git忽略文件配置 -├── .prettierignore # Prettier忽略文件配置 -├── .prettierrc # Prettier配置 -├── .stylelintrc.json # Stylelint配置 -├── .travis.yml # Travis配置 -└── .umirc.js # Umi配置 -└── package.json # 项目信息 -``` - -## 本地开发 - -项目推荐使用 `pnpm` 进行依赖管理: - -```bash -pnpm install -npm run dev -``` - -也可以使用 `npm` 或 `yarn`: - -```bash -npm install -# 或 -yarn install -# 然后使用对应的启动命令,例如 `npm run dev` 或 `yarn dev`(若 package.json 中配置了), -# 本仓库默认开发脚本为 `npm run dev`。 -``` - -启动后在浏览器打开项目(查看开发服务器输出以获取实际 URL/端口)。 diff --git a/docs/zh-cn/i18n.md b/docs/zh-cn/i18n.md deleted file mode 100644 index 4298e8d..0000000 --- a/docs/zh-cn/i18n.md +++ /dev/null @@ -1,123 +0,0 @@ -# 国际化(i18n)使用说明 - -本项目通过 Umi 的 `plugin-locale`(基于 `react-intl`)管理多语言,翻译资源位于 `src/locales`(例如 `en-US.json`、`zh-CN.json`)。下面说明如何:新增语言、增加翻译条目、切换语言与关闭多语言。示例以新增日语 `ja-JP` 为例。 - -## 1. 新增一种语言(长期支持) - -1. 在 `src/locales/` 下创建新的翻译文件,文件名为语言标识,例如:`src/locales/ja-JP.json`。 - -2. 文件内容为 JSON 格式的翻译对象(可嵌套): - -```json -{ - "welcome": "ようこそ", - "login": { - "username": "ユーザー名", - "password": "パスワード", - "submit": "ログイン" - } -} -``` - -3. 若希望 Ant Design 组件也使用对应的本地化(日期/组件文案),在 `src/app.tsx` 的 `pick` 函数中添加对应的 AntD locale 引入与映射,例如: - -```ts -import jaJP from 'antd/locale/ja_JP'; -// ... -if (l === 'ja-JP') return jaJP; -``` - -4. 在语言选择列表(例如 `src/components/TopbarControls.tsx` 的 `localeMenuItems`)中加入新语言项: - -```ts -{ key: 'ja-JP', label: '日本語' } -``` - -5. 重启开发服务器: - -```bash -pnpm run dev -``` - -Umi 会在构建时将 `src/locales` 下的文件编入 `localeInfo`,页面即可使用新语言。 - -提示:如不想重启或想动态注册翻译,可使用运行时 API `addLocale(name, messages, extraLocales)`(见下一节)。 - -## 2. 增加一条多语言文本(翻译条目) - -1. 在所有语言的 JSON 文件中添加相同的 id(推荐同时维护 `en-US.json` 与 `zh-CN.json`,新语言也应添加): - -`src/locales/en-US.json` - -```json -"my.feature.greeting": "Hello from feature" -``` - -`src/locales/zh-CN.json` - -```json -"my.feature.greeting": "来自功能的问候" -``` - -2. 在组件中使用 `useIntl()` 获取 `intl`,并调用 `formatMessage`: - -```ts -import { useIntl } from 'umi'; -const intl = useIntl(); -const text = intl.formatMessage({ id: 'my.feature.greeting', defaultMessage: 'Hello' }); -``` - -建议使用 `useIntl()`(或 `injectIntl`)以保证语言切换时组件可自动刷新并显示新文本。 - -## 3. 运行时切换或注册语言 - -- 切换语言(会写入 `localStorage` 并触发刷新或事件): - -```ts -import { setLocale } from 'umi'; -// 切换并刷新页面 -setLocale('ja-JP', true); -// 切换但不刷新(触发事件更新) -setLocale('zh-CN', false); -``` - -- 运行时注册(无需重启): - -```ts -import { addLocale, setLocale } from 'umi'; -import jaMessages from '@/locales/ja-JP.json'; -import jaAntd from 'antd/locale/ja_JP'; - -addLocale('ja-JP', jaMessages, { antd: jaAntd, momentLocale: 'ja' }); -setLocale('ja-JP', false); -``` - -`addLocale` 会把翻译合并到内部 `localeInfo`,并可同时提供 AntD 与(可选的)moment 本地化配置。 - -## 4. 关闭多语言(禁用 i18n) - -若想永久关闭多语言功能: - -1. 在 `src/app.tsx` 的 `LocaleWrapper` 中,直接返回固定的 AntD locale(忽略 `localStorage` 中的 `umi_locale`): - -```ts -const pick = (l: string | null) => { - return zhCN; // 强制使用中文 -}; -``` - -2. 移除或隐藏语言切换控件(例如删除 `src/components/TopbarControls.tsx` 中的语言菜单项或 Dropdown)。 - -3. 可选:删除 `src/locales` 中其它语言文件,仅保留目标语言文件以减少构建体积。 - -如果只是临时禁止用户切换,建议仅执行第 2 步(隐藏切换),以便未来快速恢复多语言支持。 - -## 5. 调试与常见问题 - -- 新增语言后无显示:确认已重启 dev server(或使用 `addLocale` 动态注册),并在 `app.tsx` 中为该语言提供 AntD locale。 -- 翻译未生效或缺失:检查 `src/locales/*.json` 是否包含对应 id,且 `formatMessage` 中的 id 拼写正确。 -- 想自动抽取翻译 id:仓库未内置抽取脚本,建议按需集成 `lingui`、`babel-plugin-react-intl` 或其他工具并在 `package.json` 中添加相应脚本。 - -如果你需要,我可以: -- 为你创建一个 `src/locales/ja-JP.json` 的初始翻译并更新 `TopbarControls` 与 `app.tsx` 映射;或 -- 添加一个示例组件,演示如何在运行时通过 `addLocale` 注册新语言。请告诉我你更希望我执行哪项。 diff --git a/docs/zh-cn/layout.md b/docs/zh-cn/layout.md deleted file mode 100644 index 4e55fb7..0000000 --- a/docs/zh-cn/layout.md +++ /dev/null @@ -1,60 +0,0 @@ -# 布局 - -## 新增布局 - -以新增名为 `secondary` 的布局为例,使以 `secondary` 开头的路由都使用该布局。 - -1. 在 `src/utils/config.js` 新增相关配置,参数详细请查看 [layouts](/zh-cn/configuration?id=layouts)。 - - ```javascript - layouts: [ - { - name: 'primary', - include: [/.*/], - exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/secondary\/(.*)/], - }, - { - name: 'secondary', - include: [/(\/(en|zh))*\/secondary\/(.*)/], - }, - ], - ``` - -2. 在`src/layouts/BaseLayout.js` 文件中新增 `secondary` 布局组件。 - - ```javascript - import SecondaryLayout from './SecondaryLayout' - - const LayoutMap = { - primary: PrimaryLayout, - public: PublicLayout, - secondary: SecondaryLayout, - } - ``` - -3. 在`src/layouts/` 目录中新增 `SecondaryLayout.js` 文件。 - - ```javascript - import React from 'react' - - export default ({ children }) => { - return ( -
-

Secondary

- {children} -
- ) - } - ``` - -4. 在`src/pages/` 目录中新增 `secondary/index.js` 文件。 - - ```javascript - import React from 'react' - - export default ({ children }) => { - return
Secondary page Content
- } - ``` - -5. 最后,使用 `npm run dev` 启动开发模式,然后在浏览器中打开该布局页面(查看开发服务器输出以获取实际 URL),例如 `/secondary/`。 diff --git a/docs/zh-cn/request.md b/docs/zh-cn/request.md deleted file mode 100644 index 998a4b6..0000000 --- a/docs/zh-cn/request.md +++ /dev/null @@ -1,33 +0,0 @@ -# HTTP 请求 - -本项目使用位于 `src/utils/request.ts` 的轻量封装(基于 `fetch`),默认导出为 `api`。该封装负责: - -- 将 `config.apiPrefix` 与请求 `path` 拼接为最终 URL。 -- 处理 `params`(查询字符串)与 `body`(JSON 或 FormData)。 -- 在本地有 token 时自动注入 `Authorization` 头(本项目使用 localStorage 键 `app-auth-token`)。 -- 统一解析后端返回(后端需遵循 `{ success, message, data }` 约定)并在非成功时抛出错误并显示 UI 提示(使用 `antd` 的 `message`)。 - -主要行为说明 - -- `api(path, options)` 会把 `config.apiPrefix` 与 `path` 拼接。 -- 当传入 `options.params` 时,会序列化为查询字符串并追加到 URL。 -- `options.body` 的处理策略: - - 若为 FormData,直接发送(不要手动设置 Content-Type)。 - - 若为字符串或 Blob,直接发送。 - - 其余对象会被 `JSON.stringify`,默认设置 `Content-Type: application/json`(除非已在 headers 中指定)。 -- 若 localStorage 中有 token 且未在 headers 中显式提供 Authorization,将自动设置 `Authorization: Bearer `。 -- 成功响应返回后端 `data` 字段;响应格式不符合约定或后端返回失败时会抛出异常。 - -使用示例(TypeScript): - -```ts -import api from '@/utils/request'; - -// 带查询参数的 GET -const page = await api('/users', { method: 'GET', params: { page: 1, pageSize: 10 } }); - -// 发送 JSON 请求体的 POST -const created = await api('/user', { method: 'POST', body: { name: 'Alice' } }); -``` - -详细实现请参阅 `src/utils/request.ts`,文档中仅列出使用建议与约定。 \ No newline at end of file diff --git a/docs/zh-cn/router.md b/docs/zh-cn/router.md deleted file mode 100644 index f511c2e..0000000 --- a/docs/zh-cn/router.md +++ /dev/null @@ -1,5 +0,0 @@ -# 路由 - -本项目中采用约定式路由 - -参考[umi 路由](https://umijs.org/zh/guide/router.html) diff --git a/jest.config.ts b/jest.config.ts deleted file mode 100644 index 989eaee..0000000 --- a/jest.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Config, configUmiAlias, createConfig } from 'umi/test'; - -export default async () => { - return ( - (await configUmiAlias({ - ...createConfig({ - target: 'browser', - jsTransformer: 'esbuild', - jsTransformerOpts: { jsx: 'automatic' }, - }), - // you can override umi default jest config here - setupFilesAfterEnv: ['/src/setupTests.ts'], - })) as Config.InitialOptions - ); -}; diff --git a/llm/skills/admin.skill.ts b/llm/skills/admin.skill.ts deleted file mode 100644 index 81b074b..0000000 --- a/llm/skills/admin.skill.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as adminSvc from '../../src/services/admin'; - -/** - * Skill 元数据与封装 — Admin - * 目的:为自动化代理或 LLM 提供明确的能力描述与可直接调用的包装函数。 - */ -export const meta = { - id: 'admin:manage', - name: 'Admin Management', - description: '登录/登出、获取当前管理员、管理管理员列表(增删改查)', - inputs: { - login: { adminname: 'string', password: 'string' }, - getAdmin: { id: 'string' }, - createAdmin: { values: 'object' }, - updateAdmin: { id: 'string', values: 'object' }, - deleteAdmin: { id: 'string' }, - deleteAdmins: { ids: 'string[]' }, - listAdmins: { params: 'object | undefined' }, - }, - outputs: 'JSON (see src/types/admin.ts for schema)', -}; - -export const actions = { - async login(payload: { adminname: string; password: string }) { - return adminSvc.login(payload); - }, - async logout() { - return adminSvc.logout(); - }, - async currentAdmin() { - return adminSvc.currentAdmin(); - }, - async listAdmins(params?: Record) { - return adminSvc.listAdmins(params as any); - }, - async createAdmin(values: Record) { - return adminSvc.createAdmin(values); - }, - async getAdmin(id: string) { - return adminSvc.getAdmin(id); - }, - async updateAdmin(id: string, values: Record) { - return adminSvc.updateAdmin(id, values); - }, - async deleteAdmin(id: string) { - return adminSvc.deleteAdmin(id); - }, - async deleteAdmins(ids: string[]) { - return adminSvc.deleteAdmins(ids); - }, -}; - -export default { meta, actions }; diff --git a/llm/skills/dashboard.skill.ts b/llm/skills/dashboard.skill.ts deleted file mode 100644 index 9b673e6..0000000 --- a/llm/skills/dashboard.skill.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as dashSvc from '../../src/services/dashboard'; - -export const meta = { - id: 'dashboard:view', - name: 'Dashboard View', - description: '获取仪表盘汇总数据', - inputs: {}, - outputs: 'JSON summary data from /dashboard', -}; - -export const actions = { - async getDashboard() { - return dashSvc.getDashboard(); - }, -}; - -export default { meta, actions }; diff --git a/llm/skills/index.ts b/llm/skills/index.ts deleted file mode 100644 index 34cdb01..0000000 --- a/llm/skills/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Skills registry - * - * 目的:集中导出 llm/skills 下的 skill 模块,便于运行时自动发现或静态引入。 - * 导出: - * - `skills`:按短名映射的 skill 模块集合。 - * - `listSkills()`:返回所有 skill 的 meta 数组,便于展示或自动选择。 - * - `getSkillById(id)`:基于 meta.id 查找对应 skill 模块。 - */ -import admin from './admin.skill'; -import dashboard from './dashboard.skill'; -import menu from './menu.skill'; -import user from './user.skill'; - -const skills: Record = { - admin, - menu, - user, - dashboard, -}; - -export function listSkills() { - return Object.values(skills).map((s) => s.meta); -} - -export function getSkillById(id: string) { - return Object.values(skills).find((s) => s.meta && s.meta.id === id); -} - -export { skills }; - -export default skills; diff --git a/llm/skills/menu.skill.ts b/llm/skills/menu.skill.ts deleted file mode 100644 index c542d96..0000000 --- a/llm/skills/menu.skill.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as menuSvc from '../../src/services/menu'; - -export const meta = { - id: 'menu:manage', - name: 'Menu Management', - description: '列出、创建、更新、删除与排序菜单项', - inputs: { - listMenus: {}, - createMenu: { values: 'MenuFormValues' }, - updateMenu: { id: 'number | string', values: 'Partial' }, - deleteMenu: { id: 'number | string' }, - sortMenus: { order: 'Array' }, - }, - outputs: 'JSON (see src/types/menu.ts for schema)', -}; - -export const actions = { - async listMenus() { - return menuSvc.listMenus(); - }, - async createMenu(values: any) { - return menuSvc.createMenu(values); - }, - async updateMenu(id: number | string, values: any) { - return menuSvc.updateMenu(id, values); - }, - async deleteMenu(id: number | string) { - return menuSvc.deleteMenu(id); - }, - async sortMenus(order: Array) { - return menuSvc.sortMenus(order); - }, -}; - -export default { meta, actions }; diff --git a/llm/skills/user.skill.ts b/llm/skills/user.skill.ts deleted file mode 100644 index 93a30b6..0000000 --- a/llm/skills/user.skill.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as userSvc from '../../src/services/user'; - -export const meta = { - id: 'user:manage', - name: 'User Management', - description: '用户列表/分页、创建、获取、更新、删除与批量删除', - inputs: { - listUsers: { params: 'object | undefined' }, - createUser: { values: 'object' }, - getUser: { id: 'string' }, - updateUser: { id: 'string', values: 'object' }, - deleteUser: { id: 'string' }, - deleteUsers: { ids: 'string[]' }, - }, - outputs: 'JSON (see src/types/user.ts for schema)', -}; - -export const actions = { - async listUsers(params?: Record) { - return userSvc.listUsers(params as any); - }, - async createUser(values: Record) { - return userSvc.createUser(values); - }, - async getUser(id: string) { - return userSvc.getUser(id); - }, - async updateUser(id: string, values: Record) { - return userSvc.updateUser(id, values); - }, - async deleteUser(id: string) { - return userSvc.deleteUser(id); - }, - async deleteUsers(ids: string[]) { - return userSvc.deleteUsers(ids); - }, -}; - -export default { meta, actions }; diff --git a/llms.txt b/llms.txt deleted file mode 100644 index 7d78d9f..0000000 --- a/llms.txt +++ /dev/null @@ -1,36 +0,0 @@ -项目:antd-admin_next - -目标:将仓库中的关键信息写成对大模型(LLM)友好的简短说明,便于自动化理解、代码导航和提示生成。 - -概览: -- 技术栈:React + Ant Design + TypeScript(前端单页/多页面结构),源码位于 `src/`。 -- 路由/页面:页面文件放在 `src/pages/` 目录;布局在 `src/layouts/`;组件在 `src/components/`。 -- 重要工具:`src/utils/request.ts` 为 HTTP 请求封装,使用 `fetch`,默认 API 前缀由 `src/utils/config.ts` 提供。 - -API 与约定: -- 全局 API 前缀:`/api/v1`(见 `src/utils/config.ts`)。 -- 请求函数 `api(path, options)`: - - 自动把 `options.params` 转为查询字符串。 - - 对对象 body 自动 JSON 序列化并设置 `Content-Type: application/json`(FormData 会被保留)。 - - 当响应状态为 `204` 返回空对象;当 `res.ok` 为 false 时抛出 Error,错误信息优先使用响应体中的 `message` 字段。 - -如何使用(对模型说明): -- 若要调用后端资源,请调用 `api('/resource', { method: 'GET' | 'POST', params, body })`。 -- 成功响应应为 JSON,失败会抛出 Error。处理调用时应捕获异常并展示 `error.message`。 - -本文件用途: -- 作为 LLM 的快速上下文:当模型在代码中自动生成、补全或审阅代码时,可引用此文件以获得仓库结构、关键约定和请求行为的摘要。 - -快速运行(可能依赖 package manager): -- 常用:`npm run dev` 或 `pnpm dev` 启动开发服务器。 - -如需更详细的接口契约或后端响应示例,请查看对应的 `src/services/` 目录下的 API 封装文件。 - -Agent Skills: -- 为方便自动化代理(Agent/LLM)调用,项目在 `llm/skills/` 下提供了若干能力描述与包装: - - `llm/skills/admin.skill.ts`:管理员管理相关操作(login、logout、list/create/update/delete) - - `llm/skills/menu.skill.ts`:菜单管理操作(list/create/update/delete/sort) - - `llm/skills/user.skill.ts`:用户管理操作(list/create/get/update/delete/batch) - - `llm/skills/dashboard.skill.ts`:获取仪表盘数据的读取操作 - -这些 skill 文件导出 `meta`(能力描述)和 `actions`(可直接调用的异步函数),便于代理在运行时发现并调用对应后端逻辑。 diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 3316983..0000000 --- a/manifest.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "Antd-Admin", - "start_url": ".", - "display": "standalone", - "background_color": "#fff", - "description": "A front-end solution for enterprise applications built upon Ant Design and UmiJS", - "icons": [{ - "src": "logo/logo@96.png", - "sizes": "72x72" - }, - { - "src": "logo/logo@128.png", - "sizes": "128x128" - }, - { - "src": "logo/logo@144.png", - "sizes": "144x144" - }, - { - "src": "logo/logo@152.png", - "sizes": "152x152" - }, - { - "src": "logo/logo@192.png", - "sizes": "192x192" - }, - { - "src": "logo/logo@384.png", - "sizes": "384x384" - }, - { - "src": "logo/logo@512.png", - "sizes": "512x512" - } - ] -} diff --git a/mock/index.js b/mock/index.js deleted file mode 100644 index 372bbd5..0000000 --- a/mock/index.js +++ /dev/null @@ -1,373 +0,0 @@ -// Consolidated mock server for development (simplified) -const Mock = require('mockjs'); -const qs = require('qs'); - -const ApiPrefix = '/api/v1'; - -// simple in-memory admin users with richer mock fields -const adminList = [ - { - id: 1, - username: 'admin', - password: 'admin', - real_name: '李四', - nick_name: '超级管理员', - mobile: '13800000000', - avatar: Mock.Random.image('100x100', '#4096ff', '#fff', 'png', 'A'), - role: '管理员', - department: '运维部', - permissions: '*', - status: 1, - remark: '拥有所有权限', - lastLogin: Mock.Random.datetime(), - createdBy: 'system', - }, - { - id: 2, - username: 'guest', - password: 'guest', - real_name: '访客', - nick_name: '访客', - mobile: '15100000000', - avatar: Mock.Random.image('100x100', '#ff7f50', '#fff', 'png', 'G'), - role: '访客', - department: '外部', - permissions: '/dashboard,/users,/users/list', - status: 1, - remark: '', - lastLogin: Mock.Random.datetime(), - createdBy: 'system', - }, -]; - -// users list -let users = Mock.mock({ - 'data|20-30': [ - { - id: '@id', - name: '@name', - nickName: '@last', - phone: /^1[34578]\d{9}$/, - 'age|18-60': 1, - address: '@county(true)', - isMale: '@boolean', - email: '@email', - createTime: '@datetime', - avatar() { - return Mock.Random.image( - '100x100', - '#4096ff', - '#757575', - 'png', - this.nickName ? this.nickName.substr(0, 1) : '', - ); - }, - }, - ], -}).data; - -// routes database (source of truth) -const ROUTES_DB = [ - { id: '1', name: '仪表盘', icon: 'home', route: '/dashboard' }, - { id: '2', name: 'Ai会话', icon: 'robot', route: '/ai/chat' }, - { id: '3', name: '用户管理', icon: 'user', route: '/users' }, - { - id: '4', - breadcrumbParentId: '3', - name: '用户列表', - icon: 'team', - route: '/users/list', - }, - { - id: '5', - menuParentId: '-1', - breadcrumbParentId: '4', - name: '用户详情', - route: '/users/:id', - }, - { id: '6', name: '系统设置', icon: 'settings', route: '/sys' }, - { - id: '7', - breadcrumbParentId: '6', - name: '成员列表', - icon: 'team', - route: '/admin/list', - }, - { - id: '8', - menuParentId: '-1', - breadcrumbParentId: '6', - name: '成员详情', - icon: 'user', - route: '/admin/:id', - }, - { - id: '9', - breadcrumbParentId: '6', - name: '菜单设置', - icon: 'tool', - route: '/sys/menu', - }, - { id: '10', name: '项目介绍', icon: 'info-c', route: '/guide' }, -]; - -// convert routes DB into menus tree used by UI -function buildMenusFromRoutes(db) { - const items = db - // exclude routes that should not appear in menu (explicit menuParentId === '-1') - .filter((r) => !(r.menuParentId === '-1' || r.menuParentId === -1)) - .map((r, idx) => { - const id = Number(r.id) || r.id; - // prefer explicit menuParentId, fall back to breadcrumbParentId - const parentIdRaw = r.breadcrumbParentId ?? null; - const parentId = - parentIdRaw === undefined || parentIdRaw === null - ? null - : parentIdRaw === '-1' - ? null - : Number(parentIdRaw); - return { - id, - title: r.name, - icon: r.icon, - route: r.route, - parentId, - status: 1, - sort: idx + 1, - children: [], - }; - }); - - const map = new Map(); - items.forEach((it) => map.set(it.id, it)); - const roots = []; - items.forEach((it) => { - if (it.parentId == null || !map.has(it.parentId)) { - roots.push(it); - } else { - map.get(it.parentId).children.push(it); - } - }); - - // sort recursively by sort - const sortRec = (arr) => { - arr.sort((a, b) => (a.sort || 0) - (b.sort || 0)); - arr.forEach((c) => { - if (c.children && c.children.length) sortRec(c.children); - }); - }; - sortRec(roots); - return roots; -} - -const menus = buildMenusFromRoutes(ROUTES_DB); - -// dashboard -const Dashboard = Mock.mock({ - 'sales|6': [ - { - 'name|+1': 2008, - 'Clothes|200-500': 1, - 'Food|180-400': 1, - 'Electronics|300-550': 1, - }, - ], - cpu: { 'usage|50-600': 1, space: 825, 'cpu|40-90': 1 }, - browser: [ - { name: 'Google Chrome', percent: 43.3, status: 1 }, - { name: 'Mozilla Firefox', percent: 33.4, status: 2 }, - ], -}); - -// simple helper to read cookie token -function parseTokenFromCookie(headers) { - const cookie = headers.cookie || ''; - const cookies = qs.parse(cookie.replace(/\s/g, ''), { delimiter: ';' }); - if (!cookies.token) return null; - try { - return JSON.parse(cookies.token); - } catch (e) { - return null; - } -} - -module.exports = { - [`GET ${ApiPrefix}/dashboard`](req, res) { - res.json({ success: true, data: Dashboard }); - }, - - [`GET ${ApiPrefix}/menus`](req, res) { - res.json({ success: true, data: menus }); - }, - - [`POST ${ApiPrefix}/admin/login`](req, res) { - const { username, password } = req.body || {}; - const admin = adminList.find( - (u) => u.username === username && u.password === password, - ); - - if (admin) { - const now = new Date(); - now.setDate(now.getDate() + 1); - res.cookie( - 'token', - JSON.stringify({ id: admin.id, deadline: now.getTime() }), - { maxAge: 24 * 3600 * 1000, httpOnly: true }, - ); - res.json({ success: true, message: 'Ok', data: null }); - } else { - res - .status(400) - .json({ success: false, message: 'Invalid credentials', data: null }); - } - }, - - [`GET ${ApiPrefix}/admin/logout`](req, res) { - res.clearCookie('token'); - res.json({ success: true, data: null }); - }, - - [`GET ${ApiPrefix}/admin`](req, res) { - const token = parseTokenFromCookie(req.headers); - const response = {}; - let admin = {}; - if (!token) { - res - .status(200) - .json({ success: false, message: 'Not Login', data: null }); - return; - } - response.success = token.deadline > new Date().getTime(); - if (response.success) { - const adminItem = adminList.find((u) => u.id === token.id); - if (adminItem) { - const { password, ...other } = adminItem; - admin = other; - } - } - res.json({ success: response.success, data: admin || null }); - }, - - [`GET ${ApiPrefix}/users`](req, res) { - const { query } = req; - let { pageSize, page, ...other } = query || {}; - pageSize = Number(pageSize) || 10; - page = Number(page) || 1; - let newData = users.slice(); - for (const key in other) { - if (Object.prototype.hasOwnProperty.call(other, key)) { - newData = newData.filter( - (item) => String(item[key]).indexOf(String(other[key])) > -1, - ); - } - } - res.status(200).json({ - success: true, - data: { - data: newData.slice((page - 1) * pageSize, page * pageSize), - total: newData.length, - }, - }); - }, - - [`GET ${ApiPrefix}/user/:id`](req, res) { - const { id } = req.params || {}; - let item = users.find((u) => String(u.id) === String(id)); - // 如果找不到用户,则生成一个带有相同 id 的 mock 用户,避免 404 - if (!item) { - item = Mock.mock({ - id: String(id), - name: '@name', - nickName: '@last', - phone: /^1[34578]\d{9}$/, - 'age|18-60': 1, - address: '@county(true)', - isMale: '@boolean', - email: '@email', - createTime: '@datetime', - avatar() { - return Mock.Random.image( - '100x100', - '#4096ff', - '#757575', - 'png', - this.nickName ? this.nickName.substr(0, 1) : '', - ); - }, - }); - } - // enrich with some additional mock fields: phone model and browsing history - const enriched = { - ...item, - // ensure lastLogin exists for user detail view (used by UI) - lastLogin: item.lastLogin || Mock.Random.datetime(), - phoneModel: Mock.Random.pick([ - 'iPhone 13', - 'iPhone 14 Pro', - 'Samsung S22', - 'Huawei P50', - 'Xiaomi 12', - ]), - browsingHistory: Mock.mock({ - 'data|3-6': [ - { - url: '@url', - title: '@title', - time: '@datetime', - }, - ], - }).data, - }; - const { ...other } = enriched; - res.status(200).json({ success: true, data: other }); - }, - - [`GET ${ApiPrefix}/routes`](req, res) { - res.status(200).json({ success: true, data: ROUTES_DB }); - }, - - // admin accounts CRUD - [`GET ${ApiPrefix}/admins`](req, res) { - res.status(200).json({ - success: true, - data: { data: adminList, total: adminList.length }, - }); - }, - - [`GET ${ApiPrefix}/admin/:id`](req, res) { - const { id } = req.params || {}; - const item = adminList.find((u) => String(u.id) === String(id)); - if (!item) - return res - .status(404) - .json({ success: false, message: 'Not found', data: null }); - const { password, ...other } = item; - res.status(200).json({ success: true, data: other }); - }, - - [`POST ${ApiPrefix}/admin`](req, res) { - const body = req.body || {}; - const id = adminList.length - ? Math.max(...adminList.map((u) => u.id)) + 1 - : 1; - - const next = { id, ...body }; - adminList.push(next); - const { password, ...other } = next; - res.status(200).json({ success: true, data: other }); - }, - - [`PATCH ${ApiPrefix}/admin/:id`](req, res) { - const { id } = req.params || {}; - const body = req.body || {}; - const idx = adminList.findIndex((u) => String(u.id) === String(id)); - if (idx === -1) - return res - .status(404) - .json({ success: false, message: 'Not found', data: null }); - - adminList[idx] = { ...adminList[idx], ...body }; - const { password, ...other } = adminList[idx]; - res.status(200).json({ success: true, data: other }); - }, -}; diff --git a/package.json b/package.json deleted file mode 100644 index 94d966b..0000000 --- a/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "antd-admin", - "version": "6.0.0", - "description": "An admin dashboard application demo built upon Ant Design and UmiJS", - "license": "MIT", - "scripts": { - "build": "umi build", - "dev": "umi dev", - "test": "jest", - "test:watch": "jest --watch", - "format": "prettier --write .", - "lint": "eslint --ext .ts,.tsx src", - "lint:fix": "eslint --ext .ts,.tsx src --fix", - "start": "npm run dev" - }, - "dependencies": { - "@ant-design/icons": "^6.1.0", - "@ant-design/x": "^2.3.0", - "@ant-design/x-sdk": "^2.3.0", - "@umijs/plugins": "^4.6.32", - "antd": "^6.3.2", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "umi": "^4.6.32", - "zustand": "^5.0.0" - }, - "devDependencies": { - "@testing-library/jest-dom": "^6.1.1", - "@testing-library/react": "^16.0.0", - "@types/jest": "^29.5.3", - "@types/react": "19.2.14", - "@types/react-dom": "^19.2.3", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "@umijs/test": "^4.6.32", - "eslint": "^8.47.0", - "eslint-config-prettier": "^10.1.8", - "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-prettier": "^5.1.0", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-unused-imports": "^4.4.1", - "jest": "^29.6.1", - "mockjs": "^1.1.0", - "prettier": "^3.0.0", - "prettier-plugin-organize-imports": "^3.2.2", - "prettier-plugin-packagejson": "^2.4.3", - "ts-jest": "^29.1.0", - "ts-node": "^10.9.2", - "typescript": "^5.9.3", - "jest-environment-jsdom": "^29.6.1", - "jsdom": "^21.1.1" - } -} diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 7ca2170ea0f4e0472c56924a402807e10ddf0220..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67646 zcmeHQ349bq_U?e7BIY0g5{@K5xJ3m#7d0y?c(4nKh$pvzkOSpZgoIny8wABwSy53C zMZ6H@X7K>z5Zy~xS=Ya}qH@ekuJ`|5b-zwcPxWL#VBO8c-=n&!Yx;ZrzOPMy zG>fLUKk5OY{Q4FZB-)n8Wr{_4^^Te!7oSmPU3gm1x}bB&>e}&ub-r$=2Ey99b_%Gz z-O&G%GwG=LcsPM*QZl&aI46O3lx%smEgIeE80rcwj6Bhz5wLvNNSqtf@NmLgbTh8W z_=Sv77Y5vd#57nY4q2d!?W{bY6R9IDl| zQz#YuUkLsSVRymipMPrLakS0F@wDG|JTMsicf7E3sP2WG%4>8xeV=vl86|SL|B8~v zfcrwk_2%Rn;J*pD?+v>HTt7j1CoYCP4*sWs{~l;dqr8R*XtU@~T@K6r(HP_z=Ktoz zW59oV@Lvd=g}_(|+W_p6Veb;n5JuspB8NoBVbuK^l(z8QW-;00! zxqud&g)t8bjkO0TYEHTb%})*l*;Id#!F3E=*G#C{#d zUO9}E6`*7b;{f%Mu)GsXpoQf)*O`W4j<~h)A>AU4LmuCL4{Prt@V|*hL34u7rS607 z$4F>$WZm8HV+7?V*Wvl-P=5vvB&$#NfYtN7U`~&70uS}*a^UOBesh3O5|@`r$!%PB z&bjBEy4^P*{VQ<(ufIDdQ26^U!4rF5@axn7=F}QAI_XlnCGjtaKOz1wl(BMcHe{>=_P`yoDv21JXl4pd&+0}YI!yrfz*sl`D%ado$V^$d}9 z#rc7=d*ue!z$$v4cYxS%OufzvsAE;fG1q%(=k30BAMWpdR{G`!_FtYGxWD%W!Nz^N z1+8nKvtP>(jmP?WY*J_NzZ3B%Wvl_k0rTLP@F3%nP>fa#5X}>|GD9JW!5=4Ut4`H3Ut5ng20a6T?15U z2h{PEXk)8vHyzu3xsOlZ5U&5+Ro#MF*IgQl-N7T=lP07e5B{G({7Knplx(^dOdNQ1 zkdL__|HQk&|FH+-GWf3r{Cj|1;fo)p1!zsQC^GVz)VEuJaBMXF93Sg-`HZwl$?bMub?u6)y9X9rdr7F?fXjk0`*@@}(gYrR zDLoPY?`f>VKkLA&fr#;F;K1hMQ}F*A8kG{0%QPPA{c*`%fd9F|S3V^9>(JE!eAMR# z(P9BKSde%V_^%O*12_CWVD-N&n0fuB!Pl<4BtS>SE)DDlUJ>}OGpN^p?)i{zO}D^* zB8^Xe3hOUY#_=DE<7irNIe-RCYlOnY+rj@avBV$z*QW7F_ruC;e8%>{_vR$_gYmud zQ$gaZ&_GO+Q2$=R+WmS2@4TT$pbSP;+4V8>k!wWn8~m>S?}9yU>=`Dr_}1-D|BAJp22_h`+b1KZs-Xepl&qh96n?Bm%dUupW|!E=lkKD zd+o6H;EI8lm)5)ShX2go0Ns{yEZvqo8yX-Lep8$uz!?Z zRs3mU@-eVmfnO1UAEpEL0k~ctWPKuKCH~J?J7T9Y`p}$0A06D1_#NGx5}p6a*w>$wG8EVWh41x&^)~^&hz?Eo zN(UYv>|C%H`$(5X3&-`n_gMq_1ji4!Dp&#|9Ftr23D|XRllF){dVi;%d`|DHAItUh z@~YtOfmesl9oX;atpjNa*8Ih(&A|U=%)z9L4UOt(6yn$0sTsj+B%S?rqrOKl;Oy~0le!Y zWnLX9E)Z9j2ON7j5G|;@Eb}vSk@PZ+P4-=XtK45yuQ%|&KPY^^xk2V6wMI}{*oifZ zul`sU1FsKu8hBms?*p#|hdwrl+pqIKZ~WZB^mqCWx-PhAaKDgmyrSv&V9(Js9{cqg zg8w&RM3ce)WR!k7VE)B8{(c;&T!8aqv6dT`JPOZsHBG+JOyEyVOT_qp%81W?NG^!T zNgjN^xnUgklEx<2HsMR#pc{g{VBfUeJcK}0api3E)Wj9IUzy^=&y5N=Yvnc|Cu!2yriMedw~CXzrh^z4ScY% zb$r%iRaz7sde^M>-0&UtGcKjEW_*e5-~quQgZl?dVe~7oe-9oII{23sax*cOG-rTC zGt%lH|1U-SNf~nx#sli6e4uN=)d9HDIT)W*jy2nSnwWaLpG$OagJY0`=J?_(EwB#; z9Y|gfd@1#uG$Hu~_>k&{FYSW|h6;uZ4AGE*LE8q|$AhKrDZ|^*caZZrPd~n1cwh93 za&5bBXwQ&AC9TD`BURE2yxX5qFPEm(`w|);j6+)ktOc(Q^f4LJcvQLITkI)x$Nr7+ z7HB5kGtH>i9{3;G_`vn*FieZMrmh1$FNMtwJTI^ollG$QkMVEB*Bgfn2^HQrBt&dD zh6U~8!QczWVmC^=Q`-IQzI@L9oLr%?GUf0{?Gjd+)a!qevsmpo0Km7P= z*sxHQ`<3B$aBwxcBh{i=X`L|s{(<w|+YdX#(=mJe8mtOwYn2>dc|UqD4^F+E2O8xiU| z?55C9Lx+cG=uI##!z@_Y&gZ3%DsfKwN?p$TUQfIJP2r!1AG?N+C~0a8++k{@JJXH< z2NMzhaut7_1DyxS2~l})#-z*(s!UmrvELYLKcmOsIRW^CfggmC>x1|)4Sc#<#IfM( zIZ3pjG~j4Ke7HU3LujH6O^WS%!Nc=Px(pi``UFOY(}o_}^_CCK560%WF}l$k=^lJ< z`_9y+$N{@(7Gggum5dzdI$$nTPC%Ti%n2z$nq04tijs_Dj}|mx{8Zqt3B%WQAUR3* zp(=c}MqwSKl)+|Daf4dXBT;#FL}5wV@cfeX!wbNFzHP%%vTK~W+RLKkTwh)1i%6KcQhb6U><}E;X(4U%nPQuLB?ZI%KO+; zXpQk_bc$x-J>MTve-Heh6~6L;{cv=s<4Zo!{cwHo)#9hX{|!y5a@p_tw{sa;!wXC9 z96l;kcGD>ET?ms>KbAHwVQycokK5+y8`fVxr+pT<-wYpBaw+(CeLLI-ba(v*bXV#s z#GjOL{B_fL;P{(3;QVYJkLAb@Gigdg-|t({19;}VEA3fmpu*^bjxRdYeGo0GoWy=O zI6_j|lCoiLuPk)um4y6KkC&oKH=*@)34e;zre^w7RE>%A#Mo#tWe z-&4OY;=c#+CuO1oE=32-0p|r7i$)$0cb5b7wdaM;p^Hf)(H9S88jr~F(em<))aPkd{bZWrll$k^ zPXPZDfnBcfbstnd0)8|t$lM_FLgf6kj%KDcrD?IR`@CTsaZA7|xV`N3yon_rz$ou_ zn5pD8-MF9hb=$$Kv`Oin)B4|DQkFll^vS|WW%Uat9GUL`(<6A#^FV`B>Hhlv1PA2C zpE(d7n15FTIM(L|nV)4mMsn~o_H(YG8QzFi)@e?9Q+lBOcKASaAFj`)6?oytb(Z}wF{?|78Dehd(--7s)GUi{*I~Iil9*>fjITuK75FRRXLK)WYd3b&| zjy;;!*rK@^=`^Ro3V-}4Ilu47=s>uTxmo1|^pRX}S86%9pGJ42`R=!1POby|#V)??hiE`bUYqDT6ntF=;)ASD z?oNFK&oy}I$`VIS zJ#u4S?vWnCygRpHAH;tz%;sO>&!smXxH^Cys^Y<(8~%yCoYV0>Ng%^YdK7#8^BSH@ za~pgc72lW-JX&B4=sHmILfTh&mb}F8nIp`ZL#z}|E3*oVORdSXsm7?Or6+^GDd2w% zY)=eYC`X^qpqnN5+^}1wm!;=V4q8}SScNky4$-67K1NGhTHyag#D59mPtXA=4p;|D z3*gw*fTIN&hq?~@){6Wbc~AXE>E2A=_blMQ7Vzf+zg*!fKLj78(xUhg#RsKDN0Xuh zyL<`Hv?o&85&ycBRWyez@NZ3hm}-E_#+Vnnpd5?2;Yo139_3E(iu_Re9_lMm&O@I8 zuv3uhQwERTYk~jR$U6+ZFn+;*`bCKU|G~gN;;Wmh1BVC61yYI@WSyvTvz8a`#UAo~ z^?wWgjo#9Hto!DtcY+50rtsMZ-4DT6T4Wzs2QV+bpBC>;JAgUs8u(zuKT?gOXO>&w z-O9(BZS);fqhM-TZPbs4odEt%Kv@TNT*1`xTG!=&hqYa)1^%sZcOIF2J=1;CYf z9~&J{52rs!4uSzFHJNz`5k42kMt%KdTVhJ#5c>61)F}l9n|~phwcDBL3wtQpWMuISA7M$3Mz= z^gc7pZm^1GH*7@n%(<7QbwmEi7zQ6oBJlluh=wn@N#=&x4YuJKrVXBJ{}<>wtoJ~V z;~nqgjkD>G={pdAa?RBN+Pqq*YCP&WVNQbp;$BGinZKe$f5O;*JiQ+HU*lr4AA--N z;ENV?{CQ3;(DOk!ekt&0(qlRCv2RcgzF(CV|~7p4}^nQ)(G|- z@gY5!ZeH}kAMk9^V;NV$haX&Q_CvI&bPxq!@{+6#q_na5nc?3wH@zc0cI4OmhVcHX zo9Itib3dNZ2=RXt@vr1wbRZn)T2N!r8IxWO@L1I6Xq$s#n%l@Y_GlsAGdXEDjL-qjDII$7MFWx-!ZgtE5t^6byWjMyCg=z|M-TAd-fYwx8rTO8NEz!uESe5f zUIq`M0V#z8TMN6Phtn|rjdwzepuYu8a-o5*U3~UK@RcSVU7ED0&kbh$AJjALo6U^) z)xD9P%&bcb8odDiZSF+_Qu>WWJs#l$#|?PFLV6or!PZpH~&Hp;!IC8tpWWN(-VzK!|@&cd3EUe z0iONv-Mqy385^;uWfosG;@Nbo^d!dK!p3KS|Bn!VQt@XEh;c3uQ#wFh;;)ZK|M_7* z*6+RPE;IcK|H;NJ;lmb%AI*oT_?!#aI4{8}*9fKHKaUpu)^z*TzKs@U*8&HV!G9Sz zAbNrg9Q*Mgxj;(M0`o5#P-D{NVLd(Cs3E=W_x+y63G`Hxo9M~L0fn!8aPYl8C>?@h zH!o>gRQZVW68oX);E(AaLKiJ)etg~M6+1oG%0ld$ga2KKKPlt*i-`_&9>Q_~;vAs^ z=D@Wejz?haXWr|5wxdN)H*Em?wJtvU5e;9TpJZNOA4G>?IVqzI+)sffeBU#AV^W>! zv=ZOzf4b=ida7|LbYS!E)q$%4ColNU3yv0a9v;is3SL^{eS$Hpv>5Bor<-05?EMP= zDMy3igVF-V_(IMH9{eyJIM}R*aQu()Jb4!QH{w_KMp}}chUcLT^fb6<`aLZEcCr`z`lJD=N(Bhmrz<)j*U-LtJP&yEN#|Ir>w4m|Dze`EaHai~pb718P-|GY8N6SfGEkxqKi08_> z-g}SN@w`o6Tgd$vU<`hb_}lz*{3AGE9XKXB2+IjF7QvOw&-VQA4eIC9)8_A0LVLi^ z>;(KzT>NJpd_Nze;fo$+ZhE58e&F|^N8^wDCcV?r?AnO`!-zjAV-2YIvlcWxs63!) zKp&6Be84(TnpjLvHciH!o(bNfrOj#re-f}ODt!Qs^238KIVnts=&S2cd{H`3_-pV? z(S+Y8$A7{jKrc5(?$17jp3D4_c)+oYMJXlzRjv`5lwjRB zf*v(TnO;2w+&9Yr{u}hXgRgu5Ci@}yQtJ4+5578RQV!my(~8Xa*f&rB{%g`QjJ@YE z_BsAx9Iy`9e6+yv7ZV*wUgj}qbFz~bWo1JH26U`DSDcYRFJxT}{(p+V*R`l?K{Ozx z(gA!CA9O7&YVsL9lX)_2$TH$r=SF%Nd%Z8^Gywk_zyZ1TJdPz7NU3z-(SjR)a4kAe zT%d02paOGl5zZO+T4`IhMK3l_1pX3OIKHnI;RDZ0k`K6KKTPbC*;gU<2UPsSI6#cO97N=0hX>Jt;=$2^9s9o{_NPGu#xT>XSa&XOmJ943BJf{e zd}u@bf-t+`#mNC5c|5cJo`mxfapcUn)w%V^FdWQa5Ny}uu==< z7?%&xk`~6X&;HQLX2$@3KJd$9!q@dE{JU#K8 zHS3P}e@V;1`*KGEemaoYOBpdQMCjlM>7*71!#unsvN zu;Ezn^_(ENz}Dbi%oBaVzY)JWH!AL5!Cvdj%^pVlD_}PNE(ZoJxcLC_SGn24f#LyU z&|RlxK8O88-#?rN{%e8%slYB*_^uDQzT_jL4jA8{0bK{mhs+Jw(=z{3L3Jh{_fDVP zW?}B_L@#Im3r5N~{w@cs2gI0-Ina5~bs)JwACDaSrJ4I^Y1WmP`_1q8SKyiR)fPF> z;dU3F{fO`ZZ3^EhWo~lUDV!5DtIP>yz#joE#-I5NTBnsAYSOFCrX&6pFjD5#fzkqN zz|n&65WxZZC=R4#KC)I~Pay;R8_=P@SDGh4e>cI0(&+e%?U>-}TGVwQIl=a2D|~1L z{*Cz6xsl$)yt^VNn_g@7A>vQUL<3UlV^PluiUUtRa5+Fb&kHZ$S>lE4k@TW@uZ^}| zjCEg5D)2W(#W(ukV0-;=bCSX@g%49<#@{Ka&fN38)7o|k^m_9`#6JMD`S%d(n7__h~YbW^k-E5)f6D@i@r!TE&{*%JjeGvUgY0OEI6ErR8IcZteC&2Fn z{;NCH5%*84a(L`DLG0JSNZD&Bbq-hyD*lMGmj@Y>l9Q#B@whCjoR(!zqUAZpd6(8Q zeyb#Uy~T4bKKsG=%5dyzF$#W!7SY%Cp#tT@w7mHd`+MgRLH#dkpf|AJ^G43q82@`= zHvggnod=f#T?>eDwDG9opS>G<3Mb?D#f?#+58JW+E~1q!zKV*k>rpiLir{l@!m;Ef z(V)Fv{Q>84=_B*^i;PZHqefbdvG+!c7ZHD=mEfN>AQnvrZcgCXJMs5wVYzyaoDE+5 zf1hi0i{pX+kc+ST5QY!GbPeeEtOvnnUqlCXxfnSM_ZZLpURl#Be79~@%dUw39vIOZ zh%v`s=O8R6Ahy191I~r`~ZF|N)nci&iZyTR|aCE5qVA6ri58k}w z>OkTDi2bZvYXK(lj~tH`7?Vrrl^p;5 zo^>5-(CQY`;6s_hkMN-?Ejm6J{Q&>3G+RxtwTN%@8`Mr4@XWU+=PbnkpNKyx%Y5JmpaD`QI`s1)>fFTmVLp5Se=_lGVZ^V_jkKvX;@_$jt!=pr@h4@%0hhvq zt^?76J}*Sf(cpcB$_XJn%iTz?wleSa?!wq#*Qybo{nonpx*vjX)B*Hq(xS>qj1LX0 zXjYDL5^{fh@Ee3q@3c(-|9RlQ3>=U$i9eUF23QM@9z+W=FDM=GyugN-OHQ_P!e-Q4 zVmunqvF-@r9sl~4eSmww#b-anhiLesL-9kjpyR*Z{9|b3Gd##vzPM{4fmq*1H9lBb8&SJ|4<dv$2{#EYZ(CP;8zZXWzSOdBa^l=!b11A^w@nFx-AJdyT9iR&nyhWR^?%dewBJlrB z1U~Cgbhs8inf&nPqp*CydTjm`_LI)1*RzfI)wz*E=Uenvn-s)?2R+IQZ?+;bep|HJU1T>0SVhu}-8@SzXUBV&un++>$8(5hB- z=-qRwGu6g>r|HLG{I$LW@&A@Kg5!;?C@Ke%6LbyeJV^Yde6z*382epl<9%lQcd+i< z)cQ=||5IV>_BL@>Wu=!^WSPL5r zTHx6G#a}eR&yJOI^dcN5Vg5HpmELVri{5HI1wK?n;Okm2(m;x8ixCIjw75O&C46+oX5fwFLj$ z!GX=aR|C2Z)SLhg+_?cf>*FwNEUIxC#QEX0+W#FJZE0H{_^TB@`{4TFXmAtSBo|33 zen?I-`M}Q!>spoqe+s?V*|^_yaFP1o>rI>So^5lR0hoXHfdf1CVH`M`@XZIF@hCi0 z=7Sxyp*4QFl8nKl_uE+XZrh9Loi<;&_~M7?kW0b$)1t}+jxXYaUGBzyRy%sDy%E2< zH-i7;Xmi^K!9S_^bL^F&9;*(*)(9ALm3g@WYsZPSKFTxS-|@`9x$QXMSJ1nREq-YD zZa(nQfj>U`@*r|Ye80y>=qmSaY4a*_04Z~IAUq)E5jwy)Q)ANMLvnH?4}Syy9l^g3 zI`(^awy8~9+RP5aXCGWYG<;VF4!)ij#0Qxd?6u;T!0&3#Jiabfs}r=fZBxYmZJ5o! zt^=0?M+>3@mj`h37PyeyEF9Q6D90M%0or&{)W4lY@3lV>8hpYRU(X3feAb|^4i{m~ zpF~^De@CEN5$$uc^j_P>(7;9&f7XB)>p;e#lt*-+AQSqz$N!r#v6&iRE9FT3A2K1aD8c^dA zJV)h#bv)&bfHvk-fo`7$ z|80T2GYmh>2f;@vbAqb_2RB>`Td|+n4EaAM{OaC`cAV4@^YE&O_%jDC53B>tB6x7e z~z}M4*2b2xz%_d zLj)+$+rr$5Jka47>KE?`Zrh!tU|ML9RC20R>DT0Zw76}JKU{@&b_#UbpY}J zbg&I;(rxXA0A~qoH>?!KxNJN}eT4H*W6rn^<2?myQ=Y@*GV$^kL;(CF{)p!e?9FX& z*A{W^37$uxoQfFVhH@Ybg#@PxzzJtF9@HTwej9kO*ym#@pF20|SpZMhwAUccZ1H@bH6FG55j{e~2 zK3}Qz;STulKJ4RzUtj+6NW`xYk3c*E@d(5t5RX7S0`UmMBM^^3JOc3u#3K-oKs*BR z2*e{0k3c*E@d(5g0qn0=)1M-uvMizsD@g@lcTFO;poM?%vn~NbmZFgT-38*Ru<}IDA>5p08Tv#rFAUoK{Pw20KN4eao#Zto{+b`+jE}l zjwy%Nq0WXjzj_wVXUX|u9J6WlT-0-=ZsC~Cr_Q%I8CK++H`Td~_&n~1!@$P(<L$84&uSaIrT(&wP-FE$0V$dwR&TY&g*Vj4WAu3jUR@Qe* zxKw!f%B%&viDf?u5r~DR#qS*R9C9QjdHd=Zj#u zPF;n9+qC**t?sJDId7=j>T%8=LU?R7IiKGh86F-;Zn)M}5uRfE_T6wf^(^P|x_VTT z6{#K$Pm$A~!=s18TQ`Km9$e1nasIG}hZW^qo_5>e@YdbiUsr{Ca6U{;sOPfD^-cBN z+Ibuou}Ob#9lmmckkdHkyy6ZX - - Created with Sketch. - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/logo/logo@128.png b/public/logo/logo@128.png deleted file mode 100755 index 5e218d8c50d989908d1c1d57eb94857930215331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3184 zcmXX|c{r47A4UsbvNP71!PtkGN}RGKvSvCs+8DdCj@@8ntXV@P`%prqLPf}y8QT$( zw453oj*793n9cBfGw1t$*ZV&2^<3BeyWjhF-_IXU_IW28kwXfH`1trl>};)^xnBbB z723yLx8{mie0&1z^Nz08+%1X()L#LRlq4Pl6v}~mH-KaS$PJ*{1`w}-_9&1_1Rx?@ zs~$jZ1FZo7tpz${AX^C3-vH$oK&c*xgaYj`z`hnBkqjVrfaVZTyazOgfoca(c>$mg zfmP6amQ_ zKpYR)sbN}w0+luZdkT~u0LU#MnFW~EZcJM@4f6lnVUK`94UjG2F^EI~D0f>8*w@2E z!T{UfUxEN+ivj#8VEOrpVu55D5DVwADb@n{ihl`H0sDgYPE_!2Y8cbV9$2`5f^cAQ z-f)ViS%j;69Ee2%u>`;+)ENiJ37|;=dhdW_I*=srFgqz>Y-}LwqSsCemisWQ-MBt5 zj#^42@qVG!1LVqp1Rm&ck=H{-LU{J>q+*%A{wz0dhNFj69uLfA=JqmA*l-P(&fx;W z=_egHX<^Lc)|{9S$yZa?*ND;0?ACC z|57kx5_%hFVu#-aAi6QK$Aa+C` z^MaRDK49a5nddQz_W|o#0L$N>`G*(7<`OIH8pFYzVdcgOxyp3+VY+y-u-6#Q7*=c$ z<2QR&fIr>hcZQ)gJ2seM>V)V93ikoS!kKPzj)AhYN4~7(p8kqxXD1gvA-R31BYN_} zM1MbRp@a5zwlXqQD)q0|bv4z*)RYuE!L5?Iqy>r0gM}clos&U7v>B<}hzmj+ zZQ2emO8qmvKk>JlOH%^vUH3bl><&rMFU~6K4|%`8^z8J~82-g)-OMi&EF8*mJfRE! z;?n+)pKpvz@g<@x+bdsnJ3sMD!=K?aJu*4x`VWlUOr4U|?!GlP$dsDnkKa{=HVa^~ zEXwbWx^ym2j0+C0@6mrbS9X-7jo!f3K!3yyPdG@-R~)F^SXo$DD81KBRGtm1mF<_S zU3>_i;*YXQoNh9;z+@U{BA&%gt?VVt`kKdU3;dGlt_oT&x6pGmo2S-g(WZS&-annT zuZyA2U~9XjPotAw1%FhA3q6mkd2Q5M;v+%|WR**K9w{Crtx$eMC2IGFQVM8Jdt;XA zoT7q~#Js)Indm%04Ns2eLk8_=-n#}p{yUhA*$5Ifp3wcx*RL~<#0+YkTFPthx`ccr zA*Xdps!9i^qR|2uBP?Yt_g#IR z*yqwpM(G3GGp8D;}DU$GcHtRK>e&t2}uy-Z#vj zI5k~8DwRnO?-M}ROiWJ-dpo|1yz`8m0$-0$@0R|m)6++jCy(Q|w}S+_q*K)G1uM5O zgKaL4CVMbBPnGl=nq6WKF$am%!233s9F=tn!#Ss+A#ihQMPl^^*{S(IOHMtZU!6{d z@9wtR3^xpq8raiJpV&0-vDu>yJW1?NPG#w5C9J&L=kxqTk0It&^5)-ISssSDN|vOR zySp3wZYTXp_t1Te^aWSx0hzdg#Tr%n@S8ehm4L_*CBrCr$3DX&zdI_G=niTt13^v?aydiU!#o3uw{{Og8k&2qH})8F^(hs2DX|vE*VwQ! zgAl0upoq4CnYpXuogNvQD^Dd3I5*;R-lO)|q~F3|d{}Z^T^l{8Kz_JN=W7Y=Q;J=G z!8;r&K>Fkb6fy|aIPad-fpWrMeBMEY2!{|gEM_uVHQUb0v=J{-+vL4Z{P80?Cg#A1 z%g)aMDa5uza)8b8H0V`#%m5ySp1y~Fj*NHQc8h2=!G-SJcxVt|+bdKXcIr>f+cf4& zQ~+AM_2cWfqm6f$O4Ib+`WF^fR~tr1`?qGs7ptl!$F(YPede5O+W4p=N$1M8G1+%- zSN?BrEY_1rm;ddp-A@b6A2NFz4`?ZcVaLj!ozVPqUt`8Zi;1bljNJfIR_A4f#P;A+ zk)YW3Wm8My!}uTe`COca`BQqkR)OPDw&Sy1IXNbZAbTC!T+kF`gh+UyYMSZjnk1Rz zj6qj$wDvNwNu0)7(CmnxV+&#h>ypo@Sc z|4~BFgPg#N_4SCtna|%$j_I17JCEzzB+rYb!Hv^!0yk+s^(qEw{U}O~pxbTP>Ux5S z#L^l}Y31x(j?s8T`qIVwCp4Fh3=LP~w#R;ElP2<%0!~I6HByWa=lUpiXVF$f#cTsT z->2a{FNNy&36CHtxtnvEk7xUY5xHd$^9Q5hjVA9Sjcj2xYy4FHt<=X&1wH-QvL^gP z?Kw+j!xgn_sGd51Qm=`@3R2s941owPC91OgwP{c&^_*FCZ2u}&B!q!7WhJhRU7|sa zu6qhuK z9y7vk$HZ)#(araBtOrUqTTk}lSoP;JPTy5`xT|~Vxu43O0A+qbGHSGFk9&FIv$J-x JdS>aB`d_l#hME8X diff --git a/public/logo/logo@144.png b/public/logo/logo@144.png deleted file mode 100755 index 347e0dfe245d7187f3961f5faadaf553ed942203..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3604 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 diff --git a/public/logo/logo@192.png b/public/logo/logo@192.png deleted file mode 100755 index 342ab1e9553dc1a890b73293d2e058d3bba4028a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4785 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 diff --git a/public/logo/logo@72.png b/public/logo/logo@72.png deleted file mode 100755 index 126e5efe62aff9bc28c5020187226c0fbe0b90f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2138 zcmYjRdpwkB8y=-EVH^gT8Rs#Xp=eT~uav_uQ6|P=N2r;}DaQHCvQaZ4QjtbfW*u_a za;CJ2B!?)Z)v~`fW>1&ZW(B9uW``6yT_kEuCe&6eU?(2H4=lAA$(rIXAyfO-f zLc1NMGLZSG{8rkGjGVpBNhs8&kDkYUT#(eE7mRAa=muzIfo>^iq=LmDkbB|j+?;+b zXkP~HBGAYHwHUzgKo^1W0e=T{ihw)_`gege1eTLv@e;@*Ktc*fL9*9LEl!@A4};X! z4XH)G%8q`slXlRn25DF*p#!#G2h0V)3V`qc zq^HiP#e?}v*zpvMT0uPpu;n0&3?u#s28|%`W2r^M`nhPeC?NI#vLM3Es2TJs0UZV6 zJ&tHD$Z}%L$AI`qjz?;BOTeH?-p*_Q)RI7!6;0>@gS&tr5jliTK)`0tz@!5Ve*j`P zAX z9FW+bK-#Tm$B2&wqGLdo6uEYozMdV8;R7ZYG!j8I5@gvi`c;7c9x&$slLhNp(THkf z$1D)?Pr$4f#Pm}#Zn)T%CSh_ma$=D8QYLFXE?kxtC5?+jQuS1jU5J-CFk~KVsXq&H z6L!72qSsGWopC$=lOQXW2 zncOvJUkNu%mKG`HhNpX9KZqRnsze6e8>L84H$C)UL$t#D$h~_dR}&MNOveL6&Cd(- zGljg=IDe+EtDUum%9eGhcy)DodGR-qXzu;YPvc{wBg4akgMB@n9WBkxjg578g?DaU zFDbrKbh)rFFC`^8K8_O@;Opad*xJ}YAEPY(CstSFv5drDA3y%N_(3#3_ip0V=!+L4 zL%khM4fVA(w{I5n(o@ee=~O#AYcdY2x-BIrvKEC>%y6SR`Y`*KN_@u|UfR6f$tnMe zu8W17l%3ymwwiTv#8nwh32LIp1$zf^nJbTC**#?)FBif93({9dh@3CaZmIa5_V}GI(LOyYKfROK(vnAt7~*D6RF@SK^a*RWv<~k&OVxty ze=KruhaQRRp!_ZC!()R8q&c={Tk_J$Z#~@V{q3+pjQmmGPyPXPEIA}GZ|Zc2Rn%;! zr9ye1Kwa6jIK7ph5&cHVMg|c|UVr{{?_NM#h$laQ8S?t{$RG3EhM#2|Z|D7cOW0eg zT)J5c1GOFxqqv@%6{?Qq1^m)4bTA&@^q+z9w?71It#a9kJ2|MG?YPIJz^Zct%Xg0M zdW2Su*hCQzhfn&@MYU6xmnRh~kM=HVn$sxlXI|Z=OtSD~>zh+a2`Uk56?NOrbK;4t zRi$FyUZq=~Lk1b4aaDph=~#P<{8Ia#FH}>kzi6dz5o1L2=0Dgz@SLw0Q9V&Z?2yLJ5fDsGvRFWZnF-{ILGxy z^MO5u;%;&8!-Rt^CBc+21%829lAW_KVXCdI)Vj_@p^7G0Idy^j<-KE5GsS1KV=W2i zVPSBkevG1JgnDrgIb?`=i3(h!F35)W(?98h^?&|Fw)m|C8z9yj5v8%m;Z*Kg4bza4O zgu36d=8(*~rc*q?n3>4P9k_ouSR38*UFwVJfS#3d-gtg^L+JE6>6UN*y(vC;M`j6s=U)_m@r zQ(0;9V-r3Jxib=JMJ~iSs6gwt#-fG&68(Zn?J9oV4 zc2By@=a;G3Kz`m^!paq=t?I71nwvt6aN|qImA349Qu1{?XR0TYIg;J#iuZi~+J50R zDSdK4%;1*^EE1M~E7Tk2NnPI9_Exl+jCN`BGEy&|IP50u&sBLk#AZFm8ZWJjU%fk$ z;d-JcHh|5%*i%$_mYj5`k+L;pPnjtGx_eMN<#jVg0hh0TB&ng2!ofda^I2))ax;FX ztrIz)UAHjRGBWAVTe6>6Z~f$N_vy%VOxbI^aF>_O;N{_tvxSEHW144;mnuIvy6!sP z$}rkdk}o{2s$Fazi5kyZqd67@8<;pX?|9{2W*dX2`PF)>YCox;al?)8@oly6y;gHF xYNIr^u(8y`@a3w`GqQbWe5B|INCP$$?_PgU`NH;BL*%cCa&w_m?>n(_{tqC>NnQW| diff --git a/public/logo/logo@96.png b/public/logo/logo@96.png deleted file mode 100755 index de9a9fba69a2f4a547b6607c349edf746ef3486d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2449 zcmYL~2{@E%8^@<3g^Xcr$-WH{(%4Ed*-f(L7{l9gE%;9aXG{L;8f6zZ-<2cUNwl+OT~4%j?U$N{AiFuVu4H$jv|!z6>r6Ik0x)NBUj5>PG& z%~lZ5D64oE5tDAf0`2RdkOgQeD5Qb*RnYGOTpyx}hRD!p0EE~#W`gOESQ1hl$)I}! zM9c(Xay(K_7#S%?2hj<-RXaCPT=cgzvOCq5VF^ zBmlRA!tf{oC7K@;zKVB3AoD{H^Y?`av+4Xp;fPwKcR^It8r~VnhFpk!t2_1zY~5vP zfai)`Q2hgp2E;9EUjwCLz%T%t4{}N3K*iHyK8R~bUkD|Y-vL3b*ClpP=cd>=VGbQ> zQ1`ajVr&7(XMq7*3{5^$?5x&RP$~uELBREc>Uq%nj~KoI2Xya%#$`Y!0JabW5s@0r zfIbSsOe#P4Frs<$1(2nJ@JI}wMCKm|LkKJ6g78@ER^)iJfqEkdQ{oKn1D_nBd=})h zVT~AU^cd7?KoA?fwm)Rel_ZF#toeq@r2v9Om_`*uN3Gd=iEXlWTO;_bI{N9eL690R zpvCZgLj~kWVQM@d3EZ{s)0c%Yq5VI$MN7!5X{mazkZ)E}a%16d34*krN4p zi;D{j^Ye3aU%!0$JU#h-{O#+pv5|rPJ|xFIHmj?-{`{HJ;=+PFCNnD~IVmB5PK}L; zp&SnOaj>#9QsOUt`Z)dW?-zHQiVHL8aj}6uCYTfbx$jXZ$#zedy#X=fUn+hk1ngBJ zXm^{Mq#XbMFnU!aB~!2CA$7|5_N&dm8mDBYQf9usIXE*k-0b*x-S`d4GB+y8;VITb zCkL1Y(<7vU4fy)Q?)%is< zSkKMP4bS-*(&a!y+|soszEqs3;ZIZVkg%`k=3rZn#N7Hemt^!He;9c`b(v0h8jnC5hK4{_+@|3 z^L)ZQk&D6M4{HhO=gb0qmU!#>27kY@v@<(^oH22RV%b7!_-bThFc`eJOr14eI8|Zh z8O1G?nfMy&KVR{*)eryFtn}r<{_hNL7L~EO4UHE^>RUAD|{wB$1&Y|-r+&+hBbCj((X40KdcQL)Jlv5~muws)7q&{JOzpYl=D zvYhI5Zcg5)rc|FC$z>DSd|knhTozAXKUtd@T8InkVHFg(r?!wD)M1j^)J7IIZ(WJw zZu(&2o-?EJwtzP-!Tzb`#t57|-D11|B zkso8dmEwC1g|flM^k1~tGHTRHDZZ@K5l_>VxT!sH7aTZ-oIb;v(blq;m7J4}cX81b z=so7OB+l8>=)&Y$`EF{LhsNhu?$MPt{^#Q|&dODE~Ym ztBpMVhEh5@+m<0(MH8wM-jx>o$hv|n{Vj#ZA8-8dq1^j(StF^D`f|NjS2)o|KY+zz z$u&k-nAzIy2|<;#F}(Qwj`KTzR;=8Q56bzG!~Msf_F;)vcI&UmZKEz+&8rlY@-+z# zqs84OxZ=7CzSb__2$ASzyRn60b=NRl`I~f^OQq*}(*mi%w(;BY&a9Q1t`VBFtF^O# z#0HbIt$#if^)8{U8+q{)rWq5nq}A6qn9&})G8+B zKcA;dC35*E%@y}-dsLUvg0 zw<^>gf^)C69iNtzXb-6FPT%%o%D^#crPLytK4qycT2;2L-f;EAJ$+8OFB@yOvolC? zhwa6b#4H(ajKe;)q3Y>dC+^)DNiZUAMZa;*-+s6L`;&t)TC4lpr48Qtb*GiQ_ { - if (l === 'zh-CN') return zhCN; - return enUS; - }; - let initial: typeof zhCN | typeof enUS | undefined = undefined; - const stored = window.localStorage.getItem('umi_locale') || null; - initial = pick(stored); - - const [locale, setLocale] = useState( - initial, - ); - - useEffect(() => { - const read = () => { - let l: string | null = null; - try { - l = window.localStorage.getItem('umi_locale') || null; - } catch (e) { - l = null; - } - setLocale(pick(l)); - }; - - const onStorage = (ev: StorageEvent) => { - if (ev.key === 'umi_locale') read(); - }; - - const onLocaleChange = (ev: Event) => { - // CustomEvent used by TopbarControls with detail = locale string - const possible = ev as CustomEvent | Event; - if (possible && typeof (possible as CustomEvent).detail !== 'undefined') { - const d = (possible as CustomEvent).detail; - if (d) setLocale(pick(String(d))); - } - }; - - window.addEventListener('storage', onStorage); - window.addEventListener('localechange', onLocaleChange as EventListener); - return () => { - window.removeEventListener('storage', onStorage); - window.removeEventListener( - 'localechange', - onLocaleChange as EventListener, - ); - }; - }, []); - - return {children}; -} - -export function rootContainer(container: React.ReactElement) { - return ( - - {container} - - ); -} - -// ensure mobile viewport meta exists (adds meta tag if not present) -try { - if (typeof window !== 'undefined') { - const hasVp = document.querySelector('meta[name="viewport"]'); - if (!hasVp) { - const m = document.createElement('meta'); - m.name = 'viewport'; - m.content = 'width=device-width,initial-scale=1,maximum-scale=1'; - document.head.appendChild(m); - } - } -} catch (e) { - // ignore in non-DOM environments -} diff --git a/src/components/AuthGate.tsx b/src/components/AuthGate.tsx deleted file mode 100644 index 571a79a..0000000 --- a/src/components/AuthGate.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Spin } from 'antd'; -import React, { useEffect, useState } from 'react'; -import { history } from 'umi'; - -import useGlobalSync from '@/hooks/useGlobalSync'; -import useIsMounted from '@/hooks/useIsMounted'; -import adminService from '../services/admin'; - -import useGlobalStore from '../store/useGlobalStore'; - -export default function AuthGate({ children }: { children: React.ReactNode }) { - const isLoggedIn = useGlobalSync((s) => s.isLoggedIn); - const [loading, setLoading] = useState(true); - const isMounted = useIsMounted(); - - useEffect(() => { - (async () => { - try { - const admin = (await adminService.currentAdmin()) as - | import('@/types/admin').Admin - | undefined; - if (!isMounted()) return; - if (admin) { - useGlobalStore.getState().login(); - useGlobalStore.getState().setCurrentUser(admin); - } else { - useGlobalStore.getState().logout(); - } - } catch (e) { - useGlobalStore.getState().logout(); - } finally { - if (isMounted()) setLoading(false); - } - })(); - }, [isMounted]); - - useEffect(() => { - const path = history.location.pathname; - if (!isLoggedIn && path !== '/login') { - history.replace('/login'); - } - if (isLoggedIn && path === '/login') { - history.replace('/'); - } - }, [isLoggedIn]); - - if (loading) { - return ( -
- -
- ); - } - - return <>{children}; -} diff --git a/src/components/DropOption.test.tsx b/src/components/DropOption.test.tsx deleted file mode 100644 index 5c22c32..0000000 --- a/src/components/DropOption.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react'; -import DropOption from './DropOption'; - -// Mock antd Dropdown to expose menu items for testing -jest.mock('antd', () => { - const React = require('react'); - return { - Dropdown: ({ menu, children }: any) => - React.createElement( - 'div', - null, - React.createElement('div', { 'data-testid': 'trigger' }, children), - React.createElement( - 'div', - { 'data-testid': 'menu' }, - menu?.items?.map((it: any) => - React.createElement( - 'button', - { - key: it.key, - 'data-key': it.key, - onClick: () => menu.onClick?.({ key: it.key }), - }, - typeof it.label === 'string' - ? it.label - : React.createElement('span', null, it.label), - ), - ), - ), - ), - }; -}); - -jest.mock('@ant-design/icons', () => { - const React = require('react'); - return { - BarsOutlined: () => React.createElement('span', null, 'Bars'), - DownOutlined: () => React.createElement('span', null, 'Down'), - }; -}); - -test('renders default trigger when no children provided', () => { - render(); - const trigger = screen.getByTestId('trigger'); - // default trigger in component is an - expect(trigger.querySelector('a')).toBeTruthy(); -}); - -test('wraps plain text children in a span', () => { - render(PlainChild); - const trigger = screen.getByTestId('trigger'); - expect(screen.getByText('PlainChild')).toBeTruthy(); - expect(trigger.firstElementChild?.tagName.toLowerCase()).toBe('span'); -}); - -test('clicking menu item calls onMenuClick with key', () => { - const onMenuClick = jest.fn(); - render( - , - ); - const item = screen.getByText('One'); - fireEvent.click(item); - expect(onMenuClick).toHaveBeenCalledWith('one'); -}); diff --git a/src/components/DropOption.tsx b/src/components/DropOption.tsx deleted file mode 100644 index f0286a4..0000000 --- a/src/components/DropOption.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { BarsOutlined, DownOutlined } from '@ant-design/icons'; -import { Dropdown } from 'antd'; -import React from 'react'; - -type Option = { key: string; label: React.ReactNode }; - -interface DropOptionProps { - menuOptions: Option[]; - onMenuClick?: (key: string) => void; - children?: React.ReactNode; -} - -const DropOption: React.FC = ({ - menuOptions, - onMenuClick, - children, -}) => { - const items = menuOptions.map((o) => ({ key: o.key, label: o.label })); - - const menu = { - items, - onClick: ({ key }: { key: string }) => onMenuClick?.(key), - }; - - // ensure Dropdown receives a single React element as child - let triggerNode: React.ReactElement; - if (React.Children.count(children) === 1 && React.isValidElement(children)) { - triggerNode = children as React.ReactElement; - } else if (children) { - // wrap non-element children (like plain text) in a span - triggerNode = {children}; - } else { - triggerNode = ( - e.preventDefault()}> - - - - ); - } - - return ( - - {triggerNode} - - ); -}; - -export default DropOption; diff --git a/src/components/GlobalFooter.tsx b/src/components/GlobalFooter.tsx deleted file mode 100644 index e4279c2..0000000 --- a/src/components/GlobalFooter.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -interface LinkItem { - title: string; - href: string; -} - -interface GlobalFooterProps { - links?: LinkItem[]; - copyright?: React.ReactNode; - className?: string; -} - -const GlobalFooter: React.FC = ({ - links = [], - copyright, -}) => { - return ( -
- {links.length > 0 && ( -
- {links.map((l, i) => ( - - {l.title} - - ))} -
- )} -
{copyright ?? `© ${new Date().getFullYear()}`}
-
- ); -}; - -export default GlobalFooter; diff --git a/src/components/IconMap.tsx b/src/components/IconMap.tsx deleted file mode 100644 index 5c08961..0000000 --- a/src/components/IconMap.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { - AppstoreOutlined, - HomeOutlined, - InfoCircleOutlined, - ProfileOutlined, - RobotOutlined, - SettingOutlined, - TeamOutlined, - ToolOutlined, - UserAddOutlined, - UserOutlined, -} from '@ant-design/icons'; -import React from 'react'; - -export const ICON_MAP: Record = { - home: , - 'info-c': , - user: , - team: , - profile: , - 'user-add': , - settings: , - tool: , - robot: , - 'app-store': , -}; - -export function Icon({ name }: { name?: string }) { - if (!name) return null; - return ICON_MAP[name] || null; -} - -export default Icon; diff --git a/src/components/TopbarControls.tsx b/src/components/TopbarControls.tsx deleted file mode 100644 index d0a1e9c..0000000 --- a/src/components/TopbarControls.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import useGlobalSync from '@/hooks/useGlobalSync'; -import { - BulbOutlined, - FullscreenExitOutlined, - FullscreenOutlined, - GlobalOutlined, - UserOutlined, -} from '@ant-design/icons'; -import { Avatar, Button, Dropdown, Tooltip, type MenuProps } from 'antd'; -import { useEffect, useState } from 'react'; -import { setLocale as setUmiLocale } from 'umi'; - -type Props = { - goProfile?: () => void; - onLogout?: () => void; -}; - -export default function TopbarControls({ goProfile, onLogout }: Props) { - const [isFullscreen, setIsFullscreen] = useState(false); - const [darkMode, setDarkMode] = useState(false); - const currentAdmin = useGlobalSync((s) => s.currentAdmin); - const isLoggedIn = useGlobalSync((s) => s.isLoggedIn); - - useEffect(() => { - const dm = localStorage.getItem('app-dark-mode'); - if (dm === 'true') { - setDarkMode(true); - document.documentElement.classList.add('dark-mode'); - } - const onFs = () => setIsFullscreen(!!document.fullscreenElement); - document.addEventListener('fullscreenchange', onFs); - return () => document.removeEventListener('fullscreenchange', onFs); - }, []); - - const toggleFullscreen = async () => { - try { - if (!document.fullscreenElement) - await document.documentElement.requestFullscreen(); - else await document.exitFullscreen(); - } catch (e) { - void 0; - } - }; - - const toggleDarkMode = () => { - const next = !darkMode; - setDarkMode(next); - if (next) document.documentElement.classList.add('dark-mode'); - else document.documentElement.classList.remove('dark-mode'); - - localStorage.setItem('app-dark-mode', String(next)); - }; - - const changeLocale = (l: string) => { - try { - // ensure umi locale stored, then notify app to update Antd locale immediately - setUmiLocale(l, true); - localStorage.setItem('umi_locale', l); - window.dispatchEvent(new CustomEvent('localechange', { detail: l })); - } catch (e) { - window.location.reload(); - } - }; - - const localeMenuItems: MenuProps['items'] = [ - { key: 'en-US', label: 'English' }, - { key: 'zh-CN', label: '中文' }, - ]; - - const profileMenuItems: MenuProps['items'] = [ - { key: 'profile', label: '个人信息' }, - { key: 'logout', label: '注销' }, - ]; - - const handleProfileClick = (key: string) => { - if (key === 'profile') { - goProfile?.(); - } else if (key === 'logout') { - onLogout?.(); - } - }; - - return ( -
- -
- ); -} diff --git a/src/hooks/useGlobalSync.ts b/src/hooks/useGlobalSync.ts deleted file mode 100644 index 6207e88..0000000 --- a/src/hooks/useGlobalSync.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { GlobalState } from '@/store/useGlobalStore'; -import useGlobalStore from '@/store/useGlobalStore'; -import { useSyncExternalStore } from 'react'; - -type Selector = (s: GlobalState) => T; - -export default function useGlobalSync(selector: Selector) { - return useSyncExternalStore( - (listener: () => void) => { - // subscribe returns unsubscribe - const unsub = useGlobalStore.subscribe(listener); - return unsub; - }, - () => selector(useGlobalStore.getState()), - () => selector(useGlobalStore.getState()), - ); -} diff --git a/src/hooks/useIsMounted.ts b/src/hooks/useIsMounted.ts deleted file mode 100644 index ef3a284..0000000 --- a/src/hooks/useIsMounted.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; - -// Simple hook that returns a stable function to check mounted state -export default function useIsMounted(): () => boolean { - const ref = useRef(true); - useEffect(() => { - ref.current = true; - return () => { - ref.current = false; - }; - }, []); - return useCallback(() => ref.current, []); -} diff --git a/src/layouts/BaseLayout.tsx b/src/layouts/BaseLayout.tsx deleted file mode 100644 index 2ee86d6..0000000 --- a/src/layouts/BaseLayout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import GlobalFooter from '@/components/GlobalFooter'; -import config from '@/utils/config'; -import { Layout as AntdLayout } from 'antd'; -import React from 'react'; - -const { Content } = AntdLayout; - -type Props = { - sider?: React.ReactNode; - header?: React.ReactNode; - children?: React.ReactNode; - className?: string; -}; - -export default function BaseLayout({ - sider, - header, - children, - className, -}: Props) { - return ( - - {sider} - - {header} - {children} - - - - ); -} diff --git a/src/layouts/PrimaryLayout.tsx b/src/layouts/PrimaryLayout.tsx deleted file mode 100644 index c707266..0000000 --- a/src/layouts/PrimaryLayout.tsx +++ /dev/null @@ -1,577 +0,0 @@ -import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; -import { Breadcrumb, Button, Drawer, Layout, Menu, message } from 'antd'; -import React, { useEffect, useMemo, useState } from 'react'; -import { history, Link, useLocation } from 'umi'; - -import { ICON_MAP } from '@/components/IconMap'; -import TopbarControls from '@/components/TopbarControls'; -import useGlobalSync from '@/hooks/useGlobalSync'; -import useIsMounted from '@/hooks/useIsMounted'; - -import { Admin } from '@/types/admin'; -import config from '@/utils/config'; -import api from '@/utils/request'; -import useGlobalStore from '../store/useGlobalStore'; - -import adminService from '../services/admin'; -import menuService from '../services/menu'; -import { MenuItem } from '../types/menu'; -import BaseLayout from './BaseLayout'; -import './index.less'; - -const { Sider, Header } = Layout; - -// keep warned paths module-level so dev StrictMode remounts don't re-show warnings -const WARNED_PATHS = new Set(); - -type FrontendMenu = { - key: string; - title: string; - icon?: string; - path?: string; - children?: FrontendMenu[]; -}; - -type RouteEntry = { - id: number | string; - route?: string; - name?: string; - breadcrumbParentId?: number | string | null; - icon?: string; -}; - -type BreadcrumbItem = { - key: string; - title: string; - path: string; - icon?: string; -}; - -function buildMenuItems(items: FrontendMenu[]): any[] { - return items.map((it: FrontendMenu) => { - const iconKey = it.icon || it.key; - if (it.children && it.children.length) { - return { - key: it.key, - icon: ICON_MAP[iconKey], - label: it.title, - children: buildMenuItems(it.children), - }; - } - return { - key: it.key, - icon: ICON_MAP[iconKey], - label: {it.title}, - }; - }); -} - -export default function PrimaryLayout({ - children, -}: { - children?: React.ReactNode; -}) { - const { pathname } = useLocation(); - const currentAdmin = useGlobalSync((s) => s.currentAdmin) as - | Admin - | undefined; - const [collapsed, setCollapsed] = useState(false); - const [isMobile, setIsMobile] = useState( - typeof window !== 'undefined' ? window.innerWidth <= 768 : false, - ); - const [mobileOpen, setMobileOpen] = useState(false); - const [menus, setMenus] = useState(null); - const [routesDb, setRoutesDb] = useState(null); - - useEffect(() => { - const v = localStorage.getItem('app-sider-collapsed'); - if (v !== null) setCollapsed(v === 'true'); - }, []); - - useEffect(() => { - const onResize = () => setIsMobile(window.innerWidth <= 768); - window.addEventListener('resize', onResize); - return () => window.removeEventListener('resize', onResize); - }, []); - - // load menus from API so admin changes (status/sort) reflect immediately - const isMounted = useIsMounted(); - useEffect(() => { - (async () => { - const res = await menuService.listMenus(); - if (!isMounted()) return; - // 服务现在保证返回统一的 data(即数组或期望类型),不再兼容旧格式 - setMenus(res as MenuItem[]); - })(); - }, [isMounted]); - - // load raw routes DB to support breadcrumb for parameterized routes not present in menus - useEffect(() => { - (async () => { - try { - const r = await api('/routes'); - if (!isMounted()) return; - setRoutesDb(r as RouteEntry[]); - } catch (e) { - void e; - } - })(); - }, [isMounted]); - - const filteredMenu = useMemo(() => { - // normalize currentAdmin.permissions to an array of paths or '*' string - const permsRaw: Admin['permissions'] | null = - (currentAdmin && currentAdmin.permissions) || null; - let perms: string[] = []; - if (typeof permsRaw === 'string') { - perms = permsRaw - .split(',') - .map((s: string) => s.trim()) - .filter(Boolean); - } - - const isAllowed = (item: FrontendMenu): boolean => { - // if no permissions configured for this user, deny access to guarded items - if (!perms || !perms.length) return false; - if (item.path && perms.includes(item.path)) return true; - // also allow matching by menu key (e.g. perms may contain '/dashboard' while item.path is '/') - if (item.key && perms.includes('/' + String(item.key))) return true; - if (item.children && item.children.length) { - return item.children.some((c: FrontendMenu) => isAllowed(c)); - } - return false; - }; - // choose source menu: API-loaded menus (if available) transformed to front-end shape - const source = (() => { - if (!menus || !menus.length) return []; - - // helper to normalize a tree node to expected front-end shape - const normalizeNode = (m: MenuItem): FrontendMenu => ({ - key: m.route - ? String(m.route).replace(/^\//, '') || String(m.id) - : String(m.id), - title: m.title, - icon: m.icon || String(m.id), - path: m.route || '/', - children: Array.isArray(m.children) - ? m.children.map(normalizeNode) - : [], - }); - - // detect if API returned a nested tree (children present) or flat list (parentId fields) - const looksNested = menus.some( - (m: MenuItem) => Array.isArray(m.children) && m.children.length, - ); - if (looksNested) { - const tree = menus - .filter((m: MenuItem) => m.status === undefined || m.status === 1) - .map(normalizeNode) as FrontendMenu[]; - // sort recursively - const sortRec = (arr: FrontendMenu[]) => { - arr.sort((a: any, b: any) => (a.sort || 0) - (b.sort || 0)); - arr.forEach( - (c) => c.children && sortRec(c.children as FrontendMenu[]), - ); - }; - sortRec(tree); - return tree; - } - - // otherwise treat as flat list with parentId - const map = new Map(); - const active = menus.filter( - (m: MenuItem) => m.status === undefined || m.status === 1, - ); - active.forEach((m: MenuItem) => - map.set(m.id, normalizeNode(m) as FrontendMenu), - ); - const roots: FrontendMenu[] = []; - active.forEach((m: MenuItem) => { - const node = map.get(m.id) as FrontendMenu; - if (m.parentId === null || m.parentId === undefined) roots.push(node); - else { - const parent = map.get(m.parentId); - if (parent) { - parent.children = parent.children || []; - parent.children.push(node); - } else roots.push(node); - } - }); - const sortRec = (arr: FrontendMenu[]) => { - arr.sort((a: any, b: any) => (a.sort || 0) - (b.sort || 0)); - arr.forEach((c) => c.children && sortRec(c.children as FrontendMenu[])); - }; - sortRec(roots); - return roots; - })(); - - // treat '*' as full access (return everything from source) - if (permsRaw === '*') return source; - - return source.filter((it: FrontendMenu) => isAllowed(it)); - }, [currentAdmin, menus]); - - // route-level guard: prevent access to routes not covered by currentAdmin.permissions - useEffect(() => { - const path = pathname; - // allow public paths - if (!currentAdmin) return; - - const permsRaw: Admin['permissions'] | null = - currentAdmin.permissions || null; - if (permsRaw === '*') return; - - const perms = - typeof permsRaw === 'string' - ? permsRaw - .split(',') - .map((s) => s.trim()) - .filter(Boolean) - : []; - - const pathAllowedByPerm = (perm: string) => { - if (!perm) return false; - if (perm === path) return true; - // treat perm as pattern if it contains ':' - if (perm.indexOf(':') >= 0) { - try { - const pattern = '^' + perm.replace(/:[^/]+/g, '[^/]+') + '$'; - const re = new RegExp(pattern); - return re.test(path); - } catch (e) { - return false; - } - } - return false; - }; - - const allowed = perms.some(pathAllowedByPerm); - if (!allowed) { - // also allow if filteredMenu contains a matching path/pattern - const menuAllows = (() => { - const checkItem = (it: FrontendMenu): boolean => { - if (it.path === path) return true; - if (typeof it.path === 'string' && it.path.indexOf(':') >= 0) { - try { - const pattern = '^' + it.path.replace(/:[^/]+/g, '[^/]+') + '$'; - const re = new RegExp(pattern); - if (re.test(path)) return true; - } catch (e) { - void e; - } - } - if (it.children && it.children.length) - return it.children.some(checkItem); - return false; - }; - return filteredMenu && filteredMenu.some(checkItem); - })(); - - if (!menuAllows) { - if (!WARNED_PATHS.has(path)) { - WARNED_PATHS.add(path); - message.warning('没有权限访问此页面'); - } - if (history.location.pathname !== '/') history.replace('/'); - } - } - }, [pathname, currentAdmin, filteredMenu, routesDb]); - - const menuItems = useMemo( - () => buildMenuItems(filteredMenu as FrontendMenu[]), - [filteredMenu], - ); - - // try to find the active key from pathname - const selectedKeys = useMemo(() => { - const findKey = (items: FrontendMenu[]): string | null => { - for (const it of items) { - if (it.path === pathname) return it.key; - if (it.children) { - const child = findKey(it.children); - if (child) return child; - } - } - return null; - }; - const k = findKey(filteredMenu); - return k ? [k] : []; - }, [pathname, filteredMenu]); - - // find parent keys to open corresponding submenu - const defaultOpenKeys = useMemo(() => { - if (!selectedKeys.length) return []; - const target = selectedKeys[0]; - const findParent = (items: FrontendMenu[]): string | null => { - for (const it of items) { - if ( - it.children && - it.children.some((c: FrontendMenu) => c.key === target) - ) - return it.key; - if (it.children) { - const sub = findParent(it.children as FrontendMenu[]); - if (sub) return sub; - } - } - return null; - }; - const p = findParent(filteredMenu); - return p ? [p] : []; - }, [selectedKeys, filteredMenu]); - - const handleCollapse = (c: boolean) => { - setCollapsed(c); - localStorage.setItem('app-sider-collapsed', String(c)); - }; - - const logout = useGlobalStore.getState().logout; - - const sider = ( - -
{config.siteName}
- - - ); - - const header = ( -
-
-
- -
-
-
- {currentAdmin - ? currentAdmin.real_name || currentAdmin.username - : ''} -
- { - await adminService.logout(); - logout(); - history.push('/login'); - }} - goProfile={() => { - // Navigate to profile page - history.push(`/admin/${currentAdmin?.id || 0}`); - }} - /> -
-
-
- ); - // build breadcrumb trail from MENU based on pathname - const breadcrumb = useMemo(() => { - const find = ( - items: FrontendMenu[], - path: string, - ): FrontendMenu[] | null => { - for (const it of items) { - if (it.path === path) return [it]; - if (it.children) { - const sub = find(it.children, path); - if (sub) return [it, ...sub]; - } - } - return null; - }; - - // exact match first - let trail = find(filteredMenu, pathname); - if (!trail) { - // attempt pattern matching for param routes like /admin/:id - const patternFind = ( - items: FrontendMenu[], - path: string, - ): FrontendMenu[] | null => { - for (const it of items) { - if (typeof it.path === 'string' && it.path.indexOf(':') >= 0) { - try { - const pattern = '^' + it.path.replace(/:[^/]+/g, '[^/]+') + '$'; - const re = new RegExp(pattern); - if (re.test(path)) return [it]; - } catch (e) { - void e; - } - } - if (it.children) { - const sub = patternFind(it.children, path); - if (sub) return [it, ...sub]; - } - } - return null; - }; - - // attempt prefix matching (e.g., /users -> /users/list) - const prefixFind = ( - items: FrontendMenu[], - path: string, - ): FrontendMenu[] | null => { - for (const it of items) { - if (it.path && path.startsWith(it.path) && it.path !== '/') - return [it]; - if (it.children) { - const sub = prefixFind(it.children, path); - if (sub) return [it, ...sub]; - } - } - return null; - }; - - trail = - patternFind(filteredMenu, pathname) || - prefixFind(filteredMenu, pathname); - - // try matching against raw routes DB (includes entries excluded from menu via menuParentId === '-1') - if (!trail && routesDb && routesDb.length) { - const matchRoute = (routes: RouteEntry[]): RouteEntry | null => { - for (const it of routes) { - if (!it.route) continue; - // exact match - if (it.route === pathname) return it; - // param pattern match - if (typeof it.route === 'string' && it.route.indexOf(':') >= 0) { - try { - const pattern = - '^' + it.route.replace(/:[^/]+/g, '[^/]+') + '$'; - const re = new RegExp(pattern); - if (re.test(pathname)) return it; - } catch (e) { - void e; - } - } - } - return null; - }; - - const found = matchRoute(routesDb); - if (found) { - const idToRoute = (id: number | string) => - routesDb.find((x: RouteEntry) => String(x.id) === String(id)); - const chain: BreadcrumbItem[] = []; - let cur: RouteEntry | null = found; - while (cur) { - // for the current (param) route, use the actual pathname so link works - chain.unshift({ - key: String(cur.id), - title: String(cur.name || ''), - path: cur === found ? pathname : cur.route || '/', - icon: cur.icon, - }); - if (!cur.breadcrumbParentId) break; - cur = idToRoute(cur.breadcrumbParentId) || null; - } - trail = chain.length ? chain : null; - } - } - - trail = trail || [{ key: 'dashboard', title: 'Dashboard', path: '/' }]; - } - return trail; - }, [pathname, filteredMenu, routesDb]); - - // update document title from breadcrumb - useEffect(() => { - if (breadcrumb && breadcrumb.length) { - const last = breadcrumb[breadcrumb.length - 1]; - - document.title = `${last.title} - ${config.siteName}`; - } else { - document.title = config.siteName; - } - }, [breadcrumb]); - - const pageBody = ( -
- { - const IconNode = - ICON_MAP[(b as any).icon || (b as any).key] || null; - const titleNode = ( - - {IconNode} - {(b as any).title} - - ); - return { - key: (b as any).key, - title: - idx < breadcrumb.length - 1 ? ( - {titleNode} - ) : ( - titleNode - ), - }; - }, - )} - /> -
{children}
-
- ); - - return ( - <> - - {pageBody} - - setMobileOpen(false)} - styles={{ body: { padding: 0 } }} - className="mobile-menu-drawer" - > -
- {config.siteName} -
- setMobileOpen(false)} - /> - - - ); -} diff --git a/src/layouts/PublicLayout.tsx b/src/layouts/PublicLayout.tsx deleted file mode 100644 index 0ac0051..0000000 --- a/src/layouts/PublicLayout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Layout } from 'antd'; -import React from 'react'; - -import TopbarControls from '@/components/TopbarControls'; - -const { Content } = Layout; - -export default function PublicLayout({ - children, -}: { - children?: React.ReactNode; -}) { - return ( - -
- -
- -
{children}
-
-
- ); -} diff --git a/src/layouts/index.less b/src/layouts/index.less deleted file mode 100644 index b1cfe39..0000000 --- a/src/layouts/index.less +++ /dev/null @@ -1,235 +0,0 @@ -.navs { - ul { - padding: 0; - list-style: none; - display: flex; - } - li { - margin-right: 1em; - } -} - -.content { - padding: 24px; - min-height: calc(100vh - 64px); -} - -.footer { - background: #fff; - margin-top: 0; - margin-bottom: 0; - padding-top: 24px; - padding-bottom: 24px; - min-height: 72px; -} - -.app-sider { - .logo { - height: 64px; - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 18px; - display: flex; - align-items: center; - justify-content: center; - padding: 0 24px; - box-shadow: 0 1px 9px -3px rgba(0, 0, 0, 0.2); - } -} - -/* Sider visual refinements (light) */ -.app-sider { - background: #ffffff; - color: rgba(0, 0, 0, 0.85); - - .logo { - background: transparent; - letter-spacing: 0.2px; - } - - /* Antd light menu tweaks */ - .ant-menu { - padding-top: 6px; - } - - .ant-menu-light { - background: transparent; - } - - .ant-menu-light .ant-menu-item, - .ant-menu-light .ant-menu-submenu-title { - color: rgba(0, 0, 0, 0.75); - padding-left: 20px; - } - - .ant-menu-light .ant-menu-item:hover { - background: rgba(0, 0, 0, 0.03); - color: rgba(0, 0, 0, 0.9); - } - - .ant-menu-light .ant-menu-item-selected { - background: rgba(24, 144, 255, 0.08) !important; - color: rgba(0, 0, 0, 0.9) !important; - } - - .ant-menu-light .ant-menu-item .anticon { - color: rgba(0, 0, 0, 0.55); - font-size: 16px; - } -} - -.app-header { - background: #fff; - padding: 0 16px; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); -} - -.app-header .ant-btn { - color: rgba(0, 0, 0, 0.65); -} - -.app-header .ant-btn:hover { - color: rgba(0, 0, 0, 0.85); -} - -.app-content { - min-height: 0; -} - -.page-container { - padding: 12px 16px 16px; -} - -.page-breadcrumb { - margin: 0 0 12px 6px; -} - -.page-content { - min-height: calc(100vh - 180px); -} - -/* Mobile responsive tweaks */ -@media (max-width: 768px) { - .app-sider { - display: none !important; - } - - .app-header { - padding: 0 8px; - } - - .logo { - font-size: 16px; - padding: 0 12px; - } - - .page-container { - padding: 8px; - } - - .page-breadcrumb { - display: none; - } - - .page-content { - min-height: auto; - } - - .ant-menu-item, - .ant-menu-submenu-title { - padding-left: 12px !important; - } - - .mobile-menu-drawer .ant-drawer-body { - padding: 0; - } - - /* allow tables to scroll horizontally on small screens */ - .ant-table-wrapper { - overflow-x: auto; - } - - .ant-table-thead > tr > th, - .ant-table-tbody > tr > td { - white-space: nowrap; - } -} - -/* Dark mode overrides */ -.dark-mode { - --bg: #121212; - --muted: rgba(255, 255, 255, 0.65); - --panel: #1f1f1f; - color: rgba(255, 255, 255, 0.88); - background: var(--bg); -} - -.dark-mode .app-header { - background: #141414; - box-shadow: none; -} - -.dark-mode .app-sider { - background: #111111; -} - -/* Stronger dark-mode sider contrast */ -.dark-mode .app-sider { - background: linear-gradient(180deg, #08090b 0%, #0b0f13 100%); -} - -.dark-mode .app-sider .logo { - background: rgba(255, 255, 255, 0.02); -} - -.dark-mode .logo { - color: #fff; -} - -.dark-mode .app-content, -.dark-mode .page-content { - background: var(--panel); -} - -.dark-mode .page-content { - background: transparent; -} - -.page-content .ant-card { - border-radius: 10px; -} - -.dark-mode .ant-breadcrumb, -.dark-mode .page-breadcrumb { - color: var(--muted); -} - -.dark-mode .ant-menu-dark .ant-menu-item, -.dark-mode .ant-menu-dark .ant-menu-submenu-title { - color: var(--muted); -} - -/* smooth transition for mode change */ -html, -body, -.ant-layout, -.ant-layout .ant-layout { - transition: background 0.25s ease, color 0.25s ease; -} - -/* Ensure sider collapse trigger / bottom area stays white */ -.app-sider .ant-layout-sider-trigger { - background: #ffffff; - color: rgba(0, 0, 0, 0.85); -} - -/* Button styling: primary buttons use black background, general buttons have dark text */ -.ant-btn { - color: rgba(0, 0, 0, 0.85); -} - -.ant-btn-primary { - background: #000000; - border-color: #000000; - color: #ffffff; -} diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx deleted file mode 100644 index c727471..0000000 --- a/src/layouts/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Outlet, useLocation } from 'umi'; - -import PrimaryLayout from './PrimaryLayout'; -import PublicLayout from './PublicLayout'; - -export default function LayoutWrapper() { - const { pathname } = useLocation(); - - // Simple rule: use PublicLayout for authentication/public routes - if (pathname.startsWith('/public') || pathname === '/login') { - return ( - - - - ); - } - - return ( - - - - ); -} diff --git a/src/locales/en-US.json b/src/locales/en-US.json deleted file mode 100644 index fd86458..0000000 --- a/src/locales/en-US.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "welcome": "Welcome to Antd Admin", - "login.username": "Username", - "login.password": "Password", - "login.usernameRequired": "Please enter your username", - "login.passwordRequired": "Please enter your password", - "login.remember": "Remember me", - "login.submit": "Sign In", - "login.success": "Login success, redirecting...", - "login.themeLight": "Light", - "login.themeDark": "Dark", - "login.switchEnglish": "English", - "login.switchChinese": "Chinese", - "guide.i18n.example": "Example: add an entry to locale files and use it in pages:", - "guide.i18n.usage": "Usage in pages" -} diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json deleted file mode 100644 index 748c452..0000000 --- a/src/locales/zh-CN.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "welcome": "欢迎使用 Antd Admin", - "login.username": "用户名", - "login.password": "密码", - "login.usernameRequired": "请输入用户名", - "login.passwordRequired": "请输入密码", - "login.remember": "记住我", - "login.submit": "登录", - "login.success": "登录成功", - "login.themeLight": "浅色", - "login.themeDark": "暗色", - "login.switchEnglish": "English", - "login.switchChinese": "中文", - "guide.i18n.example": "示例:在翻译文件中增加条目并在页面使用:", - "guide.i18n.usage": "页面中可使用" -} diff --git a/src/pages/404.tsx b/src/pages/404.tsx deleted file mode 100644 index dfef767..0000000 --- a/src/pages/404.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Button, Result } from 'antd'; -import React from 'react'; -import { history } from 'umi'; - -const NotFound: React.FC = () => { - return ( - history.push('/')}> - 返回首页 - - } - /> - ); -}; - -export default NotFound; diff --git a/src/pages/admin/$id.tsx b/src/pages/admin/$id.tsx deleted file mode 100644 index 68842a5..0000000 --- a/src/pages/admin/$id.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import useIsMounted from '@/hooks/useIsMounted'; -import { getAdmin } from '@/services/admin'; -import type { Admin } from '@/types/admin'; -import { - Avatar, - Button, - Card, - Col, - Descriptions, - message, - Row, - Space, - Spin, - Tag, -} from 'antd'; - -import { useEffect, useState } from 'react'; -import { useParams } from 'umi'; - -export default function AdminDetail() { - const params = useParams<{ id: string }>(); - const { id } = params; - const [admin, setAdmin] = useState(null); - const [loading, setLoading] = useState(true); - - const isMounted = useIsMounted(); - useEffect(() => { - (async () => { - try { - const resp = await getAdmin(String(id)); - if (!isMounted()) return; - setAdmin((resp as import('@/types/admin').Admin) || null); - } catch (e) { - console.error(e); - } finally { - if (isMounted()) setLoading(false); - } - })(); - }, [id, isMounted]); - - if (loading) - return ( -
- -
- ); - if (!admin) return 未找到管理员; - const perms = admin.permissions - ? String(admin.permissions) - .split(',') - .map((s: string) => s.trim()) - .filter(Boolean) - : []; - - return ( - - - - - - -
- 用户名: {admin.username} -
-
- 姓名: {admin.real_name} -
-
- 昵称: {admin.nick_name} -
-
- - - {admin.status === 1 ? '已启用' : '已禁用'} - - {admin.role || '未设置角色'} - {admin.department ? ( - {admin.department} - ) : null} - -
-
- - - - -
- - -
- 最后登录: -
-
- {(admin as any).lastLogin || '-'} -
-
- 创建者: {(admin as any).createdBy || '-'} -
- -
- - - {admin.id} - {admin.mobile} - {admin.remark || ''} - - - - {perms && perms.length ? ( -
- {perms.map((p) => ( - - {p} - - ))} -
- ) : ( -
无权限
- )} -
-
- ); -} diff --git a/src/pages/admin/components/Filter.tsx b/src/pages/admin/components/Filter.tsx deleted file mode 100644 index 883c26b..0000000 --- a/src/pages/admin/components/Filter.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Button, Space } from 'antd'; - -type Props = { - onAdd: () => void; -}; - -export default function Filter({ onAdd }: Props) { - return ( -
- - - -
- ); -} diff --git a/src/pages/admin/components/List.tsx b/src/pages/admin/components/List.tsx deleted file mode 100644 index f9d3c4e..0000000 --- a/src/pages/admin/components/List.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import DropOption from '@/components/DropOption'; -import { Avatar, Table } from 'antd'; - -import { Admin } from '@/types/admin'; - -type Props = { - data: Admin[]; - onEdit: (item: Admin) => void; - onDelete: (id: number) => void; - onDetail: (item: Admin) => void; -}; - -export default function AdminList({ data, onEdit, onDetail, onDelete }: Props) { - const columns = [ - { - title: '头像', - dataIndex: 'avatar', - render: (_: unknown, record: Admin) => , - }, - { - title: '用户名', - dataIndex: 'username', - render: (_: unknown, record: Admin) => ( - onDetail(record)}>{record.username} - ), - }, - { - title: '姓名', - dataIndex: 'real_name', - }, - { title: '手机', dataIndex: 'mobile' }, - { - title: '权限', - dataIndex: 'permissions', - render: (p: string[] | string | undefined) => p || '', - }, - { - title: '状态', - dataIndex: 'status', - render: (s: number) => (s === 1 ? '已启用' : '已禁用'), - }, - { - title: '操作', - render: (_: unknown, record: Admin) => ( - { - if (key === 'edit') onEdit(record); - if (key === 'delete') onDelete(record.id); - }} - > - ), - }, - ]; - - return ( - - ); -} diff --git a/src/pages/admin/components/Modal.tsx b/src/pages/admin/components/Modal.tsx deleted file mode 100644 index 8e6621a..0000000 --- a/src/pages/admin/components/Modal.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import type { Admin } from '@/types/admin'; -import { Form, Input, Modal, Select } from 'antd'; -import { useEffect } from 'react'; - -const layout = { labelCol: { span: 6 }, wrapperCol: { span: 14 } }; - -type Props = { - open: boolean; - item?: Admin; - routes: Array<{ id: string; name?: string; route: string }>; - onCancel: () => void; - onOk: (values: Record) => void; -}; - -export default function AdminModal({ - open, - item, - routes, - onCancel, - onOk, -}: Props) { - const [form] = Form.useForm(); - - useEffect(() => { - if (!open) return; - const perms = - item && item.permissions - ? typeof item.permissions === 'string' - ? (item.permissions as string) - .split(',') - .map((s: string) => s.trim()) - .filter(Boolean) - : (item.permissions as string[]) - : []; - form.setFieldsValue({ - username: item?.username, - real_name: item?.real_name, - nick_name: item?.nick_name, - mobile: item?.mobile, - permissions: perms, - }); - }, [open, item, form]); - - const handleOk = async () => { - try { - const values = await form.validateFields(); - onOk(values); - } catch (e) { - // validation failed - } - }; - - return ( - -
- - - - - - - - - - - - - - - - - - { - nextLabel = event.target.value; - }} - onPressEnter={(event) => { - event.preventDefault(); - }} - /> - ), - okText: '确定', - cancelText: '取消', - onOk: () => { - const value = nextLabel.trim(); - if (!value) { - message.warning('会话名称不能为空'); - return Promise.reject(); - } - setSessions((prev) => - prev.map((item) => - item.key === sessionKey ? { ...item, label: value } : item, - ), - ); - return Promise.resolve(); - }, - }); - }; - - const handleDeleteSession = (sessionKey: string) => { - if (sessions.length <= 1) { - message.warning('至少保留一个会话'); - return; - } - - Modal.confirm({ - title: '删除会话', - content: '删除后不可恢复,确定删除该会话吗?', - okText: '删除', - okButtonProps: { danger: true }, - cancelText: '取消', - onOk: () => { - const nextSessions = sessions.filter((item) => item.key !== sessionKey); - setSessions(nextSessions); - - if (activeKey === sessionKey) { - setActiveKey(nextSessions[0].key); - setLoading(false); - } - }, - }); - }; - - return ( - -
- {!isMobile ? ( -
- ({ - key: item.key, - label: item.label, - }))} - activeKey={activeKey} - onActiveChange={(key) => setActiveKey(String(key))} - creation={{ label: '新建会话', onClick: handleCreateSession }} - menu={(conversation) => ({ - items: [ - { key: 'rename', label: '重命名' }, - { key: 'delete', label: '删除' }, - ], - onClick: ({ key }) => { - if (key === 'rename') handleRenameSession(conversation.key); - if (key === 'delete') handleDeleteSession(conversation.key); - }, - })} - /> -
- ) : ( -
- handleProviderChange(value as ProviderKey)} - options={PROVIDER_OPTIONS} - /> - - - {/* 移除多标签页切换:只根据“默认 Provider”选择展示对应配置 */} - - - - 安全提示:前端直连外部模型服务会暴露 API - Key,生产环境建议使用同域代理地址(如 /api/proxy/openai/v1)。 - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/src/pages/ai/components/mockData.tsx b/src/pages/ai/components/mockData.tsx deleted file mode 100644 index 5db8e92..0000000 --- a/src/pages/ai/components/mockData.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { BulbOutlined } from '@ant-design/icons'; - -import type { ProviderConfig, ProviderKey } from '../aiConfig'; - -export const PROMPT_ITEMS = [ - { - key: 'prompt-1', - icon: , - label: '介绍一下这个示例页', - description: '页面能力说明', - }, - { - key: 'prompt-2', - icon: , - label: '给我 3 个 React 性能优化建议', - description: '开发常用问题', - }, -]; - -export function mockReply( - question: string, - _provider: ProviderKey, - _providerConfig: ProviderConfig, -) { - const text = question.toLowerCase(); - if (text.includes('react') && text.includes('性能')) { - return [ - '1. 使用 memo/useMemo 减少不必要渲染。', - '2. 大列表使用虚拟滚动。', - '3. 拆分状态,避免顶层状态过大导致全树刷新。', - ].join('\n'); - } - - return [ - '检测到未配置 API Key,当前使用本地模拟回复。', - '填写 Key 后会自动切换到真实模型请求。', - ].join('\n'); -} diff --git a/src/pages/dashboard.tsx b/src/pages/dashboard.tsx deleted file mode 100644 index 49ed164..0000000 --- a/src/pages/dashboard.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Card, Col, Row, Statistic } from 'antd'; -import { startTransition, useDeferredValue, useEffect, useState } from 'react'; - -import useIsMounted from '@/hooks/useIsMounted'; -import dashboardService from '../services/dashboard'; - -type BrowserItem = { name: string; percent?: number }; - -type DashboardData = { - cpu?: { cpu?: number; space?: number }; - sales?: unknown[]; - browser?: BrowserItem[]; -}; - -export default function DashboardPage() { - const [data, setData] = useState({} as DashboardData); - - const deferredData = useDeferredValue(data); - - const isMounted = useIsMounted(); - useEffect(() => { - (async () => { - try { - const resp = await dashboardService.getDashboard(); - if (isMounted()) startTransition(() => setData(resp || {})); - } catch (e) { - // ignore for demo - } - })(); - }, [isMounted]); - - return ( -
- -
- - - - - - - - - - - - - - - - - - {(deferredData.browser || []).map((b) => ( -
- {b.name}: {b.percent}% -
- ))} -
- - ); -} diff --git a/src/pages/guide.tsx b/src/pages/guide.tsx deleted file mode 100644 index 8addea6..0000000 --- a/src/pages/guide.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { Button, Card, Divider, Typography } from 'antd'; -import { setLocale, useIntl } from 'umi'; - -const { Title, Paragraph, Text } = Typography; - -export default function HomePage() { - const intl = useIntl(); - return ( -
- - {intl.formatMessage({ id: 'welcome' }) || 'Welcome'} - - - - - 快速开始 - - - 运行开发环境: -
-            npm run dev
-          
-
-
- - - - 添加页面 - - - 在 src/pages 下新建一个文件 (例如{' '} - src/pages/hello.tsx),Umi 会自动把它注册为路由。 - - - 示例页面代码: -
-            {`import React from 'react';
-export default function Hello() {
-  return 
Hello 页面
; -}`} -
-
-
- - - - 多语言 (i18n) - - - 本项目使用 Umi 的国际化支持,翻译文件位于{' '} - src/locales(例如 zh-CN.json、 - en-US.json)。 - - - {intl.formatMessage({ id: 'guide.i18n.example' })} - -
-          {`// src/locales/zh-CN.json
-{
-  "welcome": "欢迎使用本项目"
-}
-
-// 页面中使用
-import { useIntl } from 'umi';
-const intl = useIntl();
-intl.formatMessage({ id: 'welcome' });`}
-        
- - {intl.formatMessage({ id: 'guide.i18n.usage' })}{' '} - - - -
- - - - 引入 ECharts - - 安装依赖(项目根目录): -
-          npm install echarts --save
-        
- 在页面中使用: -
-          {`import React, { useEffect, useRef } from 'react';
-import * as echarts from 'echarts';
-
-export default function ChartPage() {
-  const ref = useRef(null);
-  useEffect(() => {
-    if (!ref.current) return;
-    const chart = echarts.init(ref.current);
-    chart.setOption({ xAxis: { type: 'category', data: ['a','b','c'] }, yAxis: { type: 'value' }, series: [{ data: [5,20,36], type: 'bar' }] });
-    return () => chart.dispose();
-  }, []);
-  return 
; -}`} -
-
- - - - 图标优化 - - - 项目中的菜单图标已抽取到组件: - src/components/IconMap.tsx,PrimaryLayout - 已从此处引入并使用统一的 ICON_MAP。 - - 使用示例: -
-          {`import { ICON_MAP, Icon } from '@/components/IconMap';
-const icon = ICON_MAP['dashboard']; // 或者 `}
-        
-
- - - - 测试(Testing) - - - 本项目使用 Jest 和 @testing-library/react - 进行单元测试。测试文件通常放在被测模块同目录,文件名以{' '} - .test.ts 或 .test.tsx 结尾。 - - - - 运行测试命令: -
-            {`pnpm install
-pnpm test
-pnpm test:watch`}
-          
-
- - - 测试函数示例(放在 src/utils): - -
-          {`// src/utils/sum.ts
-export function sum(a: number, b: number) { return a + b }
-
-// src/utils/sum.test.ts
-import { sum } from './sum'
-
-test('sum adds numbers', () => {
-  expect(sum(1, 2)).toBe(3)
-})`}
-        
- - 测试组件示例(使用 @testing-library/react): -
-          {`// src/components/DropOption.test.tsx
-import { render, screen, fireEvent } from '@testing-library/react'
-import DropOption from './DropOption'
-
-test('clicking menu item calls callback', () => {
-  const onMenuClick = jest.fn()
-  render()
-  fireEvent.click(screen.getByText('One'))
-  expect(onMenuClick).toHaveBeenCalledWith('one')
-})`}
-        
- - 小贴士: -
    -
  • - 使用 `jest.fn()` 创建 mock 回调;使用 `jest.mock()` 模拟模块。 -
  • -
  • - 已在 src/setupTests.ts 中引入 - `@testing-library/jest-dom`,可使用额外断言。 -
  • -
  • 需要模拟网络请求时,可 mock 全局 `fetch` 或使用 `msw`。
  • -
-
- - - - 更多帮助请查看项目 README 或联系维护者。 - -
- ); -} diff --git a/src/pages/index.tsx b/src/pages/index.tsx deleted file mode 100644 index e0ad9f6..0000000 --- a/src/pages/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useEffect } from 'react'; -import { history } from 'umi'; - -export default function RootRedirect() { - useEffect(() => { - try { - history.replace('/dashboard'); - } catch (e) { - window.location.href = '/dashboard'; - } - }, []); - return null; -} diff --git a/src/pages/login/index.module.less b/src/pages/login/index.module.less deleted file mode 100644 index 19b099e..0000000 --- a/src/pages/login/index.module.less +++ /dev/null @@ -1,83 +0,0 @@ -/* Styles adapted from reference login module */ -.page { - position: relative; - min-height: 100vh; -} - -.form { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - padding: 36px; - box-shadow: 0 0 40px rgba(0, 0, 0, 0.08); - border-radius: 12px; - width: 360px; - background: #fff; - - h2 { - margin-bottom: 4px; - } - - p { - color: #64748b; - margin-bottom: 18px; - } - - button { - width: 100%; - margin-top: 12px; - } - - .remember { - float: left; - } -} - -.logo { - height: 40px; - line-height: 40px; - cursor: pointer; - margin-bottom: 24px; - display: flex; - align-items: center; - justify-content: center; - gap: 12px; - - img { - height: 40px; - width: 40px; - } - - span { - vertical-align: middle; - font-size: 16px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.6px; - color: #0f172a; - display: inline-block; - } -} - -.toolbar { - position: absolute; - right: 16px; - top: 16px; - display: flex; - gap: 8px; - z-index: 20; -} - -@media (max-width: 576px) { - .form { - width: calc(100vw - 32px); - padding: 24px; - } -} - -.footer { - position: absolute; - width: 100%; - bottom: 0; -} diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx deleted file mode 100644 index 346c521..0000000 --- a/src/pages/login/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { LockOutlined, UserOutlined } from '@ant-design/icons'; -import { Button, Checkbox, Form, Input, message } from 'antd'; -import { history, useIntl } from 'umi'; - -import adminService from '@/services/admin'; -import useGlobalStore from '@/store/useGlobalStore'; -import config from '@/utils/config'; -import styles from './index.module.less'; - -export default function Login() { - const intl = useIntl(); - const [form] = Form.useForm(); - - type LoginForm = { username?: string; adminname?: string; password?: string }; - - const onFinish = (values: LoginForm) => { - (async () => { - try { - const creds = { - adminname: String(values.username ?? values.adminname ?? ''), - password: String(values.password ?? ''), - }; - await adminService.login(creds); - message.success(intl.formatMessage({ id: 'login.success' })); - useGlobalStore.getState().login(); - - const admin = (await adminService.currentAdmin()) as - | import('@/types/admin').Admin - | undefined; - if (admin) { - useGlobalStore.getState().setCurrentUser(admin); - } - - history.push('/dashboard'); - } catch (e: any) { - console.error(e); - } - })(); - }; - - return ( -
-
-
- logo - {config.siteName} -
- -
- - } - autoComplete="username" - placeholder={intl.formatMessage({ id: 'login.username' })} - /> - - - - } - autoComplete="current-password" - placeholder={intl.formatMessage({ id: 'login.password' })} - /> - - - - {intl.formatMessage({ id: 'login.remember' })} - - -
- use guest/guest or admin/admin login -
- - - -
-
- ); -} diff --git a/src/pages/sys/menu/components/Filter.tsx b/src/pages/sys/menu/components/Filter.tsx deleted file mode 100644 index d036a26..0000000 --- a/src/pages/sys/menu/components/Filter.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Button, Space } from 'antd'; - -type Props = { - selectedCount: number; - onAdd: () => void; - onSort: () => void; - onToggleSelectedStatus: () => void; -}; - -export default function Filter({ - selectedCount, - onAdd, - onSort, - onToggleSelectedStatus, -}: Props) { - return ( - - - - - - ); -} diff --git a/src/pages/sys/menu/components/List.tsx b/src/pages/sys/menu/components/List.tsx deleted file mode 100644 index 116b522..0000000 --- a/src/pages/sys/menu/components/List.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import DropOption from '@/components/DropOption'; -import type { MenuItem } from '@/types/menu'; -import type { TableColumnsType } from 'antd'; -import { Space, Table, Tag } from 'antd'; -import React from 'react'; - -type Props = { - dataSource: MenuItem[]; - selectedRowKeys: React.Key[]; - onSelectChange: (keys: React.Key[]) => void; - onEditItem: (item: MenuItem) => void; - onDeleteItem: (id: number) => void; -}; - -export default function MenuList({ - dataSource, - selectedRowKeys, - onSelectChange, - onEditItem, - onDeleteItem, -}: Props) { - const normalizeChildren = (items?: MenuItem[]): MenuItem[] | undefined => { - if (!items) return undefined; - const next = items.map((it) => ({ - ...it, - children: normalizeChildren(it.children), - })); - return next && next.length ? next : undefined; - }; - const columns: TableColumnsType = [ - { title: 'No.', dataIndex: 'id', width: 90 }, - { title: '名称', dataIndex: 'title', width: 180 }, - { title: '图标', dataIndex: 'icon', width: 180, render: (v) => v || '-' }, - { title: '路径', dataIndex: 'route', width: 260 }, - { - title: '状态', - dataIndex: 'status', - width: 120, - render: (status: number) => - status === 1 ? 已启用 : 已禁用, - }, - { - title: '操作', - key: 'action', - width: 170, - render: (_, record) => { - const menuOptions = [ - { key: 'edit', label: 修改 }, - { key: 'delete', label: 删除 }, - ]; - - const handleMenuClick = (key: string) => { - if (key === 'edit') onEditItem(record); - if (key === 'delete') onDeleteItem(record.id); - }; - - return ( - - - - ); - }, - }, - ]; - - return ( - - bordered - rowKey="id" - columns={columns} - dataSource={(normalizeChildren(dataSource) || []) as MenuItem[]} - pagination={false} - rowSelection={{ selectedRowKeys, onChange: onSelectChange }} - scroll={{ x: 'max-content' }} - /> - ); -} diff --git a/src/pages/sys/menu/components/Modal.tsx b/src/pages/sys/menu/components/Modal.tsx deleted file mode 100644 index eb3be66..0000000 --- a/src/pages/sys/menu/components/Modal.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import type { MenuFormValues, MenuItem } from '@/types/menu'; -import { - Form, - Input, - InputNumber, - Modal, - Radio, - Select, - TreeSelect, -} from 'antd'; -import { useEffect } from 'react'; - -type Props = { - open: boolean; - type: 'create' | 'update'; - item?: MenuItem; - parentOptions: Array<{ - title: string; - value: number; - children?: Array<{ title: string; value: number }>; - }>; - onOk: (values: MenuFormValues) => void; - onCancel: () => void; -}; - -const privilegeOptions = [ - { label: '增', value: 'create' }, - { label: '删', value: 'delete' }, - { label: '改', value: 'update' }, - { label: '查', value: 'query' }, -]; - -export default function MenuModal({ - open, - type, - item, - parentOptions, - onOk, - onCancel, -}: Props) { - const [form] = Form.useForm(); - - useEffect(() => { - if (!open) return; - form.setFieldsValue({ - title: item?.title || '', - icon: item?.icon || '', - route: item?.route || '', - status: item?.status ?? 1, - parentId: item?.parentId ?? null, - privilege: item?.privilege || [], - remark: item?.remark || '', - sort: item?.sort ?? 1, - }); - }, [open, item, form]); - - const handleOk = async () => { - const values = await form.validateFields(); - onOk(values); - }; - - return ( - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); -} diff --git a/src/pages/sys/menu/components/SortModal.tsx b/src/pages/sys/menu/components/SortModal.tsx deleted file mode 100644 index 95f4a3e..0000000 --- a/src/pages/sys/menu/components/SortModal.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import type { MenuItem } from '@/types/menu'; -import { Button, Modal, Space, Table } from 'antd'; -import { startTransition, useDeferredValue, useEffect, useState } from 'react'; - -type SortRow = { - id: number; - title: string; - route?: string; - sort: number; - children?: SortRow[]; -}; - -type Props = { - open: boolean; - menus: MenuItem[]; - onCancel: () => void; - // now returns the updated tree with new sort order - onConfirm: (updatedMenus: MenuItem[]) => void; -}; - -function toRows(menus: MenuItem[]): SortRow[] { - return [...menus] - .sort((a, b) => a.sort - b.sort) - .map((m) => ({ - id: m.id, - title: m.title, - route: m.route || '/', - sort: m.sort || 0, - children: m.children ? toRows(m.children) : undefined, - })); -} - -export default function SortModal({ open, menus, onCancel, onConfirm }: Props) { - const [rows, setRows] = useState([]); - useEffect(() => { - if (!open) return; - startTransition(() => setRows(toRows(menus))); - }, [open, menus]); - - const deferredRows = useDeferredValue(rows); - - const move = (list: SortRow[], idx: number, step: -1 | 1) => { - const next = [...list]; - const target = idx + step; - if (target < 0 || target >= next.length) return list; - const t = next[idx]; - next[idx] = next[target]; - next[target] = t; - return next.map((r, i) => ({ ...r, sort: i + 1 })); - }; - - const moveRoot = (idx: number, step: -1 | 1) => - setRows((prev) => move(prev, idx, step)); - - const moveChild = (rootIdx: number, childIdx: number, step: -1 | 1) => { - setRows((prev) => { - const next = [...prev]; - const root = { - ...next[rootIdx], - children: prev[rootIdx].children ? [...prev[rootIdx].children] : [], - }; - root.children = move(root.children || [], childIdx, step); - next[rootIdx] = root; - return next; - }); - }; - - const buildMenuTree = (rows: SortRow[]): MenuItem[] => { - return rows.map( - (r) => - ({ - id: r.id, - title: r.title, - route: r.route || '/', - sort: r.sort, - children: r.children ? buildMenuTree(r.children) : undefined, - }) as MenuItem, - ); - }; - - return ( - onConfirm(buildMenuTree(rows))} - destroyOnHidden - width={900} - styles={{ - body: { - maxHeight: '60vh', - overflowY: 'auto', - }, - }} - > -
- {deferredRows.map((r, idx) => ( -
-
-
- - {r.title} - - - {r.route} - -
-
- - - - -
-
- {r.children && r.children.length ? ( -
-
a.sort - b.sort)} - columns={[ - { title: '排序', dataIndex: 'sort', width: 80 }, - { title: '名称', dataIndex: 'title' }, - { title: '路径', dataIndex: 'route' }, - { - title: '操作', - width: 140, - render: (_, __, childIdx: number) => ( - - - - - ), - }, - ]} - style={{ minWidth: 600 }} - scroll={{ x: 'max-content' }} - /> - - ) : null} - - ))} - - - ); -} diff --git a/src/pages/sys/menu/index.tsx b/src/pages/sys/menu/index.tsx deleted file mode 100644 index c38e75d..0000000 --- a/src/pages/sys/menu/index.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import { Card, message } from 'antd'; -import React, { useMemo, useState } from 'react'; - -import useIsMounted from '@/hooks/useIsMounted'; -import * as menuService from '@/services/menu'; -import type { MenuFormValues, MenuItem } from '@/types/menu'; -import Filter from './components/Filter'; -import MenuList from './components/List'; -import MenuModal from './components/Modal'; -import SortModal from './components/SortModal'; - -function mapTree( - items: MenuItem[], - fn: (item: MenuItem) => MenuItem, -): MenuItem[] { - return items.map((item) => { - const next = fn(item); - if (!item.children?.length) return next; - return { ...next, children: mapTree(item.children, fn) }; - }); -} - -function normalizeChildren(items?: MenuItem[]): MenuItem[] { - if (!items || !items.length) return []; - return items - .map((it) => ({ - ...it, - children: normalizeChildren(it.children) || undefined, - })) - .map((it) => ({ - ...it, - children: it.children && it.children.length ? it.children : undefined, - })); -} - -function removeNode(items: MenuItem[], id: number): MenuItem[] { - return items - .filter((item) => item.id !== id) - .map((item) => ({ - ...item, - children: item.children ? removeNode(item.children, id) : undefined, - })); -} - -function updateNode( - items: MenuItem[], - id: number, - updater: (node: MenuItem) => MenuItem, -): MenuItem[] { - return items.map((item) => { - if (item.id === id) return updater(item); - if (!item.children?.length) return item; - return { ...item, children: updateNode(item.children, id, updater) }; - }); -} - -function addNode( - items: MenuItem[], - parentId: number | null, - node: MenuItem, -): MenuItem[] { - if (parentId === null) return [...items, node]; - return items.map((item) => { - if (item.id === parentId) { - return { ...item, children: [...(item.children || []), node] }; - } - if (!item.children?.length) return item; - return { ...item, children: addNode(item.children, parentId, node) }; - }); -} - -function toParentOptions(items: MenuItem[]): Array<{ - title: string; - value: number; - children?: Array<{ title: string; value: number }>; -}> { - return items.map((item) => ({ - title: item.title, - value: item.id, - children: item.children?.map((child) => ({ - title: child.title, - value: child.id, - })), - })); -} - -export default function SysMenuPage() { - const [menus, setMenus] = useState(() => []); - const [selectedRowKeys, setSelectedRowKeys] = useState([]); - const [modalOpen, setModalOpen] = useState(false); - const [modalType, setModalType] = useState<'create' | 'update'>('create'); - const [currentItem, setCurrentItem] = useState( - undefined, - ); - const [sortOpen, setSortOpen] = useState(false); - - const parentOptions = useMemo(() => toParentOptions(menus), [menus]); - - const handleCreate = () => { - setModalType('create'); - setCurrentItem(undefined); - setModalOpen(true); - }; - - const handleEdit = (item: MenuItem) => { - setModalType('update'); - setCurrentItem(item); - setModalOpen(true); - }; - - const handleDelete = async (id: number) => { - try { - await menuService.deleteMenu(id); - setMenus((prev) => normalizeChildren(removeNode(prev, id))); - setSelectedRowKeys((prev) => prev.filter((k) => Number(k) !== id)); - message.success('删除成功'); - } catch (e) { - console.error(e); - } - }; - - const handleSave = async (values: MenuFormValues) => { - try { - if (modalType === 'create') { - const resp = await menuService.createMenu(values); - const added = resp as MenuItem; - setMenus((prev) => - normalizeChildren(addNode(prev, values.parentId, added)), - ); - message.success('新增成功'); - } else if (currentItem) { - await menuService.updateMenu( - currentItem.id, - values as Partial, - ); - setMenus((prev) => - normalizeChildren( - updateNode(prev, currentItem.id, (node) => ({ - ...node, - ...values, - })), - ), - ); - message.success('修改成功'); - } - setModalOpen(false); - } catch (e) { - console.error(e); - } - }; - - const handleToggleSelectedStatus = () => { - if (!selectedRowKeys.length) return; - const selectedSet = new Set(selectedRowKeys.map((k) => Number(k))); - setMenus((prev) => - normalizeChildren( - mapTree(prev, (item) => { - if (!selectedSet.has(item.id)) return item; - return { ...item, status: item.status === 1 ? 0 : 1 }; - }), - ), - ); - message.success('选中项状态已切换'); - }; - - const handleSortConfirm = async (updatedMenus: MenuItem[]) => { - try { - // attempt to persist ordering to API; API may accept different shapes - try { - await menuService.sortMenus(updatedMenus as any); - } catch (e) { - // ignore API errors but still update local state - } - - // ensure sort fields are normalized and flatten structure to set state - const normalize = ( - items: MenuItem[], - parentId: number | null = null, - ): MenuItem[] => { - return items.map((it: MenuItem, idx: number) => ({ - ...it, - parentId, - sort: idx + 1, - children: it.children ? normalize(it.children, it.id) : undefined, - })); - }; - - const normalized: MenuItem[] = normalize(updatedMenus); - setMenus( - normalized.map((it: MenuItem) => ({ - ...it, - children: it.children && it.children.length ? it.children : undefined, - })), - ); - setSortOpen(false); - message.success('排序已更新'); - } catch (e) { - console.error(e); - } - }; - - // load menus from API on mount - const isMounted = useIsMounted(); - React.useEffect(() => { - (async () => { - try { - const list = await menuService.listMenus(); - if (isMounted()) setMenus(normalizeChildren(list as MenuItem[])); - } catch (e) { - // keep initial menus on error - } - })(); - }, [isMounted]); - - return ( - - setSortOpen(true)} - onToggleSelectedStatus={handleToggleSelectedStatus} - /> - - a.sort - b.sort)} - selectedRowKeys={selectedRowKeys} - onSelectChange={setSelectedRowKeys} - onEditItem={handleEdit} - onDeleteItem={handleDelete} - /> - - setModalOpen(false)} - /> - - setSortOpen(false)} - onConfirm={handleSortConfirm} - /> - - ); -} diff --git a/src/pages/users/$id.tsx b/src/pages/users/$id.tsx deleted file mode 100644 index 9c86bc6..0000000 --- a/src/pages/users/$id.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import useIsMounted from '@/hooks/useIsMounted'; -import { getUser } from '@/services/user'; -import type { User } from '@/types/user'; -import { - Avatar, - Button, - Card, - Col, - Descriptions, - Row, - Spin, - Statistic, - Tag, - message, -} from 'antd'; - -import { startTransition, useEffect, useState } from 'react'; -import { useParams } from 'umi'; - -export default function UserDetail() { - const params = useParams<{ id: string }>(); - const { id } = params; - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - - const isMounted = useIsMounted(); - useEffect(() => { - (async () => { - try { - const resp = await getUser(String(id)); - if (!isMounted()) return; - startTransition(() => { - setUser(resp || null); - }); - } catch (e) { - // log and continue - console.error(e); - } finally { - if (isMounted()) setLoading(false); - } - })(); - }, [id, isMounted]); - - if (loading) - return ( -
- -
- ); - if (!user) return 未找到用户; - - const gender = - typeof user.isMale === 'boolean' ? (user.isMale ? '男' : '女') : '未知'; - - return ( - - -
- -
-

- {user.name || user.nickName || user.id}{' '} -

-
- {user.bio} -
-
- - -
-
- -
- -
- - {user.status === 'active' ? '活跃' : '未激活'} - -
- - - - - - {user.id} - {user.name} - {user.nickName} - {user.phone} - {user.age} - {gender} - - {(user as any).phoneModel || '-'} - - {user.email} - - {user.address} - - - {user.createTime} - - - - - - {( - ((user as any).browsingHistory || []) as Array<{ - url: string; - title: string; - time: string; - }> - ).map((it) => ( -
-
-
{it.title || it.url}
-
{it.time}
-
- e.preventDefault()}> - 访问 - -
- ))} -
- - ); -} diff --git a/src/pages/users/list.tsx b/src/pages/users/list.tsx deleted file mode 100644 index 90ee111..0000000 --- a/src/pages/users/list.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import useIsMounted from '@/hooks/useIsMounted'; -import { listUsers } from '@/services/user'; -import type { PagedResponse, User } from '@/types/user'; -import { Card, Input, Table } from 'antd'; - -import { - startTransition, - useCallback, - useDeferredValue, - useEffect, - useState, -} from 'react'; - -const { Search } = Input; - -export default function UserList() { - const [data, setData] = useState([]); - const [loading, setLoading] = useState(false); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(10); - const [total, setTotal] = useState(0); - const [keyword, setKeyword] = useState(''); - - const fetch = useCallback( - async (p = page, ps = pageSize, k = keyword) => { - setLoading(true); - try { - const resp = (await listUsers({ - page: p, - pageSize: ps, - name: k, - })) as PagedResponse; - startTransition(() => { - setData(resp?.data || []); - setTotal(resp?.total ?? (resp?.data ? resp.data.length : 0)); - }); - } catch (e) { - startTransition(() => { - setData([]); - setTotal(0); - }); - } finally { - setLoading(false); - } - }, - [page, pageSize, keyword], - ); - - // defer large list rendering to avoid jank on immediate updates - const deferredData = useDeferredValue(data); - const deferredTotal = useDeferredValue(total); - - const isMounted = useIsMounted(); - useEffect(() => { - (async () => { - if (!isMounted()) return; - await fetch(page, pageSize, keyword); - })(); - }, [fetch, page, pageSize, keyword, isMounted]); - - const onDetail = (item: User) => { - window.open(`/users/${item.id}`); - }; - - const handleTableChange = (p: { current?: number; pageSize?: number }) => { - const nextPage = p.current || 1; - const nextSize = p.pageSize || 10; - startTransition(() => { - setPage(nextPage); - setPageSize(nextSize); - }); - fetch(nextPage, nextSize, keyword); - }; - - const columns = [ - { - title: 'Name', - dataIndex: 'name', - render: (_: unknown, record: User) => ( - onDetail(record)}>{record.name} - ), - }, - { title: 'Nick', dataIndex: 'nickName' }, - { title: 'Phone', dataIndex: 'phone' }, - { title: 'Age', dataIndex: 'age' }, - { title: 'Email', dataIndex: 'email' }, - { title: 'Address', dataIndex: 'address' }, - { title: 'Created', dataIndex: 'createTime' }, - ]; - - return ( - -
- { - setKeyword(v); - setPage(1); - }} - enterButton - allowClear - style={{ width: 260 }} - /> -
-
r.id} - loading={loading} - pagination={{ - current: page, - pageSize, - total: deferredTotal, - showSizeChanger: true, - }} - onChange={handleTableChange} - /> - - ); -} diff --git a/src/services/admin.ts b/src/services/admin.ts deleted file mode 100644 index 47a0d4e..0000000 --- a/src/services/admin.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Admin 服务:封装关于管理员(admin)相关的后端 API 调用。 - * - * 对 LLM 的说明: - * - 导出函数均为对后端 REST 接口的轻量封装,返回值由 `src/utils/request.ts` 统一处理(JSON 解析与错误抛出)。 - * - 常用函数:`login`, `logout`, `currentAdmin`, `listAdmins`, `createAdmin`, `getAdmin`, `updateAdmin`, `deleteAdmin`, `deleteAdmins`。 - * - 参数与返回类型:尽量参考 `src/types/admin.ts` 中的 `Admin` / `AdminResponse` 定义以了解字段结构。 - * - * 使用示例: - * ```ts - * const admin = await login({ adminname: 'root', password: 'pwd' }); - * const list = await listAdmins({ page: 1 }); - * ``` - */ -import api from '../utils/request'; - -export async function login(values: { adminname: string; password: string }) { - const payload = { - username: values.adminname, - password: values.password, - }; - return api('/admin/login', { method: 'POST', body: payload }); -} - -export async function logout() { - return api('/admin/logout'); -} - -export async function currentAdmin(): Promise< - import('@/types/admin').Admin | undefined -> { - return api('/admin'); -} - -export async function listAdmins( - params?: Record, -): Promise<{ data: import('@/types/admin').Admin[]; total?: number }> { - return api<{ data: import('@/types/admin').Admin[]; total?: number }>( - '/admins', - { method: 'GET', params }, - ); -} - -export async function createAdmin(values: Record) { - return api('/admin', { method: 'POST', body: values }); -} - -export async function getAdmin( - id: string, -): Promise { - return api(`/admin/${id}`); -} - -export async function updateAdmin(id: string, values: Record) { - return api(`/admin/${id}`, { method: 'PATCH', body: values }); -} - -export async function deleteAdmin(id: string) { - return api(`/admin/${id}`, { method: 'DELETE' }); -} - -export async function deleteAdmins(ids: string[]) { - return api('/admins/delete', { method: 'POST', body: { ids } }); -} - -export default { - login, - logout, - currentAdmin, - listAdmins, - createAdmin, - getAdmin, - updateAdmin, - deleteAdmin, - deleteAdmins, -}; diff --git a/src/services/dashboard.ts b/src/services/dashboard.ts deleted file mode 100644 index 30e9a5f..0000000 --- a/src/services/dashboard.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Dashboard 服务:获取仪表盘汇总数据的 API 封装。 - * - * 对 LLM 的说明: - * - `getDashboard()` 直接调用 `/dashboard`,返回后端提供的汇总 JSON 数据。 - */ -import api from '../utils/request'; - -export async function getDashboard() { - return api('/dashboard'); -} - -export default { getDashboard }; diff --git a/src/services/menu.ts b/src/services/menu.ts deleted file mode 100644 index 21dd53e..0000000 --- a/src/services/menu.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Menu 服务:菜单相关的后端 API 封装。 - * - * 对 LLM 的说明: - * - 提供列表、创建、更新、删除、排序等基本操作的封装函数。 - * - 返回值与参数类型参见 `src/types/menu.ts` 中的 `MenuItem` 与 `MenuFormValues`。 - */ -import type { MenuFormValues, MenuItem } from '../types/menu'; -import api from '../utils/request'; - -export async function listMenus(): Promise { - return api('/menus'); -} - -export async function createMenu(values: MenuFormValues) { - return api('/menu', { method: 'POST', body: values }); -} - -export async function updateMenu( - id: number | string, - values: Partial, -) { - return api(`/menu/${id}`, { method: 'PATCH', body: values }); -} - -export async function deleteMenu(id: number | string) { - return api('/menu/delete', { method: 'POST', body: { id } }); -} - -export async function sortMenus(order: Array) { - return api('/menu/sort', { method: 'POST', body: { order } }); -} - -export default { listMenus, createMenu, updateMenu, deleteMenu, sortMenus }; diff --git a/src/services/user.ts b/src/services/user.ts deleted file mode 100644 index c62c97c..0000000 --- a/src/services/user.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * User 服务:用户相关的后端 API 封装。 - * - * 对 LLM 的说明: - * - 提供分页/列表查询、创建、查询单个、更新、删除、批量删除等操作。 - * - 返回类型建议参考 `src/types/user.ts` 中的 `User` 和 `PagedResponse`。 - */ -import type { PagedResponse, User } from '@/types/user'; -import api from '../utils/request'; - -export async function listUsers( - params?: Record, -): Promise> { - return api>('/users', { method: 'GET', params }); -} - -export async function createUser( - values: Record, -): Promise { - return api('/user', { method: 'POST', body: values }); -} - -export async function getUser(id: string): Promise { - return api(`/user/${id}`); -} - -export async function updateUser( - id: string, - values: Record, -): Promise { - return api(`/user/${id}`, { method: 'PATCH', body: values }); -} - -export async function deleteUser(id: string) { - return api(`/user/${id}`, { method: 'DELETE' }); -} - -export async function deleteUsers(ids: string[]) { - return api('/users/delete', { method: 'POST', body: { ids } }); -} - -export default { - listUsers, - createUser, - getUser, - updateUser, - deleteUser, - deleteUsers, -}; diff --git a/src/setupTests.ts b/src/setupTests.ts deleted file mode 100644 index 7b0828b..0000000 --- a/src/setupTests.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/src/store/useGlobalStore.ts b/src/store/useGlobalStore.ts deleted file mode 100644 index 1d0c8a5..0000000 --- a/src/store/useGlobalStore.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * 全局状态(Zustand)store - * - * 对 LLM 的说明: - * - 存储与 AI 配置相关的持久化配置(`aiConfig`、`activeProvider`)以及简单的认证状态(`isLoggedIn`、`currentAdmin`)。 - * - 提供对 AI 配置的读取/保存/重置接口,内部通过 `../pages/ai/aiConfig` 的方法与 `localStorage` 协同工作。 - * - 认证方法:`login(token?)` 会在 localStorage 中写入 `app-auth-token`,`logout()` 会清除该值并重置用户状态。 - * - * 对自动化/LLM:如果需要模拟登录,请在测试环境中写入 `app-auth-token`,并调用 `setCurrentUser` 以设置 `currentAdmin`。 - */ -import { create } from 'zustand'; - -import { Admin } from '@/types/admin'; -import { - DEFAULT_CONFIG, - loadAIConfig, - loadActiveProvider, - saveAIConfig, - saveActiveProvider, - type ConfigState, - type ProviderKey, -} from '../pages/ai/aiConfig'; - -export type GlobalState = { - aiConfig: ConfigState; - activeProvider: ProviderKey; - setAiConfig: (config: ConfigState) => void; - setActiveProvider: (provider: ProviderKey) => void; - resetProvider: (provider: ProviderKey) => void; - // auth - isLoggedIn: boolean; - currentAdmin?: Admin; - setCurrentUser: (u?: Admin | undefined) => void; - login: (token?: string) => void; - logout: () => void; -}; - -const useGlobalStore = create((set, get) => ({ - aiConfig: loadAIConfig(), - activeProvider: loadActiveProvider(), - isLoggedIn: Boolean(localStorage.getItem('app-auth-token')), - currentAdmin: undefined, - - setAiConfig(config: ConfigState) { - set({ aiConfig: config }); - saveAIConfig(config); - }, - - setActiveProvider(provider: ProviderKey) { - set({ activeProvider: provider }); - saveActiveProvider(provider); - }, - - resetProvider(provider: ProviderKey) { - const next = { - ...get().aiConfig, - [provider]: DEFAULT_CONFIG[provider], - } as ConfigState; - set({ aiConfig: next }); - saveAIConfig(next); - }, - // auth methods - login(token?: string) { - const t = token || 'token-' + Date.now(); - - localStorage.setItem('app-auth-token', t); - set({ isLoggedIn: true }); - }, - setCurrentUser(u?: Admin | undefined) { - set({ currentAdmin: u }); - }, - logout() { - localStorage.removeItem('app-auth-token'); - set({ isLoggedIn: false, currentAdmin: undefined }); - }, -})); - -export default useGlobalStore; diff --git a/src/types/admin.ts b/src/types/admin.ts deleted file mode 100644 index deec2e8..0000000 --- a/src/types/admin.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type Admin = { - id: number; - username?: string; - real_name?: string; - nick_name?: string; - mobile?: string; - avatar?: string; - department?: string; - role?: string; - status?: number; - permissions?: string[] | string; - remark?: string; -}; - -/** 现已约定后端统一返回 data 字段,调用方直接接收 data */ -export type AdminResponse = Admin | undefined; diff --git a/src/types/menu.ts b/src/types/menu.ts deleted file mode 100644 index 581ae6e..0000000 --- a/src/types/menu.ts +++ /dev/null @@ -1,25 +0,0 @@ -export type MenuStatus = 0 | 1; - -export type MenuItem = { - id: number; - title: string; - icon?: string; - route: string; - status: MenuStatus; - parentId: number | null; - privilege: string[]; - remark?: string; - sort: number; - children?: MenuItem[]; -}; - -export type MenuFormValues = { - title: string; - icon?: string; - route: string; - status: MenuStatus; - parentId: number | null; - privilege: string[]; - remark?: string; - sort: number; -}; diff --git a/src/types/user.ts b/src/types/user.ts deleted file mode 100644 index 7763e84..0000000 --- a/src/types/user.ts +++ /dev/null @@ -1,19 +0,0 @@ -export type User = { - id: number | string; - name?: string; - nickName?: string; - phone?: string; - age?: number; - bio?: string; - email?: string; - address?: string; - isMale?: boolean; - createTime?: string; - lastLogin?: string; - status?: 'active' | 'inactive'; -}; - -export type PagedResponse = { - data: T[]; - total?: number; -}; diff --git a/src/utils/config.ts b/src/utils/config.ts deleted file mode 100644 index 70c944a..0000000 --- a/src/utils/config.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 全局配置 - * 说明给 LLM:用于生成请求时的默认 API 前缀和站点元信息。 - * - `apiPrefix`:API 路由前缀,供 `src/utils/request.ts` 使用。 - */ -export default { - siteName: 'Antd Admin', - copyright: 'Ant Design Admin ©2026 zuiidea', - logoPath: '/logo.svg', - apiPrefix: '/api/v1', -}; diff --git a/src/utils/request.test.ts b/src/utils/request.test.ts deleted file mode 100644 index 7c83a47..0000000 --- a/src/utils/request.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { message } from 'antd'; -import api from './request'; - -describe('api', () => { - beforeEach(() => { - jest.resetAllMocks(); - (global as any).fetch = jest.fn(); - localStorage.clear(); - }); - - test('successful response and query params', async () => { - (global.fetch as jest.Mock).mockResolvedValue({ - ok: true, - status: 200, - json: async () => ({ success: true, data: { id: 1 } }), - }); - const data = await api<{ id: number }>('/items', { params: { q: 'a' } }); - expect(data).toEqual({ id: 1 }); - expect(global.fetch).toBeCalledWith( - '/api/v1/items?q=a', - expect.any(Object), - ); - }); - - test('injects Authorization header from localStorage', async () => { - localStorage.setItem('app-auth-token', 'tok'); - let capturedReq: any; - (global.fetch as jest.Mock).mockImplementation((url, opts) => { - capturedReq = opts; - return Promise.resolve({ - ok: true, - status: 200, - json: async () => ({ success: true, data: null }), - }); - }); - await api('/me', {}); - expect(capturedReq.headers.Authorization).toBe('Bearer tok'); - }); - - test('network error shows message and throws', async () => { - const err = new Error('net fail'); - (global.fetch as jest.Mock).mockRejectedValue(err); - const spy = jest.spyOn(message, 'error') as jest.SpyInstance; - spy.mockImplementation(() => ({}) as any); - await expect(api('/x')).rejects.toThrow('net fail'); - expect(spy).toBeCalledWith('net fail'); - spy.mockRestore(); - }); - - test('invalid response format shows message and throws', async () => { - (global.fetch as jest.Mock).mockResolvedValue({ - ok: true, - status: 200, - json: async () => ({}), - }); - const spy = jest.spyOn(message, 'error') as jest.SpyInstance; - spy.mockImplementation(() => ({}) as any); - await expect(api('/x')).rejects.toThrow('Invalid API response format'); - expect(spy).toBeCalledWith('Invalid API response format'); - spy.mockRestore(); - }); -}); diff --git a/src/utils/request.ts b/src/utils/request.ts deleted file mode 100644 index 6f1c3e9..0000000 --- a/src/utils/request.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { message } from 'antd'; - -import config from './config'; - -/** 原始值类型 */ -export type Primitive = string | number | boolean; - -/** - * 请求选项 - * - `params` 会被序列化为查询字符串 - * - `body` 会在非 FormData 情况下 JSON 序列化并设置 Content-Type - */ -export type ApiOptions = Omit & { - params?: Record; - body?: unknown; -}; - -/** 约定的后端返回格式 */ -export type ApiResponse = { - success: boolean; - message?: string; - data: T; -}; - -/** - * 统一的 fetch 封装 - * - * 行为说明(对 LLM/开发者友好说明): - * - 将 `config.apiPrefix` 与 `path` 拼接为最终 URL。 - * - 当 `options.params` 存在时,转为查询字符串并添加到 URL 上。 - * - `body` 处理: - * - 如果是 FormData,直接交给 fetch 处理(不要手动设置 Content-Type)。 - * - 如果是字符串或 Blob,直接发送。 - * - 其他对象会被 JSON.stringify,并默认设置 `Content-Type: application/json`(除非已在 headers 中指定)。 - * - 当响应状态为 204 时返回空对象;否则尝试解析响应为 JSON。 - * - 当响应不 OK(res.ok === false)时抛出 Error,优先使用响应体的 `message` 字段作为错误信息。 - * - * 使用示例: - * ```ts - * const data = await api<{ id: number }>(`/items`, { method: 'POST', body: { name: 'a' } }); - * ``` - */ -export async function api( - path: string, - options: ApiOptions = {}, -): Promise { - const { params, body, headers: customHeaders, ...rest } = options; - - // 构建 url 并附加查询参数 - let url = `${config.apiPrefix}${path}`; - if (params && Object.keys(params).length) { - const qp: Record = {}; - Object.entries(params).forEach(([k, v]) => { - qp[k] = String(v); - }); - const qs = new URLSearchParams(qp).toString(); - url += url.includes('?') ? `&${qs}` : `?${qs}`; - } - - const headers: Record = { - ...((customHeaders as Record) || {}), - }; - let bodyToSend: BodyInit | undefined; - if (body !== undefined) { - // If body is FormData, let fetch handle headers. - if (typeof FormData !== 'undefined' && body instanceof FormData) { - bodyToSend = body as BodyInit; - } else if (typeof body === 'string' || body instanceof Blob) { - bodyToSend = body as BodyInit; - } else { - // For object payloads, default to JSON content-type - headers['Content-Type'] = headers['Content-Type'] || 'application/json'; - try { - bodyToSend = JSON.stringify(body); - } catch (e) { - bodyToSend = undefined; - } - } - } - - // inject auth token if present - try { - const token = - typeof window !== 'undefined' - ? localStorage.getItem('app-auth-token') - : null; - if (token && !headers.Authorization && !headers.authorization) { - headers['Authorization'] = `Bearer ${token}`; - } - } catch (e) { - // ignore storage access errors - } - - // perform request with network error handling - let res: Response; - try { - res = await fetch(url, { headers, body: bodyToSend, ...rest }); - } catch (err: any) { - const netMsg = err?.message || 'Network error'; - message.error(netMsg); - throw new Error(netMsg); - } - - const json = await res.json().catch(() => null); - - // 后端返回固定格式:{ success: boolean, message?: string, data: T } - if ( - !json || - typeof json !== 'object' || - typeof (json as any).success !== 'boolean' - ) { - const invalidMsg = 'Invalid API response format'; - message.error(invalidMsg); - throw new Error(invalidMsg); - } - const parsed = json as ApiResponse; - - if (!res.ok || !parsed.success) { - const msg = parsed.message || `Request failed: ${res.status}`; - message.error(msg); - throw new Error(msg); - } - - return parsed.data as T; -} - -export default api; diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 62a0c7c..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "./src/.umi/tsconfig.json", - "compilerOptions": { - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "esModuleInterop": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "jsx": "react-jsx" - } -} diff --git a/typings.d.ts b/typings.d.ts deleted file mode 100644 index 9ae2fc4..0000000 --- a/typings.d.ts +++ /dev/null @@ -1 +0,0 @@ -import 'umi/typings';