feat(admin): 增加工作台大屏和转码通知
This commit is contained in:
1 parent
2b2c6bd708
commit
a266d44483
10 files changed
+860
-172
No files matched your search
@@ -1,8 +1,9 @@
|
||||
<!-- Author: fruit.com.cn -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import PageHeading from '@/views/components/PageHeading.vue'
|
||||
import DashboardMetricWall from '@/views/dashboard/DashboardMetricWall.vue'
|
||||
import echarts from '@/plugins/echarts'
|
||||
import {
|
||||
getDashboardDistributionApi,
|
||||
@@ -16,22 +17,7 @@ import type {
|
||||
DashboardTrend,
|
||||
TranscodeBoard
|
||||
} from '@/api/admin/dashboard'
|
||||
import { dateCell, formatCount } from '@/utils/format'
|
||||
|
||||
/**
|
||||
* 指标展示配置:key 与后端 /admin/dashboard/overview 返回的字段一一对应,
|
||||
* 不在前端编字段;口径说明见后端 DashboardService 注释与 docs/api-phase1.md「2.8」。
|
||||
*/
|
||||
const HIGHLIGHT_CARDS = [
|
||||
{ group: 'user', key: 'total', label: '总用户数', color: '#5B8FF9' },
|
||||
{ group: 'user', key: 'newToday', label: '今日新增', color: '#5AD8A6' },
|
||||
{ group: 'content', key: 'dramaPublished', label: '已上线短剧', color: '#F6BD16' },
|
||||
{ group: 'content', key: 'episodeWithMedia', label: '有片源剧集', color: '#E86452' },
|
||||
{ group: 'playback', key: 'playTotal', label: '累计播放', color: '#6DC8EC' },
|
||||
{ group: 'playback', key: 'playEvents7d', label: '近 7 天播放', color: '#945FB9' },
|
||||
{ group: 'cost', key: 'transcodeRunning', label: '转码中', color: '#FF9845' },
|
||||
{ group: 'cost', key: 'transcodeQueued', label: '转码排队', color: '#1E9493' }
|
||||
]
|
||||
import { dateCell } from '@/utils/format'
|
||||
|
||||
const loading = ref(false)
|
||||
const overview = ref<DashboardOverview | null>(null)
|
||||
@@ -62,40 +48,6 @@ const transcodeStatusLabel = {
|
||||
CANCELED: '已取消'
|
||||
} as const
|
||||
|
||||
function groupValue(groupKey: string, metricKey: string) {
|
||||
const g = overview.value?.[groupKey as keyof DashboardOverview] as
|
||||
| Record<string, number>
|
||||
| undefined
|
||||
return g?.[metricKey]
|
||||
}
|
||||
|
||||
function display(value: unknown) {
|
||||
if (value === null || value === undefined) return '暂无数据源'
|
||||
if (typeof value === 'number' && !Number.isInteger(value)) return value.toFixed(3)
|
||||
return String(value)
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [board, t, dist, queue] = await Promise.all([
|
||||
getDashboardOverviewApi(),
|
||||
getDashboardTrendApi(),
|
||||
getDashboardDistributionApi(),
|
||||
getTranscodeBoardApi()
|
||||
])
|
||||
overview.value = board.data || null
|
||||
trend.value = t.data || null
|
||||
distribution.value = dist.data || null
|
||||
transcode.value = queue.data || null
|
||||
lastUpdated.value = new Date().toLocaleString()
|
||||
await nextTick()
|
||||
renderCharts()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function renderCharts() {
|
||||
disposeAll()
|
||||
renderTrend()
|
||||
@@ -280,26 +232,13 @@ onBeforeUnmount(() => {
|
||||
<PageHeading
|
||||
eyebrow="数据看板"
|
||||
title="运营大盘"
|
||||
description="用户、内容、播放与成本总览,以及近 7 天趋势。"
|
||||
description="用户、设备、留存、内容、播放与成本总览,以及近 7 天趋势。"
|
||||
>
|
||||
<el-button :loading="loading" @click="loadAndRender">刷新</el-button>
|
||||
</PageHeading>
|
||||
|
||||
<div v-loading="loading" class="board-wrap">
|
||||
<!-- 顶部关键指标卡 -->
|
||||
<div class="highlight-grid">
|
||||
<div
|
||||
v-for="card in HIGHLIGHT_CARDS"
|
||||
:key="card.group + card.key"
|
||||
class="highlight-card"
|
||||
:style="{ borderTopColor: card.color }"
|
||||
>
|
||||
<div class="highlight-label">{{ card.label }}</div>
|
||||
<div class="highlight-value" :class="{ empty: groupValue(card.group, card.key) === null }">
|
||||
{{ display(formatCount(groupValue(card.group, card.key))) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DashboardMetricWall :overview="overview" :trend="trend" />
|
||||
|
||||
<!-- 趋势折线图(双 Y 轴 + 柱) -->
|
||||
<section class="panel chart-panel">
|
||||
@@ -403,10 +342,10 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: var(--el-bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
@@ -434,41 +373,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.muted {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.highlight-card {
|
||||
background: var(--el-bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 14px 18px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-top: 3px solid transparent;
|
||||
|
||||
.highlight-label {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.highlight-value {
|
||||
margin-top: 6px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
&.empty {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,8 +420,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
.board-notes {
|
||||
:deep(ul) {
|
||||
margin: 4px 0 0;
|
||||
padding-left: 18px;
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user