123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- <template>
- <el-form ref="formRef" :model="modelData" label-width="0" class="mt-20px">
- <el-form-item class="mb-20px">
- <div class="w-full">
- <div
- class="w-full border-2 border-dashed border-[#dcdfe6] rounded-md p-20px text-center hover:border-[#409eff]"
- >
- <el-upload
- ref="uploadRef"
- class="upload-demo"
- drag
- :action="uploadUrl"
- :auto-upload="true"
- :on-success="handleUploadSuccess"
- :on-error="handleUploadError"
- :on-change="handleFileChange"
- :on-remove="handleFileRemove"
- :before-upload="beforeUpload"
- :http-request="httpRequest"
- :file-list="fileList"
- :multiple="true"
- :show-file-list="false"
- :accept="acceptedFileTypes"
- >
- <div class="flex flex-col items-center justify-center py-20px">
- <el-icon class="text-[48px] text-[#c0c4cc] mb-10px"><upload-filled /></el-icon>
- <div class="el-upload__text text-[16px] text-[#606266]"
- >拖拽文件至此,或者
- <em class="text-[#409eff] not-italic cursor-pointer">选择文件</em></div
- >
- <div class="el-upload__tip mt-10px text-[#909399] text-[12px]">
- 已支持 {{ supportedFileTypes.join('、') }},每个文件不超过 {{ maxFileSize }} MB。
- </div>
- </div>
- </el-upload>
- </div>
- <div
- v-if="modelData.list && modelData.list.length > 0"
- class="mt-15px grid grid-cols-1 gap-2"
- >
- <div
- v-for="(file, index) in modelData.list"
- :key="index"
- class="flex justify-between items-center py-4px px-12px border-l-4 border-l-[#409eff] rounded-sm shadow-sm hover:bg-[#ecf5ff] transition-all duration-300"
- >
- <div class="flex items-center">
- <el-icon class="mr-8px text-[#409eff]"><document /></el-icon>
- <span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span>
- </div>
- <el-button type="danger" link @click="removeFile(index)" class="ml-2">
- <el-icon><delete /></el-icon>
- </el-button>
- </div>
- </div>
- </div>
- </el-form-item>
- <!-- 添加下一步按钮 -->
- <el-form-item>
- <div class="flex justify-end w-full">
- <el-button type="primary" @click="handleNextStep" :disabled="!isAllUploaded">
- 下一步
- </el-button>
- </div>
- </el-form-item>
- </el-form>
- </template>
- <script lang="ts" setup>
- import { PropType, ref, computed, inject, getCurrentInstance, onMounted } from 'vue'
- import { Document, Delete } from '@element-plus/icons-vue' // TODO @芋艿:晚点改
- import { useMessage } from '@/hooks/web/useMessage'
- import { useUpload } from '@/components/UploadFile/src/useUpload'
- import { generateAcceptedFileTypes } from '@/utils'
- const props = defineProps({
- modelValue: {
- type: Object as PropType<any>,
- required: true
- }
- })
- const emit = defineEmits(['update:modelValue'])
- const formRef = ref() // 表单引用
- const uploadRef = ref() // 上传组件引用
- const parent = inject('parent', null) // 获取父组件实例
- const { uploadUrl, httpRequest } = useUpload() // 使用上传组件的钩子
- const message = useMessage() // 消息弹窗
- const fileList = ref([]) // 文件列表
- const uploadingCount = ref(0) // 上传中的文件数量
- // 支持的文件类型和大小限制
- const supportedFileTypes = [
- 'TXT',
- 'MARKDOWN',
- 'MDX',
- 'PDF',
- 'HTML',
- 'XLSX',
- 'XLS',
- 'DOC',
- 'DOCX',
- 'CSV',
- 'EML',
- 'MSG',
- 'PPTX',
- 'XML',
- 'EPUB',
- 'PPT',
- 'MD',
- 'HTM'
- ]
- const allowedExtensions = supportedFileTypes.map((ext) => ext.toLowerCase()) // 小写的扩展名列表
- const maxFileSize = 15 // 最大文件大小(MB)
- // 构建 accept 属性值,用于限制文件选择对话框中可见的文件类型
- const acceptedFileTypes = computed(() => generateAcceptedFileTypes(supportedFileTypes))
- /** 表单数据 */
- const modelData = computed({
- get: () => {
- return props.modelValue
- },
- set: (val) => emit('update:modelValue', val)
- })
- /** 确保 list 属性存在 */
- const ensureListExists = () => {
- if (!props.modelValue.list) {
- emit('update:modelValue', {
- ...props.modelValue,
- list: []
- })
- }
- }
- /** 是否所有文件都已上传完成 */
- const isAllUploaded = computed(() => {
- return modelData.value.list && modelData.value.list.length > 0 && uploadingCount.value === 0
- })
- /**
- * 上传前检查文件类型和大小
- *
- * @param file 待上传的文件
- * @returns 是否允许上传
- */
- const beforeUpload = (file) => {
- // 1.1 检查文件扩展名
- const fileName = file.name.toLowerCase()
- const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1)
- if (!allowedExtensions.includes(fileExtension)) {
- message.error('不支持的文件类型!')
- return false
- }
- // 1.2 检查文件大小
- if (!(file.size / 1024 / 1024 < maxFileSize)) {
- message.error(`文件大小不能超过 ${maxFileSize} MB!`)
- return false
- }
- // 2. 增加上传中的文件计数
- uploadingCount.value++
- return true
- }
- /**
- * 文件上传成功处理
- *
- * @param response 上传响应
- * @param file 上传的文件
- */
- const handleUploadSuccess = (response, file) => {
- if (response && response.data) {
- // 添加到文件列表
- ensureListExists()
- emit('update:modelValue', {
- ...props.modelValue,
- list: [
- ...props.modelValue.list,
- {
- name: file.name,
- url: response.data
- }
- ]
- })
- } else {
- message.error(`文件 ${file.name} 上传失败`)
- }
- // 减少上传中的文件计数
- uploadingCount.value = Math.max(0, uploadingCount.value - 1)
- }
- /**
- * 文件上传失败处理
- *
- * @param error 错误信息
- * @param file 上传的文件
- */
- const handleUploadError = (error, file) => {
- message.error(`文件 ${file.name} 上传失败: ${error}`)
- // 减少上传中的文件计数
- uploadingCount.value = Math.max(0, uploadingCount.value - 1)
- }
- /**
- * 文件变更处理
- *
- * @param file 变更的文件
- */
- const handleFileChange = (file) => {
- if (file.status === 'success' || file.status === 'fail') {
- uploadingCount.value = Math.max(0, uploadingCount.value - 1)
- }
- }
- /**
- * 文件移除处理
- *
- * @param file 被移除的文件
- */
- const handleFileRemove = (file) => {
- if (file.status === 'uploading') {
- uploadingCount.value = Math.max(0, uploadingCount.value - 1)
- }
- }
- /**
- * 从列表中移除文件
- *
- * @param index 要移除的文件索引
- */
- const removeFile = (index: number) => {
- // 从列表中移除文件
- const newList = [...props.modelValue.list]
- newList.splice(index, 1)
- // 更新表单数据
- emit('update:modelValue', {
- ...props.modelValue,
- list: newList
- })
- }
- /** 下一步按钮处理 */
- const handleNextStep = () => {
- // 1.1 检查是否有文件上传
- if (!modelData.value.list || modelData.value.list.length === 0) {
- message.warning('请上传至少一个文件')
- return
- }
- // 1.2 检查是否有文件正在上传
- if (uploadingCount.value > 0) {
- message.warning('请等待所有文件上传完成')
- return
- }
- // 2. 获取父组件的goToNextStep方法
- const parentEl = parent || getCurrentInstance()?.parent
- if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
- parentEl.exposed.goToNextStep()
- }
- }
- /** 初始化 */
- onMounted(() => {
- ensureListExists()
- })
- </script>
- <style lang="scss" scoped></style>
|