feat(web): 08 后台四个页面落地(内容治理 / 订单与权益 / 创作者审核 / 数据看板)
为什么:08 运营后台的接口在前几轮已经实现,但运营在界面上点不到—— 评论审核、举报工单、敏感词维护、订单补发/退款、权益回收、金币补发、对账、 创作者入驻与内容提交审核、运营大盘,都只能靠 curl。本轮把这四个页面补齐, 08 才算真正闭环。 - 内容治理:评论审核(通过/驳回/删除)、举报工单处理(可联动封禁)、敏感词库增删改查 - 订单与权益:订单检索与流水、补发、退款、权益检索/人工补发/回收、金币流水与人工补币、对账运行与历史 - 创作者审核:入驻审核(通过/驳回原因码/冻结)+ 资质抽屉;内容提交审核(通过/驳回) - 数据看板:overview 五组指标 + transcode 队列与失败任务,只读;取不到的指标显示「暂无数据源」而不是 0 - 新增 console.css(工具条/抽屉/看板卡片,含深色模式),四个页面共用;时间列统一 yyyy-MM-dd HH:mm:ss - 按钮按权限点显隐(没有 *:update / *:audit 的角色看到的是只读态),表格列与后端字段一一对应
This commit is contained in:
1 parent
44c2c46a68
commit
f037d243cd
13 files changed
+2680
No files matched your search
@@ -0,0 +1,180 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import PageHeading from '@/views/components/PageHeading.vue'
|
||||
import { getDashboardOverviewApi, getTranscodeBoardApi } from '@/api/admin/dashboard'
|
||||
import { dateCell } from '@/utils/format'
|
||||
|
||||
/**
|
||||
* 指标展示配置:key 与后端 /admin/dashboard/overview 返回的字段一一对应,
|
||||
* 不在前端编字段;口径说明见后端 DashboardService 注释与 docs/api-phase1.md「2.8」。
|
||||
*/
|
||||
const GROUPS = [
|
||||
{
|
||||
key: 'user',
|
||||
title: '用户',
|
||||
metrics: [
|
||||
{ key: 'total', label: '总用户数' },
|
||||
{ key: 'newToday', label: '今日新增' },
|
||||
{ key: 'new7d', label: '近 7 天新增' },
|
||||
{ key: 'active7d', label: '近 7 天活跃' },
|
||||
{ key: 'banned', label: '封禁' },
|
||||
{ key: 'limited', label: '受限' },
|
||||
{ key: 'deactivated', label: '已注销' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'content',
|
||||
title: '内容',
|
||||
metrics: [
|
||||
{ key: 'dramaTotal', label: '短剧总数' },
|
||||
{ key: 'dramaPublished', label: '已上线短剧' },
|
||||
{ key: 'dramaReviewing', label: '待审短剧' },
|
||||
{ key: 'dramaDraft', label: '草稿短剧' },
|
||||
{ key: 'dramaOffline', label: '已下线短剧' },
|
||||
{ key: 'episodeTotal', label: '剧集总数' },
|
||||
{ key: 'episodePublished', label: '已上线剧集' },
|
||||
{ key: 'episodeWithMedia', label: '有片源剧集' },
|
||||
{ key: 'commentPending', label: '待审评论' },
|
||||
{ key: 'reportPending', label: '待处理举报' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'playback',
|
||||
title: '播放',
|
||||
metrics: [
|
||||
{ key: 'viewTotal', label: '累计观看' },
|
||||
{ key: 'playTotal', label: '累计播放' },
|
||||
{ key: 'watchedDramas', label: '看过的剧(去重)' },
|
||||
{ key: 'finishedDramas', label: '看过且完播' },
|
||||
{ key: 'finishRate', label: '完播率' },
|
||||
{ key: 'playEvents7d', label: '近 7 天播放流水' },
|
||||
{ key: 'playEventsTotal', label: '播放流水总量' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'revenue',
|
||||
title: '收入',
|
||||
metrics: [
|
||||
{ key: 'orderTotal', label: '订单总数' },
|
||||
{ key: 'orderDelivered', label: '已交付订单' },
|
||||
{ key: 'orderRefunded', label: '已退款订单' },
|
||||
{ key: 'paidAmount', label: '已支付金额' },
|
||||
{ key: 'coinRecharged', label: '金币充值' },
|
||||
{ key: 'coinConsumed', label: '金币消耗' },
|
||||
{ key: 'coinBalance', label: '金币余额' },
|
||||
{ key: 'entitlementActive', label: '有效权益' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'cost',
|
||||
title: '成本',
|
||||
metrics: [
|
||||
{ key: 'transcodeQueued', label: '转码排队' },
|
||||
{ key: 'transcodeRunning', label: '转码中' },
|
||||
{ key: 'transcodeFailed', label: '转码失败' },
|
||||
{ key: 'transcodeSuccess', label: '转码成功' },
|
||||
{ key: 'renditions', label: '多码率产物' },
|
||||
{ key: 'otpSent7d', label: '近 7 天验证码' },
|
||||
{ key: 'cdnTrafficBytes', label: 'CDN 流量' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const loading = ref(false)
|
||||
const overview = ref(null)
|
||||
const transcode = ref(null)
|
||||
const lastUpdated = ref('')
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [board, queue] = await Promise.all([getDashboardOverviewApi(), getTranscodeBoardApi()])
|
||||
overview.value = board.data || null
|
||||
transcode.value = queue.data || null
|
||||
lastUpdated.value = new Date().toLocaleString()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 取不到的指标后端返回 null,这里显示「暂无数据源」而不是 0 */
|
||||
function display(value) {
|
||||
if (value === null || value === undefined) return '暂无数据源'
|
||||
if (typeof value === 'number' && !Number.isInteger(value)) return value.toFixed(3)
|
||||
return value
|
||||
}
|
||||
|
||||
function groupValue(groupKey, metricKey) {
|
||||
return overview.value?.[groupKey]?.[metricKey]
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeading
|
||||
eyebrow="数据看板"
|
||||
title="运营大盘"
|
||||
description="用户、内容、播放、收入与成本总览,以及转码队列。"
|
||||
>
|
||||
<el-button :loading="loading" @click="load">刷新</el-button>
|
||||
</PageHeading>
|
||||
|
||||
<section v-loading="loading" class="panel">
|
||||
<p class="console-hint"
|
||||
>数据时间:{{ lastUpdated }}(口径见每组的后端定义,缺数据源显示「暂无数据源」)</p
|
||||
>
|
||||
<div v-for="group in GROUPS" :key="group.key" class="board-group">
|
||||
<h3 class="board-title">{{ group.title }}</h3>
|
||||
<div class="board-grid">
|
||||
<div v-for="metric in group.metrics" :key="metric.key" class="board-card">
|
||||
<span class="board-label">{{ metric.label }}</span>
|
||||
<span
|
||||
class="board-value"
|
||||
:class="{ 'board-value-empty': groupValue(group.key, metric.key) === null }"
|
||||
>
|
||||
{{ display(groupValue(group.key, metric.key)) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="overview?.notes?.length"
|
||||
class="board-notes"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="口径与缺失数据源说明"
|
||||
>
|
||||
<ul>
|
||||
<li v-for="note in overview.notes" :key="note">{{ note }}</li>
|
||||
</ul>
|
||||
</el-alert>
|
||||
</section>
|
||||
|
||||
<section class="panel board-transcode">
|
||||
<h3 class="board-title">转码看板</h3>
|
||||
<p class="console-hint">
|
||||
{{ transcode?.notes?.join(';') || '' }}
|
||||
</p>
|
||||
<el-table
|
||||
:data="transcode?.failedTasks || []"
|
||||
border
|
||||
class="drama-el-table"
|
||||
empty-text="当前没有失败的转码任务"
|
||||
>
|
||||
<el-table-column prop="id" label="任务ID" width="90" />
|
||||
<el-table-column prop="episodeId" label="剧集ID" width="90" />
|
||||
<el-table-column prop="status" label="状态" width="110" />
|
||||
<el-table-column prop="retryCount" label="重试次数" width="100" />
|
||||
<el-table-column prop="errorMsg" label="失败原因" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
label="创建时间"
|
||||
min-width="165"
|
||||
show-overflow-tooltip
|
||||
:formatter="dateCell"
|
||||
/>
|
||||
</el-table>
|
||||
</section>
|
||||
</template>
|
||||
Reference in new issue
Block a user