设备数据详情 #95

Merged
pc8xi2fbj merged 1 commits from zhanghongwei_branch into develop 4 weeks ago

@ -0,0 +1,101 @@
// src/api/waterMaker.ts
import { request } from '@/api/request'
import type { ResultVO } from '@/api/types/auth'
// 设备基本信息接口
interface MachineInfo {
deviceId: string
model: string
area: string
location: string
installDate: string
status: 'online' | 'offline' | 'warning' | 'error'
lastOnlineTime: string
}
// 实时数据接口
interface RealtimeData {
tapWaterTds: number // 自来水TDS
pureWaterTds: number // 纯净水TDS
temperature: number
pressure: number
flow1: number
flow2: number
updateTime: string
}
// 滤芯状态接口
interface FilterStatus {
id: string
name: string
usage: number
remainingDays: number
}
// 历史数据记录接口
interface HistoryRecord {
date: string // 日期
tapWaterTdsAvg: number // 自来水TDS平均值
pureWaterTdsAvg: number // 纯净水TDS平均值
mineralWaterTdsAvg: number // 矿化水TDS平均值
}
// 维护记录接口
interface MaintenanceRecord {
orderNo: string
maintenanceType: string
maintainer: string
maintenanceTime: string
status: 'completed' | 'processing' | 'pending'
}
// API服务类
export class WaterMakerApi {
// 获取设备基本信息
static async getDeviceById(deviceId: string): Promise<ResultVO<MachineInfo>> {
return await request<ResultVO<MachineInfo>>(
`/api/web/device/${deviceId}`,
{ method: 'GET' }
)
}
// 获取实时数据
static async getRealtimeData(deviceId: string): Promise<ResultVO<RealtimeData>> {
return await request<ResultVO<RealtimeData>>(
`/api/web/device/${deviceId}/realtime`,
{ method: 'GET' }
)
}
// 获取滤芯状态
static async getFilterStatus(deviceId: string): Promise<ResultVO<FilterStatus[]>> {
return await request<ResultVO<FilterStatus[]>>(
`/api/web/device/${deviceId}/filter-status`,
{ method: 'GET' }
)
}
// 获取历史数据
static async getHistoryData(deviceId: string, date: string): Promise<ResultVO<HistoryRecord[]>> {
return await request<ResultVO<HistoryRecord[]>>(
`/api/web/device/${deviceId}/history?date=${date}`,
{ method: 'GET' }
)
}
// 获取维护记录
static async getMaintenanceRecords(deviceId: string): Promise<ResultVO<MaintenanceRecord[]>> {
return await request<ResultVO<MaintenanceRecord[]>>(
`/api/web/device/${deviceId}/maintenance-records`,
{ method: 'GET' }
)
}
// 刷新设备数据
static async refreshDeviceData(deviceId: string): Promise<ResultVO<any>> {
return await request<ResultVO<any>>(
`/api/web/device/${deviceId}/refresh`,
{ method: 'POST' }
)
}
}

@ -41,6 +41,12 @@ const router = createRouter({
title: '设备监控'
}
},
{
path: '/home/equipment/water-maker/:id',
name: 'WaterMakerDetail',
component: () => import('@/views/equipment/WaterMakerDetail.vue'),
meta: { requiresAuth: true }
},
{
path: 'equipment/water-maker',
name: 'water-maker',
@ -49,6 +55,13 @@ const router = createRouter({
title: '制水设备'
}
},
{
path: '/home/equipment/water-supplier/:id',
name: 'WaterSupplierDetail',
component: () => import('@/views/equipment/WaterSupplierDetail.vue'),
meta: { requiresAuth: true }
},
{
path: 'equipment/water-supplier',
name: 'water-supplier',

@ -0,0 +1,383 @@
<!-- src/views/equipment/WaterSupplierDetail.vue -->
<template>
<div class="water-supplier-detail-page">
<!-- 页面标题和面包屑 -->
<div class="page-header">
<h2>供水机详情</h2>
<div class="breadcrumb">校园矿化水平台 / 设备监控 / 供水机 / 详情</div>
</div>
<!-- 返回按钮 -->
<div class="back-button-container">
<button class="btn-back" @click="goBack"> </button>
</div>
<!-- 设备基本信息卡片 -->
<div class="card detail-card" v-if="deviceDetail">
<h3>设备基本信息</h3>
<div class="detail-grid">
<div class="detail-item">
<span class="label">设备ID:</span>
<span class="value">{{ deviceDetail.deviceInfo?.deviceId }}</span>
</div>
<div class="detail-item">
<span class="label">设备名称:</span>
<span class="value">{{ deviceDetail.deviceInfo?.deviceName }}</span>
</div>
<div class="detail-item">
<span class="label">设备类型:</span>
<span class="value">{{ formatDeviceType(deviceDetail.deviceInfo?.deviceType) }}</span>
</div>
<div class="detail-item">
<span class="label">所属片区:</span>
<span class="value">{{ deviceDetail.deviceInfo?.areaId }}</span>
</div>
<div class="detail-item">
<span class="label">安装位置:</span>
<span class="value">{{ deviceDetail.deviceInfo?.installLocation }}</span>
</div>
<div class="detail-item">
<span class="label">设备状态:</span>
<span class="value">
<span :class="`status-tag ${deviceDetail.deviceInfo?.status}`">
{{ formatStatus(deviceDetail.deviceInfo?.status) }}
</span>
</span>
</div>
<div class="detail-item">
<span class="label">创建时间:</span>
<span class="value">{{ formatDate(deviceDetail.deviceInfo?.createTime) }}</span>
</div>
<div class="detail-item">
<span class="label">最后心跳时间:</span>
<span class="value">{{ formatDate(deviceDetail.deviceInfo?.lastHeartbeatTime) }}</span>
</div>
</div>
</div>
<!-- 实时数据卡片 -->
<div class="card detail-card" v-if="deviceDetail && deviceDetail.realtimeData">
<h3>实时数据</h3>
<div class="detail-grid">
<div class="detail-item">
<span class="label">水压:</span>
<span class="value">{{ deviceDetail.realtimeData?.waterPress || '-' }} MPa</span>
</div>
<div class="detail-item">
<span class="label">流量:</span>
<span class="value">{{ deviceDetail.realtimeData?.waterFlow || '-' }} /h</span>
</div>
<div class="detail-item">
<span class="label">温度:</span>
<span class="value">{{ deviceDetail.realtimeData?.temperature || '-' }} °C</span>
</div>
<div class="detail-item">
<span class="label">水质(TDS):</span>
<span class="value">{{ deviceDetail.realtimeData?.tds || '-' }} ppm</span>
</div>
<div class="detail-item">
<span class="label">运行状态:</span>
<span class="value">
<span :class="`status-tag ${deviceDetail.realtimeData?.status || 'normal'}`">
{{ formatRunningStatus(deviceDetail.realtimeData?.status) }}
</span>
</span>
</div>
<div class="detail-item">
<span class="label">数据记录时间:</span>
<span class="value">{{ formatDate(deviceDetail.realtimeData?.timestamp) }}</span>
</div>
</div>
</div>
<!-- 加载中提示 -->
<div v-if="loading" class="loading">
正在加载设备详情...
</div>
<!-- 错误提示 -->
<div v-if="error" class="error-message">
{{ error }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { request } from '@/api/request'
import type { ResultVO } from '@/api/types/auth'
// -
interface DeviceInfo {
deviceId: string
deviceName: string
deviceType: string
areaId: string
installLocation: string
status: string
createTime?: string
lastHeartbeatTime?: string
}
// WaterSupplyRealtimeData
interface WaterSupplyRealtimeData {
waterFlow?: number //
waterPress?: number //
waterLevel?: number //
temperature?: number //
status?: string //
timestamp?: string //
createdTime?: string //
}
interface DeviceDetail {
deviceInfo: DeviceInfo
realtimeData?: WaterSupplyRealtimeData
}
//
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
const deviceDetail = ref<DeviceDetail | null>(null)
const loading = ref(true)
const error = ref('')
// ID
const deviceId = route.params.id as string
//
const formatDeviceType = (type: string | undefined): string => {
if (!type) return '-'
const typeMap: Record<string, string> = {
'water_maker': '制水机',
'water_supply': '供水机'
}
return typeMap[type] || type
}
//
const formatStatus = (status: string | undefined): string => {
if (!status) return '-'
const statusMap: Record<string, string> = {
'online': '在线',
'offline': '离线',
'fault': '故障',
'warning': '警告'
}
return statusMap[status] || status
}
//
const formatRunningStatus = (status: string | undefined): string => {
if (!status) return '正常'
const statusMap: Record<string, string> = {
'normal': '正常',
'warning': '警告',
'error': '故障'
}
return statusMap[status] || status
}
//
const formatDate = (dateString: string | undefined): string => {
if (!dateString) return '-'
try {
const date = new Date(dateString)
return date.toLocaleString('zh-CN')
} catch {
return '-'
}
}
//
const goBack = () => {
router.go(-1)
}
//
const loadDeviceDetail = async () => {
try {
loading.value = true
error.value = ''
// token
const token = authStore.token
if (!token) {
router.push('/login')
return
}
//
const result = await request<ResultVO<DeviceDetail>>(
`/api/web/device/${deviceId}`,
{ method: 'GET' }
)
if (result.code === 200 && result.data) {
deviceDetail.value = result.data
console.log('设备详情数据:', deviceDetail.value)
} else {
error.value = result.message || '获取设备详情失败'
}
} catch (err) {
console.error('加载设备详情失败:', err)
error.value = '加载设备详情失败'
if ((err as Error).message.includes('401')) {
authStore.logout()
router.push('/login')
}
} finally {
loading.value = false
}
}
//
onMounted(() => {
if (deviceId) {
loadDeviceDetail()
} else {
error.value = '无效的设备ID'
loading.value = false
}
})
</script>
<style scoped>
.water-supplier-detail-page {
padding: 20px;
}
.page-header {
margin-bottom: 24px;
}
.page-header h2 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.breadcrumb {
color: #666;
font-size: 14px;
}
.back-button-container {
margin-bottom: 20px;
}
.btn-back {
background: #f0f0f0;
color: #333;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.btn-back:hover {
background: #e0e0e0;
}
.detail-card {
margin-bottom: 24px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 20px;
}
.detail-card h3 {
margin-top: 0;
margin-bottom: 20px;
font-size: 18px;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.detail-item {
display: flex;
flex-direction: column;
min-height: 60px;
padding: 8px 0;
}
.label {
font-weight: 500;
color: #666;
margin-bottom: 4px;
font-size: 14px;
}
.value {
font-size: 16px;
color: #333;
min-height: 24px;
}
.status-tag {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
min-width: 60px;
text-align: center;
}
.status-tag.online,
.status-tag.normal {
background-color: #e6f7ee;
color: #00875a;
}
.status-tag.offline {
background-color: #f5f5f5;
color: #8c8c8c;
}
.status-tag.warning {
background-color: #fff7e6;
color: #d48806;
}
.status-tag.fault,
.status-tag.error {
background-color: #ffebe6;
color: #cf1322;
}
.loading, .error-message {
text-align: center;
padding: 40px 0;
font-size: 16px;
}
.error-message {
color: #ff4d4f;
}
/* 响应式设计 */
@media (max-width: 768px) {
.detail-grid {
grid-template-columns: 1fr;
}
.water-supplier-detail-page {
padding: 16px;
}
}
</style>
Loading…
Cancel
Save