| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- <template>
- <div class="upload-file-wrapper">
- <!-- 上传文件组件 -->
- <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="panelShow">
- <template #default="props">
- <div class="file-panel">
- <div class="file-title">
- <span class="title-span">
- 上传列表 <span class="count">({{ props.fileList.length }})</span>
- </span>
- <div class="operate">
- <a-button type="link" :title="collapse ? '展开' : '折叠'" @click="handleCollapse">
- <template #icon>
- <FullscreenOutlined v-if="collapse" />
- <MinusOutlined v-else />
- </template>
- </a-button>
- <a-button type="link" title="关闭" @click="handleClosePanel">
- <template #icon>
- <CloseOutlined />
- </template>
- </a-button>
- </div>
- </div>
- <transition name="collapse">
- <ul class="file-list" v-show="!collapse">
- <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>
- </div>
- </template>
- <script setup>
- import { ref, computed, defineExpose, nextTick, inject, getCurrentInstance, onMounted } from 'vue'
- import { message } from 'ant-design-vue'
- import { useMyResourceStore } from '@/store/myResource'
- import SparkMD5 from 'spark-md5'
- import tool from '@/utils/tool'
- import {
- UploadOutlined,
- DeleteOutlined,
- CloseCircleOutlined,
- FullscreenOutlined,
- MinusOutlined,
- CloseOutlined,
- FileExclamationOutlined
- } from '@ant-design/icons-vue'
- // 使用 inject 接收响应式数据
- const uploadProps = inject('uploadProps', {
- params: {},
- uploadWay: 1,
- serviceEl: null,
- callType: 1,
- callback: () => {}
- })
- const { proxy } = getCurrentInstance()
- const store = useMyResourceStore()
- // refs
- const uploaderRef = ref(null)
- const uploadBtn = ref(null)
- const uploadDirBtn = ref(null)
- const fileItem = ref(null)
- // 响应式数据
- const imageTypes = ref(['image/gif', 'image/jpg', 'image/jpeg', 'image/png', 'image/bmp', 'image/webp'])
- const documentTypes = ref([
- 'application/msword',
- 'text/plain',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- 'application/pdf',
- 'application/vnd.ms-excel',
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- 'application/vnd.ms-powerpoint',
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
- ])
- const opts = ref({
- categoryMap: {
- image: ['gif', 'jpg', 'jpeg', 'png', 'bmp', 'webp'],
- document: ['doc', 'txt', 'docx', 'pdf', 'xls', 'xlsx', 'ppt', 'pptx']
- }
- })
- const options = ref({
- target: `${proxy.$RESOURCE_CONFIG.baseContext}/filetransfer/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 panelShow = ref(false)
- const collapse = ref(false)
- const dropBoxShow = ref(false)
- const pasteImg = ref({
- src: '',
- name: ''
- })
- const pasteImgObj = ref(null)
- const filesLength = ref(0)
- const uploadStatus = ref({})
- // 计算属性
- const uploaderInstance = computed(() => {
- return uploaderRef.value.uploader
- })
- const remainderStorageValue = computed(() => {
- return store.remainderStorageValue
- })
- // 方法
- const hideUploadMask = (e) => {
- e.stopPropagation()
- e.preventDefault()
- dropBoxShow.value = false
- }
- const handlePrepareUpload = async () => {
- // options.value.headers.token = getCookies(getConfig().tokenKeyName)
- await nextTick()
- switch (uploadProps.uploadWay) {
- case 1: {
- if (uploadBtn.value?.$el) {
- uploadBtn.value.$el.click()
- }
- break
- }
- case 2: {
- if (uploadDirBtn.value?.$el) {
- uploadDirBtn.value.$el.click()
- }
- break
- }
- case 3: {
- pasteImg.value.src = ''
- pasteImg.value.name = ''
- pasteImgObj.value = null
- dropBoxShow.value = true
- break
- }
- }
- }
- 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) => {
- console.log('filesSource.filesSource', filesSource)
- // const inconformityFileArr = []
- // const files = filesSource.filter((file) => {
- // const isFile = isImageOrDocument(file)
- // if (isFile) {
- // return file
- // } else {
- // inconformityFileArr.push(file)
- // }
- // })
- // if (inconformityFileArr.length > 0) {
- // filesSource.ignored = true
- // message.warning('文件夹中有不符合要求的文件', {
- // duration: 0,
- // content: '无'
- // // content: () => {
- // // let warringHtml = ''
- // // inconformityFileArr.forEach((item, index) => {
- // // warringHtml += `<p><i style="margin-right:5px">${index + 1}.</i>${item.name}</p>`
- // // })
- // // return (
- // // <div>
- // // <div style="color:#E6A23C;width:290px">{warringHtml}</div>
- // // <p style="color:#f56c6c">只可以上传图片和文档!文件大小不能为0!</p>
- // // </div>
- // // )
- // // }
- // })
- // return false
- // }
- // const filesTotalSize = files.reduce((pre, next) => pre + next.size, 0)
- 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
- panelShow.value = true
- collapse.value = false
- computeMD5(file)
- })
- }
- }
- const handleFileSuccess = (rootFile, file, response) => {
- if (response === '') {
- uploadStatus.value[file.id] = '上传失败'
- uploadProps.callback(false)
- return
- }
- const result = JSON.parse(response)
- if (result.success) {
- file.statusStr = ''
- if (filesLength.value === 1) {
- message.success('上传完毕')
- if (uploadProps.callType === 1) {
- uploadProps.serviceEl.$emit('getTableDataByType')
- } else {
- // uploadProps.serviceEl.getTableDataByType()
- }
- store.showStorage()
- uploadProps.callback(true)
- }
- } else {
- message.error(result.message)
- uploadStatus.value[file.id] = '上传失败'
- }
- filesLength.value--
- }
- const handleFileError = (rootFile, file, response) => {
- message.error(response)
- }
- 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()
- }
- loadNext()
- }
- const calculateFileMD5End = (md5, file) => {
- Object.assign(uploaderInstance.value.opts, {
- query: {
- ...uploadProps.params
- }
- })
- file.uniqueIdentifier = md5
- file.resume()
- file.statusStr = ''
- }
- const handleClosePanel = () => {
- uploaderInstance.value.cancel()
- panelShow.value = false
- uploadProps.callback('cancel')
- }
- const handleCollapse = () => {
- collapse.value = !collapse.value
- }
- const isImageOrDocument = (file) => {
- const isImage = imageTypes.value.includes(file.fileType)
- const isDocument = documentTypes.value.includes(file.fileType)
- const fileSize = file.size
- return (isImage || isDocument) && fileSize !== 0
- }
- // 暴露方法给父组件
- defineExpose({
- handlePrepareUpload
- })
- </script>
- <style lang="less" scoped>
- @import '@/style/myResource/varibles.less';
- .upload-file-wrapper {
- position: fixed;
- z-index: 20;
- right: 16px;
- bottom: 16px;
- .drop-box {
- position: fixed;
- z-index: 19;
- top: 0;
- left: 0;
- border: 5px dashed #8091a5 !important;
- background: #ffffffd9;
- color: #8091a5 !important;
- text-align: center;
- box-sizing: border-box;
- height: 100%;
- line-height: 100%;
- width: 100%;
- .text {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 100%;
- transform: translate(-50%, -50%);
- font-size: 30px;
- }
- .upload-icon {
- position: absolute;
- right: 176px;
- top: 16px;
- cursor: pointer;
- &:hover {
- color: @primary-color;
- }
- }
- .delete-icon {
- position: absolute;
- right: 80px;
- top: 16px;
- cursor: pointer;
- &:hover {
- color: @error-color;
- }
- }
- .close-icon {
- position: absolute;
- right: 16px;
- top: 16px;
- cursor: pointer;
- &:hover {
- color: @success-color;
- }
- }
- .paste-img-wrapper {
- width: 100%;
- height: 100%;
- }
- .paste-img {
- margin-top: 16px;
- max-width: 90%;
- max-height: 80%;
- }
- .paste-name {
- height: 24px;
- line-height: 24px;
- font-size: 18px;
- color: @text-color;
- }
- }
- .uploader-app {
- width: 560px;
- }
- .file-panel {
- background-color: #fff;
- border: 1px solid #e2e2e2;
- border-radius: 7px 7px 0 0;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
- .file-title {
- display: flex;
- height: 40px;
- line-height: 40px;
- padding: 0 15px;
- border-bottom: 1px solid #ddd;
- .title-span {
- padding-left: 0;
- margin-bottom: 0;
- font-size: 16px;
- .count {
- color: @text-color-secondary;
- }
- }
- .operate {
- flex: 1;
- text-align: right;
- :deep(.ant-btn-link) {
- color: @text-color;
- &:hover {
- .anticon-fullscreen,
- .anticon-minus {
- color: @success-color;
- }
- .anticon-close {
- color: @error-color;
- }
- }
- }
- }
- }
- .file-list {
- position: relative;
- height: 240px;
- overflow-x: hidden;
- overflow-y: auto;
- background-color: #fff;
- font-size: 12px;
- list-style: none;
- &::-webkit-scrollbar {
- width: 6px;
- }
- &::-webkit-scrollbar-thumb {
- background: @scrollbar-thumb-color;
- border-radius: 4px;
- }
- &::-webkit-scrollbar-track {
- background: @scrollbar-track-color;
- }
- .file-item {
- position: relative;
- background-color: #fff;
- :deep(.uploader-file) {
- height: 40px;
- line-height: 40px;
- .uploader-file-progress {
- border: 1px solid @success-color;
- border-right: none;
- border-left: none;
- background: #e1f3d8;
- }
- .uploader-file-name {
- width: 44%;
- }
- .uploader-file-size {
- width: 16%;
- }
- .uploader-file-meta {
- display: none;
- }
- .uploader-file-status {
- width: 30%;
- text-indent: 0;
- }
- .uploader-file-actions > span {
- margin-top: 12px;
- }
- }
- :deep(.uploader-file[status='success']) {
- .uploader-file-progress {
- border: none;
- }
- }
- }
- .file-item.custom-status-item {
- :deep(.uploader-file-status) {
- visibility: hidden;
- }
- .custom-status {
- position: absolute;
- top: 0;
- right: 10%;
- width: 24%;
- height: 40px;
- line-height: 40px;
- }
- }
- }
- &.collapse {
- .file-title {
- background-color: #e7ecf2;
- }
- }
- }
- .no-file {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 16px;
- }
- :deep(.uploader-file-icon) {
- display: none;
- }
- :deep(.uploader-file-actions > span) {
- margin-right: 6px;
- }
- }
- .select-file-btn {
- display: none;
- }
- .collapse-enter-active,
- .collapse-leave-active {
- transition: all 0.3s ease-in-out;
- max-height: 1000px;
- overflow: hidden;
- }
- .collapse-enter-from,
- .collapse-leave-to {
- max-height: 0;
- opacity: 0;
- overflow: hidden;
- }
- </style>
|