AI驱动Web_Vue详解

O泡李华 7

AI 驱动 Web - Vue 详解

本章位置:第三阶段 Java 企业项目 + AI 助手
对应课程:AI 驱动 Web - Vue(第一部分)
前置知识:HTML、CSS、JavaScript、HTTP、RESTful、SpringBoot 请求响应
本章目标:从 Java 后端开发者视角学习 Vue 3,掌握 Vite 项目创建、组件、模板语法、响应式、事件、表单、生命周期、组件通信、Vue Router、Axios、Element Plus、SpringBoot 前后端联调,以及使用 AI 辅助前端开发的正确方式。
建议开发环境:Node.js、npm、VS Code / Cursor、Chrome / Edge、SpringBoot 后端。


一、为什么 Java 后端也必须学 Vue

以前传统 Java Web:

浏览器
↓
Servlet
↓
JSP
↓
服务器生成 HTML
↓
浏览器显示

现在前后端分离项目:

Vue
负责页面

SpringBoot
负责接口

MyBatis
负责数据库

完整流程:

Vue 页面
↓
Axios
↓
HTTP / JSON
↓
SpringBoot Controller
↓
Service
↓
Mapper
↓
MySQL
↓
JSON Response
↓
Vue 更新页面

所以你现在学 Vue,不是为了变成纯前端工程师,而是为了:

能够独立完成前后端项目

能够看懂企业后台前端

能够和 SpringBoot 接口联调

能够修改若依等 Vue 项目

能够让 AI 帮你生成前端后自己看懂和修改

二、Vue 是什么

Vue 是:

渐进式 JavaScript 前端框架

当前我们学习:

Vue 3

Vue 最核心的思想:

数据驱动视图

组件化开发

三、什么叫数据驱动视图

传统 JavaScript:

const count =
    document.querySelector(
        "#count"
    )

count.innerText =
    10

需要:

自己找 DOM

自己修改 DOM

Vue:

const count =
    ref(
        10
    )

模板:

<p>
    {{ count }}
</p>

当:

count 改变

Vue 自动:

更新页面

四、Vue 核心思想

可以先记住:

数据变化
↓
Vue 响应式系统发现变化
↓
重新计算需要更新的内容
↓
更新 DOM

五、组件化是什么

一个页面不再全部写:

一个巨大 HTML 文件

而是拆成:

Header

Sidebar

UserTable

UserForm

Pagination

LoginForm

每一块:

一个组件

六、组件树

例如后台系统:

App
│
├─ Layout
│  ├─ Sidebar
│  ├─ Header
│  └─ Main
│     └─ UserPage
│        ├─ SearchForm
│        ├─ UserTable
│        └─ UserDialog

这就是:

组件树

七、为什么 Vue 适合 SpringBoot 项目

SpringBoot:

@RestController

返回:

{
    "code": "SUCCESS",
    "message": "success",
    "data": []
}

Vue:

Axios 请求 JSON
↓
拿 data
↓
更新页面

前后端职责清晰。


八、Vue 3 推荐项目工具链

当前新项目推荐:

Vue 3

create-vue

Vite

Vue Router

Pinia

Element Plus

Axios

初学阶段重点:

Vue

Router

Axios

Element Plus

Pinia 后面再系统学习。


九、Vue CLI 为什么暂时不学

老教程经常:

vue create xxx

这是:

Vue CLI

新项目现在更推荐:

create-vue + Vite

所以本课程默认:

不从 Vue CLI 开始

十、什么是 Node.js

Vue 源代码运行在浏览器。

但是前端开发工具:

Vite

npm

构建工具

插件

需要:

Node.js

十一、Node.js 在这里的作用

你可以理解:

JDK
对 Java 开发工具链很重要

Node.js
对现代前端开发工具链很重要

但两者不是完全等价。


十二、npm 是什么

npm:

Node Package Manager

主要负责:

下载前端依赖

管理 package.json

执行脚本

类似 Maven 中:

dependency

plugin command

十三、Maven 和 npm 类比

Maven
pom.xml

npm
package.json

Maven:

mvn package

前端:

npm run build

Maven 本地依赖:

.m2/repository

npm 项目依赖:

node_modules

十四、先检查 Node

Windows PowerShell:

node -v

再:

npm -v

如果都有版本:

安装成功

十五、Node 版本问题

前端工具更新比较快。

如果执行:

npm create vue@latest

提示:

Node version unsupported

不要乱改代码。

直接:

升级 Node.js

并以当前 Vue / Vite 官方要求为准。


十六、创建 Vue 项目

推荐:

npm create vue@latest

然后命令行会让你选择:

Project name

TypeScript

JSX

Vue Router

Pinia

Vitest

ESLint

Prettier

十七、当前学习阶段怎么选

建议初学:

TypeScript
No

JSX
No

Vue Router
Yes

Pinia
可以先 No

Vitest
No

E2E
No

ESLint
Yes

Prettier
可选 Yes

十八、为什么初学先 JavaScript

因为你刚进入 Vue。

如果立刻同时学:

Vue
+
TypeScript
+
Pinia
+
Router
+
ESLint

容易:

知识叠太多

先使用:

Vue 3 + JavaScript

理解核心。


十九、以后要不要学 TypeScript

要。

企业 Vue 项目非常常见:

Vue 3 + TypeScript

但建议:

先会 Vue
再升级 TS

二十、进入项目

例如:

cd vue-demo

安装:

npm install

启动:

npm run dev

二十一、Vite 开发服务器

启动后通常显示类似:

Local:
http://localhost:5173

浏览器访问:

http://localhost:5173

二十二、为什么不是 8080

SpringBoot 常见:

8080

Vite 开发服务器常见:

5173

所以前后端开发时:

前端 5173

后端 8080

二十三、这会产生什么

浏览器会认为:

不同 Origin

因为端口不同。

于是可能出现:

CORS
跨域

后面专门处理。


二十四、Vue 项目目录

典型:

vue-demo
│
├─ node_modules
├─ public
├─ src
│  ├─ assets
│  ├─ components
│  ├─ router
│  ├─ views
│  ├─ App.vue
│  └─ main.js
│
├─ index.html
├─ package.json
├─ vite.config.js
└─ package-lock.json

二十五、package.json

类似 Maven:

pom.xml

里面定义:

项目名称

依赖

开发依赖

npm scripts

二十六、dependencies

运行项目需要:

Vue

Vue Router

Axios

Element Plus

等。


二十七、devDependencies

开发/构建工具:

Vite

ESLint

插件

通常在:

devDependencies

二十八、npm install 做什么

读取:

package.json
package-lock.json

然后把依赖安装到:

node_modules

二十九、不要提交 node_modules

因为:

非常大

可以重新安装

Git 一般:

忽略 node_modules

三十、package-lock.json

它用来:

锁定实际解析的依赖版本

企业项目通常应该:

提交 Git

这样团队安装更稳定。


三十一、npm run dev

package.json 里类似:

{
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "preview": "vite preview"
    }
}

执行:

npm run dev

实际上执行:

vite

三十二、npm run build

npm run build

生成生产静态资源:

dist

三十三、dist 是什么

生产构建结果。

里面通常:

index.html

assets/*.js

assets/*.css

最终可以交给:

Nginx

部署。


三十四、main.js

Vue 应用入口。

典型:

import {
    createApp
} from "vue"

import App
    from "./App.vue"

createApp(
    App
).mount(
    "#app"
)

三十五、createApp

创建:

Vue Application

三十六、App.vue

根组件:

整个 Vue 组件树的根

三十七、mount(“#app”)

把 Vue 应用挂载到:

<div id="app"></div>

三十八、index.html

Vite 项目中:

index.html

是非常重要的入口文件。

里面通常:

<div id="app"></div>

三十九、.vue 文件是什么

.vue:

Single File Component

中文:

单文件组件

四十、Vue 单文件组件三部分

典型:

<script setup>

</script>

<template>

</template>

<style scoped>

</style>

四十一、template

负责:

页面结构

类似:

HTML

四十二、script

负责:

JavaScript 逻辑
数据
函数
生命周期

四十三、style

负责:

CSS 样式

四十四、script setup

Vue 3 推荐常见写法:

<script setup>
</script>

相比旧 Options API:

更简洁

本课程主要使用:

Composition API + script setup

四十五、第一个 Vue 组件

<script setup>
const title =
    "学生管理"
</script>

<template>
    <h1>
        {{ title }}
    </h1>
</template>

四十六、插值表达式

{{ title }}

叫:

Mustache interpolation
插值表达式

用于显示数据。


四十七、插值可以写简单表达式

例如:

{{ age + 1 }}
{{ username.toUpperCase() }}

四十八、不要在模板写复杂逻辑

不推荐:

{{ veryLongComplexExpression... }}

复杂逻辑:

computed
函数

中处理。


四十九、响应式数据 ref

import {
    ref
} from "vue"

const count =
    ref(
        0
    )

五十、为什么要 ref

普通变量:

let count =
    0

Vue 不一定能按响应式方式追踪。

使用:

ref

Vue 能:

追踪变化
更新视图

五十一、修改 ref

JavaScript 中:

count.value++

五十二、模板中为什么不用 .value

模板:

{{ count }}

Vue 会:

自动解包 ref

所以不用:

{{ count.value }}

五十三、按钮事件

<script setup>
import {
    ref
} from "vue"

const count =
    ref(
        0
    )

const add =
    () => {
        count.value++
    }
</script>

<template>
    <button
        @click="add"
    >
        {{ count }}
    </button>
</template>

五十四、@click

等价完整形式:

v-on:click

简写:

@click

五十五、v-bind

动态绑定属性:

<img
    v-bind:src="avatarUrl"
/>

简写:

<img
    :src="avatarUrl"
/>

五十六、冒号 :

Vue 中:

:

经常表示:

v-bind

五十七、v-if

条件渲染:

<p
    v-if="score >= 60"
>
    及格
</p>

五十八、v-else

<p
    v-if="score >= 60"
>
    及格
</p>

<p
    v-else
>
    不及格
</p>

五十九、v-else-if

<p
    v-if="score >= 90"
>
    优秀
</p>

<p
    v-else-if="score >= 60"
>
    及格
</p>

<p
    v-else
>
    不及格
</p>

六十、v-show

<div
    v-show="visible"
>
    内容
</div>

六十一、v-if 和 v-show 区别

v-if:

条件为 false
DOM 可能直接不存在

v-show:

DOM 仍存在
通过 CSS display 控制

六十二、什么时候用 v-if

条件:

很少切换

可以:

v-if

六十三、什么时候用 v-show

元素:

频繁显示/隐藏

可考虑:

v-show

六十四、v-for

列表渲染:

<li
    v-for="student in students"
    :key="student.id"
>
    {{ student.name }}
</li>

六十五、v-for index

<tr
    v-for="(student, index) in students"
    :key="student.id"
>
    <td>
        {{ index + 1 }}
    </td>

    <td>
        {{ student.name }}
    </td>
</tr>

六十六、为什么要 key

Vue 更新列表时:

需要识别每一项身份

推荐:

数据库主键

例如:

:key="student.id"

六十七、不推荐 index 作为 key

如果列表会:

插入

删除

排序

index 会变化。

容易造成:

组件状态复用错误

六十八、v-model

双向绑定:

<input
    v-model="username"
/>

六十九、v-model 完整例子

<script setup>
import {
    ref
} from "vue"

const username =
    ref(
        ""
    )
</script>

<template>
    <input
        v-model="username"
    />

    <p>
        {{ username }}
    </p>
</template>

七十、什么叫双向绑定

输入框变化:

→ username 变化

username 变化:

→ 输入框显示变化

七十一、v-model.number

<input
    v-model.number="age"
/>

帮助:

转为数字

七十二、v-model.trim

<input
    v-model.trim="username"
/>

自动:

去掉首尾空格

七十三、reactive

除了 ref:

import {
    reactive
} from "vue"

const form =
    reactive({
        username: "",
        age: null
    })

七十四、ref 和 reactive 怎么选

初学建议:

简单值
ref

对象表单
reactive

例如:

loading
ref(false)

keyword
ref("")

form
reactive({...})

七十五、reactive 修改

form.username =
    "admin"

不需要:

.value

七十六、不要直接替换 reactive 对象

例如:

form =
    newForm

如果 form 用 const:

本身也不能重新赋值

更重要的是:

可能破坏响应式引用

通常:

Object.assign(
    form,
    newForm
)

七十七、computed

计算属性:

import {
    computed,
    ref
} from "vue"

const firstName =
    ref(
        "三"
    )

const lastName =
    ref(
        "张"
    )

const fullName =
    computed(
        () =>
            lastName.value
            +
            firstName.value
    )

模板:

{{ fullName }}

七十八、computed 适合什么

从已有响应式数据:

推导新数据

例如:

总价

过滤后的列表

是否有权限

全名

七十九、computed 和普通函数区别

computed:

有缓存
依赖没变化时可复用结果

普通函数:

每次调用重新执行

八十、watch

监听数据变化:

watch(
    keyword,
    (
        newValue,
        oldValue
    ) => {

        console.log(
            newValue
        )
    }
)

八十一、watch 适合什么

当某数据变化后:

执行副作用

例如:

重新请求接口

写 localStorage

触发日志

八十二、computed 和 watch 区别

computed:

计算一个结果

watch:

数据变了
执行一件事

八十三、watchEffect

watchEffect(
    () => {

        console.log(
            keyword.value
        )
    }
)

Vue 会自动追踪:

里面使用的响应式依赖

八十四、不要滥用 watch

如果只是:

a + b

应该:

computed

而不是:

watch a
再手动赋值 c

八十五、事件传参数

<button
    @click="deleteStudent(student.id)"
>
    删除
</button>

八十六、事件对象

<button
    @click="handleClick"
>
    点击
</button>

JavaScript:

const handleClick =
    event => {

        console.log(
            event
        )
    }

八十七、事件修饰符

例如:

<form
    @submit.prevent="submit"
>

.prevent:

阻止默认行为

八十八、.stop

@click.stop

阻止:

事件冒泡

八十九、组件是什么

Vue 组件:

可复用 UI 单元

例如:

UserDialog.vue

StudentTable.vue

SearchBar.vue

九十、创建组件

例如:

src/components/UserCard.vue

九十一、UserCard.vue

<script setup>
defineProps({
    username: {
        type: String,
        required: true
    }
})
</script>

<template>
    <div>
        用户:
        {{ username }}
    </div>
</template>

九十二、父组件使用

<script setup>
import UserCard
    from "@/components/UserCard.vue"
</script>

<template>
    <UserCard
        username="admin"
    />
</template>

九十三、Props

Props:

父组件
↓
给子组件传数据

九十四、Props 单向数据流

子组件不应该:

直接修改父组件传来的 prop

应该:

通知父组件修改

九十五、emit

子组件:

向父组件发送事件

九十六、defineEmits

const emit =
    defineEmits([
        "delete"
    ])

const handleDelete =
    () => {

        emit(
            "delete"
        )
    }

九十七、父组件监听子事件

子:

<button
    @click="handleDelete"
>
    删除
</button>

父:

<UserCard
    @delete="deleteUser"
/>

九十八、组件通信基本规则

父 → 子
Props

子 → 父
Emit

九十九、插槽 slot

子组件:

<div class="card">
    <slot></slot>
</div>

父:

<MyCard>
    <h2>
        用户管理
    </h2>
</MyCard>

一百、slot 的作用

让父组件:

向子组件插入内容

适合:

Dialog

Card

Layout

一百零一、生命周期

组件不是:

永远存在

它会经历:

创建

挂载

更新

卸载

一百零二、onMounted

import {
    onMounted
} from "vue"

onMounted(
    () => {

        loadStudents()
    }
)

一百零三、onMounted 常用场景

页面打开时:

请求列表

初始化图表

读取数据

一百零四、onUnmounted

组件卸载时:

onUnmounted(
    () => {

        // 清理
    }
)

一百零五、为什么需要清理

例如:

定时器

WebSocket

事件监听器

如果不清理:

可能内存泄漏

一百零六、常用生命周期

Composition API 常见:

onMounted

onUpdated

onUnmounted

onBeforeMount

onBeforeUnmount

初学重点:

onMounted

onUnmounted

一百零七、页面请求列表例子

<script setup>
import {
    onMounted,
    ref
} from "vue"

const students =
    ref([])

const loadStudents =
    async () => {

    }

onMounted(
    () => {

        loadStudents()
    }
)
</script>

一百零八、Promise

Axios 请求是:

异步操作

常返回:

Promise

一百零九、async / await

推荐:

const loadStudents =
    async () => {

        const response =
            await axios.get(
                "/api/students"
            )

        students.value =
            response.data.data
    }

一百一十、try/catch

const loadStudents =
    async () => {

        try {

            const response =
                await axios.get(
                    "/api/students"
                )

            students.value =
                response.data.data

        } catch (
            error
        ) {

            console.error(
                error
            )
        }
    }

一百一十一、loading

请求时通常:

const loading =
    ref(
        false
    )

一百一十二、完整 loading

const loadStudents =
    async () => {

        loading.value =
            true

        try {

            // request

        } finally {

            loading.value =
                false
        }
    }

一百一十三、为什么 finally

无论:

成功

失败

都要:

关闭 loading

一百一十四、Vue Router 是什么

Vue Router:

Vue 官方路由

用于:

单页应用页面切换

一百一十五、SPA

SPA:

Single Page Application

中文:

单页应用

一百一十六、单页应用不是只有一个页面

它的意思:

通常只有一个 HTML 入口

内部页面:

由 JavaScript Router 切换组件

一百一十七、传统页面

/users.html

/roles.html

/login.html

每次:

服务器返回新 HTML

一百一十八、Vue SPA

浏览器:

加载 index.html

后续:

/login

/users

/roles

由:

Vue Router

切换组件。


一百一十九、Router 配置

import {
    createRouter,
    createWebHistory
} from "vue-router"

import LoginView
    from "@/views/LoginView.vue"

import UserView
    from "@/views/UserView.vue"

const router =
    createRouter({
        history:
            createWebHistory(),

        routes: [
            {
                path: "/login",
                component:
                    LoginView
            },
            {
                path: "/users",
                component:
                    UserView
            }
        ]
    })

export default router

一百二十、main.js 注册 Router

import {
    createApp
} from "vue"

import App
    from "./App.vue"

import router
    from "./router"

const app =
    createApp(
        App
    )

app.use(
    router
)

app.mount(
    "#app"
)

一百二十一、router-view

App.vue:

<router-view />

表示:

当前路由组件
显示在这里

一百二十二、router-link

<router-link
    to="/users"
>
    用户管理
</router-link>

一百二十三、编程式跳转

import {
    useRouter
} from "vue-router"

const router =
    useRouter()

router.push(
    "/users"
)

一百二十四、带参数路由

{
    path:
        "/users/:id",

    component:
        UserDetailView
}

访问:

/users/10

一百二十五、获取路由参数

import {
    useRoute
} from "vue-router"

const route =
    useRoute()

const id =
    route.params.id

一百二十六、query 参数

访问:

/users?page=1&keyword=admin

获取:

route.query.page

route.query.keyword

一百二十七、params 和 query 对应后端

Vue Router:

/users/10
params

SpringBoot:

@PathVariable

Vue:

/users?page=1
query

SpringBoot:

@RequestParam

一百二十八、路由守卫

可以控制:

页面进入前做检查

例如:

没有 Token
→ 去登录

一百二十九、beforeEach

router.beforeEach(
    (
        to,
        from,
        next
    ) => {

        const token =
            localStorage.getItem(
                "token"
            )

        if (
            !token
            &&
            to.path !== "/login"
        ) {

            next(
                "/login"
            )

            return
        }

        next()
    }
)

一百三十、注意前端路由守卫不是安全边界

用户可以:

绕过前端

直接 Postman 请求后端

所以真正安全:

必须后端认证授权

一百三十一、localStorage

浏览器存储:

localStorage.setItem(
    "token",
    token
)

读取:

localStorage.getItem(
    "token"
)

删除:

localStorage.removeItem(
    "token"
)

一百三十二、Token 存 localStorage 有安全风险吗

有。

如果页面发生:

XSS

恶意脚本可能读取 Token。

更安全的认证方案还会考虑:

HttpOnly Cookie

SameSite

CSRF

当前学习项目先理解:

Token 流程

一百三十三、Axios 是什么

Axios:

HTTP Client

前端常用它:

调用 SpringBoot API

一百三十四、安装 Axios

npm install axios

一百三十五、最简单 GET

import axios
    from "axios"

const response =
    await axios.get(
        "http://localhost:8080/api/users"
    )

一百三十六、response

通常:

response.data

才是:

后端 JSON Body

一百三十七、假设后端返回

{
    "code": "SUCCESS",
    "message": "success",
    "data": [
        {
            "id": 1,
            "username": "admin"
        }
    ]
}

Axios:

response.data.code

response.data.message

response.data.data

一百三十八、Axios POST

await axios.post(
    "/api/users",
    {
        username:
            "admin2",

        password:
            "123456"
    }
)

一百三十九、POST JSON 对应 SpringBoot

前端:

axios.post(
    "/api/users",
    form
)

后端:

@PostMapping
public Result<?> create(
        @RequestBody
        UserCreateDTO dto
)

一百四十、Axios PUT

await axios.put(
    "/api/users/10",
    {
        nickname:
            "张三"
    }
)

一百四十一、Axios DELETE

await axios.delete(
    "/api/users/10"
)

一百四十二、Axios Query 参数

await axios.get(
    "/api/users",
    {
        params: {
            pageNum: 1,
            pageSize: 10,
            keyword: ""
        }
    }
)

一百四十三、推荐封装 Axios

不要所有页面:

重复写 baseURL

Token

401

403

建议:

src/utils/request.js

一百四十四、request.js

import axios
    from "axios"

const request =
    axios.create({
        baseURL:
            "/api",

        timeout:
            10000
    })

export default request

一百四十五、baseURL

如果调用:

request.get(
    "/users"
)

最终:

/api/users

一百四十六、请求拦截器

request.interceptors.request.use(
    config => {

        const token =
            localStorage.getItem(
                "token"
            )

        if (
            token
        ) {

            config.headers.Authorization =
                `Bearer ${token}`
        }

        return config
    }
)

一百四十七、为什么 Request Interceptor 很重要

以后每个请求自动:

带 Token

不需要每个 API:

手工写 Header

一百四十八、响应拦截器

request.interceptors.response.use(
    response => {

        return response.data
    },

    error => {

        return Promise.reject(
            error
        )
    }
)

一百四十九、统一处理 401

if (
    error.response?.status
    ===
    401
) {

    localStorage.removeItem(
        "token"
    )

    window.location.href =
        "/login"
}

一百五十、统一处理 403

if (
    error.response?.status
    ===
    403
) {

    // 提示无权限
}

一百五十一、不要把 401 和 403 都跳登录

401:

登录失效

可以:

跳登录

403:

已经登录
但无权限

应该:

提示无权限

一百五十二、API 文件

建议:

src/api

例如:

user.js

auth.js

role.js

一百五十三、auth.js

import request
    from "@/utils/request"

export const login =
    data => {

        return request.post(
            "/auth/login",
            data
        )
    }

export const getMe =
    () => {

        return request.get(
            "/auth/me"
        )
    }

一百五十四、user.js

import request
    from "@/utils/request"

export const getUserPage =
    params => {

        return request.get(
            "/users",
            {
                params
            }
        )
    }

export const createUser =
    data => {

        return request.post(
            "/users",
            data
        )
    }

export const updateUser =
    (
        id,
        data
    ) => {

        return request.put(
            `/users/${id}`,
            data
        )
    }

export const deleteUser =
    id => {

        return request.delete(
            `/users/${id}`
        )
    }

一百五十五、为什么 API 单独封装

页面只关心:

我要加载用户

我要删除用户

而不是关心:

Axios 配置细节

一百五十六、推荐前端分层

View / Component
↓
API Module
↓
request.js
↓
Axios
↓
SpringBoot

一百五十七、Element Plus 是什么

Element Plus:

Vue 3 常用桌面端 UI 组件库

非常适合:

后台管理系统

一百五十八、安装 Element Plus

npm install element-plus

一百五十九、完整引入

学习阶段可以:

import ElementPlus
    from "element-plus"

import "element-plus/dist/index.css"

app.use(
    ElementPlus
)

一百六十、为什么学习阶段先完整引入

优点:

配置简单

容易理解

少踩自动导入配置坑

项目成熟后再:

按需自动导入

一百六十一、Element Plus 常用组件

后台项目必须会:

el-button

el-input

el-form

el-form-item

el-table

el-table-column

el-dialog

el-select

el-option

el-pagination

el-message

el-message-box

el-menu

el-card

el-tag

一百六十二、el-button

<el-button
    type="primary"
>
    新增
</el-button>

一百六十三、el-input

<el-input
    v-model="query.keyword"
    placeholder="请输入用户名"
/>

一百六十四、el-form

<el-form
    :model="form"
>
    <el-form-item
        label="用户名"
    >
        <el-input
            v-model="form.username"
        />
    </el-form-item>
</el-form>

一百六十五、表单验证 rules

const rules = {
    username: [
        {
            required: true,
            message: "请输入用户名",
            trigger: "blur"
        }
    ]
}

一百六十六、el-form 绑定 rules

<el-form
    ref="formRef"
    :model="form"
    :rules="rules"
>

一百六十七、提交前校验

await formRef.value.validate()

校验通过:

再调用后端

一百六十八、前端校验不是安全边界

后端仍然需要:

@Valid

因为用户可以:

绕过前端
直接调用 API

一百六十九、el-table

<el-table
    :data="users"
>
    <el-table-column
        prop="username"
        label="用户名"
    />

    <el-table-column
        prop="nickname"
        label="昵称"
    />
</el-table>

一百七十、操作列

<el-table-column
    label="操作"
>
    <template
        #default="{ row }"
    >
        <el-button
            @click="handleEdit(row)"
        >
            编辑
        </el-button>

        <el-button
            type="danger"
            @click="handleDelete(row)"
        >
            删除
        </el-button>
    </template>
</el-table-column>

一百七十一、#default

这是:

具名/作用域插槽

Element Plus 会把当前:

row

传给模板。


一百七十二、el-dialog

<el-dialog
    v-model="dialogVisible"
    title="新增用户"
>
    ...
</el-dialog>

一百七十三、Dialog 状态

const dialogVisible =
    ref(
        false
    )

打开:

dialogVisible.value =
    true

一百七十四、新增/编辑共用 Dialog

常见:

form.id
为空
→ 新增

form.id
有值
→ 编辑

一百七十五、推荐更清晰

可以加:

const dialogMode =
    ref(
        "create"
    )

取值:

create

edit

一百七十六、为什么显式 mode 更清晰

比通过:

id 是否为空

猜业务状态:

更直观

一百七十七、el-message

import {
    ElMessage
} from "element-plus"

ElMessage.success(
    "保存成功"
)

一百七十八、错误消息

ElMessage.error(
    "保存失败"
)

一百七十九、删除确认

await ElMessageBox.confirm(
    "确认删除该用户吗?",
    "提示",
    {
        type: "warning"
    }
)

一百八十、分页组件

<el-pagination
    v-model:current-page="query.pageNum"
    v-model:page-size="query.pageSize"
    :total="total"
    @current-change="loadUsers"
    @size-change="loadUsers"
/>

一百八十一、分页数据

const query =
    reactive({
        pageNum: 1,
        pageSize: 10,
        keyword: ""
    })

const total =
    ref(
        0
    )

一百八十二、后端分页响应

例如:

{
    "code": "SUCCESS",
    "data": {
        "records": [],
        "total": 100,
        "pageNum": 1,
        "pageSize": 10
    }
}

前端:

users.value =
    result.data.records

total.value =
    result.data.total

一百八十三、完整用户列表页面思路

UserView.vue
│
├─ 查询表单
├─ 新增按钮
├─ el-table
├─ 操作按钮
├─ el-pagination
└─ UserDialog

一百八十四、UserView 数据

const loading =
    ref(
        false
    )

const users =
    ref([])

const total =
    ref(
        0
    )

const query =
    reactive({
        pageNum: 1,
        pageSize: 10,
        keyword: ""
    })

一百八十五、加载用户

const loadUsers =
    async () => {

        loading.value =
            true

        try {

            const result =
                await getUserPage(
                    query
                )

            users.value =
                result.data.records

            total.value =
                result.data.total

        } finally {

            loading.value =
                false
        }
    }

一百八十六、搜索

const handleSearch =
    () => {

        query.pageNum =
            1

        loadUsers()
    }

一百八十七、为什么搜索重置第一页

如果当前:

第 8 页

然后输入一个只有:

1 页结果

如果不回第 1 页:

可能显示空数据

一百八十八、重置搜索

const resetSearch =
    () => {

        query.keyword =
            ""

        query.pageNum =
            1

        loadUsers()
    }

一百八十九、新增用户

const openCreate =
    () => {

        dialogMode.value =
            "create"

        Object.assign(
            form,
            {
                id: null,
                username: "",
                password: "",
                nickname: ""
            }
        )

        dialogVisible.value =
            true
    }

一百九十、编辑用户

const openEdit =
    row => {

        dialogMode.value =
            "edit"

        Object.assign(
            form,
            {
                id:
                    row.id,

                username:
                    row.username,

                nickname:
                    row.nickname,

                password:
                    ""
            }
        )

        dialogVisible.value =
            true
    }

一百九十一、提交

const submit =
    async () => {

        await formRef.value.validate()

        if (
            dialogMode.value
            ===
            "create"
        ) {

            await createUser(
                form
            )

        } else {

            await updateUser(
                form.id,
                form
            )
        }

        ElMessage.success(
            "保存成功"
        )

        dialogVisible.value =
            false

        loadUsers()
    }

一百九十二、删除

const handleDelete =
    async row => {

        await ElMessageBox.confirm(
            `确认删除 ${row.username} 吗?`,
            "提示",
            {
                type:
                    "warning"
            }
        )

        await deleteUser(
            row.id
        )

        ElMessage.success(
            "删除成功"
        )

        loadUsers()
    }

一百九十三、为什么删除后重新加载列表

数据库已经:

删除

前端旧数组:

仍可能包含该用户

重新请求:

保证和后端一致

一百九十四、也可以前端直接 remove 吗

可以。

但后台 CRUD 初学时:

操作成功后重新加载

最稳。


一百九十五、登录页面

核心数据:

const form =
    reactive({
        username: "",
        password: ""
    })

一百九十六、登录提交

const handleLogin =
    async () => {

        const result =
            await login(
                form
            )

        localStorage.setItem(
            "token",
            result.data.token
        )

        router.push(
            "/"
        )
    }

一百九十七、登录后权限

后端可能返回:

roles

permissions

前端可以保存:

Pinia

或者学习阶段:

内存 ref / localStorage

一百九十八、为什么权限不建议全部长期写 localStorage

权限:

可能随时变

更合理:

页面刷新后
带 Token 请求 /auth/me

重新获得:

当前权限

一百九十九、前端菜单权限

假设:

permissions.value.includes(
    "user:list"
)

才显示:

用户管理

二百、按钮权限

<el-button
    v-if="
        permissions.includes(
            'user:add'
        )
    "
>
    新增
</el-button>

二百零一、再次强调

这个:

只是 UI 控制

后端仍然必须:

user:add

权限校验。


二百零二、SpringBoot 前后端联调

前端:

http://localhost:5173

后端:

http://localhost:8080

直接请求:

可能跨域

二百零三、什么是同源

Origin 包括:

协议

域名

端口

只要有一项不同:

不同源

二百零四、5173 和 8080

端口不同

所以:

不同 Origin

二百零五、解决方式 1:后端 CORS

SpringBoot:

@Configuration
public class WebConfig
        implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(
            CorsRegistry registry
    ) {

        registry
                .addMapping(
                        "/api/**"
                )
                .allowedOrigins(
                        "http://localhost:5173"
                )
                .allowedMethods(
                        "GET",
                        "POST",
                        "PUT",
                        "DELETE",
                        "OPTIONS"
                );
    }
}

二百零六、解决方式 2:Vite Proxy

开发阶段更常用:

Vite 代理

二百零七、vite.config.js

import {
    defineConfig
} from "vite"

import vue
    from "@vitejs/plugin-vue"

export default defineConfig({
    plugins: [
        vue()
    ],

    server: {
        proxy: {
            "/api": {
                target:
                    "http://localhost:8080",

                changeOrigin:
                    true
            }
        }
    }
})

二百零八、代理以后

浏览器请求:

http://localhost:5173/api/users

Vite:

转发到
http://localhost:8080/api/users

二百零九、前端 Axios baseURL

可以:

baseURL:
    "/api"

不必硬编码:

http://localhost:8080

二百一十、为什么推荐开发代理

前端代码:

更接近生产部署路径

也减少:

开发跨域配置

二百一十一、生产环境怎么办

生产:

Nginx

可以:

/ 
→ Vue 静态文件

/api
→ SpringBoot

浏览器看起来:

同一个域名

二百一十二、典型生产架构

Browser
↓
Nginx
├─ /
│  → Vue dist
│
└─ /api
   → SpringBoot:8080

二百一十三、这就是前后端分离部署

Vue:

构建为静态文件

SpringBoot:

运行 API 服务

Nginx:

统一入口

二百一十四、常见错误:npm command not found

说明:

Node/npm 未安装

或者:

PATH 未生效

检查:

node -v
npm -v

二百一十五、常见错误:npm install 很慢

原因:

网络

registry

代理

不要一上来:

反复删除所有项目

先确认:

网络
npm registry

二百一十六、常见错误:node_modules 损坏

可以尝试:

关闭 dev server

删除:

node_modules

然后:

npm install

但不要:

遇到任何错误都这么做

二百一十七、常见错误:页面空白

检查浏览器:

F12
→ Console

常见:

JS 报错

组件 import 错

Router 错

变量未定义

二百一十八、常见错误:组件找不到

检查:

import 路径

文件名大小写

.vue 后缀

alias @

二百一十九、@ 别名

很多 Vite Vue 项目配置:

@
→ src

于是:

import UserView
    from "@/views/UserView.vue"

二百二十、常见错误:ref 忘 .value

JavaScript:

loading =
    true

错误。

如果:

const loading =
    ref(false)

应该:

loading.value =
    true

二百二十一、模板中不用 .value

模板:

{{ loading }}

不是:

{{ loading.value }}

二百二十二、常见错误:reactive 解构丢响应式

例如:

const state =
    reactive({
        count: 0
    })

const {
    count
} =
    state

直接解构:

可能失去预期响应式连接

可以:

toRefs

或:

直接 state.count

二百二十三、初学避免复杂解构

先:

state.count

最清楚。


二百二十四、常见错误:v-for 没 key

会警告:

列表渲染缺少 key

推荐:

:key="row.id"

二百二十五、常见错误:v-if 和 v-for 写同元素

不推荐把:

v-if
v-for

复杂地放同一个节点。

建议:

computed 先过滤

二百二十六、常见错误:Axios 得到 undefined

后端:

{
    "data": {
        "records": []
    }
}

如果 response interceptor 已经:

return response.data

页面再写:

result.data.data.records

就多了一层。


二百二十七、必须统一 Axios 返回约定

例如 request.js:

return response.data

那么页面:

result.data

表示后端 Result 的:

data

二百二十八、不要不同 API 不同返回方式

否则:

有的 response.data.data

有的 result.data

有的 result

很乱。


二百二十九、常见错误:SpringBoot 404

前端请求:

/api/users

后端:

@RequestMapping(
    "/users"
)

如果后端没有:

/api

前缀,

就不匹配。


二百三十、先看 Network

浏览器:

F12
→ Network

查看:

Request URL

Method

Status

Request Payload

Response

二百三十一、前后端联调排错顺序

1. 前端是否发请求

2. Request URL

3. HTTP Method

4. Query / Body

5. Content-Type

6. SpringBoot 是否收到

7. Controller Mapping

8. Service

9. Mapper / SQL

10. Response JSON

11. Vue 是否正确取 data

二百三十二、常见错误:400

检查:

JSON 字段

类型

@Valid

@RequestBody

二百三十三、常见错误:401

检查:

Token 是否保存

Request Interceptor

Authorization Header

Bearer 空格

Token 是否过期

二百三十四、常见错误:403

说明:

大概率已经登录

检查:

permission code

角色权限关系

后端鉴权

二百三十五、常见错误:415

检查:

Content-Type

Axios POST 对象通常:

会按 JSON 发送

如果自己错误配置:

可能导致 415

二百三十六、常见错误:CORS

浏览器 Console 出现:

blocked by CORS policy

说明:

请求被浏览器跨域策略拦截

二百三十七、CORS 不是后端业务异常

SpringBoot 可能:

接口本身正常

但浏览器:

不允许前端读取响应

二百三十八、Postman 为什么没 CORS

CORS 主要是:

浏览器安全机制

Postman:

不受浏览器同源策略约束

所以常见:

Postman 成功
浏览器失败

二百三十九、这种情况优先查什么

CORS

Vite Proxy

二百四十、环境变量

Vite 项目可以:

.env.development

.env.production

二百四十一、Vite 环境变量

客户端可访问变量通常需要:

VITE_

前缀。

例如:

VITE_API_BASE_URL

二百四十二、读取

import.meta.env
    .VITE_API_BASE_URL

二百四十三、不要把 Secret 放 VITE_ 环境变量

因为:

前端最终代码发给浏览器

浏览器用户:

可以看到

所以不能放:

数据库密码

JWT 私钥

云 SecretKey

二百四十四、前端不存在真正的 Secret

只要打包到客户端:

用户就有机会查看

安全密钥:

必须放后端

二百四十五、AI 驱动 Web 是什么意思

这里不是:

让 AI 完全替你开发

而是:

利用 AI 提高前端开发效率

二百四十六、AI 适合做什么

例如:

根据接口文档生成 API 封装

根据 DTO 生成表单

根据字段生成 Element Plus 表格

排查 Console 报错

解释 Vue 代码

重构重复组件

生成 CRUD 页面初稿

二百四十七、AI 不适合直接盲信什么

尤其:

依赖版本

过时 API

不存在的组件属性

错误的 Vue2 写法

错误的 Vue3 写法混用

安全配置

二百四十八、AI 生成 Vue 代码后第一步

确认:

Vue 2 还是 Vue 3

本课程:

Vue 3

二百四十九、Vue 2 / Vue 3 容易混淆

老代码:

new Vue(...)

Vue.prototype

Options API 老写法

新项目主要:

createApp

script setup

Composition API

二百五十、给 AI 的提示词要包含项目上下文

例如:

技术栈:
Vue3 + Vite + JavaScript + Element Plus + Axios

后端:
SpringBoot RESTful

统一响应:
{
  code,
  message,
  data
}

要求:
使用 script setup
不要使用 Vue2 API

二百五十一、AI 生成 CRUD 页面提示词示例

为 Vue3 + Vite + Element Plus 项目创建用户管理页面。

后端接口:

GET /api/users
POST /api/users
PUT /api/users/{id}
DELETE /api/users/{id}

分页参数:
pageNum
pageSize
keyword

响应:
{
  code,
  message,
  data: {
    records,
    total
  }
}

要求:
使用 <script setup>
使用 Axios 已封装 request.js
页面包含搜索、重置、新增、编辑、删除和分页。

二百五十二、为什么把接口文档给 AI

如果只说:

帮我做用户管理

AI 会:

自己猜字段

自己猜接口

自己猜响应

很容易和后端:

对不上

二百五十三、AI 驱动开发正确顺序

先有数据库/API 设计
↓
后端接口跑通
↓
把接口文档给 AI
↓
AI 生成前端初稿
↓
自己运行
↓
F12 Network 验证
↓
修正

二百五十四、不要同时让 AI 改全项目

初学时最好:

一个页面

一个功能

一个报错

逐步修改。


二百五十五、为什么

一次改几十个文件:

你无法判断错误来源

二百五十六、Cursor 中使用 AI 的建议

每次给上下文:

当前文件

后端接口

项目目录

报错信息

期望行为

不要只说:

不能用了,修一下

二百五十七、错误信息要完整

最好提供:

浏览器 Console

Network Response

SpringBoot Console

三部分。


二百五十八、AI 排错最有价值的输入

例如:

Request URL:
http://localhost:5173/api/users

Status:
403

Response:
{
  "code": "FORBIDDEN"
}

当前 token 已存在

比:

页面不行

有效很多。


二百五十九、Vue 项目推荐目录

src
│
├─ api
│  ├─ auth.js
│  ├─ user.js
│  └─ role.js
│
├─ assets
│
├─ components
│
├─ layout
│
├─ router
│
├─ stores
│
├─ utils
│  └─ request.js
│
├─ views
│  ├─ login
│  ├─ user
│  └─ role
│
├─ App.vue
└─ main.js

二百六十、views 和 components 区别

views:

路由页面

components:

可复用组件

二百六十一、例如

views/user/UserView.vue

是:

用户管理整页

components/UserDialog.vue

是:

可复用弹窗

二百六十二、utils

放:

通用工具

例如:

request.js

date.js

storage.js

二百六十三、api

放:

后端接口函数

二百六十四、router

放:

路由表

路由守卫

二百六十五、stores

后面使用 Pinia:

用户状态

权限

全局设置

二百六十六、Pinia 是什么

Vue 3 推荐状态管理方案:

Pinia

适合管理:

跨组件共享状态

例如:

当前登录用户

权限

主题

菜单

二百六十七、什么时候不需要 Pinia

如果数据只在:

一个页面

使用。

就用:

ref / reactive

不要什么都放全局。


二百六十八、状态管理原则

能局部
就局部

确实多个页面共享
再全局

二百六十九、Vue DevTools

建议浏览器安装:

Vue DevTools

可以查看:

组件树

Props

响应式状态

调试很方便。


二百七十、Chrome DevTools 必须掌握

至少:

Console

Network

Elements

Application

二百七十一、Console

看:

JavaScript Error

Vue Warning

二百七十二、Network

看:

HTTP 请求

这是前后端联调最重要。


二百七十三、Application

可以看:

localStorage

sessionStorage

Cookie

二百七十四、Elements

看:

最终 DOM

CSS

二百七十五、前端学习不要只看页面效果

你要同时会看:

代码

Console

Network

状态

二百七十六、一个完整 CRUD 请求流

点击:

查询

↓

handleSearch()

↓

getUserPage(query)

↓

request.js

↓

Axios

↓

GET /api/users

↓

Vite Proxy

↓

SpringBoot

↓

UserController

↓

UserService

↓

UserMapper

↓

MySQL

↓

Result<PageResult<UserVO>>

↓

Axios Response

↓

users.value

↓

el-table 自动刷新

二百七十七、这张流程必须记住

事件
↓
函数
↓
API
↓
Axios
↓
HTTP
↓
SpringBoot
↓
JSON
↓
响应式数据
↓
Vue DOM

二百七十八、练习 1:计数器

要求:

count
+
-
reset

使用:

ref

@click

二百七十九、练习 2:登录表单

使用:

reactive

v-model

el-form

rules

字段:

username

password

二百八十、练习 3:学生列表

先不用后端。

写:

const students =
    ref([
        {
            id: 1,
            name: "张三"
        }
    ])

使用:

v-for

二百八十一、练习 4:Element Plus Table

把学生数组:

渲染为 el-table

二百八十二、练习 5:搜索

使用:

keyword
+
computed

本地过滤学生。


二百八十三、练习 6:Dialog

点击:

新增

打开:

el-dialog

二百八十四、练习 7:Vue Router

创建:

/login

/users

/roles

三个页面。


二百八十五、练习 8:Axios GET

SpringBoot 创建:

GET /api/users

Vue:

加载列表

二百八十六、练习 9:Axios POST

创建:

新增用户

前端 JSON:

→ @RequestBody

二百八十七、练习 10:Vite Proxy

配置:

/api
→ localhost:8080

前端不再写:

完整后端 URL

二百八十八、练习 11:Request Interceptor

登录后:

localStorage token

请求自动:

Authorization Bearer

二百八十九、练习 12:401

清 Token:

访问用户页面

后端返回:

401

前端自动:

跳登录

二百九十、练习 13:403

用普通用户 Token:

调用删除接口

观察:

403

前端:

提示无权限

二百九十一、练习 14:按钮权限

有:

user:add

才显示:

新增按钮

二百九十二、练习 15:分页

SpringBoot:

pageNum
pageSize

Vue:

el-pagination

完成联调。


二百九十三、练习 16:完整 User CRUD

最终完成:

搜索

分页

新增

编辑

删除

权限按钮

loading

消息提示

二百九十四、必须掌握 Vue 基础

createApp

SFC

script setup

template

ref

reactive

computed

watch

v-if

v-show

v-for

v-model

v-bind

v-on

二百九十五、必须掌握组件

Props

Emit

Slot

Component

Lifecycle

二百九十六、必须掌握 Router

createRouter

router-view

router-link

useRouter

useRoute

params

query

beforeEach

二百九十七、必须掌握 Axios

axios.create

baseURL

GET

POST

PUT

DELETE

params

Request Interceptor

Response Interceptor

二百九十八、必须掌握 Element Plus

Button

Input

Form

Table

Dialog

Select

Pagination

Message

MessageBox

二百九十九、必须掌握联调

Vite 5173

SpringBoot 8080

CORS

Proxy

Network

JSON

Token

401

403

三百、面试题 1

问:

Vue 是什么?

答:

Vue 是一个渐进式 JavaScript 前端框架。

它的核心思想包括
数据驱动视图和组件化开发。

Vue 3 中常使用 Composition API
和 Single File Component 组织应用。

三百零一、面试题 2

问:

ref 和 reactive 有什么区别?

答:

ref 可以包装任意值,
JavaScript 中读取和修改通常通过 .value。

reactive 主要用于对象类型,
可以直接通过对象属性读取和修改。

模板中 ref 通常会自动解包。

三百零二、面试题 3

问:

computed 和 watch 区别?

答:

computed 主要用于根据现有响应式数据
计算出新的派生值,并且具有缓存特性。

watch 用于监听数据变化后执行副作用,
例如重新请求接口或写入存储。

三百零三、面试题 4

问:

v-if 和 v-show 区别?

答:

v-if 会根据条件真正创建或销毁对应 DOM。

v-show 通常保留 DOM,
通过 CSS display 控制显示隐藏。

频繁切换可以考虑 v-show,
很少切换可以使用 v-if。

三百零四、面试题 5

问:

为什么 v-for 要使用 key?

答:

key 用来帮助 Vue 识别列表节点的稳定身份,
提高更新效率并避免错误复用组件状态。

通常应使用数据库 id 等稳定唯一值,
不建议在会增删排序的列表中使用 index。

三百零五、面试题 6

问:

Vue 父子组件如何通信?

答:

父组件向子组件传数据主要使用 Props。

子组件向父组件通知事件主要使用 Emit。

如果要传入一段 UI 内容,
可以使用 Slot。

三百零六、面试题 7

问:

Vue Router 是什么?

答:

Vue Router 是 Vue 单页应用的路由解决方案。

它可以根据浏览器 URL
在 router-view 中切换不同页面组件,
而不需要每次从服务器重新加载完整 HTML。

三百零七、面试题 8

问:

Axios 拦截器有什么作用?

答:

请求拦截器可以统一给请求增加 Token、
Header 等信息。

响应拦截器可以统一处理响应格式、
401、403 和错误提示。

这样可以避免每个 API 重复写相同代码。

三百零八、面试题 9

问:

为什么 Vue 前端访问 SpringBoot 会跨域?

答:

浏览器的 Origin 由协议、主机和端口组成。

开发环境中 Vue 通常运行在 5173,
SpringBoot 通常运行在 8080,
端口不同就属于不同源。

可以通过后端 CORS
或 Vite 开发代理解决。

三百零九、面试题 10

问:

为什么 Postman 成功但浏览器 CORS 失败?

答:

CORS 属于浏览器的同源安全机制。

Postman 不是浏览器页面,
通常不会受到浏览器同源策略的限制。

所以 Postman 接口成功,
不代表浏览器跨域配置一定正确。

三百一十、面试题 11

问:

前端隐藏按钮能不能代替后端权限?

答:

不能。

前端隐藏按钮只能改善用户体验。

用户仍然可以使用 Postman
或自己构造 HTTP 请求调用接口。

真正权限控制必须由后端完成。

三百一十一、面试题 12

问:

Vite 是做什么的?

答:

Vite 是现代前端开发和构建工具。

开发阶段提供 Dev Server 和快速热更新,
生产阶段通过 build 命令
生成优化后的静态资源。

三百一十二、面试题 13

问:

npm install 做了什么?

答:

npm 会根据 package.json
以及 lock 文件解析依赖,
把需要的包安装到 node_modules。

它在前端依赖管理中的角色
和 Maven 管理 Java 依赖有一定相似之处。

三百一十三、面试题 14

问:

Vue 页面怎么调用 SpringBoot?

答:

Vue 页面触发事件以后,
调用封装在 api 模块中的 Axios 方法。

Axios 发送 HTTP 请求到 SpringBoot Controller,
后端处理后返回 JSON。

前端把 JSON data 写入 ref/reactive,
Vue 再根据响应式数据自动更新页面。

三百一十四、面试题 15

问:

为什么前端不能保存真正的 Secret?

答:

Vue 最终会构建成发送给浏览器的 JavaScript 和静态资源。

用户可以查看这些客户端文件,
因此数据库密码、JWT 私钥、
云服务 SecretKey 等真正敏感密钥
必须保存在后端。

三百一十五、Vue 知识结构

Vue 3
│
├─ Project
│  ├─ Node
│  ├─ npm
│  ├─ create-vue
│  └─ Vite
│
├─ Core
│  ├─ ref
│  ├─ reactive
│  ├─ computed
│  ├─ watch
│  └─ lifecycle
│
├─ Template
│  ├─ interpolation
│  ├─ v-bind
│  ├─ v-on
│  ├─ v-model
│  ├─ v-if
│  └─ v-for
│
├─ Component
│  ├─ props
│  ├─ emit
│  └─ slot
│
├─ Router
│  ├─ path
│  ├─ params
│  ├─ query
│  └─ guard
│
├─ HTTP
│  ├─ Axios
│  ├─ Request Interceptor
│  ├─ Response Interceptor
│  └─ API Module
│
├─ UI
│  └─ Element Plus
│
└─ Backend
   ├─ SpringBoot
   ├─ RESTful
   ├─ JSON
   ├─ Token
   ├─ CORS
   └─ Vite Proxy

三百一十六、Vue 和 Java 后端知识对应

Vue package.json
≈
Maven pom.xml

npm
≈
Maven 依赖管理工具

Vue Component
≈
可复用 UI 类/模块思想

Props
≈
方法参数的单向输入思想

Router
≈
前端 URL 路由

Axios
≈
HTTP Client

request.js
≈
统一 HTTP 工具封装

Vue DTO Object
↔
SpringBoot DTO

JSON
↔
@RequestBody / Response Body

三百一十七、完整前后端项目结构

前端:

vue-admin
│
├─ src
│  ├─ api
│  ├─ components
│  ├─ router
│  ├─ utils
│  ├─ views
│  ├─ App.vue
│  └─ main.js
└─ package.json

后端:

springboot-rbac
│
├─ controller
├─ service
├─ mapper
├─ entity
├─ dto
├─ vo
└─ application.yml

三百一十八、完整联调图

Vue UserView
↓
@click
↓
loadUsers
↓
user.js
↓
request.js
↓
Axios
↓
Vite Proxy
↓
SpringBoot 8080
↓
Auth Interceptor
↓
UserController
↓
UserService
↓
UserMapper
↓
MySQL
↓
Result<PageResult<UserVO>>
↓
JSON
↓
Axios Response Interceptor
↓
users.value
↓
Element Plus Table

三百一十九、AI 驱动完整开发流程

需求
↓
数据库设计
↓
SpringBoot API
↓
Postman 验证
↓
接口文档
↓
给 AI 明确 Vue 技术栈和接口
↓
生成 Vue 页面
↓
npm run dev
↓
F12 Network
↓
修复接口和字段
↓
组件拆分
↓
统一 request.js
↓
完善权限
↓
npm run build

三百二十、本章总结

这一章最重要的是从:

Java 后端思维

成功切换到:

前后端分离思维

Vue 负责:

页面

交互

组件

状态

路由

SpringBoot 负责:

业务

权限

数据库

安全

API

Vue 核心:

ref

reactive

computed

watch

v-model

v-if

v-for

component

组件通信:

Props

Emit

Slot

路由:

Vue Router

HTTP:

Axios

后台 UI:

Element Plus

前后端联调:

Vue 5173
↓
Vite Proxy
↓
SpringBoot 8080

权限:

前端菜单/按钮
只负责展示

后端
负责真正鉴权

最重要调试工具:

F12
↓
Console
+
Network

使用 AI 时必须记住:

AI 生成的是初稿

接口和项目约束必须明确告诉 AI

自己必须能运行、验证和修改

到这里,你应该已经能完成:

Vue3 项目创建

基础组件页面

Element Plus 表单和表格

Vue Router 页面跳转

Axios 调 SpringBoot

Token 自动携带

401 / 403 处理

学生/用户管理 CRUD 页面

第三阶段课程表里:

AI 驱动 Web - Vue

还有第二部分。

下一篇会继续深入:

Vue 企业项目进阶
+
Pinia
+
动态路由
+
权限菜单
+
组件封装
+
Axios 完整封装
+
Layout
+
Element Plus 后台框架
+
SpringBoot RBAC 前后端完整联调

官方工具链参考

Vue 官方新项目脚手架:

https://vuejs.org/guide/scaling-up/tooling
https://cn.vuejs.org/guide/quick-start

Vite:

https://vite.dev/guide/

Element Plus:

https://element-plus.org/zh-CN/guide/quickstart.html

说明:

前端工具链更新较快。

如果实际创建项目时出现 Node 版本、
依赖版本或脚手架提示差异,
以当时官方文档和命令行提示为准。