1
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
// 组件映射表:后端菜单返回的 component 路径需与此处 key 一致(不含 .vue)
|
||||
import HomeIndex from '../views/home/index.vue'
|
||||
import TestPage from '../views/test/test.vue'
|
||||
import SubscriptionDashboard from '../views/subscription/dashboard.vue'
|
||||
import SubscriptionUsers from '../views/subscription/users.vue'
|
||||
import SubscriptionPlans from '../views/subscription/plans.vue'
|
||||
import SubscriptionRecords from '../views/subscription/subscriptions.vue'
|
||||
@@ -15,7 +14,7 @@ const componentMap = {
|
||||
'home/index': HomeIndex,
|
||||
'home/index.vue': HomeIndex,
|
||||
'test/test': TestPage,
|
||||
'subscription/dashboard': SubscriptionDashboard,
|
||||
'subscription/dashboard': HomeIndex,
|
||||
'subscription/user': SubscriptionUsers,
|
||||
'subscription/plan': SubscriptionPlans,
|
||||
'subscription/subscription': SubscriptionRecords,
|
||||
|
||||
@@ -1,53 +1,277 @@
|
||||
<template>
|
||||
<div class="content-view">
|
||||
<div class="table-head-tool">
|
||||
<div class="table-title-row">
|
||||
<h2 class="table-title">首页</h2>
|
||||
<div class="sub-page dash-page">
|
||||
<header class="page-header">
|
||||
<div class="page-header__text">
|
||||
<h1 class="page-header__title">订阅运营看板</h1>
|
||||
<p class="page-header__desc">关键指标一览,点击刷新同步最新数据</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-body home-body">
|
||||
<Card dis-hover>
|
||||
<p class="home-tip">欢迎使用管理后台。</p>
|
||||
<p class="home-muted">菜单由 <code>sys_menu</code> 配置:<code>path</code> 与路由一致,<code>component</code> 与 <code>component-map.js</code> 的 key 一致(如 <code>home/index</code>)。</p>
|
||||
</Card>
|
||||
<Button type="primary" ghost class="page-header__btn" @click="load">
|
||||
<Icon type="md-refresh" /> 刷新
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<div class="dash-body">
|
||||
<Row v-if="stats" :gutter="28" class="dash-grid">
|
||||
<Col v-for="(item, idx) in statCards" :key="idx" :xs="24" :sm="12" :md="8" :lg="6">
|
||||
<Card dis-hover :bordered="false" :class="['stat-card', `stat-card--${item.tone}`]">
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<Icon :type="item.icon" class="stat-card__icon" />
|
||||
</div>
|
||||
<div class="stat-card__main">
|
||||
<p class="stat-card__label">{{ item.label }}</p>
|
||||
<p :class="['stat-card__value', item.warn && 'stat-card__value--warn']">{{ item.value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div v-else class="dash-loading">
|
||||
<Spin size="large" />
|
||||
<p class="dash-loading__txt">加载中…</p>
|
||||
</div>
|
||||
|
||||
<footer v-if="stats" class="dash-footer">
|
||||
<Icon type="md-time" class="dash-footer__icon" />
|
||||
<span>数据时间:{{ stats.server_time }}</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dashboardServer from '@/api/subscription/dashboard_server.js'
|
||||
|
||||
export default {
|
||||
name: 'HomeIndex',
|
||||
data() {
|
||||
return {
|
||||
stats: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
statCards() {
|
||||
if (!this.stats) return []
|
||||
const s = this.stats
|
||||
return [
|
||||
{ label: '业务用户总数', value: s.users.total, icon: 'md-people', tone: 'blue' },
|
||||
{ label: '正常用户', value: s.users.active, icon: 'md-checkmark-circle', tone: 'teal' },
|
||||
{ label: '上线套餐数', value: s.plans.active, icon: 'md-pricetags', tone: 'violet' },
|
||||
{ label: '有效 Token', value: s.tokens.active, icon: 'md-key', tone: 'amber' },
|
||||
{ label: '待支付订阅', value: s.subscriptions.pending, icon: 'md-cart', tone: 'orange' },
|
||||
{ label: '生效中订阅', value: s.subscriptions.active, icon: 'md-star', tone: 'green' },
|
||||
{ label: '已过期订阅', value: s.subscriptions.expired, icon: 'md-close-circle', tone: 'slate' },
|
||||
{
|
||||
label: '7 天内到期(活跃)',
|
||||
value: s.subscriptions.renew_within_7d,
|
||||
icon: 'md-notifications',
|
||||
tone: 'rose',
|
||||
warn: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
async load() {
|
||||
const res = await dashboardServer.summary()
|
||||
if (res && res.code === 0) {
|
||||
this.stats = res.data
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '加载失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-view {
|
||||
padding: 16px;
|
||||
<style lang="less" scoped>
|
||||
@sub-text: #17233d;
|
||||
@sub-text-secondary: #515a6e;
|
||||
@sub-muted: #808695;
|
||||
@sub-border: #e8eaec;
|
||||
@sub-error: #ed4014;
|
||||
|
||||
.dash-page {
|
||||
width: 100%;
|
||||
max-width: 1720px;
|
||||
margin: 0 auto;
|
||||
padding: 28px 40px 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.table-head-tool {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.table-title-row {
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
gap: 20px;
|
||||
margin-bottom: 28px;
|
||||
padding-bottom: 22px;
|
||||
border-bottom: 1px solid @sub-border;
|
||||
|
||||
&__title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: @sub-text;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: @sub-muted;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
:deep(.ivu-icon) {
|
||||
margin-right: 4px;
|
||||
vertical-align: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-title {
|
||||
|
||||
.dash-grid {
|
||||
:deep(.ivu-col) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.ivu-card) {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 2px rgba(23, 35, 61, 0.06);
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||
|
||||
:deep(.ivu-card-body) {
|
||||
padding: 22px 24px 24px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 6px 20px rgba(23, 35, 61, 0.09);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 18px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
flex-shrink: 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin: 0 0 6px;
|
||||
font-size: 13px;
|
||||
color: @sub-muted;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
&__value {
|
||||
margin: 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
color: @sub-text;
|
||||
line-height: 1.2;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
&--warn {
|
||||
color: @sub-error;
|
||||
}
|
||||
}
|
||||
|
||||
&--blue .stat-card__icon-wrap {
|
||||
background: rgba(45, 140, 240, 0.12);
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
&--teal .stat-card__icon-wrap {
|
||||
background: rgba(25, 190, 107, 0.12);
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
&--violet .stat-card__icon-wrap {
|
||||
background: rgba(114, 46, 209, 0.1);
|
||||
color: #722ed1;
|
||||
}
|
||||
|
||||
&--amber .stat-card__icon-wrap {
|
||||
background: rgba(255, 153, 0, 0.14);
|
||||
color: #ff9900;
|
||||
}
|
||||
|
||||
&--orange .stat-card__icon-wrap {
|
||||
background: rgba(255, 102, 0, 0.12);
|
||||
color: #ff6600;
|
||||
}
|
||||
|
||||
&--green .stat-card__icon-wrap {
|
||||
background: rgba(25, 190, 107, 0.14);
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
&--slate .stat-card__icon-wrap {
|
||||
background: rgba(128, 134, 149, 0.15);
|
||||
color: #515a6e;
|
||||
}
|
||||
|
||||
&--rose .stat-card__icon-wrap {
|
||||
background: rgba(237, 64, 20, 0.1);
|
||||
color: #ed4014;
|
||||
}
|
||||
}
|
||||
.home-body {
|
||||
max-width: 720px;
|
||||
|
||||
.dash-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 200px;
|
||||
color: @sub-muted;
|
||||
|
||||
&__txt {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.home-tip {
|
||||
margin: 0 0 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.home-muted {
|
||||
margin: 0;
|
||||
color: #808695;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
|
||||
.dash-footer {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 28px;
|
||||
padding: 10px 18px;
|
||||
font-size: 12px;
|
||||
color: @sub-muted;
|
||||
background: #f8f8f9;
|
||||
border-radius: 20px;
|
||||
border: 1px solid @sub-border;
|
||||
|
||||
&__icon {
|
||||
font-size: 15px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user