修改新增加文件数量的查询功能

This commit is contained in:
DXC
2026-02-03 17:15:42 +08:00
parent 195c3f8fa4
commit e093ae9633
12 changed files with 548 additions and 172 deletions

View File

@ -5,7 +5,7 @@
</main>
<footer class="version-footer">
2.4版本 © 2026 Device Monitor
2.5版本加入每日数据个数 © 2026 Device Monitor
</footer>
</div>
</template>

View File

@ -103,6 +103,25 @@
</template>
</el-table-column>
<el-table-column label="今日文件" width="120" align="center">
<template #default="{ row }">
<el-tooltip content="点击查看历史文件趋势" placement="top">
<div
class="file-count-cell"
@click="openHistoryDialog(row)"
:class="{ 'has-data': row.file_count > 0 }"
>
<el-tag v-if="row.file_count > 0" type="primary" effect="plain" round size="small">
{{ row.file_count }}
</el-tag>
<span v-else style="color: #ccc; font-size: 12px;">--</span>
<el-icon v-if="!row.is_hidden" class="history-icon"><Histogram /></el-icon>
</div>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="安装地点" min-width="140">
<template #default="{ row }">
<div v-if="row.isEditingSite" class="editing-cell">
@ -235,6 +254,8 @@
<MaintenanceLogs ref="maintenanceLogsRef" />
<IoTDeviceBinder ref="iotBinderRef" @update-success="fetchData" />
<FileHistoryDialog ref="fileHistoryRef" />
<el-dialog v-model="showAddDialog" title="手动添加设备" width="400px" align-center>
<el-form :model="newDeviceForm" label-width="80px">
<el-form-item label="设备名称">
@ -262,11 +283,13 @@ import { ref, reactive, computed, onMounted, nextTick, onBeforeUnmount } from 'v
import { useRouter } from 'vue-router'
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Clock, DataLine, Document, Refresh, EditPen, Search, Edit, RefreshRight, Delete, RefreshLeft, SwitchButton, Warning, WarningFilled, Plus, Odometer, Link } from '@element-plus/icons-vue'
import { Clock, DataLine, Document, Refresh, EditPen, Search, Edit, RefreshRight, Delete, RefreshLeft, SwitchButton, Warning, WarningFilled, Plus, Odometer, Link, Histogram } from '@element-plus/icons-vue'
import DataMonitor from './DataMonitor.vue'
import MaintenanceLogs from './MaintenanceLogs.vue'
import IoTDeviceBinder from './IoTDeviceBinder.vue'
// ✅ 引入新组件
import FileHistoryDialog from './FileHistoryDialog.vue'
const router = useRouter()
const loading = ref(false)
@ -288,6 +311,8 @@ const API_BASE = import.meta.env.DEV ? 'http://127.0.0.1:5000' : ''
const dataMonitorRef = ref(null)
const maintenanceLogsRef = ref(null)
const iotBinderRef = ref(null)
// ✅ 定义新的 ref
const fileHistoryRef = ref(null)
const showAddDialog = ref(false)
const isAdding = ref(false)
@ -422,10 +447,6 @@ const fetchData = async () => {
} else if (diffHours > 24) {
statusLabel = '滞后'; statusColor = '#E6A23C'; statusType = 'warning';
statusReason = `滞后 ${Math.floor(diffDays)}`;
// === 注意:这里没有把 trafficWarning 加入到 sortWeight 或 statusType 的改变逻辑中 ===
// 从而实现了“只标黄文字,不改变行状态,不置顶”
} else if (expireWarning) {
statusLabel = '即将过期'; statusColor = '#E6A23C'; statusType = 'warning';
statusReason = `即将过期`;
@ -451,7 +472,8 @@ const fetchData = async () => {
currentValueNum,
trafficNum,
trafficWarning,
expireWarning
expireWarning,
file_count: item.file_count || 0 // ✅ 绑定后端返回的文件数字段
}
}).sort((a, b) => b.sortWeight - a.sortWeight)
@ -500,6 +522,14 @@ const totalUsageSum = computed(() => {
})
// === 交互函数 ===
// ✅ 新增:打开历史记录弹窗
const openHistoryDialog = (row) => {
if (row.is_hidden) return
if (fileHistoryRef.value) {
fileHistoryRef.value.open(row)
}
}
const handleAddDeviceSubmit = async () => { if (!newDeviceForm.name) return; await axios.post(`${API_BASE}/api/add_device`, newDeviceForm); showAddDialog.value=false; fetchData() }
const handleDeviceClick = (row) => { if (!row.is_hidden && dataMonitorRef.value) dataMonitorRef.value.open(row) }
const openLogCenter = (row) => { if (maintenanceLogsRef.value) maintenanceLogsRef.value.open(row ? { deviceName: row.name } : null) }
@ -567,6 +597,34 @@ onBeforeUnmount(() => window.removeEventListener('resize', updateDimensions))
:deep(.data-error-row) { background-color: #ffe6e6 !important; }
:deep(.data-warning-row) { background-color: #fffbe6 !important; }
/* ✅ 新增样式 */
.file-count-cell {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
transition: all 0.2s;
padding: 4px;
border-radius: 4px;
}
.file-count-cell:hover {
background-color: #f0f9ff;
}
.file-count-cell:hover .el-tag {
transform: scale(1.05);
}
.history-icon {
font-size: 14px;
color: #909399;
opacity: 0;
transition: opacity 0.2s;
}
.file-count-cell:hover .history-icon {
opacity: 1;
color: #409EFF;
}
@media screen and (max-width: 768px) {
.dashboard-container { padding: 5px; }
.left-panel, .header-actions { width: 100%; justify-content: space-between; margin-bottom: 5px; }

View File

@ -0,0 +1,177 @@
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="600px"
align-center
@closed="handleClose"
>
<div class="history-container">
<el-table
:data="paginatedData"
border
stripe
style="width: 100%"
v-loading="loading"
height="400"
>
<el-table-column prop="date" label="数据日期" align="center">
<template #default="{ row }">
<el-icon><Calendar /></el-icon> {{ row.dateDisplay }}
</template>
</el-table-column>
<el-table-column prop="count" label="文件个数" align="center">
<template #default="{ row }">
<el-tag :type="row.count > 0 ? 'primary' : 'info'" effect="plain">
{{ row.count }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<el-config-provider :locale="zhCn">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 50, 100]"
layout="total, sizes, prev, pager, next"
:total="total"
background
/>
</el-config-provider>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed } from 'vue'
import axios from 'axios'
import { Calendar } from '@element-plus/icons-vue'
import { ElMessage, ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
const visible = ref(false)
const loading = ref(false)
const currentDeviceName = ref('')
const dialogTitle = ref('')
// 数据源
const allTableData = ref([]) // 存放去重、排序后的所有数据
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0) // 这里的 total 是去重后的总天数
const API_BASE = import.meta.env.DEV ? 'http://127.0.0.1:5000' : ''
// 格式化工具:提取 YYYY-MM-DD
const getDayKey = (raw) => {
if (!raw) return ''
// 兼容 2026_02_03 和 2026-02-03 格式,且去掉时分秒
return raw.replace(/_/g, '-').split(' ')[0]
}
// 计算属性:实现前端分页切片
// 这里的逻辑是:从“总数据”中,切出“当前页”需要显示的那几条
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return allTableData.value.slice(start, end)
})
const open = (device) => {
if (!device || !device.name) return
currentDeviceName.value = device.name
dialogTitle.value = `📜 ${formatDisplayName(device.name)} - 历史文件记录`
visible.value = true
currentPage.value = 1
fetchData()
}
const fetchData = async () => {
loading.value = true
try {
// 关键点:为了让前端能准确去重和排序,这里 limit 设得很大,意图拉取“全部”或“近期所有”数据
// 如果不拉全量数据,就无法保证跨页去重的准确性
const res = await axios.get(`${API_BASE}/api/device_history_list`, {
params: {
name: currentDeviceName.value,
page: 1,
limit: 1000 // 拉取足够多的数据在前端处理
}
})
if (res.data.code === 200) {
const rawList = res.data.data || []
// 1. 分组去重:同一天取 count 最大的
const dateMap = new Map()
rawList.forEach(item => {
const dayStr = getDayKey(item.date)
if (!dayStr) return
if (dateMap.has(dayStr)) {
const exist = dateMap.get(dayStr)
// 如果当前数据的 count 比已记录的大,就替换掉
if (item.count > exist.count) {
// 保留原始 item并附加格式化好的日期方便展示
dateMap.set(dayStr, { ...item, dateDisplay: dayStr })
}
} else {
dateMap.set(dayStr, { ...item, dateDisplay: dayStr })
}
})
// 2. 转为数组
const processedList = Array.from(dateMap.values())
// 3. 强制排序:按日期字符串降序 (2026-02-03 -> 2026-02-01)
processedList.sort((a, b) => {
return b.dateDisplay.localeCompare(a.dateDisplay)
})
// 4. 赋值
allTableData.value = processedList
// 5. 修正 Total现在的 Total 是“有多少个不同的日期”,而不是后端的 raw total
total.value = processedList.length
} else {
ElMessage.error(res.data.message || '获取历史记录失败')
}
} catch (e) {
console.error(e)
ElMessage.error('网络请求异常')
} finally {
loading.value = false
}
}
const handleClose = () => {
allTableData.value = []
}
const formatDisplayName = (name) => name ? name.toUpperCase().replace(/_/g, ' ') : ''
defineExpose({ open })
</script>
<style scoped>
.history-container {
padding: 10px;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>