feat: enhance sidebar quota display with progress bar and improve upload modal UX
- Replace percentage-based quota with point-based display in sidebar - Add visual progress bar for remaining quota with gradient styling - Implement upload progress tracking in material upload modal - Add loading indicators and progress information during file uploads - Prevent modal interaction while uploading by disabling close controls - Show current upload status including file index and completion percentage
This commit is contained in:
@@ -66,7 +66,12 @@ const remainingPercent = computed(() => {
|
|||||||
>
|
>
|
||||||
<div class="user-card">
|
<div class="user-card">
|
||||||
<div class="user-card__mobile">{{ maskedMobile }}</div>
|
<div class="user-card__mobile">{{ maskedMobile }}</div>
|
||||||
<div class="user-card__quota">剩余额度 {{ remainingPercent }}%</div>
|
<div class="user-card__quota">
|
||||||
|
<span>剩余额度 {{ userStore.remainingPoints }} 点</span>
|
||||||
|
<div class="quota-progress">
|
||||||
|
<div class="quota-progress__bar" :style="{ width: remainingPercent + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -187,4 +192,33 @@ const remainingPercent = computed(() => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quota-progress {
|
||||||
|
margin-top: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: var(--color-gray-100);
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quota-progress__bar {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #3b82f6, #60a5fa);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 1px 3px rgba(59, 130, 246, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quota-progress__bar::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 50%;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.3), transparent);
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
title="上传素材"
|
title="上传素材"
|
||||||
:width="600"
|
:width="600"
|
||||||
:footer="false"
|
:footer="false"
|
||||||
|
:closable="!uploading"
|
||||||
|
:maskClosable="!uploading"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
@update:visible="handleVisibleChange"
|
@update:visible="handleVisibleChange"
|
||||||
>
|
>
|
||||||
<div class="upload-modal-content">
|
<div class="upload-modal-content">
|
||||||
<!-- 文件上传区域 -->
|
<!-- 文件上传区域 -->
|
||||||
<div class="upload-area">
|
<div class="upload-area" v-if="!uploading">
|
||||||
<a-upload-dragger
|
<a-upload-dragger
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
name="file"
|
name="file"
|
||||||
@@ -33,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 已选文件列表 -->
|
<!-- 已选文件列表 -->
|
||||||
<div v-if="fileList.length > 0" class="upload-file-list">
|
<div v-if="fileList.length > 0 && !uploading" class="upload-file-list">
|
||||||
<div class="upload-file-list-title">
|
<div class="upload-file-list-title">
|
||||||
已选择 {{ fileList.length }} 个文件,总大小:{{ getTotalSize() }}
|
已选择 {{ fileList.length }} 个文件,总大小:{{ getTotalSize() }}
|
||||||
<span v-if="getTotalSizeBytes() > MAX_TOTAL_SIZE" style="color: #ff4d4f; margin-left: 8px;">
|
<span v-if="getTotalSizeBytes() > MAX_TOTAL_SIZE" style="color: #ff4d4f; margin-left: 8px;">
|
||||||
@@ -61,11 +63,69 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 上传进度区域 -->
|
||||||
|
<div v-if="uploading" class="upload-progress-area">
|
||||||
|
<div class="upload-progress-header">
|
||||||
|
<LoadingOutlined spin class="upload-spinner" />
|
||||||
|
<span>正在上传 {{ currentUploadIndex }} / {{ totalUploadCount }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 总体进度 -->
|
||||||
|
<div class="upload-total-progress">
|
||||||
|
<div class="progress-info">
|
||||||
|
<span>总体进度</span>
|
||||||
|
<span>{{ totalProgress }}%</span>
|
||||||
|
</div>
|
||||||
|
<a-progress
|
||||||
|
:percent="totalProgress"
|
||||||
|
:show-info="false"
|
||||||
|
:stroke-color="{
|
||||||
|
'0%': '#3B82F6',
|
||||||
|
'100%': '#10B981'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 当前文件进度 -->
|
||||||
|
<div class="upload-current-file">
|
||||||
|
<div class="current-file-name">
|
||||||
|
<FileOutlined />
|
||||||
|
<span>{{ currentFileName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="current-file-progress">
|
||||||
|
<div class="progress-info">
|
||||||
|
<span>{{ formatFileSize(currentFileUploaded) }} / {{ formatFileSize(currentFileSize) }}</span>
|
||||||
|
<span>{{ currentFileProgress }}%</span>
|
||||||
|
</div>
|
||||||
|
<a-progress
|
||||||
|
:percent="currentFileProgress"
|
||||||
|
:show-info="false"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 已完成文件列表 -->
|
||||||
|
<div v-if="completedFiles.length > 0" class="completed-files">
|
||||||
|
<div class="completed-header">
|
||||||
|
<CheckCircleOutlined style="color: #10B981" />
|
||||||
|
<span>已完成 {{ completedFiles.length }} 个文件</span>
|
||||||
|
</div>
|
||||||
|
<div class="completed-list">
|
||||||
|
<div v-for="file in completedFiles" :key="file.name" class="completed-item">
|
||||||
|
<CheckOutlined style="color: #10B981; font-size: 12px" />
|
||||||
|
<span>{{ file.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="upload-actions">
|
<div class="upload-actions">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="handleCancel">取消</a-button>
|
<a-button v-if="!uploading" @click="handleCancel">取消</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
|
v-if="!uploading"
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="uploading"
|
:loading="uploading"
|
||||||
:disabled="fileList.length === 0"
|
:disabled="fileList.length === 0"
|
||||||
@@ -73,6 +133,7 @@
|
|||||||
>
|
>
|
||||||
{{ uploading ? '上传中...' : `上传 (${fileList.length})` }}
|
{{ uploading ? '上传中...' : `上传 (${fileList.length})` }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<span v-else class="upload-tip">上传中请勿关闭窗口...</span>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,11 +141,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { UploadOutlined, FileOutlined } from '@ant-design/icons-vue'
|
import { UploadOutlined, FileOutlined, LoadingOutlined, CheckCircleOutlined, CheckOutlined } from '@ant-design/icons-vue'
|
||||||
import { isVideoFile, extractVideoCover } from '@/utils/video-cover'
|
import { isVideoFile, extractVideoCover } from '@/utils/video-cover'
|
||||||
import { getFileName, getFileSize, formatFileSize } from '@/utils/file'
|
import { getFileName, getFileSize, formatFileSize } from '@/utils/file'
|
||||||
|
import { useUpload } from '@/composables/useUpload'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@@ -105,7 +167,10 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel'])
|
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'complete'])
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
const { upload } = useUpload()
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const fileList = ref([])
|
const fileList = ref([])
|
||||||
@@ -117,6 +182,24 @@ const acceptTypes = 'video/*,image/*,audio/*,.mp4,.mov,.avi,.mkv,.jpg,.jpeg,.png
|
|||||||
const MAX_TOTAL_SIZE = 1 * 1024 * 1024 * 1024 // 1GB
|
const MAX_TOTAL_SIZE = 1 * 1024 * 1024 * 1024 // 1GB
|
||||||
const MAX_SINGLE_FILE_SIZE = 1 * 1024 * 1024 * 1024 // 1GB (单个文件最大1GB)
|
const MAX_SINGLE_FILE_SIZE = 1 * 1024 * 1024 * 1024 // 1GB (单个文件最大1GB)
|
||||||
|
|
||||||
|
// 上传状态
|
||||||
|
const uploading = ref(false)
|
||||||
|
const currentUploadIndex = ref(0)
|
||||||
|
const totalUploadCount = ref(0)
|
||||||
|
const currentFileName = ref('')
|
||||||
|
const currentFileProgress = ref(0)
|
||||||
|
const currentFileSize = ref(0)
|
||||||
|
const currentFileUploaded = ref(0)
|
||||||
|
const completedFiles = ref([])
|
||||||
|
|
||||||
|
// 计算总进度
|
||||||
|
const totalProgress = computed(() => {
|
||||||
|
if (totalUploadCount.value === 0) return 0
|
||||||
|
const baseProgress = ((currentUploadIndex.value - 1) / totalUploadCount.value) * 100
|
||||||
|
const currentContribution = (currentFileProgress.value / 100) / totalUploadCount.value * 100
|
||||||
|
return Math.round(baseProgress + currentContribution)
|
||||||
|
})
|
||||||
|
|
||||||
// 计算总大小(字节)
|
// 计算总大小(字节)
|
||||||
const getTotalSizeBytes = () => {
|
const getTotalSizeBytes = () => {
|
||||||
return fileList.value.reduce((total, item) => {
|
return fileList.value.reduce((total, item) => {
|
||||||
@@ -134,9 +217,22 @@ watch(() => props.visible, (newVal) => {
|
|||||||
if (!newVal) {
|
if (!newVal) {
|
||||||
fileList.value = []
|
fileList.value = []
|
||||||
fileCoverMap.value.clear()
|
fileCoverMap.value.clear()
|
||||||
|
resetUploadState()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 重置上传状态
|
||||||
|
const resetUploadState = () => {
|
||||||
|
uploading.value = false
|
||||||
|
currentUploadIndex.value = 0
|
||||||
|
totalUploadCount.value = 0
|
||||||
|
currentFileName.value = ''
|
||||||
|
currentFileProgress.value = 0
|
||||||
|
currentFileSize.value = 0
|
||||||
|
currentFileUploaded.value = 0
|
||||||
|
completedFiles.value = []
|
||||||
|
}
|
||||||
|
|
||||||
// 上传前处理
|
// 上传前处理
|
||||||
const handleBeforeUpload = (file) => {
|
const handleBeforeUpload = (file) => {
|
||||||
// 检查单个文件大小
|
// 检查单个文件大小
|
||||||
@@ -172,7 +268,7 @@ const handleBeforeUpload = (file) => {
|
|||||||
// 文件列表变化
|
// 文件列表变化
|
||||||
const handleFileChange = async (info) => {
|
const handleFileChange = async (info) => {
|
||||||
const { file, fileList: newFileList } = info
|
const { file, fileList: newFileList } = info
|
||||||
|
|
||||||
if (file && file.status !== 'uploading') {
|
if (file && file.status !== 'uploading') {
|
||||||
fileList.value = newFileList
|
fileList.value = newFileList
|
||||||
.map(item => {
|
.map(item => {
|
||||||
@@ -182,12 +278,12 @@ const handleFileChange = async (info) => {
|
|||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
.filter(item => item.status !== 'removed')
|
.filter(item => item.status !== 'removed')
|
||||||
|
|
||||||
// 如果是视频文件,自动提取封面
|
// 如果是视频文件,自动提取封面
|
||||||
const fileObj = file.file || file.originFileObj || file
|
const fileObj = file.file || file.originFileObj || file
|
||||||
if (fileObj instanceof File && isVideoFile(fileObj)) {
|
if (fileObj instanceof File && isVideoFile(fileObj)) {
|
||||||
const fileKey = file.uid || fileObj.name
|
const fileKey = file.uid || fileObj.name
|
||||||
|
|
||||||
if (!fileCoverMap.value.has(fileKey)) {
|
if (!fileCoverMap.value.has(fileKey)) {
|
||||||
try {
|
try {
|
||||||
const coverResult = await extractVideoCover(fileObj, {
|
const coverResult = await extractVideoCover(fileObj, {
|
||||||
@@ -206,8 +302,8 @@ const handleFileChange = async (info) => {
|
|||||||
|
|
||||||
// 移除文件
|
// 移除文件
|
||||||
const handleRemove = (fileItem) => {
|
const handleRemove = (fileItem) => {
|
||||||
const index = fileList.value.findIndex(item =>
|
const index = fileList.value.findIndex(item =>
|
||||||
(item.uid && item.uid === fileItem.uid) ||
|
(item.uid && item.uid === fileItem.uid) ||
|
||||||
(getFileName(item) === getFileName(fileItem))
|
(getFileName(item) === getFileName(fileItem))
|
||||||
)
|
)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -219,7 +315,7 @@ const handleRemove = (fileItem) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 确认上传
|
// 确认上传
|
||||||
const handleConfirm = () => {
|
const handleConfirm = async () => {
|
||||||
if (fileList.value.length === 0) {
|
if (fileList.value.length === 0) {
|
||||||
message.warning('请选择文件')
|
message.warning('请选择文件')
|
||||||
return
|
return
|
||||||
@@ -250,9 +346,82 @@ const handleConfirm = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 开始上传
|
||||||
|
await performUpload(filesWithCover)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
const performUpload = async (filesWithCover) => {
|
||||||
|
uploading.value = true
|
||||||
|
totalUploadCount.value = filesWithCover.length
|
||||||
|
currentUploadIndex.value = 0
|
||||||
|
completedFiles.value = []
|
||||||
|
|
||||||
// 使用传入的fileCategory,如果没有则使用默认值
|
// 使用传入的fileCategory,如果没有则使用默认值
|
||||||
const category = props.fileCategory || DEFAULT_FILE_CATEGORY
|
const category = props.fileCategory || DEFAULT_FILE_CATEGORY
|
||||||
emit('confirm', filesWithCover, category, props.groupId)
|
let successCount = 0
|
||||||
|
let failCount = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < filesWithCover.length; i++) {
|
||||||
|
const fileWithCover = filesWithCover[i]
|
||||||
|
currentUploadIndex.value = i + 1
|
||||||
|
currentFileName.value = fileWithCover.file.name
|
||||||
|
currentFileSize.value = fileWithCover.file.size
|
||||||
|
currentFileUploaded.value = 0
|
||||||
|
currentFileProgress.value = 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
await upload(fileWithCover.file, {
|
||||||
|
fileCategory: category,
|
||||||
|
groupId: props.groupId,
|
||||||
|
coverBase64: fileWithCover.coverBase64,
|
||||||
|
onProgress: (progress) => {
|
||||||
|
currentFileProgress.value = progress
|
||||||
|
currentFileUploaded.value = Math.round((progress / 100) * currentFileSize.value)
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
console.log('文件上传成功:', fileWithCover.file.name)
|
||||||
|
successCount++
|
||||||
|
completedFiles.value.push({
|
||||||
|
name: fileWithCover.file.name,
|
||||||
|
success: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('文件上传失败:', fileWithCover.file.name, error)
|
||||||
|
failCount++
|
||||||
|
completedFiles.value.push({
|
||||||
|
name: fileWithCover.file.name,
|
||||||
|
success: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('上传异常:', error)
|
||||||
|
failCount++
|
||||||
|
completedFiles.value.push({
|
||||||
|
name: fileWithCover.file.name,
|
||||||
|
success: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = false
|
||||||
|
|
||||||
|
// 显示结果
|
||||||
|
if (successCount > 0 && failCount === 0) {
|
||||||
|
message.success(`成功上传 ${successCount} 个文件`)
|
||||||
|
} else if (successCount > 0 && failCount > 0) {
|
||||||
|
message.warning(`上传完成:${successCount} 个成功,${failCount} 个失败`)
|
||||||
|
} else {
|
||||||
|
message.error('上传失败,请重试')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知父组件上传完成
|
||||||
|
emit('complete', { successCount, failCount })
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
emit('update:visible', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理 visible 变化
|
// 处理 visible 变化
|
||||||
@@ -262,6 +431,7 @@ const handleVisibleChange = (value) => {
|
|||||||
|
|
||||||
// 取消
|
// 取消
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
if (uploading.value) return
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
emit('cancel')
|
emit('cancel')
|
||||||
}
|
}
|
||||||
@@ -335,13 +505,104 @@ const handleCancel = () => {
|
|||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-tips {
|
.upload-tip {
|
||||||
font-size: 12px;
|
color: var(--color-text-3);
|
||||||
line-height: 1.8;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-tips > div {
|
/* 上传进度区域 */
|
||||||
margin-bottom: 4px;
|
.upload-progress-area {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-spinner {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #3B82F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-total-progress {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-current-file {
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--color-bg-2);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-file-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-file-name span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-file-progress .progress-info {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed-files {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed-list {
|
||||||
|
max-height: 120px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed-item span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,9 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
await userStore.fetchUserInfo()
|
await userStore.fetchUserInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 路由切换时更新用户额度
|
||||||
|
userStore.fetchUserProfile()
|
||||||
|
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -241,8 +241,8 @@
|
|||||||
<MaterialUploadModal
|
<MaterialUploadModal
|
||||||
v-model:visible="uploadModalVisible"
|
v-model:visible="uploadModalVisible"
|
||||||
:group-id="selectedGroupId"
|
:group-id="selectedGroupId"
|
||||||
:uploading="uploadLoading"
|
:file-category="activeCategory === 'DIGITAL_HUMAN' ? 'digital_human' : 'video'"
|
||||||
@confirm="handleFileUpload"
|
@complete="handleUploadComplete"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 新建分组弹窗 -->
|
<!-- 新建分组弹窗 -->
|
||||||
@@ -284,7 +284,6 @@ import {
|
|||||||
import { message, Modal } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
import MaterialUploadModal from '@/components/material/MaterialUploadModal.vue';
|
import MaterialUploadModal from '@/components/material/MaterialUploadModal.vue';
|
||||||
import MaterialService, { MaterialGroupService } from '@/api/material';
|
import MaterialService, { MaterialGroupService } from '@/api/material';
|
||||||
import { useUpload } from '@/composables/useUpload';
|
|
||||||
import FullWidthLayout from '@/layouts/components/FullWidthLayout.vue';
|
import FullWidthLayout from '@/layouts/components/FullWidthLayout.vue';
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
|
|
||||||
@@ -302,7 +301,6 @@ const searchKeyword = ref('')
|
|||||||
|
|
||||||
// 模态框状态
|
// 模态框状态
|
||||||
const uploadModalVisible = ref(false)
|
const uploadModalVisible = ref(false)
|
||||||
const uploadLoading = ref(false)
|
|
||||||
const createGroupModalVisible = ref(false)
|
const createGroupModalVisible = ref(false)
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
@@ -329,9 +327,6 @@ const pagination = reactive({
|
|||||||
total: 0
|
total: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// Hooks
|
|
||||||
const { upload } = useUpload()
|
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const handleCategoryChange = async (category) => {
|
const handleCategoryChange = async (category) => {
|
||||||
activeCategory.value = category
|
activeCategory.value = category
|
||||||
@@ -557,46 +552,15 @@ const handleOpenUploadModal = () => {
|
|||||||
uploadModalVisible.value = true
|
uploadModalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileUpload = async (filesWithCover, category, groupId) => {
|
// 上传完成后的回调
|
||||||
try {
|
const handleUploadComplete = async ({ successCount, failCount }) => {
|
||||||
uploadLoading.value = true
|
// 刷新文件列表
|
||||||
|
await loadFileList()
|
||||||
// 根据当前分类确定 fileCategory
|
// 刷新存储配额
|
||||||
// 混剪素材用 video,数字人素材用 digital_human
|
await userStore.fetchUserProfile()
|
||||||
const fileCategory = activeCategory.value === 'DIGITAL_HUMAN' ? 'digital_human' : 'video'
|
// 混剪素材才刷新分组列表
|
||||||
// 数字人素材不关联分组
|
if (activeCategory.value === 'MIX') {
|
||||||
const uploadGroupId = activeCategory.value === 'DIGITAL_HUMAN' ? null : groupId
|
await loadGroupList()
|
||||||
|
|
||||||
for (const fileWithCover of filesWithCover) {
|
|
||||||
await upload(fileWithCover.file, {
|
|
||||||
fileCategory,
|
|
||||||
groupId: uploadGroupId,
|
|
||||||
coverBase64: fileWithCover.coverBase64,
|
|
||||||
onStart: () => {},
|
|
||||||
onProgress: () => {},
|
|
||||||
onSuccess: (id) => {
|
|
||||||
console.log('文件上传成功:', id)
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
message.error(error.message || '上传失败')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success(`成功上传 ${filesWithCover.length} 个文件`)
|
|
||||||
uploadModalVisible.value = false
|
|
||||||
await loadFileList()
|
|
||||||
// 刷新存储配额
|
|
||||||
await userStore.fetchUserProfile()
|
|
||||||
// 混剪素材才刷新分组列表
|
|
||||||
if (activeCategory.value === 'MIX') {
|
|
||||||
await loadGroupList()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("文件上传失败:", error)
|
|
||||||
message.error("文件上传失败: " + (error.message || "未知错误"))
|
|
||||||
} finally {
|
|
||||||
uploadLoading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user