|
@@ -0,0 +1,587 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="upload-area" @click="handleUpload">
|
|
|
|
|
+ <a-icon type="cloud-upload" style="font-size: 60px; color: #3ca9f5" />
|
|
|
|
|
+ <p>点击上传</p>
|
|
|
|
|
+ <p>按住Ctrl可同时多选,支持上传PPT/word/excel/pdf/mp4/zip/rar,单个文件不能超过2G</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 上传组件 -->
|
|
|
|
|
+ <uploader
|
|
|
|
|
+ class="uploader-app"
|
|
|
|
|
+ ref="uploaderRef"
|
|
|
|
|
+ :options="options"
|
|
|
|
|
+ :autoStart="false"
|
|
|
|
|
+ :fileStatusText="fileStatusText"
|
|
|
|
|
+ @files-added="handleFilesAdded"
|
|
|
|
|
+ @file-success="handleFileSuccess"
|
|
|
|
|
+ @file-error="handleFileError"
|
|
|
|
|
+ @dragleave="hideUploadMask"
|
|
|
|
|
+ >
|
|
|
|
|
+ <uploader-unsupport></uploader-unsupport>
|
|
|
|
|
+ <!-- 选择按钮 在这里隐藏 -->
|
|
|
|
|
+ <uploader-btn class="select-file-btn" :attrs="attrs" ref="uploadBtn"> 选择文件 </uploader-btn>
|
|
|
|
|
+ <uploader-btn class="select-file-btn" :attrs="attrs" :directory="true" ref="uploadDirBtn"> 选择目录 </uploader-btn>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 拖拽上传 -->
|
|
|
|
|
+ <uploader-drop class="drop-box" id="dropBox" @paste="handlePaste" v-show="dropBoxShow">
|
|
|
|
|
+ <div class="paste-img-wrapper" v-show="pasteImg.src">
|
|
|
|
|
+ <div class="paste-name">{{ pasteImg.name }}</div>
|
|
|
|
|
+ <img class="paste-img" :src="pasteImg.src" :alt="pasteImg.name" v-if="pasteImg.src" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text" v-show="!pasteImg.src"> 截图粘贴或将文件拖拽至此区域上传 </span>
|
|
|
|
|
+ <UploadOutlined class="upload-icon" v-show="pasteImg.src" @click="handleUploadPasteImg" />
|
|
|
|
|
+ <DeleteOutlined class="delete-icon" v-show="pasteImg.src" @click="handleDeletePasteImg" />
|
|
|
|
|
+ <CloseCircleOutlined class="close-icon" @click="dropBoxShow = false" />
|
|
|
|
|
+ </uploader-drop>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 上传列表 -->
|
|
|
|
|
+ <uploader-list v-show="true">
|
|
|
|
|
+ <template #default="props">
|
|
|
|
|
+ <div class="file-panel">
|
|
|
|
|
+ <div class="file-title">
|
|
|
|
|
+ <span class="title-span">
|
|
|
|
|
+ 上传列表 <span class="count">({{ props.fileList.length }})</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <transition name="collapse">
|
|
|
|
|
+ <ul class="file-list">
|
|
|
|
|
+ <li
|
|
|
|
|
+ v-for="file in props.fileList"
|
|
|
|
|
+ :key="file.id"
|
|
|
|
|
+ class="file-item"
|
|
|
|
|
+ :class="{ 'custom-status-item': file.statusStr !== '' }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <uploader-file ref="fileItem" :file="file" :list="true" />
|
|
|
|
|
+ <span class="custom-status">{{ file.statusStr }}</span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <div class="no-file" v-if="!props.fileList.length"><FileExclamationOutlined /> 暂无待上传文件</div>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </transition>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </uploader-list>
|
|
|
|
|
+ </uploader>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+ import { ref, reactive, computed, nextTick, getCurrentInstance } from 'vue'
|
|
|
|
|
+ import { message } from 'ant-design-vue'
|
|
|
|
|
+ import { useMyResourceStore } from '@/store/myResource'
|
|
|
|
|
+ import SparkMD5 from 'spark-md5'
|
|
|
|
|
+ import tool from '@/utils/tool'
|
|
|
|
|
+ const props = defineProps({
|
|
|
|
|
+ visible: {
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+ default: true
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const emit = defineEmits(['update:visible', 'success'])
|
|
|
|
|
+
|
|
|
|
|
+ const { proxy } = getCurrentInstance()
|
|
|
|
|
+ const store = useMyResourceStore()
|
|
|
|
|
+
|
|
|
|
|
+ // refs
|
|
|
|
|
+ const formRef = ref(null)
|
|
|
|
|
+ const uploaderRef = ref(null)
|
|
|
|
|
+ const uploadBtn = ref(null)
|
|
|
|
|
+ const uploadDirBtn = ref(null)
|
|
|
|
|
+ const fileItem = ref(null)
|
|
|
|
|
+
|
|
|
|
|
+ // 表单数据
|
|
|
|
|
+ const formState = reactive({
|
|
|
|
|
+ title: '',
|
|
|
|
|
+ description: '',
|
|
|
|
|
+ category: undefined,
|
|
|
|
|
+ fileIds: []
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const rules = {
|
|
|
|
|
+ title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
|
|
|
|
+ category: [{ required: true, message: '请选择分类', trigger: 'change' }]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 上传相关数据
|
|
|
|
|
+ const options = ref({
|
|
|
|
|
+ target: `${proxy.$RESOURCE_CONFIG.baseContext}/resourceFile/uploadfile`,
|
|
|
|
|
+ chunkSize: 1024 * 1024,
|
|
|
|
|
+ fileParameterName: 'file',
|
|
|
|
|
+ maxChunkRetries: 3,
|
|
|
|
|
+ testChunks: true,
|
|
|
|
|
+ checkChunkUploadedByResponse: (chunk, message) => {
|
|
|
|
|
+ let objMessage = JSON.parse(message)
|
|
|
|
|
+ if (objMessage.success) {
|
|
|
|
|
+ let data = objMessage.data
|
|
|
|
|
+ if (data.skipUpload) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ return (data.uploaded || []).indexOf(chunk.offset + 1) >= 0
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ token: tool.data.get('TOKEN')
|
|
|
|
|
+ },
|
|
|
|
|
+ query: () => {}
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const fileStatusText = ref({
|
|
|
|
|
+ success: '上传成功',
|
|
|
|
|
+ error: 'error',
|
|
|
|
|
+ uploading: '上传中',
|
|
|
|
|
+ paused: '暂停中',
|
|
|
|
|
+ waiting: '等待中'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const attrs = ref({
|
|
|
|
|
+ accept: '*'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const dropBoxShow = ref(false)
|
|
|
|
|
+ const pasteImg = ref({
|
|
|
|
|
+ src: '',
|
|
|
|
|
+ name: ''
|
|
|
|
|
+ })
|
|
|
|
|
+ const pasteImgObj = ref(null)
|
|
|
|
|
+ const filesLength = ref(0)
|
|
|
|
|
+ const uploadStatus = ref({})
|
|
|
|
|
+ const submitting = ref(false)
|
|
|
|
|
+ const canClose = ref(true)
|
|
|
|
|
+
|
|
|
|
|
+ // 计算属性
|
|
|
|
|
+ const uploaderInstance = computed(() => {
|
|
|
|
|
+ return uploaderRef.value?.uploader
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const remainderStorageValue = computed(() => {
|
|
|
|
|
+ return store.remainderStorageValue
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 方法
|
|
|
|
|
+ const hideUploadMask = (e) => {
|
|
|
|
|
+ e.stopPropagation()
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ dropBoxShow.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleUpload = () => {
|
|
|
|
|
+ if (uploadBtn.value?.$el) {
|
|
|
|
|
+ uploadBtn.value.$el.click()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleUploadDir = () => {
|
|
|
|
|
+ if (uploadDirBtn.value?.$el) {
|
|
|
|
|
+ uploadDirBtn.value.$el.click()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handlePasteUpload = () => {
|
|
|
|
|
+ pasteImg.value.src = ''
|
|
|
|
|
+ pasteImg.value.name = ''
|
|
|
|
|
+ pasteImgObj.value = null
|
|
|
|
|
+ dropBoxShow.value = true
|
|
|
|
|
+ }
|
|
|
|
|
+ const handlePaste = (event) => {
|
|
|
|
|
+ let pasteItems = (event.clipboardData || window.clipboardData).items
|
|
|
|
|
+ if (pasteItems && pasteItems.length) {
|
|
|
|
|
+ let imgObj = pasteItems[0].getAsFile()
|
|
|
|
|
+ pasteImgObj.value =
|
|
|
|
|
+ imgObj !== null
|
|
|
|
|
+ ? new File([imgObj], `qiwenshare_${new Date().valueOf()}.${imgObj.name.split('.')[1]}`, { type: imgObj.type })
|
|
|
|
|
+ : null
|
|
|
|
|
+ } else {
|
|
|
|
|
+ message.error('当前浏览器不支持')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!pasteImgObj.value) {
|
|
|
|
|
+ message.error('粘贴内容非图片')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pasteImg.value.name = pasteImgObj.value.name
|
|
|
|
|
+
|
|
|
|
|
+ let reader = new FileReader()
|
|
|
|
|
+ reader.onload = (event) => {
|
|
|
|
|
+ pasteImg.value.src = event.target.result
|
|
|
|
|
+ }
|
|
|
|
|
+ reader.readAsDataURL(pasteImgObj.value)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleUploadPasteImg = () => {
|
|
|
|
|
+ uploaderInstance.value.addFile(pasteImgObj.value)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleDeletePasteImg = () => {
|
|
|
|
|
+ pasteImg.value.src = ''
|
|
|
|
|
+ pasteImg.value.name = ''
|
|
|
|
|
+ pasteImgObj.value = null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleFilesAdded = (filesSource) => {
|
|
|
|
|
+ const filesTotalSize = filesSource
|
|
|
|
|
+ .map((item) => item.size)
|
|
|
|
|
+ .reduce((pre, next) => {
|
|
|
|
|
+ return pre + next
|
|
|
|
|
+ }, 0)
|
|
|
|
|
+
|
|
|
|
|
+ if (remainderStorageValue.value < filesTotalSize) {
|
|
|
|
|
+ // 批量选择的文件超出剩余存储空间
|
|
|
|
|
+ message.warning(`剩余存储空间不足,请重新选择${filesSource.length > 1 ? '批量' : ''}文件`)
|
|
|
|
|
+ filesSource.ignored = true // 本次选择的文件过滤掉
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 批量或单个选择的文件未超出剩余存储空间,正常上传
|
|
|
|
|
+ filesLength.value += filesSource.length
|
|
|
|
|
+ filesSource.forEach((file) => {
|
|
|
|
|
+ dropBoxShow.value = false
|
|
|
|
|
+ computeMD5(file)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleFileSuccess = (rootFile, file, response) => {
|
|
|
|
|
+ if (response === '') {
|
|
|
|
|
+ uploadStatus.value[file.id] = '上传失败'
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const result = JSON.parse(response)
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ file.statusStr = ''
|
|
|
|
|
+ // 将上传成功的文件ID添加到表单数据中
|
|
|
|
|
+ formState.fileIds.push(result.data.userFileId)
|
|
|
|
|
+ emit('success', formState.fileIds)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ message.error(result.msg)
|
|
|
|
|
+ uploadStatus.value[file.id] = '上传失败'
|
|
|
|
|
+ }
|
|
|
|
|
+ filesLength.value--
|
|
|
|
|
+ // 所有文件上传完成后,允许关闭弹窗
|
|
|
|
|
+ canClose.value = filesLength.value === 0
|
|
|
|
|
+ console.log(formState.fileIds, response, result, 'formState.fileIdsformState.fileIdsformState.fileIds')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleFileError = (rootFile, file, response) => {
|
|
|
|
|
+ message.error(response)
|
|
|
|
|
+ filesLength.value--
|
|
|
|
|
+ canClose.value = filesLength.value === 0
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const computeMD5 = (file) => {
|
|
|
|
|
+ let fileReader = new FileReader()
|
|
|
|
|
+ let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
|
|
|
|
|
+ let currentChunk = 0
|
|
|
|
|
+ const chunkSize = 1024 * 1024
|
|
|
|
|
+ let chunks = Math.ceil(file.size / chunkSize)
|
|
|
|
|
+ let spark = new SparkMD5.ArrayBuffer()
|
|
|
|
|
+
|
|
|
|
|
+ file.statusStr = '计算MD5'
|
|
|
|
|
+ file.pause()
|
|
|
|
|
+
|
|
|
|
|
+ const loadNext = () => {
|
|
|
|
|
+ let start = currentChunk * chunkSize
|
|
|
|
|
+ let end = start + chunkSize >= file.size ? file.size : start + chunkSize
|
|
|
|
|
+ fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fileReader.onload = (e) => {
|
|
|
|
|
+ spark.append(e.target.result)
|
|
|
|
|
+ if (currentChunk < chunks) {
|
|
|
|
|
+ currentChunk++
|
|
|
|
|
+ loadNext()
|
|
|
|
|
+ file.statusStr = `校验MD5 ${((currentChunk / chunks) * 100).toFixed(0)}%`
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let md5 = spark.end()
|
|
|
|
|
+ calculateFileMD5End(md5, file)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fileReader.onerror = () => {
|
|
|
|
|
+ message.error({
|
|
|
|
|
+ content: `文件${file.name}读取出错,请检查该文件`,
|
|
|
|
|
+ duration: 2
|
|
|
|
|
+ })
|
|
|
|
|
+ file.cancel()
|
|
|
|
|
+ filesLength.value--
|
|
|
|
|
+ canClose.value = filesLength.value === 0
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ loadNext()
|
|
|
|
|
+ }
|
|
|
|
|
+ const uploadFileParams = computed(() => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ filePath: '/',
|
|
|
|
|
+ isDir: 0,
|
|
|
|
|
+ funcType: 0
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ const calculateFileMD5End = (md5, file) => {
|
|
|
|
|
+ Object.assign(uploaderInstance.value.opts, {
|
|
|
|
|
+ query: uploadFileParams.value
|
|
|
|
|
+ })
|
|
|
|
|
+ file.uniqueIdentifier = md5
|
|
|
|
|
+ file.resume()
|
|
|
|
|
+ file.statusStr = ''
|
|
|
|
|
+ // 文件开始上传时,禁止关闭弹窗
|
|
|
|
|
+ canClose.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleCancel = () => {
|
|
|
|
|
+ if (!canClose.value) {
|
|
|
|
|
+ message.warning('文件正在上传中,请等待上传完成后再关闭')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 重置表单和上传状态
|
|
|
|
|
+ formRef.value?.resetFields()
|
|
|
|
|
+ formState.fileIds = []
|
|
|
|
|
+ if (uploaderInstance.value) {
|
|
|
|
|
+ uploaderInstance.value.cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+ emit('update:visible', false)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleSubmit = () => {
|
|
|
|
|
+ formRef.value
|
|
|
|
|
+ .validate()
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ if (formState.fileIds.length === 0) {
|
|
|
|
|
+ message.warning('请上传文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (filesLength.value > 0) {
|
|
|
|
|
+ message.warning('文件正在上传中,请等待上传完成后再提交')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟提交数据
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ message.success('提交成功')
|
|
|
|
|
+ submitting.value = false
|
|
|
|
|
+ emit('success', formState)
|
|
|
|
|
+ // 重置表单和上传状态
|
|
|
|
|
+ formRef.value?.resetFields()
|
|
|
|
|
+ formState.fileIds = []
|
|
|
|
|
+ emit('update:visible', false)
|
|
|
|
|
+ }, 1000)
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(() => {
|
|
|
|
|
+ // 表单验证失败
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 暴露方法给父组件
|
|
|
|
|
+ defineExpose({
|
|
|
|
|
+ handleUpload
|
|
|
|
|
+ })
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
|
+ .upload-btn-wrapper {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .select-file-btn {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .drop-box {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 200px;
|
|
|
|
|
+ border: 2px dashed #e9e9e9;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ background-color: #fafafa;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+
|
|
|
|
|
+ .text {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .paste-img-wrapper {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+
|
|
|
|
|
+ .paste-name {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .paste-img {
|
|
|
|
|
+ max-width: 80%;
|
|
|
|
|
+ max-height: 120px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-icon,
|
|
|
|
|
+ .delete-icon {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: 10px;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-icon {
|
|
|
|
|
+ right: 40px;
|
|
|
|
|
+ color: #1890ff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .delete-icon {
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ color: #ff4d4f;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .close-icon {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 10px;
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .file-panel {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin-top: 15px;
|
|
|
|
|
+ border: 1px solid #e9e9e9;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+
|
|
|
|
|
+ .file-title {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ background-color: #f5f5f5;
|
|
|
|
|
+ border-bottom: 1px solid #e9e9e9;
|
|
|
|
|
+
|
|
|
|
|
+ .title-span {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+
|
|
|
|
|
+ .count {
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ font-weight: normal;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .file-list {
|
|
|
|
|
+ list-style: none;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ max-height: 200px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+
|
|
|
|
|
+ .file-item {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
+
|
|
|
|
|
+ &:last-child {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.custom-status-item {
|
|
|
|
|
+ padding-right: 100px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .custom-status {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ right: 12px;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ transform: translateY(-50%);
|
|
|
|
|
+ color: #1890ff;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .no-file {
|
|
|
|
|
+ padding: 20px 0;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .collapse-enter-active,
|
|
|
|
|
+ .collapse-leave-active {
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ max-height: 300px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .collapse-enter-from,
|
|
|
|
|
+ .collapse-leave-to {
|
|
|
|
|
+ max-height: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .upload-area {
|
|
|
|
|
+ border: 2px dashed #3ca9f5;
|
|
|
|
|
+ padding: 40px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-area p {
|
|
|
|
|
+ margin: 10px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .file-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin: 10px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .file-item .ant-progress {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ margin: 0 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 新增表单样式 */
|
|
|
|
|
+ .ant-form-item {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .public-status-buttons {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .status-button {
|
|
|
|
|
+ padding: 5px 10px;
|
|
|
|
|
+ /* margin-right: 10px; */
|
|
|
|
|
+ border: 1px solid #ccc;
|
|
|
|
|
+ /* border-radius: 3px; */
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .status-button.active {
|
|
|
|
|
+ background-color: #40a9ff;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ border-color: #40a9ff;
|
|
|
|
|
+ }
|
|
|
|
|
+ .upload-area {
|
|
|
|
|
+ border: 2px dashed #3ca9f5;
|
|
|
|
|
+ padding: 40px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ transition: border-color 0.3s; /* 平滑过渡效果 */
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-area.drag-over {
|
|
|
|
|
+ border-color: #1890ff;
|
|
|
|
|
+ background-color: rgba(24, 144, 255, 0.05);
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|