news 2026/6/3 8:00:57

后端使用 AI 开发前端速成:第三期:Vue 3 深入实战 —— 列表页开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
后端使用 AI 开发前端速成:第三期:Vue 3 深入实战 —— 列表页开发

第三期:Vue 3 深入实战 —— 列表页开发

本期目标:掌握 Vue 3 Composition API,独立完成标准管理后台列表页
核心理念:Vue 页面的逻辑 = 后端 Controller 层 —— 定义状态 → 定义方法 → 在合适时机调用
产出物:一个可运行的用户列表页(搜索 + 表格 + 分页 + 删除确认)+ 手写 Pinia store


目录

  • 第一章:为什么先学 Vue
  • 第二章:Vue 3 五个核心概念深入
  • 第三章:Element Plus 组件库实战
  • 第四章:Pinia 状态管理
  • 第五章:实战——用户管理列表页
  • 第六章:AI Prompt 模板与审查清单
  • 第七章:课后作业

第一章:为什么先学 Vue

Vue 对后端工程师更友好

方面Vue 3React 18
模板语法接近 HTML,直觉性强JSX = JS,初期会晕
条件渲染v-if就像 if 语句{condition && <X />}
列表渲染v-for就像 for 循环.map()
双向绑定v-model一行搞定受控组件要写 onChange
响应式自动追踪依赖手动管理依赖数组

Vue 的心智模型

数据变化 → Vue 自动检测 → 自动更新界面

这就像观察者模式:你改了数据,Vue 像是一个自动通知系统,帮你刷新界面。

后端类比

  • ref/reactive≈ 类的成员变量
  • computed≈ 数据库视图(自动缓存)
  • watch≈ 触发器 / MQ 消费者
  • onMounted≈ 构造函数 / @PostConstruct

第二章:Vue 3 五个核心概念深入

2.1 ref —— 管理基础类型状态

<script setup> import { ref } from 'vue' // ref 返回一个响应式对象,通过 .value 访问和修改 const count = ref(0) const loading = ref(false) const keyword = ref('') // 修改时必须用 .value count.value++ loading.value = true </script> <template> <!-- 模板中自动解包,不需要 .value --> <p>{{ count }}</p> <button @click="count++">+1</button> </template>

何时用 ref

  • 基础类型(string、number、boolean)
  • 需要替换整个对象的场景(如接口返回的数组)

常见错误

// ❌ 错误:解构 ref 会失去响应式const{value}=count// ❌ 错误:直接赋值不会触发更新count=1// 这是给变量赋值,不是修改 .value// ✅ 正确:通过 .value 修改count.value=1

2.2 reactive —— 管理对象类型状态

<script setup> import { reactive } from 'vue' // reactive 的对象可以直接修改属性,不需要 .value const form = reactive({ keyword: '', status: '', dateRange: [] }) // 直接修改属性 form.keyword = '张三' form.status = 'active' </script>

何时用 reactive

  • 表单数据(天然就是对象结构)
  • 配置对象、状态集合

注意点

// ❌ 错误:解构 reactive 对象会失去响应式const{keyword}=form// keyword 不再是响应式的// ✅ 正确:使用 toRefsimport{toRefs}from'vue'const{keyword}=toRefs(form)// keyword 仍然是 ref// ❌ 错误:直接赋值新对象会断开响应式form={keyword:'',status:''}// ✅ 正确:用 Object.assign 或直接改属性Object.assign(form,{keyword:'',status:''})

2.3 computed —— 派生状态(自动缓存)

<script setup> import { ref, computed } from 'vue' const firstName = ref('张') const lastName = ref('三') // 自动缓存,只有依赖变化时才重新计算 const fullName = computed(() => { console.log('重新计算 fullName') // 只在 firstName/lastName 变化时执行 return firstName.value + lastName.value }) // 带 setter 的 computed const displayName = computed({ get: () => `${lastName.value}先生`, set: (val) => { // val = '李先生' → 解析出 '李' lastName.value = val.charAt(0) } }) </script>

使用场景

  • 表格数据的格式化显示
  • 基于搜索条件的过滤结果
  • 分页信息的文字描述(如"第 1 页,共 10 页")

2.4 watch —— 监听器

<script setup> import { ref, watch } from 'vue' const searchKeyword = ref('') const form = reactive({ keyword: '', status: '' }) // 监听 ref watch(searchKeyword, (newVal, oldVal) => { console.log('搜索词从', oldVal, '变成', newVal) fetchData() // 自动触发搜索 }) // 监听 reactive 对象的单个属性 watch(() => form.status, (newVal) => { console.log('状态变化:', newVal) }) // 监听整个 reactive 对象(深度监听) watch(form, () => { console.log('form 任何属性变化') }, { deep: true }) // 立即执行一次 watch(searchKeyword, () => { fetchData() }, { immediate: true }) </script>

使用场景

  • 搜索条件变化自动触发查询
  • 路由参数变化重新加载数据
  • 表单数据变化自动保存草稿

注意事项

// ❌ 错误:监听 reactive 对象不加函数包裹watch(form,...)// 这样只能监听引用变化// ✅ 正确:监听 reactive 对象的属性watch(()=>form.keyword,...)// ✅ 或者深度监听watch(form,...,{deep:true})

2.5 生命周期钩子

钩子后端类比用途
onMounted@PostConstruct页面加载完成,发起初始请求
onUnmounted@PreDestroy页面销毁,清理定时器/WebSocket
onUpdated-数据更新后操作 DOM(少用)
<script setup> import { onMounted, onUnmounted } from 'vue' let timer = null let controller = null onMounted(() => { fetchData() // 页面加载完自动请求数据 timer = setInterval(() => { ... }, 5000) // 开启定时器 }) onUnmounted(() => { clearInterval(timer) // 必须清理,否则内存泄漏 controller?.abort() // 取消未完成的请求 }) </script>

核心洞察

Vue 页面的逻辑和后端 Controller 层几乎一样: 定义状态变量 → 定义业务方法 → 在合适的时机调用 模板部分只是数据的"呈现层"

第三章:Element Plus 组件库实战

3.1 管理后台最常用的 10 个组件

组件用途使用频率
el-form+el-form-item表单布局⭐⭐⭐⭐⭐
el-input文本输入⭐⭐⭐⭐⭐
el-select+el-option下拉选择⭐⭐⭐⭐⭐
el-button按钮⭐⭐⭐⭐⭐
el-table+el-table-column数据表格⭐⭐⭐⭐⭐
el-pagination分页⭐⭐⭐⭐⭐
el-dialog弹窗⭐⭐⭐⭐
el-tag标签⭐⭐⭐⭐
el-date-picker日期选择⭐⭐⭐
el-message/el-message-box提示/确认框⭐⭐⭐

3.2 表单组件

<template> <el-form :model="searchForm" inline> <el-form-item label="关键词"> <el-input v-model="searchForm.keyword" placeholder="请输入用户名" clearable /> </el-form-item> <el-form-item label="状态"> <el-select v-model="searchForm.status" placeholder="请选择" clearable> <el-option label="全部" value="" /> <el-option label="启用" value="active" /> <el-option label="禁用" value="inactive" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSearch">搜索</el-button> <el-button @click="handleReset">重置</el-button> </el-form-item> </el-form> </template> <script setup> import { reactive } from 'vue' const searchForm = reactive({ keyword: '', status: '' }) const handleSearch = () => { console.log('搜索条件:', searchForm) } const handleReset = () => { searchForm.keyword = '' searchForm.status = '' } </script>

关键知识点

  • :model="searchForm":绑定表单数据对象
  • v-model="searchForm.keyword":双向绑定
  • inline:表单项横向排列
  • clearable:显示清空按钮

3.3 表格组件

<template> <el-table :data="tableData" v-loading="loading" row-key="id"> <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="name" label="用户名" /> <el-table-column prop="status" label="状态"> <!-- 自定义列内容 --> <template #default="{ row }"> <el-tag :type="row.status === 'active' ? 'success' : 'danger'"> {{ row.status === 'active' ? '启用' : '禁用' }} </el-tag> </template> </el-table-column> <el-table-column label="操作" width="150"> <template #default="{ row }"> <el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button> <el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button> </template> </el-table-column> </el-table> </template>

关键知识点

  • :data="tableData":绑定表格数据源
  • v-loading="loading":加载状态
  • row-key="id":行唯一标识(用于展开行和树形数据)
  • #default="{ row }":自定义列内容,row 是当前行数据

3.4 分页组件

<template> <el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" @change="handlePageChange" /> </template> <script setup> import { reactive } from 'vue' const pagination = reactive({ page: 1, pageSize: 10, total: 0 }) const handlePageChange = () => { fetchData() } </script>

第四章:Pinia 状态管理

4.1 为什么需要 Pinia

管理后台中,多个页面需要共享状态:

  • 用户信息(登录态、权限)
  • 系统配置(主题、语言)
  • 缓存数据(字典表、下拉选项)

后端类比:Pinia ≈ 后端的 Service 层,管理全局状态。

4.2 定义 Store

// stores/user.tsimport{defineStore}from'pinia'import{ref,computed}from'vue'// 命名规范:useXxxStoreexportconstuseUserStore=defineStore('user',()=>{// ========== State ==========consttoken=ref(localStorage.getItem('token')||'')constuserInfo=ref(null)// ========== Getter(computed)==========constisLoggedIn=computed(()=>!!token.value)constuserName=computed(()=>userInfo.value?.name||'未登录')// ========== Action ==========constsetToken=(newToken:string)=>{token.value=newToken localStorage.setItem('token',newToken)}constsetUserInfo=(info:any)=>{userInfo.value=info}constlogout=()=>{token.value=''userInfo.value=nulllocalStorage.removeItem('token')}// 必须返回所有要暴露的状态和方法return{token,userInfo,isLoggedIn,userName,setToken,setUserInfo,logout}})

4.3 在组件中使用

<script setup> import { useUserStore } from '@/stores/user' // 获取 store 实例 const userStore = useUserStore() // 直接使用 console.log(userStore.isLoggedIn) console.log(userStore.token) // 调用 action userStore.setToken('xxx') userStore.logout() // 解构(需要用 storeToRefs 保持响应式) import { storeToRefs } from 'pinia' const { token, userInfo } = storeToRefs(userStore) // 现在 token.value 是响应式的 </script>

第五章:实战——用户管理列表页

5.1 需求描述

开发一个完整的用户管理列表页,包含:

  1. 搜索区域:用户名输入框、状态下拉框、搜索按钮、重置按钮
  2. 数据表格:ID、用户名、邮箱、状态(Tag 组件)、创建时间、操作列
  3. 分页功能
  4. 删除操作需要二次确认
  5. 页面加载时自动请求数据

5.2 环境准备

npmcreate vite@latest vue-admin ----templatevue-tscdvue-adminnpminstallelement-plus axios vue-router pinianpminstall-D@types/nodenpmrun dev

5.3 给 AI 的 Prompt

请帮我写一个 Vue 3.4 + TypeScript + Element Plus 的用户管理列表页。 技术栈: - Vue 3.4 Composition API + <script setup> - TypeScript(严格模式,不要用 any) - Element Plus 组件库 - Axios 请求 功能需求: 1. 搜索区域:用户名输入框、状态下拉框、搜索按钮、重置按钮 2. 表格展示:ID、用户名、邮箱、状态(用 Tag 组件)、创建时间、操作列(编辑/删除按钮) 3. 分页功能(支持页码和页大小切换) 4. 页面加载时自动请求数据 5. 删除操作需要二次确认(用 ElMessageBox) 接口定义: GET /api/users 参数:{ page: number, pageSize: number, keyword?: string, status?: string } 返回:{ code: number, data: { list: User[], total: number }, message: string } TypeScript 类型: interface User { id: number name: string email: string status: 'active' | 'inactive' createdAt: string } 状态管理要求: - 使用 ref 和 reactive 管理状态 - 搜索表单用 reactive - 表格数据用 ref 输出要求: - 使用 Element Plus 的 el-table、el-pagination、el-form 等组件 - 表格需要 loading 状态 - 空数据时显示 "暂无数据" - 代码注释用中文 - 将组件保存为 src/views/UserList.vue

5.4 AI 生成后的审查清单

技术栈检查

  • <script setup>不是export default
  • 使用了refreactive
  • 没有使用any

功能检查

  • 搜索时页码重置为 1
  • 重置时清空搜索条件并重新请求
  • 表格有 loading 状态
  • 空数据显示 Empty 组件
  • 删除有二次确认

安全与性能

  • 没有使用v-html
  • 定时器/WebSocket 在 onUnmounted 中清理

5.5 完整代码参考

<template> <div class="user-list-page"> <!-- 搜索表单 --> <el-card class="search-card"> <el-form :model="searchForm" inline> <el-form-item label="关键词"> <el-input v-model="searchForm.keyword" placeholder="请输入用户名" clearable @keyup.enter="handleSearch" /> </el-form-item> <el-form-item label="状态"> <el-select v-model="searchForm.status" placeholder="请选择" clearable> <el-option label="全部" value="" /> <el-option label="启用" value="active" /> <el-option label="禁用" value="inactive" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button> <el-button :icon="RefreshRight" @click="handleReset">重置</el-button> </el-form-item> </el-form> </el-card> <!-- 数据表格 --> <el-card class="table-card"> <el-table :data="tableData" v-loading="loading" row-key="id"> <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="name" label="用户名" min-width="120" /> <el-table-column prop="email" label="邮箱" min-width="180" /> <el-table-column prop="status" label="状态" width="100"> <template #default="{ row }"> <el-tag :type="row.status === 'active' ? 'success' : 'danger'"> {{ row.status === 'active' ? '启用' : '禁用' }} </el-tag> </template> </el-table-column> <el-table-column prop="createdAt" label="创建时间" min-width="160" /> <el-table-column label="操作" width="150" fixed="right"> <template #default="{ row }"> <el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button> <el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" @change="handlePageChange" /> </el-card> </div> </template> <script setup lang="ts"> import { ref, reactive, onMounted } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { Search, RefreshRight } from '@element-plus/icons-vue' import axios from 'axios' // ========== TypeScript 类型定义 ========== interface User { id: number name: string email: string status: 'active' | 'inactive' createdAt: string } interface ApiResponse<T> { code: number data: T message: string } // ========== 状态定义 ========== const loading = ref(false) const tableData = ref<User[]>([]) const searchForm = reactive({ keyword: '', status: '' }) const pagination = reactive({ page: 1, pageSize: 10, total: 0 }) // ========== 方法定义 ========== const fetchData = async () => { loading.value = true try { const { data } = await axios.get<ApiResponse<{ list: User[]; total: number }>>('/api/users', { params: { page: pagination.page, pageSize: pagination.pageSize, ...searchForm } }) tableData.value = data.data.list pagination.total = data.data.total } catch (error) { ElMessage.error('获取数据失败') console.error(error) } finally { loading.value = false } } const handleSearch = () => { pagination.page = 1 fetchData() } const handleReset = () => { searchForm.keyword = '' searchForm.status = '' handleSearch() } const handlePageChange = () => { fetchData() } const handleEdit = (row: User) => { console.log('编辑用户:', row) // TODO: 跳转到编辑页或打开编辑弹窗 } const handleDelete = async (row: User) => { try { await ElMessageBox.confirm( `确定要删除用户 "${row.name}" 吗?`, '删除确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' } ) await axios.delete(`/api/users/${row.id}`) ElMessage.success('删除成功') fetchData() // 刷新列表 } catch (error: any) { if (error !== 'cancel') { ElMessage.error('删除失败') } } } // ========== 生命周期 ========== onMounted(() => { fetchData() }) </script> <style scoped> .user-list-page { padding: 20px; } .search-card { margin-bottom: 20px; } .table-card { margin-bottom: 20px; } .el-pagination { margin-top: 20px; justify-content: flex-end; } </style>

第六章:AI Prompt 模板与审查清单

6.1 本期专用 Prompt 模板

模板:Vue 列表页生成

请用 Vue 3.4 + TypeScript + Element Plus 写一个 [模块名] 管理列表页。 功能需求: 1. 搜索区域:[字段列表] 2. 表格展示:[字段列表] 3. 分页功能(支持页码和页大小切换) 4. 操作列:[编辑/删除/详情] 5. [批量操作/其他功能] 接口定义: GET [接口地址] 参数:{ page, pageSize, ... } 返回:{ code, data: { list, total }, message } TypeScript 类型: interface [Entity] { [字段定义] } 输出要求: - 使用 <script setup> + Composition API - 使用 ref 和 reactive 管理状态 - 严格 TypeScript,禁止 any - 处理 loading、error、empty 三种状态 - 代码注释中文

6.2 AI 代码审查清单

检查项合格标准检查方法
技术栈Vue 3 + TS + Element Plus看文件头部 import
状态管理用 ref/reactive,没有用选项式 API

第七章:实战

7.1 必做实战

作业 1:手写 Pinia Store

实现一个useDictStore,管理系统的字典数据(如用户状态、订单状态等):

// 要求:// 1. state:dictMap(Record<string, any[]>),存储各类字典数据// 2. action:fetchDict(type: string),从 /api/dict/:type 获取字典数据并缓存// 3. getter:getDict(type: string),获取指定类型的字典,如果不存在自动调用 fetchDict// 4. 使用 localStorage 做持久化

作业 2:扩展用户列表页

在课堂代码基础上,增加以下功能:

  1. 搜索条件增加"创建时间范围"(使用 el-date-picker)
  2. 表格增加"批量删除"功能(使用 el-table 的 selection 列)
  3. 操作列增加"查看详情"按钮,点击后弹出详情弹窗(el-dialog)

7.2 检验标准

检验项标准自评
ref/reactive能正确选择使用场景
computed能写出带缓存的派生状态
watch能监听 ref 和 reactive
Element Plus能独立查阅文档使用新组件
Pinia能独立写出完整的 Store
AI 协作能用 Prompt 生成完整列表页并审查

7.3 常见问题 FAQ

Q:ref 和 reactive 到底怎么选?

表单用 reactive,其他基础类型用 ref,数组也用 ref。记住:需要替换整个对象时,用 ref。

Q:为什么我的修改不触发界面更新?

检查三个问题:

  1. ref 是否通过 .value 修改?
  2. reactive 是否解构后修改?
  3. 数组是否通过索引直接修改?(用 splice 或赋值新数组)

Q:Element Plus 组件名记不住怎么办?

不需要记。给 AI 描述功能,它会自动使用正确的组件。你只需要会查文档确认组件 API。


附录:Vue 3 核心 API 速查表

API作用使用方式注意
ref响应式基础类型const x = ref(0)修改用.value
reactive响应式对象const obj = reactive({...})不要解构
computed派生状态const x = computed(() => ...)自动缓存
watch监听变化watch(source, callback, options)reactive 要加() =>
onMounted挂载后执行onMounted(() => ...)发起初始请求
onUnmounted卸载前执行onUnmounted(() => ...)清理资源
toRefs解构保持响应式const { x } = toRefs(obj)配合 reactive 使用
nextTickDOM 更新后执行await nextTick()操作更新后的 DOM

下一期预告:React 18 深入实战 —— 用同样的需求,实现 React 版本的用户管理列表页,对比 Vue 和 React 的差异

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 7:54:14

基于MPU-9250与Arduino的3D记忆游戏立方体设计与实现

1. 项目概述如果你玩过经典的“西蒙说”&#xff08;Simon Says&#xff09;记忆游戏&#xff0c;大概会记得那个会按顺序闪烁不同颜色、需要你复现序列的玩具。这次&#xff0c;我想把这个游戏从二维的平面按钮&#xff0c;搬到三维的物理空间里。核心想法很简单&#xff1a;用…

作者头像 李华
网站建设 2026/6/3 7:54:11

PTC全家桶的license管理,我劝你别一个个单搞了

你搜这个标题&#xff0c;八成是被PTC的license搞疯了。Creo、Windchill、ThingWorx、Mathcad……每个产品一套license server&#xff0c;每个服务器一套管理逻辑。我去年接了个项目&#xff0c;一个客户光PTC相关的license就涉及7个不同的管理平台&#xff0c;IT三个人专门管…

作者头像 李华