diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..514f5a6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,201 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a multi-tenant service management platform ("百业到家") built with Ant Design Pro, React 19, TypeScript, and UmiJS Max framework. The application uses dynamic routing loaded from a backend API and implements a comprehensive permission system. + +## Development Commands + +```bash +# Start development server (with mocks) +pnpm dev +# or +pnpm start + +# Start development server (without mocks, connects to backend) +pnpm start:dev + +# Build for production +pnpm build + +# Lint (Biome + TypeScript check) +pnpm lint + +# Run tests +pnpm test + +# Run tests with coverage +pnpm test:coverage + +# Type check only +pnpm tsc +``` + +**Important**: This project uses **pnpm** as the package manager, not npm or yarn. + +## Architecture + +### Dynamic Routing System + +Routes are **not** statically defined in `config/routes.ts` (most routes are commented out). Instead, routes are dynamically loaded from the backend API: + +1. On app initialization, `getInfo()` fetches user permissions and menu structure +2. `patchClientRoutes()` in `src/app.tsx` transforms backend menu data into UmiJS routes +3. `loopMenuItem()` in `src/utils/menuUtils.tsx` recursively converts menu items to route objects +4. Routes are lazy-loaded using `React.lazy(() => import('@/pages/${component}'))` + +**When adding new pages**: Create the page component in `src/pages/`, then configure the route in the backend system (not in code). + +### Multi-Tenant Architecture + +Every API request includes a `tenant-id` header. Tenant information is: +- Stored in web-storage-cache with key `CACHE_KEY.TenantId` +- Set during login via `setTenantId()` +- Automatically added to all requests by the request interceptor in `src/app.tsx` + +### Authentication Flow + +1. User logs in with tenant name, username, and password (password is RSA encrypted via JSEncrypt) +2. Backend returns `accessToken` and `refreshToken` +3. Tokens stored in web-storage-cache (see `src/utils/auth.ts`) +4. All requests include `Authorization: Bearer ${accessToken}` header +5. On 401 response, automatically attempts token refresh using `refreshToken` +6. If refresh fails, redirects to login page + +**Token refresh logic** is in `src/requestErrorConfig.ts` with request queuing to prevent duplicate refresh attempts. + +### Request Interceptors + +All API requests are automatically modified (see `src/app.tsx`): +- **API prefix**: `/admin-api` is prepended to all URLs (unless already present) +- **Headers**: `Authorization`, `tenant-id`, and `Accept` headers are added +- **Parameter serialization**: Arrays are serialized specially: + - `createTime` arrays become `createTime[0]` and `createTime[1]` + - Other arrays become `key[]` format + +### Caching Strategy + +Uses `web-storage-cache` library (see `src/hooks/web/useCache.ts`): +- `CACHE_KEY.USER`: Current user info +- `CACHE_KEY.ROLE_ROUTERS`: Dynamic menu/route data +- `CACHE_KEY.ACCESS_TOKEN` / `CACHE_KEY.REFRESH_TOKEN`: Auth tokens +- `CACHE_KEY.DICT_CACHE`: Dictionary/enum data loaded on app init +- `CACHE_KEY.LoginForm`: Remember me functionality (password encrypted) + +### State Management + +- **UmiJS Model Plugin**: For global state (see `src/app.tsx` - `initialState`) +- **Custom Hooks**: In `src/hooks/stores/` (e.g., `useDictStore` for dictionary management) +- **Web Storage Cache**: For persistent data across sessions + +## Project Structure + +``` +src/ +├── pages/ # Feature pages organized by domain +│ ├── ai/ # AI model and sample management +│ ├── prod/ # Product and category management +│ ├── system/ # System admin (users, roles, menus, tenants, logs, messages) +│ ├── trade/ # Order and sales management +│ └── user/ # Login page +├── services/ # API service layer (mirrors page structure) +├── components/ # Reusable components +│ ├── Draggable/ # Drag-and-drop components using @dnd-kit +│ ├── Upload/ # File/image/video upload components +│ ├── GroupTag/ # Tag management components +│ └── Tinymce/ # Rich text editor wrapper +├── hooks/ # Custom React hooks +│ ├── antd/ # Ant Design related hooks +│ ├── stores/ # State management hooks +│ └── web/ # Web utilities (cache, etc.) +├── utils/ # Utility functions +│ ├── auth.ts # Token and tenant management +│ ├── menuUtils.tsx # Dynamic route conversion +│ ├── dict.ts # Dictionary/enum utilities +│ └── tree.ts # Tree data structure helpers +├── constants/ # Shared constants and enums +├── locales/ # i18n translations (zh-CN default) +└── app.tsx # App initialization and runtime config +``` + +## Environment Configuration + +Multiple environment files: +- `.env.local` - Local overrides (gitignored) +- `.env.dev` - Development environment +- `.env.prod` - Production environment + +**Proxy configuration** in `config/proxy.ts`: +- `dev`: Points to local backend (currently `http://192.168.1.89:48080`) +- `test`: Points to test environment +- `pre`: Points to pre-production environment + +Switch environments using `UMI_ENV`: +```bash +cross-env UMI_ENV=dev pnpm start # Development +cross-env UMI_ENV=test pnpm start # Test +cross-env UMI_ENV=pre pnpm start # Pre-production +``` + +## Code Conventions + +### Linting +- Uses **Biome** (not ESLint) for linting and formatting +- Configured in `biome.json` +- Pre-commit hook runs Biome via lint-staged + +### Commit Messages +- Uses commitlint with conventional commits +- Enforced via Husky pre-commit hook +- Format: `type(scope): subject` (e.g., `feat: add user management`) + +### Component Patterns +- **ProTable**: Use `EnhancedProTable` wrapper for common table functionality +- **Forms**: Use `DrawerForm` or `ModalForm` from `@ant-design/pro-components` +- **File Uploads**: Use custom `UploadImages` or `UploadVideo` components in `src/components/Upload/` +- **Rich Text**: Use `Tinymce` component wrapper (TinyMCE is in `public/tinymce/`) + +### Service Layer +- All API calls go through `src/services/` +- Use UmiJS `request` from `@umijs/max` (configured in `src/app.tsx`) +- Response format: `{ code: number, data: any, msg: string }` +- Success when `code === 0` + +## Common Patterns + +### Adding a New Page + +1. Create page component in `src/pages/{domain}/{feature}/index.tsx` +2. Create corresponding service in `src/services/{domain}/{feature}/index.ts` +3. Configure the menu/route in the backend system (not in code) +4. The route will automatically appear after backend configuration + +### Working with Dictionaries/Enums + +Dictionaries are loaded on app init and cached: +```typescript +import { useDictStore } from '@/hooks/stores/dict'; + +const dictStore = useDictStore(); +const dictData = dictStore.getDictByType('dict_type_key'); +``` + +### File Uploads + +Media files are uploaded via `src/services/infra/file/index.ts` or `src/services/infra/media/index.ts`. Use the Upload components which handle the API integration. + +## Testing + +- Jest configured with `jest.config.ts` +- Test files should be colocated with components or in `__tests__` directories +- Run single test: `pnpm test -- path/to/test.spec.ts` + +## Important Notes + +- **Node version**: Requires Node.js >= 20.0.0 (see `package.json` engines) +- **React 19**: Uses React 19 with `@ant-design/v5-patch-for-react-19` compatibility patch +- **Mako bundler**: Uses UmiJS Mako for faster builds (configured in `config/config.ts`) +- **Mock data**: Located in `mock/` directory, enabled by default in dev mode +- **TinyMCE**: Static files in `public/tinymce/`, Chinese language pack included diff --git a/auto-deploy.ps1 b/auto-deploy.ps1 new file mode 100644 index 0000000..f0879d1 --- /dev/null +++ b/auto-deploy.ps1 @@ -0,0 +1,30 @@ +# Auto Deploy Script for PowerShell +$SERVER = "43.139.42.137" +$USER = "root" +$REMOTE_PATH = "/home/1panel/www/sites/admin.petshy.tashowz.com/index" + +Write-Host "=== Auto Deploy ===" -ForegroundColor Cyan +Write-Host "" + +# 1. Build project +Write-Host "Building project..." -ForegroundColor Yellow +pnpm build +if ($LASTEXITCODE -ne 0) { + Write-Host "Build failed" -ForegroundColor Red + exit 1 +} +Write-Host "Build complete" -ForegroundColor Green +Write-Host "" + +# 2. Upload files +Write-Host "Uploading files to server..." -ForegroundColor Yellow +scp -r dist/* ${USER}@${SERVER}:${REMOTE_PATH}/ + +if ($LASTEXITCODE -eq 0) { + Write-Host "" + Write-Host "Deploy complete!" -ForegroundColor Green + Write-Host "Visit: http://admin.petshy.tashowz.com" -ForegroundColor Cyan +} else { + Write-Host "Upload failed" -ForegroundColor Red + exit 1 +} diff --git a/src/pages/user/login/index.tsx b/src/pages/user/login/index.tsx index ae2927e..837fd53 100644 --- a/src/pages/user/login/index.tsx +++ b/src/pages/user/login/index.tsx @@ -143,7 +143,7 @@ const Page = () => { fontSize: 14, }} > - 其他登录方式 + 其他登录方式111