编辑用户资料:
1. 创建组件并配置路由
创建 `views/user-profile/index.vue` , 并将该页面配置到跟路由 , 给父组件编辑资料按钮绑定跳转
2. 页面布局:引入vant组件,van-nav-bar,van-cell,van-image
3. 展示用户信息
思路:
- 找到数据接口
- 封装请求方法
- 请求获取数据
- 模板绑
4. 修改昵称
1、准备弹出层
定义弹出层控制变量:isUpdateNameShow: false
绘制弹出层组件:
<!-- 编辑昵称 -->
<van-popup
v-model="isUpdateNameShow"
style="height: 100%;"
position="bottom"
>
昵称编辑修改
</van-popup>
<!-- /编辑昵称 -->
打开弹出层:
<van-cell
title="昵称"
:value="user.name"
is-link
@click="isUpdateNameShow = true"
/>
2、封装组件和布局
创建`user-profile/components/update-name.vue`组件
父组件 `user-profile/index.vue` 导入,注册,使用 且监听关闭事件
3、数据传递
父组件 传递用户昵称用于显示,且将来子组件内部会更新这个昵称,故而可使用`v-model`
子组件定义`props`接收
将接收到的数据赋给`data`变量(如果直接绑定给输入框则违法了修改`props`的原则)
4、发送请求
在`api/user.js`里面封装修改资料方法
确定按钮绑定事件
<!-- 导航栏 -->
<van-nav-bar
title="设置昵称"
left-text="取消"
right-text="完成"
@click-left="$emit('close')"
@click-right="onConfirm"
/>
导入请求方法,发送请求
import { updateUserProfile } from '@/api/user'
async onConfirm () {
this.$toast.loading({
message: '保存中...',
forbidClick: true, // 禁止背景点击
duration: 0 // 持续展示
})try {
const message = this.message
if (!message.length) {
this.$toast('昵称不能为空')
return
}await updateUserProfile({
name: message
})// 更新视图
this.$emit('input', message)// 关闭弹层
this.$emit('close')// 提示成功
this.$toast.success('更新成功')
} catch (err) {
this.$toast.fail('更新失败')
}
}
5. 修改性别
1、准备弹出层
定义弹出层控制变量:isUpdateGenderShow: false
绘制弹出层组件:
<!-- 编辑性别 -->
<van-popup
v-model="isUpdateGenderShow"
position="bottom"
>
编辑性别
</van-popup>
<!-- /编辑昵称 -->
打开弹出层:
<van-cell
title="性别"
:value="user.gender === 0 ? '男' : '女'"
is-link
@click="isUpdateGenderShow = true"
/>
2、封装组件和布局
创建`user-profile/components/update-gender.vue`组件
父组件 `user-profile/index.vue` 导入,注册,使用 且监听关闭事件
3、数据传递
父组件 传递用户昵称用于显示,且将来子组件内部会更新这个性别,故而可使用`v-model`
子组件定义`props`接收
将接收到的数据赋给`data`变量(如果直接绑定给输入框则违法了修改`props`的原则)
4、发送请求
在`api/user.js`里面封装修改资料方法(相同不用封装)
导入请求方法,发送请求
import { updateUserProfile } from '@/api/user'
async onConfirm () {
this.$toast.loading({
message: '保存中...',
forbidClick: true, // 禁止背景点击
duration: 0 // 持续展示
})try {
const localGender = this.localGenderawait updateUserProfile({
gender: localGender
})// 更新视图
this.$emit('input', localGender)// 关闭弹层
this.$emit('close')// 提示成功
this.$toast.success('更新成功')
} catch (err) {
this.$toast.fail('更新失败')
}
}
6. 修改生日
1、准备弹出层
定义弹出层控制变量:isUpdateBirthdayShow: false
绘制弹出层组件:
<!-- 编辑生日 -->
<van-popup
v-model="isUpdateBirthdayShow"
position="bottom"
>
编辑生日
</van-popup>
<!-- /编辑生日 -->
打开弹出层:
<van-cell
title="生日"
:value="user.birthday"
is-link
@click="isUpdateBirthdayShow = true"
/>
2、封装组件和布局
创建`user-profile/components/update-birthday.vue`组件
父组件 `user-profile/index.vue` 导入,注册,使用 且监听关闭事件
3、数据传递
父组件 传递用户昵称用于显示,且将来子组件内部会更新这个生日,故而可使用`v-model`
子组件定义`props`接收
将接收到的数据赋给`data`变量(如果直接绑定给输入框则违法了修改`props`的原则)
4、发送请求
在`api/user.js`里面封装修改资料方法(相同不用封装)
导入请求方法,发送请求
import { updateUserProfile } from '@/api/user'
import dayjs from 'dayjs'async onConfirm () {
this.$toast.loading({
message: '保存中...',
forbidClick: true, // 禁止背景点击
duration: 0 // 持续展示
})try {
const currentDate = dayjs(this.currentDate).format('YYYY-MM-DD')await updateUserProfile({
birthday: currentDate
})// 更新视图
this.$emit('input', currentDate)// 关闭弹层
this.$emit('close')// 提示成功
this.$toast.success('更新成功')
} catch (err) {
this.$toast.fail('更新失败')
}
}
7. 修改头像
1、图片预览思路
方式一:结合服务器的图片上传预览
客户端请求上传图片,将图片储存到服务器,返回图片文件在服务器的http访问地址,客户端获取上传返回结果,将img的src设置为返回结果<img src="http://xxx.png">
方式二:纯客户端实现上传图片预览
// 获取文文件对象
const file = fileInput.files[0]
// 设置图片的 src
img.src = window.URL.createObjectURL(file)
客户端上传预览示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>客户端图片上传预览示例</title>
<style>
.img-wrap {
width: 200px;
height: 200px;
border: 1px solid #ccc;
}img {
max-width: 100%;
}
</style>
</head>
<body>
<h1>客户端图片上传预览示例</h1>
<div class="img-wrap">
<img src="" alt="" id="img">
</div>
<br>
<input type="file" id="file" οnchange="onFileChange()">
<script>
const img = document.querySelector('#img')
const file = document.querySelector('#file')function onFileChange() {
// 得到 file-input 的文件对象
const fileObj = file.files[0]
const data = window.URL.createObjectURL(fileObj)
img.src = data
}
</script>
</body>
</html>
2、头像裁切思路
方案一:结合服务端的图片裁切上传流程
客户端上传图片,服务器存储图片,返回原图地址,再上传图片裁切参数,服务器处理裁切,存储裁切结果,返回裁切之后的图片地址给客户端
方案二:纯客户端的图片裁切上传流程
[\<input type="file"\>]
3、实现图片预览功能
处理file-input
增加一个文件选择标签,让头像行点击触发文件选择器的点击事件
<input type="file" hidden ref="file" @change="onFileChange" >
<!-- 个人信息 -->
<van-cell
class="photo-cell"
title="头像"
is-link
center
@click="$refs.file.click()"
>
<van-image
class="avatar"
fit="cover"
round
:src="user.photo"/>
</van-cell>
onFileChange () {
// 获取文件对象
const file = this.$refs.file.files[0]// 基于文章对象获取 blob 数据
const data = window.URL.createObjectURL(file)console.log(data)
}
功能处理
准备弹出层
<!-- 编辑头像 -->
<van-popup
v-model="isUpdatePhotoShow"
position="bottom"
style="height: 100%;"
>
编辑头像编辑头像编辑头像
</van-popup>
<!-- /编辑头像 -->
定义存储头像预览数据变量和弹框控制变量
data () {
return{
// 其他数据...
isUpdatePhotoShow: false,
img: null // 预览的图片
}
}
打开弹框,赋值预览图片地址数据
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 = ''
}
创建`user-profile/components/update-photo.vue`组件
<template>
<div class="update-photo">
<img class="img" :src="img" ref="img"><div class="toolbar">
<div class="cancel" @click="$emit('close')">取消</div>
<div class="confirm" @click="onConfirm">完成</div>
</div>
</div>
</template><script>
export default {
name: 'UpdatePhoto',
props: {
// 预览图片地址信息
img: {
type: [String, Object],
required: true
}
},
methods: {
// 确定事件
onConfirm(){
}
}
}
父组件 `user-profile/index.vue` 导入,注册,使用 且监听关闭事件
4、实现图片裁剪上传
使用裁剪组件
安装`cropperjs`导入使用:
npm i cropperjs
import 'cropperjs/dist/cropper.css'
import Cropper from 'cropperjs'
data () {
return {
cropper: null // 裁剪器对象
}
}
mounted () {
const image = this.$refs.img
this.cropper = new Cropper(image, {
viewMode: 1,
dragMode: 'move',
aspectRatio: 1,
// autoCropArea: 1,
cropBoxMovable: false,
cropBoxResizable: false,
background: false
})
// 不能在这里调用 this.cropper.getCroppedCanvas() 方法!因为裁剪器还没初始化好!
}
**注意:**
1. 要求被裁剪的`img`标签元素,必须有个块标签包裹!
2. 必须在`mounted`里面调用,因为这里组件才挂载完成
**获取裁剪结果信息**
注意,不能在初始化后立即调用获取结果的方法,因为裁剪器初始化是一个异步过程,这里可能存在没有初始化好的情况
onConfirm () {
// 基于服务端的裁切使用 getData 方法获取裁切参数
// console.log(this.cropper.getData())// 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象
this.cropper.getCroppedCanvas().toBlob(blob => {
console.log(blob) // 裁剪后的结果信息
})
},
-**提交头像修改**
`api/user.js`封装头像修改方法
页面中导入使用,提交头像资料
import { updateUserPhoto } from '@/api/user'
async updateUserPhoto (blob) {
this.$toast.loading({
message: '保存中...',
forbidClick: true, // 禁止背景点击
duration: 0 // 持续展示
})
try {
// 错误的用法
// 如果接口要求 Content-Type 是 application/json
// 则传递普通 JavaScript 对象
// updateUserPhoto({
// photo: blob
// })// 如果接口要求 Content-Type 是 multipart/form-data
// 则你必须传递 FormData 对象
const formData = new FormData()
formData.append('photo', blob)const { data } = await updateUserPhoto(formData)
// 关闭弹出层
this.$emit('close')// 更新视图
this.$emit('update-photo', data.data.photo)// 提示成功
this.$toast.success('更新成功')
} catch (err) {
this.$toast.fail('更新失败')
}
}
onConfirm () {
// 基于服务端的裁切使用 getData 方法获取裁切参数
// console.log(this.cropper.getData())// 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象
this.cropper.getCroppedCanvas().toBlob(blob => {
this.updateUserPhoto(blob)
})
}
父组件里面监听头像更新事件,修改头像信息