gaoshp
2024-05-14 472f9edec5c966feaee13aabe91aef6be7307ea2
update
已修改1个文件
574 ■■■■■ 文件已修改
src/components/scUpload/index.vue 574 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/scUpload/index.vue
@@ -1,13 +1,14 @@
<template>
    <div class="sc-upload" :class="{'sc-upload-round':round}" :style="style">
    <div class="sc-upload" :class="{ 'sc-upload-round': round }" :style="style">
        <div v-if="file && file.status != 'success'" class="sc-upload__uploading">
            <div class="sc-upload__progress">
                <el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16"/>
                <el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16" />
            </div>
            <el-image class="image" :src="file.tempFile" fit="cover"></el-image>
        </div>
        <div v-if="file && file.status=='success'" class="sc-upload__img">
            <el-image class="image" :src="file.url" :preview-src-list="[file.url]" fit="cover" hide-on-click-modal append-to-body :z-index="9999">
        <div v-if="file && file.status == 'success'" class="sc-upload__img">
            <el-image class="image" :src="file.url" :preview-src-list="[file.url]" fit="cover" hide-on-click-modal
                append-to-body :z-index="9999">
                <template #placeholder>
                    <div class="sc-upload__img-slot">
                        Loading...
@@ -18,35 +19,28 @@
                <span class="del" @click="handleRemove()"><el-icon><el-icon-delete /></el-icon></span>
            </div>
        </div>
        <el-upload v-if="!file" class="uploader" ref="uploader"
            :auto-upload="cropper?false:autoUpload"
            :disabled="disabled"
            :show-file-list="showFileList"
            :action="action"
            :name="name"
            :data="data"
            :accept="accept"
            :limit="1"
            :http-request="request"
            :on-change="change"
            :before-upload="before"
            :on-success="success"
            :on-error="error"
            :on-exceed="handleExceed">
        <el-upload v-if="!file" class="uploader" ref="uploader" :auto-upload="cropper ? false : autoUpload"
            :disabled="disabled" :show-file-list="showFileList" :action="action" :name="name" :data="data"
            :accept="accept" :limit="1" :http-request="request" :on-change="change" :before-upload="before"
            :on-success="success" :on-error="error" :on-exceed="handleExceed">
            <slot>
                <div class="el-upload--picture-card">
                    <div class="file-empty">
                        <el-icon><component :is="icon" /></el-icon>
                        <h4 v-if="title">{{title}}</h4>
                        <el-icon>
                            <component :is="icon" />
                        </el-icon>
                        <h4 v-if="title">{{ title }}</h4>
                    </div>
                </div>
            </slot>
        </el-upload>
        <span style="display:none!important"><el-input v-model="value"></el-input></span>
        <el-dialog title="剪裁" draggable v-model="cropperDialogVisible" :width="580" @closed="cropperClosed" destroy-on-close>
            <sc-cropper :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio" ref="cropper"></sc-cropper>
        <el-dialog title="剪裁" draggable v-model="cropperDialogVisible" :width="580" @closed="cropperClosed"
            destroy-on-close>
            <sc-cropper :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio"
                ref="cropper"></sc-cropper>
            <template #footer>
                <el-button @click="cropperDialogVisible=false" >取 消</el-button>
                <el-button @click="cropperDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="cropperSave">确 定</el-button>
            </template>
        </el-dialog>
@@ -54,241 +48,347 @@
</template>
<script>
    import { defineAsyncComponent } from 'vue'
    import { genFileId } from 'element-plus'
    const scCropper = defineAsyncComponent(() => import('@/components/scCropper'))
    import config from "@/config/upload"
import { defineAsyncComponent } from 'vue'
import { genFileId } from 'element-plus'
const scCropper = defineAsyncComponent(() => import('@/components/scCropper'))
import config from "@/config/upload"
    export default {
        props: {
            modelValue: { type: String, default: "" },
            height: {type: Number, default: 148},
            width: {type: Number, default: 148},
            title: { type: String, default: "" },
            icon: { type: String, default: "el-icon-plus" },
            action: { type: String, default: "" },
            apiObj: { type: Object, default: () => {} },
            name: { type: String, default: config.filename },
            data: { type: Object, default: () => {} },
            accept: { type: String, default: "image/gif, image/jpeg, image/png" },
            maxSize: { type: Number, default: config.maxSizeFile },
            limit: { type: Number, default: 1 },
            autoUpload: { type: Boolean, default: true },
            showFileList: { type: Boolean, default: false },
            disabled: { type: Boolean, default: false },
            round: { type: Boolean, default: false },
            onSuccess: { type: Function, default: () => { return true } },
            host: {type: String, default: ''},
            cropper: { type: Boolean, default: false },
            compress: {type: Number, default: 1},
            aspectRatio:  {type: Number, default: NaN}
        },
        components: {
            scCropper
        },
        data() {
            return {
                value: "",
                file: null,
                style: {
                    width: this.width + "px",
                    height: this.height + "px"
                },
                cropperDialogVisible: false,
                cropperFile: null,
                hostNew: ''
            }
        },
        watch:{
            modelValue(val){
                this.value = val
                this.newFile(val)
export default {
    props: {
        modelValue: { type: String, default: "" },
        height: { type: Number, default: 148 },
        width: { type: Number, default: 148 },
        title: { type: String, default: "" },
        icon: { type: String, default: "el-icon-plus" },
        action: { type: String, default: "" },
        apiObj: { type: Object, default: () => { } },
        name: { type: String, default: config.filename },
        data: { type: Object, default: () => { } },
        accept: { type: String, default: "image/gif, image/jpeg, image/png" },
        maxSize: { type: Number, default: config.maxSizeFile },
        limit: { type: Number, default: 1 },
        autoUpload: { type: Boolean, default: true },
        showFileList: { type: Boolean, default: false },
        disabled: { type: Boolean, default: false },
        round: { type: Boolean, default: false },
        onSuccess: { type: Function, default: () => { return true } },
        host: { type: String, default: '' },
        cropper: { type: Boolean, default: false },
        compress: { type: Number, default: 1 },
        aspectRatio: { type: Number, default: NaN }
    },
    components: {
        scCropper
    },
    data() {
        return {
            value: "",
            file: null,
            style: {
                width: this.width + "px",
                height: this.height + "px"
            },
            value(val){
                this.$emit('update:modelValue', val)
            }
            cropperDialogVisible: false,
            cropperFile: null,
            hostNew: ''
        }
    },
    watch: {
        modelValue(val) {
            this.value = val
            this.newFile(val)
        },
        mounted() {
            this.getHost().then(res => {
                this.value = this.modelValue
                this.newFile(this.modelValue)
        value(val) {
            this.$emit('update:modelValue', val)
        }
    },
    mounted() {
        this.getHost().then(res => {
            this.value = this.modelValue
            this.newFile(this.modelValue)
        })
    },
    methods: {
        getHost() {
            if (this.host) {
                this.hostNew = this.host
                return Promise.resolve()
            }
            return this.$API.setting.component.getImgHost.get().then(res => {
                this.hostNew = res.data + '/visual/'
            })
        },
        methods: {
            getHost () {
                if (this.host) return this.hostNew = this.host
                return this.$API.setting.component.getImgHost.get().then(res => {
                    this.hostNew = res.data + '/visual/'
                })
            },
            newFile(url){
                if(url){
                    this.file = {
                        status: "success",
                        url: `${this.hostNew}${url}`
                    }
                }else{
                    this.file = null
        newFile(url) {
            if (url) {
                this.file = {
                    status: "success",
                    url: `${this.hostNew}${url}`
                }
            },
            cropperSave(){
                this.$refs.cropper.getCropFile(file => {
                    file.uid = this.cropperFile.uid
                    this.cropperFile.raw = file
                    this.file = this.cropperFile
                    this.file.tempFile = URL.createObjectURL(this.file.raw)
                    this.$refs.uploader.submit()
                }, this.cropperFile.name, this.cropperFile.type)
                this.cropperDialogVisible = false
            },
            cropperClosed(){
                URL.revokeObjectURL(this.cropperFile.tempCropperFile)
                delete this.cropperFile.tempCropperFile
            },
            handleRemove(){
                this.clearFiles()
            },
            clearFiles(){
                URL.revokeObjectURL(this.file.tempFile)
                this.value = ""
            } else {
                this.file = null
                this.$nextTick(()=>{
                    this.$refs.uploader.clearFiles()
                })
            },
            change(file,files){
                if(files.length > 1){
                    files.splice(0, 1)
                }
                if(this.cropper && file.status=='ready'){
                    const acceptIncludes = ["image/gif", "image/jpeg", "image/png"].includes(file.raw.type)
                    if(!acceptIncludes){
                        this.$notify.warning({
                            title: '上传文件警告',
                            message: '选择的文件非图像类文件'
                        })
                        return false
                    }
                    this.cropperFile = file
                    this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw)
                    this.cropperDialogVisible = true
                    return false
                }
                this.file = file
                if(file.status=='ready'){
                    file.tempFile = URL.createObjectURL(file.raw)
                }
            },
            before(file){
                const acceptIncludes = this.accept.replace(/\s/g,"").split(",").includes(file.type)
                if(!acceptIncludes){
            }
        },
        cropperSave() {
            this.$refs.cropper.getCropFile(file => {
                file.uid = this.cropperFile.uid
                this.cropperFile.raw = file
                this.file = this.cropperFile
                this.file.tempFile = URL.createObjectURL(this.file.raw)
                this.$refs.uploader.submit()
            }, this.cropperFile.name, this.cropperFile.type)
            this.cropperDialogVisible = false
        },
        cropperClosed() {
            URL.revokeObjectURL(this.cropperFile.tempCropperFile)
            delete this.cropperFile.tempCropperFile
        },
        handleRemove() {
            this.clearFiles()
        },
        clearFiles() {
            URL.revokeObjectURL(this.file.tempFile)
            this.value = ""
            this.file = null
            this.$nextTick(() => {
                this.$refs.uploader.clearFiles()
            })
        },
        change(file, files) {
            if (files.length > 1) {
                files.splice(0, 1)
            }
            if (this.cropper && file.status == 'ready') {
                const acceptIncludes = ["image/gif", "image/jpeg", "image/png"].includes(file.raw.type)
                if (!acceptIncludes) {
                    this.$notify.warning({
                        title: '上传文件警告',
                        message: '选择的文件非图像类文件'
                    })
                    this.clearFiles()
                    return false
                }
                const maxSize = file.size / 1024 / 1024 < this.maxSize;
                if (!maxSize) {
                    this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
                    this.clearFiles()
                    return false
                }
            },
            handleExceed(files){
                const file = files[0]
                file.uid = genFileId()
                this.$refs.uploader.handleStart(file)
            },
            success(res, file){
                //释放内存删除blob
                URL.revokeObjectURL(file.tempFile)
                try {
                    delete file.tempFile
                } catch (error) {
                }
                var os = this.onSuccess(res, file)
                if(os!=undefined && os==false){
                    this.$nextTick(() => {
                        this.file = null
                        this.value = ""
                    })
                    return false
                }
                var response = config.parseData(res)
                file.url = response.src
                this.value = file.url
            },
            error(err){
                this.$nextTick(()=>{
                    this.clearFiles()
                })
                this.$notify.error({
                    title: '上传文件未成功',
                    message: err
                })
            },
            request(param){
                var apiObj = config.apiObj;
                if(this.apiObj){
                    apiObj = this.apiObj;
                }
                const data = new FormData();
                data.append(param.filename, param.file);
                for (const key in param.data) {
                    data.append(key, param.data[key]);
                }
                apiObj.post(data, {
                    onUploadProgress: e => {
                        const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
                        param.onProgress({percent: complete})
                    }
                }).then(res => {
                    var response = config.parseData(res);
                    if(response.code == config.successCode){
                        param.onSuccess(res)
                    }else{
                        param.onError(response.msg || "未知错误")
                    }
                }).catch(err => {
                    param.onError(err)
                })
                this.cropperFile = file
                this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw)
                this.cropperDialogVisible = true
                return false
            }
            this.file = file
            if (file.status == 'ready') {
                file.tempFile = URL.createObjectURL(file.raw)
            }
        },
        before(file) {
            const acceptIncludes = this.accept.replace(/\s/g, "").split(",").includes(file.type)
            if (!acceptIncludes) {
                this.$notify.warning({
                    title: '上传文件警告',
                    message: '选择的文件非图像类文件'
                })
                this.clearFiles()
                return false
            }
            const maxSize = file.size / 1024 / 1024 < this.maxSize;
            if (!maxSize) {
                this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
                this.clearFiles()
                return false
            }
        },
        handleExceed(files) {
            const file = files[0]
            file.uid = genFileId()
            this.$refs.uploader.handleStart(file)
        },
        success(res, file) {
            //释放内存删除blob
            URL.revokeObjectURL(file.tempFile)
            try {
                delete file.tempFile
            } catch (error) {
            }
            var os = this.onSuccess(res, file)
            if (os != undefined && os == false) {
                this.$nextTick(() => {
                    this.file = null
                    this.value = ""
                })
                return false
            }
            var response = config.parseData(res)
            file.url = response.src
            this.value = file.url
        },
        error(err) {
            this.$nextTick(() => {
                this.clearFiles()
            })
            this.$notify.error({
                title: '上传文件未成功',
                message: err
            })
        },
        request(param) {
            var apiObj = config.apiObj;
            if (this.apiObj) {
                apiObj = this.apiObj;
            }
            const data = new FormData();
            data.append(param.filename, param.file);
            for (const key in param.data) {
                data.append(key, param.data[key]);
            }
            apiObj.post(data, {
                onUploadProgress: e => {
                    const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
                    param.onProgress({ percent: complete })
                }
            }).then(res => {
                var response = config.parseData(res);
                if (response.code == config.successCode) {
                    param.onSuccess(res)
                } else {
                    param.onError(response.msg || "未知错误")
                }
            }).catch(err => {
                param.onError(err)
            })
        }
    }
}
</script>
<style scoped>
    .el-form-item.is-error .sc-upload .el-upload--picture-card {border-color: var(--el-color-danger);}
    .sc-upload .el-upload--picture-card {border-radius: 0;}
.el-form-item.is-error .sc-upload .el-upload--picture-card {
    border-color: var(--el-color-danger);
}
    .sc-upload .uploader,.sc-upload:deep(.el-upload) {width: 100%;height: 100%;}
.sc-upload .el-upload--picture-card {
    border-radius: 0;
}
    .sc-upload__img {width: 100%;height: 100%;position: relative;}
    .sc-upload__img .image {width: 100%;height: 100%;}
    .sc-upload__img-actions {position: absolute;top:0;right: 0;display: none;}
    .sc-upload__img-actions span {display: flex;justify-content: center;align-items: center;width: 25px;height:25px;cursor: pointer;color: #fff;}
    .sc-upload__img-actions span i {font-size: 12px;}
    .sc-upload__img-actions .del {background: #F56C6C;}
    .sc-upload__img:hover .sc-upload__img-actions {display: block;}
    .sc-upload__img-slot {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%;font-size: 12px;background-color: var(--el-fill-color-lighter);}
.sc-upload .uploader,
.sc-upload:deep(.el-upload) {
    width: 100%;
    height: 100%;
}
    .sc-upload__uploading {width: 100%;height: 100%;position: relative;}
    .sc-upload__progress {position: absolute;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background-color: var(--el-overlay-color-lighter);z-index: 1;padding:10px;}
    .sc-upload__progress .el-progress {width: 100%;}
    .sc-upload__uploading .image {width: 100%;height: 100%;}
.sc-upload__img {
    width: 100%;
    height: 100%;
    position: relative;
}
    .sc-upload .file-empty {width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;flex-direction: column;}
    .sc-upload .file-empty i {font-size: 28px;}
    .sc-upload .file-empty h4 {font-size: 12px;font-weight: normal;color: #8c939d;margin-top: 8px;}
.sc-upload__img .image {
    width: 100%;
    height: 100%;
}
    .sc-upload.sc-upload-round {border-radius: 50%;overflow: hidden;}
    .sc-upload.sc-upload-round .el-upload--picture-card {border-radius: 50%;}
    .sc-upload.sc-upload-round .sc-upload__img-actions {top: auto;left: 0;right: 0;bottom: 0;}
    .sc-upload.sc-upload-round .sc-upload__img-actions span {width: 100%;}
.sc-upload__img-actions {
    position: absolute;
    top: 0;
    right: 0;
    display: none;
}
.sc-upload__img-actions span {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 25px;
    height: 25px;
    cursor: pointer;
    color: #fff;
}
.sc-upload__img-actions span i {
    font-size: 12px;
}
.sc-upload__img-actions .del {
    background: #F56C6C;
}
.sc-upload__img:hover .sc-upload__img-actions {
    display: block;
}
.sc-upload__img-slot {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
    font-size: 12px;
    background-color: var(--el-fill-color-lighter);
}
.sc-upload__uploading {
    width: 100%;
    height: 100%;
    position: relative;
}
.sc-upload__progress {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: var(--el-overlay-color-lighter);
    z-index: 1;
    padding: 10px;
}
.sc-upload__progress .el-progress {
    width: 100%;
}
.sc-upload__uploading .image {
    width: 100%;
    height: 100%;
}
.sc-upload .file-empty {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}
.sc-upload .file-empty i {
    font-size: 28px;
}
.sc-upload .file-empty h4 {
    font-size: 12px;
    font-weight: normal;
    color: #8c939d;
    margin-top: 8px;
}
.sc-upload.sc-upload-round {
    border-radius: 50%;
    overflow: hidden;
}
.sc-upload.sc-upload-round .el-upload--picture-card {
    border-radius: 50%;
}
.sc-upload.sc-upload-round .sc-upload__img-actions {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
}
.sc-upload.sc-upload-round .sc-upload__img-actions span {
    width: 100%;
}
</style>