213 lines
5.0 KiB
Vue
213 lines
5.0 KiB
Vue
|
|
<template>
|
||
|
|
<view class="home-page">
|
||
|
|
<!-- Welcome -->
|
||
|
|
<view class="welcome">
|
||
|
|
<text class="greeting">{{ user?.name ? `${user.name},你好` : '你好' }}</text>
|
||
|
|
<text class="desc">欢迎使用安知充充电柜</text>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- Stats Grid -->
|
||
|
|
<view class="stats-grid">
|
||
|
|
<view class="stat-card">
|
||
|
|
<text class="stat-value blue">{{ data?.online_cabinets ?? 0 }}</text>
|
||
|
|
<text class="stat-label">在线设备</text>
|
||
|
|
</view>
|
||
|
|
<view class="stat-card">
|
||
|
|
<text class="stat-value green">{{ data?.charging_count ?? 0 }}</text>
|
||
|
|
<text class="stat-label">充电中</text>
|
||
|
|
</view>
|
||
|
|
<view class="stat-card">
|
||
|
|
<text class="stat-value yellow">{{ data?.idle_channels ?? 0 }}</text>
|
||
|
|
<text class="stat-label">空闲仓体</text>
|
||
|
|
</view>
|
||
|
|
<view class="stat-card">
|
||
|
|
<text class="stat-value" :class="data?.fault_channels ? 'red' : 'gray'">
|
||
|
|
{{ data?.fault_channels ?? 0 }}
|
||
|
|
</text>
|
||
|
|
<text class="stat-label">故障</text>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- Today summary -->
|
||
|
|
<view class="card">
|
||
|
|
<text class="card-title">今日概况</text>
|
||
|
|
<view class="today-row">
|
||
|
|
<view class="today-item">
|
||
|
|
<text class="today-value">{{ data?.today_charge_count ?? 0 }}</text>
|
||
|
|
<text class="today-label">充电次数</text>
|
||
|
|
</view>
|
||
|
|
<view class="today-item">
|
||
|
|
<text class="today-value">{{ data?.today_energy_kwh?.toFixed(1) ?? '0.0' }}</text>
|
||
|
|
<text class="today-label">充电量(kWh)</text>
|
||
|
|
</view>
|
||
|
|
<view class="today-item">
|
||
|
|
<text class="today-value">{{ data?.today_charge_hours?.toFixed(1) ?? '0.0' }}</text>
|
||
|
|
<text class="today-label">时长(h)</text>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- Fault alerts -->
|
||
|
|
<view v-if="data?.alerts && data.alerts.length > 0" class="card">
|
||
|
|
<text class="card-title red-text">故障告警</text>
|
||
|
|
<view v-for="(alert, i) in data.alerts.slice(0, 5)" :key="i" class="alert-item">
|
||
|
|
<text class="alert-text">{{ alert.cabinet }} — 通道 {{ alert.channel }}</text>
|
||
|
|
<text class="alert-project">{{ alert.project }}</text>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- Quick Actions -->
|
||
|
|
<view class="card">
|
||
|
|
<text class="card-title">快捷操作</text>
|
||
|
|
<view class="action-item" @click="goDevices">
|
||
|
|
<text class="action-text">查看设备</text>
|
||
|
|
</view>
|
||
|
|
<view class="action-item" @click="goMe">
|
||
|
|
<text class="action-text">个人中心</text>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref } from 'vue'
|
||
|
|
import { onShow } from '@dcloudio/uni-app'
|
||
|
|
import { h5Api, type DashboardData } from '../../api'
|
||
|
|
import { useAuthStore } from '../../store/auth'
|
||
|
|
|
||
|
|
const authStore = useAuthStore()
|
||
|
|
const user = authStore.user
|
||
|
|
const data = ref<DashboardData | null>(null)
|
||
|
|
|
||
|
|
onShow(() => {
|
||
|
|
if (!authStore.isAuthenticated) {
|
||
|
|
uni.navigateTo({ url: '/pages/login/index' })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
fetchDashboard()
|
||
|
|
})
|
||
|
|
|
||
|
|
async function fetchDashboard() {
|
||
|
|
try {
|
||
|
|
const res = await h5Api.getDashboard()
|
||
|
|
data.value = res.data
|
||
|
|
} catch {
|
||
|
|
// 静默失败,页面显示零值
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function goDevices() {
|
||
|
|
uni.switchTab({ url: '/pages/devices/index' })
|
||
|
|
}
|
||
|
|
|
||
|
|
function goMe() {
|
||
|
|
uni.switchTab({ url: '/pages/me/index' })
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.home-page {
|
||
|
|
padding: 24px;
|
||
|
|
}
|
||
|
|
.welcome {
|
||
|
|
margin-bottom: 32px;
|
||
|
|
}
|
||
|
|
.greeting {
|
||
|
|
font-size: 36px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #111;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
.desc {
|
||
|
|
font-size: 26px;
|
||
|
|
color: #6b7280;
|
||
|
|
margin-top: 8px;
|
||
|
|
}
|
||
|
|
.stats-grid {
|
||
|
|
display: flex;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
gap: 16px;
|
||
|
|
margin-bottom: 32px;
|
||
|
|
}
|
||
|
|
.stat-card {
|
||
|
|
width: calc(50% - 8px);
|
||
|
|
background-color: #fff;
|
||
|
|
border-radius: 16px;
|
||
|
|
padding: 24px;
|
||
|
|
}
|
||
|
|
.stat-value {
|
||
|
|
font-size: 40px;
|
||
|
|
font-weight: 700;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
.stat-value.blue { color: #2563eb; }
|
||
|
|
.stat-value.green { color: #16a34a; }
|
||
|
|
.stat-value.yellow { color: #f59e0b; }
|
||
|
|
.stat-value.red { color: #dc2626; }
|
||
|
|
.stat-value.gray { color: #6b7280; }
|
||
|
|
.stat-label {
|
||
|
|
font-size: 24px;
|
||
|
|
color: #6b7280;
|
||
|
|
margin-top: 4px;
|
||
|
|
}
|
||
|
|
.card {
|
||
|
|
background-color: #fff;
|
||
|
|
border-radius: 16px;
|
||
|
|
padding: 24px;
|
||
|
|
margin-bottom: 24px;
|
||
|
|
}
|
||
|
|
.card-title {
|
||
|
|
font-size: 28px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #111;
|
||
|
|
margin-bottom: 16px;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
.red-text { color: #dc2626; }
|
||
|
|
.today-row {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
.today-item {
|
||
|
|
flex: 1;
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
.today-value {
|
||
|
|
font-size: 32px;
|
||
|
|
font-weight: 700;
|
||
|
|
color: #111;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
.today-label {
|
||
|
|
font-size: 22px;
|
||
|
|
color: #6b7280;
|
||
|
|
}
|
||
|
|
.alert-item {
|
||
|
|
padding: 12px 0;
|
||
|
|
border-bottom: 1px solid #f3f4f6;
|
||
|
|
}
|
||
|
|
.alert-item:last-child {
|
||
|
|
border-bottom: none;
|
||
|
|
}
|
||
|
|
.alert-text {
|
||
|
|
font-size: 26px;
|
||
|
|
color: #111;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
.alert-project {
|
||
|
|
font-size: 22px;
|
||
|
|
color: #9ca3af;
|
||
|
|
}
|
||
|
|
.action-item {
|
||
|
|
padding: 20px 0;
|
||
|
|
border-bottom: 1px solid #f3f4f6;
|
||
|
|
}
|
||
|
|
.action-item:last-child {
|
||
|
|
border-bottom: none;
|
||
|
|
}
|
||
|
|
.action-text {
|
||
|
|
font-size: 28px;
|
||
|
|
color: #374151;
|
||
|
|
}
|
||
|
|
</style>
|