feat: 分片上传断点续传、临时目录后台配置与清扫、宇恒云账号管理

- 管理端大文件分片上传与 sessionStorage 续传;Nginx 大请求体/超时
- .chunk-uploads 定期清扫;system_config 后台配置保留时长与扫描间隔
- 宇恒云 POST /register 对接与 yuheng_cloud_register_records 留痕;yuheng_cloud:manage 权限

Made-with: Cursor
This commit is contained in:
whm
2026-04-13 14:50:27 +08:00
parent 03f5fbb41a
commit 0800982224
20 changed files with 1413 additions and 47 deletions

View File

@@ -57,6 +57,7 @@
<!-- 上传前选择是否可下载 -->
<el-dialog v-model="uploadDialogVisible" title="上传文件" width="440px" :close-on-click-modal="false">
<p class="upload-resume-hint">8MB 将自动分片上传中断后<strong>同一文件</strong>再次选择上传可续传勿改文件名/大小</p>
<el-form label-width="112px">
<el-form-item label="当前目录">
<span>{{ currentPath || '根目录' }}</span>
@@ -68,6 +69,9 @@
<el-form-item label="允许下载">
<el-switch v-model="uploadDownloadable" />
</el-form-item>
<el-form-item v-if="uploading && uploadPercent > 0" label="进度">
<el-progress :percentage="uploadPercent" :stroke-width="16" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="uploadDialogVisible = false">取消</el-button>
@@ -93,7 +97,8 @@
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getSites, getSiteAssets, uploadSiteAsset, deleteSiteAsset, createSiteFolder } from '../../api/admin'
import { getSites, getSiteAssets, deleteSiteAsset, createSiteFolder } from '../../api/admin'
import { uploadSiteAssetWithResume } from '../../utils/siteAssetResumableUpload'
const activeTab = ref('module')
const siteId = ref('')
@@ -103,6 +108,7 @@ const subDirs = ref([])
const loading = ref(false)
const currentPath = ref('')
const uploading = ref(false)
const uploadPercent = ref(0)
const uploadDialogVisible = ref(false)
const uploadDownloadable = ref(false)
const uploadPreserveFilename = ref(false)
@@ -167,12 +173,22 @@ const beforeUpload = (file) => {
const doUpload = async () => {
if (!pendingFile.value || !siteId.value) return
uploading.value = true
uploadPercent.value = 0
try {
await uploadSiteAsset(siteId.value, pendingFile.value, {
folder: currentPath.value || undefined,
downloadable: uploadDownloadable.value,
preserveFilename: uploadPreserveFilename.value
})
await uploadSiteAssetWithResume(
siteId.value,
pendingFile.value,
{
folder: currentPath.value || undefined,
downloadable: uploadDownloadable.value,
preserveFilename: uploadPreserveFilename.value
},
{
onProgress: ({ percent }) => {
uploadPercent.value = percent
}
}
)
ElMessage.success('上传成功')
uploadDialogVisible.value = false
pendingFile.value = null
@@ -181,6 +197,7 @@ const doUpload = async () => {
ElMessage.error(e.response?.data?.error || e.message || '上传失败')
} finally {
uploading.value = false
uploadPercent.value = 0
}
}
@@ -230,4 +247,10 @@ onMounted(() => fetchSites().then(() => fetchList()))
.breadcrumb-wrap { margin-top: 12px; }
.subdirs { margin-top: 8px; font-size: 13px; color: #666; }
.subdirs .label { margin-right: 8px; }
.upload-resume-hint {
margin: 0 0 12px;
font-size: 12px;
color: #606266;
line-height: 1.5;
}
</style>