vue3学习笔记

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>

测试效果

  1. 启动项目:npm run dev
  2. 访问首页 http://localhost:5173,能看到新增的「跳转到模板语法演示页」链接;
  3. 点击该链接,会跳转到 http://localhost:5173/template-syntax,展示完整的模板语法演示内容;
  4. 演示页面底部有「返回首页」链接,点击可回到首页。

网络请求和列表增删改查

建立一个简单的后端

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>

关键说明

  1. 请求封装request.js 统一处理了请求基础地址、超时、拦截器,后续新增接口只需复用这个工具,无需重复配置。
  2. CRUD 实现
    • 查(列表):getUserList 调用 /users/getList
    • 查(详情):getUserDetail 调用 /users/getDetail(带ID参数)
    • 增:submitUser(非编辑状态)调用 /users/addUser
    • 改:submitUser(编辑状态)调用 /users/editUser
    • 删:deleteUser 调用 /users/deleteUser(带ID参数)
  3. 用户体验优化
    • 添加了loading状态,防止重复请求;
    • 表单验证(必填项检查);
    • 删除前确认提示;
    • 编辑时自动回显数据并滚动到表单;
    • 空列表提示。
相关推荐
闪闪发亮的小星星2 小时前
asin和atan2的区别 (CPA指向相关)
笔记·其他
瞎某某Blinder2 小时前
DFT学习记录[3]:material project api使用方法 mp_api调取与pymatgen保存
java·笔记·python·学习
IT19952 小时前
Java文档阅读笔记-AI LangChain4j - Agent Multiple Tools Calling Example
java·笔记·文档阅读
liuchangng3 小时前
OpenCode AI编程工具笔记_20260212115022
笔记·ai编程
Coisinilove4 小时前
MATLAB学习笔记——第二章
笔记·学习·matlab
Titan20244 小时前
C++异常学习笔记
c++·笔记·学习
小陈phd4 小时前
多模态大模型学习笔记(五)—— 神经网络激活函数完整指南
人工智能·笔记·神经网络·学习·自然语言处理
wdfk_prog4 小时前
[Linux]学习笔记系列 -- [drivers]char
linux·笔记·学习
深蓝海拓5 小时前
PySide6的QTimeLine详解
笔记·python·qt·学习·pyqt