背景
文件的上傳是系統(tǒng)的必備功能,Element提供了上傳組件upload,也基本能滿足常見常用的文件上傳功能,特別是應(yīng)對小型文件(10M以下)的處理。但如果是遇到要求更多更高的場景,上傳幾百兆甚至上G的視頻文件,要求分塊上傳,能斷點續(xù)傳,顯示進(jìn)度,能暫停,能重試……這時候就顯得乏力了。如果基于upload實現(xiàn),需要附加大量的二次開發(fā),這未必是一種最佳實現(xiàn)方案。
這時候,就需要找一找看一看,市面上是否有現(xiàn)成的“輪子”可用了。
上一篇介紹了開發(fā)平臺中關(guān)于文件處理的需求、方案和整體設(shè)計,今天首先來說下前端那些事兒。
技術(shù)選型
vue-simple-uploader,作者對vue3做了適配。
官網(wǎng) https://github.com/simple-uploader/vue-uploader/blob/vue3/README_zh-CN.md
特性
- 支持文件、多文件、文件夾上傳
- 支持拖拽文件、文件夾上傳
- 統(tǒng)一對待文件和文件夾,方便操作管理
- 可暫停、繼續(xù)上傳
- 錯誤處理
- 支持“快傳”,通過文件判斷服務(wù)端是否已存在從而實現(xiàn)“快傳”
- 上傳隊列管理,支持最大并發(fā)上傳
- 分塊上傳
- 支持進(jìn)度、預(yù)估剩余時間、出錯自動重試、重傳等操作
功能設(shè)計
組件提供了通用能力,整合到平臺中,需要根據(jù)需求做定制和集成。
在本平臺中,我使用該上傳組件,主要解決的是與業(yè)務(wù)實體關(guān)聯(lián)的附件,不同場景下文件有大有小。
需要支持文件、多文件上傳,但不需要直接上傳文件夾(上傳文件夾通常做文檔庫、網(wǎng)盤場景中需要)。
暫停、重試、分塊、預(yù)估時間、進(jìn)度顯示、重傳這些是需要的,但快傳、秒傳功能不需要(快傳、秒傳往往只在互聯(lián)網(wǎng)應(yīng)用的網(wǎng)盤應(yīng)用場景有需求,企業(yè)應(yīng)用里都是些獨立的,不重復(fù)的文件)。
安裝及注冊
安裝,執(zhí)行如下命令
pnpm install vue-simple-uploader@next --save
初始化,修改main.js,全局注冊
import uploader from 'vue-simple-uploader'
import 'vue-simple-uploader/dist/style.css'
// 創(chuàng)建實例
const setupAll = async () => {
const app = createApp(App)
await setupI18n(app)
setupStore(app)
setupGlobCom(app)
setupRouter(app)
setupPermission(app)
……略
// 文件上傳
app.use(uploader)
app.mount('#app')
}
封裝組件
上傳組件
<template>
<uploader
ref="uploader"
:options="finalOptions"
:file-status-text="statusText"
:show-success-files="showSuccessFiles"
:single-flag="singleFlag"
:auto-start="autoStart"
:show-list-flag="showListFlag"
@file-complete="fileComplete"
@file-added="fileAdded"
/>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
components: {},
props: {
options: {
type: Object,
required: false,
default() {
return {}
}
},
singleFlag: {
type: Boolean,
default: false
},
autoStart: {
type: Boolean,
default: true
},
// 是否顯示文件列表
showListFlag: {
type: Boolean,
default: true
},
entityType: {
type: String,
required: true
},
entityId: {
type: String,
default: '',
required: true
},
moduleCode: {
type: String,
required: true
},
showSuccessFiles: {
type: Boolean,
default: false,
required: false
},
serverUrl: {
type: String,
default: '',
required: true
}
},
data() {
const token = getToken()
return {
defaultOptions: {
target: import.meta.env.VITE_BASE_URL + this.serverUrl,
testChunks: false,
maxChunkRetries: 3,
chunkSize: 10485760,
query: {
entityType: this.entityType,
entityId: this.entityId,
moduleCode: this.moduleCode
},
headers: { 'X-Token': token },
generateUniqueIdentifier: () => {
// 業(yè)務(wù)主鍵+時間戳最大限度降低并發(fā)沖突發(fā)生的概率
return this.entityId + new Date().getTime()
},
parseTimeRemaining(timeRemaining, parsedTimeRemaining) {
return parsedTimeRemaining
.replace(/\syears?/, '年')
.replace(/\days?/, '天')
.replace(/\shours?/, '時')
.replace(/\sminutes?/, '分')
.replace(/\sseconds?/, '秒')
}
},
statusText: {
success: '100%',
error: '失敗',
uploading: '上傳中',
paused: '暫停中',
waiting: '等待中'
}
}
},
computed: {
finalOptions: function () {
const opts = Object.assign(this.defaultOptions, this.options)
return opts
}
},
watch: {
entityId: function () {
this.$refs.uploader.options.query.entityId = this.entityId
}
},
methods: {
fileComplete(file) {
this.$emit('fileComplete', file)
},
fileAdded(file) {
this.$emit('fileAdded', file)
}
}
}
</script>
有幾個需要注意的點,下面來說說。
首先,為了簡化使用方設(shè)置,我先通過defaultOptions,將組件大部分公用配置設(shè)置好,作為缺省配置。同時將可能需要設(shè)置的屬性作為props提供出來,供使用方按需調(diào)整。
其次,受組件自身限制,沒法走前端的api調(diào)用框架axios,而是在組件配置options的target屬性指定了文件上傳的后端地址。
再次,平臺使用JWT做身份認(rèn)證,繞過api調(diào)用框架axios的結(jié)果就是沒有自動附加token,同樣需要在組件配置中附加,否則后端會視為未授權(quán)。
const token = getToken()
return {
defaultOptions: {
……
headers: { 'X-Token': token }
……
最后,我加入了平臺自己的控制,即將模塊編碼moduleCode,實體類型entityType和實體標(biāo)識entityId也傳入了進(jìn)來,并通過組件的query屬性傳給了后端,這幾個參數(shù),用于后端來生成結(jié)構(gòu)化的保存路徑。
這么做的主要目的,除了分門別類存儲附件外,還有個顯著優(yōu)勢可以按需進(jìn)行附件歸檔。比如系統(tǒng)運行3年了,磁盤占用越來越大,業(yè)務(wù)上通常只需要查看最近一年的單據(jù)和附件,那么就可以把1年前的附件,從服務(wù)器上移動到備份服務(wù)器上。
默認(rèn)UI丑到爆,需要自己調(diào)整
調(diào)整后效果如下:
管理組件
管理組件通常是配合上傳組件一起使用的,對上傳結(jié)果進(jìn)行預(yù)覽、驗證和移除,如下圖所示。
vue-simple-uploader組件雖然有文件列表,但這個列表實際是有功能缺失的,無法下載,更無法移除,因此自己另行封裝了一個。
<template>
<el-table :data="entityData" highlight-current-row border>
<el-table-column type="index" label="序號" sortable width="65" />
<el-table-column prop="name" label="名稱" show-overflow-tooltip />
<el-table-column prop="size" label="大小" width="80" />
<el-table-column prop="createTime" label="時間" width="100" />
<el-table-column fixed="right" label="操作" width="200">
<template #default="scope">
<el-button
type="primary"
icon="Download"
class="header-search_button"
size="small"
@click="download(scope.row)"
>下載</el-button
>
<el-button
type="primary"
icon="Delete"
class="header-search_button"
size="small"
@click="remove(scope.row)"
>刪除</el-button
>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
components: {},
props: {
entityId: {
type: String,
default: '',
required: true
},
showDelete: {
type: Boolean,
default: true,
required: false
}
},
data() {
return {
entityData: []
}
},
watch: {
entityId: function () {
this.list()
}
},
mounted: function () {
this.list()
},
methods: {
list() {
if (this.entityId) {
// 只有當(dāng)entityId不為空時才發(fā)起查詢
this.$api.support.attachment.list({ entityId: this.entityId }).then((res) => {
this.entityData = res.data
})
}
},
remove(row) {
this.$confirm('是否刪除該附件?', '確認(rèn)', {
type: 'warning'
})
.then(() => {
this.$api.support.attachment.remove(row.id).finally(() => {
this.list()
})
})
.catch(() => {
this.$message.info('已取消')
})
},
download(row) {
this.$api.support.attachment.download(row.id, row.name)
}
}
}
</script>
瀏覽組件
瀏覽組件通常用于查看視圖,即查看當(dāng)前單據(jù)上傳了哪些附件,并可以下載,但不能刪除,如下圖所示:
<template>
<el-row>
<ul style="margin: 0px; padding: 0px">
<li
v-for="item in entityData"
:key="item.id"
style="
line-height: 30px;
width: 100%;
overflow: hidden;
word-break: keep-all;
white-space: nowrap;
text-overflow: ellipsis;
"
>
<a :title="item.name" @click="download(item)" class="cursor-pointer">
<span> {{ item.name }}</span>
<span>({{ item.size }} )</span>
</a>
</li>
</ul>
</el-row>
</template>
<script>
export default {
props: {
entityId: {
type: String,
default: '',
required: true
}
},
data() {
return {
entityData: []
}
},
watch: {
entityId: function () {
this.list()
}
},
mounted: function () {
this.list()
},
methods: {
list() {
if (this.entityId) {
// 只有當(dāng)entityId不為空時才發(fā)起查詢
this.$api.support.attachment.list({ entityId: this.entityId }).then((res) => {
this.entityData = res.data
})
}
},
download(item) {
this.$api.support.attachment.download(item.id)
}
}
}
</script>
遺留問題(已解決)
同時使用封裝的上傳組件和管理組件,會出現(xiàn)下面這種情況,也會影響用戶體驗。
官方控件自帶的上傳文件列表,上傳成功的文件沒有屬性或方法從列表中移除。我嘗試過使用fileSuccess事件自己實現(xiàn)邏輯,如下圖所示:
fileSuccess(rootFile, file) {
// 文件上傳成功后從列表中移除
// TODO:以下代碼未調(diào)試成功
// let index = this.$refs.uploader.fileList.findIndex(
// (successFile) => successFile.id === file.id
// )
// if (index !== -1) {
// console.log(this.$refs.uploader.fileList)
// this.$refs.uploader.fileList.splice(index, 1)
// console.log(this.$refs.uploader.fileList)
// }
}
}
實際調(diào)試發(fā)現(xiàn)fileList并不能按預(yù)期正常變更集合元素,原因不明。后續(xù)有時間了再處理,要么將上面代碼邏輯調(diào)整好,要么直接基于源碼去修改。
2023-11-26 填坑
查閱vue-simple-uploader使用的組件simple-uploader.js文檔,并經(jīng)過摸索嘗試,使用removeFile來移除已上傳的文件,測試通過
fileSuccess(rootFile) {
this.$refs.uploader.uploader.removeFile(rootFile)
}
注意需要調(diào)用到內(nèi)部uploader組件。
同時推測之前操作fileList不成功,是因為fileList組件從根本上是讀取uploader里的文件數(shù)據(jù)。文章來源:http://www.zghlxwxcb.cn/news/detail-473688.html
開發(fā)平臺資料
平臺名稱:一二三開發(fā)平臺
簡介: 企業(yè)級通用開發(fā)平臺
設(shè)計資料:csdn專欄
開源地址:Gitee
開源協(xié)議:MIT
歡迎收藏、點贊、評論,你的支持是我前行的動力。文章來源地址http://www.zghlxwxcb.cn/news/detail-473688.html
到了這里,關(guān)于應(yīng)用開發(fā)平臺前端集成vue-simple-uploader實現(xiàn)文件分塊上傳的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!