feat: 搭建后台管理框架
This commit is contained in:
1 parent
6d28bab72b
commit
40546bfd01
238 files changed
+18160
-3837
No files matched your search
@@ -14,9 +14,19 @@ const pageSize = ref(10)
|
||||
const dialogVisible = ref(false)
|
||||
const editingId = ref(null)
|
||||
const form = reactive({ title: '', genre: '', description: '', episodes: 1, cover: '' })
|
||||
const filtered = computed(() => dramas.filter(item => item.title.includes(search.value) && (status.value === '全部状态' || item.status === status.value)))
|
||||
const paginated = computed(() => filtered.value.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value))
|
||||
watch([search, status], () => { currentPage.value = 1 })
|
||||
const filtered = computed(() =>
|
||||
dramas.filter(
|
||||
(item) =>
|
||||
item.title.includes(search.value) &&
|
||||
(status.value === '全部状态' || item.status === status.value)
|
||||
)
|
||||
)
|
||||
const paginated = computed(() =>
|
||||
filtered.value.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value)
|
||||
)
|
||||
watch([search, status], () => {
|
||||
currentPage.value = 1
|
||||
})
|
||||
function openCreate() {
|
||||
editingId.value = null
|
||||
Object.assign(form, { title: '', genre: '', description: '', episodes: 1, cover: '' })
|
||||
@@ -24,14 +34,20 @@ function openCreate() {
|
||||
}
|
||||
function openEdit(item) {
|
||||
editingId.value = item.id
|
||||
Object.assign(form, { title: item.title, genre: item.genre, description: item.description, episodes: item.episodes, cover: item.cover || '' })
|
||||
Object.assign(form, {
|
||||
title: item.title,
|
||||
genre: item.genre,
|
||||
description: item.description,
|
||||
episodes: item.episodes,
|
||||
cover: item.cover || ''
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
function saveDrama() {
|
||||
if (!form.title || !form.genre) return ElMessage.warning('请填写短剧名称和类型')
|
||||
const updatedAt = new Date().toISOString().slice(0, 10)
|
||||
if (editingId.value) {
|
||||
const drama = dramas.find(item => item.id === editingId.value)
|
||||
const drama = dramas.find((item) => item.id === editingId.value)
|
||||
if (drama) Object.assign(drama, { ...form, updatedAt })
|
||||
} else {
|
||||
dramas.unshift({ id: Date.now(), ...form, status: '草稿', views: '—', updatedAt })
|
||||
@@ -41,7 +57,9 @@ function saveDrama() {
|
||||
ElMessage.success(editingId.value ? '短剧信息已更新' : '短剧草稿已保存')
|
||||
editingId.value = null
|
||||
}
|
||||
function uploadCover(file) { if (can('上传封面素材')) form.cover = URL.createObjectURL(file.raw) }
|
||||
function uploadCover(file) {
|
||||
if (can('上传封面素材')) form.cover = URL.createObjectURL(file.raw)
|
||||
}
|
||||
async function confirmDelete(item) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除短剧「${item.title}」吗?删除后无法恢复。`, '删除确认', {
|
||||
@@ -49,11 +67,12 @@ async function confirmDelete(item) {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'el-button--danger',
|
||||
distinguishCancelAndClose: true,
|
||||
distinguishCancelAndClose: true
|
||||
})
|
||||
const index = dramas.findIndex(drama => drama.id === item.id)
|
||||
const index = dramas.findIndex((drama) => drama.id === item.id)
|
||||
if (index !== -1) dramas.splice(index, 1)
|
||||
if (currentPage.value > Math.ceil(filtered.value.length / pageSize.value)) currentPage.value = Math.max(1, currentPage.value - 1)
|
||||
if (currentPage.value > Math.ceil(filtered.value.length / pageSize.value))
|
||||
currentPage.value = Math.max(1, currentPage.value - 1)
|
||||
ElMessage.success('短剧已删除')
|
||||
} catch {
|
||||
// 用户取消或关闭确认框时不执行删除。
|
||||
@@ -62,19 +81,134 @@ async function confirmDelete(item) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-heading"><div><div class="eyebrow">内容管理</div><h1>短剧管理</h1><p>管理短剧信息、封面、剧集和发布状态。</p></div><el-button v-if="can('创建短剧')" type="primary" class="primary-button" @click="openCreate"><el-icon><Plus /></el-icon>新建短剧</el-button></div>
|
||||
<div class="page-heading"
|
||||
><div
|
||||
><div class="eyebrow">内容管理</div><h1>短剧管理</h1
|
||||
><p>管理短剧信息、封面、剧集和发布状态。</p></div
|
||||
><el-button v-if="can('创建短剧')" type="primary" class="primary-button" @click="openCreate"
|
||||
><el-icon><Plus /></el-icon>新建短剧</el-button
|
||||
></div
|
||||
>
|
||||
<section class="panel library-panel">
|
||||
<div class="library-toolbar"><div class="drama-filters"><el-input v-model="search" placeholder="搜索短剧名称" :prefix-icon="Search" clearable /><el-select v-model="status"><el-option v-for="item in ['全部状态','已上线','审核中','草稿']" :key="item" :label="item" :value="item" /></el-select></div><span class="table-result-count">共 {{filtered.length}} 部短剧</span></div>
|
||||
<div class="library-toolbar"
|
||||
><div class="drama-filters"
|
||||
><el-input
|
||||
v-model="search"
|
||||
placeholder="搜索短剧名称"
|
||||
:prefix-icon="Search"
|
||||
clearable /><el-select v-model="status"
|
||||
><el-option
|
||||
v-for="item in ['全部状态', '已上线', '审核中', '草稿']"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item" /></el-select></div
|
||||
><span class="table-result-count">共 {{ filtered.length }} 部短剧</span></div
|
||||
>
|
||||
<el-table :data="paginated" row-key="id" empty-text="没有符合条件的短剧" class="drama-el-table">
|
||||
<el-table-column label="短剧名称" min-width="340"><template #default="{row}"><div class="drama-cell"><img v-if="row.cover" :src="row.cover" class="drama-cover-image"/><div v-else class="drama-cover">暂无封面</div><div><b>{{row.title}}</b><small>{{row.description}}</small></div></div></template></el-table-column>
|
||||
<el-table-column label="短剧名称" min-width="340"
|
||||
><template #default="{ row }"
|
||||
><div class="drama-cell"
|
||||
><img v-if="row.cover" :src="row.cover" class="drama-cover-image" /><div
|
||||
v-else
|
||||
class="drama-cover"
|
||||
>暂无封面</div
|
||||
><div
|
||||
><b>{{ row.title }}</b
|
||||
><small>{{ row.description }}</small></div
|
||||
></div
|
||||
></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column prop="genre" label="类型" width="140" />
|
||||
<el-table-column label="集数" width="110"><template #default="{row}">{{row.episodes}} 集</template></el-table-column>
|
||||
<el-table-column label="集数" width="110"
|
||||
><template #default="{ row }">{{ row.episodes }} 集</template></el-table-column
|
||||
>
|
||||
<el-table-column prop="views" label="播放量" width="120" />
|
||||
<el-table-column label="状态" width="120"><template #default="{row}"><el-tag :class="['status-badge',statusClass(row.status)]" effect="light">{{row.status}}</el-tag></template></el-table-column>
|
||||
<el-table-column label="状态" width="120"
|
||||
><template #default="{ row }"
|
||||
><el-tag :class="['status-badge', statusClass(row.status)]" effect="light">{{
|
||||
row.status
|
||||
}}</el-tag></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column prop="updatedAt" label="更新时间" width="150" />
|
||||
<el-table-column label="操作" width="240" fixed="right"><template #default="{row}"><div class="table-actions"><el-button v-if="can('编辑短剧信息')" type="primary" size="small" @click="openEdit(row)">编辑</el-button><el-button v-if="can('查看剧集列表')" type="success" size="small" @click="$router.push({name:'episodes',query:{drama:row.id}})">剧集</el-button><el-button v-if="can('删除短剧')" type="danger" size="small" @click="confirmDelete(row)">删除</el-button></div></template></el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right"
|
||||
><template #default="{ row }"
|
||||
><div class="table-actions"
|
||||
><el-button
|
||||
v-if="can('编辑短剧信息')"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openEdit(row)"
|
||||
>编辑</el-button
|
||||
><el-button
|
||||
v-if="can('查看剧集列表')"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="$router.push({ name: 'episodes', query: { drama: row.id } })"
|
||||
>剧集</el-button
|
||||
><el-button
|
||||
v-if="can('删除短剧')"
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="confirmDelete(row)"
|
||||
>删除</el-button
|
||||
></div
|
||||
></template
|
||||
></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
<div class="pagination"><el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[5,10,20,50]" :total="filtered.length" :pager-count="7" layout="total, sizes, prev, pager, next, jumper" background /></div>
|
||||
<div class="pagination"
|
||||
><el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[5, 10, 20, 50]"
|
||||
:total="filtered.length"
|
||||
:pager-count="7"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
background
|
||||
/></div>
|
||||
</section>
|
||||
<el-dialog v-model="dialogVisible" :title="editingId?'编辑短剧':'新建短剧'" width="600px"><el-form label-position="top"><div class="form-row"><el-form-item label="短剧名称" required><el-input v-model="form.title" placeholder="请输入短剧名称" /></el-form-item><el-form-item label="类型" required><el-select v-model="form.genre" placeholder="请选择"><el-option v-for="genre in ['都市情感','甜宠爱情','重生逆袭','悬疑复仇','古装言情']" :key="genre" :label="genre" :value="genre" /></el-select></el-form-item></div><el-form-item label="简介"><el-input v-model="form.description" type="textarea" :rows="3" maxlength="200" show-word-limit /></el-form-item><div class="form-row"><el-form-item label="预计集数"><el-input-number v-model="form.episodes" :min="1" :max="200" /></el-form-item><el-form-item v-if="can('上传封面素材')" label="短剧封面"><el-upload action="#" :auto-upload="false" :show-file-list="false" accept="image/*" :on-change="uploadCover"><el-button size="small">{{form.cover?'更换封面':'选择封面'}}</el-button></el-upload></el-form-item></div><img v-if="form.cover" :src="form.cover" class="form-cover-preview" /></el-form><template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" @click="saveDrama">{{editingId?'保存修改':'保存草稿'}}</el-button></template></el-dialog>
|
||||
<el-dialog v-model="dialogVisible" :title="editingId ? '编辑短剧' : '新建短剧'" width="600px"
|
||||
><el-form label-position="top"
|
||||
><div class="form-row"
|
||||
><el-form-item label="短剧名称" required
|
||||
><el-input v-model="form.title" placeholder="请输入短剧名称" /></el-form-item
|
||||
><el-form-item label="类型" required
|
||||
><el-select v-model="form.genre" placeholder="请选择"
|
||||
><el-option
|
||||
v-for="genre in ['都市情感', '甜宠爱情', '重生逆袭', '悬疑复仇', '古装言情']"
|
||||
:key="genre"
|
||||
:label="genre"
|
||||
:value="genre" /></el-select></el-form-item></div
|
||||
><el-form-item label="简介"
|
||||
><el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="200"
|
||||
show-word-limit /></el-form-item
|
||||
><div class="form-row"
|
||||
><el-form-item label="预计集数"
|
||||
><el-input-number v-model="form.episodes" :min="1" :max="200" /></el-form-item
|
||||
><el-form-item v-if="can('上传封面素材')" label="短剧封面"
|
||||
><el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept="image/*"
|
||||
:on-change="uploadCover"
|
||||
><el-button size="small">{{
|
||||
form.cover ? '更换封面' : '选择封面'
|
||||
}}</el-button></el-upload
|
||||
></el-form-item
|
||||
></div
|
||||
><img v-if="form.cover" :src="form.cover" class="form-cover-preview" /></el-form
|
||||
><template #footer
|
||||
><el-button @click="dialogVisible = false">取消</el-button
|
||||
><el-button type="primary" @click="saveDrama">{{
|
||||
editingId ? '保存修改' : '保存草稿'
|
||||
}}</el-button></template
|
||||
></el-dialog
|
||||
>
|
||||
</template>
|
||||
@@ -13,12 +13,30 @@ const { can } = useRbac()
|
||||
const selectedDramaId = ref(Number(route.query.drama) || dramas[0]?.id)
|
||||
const dialogVisible = ref(false)
|
||||
const form = ref({ title: '', fileName: '' })
|
||||
const selectedDrama = computed(() => dramas.find(item => item.id === selectedDramaId.value) || dramas[0])
|
||||
const episodes = computed(() => episodeData.filter(item => item.dramaId === selectedDramaId.value).sort((a,b) => a.number-b.number))
|
||||
function pickVideo(file) { form.value.fileName = file.name }
|
||||
const selectedDrama = computed(
|
||||
() => dramas.find((item) => item.id === selectedDramaId.value) || dramas[0]
|
||||
)
|
||||
const episodes = computed(() =>
|
||||
episodeData
|
||||
.filter((item) => item.dramaId === selectedDramaId.value)
|
||||
.sort((a, b) => a.number - b.number)
|
||||
)
|
||||
function pickVideo(file) {
|
||||
form.value.fileName = file.name
|
||||
}
|
||||
function saveEpisode() {
|
||||
if (!form.value.title || !form.value.fileName) return ElMessage.warning('请填写标题并选择视频文件')
|
||||
episodeData.push({ id: Date.now(), dramaId: selectedDramaId.value, number: episodes.value.length + 1, title: form.value.title, duration: '待处理', status: '待发布', views: '—', fileName: form.value.fileName })
|
||||
if (!form.value.title || !form.value.fileName)
|
||||
return ElMessage.warning('请填写标题并选择视频文件')
|
||||
episodeData.push({
|
||||
id: Date.now(),
|
||||
dramaId: selectedDramaId.value,
|
||||
number: episodes.value.length + 1,
|
||||
title: form.value.title,
|
||||
duration: '待处理',
|
||||
status: '待发布',
|
||||
views: '—',
|
||||
fileName: form.value.fileName
|
||||
})
|
||||
selectedDrama.value.episodes += 1
|
||||
form.value = { title: '', fileName: '' }
|
||||
dialogVisible.value = false
|
||||
@@ -27,18 +45,92 @@ function saveEpisode() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-heading"><div><div class="eyebrow">内容管理</div><h1>剧集管理</h1><p>为短剧添加视频剧集并维护播放顺序。</p></div><el-button v-if="can('上传剧集视频')" type="primary" class="primary-button" @click="dialogVisible=true"><el-icon><Plus /></el-icon>上传剧集</el-button></div>
|
||||
<div class="page-heading"
|
||||
><div
|
||||
><div class="eyebrow">内容管理</div><h1>剧集管理</h1
|
||||
><p>为短剧添加视频剧集并维护播放顺序。</p></div
|
||||
><el-button
|
||||
v-if="can('上传剧集视频')"
|
||||
type="primary"
|
||||
class="primary-button"
|
||||
@click="dialogVisible = true"
|
||||
><el-icon><Plus /></el-icon>上传剧集</el-button
|
||||
></div
|
||||
>
|
||||
<section class="panel episode-panel">
|
||||
<div class="episode-context"><div class="episode-context-copy"><span>选择短剧</span><b>{{selectedDrama?.title}}</b></div><el-select v-model="selectedDramaId" class="drama-select"><el-option v-for="item in dramas" :key="item.id" :label="item.title" :value="item.id" /></el-select></div>
|
||||
<div class="episode-toolbar"><div><h2>剧集列表({{episodes.length}})</h2><p>按剧集编号顺序播放</p></div></div>
|
||||
<el-table :data="episodes" row-key="id" empty-text="该短剧暂无剧集" class="drama-el-table episode-el-table">
|
||||
<el-table-column label="集数" width="100"><template #default="{row}">{{String(row.number).padStart(2,'0')}}</template></el-table-column>
|
||||
<el-table-column label="剧集名称" min-width="280"><template #default="{row}"><div class="episode-table-title"><b>{{row.title}}</b><small v-if="row.fileName">{{row.fileName}}</small></div></template></el-table-column>
|
||||
<div class="episode-context"
|
||||
><div class="episode-context-copy"
|
||||
><span>选择短剧</span><b>{{ selectedDrama?.title }}</b></div
|
||||
><el-select v-model="selectedDramaId" class="drama-select"
|
||||
><el-option
|
||||
v-for="item in dramas"
|
||||
:key="item.id"
|
||||
:label="item.title"
|
||||
:value="item.id" /></el-select
|
||||
></div>
|
||||
<div class="episode-toolbar"
|
||||
><div
|
||||
><h2>剧集列表({{ episodes.length }})</h2><p>按剧集编号顺序播放</p></div
|
||||
></div
|
||||
>
|
||||
<el-table
|
||||
:data="episodes"
|
||||
row-key="id"
|
||||
empty-text="该短剧暂无剧集"
|
||||
class="drama-el-table episode-el-table"
|
||||
>
|
||||
<el-table-column label="集数" width="100"
|
||||
><template #default="{ row }">{{
|
||||
String(row.number).padStart(2, '0')
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="剧集名称" min-width="280"
|
||||
><template #default="{ row }"
|
||||
><div class="episode-table-title"
|
||||
><b>{{ row.title }}</b
|
||||
><small v-if="row.fileName">{{ row.fileName }}</small></div
|
||||
></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column prop="duration" label="时长" width="140" />
|
||||
<el-table-column prop="views" label="播放量" width="180"><template #default="{row}">{{row.views}}<span v-if="row.views !== '—'"> 次</span></template></el-table-column>
|
||||
<el-table-column label="状态" width="150"><template #default="{row}"><el-tag :class="['status-badge',statusClass(row.status)]" effect="light">{{row.status}}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right"><template #default><el-button v-if="can('管理剧集排序')" type="primary" link>调整顺序</el-button></template></el-table-column>
|
||||
<el-table-column prop="views" label="播放量" width="180"
|
||||
><template #default="{ row }"
|
||||
>{{ row.views }}<span v-if="row.views !== '—'"> 次</span></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column label="状态" width="150"
|
||||
><template #default="{ row }"
|
||||
><el-tag :class="['status-badge', statusClass(row.status)]" effect="light">{{
|
||||
row.status
|
||||
}}</el-tag></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column label="操作" width="150" fixed="right"
|
||||
><template #default
|
||||
><el-button v-if="can('管理剧集排序')" type="primary" link>调整顺序</el-button></template
|
||||
></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
</section>
|
||||
<el-dialog v-model="dialogVisible" title="上传剧集" width="560px"><el-form label-position="top"><el-form-item label="剧集标题" required><el-input v-model="form.title" placeholder="例如:命运的相遇" /></el-form-item><el-form-item label="视频文件" required><el-upload drag action="#" :auto-upload="false" accept="video/mp4,video/quicktime" :on-change="pickVideo"><div class="upload-copy">拖放视频文件到此处,或点击选择<br/><small>支持 MP4、MOV</small></div></el-upload></el-form-item><p v-if="form.fileName" class="selected-file">已选择:{{form.fileName}}</p></el-form><template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" @click="saveEpisode">加入上传队列</el-button></template></el-dialog>
|
||||
<el-dialog v-model="dialogVisible" title="上传剧集" width="560px"
|
||||
><el-form label-position="top"
|
||||
><el-form-item label="剧集标题" required
|
||||
><el-input v-model="form.title" placeholder="例如:命运的相遇" /></el-form-item
|
||||
><el-form-item label="视频文件" required
|
||||
><el-upload
|
||||
drag
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
accept="video/mp4,video/quicktime"
|
||||
:on-change="pickVideo"
|
||||
><div class="upload-copy"
|
||||
>拖放视频文件到此处,或点击选择<br /><small>支持 MP4、MOV</small></div
|
||||
></el-upload
|
||||
></el-form-item
|
||||
><p v-if="form.fileName" class="selected-file">已选择:{{ form.fileName }}</p></el-form
|
||||
><template #footer
|
||||
><el-button @click="dialogVisible = false">取消</el-button
|
||||
><el-button type="primary" @click="saveEpisode">加入上传队列</el-button></template
|
||||
></el-dialog
|
||||
>
|
||||
</template>
|
||||
Reference in new issue
Block a user