Files
medical-mall/pages/mall/admin/index.uvue

527 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>