一、环境准备
1.1 创建vue工程(big-event)
1 2 3 4
| npm init vue@latest
cd big-event npm install
|
1.2 安装插件
安装element-plus
1 2 3 4 5
| 1.1 执行命令: npm install element-plus --save 1.2 在main.js中做如下配置 import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' app.use(ElementPlus)
|
安装axios
安装sass依赖
1.3 目录调整
删除components目录下的内容
删除App.vue中的内容,只保留script和template标签
新建如下目录:
api:存放接口调用的js文件
utils:存放工具js文件
拷贝request.js到util目录
views:存放页面的.vue文件
- 删除assets目录中的内容, 将资料中的静态资源文件全部拷贝到该目录下
二、注册
2.1 页面搭建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| <script setup> import { User, Lock } from '@element-plus/icons-vue' import { ref } from 'vue'
const isRegister = ref(false) </script>
<template> <el-row class="login-page"> <el-col :span="12" class="bg"></el-col> <el-col :span="6" :offset="3" class="form"> <el-form ref="form" size="large" autocomplete="off" v-if="isRegister"> <el-form-item> <h1>注册</h1> </el-form-item> <el-form-item> <el-input :prefix-icon="User" placeholder="请输入用户名"></el-input> </el-form-item> <el-form-item> <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input> </el-form-item> <el-form-item> <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"></el-input> </el-form-item> <el-form-item> <el-button class="button" type="primary" auto-insert-space> 注册 </el-button> </el-form-item> <el-form-item class="flex"> <el-link type="info" :underline="false" @click="isRegister = false"> ← 返回 </el-link> </el-form-item> </el-form> <el-form ref="form" size="large" autocomplete="off" v-else> <el-form-item> <h1>登录</h1> </el-form-item> <el-form-item> <el-input :prefix-icon="User" placeholder="请输入用户名"></el-input> </el-form-item> <el-form-item> <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input> </el-form-item> <el-form-item class="flex"> <div class="flex"> <el-checkbox>记住我</el-checkbox> <el-link type="primary" :underline="false">忘记密码?</el-link> </div> </el-form-item> <el-form-item> <el-button class="button" type="primary" auto-insert-space>登录</el-button> </el-form-item> <el-form-item class="flex"> <el-link type="info" :underline="false" @click="isRegister = true"> 注册 → </el-link> </el-form-item> </el-form> </el-col> </el-row> </template>
<style lang="scss" scoped>
.login-page { height: 100vh; background-color: #fff;
.bg { background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto, url('@/assets/login_bg.jpg') no-repeat center / cover; border-radius: 0 20px 20px 0; }
.form { display: flex; flex-direction: column; justify-content: center; user-select: none;
.title { margin: 0 auto; }
.button { width: 100%; }
.flex { width: 100%; display: flex; justify-content: space-between; } } } </style>
|
2.2 页面数据绑定与事件绑定
2.2.1 数据绑定
1 2 3 4 5 6
| const registerData = ref({ username: '', password: '', rePassword: '' })
|
2.2.2 表单校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const rePasswordValid = (rule, value, callback) => { if (value == null || value === '') { return callback(new Error('请再次确认密码')) } if (registerData.password !== value) { return callback(new Error('两次输入密码不一致')) } }
const registerDataRules = ref({ username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 5, max: 16, message: '用户名的长度必须为5~16位', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 5, max: 16, message: '密码长度必须为5~16位', trigger: 'blur' } ], rePassword: [ { validator: rePasswordValid, trigger: 'blur' } ] })
|
2.2.3 事件绑定
1 2 3 4
| const register = () => { console.log('注册...'); }
|
2.3 接口调用
2.3.1 在src/api/user.js中提供访问注册接口的函数
1 2 3 4 5 6 7 8
| export const registerService = (registerData) => { var params = new URLSearchParams() for (let key in registerData) { params.append(key, registerData[key]) } return request.post('/user/register', params) }
|
2.3.2 在Login.vue中完成注册接口调用
1 2 3 4 5 6 7 8 9 10 11
| import { registerService} from '@/api/user.js'
const register = async () => { let result = await registerService(registerData.value); if (result.code == 0) { alert('注册成功!') } else { alert('注册失败!') } }
|
2.4 处理跨域问题
由于发起ajax请求的域为http://localhost:5173, 而后台服务器的域为 http://localhost:8080, 所以浏览器会限制该请求的发送, 这种问题称为跨域问题, 跨域问题可以在服务器端解决,也可以在浏览器端解决, 咱们这一块通过配置代理的方式解决
request.js中配置统一前缀 /api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
import axios from 'axios';
const baseURL = '/api'; const instance = axios.create({baseURL})
instance.interceptors.response.use( result=>{ return result.data; }, err=>{ alert('服务异常'); return Promise.reject(err); } )
export default instance;
|
vie.config.js中配置代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue'
export default defineConfig({ plugins: [ vue(), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })
|
三、登录
3.1页面数据绑定与事件绑定
3.1.1 绑定数据
1 2 3 4 5 6
| const registerData = ref({ username: '', password: '', rePassword: '' })
|
3.1.2 每次点击注册或者登录,共用数据模型中的数据
1 2 3 4 5 6 7 8
| const clearRegisterData = () => { registerData.value = { username: '', password: '', rePassword: '' } }
|
3.1.3 事件绑定
1 2 3
| const login = () => { }
|
3.2 接口调用
3.2.1 在src/api/user.js中提供访问注册接口的函数
1 2 3 4 5 6 7 8
| export const loginService = (loginData)=>{ var params = new URLSearchParams() for(let key in loginData){ params.append(key,loginData[key]) } return request.post('/user/login',params) }
|
3.2.2 在Login.vue中完成登录接口调用
1 2 3 4 5 6 7 8 9 10 11
| import { registerService, loginService } from '@/api/user.js'
const login = async () => { let result = await loginService(registerData.value) if(result.code==0){ alert('登录成功!') }else{ alert('登录失败!') } }
|
四、优化axios响应截器
在接口调用的API中,我们都需要对业务响应的状态进行判断,从而给用户对应的提示,这个工作不难,但是每个接口的调用,都这样写代码,显然是比较繁琐的,我们可以在axios的相应拦截器中,如果服务器响应成功了,统一判断后台返回的业务状态码code,如果成功了,正常返回数据,如果失败了,则给出用户对应的提示即可
请求工具request.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| instance.interceptors.response.use( result => { if (result.data.code == 0) { return result.data; } alert(result.data.message || '服务异常'); return Promise.reject(result.data); }, err => { alert('服务异常'); return Promise.reject(err); } )
|
接口调用user.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const register = async () => { await registerService(registerData.value); alert('注册成功!')
}
const login = async () => { await loginService(registerData.value) alert('登录成功!') }
|
Element-Plus提示框的使用
1 2 3 4
| import { ElMessage } from 'element-plus'
ElMessage.error('服务异常'); ElMessage.success('登录成功!')
|
五、主页面布局

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
| <script setup> import { Management, Promotion, UserFilled, User, Crop, EditPen, SwitchButton, CaretBottom } from '@element-plus/icons-vue' import avatar from '@/assets/default.png' </script>
<template> <el-container class="layout-container"> <el-aside width="200px"> <div class="el-aside__logo"></div> <el-menu active-text-color="#ffd04b" background-color="#232323" text-color="#fff" router> <el-menu-item > <el-icon> <Management /> </el-icon> <span>文章分类</span> </el-menu-item> <el-menu-item > <el-icon> <Promotion /> </el-icon> <span>文章管理</span> </el-menu-item> <el-sub-menu > <template #title> <el-icon> <UserFilled /> </el-icon> <span>个人中心</span> </template> <el-menu-item > <el-icon> <User /> </el-icon> <span>基本资料</span> </el-menu-item> <el-menu-item > <el-icon> <Crop /> </el-icon> <span>更换头像</span> </el-menu-item> <el-menu-item > <el-icon> <EditPen /> </el-icon> <span>重置密码</span> </el-menu-item> </el-sub-menu> </el-menu> </el-aside> <el-container> <el-header> <div>黑马程序员:<strong>东哥</strong></div> <el-dropdown placement="bottom-end"> <span class="el-dropdown__box"> <el-avatar :src="avatar" /> <el-icon> <CaretBottom /> </el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item> <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item> <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item> <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </el-header> <el-main> <div style="width: 1290px; height: 570px;border: 1px solid red;"> 内容展示区 </div> </el-main> <el-footer>大事件 ©2023 Created by 黑马程序员</el-footer> </el-container> </el-container> </template>
<style lang="scss" scoped> .layout-container { height: 100vh;
.el-aside { background-color: #232323;
&__logo { height: 120px; background: url('@/assets/logo.png') no-repeat center / 120px auto; }
.el-menu { border-right: none; } }
.el-header { background-color: #fff; display: flex; align-items: center; justify-content: space-between;
.el-dropdown__box { display: flex; align-items: center;
.el-icon { color: #999; margin-left: 10px; }
&:active, &:focus { outline: none; } } }
.el-footer { display: flex; align-items: center; justify-content: center; font-size: 14px; color: #666; } } </style>
|
六、路由
在App.vue中,不能同时展示Login.vue和Layout.vue,实际的需求是用户第一次访问程序,先展示登录页面,当用户登录成功后,再展示主页面,如果要达成这个需求,需要用到vue提供的路由相关的知识
路由,从起点到终点时,决定从起点到终点的路径的进程,在前端工程中,路由指的是根据不同的访问路径,展示不同组件的内容。Vue Router是Vue.js的官方路由,它与Vue.js深度集成,让Vue.js构建单页面应用变得更加轻而易举
6.1安装路由
1
| npm install vue-router@4
|
6.2创建路由器,并导出
在src/router目录下,定义一个js文件,起名为index.js。这样名字的js文件在导入时,可以不写文件名,只要定位到文件所在的文件夹即可,使用起来很方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createRouter, createWebHistory } from 'vue-router'
import LoginVue from '@/views/Login.vue' import LayoutVue from '@/views/Layout.vue'
const routes = [ { path: '/login', component: LoginVue }, { path: '/', component: LayoutVue } ]
const router = createRouter({ history: createWebHistory(), routes: routes });
export default router
|
6.3在vue应用实例中使用router
在main.js中导入创建应用实力的js文件,并调用实例的use方法使用路由器
1 2 3
| import router from '@/router'
app.use(router)
|
6.4定义展示路由组件的地方
在App.vue文件的template标签中,定义router-view标签
1 2 3
| <template> <router-view></router-view> </template>
|
将来不管根据路由匹配到的组件内容,会在router-view标签内进行展示
6.5 测试
在浏览器地址栏分别访问:http://localhost:5173/ 和 http://localhost:5173/login
6.6 路由API
在登录成功后,需要通过代码的方式将页面切换到首页,此时就需要调用路由器相关的API
获取路由器
1 2
| import { useRouter } from 'vue-router' const router = useRouter();
|
调用API
七、子路由
在咱们的主页面中,当用户点击左侧的菜单时,右侧主区域的内容需要发生变化,将来每切换一个菜单,右侧需要加载对应组件的内容进行展示,像这样的场景咱们也需要使用路由来完成
由于这些组件都需要在Layout.vue中展示, 而Layout.vue本身已经参与了路由,因此我们需要在Layout.vue中通过子路由的方式来完成组件的切换
7.1提供菜单对应的组件
可以复制资料中的文件,也可以自己创建:
- ArticleCategory.vue
- ArticleManage.vue
- UserInfo.vue
- UserAvatar.vue
- UserResetPassword.vue
7.2配置子路由
在src/router/index.js中配置子路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const routes = [ { path: '/login', component: LoginVue }, { path: '/', component: LayoutVue, redirect: '/article/manage', children: [ { path: '/article/category', component: ArticleCategoryVue }, { path: '/article/manage', component: ArticleManageVue }, { path: '/user/info', component: UserInfoVue }, { path: '/user/avatar', component: UserAvatarVUe }, { path: '/user/password', component: UserResetPasswordVue }, ] } ]
|
7.3 在Layout.vue组件的右侧中间区域,添加router-view标签
1 2 3 4 5 6
| <el-main> <div style="width: 1290px; height: 570px;border: 1px solid red;"> <router-view></router-view> </div> </el-main>
|
7.4 菜单项设置点击后跳转的路由路径
el-menu-item 标签的index属性可以设置点击后的路由路径
1 2 3 4 5 6
| <el-menu-item index="/article/category"> <el-icon> <Management /> </el-icon> <span>文章分类</span> </el-menu-item>
|
八、文章分类列表
8.1 文章分类组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <script setup> import { Edit, Delete } from '@element-plus/icons-vue' import { ref } from 'vue' const categorys = ref([ { "id": 3, "categoryName": "美食", "categoryAlias": "my", "createTime": "2023-09-02 12:06:59", "updateTime": "2023-09-02 12:06:59" }, { "id": 4, "categoryName": "娱乐", "categoryAlias": "yl", "createTime": "2023-09-02 12:08:16", "updateTime": "2023-09-02 12:08:16" }, { "id": 5, "categoryName": "军事", "categoryAlias": "js", "createTime": "2023-09-02 12:08:33", "updateTime": "2023-09-02 12:08:33" } ]) </script> <template> <el-card class="page-container"> <template #header> <div class="header"> <span>文章分类</span> <div class="extra"> <el-button type="primary">添加分类</el-button> </div> </div> </template> <el-table :data="categorys" style="width: 100%"> <el-table-column label="序号" width="100" type="index"> </el-table-column> <el-table-column label="分类名称" prop="categoryName"></el-table-column> <el-table-column label="分类别名" prop="categoryAlias"></el-table-column> <el-table-column label="操作" width="100"> <template #default="{ row }"> <el-button :icon="Edit" circle plain type="primary" ></el-button> <el-button :icon="Delete" circle plain type="danger"></el-button> </template> </el-table-column> <template #empty> <el-empty description="没有数据" /> </template> </el-table> </el-card> </template>
<style lang="scss" scoped> .page-container { min-height: 100%; box-sizing: border-box;
.header { display: flex; align-items: center; justify-content: space-between; } } </style>
|
8.2 列表接口调用
src/api/article.js
1 2 3 4 5 6 7
| import request from '@/utils/request.js'
export const articleCategoryListService = ()=>{ return request.get('/category') }
|
ArticleCategory.vue
1 2 3 4 5 6 7
| import { articleCategoryListService } from '@/api/article.js' const getAllCategory = async () => { let result = await articleCategoryListService(); categorys.value = result.data; } getAllCategory();
|
但是上述的代码并不能真正的获取到所有文章分类数据,服务器响应状态码为401,因为目前请求头中并没有携带token
九、Pinia状态管理库
Pinia是Vue的专属状态管理库,它允许你跨组件或页面共享状态
9.1安装
9.2使用Pinia
在main.js中,引入pinia,创建pinia实例,并调用vue应用实例的use方法使用pinia
1 2 3 4
| import { createPinia } from 'pinia'
const pinia = createPinia() app.use(pinia)
|
9.3 定义Store
在src/stores目录下定义token.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { defineStore } from "pinia"; import {ref} from 'vue';
export const useTokenStore = defineStore('token',()=>{ const token = ref('')
const setToken = (newToken)=>{ token.value = newToken }
const removeToken = ()=>{ token.value='' } return { token,setToken,removeToken } })
|
9.4 使用Store
在需要使用状态的地方,导入@/stores/*.js , 使用即可
在Login.vue中导入@/stores/token.js, 并且当用户登录成功后,将token保存pinia中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useTokenStore } from '@/stores/token.js'
const tokenStore = useTokenStore();
const login = async () => { let result = await loginService(registerData.value) tokenStore.setToken(result.data) ElMessage.success('登录成功!') router.push('/') }
|
在article.js中导入@/stores/token.js, 从pinia中获取到存储的token,在发起查询文章分类列表的时候把token通过请求头的形式携带给服务器
1 2 3 4 5 6 7 8 9 10 11
| import { useTokenStore } from '../stores/token'
export const articleCategoryListService = () => { const tokenStore = useTokenStore() return request.get('/category', { headers: { 'Authorization': tokenStore.token } }) }
|
十、axios请求拦截器
当进入主页后,将来要与后台交互,都需要携带token,如果每次请求都写这样的代码,将会比较繁琐,此时可以将携带token的代码通过请求拦截器统一处理
在 src/util/request.js中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { useTokenStore } from '@/stores/token.js';
instance.interceptors.request.use( (config)=>{ let tokenStore = useTokenStore() if(tokenStore.token){ config.headers.Authorization=tokenStore.token } return config }, (err)=>{ Promise.reject(err) } )
|
十一、Pinia持久化插件
默认情况下,由于pinia是内存存储,当你刷新页面的时候pinia中的数据会丢失,可以借助于persist插件解决这个问题,persist插件支持将pinia中的数据持久化到sessionStorage和localStorage中
11.1 安装persist插件
1
| npm install pinia-persistedstate-plugin
|
11.2 pinia中使用persist插件
在main.js中
1 2 3 4 5 6 7 8
| import { createPinia } from 'pinia'
import {createPersistedState} from'pinia-persistedstate-plugin' const pinia = createPinia() const persist = createPersistedState()
pinia.use(persist) app.use(pinia)
|
11.3 在创建定义状态是配置持久化
在src/stores/token.js中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export const useTokenStore = defineStore('token',()=>{ const token = ref('')
const setToken = (newToken)=>{ token.value = newToken }
const removeToken = ()=>{ token.value='' } return { token,setToken,removeToken } } ,
{ persist:true } )
|
十二、未登录统一处理
在后续访问接口时,如果没有登录,则前端不携带token,后台服务器会返回响应状态码401,代表未登录,此时可以在axios的响应拦截器中,统一对未登录的情况做处理
request.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import router from '@/router'
instance.interceptors.response.use( result => { if (result.data.code == 0) { return result.data; } ElMessage.error(result.data.message || '服务异常'); return Promise.reject(result.data); }, err => { if(err.response.status===401){ ElMessage.error('请先登录!') router.push('/login') }else{ ElMessage.error('服务异常'); } return Promise.reject(err); } )
|
十三、添加文章分类
13.1 添加分类弹窗页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <el-dialog v-model="dialogVisible" title="添加弹层" width="30%"> <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px"> <el-form-item label="分类名称" prop="categoryName"> <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input> </el-form-item> <el-form-item label="分类别名" prop="categoryAlias"> <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary"> 确认 </el-button> </span> </template> </el-dialog>
|
13.2 数据模型和校验规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const dialogVisible = ref(false)
const categoryModel = ref({ categoryName: '', categoryAlias: '' })
const rules = { categoryName: [ { required: true, message: '请输入分类名称', trigger: 'blur' }, ], categoryAlias: [ { required: true, message: '请输入分类别名', trigger: 'blur' }, ] }
|
13.3 添加分类按钮单击事件
1
| <el-button type="primary" @click="dialogVisible = true">添加分类</el-button>
|
13.4 接口调用
在article.js中提供添加分类的函数
1 2 3 4
| export const articleCategoryAddService = (categoryModel) => { return request.post('/category', categoryModel) }
|
在页面中调用接口
1 2 3 4 5 6 7 8 9
| const addCategory = async ()=>{ let result = await articleCategoryAddService(categoryModel.value); ElMessage.success(result.message? result.message:'添加成功') dialogVisible.value = false getAllCategory() }
|
1
| <el-button type="primary" @click="addCategory"> 确认 </el-button>
|
十四、修改文章分类
14.1 修改分类弹窗页面
修改分类弹窗和新增文章分类弹窗长的一样,所以可以服用添加分类的弹窗
弹窗标题显示
定义标题
在弹窗上绑定标题
1
| <el-dialog v-model="dialogVisible" :title="title" width="30%">
|
为添加分类按钮绑定事件
1
| <el-button type="primary" @click="title='添加分类';dialogVisible = true">添加分类</el-button>
|
为修改分类按钮绑定事件
1
| <el-button :icon="Edit" circle plain type="primary" @click="title='修改分类';dialogVisible=true"></el-button>
|
14.2 数据回显
当点击修改分类按钮时,需要把当前这一条数据的详细信息显示到修改分类的弹窗上,这个叫回显
通过插槽的方式得到被点击按钮所在行的数据
1 2 3 4
| <template #default="{ row }"> <el-button :icon="Edit" circle plain type="primary" @click="updateCategoryEcho(row)"></el-button> <el-button :icon="Delete" circle plain type="danger"></el-button> </template>
|
回显函数
1 2 3 4 5 6 7 8 9 10
| const updateCategoryEcho = (row) => { title.value = '修改分类' dialogVisible.value = true categoryModel.value.categoryName=row.categoryName categoryModel.value.categoryAlias=row.categoryAlias categoryModel.value.id=row.id }
|
14.3 接口调用
article.js中提供修改分类的函数
1 2 3 4
| export const articleCategoryUpdateService = (categoryModel)=>{ return request.put('/category',categoryModel) }
|
修改确定按钮的绑定事件
1 2 3 4
| <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button> </span>
|
调用接口完成修改的函数
1 2 3 4 5 6 7 8 9
| const updateCategory=async ()=>{ let result = await articleCategoryUpdateService(categoryModel.value) ElMessage.success(result.message? result.message:'修改成功') dialogVisible.value=false getAllCategory() }
|
由于现在修改和新增共用了一个数据模型,所以在点击添加分类后,有时候会显示数据,此时可以将categoryModel中的数据清空
1 2 3 4 5
| const clearCategoryModel = ()=>{ categoryModel.value.categoryName='', categoryModel.value.categoryAlias='' }
|
修改添加按钮的点击事件
1
| <el-button type="primary" @click="title = '添加分类'; dialogVisible = true;clearCategoryModel()">添加分类</el-button>
|
十五、删除分类(后台需要补齐)
15.1 确认框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const deleteCategory = (row) => { ElMessageBox.confirm( '你确认删除该分类信息吗?', '温馨提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', } ) .then(() => { ElMessage({ type: 'success', message: '删除成功', }) }) .catch(() => { ElMessage({ type: 'info', message: '取消删除', }) }) }
|
15.2 接口调用
article.js中提供删除分类的函数
1 2 3 4 5
|
export const articleCategoryDeleteService = (id) => { return request.delete('/category?id='+id) }
|
当用户点击确认后,调用接口删除分类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const deleteCategory = (row) => { ElMessageBox.confirm( '你确认删除该分类信息吗?', '温馨提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', } ) .then(async () => { let result = await articleCategoryDeleteService(row.id) ElMessage.success(result.message?result.message:'删除成功') getAllCategory() }) .catch(() => { ElMessage({ type: 'info', message: '取消删除', }) }) }
|
十六、文章列表
16.1 文章列表页面组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| <script setup> import { Edit, Delete } from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = ref([ { "id": 3, "categoryName": "美食", "categoryAlias": "my", "createTime": "2023-09-02 12:06:59", "updateTime": "2023-09-02 12:06:59" }, { "id": 4, "categoryName": "娱乐", "categoryAlias": "yl", "createTime": "2023-09-02 12:08:16", "updateTime": "2023-09-02 12:08:16" }, { "id": 5, "categoryName": "军事", "categoryAlias": "js", "createTime": "2023-09-02 12:08:33", "updateTime": "2023-09-02 12:08:33" } ])
const categoryId=ref('')
const state=ref('')
const articles = ref([ { "id": 5, "title": "陕西旅游攻略", "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...", "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png", "state": "草稿", "categoryId": 2, "createTime": "2023-09-03 11:55:30", "updateTime": "2023-09-03 11:55:30" }, { "id": 5, "title": "陕西旅游攻略", "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...", "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png", "state": "草稿", "categoryId": 2, "createTime": "2023-09-03 11:55:30", "updateTime": "2023-09-03 11:55:30" }, { "id": 5, "title": "陕西旅游攻略", "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...", "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png", "state": "草稿", "categoryId": 2, "createTime": "2023-09-03 11:55:30", "updateTime": "2023-09-03 11:55:30" }, ])
const pageNum = ref(1) const total = ref(20) const pageSize = ref(3)
const onSizeChange = (size) => { pageSize.value = size }
const onCurrentChange = (num) => { pageNum.value = num } </script> <template> <el-card class="page-container"> <template #header> <div class="header"> <span>文章管理</span> <div class="extra"> <el-button type="primary">添加文章</el-button> </div> </div> </template> <el-form inline> <el-form-item label="文章分类:"> <el-select placeholder="请选择" v-model="categoryId"> <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"> </el-option> </el-select> </el-form-item>
<el-form-item label="发布状态:"> <el-select placeholder="请选择" v-model="state"> <el-option label="已发布" value="已发布"></el-option> <el-option label="草稿" value="草稿"></el-option> </el-select> </el-form-item> <el-form-item> <el-button type="primary">搜索</el-button> <el-button>重置</el-button> </el-form-item> </el-form> <el-table :data="articles" style="width: 100%"> <el-table-column label="文章标题" width="400" prop="title"></el-table-column> <el-table-column label="分类" prop="categoryId"></el-table-column> <el-table-column label="发表时间" prop="createTime"> </el-table-column> <el-table-column label="状态" prop="state"></el-table-column> <el-table-column label="操作" width="100"> <template #default="{ row }"> <el-button :icon="Edit" circle plain type="primary"></el-button> <el-button :icon="Delete" circle plain type="danger"></el-button> </template> </el-table-column> <template #empty> <el-empty description="没有数据" /> </template> </el-table> <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5 ,10, 15]" layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange" @current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" /> </el-card> </template> <style lang="scss" scoped> .page-container { min-height: 100%; box-sizing: border-box;
.header { display: flex; align-items: center; justify-content: space-between; } } </style>
|
使用中文语言包,解决分页条中文问题, 在main.js中完成
1 2 3
| import locale from 'element-plus/dist/locale/zh-cn.js'
app.use(ElementPlus,{locale})
|
16.2、文章分类数据回显
ArticleMange.vue
1 2 3 4 5 6 7 8
| import { articleCategoryListService } from '@/api/article.js' const getArticleCategoryList = async () => { let resultC = await articleCategoryListService(); categorys.value = resultC.data } getArticleCategoryList();
|
十七、 文章列表接口调用
article.js中提供获取文章列表数据的函数
1 2 3 4
| export const articleListService = (params) => { return request.get('/article', { params: params }) }
|
ArticleManage.vue中,调用接口获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { articleListService } from '@/api/article.js' const getArticles = async () => { let params = { pageNum: pageNum.value, pageSize: pageSize.value, categoryId: categoryId.value ? categoryId.value : null, state: state.value ? state.value : null } let result = await articleListService(params); articles.value = result.data.items for(let i=0;i<articles.value.length;i++){ let article = articles.value[i]; for(let j=0;j<categorys.value.length;j++){ if(article.categoryId===categorys.value[j].id){ article.categoryName=categorys.value[j].categoryName } } } total.value=result.data.total } getArticles()
|
当分页条的当前页和每页条数发生变化,重新发送请求获取数据
1 2 3 4 5 6 7 8 9 10 11
| const onSizeChange = (size) => { pageSize.value = size getArticles() }
const onCurrentChange = (num) => { pageNum.value = num getArticles() }
|
十八、搜索和重置
为搜索按钮绑定单击事件,调用getArticles函数即可
1
| <el-button type="primary" @click="getArticles">搜索</el-button>
|
为重置按钮绑定单击事件,清除categoryId和state的之即可
1
| <el-button @click="categoryId='';state=''">重置</el-button>
|
十九、添加文章
19.1 添加文章抽屉组件
1 2 3 4 5 6 7 8 9 10 11
| import {Plus} from '@element-plus/icons-vue'
const visibleDrawer = ref(false)
const articleModel = ref({ title: '', categoryId: '', coverImg: '', content:'', state:'' })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%"> <el-form :model="articleModel" label-width="100px" > <el-form-item label="文章标题" > <el-input v-model="articleModel.title" placeholder="请输入标题"></el-input> </el-form-item> <el-form-item label="文章分类"> <el-select placeholder="请选择" v-model="articleModel.categoryId"> <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"> </el-option> </el-select> </el-form-item> <el-form-item label="文章封面">
<el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false"> <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /> <el-icon v-else class="avatar-uploader-icon"> <Plus /> </el-icon> </el-upload> </el-form-item> <el-form-item label="文章内容"> <div class="editor">富文本编辑器</div> </el-form-item> <el-form-item> <el-button type="primary">发布</el-button> <el-button type="info">草稿</el-button> </el-form-item> </el-form> </el-drawer>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| .avatar-uploader { :deep() { .avatar { width: 178px; height: 178px; display: block; }
.el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; transition: var(--el-transition-duration-fast); }
.el-upload:hover { border-color: var(--el-color-primary); }
.el-icon.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 178px; height: 178px; text-align: center; } } } .editor { width: 100%; :deep(.ql-editor) { min-height: 200px; } }
|
为添加文章按钮添加单击事件,展示抽屉
1
| <el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>
|
19.2 富文本编辑器
文章内容需要使用到富文本编辑器,这里咱们使用一个开源的富文本编辑器 Quill
官网地址: https://vueup.github.io/vue-quill/
安装:
1
| npm install @vueup/vue-quill@latest --save
|
导入组件和样式:
1 2
| import { QuillEditor } from '@vueup/vue-quill' import '@vueup/vue-quill/dist/vue-quill.snow.css'
|
页面长使用quill组件:
1 2 3 4 5 6 7 8
| <quill-editor theme="snow" v-model:content="articleModel.content" contentType="html" > </quill-editor>
|
样式美化:
1 2 3 4 5 6
| .editor { width: 100%; :deep(.ql-editor) { min-height: 200px; } }
|
19.3 文章封面图片上传
将来当点击+图标,选择本地图片后,el-upload这个组件会自动发送请求,把图片上传到指定的服务器上,而不需要我们自己使用axios发送异步请求,所以需要给el-upload标签添加一些属性,控制请求的发送
auto-upload:是否自动上传
action: 服务器接口路径
name: 上传的文件字段名
headers: 设置上传的请求头
on-success: 上传成功的回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { Plus } from '@element-plus/icons-vue'
<el-form-item label="文章封面"> <el-upload class="avatar-uploader" :show-file-list="false" > <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /> <el-icon v-else class="avatar-uploader-icon"> <Plus /> </el-icon> </el-upload> </el-form-item>
|
注意:
由于这个请求时el-upload自动发送的异步请求,并没有使用咱们的request.js请求工具,所以在请求的路ing上,需要加上/api, 这个时候请求代理才能拦截到这个请求,转发到后台服务器上
要携带请求头,还需要导入pinia状态才可以使用
1 2
| import { useTokenStore } from '@/stores/token.js' const tokenStore = useTokenStore();
|
在成功的回调函数中,可以拿到服务器响应的数据,其中有一个属性为data,对应的就是图片在阿里云oss上存储的访问地址,需要把它赋值给articleModel的coverImg属性,这样img标签就能显示这张图片了,因为img标签上通过src属性绑定了articleModel.coverImg
1 2 3 4 5
| const uploadSuccess = (img) => { articleModel.value.coverImg=img.data }
|
19.4 添加文章接口调用
article.js中提供添加文章函数
1 2 3 4
| export const articleAddService = (articleModel)=>{ return request.post('/article',articleModel) }
|
为已发布和草稿按钮绑定事件
1 2 3 4
| <el-form-item> <el-button type="primary" @click="addArticle('已发布')">发布</el-button> <el-button type="info" @click="addArticle('草稿')">草稿</el-button> </el-form-item>
|
ArticleManage.vue中提供addArticle函数完成添加文章接口的调用
1 2 3 4 5 6 7 8 9 10
| const addArticle=async (state)=>{ articleModel.value.state = state let result = await articleAddService(articleModel.value); ElMessage.success(result.message? result.message:'添加成功') getArticles() visibleDrawer.value=false }
|
二十、顶部导航栏个人信息显示
在Layout.vue中,页面加载完就发送请求,获取个人信息展示,并存储到pinia中,因为将来在个人中心中修改信息的时候还需要使用
user.js中提供获取个人信息的函数
1 2 3 4
| export const userInfoGetService = ()=>{ return request.get('/user/userInfo'); }
|
src/stores/user.js中,定义个人中心状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { defineStore } from "pinia" import {ref} from 'vue'
export const useUserInfoStore = defineStore('userInfo',()=>{ const info = ref({}) const setInfo = (newInfo)=>{ info.value = newInfo } const removeInfo = ()=>{ info.value={} }
return{info,setInfo,removeInfo} },{ persist:true })
|
Layout.vue中获取个人信息,并存储到pinia中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import {userInfoGetService} from '@/api/user.js'
import {useUserInfoStore} from '@/stores/user.js' const userInfoStore = useUserInfoStore(); import {ref} from 'vue'
const getUserInf = async ()=>{ let result = await userInfoGetService(); userInfoStore.info =result.data; } getUserInf()
|
Layout.vue的顶部导航栏中,展示昵称和头像
1 2 3 4 5
| <div>黑马程序员:<strong>{{ userInfoStore.info.nickname ? userInfoStore.info.nickname : userInfoStore.info.usrename }}</strong></div>
<el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" />
|
二十一、el-dropdown中功能实现
在el-dropdown中有四个子条目,分别是:
其中其三个起到路由功能,跟左侧菜单中【个人中心】下面的二级菜单是同样的功能,退出登录需要删除本地pinia中存储的token以及userInfo
路由实现:
在el-dropdown-item标签上添加command属性,属性值和路由表中/user/xxx保持一致
1 2 3 4 5 6
| <el-dropdown-menu> <el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item> <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item> <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item> <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item> </el-dropdown-menu>
|
在el-dropdown标签上绑定command事件,当有条目被点击后,会触发这个事件
1
| <el-dropdown placement="bottom-end" @command="handleCommand">
|
提供handleCommand函数,参数为点击条目的command属性值
1 2 3 4 5 6 7 8 9 10 11 12
| import {useRouter} from 'vue-router' const router = useRouter() const handleCommand = (command)=>{ if(command==='logout'){ alert('退出登录') }else{ router.push('/user/'+command) } }
|
退出登录实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import {ElMessage,ElMessageBox} from 'element-plus' import { useTokenStore } from '@/stores/token.js' const tokenStore = useTokenStore() const handleCommand = (command) => { if (command === 'logout') { ElMessageBox.confirm( '你确认退出登录码?', '温馨提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', } ) .then(async () => { userInfoStore.info={} tokenStore.token='' router.push('/login') }) .catch(() => { ElMessage({ type: 'info', message: '取消退出', }) }) } else { router.push('/user/' + command) } }
|
二十二、基本资料修改
22.1 基本资料页面组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <script setup> import { ref } from 'vue' const userInfo = ref({ id: 0, username: 'zhangsan', nickname: 'zs', email: 'zs@163.com', }) const rules = { nickname: [ { required: true, message: '请输入用户昵称', trigger: 'blur' }, { pattern: /^\S{2,10}$/, message: '昵称必须是2-10位的非空字符串', trigger: 'blur' } ], email: [ { required: true, message: '请输入用户邮箱', trigger: 'blur' }, { type: 'email', message: '邮箱格式不正确', trigger: 'blur' } ] } </script> <template> <el-card class="page-container"> <template #header> <div class="header"> <span>基本资料</span> </div> </template> <el-row> <el-col :span="12"> <el-form :model="userInfo" :rules="rules" label-width="100px" size="large"> <el-form-item label="登录名称"> <el-input v-model="userInfo.username" disabled></el-input> </el-form-item> <el-form-item label="用户昵称" prop="nickname"> <el-input v-model="userInfo.nickname"></el-input> </el-form-item> <el-form-item label="用户邮箱" prop="email"> <el-input v-model="userInfo.email"></el-input> </el-form-item> <el-form-item> <el-button type="primary">提交修改</el-button> </el-form-item> </el-form> </el-col> </el-row> </el-card> </template>
|
22.2 表单数据回显
个人信息之前已经存储到了pinia中,只需要从pinia中获取个人信息,替换模板数据即可
1 2 3
| import { useUserInfoStore } from '@/stores/user.js'; const userInfoStore = useUserInfoStore() const userInfo = ref({...userInfoStore.info})
|
22.3 接口调用
在src/api/user.js中提供修改基本资料的函数
1 2 3 4
| export const userInfoUpdateService = (userInfo)=>{ return request.put('/user/update',userInfo) }
|
为修改按钮绑定单击事件
1
| <el-button type="primary" @click="updateUserInfo">提交修改</el-button>
|
提供updateUserInfo函数
1 2 3 4 5 6 7 8 9 10
| import {userInfoUpdateService} from '@/api/user.js' import { ElMessage } from 'element-plus'; const updateUserInfo = async ()=>{ let result = await userInfoUpdateService(userInfo.value) ElMessage.success(result.message? result.message:'修改成功') userInfoStore.info.nickname=userInfo.value.nickname userInfoStore.info.email = userInfo.value.email }
|
二十三、修改头像
23.1 修改头像页面组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| <script setup> import { Plus, Upload } from '@element-plus/icons-vue' import {ref} from 'vue' import avatar from '@/assets/default.png' const uploadRef = ref()
const imgUrl= avatar
</script>
<template> <el-card class="page-container"> <template #header> <div class="header"> <span>更换头像</span> </div> </template> <el-row> <el-col :span="12"> <el-upload ref="uploadRef" class="avatar-uploader" :show-file-list="false" > <img v-if="imgUrl" :src="imgUrl" class="avatar" /> <img v-else src="avatar" width="278" /> </el-upload> <br /> <el-button type="primary" :icon="Plus" size="large" @click="uploadRef.$el.querySelector('input').click()"> 选择图片 </el-button> <el-button type="success" :icon="Upload" size="large"> 上传头像 </el-button> </el-col> </el-row> </el-card> </template>
<style lang="scss" scoped> .avatar-uploader { :deep() { .avatar { width: 278px; height: 278px; display: block; }
.el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; transition: var(--el-transition-duration-fast); }
.el-upload:hover { border-color: var(--el-color-primary); }
.el-icon.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 278px; height: 278px; text-align: center; } } } </style>
|
23.2 头像回显
从pinia中读取用户的头像数据
1 2 3 4 5
| import {ref} from 'vue' import {useUserInfoStore} from '@/stores/user.js' const userInfoStore = useUserInfoStore() const imgUrl=ref(userInfoStore.info.userPic)
|
img标签上绑定图片地址
1 2
| <img v-if="imgUrl" :src="imgUrl" class="avatar" /> <img v-else src="@/assets/avatar.jpg" width="278" />
|
23.3 头像上传
为el-upload指定属性值,分别有:
- action: 服务器接口路径
- headers: 设置请求头,需要携带token
- on-success: 上传成功的回调函数
- name: 上传图片的字段名称
1 2 3 4 5 6 7 8 9 10 11 12
| <el-upload class="avatar-uploader" :show-file-list="false" :auto-upload="true" action="/api/upload" name="file" :headers="{'Authorization':tokenStore.token}" :on-success="uploadSuccess" > <img v-if="imgUrl" :src="imgUrl" class="avatar" /> <img v-else src="@/assets/avatar.jpg" width="278" /> </el-upload>
|
提供上传成功的回调函数
1 2 3 4 5 6 7 8 9
| import {useTokenStore} from '@/stores/token.js' const tokenStore = useTokenStore()
const uploadSuccess = (result)=>{ imgUrl.value = result.data }
|
外部触发图片选择
需要获取到el-upload组件,然后再通过$el.querySelector(‘input’)获取到el-upload对应的元素,触发click事件
1 2 3 4 5 6 7
| //获取el-upload元素 const uploadRef = ref()
<el-button type="primary" :icon="Plus" size="large" @click="uploadRef.$el.querySelector('input').click()"> 选择图片 </el-button>
|
24.4 接口调用
在user.js中提供修改头像的函数
1 2 3 4 5 6
| export const userAvatarUpdateService=(avatarUrl)=>{ let params = new URLSearchParams(); params.append('avatarUrl',avatarUrl) return request.patch('/user/updateAvatar',params) }
|
为【上传头像】按钮绑定单击事件
1 2 3
| <el-button type="success" :icon="Upload" size="large" @click="updateAvatar"> 上传头像 </el-button>
|
提供updateAvatar函数,完成头像更新
1 2 3 4 5 6 7 8 9
| import {userAvatarUpdateService} from '@/api/user.js' import {ElMessage} from 'element-plus' const updateAvatar = async ()=>{ let result = await userAvatarUpdateService(imgUrl.value) ElMessage.success(result.message? result.message:'修改成功') userInfoStore.info.userPic=imgUrl.value }
|