修改新增加文件数量的查询功能
This commit is contained in:
@ -5,7 +5,7 @@
|
||||
</main>
|
||||
|
||||
<footer class="version-footer">
|
||||
2.4版本 © 2026 Device Monitor
|
||||
2.5版本(加入每日数据个数) © 2026 Device Monitor
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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; }
|
||||
|
||||
177
zhandianxinxi/光谱数据监控/src/views/FileHistoryDialog.vue
Normal file
177
zhandianxinxi/光谱数据监控/src/views/FileHistoryDialog.vue
Normal 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>
|
||||
Reference in New Issue
Block a user