527 lines
13 KiB
Plaintext
527 lines
13 KiB
Plaintext
<template>
|
||
<AdminLayout current-page="dashboard">
|
||
<view class="dashboard-page">
|
||
<!-- 第一行:4 个 KPI 卡片 -->
|
||
<view class="kpi-cards-row">
|
||
<!-- 销售额卡片 -->
|
||
<view class="kpi-card">
|
||
<view class="kpi-card-content">
|
||
<view class="kpi-card-header">
|
||
<text class="kpi-card-title">销售额</text>
|
||
<view class="kpi-card-tag">
|
||
<text class="kpi-tag-text">今日</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-value">
|
||
<text class="kpi-value-number">¥{{ formatNumber(kpiData.sales.today) }}</text>
|
||
<view class="kpi-value-trend" :class="{ 'up': kpiData.sales.change > 0, 'down': kpiData.sales.change < 0 }">
|
||
<text class="iconfont" :class="{ 'icon-up': kpiData.sales.change > 0, 'icon-down': kpiData.sales.change < 0 }"></text>
|
||
<text class="kpi-trend-text">{{ Math.abs(kpiData.sales.change) }}%</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-footer">
|
||
<text class="kpi-footer-text">昨日:¥{{ formatNumber(kpiData.sales.yesterday) }}</text>
|
||
<text class="kpi-footer-text">本月累计:¥{{ formatNumber(kpiData.sales.monthTotal) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-icon">
|
||
<text class="iconfont icon-sales"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 访问量卡片 -->
|
||
<view class="kpi-card">
|
||
<view class="kpi-card-content">
|
||
<view class="kpi-card-header">
|
||
<text class="kpi-card-title">访问量</text>
|
||
<view class="kpi-card-tag">
|
||
<text class="kpi-tag-text">今日</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-value">
|
||
<text class="kpi-value-number">{{ formatNumber(kpiData.visits.today) }}</text>
|
||
<view class="kpi-value-trend" :class="{ 'up': kpiData.visits.change > 0, 'down': kpiData.visits.change < 0 }">
|
||
<text class="iconfont" :class="{ 'icon-up': kpiData.visits.change > 0, 'icon-down': kpiData.visits.change < 0 }"></text>
|
||
<text class="kpi-trend-text">{{ Math.abs(kpiData.visits.change) }}%</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-footer">
|
||
<text class="kpi-footer-text">昨日:{{ formatNumber(kpiData.visits.yesterday) }}</text>
|
||
<text class="kpi-footer-text">本月累计:{{ formatNumber(kpiData.visits.monthTotal) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-icon">
|
||
<text class="iconfont icon-visits"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单量卡片 -->
|
||
<view class="kpi-card">
|
||
<view class="kpi-card-content">
|
||
<view class="kpi-card-header">
|
||
<text class="kpi-card-title">订单量</text>
|
||
<view class="kpi-card-tag">
|
||
<text class="kpi-tag-text">今日</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-value">
|
||
<text class="kpi-value-number">{{ formatNumber(kpiData.orders.today) }}</text>
|
||
<view class="kpi-value-trend" :class="{ 'up': kpiData.orders.change > 0, 'down': kpiData.orders.change < 0 }">
|
||
<text class="iconfont" :class="{ 'icon-up': kpiData.orders.change > 0, 'icon-down': kpiData.orders.change < 0 }"></text>
|
||
<text class="kpi-trend-text">{{ Math.abs(kpiData.orders.change) }}%</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-footer">
|
||
<text class="kpi-footer-text">昨日:{{ formatNumber(kpiData.orders.yesterday) }}</text>
|
||
<text class="kpi-footer-text">本月累计:{{ formatNumber(kpiData.orders.monthTotal) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-icon">
|
||
<text class="iconfont icon-orders"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 新增用户卡片 -->
|
||
<view class="kpi-card">
|
||
<view class="kpi-card-content">
|
||
<view class="kpi-card-header">
|
||
<text class="kpi-card-title">新增用户</text>
|
||
<view class="kpi-card-tag">
|
||
<text class="kpi-tag-text">今日</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-value">
|
||
<text class="kpi-value-number">{{ formatNumber(kpiData.users.today) }}</text>
|
||
<view class="kpi-value-trend" :class="{ 'up': kpiData.users.change > 0, 'down': kpiData.users.change < 0 }">
|
||
<text class="iconfont" :class="{ 'icon-up': kpiData.users.change > 0, 'icon-down': kpiData.users.change < 0 }"></text>
|
||
<text class="kpi-trend-text">{{ Math.abs(kpiData.users.change) }}%</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-footer">
|
||
<text class="kpi-footer-text">昨日:{{ formatNumber(kpiData.users.yesterday) }}</text>
|
||
<text class="kpi-footer-text">本月累计:{{ formatNumber(kpiData.users.monthTotal) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="kpi-card-icon">
|
||
<text class="iconfont icon-users"></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 第二行:订单统计图表 -->
|
||
<view class="chart-section">
|
||
<view class="admin-card">
|
||
<view class="admin-card-header">
|
||
<text class="admin-card-title">订单统计</text>
|
||
<view class="chart-controls">
|
||
<button
|
||
v-for="period in chartPeriods"
|
||
:key="period.value"
|
||
class="period-btn"
|
||
:class="{ 'active': selectedPeriod === period.value }"
|
||
@click="changePeriod(period.value)"
|
||
>
|
||
{{ period.label }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
<view class="admin-card-body">
|
||
<!-- ECharts 组合图容器 -->
|
||
<view class="echarts-container">
|
||
<text class="chart-placeholder">📊 ECharts 组合图:柱状图(订单金额) + 折线图(订单数量)</text>
|
||
<text class="chart-desc">时间粒度:{{ selectedPeriodLabel }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 第三行:用户统计图表 -->
|
||
<view class="charts-row">
|
||
<!-- 用户趋势折线图 -->
|
||
<view class="chart-col">
|
||
<view class="admin-card">
|
||
<view class="admin-card-header">
|
||
<text class="admin-card-title">用户趋势</text>
|
||
</view>
|
||
<view class="admin-card-body">
|
||
<view class="echarts-container">
|
||
<text class="chart-placeholder">📈 ECharts 折线图:用户增长趋势</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户构成饼图 -->
|
||
<view class="chart-col">
|
||
<view class="admin-card">
|
||
<view class="admin-card-header">
|
||
<text class="admin-card-title">用户构成</text>
|
||
</view>
|
||
<view class="admin-card-body">
|
||
<view class="echarts-container">
|
||
<text class="chart-placeholder">🥧 ECharts 饼图:用户来源分布</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</AdminLayout>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref } from 'vue'
|
||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||
|
||
// KPI 数据
|
||
const kpiData = ref({
|
||
sales: {
|
||
today: 125680.50,
|
||
yesterday: 118920.30,
|
||
monthTotal: 2857808.90,
|
||
change: 5.7
|
||
},
|
||
visits: {
|
||
today: 15420,
|
||
yesterday: 14890,
|
||
monthTotal: 342680,
|
||
change: 3.4
|
||
},
|
||
orders: {
|
||
today: 342,
|
||
yesterday: 318,
|
||
monthTotal: 8956,
|
||
change: 7.5
|
||
},
|
||
users: {
|
||
today: 156,
|
||
yesterday: 142,
|
||
monthTotal: 3245,
|
||
change: 9.9
|
||
}
|
||
})
|
||
|
||
// 图表配置
|
||
const selectedPeriod = ref('30days')
|
||
const selectedPeriodLabel = ref('30天')
|
||
|
||
const chartPeriods = [
|
||
{ label: '30天', value: '30days' },
|
||
{ label: '周', value: 'week' },
|
||
{ label: '月', value: 'month' },
|
||
{ label: '年', value: 'year' }
|
||
]
|
||
|
||
// 方法
|
||
const formatNumber = (num: number) => {
|
||
if (num >= 10000) {
|
||
return (num / 10000).toFixed(1) + '万'
|
||
} else if (num >= 1000) {
|
||
return (num / 1000).toFixed(1) + 'k'
|
||
}
|
||
return num.toString()
|
||
}
|
||
|
||
const changePeriod = (period: string) => {
|
||
selectedPeriod.value = period
|
||
const periodMap: Record<string, string> = {
|
||
'30days': '30天',
|
||
'week': '周',
|
||
'month': '月',
|
||
'year': '年'
|
||
}
|
||
selectedPeriodLabel.value = periodMap[period] || '30天'
|
||
|
||
// TODO: 重新加载图表数据
|
||
console.log('切换时间粒度:', period)
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
/* ===== Dashboard 页面样式 ===== */
|
||
.dashboard-page {
|
||
width: 100%;
|
||
}
|
||
|
||
/* ===== KPI 卡片行 ===== */
|
||
.kpi-cards-row {
|
||
display: flex;
|
||
gap: 24px;
|
||
margin-bottom: 24px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.kpi-card {
|
||
flex: 1;
|
||
min-width: 280px;
|
||
background-color: #ffffff;
|
||
border: 1px solid #e8e8e8;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.kpi-card-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.kpi-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.kpi-card-title {
|
||
font-size: 16px;
|
||
color: #666666;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.kpi-card-tag {
|
||
background-color: #1890ff;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.kpi-tag-text {
|
||
font-size: 12px;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.kpi-card-value {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.kpi-value-number {
|
||
font-size: 32px;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
.kpi-value-trend {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 14px;
|
||
border-radius: 12px;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
.kpi-value-trend.up {
|
||
background-color: #f6ffed;
|
||
color: #52c41a;
|
||
}
|
||
|
||
.kpi-value-trend.down {
|
||
background-color: #fff2f0;
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.kpi-trend-text {
|
||
margin-left: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.kpi-card-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.kpi-footer-text {
|
||
font-size: 14px;
|
||
color: #999999;
|
||
}
|
||
|
||
.kpi-card-icon {
|
||
width: 64px;
|
||
height: 64px;
|
||
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #ffffff;
|
||
font-size: 32px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ===== 图表区域 ===== */
|
||
.chart-section {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.charts-row {
|
||
display: flex;
|
||
gap: 24px;
|
||
}
|
||
|
||
.chart-col {
|
||
flex: 1;
|
||
}
|
||
|
||
/* ===== Admin Card 组件样式 ===== */
|
||
.admin-card {
|
||
background-color: #ffffff;
|
||
border: 1px solid #e8e8e8;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.admin-card-header {
|
||
padding: 24px 24px 0 24px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.admin-card-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
}
|
||
|
||
.admin-card-body {
|
||
padding: 0 24px 24px 24px;
|
||
}
|
||
|
||
/* ===== 图表控件 ===== */
|
||
.chart-controls {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.period-btn {
|
||
padding: 6px 16px;
|
||
border: 1px solid #d9d9d9;
|
||
background-color: #ffffff;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.period-btn:hover {
|
||
border-color: #1890ff;
|
||
color: #1890ff;
|
||
}
|
||
|
||
.period-btn.active {
|
||
background-color: #1890ff;
|
||
color: #ffffff;
|
||
border-color: #1890ff;
|
||
}
|
||
|
||
/* ===== ECharts 容器 ===== */
|
||
.echarts-container {
|
||
height: 350px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #fafafa;
|
||
border: 1px solid #e8e8e8;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.chart-placeholder {
|
||
font-size: 16px;
|
||
color: #666666;
|
||
text-align: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.chart-desc {
|
||
font-size: 14px;
|
||
color: #999999;
|
||
text-align: center;
|
||
}
|
||
|
||
/* ===== 响应式设计 ===== */
|
||
@media (max-width: 1200px) {
|
||
.kpi-cards-row {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.kpi-card {
|
||
min-width: 45%;
|
||
flex: 0 0 auto;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.kpi-cards-row {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.kpi-card {
|
||
min-width: auto;
|
||
width: 100%;
|
||
}
|
||
|
||
.charts-row {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.dashboard-page {
|
||
padding: 16px;
|
||
}
|
||
|
||
.kpi-cards-row,
|
||
.chart-section,
|
||
.charts-row {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.kpi-card {
|
||
padding: 16px;
|
||
}
|
||
|
||
.admin-card-header,
|
||
.admin-card-body {
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
}
|
||
}
|
||
|
||
/* ===== 图标字体 ===== */
|
||
.iconfont {
|
||
font-family: 'iconfont';
|
||
font-size: 16px;
|
||
font-style: normal;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
.icon-up:before {
|
||
content: '↑';
|
||
}
|
||
|
||
.icon-down:before {
|
||
content: '↓';
|
||
}
|
||
|
||
.icon-sales:before {
|
||
content: '💰';
|
||
}
|
||
|
||
.icon-visits:before {
|
||
content: '👁️';
|
||
}
|
||
|
||
.icon-orders:before {
|
||
content: '📦';
|
||
}
|
||
|
||
.icon-users:before {
|
||
content: '👥';
|
||
}
|
||
</style> |