<template>
    <div id="global-uploader" :class="{ 'global-uploader-single': !global }">
        <!-- 上传 -->
        <HcUploader
            ref="uploaderRef"
            class="uploader-app"
            :options="optionsValue"
            :file-status-text="fileStatusText"
            :auto-start="false"
            @file-added="onFileAdded"
            @file-success="onFileSuccess"
            @file-progress="onFileProgress"
            @file-error="onFileError"
        >
            <HcUploaderUnsupport/>
            <HcUploaderBtn id="global-uploader-btn" ref="uploadBtnRef">选择文件</HcUploaderBtn>
            <HcUploaderList v-show="panelShow">
                <template #default="{ fileList }">
                    <div class="file-panel" :class="{ collapse: collapse }">
                        <div class="file-title">
                            <div class="title">文件列表 {{fileList.length > 0 ? `（${fileList.length}）` : ''}}</div>
                            <div class="operate">
                                <el-button :title="collapse ? '展开' : '折叠'" link @click="collapse = !collapse">
                                    <i :class="collapse ? 'ri-fullscreen-line' : 'ri-subtract-line'"/>
                                </el-button>
                                <el-button title="关闭" link @click="close">
                                    <i class="ri-close-line"/>
                                </el-button>
                            </div>
                        </div>

                        <ul class="file-list">
                            <li v-for="file in fileList" :key="file.id" class="file-item">
                                <HcUploaderFile ref="files" :class="['file_' + file.id, customStatus]" :file="file" :list="true"/>
                            </li>
                            <div v-if="!fileList.length" class="no-file">
                                <i class="ri-file-text-line" style="font-size: 24px"/>
                                暂无待上传文件
                            </div>
                        </ul>
                    </div>
                </template>
            </HcUploaderList>
        </HcUploader>
    </div>
</template>

<script>
import {computed, nextTick, onMounted, ref} from 'vue'
import {getTokenHeader} from '~src/api/request/header';
import HcUploader from './components/uploader.vue'
import HcUploaderBtn from './components/btn.vue'
import HcUploaderUnsupport from './components/unsupport.vue'
import HcUploaderList from './components/list.vue'
import HcUploaderFile from './components/file.vue'
import {getArrValue, getObjValue, getFileSuffix} from "js-fast-way";
import {ElNotification} from "element-plus";
import {generateMD5} from './common/md5'
import Bus from '~src/plugins/bus.js'

const acceptType = 'image/png,image/jpg,image/jpeg,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword'

export default {
    name: 'GlobalUploader',
    props: {
        global: {
            type: Boolean,
            default: true
        }
    },
    components: {
        HcUploader,
        HcUploaderBtn,
        HcUploaderUnsupport,
        HcUploaderList,
        HcUploaderFile
    },
    emits: ['fileAdded', 'fileSuccess', 'fileError', 'filesChange', 'fileProgress'],
    setup(props, {emit}) {
        const optionsValue = {
            target: '/api/blade-resource/largeFile/endpoint/upload-file',
            chunkSize: '2048000',
            fileParameterName: 'file',
            maxChunkRetries: 3,
            headers: getTokenHeader(),
            // 是否开启服务器分片校验
            testChunks: true,
            testMethod: 'POST',
            // 服务器分片校验函数，秒传及断点续传基础
            checkChunkUploadedByResponse: (chunk, message) => {
                let skip = false
                try {
                    let objMessage = getObjValue(JSON.parse(message))
                    if (objMessage['skipUpload']) {
                        skip = true
                    } else {
                        skip = (getArrValue(objMessage['uploaded'])).indexOf(chunk.offset + 1) >= 0
                    }
                } catch (e) {}
                return skip
            },
            query: (file, chunk) => {
                return {...file.params}
            }
        }

        const initOptions = ({target, fileName, maxChunk, accept}) => {
            // 自定义上传url
            if (target) {
                uploader.value.opts.target = target
            }
            // 自定义文件上传参数名
            if (fileName) {
                uploader.value.opts.fileParameterName = fileName
            }
            // 并发上传数量
            if (maxChunk) {
                uploader.value.opts.maxChunkRetries = maxChunk
            }
            // 自定义文件上传类型
            if (accept) {
                nextTick(() => {
                    let input = document.querySelector('#global-uploader-btn input')
                    input.setAttribute('accept', accept ? accept : acceptType)
                })
            }
        }
        const fileStatusText = {
            success: '上传成功',
            error: '上传失败',
            uploading: '上传中',
            paused: '已暂停',
            waiting: '等待上传'
        }
        const customStatus = ref('')
        const panelShow = ref(false)
        const collapse = ref(false)
        const uploaderRef = ref()
        const uploadBtnRef = ref()

        const uploader = computed(() => uploaderRef.value?.uploader)

        let customParams = {}

        async function onFileAdded(file) {
            panelShow.value = true
            trigger('fileAdded')
            // 将额外的参数赋值到每个文件上，以不同文件使用不同params的需求
            file.params = {
                ...customParams,
                objectType: getFileSuffix(file.name),
                fileType: file.fileType,
            }
            // 计算MD5
            const md5 = await computeMD5(file)
            startUpload(file, md5)
        }

        function computeMD5(file) {
            // 文件状态设为"计算MD5"
            statusSet(file.id, 'md5')
            // 暂停文件
            file.pause()
            // 计算MD5时隐藏”开始“按钮
            setResumeStyle(file.id, 'none')
            nextTick(() => {
                document.querySelector(`.custom-status-${file.id}`).innerText = '校验MD5中'
            })
            // 开始计算MD5
            return new Promise((resolve, reject) => {
                generateMD5(file, {
                    onSuccess(md5) {
                        statusRemove(file.id)
                        resolve(md5)
                    },
                    onError() {
                        error(`文件${file.name}读取出错，请检查该文件`)
                        file.cancel()
                        statusRemove(file.id)
                        reject()
                    }
                })
            })
        }

        const setResumeStyle = (id, val = 'none') => {
            nextTick(() => {
                try {
                    document.querySelector(`.file_${id} .uploader-file-resume`).style.display = val
                } catch (e) {}
            })
        }

        // md5计算完毕，开始上传
        const beforeFileNum = ref(0)
        function startUpload(file, md5) {
            const fileList = uploader.value.fileList;
            //判断是否满足条件
            const result = fileList.every(({uniqueIdentifier}) => {
                return uniqueIdentifier !== md5
            })
            if (result) {
                file.uniqueIdentifier = md5
                setResumeStyle(file.id,'')
                beforeFileNum.value ++;
                file.resume()
                trigger('fileProgress', true)
            } else {
                file.cancel()
                error('请不要重复上传相同文件')
            }
        }

        //上传完成
        const finishFileNum = ref(0)
        function onFileSuccess(rootFile, file, response, chunk) {
            let res = JSON.parse(response)
            // 服务端自定义的错误（即http状态码为200，但是是错误的情况），这种错误是Uploader无法拦截的
            if (res.code !== 200) {
                errorFileNum.value ++;
                error(res.msg)
                // 文件状态设为“失败”
                statusSet(file.id, 'failed')
            } else {
                finishFileNum.value ++;
                trigger('fileSuccess', getObjValue(res.data))
            }
            if (beforeFileNum.value === (finishFileNum.value + errorFileNum.value)) {
                trigger('filesChange')
                trigger('fileProgress', false)
            }
        }

        function onFileProgress(rootFile, file, chunk) {
            console.log(`上传中 ${file.name}，chunk：${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
        }

        //上传失败
        const errorFileNum = ref(0)
        function onFileError(rootFile, file, response, chunk) {
            errorFileNum.value ++;
            error(response)
            trigger('fileError')
        }

        function close() {
            finishFileNum.value = 0
            beforeFileNum.value = 0
            errorFileNum.value = 0
            uploader.value.cancel()
            panelShow.value = false
        }

        //新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
        function statusSet(id, status) {
            const statusMap = {
                md5: {text: '校验MD5', bgc: '#fff'},
                merging: {text: '合并中', bgc: '#e2eeff'},
                transcoding: {text: '转码中', bgc: '#e2eeff'},
                failed: {text: '上传失败', bgc: '#e2eeff'}
            }
            customStatus.value = status
            nextTick(() => {
                const statusTag = document.createElement('p')
                statusTag.className = `custom-status-${id} custom-status`
                statusTag.innerText = statusMap[status].text
                statusTag.style.backgroundColor = statusMap[status].bgc

                const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
                statusWrap.appendChild(statusTag)
            })
        }

        function statusRemove(id) {
            customStatus.value = ''
            nextTick(() => {
                const statusTag = document.querySelector(`.custom-status-${id}`)
                statusTag.remove()
            })
        }

        function trigger(key, data) {
            Bus.emit(key, data)
            emit(key, data)
        }

        function error(msg) {
            ElNotification({
                title: '错误',
                message: msg,
                type: 'error',
                duration: 2000
            })
        }

        onMounted(() => {
            finishFileNum.value = 0
            beforeFileNum.value = 0
            errorFileNum.value = 0
            nextTick(() => {
                let input = document.querySelector('#global-uploader-btn input')
                input.setAttribute('accept', acceptType)
            })
            //打开上传器
            Bus.on('openUploader', ({params = {}, options = {}}) => {
                customParams = params
                initOptions(options)
                if (uploadBtnRef.value) {
                    uploadBtnRef.value.$el.click()
                }
            })
            //关闭上传器
            Bus.on('closeUploader', () => {
                close()
            })
        })

        return {
            optionsValue,
            initOptions,
            fileStatusText,
            customStatus,
            panelShow,
            collapse,
            uploaderRef,
            uploadBtnRef,
            onFileAdded,
            onFileSuccess,
            onFileProgress,
            onFileError,
            close
        }
    }
}
</script>

<style lang="scss">
@import "./style/index";
</style>
