|
|
@@ -0,0 +1,584 @@
|
|
|
+<template>
|
|
|
+ <div class="dashboard-page">
|
|
|
+ <!-- 顶部标题栏 -->
|
|
|
+ <div class="dashboard-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <h2 class="header-title">营业数据看板</h2>
|
|
|
+ <NTag type="success" size="medium" round>
|
|
|
+ 今日 {{ todayStr }}
|
|
|
+ </NTag>
|
|
|
+ </div>
|
|
|
+ <NButton :loading="loading" @click="loadDashboard">
|
|
|
+ <template #icon><NIcon><RefreshIcon /></NIcon></template>
|
|
|
+ 刷新数据
|
|
|
+ </NButton>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- KPI 卡片行 -->
|
|
|
+ <div class="kpi-grid">
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-label">今日订单数</div>
|
|
|
+ <div class="kpi-value">{{ kpi.orderCount }}<span class="kpi-unit">单</span></div>
|
|
|
+ <div class="kpi-growth" :class="growthClass(kpi.orderCountGrowth)">
|
|
|
+ <span v-if="kpi.orderCountGrowth !== null">{{ formatGrowth(kpi.orderCountGrowth) }} 环比昨日</span>
|
|
|
+ <span v-else class="text-gray-400">昨日无数据</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card kpi-card--revenue">
|
|
|
+ <div class="kpi-label">今日营业额</div>
|
|
|
+ <div class="kpi-value kpi-value--revenue">¥{{ formatMoney(kpi.revenue) }}</div>
|
|
|
+ <div class="kpi-growth" :class="growthClass(kpi.revenueGrowth)">
|
|
|
+ <span v-if="kpi.revenueGrowth !== null">{{ formatGrowth(kpi.revenueGrowth) }} 环比昨日</span>
|
|
|
+ <span v-else class="text-gray-400">昨日无数据</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-label">平均送达时长</div>
|
|
|
+ <div class="kpi-value">{{ kpi.avgDeliveryMinutes }}<span class="kpi-unit">分钟</span></div>
|
|
|
+ <div class="kpi-growth" :class="growthClass(kpi.avgDeliveryGrowth, true)">
|
|
|
+ <span v-if="kpi.avgDeliveryGrowth !== null">{{ formatGrowth(kpi.avgDeliveryGrowth) }} 环比昨日</span>
|
|
|
+ <span v-else class="text-gray-400">昨日无数据</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-label">退单率</div>
|
|
|
+ <div class="kpi-value">{{ formatPercent(kpi.refundRate) }}</div>
|
|
|
+ <div class="kpi-growth" :class="growthClass(kpi.refundRateGrowth, true)">
|
|
|
+ <span v-if="kpi.refundRateGrowth !== null">{{ formatGrowth(kpi.refundRateGrowth) }} 环比昨日</span>
|
|
|
+ <span v-else class="text-gray-400">昨日无数据</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 中间区域:趋势图 + 热门菜品 -->
|
|
|
+ <div class="middle-grid">
|
|
|
+ <!-- 近7天订单趋势 -->
|
|
|
+ <div class="chart-card chart-card--trend">
|
|
|
+ <div class="chart-title">近7天订单趋势</div>
|
|
|
+ <div ref="trendChartRef" class="chart-container"></div>
|
|
|
+ </div>
|
|
|
+ <!-- 本周热门菜品 -->
|
|
|
+ <div class="chart-card chart-card--hot">
|
|
|
+ <div class="chart-title">本周热门菜品 Top5</div>
|
|
|
+ <div v-if="hotDishes.length === 0" class="empty-tip">
|
|
|
+ 本周暂无订单数据
|
|
|
+ </div>
|
|
|
+ <div v-else class="hot-dish-list">
|
|
|
+ <div v-for="dish in hotDishes" :key="dish.rank" class="hot-dish-item">
|
|
|
+ <span class="rank-badge" :class="dish.rank <= 3 ? 'rank-top' : ''">{{ dish.rank }}</span>
|
|
|
+ <span class="dish-name">{{ dish.dishName }}</span>
|
|
|
+ <span class="dish-count">{{ dish.salesCount }} 份</span>
|
|
|
+ <div class="dish-bar-wrap">
|
|
|
+ <div class="dish-bar" :style="{ width: dishBarWidth(dish.salesCount) }"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部:餐段订单分布 -->
|
|
|
+ <div class="chart-card chart-card--meal">
|
|
|
+ <div class="chart-title">今日餐段订单分布</div>
|
|
|
+ <div v-if="mealDistribution.length === 0" class="empty-tip">
|
|
|
+ 暂无营业时段配置
|
|
|
+ </div>
|
|
|
+ <div v-else class="meal-grid">
|
|
|
+ <div v-for="meal in mealDistribution" :key="meal.mealType" class="meal-item" :class="{ 'meal-active': meal.isInProgress }">
|
|
|
+ <div class="meal-header">
|
|
|
+ <span class="meal-name">{{ meal.mealName }}</span>
|
|
|
+ <NTag v-if="meal.isInProgress" type="success" size="small" round>进行中</NTag>
|
|
|
+ </div>
|
|
|
+ <div class="meal-count">{{ meal.orderCount }}<span class="meal-unit">单</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
|
+import { NButton, NIcon, NTag, useMessage } from 'naive-ui'
|
|
|
+import { RefreshOutline as RefreshIcon } from '@vicons/ionicons5'
|
|
|
+import { getDashboard } from '@/api/hotel'
|
|
|
+import * as echarts from 'echarts'
|
|
|
+
|
|
|
+defineOptions({ name: 'HotelDashboard' })
|
|
|
+
|
|
|
+const message = useMessage()
|
|
|
+const loading = ref(false)
|
|
|
+
|
|
|
+// ==================== 数据 ====================
|
|
|
+
|
|
|
+const kpi = ref({
|
|
|
+ orderCount: 0,
|
|
|
+ orderCountGrowth: null,
|
|
|
+ revenue: 0,
|
|
|
+ revenueGrowth: null,
|
|
|
+ avgDeliveryMinutes: 0,
|
|
|
+ avgDeliveryGrowth: null,
|
|
|
+ refundRate: 0,
|
|
|
+ refundRateGrowth: null,
|
|
|
+})
|
|
|
+const trend = ref([])
|
|
|
+const hotDishes = ref([])
|
|
|
+const mealDistribution = ref([])
|
|
|
+
|
|
|
+// ==================== 日期 ====================
|
|
|
+
|
|
|
+const todayStr = computed(() => {
|
|
|
+ const d = new Date()
|
|
|
+ const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(d.getDate()).padStart(2, '0')
|
|
|
+ return m + '/' + day
|
|
|
+})
|
|
|
+
|
|
|
+// ==================== 加载数据 ====================
|
|
|
+
|
|
|
+async function loadDashboard() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getDashboard()
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const d = res.data
|
|
|
+ kpi.value = d.todayKpi || kpi.value
|
|
|
+ trend.value = d.trend || []
|
|
|
+ hotDishes.value = d.hotDishes || []
|
|
|
+ mealDistribution.value = d.mealDistribution || []
|
|
|
+ renderTrendChart()
|
|
|
+ } else {
|
|
|
+ message.error(res.msg || '加载看板数据失败')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ message.error('加载看板数据失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== KPI 格式化 ====================
|
|
|
+
|
|
|
+function formatMoney(val) {
|
|
|
+ if (val == null) return '0.00'
|
|
|
+ return Number(val).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
|
+}
|
|
|
+
|
|
|
+function formatPercent(val) {
|
|
|
+ if (val == null || val === 0) return '0%'
|
|
|
+ return (val * 100).toFixed(1) + '%'
|
|
|
+}
|
|
|
+
|
|
|
+function formatGrowth(val) {
|
|
|
+ if (val == null) return ''
|
|
|
+ const pct = (val * 100).toFixed(1)
|
|
|
+ if (val > 0) return '↑' + pct + '%'
|
|
|
+ if (val < 0) return '↓' + Math.abs(pct) + '%'
|
|
|
+ return '→ 0%'
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 环比增长率样式。
|
|
|
+ * @param val 增长率
|
|
|
+ * @param invertColor 是否反转颜色(送达时长/退单率:上升为红色,下降为绿色)
|
|
|
+ */
|
|
|
+function growthClass(val, invertColor) {
|
|
|
+ if (val == null) return 'text-gray-400'
|
|
|
+ if (val === 0) return 'text-gray-400'
|
|
|
+ if (invertColor) {
|
|
|
+ return val > 0 ? 'text-error' : 'text-success'
|
|
|
+ }
|
|
|
+ return val > 0 ? 'text-success' : 'text-error'
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 热门菜品 ====================
|
|
|
+
|
|
|
+function dishBarWidth(count) {
|
|
|
+ if (hotDishes.value.length === 0) return '0%'
|
|
|
+ const max = hotDishes.value[0].salesCount
|
|
|
+ if (max === 0) return '0%'
|
|
|
+ return Math.max(10, (count / max) * 100) + '%'
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== ECharts 趋势图 ====================
|
|
|
+
|
|
|
+const trendChartRef = ref(null)
|
|
|
+let trendChart = null
|
|
|
+
|
|
|
+function renderTrendChart() {
|
|
|
+ if (!trendChartRef.value) return
|
|
|
+ if (!trendChart) {
|
|
|
+ trendChart = echarts.init(trendChartRef.value)
|
|
|
+ }
|
|
|
+ const dates = []
|
|
|
+ const orders = []
|
|
|
+ const revenues = []
|
|
|
+ for (let i = 0; i < trend.value.length; i++) {
|
|
|
+ dates.push(trend.value[i].date)
|
|
|
+ orders.push(trend.value[i].orderCount)
|
|
|
+ revenues.push(trend.value[i].revenue)
|
|
|
+ }
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: { type: 'cross' },
|
|
|
+ textStyle: { fontSize: 13 },
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['订单数', '营业额'],
|
|
|
+ bottom: 4,
|
|
|
+ textStyle: { fontSize: 13, color: '#666' },
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: 60,
|
|
|
+ right: 60,
|
|
|
+ top: 20,
|
|
|
+ bottom: 44,
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: dates,
|
|
|
+ axisLine: { lineStyle: { color: '#ddd' } },
|
|
|
+ axisLabel: { color: '#666', fontSize: 12 },
|
|
|
+ },
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ name: '订单数',
|
|
|
+ nameTextStyle: { color: '#999', fontSize: 12 },
|
|
|
+ axisLabel: { color: '#666', fontSize: 12 },
|
|
|
+ splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ name: '营业额(元)',
|
|
|
+ nameTextStyle: { color: '#999', fontSize: 12 },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#666',
|
|
|
+ fontSize: 12,
|
|
|
+ formatter: function (value) {
|
|
|
+ return value >= 1000 ? (value / 1000).toFixed(1) + 'k' : value
|
|
|
+ },
|
|
|
+ },
|
|
|
+ splitLine: { show: false },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '订单数',
|
|
|
+ type: 'line',
|
|
|
+ data: orders,
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 8,
|
|
|
+ lineStyle: { width: 2.5 },
|
|
|
+ itemStyle: { color: '#2080f0' },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(32,128,240,0.20)' },
|
|
|
+ { offset: 1, color: 'rgba(32,128,240,0.01)' },
|
|
|
+ ]),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '营业额',
|
|
|
+ type: 'line',
|
|
|
+ yAxisIndex: 1,
|
|
|
+ data: revenues,
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 8,
|
|
|
+ lineStyle: { width: 2.5 },
|
|
|
+ itemStyle: { color: '#18a058' },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(24,160,88,0.15)' },
|
|
|
+ { offset: 1, color: 'rgba(24,160,88,0.01)' },
|
|
|
+ ]),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ trendChart.setOption(option, true)
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 窗口 resize ====================
|
|
|
+
|
|
|
+function handleResize() {
|
|
|
+ if (trendChart) {
|
|
|
+ trendChart.resize()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 生命周期 ====================
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadDashboard()
|
|
|
+ window.addEventListener('resize', handleResize)
|
|
|
+})
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ window.removeEventListener('resize', handleResize)
|
|
|
+ if (trendChart) {
|
|
|
+ trendChart.dispose()
|
|
|
+ trendChart = null
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* ==================== 页面容器 ==================== */
|
|
|
+.dashboard-page {
|
|
|
+ height: 100%;
|
|
|
+ min-height: 100%;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 20px 24px 32px;
|
|
|
+ background: #f5f6f8;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== 顶部标题栏 ==================== */
|
|
|
+.dashboard-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1a1a1a;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== KPI 卡片 ==================== */
|
|
|
+.kpi-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 24px;
|
|
|
+ border: 1px solid #e8e8ec;
|
|
|
+ transition: box-shadow 0.2s, transform 0.15s;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-card:hover {
|
|
|
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #8a8a8e;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-value {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1a1a1a;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-value--revenue {
|
|
|
+ color: #2080f0;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-unit {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 400;
|
|
|
+ color: #aaa;
|
|
|
+ margin-left: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-growth {
|
|
|
+ font-size: 13px;
|
|
|
+ margin-top: 10px;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== 中间区域 ==================== */
|
|
|
+.middle-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1.8fr 1fr;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== 通用卡片 ==================== */
|
|
|
+.chart-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 24px;
|
|
|
+ border: 1px solid #e8e8ec;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1a1a1a;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding-bottom: 12px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 340px;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-tip {
|
|
|
+ text-align: center;
|
|
|
+ padding: 48px 0;
|
|
|
+ color: #bbb;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== 热门菜品 ==================== */
|
|
|
+.hot-dish-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px 0;
|
|
|
+ border-bottom: 1px solid #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.hot-dish-item:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-badge {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 700;
|
|
|
+ background: #f0f0f0;
|
|
|
+ color: #999;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-badge.rank-top {
|
|
|
+ background: #2080f0;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.dish-name {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.dish-count {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #2080f0;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.dish-bar-wrap {
|
|
|
+ width: 80px;
|
|
|
+ height: 8px;
|
|
|
+ background: #f0f0f0;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.dish-bar {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, #2080f0, #409eff);
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: width 0.6s ease;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== 餐段分布 ==================== */
|
|
|
+.meal-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.meal-item {
|
|
|
+ background: #fafbfc;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 24px;
|
|
|
+ border: 2px solid #eee;
|
|
|
+ transition: border-color 0.2s, background 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.meal-item.meal-active {
|
|
|
+ border-color: #18a058;
|
|
|
+ background: #f0faf4;
|
|
|
+}
|
|
|
+
|
|
|
+.meal-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.meal-name {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1a1a1a;
|
|
|
+}
|
|
|
+
|
|
|
+.meal-count {
|
|
|
+ font-size: 36px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1a1a1a;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.meal-unit {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 400;
|
|
|
+ color: #aaa;
|
|
|
+ margin-left: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== 响应式 ==================== */
|
|
|
+@media (max-width: 1400px) {
|
|
|
+ .kpi-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .middle-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .dashboard-page {
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+ .kpi-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .meal-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .kpi-value {
|
|
|
+ font-size: 26px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|