个人中心的页面实现
按照左右两部分的布局划分即可
提示:这里可以主体参考elementplus官网的Layout布局实现https://element-plus.gitee.io/zh-CN/component/layout.html
修改views/userCenter/index.vue文件
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :span="6">
<el-card class="box-card">
<template v-slot:header>
<div class="clearfix">
<span>个人信息</span>
</div>
</template>
<div>
<div class="text-center">
<avatar :user="currentUser"/>
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon="user" /> 用户名称
<div class="pull-right">{{currentUser.username}}</div>
</li>
<li class="list-group-item">
<svg-icon icon="phone" /> 手机号码
<div class="pull-right">{{currentUser.phonenumber}}</div>
</li>
<li class="list-group-item">
<svg-icon icon="email" /> 用户邮箱
<div class="pull-right">{{currentUser.email}}</div>
</li>
<li class="list-group-item">
<svg-icon icon="peoples" /> 所属角色
<div class="pull-right">{{currentUser.roles}}</div>
</li>
<li class="list-group-item">
<svg-icon icon="date" /> 创建日期
<div class="pull-right">{{formatDate(currentUser.loginDate)}}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18">
<el-card>
<template v-slot:header>
<div class="clearfix">
<span>基本资料</span>
</div>
</template>
<el-tabs v-model="activeTab">
<el-tab-pane label="基本资料" name="userinfo">
基本资料信息
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
修改密码信息
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import {ref} from 'vue'
import store from '@/store'
import { formatDate } from '@/util/formatDate.js'
const currentUser = ref(store.getters.GET_USERINFO);
const activeTab = ref("userinfo");
</script>
<style lang="scss" scoped>
.list-group-striped>.list-group-item {
border-left: 0;
border-right: 0;
border-radius: 0;
padding-left: 0;
padding-right: 0;
}
.list-group-item {
border-bottom: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
margin-bottom: -1px;
padding: 11px 0;
font-size: 13px;
}
.pull-right{
float: right!important;
}
::v-deep .el-card__body{
height:230px;
}
::v-deep .box-card{
height:450px;
}
</style>
在util目录下新建处理时间的js【formatDate.js】
//方法一
export function formatDate(val) {
var date = new Date(Number(val)); //时间戳为10位需*1000,时间戳为13位的话不需乘1000
var Y = date.getFullYear() + "-";
var M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "-";
var D = date.getDate() + " ";
var h = date.getHours() + ":";
var m = date.getMinutes() + ":";
var s = (date.getSeconds() < 10 ? "0" + (date.getSeconds()) : date.getSeconds());
return Y + M + D + h + m + s;
}
时间处理解析有很多的方式,按照你喜欢的方式使用即可!!!
预览效果:
所属的角色信息没有,需要修改后端的数据映射
- 修改用户对象的属性【添加用户对应的角色信息属性】
- 修改登录成功的逻辑处理角色信息
组件细化处理
在userCenter目录下新建三个组件来对应三个内容
修改index.vue中的引入以及组件的使用
基本资料修改的实现
userInfo.vue组件内容的编写
<template>
<el-form ref="userRef" :model="form" :rules="rules" label-width="100px" >
<el-form-item label="手机号码:" prop="phonenumber">
<el-input v-model="form.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="用户邮箱:" prop="email">
<el-input v-model="form.email" maxlength="50" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import {defineProps, ref} from "vue";
import requestUtil from "@/util/request";
import { ElMessage } from 'element-plus'
import store from "@/store";
const props=defineProps(
{
user:{
type:Object,
default:()=>{},
required:true
}
}
)
const form=ref({
id:-1,
phonenumber:'',
email:''
})
const userRef=ref(null)
const rules = ref({
email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, { type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ required: true, message: "手机号码不能为空", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
});
form.value=props.user;
修改信息的的事件:
后端用户请求的控制器处理
如果之前已经在controller包中编写过SysUserController的需要注意,那么重命名,要么处理一下即可
页面内容:
数据表的变化:
代码规整:
个人中心userInfo.vue的组件页面:
<template>
<el-form ref="userRef" :model="form" :rules="rules" label-width="100px" >
<el-form-item label="手机号码:" prop="phonenumber">
<el-input v-model="form.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="用户邮箱:" prop="email">
<el-input v-model="form.email" maxlength="50" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import {defineProps, ref} from "vue";
import requestUtil from "@/util/request";
import { ElMessage } from 'element-plus'
import store from "@/store";
const props=defineProps(
{
user:{
type:Object,
default:()=>{},
required:true
}
}
)
const form=ref({
id:-1,
phonenumber:'',
email:''
})
const userRef=ref(null)
const rules = ref({
email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, { type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ required: true, message: "手机号码不能为空", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
});
form.value=props.user;
const handleSubmit=()=>{
userRef.value.validate(async (valid)=>{
if(valid) {
let result = await requestUtil.post("sys/user/save", form.value);
let data = result.data;
if (data.code == 200) {
ElMessage.success("执行成功!")
store.commit("SET_USERINFO", form.value)
}
}
})
}
</script>
<style lang="scss" scoped>
</style>
后端控制器的编写:
需要在请求方法头部加以权限处理
@RestController
@RequestMapping("/sys/user")
public class SysUsersController {
@Autowired
private SysUserService sysUserService;
/**
* 添加或者修改
* @param sysUser
* @return
*/
@PostMapping("/save")
@PreAuthorize("hasAuthority('system:user:add')"+"||"+"hasAuthority('system:user:edit')")
public ResponseData save(@RequestBody SysUser sysUser){
if(sysUser.getId()==null || sysUser.getId()==-1){
}else{
//此处修改更新时间可以使用MybatisPlus中的自动填充策略去实现【解决硬编码问题】
sysUser.setUpdateTime(new Date());
sysUserService.updateById(sysUser);
}
return new ResponseData("操作成功",200);
}
}
修改密码功能的实现
编写resetPwd.vue页面组件的内容
基本的效果如下:
表单校验的规则处理:
编写提交修改请求的事件处理:
整个代码示例可参考:【resetPwd.vue】
<template>
<el-form ref="pwdRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="form.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="form.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" placeholder="请确认密码" type="password" show-password/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import {defineProps, ref} from "vue";
import requestUtil from "@/util/request";
import { ElMessage } from 'element-plus'
import store from "@/store";
const props=defineProps(
{
user:{
type:Object,
default:()=>{},
required:true
}
}
)
const form=ref({
id:-1,
oldPassword:'',
newPassword:'',
confirmPassword:''
})
const pwdRef=ref(null)
form.value=props.user;
const equalToPassword = (rule, value, callback) => {
if (form.value.newPassword !== value) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
};
const rules = ref({
oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" }],
confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
});
const handleSubmit=()=>{
pwdRef.value.validate(async (valid)=>{
if(valid) {
let result = await requestUtil.post("sys/user/updateUserPwd", form.value);
let data = result.data;
if (data.code == 200) {
ElMessage.success("密码修改成功,下一次登录生效!")
store.commit("SET_USERINFO", form.value)
}else{
ElMessage.error(data.msg)
}
}
})
}
</script>
<style lang="scss" scoped>
</style>
后端处理
- 修改用户实体对象的属性【需要与前端表单的属性进行映射对应到后端实体对象中】
- 控制器修改的请求方法编写
测试一下:
①、当不输入内容或者没有符合校验规则时,有提示
②、如果输入的原密码错误时,有提示
③、当且仅当输入原密码以及校验通过时,才是正确的!!!
④、点击右上角安全退出之后,尝试用原来的密码登录(提示)以及使用修改后的密码登录
代码规整:
前端resetPwd.vue组件的内容:
<template>
<el-form ref="pwdRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="form.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="form.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" placeholder="请确认密码" type="password" show-password/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import {defineProps, ref} from "vue";
import requestUtil from "@/util/request";
import { ElMessage } from 'element-plus'
import store from "@/store";
const props=defineProps(
{
user:{
type:Object,
default:()=>{},
required:true
}
}
)
const form=ref({
id:-1,
oldPassword:'',
newPassword:'',
confirmPassword:''
})
const pwdRef=ref(null)
form.value=props.user;
const equalToPassword = (rule, value, callback) => {
if (form.value.newPassword !== value) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
};
const rules = ref({
oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" }],
confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
});
const handleSubmit=()=>{
pwdRef.value.validate(async (valid)=>{
if(valid) {
let result = await requestUtil.post("sys/user/updateUserPwd", form.value);
let data = result.data;
if (data.code == 200) {
ElMessage.success("密码修改成功,下一次登录生效!")
store.commit("SET_USERINFO", form.value)
}else{
ElMessage.error(data.msg)
}
}
})
}
</script>
<style lang="scss" scoped>
</style>
后端的控制器代码如下:
@RestController
@RequestMapping("/sys/user")
public class SysUsersController {
@Autowired
private SysUserService sysUserService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* 修改密码
* @param sysUser
* @return
*/
@PostMapping("/updateUserPwd")
@PreAuthorize("hasAuthority('system:user:edit')")
public ResponseData updateUserPwd(@RequestBody SysUser sysUser){
SysUser currentUser = sysUserService.getById(sysUser.getId());
if(bCryptPasswordEncoder.matches(sysUser.getOldPassword(),currentUser.getPassword())){
currentUser.setPassword(bCryptPasswordEncoder.encode(sysUser.getNewPassword()));
currentUser.setUpdateTime(new Date());
sysUserService.updateById(currentUser);
return new ResponseData("操作成功",200);
}else{
return new ResponseData("输入原密码错误!",500);
}
}
}
头像更换功能的实现
参考官网的示例去实现
https://element-plus.gitee.io/zh-CN/component/upload.html#%E7%94%A8%E6%88%B7%E5%A4%B4%E5%83%8F
编写avatar.vue的组件代码:
需要注意的点:
1、:headers="headers"需要在请求的时候携带好token值
2、:action="getServerUrl()+‘sys/user/uploadImage’"后端的请求资源路径
3、:on-success="handleAvatarSuccess"成功的回调函数
4、:before-upload="beforeAvatarUpload"上传之前的回调函数
代码如下:
<template>
<el-form
ref="formRef"
:model="form"
label-width="100px"
style="text-align: center;padding-bottom:10px"
>
<el-upload
:headers="headers"
class="avatar-uploader"
:action="getServerUrl()+'sys/user/uploadImage'"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
<el-button @click="handleConfirm" >确认更换</el-button>
</el-form>
</template>
<script setup>
import {defineProps, ref} from "vue";
import requestUtil,{getServerUrl} from "@/util/request";
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import store from "@/store";
const props=defineProps(
{
user:{
type:Object,
default:()=>{},
required:true
}
}
)
const headers=ref({
token:store.getters.GET_TOKEN
})
const form=ref({
id:-1,
avatar:''
})
const formRef=ref(null)
const imageUrl=ref("")
form.value=props.user;
imageUrl.value=getServerUrl()+'image/userAvatar/'+form.value.avatar
const handleAvatarSuccess=(res)=>{
imageUrl.value=getServerUrl()+res.data.src
form.value.avatar=res.data.title;
}
const beforeAvatarUpload = (file) => {
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
ElMessage.error('图片必须是jpg格式')
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过2M!')
}
return isJPG && isLt2M
}
const handleConfirm=async()=>{
let result=await requestUtil.post("sys/user/updateAvatar",form.value);
let data=result.data;
if(data.code==200){
ElMessage.success("执行成功!")
store.commit("SET_USERINFO",form.value)
}else{
ElMessage.error(data.msg);
}
}
</script>
<style>
.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;
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar {
width: 120px;
height: 120px;
display: block;
}
</style>
预览效果:
#### 后端的代码实现
为了防止传输文件名重复【使用时间格式化或者uuid之类的处理】
package com.xuguoguo.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日期工具类
* @author xuguoguo
*
*/
public class DateUtil {
/**
* 日期对象转字符串
* @param date
* @param format
* @return
*/
public static String formatDate(Date date,String format){
String result="";
SimpleDateFormat sdf=new SimpleDateFormat(format);
if(date!=null){
result=sdf.format(date);
}
return result;
}
/**
* 字符串转日期对象
* @param str
* @param format
* @return
* @throws Exception
*/
public static Date formatString(String str,String format) throws Exception{
if(StringUtil.isEmpty(str)){
return null;
}
SimpleDateFormat sdf=new SimpleDateFormat(format);
return sdf.parse(str);
}
public static String getCurrentDateStr(){
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddhhmmssSSSSSSSSS");
return sdf.format(date);
}
public static String getCurrentDatePath()throws Exception{
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd/");
return sdf.format(date);
}
public static void main(String[] args) {
try {
System.out.println(getCurrentDateStr());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
后端文件上传的请求方法
请求的文件上传的地址【修改yml进行动态的映射地址】
请求方法的编写
预览效果:
- 未上传之前查看内容
- 选择头像弹出选择框进行选择头像
- 选择之后
- 查看存放文件的目录
确认修改头像提交
代码规整:
avatar.vue组件的内容如下:
<template>
<el-form
ref="formRef"
:model="form"
label-width="100px"
style="text-align: center;padding-bottom:10px"
>
<el-upload
:headers="headers"
class="avatar-uploader"
:action="getServerUrl()+'sys/user/uploadImage'"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
<el-button @click="handleConfirm" >确认更换</el-button>
</el-form>
</template>
<script setup>
import {defineProps, ref} from "vue";
import requestUtil,{getServerUrl} from "@/util/request";
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import store from "@/store";
const props=defineProps(
{
user:{
type:Object,
default:()=>{},
required:true
}
}
)
const headers=ref({
token:store.getters.GET_TOKEN
})
const form=ref({
id:-1,
avatar:''
})
const formRef=ref(null)
const imageUrl=ref("")
form.value=props.user;
imageUrl.value=getServerUrl()+'image/userAvatar/'+form.value.avatar
const handleAvatarSuccess=(res)=>{
imageUrl.value=getServerUrl()+res.data.src
form.value.avatar=res.data.title;
}
const beforeAvatarUpload = (file) => {
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
ElMessage.error('图片必须是jpg格式')
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过2M!')
}
return isJPG && isLt2M
}
const handleConfirm=async()=>{
let result=await requestUtil.post("sys/user/updateAvatar",form.value);
let data=result.data;
if(data.code==200){
ElMessage.success("执行成功!")
store.commit("SET_USERINFO",form.value)
}else{
ElMessage.error(data.msg);
}
}
</script>
<style>
.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;
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar {
width: 120px;
height: 120px;
display: block;
}
</style>
后端的请求处理:
@RestController
@RequestMapping("/sys/user")
public class SysUsersController {
@Autowired
private SysUserService sysUserService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Value("${avatarImagesFilePath}")
private String avatarImagesFilePath;
/**
* 上传用户头像图片
* @param file
* @return
* @throws Exception
*/
@RequestMapping("/uploadImage")
@PreAuthorize("hasAuthority('system:user:edit')")
public Map<String,Object> uploadImage(MultipartFile file)throws Exception{
Map<String,Object> resultMap=new HashMap<>();
if(!file.isEmpty()){
// 获取文件名
String originalFilename = file.getOriginalFilename();
String suffixName=originalFilename.substring(originalFilename.lastIndexOf("."));
//防止重复的文件名[使用时间或者uuid之类的唯一值处理即可]
String newFileName= DateUtil.getCurrentDateStr()+suffixName;
FileUtils.copyInputStreamToFile(file.getInputStream(),new File(avatarImagesFilePath+newFileName));
resultMap.put("code",0);
resultMap.put("msg","上传成功");
Map<String,Object> dataMap=new HashMap<>();
dataMap.put("title",newFileName);
dataMap.put("src","image/userAvatar/"+newFileName);
resultMap.put("data",dataMap);
}
return resultMap;
}
/**
* 修改用户头像
* @param sysUser
* @return
*/
@RequestMapping("/updateAvatar")
@PreAuthorize("hasAuthority('system:user:edit')")
public ResponseData updateAvatar(@RequestBody SysUser sysUser){
SysUser currentUser = sysUserService.getById(sysUser.getId());
currentUser.setUpdateTime(new Date());
currentUser.setAvatar(sysUser.getAvatar());
sysUserService.updateById(currentUser);
return new ResponseData("操作成功",200);
}
}
小结
提示:本小结主体写的是个人中心、修改密码以及用户头像三个功能模块细化成前端的不同组件来实现的基本流程摘录,也是每个后台必有得功能哦!!!
本章的第八小节完毕,敬请期待后续更新(可留言需要学习哪方面的内容哈)!如果需要源码或者工具的朋友们可关注微信公众号"锅锅编程生活"或者扫描二维码关注回复关键字/后台留言获取即可!