项目实战第十七记
- 写在前面
- 1. 个人信息
- 1.1 Person.vue
- 1.2 设置路由并改动Header.vue
- 1.3 动态刷新头像
- 1.3.1 在保存个人信息时,触发方法
- 1.3.2 父组件Manage.vue
- 1.3.3 再将user以prop方式传递给子组件Header.vue
- 1.3.4 Header.vue使用user
- 1.4 效果图
- 2. 修改密码
- 2.1 前端页面编写(Password.vue)
- 2.2 修改密码后退出系统
- 2.3 路由设置并改动Header.vue
- 2.4 后端接口编写
- 2.4.1 UserController
- 2.4.2 UserServiceImpl
- 2.4.3 UserMapper
- 2.5 页面效果
- 总结
- 写在最后
写在前面
- 本篇主要讲解个人信息和修改密码页面,补充篇
1. 个人信息
1.1 Person.vue
<template><el-card style="width: 500px;"><el-form label-width="80px" size="small"><el-uploadclass="avatar-uploader"action="http://localhost:9000/file/upload":show-file-list="false":on-success="handleAvatarSuccess"><img v-if="form.avatarUrl" :src="form.avatarUrl" class="avatar"><i v-else class="el-icon-plus avatar-uploader-icon"></i></el-upload><el-form-item label="用户名"><el-input v-model="form.username" autocomplete="off"></el-input></el-form-item><el-form-item label="昵称"><el-input v-model="form.nickname" autocomplete="off"></el-input></el-form-item><el-form-item label="邮箱"><el-input v-model="form.email" autocomplete="off"></el-input></el-form-item><el-form-item label="电话"><el-input v-model="form.phone" autocomplete="off"></el-input></el-form-item><el-form-item label="地址"><el-input v-model="form.address" autocomplete="off"></el-input></el-form-item><el-form-item><el-button type="primary" @click="save">确 定</el-button></el-form-item></el-form></el-card></template><script>export default {name: "Person",data() {return {form: {},user: localStorage.getItem("loginUser") ? JSON.parse(localStorage.getItem("loginUser")) : {}}},created() {this.load();},methods: {load(){const username = this.user.usernameif(!username){this.$message.error("当前无法获取用户信息!");return false}// 数据库做了唯一性处理this.request.get("/user/username/"+username).then(res => {this.form = res.data})},save() {this.request.post("/user", this.form).then(res => {if (res.code === '200') {this.$message.success("保存成功")//this.dialogFormVisible("保存成功")this.load()//向父组件传递值 触发方法this.$emit('refreshUser')} else {this.$message.error("保存失败")}})},handleAvatarSuccess(res) {console.log('===',res)//res就是文件的路径this.form.avatarUrl = res}}}</script><style>.avatar-uploader {text-align: center;padding-bottom: 10px;}.avatar-uploader .el-upload {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;}.avatar-uploader .el-upload:hover {border-color: #409EFF;}.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 138px;height: 138px;line-height: 138px;text-align: center;}.avatar {width: 138px;height: 138px;display: block;}</style>
1.2 设置路由并改动Header.vue
// 拼装动态路由const manageRoute = { path: '/', name: 'Manage', component: () => import('../views/Manage.vue'), redirect: "/home", children: [{ path: 'person', name: '个人信息', component: () => import('../views/Person.vue'),meta: { title: '个人信息' }},{ path: 'password', name: '修改密码', component: () => import('../views/Password.vue'),meta: { title: '修改密码' }}] }
在Header.vue中添加
<el-dropdown-item style="font-size: 14px; padding: 5px 0;"><router-link to="/person">个人信息</router-link>
</el-dropdown-item>
1.3 动态刷新头像
当更换头像时,点击确定时,实现头像的同步更新
1.3.1 在保存个人信息时,触发方法
save() {this.request.post("/user", this.form).then(res => {if (res.code === '200') {this.$message.success("保存成功")this.load()/*这段Vue模板代码定义了一个方法,当调用这个方法时,会触发名为refreshUser的自定义事件。这个事件可以被父组件监听到,从而在父组件中执行相应的逻辑操作。**/this.$emit('refreshUser')} else {this.$message.error("保存失败")}})},
1.3.2 父组件Manage.vue
// 触发@refreshUser自定义事件
<el-main><!-- 表示当前页面的子路由会在 <router-view/> 里面显示 --><router-view @refreshUser="getUser"/>
</el-main>// 获取用户的最新数据
getUser(){const username = localStorage.getItem('loginUser') ? JSON.parse(localStorage.getItem('loginUser')).username : '';this.request.get('/user/username/' + username).then(res => {if(res.code == '200'){// 重新赋值后台的最新User数据this.user = res.data;}})}
1.3.3 再将user以prop方式传递给子组件Header.vue
<el-header style="border-bottom: 1px solid #ccc;"><Header :collapse-btn-class="collapseBtnClass" :collapse="isCollapse" :user="user"/>
</el-header>
完整的Manage.vue代码
<template><el-container style="min-height: 100vh"><el-aside :width="sideWidth + 'px'" style="box-shadow: 2px 0 6px rgb(0 21 41 / 0.35);"><Aside :is-collapse="isCollapse" :logo-text-show="logoTextShow"/></el-aside><el-container><el-header style="border-bottom: 1px solid #ccc;"><Header :collapse-btn-class="collapseBtnClass" :collapse="isCollapse" :user="user"/></el-header><el-main><!-- 表示当前页面的子路由会在 <router-view/> 里面显示 --><router-view @refreshUser="getUser"/></el-main></el-container></el-container>
</template><script>import Aside from "@/components/Aside";
import Header from "@/components/Header";export default {name: 'HomeView',components: {Aside,Header},data() {return {collapseBtnClass: 'el-icon-s-fold',isCollapse: false,sideWidth: 200,logoTextShow: true,headerBg: 'headerBg',user: {}}},created() {this.getUser()},methods: {collapse() { // 点击收缩按钮触发this.isCollapse = !this.isCollapseif (this.isCollapse) { // 收缩this.sideWidth = 64this.collapseBtnClass = 'el-icon-s-unfold'this.logoTextShow = false} else { // 展开this.sideWidth = 200this.collapseBtnClass = 'el-icon-s-fold'this.logoTextShow = true}},// 获取用户的最新数据getUser(){const username = localStorage.getItem('loginUser') ? JSON.parse(localStorage.getItem('loginUser')).username : '';this.request.get('/user/username/' + username).then(res => {if(res.code == '200'){// 重新赋值后台的最新User数据this.user = res.data;}})}}
}
</script><style>
.headerBg {background: #eee!important;
}
</style>
1.3.4 Header.vue使用user
// 接收数据
props: {collapseBtnClass: String,collapse: Boolean,// 定义一个user属性接受从Manage.vue传进来的user对象user: Object},// 使用数据
<div style="display: inline-block"><img :src="user.avatarUrl" alt=""style="width: 30px; border-radius: 50%; position: relative; top: 10px; right: 5px;overflow: hidden;"><span>{{ user.nickname }}</span><i class="el-icon-arrow-down" style="margin-left: 5px"></i>
</div>
完整的Header.vue代码
<template><div style="line-height: 60px; display: flex"><div style="flex: 1;"><span :class="collapseBtnClass" style="cursor: pointer; font-size: 18px" @click="collapse"></span><el-breadcrumb separator=">" style="display: inline-block; margin-left: 10px"><el-breadcrumb-item :to="'/'">首页</el-breadcrumb-item>
<!-- <el-breadcrumb-item>{{ currentPathName }}</el-breadcrumb-item>--><el-breadcrumb-item v-for="(item, index) in breadCrumbs" :key="item.path"><router-link :to="item.path">{{ item.meta.title }}</router-link></el-breadcrumb-item></el-breadcrumb></div><el-dropdown style="width: 100px; cursor: pointer"><div style="display: inline-block"><img :src="user.avatarUrl" alt=""style="width: 30px; border-radius: 50%; position: relative; top: 10px; right: 5px;overflow: hidden;"><span>{{ user.nickname }}</span><i class="el-icon-arrow-down" style="margin-left: 5px"></i></div><el-dropdown-menu slot="dropdown" style="width: 100px; text-align: center"><el-dropdown-item style="font-size: 14px; padding: 5px 0;"><router-link to="/person">个人信息</router-link></el-dropdown-item><el-dropdown-item style="font-size: 14px; padding: 5px 0;"><router-link to="/password">修改密码</router-link></el-dropdown-item><el-dropdown-item style="font-size: 14px; padding: 5px 0"><span style="text-decoration: none" @click="logout">退出</span></el-dropdown-item></el-dropdown-menu></el-dropdown></div>
</template><script>
export default {name: "Header",props: {collapseBtnClass: String,collapse: Boolean,// 定义一个user属性接受从Manage.vue传进来的user对象user: Object},// 当当前路由发生变化时,调用getBreadcrumb方法来更新面包屑导航的数据watch: {$route() {this.getBreadcrumb();}},data(){return {breadCrumbs: [],//user: localStorage.getItem("loginUser") ? JSON.parse(localStorage.getItem("loginUser")) : ""}},created() {this.getBreadcrumb()},methods: {getBreadcrumb(){// 从当前路由的匹配记录中过滤出具有meta属性且包含title属性的路由记录this.breadCrumbs = this.$route.matched.filter(item => item.meta && item.meta.title);},// 退出登录logout(){this.$router.push("/login");localStorage.removeItem("loginUser")this.$message.success("退出成功")}}// computed: {// currentPathName () {// return this.$store.state.currentPathName; //需要监听的数据// }// },}
</script><style scoped></style>
1.4 效果图
注意:上传的头像最好是正方形的大小,形成的头像才会是规整的
2. 修改密码
2.1 前端页面编写(Password.vue)
<template><el-card style="width: 500px;"><el-form label-width="120px" size="small" :model="form" :rules="rules" ref="pass"><el-form-item label="原密码" prop="password"><el-input v-model="form.password" autocomplete="off" show-password></el-input></el-form-item><el-form-item label="新密码" prop="newPassword"><el-input v-model="form.newPassword" autocomplete="off" show-password></el-input></el-form-item><el-form-item label="确认新密码" prop="confirmPassword"><el-input v-model="form.confirmPassword" autocomplete="off" show-password></el-input></el-form-item><el-form-item><el-button type="primary" @click="save">确 定</el-button></el-form-item></el-form></el-card>
</template><script>
export default {name: "Password",data() {return {form: {},user: localStorage.getItem("loginUser") ? JSON.parse(localStorage.getItem("loginUser")) : {},rules: {password: [{ required: true, message: '请输入原密码', trigger: 'blur' },{ min: 3, message: '长度不少于3位', trigger: 'blur' }],newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' },{ min: 3, message: '长度不少于3位', trigger: 'blur' }],confirmPassword: [{ required: true, message: '请输入密码', trigger: 'blur' },{ min: 3, message: '长度不少于3位', trigger: 'blur' }],}}},created() {// 通过用户名和旧密码来唯一标识用户,然后再修改密码this.form.username = this.user.username},methods: {save() {this.$refs.pass.validate((valid) => {if (valid) {if (this.form.newPassword !== this.form.confirmPassword) {this.$message.error("2次输入的新密码不相同")return false}this.request.post("/user/password", this.form).then(res => {if (res.code === '200') {this.$message.success("修改成功")this.$store.commit("logout")} else {this.$message.error(res.msg)}})}})},}
}
</script><style>
.avatar-uploader {text-align: center;padding-bottom: 10px;
}
.avatar-uploader .el-upload {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;
}
.avatar-uploader .el-upload:hover {border-color: #409EFF;
}
.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 138px;height: 138px;line-height: 138px;text-align: center;
}
.avatar {width: 138px;height: 138px;display: block;
}
</style>
2.2 修改密码后退出系统
// 退出系统
this.$store.commit("logout")
store文件下index.js
import Vue from 'vue'
import Vuex from 'vuex'
import router from "@/router";Vue.use(Vuex)export default new Vuex.Store({state: {currentPathName: ''},getters: {},mutations: {// setPath(state){// state.currentPathName = localStorage.getItem('currentPathName')// },logout() {// 清空缓存localStorage.removeItem("loginUser")localStorage.removeItem("menus")router.push("/login")// 重置路由//resetRouter()}},actions: {},modules: {}
})
2.3 路由设置并改动Header.vue
// 拼装动态路由
const manageRoute = { path: '/', name: 'Manage', component: () => import('../views/Manage.vue'), redirect: "/home", children: [{ path: 'person', name: '个人信息', component: () => import('../views/Person.vue'),meta: { title: '个人信息' }},{ path: 'password', name: '修改密码', component: () => import('../views/Password.vue'),meta: { title: '修改密码' }}] }
完整的index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Manage from '../views/Manage.vue'
import store from "@/store";Vue.use(VueRouter)
//定义一个路由对象数组
const routes = [{path: '/login',name: '登录',component: () => import('../views/Login.vue')},{path: '/register',name: '注册',component: () => import('../views/Register.vue')},{path: '/404',name: '404',component: () => import('../views/404.vue')}]//使用路由对象数组创建路由实例,供main.js引用
const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})// 注意:刷新页面会导致页面路由重置
export const setRoutes = () => {const storeMenus = localStorage.getItem("menus");if (storeMenus) {// 获取当前的路由对象名称数组const currentRouteNames = router.getRoutes().map(v => v.name)if (!currentRouteNames.includes('Manage')) {// 拼装动态路由const manageRoute = { path: '/', name: 'Manage', component: () => import('../views/Manage.vue'), redirect: "/home", children: [{ path: 'person', name: '个人信息', component: () => import('../views/Person.vue'),meta: { title: '个人信息' }},{ path: 'password', name: '修改密码', component: () => import('../views/Password.vue'),meta: { title: '修改密码' }}] }const menus = JSON.parse(storeMenus)menus.forEach(item => {if (item.path) { // 当且仅当path不为空的时候才去设置路由let itemMenu = { path: item.path.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue'),meta: { title: item.name }}manageRoute.children.push(itemMenu)} else if(item.children.length) {item.children.forEach(item => {if (item.path) {let itemMenu = { path: item.path.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue'),meta: { title: item.name }}manageRoute.children.push(itemMenu)}})}})// 动态添加到现在的路由对象中去router.addRoute(manageRoute)}}
}// 重置我就再set一次路由
setRoutes()// 路由守卫
router.beforeEach((to, from, next) => {// localStorage.setItem('currentPathName',to.name); // 设置当前的路由名称,为了在Header组件中去使用// store.commit('setPath') // 触发store的数据更新// 未找到路由情况if(!to.matched.length){const storeMenus = localStorage.getItem("menus");if(storeMenus){ // 有菜单没有找到路由,跳转至 404页面next("/404")}else { // // 没有菜单,直接跳转至登录页next("/login")}}next() // 放行路由
})export default router
Header.vue改动
<el-dropdown-item style="font-size: 14px; padding: 5px 0;"><router-link to="/password">修改密码</router-link>
</el-dropdown-item>
完整的Header.vue
<template><div style="line-height: 60px; display: flex"><div style="flex: 1;"><span :class="collapseBtnClass" style="cursor: pointer; font-size: 18px" @click="collapse"></span><el-breadcrumb separator=">" style="display: inline-block; margin-left: 10px"><el-breadcrumb-item :to="'/'">首页</el-breadcrumb-item>
<!-- <el-breadcrumb-item>{{ currentPathName }}</el-breadcrumb-item>--><el-breadcrumb-item v-for="(item, index) in breadCrumbs" :key="item.path"><router-link :to="item.path">{{ item.meta.title }}</router-link></el-breadcrumb-item></el-breadcrumb></div><el-dropdown style="width: 100px; cursor: pointer"><div style="display: inline-block"><img :src="user.avatarUrl" alt=""style="width: 30px; border-radius: 50%; position: relative; top: 10px; right: 5px;overflow: hidden;"><span>{{ user.nickname }}</span><i class="el-icon-arrow-down" style="margin-left: 5px"></i></div><el-dropdown-menu slot="dropdown" style="width: 100px; text-align: center"><el-dropdown-item style="font-size: 14px; padding: 5px 0;"><router-link to="/person">个人信息</router-link></el-dropdown-item><el-dropdown-item style="font-size: 14px; padding: 5px 0;"><router-link to="/password">修改密码</router-link></el-dropdown-item><el-dropdown-item style="font-size: 14px; padding: 5px 0"><span style="text-decoration: none" @click="logout">退出</span></el-dropdown-item></el-dropdown-menu></el-dropdown></div>
</template><script>
export default {name: "Header",props: {collapseBtnClass: String,collapse: Boolean,// 定义一个user属性接受从Manage.vue传进来的user对象user: Object},// 当当前路由发生变化时,调用getBreadcrumb方法来更新面包屑导航的数据watch: {$route() {this.getBreadcrumb();}},data(){return {breadCrumbs: [],//user: localStorage.getItem("loginUser") ? JSON.parse(localStorage.getItem("loginUser")) : ""}},created() {this.getBreadcrumb()},methods: {getBreadcrumb(){// 从当前路由的匹配记录中过滤出具有meta属性且包含title属性的路由记录this.breadCrumbs = this.$route.matched.filter(item => item.meta && item.meta.title);},// 退出登录logout(){this.$router.push("/login");localStorage.removeItem("loginUser")this.$message.success("退出成功")}}// computed: {// currentPathName () {// return this.$store.state.currentPathName; //需要监听的数据// }// },}
</script><style scoped></style>
2.4 后端接口编写
2.4.1 UserController
@PostMapping("/password")
public Result updatePassword(@RequestBody UserPasswordDTO userPasswordDTO){if(StrUtil.isBlank(userPasswordDTO.getUsername()) || StrUtil.isBlank(userPasswordDTO.getPassword())){return Result.error(Constants.CODE_400,"参数错误");}userService.updatePassword(userPasswordDTO);return Result.success();}
用UserPasswordDTO接收前端传过来的表单参数
package com.ppj.entity.dto;import lombok.Data;@Data
public class UserPasswordDTO {private String username;private String password;private String newPassword;
}
2.4.2 UserServiceImpl
@Override
public void updatePassword(UserPasswordDTO userPasswordDTO) {int res = userMapper.updatePassword(userPasswordDTO);if(res<1){throw new ServiceException(Constants.CODE_600,"密码修改失败");}
}
2.4.3 UserMapper
package com.ppj.mapper;import com.ppj.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ppj.entity.dto.UserPasswordDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;/*** <p>* Mapper 接口* </p>** @author ppj* @since 2024-04-20*/
@Mapper
public interface UserMapper extends BaseMapper<User> {int updatePassword(UserPasswordDTO userPasswordDTO);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ppj.mapper.UserMapper"><update id="updatePassword" parameterType="com.ppj.entity.dto.UserPasswordDTO">update sys_userset password = #{newPassword}where username = #{username} and password = #{password}</update></mapper>
2.5 页面效果
总结
- 这篇主要的难点是头像上传后的同步刷新;
- 修改密码其实是更新用户,通过用户名和旧密码确定唯一用户,然后才进行密码更改。
写在最后
如果此文对您有所帮助,请帅戈靓女们务必不要吝啬你们的Zan,感谢!!不懂的可以在评论区评论,有空会及时回复。
文章会一直更新