553 lines
19 KiB
Vue
553 lines
19 KiB
Vue
<template>
|
||
<div class="dashboard">
|
||
<h2 class="page-title">营养监控项目运营核心数据总览</h2>
|
||
<h4 class="time-title">{{ stResourceBasicStats.updateTime || '暂无数据'}}</h4>
|
||
<div class="cards">
|
||
<!-- 基础资源 -->
|
||
<section class="card">
|
||
<header class="card-header card-header--blue"><span>基础资源</span></header>
|
||
<div class="card-body">
|
||
<ul class="list">
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>基础规模</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col">
|
||
<span>单位数:</span><span class="num">{{ stResourceBasicStats.unitCount || 0 }}</span><span> 个</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>食堂数:</span><span class="num">{{ stResourceBasicStats.canteenCount || 0 }}</span><span>
|
||
个</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>餐品资源</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col">
|
||
<span>餐品总数:</span><span class="num">{{ stResourceBasicStats.foodTotal || 0 }}</span><span> 个</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>食堂餐品:</span><span class="num">{{ stResourceBasicStats.canteenFood || 0 }}</span><span>
|
||
个</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>使用数据</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col">
|
||
<span>总人数:</span><span class="num">{{ stResourceBasicStats.totalPeople || 0 }}</span><span> 个</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>本周总使用人次:</span><span class="num">{{ stResourceBasicStats.weekUsage || 0 }}</span><span>
|
||
人,较上周:</span><span :class="stResourceBasicStats.weekUsageRate > 0 ? 'red' : 'green'">{{
|
||
stResourceBasicStats.weekUsageRate || 0 }}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>设备配置</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col">
|
||
<span>餐线数:</span><span class="num">{{ stResourceBasicStats.mealLine || 0 }}</span><span> 条</span>
|
||
</div>
|
||
</div>
|
||
<div class="sub-title">
|
||
<span>包含设备</span>
|
||
</div>
|
||
<div class="equip-grid">
|
||
<div>D款:<span>{{ stResourceBasicStats.deviceD || 0 }}</span> 台</div>
|
||
<div>H款:<span>{{ stResourceBasicStats.deviceH || 0 }}</span> 台</div>
|
||
<div>F款:<span>{{ stResourceBasicStats.deviceF || 0 }}</span> 台</div>
|
||
<div>绑盘机款:<span>{{ stResourceBasicStats.deviceDish || 0 }}</span> 台</div>
|
||
<div>落地屏:<span>{{ stResourceBasicStats.deviceScreen || 0 }}</span> 台</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 核心运营 -->
|
||
<section class="card">
|
||
<header class="card-header card-header--green"><span>核心运营</span></header>
|
||
<div class="card-body">
|
||
<ul class="list">
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>运营周期</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col">
|
||
<span>总运行天数:</span><span class="num">{{ stOperationCoreStats.operationDays || 0 }}</span><span>
|
||
天</span>
|
||
</div>
|
||
<div class="sub-col"></div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>重量消耗</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col">
|
||
<span>总称餐重量:</span><span class="num">{{ fmtNum(stOperationCoreStats.totalFoodWeight) }}</span><span>
|
||
kg</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>总用餐重量:</span><span class="num">{{ fmtNum(stOperationCoreStats.totalMealWeight) }}</span><span>
|
||
kg</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>用餐数据</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col">
|
||
<span>总用餐人次数:</span><span class="num">{{ fmtNum(stOperationCoreStats.mealPeople) }}</span><span>
|
||
人</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>总用餐热量:</span><span class="num">{{ fmtNum(stOperationCoreStats.mealEnergy) }}</span><span>
|
||
兆卡</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>数据资产</div>
|
||
<div class="sub-title">
|
||
<span>总数据量</span>
|
||
</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col indent">
|
||
<span>存储空间:</span><span class="num">{{ stOperationCoreStats.dataStorage || 0 }}</span><span> {{
|
||
stOperationCoreStats.dataStorageUnit || 'Gb' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="sub-title">
|
||
<span>总数据条</span>
|
||
</div>
|
||
<div class="sub-row">
|
||
<div class="sub-col indent">
|
||
<span>就餐记录:</span><span class="num">{{ stOperationCoreStats.mealRecord || 0 }}</span><span> 条</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 人均洞察 -->
|
||
<section class="card">
|
||
<header class="card-header card-header--orange"><span>人均洞察</span></header>
|
||
<div class="card-body">
|
||
<ul class="list">
|
||
<li class="item">
|
||
<div class="item-content">
|
||
<div class="item-title"><span class="sq"></span>人均指标<span class="unit">(单位:重量g/热量kcal)</span></div>
|
||
|
||
<div class="group">
|
||
<div class="item-sub group-title">日平均(本周/上周)</div>
|
||
<div class="sub-row indent">
|
||
<!-- <div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.dailyAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.dailyAvgWeightChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.dailyAvgWeightChange || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.dailyAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.dailyAvgEnergyChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.dailyAvgEnergyChange || 0 }}</span><span> kcal</span>
|
||
</div> -->
|
||
<div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.dailyAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.dailyAvgWeightLast || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.dailyAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.dailyEnergyLast || 0 }}</span><span> kcal</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="group">
|
||
<div class="item-sub group-title">早餐平均(本周/上周)</div>
|
||
<div class="sub-row indent">
|
||
<!-- <div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.morningAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.morningAvgWeightChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.morningAvgWeightChange || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.morningAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.morningAvgEnergyChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.morningAvgEnergyChange || 0 }}</span><span> kcal</span>
|
||
</div> -->
|
||
<div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.morningAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.breakfastAvgWeightLast || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.morningAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.breakfastEnergyLast || 0 }}</span><span> kcal</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="group">
|
||
<div class="item-sub group-title">午餐平均(本周/上周)</div>
|
||
<div class="sub-row indent">
|
||
<!-- <div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.lunchAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.lunchAvgWeightChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.lunchAvgWeightChange || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.lunchAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.lunchAvgEnergyChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.lunchAvgEnergyChange || 0 }}</span><span> kcal</span>
|
||
</div> -->
|
||
<div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.lunchAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.lunchAvgAvgWeightLast || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.lunchAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.lunchEnergyLast || 0 }}</span><span> kcal</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="group">
|
||
<div class="item-sub group-title">晚餐平均(本周/上周)</div>
|
||
<div class="sub-row indent">
|
||
<!-- <div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.dinnerAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.dinnerAvgWeightChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.dinnerAvgWeightChange || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.dinnerAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span
|
||
:class="stInsightPerCapitaStats.dinnerAvgEnergyChange > 0 ? 'red' : 'green'">{{
|
||
stInsightPerCapitaStats.dinnerAvgEnergyChange || 0 }}</span><span> kcal</span>
|
||
</div> -->
|
||
<div class="sub-col">
|
||
<span>重量:</span><span class="num">{{ stInsightPerCapitaStats.dinnerAvgWeight || 0 }}</span><span>
|
||
g</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.dinnerAvgWeightLast || 0 }}</span><span> g</span>
|
||
</div>
|
||
<div class="sub-col">
|
||
<span>热量:</span><span class="num">{{ stInsightPerCapitaStats.dinnerAvgEnergy || 0 }}</span><span>
|
||
kcal</span><span> / </span><span class="num">{{ stInsightPerCapitaStats.dinnerEnergyLast || 0 }}</span><span> kcal</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getStResourceBasicStats, getStOperationCoreStats, getStInsightPerCapitaStats } from '@/api/api'
|
||
export default {
|
||
name: 'Home',
|
||
data() {
|
||
return {
|
||
stResourceBasicStats: {},
|
||
stOperationCoreStats: {},
|
||
stInsightPerCapitaStats: {}
|
||
}
|
||
},
|
||
async mounted() {
|
||
await this.refreshAll()
|
||
this._hourlyTimer = setInterval(() => {
|
||
this.refreshAll()
|
||
}, 3600000)
|
||
},
|
||
methods: {
|
||
async refreshAll() {
|
||
await Promise.all([
|
||
this.loadBasicStatsData(),
|
||
this.loadCoreStatsData(),
|
||
this.loadCapitaStatsData()
|
||
])
|
||
},
|
||
async loadBasicStatsData() {
|
||
try {
|
||
const res = await getStResourceBasicStats({})
|
||
this.stResourceBasicStats = res.result || {}
|
||
} catch (err) { console.error(err) }
|
||
},
|
||
async loadCoreStatsData() {
|
||
try {
|
||
const res = await getStOperationCoreStats({})
|
||
this.stOperationCoreStats = res.result || {}
|
||
} catch (err) { console.error(err) }
|
||
},
|
||
async loadCapitaStatsData() {
|
||
try {
|
||
const res = await getStInsightPerCapitaStats({})
|
||
this.stInsightPerCapitaStats = res.result || {}
|
||
} catch (err) { console.error(err) }
|
||
},
|
||
fmtNum(n) {
|
||
if (n === undefined || n === null) return 0
|
||
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.dashboard {
|
||
min-height: 100vh;
|
||
padding: 48px 24px;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.page-title {
|
||
text-align: center;
|
||
font-size: 36px;
|
||
font-weight: 600;
|
||
margin: 12px 0 20px 0;
|
||
color: #2b2b2b;
|
||
}
|
||
|
||
.time-title{
|
||
text-align: center;
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
margin: 12px 0 10px 0;
|
||
color: #2b2b2b;
|
||
}
|
||
|
||
.cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 30px;
|
||
}
|
||
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
box-shadow: 0 6px 20px rgba(25, 55, 88, 0.08);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.card-header {
|
||
height: 70px;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 18px;
|
||
color: #fff;
|
||
font-weight: 400;
|
||
font-size: 24px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.card-header--blue {
|
||
background: linear-gradient(90deg, #3569E6 0%, #519DF9 100%);
|
||
}
|
||
|
||
.card-header--green {
|
||
background: linear-gradient(90deg, #00C27F 0%, #1AD0CE 100%);
|
||
}
|
||
|
||
.card-header--orange {
|
||
background: linear-gradient(90deg, #E26229 0%, #F29554 100%);
|
||
}
|
||
|
||
.card-body {
|
||
padding: 16px 18px 22px;
|
||
}
|
||
|
||
.list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.item-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.item-title {
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
color: #000;
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.unit{
|
||
font-size: 14px;
|
||
font-weight: normal;
|
||
color: #5a6678;
|
||
}
|
||
|
||
.sq {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 2px;
|
||
display: inline-block;
|
||
background: #366AE6;
|
||
}
|
||
|
||
.num {
|
||
font-weight: 700;
|
||
color: #000;
|
||
}
|
||
|
||
.item-sub {
|
||
color: #5a6678;
|
||
line-height: 22px;
|
||
padding: 0 15px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.red {
|
||
color: #E63838 !important;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.green {
|
||
color: #0DA700 !important;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.group-title {
|
||
padding-left: 15px;
|
||
margin-bottom: 20px;
|
||
font-weight: 400;
|
||
font-size: 14px;
|
||
color: #000;
|
||
}
|
||
|
||
.sub-row {
|
||
display: flex;
|
||
gap: 24px;
|
||
padding: 2px 15px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.sub-col {
|
||
flex: 1;
|
||
text-align: left;
|
||
min-width: 0;
|
||
}
|
||
|
||
.indent {
|
||
text-indent: 1em;
|
||
}
|
||
|
||
.sub-col span {
|
||
color: #5a6678;
|
||
}
|
||
|
||
.sub-col .num {
|
||
color: #000;
|
||
}
|
||
|
||
.sub-title {
|
||
padding: 2px 15px;
|
||
}
|
||
|
||
.sub-title span {
|
||
font-weight: 400;
|
||
font-size: 14px;
|
||
color: #000;
|
||
}
|
||
|
||
.cards .card:nth-child(-n+2) .list .item:nth-child(3) .sub-row {
|
||
display: block;
|
||
}
|
||
|
||
.cards .card:nth-child(-n+2) .list .item:nth-child(3) .sub-col {
|
||
display: block;
|
||
width: 100%;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.equip-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
padding: 10px 2em;
|
||
gap: 6px 12px;
|
||
margin-top: 6px;
|
||
color: #5a6678;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.equip-grid span {
|
||
font-weight: bold;
|
||
color: #000;
|
||
}
|
||
|
||
.trend {
|
||
font-weight: 400;
|
||
}
|
||
|
||
.trend--up {
|
||
color: #0DA700;
|
||
}
|
||
|
||
.trend--down {
|
||
color: #E63838;
|
||
}
|
||
|
||
.pos {
|
||
color: #E63838 !important;
|
||
}
|
||
|
||
.neg {
|
||
color: #0DA700 !important;
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.cards {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|