This commit is contained in:
lz_db
2026-10-24 00:04:50 +08:00
parent 1d440b2916
commit 893dfed0ed
11 changed files with 388 additions and 85 deletions
+121 -11
View File
@@ -4,16 +4,39 @@
<template #header>
<div class="header">
<span>系列管理</span>
<div>
<div class="header-right">
<el-input v-model="keyword" placeholder="搜索系列标题" clearable
style="width: 200px; margin-right: 8px" @keyup.enter="load" />
<el-button :loading="syncAllLoading" @click="syncAll">同步新剧</el-button>
<el-button type="primary" @click="showCreate">+ 新建系列</el-button>
<el-dropdown trigger="click" @command="handleBatchCmd">
<el-button :loading="batchLoading">批量更新 <el-icon class="el-icon--right"><arrow-down /></el-icon></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="rebuild" :disabled="batchLoading">
<el-icon><refresh-left /></el-icon> 重算集数(total_episodes)
</el-dropdown-item>
<el-dropdown-item command="syncDrama" :disabled="batchLoading">
<el-icon><download /></el-icon> 同步短剧采集(新集数)
</el-dropdown-item>
<el-dropdown-item command="syncSources" :disabled="batchLoading">
<el-icon><collection-tag /></el-icon> 同步视频采集源
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button v-if="selectedIds.length" type="warning" plain
style="margin-left: 8px"
@click="showBatchEdit">批量编辑(已选 {{ selectedIds.length }})</el-button>
<el-button type="primary" style="margin-left: 8px" @click="showCreate">+ 新建系列</el-button>
</div>
</div>
</template>
<el-table :data="list" v-loading="loading" stripe>
<el-table :data="list" v-loading="loading" stripe
@selection-change="onSelectionChange">
<el-table-column type="selection" width="42" />
<el-table-column prop="id" label="ID" width="70" />
<el-table-column prop="title" label="系列标题" min-width="160">
<template #default="{ row }">
@@ -106,12 +129,41 @@
<el-button type="primary" :loading="submitting" @click="save">保存</el-button>
</template>
</el-dialog>
<!-- 批量编辑弹窗 -->
<el-dialog v-model="batchDialogVisible" title="批量编辑系列" width="400px">
<div style="margin-bottom: 12px; color: #606266; font-size: 13px">
共选中 <b>{{ selectedIds.length }}</b> 个系列,只修改下面勾选的字段:
</div>
<el-form :model="batchForm" label-width="80px">
<el-form-item label="状态">
<el-switch v-model="batchForm.touchStatus" active-text="修改" inactive-text="不改" />
<el-select v-if="batchForm.touchStatus" v-model="batchForm.status"
style="width: 120px; margin-left: 12px">
<el-option label="正常" :value="1" />
<el-option label="隐藏" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="分类">
<el-switch v-model="batchForm.touchCategory" active-text="修改" inactive-text="不改" />
<el-select v-if="batchForm.touchCategory" v-model="batchForm.category_id"
placeholder="请选择" style="width: 180px; margin-left: 12px">
<el-option v-for="c in categories" :key="c.id" :label="c.name" :value="c.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="batchDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="batchSubmitting" @click="batchSave">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowDown, RefreshLeft, Download, CollectionTag } from '@element-plus/icons-vue'
import request from '@/api/request'
const list = ref([])
@@ -125,13 +177,25 @@ const dialogVisible = ref(false)
const editing = ref(null)
const submitting = ref(false)
const coverUploading = ref(false)
const syncAllLoading = ref(false)
const syncingId = ref(0)
const selectedIds = ref([])
const batchLoading = ref(false)
const batchDialogVisible = ref(false)
const batchSubmitting = ref(false)
const form = reactive({
title: '', cover_url: '', description: '', category_id: 0,
})
const batchForm = reactive({
touchStatus: false, status: 1,
touchCategory: false, category_id: 0,
})
function onSelectionChange(rows) {
selectedIds.value = rows.map(r => r.id)
}
async function uploadCover(file) {
if (!file || !file.raw) return
const allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']
@@ -247,20 +311,66 @@ async function syncOne(row) {
}
}
async function syncAll() {
syncAllLoading.value = true
// ---------- 批量操作 ----------
async function handleBatchCmd(cmd) {
batchLoading.value = true
try {
await request({ url: '/admin/collection/sync-all', method: 'post', data: { pages: 2 } })
ElMessage.success('已触发后台同步,稍后刷新查看')
setTimeout(load, 15000)
if (cmd === 'rebuild') {
await ElMessageBox.confirm(
'将遍历全部系列,按已发布视频真实数量重算 total_episodes。确定执行?',
'确认', { type: 'warning' }
)
const r = await request({ url: '/admin/series/rebuild-counts', method: 'post' })
ElMessage.success(`共 ${r.total} 个系列,修复了 ${r.fixed} 个`)
load()
} else if (cmd === 'syncDrama') {
const r = await request({
url: '/admin/collection/sync-all', method: 'post', data: { pages: 2 },
})
ElMessage.success(`已触发后台增量同步(pages=2),稍后刷新查看`)
setTimeout(load, 15000)
} else if (cmd === 'syncSources') {
await ElMessageBox.confirm(
'将同时触发所有 23 个视频采集源的全量同步(异步后台执行)。确定?',
'确认', { type: 'warning' }
)
const r = await request({ url: '/admin/collection/sources/sync-all', method: 'post' })
ElMessage.success(`已触发 ${r.started?.length || 0} 个采集源同步`)
}
} catch (e) {} finally {
syncAllLoading.value = false
batchLoading.value = false
}
}
function showBatchEdit() {
Object.assign(batchForm, { touchStatus: false, status: 1, touchCategory: false, category_id: 0 })
batchDialogVisible.value = true
}
async function batchSave() {
if (!batchForm.touchStatus && !batchForm.touchCategory) {
ElMessage.warning('至少勾选一个要修改的字段')
return
}
batchSubmitting.value = true
try {
const data = { series_ids: selectedIds.value }
if (batchForm.touchStatus) data.status = batchForm.status
if (batchForm.touchCategory) data.category_id = batchForm.category_id
const r = await request({ url: '/admin/series/batch', method: 'put', data })
ElMessage.success(`已更新 ${r.affected} 个系列`)
batchDialogVisible.value = false
load()
} catch (e) {} finally {
batchSubmitting.value = false
}
}
</script>
<style scoped>
.header { display: flex; justify-content: space-between; align-items: center; }
.header-right { display: flex; align-items: center; }
.title-col { display: flex; align-items: center; gap: 8px; }
.cover { width: 40px; height: 40px; border-radius: 4px; object-fit: cover; }
.cover-upload { display: flex; align-items: center; }