Files
front/AGENTS.md
2026-04-05 16:14:23 +08:00

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 + 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 - Custom instance with workspace header support and needWorkspace param. Returns response.data directly.
  • 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
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/isMenuLoaded flags
  • Routes are registered dynamically via router.addRoute()
  • Menu data comes from userPmn API and transformed via buildTree + 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

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