结构化Prompt——让AI说“人话”

51 阅读7分钟

引言:为什么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的输出质量会有质的飞跃!

对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!