Files
medical-mall/pages/mall/admin/homePage/index.uvue
2026-01-27 20:02:57 +08:00

494 lines
11 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">
<KpiMiniCard
title="销售额"
tagText="今日"
valuePrefix="¥"
:valueText="String(formatNumber(kpiData.sales.today))"
:metaLeft="`昨日 ${formatNumber(kpiData.sales.yesterday)}`"
:metaRight="`日环比 ${Math.abs(kpiData.sales.change)}%`"
:trend="kpiData.sales.change > 0 ? 'up' : (kpiData.sales.change < 0 ? 'down' : 'flat')"
:footerLeftText="'本月销售额'"
:footerRightText="`¥${formatNumber(kpiData.sales.monthTotal)}`"
/>
<KpiMiniCard
title="用户访问量"
tagText="今日"
:valueText="String(formatNumber(kpiData.visits.today))"
:metaLeft="`昨日 ${formatNumber(kpiData.visits.yesterday)}`"
:metaRight="`日环比 ${Math.abs(kpiData.visits.change)}%`"
:trend="kpiData.visits.change > 0 ? 'up' : (kpiData.visits.change < 0 ? 'down' : 'flat')"
footerLeftText="本月访问量"
:footerRightText="`${formatNumber(kpiData.visits.monthTotal)}Pv`"
/>
<KpiMiniCard
title="订单量"
tagText="今日"
:valueText="String(formatNumber(kpiData.orders.today))"
:metaLeft="`昨日 ${formatNumber(kpiData.orders.yesterday)}`"
:metaRight="`日环比 ${Math.abs(kpiData.orders.change)}%`"
:trend="kpiData.orders.change > 0 ? 'up' : (kpiData.orders.change < 0 ? 'down' : 'flat')"
footerLeftText="本月订单量"
:footerRightText="`${formatNumber(kpiData.orders.monthTotal)}单`"
/>
<KpiMiniCard
title="新增用户"
tagText="今日"
:valueText="String(formatNumber(kpiData.users.today))"
:metaLeft="`昨日 ${formatNumber(kpiData.users.yesterday)}`"
:metaRight="`日环比 ${Math.abs(kpiData.users.change)}%`"
:trend="kpiData.users.change > 0 ? 'up' : (kpiData.users.change < 0 ? 'down' : 'flat')"
footerLeftText="本月新增用户"
:footerRightText="`${formatNumber(kpiData.users.monthTotal)}人`"
/>
</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'
import KpiMiniCard from './components/KpiMiniCard.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 卡片行 ===== */
/* 第一行4 个 KPI 卡片一行 */
.kpi-cards-row{
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); /* 一行 4 列等分 */
gap: 16px;
align-items: stretch;
}
/* 卡片本体:不要写死宽高 */
.kpi-card{
background-color: #ffffff;
border: 1px solid #e8e8e8;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
position: relative;
overflow: hidden;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 200px; /* 你可以改成 140/160别写死 200px */
box-sizing: border-box;
margin-bottom: 20rpx;
min-width: 200rpx;
}
/* 响应式:宽度不够时变 2 列 / 1 列(可选) */
@media (max-width: 1200px){
.kpi-cards-row{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 768px){
.kpi-cards-row{ grid-template-columns: 1fr; }
}
.kpi-card-content {
flex: 1;
}
.kpi-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.kpi-card-title {
position: absolute;
top: 10rpx;
left: 5rpx;
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>