feat(web): 07 创作者中心四个页面(我的作品 / 作品编辑 / 收费配置 / 数据看板)

为什么:07 的接口五批都做完了,但创作者在界面上一个都点不到 —— 只能靠 curl。
这批把创作者中心做进后台管理(沿用 AppUsersView 那套 api 封装 + composable + 视图 + 样式与 console.css),
创作者用自己的后台账号登录就能完成"建草稿 → 编辑分集传片 → 提交审核 → 配置收费 → 看数据"的闭环。

- WorkspaceView:主体状态 / 我的作品(含最近一次提交的审核进度)/ 通知列表;新建草稿、按状态给出可提交的动作、撤回
- DramaEditView:作品信息(封面、预告片)、分集增删与上移下移、分片上传(复用 /admin/media)+ 断点续传
  (会话存 localStorage,中断后「继续上传」接着传)、产物挂载、提交记录与撤回;非草稿/已下线状态整页转只读
- PriceConfigView:整剧免费集数与解锁金币 + 逐集免费/收费/试看秒数;已上线只读(需先申请下线)
- DashboardView:作品 / 播放 / 互动 / 解锁收入四组指标,取不到的显示「暂无数据源」+ notes 口径说明
- 时间列统一 yyyy-MM-dd HH:mm:ss;表格列与后端字段一一对应,不在前端编字段
- media.ts 补上传进度接口;uploader 补 resumeUpload(先查服务端进度再续传)
This commit is contained in:
Vicky committed 2026-10-15 14:47:28 +08:00
1 parent f037d243cd
commit 0a2e3e0b81
13 files changed
+2113 -1

No files matched your search

+295
View File
@@ -0,0 +1,295 @@
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { Search } from '@element-plus/icons-vue'
import PageHeading from '@/views/components/PageHeading.vue'
import { useRbac } from '@/stores/rbac'
import { dateCell } from '@/utils/format'
import {
DRAMA_STATUS,
dramaStatusMeta,
submissionStatusMeta,
submissionTypeText,
submitOptions,
useCreatorWorkspace
} from './composables/useCreatorWorkspace'
const router = useRouter()
const { can } = useRbac()
const canEdit = can('creator-center:drama:edit')
const {
profile,
profileLoading,
dramas,
notices,
createVisible,
createSaving,
createForm,
queryDramas,
loadDramas,
loadNotices,
submit,
withdraw,
openCreate,
confirmCreate
} = useCreatorWorkspace()
const activeTab = ref('dramas')
function onTabChange(name) {
if (name === 'notices' && !notices.loaded) loadNotices()
}
function openEdit(row) {
router.push(`/creator-center/dramas/${row.id}`)
}
</script>
<template>
<PageHeading
eyebrow="创作者中心"
title="我的作品"
description="查看作品与审核进度,提交上线 / 下线申请。"
>
<el-button v-if="canEdit" type="primary" @click="openCreate">新建作品</el-button>
</PageHeading>
<section v-loading="profileLoading" class="panel creator-profile">
<div class="creator-profile-main">
<span class="creator-profile-name">{{ profile?.name || '—' }}</span>
<el-tag
size="small"
effect="light"
:type="profile?.status === 'approved' ? 'success' : 'warning'"
>
{{ profile?.statusText || '—' }}
</el-tag>
<span class="console-muted">{{ profile?.code || '' }}</span>
</div>
<div class="creator-profile-meta">
<span
>作品数 <b>{{ profile?.dramaTotal ?? 0 }}</b></span
>
<span
>联系人 <b>{{ profile?.contactName || '—' }}</b></span
>
<span
>联系邮箱 <b>{{ profile?.contactEmail || '—' }}</b></span
>
</div>
<el-alert
v-if="profile && profile.status === 'rejected'"
class="creator-profile-alert"
type="error"
:closable="false"
show-icon
:title="`入驻审核未通过:${profile.auditNote || '未填写原因'}`"
/>
<el-alert
v-else-if="profile && profile.status !== 'approved'"
class="creator-profile-alert"
type="warning"
:closable="false"
show-icon
title="入驻审核通过后才能创建作品与提交审核"
/>
</section>
<section class="panel">
<el-tabs v-model="activeTab" @tab-change="onTabChange">
<el-tab-pane label="我的作品" name="dramas">
<div class="console-toolbar">
<el-input
v-model="dramas.keyword"
class="console-keyword"
:prefix-icon="Search"
clearable
placeholder="搜索作品名称"
@keyup.enter="queryDramas"
@clear="queryDramas"
/>
<el-select
v-model="dramas.status"
class="console-select"
clearable
placeholder="全部状态"
@change="queryDramas"
>
<el-option
v-for="item in DRAMA_STATUS"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button type="primary" @click="queryDramas">查询</el-button>
<span class="console-count">共 {{ dramas.total }} 部作品</span>
</div>
<el-table
v-loading="dramas.loading"
:data="dramas.rows"
row-key="id"
border
class="drama-el-table"
empty-text="还没有作品,点右上角「新建作品」开始"
>
<el-table-column label="作品" min-width="220">
<template #default="{ row }">
<div class="creator-drama-cell">
<div class="creator-drama-cover">
<img v-if="row.cover" :src="row.cover" alt="" />
<span v-else>{{ (row.title || '?').slice(0, 1) }}</span>
</div>
<div class="creator-drama-meta">
<span class="creator-drama-title">{{ row.title }}</span>
<span class="creator-drama-code">{{ row.code }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="dramaStatusMeta(row.status).tag" effect="light">{{
dramaStatusMeta(row.status).label
}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="episodes" label="分集" width="80" />
<el-table-column prop="views" label="播放" width="100" />
<el-table-column label="审核进度" min-width="200">
<template #default="{ row }">
<div v-if="row.submissionStatus" class="creator-progress">
<el-tag
size="small"
:type="submissionStatusMeta(row.submissionStatus).tag"
effect="plain"
>
{{ submissionTypeText(row.submissionType) }}·{{
submissionStatusMeta(row.submissionStatus).label
}}
</el-tag>
<span v-if="row.reviewNote" class="console-muted" :title="row.reviewNote">{{
row.reviewNote
}}</span>
</div>
<span v-else class="console-muted">尚未提交</span>
</template>
</el-table-column>
<el-table-column
prop="updatedAt"
label="更新时间"
min-width="165"
:formatter="dateCell"
show-overflow-tooltip
/>
<el-table-column label="操作" width="210" fixed="right">
<template #default="{ row }">
<div class="table-actions">
<el-button v-if="canEdit" type="primary" size="small" @click="openEdit(row)"
>编辑</el-button
>
<el-dropdown
v-if="canEdit && submitOptions(row).length"
trigger="click"
@command="(type) => submit(row, type)"
>
<el-button size="small">提交审核</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="type in submitOptions(row)"
:key="type"
:command="type"
>
{{ submissionTypeText(type) }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button
v-if="
canEdit &&
row.submissionId &&
['SUBMITTED', 'REVIEWING'].includes(row.submissionStatus)
"
size="small"
class="danger-action"
@click="withdraw(row)"
>撤回</el-button
>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="dramas.page"
v-model:page-size="dramas.size"
:page-sizes="[10, 20, 50]"
:total="dramas.total"
layout="total, prev, pager, next, sizes"
background
@current-change="loadDramas"
@size-change="queryDramas"
/>
</div>
</el-tab-pane>
<el-tab-pane label="通知" name="notices">
<div v-loading="notices.loading" class="creator-notice-list">
<el-empty v-if="!notices.rows.length" description="还没有审核结果通知" />
<div
v-for="item in notices.rows"
:key="`${item.bizType}-${item.id}`"
class="creator-notice"
>
<div class="creator-notice-head">
<span class="creator-notice-title">{{ item.title }}</span>
<span class="console-muted">{{
item.time ? String(item.time).replace('T', ' ').slice(0, 19) : ''
}}</span>
</div>
<p class="creator-notice-body">{{ item.content || '—' }}</p>
</div>
</div>
</el-tab-pane>
</el-tabs>
</section>
<el-dialog v-model="createVisible" title="新建作品" width="480px">
<el-form label-position="top">
<el-form-item label="作品名称" required>
<el-input
v-model="createForm.title"
maxlength="64"
placeholder="例如:闪婚后,傅先生每天都在撒糖"
/>
</el-form-item>
<el-form-item label="题材">
<el-input v-model="createForm.genre" maxlength="32" placeholder="例如:都市情感" />
</el-form-item>
<el-form-item label="简介">
<el-input
v-model="createForm.description"
type="textarea"
:rows="3"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-alert
type="info"
:closable="false"
show-icon
title="先建草稿,再去作品编辑页完善信息、分集与视频,然后提交审核"
/>
</el-form>
<template #footer>
<el-button @click="createVisible = false">取消</el-button>
<el-button type="primary" :loading="createSaving" @click="confirmCreate">创建草稿</el-button>
</template>
</el-dialog>
</template>