1. 修改头像
1.1 图片上传预览
-
准备file类型输入框,并通过点击头像触发上传
设置hidden属性让其隐藏
<!-- 头像上传 --> <input type="file" hidden ref="file" @change="onFileChange" /> -
让头像单元格手动触发上传框点击事件
<!-- 个人信息 --> <van-cell title="头像" is-link @click="$refs.file.click()"> <van-image class="avatar" fit="cover" round :src="user.photo" /> </van-cell> -
给input定义change事件
-
获取上传文件信息
onFileChange () { // 获取文件对象 const file = this.$refs.file.files[0] // 基于文章对象获取 blob 数据 this.img = window.URL.createObjectURL(file) //当连续上传两个一样的图片时,会无法触发change事件, 解决办法就是每次使用完毕,把它的 value 清空 this.$refs.file.value = '' }
1.2 图片上传预览功能处理
-
定义弹出层
<!-- 编辑头像弹层 --> <van-popup v-model="isUpdatePhotoShow" style="height: 100%" position="bottom" > hello </van-popup> <!-- 编辑头像弹层 --> -
上传图片后展示弹出层
onFileChange () { // 获取文件对象 const file = this.$refs.file.files[0] // 基于文章对象获取 blob 数据 this.img = window.URL.createObjectURL(file) // 展示预览图片弹出层 this.isUpdatePhotoShow = true // file-input 如果选了同一个文件不会触发 change 事件 // 解决办法就是每次使用完毕,把它的 value 清空 this.$refs.file.value = '' } -
定义更新图片组件
updata-photo.vue<template> <div> <img :src="img" /> </div> </template> <script> export default { props: { img: { type: String, default: '' } } } </script> <style scoped lang='less'> </style> -
引用、注册、使用
import UpdataPhoto from './components/updata-photo.vue' components: { UpdataName, UpdataGender, UpdataBirthday, UpdataPhoto },<!-- 编辑头像弹层 --> <van-popup v-model="isUpdatePhotoShow" style="height: 100%" position="bottom" > <updata-photo :img="img"></updata-photo> </van-popup> <!-- 编辑头像弹层 -->
1.3 图片上传预览样式处理
-
页面结构
<template> <div class="updata-photo"> <img :src="img" /> <div class="toolbar"> <span class="cancel">取消</span> <span class="confirm">完成</span> </div> </div> </template> -
CSS
.updata-photo { background-color: #000; height: 100%; .toolbar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between; .cancel, .confirm { width: 90px; height: 90px; font-size: 30px; display: flex; justify-content: center; align-items: center; color: #fff; } } } .img { display: block; max-width: 100%; } -
点击取消
<span class="cancel" @click="$emit('close')">取消</span><updata-photo :img="img" @close="isUpdatePhotoShow = false"></updata-photo>
1.4 头像剪裁
方案一: 结合服务器的图片上传剪裁预览 (前端仅展示,其余操作交给后端)
1.5 cropperjs基本使用
-
安装cropperjs
npm install cropperjs -
在
updata-photo.vue引入css、jsimport 'cropperjs/dist/cropper.css' import Cropper from 'cropperjs' -
在mounted中初始化
<template> <div class="updata-photo"> //设置ref属性获取DOM <img ref="img" :src="image" /> <div class="toolbar"> <span class="cancel" @click="$emit('close')">取消</span> <span class="confirm" @click="onConfirm">完成</span> </div> </div> </template>data () { return { cropper: null } }, mounted () { // 获取图片页面元素 const image = this.$refs.img this.cropper = new Cropper(image, { viewMode: 1, // 定义裁剪模式,0:裁剪框可以扩展到画布外, 1,2,3会将裁剪框限制在画布内 dragMode: 'move', // 拖动模式 move: 移动画布 aspectRatio: 1, // 截图比例, 1:1可以简写为1 autoCropArea: 1, // 自动截图区 cropBoxMovable: false, // 截图区域是否可以移动 cropBoxResizable: false, // 截图区的缩放功能 background: false, // 是否显示默认背景 movable: true // 背景图片是否可以移动,默认true // crop (event) { // 当移动图片是会返回的各种数据(今天的案例中不需要使用) // console.log(event.detail.x) // console.log(event.detail.y) // console.log(event.detail.width) // console.log(event.detail.height) // console.log(event.detail.rotate) // console.log(event.detail.scaleX) // console.log(event.detail.scaleY) // } }) }
1.6 裁剪结果的两种方式
1.6.1 服务端处理数据
如果基于服务端裁剪,则使用getData方法,该方法可以得到裁剪区域的各参数
-
获取cropper实例
this.cropper = new Cropper(image, { viewMode: 1, // 定义裁剪模式,0:裁剪框可以扩展到画布外, 1,2,3会将裁剪框限制在画布内 dragMode: 'move', // 拖动模式 move: 移动画布 aspectRatio: 1, // 截图比例, 1:1可以简写为1 autoCropArea: 1, // 自动截图区 cropBoxMovable: false, // 截图区域是否可以移动 cropBoxResizable: false, // 截图区的缩放功能 background: false, // 是否显示默认背景 movable: true // 背景图片是否可以移动,默认true }) -
给确定注册点击事件,在事件函数中获取参数
<span class="confirm" @click="onConfirm()">完成</span> -
处理数据(把数据传递给后端,后端全权处理)
onConfirm () { console.log(this.cropper.getData()) }
1.6.2 前端处理数据
confirm () {
// console.log(this.cropper.getData())
this.cropper.getCroppedCanvas().toBlob(blob => {
console.log(blob)
})
}
1.7 实现裁剪图片提交
-
在
api/user.js封装更新头像api/** * 更新用户照片资料 */ export const updateUserPhoto = data => { return request({ method: 'PATCH', url: '/v1_0/user/photo', data }) } -
在
api/index.js中输出import { updateUserPhoto } from './user.js' export const updateUserPhotoAPI = updateUserPhoto -
引入方法并调用
import { updateUserPhotoAPI } from '../../../api/index.js'onConfirm () { // 基于服务端的裁切使用 getData 方法获取裁切参数 // console.log(this.cropper.getData()) // 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象 this.cropper.getCroppedCanvas().toBlob(blob => { this.updateUserPhoto(blob) }) },// 处理裁剪图片并上传 async updateUserPhoto (blob) { try { // 如果接口要求 Content-Type 是 multipart/form-data // 则你必须传递 FormData 对象 // 创建formData数据 const formData = new FormData() formData.append('photo', blob) const { data: res } = await updateUserPhotoAPI(formData) // 提示成功 this.$toast.success('更新成功') } catch (error) { this.$toast.fail('更新失败') } } -
关闭弹层、更新视图
// 处理裁剪图片并上传 async updateUserPhoto (blob) { try { ... // 关闭弹出层 this.$emit('close') // 更新视图 this.$emit('update-photo', res.data.photo) // 提示成功 this.$toast.success('更新成功') } catch (error) { this.$toast.fail('更新失败') } }<updata-photo :img="img" @close="isUpdatePhotoShow = false" @update-photo="user.photo = $event" ></updata-photo> -
利用v-if重置数据
<updata-photo v-if="isUpdatePhotoShow" :img="img" @close="isUpdatePhotoShow = false" @update-photo="user.photo = $event" ></updata-photo> -
优化loading效果
// 处理裁剪图片并上传 async updateUserPhoto (blob) { this.$toast.loading({ message: '保存中...', forbidClick: true, // 禁止背景点击 duration: 0 // 持续展示 }) ... }
2.完整代码
<template>
<div class="updata-photo">
<img ref="img" :src="image" />
<div class="toolbar">
<span class="cancel" @click="$emit('close')">取消</span>
<span class="confirm" @click="onConfirm">完成</span>
</div>
</div>
</template>
<script>
import 'cropperjs/dist/cropper.css'
import Cropper from 'cropperjs'
import { updateUserPhotoAPI } from '../../../api/index.js'
export default {
props: {
image: {
type: String,
default: ''
}
},
data () {
return {
Cropper: null
}
},
mounted () {
// 初始化裁剪功能Cropper
this.initCropper()
},
methods: {
initCropper () {
// 获取图片页面元素
const image = this.$refs.img
this.cropper = new Cropper(image, {
viewMode: 1, // 定义裁剪模式,0:裁剪框可以扩展到画布外, 1,2,3会将裁剪框限制在画布内
dragMode: 'move', // 拖动模式 move: 移动画布
aspectRatio: 1, // 截图比例, 1:1可以简写为1
autoCropArea: 1, // 自动截图区
cropBoxMovable: false, // 截图区域是否可以移动
cropBoxResizable: false, // 截图区的缩放功能
background: false, // 是否显示默认背景
movable: true // 背景图片是否可以移动,默认true
// crop (event) { // 当移动图片是会返回的各种数据(今天的案例中不需要使用)
// console.log(event.detail.x)
// console.log(event.detail.y)
// console.log(event.detail.width)
// console.log(event.detail.height)
// console.log(event.detail.rotate)
// console.log(event.detail.scaleX)
// console.log(event.detail.scaleY)
// }
})
},
onConfirm () {
// 基于服务端的裁切使用 getData 方法获取裁切参数
// console.log(this.cropper.getData())
// 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象
this.cropper.getCroppedCanvas().toBlob(blob => {
this.updateUserPhoto(blob)
})
},
async updateUserPhoto (blob) {
try {
// 如果接口要求 Content-Type 是 multipart/form-data
// 则你必须传递 FormData 对象
// 创建formData数据
const formData = new FormData()
formData.append('photo', blob)
const res = await updateUserPhotoAPI(formData)
console.log(res)
// 关闭弹出层
this.$emit('close')
// 父组件修改数据
this.$emit('update-photo', res.data.data.photo)
// 提示成功
this.$toast.success('更新成功')
} catch (error) {
this.$toast.fail('更新失败')
}
}
}
}
</script>
<style scoped lang='less'>
.updata-photo {
background-color: #000;
height: 100%;
.toolbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: space-between;
.cancel, .confirm {
width: 90px;
height: 90px;
font-size: 30px;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
}
}
.img {
display: block;
max-width: 100%;
}
</style>