AI赋能前端工程化实践
- Prompt工程:建立前端场景化Prompt库(组件开发/样式调优/性能优化等),制定团队AI协作规范,提升代码生成准确率
- 工具链定制:开发前端专用Skill指令(React/Vue组件生成、Tailwind样式优化、TypeScript类型推导等)和MCP服务器集成
- 流程标准化:引入SpecKit规范需求拆解、UI还原、组件开发、代码review全链路,实现AI深度参与各环节
- 成果验证:组件开发效率提升50%+,样式还原准确度提高,代码规范性和可维护性显著改善
4个场景具体的代码
场景一:组件开发提效
业务背景: 项目需要开发大量表单组件(用户信息表单、订单表单、筛选表单等),传统开发需要反复编写表单校验、数据绑定、样式适配等重复代码。
AI应用实践
- 定制Prompt模板:
基于Vue3 Composition API + Element Plus开发表单组件
基于React react-hook-form + Ant Design开发表单组件
要求:
- 使用TypeScript定义Props类型
- 集成Vuelidate进行表单校验
- 支持v-model双向绑定
- 响应式布局适配
1. 性能更好:非受控组件,减少重渲染
2. API更简洁:useForm一个hook搞定
3. 类型安全:TypeScript支持完美
4. 生态丰富:支持各种UI库
- 开发自定义Skill:
- 创建
vue3-form-generatorSkill,输入字段配置JSON,自动生成完整组件代码 - 包含:template结构、script setup逻辑、TypeScript类型、校验规则、样式
- 创建
- 具体成果:
- 原本1个表单组件需要2-3小时,现在30分钟完成
- 代码规范统一,减少code review时间
- 自动生成单元测试框架
场景二:样式还原与优化
业务背景: 设计师提供Figma设计稿,需要精准还原复杂UI(卡片列表、数据大屏、响应式布局等),传统开发需要反复调试CSS。
AI应用实践
- Prompt工程:
根据设计稿描述生成Tailwind CSS类名
要求:
- 响应式断点处理(sm/md/lg/xl)
- 深色模式适配(dark:)
- 动画效果(transition/animate)
- 遵循项目Tailwind配置
- 工作流程:
- 截图设计稿 → AI分析布局结构 → 生成Tailwind类名 → 微调适配
- 使用AI优化现有CSS,转换为Tailwind工具类,减少自定义样式
- 具体案例:
- 数据大屏页面:20+图表卡片布局,AI辅助完成响应式适配,原需2天,现1天完成
- 减少自定义CSS代码量60%,样式一致性提升
场景三:TypeScript类型推导与优化
业务背景: Vue3项目使用TypeScript,API接口众多(100+接口),需要维护大量类型定义,手动编写易出错且效率低。
AI应用实践
- MCP集成:
- 开发
api-type-generatorMCP服务器 - 读取Swagger/OpenAPI文档,自动生成TypeScript接口类型
- 开发
- Prompt优化:
根据API响应JSON生成TypeScript类型
要求:
- 嵌套对象递归定义
- 可选字段标注(?:)
- 枚举类型提取
- 添加JSDoc注释
- 具体成果:
- API类型定义从手动1小时/接口 → AI生成5分钟/批量接口
- 类型覆盖率从60% → 95%+
- 减少运行时类型错误
场景四:代码Review与重构
业务背景: 老项目Vue2迁移至Vue3,需要重构Options API为Composition API,同时优化性能和代码质量。
AI应用实践
- SpecKit流程规范:
- 需求:明确迁移范围和目标(性能提升、类型安全)
- 设计:AI分析组件依赖关系,生成迁移优先级
- 开发:AI辅助批量转换API语法
- Review:AI检查常见问题(ref/reactive误用、watch依赖遗漏)
- 具体操作:
将Vue2 Options API组件转换为Vue3 Composition API
要求:
- data → ref/reactive
- methods → 普通函数
- computed → computed()
- watch → watch/watchEffect
- 生命周期钩子对应转换
- 优化响应式性能
- 成果数据:
- 50+组件迁移,人工审核为主,AI辅助转换节省70%时间
- 性能优化:首屏渲染提升30%
场景五:单元测试自动生成
业务背景: 项目要求测试覆盖率>80%,但编写单元测试耗时,开发人员不愿写测试。
AI应用实践
- 自定义Skill:
vue3-test-generator- 输入:Vue组件代码
- 输出:Vitest单元测试代码(组件渲染、Props测试、事件测试、快照测试)
- Prompt示例:
为Vue3组件生成Vitest单元测试
要求:
- 使用@vue/test-utils
- 测试Props传递
- 测试事件触发
- 测试插槽渲染
- 边界情况覆盖
- 具体成果:
- 测试覆盖率从40% → 85%
- 单个组件测试编写从1小时 → 15分钟(AI生成+人工审核)
面试回答技巧
当面试官追问时,可以这样回答
"以组件开发为例,我们项目中有大量表单场景。我建立了一套Prompt模板库,针对不同类型表单(新增/编辑/筛选),AI可以根据字段配置自动生成完整的Vue3组件,包括Composition API逻辑、TypeScript类型定义、Element Plus集成、表单校验规则等。
同时开发了自定义Skill,比如输入一个JSON配置,就能生成标准化的表单组件。这让我们的表单开发效率从平均3小时缩短到30分钟,而且代码规范性更好,减少了code review的时间。
另外在样式还原方面,我会让AI分析设计稿,直接生成Tailwind类名,特别是响应式布局和深色模式适配,效率提升非常明显。"
场景一:Vue、React组件自动生成(具体实案例-参考代码)
1. 创建自定义Skill
步骤1:创建Skill目录结构
bash
# 在本地创建skill目录
mkdir -p ~/claude-skills/vue3-form-generator
cd ~/claude-skills/vue3-form-generator
步骤2:创建SKILL.md文件
bash
touch SKILL.md
SKILL.md内容
markdown
# Vue3 Form Generator Skill
## 功能描述
根据字段配置JSON自动生成Vue3表单组件(Composition API + TypeScript + Element Plus)
## 输入格式
用户提供JSON配置,包含表单字段信息:
- fieldName: 字段名
- label: 字段标签
- type: 字段类型(input/select/date/number等)
- required: 是否必填
- rules: 校验规则
## 输出要求
生成完整的Vue3单文件组件,包含:
1. TypeScript类型定义
2. Composition API逻辑
3. Element Plus表单组件
4. Vuelidate表单校验
5. 响应式样式
## 代码规范
- 使用script setup语法
- 使用ref/reactive管理状态
- 所有Props必须有TypeScript类型
- 表单必须有完整校验
- 支持v-model双向绑定
## 示例
输入:
````json
{
"formName": "UserForm",
"fields": [
{
"fieldName": "username",
"label": "用户名",
"type": "input",
"required": true,
"rules": "minLength:3,maxLength:20"
},
{
"fieldName": "email",
"label": "邮箱",
"type": "input",
"required": true,
"rules": "email"
},
{
"fieldName": "age",
"label": "年龄",
"type": "number",
"required": false,
"rules": "min:18,max:100"
}
]
}
```text
输出:生成完整的UserForm.vue组件
````
### 步骤3:在Claude中上传Skill
1. 打开 Claude.ai
2. 点击右上角设置图标
3. 选择「Custom Skills」或直接在对话中上传
4. 上传 `SKILL.md` 文件
### 步骤4:使用Skill生成组件
#### 在Claude对话框中输入
```text
使用vue3-form-generator skill,根据以下配置生成表单组件:
{
"formName": "UserForm",
"fields": [
{
"fieldName": "username",
"label": "用户名",
"type": "input",
"required": true,
"rules": "minLength:3,maxLength:20"
},
{
"fieldName": "email",
"label": "邮箱",
"type": "input",
"required": true,
"rules": "email"
},
{
"fieldName": "age",
"label": "年龄",
"type": "number",
"required": false,
"rules": "min:18,max:100"
},
{
"fieldName": "gender",
"label": "性别",
"type": "select",
"required": true,
"options": ["男", "女", "其他"]
}
]
}
```
### 步骤5:Claude会生成完整组件代码
#### 生成的UserForm.vue
vue
```vue
<template>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="120px"
class="user-form"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="formData.username"
placeholder="请输入用户名"
clearable
/>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input
v-model="formData.email"
placeholder="请输入邮箱"
clearable
/>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input-number
v-model="formData.age"
:min="18"
:max="100"
placeholder="请输入年龄"
/>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="formData.gender" placeholder="请选择性别">
<el-option label="男" value="男" />
<el-option label="女" value="女" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
// 类型定义
interface UserFormData {
username: string
email: string
age: number | undefined
gender: string
}
// Props定义
interface Props {
modelValue?: UserFormData
}
const props = withDefaults(defineProps<Props>(), {
modelValue: () => ({
username: '',
email: '',
age: undefined,
gender: '',
}),
})
// Emits定义
const emit = defineEmits<{
'update:modelValue': [value: UserFormData]
submit: [value: UserFormData]
}>()
// 表单引用
const formRef = ref<FormInstance>()
// 表单数据
const formData = reactive<UserFormData>({
username: props.modelValue?.username || '',
email: props.modelValue?.email || '',
age: props.modelValue?.age,
gender: props.modelValue?.gender || '',
})
// 校验规则
const rules = reactive<FormRules<UserFormData>>({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在3-20个字符', trigger: 'blur' },
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' },
],
age: [
{ type: 'number', message: '年龄必须是数字', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== undefined && (value < 18 || value > 100)) {
callback(new Error('年龄必须在18-100之间'))
} else {
callback()
}
},
trigger: 'blur',
},
],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
})
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
emit('update:modelValue', formData)
emit('submit', formData)
}
})
}
// 重置表单
const handleReset = () => {
formRef.value?.resetFields()
}
// 暴露方法供父组件调用
defineExpose({
validate: () => formRef.value?.validate(),
resetFields: () => formRef.value?.resetFields(),
})
</script>
<style scoped>
.user-form {
max-width: 600px;
padding: 20px;
}
</style>
```
### 步骤6:在项目中使用生成的组件
#### 创建测试页面 TestForm.vue
vue
```vue
<template>
<div class="test-page">
<h2>表单测试页面</h2>
<UserForm v-model="userData" @submit="handleFormSubmit" />
<div v-if="submittedData" class="result">
<h3>提交结果:</h3>
<pre>{{ JSON.stringify(submittedData, null, 2) }}</pre>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import UserForm from './UserForm.vue'
const userData = ref({
username: '',
email: '',
age: undefined,
gender: '',
})
const submittedData = ref(null)
const handleFormSubmit = (data: any) => {
submittedData.value = data
console.log('表单提交数据:', data)
}
</script>
<style scoped>
.test-page {
padding: 20px;
}
.result {
margin-top: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 4px;
}
pre {
background: #fff;
padding: 10px;
border-radius: 4px;
}
</style>
```
### 步骤7:运行项目
bash
```bash
# 1. 创建Vue3项目(如果还没有)
npm create vue@latest my-form-project
cd my-form-project
# 2. 安装依赖
npm install
npm install element-plus
# 3. 配置Element Plus(main.ts)
# 添加以下代码到 src/main.ts
```
#### src/main.ts
typescript
```typescript
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
```
bash
```bash
# 4. 将生成的 UserForm.vue 和 TestForm.vue 放到 src/components/
# 5. 修改 App.vue 引入 TestForm
# 6. 运行项目
npm run dev
```
##### App.vue
vue
```vue
<template>
<TestForm />
</template>
<script setup lang="ts">
import TestForm from './components/TestForm.vue'
</script>
```
---
## **场景二:Tailwind样式自动生成(具体实案例-参考代码)**
## **完整运行步骤**
### 步骤1:创建Vue3 + Tailwind项目
bash
```bash
# 创建项目
npm create vue@latest tailwind-demo
cd tailwind-demo
# 安装依赖
npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
### 步骤2:配置Tailwind
#### tailwind.config.js
javascript
```javascript
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: 'class',
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
},
},
},
},
plugins: [],
}
```
##### src/style.css
css
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
###### src/main.ts
typescript
```typescript
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
```
### 步骤3:在Claude中使用Prompt生成样式
#### 在Claude对话框中输入
```text
我需要开发一个数据卡片组件,请根据以下设计要求生成Tailwind CSS类名:
设计要求:
1. 卡片宽度:默认全宽,md断点以上为1/3宽度,lg断点以上为1/4宽度
2. 内边距:16px
3. 背景:白色,深色模式下为深灰色(#1f2937)
4. 圆角:8px
5. 阴影:轻微阴影,hover时加深
6. 边框:1px浅灰色边框
7. 标题:文字大小18px,加粗,深色模式下白色
8. 数值:文字大小32px,蓝色(#3b82f6)
9. 描述:文字大小14px,灰色
10. 包含过渡动画效果
请生成完整的Vue3组件代码
```
### 步骤4:Claude生成的组件代码
#### src/components/DataCard.vue
vue
```vue
<template>
<div
class="w-full md:w-1/3 lg:w-1/4 p-4 transition-transform duration-300 hover:scale-105"
>
<div
class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 shadow-md hover:shadow-xl transition-shadow duration-300"
>
<!-- 标题 -->
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">
{{ title }}
</h3>
<!-- 数值 -->
<div
class="text-3xl font-bold text-blue-500 dark:text-blue-400 my-3"
>
{{ value }}
</div>
<!-- 描述 -->
<p class="text-sm text-gray-500 dark:text-gray-400">
{{ description }}
</p>
<!-- 趋势指示器(可选) -->
<div v-if="trend" class="mt-3 flex items-center">
<span
:class="[
'text-sm font-medium',
trend > 0 ? 'text-green-500' : 'text-red-500',
]"
>
{{ trend > 0 ? '↑' : '↓' }} {{ Math.abs(trend) }}%
</span>
<span class="text-xs text-gray-400 ml-2">vs 上周</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
title: string
value: string | number
description: string
trend?: number
}
withDefaults(defineProps<Props>(), {
trend: undefined,
})
</script>
```
### 步骤5:创建演示页面
#### src/components/Dashboard.vue
vue
```vue
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 p-8">
<div class="max-w-7xl mx-auto">
<!-- 标题栏 -->
<div class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">
数据看板
</h1>
<button
@click="toggleDarkMode"
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
切换{{ isDark ? '浅色' : '深色' }}模式
</button>
</div>
<!-- 数据卡片网格 -->
<div class="flex flex-wrap -mx-4">
<DataCard
v-for="card in dataCards"
:key="card.id"
:title="card.title"
:value="card.value"
:description="card.description"
:trend="card.trend"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import DataCard from './DataCard.vue'
const isDark = ref(false)
const dataCards = [
{
id: 1,
title: '总用户数',
value: '12,543',
description: '累计注册用户',
trend: 12.5,
},
{
id: 2,
title: '活跃用户',
value: '8,321',
description: '本周活跃用户',
trend: 8.3,
},
{
id: 3,
title: '总订单',
value: '3,456',
description: '本月订单数量',
trend: -2.4,
},
{
id: 4,
title: '收入',
value: '¥89,234',
description: '本月总收入',
trend: 15.8,
},
{
id: 5,
title: '新增用户',
value: '234',
description: '今日新增',
trend: 5.2,
},
{
id: 6,
title: '转化率',
value: '3.2%',
description: '平均转化率',
trend: 0.8,
},
{
id: 7,
title: '客单价',
value: '¥456',
description: '平均客单价',
trend: -1.2,
},
{
id: 8,
title: '退款率',
value: '1.8%',
description: '本月退款率',
trend: -0.5,
},
]
const toggleDarkMode = () => {
isDark.value = !isDark.value
if (isDark.value) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
onMounted(() => {
// 检查系统主题偏好
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
isDark.value = true
document.documentElement.classList.add('dark')
}
})
</script>
```
### 步骤6:运行项目
#### App.vue
vue
```vue
<template>
<Dashboard />
</template>
<script setup lang="ts">
import Dashboard from './components/Dashboard.vue'
</script>
```
bash
```bash
# 运行项目
npm run dev
# 访问 http://localhost:5173
# 可以看到响应式数据卡片,支持深色模式切换
```
---
## **场景三:API类型自动生成(MCP服务器)(具体实案例-参考代码)**
## **完整运行步骤**
### 步骤1:创建MCP服务器项目
bash
```bash
# 创建项目目录
mkdir api-type-generator-mcp
cd api-type-generator-mcp
# 初始化项目
npm init -y
# 安装依赖
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node tsx
```
### 步骤2:创建TypeScript配置
#### tsconfig.json
json
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
### 步骤3:创建MCP服务器代码
#### src/index.ts
typescript
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
// API响应转TypeScript类型
function jsonToTypeScript(json: any, interfaceName: string): string {
const generateType = (
obj: any,
name: string,
indent: number = 0
): string => {
const spaces = ' '.repeat(indent)
if (Array.isArray(obj)) {
if (obj.length === 0) return 'any[]'
return generateType(obj[0], name, indent) + '[]'
}
if (obj === null) return 'null'
if (typeof obj !== 'object') {
return typeof obj
}
let result = `${spaces}interface ${name} {\n`
for (const [key, value] of Object.entries(obj)) {
const propType = Array.isArray(value)
? `${generateType(value[0], `${name}${key.charAt(0).toUpperCase()}${key.slice(1)}Item`, indent + 1)}[]`
: typeof value === 'object' && value !== null
? `${name}${key.charAt(0).toUpperCase()}${key.slice(1)}`
: typeof value
result += `${spaces} ${key}: ${propType}\n`
}
result += `${spaces}}\n\n`
// 生成嵌套接口
for (const [key, value] of Object.entries(obj)) {
if (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
) {
result =
generateType(
value,
`${name}${key.charAt(0).toUpperCase()}${key.slice(1)}`,
indent
) + result
} else if (
Array.isArray(value) &&
value.length > 0 &&
typeof value[0] === 'object'
) {
result =
generateType(
value[0],
`${name}${key.charAt(0).toUpperCase()}${key.slice(1)}Item`,
indent
) + result
}
}
return result
}
return generateType(json, interfaceName)
}
// OpenAPI/Swagger转TypeScript
function swaggerToTypeScript(swagger: any): string {
let result = ''
if (swagger.definitions) {
for (const [name, schema] of Object.entries(swagger.definitions)) {
result += `interface ${name} {\n`
const props = (schema as any).properties || {}
for (const [propName, propSchema] of Object.entries(props)) {
const type = (propSchema as any).type || 'any'
const required = (schema as any).required?.includes(propName)
result += ` ${propName}${required ? '' : '?'}: ${type}\n`
}
result += `}\n\n`
}
}
return result
}
// 创建MCP服务器
const server = new Server(
{
name: 'api-type-generator',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
)
// 注册工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'generate_types_from_json',
description: '根据JSON响应生成TypeScript类型定义',
inputSchema: {
type: 'object',
properties: {
json: {
type: 'string',
description: 'JSON字符串',
},
interfaceName: {
type: 'string',
description: '接口名称',
},
},
required: ['json', 'interfaceName'],
},
},
{
name: 'generate_types_from_swagger',
description: '根据Swagger/OpenAPI文档生成TypeScript类型',
inputSchema: {
type: 'object',
properties: {
swagger: {
type: 'string',
description: 'Swagger JSON字符串',
},
},
required: ['swagger'],
},
},
],
}
})
// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params
try {
if (name === 'generate_types_from_json') {
const json = JSON.parse(args.json as string)
const types = jsonToTypeScript(json, args.interfaceName as string)
return {
content: [
{
type: 'text',
text: types,
},
],
}
}
if (name === 'generate_types_from_swagger') {
const swagger = JSON.parse(args.swagger as string)
const types = swaggerToTypeScript(swagger)
return {
content: [
{
type: 'text',
text: types,
},
],
}
}
throw new Error(`Unknown tool: ${name}`)
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
}
}
})
// 启动服务器
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('API Type Generator MCP Server running on stdio')
}
main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})
```
### 步骤4:配置package.json
#### package.json
json
```json
{
"name": "api-type-generator-mcp",
"version": "1.0.0",
"type": "module",
"bin": {
"api-type-generator-mcp": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.6.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0"
}
}
```
### 步骤5:编译和测试
bash
```bash
# 编译
npm run build
# 测试运行
npm run dev
```
### 步骤6:在Claude Desktop配置MCP
#### 配置文件位置
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
##### claude_desktop_config.json
json
```json
{
"mcpServers": {
"api-type-generator": {
"command": "node",
"args": ["/绝对路径/api-type-generator-mcp/dist/index.js"]
}
}
}
```
### 步骤7:在Claude中使用MCP
#### 重启Claude Desktop,然后在对话框中输入
```text
使用api-type-generator MCP工具,根据以下API响应生成TypeScript类型:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"username": "zhangsan",
"email": "zhangsan@example.com",
"profile": {
"avatar": "https://example.com/avatar.jpg",
"bio": "前端开发工程师",
"location": "北京"
},
"posts": [
{
"id": 1,
"title": "我的第一篇文章",
"content": "文章内容...",
"createdAt": "2024-01-01T00:00:00Z"
}
]
}
}
接口名称:UserResponse
```
##### Claude会调用MCP工具生成
typescript
```typescript
interface UserResponseDataProfileType {
avatar: string
bio: string
location: string
}
interface UserResponseDataPostsItem {
id: number
title: string
content: string
createdAt: string
}
interface UserResponseData {
id: number
username: string
email: string
profile: UserResponseDataProfileType
posts: UserResponseDataPostsItem[]
}
interface UserResponse {
code: number
message: string
data: UserResponseData
}
```
### 步骤8:在Vue3项目中使用生成的类型
#### src/types/api.ts
typescript
```typescript
// 复制Claude生成的类型定义
interface UserResponseDataProfile {
avatar: string
bio: string
location: string
}
interface UserResponseDataPostsItem {
id: number
title: string
content: string
createdAt: string
}
interface UserResponseData {
id: number
username: string
email: string
profile: UserResponseDataProfile
posts: UserResponseDataPostsItem[]
}
export interface UserResponse {
code: number
message: string
data: UserResponseData
}
```
##### src/api/user.ts
typescript
```typescript
import type { UserResponse } from '../types/api'
export const getUserInfo = async (userId: number): Promise<UserResponse> => {
const response = await fetch(`/api/users/${userId}`)
return response.json()
}
```
###### src/components/UserProfile.vue
vue
```vue
<template>
<div v-if="userInfo" class="user-profile">
<img :src="userInfo.data.profile.avatar" alt="avatar" />
<h2>{{ userInfo.data.username }}</h2>
<p>{{ userInfo.data.email }}</p>
<p>{{ userInfo.data.profile.bio }}</p>
<div class="posts">
<h3>文章列表</h3>
<div v-for="post in userInfo.data.posts" :key="post.id">
<h4>{{ post.title }}</h4>
<p>{{ post.content }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getUserInfo } from '../api/user'
import type { UserResponse } from '../types/api'
const userInfo = ref<UserResponse | null>(null)
onMounted(async () => {
userInfo.value = await getUserInfo(1)
})
</script>
```
---
## **场景四:Vue2迁移Vue3(代码重构)(具体实案例-参考代码)**
## **完整运行步骤**
### 步骤1:准备Vue2组件示例
#### 创建测试项目
bash
```bash
mkdir vue-migration-demo
cd vue-migration-demo
npm create vue@latest vue3-project
cd vue3-project
npm install
```
##### 创建Vue2风格组件 src/components/TodoListVue2.vue(模拟旧代码)
vue
```vue
<template>
<div class="todo-list">
<h2>待办事项</h2>
<div class="add-todo">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="添加新任务"
/>
<button @click="addTodo">添加</button>
</div>
<ul>
<li
v-for="todo in filteredTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input type="checkbox" v-model="todo.completed" />
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<div class="filters">
<button @click="filter = 'all'">全部</button>
<button @click="filter = 'active'">未完成</button>
<button @click="filter = 'completed'">已完成</button>
</div>
<p>统计:{{ stats }}</p>
</div>
</template>
<script>
// Vue2 Options API 风格
export default {
name: 'TodoListVue2',
data() {
return {
newTodo: '',
todos: [],
filter: 'all',
nextId: 1,
}
},
computed: {
filteredTodos() {
if (this.filter === 'active') {
return this.todos.filter((todo) => !todo.completed)
}
if (this.filter === 'completed') {
return this.todos.filter((todo) => todo.completed)
}
return this.todos
},
stats() {
const total = this.todos.length
const completed = this.todos.filter((t) => t.completed).length
return `总计:${total},已完成:${completed}`
},
},
methods: {
addTodo() {
if (this.newTodo.trim()) {
this.todos.push({
id: this.nextId++,
text: this.newTodo,
completed: false,
})
this.newTodo = ''
}
},
removeTodo(id) {
this.todos = this.todos.filter((todo) => todo.id !== id)
},
},
mounted() {
console.log('组件已挂载')
},
watch: {
todos: {
handler(newVal) {
localStorage.setItem('todos', JSON.stringify(newVal))
},
deep: true,
},
},
created() {
const saved = localStorage.getItem('todos')
if (saved) {
this.todos = JSON.parse(saved)
this.nextId = Math.max(...this.todos.map((t) => t.id), 0) + 1
}
},
}
</script>
<style scoped>
.todo-list {
max-width: 500px;
margin: 20px auto;
padding: 20px;
}
.add-todo {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
input[type='text'] {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
ul {
list-style: none;
padding: 0;
}
li {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
}
li.completed span {
text-decoration: line-through;
color: #999;
}
.filters {
margin-top: 20px;
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #f0f0f0;
}
</style>
```
### 步骤2:在Claude中使用Prompt迁移
#### 在Claude对话框中输入
```text
请将以下Vue2 Options API组件迁移为Vue3 Composition API,要求:
1. 使用 <script setup lang="ts">
2. data → ref/reactive
3. methods → 普通函数
4. computed → computed()
5. watch → watch()
6. 生命周期钩子对应转换(mounted → onMounted)
7. 添加完整的TypeScript类型定义
8. 优化性能(使用toRefs等)
9. 保持原有功能不变
[粘贴上面的TodoListVue2.vue完整代码]
```
### 步骤3:Claude生成的Vue3版本
#### src/components/TodoListVue3.vue
vue
```vue
<template>
<div class="todo-list">
<h2>待办事项(Vue3版本)</h2>
<div class="add-todo">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="添加新任务"
/>
<button @click="addTodo">添加</button>
</div>
<ul>
<li
v-for="todo in filteredTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input type="checkbox" v-model="todo.completed" />
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<div class="filters">
<button
v-for="f in filters"
:key="f.value"
:class="{ active: filter === f.value }"
@click="filter = f.value"
>
{{ f.label }}
</button>
</div>
<p>{{ stats }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted } from 'vue'
// 类型定义
interface Todo {
id: number
text: string
completed: boolean
}
type FilterType = 'all' | 'active' | 'completed'
interface Filter {
value: FilterType
label: string
}
// 响应式状态
const newTodo = ref('')
const todos = ref<Todo[]>([])
const filter = ref<FilterType>('all')
const nextId = ref(1)
// 过滤器选项
const filters: Filter[] = [
{ value: 'all', label: '全部' },
{ value: 'active', label: '未完成' },
{ value: 'completed', label: '已完成' },
]
// 计算属性
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active':
return todos.value.filter((todo) => !todo.completed)
case 'completed':
return todos.value.filter((todo) => todo.completed)
default:
return todos.value
}
})
const stats = computed(() => {
const total = todos.value.length
const completed = todos.value.filter((t) => t.completed).length
const active = total - completed
return `总计:${total},已完成:${completed},未完成:${active}`
})
// 方法
const addTodo = () => {
const text = newTodo.value.trim()
if (text) {
todos.value.push({
id: nextId.value++,
text,
completed: false,
})
newTodo.value = ''
}
}
const removeTodo = (id: number) => {
todos.value = todos.value.filter((todo) => todo.id !== id)
}
const loadTodos = () => {
const saved = localStorage.getItem('todos-vue3')
if (saved) {
try {
const parsed = JSON.parse(saved)
todos.value = parsed
nextId.value = Math.max(...parsed.map((t: Todo) => t.id), 0) + 1
} catch (e) {
console.error('加载待办事项失败', e)
}
}
}
const saveTodos = () => {
localStorage.setItem('todos-vue3', JSON.stringify(todos.value))
}
// 监听器
watch(
todos,
() => {
saveTodos()
},
{ deep: true }
)
// 生命周期
onMounted(() => {
loadTodos()
console.log('Vue3组件已挂载')
})
// 暴露给模板(可选,用于调试)
defineExpose({
todos,
addTodo,
removeTodo,
})
</script>
<style scoped>
.todo-list {
max-width: 500px;
margin: 20px auto;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.add-todo {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
input[type='text'] {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
input[type='text']:focus {
outline: none;
border-color: #3b82f6;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
display: flex;
align-items: center;
gap: 10px;
padding: 12px;
border-bottom: 1px solid #f0f0f0;
transition: background 0.2s;
}
li:hover {
background: #f9f9f9;
}
li.completed span {
text-decoration: line-through;
color: #999;
}
li span {
flex: 1;
}
.filters {
margin-top: 20px;
display: flex;
gap: 10px;
}
.filters button {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
background: white;
transition: all 0.2s;
}
.filters button:hover {
background: #f0f0f0;
}
.filters button.active {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
button {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
background: white;
}
button:hover {
background: #f0f0f0;
}
p {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
color: #666;
font-size: 14px;
}
</style>
```
### 步骤4:性能优化版本(进阶)
#### src/components/TodoListOptimized.vue
vue
```vue
<script setup lang="ts">
import { ref, computed, watch, onMounted, toRefs } from 'vue'
interface Todo {
id: number
text: string
completed: boolean
}
type FilterType = 'all' | 'active' | 'completed'
// 使用 composable 抽离逻辑
const useTodoStorage = () => {
const STORAGE_KEY = 'todos-optimized'
const load = (): Todo[] => {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
try {
return JSON.parse(saved)
} catch (e) {
console.error('加载失败', e)
}
}
return []
}
const save = (todos: Todo[]) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
}
return { load, save }
}
const useTodos = () => {
const { load, save } = useTodoStorage()
const todos = ref<Todo[]>(load())
const nextId = ref(
todos.value.length > 0
? Math.max(...todos.value.map((t) => t.id)) + 1
: 1
)
const addTodo = (text: string) => {
if (text.trim()) {
todos.value.push({
id: nextId.value++,
text: text.trim(),
completed: false,
})
}
}
const removeTodo = (id: number) => {
const index = todos.value.findIndex((t) => t.id === id)
if (index > -1) {
todos.value.splice(index, 1)
}
}
const toggleTodo = (id: number) => {
const todo = todos.value.find((t) => t.id === id)
if (todo) {
todo.completed = !todo.completed
}
}
// 自动保存
watch(todos, () => save(todos.value), { deep: true })
return {
todos,
addTodo,
removeTodo,
toggleTodo,
}
}
// 使用 composables
const { todos, addTodo, removeTodo, toggleTodo } = useTodos()
const newTodo = ref('')
const filter = ref<FilterType>('all')
// 计算属性
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active':
return todos.value.filter((t) => !t.completed)
case 'completed':
return todos.value.filter((t) => t.completed)
default:
return todos.value
}
})
const stats = computed(() => {
const total = todos.value.length
const completed = todos.value.filter((t) => t.completed).length
return {
total,
completed,
active: total - completed,
}
})
// 方法
const handleAddTodo = () => {
addTodo(newTodo.value)
newTodo.value = ''
}
onMounted(() => {
console.log('优化版组件已挂载')
})
</script>
<template>
<div class="todo-list-optimized">
<h2>待办事项(性能优化版)</h2>
<div class="add-todo">
<input
v-model="newTodo"
@keyup.enter="handleAddTodo"
placeholder="添加新任务"
/>
<button @click="handleAddTodo">添加</button>
</div>
<ul>
<li
v-for="todo in filteredTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo.id)"
/>
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<div class="filters">
<button
:class="{ active: filter === 'all' }"
@click="filter = 'all'"
>
全部
</button>
<button
:class="{ active: filter === 'active' }"
@click="filter = 'active'"
>
未完成
</button>
<button
:class="{ active: filter === 'completed' }"
@click="filter = 'completed'"
>
已完成
</button>
</div>
<div class="stats">
<span>总计:{{ stats.total }}</span>
<span>已完成:{{ stats.completed }}</span>
<span>未完成:{{ stats.active }}</span>
</div>
</div>
</template>
<style scoped>
/* 样式同上,这里省略 */
.stats {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
display: flex;
gap: 20px;
font-size: 14px;
color: #666;
}
</style>
```
### 步骤5:对比测试页面
#### src/App.vue
vue
```vue
<template>
<div class="app">
<h1>Vue2 vs Vue3 迁移对比</h1>
<div class="comparison">
<div class="column">
<TodoListVue2 />
</div>
<div class="column">
<TodoListVue3 />
</div>
<div class="column">
<TodoListOptimized />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import TodoListVue2 from './components/TodoListVue2.vue'
import TodoListVue3 from './components/TodoListVue3.vue'
import TodoListOptimized from './components/TodoListOptimized.vue'
</script>
<style>
.app {
padding: 20px;
max-width: 1600px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 30px;
}
.comparison {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
}
.column {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 15px;
background: #fafafa;
}
</style>
```
### 步骤6:运行项目
bash
```bash
# 确保在项目目录
cd vue3-project
# 安装依赖(如果还没安装)
npm install
# 运行项目
npm run dev
# 访问 http://localhost:5173
```
### 步骤7:性能对比测试
在浏览器开发者工具中:
1. **打开 Performance 面板**
2. **录制交互过程**(添加/删除任务)
3. **对比三个版本的性能差异**
#### 预期结果
- Vue3 Composition API 版本:更好的类型推导
- 优化版本:更少的重渲染,更好的代码组织