在 Vue3 + Vite + Codex + TypeScript + Ant Design Vue 技术栈中,Agent 通常以 三种角色 融入项目框架:
| 角色 | 定位 | 触发时机 |
|---|---|---|
| 生成 Agent | 基于规范/需求生成代码 | 开发阶段(写代码时) |
| 审查 Agent | 自动 Review、找 Bug、规范检查 | 提交前 / CI 阶段 |
| 运行时 Agent | 应用内智能交互、动态 UI | 用户使用时 |
以下是每种角色的完整落地配置和案例。
一、生成 Agent(Spec → Vue3 代码)
核心: 用 Codex CLI / 自定义脚本,将 .spec.md 或 OpenAPI 规范转换为 Vue3 + TS + AntD Vue 组件。
1. 项目结构
my-vue-project/
├── specs/ # 规范目录(Agent 的输入)
│ ├── components/
│ │ └── UserForm.spec.md # 组件规范
│ └── api/
│ └── user.api.yaml # OpenAPI 规范
├── src/
│ ├── components/ # Agent 生成(只读)
│ │ └── UserForm/
│ │ ├── index.vue
│ │ ├── types.ts
│ │ └── schema.ts
│ ├── api/ # 生成的 API SDK
│ └── views/ # 生成的页面
├── .codex/ # Codex Agent 配置
│ ├── instructions.md # 系统提示词
│ └── templates/ # 生成模板
├── scripts/
│ └── agent-generate.ts # 生成脚本
└── codex.yaml # Codex 项目配置
2. Codex 配置 .codex/instructions.md
# Vue3 + TS + AntD Vue 代码生成规范
## 技术约束
- 使用 Vue3 Composition API + `<script setup lang="ts">`
- 使用 TypeScript,所有 props/emits 必须显式类型化
- UI 组件统一使用 `ant-design-vue`
- 表单使用 `a-form` + `zod` 做校验
- API 调用使用生成的 `@/api` SDK + TanStack Vue Query
## 代码风格
- 组件名使用 PascalCase
- Props 接口名:`{ComponentName}Props`
- Emits 接口名:`{ComponentName}Emits`
- 样式使用 Scoped CSS + CSS Variables
## 禁止
- 不要使用 Options API
- 不要使用 `any` 类型
- 不要直接调用 `fetch/axios`,必须使用生成的 SDK
3. 组件规范示例 specs/components/UserForm.spec.md
---
name: UserForm
type: component
framework: vue3
ui: ant-design-vue
---
## 功能
用户编辑表单,包含:用户名、邮箱、角色、状态
## Props
- user: UserDTO | undefined — 编辑时传入,新增时为 undefined
- loading: boolean — 提交加载状态
## Emits
- submit: (values: UserFormValues) => void
- cancel: () => void
## 校验规则
- username: 必填,3-20 字符,仅字母数字下划线
- email: 必填,邮箱格式
- role: 必填,枚举 ['admin', 'editor', 'viewer']
- status: 必填,枚举 ['active', 'inactive']
## UI 要求
- 使用 a-form 布局,label 宽度 100px
- 底部有「保存」「取消」按钮
- 加载时按钮禁用并显示 Spin
4. 生成脚本 scripts/agent-generate.ts
import { execSync } from 'child_process';
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
import matter from 'gray-matter';
interface Spec {
name: string;
type: string;
framework: string;
ui: string;
}
function generateComponent(specPath: string) {
const content = readFileSync(specPath, 'utf-8');
const { data, content: body } = matter(content) as { data: Spec; content: string };
const prompt = `
基于以下规范生成 Vue3 + TypeScript + Ant Design Vue 组件代码。
规范文件:${specPath}
组件名:${data.name}
${body}
要求:
1. 使用 <script setup lang="ts">
2. Props 和 Emits 使用类型化定义
3. 表单校验使用 zod + ant-design-vue 的 validate
4. 导出组件和类型
5. 添加 JSDoc 注释
请生成完整的 index.vue 文件内容。
`;
// 调用 Codex CLI 生成代码
const result = execSync(`codex -p "${prompt.replace(/"/g, '\\"')}"`, {
encoding: 'utf-8',
cwd: process.cwd(),
});
const outputDir = `src/components/${data.name}`;
mkdirSync(outputDir, { recursive: true });
writeFileSync(`${outputDir}/index.vue`, result);
console.log(`✅ Generated: ${outputDir}/index.vue`);
}
// 遍历 specs 目录生成
const specs = process.argv.slice(2);
if (specs.length === 0) {
console.log('Usage: tsx scripts/agent-generate.ts specs/components/*.md');
}
specs.forEach(generateComponent);
5. package.json Scripts
{
"scripts": {
"agent:gen": "tsx scripts/agent-generate.ts",
"agent:gen:all": "tsx scripts/agent-generate.ts specs/components/*.md specs/views/*.md",
"agent:gen:watch": "chokidar 'specs/**/*.md' -c 'npm run agent:gen -- {path}'"
}
}
6. 生成的组件示例 src/components/UserForm/index.vue
<template>
<a-form
:model="formState"
:rules="rules"
@finish="handleSubmit"
label-align="right"
:label-col="{ style: { width: '100px' } }"
>
<a-form-item label="用户名" name="username">
<a-input v-model:value="formState.username" placeholder="请输入用户名" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formState.email" placeholder="请输入邮箱" />
</a-form-item>
<a-form-item label="角色" name="role">
<a-select v-model:value="formState.role" placeholder="请选择角色">
<a-select-option value="admin">管理员</a-select-option>
<a-select-option value="editor">编辑</a-select-option>
<a-select-option value="viewer">访客</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="formState.status">
<a-radio value="active">启用</a-radio>
<a-radio value="inactive">禁用</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" html-type="submit" :loading="props.loading">
保存
</a-button>
<a-button @click="handleCancel">取消</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import { z } from 'zod';
export interface UserFormValues {
username: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
status: 'active' | 'inactive';
}
export interface UserFormProps {
initialValues?: Partial<UserFormValues>;
loading?: boolean;
}
export interface UserFormEmits {
(e: 'submit', values: UserFormValues): void;
(e: 'cancel'): void;
}
const props = withDefaults(defineProps<UserFormProps>(), {
loading: false,
});
const emit = defineEmits<UserFormEmits>();
const formState = reactive<UserFormValues>({
username: '',
email: '',
role: 'viewer',
status: 'active',
});
// 同步初始值
watch(
() => props.initialValues,
(val) => {
if (val) {
Object.assign(formState, val);
}
},
{ immediate: true }
);
const rules = {
username: [
{ required: true, message: '请输入用户名' },
{ min: 3, max: 20, message: '长度 3-20 字符' },
{ pattern: /^[a-zA-Z0-9_]+$/, message: '仅允许字母数字下划线' },
],
email: [
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '邮箱格式不正确' },
],
role: [{ required: true, message: '请选择角色' }],
status: [{ required: true, message: '请选择状态' }],
};
const handleSubmit = () => {
emit('submit', { ...formState });
};
const handleCancel = () => {
emit('cancel');
};
</script>
二、审查 Agent(Git Hook + CI)
核心: 在 pre-commit 和 CI 中运行 Agent,自动 Review 代码质量、类型安全、AntD Vue 使用规范。
1. 本地审查脚本 scripts/agent-review.ts
import { execSync } from 'child_process';
import { readFileSync } from 'fs';
function getStagedFiles(): string[] {
return execSync('git diff --cached --name-only --diff-filter=ACM')
.toString()
.split('\n')
.filter(f => f.endsWith('.vue') || f.endsWith('.ts'));
}
function reviewFile(filePath: string): string {
const code = readFileSync(filePath, 'utf-8');
const prompt = `
你是一名资深 Vue3 + TypeScript 代码审查员。请审查以下代码,按规则检查:
## 检查项
1. 是否使用 Composition API(<script setup>)
2. Props 是否有显式类型定义
3. 是否使用了 any 类型
4. AntD Vue 组件使用是否正确(如 a-form 的 model 绑定)
5. 是否有未使用的导入或变量
6. 是否有潜在的空值/类型错误
7. 是否符合团队命名规范
## 输出格式
对每个问题,输出:
- [严重/警告/建议] 行号: 问题描述 → 修复建议
如果无问题,输出:✅ 通过
## 代码文件:${filePath}
\`\`\`vue
${code}
\`\`\`
`;
try {
const result = execSync(`codex --no-interactive -p "${prompt.replace(/"/g, '\\"')}"`, {
encoding: 'utf-8',
timeout: 30000,
});
return result;
} catch (e) {
return `⚠️ 审查超时: ${filePath}`;
}
}
const files = getStagedFiles();
if (files.length === 0) {
console.log('No staged files to review.');
process.exit(0);
}
let hasError = false;
for (const file of files) {
console.log(`\n🔍 Reviewing: ${file}`);
const report = reviewFile(file);
console.log(report);
if (report.includes('[严重]')) {
hasError = true;
}
}
if (hasError) {
console.error('\n❌ 审查发现严重问题,提交被拒绝。');
console.error('如需强制提交,使用: git commit --no-verify');
process.exit(1);
} else {
console.log('\n✅ 审查通过');
}
2. Git Hook 配置 .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# 先跑 ESLint
npx lint-staged
# 再跑 Agent 审查
npx tsx scripts/agent-review.ts
3. CI 中的批量审查 .github/workflows/agent-review.yml
name: Agent Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Get changed files
id: changed
run: |
echo "files=$(git diff --name-only origin/${{ github.base_ref }} | grep -E '\.(vue|ts)$' | tr '\n' ' ')" >> $GITHUB_OUTPUT
- name: Run Agent Review
if: steps.changed.outputs.files != ''
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
for file in ${{ steps.changed.outputs.files }}; do
echo "🔍 Reviewing $file"
npx tsx scripts/agent-review.ts "$file"
done
三、运行时 Agent(应用内 AI 助手)
核心: 在 Vue3 应用内嵌入 AI Agent,提供智能表单填充、页面导航、代码辅助等功能。
1. 运行时 Agent 组件 src/components/AIAgent/AgentPanel.vue
<template>
<div class="ai-agent-panel">
<a-float-button
type="primary"
:style="{ right: '24px', bottom: '24px' }"
@click="togglePanel"
>
<template #icon>🤖</template>
</a-float-button>
<a-drawer
v-model:open="visible"
title="AI 助手"
placement="right"
:width="480"
>
<div class="chat-container">
<div v-for="(msg, idx) in messages" :key="idx" :class="['msg', msg.role]">
<a-avatar v-if="msg.role === 'assistant'" style="background-color: #1890ff">AI</a-avatar>
<div class="bubble">{{ msg.content }}</div>
</div>
<div v-if="loading" class="msg assistant">
<a-spin size="small" />
</div>
</div>
<div class="input-area">
<a-textarea
v-model:value="input"
:rows="3"
placeholder="描述你的需求,例如:帮我生成一个用户查询表单"
@pressEnter="handleSend"
/>
<a-button type="primary" :loading="loading" @click="handleSend">
发送
</a-button>
</div>
</a-drawer>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue';
import { useAgent } from './useAgent';
const visible = ref(false);
const input = ref('');
const { messages, loading, sendMessage } = useAgent();
const togglePanel = () => {
visible.value = !visible.value;
};
const handleSend = async () => {
if (!input.value.trim() || loading.value) return;
const text = input.value;
input.value = '';
await sendMessage(text);
};
</script>
<style scoped>
.chat-container {
height: calc(100% - 120px);
overflow-y: auto;
padding: 16px 0;
}
.msg {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.msg.user {
flex-direction: row-reverse;
}
.msg.user .bubble {
background: #1890ff;
color: white;
border-radius: 12px 12px 0 12px;
}
.msg.assistant .bubble {
background: #f0f0f0;
border-radius: 12px 12px 12px 0;
}
.bubble {
padding: 8px 12px;
max-width: 80%;
word-break: break-word;
}
.input-area {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: column;
gap: 8px;
}
</style>
2. Agent 逻辑 Hook src/components/AIAgent/useAgent.ts
import { ref } from 'vue';
interface Message {
role: 'user' | 'assistant';
content: string;
}
export function useAgent() {
const messages = ref<Message[]>([
{ role: 'assistant', content: '你好!我是你的开发助手。你可以让我:\n1. 生成组件代码\n2. 解释现有代码\n3. 优化表单逻辑' }
]);
const loading = ref(false);
const sendMessage = async (text: string) => {
messages.value.push({ role: 'user', content: text });
loading.value = true;
try {
// 调用后端 Agent API 或 Codex CLI
const response = await fetch('/api/agent/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: messages.value,
context: {
framework: 'vue3',
ui: 'ant-design-vue',
project: 'my-vue-project'
}
}),
});
const data = await response.json();
messages.value.push({ role: 'assistant', content: data.reply });
} catch (e) {
messages.value.push({ role: 'assistant', content: '请求失败,请稍后重试' });
} finally {
loading.value = false;
}
};
return { messages, loading, sendMessage };
}
3. 后端 Agent API 示例(Node.js)
// server/agent.ts
import { OpenAI } from 'openai';
import { readFileSync } from 'fs';
import { glob } from 'glob';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// 加载项目上下文(组件库、类型定义等)
async function loadProjectContext() {
const components = await glob('src/components/**/*.vue');
const types = await glob('src/**/*.ts');
const context = {
components: components.slice(0, 10), // 取前10个作为示例
typeDefinitions: types.slice(0, 5),
};
return context;
}
export async function handleAgentChat(req: Request) {
const { messages, context } = await req.json();
const systemPrompt = `
你是 ${context.project} 项目的 AI 开发助手。
项目技术栈:
- Vue3 + Composition API
- TypeScript
- Ant Design Vue
- Vite
可用组件:
${context.components.join('\n')}
规则:
1. 生成代码时必须使用 <script setup lang="ts">
2. 使用 ant-design-vue 组件,不要引入其他 UI 库
3. 类型必须严格,禁止 any
4. 如果用户要求生成组件,输出完整 .vue 文件内容
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: systemPrompt },
...messages.map((m: any) => ({ role: m.role, content: m.content }))
],
temperature: 0.2,
});
return new Response(JSON.stringify({
reply: completion.choices[0].message.content
}), {
headers: { 'Content-Type': 'application/json' }
});
}
四、三种角色的协同工作流
┌─────────────────────────────────────────────────────────────────┐
│ 开发阶段 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 写规范 specs/components/UserForm.spec.md │
│ 2. 运行 npm run agent:gen → 生成 Agent 产出 Vue 组件 │
│ 3. 人工微调(如需)→ 规范同步回 specs/ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 提交阶段 │
├─────────────────────────────────────────────────────────────────┤
│ git add . │
│ git commit │
│ ├─ lint-staged (ESLint + Prettier) │
│ └─ agent-review.ts (审查 Agent 检查类型/规范) │
│ 不通过 → 阻断提交,输出修复建议 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 运行阶段 │
├─────────────────────────────────────────────────────────────────┤
│ 用户打开应用 → 看到右下角 🤖 AI 助手 │
│ 用户说:"帮我生成一个带搜索的用户表格" │
│ 运行时 Agent → 调用 API → 返回代码 → 用户一键插入到项目 │
└─────────────────────────────────────────────────────────────────┘
五、Vite 配置要点 vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@specs': resolve(__dirname, 'specs'),
},
},
server: {
proxy: {
'/api/agent': {
target: 'http://localhost:3001',
changeOrigin: true,
},
},
},
});
六、快速启动模板
如果你要新建项目,可以用这个一键脚本:
# 1. 创建项目
npm create vite@latest my-vue-agent -- --template vue-ts
cd my-vue-agent
# 2. 安装依赖
npm install vue-router@4 pinia ant-design-vue zod
npm install -D @vitejs/plugin-vue typescript tsx husky lint-staged
# 3. 初始化 Agent 工作流
mkdir -p .codex specs/components specs/api scripts
echo 'node_modules/' >> .gitignore
echo 'src/api/' >> .gitignore
# 4. 配置 Husky
npx husky install
npx husky add .husky/pre-commit "npx lint-staged && npx tsx scripts/agent-review.ts"
# 5. 启动
npm run dev
总结: 在 Vue3 + Vite + Codex 项目中,Agent 不是"一个功能",而是贯穿 生成 → 审查 → 运行 三个阶段的基础设施。生成 Agent 把规范变成代码,审查 Agent 守住质量门槛,运行时 Agent 让最终用户也能享受 AI 能力。

1153

被折叠的 条评论
为什么被折叠?



