vue3学习笔记
一、项目骨架(脚手架)创建
创建项目
npm create vite@latest my-vue3-project -- --template vue
如果是node18 需要指定vite版本
npm create vite@6.4.0 my-vue3-project -- --template vue
项目结构
D:\code>tree my-vue3-project /f
文件夹 PATH 列表
卷序列号为 00000095 6072:0BDA
D:\CODE\MY-VUE3-PROJECT
│ .gitignore
│ index.html #项目的入口 HTML 文件
│ package-lock.json
│ package.json #核心配置文件
│ README.md
│ vite.config.js #Vite 的配置文件
│
├─.vscode
│ extensions.json
│
├─public #静态资源目录
│ vite.svg
│
└─src
│ App.vue #项目的根组件
│ main.js #项目的入口 JS 文件
│ style.css #全局样式文件
│
├─assets
│ vue.svg #编译型静态资源目录
│
└─components #公共组件目录
HelloWorld.vue
二、页面创建和跳转
使用vue-router路由库
在 Vue3 中实现页面创建和跳转,核心是使用 vue-router 这个官方路由库。下面我会一步步教你完成整个流程。
第一步:安装 vue-router
首先打开终端,进入你的项目根目录(D:\code\my-vue3-project),执行以下命令安装路由:
bash
npm install vue-router@4
(vue-router@4 是专门适配 Vue3 的版本)
第二步:创建页面文件和路由配置
1. 新建页面文件
在 src 目录下创建 views 文件夹(专门存放页面组件,和 components 区分开),然后创建两个示例页面:
src
└─views
Home.vue # 首页
About.vue # 关于页
Home.vue 内容:
vue
<template>
<div class="home">
<h1>首页</h1>
<!-- 跳转链接 -->
<router-link to="/about">跳转到关于页</router-link>
</div>
</template>
<script setup>
// 这里可以写页面逻辑
</script>
<style scoped>
.home {
padding: 20px;
}
</style>
About.vue 内容:
vue
<template>
<div class="about">
<h1>关于页</h1>
<!-- 跳转回首页 -->
<router-link to="/">返回首页</router-link>
<!-- 也可以用编程式导航 -->
<button @click="goBack">编程式返回首页</button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
// 创建路由实例
const router = useRouter()
// 编程式跳转方法
const goBack = () => {
router.push('/')
// 也可以用 router.go(-1) 返回上一页
}
</script>
<style scoped>
.about {
padding: 20px;
}
button {
margin-top: 10px;
padding: 5px 10px;
}
</style>
2. 创建路由配置文件
在 src 目录下创建 router 文件夹,然后新建 index.js:
javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 导入页面组件
import Home from '../views/Home.vue'
import About from '../views/About.vue'
// 定义路由规则
const routes = [
{
path: '/', // 访问路径
name: 'Home', // 路由名称
component: Home // 对应组件
},
{
path: '/about',
name: 'About',
component: About
}
]
// 创建路由实例
const router = createRouter({
history: createWebHistory(), // 使用 HTML5 历史模式(无 # 号)
routes // 传入路由规则
})
export default router
第三步:在项目入口挂载路由
修改 src/main.js 文件,引入并使用路由:
javascript
// src/main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 导入路由配置
import router from './router'
// 创建 App 实例并挂载路由
const app = createApp(App)
app.use(router) // 注册路由
app.mount('#app') // 挂载到 DOM
第四步:修改根组件 App.vue
在 App.vue 中添加 <router-view>(路由出口,页面内容会渲染在这里):
vue
<!-- src/App.vue -->
<template>
<div id="app">
<!-- 路由出口:匹配到的页面会渲染在这里 -->
<router-view></router-view>
</div>
</template>
<script setup>
// 无需额外代码,路由会自动生效
</script>
<style scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
第五步:启动项目测试
执行以下命令启动项目:
bash
npm run dev
打开浏览器访问 http://localhost:5173(Vite 默认端口),就能看到首页,点击链接可以跳转到关于页,实现页面跳转功能。
三、模板语法演示
新建一个专门的「模板语法演示」页面,同时在首页添加跳转到这个新页面的链接,
按照这个需求,需要完成新建页面、配置路由、首页添加跳转链接这三步。
第一步:新建模板语法演示页面
在 src/views 目录下新建 TemplateSyntax.vue 文件,内容如下(就是之前准备的模板语法演示代码):
vue
<template>
<div class="template-syntax">
<h1>Vue3 模板语法演示</h1>
<hr />
<!-- 1. 文本插值(最基础) -->
<div class="demo-item">
<h3>1. 文本插值</h3>
<p>直接渲染变量:{{ message }}</p>
<p>渲染表达式:{{ message + ' - ' + (count * 2) }}</p>
<p>渲染布尔值:{{ isShow }}</p>
</div>
<hr />
<!-- 2. 原始 HTML 渲染(v-html) -->
<div class="demo-item">
<h3>2. 原始 HTML 渲染 (v-html)</h3>
<p>普通插值:{{ htmlContent }}</p>
<p>v-html 渲染:<span v-html="htmlContent"></span></p>
</div>
<hr />
<!-- 3. 属性绑定(v-bind,简写 :) -->
<div class="demo-item">
<h3>3. 属性绑定 (v-bind/:)</h3>
<!-- 绑定普通属性 -->
<a :href="linkUrl" :title="linkTitle">Vue 官方文档</a>
<!-- 绑定 class(对象语法) -->
<div :class="{ active: isActive, 'text-red': !isActive }" class="box">
动态 Class 演示
</div>
<!-- 绑定 style(对象语法) -->
<div :style="{ color: textColor, fontSize: '20px' }" class="box">
动态 Style 演示
</div>
</div>
<hr />
<!-- 4. 事件绑定(v-on,简写 @) -->
<div class="demo-item">
<h3>4. 事件绑定 (v-on/@)</h3>
<p>当前计数:{{ count }}</p>
<!-- 基础事件绑定 -->
<button @click="increment">点击+1</button>
<button @click="decrement">点击-1</button>
<!-- 带参数的事件绑定 -->
<button @click="changeCount(5)">直接设置为5</button>
<!-- 事件修饰符(阻止默认行为) -->
<a href="https://vuejs.org" @click.prevent="handleLinkClick">
点击不跳转(prevent 修饰符)
</a>
</div>
<hr />
<!-- 5. 条件渲染(v-if/v-else-if/v-else) -->
<div class="demo-item">
<h3>5. 条件渲染 (v-if/v-else)</h3>
<button @click="toggleShow">切换显示/隐藏</button>
<div v-if="isShow" class="box">我是 v-if 渲染的内容</div>
<div v-else class="box">我是 v-else 渲染的内容</div>
<!-- v-show 对比(CSS 隐藏,DOM 仍存在) -->
<p>v-show 演示:<span v-show="isShow">我是 v-show 内容</span></p>
</div>
<hr />
<!-- 6. 列表渲染(v-for) -->
<div class="demo-item">
<h3>6. 列表渲染 (v-for)</h3>
<ul>
<!-- 遍历数组,key 必须加(推荐用唯一标识) -->
<li v-for="(item, index) in list" :key="item.id">
索引:{{ index }} - 名称:{{ item.name }} - 年龄:{{ item.age }}
<button @click="deleteItem(index)">删除</button>
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
<hr />
<!-- 7. 双向绑定(v-model) -->
<div class="demo-item">
<h3>7. 双向绑定 (v-model)</h3>
<!-- 输入框双向绑定 -->
<input v-model="inputValue" placeholder="输入内容..." />
<p>你输入的内容:{{ inputValue }}</p>
<!-- 复选框双向绑定 -->
<div>
<input type="checkbox" v-model="hobbies" value="篮球" /> 篮球
<input type="checkbox" v-model="hobbies" value="游戏" /> 游戏
<input type="checkbox" v-model="hobbies" value="阅读" /> 阅读
<p>你的爱好:{{ hobbies.join(', ') }}</p>
</div>
</div>
<hr />
<!-- 返回首页链接 -->
<router-link to="/">← 返回首页</router-link>
</div>
</template>
<script setup>
// Vue3 setup 语法糖,无需手动导出
import { ref, reactive } from 'vue'
// 1. 基础响应式变量(ref 用于基本类型)
const message = ref('Hello Vue3!')
const count = ref(1)
const isShow = ref(true)
const isActive = ref(true)
const inputValue = ref('')
// 数组类型的 ref
const hobbies = ref([])
// 2. 复杂数据(reactive 用于对象/数组)
const htmlContent = ref('<span style="color: red;">这是红色的HTML内容</span>')
const linkUrl = ref('https://vuejs.org/guide/introduction.html')
const linkTitle = ref('Vue3 官方指南')
const textColor = ref('blue')
// 列表数据(reactive 数组)
const list = reactive([
{ id: 1, name: '张三', age: 20 },
{ id: 2, name: '李四', age: 22 },
{ id: 3, name: '王五', age: 25 }
])
// 3. 事件处理函数
// 计数+1
const increment = () => {
count.value++ // ref 变量需要通过 .value 修改
}
// 计数-1
const decrement = () => {
count.value--
}
// 直接设置计数
const changeCount = (num) => {
count.value = num
}
// 链接点击事件
const handleLinkClick = () => {
alert('点击了链接,但被 prevent 修饰符阻止跳转')
}
// 切换显示/隐藏
const toggleShow = () => {
isShow.value = !isShow.value
}
// 添加列表项
const addItem = () => {
list.push({
id: list.length + 1,
name: `用户${list.length + 1}`,
age: 18 + list.length
})
}
// 删除列表项
const deleteItem = (index) => {
list.splice(index, 1)
}
</script>
<style scoped>
.template-syntax {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.demo-item {
margin: 10px 0;
}
.box {
padding: 10px;
margin: 5px 0;
border: 1px solid #ccc;
}
.active {
background-color: #f0f0f0;
}
.text-red {
color: red;
}
button {
margin: 0 5px;
padding: 5px 10px;
cursor: pointer;
}
ul {
list-style: none;
padding: 0;
}
li {
padding: 8px;
border-bottom: 1px dashed #eee;
}
a {
color: #42b983;
text-decoration: none;
}
</style>
第二步:修改路由配置(添加新页面路由)
打开 src/router/index.js,新增模板语法演示页面的路由规则:
javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 导入页面组件
import Home from '../views/Home.vue'
import About from '../views/About.vue'
// 新增导入模板语法演示页面
import TemplateSyntax from '../views/TemplateSyntax.vue'
// 定义路由规则
const routes = [
{
path: '/', // 访问路径
name: 'Home', // 路由名称
component: Home // 对应组件
},
{
path: '/about',
name: 'About',
component: About
},
// 新增模板语法演示页面的路由
{
path: '/template-syntax', // 页面访问路径
name: 'TemplateSyntax', // 路由名称
component: TemplateSyntax // 对应组件
}
]
// 创建路由实例
const router = createRouter({
history: createWebHistory(), // 使用 HTML5 历史模式(无 # 号)
routes // 传入路由规则
})
export default router
第三步:修改首页(Home.vue)添加跳转链接
打开原有的 src/views/Home.vue,添加跳转到模板语法演示页面的链接(不修改其他内容):
vue
<template>
<div class="home">
<h1>首页</h1>
<!-- 原有跳转链接 -->
<router-link to="/about">跳转到关于页</router-link>
<!-- 新增:跳转到模板语法演示页面 -->
<router-link to="/template-syntax" style="margin-left: 20px;">
跳转到模板语法演示页
</router-link>
</div>
</template>
<script setup>
// 这里可以写页面逻辑
</script>
<style scoped>
.home {
padding: 20px;
}
</style>
测试效果
- 启动项目:
npm run dev - 访问首页
http://localhost:5173,能看到新增的「跳转到模板语法演示页」链接; - 点击该链接,会跳转到
http://localhost:5173/template-syntax,展示完整的模板语法演示内容; - 演示页面底部有「返回首页」链接,点击可回到首页。
网络请求和列表增删改查
建立一个简单的后端
golang版本user增删改查(操作json)
main.go
// cmd/api-server/main.go
package main
import (
"fmt"
"gin-epg/internal/app/dao"
"gin-epg/internal/app/routes"
"gin-epg/internal/app/schedule"
_ "github.com/jinzhu/gorm/dialects/mysql"
"log"
)
func main() {
// 初始化定时器服务
defer schedule.Conrs.Stop()
// 初始化日志服务
err := dao.InitLogger()
if err != nil {
log.Fatalf("初始化日志服务失败: %v", err)
} else {
fmt.Println("初始化日志服务成功")
}
r := routes.SetRouter()
r.Run(":8088")
}
路由
// internal/app/routes/Routers.go
package routes
import (
"gin-epg/internal/app/controller"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"time"
)
func SetRouter() *gin.Engine {
r := gin.Default()
// 配置 CORS 中间件
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"}, // 允许所有来源
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization", "token"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
// 用户管理路由
userGroup := r.Group("users")
{
userGroup.GET("/getList", controller.GetUsers) // 获取所有用户
userGroup.GET("/getDetail", controller.GetUser) // 获取单个用户
userGroup.POST("/addUser", controller.AddUser) // 添加用户 传入json
userGroup.POST("/editUser", controller.UpdateUser) // 更新用户 传入json json里的id必传
userGroup.GET("/deleteUser", controller.DeleteUser) // 删除用户
}
return r
}
封装共通返回
// internal/common/rsp/BaseResultRsp.go
package rsp
import (
"github.com/gin-gonic/gin"
"io"
"net/http"
)
// BaseResultRsp 是用于封装 API 响应的基础结构体
type BaseResultRsp struct {
Code int `json:"code"` // 将 Status 改为 Code
Message string `json:"message"`
}
// SuccessResultRsp 是用于封装 API 成功响应的结构体
type SuccessResultRsp struct {
BaseResultRsp
Data interface{} `json:"data"`
}
// ErrorResultRsp 是用于封装 API 错误响应的结构体
type ErrorResultRsp struct {
BaseResultRsp
}
// 原始
func OriginSuccess(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, data)
}
// 正确状态处理
func Success(c *gin.Context, msg string, data interface{}) {
response := SuccessResultRsp{
BaseResultRsp: BaseResultRsp{
Code: 200, // 将 Status 改为 Code
Message: msg,
},
Data: data,
}
c.JSON(http.StatusOK, response)
}
// 错误状态处理
func Error(c *gin.Context, msg string) {
response := ErrorResultRsp{
BaseResultRsp: BaseResultRsp{
Code: 400, // 将 Status 改为 Code
Message: msg,
},
}
c.JSON(http.StatusBadRequest, response)
}
func Error2(c *gin.Context, code int, msg string) {
response := ErrorResultRsp{
BaseResultRsp: BaseResultRsp{
Code: code, // 将 Status 改为 Code
Message: msg,
},
}
c.JSON(http.StatusBadRequest, response)
}
// 文件流响应处理
func FileResponse(c *gin.Context, key string, fileStream io.ReadCloser, contentType string) {
defer fileStream.Close()
// 设置响应头
c.Header("Content-Disposition", "attachment; filename="+key)
c.Header("Content-Type", contentType)
// 返回文件流
c.DataFromReader(http.StatusOK, int64(-1), contentType, fileStream, nil)
}
controller
// file: internal/app/controller/UserController.go
package controller
import (
"encoding/json"
"gin-epg/internal/app/common/rsp"
"github.com/gin-gonic/gin"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"time"
)
const userFilePath = "configs/user.json"
// User 用户结构体
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
Hobby string `json:"hobby"`
}
// getUsers 读取用户数据
func getUsers() ([]User, error) {
data, err := ioutil.ReadFile(userFilePath)
if err != nil {
return nil, err
}
var users []User
err = json.Unmarshal(data, &users)
return users, err
}
// saveUsers 保存用户数据
func saveUsers(users []User) error {
data, err := json.MarshalIndent(users, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(userFilePath, data, 0644)
}
// GetUsers 获取所有用户
func GetUsers(c *gin.Context) {
users, err := getUsers()
if err != nil {
rsp.Error(c, "读取用户失败")
return
}
rsp.Success(c, "获取用户列表成功", users)
}
// GetUser 获取单个用户
func GetUser(c *gin.Context) {
id := c.Query("id") // 从查询参数中获取 id
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing user id"})
return
}
users, err := getUsers()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read users"})
return
}
for _, user := range users {
if user.ID == id {
rsp.Success(c, "获取用户详情成功", user)
return
}
}
rsp.Error(c, "用户未找到")
}
// AddUser 添加用户
func AddUser(c *gin.Context) {
var newUser User
if err := c.ShouldBindJSON(&newUser); err != nil {
rsp.Error(c, "输入无效")
return
}
if newUser.ID == "" {
rand.Seed(time.Now().UnixNano())
newUser.ID = strconv.Itoa(rand.Intn(1000000))
}
users, err := getUsers()
if err != nil {
rsp.Error(c, "读取用户失败")
return
}
users = append(users, newUser)
if err := saveUsers(users); err != nil {
rsp.Error(c, "保存用户失败")
return
}
rsp.Success(c, "添加用户成功", newUser)
}
// UpdateUser 更新用户
func UpdateUser(c *gin.Context) {
var updatedUser User
if err := c.ShouldBindJSON(&updatedUser); err != nil {
rsp.Error(c, "输入无效")
return
}
if updatedUser.ID == "" {
rsp.Error(c, "User ID 必填")
return
}
users, err := getUsers()
if err != nil {
rsp.Error(c, "读取用户失败")
return
}
found := false
for i, user := range users {
if user.ID == updatedUser.ID {
users[i] = updatedUser
found = true
break
}
}
if !found {
rsp.Error(c, "用户未找到")
return
}
if err := saveUsers(users); err != nil {
rsp.Error(c, "保存用户失败")
return
}
rsp.Success(c, "更新用户成功", updatedUser)
}
// DeleteUser 删除用户
func DeleteUser(c *gin.Context) {
id := c.Query("id") // 从查询参数中获取 id
if id == "" {
rsp.Error(c, "用户ID 必填")
return
}
users, err := getUsers()
if err != nil {
rsp.Error(c, "读取用户失败")
return
}
found := false
for i, user := range users {
if user.ID == id {
users = append(users[:i], users[i+1:]...)
found = true
break
}
}
if !found {
rsp.Error(c, "用户未找到")
return
}
if err := saveUsers(users); err != nil {
rsp.Error(c, "保存用户失败")
return
}
rsp.Success(c, "删除用户成功", nil)
}
configs/user.json示例
[
{
"id": "123456",
"name": "张三",
"age": 18,
"hobby": "篮球"
}
]
增删改查请求
curl --location 'localhost:8088/users/getList'
curl --location 'localhost:8088/users/getDetail?id=123456'
curl --location 'localhost:8088/users/addUser' \
--header 'Content-Type: application/json' \
--data '{
"name": "李四",
"age": 19,
"hobby": "足球"
}'
curl --location 'localhost:8088/users/editUser' \
--header 'Content-Type: application/json' \
--data '{
"id": "33841",
"name": "李四",
"age": 19,
"hobby": "乒乓球"
}'
curl --location 'localhost:8088/users/deleteUser?id=33841'
vue页面处理
需要完成新建页面、封装请求工具、实现增删改查逻辑、配置路由、首页添加跳转链接这几步
前置准备
首先安装网络请求库(Vue3项目常用axios):
bash
npm install axios
第一步:封装Axios请求工具(复用性更高)
在src目录下新建utils/request.js,统一处理请求:
javascript
// src/utils/request.js
import axios from 'axios'
// 创建axios实例
const request = axios.create({
baseURL: 'http://localhost:8088', // 接口基础地址
timeout: 5000, // 请求超时时间
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器(可选,比如添加token)
request.interceptors.request.use(
(config) => {
// 可在这里添加请求头,如token
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器(统一处理错误)
request.interceptors.response.use(
(response) => {
// 只返回响应数据
return response.data
},
(error) => {
// 统一错误提示
console.error('请求失败:', error.message)
alert(`请求失败:${error.message}`)
return Promise.reject(error)
}
)
export default request
第二步:新建列表和网络请求演示页面
在src/views目录下新建UserList.vue文件:
vue
<template>
<div class="user-list">
<h1>用户列表 & 网络请求演示(CRUD)</h1>
<hr />
<!-- 1. 添加用户表单 -->
<div class="form-section">
<h3>添加/编辑用户</h3>
<div class="form-item">
<label>ID:</label>
<input
v-model="formData.id"
placeholder="编辑时填写ID,新增留空"
:disabled="!isEdit"
/>
</div>
<div class="form-item">
<label>姓名:</label>
<input v-model="formData.name" placeholder="请输入姓名" />
</div>
<div class="form-item">
<label>年龄:</label>
<input
v-model.number="formData.age"
type="number"
placeholder="请输入年龄"
/>
</div>
<div class="form-item">
<label>爱好:</label>
<input v-model="formData.hobby" placeholder="请输入爱好" />
</div>
<button
@click="submitUser"
:disabled="loading"
class="btn-submit"
>
{{ loading ? '提交中...' : (isEdit ? '修改用户' : '添加用户') }}
</button>
<button
@click="resetForm"
style="margin-left: 10px"
>
重置
</button>
</div>
<hr />
<!-- 2. 用户列表 -->
<div class="list-section">
<h3>用户列表</h3>
<button @click="getUserList" :disabled="loading">
{{ loading ? '加载中...' : '刷新列表' }}
</button>
<!-- 列表为空提示 -->
<div v-if="userList.length === 0 && !loading" class="empty-tip">
暂无用户数据,请添加用户
</div>
<!-- 列表内容 -->
<table v-else class="user-table" border="1" cellpadding="8" cellspacing="0">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>爱好</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="user in userList" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
<td>{{ user.hobby }}</td>
<td class="operate">
<button @click="getUserDetail(user.id)">查看详情</button>
<button @click="editUser(user)">编辑</button>
<button @click="deleteUser(user.id)">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 返回首页 -->
<router-link to="/" class="back-home">← 返回首页</router-link>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import request from '../utils/request'
// 响应式数据
const loading = ref(false) // 加载状态
const userList = ref([]) // 用户列表
const isEdit = ref(false) // 是否为编辑状态
// 表单数据
const formData = reactive({
id: '',
name: '',
age: '',
hobby: ''
})
// 1. 获取用户列表
const getUserList = async () => {
try {
loading.value = true
// 调用列表接口
const res = await request.get('/users/getList')
// 假设接口返回的数据格式是 { code: 200, data: [...] }
userList.value = res.data || res // 兼容不同的返回格式
console.log('用户列表:', userList.value)
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
// 2. 获取用户详情
const getUserDetail = async (id) => {
try {
loading.value = true
const res = await request.get('/users/getDetail', {
params: { id } // URL参数
})
// 假设接口返回格式为 { code: 200, message: '获取用户详情成功', data: {...} }
const userData = res.data || res // 兼容不同返回格式
alert(`用户详情:\nID:${userData.id}\n姓名:${userData.name}\n年龄:${userData.age}\n爱好:${userData.hobby}`)
console.log('用户详情:', userData)
} catch (error) {
console.error('获取详情失败:', error)
} finally {
loading.value = false
}
}
// 3. 添加/编辑用户
const submitUser = async () => {
// 简单表单验证
if (!formData.name || !formData.age || !formData.hobby) {
alert('姓名、年龄、爱好不能为空!')
return
}
try {
loading.value = true
if (isEdit.value) {
// 编辑用户
await request.post('/users/editUser', formData)
alert('修改用户成功!')
} else {
// 新增用户
await request.post('/users/addUser', formData)
alert('添加用户成功!')
}
// 重置表单 + 刷新列表
resetForm()
getUserList()
} catch (error) {
console.error(isEdit.value ? '修改失败' : '添加失败', error)
} finally {
loading.value = false
}
}
// 4. 删除用户
const deleteUser = async (id) => {
if (!confirm(`确定要删除ID为${id}的用户吗?`)) {
return
}
try {
loading.value = true
await request.get('/users/deleteUser', {
params: { id }
})
alert('删除用户成功!')
// 刷新列表
getUserList()
} catch (error) {
console.error('删除失败:', error)
} finally {
loading.value = false
}
}
// 编辑用户(回显数据)
const editUser = (user) => {
isEdit.value = true
// 回显用户数据到表单
formData.id = user.id
formData.name = user.name
formData.age = user.age
formData.hobby = user.hobby
// 滚动到表单区域
document.querySelector('.form-section').scrollIntoView({ behavior: 'smooth' })
}
// 重置表单
const resetForm = () => {
isEdit.value = false
formData.id = ''
formData.name = ''
formData.age = ''
formData.hobby = ''
}
// 页面加载时自动获取列表
getUserList()
</script>
<style scoped>
.user-list {
padding: 20px;
max-width: 1000px;
margin: 0 auto;
}
.form-section {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #eee;
border-radius: 4px;
}
.form-item {
margin: 10px 0;
display: flex;
align-items: center;
}
.form-item label {
width: 60px;
display: inline-block;
}
.form-item input {
padding: 6px 8px;
width: 300px;
border: 1px solid #ccc;
border-radius: 4px;
}
.btn-submit {
background-color: #42b983;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.btn-submit:disabled {
background-color: #999;
cursor: not-allowed;
}
.list-section {
margin-bottom: 20px;
}
.empty-tip {
margin: 20px 0;
color: #999;
text-align: center;
}
.user-table {
width: 100%;
margin-top: 10px;
border-collapse: collapse;
}
.user-table th {
background-color: #f5f5f5;
}
.operate button {
margin: 0 5px;
padding: 4px 8px;
cursor: pointer;
}
.back-home {
display: inline-block;
margin-top: 20px;
color: #42b983;
text-decoration: none;
}
</style>
第三步:修改路由配置(添加新页面路由)
打开src/router/index.js,新增用户列表页面的路由:
javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 导入页面组件
import Home from '../views/Home.vue'
import About from '../views/About.vue'
import TemplateSyntax from '../views/TemplateSyntax.vue'
// 新增导入用户列表页面
import UserList from '../views/UserList.vue'
// 定义路由规则
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/template-syntax',
name: 'TemplateSyntax',
component: TemplateSyntax
},
// 新增用户列表页面路由
{
path: '/user-list',
name: 'UserList',
component: UserList
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
第四步:修改首页(Home.vue)添加跳转链接
打开src/views/Home.vue,新增跳转到用户列表页面的链接:
vue
<template>
<div class="home">
<h1>首页</h1>
<!-- 原有链接 -->
<router-link to="/about">跳转到关于页</router-link>
<router-link to="/template-syntax" style="margin-left: 20px;">
跳转到模板语法演示页
</router-link>
<!-- 新增:跳转到用户列表演示页 -->
<router-link to="/user-list" style="margin-left: 20px;">
跳转到列表和网络请求演示页
</router-link>
</div>
</template>
<script setup>
// 页面逻辑
</script>
<style scoped>
.home {
padding: 20px;
}
/* 可选:给链接加样式,更美观 */
a {
margin: 0 10px;
color: #42b983;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
</style>
关键说明
- 请求封装 :
request.js统一处理了请求基础地址、超时、拦截器,后续新增接口只需复用这个工具,无需重复配置。 - CRUD 实现 :
- 查(列表):
getUserList调用/users/getList - 查(详情):
getUserDetail调用/users/getDetail(带ID参数) - 增:
submitUser(非编辑状态)调用/users/addUser - 改:
submitUser(编辑状态)调用/users/editUser - 删:
deleteUser调用/users/deleteUser(带ID参数)
- 查(列表):
- 用户体验优化 :
- 添加了
loading状态,防止重复请求; - 表单验证(必填项检查);
- 删除前确认提示;
- 编辑时自动回显数据并滚动到表单;
- 空列表提示。
- 添加了