feat
This commit is contained in:
133
.kilo/README.md
Normal file
133
.kilo/README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Kilo Configuration for Vue Admin Project
|
||||
|
||||
This directory contains the Kilo AI assistant configuration for this Vue 3 + Arco Design admin project.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
.kilo/
|
||||
├── agent/ # Agent mode rules
|
||||
│ ├── rules-code.md # Code writing rules
|
||||
│ ├── rules-architect.md # Architecture rules
|
||||
│ ├── rules-debug.md # Debugging rules
|
||||
│ └── rules-ask.md # Q&A rules
|
||||
├── command/ # Slash commands
|
||||
│ ├── new-page.md # Create new page module
|
||||
│ ├── new-api.md # Create new API module
|
||||
│ ├── new-component.md # Create new component
|
||||
│ ├── lint-fix.md # Fix lint issues
|
||||
│ └── type-check.md # Check TypeScript types
|
||||
├── skill/ # Skill definitions
|
||||
│ └── vue-admin-dev.md # Vue admin development skill
|
||||
├── templates/ # Code templates
|
||||
│ ├── api-module.ts # API module template
|
||||
│ ├── columns.ts # Table columns template
|
||||
│ ├── search-form.ts # Search form template
|
||||
│ ├── index.vue # Main page template
|
||||
│ ├── FormDialog.vue # Form dialog template
|
||||
│ └── Detail.vue # Detail view template
|
||||
├── logs/ # Log files
|
||||
└── node_modules/ # Dependencies
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### kilo.json
|
||||
|
||||
Main configuration file with:
|
||||
|
||||
- Agent settings (default mode, allowed modes)
|
||||
- Command directory path
|
||||
- Rules directory path
|
||||
- Skills directory path
|
||||
- Hooks (beforeFileWrite, afterTaskComplete)
|
||||
- Context settings (maxTokens, include/exclude patterns)
|
||||
- Logging configuration
|
||||
|
||||
### AGENTS.md
|
||||
|
||||
Project-level guidance file in root directory with:
|
||||
|
||||
- Build commands
|
||||
- Architecture notes
|
||||
- Module structure patterns
|
||||
- Code style requirements
|
||||
- Import order
|
||||
- Debug tips
|
||||
|
||||
## Usage
|
||||
|
||||
### Commands
|
||||
|
||||
```
|
||||
/new-page <module-name> <category> # Create new page module
|
||||
/new-api <module-name> [base-path] # Create new API module
|
||||
/new-component <name> [type] # Create new component
|
||||
/lint-fix [path] # Fix lint issues
|
||||
/type-check # Check TypeScript types
|
||||
```
|
||||
|
||||
### Skills
|
||||
|
||||
Load the vue-admin-dev skill for comprehensive development guidance:
|
||||
|
||||
```
|
||||
/skill vue-admin-dev
|
||||
```
|
||||
|
||||
## Key Rules
|
||||
|
||||
### Storage
|
||||
|
||||
Always use `SafeStorage` with `AppStorageKey` enum - never use localStorage directly.
|
||||
|
||||
### API Choice
|
||||
|
||||
- Use `request.ts` for workspace-aware APIs (most ops APIs)
|
||||
- Use `interceptor.ts` for Bearer token APIs expecting `code: 20000`
|
||||
|
||||
### API Response Format
|
||||
|
||||
Standard response from `request.ts`: `{code: 0, details: {data: [...], total: number}}`
|
||||
|
||||
- Success code is `0`, NOT `200` or `20000`
|
||||
- Access data via `response.details.data`
|
||||
- Access total via `response.details.total`
|
||||
|
||||
### Comments
|
||||
|
||||
Add JSDoc comments for interfaces and functions:
|
||||
|
||||
```ts
|
||||
/** 服务器类型 */
|
||||
export interface ServerItem { ... }
|
||||
|
||||
/** 获取服务器列表(分页) */
|
||||
export const fetchServerList = (params?: ServerListParams) => { ... }
|
||||
```
|
||||
|
||||
### useRequest Hook
|
||||
|
||||
Does NOT work in async functions. Use `.bind(null, params)` pattern in setup scope.
|
||||
|
||||
### Dynamic Routes
|
||||
|
||||
Routes loaded from server via permission guard. Don't modify `isMenuLoading`/`isMenuLoaded` flags.
|
||||
|
||||
### Code Style
|
||||
|
||||
- No semicolons
|
||||
- Single quotes
|
||||
- Print width: 140 characters
|
||||
- JSDoc comments for types and functions
|
||||
|
||||
## Templates
|
||||
|
||||
Templates use placeholder syntax:
|
||||
|
||||
- `{{Module}}` - PascalCase module name (e.g., `UrlDevice`)
|
||||
- `{{module}}` - lowercase module name (e.g., `url-device`)
|
||||
- `{{ModuleTitle}}` - Chinese title (e.g., `URL监控设备`)
|
||||
- `{{ModuleName}}` - Vue component name (e.g., `UrlDeviceManagement`)
|
||||
|
||||
Replace these placeholders when generating new modules.
|
||||
68
.kilo/agent/rules-architect.md
Normal file
68
.kilo/agent/rules-architect.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Architect Mode Rules (Non-Obvious Only)
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
- Vue 3 SPA with dynamic route loading from server
|
||||
- Pinia stores: `app`, `user`, `tab-bar` (see [`src/store/`](src/store/))
|
||||
- Two-layer route guard: user login check + permission check
|
||||
|
||||
## Dynamic Route System
|
||||
|
||||
- Server menu fetched in [`app store`](src/store/modules/app/index.ts) via `fetchServerMenuConfig()`
|
||||
- Routes registered dynamically in [`permission.ts`](src/router/guard/permission.ts)
|
||||
- Uses `isMenuLoading`/`isMenuLoaded` flags to prevent duplicate loads
|
||||
|
||||
## API Architecture
|
||||
|
||||
- Two axios instances for different auth patterns:
|
||||
- [`request.ts`](src/api/request.ts): Workspace header + custom token format
|
||||
- [`interceptor.ts`](src/api/interceptor.ts): Bearer token + code 20000 validation
|
||||
- Choose based on backend API requirements
|
||||
|
||||
## API Response Format
|
||||
|
||||
- Standard response structure: `{code: 0, details: {data: [...], total: number}}`
|
||||
- Success code is `0`, NOT `200` or `20000`
|
||||
- `request.ts` returns `response.data` directly
|
||||
- Access list data via `response.details.data`
|
||||
- Access total count via `response.details.total`
|
||||
|
||||
## State Management
|
||||
|
||||
- [`SafeStorage`](src/utils/safeStorage.ts) required for all localStorage access
|
||||
- Supports TTL expiry and type-safe keys via enum
|
||||
|
||||
## Component Patterns
|
||||
|
||||
- Global components registered in [`components/index.ts`](src/components/index.ts)
|
||||
- ECharts modules manually imported for bundle size optimization
|
||||
- Use `SearchTable` composite component for list pages
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/
|
||||
api/ # API layer (ops/ for business, module/ for auth)
|
||||
components/ # Global reusable components
|
||||
hooks/ # Composition functions
|
||||
router/ # Route config and guards
|
||||
store/ # Pinia stores
|
||||
utils/ # Utility functions
|
||||
views/ops/ # Main business modules
|
||||
pages/
|
||||
dc/ # Data center management
|
||||
monitor/ # Monitoring modules
|
||||
netarch/ # Network architecture
|
||||
report/ # Reports
|
||||
system-settings/ # System config
|
||||
```
|
||||
|
||||
## Module Creation Checklist
|
||||
|
||||
1. Create API file in `src/api/ops/`
|
||||
2. Create page directory in `src/views/ops/pages/`
|
||||
3. Create `config/columns.ts` for table config
|
||||
4. Create `config/search-form.ts` for search config
|
||||
5. Create main `index.vue` with SearchTable
|
||||
6. Create `components/FormDialog.vue` for CRUD
|
||||
7. Create `components/Detail.vue` for detail view
|
||||
47
.kilo/agent/rules-ask.md
Normal file
47
.kilo/agent/rules-ask.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Ask Mode Rules (Non-Obvious Only)
|
||||
|
||||
## Project Structure
|
||||
|
||||
- Vue 3 + Arco Design admin template with Pinia state management
|
||||
- Vite config files located in `config/` directory (not root)
|
||||
|
||||
## Key Directories
|
||||
|
||||
- `src/api/` - API layer with two axios instances
|
||||
- `src/views/ops/` - Main business modules (kb, netarch, asset, etc.)
|
||||
- `src/router/guard/` - Route guards including dynamic menu loading
|
||||
- `src/store/modules/app/` - App store with server menu fetching
|
||||
|
||||
## Documentation References
|
||||
|
||||
- Arco Design Vue: https://arco.design/vue
|
||||
- Vue Flow (for topology): https://vueflow.dev/
|
||||
|
||||
## API Patterns
|
||||
|
||||
- Use [`request.ts`](src/api/request.ts) for workspace-aware requests
|
||||
- Use [`interceptor.ts`](src/api/interceptor.ts) for standard Bearer token auth
|
||||
|
||||
## API Response Format
|
||||
|
||||
- Standard response: `{code: 0, details: {...}}`
|
||||
- Success code is `0`, NOT `200` or `20000`
|
||||
- Access data via `response.details.data`
|
||||
|
||||
## Component Libraries
|
||||
|
||||
- Arco Design Vue components: `<a-button>`, `<a-table>`, `<a-form>`, etc.
|
||||
- Global components: `SearchTable`, `SearchForm`, `DataTable`, `Chart`
|
||||
|
||||
## Code Style
|
||||
|
||||
- No semicolons
|
||||
- Single quotes
|
||||
- Print width: 140 characters
|
||||
- Path alias: `@/` → `src/`
|
||||
|
||||
## Build Commands
|
||||
|
||||
- `pnpm dev` - Start dev server
|
||||
- `pnpm build` - Production build
|
||||
- `pnpm lint` - Run ESLint + Prettier
|
||||
85
.kilo/agent/rules-code.md
Normal file
85
.kilo/agent/rules-code.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Code Mode Rules (Non-Obvious Only)
|
||||
|
||||
## API Layer
|
||||
|
||||
- Two axios instances exist: [`request.ts`](src/api/request.ts) (custom with workspace header) and [`interceptor.ts`](src/api/interceptor.ts) (global with Bearer token). Choose based on whether you need workspace support.
|
||||
- [`request.ts`](src/api/request.ts) returns `response.data` directly (no nesting)
|
||||
|
||||
## Storage
|
||||
|
||||
- Always use [`SafeStorage`](src/utils/safeStorage.ts) with `AppStorageKey` enum - never use localStorage directly. Supports TTL via third parameter.
|
||||
|
||||
## useRequest Hook
|
||||
|
||||
- [`useRequest()`](src/hooks/request.ts) invokes API immediately - does NOT work in async functions. Pass params via `.bind(null, params)` pattern.
|
||||
|
||||
## Dynamic Routes
|
||||
|
||||
- Routes loaded from server in [`permission.ts`](src/router/guard/permission.ts) using `isMenuLoading`/`isMenuLoaded` flags. Don't modify these flags manually.
|
||||
|
||||
## Vue Component Structure
|
||||
|
||||
- Use `<script lang="ts" setup>` for main logic
|
||||
- Add `<script lang="ts">` block with `export default { name: 'ComponentName' }` for component naming
|
||||
- Style: `<style scoped lang="less">`
|
||||
|
||||
## Page Module Pattern
|
||||
|
||||
```
|
||||
src/views/ops/pages/{module}/
|
||||
index.vue # Main page component
|
||||
components/
|
||||
FormDialog.vue # CRUD form dialog
|
||||
Detail.vue # Detail drawer/view
|
||||
QuickConfigDialog.vue # Quick config dialog (optional)
|
||||
config/
|
||||
columns.ts # Table columns config
|
||||
search-form.ts # Search form config
|
||||
```
|
||||
|
||||
## API File Pattern
|
||||
|
||||
```
|
||||
src/api/ops/{module}.ts
|
||||
- Export interfaces for types
|
||||
- Export fetch functions using `request.get/post/put/patch/delete`
|
||||
- Name functions: `fetch{Resource}List`, `fetch{Resource}Detail`, `create{Resource}`, `update{Resource}`, `delete{Resource}`
|
||||
```
|
||||
|
||||
## Import Order
|
||||
|
||||
1. Vue core (ref, reactive, computed, etc.)
|
||||
2. Third-party (arco-design, axios, etc.)
|
||||
3. Local components
|
||||
4. Local configs
|
||||
5. Local APIs
|
||||
6. Types
|
||||
|
||||
## Comments (JSDoc Style)
|
||||
|
||||
- Add JSDoc comments for interfaces, types, and functions
|
||||
- Interface comment format: `/** {{TypeName}} */`
|
||||
- Function comment format: `/** {{Function description}} */`
|
||||
- Example:
|
||||
|
||||
```ts
|
||||
/** 服务器类型 */
|
||||
export interface ServerItem { ... }
|
||||
|
||||
/** 获取服务器列表(分页) */
|
||||
export const fetchServerList = (params?: ServerListParams) => { ... }
|
||||
```
|
||||
|
||||
## API Response Format
|
||||
|
||||
- Standard response: `{code: 0, details: {data: [...], total: number}}`
|
||||
- Success code is `0`, NOT `200` or `20000`
|
||||
- Access data via `response.details.data` and total via `response.details.total`
|
||||
|
||||
## Code Style (Prettier)
|
||||
|
||||
- No semicolons
|
||||
- Single quotes
|
||||
- Trailing commas (es5)
|
||||
- Print width: 140 characters
|
||||
- Path alias: `@/` → `src/`
|
||||
60
.kilo/agent/rules-debug.md
Normal file
60
.kilo/agent/rules-debug.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Debug Mode Rules (Non-Obvious Only)
|
||||
|
||||
## API Response Codes
|
||||
|
||||
- [`request.ts`](src/api/request.ts) returns `response.data` directly, expects `{code: 0, details: ...}`
|
||||
- [`interceptor.ts`](src/api/interceptor.ts) expects `code: 20000` for success
|
||||
- Token expiry codes: 50008, 50012, 50014 trigger logout modal (interceptor.ts)
|
||||
- Token expiry status: 401 or error "Token has expired" (request.ts)
|
||||
|
||||
## Token Storage
|
||||
|
||||
- Tokens stored via [`SafeStorage`](src/utils/safeStorage.ts) with key `AppStorageKey.TOKEN`.
|
||||
- Token expiry redirects to `/auth/login` (not `/login`).
|
||||
|
||||
## Route Loading Issues
|
||||
|
||||
- If routes not loading, check `isMenuLoading`/`isMenuLoaded` flags in [`permission.ts`](src/router/guard/permission.ts).
|
||||
- Server menu fetched via [`fetchServerMenuConfig()`](src/store/modules/app/index.ts).
|
||||
|
||||
## Environment
|
||||
|
||||
- Dev config: `.env.development`, Prod config: `.env.production`
|
||||
- API base URL: `VITE_API_BASE_URL`, Workspace: `VITE_APP_WORKSPACE`
|
||||
|
||||
## Common Issues
|
||||
|
||||
### useRequest Not Working in Async
|
||||
|
||||
- useRequest invokes API immediately - use in setup scope only
|
||||
- For async contexts, call API directly without useRequest
|
||||
|
||||
### Menu Not Loading
|
||||
|
||||
- Check console for `[Permission Guard]` logs
|
||||
- Verify `menuFromServer` setting in app store
|
||||
- Check `fetchServerMenuConfig()` response
|
||||
|
||||
### Token Expiry
|
||||
|
||||
- Response `status: 401` or `error: 'Token has expired'` triggers logout
|
||||
- Clear storage via `SafeStorage.clearAppStorage()`
|
||||
|
||||
## Debug Commands
|
||||
|
||||
```bash
|
||||
# Check lint errors
|
||||
pnpm lint
|
||||
|
||||
# Type check
|
||||
pnpm build
|
||||
|
||||
# Dev server with logs
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Browser DevTools
|
||||
|
||||
- Vue DevTools for component state
|
||||
- Network tab for API calls
|
||||
- Console for `[Permission Guard]` logs
|
||||
27
.kilo/command/lint-fix.md
Normal file
27
.kilo/command/lint-fix.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# /lint-fix Command
|
||||
|
||||
Run linting and auto-fix code style issues.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/lint-fix [path]
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `path`: Optional file or directory path (defaults to current working directory)
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. Runs `pnpm lint:eslint --fix` to fix ESLint issues
|
||||
2. Runs `pnpm lint:prettier --write` to fix Prettier formatting
|
||||
3. Reports any remaining issues that need manual fixing
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
/lint-fix src/views/ops/pages/dc/url-harvest/
|
||||
```
|
||||
|
||||
Fixes lint issues in the url-harvest module.
|
||||
40
.kilo/command/new-api.md
Normal file
40
.kilo/command/new-api.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# /new-api Command
|
||||
|
||||
Create a new API module with standard interfaces and functions.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/new-api <module-name> [base-path]
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `module-name`: The API module name (e.g., `url-device`, `server`, `alert`)
|
||||
- `base-path`: Optional API base path (defaults to `/DC-Control/v1/{module-name}`)
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. Creates API file: `src/api/ops/{module-name}.ts`
|
||||
|
||||
2. Generates standard interfaces:
|
||||
- `{Module}Item` - Single item type
|
||||
- `{Module}ListResponse` - List response type
|
||||
- `{Module}ListParams` - Query parameters
|
||||
- `{Module}CreateData` - Create payload
|
||||
- `{Module}UpdateData` - Update payload
|
||||
|
||||
3. Generates standard functions:
|
||||
- `fetch{Module}List` - GET list with pagination
|
||||
- `fetch{Module}Detail` - GET single item
|
||||
- `create{Module}` - POST create
|
||||
- `update{Module}` - PUT update
|
||||
- `delete{Module}` - DELETE remove
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
/new-api alert-template /Alert/v1/templates
|
||||
```
|
||||
|
||||
Creates `src/api/ops/alert-template.ts` with full CRUD API functions.
|
||||
33
.kilo/command/new-component.md
Normal file
33
.kilo/command/new-component.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# /new-component Command
|
||||
|
||||
Create a new global reusable component.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/new-component <component-name> [type]
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `component-name`: The component name (e.g., `status-badge`, `time-picker`)
|
||||
- `type`: Optional component type (dialog, drawer, form, display, chart)
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. Creates component file: `src/components/{component-name}/index.vue`
|
||||
|
||||
2. Generates template following Arco Design patterns:
|
||||
- Props definition with TypeScript types
|
||||
- Emits definition with typed events
|
||||
- Scoped styles with Less
|
||||
|
||||
3. Optionally registers in `src/components/index.ts` for global use
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
/new-component status-badge display
|
||||
```
|
||||
|
||||
Creates a display component for showing status with color-coded badges.
|
||||
51
.kilo/command/new-page.md
Normal file
51
.kilo/command/new-page.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# /new-page Command
|
||||
|
||||
Create a new page module with standard structure.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/new-page <module-name> <category>
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `module-name`: The module name (e.g., `url-harvest`, `server`, `database`)
|
||||
- `category`: The category directory (e.g., `dc`, `monitor`, `netarch`, `report`, `system-settings`)
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. Creates directory structure:
|
||||
|
||||
```
|
||||
src/views/ops/pages/{category}/{module-name}/
|
||||
index.vue
|
||||
components/
|
||||
FormDialog.vue
|
||||
Detail.vue
|
||||
config/
|
||||
columns.ts
|
||||
search-form.ts
|
||||
```
|
||||
|
||||
2. Creates API file:
|
||||
|
||||
```
|
||||
src/api/ops/{module-name}.ts
|
||||
```
|
||||
|
||||
3. Generates template code following project patterns:
|
||||
- Main page with SearchTable component
|
||||
- CRUD dialog with form validation
|
||||
- Detail drawer for viewing records
|
||||
- Table columns configuration
|
||||
- Search form configuration
|
||||
- API interfaces and functions
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
/new-page asset dc
|
||||
```
|
||||
|
||||
Creates `src/views/ops/pages/dc/asset/` with full CRUD functionality.
|
||||
23
.kilo/command/type-check.md
Normal file
23
.kilo/command/type-check.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# /type-check Command
|
||||
|
||||
Run TypeScript type checking on the project.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/type-check
|
||||
```
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. Runs `vue-tsc --noEmit` to check TypeScript types
|
||||
2. Reports type errors with file locations and line numbers
|
||||
3. Helps catch type issues before build
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
/type-check
|
||||
```
|
||||
|
||||
Checks all TypeScript files for type errors.
|
||||
219
.kilo/skill/vue-admin-dev.md
Normal file
219
.kilo/skill/vue-admin-dev.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Vue Admin Development Skill
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides comprehensive guidance for developing features in this Vue 3 + Arco Design admin project.
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### 1. Page Module Structure
|
||||
|
||||
Every page module follows this pattern:
|
||||
|
||||
```
|
||||
src/views/ops/pages/{category}/{module}/
|
||||
├── index.vue # Main page (SearchTable + CRUD)
|
||||
├── components/
|
||||
│ ├── FormDialog.vue # Create/Edit dialog
|
||||
│ ├── Detail.vue # Detail view
|
||||
│ └── QuickConfigDialog.vue # Optional quick config
|
||||
└── config/
|
||||
├── columns.ts # Table columns config
|
||||
└── search-form.ts # Search form config
|
||||
```
|
||||
|
||||
### 2. API Module Structure
|
||||
|
||||
```
|
||||
src/api/ops/{module}.ts
|
||||
├── /** {{Module}}类型 */
|
||||
├── Interfaces (Item, ListResponse, ListParams, CreateData, UpdateData)
|
||||
├── /** 获取{{Module}}列表(分页) */
|
||||
├── fetch{Module}List(params)
|
||||
├── /** 获取{{Module}}详情 */
|
||||
├── fetch{Module}Detail(id)
|
||||
├── /** 创建{{Module}} */
|
||||
├── create{Module}(data)
|
||||
├── /** 更新{{Module}} */
|
||||
├── update{Module}(id, data)
|
||||
└── /** 删除{{Module}} */
|
||||
└── delete{Module}(id)
|
||||
```
|
||||
|
||||
### 3. API Response Handling
|
||||
|
||||
```ts
|
||||
// Standard response: {code: 0, details: {...}}
|
||||
const response = await fetchList(params)
|
||||
if (response && response.code === 0 && response.details) {
|
||||
tableData.value = response.details.data || []
|
||||
pagination.total = response.details.total || 0
|
||||
}
|
||||
```
|
||||
|
||||
src/api/ops/{module}.ts
|
||||
├── Interfaces (Item, ListResponse, ListParams, CreateData, UpdateData)
|
||||
├── fetch{Module}List(params)
|
||||
├── fetch{Module}Detail(id)
|
||||
├── create{Module}(data)
|
||||
├── update{Module}(id, data)
|
||||
└── delete{Module}(id)
|
||||
|
||||
````
|
||||
|
||||
### 3. Component Template
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="container">
|
||||
<search-table ...>
|
||||
<template #toolbar-left>
|
||||
<!-- Action buttons -->
|
||||
</template>
|
||||
<template #column-slot="{ record }">
|
||||
<!-- Custom column rendering -->
|
||||
</template>
|
||||
</search-table>
|
||||
<FormDialog v-model:visible="..." />
|
||||
<a-drawer v-model:visible="...">
|
||||
<Detail :record="..." />
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// Imports in order: Vue → Third-party → Components → Configs → APIs → Types
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'ModuleName' }
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
````
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### Storage
|
||||
|
||||
```ts
|
||||
// CORRECT
|
||||
SafeStorage.set(AppStorageKey.TOKEN, token, 3600000)
|
||||
SafeStorage.get(AppStorageKey.USER_INFO)
|
||||
|
||||
// WRONG
|
||||
localStorage.setItem('token', token)
|
||||
```
|
||||
|
||||
### API Choice
|
||||
|
||||
```ts
|
||||
// Workspace-aware APIs (most ops APIs)
|
||||
import { request } from '@/api/request'
|
||||
request.post('/DC-Control/v1/servers', data)
|
||||
// Response: {code: 0, details: {data: [...], total: number}}
|
||||
|
||||
// Standard Bearer token APIs
|
||||
import axios from 'axios' // Uses interceptor.ts defaults
|
||||
// Response: {code: 20000, data: {...}}
|
||||
```
|
||||
|
||||
### Comments (JSDoc Style)
|
||||
|
||||
```ts
|
||||
/** 服务器类型 */
|
||||
export interface ServerItem {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
/** 获取服务器列表(分页) */
|
||||
export const fetchServerList = (params?: ServerListParams) => {
|
||||
return request.get<ServerListResponse>('/DC-Control/v1/servers', { params })
|
||||
}
|
||||
```
|
||||
|
||||
### API Choice
|
||||
|
||||
```ts
|
||||
// Workspace-aware APIs (most ops APIs)
|
||||
import { request } from '@/api/request'
|
||||
request.post('/DC-Control/v1/servers', data)
|
||||
|
||||
// Standard Bearer token APIs
|
||||
import axios from 'axios' // Uses interceptor.ts defaults
|
||||
```
|
||||
|
||||
### useRequest Hook
|
||||
|
||||
```ts
|
||||
// CORRECT - Use in setup scope
|
||||
const { loading, response } = useRequest(fetchList.bind(null, params))
|
||||
|
||||
// WRONG - Use in async function
|
||||
async function loadData() {
|
||||
const { loading, response } = useRequest(fetchList()) // Doesn't work!
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Routes
|
||||
|
||||
```ts
|
||||
// Routes auto-loaded by permission guard
|
||||
// Don't manually add routes except in menu config
|
||||
// Don't modify isMenuLoading/isMenuLoaded flags
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Arco Design Components
|
||||
|
||||
- Table: `<a-table>` with columns, pagination, slots
|
||||
- Form: `<a-form>` with `<a-form-item>` and validation
|
||||
- Dialog: `<a-modal>` with v-model:visible
|
||||
- Drawer: `<a-drawer>` for side panels
|
||||
- Message: `Message.success/error/info/warning()`
|
||||
- Modal: `Modal.confirm/info/error()`
|
||||
|
||||
### Common Slots
|
||||
|
||||
- `#toolbar-left` - Left toolbar buttons
|
||||
- `#toolbar-right` - Right toolbar buttons
|
||||
- `#actions` - Row actions column
|
||||
- `#enabled` - Status toggle column
|
||||
- `#{fieldName}` - Custom field rendering
|
||||
|
||||
### Import Order
|
||||
|
||||
1. Vue: `ref, reactive, computed, watch, onMounted`
|
||||
2. Arco: `Message, Modal` + Icons
|
||||
3. Components: `SearchTable, FormDialog`
|
||||
4. Configs: `searchFormConfig, columns`
|
||||
5. APIs: `fetchList, createItem, updateItem`
|
||||
6. Types: `Item, ListParams, CreateData`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Creating New Module
|
||||
|
||||
1. Run `/new-api {module}` to create API
|
||||
2. Run `/new-page {module} {category}` to create page
|
||||
3. Adjust columns and search-form configs
|
||||
4. Customize FormDialog fields
|
||||
5. Add Detail view content
|
||||
6. Test with `pnpm dev`
|
||||
|
||||
### Adding Feature to Existing Module
|
||||
|
||||
1. Check existing patterns in similar modules
|
||||
2. Add API function if needed
|
||||
3. Update columns/search-form config
|
||||
4. Add component or slot if needed
|
||||
5. Run `pnpm lint` after changes
|
||||
|
||||
## Debugging
|
||||
|
||||
- Console logs prefixed with `[Permission Guard]` for route issues
|
||||
- Network tab for API debugging
|
||||
- Vue DevTools for state inspection
|
||||
- `pnpm lint` for code issues
|
||||
77
.kilo/templates/Detail.vue
Normal file
77
.kilo/templates/Detail.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="detail-container">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="ID">{{ record.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="名称">{{ record.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="描述" :span="2">{{ record.description || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="启用状态">
|
||||
<a-tag :color="record.enabled ? 'green' : 'gray'">
|
||||
{{ record.enabled ? '已启用' : '已禁用' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ formatTime(record.created_at) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">{{ formatTime(record.updated_at) }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<div class="actions">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit">
|
||||
<template #icon>
|
||||
<icon-edit />
|
||||
</template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button status="danger" @click="handleDelete">
|
||||
<template #icon>
|
||||
<icon-delete />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { IconEdit, IconDelete } from '@arco-design/web-vue/es/icon'
|
||||
import type { {{Module}}Item } from '@/api/ops/{{module}}'
|
||||
|
||||
const props = defineProps<{
|
||||
record: {{Module}}Item
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'edit'): void
|
||||
(e: 'delete'): void
|
||||
}>()
|
||||
|
||||
const formatTime = (time?: string) => {
|
||||
if (!time) return '-'
|
||||
const date = new Date(time)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
const handleEdit = () => {
|
||||
emit('edit')
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
emit('delete')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.detail-container {
|
||||
.actions {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
.kilo/templates/FormDialog.vue
Normal file
107
.kilo/templates/FormDialog.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
:title="isEdit ? '编辑{{ModuleTitle}}' : '新增{{ModuleTitle}}'"
|
||||
:width="600"
|
||||
:mask-closable="false"
|
||||
unmount-on-close
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical">
|
||||
<a-form-item field="name" label="名称" required>
|
||||
<a-input v-model="formData.name" placeholder="请输入名称" :max-length="100" />
|
||||
</a-form-item>
|
||||
<a-form-item field="description" label="描述">
|
||||
<a-textarea v-model="formData.description" placeholder="请输入描述" :max-length="500" :auto-size="{ minRows: 3, maxRows: 5 }" />
|
||||
</a-form-item>
|
||||
<a-form-item field="enabled" label="启用状态">
|
||||
<a-switch v-model="formData.enabled" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { {{Module}}Item, {{Module}}CreateData, {{Module}}UpdateData } from '@/api/ops/{{module}}'
|
||||
import { create{{Module}}, update{{Module}} } from '@/api/ops/{{module}}'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
record: {{Module}}Item | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
const formRef = ref()
|
||||
const isEdit = computed(() => !!props.record)
|
||||
|
||||
const formData = ref<{{Module}}CreateData>({
|
||||
name: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max_length: 100, message: '名称不能超过100个字符' },
|
||||
],
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val && props.record) {
|
||||
formData.value = {
|
||||
name: props.record.name,
|
||||
description: props.record.description || '',
|
||||
enabled: props.record.enabled,
|
||||
}
|
||||
} else if (val) {
|
||||
formData.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (valid) return
|
||||
|
||||
if (isEdit.value && props.record) {
|
||||
const res = await update{{Module}}(props.record.id, formData.value as {{Module}}UpdateData)
|
||||
if (res && res.code === 0) {
|
||||
Message.success('更新成功')
|
||||
emit('success')
|
||||
emit('update:visible', false)
|
||||
}
|
||||
} else {
|
||||
const res = await create{{Module}}(formData.value)
|
||||
if (res && res.code === 0) {
|
||||
Message.success('创建成功')
|
||||
emit('success')
|
||||
emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error)
|
||||
Message.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
66
.kilo/templates/api-module.ts
Normal file
66
.kilo/templates/api-module.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { request } from '@/api/request'
|
||||
|
||||
/** {{Module}}类型 */
|
||||
export interface {{Module}}Item {
|
||||
id: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
name: string
|
||||
description?: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
/** {{Module}}列表响应 */
|
||||
export interface {{Module}}ListResponse {
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
data: {{Module}}Item[]
|
||||
}
|
||||
|
||||
/** {{Module}}列表请求参数 */
|
||||
export interface {{Module}}ListParams {
|
||||
page?: number
|
||||
size?: number
|
||||
keyword?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
/** 创建{{Module}}请求参数 */
|
||||
export interface {{Module}}CreateData {
|
||||
name: string
|
||||
description?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
/** 更新{{Module}}请求参数 */
|
||||
export interface {{Module}}UpdateData {
|
||||
name?: string
|
||||
description?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
/** 获取{{Module}}列表(分页) */
|
||||
export const fetch{{Module}}List = (params?: {{Module}}ListParams) => {
|
||||
return request.get<{{Module}}ListResponse>('/DC-Control/v1/{{module}}s', { params })
|
||||
}
|
||||
|
||||
/** 获取{{Module}}详情 */
|
||||
export const fetch{{Module}}Detail = (id: number) => {
|
||||
return request.get<{{Module}}Item>(`/DC-Control/v1/{{module}}s/${id}`)
|
||||
}
|
||||
|
||||
/** 创建{{Module}} */
|
||||
export const create{{Module}} = (data: {{Module}}CreateData) => {
|
||||
return request.post<{ message: string; id: number }>('/DC-Control/v1/{{module}}s', data)
|
||||
}
|
||||
|
||||
/** 更新{{Module}} */
|
||||
export const update{{Module}} = (id: number, data: {{Module}}UpdateData) => {
|
||||
return request.put<{ message: string }>(`/DC-Control/v1/{{module}}s/${id}`, data)
|
||||
}
|
||||
|
||||
/** 删除{{Module}} */
|
||||
export const delete{{Module}} = (id: number) => {
|
||||
return request.delete<{ message: string }>(`/DC-Control/v1/{{module}}s/${id}`)
|
||||
}
|
||||
39
.kilo/templates/columns.ts
Normal file
39
.kilo/templates/columns.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export const columns = [
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
slotName: 'id',
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '名称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '描述',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: '启用状态',
|
||||
width: 100,
|
||||
slotName: 'enabled',
|
||||
},
|
||||
{
|
||||
dataIndex: 'created_at',
|
||||
title: '创建时间',
|
||||
width: 180,
|
||||
slotName: 'created_at',
|
||||
},
|
||||
{
|
||||
dataIndex: 'actions',
|
||||
title: '操作',
|
||||
width: 180,
|
||||
fixed: 'right' as const,
|
||||
slotName: 'actions',
|
||||
},
|
||||
]
|
||||
235
.kilo/templates/index.vue
Normal file
235
.kilo/templates/index.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<search-table
|
||||
:form-model="formModel"
|
||||
:form-items="formItems"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
title="{{ModuleTitle}}管理"
|
||||
search-button-text="查询"
|
||||
reset-button-text="重置"
|
||||
@update:form-model="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
@page-change="handlePageChange"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
新增
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template #id="{ record }">
|
||||
{{ record.id }}
|
||||
</template>
|
||||
|
||||
<template #enabled="{ record }">
|
||||
<a-tag :color="record.enabled ? 'green' : 'gray'">
|
||||
{{ record.enabled ? '已启用' : '已禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template #created_at="{ record }">
|
||||
{{ formatTime(record.created_at) }}
|
||||
</template>
|
||||
|
||||
<template #actions="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleDetail(record)">
|
||||
<template #icon>
|
||||
<icon-eye />
|
||||
</template>
|
||||
详情
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click="handleEdit(record)">
|
||||
<template #icon>
|
||||
<icon-edit />
|
||||
</template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
|
||||
<template #icon>
|
||||
<icon-delete />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</search-table>
|
||||
|
||||
<FormDialog v-model:visible="formDialogVisible" :record="currentRecord" @success="handleFormSuccess" />
|
||||
|
||||
<a-drawer v-model:visible="detailVisible" :width="600" title="{{ModuleTitle}}详情" :footer="false" unmount-on-close>
|
||||
<Detail v-if="currentRecord" :record="currentRecord" @edit="handleDetailEdit" @delete="handleDetailDelete" />
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import { IconPlus, IconEdit, IconDelete, IconEye } from '@arco-design/web-vue/es/icon'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import FormDialog from './components/FormDialog.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import { searchFormConfig } from './config/search-form'
|
||||
import { columns as columnsConfig } from './config/columns'
|
||||
import { fetch{{Module}}List, delete{{Module}}, type {{Module}}Item, type {{Module}}ListParams } from '@/api/ops/{{module}}'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref<{{Module}}Item[]>([])
|
||||
const formDialogVisible = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const currentRecord = ref<{{Module}}Item | null>(null)
|
||||
const formModel = ref({
|
||||
keyword: '',
|
||||
enabled: undefined as boolean | undefined,
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const formItems = computed<FormItem[]>(() => searchFormConfig)
|
||||
const columns = computed(() => columnsConfig)
|
||||
|
||||
const formatTime = (time?: string) => {
|
||||
if (!time) return '-'
|
||||
const date = new Date(time)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: {{Module}}ListParams = {
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
keyword: formModel.value.keyword,
|
||||
enabled: formModel.value.enabled,
|
||||
}
|
||||
const response: any = await fetch{{Module}}List(params)
|
||||
if (response && response.code === 0 && response.details) {
|
||||
tableData.value = response.details?.data || []
|
||||
pagination.total = response.details?.total || 0
|
||||
} else {
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
Message.error('获取列表失败')
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleFormModelUpdate = (value: any) => {
|
||||
formModel.value = value
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
formModel.value = {
|
||||
keyword: '',
|
||||
enabled: undefined,
|
||||
}
|
||||
pagination.current = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handlePageChange = (current: number) => {
|
||||
pagination.current = current
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchData()
|
||||
Message.success('数据已刷新')
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
currentRecord.value = null
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record: {{Module}}Item) => {
|
||||
currentRecord.value = record
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleDetail = (record: {{Module}}Item) => {
|
||||
currentRecord.value = record
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
const handleDetailEdit = () => {
|
||||
detailVisible.value = false
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleDetailDelete = () => {
|
||||
detailVisible.value = false
|
||||
if (currentRecord.value) {
|
||||
handleDelete(currentRecord.value)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormSuccess = () => {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleDelete = (record: {{Module}}Item) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确认删除 "${record.name}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res = await delete{{Module}}(record.id)
|
||||
if (res && res.code === 0) {
|
||||
Message.success('删除成功')
|
||||
fetchData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error)
|
||||
Message.error('删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fetchData()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: '{{ModuleName}}',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
22
.kilo/templates/search-form.ts
Normal file
22
.kilo/templates/search-form.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
|
||||
export const searchFormConfig: FormItem[] = [
|
||||
{
|
||||
field: 'keyword',
|
||||
label: '关键词',
|
||||
type: 'input',
|
||||
placeholder: '请输入名称',
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
field: 'enabled',
|
||||
label: '启用状态',
|
||||
type: 'select',
|
||||
placeholder: '请选择启用状态',
|
||||
options: [
|
||||
{ label: '已启用', value: true },
|
||||
{ label: '已禁用', value: false },
|
||||
],
|
||||
span: 6,
|
||||
},
|
||||
]
|
||||
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
|
||||
|
||||
28
kilo.json
Normal file
28
kilo.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"agents": {
|
||||
"default": "code",
|
||||
"allowed": ["code", "architect", "ask", "debug"]
|
||||
},
|
||||
"commands": {
|
||||
"dir": ".kilo/command"
|
||||
},
|
||||
"rules": {
|
||||
"dir": ".kilo/agent"
|
||||
},
|
||||
"skills": {
|
||||
"dir": ".kilo/skill"
|
||||
},
|
||||
"hooks": {
|
||||
"beforeFileWrite": "pnpm lint --fix",
|
||||
"afterTaskComplete": "pnpm lint"
|
||||
},
|
||||
"context": {
|
||||
"maxTokens": 8000,
|
||||
"includePatterns": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx"],
|
||||
"excludePatterns": ["node_modules", "dist", ".git"]
|
||||
},
|
||||
"logging": {
|
||||
"level": "info",
|
||||
"file": ".kilo/logs/kilo.log"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user