Files
ReelShortWeb/src/views/common/DashboardView.vue
T

117 lines
4.0 KiB
Vue

<!-- Author: fruit.com.cn -->
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { AppIcons } from '@/constants/icons'
import { pageDramasApi } from '@/api/admin/drama'
import { useRbac } from '@/stores/rbac'
import { formatCount } from '@/utils/format'
import { statusClass } from '@/utils/status'
const router = useRouter()
const { can } = useRbac()
const loading = ref(false)
const dramas = ref([])
const total = ref(0)
const totalViews = computed(() =>
dramas.value.reduce((sum, item) => sum + Number(item.views || 0), 0)
)
const reviewingCount = computed(
() => dramas.value.filter((item) => item.status === 'reviewing').length
)
const publishedCount = computed(
() => dramas.value.filter((item) => item.status === 'published').length
)
const recentDramas = computed(() =>
[...dramas.value]
.sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')))
.slice(0, 5)
)
onMounted(async () => {
loading.value = true
try {
const res = await pageDramasApi({ pageIndex: 1, pageSize: 200 })
// 接口失败时 axios 拦截器会把业务错误吞成 undefined,这里不能让页面跟着崩
dramas.value = res?.data?.list || []
total.value = res?.data?.total || 0
} finally {
loading.value = false
}
})
</script>
<template>
<div class="page-heading"
><div><div class="eyebrow">内容概览</div><h1>工作台</h1><p>查看短剧内容与近期处理事项。</p></div
><el-button
v-if="can('drama:create')"
type="primary"
class="primary-button"
@click="router.push({ name: 'dramas' })"
><AppIcon :icon="AppIcons.add" />上传新短剧</el-button
></div
>
<div v-loading="loading" class="stats-grid">
<div class="stat-card"
><div class="stat-top"
><span>短剧总数</span
><span class="stat-icon"><AppIcon :icon="AppIcons.content" /></span></div
><div class="stat-value">{{ total }} <small>部</small></div></div
>
<div class="stat-card"
><div class="stat-top"
><span>累计播放量</span
><span class="stat-icon"><AppIcon :icon="AppIcons.videoPlay" /></span></div
><div class="stat-value">{{ formatCount(totalViews) }}</div></div
>
<div class="stat-card"
><div class="stat-top"
><span>待审核内容</span
><span class="stat-icon"><AppIcon :icon="AppIcons.clock" /></span></div
><div class="stat-value">{{ reviewingCount }} <small>项</small></div></div
>
<div class="stat-card"
><div class="stat-top"
><span>已上线</span
><span class="stat-icon"><AppIcon :icon="AppIcons.checkCircle" /></span></div
><div class="stat-value">{{ publishedCount }} <small>部</small></div></div
>
</div>
<section class="panel recent-panel"
><div class="panel-heading"
><div><h2>最近更新</h2><p>短剧内容与发布状态</p></div
><el-button text class="view-all" @click="router.push({ name: 'dramas' })"
>短剧管理</el-button
></div
>
<el-table
v-loading="loading"
:data="recentDramas"
row-key="id"
empty-text="暂无短剧数据"
class="dashboard-el-table"
><el-table-column prop="title" label="短剧名称" min-width="280" /><el-table-column
prop="genre"
label="类型"
width="150" /><el-table-column label="集数" width="120"
><template #default="scope">{{ scope?.row?.episodes }} 集</template></el-table-column
><el-table-column label="播放量" width="140"
><template #default="scope">{{ formatCount(scope?.row?.views) }}</template></el-table-column
><el-table-column label="状态" width="130"
><template #default="scope"
><el-tag
v-if="scope?.row"
:class="['status-badge', statusClass(scope.row.status)]"
effect="light"
>{{ scope.row.statusText }}</el-tag
></template
></el-table-column
><el-table-column prop="updatedAt" label="更新时间" width="180"
/></el-table>
</section>
</template>