Quellcode durchsuchen

上传图片使用兼容ipad的

Changpeng Duan vor 5 Jahren
Ursprung
Commit
05b25dee85

+ 293 - 0
pc/src/components/cropper.vue

@@ -0,0 +1,293 @@
+<template>
+    <div class="custom-upload">
+        <el-dialog
+                title="图片裁剪"
+                :visible.sync="showCropper"
+                width="50%"
+                height="600"
+                class="cropper-dialog"
+                center
+                append-to-body
+        >
+            <vue-cropper
+                    v-if="showCropper"
+                    id="corpper"
+                    ref="cropper"
+                    :class="{'corpper-warp':showCropper}"
+                    v-bind="cropper"
+            />
+            <div v-if="showCropper" class="cropper-button">
+                <el-button class="cancel-btn" size="small" @click.native="showCropper=false">取消</el-button>
+                <el-button size="small" type="primary" :loading="loading" @click="uploadCover">完成</el-button>
+            </div>
+        </el-dialog>
+        <input
+                :id="id"
+                type="file"
+                style="display: none"
+                name="single"
+                accept="image/*"
+                @change="onChange($event)"
+        />
+
+        <el-button size="small" type="primary" :loading="loading" @click="handleOpenFile()">
+            <i class="fa fa-upload"/>
+            {{ buttonName }}
+        </el-button>
+        <div v-if="tips" class="tips clear-margin-top">{{ tips }}</div>
+    </div>
+</template>
+
+<script>
+    import * as qiniu from 'qiniu-js';
+    // 上传文件组件
+    import {VueCropper} from 'vue-cropper'
+
+    // 定义的接口根据自己项目更换
+    // import {uploadImage} from '@/api/upload'
+
+    import {isImageFile, isMaxFileSize, readFile} from '@/utils/upload'   // 见下文
+    let qs = require('qs');
+    export default {
+        components: {
+            VueCropper
+        },
+        props: {
+            // 最大上传文件的大小
+            maxFileSize: {
+                type: Number,
+                default: 50 // (MB)
+            },
+            // 按钮文字
+            buttonName: {
+                type: String,
+                default: '添加图片'
+            },
+            // 提示内容
+            tips: {
+                type: String
+            },
+            // 图片裁剪比列
+            fixedNumber: {
+                type: Array,
+                default: function () {
+                    return []
+                }
+            },
+            // 图片文件分辨率的宽度
+            width: {
+                type: Number,
+                default: 460
+            },
+            // 图片文件分辨率的高度
+            height: {
+                type: Number,
+                default: 300
+            }
+        },
+        data() {
+            return {
+                imageUrl: '',
+                token: {},
+                // 七牛云的上传地址,根据自己所在地区选择,我这里是华南区
+                // domain: 'https://up-z1.qiniup.com',
+                // 这是七牛云空间的外链默认域名
+                // qiniuaddr: 'qjzpcd34v.hb-bkt.clouddn.com',
+                // domain: 'https://up-z1.qiniup.com', // 这是七牛云空间的外链默认域名
+                domain: 'https://up-z1.qiniup.com', // 这是七牛云空间的外链默认域名
+                qiniuaddr: 'xhead.beswell.com',//xhead.beswell.com 旧的 qjzpcd34v.hb-bkt.clouddn.com
+                id: 'cropper-input-' + +new Date(),
+                loading: false,
+                showCropper: false,
+                cropper: {
+                    img: '',
+                    info: true,
+                    size: 0.9,
+                    outputType: 'png',
+                    canScale: true,
+                    autoCrop: true,
+                    full: true,
+                    // 只有自动截图开启 宽度高度才生效
+                    autoCropWidth: this.width,
+                    autoCropHeight: this.height,
+                    fixedBox: false,
+                    // 开启宽度和高度比例
+                    fixed: true,
+                    fixedNumber: this.fixedNumber,
+                    original: false,
+                    canMoveBox: true,
+                    canMove: true,
+                    outputSize: 1, // 裁剪生成图片的质量
+                    outputType: 'png', // 裁剪生成图片的格式
+                    full: true, // 是否输出原图比例的截图
+                    info: true, // 图片大小信息
+                    canScale: true, // 图片是否允许滚轮缩放
+                    autoCrop: true, // 是否默认生成截图框
+                    autoCropWidth: 200, // 默认生成截图框宽度
+                    autoCropHeight: 150, // 默认生成截图框高度
+                    canMove: true, // 上传图片是否可以移动
+                    fixedBox: true, // 固定截图框大小 不允许改变
+                    fixed: false, // 是否开启截图框宽高固定比例
+                    canMoveBox: true, // 截图框能否拖动
+                    original: false, // 上传图片按照原始比例渲染
+                    centerBox: false, // 截图框是否被限制在图片里面
+                    height: true,
+                    infoTrue: false, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
+                    enlarge: 1, // 图片根据截图框输出比例倍数
+                    mode: 'container', // 图片默认渲染方式
+                    maxImgSize: 375 // 限制图片最大宽度和高度
+                }
+            }
+        },
+        methods: {
+            // 打开文件
+            handleOpenFile() {
+                const input = document.getElementById(this.id)
+                // 解决同一个文件不能监听的问题
+                input.addEventListener(
+                    'click',
+                    function () {
+                        this.value = ''
+                    },
+                    false
+                )
+                // 点击input
+                input.click()
+            },
+
+            // 裁剪input 监听
+            async onChange(e) {
+                let file = e.target.files[0];
+                console.log(file);
+                if (!file) {
+                    console.log('选择图片失败');
+                    return false
+                }
+                // 验证文件类型
+                if (!isImageFile(file)) {
+                    return
+                }
+                try {
+                    // 读取文件
+                    const src = await readFile(file)
+                    this.showCropper = true
+                    this.cropper.img = src
+                } catch (error) {
+                    console.log(error)
+                }
+            },
+
+            // 封面上传功能
+            uploadCover() {
+                this.$refs.cropper.getCropBlob(async imgRes => {
+                    try {
+                        // 文件大小限制
+                        if (!isMaxFileSize(imgRes, this.maxFileSize)) {
+                            return
+                        }
+                        this.loading = true
+                        const url = imgRes;
+                        // this.$emit('subUploadSucceed', url);
+                        this.loading = false
+                        this.showCropper = false
+                        // 上传图片
+                        this.upqiniu(imgRes);
+                    } catch (error) {
+                        this.loading = false
+                        this.showCropper = false
+                        console.log(error);
+                    }
+                })
+            },
+            // 上传文件到七牛云
+            upqiniu(req)
+            {
+                const config = {
+                    headers: {'Content-Type': 'multipart/form-data'}
+                };
+                let filetype = 'jpg';
+                // 重命名要上传的文件
+                const keyname = 'GoAllOut' + new Date().valueOf() + Math.floor(Math.random() * 100) + '.' + filetype;
+                // 从后端获取上传凭证token
+                let param = {
+                    token: localStorage.token,
+                };
+                let postdata = qs.stringify(param);
+                this.axios.post('/api/v1/QiNiu/GetSimpleQiNiuToken', postdata).then(res => {
+                    const formdata = new FormData();
+                    formdata.append('file', req);
+                    formdata.append('token', res.data.QiNinToken);
+                    formdata.append('key', keyname);
+                    // 获取到凭证之后再将文件上传到七牛云空间
+                    this.axios.post(this.domain, formdata, config).then(res => {
+                        this.imageUrl = 'http://' + this.qiniuaddr + '/' + res.data.key
+                        this.$emit('subUploadSucceed',  this.imageUrl)
+                    })
+                })
+            }
+        }
+    }
+</script>
+
+<style scoped>
+    #corpper {
+        width: 90%;
+        height: 400px;
+        margin: 0 auto;
+        background-image: none;
+        background: #fff;
+        z-index: 1002;
+    }
+
+    .cropper-dialog {
+        height: 800px;
+        text-align: center;
+    }
+
+    .el-dialog__header {
+        padding-top: 15px;
+    }
+
+    .el-dialog--center .el-dialog__body {
+        padding-top: 0;
+        padding-bottom: 15px;
+    }
+
+    .el-dialog {
+        text-align: center;
+    }
+
+    .cropper-button {
+        z-index: 1003;
+        text-align: center;
+        margin-top: 20px;
+    }
+
+    .el-button {
+        font-size: 16px;
+        cursor: pointer;
+        text-align: center;
+    }
+
+    .cancel-btn {
+        color: #373737;
+    }
+
+    .el-button:last-child {
+        margin-left: 0px;
+    }
+
+    .cropper-modal {
+        background-color: rgba(0, 0, 0, 0.5) !important;
+    }
+
+    .custom-upload .tips {
+        margin-top: 10px;
+        color: red;
+        font-size: 12px;
+    }
+
+    .custom-upload.clear-margin-top {
+        margin-top: 0;
+    }
+</style>

+ 143 - 53
pc/src/components/upHead.vue

@@ -1,23 +1,55 @@
 <template>
     <div>
-        <div class="upload">
+        <!--<div class="upload">-->
+        <!--<el-upload-->
+        <!--class="avatar-uploader"-->
+        <!--:action=domain-->
+        <!--:http-request=upqiniu-->
+        <!--:show-file-list="false"-->
+        <!--:before-upload="beforeUpload">-->
+        <!--<img v-if="imageUrl" :src="imageUrl" class="avatar">-->
+        <!--<i v-else class="el-icon-plus avatar-uploader-icon"></i>-->
+        <!--</el-upload>-->
+        <!--<ImgCutter v-on:cutDown="cutDown"></ImgCutter>-->
+        <!--</div>-->
+        <div class="one-form-item">
             <el-upload
                     class="avatar-uploader"
-                    :action=domain
-                    :http-request=upqiniu
+                    action
+                    :auto-upload="false"
+                    :on-change="uploadFileMethodAnswer"
                     :show-file-list="false"
-                    :before-upload="beforeUpload">
-                <img v-if="imageUrl" :src="imageUrl" class="avatar">
-                <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+                    :multiple="true"
+                    :before-upload="beforeUpload"
+            >
+                <el-button class="uploader-button" type="primary">上传banner图</el-button>
             </el-upload>
-            <ImgCutter v-on:cutDown="cutDown"></ImgCutter>
         </div>
+
+        <el-dialog title="图片剪裁(为确保剪切后图片的分辨率足够,请尽量不缩放剪切原图)" :visible.sync="dialogVisible" append-to-body width="70%">
+            <div class="cropper-content">
+                <div class="cropper" style="text-align:center">
+                    <vueCropper
+                            ref="cropper"
+                            :img="option.img"
+                            :outputSize="option.size"
+                            :outputType="option.outputType"
+                    ></vueCropper>
+                </div>
+            </div>
+            <div slot="footer" class="dialog-footer">
+                <el-button @click="dialogVisible = false">取 消</el-button>
+                <el-button type="primary" @click="finish" :loading="loading">确认</el-button>
+            </div>
+        </el-dialog>
+
     </div>
 </template>
 
 <script>
     import * as qiniu from 'qiniu-js';
     import ImgCutter from 'vue-img-cutter'
+    import VueCropper from 'vue-cropper'
     let qs = require('qs');
     export default {
         data() {
@@ -31,63 +63,121 @@
                 // domain: 'https://up-z1.qiniup.com', // 这是七牛云空间的外链默认域名
                 domain: 'https://up-z1.qiniup.com', // 这是七牛云空间的外链默认域名
                 qiniuaddr: 'xhead.beswell.com',//xhead.beswell.com 旧的 qjzpcd34v.hb-bkt.clouddn.com
+                option: {
+                    img: "",
+                    outputSize: 1, //剪切后的图片质量(0.1-1)
+                    full: false, //输出原图比例截图 props名full
+                    outputType: "png",
+                    canMove: true,
+                    original: false,
+                    canMoveBox: true,
+                    autoCrop: true,
+                    autoCropWidth: 868,
+                    autoCropHeight: 488,
+                    fixedBox: false,
+                    fixed: true,
+                    maxImgSize: 3000, // 图片最大像素
+                    fixedNumber: [16, 9]
+                },
+                dialogVisible: false,
+                loading: false,
             };
         },
         methods: {
-            cutDown(e){
-                console.log(e);
-                this.imageUrl = e.dataURL;
-                this.upqiniu(e);
-            },
-            // 上传文件到七牛云
-            upqiniu(req) {
-                const config = {
-                    headers: {'Content-Type': 'multipart/form-data'}
-                };
-                let filetype = '';
-                if (req.file.type === 'image/png') {
-                    filetype = 'png'
-                } else {
-                    filetype = 'jpg'
+                finish()
+                {
+                    let _this = this;
+                    this.$refs.cropper.getCropBlob(data => {
+                        let base64Data = null;
+                        let a = new FileReader();
+                        a.onload = function (e) {
+                            base64Data = e.target.result;
+                            _this.loading = true;
+                            const formData = new FormData();
+                            formData.append("imageFile", base64Data);
+                            uploadImg({
+                                formData: formData
+                            })
+                                .then(res => {
+
+                                })
+                                .catch(() => {
+                                    // this.$message.error('图片上传失败!');
+                                });
+                        };
+                        a.readAsDataURL(data);
+                    });
                 }
-                // 重命名要上传的文件
-                const keyname = 'GoAllOut' + new Date().valueOf() + Math.floor(Math.random() * 100) + '.' + filetype;
-                // 从后端获取上传凭证token
-                let param = {
-                    token: localStorage.token,
-                };
-                let postdata = qs.stringify(param);
-                this.axios.post('/api/v1/QiNiu/GetSimpleQiNiuToken',postdata).then(res => {
-                    const formdata = new FormData();
-                    formdata.append('file', req.file);
-                    formdata.append('token', res.data.QiNinToken);
-                    formdata.append('key', keyname);
-                    // 获取到凭证之后再将文件上传到七牛云空间
-                    this.axios.post(this.domain, formdata, config).then(res => {
-                        this.imageUrl = 'http://' + this.qiniuaddr + '/' + res.data.key
+            ,
+                uploadFileMethodAnswer()
+                {
+                    this.dialogVisible = true;
+                }
+            ,
+                cutDown(e)
+                {
+                    console.log(e);
+                    this.imageUrl = e.dataURL;
+                    this.upqiniu(e);
+                }
+            ,
+                // 上传文件到七牛云
+                upqiniu(req)
+                {
+                    const config = {
+                        headers: {'Content-Type': 'multipart/form-data'}
+                    };
+                    let filetype = '';
+                    if (req.file.type === 'image/png') {
+                        filetype = 'png'
+                    } else {
+                        filetype = 'jpg'
+                    }
+                    // 重命名要上传的文件
+                    const keyname = 'GoAllOut' + new Date().valueOf() + Math.floor(Math.random() * 100) + '.' + filetype;
+                    // 从后端获取上传凭证token
+                    let param = {
+                        token: localStorage.token,
+                    };
+                    let postdata = qs.stringify(param);
+                    this.axios.post('/api/v1/QiNiu/GetSimpleQiNiuToken', postdata).then(res => {
+                        const formdata = new FormData();
+                        formdata.append('file', req.file);
+                        formdata.append('token', res.data.QiNinToken);
+                        formdata.append('key', keyname);
+                        // 获取到凭证之后再将文件上传到七牛云空间
+                        this.axios.post(this.domain, formdata, config).then(res => {
+                            this.imageUrl = 'http://' + this.qiniuaddr + '/' + res.data.key
+                        })
                     })
-                })
-            },
-            // 验证文件合法性
-            beforeUpload(file) {
-                // const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
-                const isLt2M = file.size / 1024 / 1024 < 70;
-                // if (!isJPG) {
-                //     this.$message.error('上传头像图片只能是 JPG 格式!')
-                // }
-                if (!isLt2M) {
-                    this.$message.error('上传头像图片大小不能超过 70MB!')
                 }
-                return isJPG && isLt2M
+            ,
+                // 验证文件合法性
+                beforeUpload(file)
+                {
+                    // const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
+                    const isLt2M = file.size / 1024 / 1024 < 70;
+                    // if (!isJPG) {
+                    //     this.$message.error('上传头像图片只能是 JPG 格式!')
+                    // }
+                    if (!isLt2M) {
+                        this.$message.error('上传头像图片大小不能超过 70MB!')
+                    }
+                    return isJPG && isLt2M
+                }
+            },
+            components: {
+                ImgCutter, VueCropper
             }
-        },
-        components: {
-            ImgCutter
         }
-    }
 </script>
 
 <style scoped>
+    .cropper-content .cropper {
+        width: auto;
+        height: 500px !important;
+    }
+
     .avatar-uploader .el-upload {
         border: 1px dashed #d9d9d9;
         border-radius: 6px;

+ 0 - 1
pc/src/main.js

@@ -10,7 +10,6 @@ import axios from 'axios'
 
 Vue.config.productionTip = false;
 import VCharts from 'v-charts'
-
 Vue.use(VCharts);
 Vue.use(ElementUI);
 

+ 133 - 0
pc/src/utils/upload.js

@@ -0,0 +1,133 @@
+import { Message } from 'element-ui'
+
+/**
+ *
+ * @param {file} file 源文件
+ * @desc 限制为图片文件
+ * @retutn 是图片文件返回true否则返回false
+ */
+export const isImageFile = (file,fileTypes) => {
+    const types =fileTypes|| [
+        'image/png',
+        'image/gif',
+        'image/jpeg',
+        'image/jpg',
+        'image/bmp',
+        'image/x-icon'
+    ]
+    const isImage = types.includes(file.type)
+    if (!isImage) {
+        Message.error('上传文件非图片格式!')
+        return false
+    }
+
+    return true
+}
+
+/**
+ *
+ * @param {file} file 源文件
+ * @param {number} fileMaxSize  图片限制大小单位(MB)
+ * @desc 限制为文件上传大小
+ * @retutn 在限制内返回true否则返回false
+ */
+export const isMaxFileSize = (file, fileMaxSize = 2) => {
+    const isMaxSize = file.size / 1024 / 1024 < fileMaxSize
+    if (!isMaxSize) {
+        Message.error('上传头像图片大小不能超过 ' + fileMaxSize + 'MB!')
+        return false
+    }
+    return true
+}
+
+/**
+ *
+ * @param {file} file 源文件
+ * @desc 读取图片文件为base64文件格式
+ * @retutn 返回base64文件
+ */
+export const readFile = file => {
+    return new Promise((resolve, reject) => {
+        const reader = new FileReader()
+        reader.onload = e => {
+            const data = e.target.result
+            resolve(data)
+        }
+        reader.onerror = () => {
+            const err = new Error('读取图片失败')
+            reject(err.message)
+        }
+
+        reader.readAsDataURL(file)
+    })
+}
+
+/**
+ *
+ * @param {string} src  图片地址
+ * @desc 加载真实图片
+ * @return 读取成功返回图片真实宽高对象 ag: {width:100,height:100}
+ */
+export const loadImage = src => {
+    return new Promise((resolve, reject) => {
+        const image = new Image()
+        image.src = src
+        image.onload = () => {
+            const data = {
+                width: image.width,
+                height: image.height
+            }
+            resolve(data)
+        }
+        image.onerror = () => {
+            const err = new Error('加载图片失败')
+            reject(err)
+        }
+    })
+}
+
+/**
+ *
+ * @param {file} file 源文件
+ * @param {object} props   文件分辨率的宽和高   ag: props={width:100, height :100}
+ * @desc  判断图片文件的分辨率是否在限定范围之内
+ * @throw  分辨率不在限定范围之内则抛出异常
+ *
+ */
+export const isAppropriateResolution = async(file, props) => {
+    try {
+        const { width, height } = props
+        const base64 = await readFile(file)
+        const image = await loadImage(base64)
+        if (image.width !== width || image.height !== height) {
+            throw new Error('上传图片的分辨率必须为' + width + '*' + height)
+        }
+    } catch (error) {
+        throw error
+    }
+}
+
+/**
+ *
+ * @param {file} file 源文件
+ * @param {array} ratio   限制的文件比例 ag:  ratio= [1,1]
+ * @desc 判断图片文件的比列是否在限定范围
+ * @throw  比例不在限定范围之内则抛出异常
+ */
+export const isAppRatio = async(file, ratio) => {
+    try {
+        const [w, h] = ratio
+        if (h === 0 || w === 0) {
+            const err = '上传图片的比例不能出现0'
+            Message.error(err)
+            throw new Error(err)
+        }
+        const base64 = await readFile(file)
+        const image = await loadImage(base64)
+        if (image.width / image.height !== w / h) {
+            throw new Error('上传图片的宽高比例必须为 ' + w + ' : ' + h)
+        }
+    } catch (error) {
+        throw error
+    }
+}

+ 1 - 1
pc/src/views/Index.vue

@@ -201,7 +201,7 @@
         }
     }
 
-    @media (min-width: 960px) and (max-width: 1024px) {
+    @media (min-width: 320px) and (max-width: 1024px) {
         .shortElaside {
             /*width: 140px !important;*/
             width: 60px !important;

+ 14 - 2
pc/src/views/Member.vue

@@ -299,7 +299,13 @@
                                     <!--<i v-else class="el-icon-plus avatar-uploader-icon"></i>-->
                                 </el-upload>
                                 <img v-if="imageUrl" :src="imageUrl" class="avatar">
-                                <ImgCutter v-on:cutDown="cutDown"></ImgCutter>
+                                <cropper
+                                        :width="300"
+                                        :height="300"
+                                        :fixed-number="[1,1]"
+                                        @subUploadSucceed="getShopImages"
+                                ></cropper>
+                                <!--<ImgCutter v-on:cutDown="cutDown"></ImgCutter>-->
                             </div>
                         </el-form-item>
                         <el-form-item label="出生年份" :required="true">
@@ -414,6 +420,7 @@
         testTable,
         testSelect
     } from "../api/getApiRes";
+    import cropper from '@/components/cropper.vue'
 
     let qs = require('qs');
     export default {
@@ -1336,9 +1343,14 @@
                     return nonTfmtDatetoLength(column, 10);
                 }
             },
+            // 海报上传成功
+            getShopImages(url) {
+                this.imageUrl = url;
+                this.form.head = url;
+            }
         },
         components: {
-            ImgCutter
+            cropper
         }
     }
 </script>

+ 18 - 3
pc/src/views/Test.vue

@@ -1,19 +1,34 @@
 <template>
     <div>
-        <upHead></upHead>
+        <!--<upHead></upHead>-->
+        <cropper
+                :width="300"
+                :height="300"
+                :fixed-number="[1,1]"
+                @subUploadSucceed="getShopImages"
+        ></cropper>
+        {{url}}
     </div>
 </template>
 
 <script>
     import upHead from '@/components/upHead.vue'
+    import cropper from '@/components/cropper.vue'
     export default {
         data() {
             return {
-                editableTabsValue: '1'
+                editableTabsValue: '1',
+                url: ''
+            }
+        },
+        methods: {
+            // 海报上传成功
+            getShopImages(url) {
+                this.url = url
             }
         },
         components: {
-            upHead
+            upHead,cropper
         }
     }
 </script>

+ 14 - 2
pc/src/views/tempUser.vue

@@ -221,7 +221,13 @@
                                     <!--<i v-else class="el-icon-plus avatar-uploader-icon"></i>-->
                                 </el-upload>
                                 <img v-if="imageUrl" :src="imageUrl" class="avatar">
-                                <ImgCutter v-on:cutDown="cutDown"></ImgCutter>
+                                <cropper
+                                        :width="300"
+                                        :height="300"
+                                        :fixed-number="[1,1]"
+                                        @subUploadSucceed="getShopImages"
+                                ></cropper>
+                                <!--<ImgCutter v-on:cutDown="cutDown"></ImgCutter>-->
                             </div>
                         </el-form-item>
                     </el-form>
@@ -252,6 +258,7 @@
         testTable,
         testSelect
     } from "../api/getApiRes";
+    import cropper from '@/components/cropper.vue'
 
     let qs = require('qs');
     export default {
@@ -726,9 +733,14 @@
             filterFmtDate(value, row, column) {
                 return nonTfmtDatetoLength(column, 11);
             },
+            // 海报上传成功
+            getShopImages(url) {
+                this.imageUrl = url;
+                this.form.head = url;
+            }
         },
         components: {
-            ImgCutter
+            cropper
         }
     }
 </script>