Laravel+Vue前后端分离项目(五) 裁剪+上传头像

效果:

选择图片,调整大小位置,上传图片,图片会被存到服务器并返回图片名称,前端再更新本地头像地址。

目录结构

为了方便测试,vue在本地目录测试,laravel接口放在服务器上

         

Vue前端

1、/src/views/test.vue组件的html部分:

<template>

    //左边
    <input
        type="file"              //输入类型为文件
        accept="image/*"         //仅可选图片类型文件
        ref="avatarInput"        //ref相当于id
        @change="changeImage"    //选中图片后调用changeImage函数
        style="display:none"     //隐藏input组件,因为太丑
    >
    <img 
        :src="my_avatar"         //我的头像
        class="avatar"           //样式为圆形
    />


    //右边
    <div style="display:flex;flex-direction:column">
        <div class="big_avatar" ref="imageWrapper">   //裁剪框
            <img 
              :class="moveimg_class" 
              :src="preview_avatar" 
              :style="{'top':top,'left':left}"
              @mousedown="movestart"
              @mouseenter="moveenter"
              @mouseleave="moveleave"
              ref="move_img">
        </div>
        <div class="long">   //滑动条
            <img @mousedown="potstart" class="pot" />          
        </div>
        <div class="avatar_button" @click="upLoad">选择图片</div>
        <div class="avatar_button" @click="draw">上传头像</div>
    </div>
</template>

2、/src/test.vue组件的js部分:

<script>
import axios from 'axios';
import html2canvas from 'html2canvas';
export default {
  name:'test',
  data(){
    return{
      cursor:'',  //鼠标进入裁剪框会变图标
      my_avatar:'http://lanyue.ink/api/default_avatar.png',    //左边头像
      preview_avatar:'http://lanyue.ink/api/default_avatar.png',  //裁剪框里的头像
      ava_width:0,  //头像上传前的宽度
      top:'0px',    //裁剪框内头像的top
      left:'0px',    //裁剪框内头像的left
      ava_left:0,    //头像在每次缩放后的left
      moveimg_class:'move_img3', //裁剪框内图片的class
    }
  },
  mounted(){
    this.$refs.move_img.onmousedown = function(e){  //取消img自带的拖拽效果
      e.preventDefault();
    };
  },
  methods:{
    upLoad:function(){     //上传头像,触发input点击事件
      this.$refs.avatarInput.dispatchEvent(new MouseEvent("click"));
    },
    
    changeImage() {
      let files = this.$refs.avatarInput.files;
      let that = this;
      this.top = '0px';
      this.left = '0px';
      if (/\.(jpe?g|png|gif)$/i.test(files[0].name)) {
        let reader = new FileReader();
        reader.readAsDataURL(files[0]);
        reader.onload = function() {
          that.preview_avatar = this.result;  //裁剪框内的图片换成刚刚选择的图片
          let img = new Image();              //新建Image是为了获取图片原本的高度宽度
          img.src = this.result;              //如果图片高>=宽,则让图片的宽抵满裁剪框
          img.onload = function () {          //如果图片高<宽,则让图片的高抵满裁剪框
            if(this.height>=this.width){
              that.moveimg_class = 'move_img1';
            }else{
              that.moveimg_class = 'move_img2';
            }
          }
        }
      }
    },

    draw:function(){
      let that = this
      let opts = {
        logging: true, // 启用日志记录以进行调试 (发现加上对去白边有帮助)
        allowTaint: true, // 否允许跨源图像污染画布
        backgroundColor: null, // 解决生成的图片有白边
        useCORS: true // 如果截图的内容里有图片,解决文件跨域问题
      }
      window.pageYOffset = 0;    //绘制div前必须保证滚动条是在最顶端,否则图片会错位
      document.documentElement.scrollTop = 0
      document.body.scrollTop = 0
      html2canvas(that.$refs.imageWrapper, opts).then((canvas) => {   //绘制裁剪框div
        let url = canvas.toDataURL('image/png')
        that.my_avatar = url;
        axios.post('/api/upload_avatar',{data:url},{    //上传图片的axios请求
          headers:{'Content-Type':'application/json;charset=UTF-8'}
        }).then(res=>{
          this.my_avatar = 'http://lanyue.ink/api/'+res.data;
        });
      })
    },

    moveenter:function(){    //鼠标移入裁剪框
      this.cursor = "cursor: url('http://lanyue.ink/api/icon.png'),auto;";
    },
    moveleave:function(){    //鼠标移出裁剪框
      this.cursor = "cursor: ;";
    },

    movestart(e){            //拖动裁剪框中的图片
      let op = e.target;
      let that = this;
      let disX = e.clientX - op.offsetLeft;
      let disY = e.clientY - op.offsetTop;
      document.onmousemove = (e)=>{
        //用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
        let left = e.clientX - disX;
        let top = e.clientY - disY;
        
        if(top <= 0 && top >= (256-op.height))
          that.top = top + 'px';
        if(left <=0 && left >= (256-op.width))
          that.left = left + 'px';
      };
      document.onmouseup = () => {
        document.onmousemove = null;
        document.onmouseup = null;
      };
    },

    potstart(e){        //拖动滑动条,调整图片大小
      let op = e.target;
      let disX = e.clientX - op.offsetLeft;
      let that = this;
      //获取原始宽
      if(!this.ava_width)
        this.ava_width = that.$refs.move_img.width;
      let str1 = that.top.slice(0,-2)
      let img_top = parseInt(str1);
      let str2 = that.left.slice(0,-2)
      let img_left = parseInt(str2);
      let record_letf = 0;
      document.onmousemove = (e) =>{
        let left = e.clientX - disX;
        if(left >= 5 && left <= 221){
          //滑动条移动
          op.style.left = left+'px';
          //裁剪框内图片的宽增加
          that.$refs.move_img.style.width = (left+this.ava_width) +'px';
          //中心缩放
          let a=(left-that.ava_left)/2;
          that.top = (img_top-a)+'px';
          that.left = (img_left-a)+'px';
          record_letf = left;
        }
      };
      document.onmouseup = () => {
        that.ava_left = record_letf;    //记录每次拖完滑动条的left
        document.onmousemove = null;
        document.onmouseup = null;
      };
    }
  }
}
</script>

3、 /src/test.vue组件的css部分:

<style scoped>
.avatar{
  width: 128px;
  height: 128px;
  border-radius: 50%;
}
.big_avatar{
    position: relative;
    width: 256px;
    height: 256px;
    background-color: rgb(228, 51, 51);
    overflow: hidden;
}
//竖直
  .move_img1{
    position: absolute;
    min-width: 256px;
  }
  //横向
  .move_img2{
    position: absolute;
    min-height: 256px;
  }
  .move_img3{
    width: 256px;
    height: 256px;
  }
.avatar_button{
    width: 256px;
    height: 40px;
    line-height: 40px;
    background-color: rgba(207, 207, 207, 0.5);
    margin-top: 20px;
  }
  .long{
    margin-top: 20px;
    width: 256px;
    height: 40px;
    border-radius: 20px;
    background-color: #fff;
    position: relative;
    .pot{
      width: 30px;
      height: 30px;
      background-color: rgba(0, 0, 0, 0.5);
      position: absolute;
      border-radius: 100%;
      top:5px;
      left: 5px;
    }
  }
</style>

4、在vue.config.js中设置跨域访问(如果没有就新建这个文件,并且要重新npm run serve)

module.exports = {
    devServer: {
        proxy: {
          '/api': {
            target: 'http://***.com',
            ws: true,
            changeOrigin: true,
            pathRewrite: {
              '^/api': '/api/api'  //我们要把接口放到routes/api.php上,所以将前缀/api转为/api/api
                                   //要是放到routes/web.php上,需要进行csrf_token的验证
            }
          }
        }
      }
  }

Laravel后端

1、/routes/api.php

Route::post('/upload_avatar','UserController@upload_avatar');

2、App/Http/Controllers/UserController.php

public function upload_avatar(Request $request){
        //获取base64编码
        $img = $request->data;
        //用逗号分割base64编码
        $explode = explode(',',$img);    
        //对base64解码
        $decoded = base64_decode($explode[1]);    
        //判断图片的后缀
        if(strpos($explode[0],'jpeg')){           
            $extension = 'jpg';
        }else if(strpos($explode[0],'png')){
            $extension = 'png';
        }
        //生成随机字符串
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        $length = 15;
        for($i = 0; $i < $length; $i++)
        {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        //生成文件名
        $filename = $str.'.'.$extension;
        //图片存放的public目录
        $path = public_path().'/'.$filename;
        //将图片放到public目录的方法
        file_put_contents($path,$decoded);
        //返回前端文件名
        return $filename;
}

猜你喜欢

转载自blog.csdn.net/qq_33514421/article/details/104861822
今日推荐