头像上传:change 事件问题 和 上传头像参数问题multipart/form-data

542 阅读1分钟

问题

  1. file-input 如果选了同一个文件 不会触发 chang 事件 解决办法就是每次使用完毕 把他的 value 清空

    this.$refs.file.value = ''
    
  2. 上传头像请求参数传递:接口要求Content-Type 为 multipart/form-data

     // 注意这里请求头的 Content-Type 是	multipart/form-data
     const formData = new FormData()
     formData.append('photo', blob)
     const { data } = await uploadUserPhoto(formData)
    

image.png 点击头像:选择文件 这样写,其中选择文件的事件通过: @change 绑定

image.png

image.png

头像上传弹出层:

image.png

image.png

第三方库:如果是纯客户端的图片裁切,则使用:getCroppedCanvas 方法

如果是基于服务端的裁切,则使用:getData 方法,该方法得到裁切的区域参数。

体验预览效果

  1. npm install cropperjs

  2. 加载css js

     import 'cropperjs/dist/cropper.css';
     import Cropper from 'cropperjs';
    
  3. 将图片img标签 放到一个div容器中

  4. 官方要求设置图片样式:是这个

     img {
       display: block;
    
       /* This rule is very important, please don't ignore this */
       max-width: 100%;
     }
    
  5. 图片裁切:写在mounted中

     import 'cropperjs/dist/cropper.css';
     import Cropper from 'cropperjs';  
     mounted() {
         const image = this.$refs.image;
         this.cropper = new Cropper(image, {
           viewMode: 1,
           dragMode: 'move',
           aspectRatio: 1,
           autoCropArea: 1,
           cropBoxMovable: false,
           cropBoxResizable: false,
           background: false,
           movable: true
         })
       },    
    

父组件完整代码:

<template>
  <div class="user-profile">
    <van-nav-bar title="个人信息" class="page-nav-bar" left-arrow @click-left="$router.back()"></van-nav-bar>
    <!-- 上传头像 -->
    <input type="file" ref="file" hidden @change="onFileChange">
    <van-cell title="头像" is-link @click="$refs.file.click()">
      <van-image class="head" fit="cover" round :src="user.photo" />
    </van-cell>
    <van-cell title="昵称" :value="user.name" is-link @click="isShowNickName=true" />
    <van-cell title="性别" :value="user.gender === 0 ? '男' : '女'" is-link @click="isShowGender=true" />
    <van-cell title="生日" :value="user.birthday" is-link @click="isShowBirthday=true" />

    <!-- 昵称弹出层 -->
    <van-popup v-model="isShowNickName" style="height:100%" position="bottom">
      <update-name v-if="isShowNickName" v-model="user.name" @close="isShowNickName = false"> </update-name>
    </van-popup>

    <!-- 性别弹出层 -->
    <van-popup v-model="isShowGender" position="bottom">
      <update-gender v-if="isShowGender" v-model="user.gender" @close="isShowGender = false"> </update-gender>
    </van-popup>

    <!-- 生日弹出层 -->
    <van-popup v-model="isShowBirthday" position="bottom">
      <!-- <update-birthday v-if="isShowBirthday" @close="isShowBirthday = false" v-model="user.birthday"> </update-birthday> -->
      <update-birthday v-if="isShowBirthday" @close="isShowBirthday = false" v-model="user.birthday" />
    </van-popup>

    <!-- 头像弹出层 -->
    <van-popup v-model="isShowPhoto" position="bottom" style="height:100%">
      <!-- <update-birthday v-if="isShowBirthday" @close="isShowBirthday = false" v-model="user.birthday"> </update-birthday> -->
      <update-photo v-if="isShowPhoto" :img="img" @close="isShowPhoto=false" @update-photo="user.photo=$event" />
    </van-popup>
  </div>
</template>

<script>
import { getUserProfile } from '@/api/user'
import UpdateName from './components/update-name'
import UpdateGender from './components/update-gender'
import UpdateBirthday from './components/update-birthday'
import UpdatePhoto from './components/update-photo'

export default {
  name: "UserProfile",
  components: { UpdateName, UpdateGender, UpdateBirthday, UpdatePhoto },
  data() {
    return {
      user: {},
      isShowNickName: false,
      isShowGender: false,
      isShowBirthday: false,
      isShowPhoto: false,
      img: null
    }
  },
  created() {
    this.loadUserProfile()
  },
  methods: {
    async loadUserProfile() {
      try {
        const { data } = await getUserProfile()
        console.log(data);
        this.user = data.data
      } catch (error) {

      }
    },
    onFileChange() {
      // 获取文件对象
      const file = this.$refs.file.files[0]
      // 基于问斩搞对象获取blob数据
      this.img = window.URL.createObjectURL(file)
      this.isShowPhoto = true
      // change 事件问题 :file-input 如果选了同一个文件 不会触发 chang 事件
      // 解决办法就是每次使用完毕 把他的 value 清空
      this.$refs.file.value = ''
    }
  }
}
</script>

<style scoped lang="less">
.van-popup {
  background-color: #f1f1f1;
}
.head {
  width: 60px;
  height: 60px;
}
</style>

子组件:完整代码:

<template>
  <div class="update-photo">
    <img class="img" :src="img" ref="image">
    <div class="toolbar">
      <div class="cancel" @click="$emit('close')">取消</div>
      <div class="confirm" @click="onConfirm">完成</div>
    </div>
  </div>
</template>

<script>
import 'cropperjs/dist/cropper.css';
import Cropper from 'cropperjs';
import { uploadUserPhoto } from '@/api/user'
export default {
  name: "update-photo",
  props: {
    img: {
      type: [Object, String],
      required: true
    }
  },
  data() {
    return {
      cropper: null
    }
  },
  mounted() {
    const image = this.$refs.image;
    this.cropper = new Cropper(image, {
      viewMode: 1,
      dragMode: 'move',
      aspectRatio: 1,
      autoCropArea: 1,
      cropBoxMovable: false,
      cropBoxResizable: false,
      background: false,
      movable: true
    })
  },
  methods: {
    onConfirm() {
      // 基于服务端的裁切使用 getData 方法
      // console.log(this.cropper.getData());
      this.cropper.getCroppedCanvas().toBlob(blob => {
        // console.log(blob);

        // 发送请求
        this.uploadUserPhoto(blob)

      }
      )
    },
    async uploadUserPhoto(blob) {
      this.$toast.loading({
        message: '上传中...',
        forbidClick: true, // 禁用背景点击
        duration: 0, // 持续展示 toast
      })
      try {
        // 注意这里请求头的 Content-Type 是	multipart/form-data
        const formData = new FormData()
        formData.append('photo', blob)
        const { data } = await uploadUserPhoto(formData)
        // console.log(data);
        // 更新图片
        this.$emit('update-photo', data.data.photo)
        // 关闭图层
        this.$emit('close')
        // 提示更新成功
        this.$toast('头像更新成功')
      } catch (error) {
        this.$toast.fail('更新失败')
      }
    }
  }
}
</script>

<style scoped lang="less">
.update-photo {
  height: 100%;
  background-color: #000;
  .toolbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    justify-content: space-between;
    .cancel,
    .confirm {
      display: flex;
      width: 90px;
      height: 90px;
      font-size: 30px;
      justify-content: center;
      align-items: center;
      color: #fff;
    }
  }
}
.img {
  display: block;
  max-width: 100%;
}
</style>