第一次提交

This commit is contained in:
huangzhen
2025-11-04 15:49:23 +08:00
commit 968fe28de1
20 changed files with 19619 additions and 0 deletions
+506
View File
@@ -0,0 +1,506 @@
<template>
<div class="dashboard">
<h2 class="page-title">营养监控项目运营核心数据总览</h2>
<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>
mcal</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>人均指标</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>
</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>
</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>
</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>
</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: center;
}
.page-title {
text-align: center;
font-size: 36px;
font-weight: 600;
margin: 12px 0 60px 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;
}
.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>