头条案例day09:

编辑用户资料:

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.localGender

        await 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)
    })
}

父组件里面监听头像更新事件,修改头像信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值