引言:为什么AI总是“听不懂人话”?
作为一名前端开发者,我经常需要使用AI辅助编写代码。在使用过程中,我常常遇到这样的情况: 我的提问:“帮我写一个Vue3的用户表格组件”。
AI的回复:一个很简单的表格,原生组件,没有样式,没有搜索功能,没有分页...
这就好比我们去餐厅说:“我要吃饭”,结果服务员端来一碗白米饭——虽然没错,但完全不是我们想要的结果!
后来经过不断探索,我发现问题不在AI,而在于我的提问方式。今天,我将以一个Vue3组件生成的实战案例,带领我们掌握结构化Prompt的核心技巧,让AI真正理解我们的需求,输出可直接使用的专业代码。
Prompt四大要素:让AI听懂我们的“弦外之音”
在深入实战前,我们先理解结构化Prompt的四大核心要素:
1. 角色
让AI扮演特定身份的专家,激活其在该领域的专业知识。
- ❌ 普通提问:“帮我写代码”
- ✅ 角色设定:“你是一名资深Vue3架构师”
2. 上下文
提供背景信息,让AI理解使用场景和技术栈限制。
- ❌ 普通提问:“写一个表格组件”
- ✅ 上下文:“项目使用Element Plus组件库,需要TypeScript支持”
3. 任务
明确具体的功能需求,越详细越好。
- ❌ 普通提问:“带功能的表格”
- ✅ 任务:“带有搜索和分页功能的用户表格组件”
4. 输出格式
规定输出的结构、语法规范,减少后期修改成本。
- ❌ 普通提问:“用Vue3写”
- ✅ 输出格式:“必须使用
<script setup lang="ts">语法,附带Props和Emits的类型定义”
掌握了这四大要素,我们来看看实际效果对比!
开放式提问vs结构化提问:天壤之别的输出效果
版本1:开放式提问(小白常见写法)
我的Prompt:
帮我写一个Vue3的用户表格组件
AI输出::
<template>
<div>
<table>
<tr v-for="user in users" :key="user.id">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
data() {
return {
users: []
}
},
mounted() {
// 模拟数据
this.users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com' }
]
}
}
</script>
问题分析:
- 使用了原生table,而不是Element Plus
- 没有搜索和分页功能
- 使用Options API而非
<script setup> - 没有TypeScript类型定义
- 代码风格陈旧,无法直接用于项目
版本2:初步结构化(添加了基本要求)
我的Prompt:
你是一名Vue3工程师,请帮我用Element Plus组件库写一个用户表格组件,
需要包含搜索和分页功能,使用TypeScript和<script setup>语法。
AI输出:
<template>
<div>
<el-input
v-model="searchText"
placeholder="搜索用户"
@input="handleSearch"
/>
<el-table :data="tableData">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="email" label="邮箱" />
</el-table>
<el-pagination
:current-page="currentPage"
:page-size="pageSize"
:total="total"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const searchText = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const tableData = ref([])
const handleSearch = () => {
// 搜索逻辑
}
const handleCurrentChange = (page: number) => {
currentPage.value = page
// 加载数据逻辑
}
</script>
进步:
- 使用了Element Plus组件
- 采用了
<script setup lang="ts">语法 - 包含了搜索和分页的基础结构
不足:
- 没有Props和Emits类型定义
- 搜索和分页是独立的,没有联动
- 数据类型未定义,代码不完整
版本3:完整结构化Prompt(专业级输出)
我的Prompt:
# 角色
你是一名资深Vue3架构师,精通Element Plus组件库和TypeScript。
# 上下文
我正在开发一个后台管理系统,技术栈为:Vue3 + TypeScript + Element Plus。
现在需要创建一个可复用的用户表格组件,用于用户管理页面。
# 任务
请帮我生成一个带有搜索和分页功能的用户表格组件,具体要求:
1. 功能需求:
- 支持通过用户名或邮箱搜索用户
- 支持分页显示(每页10条)
- 表格列包括:姓名、邮箱、角色、注册时间、操作(编辑和删除按钮)
- 搜索和分页状态需要联动(改变搜索条件时重置到第一页)
2. 数据交互:
- 通过Props接收外部传入的用户数据
- 通过Emits向父组件传递:搜索条件变化、分页变化、编辑事件、删除事件
- 组件内部不负责数据获取,只负责展示和交互
# 输出格式要求
1. 必须使用<script setup>语法
2. 完整的TypeScript类型定义(包括Props和Emits接口)
3. 代码必须包含必要的注释
4. 可以直接复制到Vue3 + Element Plus项目中运行
AI的最终输出:
<!--
* 可复用用户表格组件
* 功能:支持搜索、分页、编辑、删除
* 数据流:纯展示组件,所有数据通过Props传入,事件通过Emits抛出
-->
<template>
<div class="user-table-container">
<!-- 搜索区域 -->
<el-input
v-model="searchText"
placeholder="搜索用户名或邮箱"
:prefix-icon="Search"
clearable
@input="handleSearchInput"
@clear="handleSearchInput"
class="search-input"
/>
<!-- 表格区域 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="name" label="姓名" min-width="120" />
<el-table-column prop="email" label="邮箱" min-width="180" />
<el-table-column prop="role" label="角色" min-width="100">
<template #default="{ row }">
<el-tag :type="row.role === 'admin' ? 'danger' : 'primary'">
{{ row.role === 'admin' ? '管理员' : '普通用户' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="registerTime" label="注册时间" min-width="160" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">
编辑
</el-button>
<el-button type="danger" link @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 30, 50]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
class="pagination"
/>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { Search } from '@element-plus/icons-vue'
import type { PropType } from 'vue'
// ==================== 类型定义 ====================
/** 用户数据类型 */
export interface User {
id: number | string
name: string
email: string
role: 'admin' | 'user'
registerTime: string // ISO 格式日期字符串
}
/** 搜索参数类型 */
export interface SearchParams {
keyword: string
page: number
pageSize: number
}
// ==================== Props 定义 ====================
const props = defineProps({
/** 表格数据数组 */
data: {
type: Array as PropType<User[]>,
required: true,
default: () => []
},
/** 数据总条数 */
total: {
type: Number,
required: true,
default: 0
},
/** 加载状态 */
loading: {
type: Boolean,
default: false
},
/** 默认每页条数 */
defaultPageSize: {
type: Number,
default: 10
}
})
// ==================== Emits 定义 ====================
const emit = defineEmits<{
/** 搜索条件变化 */
(e: 'search-change', params: SearchParams): void
/** 编辑用户 */
(e: 'edit', user: User): void
/** 删除用户 */
(e: 'delete', user: User): void
}>()
// ==================== 响应式状态 ====================
const searchText = ref('')
const currentPage = ref(1)
const pageSize = ref(props.defaultPageSize)
// 用于展示的表格数据(直接使用props.data,但可以通过计算属性进行格式化)
const tableData = computed(() => props.data)
// ==================== 搜索防抖处理 ====================
let searchTimer: ReturnType<typeof setTimeout> | null = null
const handleSearchInput = () => {
if (searchTimer) clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
// 重置到第一页
currentPage.value = 1
// 触发搜索事件
emitSearch()
}, 300)
}
// ==================== 分页处理 ====================
const handleSizeChange = (size: number) => {
pageSize.value = size
currentPage.value = 1 // 改变每页条数时重置到第一页
emitSearch()
}
const handleCurrentChange = (page: number) => {
currentPage.value = page
emitSearch()
}
// ==================== 通用搜索事件触发 ====================
const emitSearch = () => {
emit('search-change', {
keyword: searchText.value,
page: currentPage.value,
pageSize: pageSize.value
})
}
// ==================== 操作事件 ====================
const handleEdit = (user: User) => {
emit('edit', user)
}
const handleDelete = (user: User) => {
emit('delete', user)
}
// ==================== 监听外部数据变化 ====================
// 当外部传入的total变化时,可能需要调整当前页
watch(() => props.total, (newTotal) => {
const maxPage = Math.ceil(newTotal / pageSize.value)
if (currentPage.value > maxPage && maxPage > 0) {
currentPage.value = maxPage
emitSearch()
}
})
</script>
<style scoped>
.user-table-container {
padding: 20px;
background-color: #fff;
border-radius: 4px;
}
.search-input {
width: 300px;
margin-bottom: 20px;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>
配套父组件示例:如何优雅地使用这个组件
为了让我们更好地理解如何使用这个组件,AI还贴心地提供了一个示例父组件:
<template>
<div>
<h2>用户管理</h2>
<UserTable
:data="userList"
:total="total"
:loading="loading"
@search-change="handleSearch"
@edit="handleEdit"
@delete="handleDelete"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import UserTable from './components/UserTable.vue'
import type { User, SearchParams } from './components/UserTable.vue'
// 状态
const userList = ref<User[]>([])
const total = ref(0)
const loading = ref(false)
// 模拟获取数据
const fetchUsers = async (params: SearchParams) => {
loading.value = true
try {
// 模拟API请求
await new Promise(resolve => setTimeout(resolve, 500))
// 模拟数据
const mockData: User[] = Array.from({ length: 50 }, (_, index) => ({
id: index + 1,
name: `用户${index + 1}`,
email: `user${index + 1}@example.com`,
role: index % 5 === 0 ? 'admin' : 'user',
registerTime: new Date(Date.now() - index * 86400000).toISOString()
}))
// 模拟搜索过滤
let filtered = mockData
if (params.keyword) {
filtered = mockData.filter(user =>
user.name.includes(params.keyword) ||
user.email.includes(params.keyword)
)
}
// 模拟分页
const start = (params.page - 1) * params.pageSize
const end = start + params.pageSize
userList.value = filtered.slice(start, end)
total.value = filtered.length
} finally {
loading.value = false
}
}
// 搜索处理
const handleSearch = (params: SearchParams) => {
console.log('搜索参数:', params)
fetchUsers(params)
}
// 编辑处理
const handleEdit = (user: User) => {
console.log('编辑用户:', user)
// 打开编辑对话框等操作
}
// 删除处理
const handleDelete = (user: User) => {
console.log('删除用户:', user)
// 确认删除对话框等操作
}
// 初始化
onMounted(() => {
fetchUsers({
keyword: '',
page: 1,
pageSize: 10
})
})
</script>
为什么结构化Prompt如此有效?
对比三个版本的输出,我们可以总结出结构化Prompt的优势:
1. 角色设定激活专业知识
让AI扮演“资深Vue3架构师”,它会自动调用最佳实践:组件拆分、Props/Emits设计、防抖处理等。
2. 上下文提供技术约束
明确“Element Plus + TS”后,AI会主动使用el-table、el-pagination,并生成完整的类型定义。
3. 任务分解确保功能完整
逐条列出功能需求,AI不会遗漏任何细节:搜索防抖、分页联动、加载状态、操作按钮等。
4. 输出格式降低使用成本
要求“可直接复制运行”,AI会考虑实际使用场景:添加必要的样式、导出类型接口、编写注释。
进阶技巧:让结构化Prompt更强大
掌握了基础四大要素后,我们还可以在Prompt中添加这些“调料”:
1. 添加示例
类似 Ant Design Pro 的表格风格,带操作栏和状态标签
2. 指定边界情况
需要考虑搜索为空时的显示状态,分页数据不足时的处理
3. 要求解释思路
请在代码注释中解释关键设计决策,为什么这样实现
4. 多轮迭代优化
第一轮:生成基础结构 第二轮:优化交互体验 第三轮:添加单元测试 第四轮:....
我们的Prompt决定了AI的上限
通过这个Vue3组件的实战案例,我们可以看到:
- 开放式提问 → 得到基础、不完整的代码
- 结构化提问 → 获得专业、可直接使用的代码
记住这个公式:
结语
现在,我们已经掌握了结构化Prompt的核心技巧。下次使用AI时,不妨多花30秒思考:我的Prompt包含了四大要素吗?到时候就会发现,AI的输出质量会有质的飞跃!
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!