|
|
@@ -70,6 +70,42 @@
|
|
|
@remove="removeQueueItem"
|
|
|
/>
|
|
|
|
|
|
+ <div class="uploaded-files">
|
|
|
+ <div class="uploaded-files__header">
|
|
|
+ <h2>已上传资料({{ uploadedFiles.length }})</h2>
|
|
|
+ <el-button type="text" :loading="filesLoading" @click="refreshUploadedFiles">
|
|
|
+ 刷新
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-table
|
|
|
+ v-loading="filesLoading"
|
|
|
+ :data="uploadedFiles"
|
|
|
+ size="small"
|
|
|
+ border
|
|
|
+ empty-text="暂无已上传资料"
|
|
|
+ >
|
|
|
+ <el-table-column label="文件名" prop="fileName" min-width="220" show-overflow-tooltip />
|
|
|
+ <el-table-column label="大小" width="110">
|
|
|
+ <template #default="{ row }">{{ formatFileSize(row.fileSize) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="来源" width="90">
|
|
|
+ <template #default="{ row }">{{ sourceLabels[row.source] || row.source || '未知' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="上传时间" prop="createDate" width="170" />
|
|
|
+ <el-table-column label="操作" width="80" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button
|
|
|
+ type="text"
|
|
|
+ size="mini"
|
|
|
+ class="danger-action"
|
|
|
+ :loading="isDeleting(row.id)"
|
|
|
+ @click="deleteUploadedFile(row)"
|
|
|
+ >删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
<el-button
|
|
|
type="primary"
|
|
|
class="primary-action"
|
|
|
@@ -87,11 +123,13 @@
|
|
|
import TeamBackgroundUploadQueue from '@/components/team-background/TeamBackgroundUploadQueue.vue'
|
|
|
import {
|
|
|
completePublicUpload,
|
|
|
+ deletePublicTeamBackgroundFile,
|
|
|
getPublicUploadSession,
|
|
|
+ listPublicTeamBackgroundFiles,
|
|
|
uploadPublicTeamBackgroundFile,
|
|
|
verifyPublicUploadCode
|
|
|
} from '@/api/teamBackgroundFile'
|
|
|
-import { ACCEPT, validateBackgroundFile } from '@/utils/teamBackgroundFile'
|
|
|
+import { ACCEPT, formatFileSize, validateBackgroundFile } from '@/utils/teamBackgroundFile'
|
|
|
|
|
|
const BACKEND_EXPIRY_PATTERN = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/
|
|
|
const ISO_EXPIRY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})$/
|
|
|
@@ -145,12 +183,20 @@ export default {
|
|
|
session: null,
|
|
|
code: '',
|
|
|
queue: [],
|
|
|
+ uploadedFiles: [],
|
|
|
+ filesLoading: false,
|
|
|
+ filesRequestId: 0,
|
|
|
+ deletingIds: [],
|
|
|
remainingSeconds: 0,
|
|
|
successCount: 0,
|
|
|
completing: false,
|
|
|
countdownTimer: null,
|
|
|
asyncGeneration: 0,
|
|
|
- verificationRequestId: 0
|
|
|
+ verificationRequestId: 0,
|
|
|
+ sourceLabels: {
|
|
|
+ WECHAT: '微信端',
|
|
|
+ PC: '电脑端'
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -160,7 +206,8 @@ export default {
|
|
|
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
|
|
},
|
|
|
uploadDisabled() {
|
|
|
- return this.state !== 'ready' || this.completing || this.hasActiveUpload()
|
|
|
+ return this.state !== 'ready' || this.completing ||
|
|
|
+ this.hasActiveUpload() || this.hasActiveDelete()
|
|
|
},
|
|
|
completeDisabled() {
|
|
|
return this.uploadDisabled
|
|
|
@@ -188,6 +235,10 @@ export default {
|
|
|
this.clearCountdown()
|
|
|
this.session = null
|
|
|
this.queue = []
|
|
|
+ this.uploadedFiles = []
|
|
|
+ this.filesLoading = false
|
|
|
+ this.filesRequestId += 1
|
|
|
+ this.deletingIds = []
|
|
|
this.successCount = 0
|
|
|
this.code = ''
|
|
|
this.remainingSeconds = 0
|
|
|
@@ -203,7 +254,7 @@ export default {
|
|
|
try {
|
|
|
const response = await getPublicUploadSession(token)
|
|
|
if (!this.isVerificationCurrent(context)) return
|
|
|
- this.activateSession(this.requireSuccess(response, '上传链接无效或已过期'))
|
|
|
+ await this.activateSession(this.requireSuccess(response, '上传链接无效或已过期'))
|
|
|
} catch (_reason) {
|
|
|
if (this.isVerificationCurrent(context)) this.expireSession()
|
|
|
}
|
|
|
@@ -217,7 +268,7 @@ export default {
|
|
|
try {
|
|
|
const response = await verifyPublicUploadCode(this.code)
|
|
|
if (!this.isVerificationCurrent(context)) return
|
|
|
- this.activateSession(this.requireSuccess(response, '验证码无效或已过期'))
|
|
|
+ await this.activateSession(this.requireSuccess(response, '验证码无效或已过期'))
|
|
|
} catch (reason) {
|
|
|
if (!this.isVerificationCurrent(context)) return
|
|
|
this.state = 'code'
|
|
|
@@ -241,10 +292,15 @@ export default {
|
|
|
this.invalidateAsyncWork()
|
|
|
this.session = session
|
|
|
this.queue = []
|
|
|
+ this.uploadedFiles = []
|
|
|
+ this.filesLoading = false
|
|
|
+ this.filesRequestId += 1
|
|
|
+ this.deletingIds = []
|
|
|
this.successCount = 0
|
|
|
this.completing = false
|
|
|
this.state = 'ready'
|
|
|
this.startCountdown()
|
|
|
+ return this.refreshUploadedFiles()
|
|
|
},
|
|
|
startCountdown() {
|
|
|
this.clearCountdown()
|
|
|
@@ -273,6 +329,9 @@ export default {
|
|
|
this.clearCountdown()
|
|
|
this.session = null
|
|
|
this.completing = false
|
|
|
+ this.filesLoading = false
|
|
|
+ this.filesRequestId += 1
|
|
|
+ this.deletingIds = []
|
|
|
this.state = 'expired'
|
|
|
},
|
|
|
createQueueItems(files) {
|
|
|
@@ -288,10 +347,96 @@ export default {
|
|
|
hasActiveUpload() {
|
|
|
return this.queue.some(item => item.status === 'uploading')
|
|
|
},
|
|
|
+ hasActiveDelete() {
|
|
|
+ return this.deletingIds.length > 0
|
|
|
+ },
|
|
|
+ formatFileSize,
|
|
|
+ isDeleting(fileId) {
|
|
|
+ return this.deletingIds.some(id => String(id) === String(fileId))
|
|
|
+ },
|
|
|
+ async refreshUploadedFiles() {
|
|
|
+ if (this.state !== 'ready' || !this.session) return
|
|
|
+ const context = this.sessionContext()
|
|
|
+ const requestId = ++this.filesRequestId
|
|
|
+ this.filesLoading = true
|
|
|
+ try {
|
|
|
+ const response = await listPublicTeamBackgroundFiles(context.uploadToken)
|
|
|
+ if (!this.isFileListRequestCurrent(context, requestId)) return
|
|
|
+ const data = this.requireSuccess(response, '已上传资料加载失败')
|
|
|
+ this.uploadedFiles = Array.isArray(data) ? data : []
|
|
|
+ } catch (reason) {
|
|
|
+ if (!this.isFileListRequestCurrent(context, requestId)) return
|
|
|
+ if (this.isSessionFailure(reason)) {
|
|
|
+ this.expireSession()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.$message.error(this.errorMessage(reason, '已上传资料加载失败'))
|
|
|
+ } finally {
|
|
|
+ if (this.isFileListRequestCurrent(context, requestId)) this.filesLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ isFileListRequestCurrent(context, requestId) {
|
|
|
+ return this.isSessionContextCurrent(context) && requestId === this.filesRequestId
|
|
|
+ },
|
|
|
+ upsertUploadedFile(file) {
|
|
|
+ if (!file || file.id === undefined || file.id === null) return
|
|
|
+ this.filesRequestId += 1
|
|
|
+ this.filesLoading = false
|
|
|
+ const index = this.uploadedFiles.findIndex(
|
|
|
+ candidate => String(candidate.id) === String(file.id)
|
|
|
+ )
|
|
|
+ if (index >= 0) {
|
|
|
+ this.uploadedFiles.splice(index, 1, file)
|
|
|
+ } else {
|
|
|
+ this.uploadedFiles.unshift(file)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async deleteUploadedFile(file) {
|
|
|
+ if (this.state !== 'ready' || this.completing || !this.session ||
|
|
|
+ !file || file.id === undefined || file.id === null || this.isDeleting(file.id)) return
|
|
|
+ const context = this.sessionContext()
|
|
|
+ try {
|
|
|
+ await this.$confirm(`确认删除文件“${file.fileName || '未命名文件'}”吗?`, '提示', {
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ } catch (_reason) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!this.isSessionContextCurrent(context)) return
|
|
|
+ this.deletingIds.push(file.id)
|
|
|
+ try {
|
|
|
+ const response = await deletePublicTeamBackgroundFile(context.uploadToken, file.id)
|
|
|
+ if (!this.isSessionContextCurrent(context)) return
|
|
|
+ this.requireSuccess(response, '删除失败,请重试')
|
|
|
+ this.filesRequestId += 1
|
|
|
+ this.filesLoading = false
|
|
|
+ this.uploadedFiles = this.uploadedFiles.filter(
|
|
|
+ candidate => String(candidate.id) !== String(file.id)
|
|
|
+ )
|
|
|
+ this.queue = this.queue.filter(
|
|
|
+ item => String(item.serverFileId) !== String(file.id)
|
|
|
+ )
|
|
|
+ this.$message.success('文件已删除')
|
|
|
+ } catch (reason) {
|
|
|
+ if (!this.isSessionContextCurrent(context)) return
|
|
|
+ if (this.isSessionFailure(reason)) {
|
|
|
+ this.expireSession()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.$message.error(this.errorMessage(reason, '删除失败,请重试'))
|
|
|
+ } finally {
|
|
|
+ if (this.isSessionContextCurrent(context)) {
|
|
|
+ this.deletingIds = this.deletingIds.filter(
|
|
|
+ id => String(id) !== String(file.id)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
async handleFileChange(event) {
|
|
|
const selectedFiles = Array.from((event.target && event.target.files) || [])
|
|
|
if (event.target) event.target.value = ''
|
|
|
- if (this.state !== 'ready' || this.completing || !selectedFiles.length) return
|
|
|
+ if (this.state !== 'ready' || this.completing || this.hasActiveDelete() ||
|
|
|
+ !selectedFiles.length) return
|
|
|
if (this.hasActiveUpload()) {
|
|
|
this.$message.error('文件正在上传,请稍后再试')
|
|
|
return
|
|
|
@@ -334,7 +479,11 @@ export default {
|
|
|
try {
|
|
|
const response = await uploadPublicTeamBackgroundFile(context.uploadToken, item.file, onProgress)
|
|
|
if (!this.isSessionContextCurrent(context)) return
|
|
|
- this.requireSuccess(response, '上传失败,请重试')
|
|
|
+ const uploaded = this.requireSuccess(response, '上传失败,请重试')
|
|
|
+ if (uploaded && uploaded.id !== undefined && uploaded.id !== null) {
|
|
|
+ item.serverFileId = uploaded.id
|
|
|
+ this.upsertUploadedFile(uploaded)
|
|
|
+ }
|
|
|
item.status = 'success'
|
|
|
item.progress = 100
|
|
|
} catch (reason) {
|
|
|
@@ -345,7 +494,8 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
retryUpload(item) {
|
|
|
- if (this.state !== 'ready' || this.completing || this.hasActiveUpload()) return Promise.resolve()
|
|
|
+ if (this.state !== 'ready' || this.completing ||
|
|
|
+ this.hasActiveUpload() || this.hasActiveDelete()) return Promise.resolve()
|
|
|
return this.uploadOne(item)
|
|
|
},
|
|
|
removeQueueItem(item) {
|
|
|
@@ -355,7 +505,8 @@ export default {
|
|
|
if (index >= 0) this.queue.splice(index, 1)
|
|
|
},
|
|
|
async completeUpload() {
|
|
|
- if (this.state !== 'ready' || this.completing || !this.session || this.hasActiveUpload()) return
|
|
|
+ if (this.state !== 'ready' || this.completing || !this.session ||
|
|
|
+ this.hasActiveUpload() || this.hasActiveDelete()) return
|
|
|
const context = this.sessionContext()
|
|
|
this.completing = true
|
|
|
try {
|
|
|
@@ -473,6 +624,26 @@ export default {
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
+.uploaded-files {
|
|
|
+ margin-top: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.uploaded-files__header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ margin: 0;
|
|
|
+ color: #303133;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.danger-action {
|
|
|
+ color: #f56c6c;
|
|
|
+}
|
|
|
+
|
|
|
.file-input {
|
|
|
display: none;
|
|
|
}
|