1
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
// 组件映射表:后端菜单返回的 component 路径需与此处 key 一致(不含 .vue)
|
// 组件映射表:后端菜单返回的 component 路径需与此处 key 一致(不含 .vue)
|
||||||
import HomeIndex from '../views/home/index.vue'
|
import HomeIndex from '../views/home/index.vue'
|
||||||
import TestPage from '../views/test/test.vue'
|
import TestPage from '../views/test/test.vue'
|
||||||
import SubscriptionDashboard from '../views/subscription/dashboard.vue'
|
|
||||||
import SubscriptionUsers from '../views/subscription/users.vue'
|
import SubscriptionUsers from '../views/subscription/users.vue'
|
||||||
import SubscriptionPlans from '../views/subscription/plans.vue'
|
import SubscriptionPlans from '../views/subscription/plans.vue'
|
||||||
import SubscriptionRecords from '../views/subscription/subscriptions.vue'
|
import SubscriptionRecords from '../views/subscription/subscriptions.vue'
|
||||||
@@ -15,7 +14,7 @@ const componentMap = {
|
|||||||
'home/index': HomeIndex,
|
'home/index': HomeIndex,
|
||||||
'home/index.vue': HomeIndex,
|
'home/index.vue': HomeIndex,
|
||||||
'test/test': TestPage,
|
'test/test': TestPage,
|
||||||
'subscription/dashboard': SubscriptionDashboard,
|
'subscription/dashboard': HomeIndex,
|
||||||
'subscription/user': SubscriptionUsers,
|
'subscription/user': SubscriptionUsers,
|
||||||
'subscription/plan': SubscriptionPlans,
|
'subscription/plan': SubscriptionPlans,
|
||||||
'subscription/subscription': SubscriptionRecords,
|
'subscription/subscription': SubscriptionRecords,
|
||||||
|
|||||||
@@ -1,53 +1,277 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-view">
|
<div class="sub-page dash-page">
|
||||||
<div class="table-head-tool">
|
<header class="page-header">
|
||||||
<div class="table-title-row">
|
<div class="page-header__text">
|
||||||
<h2 class="table-title">首页</h2>
|
<h1 class="page-header__title">订阅运营看板</h1>
|
||||||
|
<p class="page-header__desc">关键指标一览,点击刷新同步最新数据</p>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
</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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import dashboardServer from '@/api/subscription/dashboard_server.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HomeIndex',
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="less" scoped>
|
||||||
.content-view {
|
@sub-text: #17233d;
|
||||||
padding: 16px;
|
@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;
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
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-row {
|
|
||||||
|
.dash-grid {
|
||||||
|
:deep(.ivu-col) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ivu-card) {
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
margin: 0;
|
||||||
|
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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
margin-bottom: 12px;
|
}
|
||||||
}
|
|
||||||
.table-title {
|
&__icon {
|
||||||
margin: 0;
|
font-size: 22px;
|
||||||
font-size: 18px;
|
}
|
||||||
}
|
|
||||||
.home-body {
|
&__label {
|
||||||
max-width: 720px;
|
margin: 0 0 6px;
|
||||||
}
|
|
||||||
.home-tip {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.home-muted {
|
|
||||||
margin: 0;
|
|
||||||
color: #808695;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.6;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user