<template>
    <div class="app-upload">
        <el-upload ref="uploadRef" :headers="headers" :accept="acceptTypes.join(',')" v-model:file-list="fileList"
            :limit="limit" :multiple="limit > 1" :action="uploadUrl" :before-upload="onBeforeUpload"
            :on-success="onSuccess" :on-preview="onPreview" :on-exceed="onExceed" :on-change="onChange"
            :on-error="onError">
            <template #tip>
                <div class="el-upload__tip">
                    <slot name="tip"></slot>
                </div>
            </template>
            <slot>
                <el-button type="primary">{{ t('operation.upload') }}</el-button>
            </slot>
        </el-upload>
        <el-image-viewer :url-list="imageList" :initial-index="imageIndex" @close="onViewerClose" v-if="showViewer"
            teleported />
    </div>
</template>

<script lang="ts" setup>
import { shallowRef, shallowReactive, computed, PropType, onMounted } from 'vue'
import { ElMessage, UploadProps, UploadRawFile, UploadUserFile, UploadFile, UploadFiles, UploadInstance, genFileId } from 'element-plus'
import { i18n, useLoginStore } from '@/stores'
import { localData } from '@/stores/storage'
import service from '@/services'
import cryptojs from 'crypto-js'

const props = defineProps({
    modelValue: {
        type: Array as PropType<UploadUserFile[]>,
        default: () => ([])
    },
    fileTypes: {
        type: Array as PropType<readonly ('image' | 'pdf' | 'word' | 'excel')[]>,
        default: () => ([])
    },
    limit: {
        type: Number,
        default: 1
    },
    maxSize: {
        type: Number,
        default: 0
    },
    typeMessage: {
        type: String,
        default: '请选择正确的文件类型'
    }
})

const { global: { t } } = i18n
const emit = defineEmits(['update:modelValue', 'change'])

const fileList = computed({
    get: () => props.modelValue,
    set: (val) => emit('update:modelValue', val)
})

const uploadUrl = service.getConfig('apiUrl') + '/common/uploadFile'
const uploadRef = shallowRef<UploadInstance>()
const showViewer = shallowRef(false)
const acceptTypes = shallowReactive<string[]>([]) // 接受上传的文件类型
const uploadTypes = shallowReactive<string[]>([]) // 允许上传的文件类型
const imageIndex = shallowRef(0)
const imageTypes = ['image/png', 'image/jpeg']

const loginStore = useLoginStore()
const timestamp = new Date().getTime()

const headers = {
    'Accept-Language': localData.getValue('appLanguage'),
    'Sign-Id': loginStore.signId,
    Sign: cryptojs.SHA256(loginStore.sign + timestamp.toString()).toString(),
    Authorization: loginStore.token,
    Timestamp: timestamp.toString(),
}

// 预览图列表
const imageList = computed(() => {
    return fileList.value.reduce<string[]>((pre, { url, raw }) => {
        // 判断是否图片类型
        if (url && raw && imageTypes.includes(raw.type)) {
            pre.push(url)
        }
        return pre
    }, [])
})

const onSuccess = (response: { code: number }, uploadFile: UploadFile) => {
    const { url, raw } = uploadFile
    if (response.code === 200) {
        if (!url && raw) {
            uploadFile.url = URL.createObjectURL(raw)
        }
    } else {
        fileList.value.pop()
        onError()
    }
}

const onChange = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
    emit('change', uploadFile, uploadFiles)
}

// 当超出限制时的回调
const onExceed = (files: File[]) => {
    if (props.limit === 1) {
        const el = uploadRef.value
        if (el) {
            const rawFile = files[0] as UploadRawFile
            if (!uploadTypes.length || uploadTypes.includes(rawFile.type)) {
                rawFile.uid = genFileId()
                el.clearFiles()
                el.handleStart(rawFile)
                el.submit()
            } else {
                ElMessage.warning(props.typeMessage)
            }
        }
    } else {
        ElMessage.warning(`最多只能上传${props.limit}个文件`)
    }
}

// 上传之前判断文件类型
const onBeforeUpload = (rawFile: UploadRawFile) => {
    if (!uploadTypes.length || uploadTypes.includes(rawFile.type)) {
        if (rawFile.size / 1024 > props.maxSize) {
            ElMessage.warning(`上传文件超过 ${props.maxSize}KB 大小限制`)
            return false
        }
        return true
    }
    ElMessage.warning(props.typeMessage)
    return false
}

// 打开预览图
const onPreview: UploadProps['onPreview'] = ({ url }: UploadFile) => {
    const index = imageList.value.findIndex((val) => val === url)
    if (index > -1) {
        imageIndex.value = index
        showViewer.value = true
    } else {
        window.open(url, '_blank')
    }
}

const onViewerClose = () => {
    showViewer.value = false
}

const onError = () => {
    ElMessage.error('上传失败')
}

onMounted(() => {
    props.fileTypes.forEach((value) => {
        switch (value) {
            case 'image': {
                acceptTypes.push('.jpg', '.jpeg', '.png')
                uploadTypes.push(...imageTypes)
                break
            }
            case 'pdf': {
                acceptTypes.push('.pdf')
                uploadTypes.push('application/pdf')
                break
            }
            case 'word': {
                acceptTypes.push('.doc', '.docx')
                uploadTypes.push('application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
                break
            }
            case 'excel': {
                acceptTypes.push('.xls', '.xlsx')
                uploadTypes.push('application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
                break
            }
        }
    })
})
</script>

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