feat(admin): 剧集多选分片上传与断点续传

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Zhou WeidongandCursor committed 2026-10-21 21:58:28 +08:00
1 parent 8e1a02ace0
commit 6b3620c3c4
9 files changed
+1210 -8

No files matched your search

@@ -16,10 +16,15 @@ import {
import { useRbac } from '@/stores/rbac'
import { formatCount } from '@/utils/format'
import { statusClass } from '@/utils/status'
import { useI18n } from '@/hooks/web/useI18n'
import { probeVideoDuration, uploadFile } from '@/utils/uploader'
import BatchUploadDialog from './upload/BatchUploadDialog.vue'
import { listPendingSessions } from './upload/uploadSessions'
import { MAX_VIDEO_SIZE } from './upload/videoLimit'
const route = useRoute()
const { can } = useRbac()
const { t } = useI18n()
const loading = ref(false)
const dramas = ref([])
@@ -27,6 +32,8 @@ const episodes = ref([])
const selectedDramaId = ref(undefined)
const dialogVisible = ref(false)
const batchUploadVisible = ref(false)
const batchNextNumber = computed(() => nextNumber())
const saving = ref(false)
const uploadingVideo = ref(false)
const videoPercent = ref(0)
@@ -55,7 +62,6 @@ const statusOptions = [
{ code: 'offline', text: '已下线' }
]
const MAX_VIDEO_SIZE = 500 * 1024 * 1024
const VIDEO_EXTENSIONS = ['mp4', 'mov']
const formRules = {
number: [{ required: true, message: '请输入集号', trigger: 'change' }],
@@ -120,6 +126,19 @@ function nextNumber() {
return episodes.value.reduce((max, item) => Math.max(max, item.number || 0), 0) + 1
}
const existingEpisodeNumbers = computed(() =>
episodes.value.map((item) => Number(item.number)).filter((n) => Number.isFinite(n))
)
function openBatchUpload() {
if (!selectedDramaId.value) return
batchUploadVisible.value = true
}
async function onBatchEpisodesCreated() {
await loadEpisodes()
}
function openCreate() {
editingId.value = null
Object.assign(form, {
@@ -304,11 +323,20 @@ async function move(index, offset) {
async function onDramaChange() {
await loadEpisodes()
openBatchIfPendingSessions()
}
/** 进入页面或切换短剧时,若有 24h 内未完成上传则打开批量上传弹窗以提示续传。 */
function openBatchIfPendingSessions() {
if (!selectedDramaId.value || !can('episode:upload')) return
const pending = listPendingSessions(String(selectedDramaId.value))
if (pending.length) batchUploadVisible.value = true
}
onMounted(async () => {
await loadDramas()
await loadEpisodes()
openBatchIfPendingSessions()
})
</script>
@@ -317,13 +345,17 @@ onMounted(async () => {
><div
><div class="eyebrow">内容管理</div><h1>剧集管理</h1
><p>为短剧维护剧集信息、视频地址与播放顺序。</p></div
><el-button
v-if="can('episode:upload')"
type="primary"
class="primary-button"
:disabled="!selectedDramaId"
@click="openCreate"
><AppIcon :icon="AppIcons.add" />新增剧集</el-button
><div class="page-heading-actions">
<el-button v-if="can('episode:upload')" :disabled="!selectedDramaId" @click="openBatchUpload"
><AppIcon :icon="AppIcons.upload" />{{ t('episodeBatch.entry') }}</el-button
><el-button
v-if="can('episode:upload')"
type="primary"
class="primary-button"
:disabled="!selectedDramaId"
@click="openCreate"
><AppIcon :icon="AppIcons.add" />新增剧集</el-button
></div
></div
>
<section class="panel episode-panel">
@@ -672,4 +704,14 @@ onMounted(async () => {
></video>
<el-empty v-else description="该剧集还没有上传视频" />
</el-dialog>
<BatchUploadDialog
v-if="selectedDramaId"
v-model="batchUploadVisible"
:drama-id="selectedDramaId"
:next-episode-number="batchNextNumber"
:default-unlock-coin="selectedDrama?.unlockCoin ?? 0"
:existing-numbers="existingEpisodeNumbers"
@refreshed="loadEpisodes"
@created="onBatchEpisodesCreated"
/>
</template>