本文是我在写上传头像功能后的一个详细的总结,用作以后可以回顾,可能较为繁琐,如果明白上传只想看压缩,可直接拉到文章后半部分。
在后台/public文件夹里
创建一个文件夹uploads,来存放头像
开始:
1.models/user.js
添加头像字段
userHead:{
type:String,
default:url.resolve(Head.baseUrl,'default.png') //初始化的默认头像
}
为了更有通用性,在unitils/config.js里面添加配置
var Head={
baseUrl:'http://localhost:3000/uploads/'
}
module.exports={
Mongoose,
Email,
Head
};
baseUrl路径说明:因为express设置的(app.js里设置)静态资源在public目录下,所以public作为根目录是可以省略的。
app.use(express.static(path.join(__dirname, 'public')));
所以可以在网址栏省略public直接访问到头像:
http://localhost:3000/uploads/default.png
到目前,查看数据库,新注册的账号在数据库就有一个默认的userHead字段来表示头像了。
2.前端渲染
views/admin/users.vue
加到第一列
<el-table-column
prop="userHead"
label="用户头像">
<template slot-scope="scope">
<img :src="scope.row.userHead">
</template>
</el-table-column>
到这默认头像就完全都显示在管理端了(因为未上传图片就是是默认的default,所以每一个用户都有默认头像)
有了默认头像之后,我们想到的就是让用户可以自行上传图片来修改头像的操作了。
使用express的中间件multer上传头像。
http://www.expressjs.com.cn/en/resources/middleware/multer.html
步骤:
1.安装
$ cnpm install --save multer
2.引入
routes/users.js
var multer = require('multer');//引入模块
var upload = multer({ dest: 'public/uploads/' }); //指定将要上传的文件的路径
router.post('/uploadUserHead', upload.single('file') , usersController.uploadUserHead);
//参数:文件用post请求,file是指定的key值(表示上传文件,到时候前端也指定key值为file就可以对应起来了),调用函数
要在个人主页渲染出头像:
在登录的时候挂载userHead,从数据库的个人信息里拿到用户的头像字段保存起来
req.session.userHead = result.userHead;
在getUser(这是我写的一个判断登陆态的接口)的时候发送给前台,意思是判断用户在登录状态的时候渲染头像
userHead : req.session.userHead
models/user.js
var updateUserHead = ( username , userHead )=>{
return UserModel.update({username},{$set:{userHead}})
.then(()=>{
return true;
})
.catch(()=>{
return false;
});
}
对外暴露
根据用户名来查找,修改成你上传的那个 userHead(头像)字段
到这里,用户登录的时候接口里就有用户的头像字段了。
现在我们要从前端拿到头像字段。
因为管理端也显示头像,个人主页也显示头像,所以我们用状态管理。
//在beforeRouteEnter的vm.$store.commit里
userHead=res.data.data.userHead
因为虽然有两个组件都有头像字段,但是并不存在共享(因为管理端的头像是直接通过表格的属性scope.row.userHead获取的,没有用状态管理获取值。而个人中心是用状态管理获取值的),所以不localStorage了。只是添加一个空值:
src/stores/user/index.js
userHead:''
步骤:1.利用input [type=”file”]上传文件,这个文件是一个图片
<input type="file" name="file" value="上传头像">
type=“file” file可以对应的调用刚才我们说的值为key的模块。
type 类型为 file 的 input 元素,让用户可以选择一个或多个元素以提交表单的方式上传到服务器上, 或者通过 Javascript 的 File API 对文件进行操作。
这里我们用的是input表单。
2.绑定方法
<input type="file" name="file" value="上传头像" @change="handleToUpload">
这样每次在选择完头像之后都会触发handleToUpload(更新头像)方法。
handleToUpload(ev){
var file = ev.target.files[0]; //拿到存储图片的file文件
var param = new FormData(); //FormData创建出的实例,可以把文件存储起来,然后传输给后台
param.append('file',file,file.name); //key值 文件 文件下的name
var config={
headers:{ //想上传文件post需要改成二进制的数据流,所以设置头信息
'Content-Type':'multipart/form-data'
}
}
this.axios.post('/api2/users/uploadUserHead',param,config).then((res)=>{
var status=res.data.status;
if(status === 0){
msgBox({ //我封装的一个消息弹窗组件
title:'信息',
content:'上传头像成功',
ok:'确定',
handleOk(){ //头像上传成功之后更新状态管理
this.$store.commit('user/USER_NAME',{
name:_this.$store.state.user.name ,
isAdmin:_this.$store.state.user.isAdmin,
userHead:res.data.data.userHead
});
}
})
}else{
msgBox({
title:'信息',
content:'上传头像失败',
ok:'确定',
})
}
});
}
handleToUpload(更新头像)方法调用uploadUserHead接口,发送图片文件。后台通过Router路由层走到这一步
router.post('/uploadUserHead', upload.single('file') , usersController.uploadUserHead);
,请求后调用uploadUserHead接口,完成头像的修改。
到这一步测试一下,随便上传一个图片,会发现upload文件夹里面会有图片
但是图片的名字是随机生成的一长串数字,我们就无法获取它让它在前端的个人中心显示出来,所以要把图片名变成一个好获取的,我们想的是变成 ‘用户名.jpg’。
使用fs的rename操作(后面的文件名代替前面),保存的图片名是用户名.jpg
server\controlers\users.js\uploadUserHead接口
await fs.rename('public/uploads/'+req.file.filename , 'public/uploads/'+ req.session.username+'.jpg',function(err){}); //修改图片名
var result =await UserModel.updateUserHead(req.session.username , url.resolve(Head.baseUrl , req.session.username+'.jpg')); //2
对2句说明:
然后判断是否处理好,如果处理好,用data向前端返回userHead,好让前端更新头像。
到这,就可以成功地上传头像了。
但是现在会发现一个问题,发现每次刷新之后只能更改一次头像,再更改前端头像都不变,但是后台头像更新了,这是为什么呢?
这是因为前端有缓存的原因,解决办法:加随机数让每次请求都是新的请求。
userHead:res.data.data.userHead+'?'+Math.random() //上传成功后点击确定时修改的状态管理值
首先,为什么要压缩图片呢?
对于大尺寸图片的上传,在前端进行压缩除了省流量外,最大的意义是极大的提高了用户体验。
表现在:
明白了上传头像一系列操作之后,压缩图片其实很简单,只是在上传之前对图片进行压缩操作。
–> 因为无法直接对文件进行获取宽高等操作,所以创建一个image对象来接收文件的base64编码,通过操作这个image对象达到操作文件的目的
–> 计算等比缩放后的图片宽高
–> 清空画布之后,按照计算好的宽高,使用drawImage(image,x,y,width,height)的方法对图片进行压缩,绘制到新的画布上
–> 把canvas转换成Blob文件进行上传
handleToUpload(ev){
// console.log(ev);
var file = ev.target.files[0]; //拿到存储图片的file文件
//压缩图片开始
var img = new Image(); //img对象,当作一个容器接收file文件转化成的的base64,等于接受了图片,可以进行图片操作了,否则无法对文件进行图片操作
var reader = new FileReader(); //创建FileReader对象
reader.readAsDataURL(file); //读取存储图片的file文件,返回一个base64字符串存储到result属性中
reader.onload = function (ev) { //监听reader读取完成事件
img.src=this.result; //当读取完成时,reader.result就是要的base64,将它赋值给src
}
var _this=this;
img.onload=function(){ //必须要加这一句,监听img完成之后才能获取到宽和高,否则获取的值都是0
// console.log(img.width);
// console.log(img.height);
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d'); //为canvas设置上下文
var originW = img.width; //原图的初始宽、高
var originH = img.height;
var maxW = 400 , maxH = 400; //容许的最大宽、高
var targW = originW,targH=originH; //如果不需要压缩,就还是原始的宽高
if(originW>maxW || originH>maxH){
if(originH/originW > maxH/maxW){ //比起想要的比例(当前400/400是1),高/宽更大,说明高更大,设置高作为最大值高,那么宽一定不会超出范围,且是等比缩放
targH=maxH;
targW=Math.round(maxH * (originW / originH)); //取整
}else {
targW = maxW;
targH = Math.round(maxW * (originH / originW));
}
}
canvas.width = targW;
canvas.height = targH;
context.clearRect(0,0,targW,targH); //清除画布
context.drawImage(img,0,0,targW,targH); //将原图按照比例绘制到新的画布上去
canvas.toBlob(function(blob){ // canvas转为blob并上传
//压缩图片到这里就结束了,剩下的操作是把前文将的上传文件放在toBlob的回调函数里执行
var param = new FormData(); //FormData创建出的实例,可以把文件存储起来,然后传输给后台
param.append('file' , blob , file.name); //key值 文件 文件下的name
var config={
headers:{ //想上传文件post需要改成二进制的数据流,所以设置头信息
'Content-Type':'multipart/form-data'
}
}
_this.axios.post('/api2/users/uploadUserHead',param,config).then((res)=>{
var status=res.data.status;
// var This=this;
if(status === 0){
msgBox({
title:'信息',
content:'上传头像成功',
ok:'确定',
handleOk(){ //头像上传成功之后更新状态管理
_this.$store.commit('user/USER_NAME',{
name:_this.$store.state.user.name ,
isAdmin:_this.$store.state.user.isAdmin,
userHead:res.data.data.userHead+'?'+Math.random() //解决前端的缓存
});
}
})
}else{
msgBox({
title:'信息',
content:'上传头像失败',
ok:'确定',
})
}
});
},file.type || 'image/png')
}
}
参数说明:
img
图片对象,可以是页面上获取的DOM对象,也可以是虚拟DOM中的图片对象。
dx, dy, dWidth, dHeight
表示在canvas画布上规划出一片区域用来放置图片(拉伸压缩)。dx, dy为canvas元素的左上角坐标,dWidth, dHeight指canvas元素上用在显示图片的区域大小。如果没有指定sx,sy,sWidth,sHeight这4个参数,则图片会被拉伸或缩放在这片区域内。
sx,sy,swidth,sheight
这4个坐标是针对图片元素的,表示图片在canvas画布上显示的大小和位置(裁剪)。sx,sy表示图片上sx,sy这个坐标作为左上角,然后往右下角的swidth,sheight尺寸范围图片作为最终在canvas上显示的图片内容。
需要注意的是:那就是5参数和9参数里面参数位置是不一样的,这个和一般的API有所不同。一般API可选参数是放在后面。但是,这里的drawImage()9个参数时候,可选参数sx,sy,swidth,sheight是在前面的。如果不注意这一点,有些表现会让你无法理解。
举个简单的例子:
对于本文的图片压缩,需要用的是是5参数的语法。
假如一张图片(假设图片对象是img)的原始尺寸是40003000,现在需要把尺寸限制为400300大小,很简单,原理如下代码示意:
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.width = 400;
canvas.height = 300;
// 核心JS就这个
context.drawImage(img,0,0,400,300);
把一张大的图片,直接画在一张小小的画布上。此时大图片就天然变成了小图片,压缩就这么实现了,是不是简单的有点超乎想象。
参考资料:https://www.cnblogs.com/goloving/p/8260206.html
找一张超过你设置的最大宽高的图(本文是400×400),当作头像上传。
对比原图的宽高 和 存放头像uploads的文件夹里压缩过的图片的宽高:
看来压缩成功了。