test--upload-file

<script lang="ts" setup name="upload-oss">

import type { UploadFile, UploadFiles, UploadProps, UploadRawFile, UploadRequestOptions } from 'element-plus'

import { Delete, Document, Plus, Upload, ZoomIn } from '@element-plus/icons-vue'

import { useGetOssPrivateUrlList, useOssUpload } from '@fl/api/anjutong'

import { useMallOssUpload } from '@fl/api/supplier'

import { ALLOW_CONTENT_TYPE_DICT } from '@fl/constants'

import { useMessage } from '@fl/hooks/web/use-message'

import { cloneDeep, isEmpty, isString } from 'lodash-es'

// 本文件只允许一张张勾选图片, 不准多选

type IProps = {

    accept?: string // 允许上传的文件类型, * 代表所有文件类型

    disabled?: boolean

    fileSize?: number // 限制的大小(MB)

    hidePlusIcon?: boolean

    isMallAdmin?: boolean // 是否为商城运营端, 商城运营端和非运营是两个不同的接口调用

    isPrivate?: boolean // 默认公域

    isVideo?: boolean

    limit?: number // 文件个数

    listType?: 'picture-card' | 'text' // 覆盖默认 list-type, 没有 picture 类型, 加入 video 类型

    tips?: boolean | string // 提示语

}

const props = withDefaults(defineProps<IProps>(), {

    accept: '.jpg,.jpeg,.png,.webp',

    disabled: false,

    fileSize: 5, // MB

    hidePlusIcon: false,

    isPrivate: false,

    isVideo: false,

    limit: 1,

    listType: 'picture-card',

    tips: false,

})

const emits = defineEmits(['success', 'remove'])

const { createErrorModal } = useMessage()

const { mutate: mutateMall } = useMallOssUpload()

const { mutate: mutateOssUpload } = useOssUpload()

const ONE_Megabit = 1024 * 1024

const attrs = useAttrs() as UploadProps

const getBindValue = computed(() => {

    const obj = {

        ...attrs,

        ...props,

    }

    return obj

})

const { accept, fileSize, hidePlusIcon, isMallAdmin, isPrivate, isVideo, limit, listType, tips } = toRefs(getBindValue.value)

const isText = computed(() => listType.value === 'text')

const fileTypeText = computed(() => isText.value ? '文件' : '图片')

// 默认提示, 用户可以覆盖

const finalTips = computed(() => {

    // 如果存在 tips

    if (tips.value) {

        // 进一步判断是否为字符串

        if (isString(tips.value)) {

            return tips.value

        }

        if (accept.value === '*') {

            return `支持任意格式,所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB,最多上传 ${limit.value} 个文件`

        }

        return `支持扩展名: ${accept.value},所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB,最多上传 ${limit.value} 个文件`

    }

    return false

})

const hiddenAdd = computed(() => {

    // 传普通文件时, 始终显示上传按钮

    if (isText.value) {

        return false

    }

    // 如果为视频, 只能一次传一个

    if (isVideo.value) {

        return true

    }

    // 上传数量超限

    if (fileList.value.length >= limit.value!) {

        return true

    }

    return false

})

const hidePlusIconFirstTime = computed(() => {

    if (hidePlusIcon.value && fileList.value.length === 0) {

        return true

    }

    else {

        return false

    }

})

// 外部传入的文件 url

// 如果是多个文件, 用逗号分割;

// 私域文件只有后面半截, 需要一个拼接流程

type ISingleFile = { name: string, url: string }

const model = defineModel<ISingleFile[] | string>({ required: true })

const modelIsString = computed(() => {

    return isString(model.value)

})

const initialModelValue = cloneDeep(model.value)

// 回显的文件列表

const fileList = ref<UploadFiles>([] as UploadFiles)

// 初始传入的图片回显

onMounted(() => {

    // 类型守卫

    const vStr = initialModelValue as string

    const vArr = initialModelValue as ISingleFile[]

    if (modelIsString.value) {

        const urlList = vStr ? vStr.split(',') : []

        fileList.value = urlList.map(

            (fullPath: string) => ({

                name: fullPath,

                url: fullPath,

                validUrl: fullPath,

            }),

        ) as any

    }

    else {

        fileList.value = vArr.map(

            (item: ISingleFile) => ({

                name: item.name,

                url: item.url,

                validUrl: item.url,

            }),

        ) as any

    }

})

watchEffect(() => {

    // 如果传入的值为空, 清掉 fileList, 这在新建表单的取消过程中会十分有用

    if (isEmpty(model.value)) {

        fileList.value = []

    }

})

// 私有化图片只有半截, 回显的图片是定死的, 只在初次生效

const results = useGetOssPrivateUrlList(initialModelValue as string, isPrivate.value)

watchEffect(() => {

    if (results.value.isSuccess) {

        fileList.value = results.value.data.map(

            (fullPath: string) => ({

                name: fullPath,

                url: fullPath,

                validUrl: fullPath,

            }),

        ) as any

    }

})

const dialogImageUrl = ref('')

const dialogVisible = ref(false)

// 判断文件超大

function checkOverSize(paramsSize: number) {

    if (paramsSize / ONE_Megabit > fileSize.value) {

        ElMessage.error(`所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB`)

        return true

    }

    return false

}

const contentTypeArr = computed(() => {

    let tmpArr = [] as string[]

    if (accept.value) {

        const acceptArr = accept.value.split(',').map(str => str.toLowerCase())

        tmpArr = acceptArr.map((x: string) => ALLOW_CONTENT_TYPE_DICT[x])

    }

    return tmpArr

})

// 判断文件类型

function checkFileType(fileType: string) {

    if (!contentTypeArr.value.includes(fileType)) {

        ElMessage.error(`${fileTypeText.value}类型不正确,接收类型为 ${accept.value}`)

        return true

    }

    return false

}

// 文件超出数量限制回调

const handleExceed: UploadProps['onExceed'] = () => {

    ElMessage.error(`最多上传${limit.value}个文件!`)

}

// 上传前的检查

function handleBeforeUpload(rawFile: UploadRawFile) {

    // 文件限制大小

    if (checkOverSize(rawFile.size!))

        return false

    // 文件限制类型

    if (accept.value === '*') {

        return true

    }

    else {

        if (checkFileType(rawFile.type))

            return false

    }

    return true

}

// 上传方法

async function handleHttpRequest(options: UploadRequestOptions) {

    // 传到集采商城

    if (import.meta.env.VITE_IS_MALL === 'true') {

        uploadMall(options.file)

    }

    else {

        // 传到数据组

        uploadTob(options.file)

    }

}

// 上传失败

function handleUploadError(error: Error, uploadFile: UploadRawFile) {

    // 没有上传成功, 把图片给去掉

    createErrorModal({ content: error as unknown as string, title: '上传失败' })

    const uid = uploadFile.uid

    const targetIndex = fileList.value.findIndex(item => item.uid === uid)

    fileList.value.splice(targetIndex, 1)

}

// 上传到集采

function uploadMall(uploadFile: UploadRawFile) {

    const formData = new FormData()

    formData.append('file', uploadFile)

    const params = {

        data: formData,

        isMallAdmin: isMallAdmin.value,

    }

    mutateMall(params, {

        onError: (error) => {

            handleUploadError(error, uploadFile)

        },

        onSuccess: (url: string) => {

            handleUploadSuccess(url, uploadFile)

        },

    })

}

// 上传到数据组

function uploadTob(uploadFile: UploadRawFile) {

    const formData = new FormData()

    formData.append('file', uploadFile)

    const params = {

        data: formData,

        isPrivate: isPrivate.value,

    }

    mutateOssUpload(params, {

        onError: (error) => {

            handleUploadError(error, uploadFile)

        },

        onSuccess: (url: string) => {

            handleUploadSuccess(url, uploadFile)

        },

    })

}

// 上传成功后的回调

function handleUploadSuccess(url: string, uploadFile: UploadRawFile) {

    const uid = uploadFile.uid

    const targetIndex = fileList.value.findIndex(item => item.uid === uid)

    const targetItem = fileList.value.find(item => item.uid === uid)

    fileList.value.splice(targetIndex, 1, {

        ...targetItem,

        validUrl: url,

    } as any)

    const currentUrls = (fileList.value as any).map(item => item.validUrl)

    if (modelIsString.value) {

        model.value = currentUrls.join(',')

    }

    else {

        model.value = (fileList.value as any).map(item => ({

            name: item.name,

            url: item.validUrl,

        }))

    }

    emits('success', { name: uploadFile.name, url })

}

// 删除文件

function handleRemove(file: UploadFile) {

    const targetIndex = fileList.value.findIndex(item => item.uid === file.uid)

    if (targetIndex >= 0) {

        const currentUrls = fileList.value.map(item => item.url)

        currentUrls.splice(targetIndex, 1)

        fileList.value.splice(targetIndex, 1)

        if (modelIsString.value) {

            model.value = currentUrls.join(',')

        }

        else {

            model.value = (fileList.value as any).map(item => ({

                name: item.name,

                url: item.validUrl,

            }))

        }

    }

}

// 预览文件

function handlePreview(file: UploadFile) {

    if (isText.value) {

        // !text 形式下, fileList 没有 url, 使用自定义的 validUrl

        window.open((file as any).validUrl, '_blank')

    }

    else {

        dialogVisible.value = true

        dialogImageUrl.value = file.url!

    }

}

</script>

<template>

    <ElUpload v-bind="getBindValue"

              v-model:file-list="fileList"

              class="w-full"

              :class="{

                  hidden_add: hiddenAdd || disabled,

                  hide_plus_icon: hidePlusIconFirstTime,

                  only_show: disabled,

              }"

              :on-remove="handleRemove"

              :http-request="handleHttpRequest"

              :before-upload="handleBeforeUpload"

              :disabled="disabled"

              :on-exceed="handleExceed"

    >

        <!-- #region 自定义默认内容 -->

        <template v-if="!disabled"

                  #default

        >

            <ElButton v-if="isText"

                      :icon="Upload"

            >

                上传文件

            </ElButton>

            <template v-else>

                <ElIcon>

                    <Plus />

                </ElIcon>

            </template>

        </template>

        <!-- #endregion -->

        <!-- #region 提示说明文字 -->

        <template v-if="!disabled"

                  #tip

        >

            <div v-if="tips"

                 class="el-upload__tip"

            >

                {{ finalTips }}

            </div>

        </template>

        <!-- #endregion -->

        <!-- #region 缩略图模板的内容 -->

        <template #file="{ file }">

            <!-- #region 渲染文件 -->

            <template v-if="isText">

                <div class="el-upload-list__item-info">

                    <a class="el-upload-list__item-name"

                       @click.prevent="handlePreview(file)"

                    >

                        <ElIcon>

                            <Document />

                        </ElIcon>

                        <span class="el-upload-list__item-file-name">

                            {{ file.name }}

                        </span>

                    </a>

                </div>

                <div v-if="!disabled"

                     class="i-material-symbols:delete-outline text-24 text-red cursor-pointer"

                     @click="handleRemove(file)"

                />

            </template>

            <!-- #endregion -->

            <template v-else>

                <video v-if="isVideo"

                       :autoplay="false"

                       name="media"

                       :src="file.url"

                >

                    <source :src="file.url"

                            type="video/mp4"

                    >

                </video>

                <img v-else

                     class="el-upload-list__item-thumbnail"

                     :src="file.url"

                     alt=""

                >

                <div class="el-upload-list__item-actions">

                    <span class="el-upload-list__item-preview"

                          @click="handlePreview(file)"

                    >

                        <ElIcon>

                            <ZoomIn />

                        </ElIcon>

                    </span>

                    <span v-if="!disabled"

                          class="el-upload-list__item-delete"

                          @click="handleRemove(file)"

                    >

                        <ElIcon>

                            <Delete />

                        </ElIcon>

                    </span>

                </div>

            </template>

        </template>

        <!-- #endregion -->

    </ElUpload>

    <ElDialog v-model="dialogVisible">

        <div class="flex h-800 justify-center">

            <video v-if="isVideo"

                   class="h-full"

                   autoplay="true"

                   name="media"

            >

                <source :src="dialogImageUrl"

                        type="video/mp4"

                >

            </video>

            <img v-else

                 class="h-full"

                 :src="dialogImageUrl"

            >

        </div>

    </ElDialog>

</template>

<style lang="less" scoped>

.hidden_add {

    :deep(.el-upload, .el-upload--picture-card) {

        display: none;

        background: blue;

    }

}

.hide_plus_icon {

    :deep(.el-icon) {

        display: none;

    }

}

.only_show {

    :deep(.el-upload-list) {

        margin-top: 0;

    }

}

</style>

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要使用uni-file-picker组件上传视频,你可以按照以下步骤进行操作: 1. 首先,找到引入的uni-file-picker组件的choose-and-upload-file.js文件。 2. 在chooseAndUploadFile方法中添加代码,以支持视频上传功能。你可以使用File API HTML5提供的方法来读取和上传文件信息。 3. 例如,你可以在HTML中添加一个div元素来展示视频预览: ```html <html> <body> <div id="test-video-preview" style=""></div> </body> </html> ``` 4. 接下来,在chooseAndUploadFile方法中,你可以添加逻辑来处理视频文件的选择和上传。具体的实现方式取决于你的项目需求和后端接口。 ``` // 选择文件 var file = document.getElementById("your-file-input").files<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [uniapp上传图片视频文件集合组件](https://blog.csdn.net/weixin_40565812/article/details/125805142)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [js 上传文件预览的简单实例](https://download.csdn.net/download/weixin_38607554/13972231)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值