main
parent
12f80d3310
commit
3c7863a90a
@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div class="order-type-chart">
|
||||||
|
<div class="chart-header">
|
||||||
|
<h3>订单类型分布</h3>
|
||||||
|
</div>
|
||||||
|
<v-chart class="chart" :option="chartOption" autoresize />
|
||||||
|
<!-- <div class="legend">
|
||||||
|
<div class="legend-item" v-for="item in legendData" :key="item.name">
|
||||||
|
<span class="legend-color" :style="{ backgroundColor: item.color }"></span>
|
||||||
|
<span class="legend-text">{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { use } from 'echarts/core'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import { PieChart } from 'echarts/charts'
|
||||||
|
import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
|
||||||
|
import VChart from 'vue-echarts'
|
||||||
|
import RPC from '@/utils/rpc'
|
||||||
|
|
||||||
|
use([CanvasRenderer, PieChart, TitleComponent, TooltipComponent, LegendComponent])
|
||||||
|
|
||||||
|
const legendData = ref([
|
||||||
|
{ name: '充值', color: '#409eff' },
|
||||||
|
{ name: '包月', color: '#67c23a' },
|
||||||
|
{ name: '包季', color: '#e6a23c' },
|
||||||
|
{ name: '包年', color: '#f56c6c' },
|
||||||
|
])
|
||||||
|
|
||||||
|
RPC.post('/dashboard/order_type_distribution').then((res) => {
|
||||||
|
console.log('order_type_distribution', res)
|
||||||
|
chartData.value = res.items.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item.type,
|
||||||
|
value: item.value_cents / 100,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartData = ref([
|
||||||
|
// { value: 35, name: '充值', itemStyle: { color: '#409eff' } },
|
||||||
|
// { value: 25, name: '包月', itemStyle: { color: '#67c23a' } },
|
||||||
|
// { value: 25, name: '包季', itemStyle: { color: '#e6a23c' } },
|
||||||
|
// { value: 15, name: '包年', itemStyle: { color: '#f56c6c' } },
|
||||||
|
])
|
||||||
|
|
||||||
|
const chartOption = ref({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b}: {c}% ({d}%)',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: '0%',
|
||||||
|
left: 'center',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '订单类型',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['50%', '80%'],
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
data: chartData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.order-type-chart {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
height: 300px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.legend-color {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="recent-orders">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>最近订单</h3>
|
||||||
|
<el-button type="primary" size="small" plain @click="goAll">查看全部</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="table-container">
|
||||||
|
<el-table :data="orderData" style="width: 100%" size="small" height="270">
|
||||||
|
<el-table-column align="center" prop="order_no" label="订单编号" show-overflow-tooltip />
|
||||||
|
<el-table-column align="center" prop="user_name" label="用户" show-overflow-tooltip />
|
||||||
|
<el-table-column align="center" prop="amount" label="金额" width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
<span class="amount">{{ scope.row.amount }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" prop="order_type" label="类型" width="100">
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" prop="status" label="状态" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.status === 'created'" type="warning" size="small">待付款</el-tag>
|
||||||
|
<el-tag v-if="row.status === 'pending'" type="warning" size="small">待支付</el-tag>
|
||||||
|
<el-tag v-if="row.status === 'paid'" type="success" size="small">已支付</el-tag>
|
||||||
|
<el-tag v-if="row.status === 'failed'" type="danger" size="small">支付失败</el-tag>
|
||||||
|
<el-tag v-if="row.status === 'canceled'" type="info" size="small">已取消</el-tag>
|
||||||
|
<el-tag v-if="row.status === 'refunded'" type="info" size="small">已退款</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import RPC from '@/utils/rpc'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
const router = useRouter()
|
||||||
|
const goAll = () => router.push('/OrderManage')
|
||||||
|
|
||||||
|
const orderData = ref([])
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const getList = () => {
|
||||||
|
loading.value = true
|
||||||
|
RPC.post('/membership/order/list', { page: 1, size: 10 })
|
||||||
|
.then((res) => {
|
||||||
|
orderData.value = res.list
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getList()
|
||||||
|
|
||||||
|
const getTypeTagType = (type) => {
|
||||||
|
const typeMap = {
|
||||||
|
包年: '',
|
||||||
|
包季: 'success',
|
||||||
|
充值: 'info',
|
||||||
|
包月: 'warning',
|
||||||
|
}
|
||||||
|
return typeMap[type] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusTagType = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
已支付: 'success',
|
||||||
|
待支付: 'warning',
|
||||||
|
已取消: 'danger',
|
||||||
|
}
|
||||||
|
return statusMap[status] || ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.recent-orders {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
.amount {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
.el-table__header {
|
||||||
|
th {
|
||||||
|
background-color: #fafafa;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table__row {
|
||||||
|
&:hover {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="revenue-chart">
|
||||||
|
<div class="chart-header">
|
||||||
|
<h3>收入趋势</h3>
|
||||||
|
<div class="chart-tabs">
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:class="{ active: activeTab === 'month' }"
|
||||||
|
@click="switchTab('month')"
|
||||||
|
>
|
||||||
|
月度
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:class="{ active: activeTab === 'quarter' }"
|
||||||
|
@click="switchTab('quarter')"
|
||||||
|
>
|
||||||
|
季度
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:class="{ active: activeTab === 'year' }"
|
||||||
|
@click="switchTab('year')"
|
||||||
|
>
|
||||||
|
年度
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<v-chart class="chart" :option="chartOption" autoresize />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { use } from 'echarts/core'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import { LineChart } from 'echarts/charts'
|
||||||
|
import {
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
LegendComponent,
|
||||||
|
GridComponent,
|
||||||
|
} from 'echarts/components'
|
||||||
|
import VChart from 'vue-echarts'
|
||||||
|
import RPC from '@/utils/rpc'
|
||||||
|
|
||||||
|
use([CanvasRenderer, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||||
|
|
||||||
|
const activeTab = ref('month')
|
||||||
|
|
||||||
|
const switchTab = (tab) => {
|
||||||
|
activeTab.value = tab
|
||||||
|
console.log(activeTab.value)
|
||||||
|
getData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ref([])
|
||||||
|
const getData = () => {
|
||||||
|
RPC.post('/dashboard/revenue_trend', { granularity: activeTab.value }).then((res) => {
|
||||||
|
console.log(activeTab.value, res)
|
||||||
|
data.value = res.items || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getData()
|
||||||
|
|
||||||
|
const chartOption = computed(() => {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: '{b}: ¥{c}',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: data.value.map((item) => item.label),
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e4e7ed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#606266',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#606266',
|
||||||
|
formatter: '{value}',
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#f5f7fa',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
data: data.value.map((item) => item.value_cents / 100),
|
||||||
|
smooth: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#409eff',
|
||||||
|
width: 3,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#409eff',
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(64, 158, 255, 0.3)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(64, 158, 255, 0.1)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.revenue-chart {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-tabs {
|
||||||
|
.el-button {
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #157ee8;
|
||||||
|
border-color: #177de3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
height: 300px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<!-- 统计卡片 -->
|
||||||
|
<div class="stats-cards">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">总用户数</div>
|
||||||
|
<div class="stat-value">{{ data.total_users }}</div>
|
||||||
|
<div
|
||||||
|
class="stat-trend"
|
||||||
|
:class="{
|
||||||
|
positive: data.total_users_mom_ratio?.includes('+'),
|
||||||
|
negative:
|
||||||
|
data.total_users_mom_ratio?.includes('-') &&
|
||||||
|
data.total_users_mom_ratio.length > 1,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-icon v-if="total_users_mom_delta > 0"><ArrowUp /></el-icon>
|
||||||
|
<el-icon v-if="total_users_mom_delta < 0"><ArrowDown /></el-icon>
|
||||||
|
{{ data.total_users_mom_ratio }} 较上月
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon user">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">今日活跃</div>
|
||||||
|
<div class="stat-value">{{ data.active_today }}</div>
|
||||||
|
<div
|
||||||
|
class="stat-trend"
|
||||||
|
:class="{
|
||||||
|
positive: data.active_today_dod_ratio?.includes('+'),
|
||||||
|
negative:
|
||||||
|
data.active_today_dod_ratio?.includes('-') &&
|
||||||
|
data.active_today_dod_ratio.length > 1,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-icon><ArrowDown /></el-icon>
|
||||||
|
{{ data.active_today_dod_ratio }} 较昨日
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon active">
|
||||||
|
<el-icon><TrendCharts /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">商品生成量</div>
|
||||||
|
<div class="stat-value">{{ data.drawings_generated }}</div>
|
||||||
|
<div
|
||||||
|
class="stat-trend"
|
||||||
|
:class="{
|
||||||
|
positive: data.drawings_generated_mom_ratio?.includes('+'),
|
||||||
|
negative:
|
||||||
|
data.drawings_generated_mom_ratio?.includes('-') &&
|
||||||
|
data.drawings_generated_mom_ratio?.length > 1,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-icon><ArrowUp /></el-icon>
|
||||||
|
{{ data.drawings_generated_mom_ratio }} 较上月
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon generate">
|
||||||
|
<el-icon><Document /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">本月收入</div>
|
||||||
|
<div class="stat-value">¥{{ data.month_income_cents / 100 }}</div>
|
||||||
|
<div
|
||||||
|
class="stat-trend"
|
||||||
|
:class="{
|
||||||
|
positive: data.month_income_cents_mom_ratio?.includes('+'),
|
||||||
|
negative:
|
||||||
|
data.month_income_cents_mom_ratio?.includes('-') &&
|
||||||
|
data.month_income_cents_mom_ratio?.length > 1,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-icon><ArrowUp /></el-icon>
|
||||||
|
{{ data.month_income_cents_mom_ratio }} 较上月
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-icon revenue">
|
||||||
|
<el-icon><Money /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图表区域 -->
|
||||||
|
<div class="charts-section">
|
||||||
|
<div class="chart-container">
|
||||||
|
<RevenueChart />
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<OrderTypeChart />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 最近数据区域 -->
|
||||||
|
<div class="recent-section">
|
||||||
|
<div class="recent-container">
|
||||||
|
<RecentOrders />
|
||||||
|
</div>
|
||||||
|
<div class="recent-container">
|
||||||
|
<RecentDrawings />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ArrowUp, ArrowDown, User, TrendCharts, Document, Money } from '@element-plus/icons-vue'
|
||||||
|
import RevenueChart from './RevenueChart.vue'
|
||||||
|
import OrderTypeChart from './OrderTypeChart.vue'
|
||||||
|
import RecentOrders from './RecentOrders.vue'
|
||||||
|
import RecentDrawings from './RecentDrawings.vue'
|
||||||
|
import RPC from '@/utils/rpc'
|
||||||
|
|
||||||
|
const data = ref({})
|
||||||
|
const getData = () => {
|
||||||
|
RPC.post('/dashboard/summary').then((res) => {
|
||||||
|
data.value = res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getData()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dashboard {
|
||||||
|
padding: 20px;
|
||||||
|
// background-color: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
.stats-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.stat-info {
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-trend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&.positive {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.negative {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.user {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.generate {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.revenue {
|
||||||
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
min-height: 400px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-container {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in new issue