190 lines
6.5 KiB
Markdown
190 lines
6.5 KiB
Markdown
# AGENTS.md
|
|
|
|
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. 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.
|
|
|
|
## 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
|