| | |
| | | |
| | | <section class="table-panel"> |
| | | <h3 class="panel-title">设备环境数据列表</h3> |
| | | <div class="sensor-table"> |
| | | <div class="sensor-table__head"> |
| | | <span>设备编号</span> |
| | | <span>设备名称</span> |
| | | <span>状态</span> |
| | | <span>电量</span> |
| | | <span>温度</span> |
| | | <span>湿度</span> |
| | | <span>二氧化碳</span> |
| | | <span>光照</span> |
| | | <span>操作</span> |
| | | </div> |
| | | <div v-for="item in deviceRows" :key="item.guid" class="sensor-table__row"> |
| | | <span>{{ item.guid }}</span> |
| | | <span>{{ item.name }}</span> |
| | | <span> |
| | | <el-tag |
| | | v-if="item.statusLabel !== '-'" |
| | | :type="item.statusTagType" |
| | | effect="light" |
| | | size="small" |
| | | > |
| | | {{ item.statusLabel }} |
| | | <el-table :data="deviceRows" |
| | | border |
| | | stripe |
| | | empty-text="暂无环境数据" |
| | | show-overflow-tooltip> |
| | | <el-table-column label="设备编号" prop="guid" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="设备名称" prop="name" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="状态" prop="statusLabel" width="80" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-tag v-if="row.statusLabel !== '-'" |
| | | :type="row.statusTagType" |
| | | effect="light" |
| | | size="small"> |
| | | {{ row.statusLabel }} |
| | | </el-tag> |
| | | <span v-else>{{ item.statusLabel }}</span> |
| | | </span> |
| | | <span>{{ item.battery }}</span> |
| | | <span>{{ item.temperature }}</span> |
| | | <span>{{ item.humidity }}</span> |
| | | <span>{{ item.co2 }}</span> |
| | | <span>{{ item.light }}</span> |
| | | <span class="sensor-table__action"> |
| | | <el-button type="primary" link @click="openHistoryDialog(item)"> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="电量" prop="battery" width="80" align="center" /> |
| | | <el-table-column label="温度" prop="temperature" width="90" align="center" /> |
| | | <el-table-column label="湿度" prop="humidity" width="90" align="center" /> |
| | | <el-table-column label="二氧化碳" prop="co2" width="100" align="center" /> |
| | | <el-table-column label="光照" prop="light" width="90" align="center" /> |
| | | <el-table-column label="存放位置" prop="storageLocation" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="附件" width="100" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button v-if="row.commonFileListVO && row.commonFileListVO.length" |
| | | type="primary" |
| | | link |
| | | @click="openFileDialog(row)"> |
| | | 查看附件 |
| | | </el-button> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="130" align="center" fixed="right"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openHistoryDialog(row)"> |
| | | 查看历史数据 |
| | | </el-button> |
| | | </span> |
| | | </div> |
| | | <div v-if="!deviceRows.length" class="sensor-table__empty">暂无环境数据</div> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </section> |
| | | |
| | | <el-dialog |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="history-table"> |
| | | <div class="history-table__head"> |
| | | <span>时间</span> |
| | | <span>温度</span> |
| | | <span>湿度</span> |
| | | <span>二氧化碳</span> |
| | | <span>光照</span> |
| | | </div> |
| | | <div v-for="item in historyRows" :key="`${item.time}-${item.index}`" class="history-table__row"> |
| | | <span>{{ item.time }}</span> |
| | | <span>{{ item.temperature }}</span> |
| | | <span>{{ item.humidity }}</span> |
| | | <span>{{ item.co2 }}</span> |
| | | <span>{{ item.light }}</span> |
| | | </div> |
| | | <div v-if="!historyRows.length" class="sensor-table__empty">暂无历史数据</div> |
| | | <el-table :data="historyRows" |
| | | v-loading="historyLoading" |
| | | border |
| | | stripe |
| | | empty-text="暂无历史数据" |
| | | show-overflow-tooltip> |
| | | <el-table-column label="时间" prop="time" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="温度" prop="temperature" width="100" align="center" /> |
| | | <el-table-column label="湿度" prop="humidity" width="100" align="center" /> |
| | | <el-table-column label="二氧化碳" prop="co2" width="100" align="center" /> |
| | | <el-table-column label="光照" prop="light" width="100" align="center" /> |
| | | </el-table> |
| | | </el-dialog> |
| | | |
| | | <el-dialog |
| | | v-model="fileDialogVisible" |
| | | title="查看附件" |
| | | width="600px" |
| | | append-to-body |
| | | destroy-on-close |
| | | > |
| | | <div class="file-dialog__meta"> |
| | | <span>设备编号:{{ fileDevice.guid || "-" }}</span> |
| | | <span>设备名称:{{ fileDevice.name || "-" }}</span> |
| | | </div> |
| | | <el-table :data="fileList" |
| | | border |
| | | stripe |
| | | empty-text="暂无附件" |
| | | show-overflow-tooltip> |
| | | <el-table-column label="附件名称" prop="originalFilename" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column label="操作" width="120" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="downloadAttachment(row)"> |
| | | 下载 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onBeforeUnmount, onMounted, ref } from "vue"; |
| | | import { computed, getCurrentInstance, onBeforeUnmount, onMounted, ref } from "vue"; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { |
| | | getEnvironmentalHistoryData, |
| | | getEnvironmentalRealData, |
| | | } from "@/api/inventoryManagement/environmentalMonitoring"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const POLL_INTERVAL = import.meta.env.DEV ? 73000 : 30000; |
| | | const TEN_DAYS_MS = 10 * 24 * 60 * 60 * 1000; |
| | | |
| | |
| | | const historyDate = ref(formatDateOnly(new Date())); |
| | | const historyList = ref([]); |
| | | const historyLoading = ref(false); |
| | | const fileDialogVisible = ref(false); |
| | | const fileDevice = ref({}); |
| | | const fileList = ref([]); |
| | | |
| | | let pollTimer = null; |
| | | |
| | |
| | | name: source.deviceName || source.name || `设备${index + 1}`, |
| | | status: source.status || source.deviceStatus || "", |
| | | battery: source.battery ?? source.deviceBattery ?? "", |
| | | storageLocation: source.storageLocation || source.location || "", |
| | | commonFileListVO: source.commonFileListVO || [], |
| | | temperature: 0, |
| | | humidity: 0, |
| | | co2: 0, |
| | |
| | | humidity: formatMetricValue(item.humidity, "%RH"), |
| | | co2: formatMetricValue(item.co2, "ppm"), |
| | | light: formatMetricValue(item.light, "Lux"), |
| | | storageLocation: item.storageLocation || "-", |
| | | commonFileListVO: item.commonFileListVO || [], |
| | | })) |
| | | ); |
| | | |
| | |
| | | fetchHistoryData(); |
| | | } |
| | | |
| | | function openFileDialog(row) { |
| | | fileDevice.value = row; |
| | | fileList.value = row.commonFileListVO || []; |
| | | fileDialogVisible.value = true; |
| | | } |
| | | |
| | | function downloadAttachment(file) { |
| | | if (!file.url) { |
| | | proxy.$modal.msgWarning("附件链接不存在"); |
| | | return; |
| | | } |
| | | proxy.$download.byUrl(file.url, file.originalFilename || file.fileName); |
| | | } |
| | | |
| | | function openHistoryDialog(row) { |
| | | historyDevice.value = { ...row }; |
| | | historyDate.value = formatDateOnly(new Date()); |
| | |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .sensor-table, |
| | | .history-table { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .sensor-table__head, |
| | | .sensor-table__row { |
| | | display: grid; |
| | | grid-template-columns: 1.2fr 1fr 0.8fr 0.8fr 0.9fr 0.9fr 1fr 0.9fr 0.9fr; |
| | | gap: 12px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .history-table__head, |
| | | .history-table__row { |
| | | display: grid; |
| | | grid-template-columns: 1.3fr 1fr 1fr 1fr 1fr; |
| | | gap: 12px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .sensor-table__head, |
| | | .history-table__head { |
| | | padding: 0 6px 10px; |
| | | color: #8393a8; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .sensor-table__row, |
| | | .history-table__row { |
| | | padding: 14px 16px; |
| | | border-radius: 12px; |
| | | background: #f6f9fc; |
| | | color: #1d344f; |
| | | } |
| | | |
| | | .sensor-table__action { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .sensor-table__empty { |
| | | padding: 32px 0; |
| | | color: #8393a8; |
| | | text-align: center; |
| | | } |
| | | |
| | | .history-toolbar { |
| | | display: flex; |
| | | justify-content: space-between; |
| | |
| | | align-items: center; |
| | | } |
| | | |
| | | .history-table { |
| | | margin-top: 16px; |
| | | .file-dialog__meta { |
| | | display: flex; |
| | | gap: 20px; |
| | | color: #1d344f; |
| | | margin-bottom: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | |
| | | .chart-panel, |
| | | .table-panel { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .sensor-table__head, |
| | | .sensor-table__row, |
| | | .history-table__head, |
| | | .history-table__row { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | |
| | | .history-toolbar { |