从零构建带AI流式对话的Vue2全栈应用-CSDN博客本文是对这篇文章阶段三的讲解
一、阶段三解决什么问题
阶段二的数据写死在前端:
users: [ { id: 1, name: '张三' } ]这种数据刷新后不会真正变化,也不是从服务器获取。
阶段三开始让数据来自后端:
Vue 页面 ↓ 请求 Express 后端 ↓ 返回 JSON Vue 页面显示结果核心技术:
HTTP REST API Express Axios Vue 开发代理 请求拦截器 响应拦截器 async/await二、Express 后端基础
文件:
mock-server/server.js顶部:
const express = require('express') const app = express() const PORT = 30001.require('express')
const express = require('express')从已经安装的express软件包中读取 Express。
Express 是一个 Node.js 后端框架。
它能帮助我们处理:
GET 请求 POST 请求 PATCH 请求 DELETE 请求 JSON 数据 URL 参数2. 创建服务器对象
app可以理解成后端服务器。
后面所有接口都写在app上:
app.get(...) app.post(...) app.patch(...) app.delete(...)3. 设置端口
const PORT = 3000后端运行后地址是:
http://localhost:3000localhost表示当前电脑。
3000表示端口。
端口可以理解成一栋楼里的房间号:
localhost:这台电脑 3000:后端程序所在房间 8081:前端程序所在房间4. 解析 JSON 请求体
app.use(express.json())前端发送:
{ "name": "张三", "email": "zhangsan@example.com" }Express 需要把这段 JSON 转成 JavaScript 对象。
有了express.json(),后端才能使用:
req.body.name req.body.email没有这一行,req.body可能是undefined。
三、模拟数据库
let users = [ { id: 1, name: '张三', email: 'zhangsan@example.com', role: 'admin', status: true } ]这里暂时没有真正数据库。
使用数组模拟数据库。
users 数组 ≈ 用户表 数组中的对象 ≈ 数据库中的一行因为使用的是内存数组:
新增用户后可以暂时看到 后端重启后数据恢复这是教学阶段的正常现象。
四、GET 查询接口
app.get('/api/users', (req, res) => { res.json({ code: 0, message: '获取用户列表成功', data: users }) })app.get
表示处理 GET 请求。
前端请求:
GET /api/users后端进入这个函数。
req和res
(req, res) => {}req是 request,请求。
里面有:
req.query:查询参数 req.params:路径参数 req.body:请求体 req.headers:请求头 req.cookies:Cookieres是 response,响应。
常用方法:
res.json() res.status() res.send() res.redirect()返回统一格式
res.json({ code: 0, message: '获取用户列表成功', data: users })统一返回格式:
code:业务状态 message:提示信息 data:真正的数据成功:
code: 0失败:
code: 400 code: 401 code: 404 code: 500五、查询参数
前端请求:
/api/users?keyword=张三&role=admin后端读取:
req.query.keyword req.query.role代码:
const keyword = String( req.query.keyword || '' ) .trim() .toLowerCase()逐步解释:
req.query.keyword || ''表示有关键词就使用关键词,没有就使用空字符串。
String(...)确保它是字符串。
.trim()去掉前后空格。
例如:
" 张三 "变成:
"张三".toLowerCase()转成小写,方便不区分大小写搜索。
六、数组筛选
const result = users.filter(user => { return user.name.includes(keyword) })filter()用于筛选数组。
它会逐个检查用户。
return true保留这个用户。
return false删除这个用户。
例如:
users.filter(user => { return user.role === 'admin' })只保留管理员。
七、POST 新增接口
app.post('/api/users', (req, res) => { const name = req.body.name const email = req.body.email const newUser = { id: nextUserId, name, email } nextUserId += 1 users.unshift(newUser) res.status(201).json({ code: 0, message: '新增成功', data: newUser }) })POST 和 GET 的区别
GET 通常用于查询:
GET /api/usersPOST 通常用于新增:
POST /api/users新增的数据放在请求体中。
users.unshift(newUser)
users.unshift(newUser)把新用户放到数组最前面。
如果使用:
users.push(newUser)则会放到最后面。
状态码 201
res.status(201)HTTP 状态码:
200:请求成功 201:创建成功 400:参数错误 401:没有登录 403:没有权限 404:找不到资源 500:服务器错误八、PATCH 修改状态
路由:
app.patch('/api/users/:id/status', ...)其中:
:id是动态路径参数。
请求:
PATCH /api/users/3/status后端读取:
req.params.id得到字符串:
"3"转换为数字:
const userId = Number(req.params.id)然后查找用户:
const user = users.find(item => { return item.id === userId })find()找到第一个符合条件的对象。
九、DELETE 删除用户
const userIndex = users.findIndex(item => { return item.id === userId })findIndex()返回数组下标。
例如张三在第一个位置:
userIndex === 0删除:
users.splice(userIndex, 1)含义:
从 userIndex 位置开始 删除 1 个元素十、为什么 404 必须放在最后
app.use((req, res) => { res.status(404).json({ code: 404, message: '接口不存在' }) })Express 从上往下匹配代码。
正确顺序:
GET /api/users POST /api/users PATCH /api/users/:id/status DELETE /api/users/:id 最后处理 404如果把 404 放最前面:
所有请求都会先匹配 404 后面的接口永远不会执行十一、Vue 代理vue.config.js
module.exports = defineConfig({ devServer: { proxy: { '^/api': { target: 'http://localhost:3000', changeOrigin: true } } } })前端代码请求:
/api/users浏览器实际访问前端服务器:
http://localhost:8081/api/usersVue 开发服务器发现路径以/api开头,然后转发:
http://localhost:3000/api/users代理流程:
浏览器 ↓ /api/users Vue 开发服务器 8081 ↓ 转发 Express 后端 3000十二、Axios 实例request.js
const service = axios.create({ baseURL: '/api', timeout: 10000 })baseURL
调用:
request({ url: '/users' })Axios 会组合成:
/api/userstimeout
timeout: 10000单位是毫秒。
10000 毫秒 = 10 秒如果后端 10 秒都没有响应,Axios 会认为超时。
十三、请求拦截器
service.interceptors.request.use( config => { console.log(config) return config }, error => { return Promise.reject(error) } )请求拦截器会在请求真正发出之前执行。
流程:
页面调用 getUsers() ↓ 进入请求拦截器 ↓ 可以修改请求头、打印日志 ↓ 发送到后端为什么一定要:
return config因为 Axios 需要继续使用这个请求配置。
不返回,后续请求可能无法正常发出。
十四、响应拦截器
后端返回:
{ "code": 0, "message": "成功", "data": [ { "id": 1 } ] }响应拦截器:
return responseBody.data所以页面最终得到的不是整个对象,而是:
[ { id: 1 } ]页面可以直接:
this.users = result而不用:
this.users = result.data.data十五、API 文件user.js
export function getUsers(params) { return request({ url: '/users', method: 'get', params }) }为什么不直接在页面写 Axios?
因为把接口集中放在src/api中更容易管理。
页面只关心:
getUsers() createUser() deleteUser()不需要反复写:
axios.get(...) axios.post(...)这是代码分层:
页面层:控制页面显示 API 层:描述调用哪个接口 request 层:统一处理 Axios 后端层:处理真实业务十六、created()生命周期
created() { this.fetchUsers() }created()是 Vue 生命周期钩子。
组件创建完成时自动执行一次。
所以进入页面后,会自动加载用户列表。
不需要用户先点击“查询”。
十七、async/await
async fetchUsers() { const result = await getUsers() this.users = result }接口请求需要时间,不会立刻返回。
await表示:
等待请求完成,再继续往下执行。
使用await的函数前必须有:
async错误:
fetchUsers() { const result = await getUsers() }正确:
async fetchUsers() { const result = await getUsers() }十八、try/catch/finally
async fetchUsers() { this.loading = true try { this.users = await getUsers() } catch (error) { this.$message.error('加载失败') } finally { this.loading = false } }执行过程:
开始请求 loading = true ↓ 成功:进入 try 失败:进入 catch ↓ 无论成功失败,都进入 finally loading = false所以finally非常适合关闭加载动画。
十九、阶段三完整数据流
ApiDemoView.vue 调用 getUsers() ↓ src/api/user.js 配置 GET /users ↓ src/utils/request.js 添加 baseURL /api ↓ 请求 /api/users ↓ Vue 代理到 localhost:3000 ↓ Express app.get('/api/users') ↓ 返回 JSON ↓ 响应拦截器提取 data ↓ this.users = result ↓ el-table 自动更新