|
@@ -52,29 +52,96 @@
|
|
|
</el-col>
|
|
</el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
+ <!-- 流量分析区块 -->
|
|
|
|
|
+ <el-row :gutter="16" class="section-row">
|
|
|
|
|
+ <el-col :xs="24" :md="6" v-for="(card, idx) in visitCards" :key="idx">
|
|
|
|
|
+ <el-card class="stat-card" shadow="never">
|
|
|
|
|
+ <div class="stat-icon-wrap" :class="'visit-' + card.color">
|
|
|
|
|
+ <i :class="card.icon"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-body">
|
|
|
|
|
+ <div class="stat-value">{{ card.value }}</div>
|
|
|
|
|
+ <div class="stat-label">{{ card.label }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="16" class="section-row">
|
|
|
|
|
+ <el-col :xs="24" :md="16">
|
|
|
|
|
+ <el-card class="chart-card" v-loading="trafficLoading">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span><i class="el-icon-data-line"></i> 近30天用户访问趋势</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div ref="trafficChart" class="echarts-container"></div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :xs="24" :md="8">
|
|
|
|
|
+ <el-card class="chart-card" v-loading="trafficLoading">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span><i class="el-icon-share"></i> 分享渠道分布</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div ref="sharePieChart" class="echarts-container"></div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="16" class="section-row">
|
|
|
|
|
+ <el-col :xs="24" :md="12">
|
|
|
|
|
+ <el-card class="hot-card" v-loading="trafficLoading">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span><i class="el-icon-top"></i> 访问热点(文章阅读量 Top5)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="hot-list">
|
|
|
|
|
+ <div class="hot-item" v-for="(item, idx) in hotArticles" :key="item.id">
|
|
|
|
|
+ <span class="hot-rank" :class="'rank-' + (idx + 1)">{{ idx + 1 }}</span>
|
|
|
|
|
+ <span class="hot-title" :title="item.title">{{ item.title }}</span>
|
|
|
|
|
+ <span class="hot-value"><i class="el-icon-view"></i> {{ item.viewCount || 0 }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="hotArticles.length === 0" class="hot-empty">暂无阅读数据</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :xs="24" :md="12">
|
|
|
|
|
+ <el-card class="hot-card" v-loading="trafficLoading">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span><i class="el-icon-shopping-cart-2"></i> 购买热点(近30天销量 Top5)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="hot-list">
|
|
|
|
|
+ <div class="hot-item" v-for="(item, idx) in hotProducts" :key="idx">
|
|
|
|
|
+ <span class="hot-rank" :class="'rank-' + (idx + 1)">{{ idx + 1 }}</span>
|
|
|
|
|
+ <span class="hot-title" :title="item.productName">{{ item.productName }}</span>
|
|
|
|
|
+ <span class="hot-value"><i class="el-icon-s-promotion"></i> {{ item.salesCount }}件</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="hotProducts.length === 0" class="hot-empty">暂无购买数据</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
<el-card class="section-card" v-loading="statLoading">
|
|
<el-card class="section-card" v-loading="statLoading">
|
|
|
<div slot="header" class="card-header">
|
|
<div slot="header" class="card-header">
|
|
|
<span><i class="el-icon-s-order"></i> 最近任务</span>
|
|
<span><i class="el-icon-s-order"></i> 最近任务</span>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="adm-table-wrap">
|
|
|
|
|
-<el-table :data="recentTasks" border stripe empty-text="暂无任务" size="small" height="100%">
|
|
|
|
|
- <el-table-column prop="id" label="ID" width="60" />
|
|
|
|
|
- <el-table-column prop="title" label="任务名称" min-width="160" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column prop="type" label="类型" width="80">
|
|
|
|
|
- <template slot-scope="{ row }">
|
|
|
|
|
- <el-tag :type="taskTypeTag(row.type)" size="mini">{{ row.type }}</el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="points" label="积分" width="60" />
|
|
|
|
|
- <el-table-column prop="status" label="状态" width="80">
|
|
|
|
|
- <template slot-scope="{ row }">
|
|
|
|
|
- <el-tag :type="taskStatusTag(row.status)" size="mini">{{ taskStatusLabel(row.status) }}</el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="创建时间" width="150">
|
|
|
|
|
- <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- </el-table>
|
|
|
|
|
|
|
+ <div class="adm-table-wrap">
|
|
|
|
|
+ <el-table :data="recentTasks" border stripe empty-text="暂无任务" size="small" height="100%">
|
|
|
|
|
+ <el-table-column prop="id" label="ID" width="60" />
|
|
|
|
|
+ <el-table-column prop="title" label="任务名称" min-width="160" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="type" label="类型" width="80">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <el-tag :type="taskTypeTag(row.type)" size="mini">{{ row.type }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="points" label="积分" width="60" />
|
|
|
|
|
+ <el-table-column prop="status" label="状态" width="80">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <el-tag :type="taskStatusTag(row.status)" size="mini">{{ taskStatusLabel(row.status) }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="创建时间" width="150">
|
|
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
</div>
|
|
</div>
|
|
|
</el-card>
|
|
</el-card>
|
|
|
</div>
|
|
</div>
|
|
@@ -82,7 +149,7 @@
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
import * as echarts from 'echarts'
|
|
import * as echarts from 'echarts'
|
|
|
-import { getDashboardStats, getRevenueStats, getMembershipStats, getTrendStats } from '@/api/admin'
|
|
|
|
|
|
|
+import { getDashboardStats, getRevenueStats, getMembershipStats, getTrendStats, getTrafficAnalytics } from '@/api/admin'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
name: 'AdminDashboard',
|
|
name: 'AdminDashboard',
|
|
@@ -92,15 +159,22 @@ export default {
|
|
|
trendLoading: false,
|
|
trendLoading: false,
|
|
|
memberLoading: false,
|
|
memberLoading: false,
|
|
|
revenueLoading: false,
|
|
revenueLoading: false,
|
|
|
|
|
+ trafficLoading: false,
|
|
|
statCards: [
|
|
statCards: [
|
|
|
{ label: '家庭总数', value: '-', icon: 'el-icon-s-home', color: 'orange' },
|
|
{ label: '家庭总数', value: '-', icon: 'el-icon-s-home', color: 'orange' },
|
|
|
{ label: '家长总数', value: '-', icon: 'el-icon-user', color: 'blue' },
|
|
{ label: '家长总数', value: '-', icon: 'el-icon-user', color: 'blue' },
|
|
|
- { label: '孩子总数', value: '-', icon: 'el-icon-s-custom', color: 'cyan' },
|
|
|
|
|
|
|
+ { label: '成员总数', value: '-', icon: 'el-icon-s-custom', color: 'cyan' },
|
|
|
{ label: '规划师总数', value: '-', icon: 'el-icon-school', color: 'green' },
|
|
{ label: '规划师总数', value: '-', icon: 'el-icon-school', color: 'green' },
|
|
|
{ label: '待审核规划师', value: '-', icon: 'el-icon-time', color: 'red' },
|
|
{ label: '待审核规划师', value: '-', icon: 'el-icon-time', color: 'red' },
|
|
|
{ label: '待审核任务模板', value: '-', icon: 'el-icon-document', color: 'purple' },
|
|
{ label: '待审核任务模板', value: '-', icon: 'el-icon-document', color: 'purple' },
|
|
|
{ label: '待退款审核', value: '-', icon: 'el-icon-warning', color: 'orange' }
|
|
{ label: '待退款审核', value: '-', icon: 'el-icon-warning', color: 'orange' }
|
|
|
],
|
|
],
|
|
|
|
|
+ visitCards: [
|
|
|
|
|
+ { label: '今日 PV', value: '-', icon: 'el-icon-view', color: 'blue' },
|
|
|
|
|
+ { label: '今日 UV', value: '-', icon: 'el-icon-user', color: 'green' },
|
|
|
|
|
+ { label: '本月 PV', value: '-', icon: 'el-icon-data-line', color: 'orange' },
|
|
|
|
|
+ { label: '本月 UV', value: '-', icon: 'el-icon-users', color: 'cyan' }
|
|
|
|
|
+ ],
|
|
|
revenueCards: [
|
|
revenueCards: [
|
|
|
{ label: '本月佣金', value: '-', icon: 'el-icon-money', color: 'orange', sub: '' },
|
|
{ label: '本月佣金', value: '-', icon: 'el-icon-money', color: 'orange', sub: '' },
|
|
|
{ label: '累计佣金', value: '-', icon: 'el-icon-coin', color: 'green', sub: '' },
|
|
{ label: '累计佣金', value: '-', icon: 'el-icon-coin', color: 'green', sub: '' },
|
|
@@ -109,8 +183,16 @@ export default {
|
|
|
recentTasks: [],
|
|
recentTasks: [],
|
|
|
trendData: { familyTrend: {}, userTrend: {} },
|
|
trendData: { familyTrend: {}, userTrend: {} },
|
|
|
membershipData: {},
|
|
membershipData: {},
|
|
|
|
|
+ hotArticles: [],
|
|
|
|
|
+ hotProducts: [],
|
|
|
|
|
+ sharePieData: [],
|
|
|
|
|
+ trafficDates: [],
|
|
|
|
|
+ pvData: [],
|
|
|
|
|
+ uvData: [],
|
|
|
trendChart: null,
|
|
trendChart: null,
|
|
|
memberChart: null,
|
|
memberChart: null,
|
|
|
|
|
+ trafficChart: null,
|
|
|
|
|
+ sharePieChart: null,
|
|
|
resizeHandler: null
|
|
resizeHandler: null
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -119,15 +201,20 @@ export default {
|
|
|
this.loadRevenue()
|
|
this.loadRevenue()
|
|
|
this.loadMembership()
|
|
this.loadMembership()
|
|
|
this.loadTrend()
|
|
this.loadTrend()
|
|
|
|
|
+ this.loadTraffic()
|
|
|
this.resizeHandler = () => {
|
|
this.resizeHandler = () => {
|
|
|
this.trendChart && this.trendChart.resize()
|
|
this.trendChart && this.trendChart.resize()
|
|
|
this.memberChart && this.memberChart.resize()
|
|
this.memberChart && this.memberChart.resize()
|
|
|
|
|
+ this.trafficChart && this.trafficChart.resize()
|
|
|
|
|
+ this.sharePieChart && this.sharePieChart.resize()
|
|
|
}
|
|
}
|
|
|
window.addEventListener('resize', this.resizeHandler)
|
|
window.addEventListener('resize', this.resizeHandler)
|
|
|
},
|
|
},
|
|
|
beforeDestroy() {
|
|
beforeDestroy() {
|
|
|
this.trendChart && this.trendChart.dispose()
|
|
this.trendChart && this.trendChart.dispose()
|
|
|
this.memberChart && this.memberChart.dispose()
|
|
this.memberChart && this.memberChart.dispose()
|
|
|
|
|
+ this.trafficChart && this.trafficChart.dispose()
|
|
|
|
|
+ this.sharePieChart && this.sharePieChart.dispose()
|
|
|
this.resizeHandler && window.removeEventListener('resize', this.resizeHandler)
|
|
this.resizeHandler && window.removeEventListener('resize', this.resizeHandler)
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
@@ -158,12 +245,12 @@ export default {
|
|
|
const res = await getRevenueStats()
|
|
const res = await getRevenueStats()
|
|
|
if (res.code === 200 && res.data) {
|
|
if (res.code === 200 && res.data) {
|
|
|
const d = res.data
|
|
const d = res.data
|
|
|
- this.revenueCards[0].value = '\u00a5' + (d.monthCommission || 0)
|
|
|
|
|
- this.revenueCards[0].sub = '\u5f85\u7ed3\u7b97: \u00a5' + (d.pendingCommission || 0)
|
|
|
|
|
- this.revenueCards[1].value = '\u00a5' + (d.totalCommission || 0)
|
|
|
|
|
|
|
+ this.revenueCards[0].value = '¥' + (d.monthCommission || 0)
|
|
|
|
|
+ this.revenueCards[0].sub = '待结算: ¥' + (d.pendingCommission || 0)
|
|
|
|
|
+ this.revenueCards[1].value = '¥' + (d.totalCommission || 0)
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- this.$message.error('\u52a0\u8f7d\u6536\u5165\u6570\u636e\u5931\u8d25')
|
|
|
|
|
|
|
+ this.$message.error('加载收入数据失败')
|
|
|
} finally {
|
|
} finally {
|
|
|
this.revenueLoading = false
|
|
this.revenueLoading = false
|
|
|
}
|
|
}
|
|
@@ -175,11 +262,11 @@ export default {
|
|
|
if (res.code === 200 && res.data) {
|
|
if (res.code === 200 && res.data) {
|
|
|
this.membershipData = res.data
|
|
this.membershipData = res.data
|
|
|
this.revenueCards[2].value = res.data.monthUpgrades || 0
|
|
this.revenueCards[2].value = res.data.monthUpgrades || 0
|
|
|
- this.revenueCards[2].sub = '\u8bd5\u7528\u4f1a\u5458: ' + (res.data.trialCount || 0)
|
|
|
|
|
|
|
+ this.revenueCards[2].sub = '试用会员: ' + (res.data.trialCount || 0)
|
|
|
this.$nextTick(() => this.initMemberPieChart())
|
|
this.$nextTick(() => this.initMemberPieChart())
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- this.$message.error('\u52a0\u8f7d\u4f1a\u5458\u6570\u636e\u5931\u8d25')
|
|
|
|
|
|
|
+ this.$message.error('加载会员数据失败')
|
|
|
} finally {
|
|
} finally {
|
|
|
this.memberLoading = false
|
|
this.memberLoading = false
|
|
|
}
|
|
}
|
|
@@ -193,11 +280,75 @@ export default {
|
|
|
this.$nextTick(() => this.initTrendChart())
|
|
this.$nextTick(() => this.initTrendChart())
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- this.$message.error('\u52a0\u8f7d\u8d8b\u52bf\u6570\u636e\u5931\u8d25')
|
|
|
|
|
|
|
+ this.$message.error('加载趋势数据失败')
|
|
|
} finally {
|
|
} finally {
|
|
|
this.trendLoading = false
|
|
this.trendLoading = false
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ async loadTraffic() {
|
|
|
|
|
+ this.trafficLoading = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getTrafficAnalytics()
|
|
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
|
|
+ const d = res.data
|
|
|
|
|
+ const vs = d.visitSummary || {}
|
|
|
|
|
+ this.visitCards[0].value = vs.todayPv || 0
|
|
|
|
|
+ this.visitCards[1].value = vs.todayUv || 0
|
|
|
|
|
+ this.visitCards[2].value = vs.monthPv || 0
|
|
|
|
|
+ this.visitCards[3].value = vs.monthUv || 0
|
|
|
|
|
+ const ch = d.shareSummary && d.shareSummary.channelStats ? Object.entries(d.shareSummary.channelStats) : []
|
|
|
|
|
+ this.sharePieData = ch.map(([name, value]) => ({ name, value }))
|
|
|
|
|
+ this.hotArticles = (d.hotArticleList || []).slice(0, 5)
|
|
|
|
|
+ this.hotProducts = (d.hotProductList || []).slice(0, 5)
|
|
|
|
|
+ this.trafficDates = d.trafficDates || []
|
|
|
|
|
+ this.pvData = d.pvData || []
|
|
|
|
|
+ this.uvData = d.uvData || []
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.initTrafficChart()
|
|
|
|
|
+ this.initSharePieChart()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.$message.error('加载流量分析数据失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.trafficLoading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ initTrafficChart() {
|
|
|
|
|
+ if (this.trafficChart) this.trafficChart.dispose()
|
|
|
|
|
+ this.trafficChart = echarts.init(this.$refs.trafficChart)
|
|
|
|
|
+ this.trafficChart.setOption({
|
|
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
|
|
+ legend: { data: ['PV', 'UV'], bottom: 0 },
|
|
|
|
|
+ grid: { left: 50, right: 20, bottom: 36, top: 20 },
|
|
|
|
|
+ xAxis: { type: 'category', data: this.trafficDates, axisLabel: { rotate: 45, interval: 2, fontSize: 10 } },
|
|
|
|
|
+ yAxis: { type: 'value' },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ { name: 'PV', type: 'bar', barWidth: '30%', data: this.pvData, itemStyle: { color: '#3B82F6', borderRadius: [4, 4, 0, 0] } },
|
|
|
|
|
+ { name: 'UV', type: 'line', smooth: true, data: this.uvData, lineStyle: { color: '#10B981', width: 2 }, itemStyle: { color: '#10B981' } }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ initSharePieChart() {
|
|
|
|
|
+ if (this.sharePieChart) this.sharePieChart.dispose()
|
|
|
|
|
+ this.sharePieChart = echarts.init(this.$refs.sharePieChart)
|
|
|
|
|
+ if (this.sharePieData.length === 0) {
|
|
|
|
|
+ this.sharePieData = [{ name: '暂无分享', value: 1 }]
|
|
|
|
|
+ }
|
|
|
|
|
+ this.sharePieChart.setOption({
|
|
|
|
|
+ tooltip: { trigger: 'item', formatter: '{b}: {c}次 ({d}%)' },
|
|
|
|
|
+ legend: { orient: 'vertical', left: 'left', top: 'center' },
|
|
|
|
|
+ series: [{
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: ['35%', '60%'],
|
|
|
|
|
+ center: ['60%', '50%'],
|
|
|
|
|
+ label: { show: false },
|
|
|
|
|
+ emphasis: { label: { show: true, fontSize: 13, fontWeight: 'bold' } },
|
|
|
|
|
+ data: this.sharePieData,
|
|
|
|
|
+ color: ['#F97316', '#3B82F6', '#10B981', '#8B5CF6', '#EC4899']
|
|
|
|
|
+ }]
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
initTrendChart() {
|
|
initTrendChart() {
|
|
|
if (this.trendChart) this.trendChart.dispose()
|
|
if (this.trendChart) this.trendChart.dispose()
|
|
|
this.trendChart = echarts.init(this.$refs.trendChart)
|
|
this.trendChart = echarts.init(this.$refs.trendChart)
|
|
@@ -206,13 +357,13 @@ export default {
|
|
|
const xAxis = Object.keys(this.trendData.familyTrend || {})
|
|
const xAxis = Object.keys(this.trendData.familyTrend || {})
|
|
|
this.trendChart.setOption({
|
|
this.trendChart.setOption({
|
|
|
tooltip: { trigger: 'axis' },
|
|
tooltip: { trigger: 'axis' },
|
|
|
- legend: { data: ['\u65b0\u589e\u5bb6\u5ead', '\u65b0\u589e\u7528\u6237'] },
|
|
|
|
|
|
|
+ legend: { data: ['新增家庭', '新增用户'] },
|
|
|
grid: { left: 50, right: 20, bottom: 30, top: 40 },
|
|
grid: { left: 50, right: 20, bottom: 30, top: 40 },
|
|
|
xAxis: { type: 'category', data: xAxis, axisLabel: { rotate: xAxis.length > 15 ? 45 : 0 } },
|
|
xAxis: { type: 'category', data: xAxis, axisLabel: { rotate: xAxis.length > 15 ? 45 : 0 } },
|
|
|
yAxis: { type: 'value', minInterval: 1 },
|
|
yAxis: { type: 'value', minInterval: 1 },
|
|
|
series: [
|
|
series: [
|
|
|
- { name: '\u65b0\u589e\u5bb6\u5ead', type: 'bar', barWidth: '40%', data: familyData, itemStyle: { color: '#F97316', borderRadius: [4, 4, 0, 0] } },
|
|
|
|
|
- { name: '\u65b0\u589e\u7528\u6237', type: 'line', smooth: true, data: userData, lineStyle: { color: '#0EA5E9', width: 2 }, itemStyle: { color: '#0EA5E9' } }
|
|
|
|
|
|
|
+ { name: '新增家庭', type: 'bar', barWidth: '40%', data: familyData, itemStyle: { color: '#F97316', borderRadius: [4, 4, 0, 0] } },
|
|
|
|
|
+ { name: '新增用户', type: 'line', smooth: true, data: userData, lineStyle: { color: '#0EA5E9', width: 2 }, itemStyle: { color: '#0EA5E9' } }
|
|
|
]
|
|
]
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
@@ -230,9 +381,9 @@ export default {
|
|
|
label: { show: false },
|
|
label: { show: false },
|
|
|
emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
|
|
emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
|
|
|
data: [
|
|
data: [
|
|
|
- { value: this.membershipData.freeCount || 0, name: '\u514d\u8d39\u7528\u6237', itemStyle: { color: '#94A3B8' } },
|
|
|
|
|
- { value: this.membershipData.familyCount || 0, name: '\u5bb6\u5ead\u4f1a\u5458', itemStyle: { color: '#F97316' } },
|
|
|
|
|
- { value: this.membershipData.providerCount || 0, name: '\u670d\u52a1\u5546', itemStyle: { color: '#10B981' } }
|
|
|
|
|
|
|
+ { value: this.membershipData.freeCount || 0, name: '免费用户', itemStyle: { color: '#94A3B8' } },
|
|
|
|
|
+ { value: this.membershipData.familyCount || 0, name: '家庭会员', itemStyle: { color: '#F97316' } },
|
|
|
|
|
+ { value: this.membershipData.providerCount || 0, name: '服务商', itemStyle: { color: '#10B981' } }
|
|
|
]
|
|
]
|
|
|
}]
|
|
}]
|
|
|
})
|
|
})
|
|
@@ -246,10 +397,12 @@ export default {
|
|
|
return map[status] || 'info'
|
|
return map[status] || 'info'
|
|
|
},
|
|
},
|
|
|
taskStatusLabel(status) {
|
|
taskStatusLabel(status) {
|
|
|
- const map = { pending: '\u5f85\u5ba1\u6838', approved: '\u5df2\u901a\u8fc7', completed: '\u5df2\u5b8c\u6210', rejected: '\u5df2\u62d2\u7edd' }
|
|
|
|
|
|
|
+ const map = { pending: '待审核', approved: '已通过', completed: '已完成', rejected: '已拒绝' }
|
|
|
return map[status] || status
|
|
return map[status] || status
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
|
|
+ formatTime(t) {
|
|
|
|
|
+ return t ? t.replace('T', ' ').substring(0, 19) : '-'
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
@@ -292,6 +445,26 @@ export default {
|
|
|
.revenue-green { color: #10B981; }
|
|
.revenue-green { color: #10B981; }
|
|
|
.revenue-blue { color: #3B82F6; }
|
|
.revenue-blue { color: #3B82F6; }
|
|
|
.revenue-sub { font-size: 13px; color: #94A3B8; margin-top: 6px; }
|
|
.revenue-sub { font-size: 13px; color: #94A3B8; margin-top: 6px; }
|
|
|
|
|
+.hot-card { min-height: 200px; }
|
|
|
|
|
+.hot-list { padding: 8px 0; }
|
|
|
|
|
+.hot-item {
|
|
|
|
|
+ display: flex; align-items: center; padding: 8px 12px;
|
|
|
|
|
+ border-bottom: 1px solid #f5f5f5; transition: background 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+.hot-item:hover { background: #fafafa; }
|
|
|
|
|
+.hot-item:last-child { border-bottom: none; }
|
|
|
|
|
+.hot-rank {
|
|
|
|
|
+ width: 24px; height: 24px; border-radius: 50%;
|
|
|
|
|
+ display: inline-flex; align-items: center; justify-content: center;
|
|
|
|
|
+ font-size: 12px; font-weight: 700; margin-right: 12px; flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.rank-1 { background: #FEF3C7; color: #F59E0B; }
|
|
|
|
|
+.rank-2 { background: #F1F5F9; color: #64748B; }
|
|
|
|
|
+.rank-3 { background: #FFF7ED; color: #EA580C; }
|
|
|
|
|
+.rank-other { background: #F8FAFC; color: #94A3B8; }
|
|
|
|
|
+.hot-title { flex: 1; font-size: 13px; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
+.hot-value { font-size: 13px; color: #64748B; margin-left: 8px; }
|
|
|
|
|
+.hot-empty { text-align: center; color: #94A3B8; padding: 20px 0; font-size: 13px; }
|
|
|
@media (max-width: 768px) {
|
|
@media (max-width: 768px) {
|
|
|
.dashboard-container { padding: 12px; }
|
|
.dashboard-container { padding: 12px; }
|
|
|
.stat-card { padding: 12px 10px; }
|
|
.stat-card { padding: 12px 10px; }
|