383 lines
13 KiB
Vue
383 lines
13 KiB
Vue
<!-- Author: fruit.com.cn -->
|
||
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
import { AppIcons } from '@/constants/icons'
|
||
import { AppImage } from '@/components/AppImage'
|
||
import PageHeading from '@/views/components/PageHeading.vue'
|
||
import CreatorCenterPage from './components/CreatorCenterPage.vue'
|
||
import { useRbac } from '@/stores/rbac'
|
||
import { useDramaEdit } from './composables/useDramaEdit'
|
||
import { useCreatorAccess } from './composables/useCreatorAccess'
|
||
import { dramaStatusMeta } from './composables/useCreatorWorkspace'
|
||
|
||
const { can } = useRbac()
|
||
const canEdit = can('creator-center:drama:edit')
|
||
const canUpload = can('creator-center:upload')
|
||
// 代看只读:提交/撤回这些写入口直接不渲染
|
||
const { impersonating } = useCreatorAccess()
|
||
|
||
const {
|
||
loading,
|
||
drama,
|
||
episodes,
|
||
submissions,
|
||
editable,
|
||
readonlyReason,
|
||
infoForm,
|
||
savingInfo,
|
||
episodeVisible,
|
||
episodeSaving,
|
||
episodeEditingId,
|
||
episodeForm,
|
||
uploadingId,
|
||
uploadPercent,
|
||
resumeHint,
|
||
submitOptions,
|
||
submissionStatusMeta,
|
||
submissionTypeText,
|
||
dateCell,
|
||
saveInfo,
|
||
openEpisode,
|
||
saveEpisode,
|
||
removeEpisode,
|
||
moveEpisode,
|
||
uploadVideo,
|
||
uploadCover,
|
||
hasResumeSession,
|
||
submit,
|
||
withdraw
|
||
} = useDramaEdit()
|
||
|
||
const episodeCoverPreviewList = computed(() =>
|
||
episodes.value.map((row) => row.cover).filter(Boolean)
|
||
)
|
||
|
||
function episodeCoverPreviewIndex(row) {
|
||
return Math.max(0, episodeCoverPreviewList.value.indexOf(row.cover))
|
||
}
|
||
|
||
function back() {
|
||
window.history.back()
|
||
}
|
||
|
||
/** el-upload 的 http-request 回调:拿到原始 File 后交给统一的(可续传)上传逻辑 */
|
||
function requestVideo(row, options) {
|
||
return uploadVideo(row, options.file)
|
||
}
|
||
|
||
function requestEpisodeCover(row, options) {
|
||
return uploadCover(row, options.file)
|
||
}
|
||
|
||
function requestDramaCover(options) {
|
||
return uploadCover(null, options.file)
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<CreatorCenterPage>
|
||
<PageHeading
|
||
eyebrow="创作者中心"
|
||
title="作品编辑"
|
||
:description="drama ? `${drama.title}(${drama.code})` : ''"
|
||
>
|
||
<el-button @click="back"><AppIcon :icon="AppIcons.arrowBack" />返回</el-button>
|
||
</PageHeading>
|
||
|
||
<section v-loading="loading" class="panel">
|
||
<div class="creator-edit-head">
|
||
<el-tag :type="dramaStatusMeta(drama?.status).tag" effect="light">
|
||
{{ dramaStatusMeta(drama?.status).label }}
|
||
</el-tag>
|
||
<span class="console-muted"
|
||
>分集 {{ drama?.episodes ?? 0 }} 集 · 播放 {{ drama?.views ?? 0 }}</span
|
||
>
|
||
<div class="creator-edit-actions">
|
||
<el-button
|
||
v-for="type in canEdit && !impersonating ? submitOptions(drama || {}) : []"
|
||
:key="type"
|
||
type="primary"
|
||
plain
|
||
size="small"
|
||
@click="submit(type)"
|
||
>{{ submissionTypeText(type) }}</el-button
|
||
>
|
||
</div>
|
||
</div>
|
||
<el-alert
|
||
v-if="readonlyReason"
|
||
class="creator-edit-alert"
|
||
type="warning"
|
||
:closable="false"
|
||
show-icon
|
||
:title="readonlyReason"
|
||
/>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<h3 class="creator-section-title">作品信息</h3>
|
||
<el-form class="creator-form" label-position="top" :disabled="!editable">
|
||
<div class="creator-form-grid">
|
||
<el-form-item label="作品名称" required>
|
||
<el-input v-model="infoForm.title" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="副标题">
|
||
<el-input v-model="infoForm.subtitle" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="题材">
|
||
<el-input v-model="infoForm.genre" maxlength="32" placeholder="例如:都市情感" />
|
||
</el-form-item>
|
||
<el-form-item label="语言">
|
||
<el-input v-model="infoForm.language" maxlength="16" placeholder="zh-CN / id-ID" />
|
||
</el-form-item>
|
||
</div>
|
||
<el-form-item label="简介">
|
||
<el-input
|
||
v-model="infoForm.description"
|
||
type="textarea"
|
||
:rows="3"
|
||
maxlength="500"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
<div class="creator-form-grid">
|
||
<el-form-item label="作品封面">
|
||
<div class="creator-cover-row">
|
||
<div class="creator-cover-preview">
|
||
<img v-if="infoForm.cover" :src="infoForm.cover" alt="" />
|
||
<span v-else>无</span>
|
||
</div>
|
||
<el-upload
|
||
v-if="canUpload"
|
||
:show-file-list="false"
|
||
accept="image/*"
|
||
:http-request="requestDramaCover"
|
||
:disabled="uploadingId !== null"
|
||
>
|
||
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
|
||
</el-upload>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="预告片地址">
|
||
<el-input
|
||
v-model="infoForm.trailerUrl"
|
||
maxlength="512"
|
||
placeholder="整剧一支预告片,可留空"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<el-button
|
||
v-if="canEdit"
|
||
type="primary"
|
||
:loading="savingInfo"
|
||
:disabled="!editable"
|
||
@click="saveInfo"
|
||
>保存作品信息</el-button
|
||
>
|
||
</el-form>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<div class="creator-section-head">
|
||
<h3 class="creator-section-title">分集管理</h3>
|
||
<el-button
|
||
v-if="canEdit"
|
||
type="primary"
|
||
size="small"
|
||
:disabled="!editable"
|
||
@click="openEpisode(null)"
|
||
>新增分集</el-button
|
||
>
|
||
</div>
|
||
<el-alert
|
||
v-if="resumeHint"
|
||
class="creator-edit-alert"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
:title="resumeHint"
|
||
/>
|
||
<el-table :data="episodes" row-key="id" border class="drama-el-table" empty-text="还没有分集">
|
||
<el-table-column prop="number" label="集号" width="70" />
|
||
<el-table-column label="封面" width="88">
|
||
<template #default="{ row }">
|
||
<div class="creator-episode-cover">
|
||
<AppImage
|
||
v-if="row.cover"
|
||
:src="row.cover"
|
||
:preview-list="episodeCoverPreviewList"
|
||
:initial-index="episodeCoverPreviewIndex(row)"
|
||
fit="cover"
|
||
class="creator-episode-image"
|
||
:alt="row.title"
|
||
/>
|
||
<span v-else>无</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="title" label="标题" min-width="150" show-overflow-tooltip />
|
||
<el-table-column prop="duration" label="时长" width="90" />
|
||
<el-table-column label="片源" min-width="120">
|
||
<template #default="{ row }">
|
||
<span class="console-muted">{{ row.videoUrl ? '已上传' : '未上传' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="90">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
size="small"
|
||
effect="light"
|
||
:type="row.status === 'published' ? 'success' : 'info'"
|
||
>
|
||
{{ row.statusText }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="300" fixed="right">
|
||
<template #default="{ row, $index }">
|
||
<div class="table-actions">
|
||
<el-button
|
||
size="small"
|
||
:disabled="!editable || $index === 0"
|
||
@click="moveEpisode(row, -1)"
|
||
>上移</el-button
|
||
>
|
||
<el-button
|
||
size="small"
|
||
:disabled="!editable || $index === episodes.length - 1"
|
||
@click="moveEpisode(row, 1)"
|
||
>下移</el-button
|
||
>
|
||
<el-button size="small" :disabled="!editable" @click="openEpisode(row)"
|
||
>编辑</el-button
|
||
>
|
||
<el-upload
|
||
v-if="canUpload"
|
||
:show-file-list="false"
|
||
accept="video/*"
|
||
:http-request="(options) => requestVideo(row, options)"
|
||
:disabled="!editable || uploadingId !== null"
|
||
>
|
||
<el-button size="small">{{ hasResumeSession ? '继续上传' : '上传视频' }}</el-button>
|
||
</el-upload>
|
||
<el-button
|
||
type="danger"
|
||
size="small"
|
||
:disabled="!editable"
|
||
@click="removeEpisode(row)"
|
||
>删除</el-button
|
||
>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<p v-if="uploadingId !== null" class="creator-upload-hint"
|
||
>上传中 {{ uploadPercent }}%(分片上传,可断点续传)</p
|
||
>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<h3 class="creator-section-title">提交记录</h3>
|
||
<el-table
|
||
:data="submissions"
|
||
row-key="id"
|
||
border
|
||
class="drama-el-table"
|
||
empty-text="还没有提交记录"
|
||
>
|
||
<el-table-column prop="version" label="版本" width="70" />
|
||
<el-table-column label="类型" width="100">
|
||
<template #default="{ row }">{{ submissionTypeText(row.type) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag size="small" :type="submissionStatusMeta(row.status).tag" effect="light">
|
||
{{ submissionStatusMeta(row.status).label }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="submitAt"
|
||
label="提交时间"
|
||
min-width="165"
|
||
:formatter="dateCell"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="reviewAt"
|
||
label="审核时间"
|
||
min-width="165"
|
||
:formatter="dateCell"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="reviewNote" label="审核意见" min-width="200" show-overflow-tooltip />
|
||
<el-table-column label="操作" width="110" fixed="right">
|
||
<template #default="{ row }">
|
||
<div class="table-actions">
|
||
<el-button
|
||
v-if="canEdit && ['SUBMITTED', 'REVIEWING'].includes(row.status)"
|
||
type="danger"
|
||
size="small"
|
||
@click="withdraw(row.id)"
|
||
>撤回</el-button
|
||
>
|
||
<span v-else class="console-muted">—</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section>
|
||
|
||
<el-dialog
|
||
v-model="episodeVisible"
|
||
:title="episodeEditingId ? '编辑分集' : '新增分集'"
|
||
width="480px"
|
||
>
|
||
<el-form label-position="top">
|
||
<el-form-item label="分集标题">
|
||
<el-input v-model="episodeForm.title" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="时长(秒)">
|
||
<el-input-number
|
||
v-model="episodeForm.durationSec"
|
||
:min="0"
|
||
:max="36000"
|
||
class="console-number"
|
||
/>
|
||
<span class="console-muted">上传视频后会自动带出时长</span>
|
||
</el-form-item>
|
||
<el-form-item v-if="episodeEditingId" label="封面">
|
||
<div class="creator-cover-row">
|
||
<div class="creator-cover-preview">
|
||
<img v-if="episodeForm.cover" :src="episodeForm.cover" alt="" />
|
||
<span v-else>无</span>
|
||
</div>
|
||
<el-upload
|
||
v-if="canUpload"
|
||
:show-file-list="false"
|
||
accept="image/*"
|
||
:http-request="(options) => requestEpisodeCover({ id: episodeEditingId }, options)"
|
||
>
|
||
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
|
||
</el-upload>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item v-if="episodeEditingId" label="集号">
|
||
<el-input-number v-model="episodeForm.epNo" :min="1" :max="9999" class="console-number" />
|
||
</el-form-item>
|
||
<el-alert
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
title="免费 / 收费与试看秒数在「收费配置」页设置;新分集默认免费"
|
||
/>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="episodeVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="episodeSaving" @click="saveEpisode">保存</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</CreatorCenterPage>
|
||
</template>
|