feat
This commit is contained in:
157
AGENTS.md
157
AGENTS.md
@@ -3,34 +3,187 @@
|
||||
This file provides guidance to agents when working with code in this repository.
|
||||
|
||||
## Build Commands
|
||||
|
||||
- `pnpm dev` - Start dev server (config: `config/vite.config.dev.ts`)
|
||||
- `pnpm build` - Production build (config: `config/vite.config.prod.ts`)
|
||||
- `pnpm lint` - Run ESLint + Prettier
|
||||
- `pnpm lint:eslint --fix` - Fix ESLint issues
|
||||
- `pnpm lint:prettier --write` - Fix Prettier formatting
|
||||
- No test framework configured
|
||||
|
||||
## Critical Architecture Notes
|
||||
|
||||
### Vite Config Location
|
||||
|
||||
Config files are in `config/` directory, NOT root. All vite commands reference `./config/vite.config.*.ts`.
|
||||
|
||||
### Two Axios Instances
|
||||
- [`src/api/request.ts`](src/api/request.ts) - Custom instance with workspace header support and `needWorkspace` param
|
||||
|
||||
- [`src/api/request.ts`](src/api/request.ts) - Custom instance with workspace header support and `needWorkspace` param. Returns `response.data` directly.
|
||||
- [`src/api/interceptor.ts`](src/api/interceptor.ts) - Global instance with Bearer token and code 20000 validation
|
||||
- Use `request.ts` for most ops APIs that need workspace header
|
||||
- Use `interceptor.ts` pattern for APIs expecting `code: 20000` response format
|
||||
|
||||
### API Response Format
|
||||
|
||||
Standard response from `request.ts`: `{code: 0, details: {data: [...], total: number}}`
|
||||
|
||||
- Success code is `0`, NOT `200` or `20000`
|
||||
- Access list data via `response.details.data`
|
||||
- Access total count via `response.details.total`
|
||||
|
||||
```ts
|
||||
const response = await fetchList(params)
|
||||
if (response && response.code === 0 && response.details) {
|
||||
tableData.value = response.details.data || []
|
||||
pagination.total = response.details.total || 0
|
||||
}
|
||||
```
|
||||
|
||||
### Storage Pattern
|
||||
|
||||
Use [`SafeStorage`](src/utils/safeStorage.ts) instead of localStorage directly. Supports TTL expiry and type-safe keys via `AppStorageKey` enum.
|
||||
|
||||
```ts
|
||||
// Set with optional TTL (milliseconds)
|
||||
SafeStorage.set(AppStorageKey.TOKEN, token, 3600000)
|
||||
// Get
|
||||
SafeStorage.get(AppStorageKey.USER_INFO)
|
||||
// Clear all app storage
|
||||
SafeStorage.clearAppStorage()
|
||||
```
|
||||
|
||||
### Dynamic Route Loading
|
||||
|
||||
Routes are loaded from server via [`fetchServerMenuConfig()`](src/store/modules/app/index.ts) with permission guard in [`permission.ts`](src/router/guard/permission.ts). Uses flags `isMenuLoading`/`isMenuLoaded` to prevent duplicate loads.
|
||||
|
||||
- Do NOT manually modify `isMenuLoading`/`isMenuLoaded` flags
|
||||
- Routes are registered dynamically via `router.addRoute()`
|
||||
- Menu data comes from `userPmn` API and transformed via `buildTree` + `transformMenuToRoutes`
|
||||
|
||||
### useRequest Hook Limitation
|
||||
|
||||
[`useRequest()`](src/hooks/request.ts) does NOT work in async functions - it immediately invokes the API. Use `.bind(null, params)` to pass arguments.
|
||||
|
||||
```ts
|
||||
// CORRECT - Use in setup scope with bind
|
||||
const { loading, response } = useRequest(fetchList.bind(null, { page: 1 }))
|
||||
// WRONG - Use in async function
|
||||
async function loadData() {
|
||||
const { loading, response } = useRequest(fetchList()) // Won't work!
|
||||
}
|
||||
```
|
||||
|
||||
## Standard Module Structure
|
||||
|
||||
### Page Module Pattern
|
||||
|
||||
```
|
||||
src/views/ops/pages/{category}/{module}/
|
||||
├── index.vue # Main page component (SearchTable)
|
||||
├── components/
|
||||
│ ├── FormDialog.vue # Create/Edit form dialog
|
||||
│ ├── Detail.vue # Detail drawer/view
|
||||
│ └── QuickConfigDialog.vue # Optional quick config dialog
|
||||
└── config/
|
||||
├── columns.ts # Table columns configuration
|
||||
└── search-form.ts # Search form configuration
|
||||
```
|
||||
|
||||
### API Module Pattern
|
||||
|
||||
```
|
||||
src/api/ops/{module}.ts
|
||||
├── {Module}Item # Single item interface
|
||||
├── {Module}ListResponse # List response interface
|
||||
├── {Module}ListParams # Query parameters interface
|
||||
├── {Module}CreateData # Create payload interface
|
||||
├── {Module}UpdateData # Update payload interface
|
||||
├── fetch{Module}List() # GET list with pagination
|
||||
├── fetch{Module}Detail() # GET single item
|
||||
├── create{Module}() # POST create
|
||||
├── update{Module}() # PUT update
|
||||
├── patch{Module}() # PATCH partial update
|
||||
└── delete{Module}() # DELETE remove
|
||||
```
|
||||
|
||||
### Component Template Pattern
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="container">
|
||||
<!-- SearchTable with slots -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// Import order: Vue → Third-party → Components → Configs → APIs → Types
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import FormDialog from './components/FormDialog.vue'
|
||||
import { columns } from './config/columns'
|
||||
import { fetchList } from '@/api/ops/module'
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'ModuleName' }
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
- No semicolons (Prettier enforced)
|
||||
- Single quotes, trailing commas (es5)
|
||||
- Print width: 140 characters
|
||||
- Path alias: `@/` → `src/`
|
||||
- **Add JSDoc comments** for interfaces, types, and functions
|
||||
|
||||
```ts
|
||||
/** 服务器类型 */
|
||||
export interface ServerItem { ... }
|
||||
|
||||
/** 获取服务器列表(分页) */
|
||||
export const fetchServerList = (params?: ServerListParams) => { ... }
|
||||
```
|
||||
|
||||
## Vue/i18n Aliases Required
|
||||
Vite config includes aliases for `vue-i18n/dist/vue-i18n.cjs.js` and `vue/dist/vue.esm-bundler.js` - don't remove these.
|
||||
|
||||
Vite config includes aliases for `vue-i18n/dist/vue-i18n.cjs.js` and `vue/dist/vue.esm-bundler.js` - don't remove these.
|
||||
|
||||
## Key Components
|
||||
|
||||
- [`SearchTable`](src/components/search-table/index.vue) - Composite search + table component
|
||||
- [`SearchForm`](src/components/search-form/index.vue) - Search form with dynamic fields
|
||||
- [`DataTable`](src/components/data-table/index.vue) - Enhanced table with toolbar
|
||||
- [`Chart`](src/components/chart/index.vue) - ECharts wrapper component
|
||||
|
||||
## Import Order
|
||||
|
||||
1. Vue core (ref, reactive, computed, watch, onMounted)
|
||||
2. Third-party (Message, Modal, icons from Arco Design)
|
||||
3. Global components (SearchTable, DataTable)
|
||||
4. Local components (FormDialog, Detail)
|
||||
5. Config files (columns, searchForm)
|
||||
6. API functions (fetchList, createItem)
|
||||
7. Type interfaces (Item, ListParams)
|
||||
|
||||
## API Response Handling
|
||||
|
||||
- `request.ts` returns `response.data` directly (no `.data.data` nesting)
|
||||
- Success code: `0` (NOT `200` or `20000`)
|
||||
- Access data: `response.details.data`
|
||||
- Access total: `response.details.total`
|
||||
- `interceptor.ts` expects `code: 20000` for success
|
||||
- Token expiry codes: 50008, 50012, 50014 trigger logout (interceptor.ts)
|
||||
- Status 401 or error "Token has expired" redirects to `/auth/login` (request.ts)
|
||||
|
||||
## Debug Tips
|
||||
|
||||
- Console logs prefixed with `[Permission Guard]` for route loading issues
|
||||
- Check `isMenuLoading`/`isMenuLoaded` flags if routes not appearing
|
||||
- Network tab for API debugging
|
||||
- Vue DevTools for component state inspection
|
||||
- Run `pnpm lint` after changes to catch issues
|
||||
|
||||
Reference in New Issue
Block a user