第一次提交
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user