useUploadImgs.ts 5.46 KB
import { ref, type Ref } from 'vue'
import { uploadImages } from '@/common/utils/upload'
import type { UniFormRef } from '@/uni_modules/uview-plus/types'

// 定义上传配置类型
export interface UploadImgConfig {
    maxCount?: number // 最大上传数量,默认3
    uploadText?: string // 上传按钮文案
    sizeType?: UniApp.UploadFileOption['sizeType'] // 图片压缩类型
    formRef: Ref<UniFormRef | null> // 表单ref,用于校验
    fieldName: string // 表单校验字段名(如problemImgs)
}

// 定义图片项类型
export interface UploadImgItem {
    url: string
    status: 'uploading' | 'success' | 'failed'
    message: string
    [key: string]: any // 兼容其他字段
}

/**
 * 图片上传组合式函数(支持多实例复用)
 * @param config 上传配置
 * @returns 上传相关方法和状态
 */
export function useUploadImgs(config: UploadImgConfig) {
    // 默认配置
    const defaultConfig = {
        maxCount: 3,
        uploadText: '选择图片',
        sizeType: ['compressed'] as UniApp.UploadFileOption['sizeType'],
        ...config
    }

    // 图片列表
    const imgList = ref<UploadImgItem[]>([])

    /**
     * 删除图片
     */
    const deleteImg = (event: { index: number }) => {
        imgList.value.splice(event.index, 1)
        // 删除后重新校验
        defaultConfig.formRef.value?.validateField(defaultConfig.fieldName)
        uni.showToast({ title: '图片删除成功', icon: 'success' })
    }

    /**
     * 上传图片
     */
    const uploadImgs = async (event: { file: UniApp.ChooseImageSuccessCallbackResult | UniApp.ChooseImageSuccessCallbackResult[] }) => {
        const fileList = Array.isArray(event.file) ? event.file : [event.file]
        const targetImgList = imgList.value

        // 过滤超出最大数量的图片
        if (targetImgList.length + fileList.length > defaultConfig.maxCount) {
            uni.showToast({ title: `最多只能上传${defaultConfig.maxCount}张图片`, icon: 'none' })
            return
        }

        const filePaths = fileList.map(item => item.url)
        const tempItems = fileList.map(item => ({
            ...item,
            status: 'uploading' as const,
            message: '上传中'
        }))
        const startIndex = targetImgList.length
        targetImgList.push(...tempItems)

        try {
            const uploadResultUrls = await uploadImages({
                filePaths: filePaths,
                ignoreError: true
            })

            // 更新上传成功的图片
            uploadResultUrls.forEach((url, index) => {
                if (targetImgList[startIndex + index]) {
                    targetImgList.splice(startIndex + index, 1, {
                        ...fileList[index],
                        status: 'success' as const,
                        message: '',
                        url: url
                    })
                }
            })

            // 处理上传失败的图片
            if (uploadResultUrls.length < fileList.length) {
                const failCount = fileList.length - uploadResultUrls.length
                for (let i = uploadResultUrls.length; i < fileList.length; i++) {
                    if (targetImgList[startIndex + i]) {
                        targetImgList.splice(startIndex + i, 1, {
                            ...fileList[i],
                            status: 'failed' as const,
                            message: '上传失败'
                        })
                    }
                }
                uni.showToast({ title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none' })
            } else {
                uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' })
            }

            // 上传完成后校验
            defaultConfig.formRef.value?.validateField(defaultConfig.fieldName)
        } catch (err) {
            console.error(`【${defaultConfig.fieldName}】图片上传失败:`, err)
            // 标记所有上传失败
            for (let i = 0; i < fileList.length; i++) {
                if (targetImgList[startIndex + i]) {
                    targetImgList.splice(startIndex + i, 1, {
                        ...fileList[i],
                        status: 'failed' as const,
                        message: '上传失败'
                    })
                }
            }
            uni.showToast({ title: '图片上传失败,请重试', icon: 'none' })
            // 上传失败后校验
            defaultConfig.formRef.value?.validateField(defaultConfig.fieldName)
        }
    }

    /**
     * 获取成功上传的图片URL列表
     */
    const getSuccessImgUrls = () => {
        return imgList.value.filter(item => item.status === 'success').map(item => item.url)
    }

    /**
     * 图片校验规则(供表单使用)
     */
    const imgValidateRule = {
        required: true,
        message: `请上传${defaultConfig.uploadText.replace('选择', '')}`,
        trigger: 'change',
        validator: (rule: any, value: any, callback: (error?: Error) => void) => {
            const hasSuccessImg = imgList.value.some(item => item.status === 'success')
            hasSuccessImg ? callback() : callback(new Error(`请上传至少1张${defaultConfig.uploadText.replace('选择', '')}`))
        }
    }

    return {
        imgList,
        uploadImgs,
        deleteImg,
        getSuccessImgUrls,
        imgValidateRule,
        uploadConfig: defaultConfig
    }
}