6.5 KiB
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 + Prettierpnpm lint:eslint --fix- Fix ESLint issuespnpm 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- Custom instance with workspace header support andneedWorkspaceparam. Returnsresponse.datadirectly.src/api/interceptor.ts- Global instance with Bearer token and code 20000 validation- Use
request.tsfor most ops APIs that need workspace header - Use
interceptor.tspattern for APIs expectingcode: 20000response format
API Response Format
Standard response from request.ts: {code: 0, details: {data: [...], total: number}}
- Success code is
0, NOT200or20000 - Access list data via
response.details.data - Access total count via
response.details.total
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 instead of localStorage directly. Supports TTL expiry and type-safe keys via AppStorageKey enum.
// 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() with permission guard in permission.ts. Uses flags isMenuLoading/isMenuLoaded to prevent duplicate loads.
- Do NOT manually modify
isMenuLoading/isMenuLoadedflags - Routes are registered dynamically via
router.addRoute() - Menu data comes from
userPmnAPI and transformed viabuildTree+transformMenuToRoutes
useRequest Hook Limitation
useRequest() does NOT work in async functions - it immediately invokes the API. Use .bind(null, params) to pass arguments.
// 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
<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
/** 服务器类型 */ 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- Composite search + table componentSearchForm- Search form with dynamic fieldsDataTable- Enhanced table with toolbarChart- ECharts wrapper component
Import Order
- Vue core (ref, reactive, computed, watch, onMounted)
- Third-party (Message, Modal, icons from Arco Design)
- Global components (SearchTable, DataTable)
- Local components (FormDialog, Detail)
- Config files (columns, searchForm)
- API functions (fetchList, createItem)
- Type interfaces (Item, ListParams)
API Response Handling
request.tsreturnsresponse.datadirectly (no.data.datanesting)- Success code:
0(NOT200or20000) - Access data:
response.details.data - Access total:
response.details.total interceptor.tsexpectscode: 20000for 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/isMenuLoadedflags if routes not appearing - Network tab for API debugging
- Vue DevTools for component state inspection
- Run
pnpm lintafter changes to catch issues