feat(hfnf): 总览页全屏/加载态 + 瓮福明细列合并与自动查询

- hfnf_index: 全屏按钮(浏览器全屏+CSS覆盖菜单,退出恢复菜单),loading 蒙层;
  标题改"工业生产数据采集总览",系统名称改"水泥生产数据采集";
  瓮福烧结矿卡片跳转 /wengfu_mplogx
- wengfu_mplogx: 监测值/布尔值合并为单列"监测值"(开关测点显示 开/关);
  测点与时间选择器 @change 自动查询
- hfnf api: 新增 stats 接口, mplogx 指向 10.0.11.52:5800
- router: BASE_INFO 缺失时回登录页(防空指针)
- vue.config: 新增 /hfnf_stats 代理

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shijing 2026-07-31 03:47:24 -04:00
parent 74da00c70e
commit 65c26f7d15
5 changed files with 104 additions and 24 deletions

View File

@ -66,8 +66,13 @@ router.beforeEach(async (to, from, next) => {
}
//加载动态/静态路由
if (!isGetRouter) {
let baseInfo = tool.data.get('BASE_INFO');
if (!baseInfo) {
next({ path: '/login' });
return false;
}
let userMenu = [];
if(tool.data.get('BASE_INFO').base.base_menucate=='dynamic'){
if(baseInfo.base.base_menucate=='dynamic'){
userMenu = tool.data.get("MENU") || [];
}else{
let permissions = tool.data.get("PERMISSIONS") || [];//账号页面权限
@ -114,7 +119,9 @@ router.onError((error) => {
//入侵追加自定义方法、对象
router.sc_getMenu = () => {
if(tool.data.get('BASE_INFO').base.base_menucate=='dynamic'){
let baseInfo = tool.data.get('BASE_INFO');
if(!baseInfo) return [];
if(baseInfo.base.base_menucate=='dynamic'){
return tool.data.get("MENU") || []
}
else{

View File

@ -1,5 +1,5 @@
<template>
<div class="hfnf-dashboard">
<div class="hfnf-dashboard" :class="{ 'is-fullscreen': isFullscreen }" v-loading="loading" element-loading-text="数据加载中..." element-loading-background="rgba(245, 247, 250, 0.85)">
<!-- 顶部总览横幅 -->
<div class="summary-banner">
<div class="banner-left">
@ -7,7 +7,7 @@
<img src="/media/logo2.png" class="banner-logo" />
</div>
<div class="banner-info">
<h2>水泥生产数据采集总览</h2>
<h2>工业生产数据采集总览</h2>
<p>覆盖 {{ factories.length }} 家工厂 · 实时数据监控</p>
</div>
</div>
@ -17,13 +17,17 @@
<span class="total-value">{{ formatNumber(totalCount) }}</span>
<span class="total-unit"></span>
</div>
<el-button type="primary" plain class="back-btn" @click="toggleFullscreen">
<el-icon><FullScreen /></el-icon>
{{ isFullscreen ? '退出全屏' : '进入全屏' }}
</el-button>
</div>
</div>
<!-- 工厂数据卡片 -->
<el-row :gutter="16" class="factory-row">
<el-col :xs="24" :sm="12" :md="6" v-for="(item, index) in factories" :key="item.name">
<div class="factory-card" :style="{ borderTopColor: item.color }">
<div class="factory-card" :style="{ borderTopColor: item.color }" @click="goDetail(item)">
<div class="card-header">
<div class="card-icon" :style="{ backgroundColor: item.bgColor }">
<el-icon :size="24" :color="item.color"><OfficeBuilding /></el-icon>
@ -114,7 +118,7 @@
</div>
<div class="sys-info-content">
<span class="sys-info-label">系统名称</span>
<span class="sys-info-value">水泥生产数据人工智能采集融合系统</span>
<span class="sys-info-value">水泥生产数据采集</span>
</div>
</div>
<div class="sys-info-item">
@ -190,8 +194,8 @@ const pieChartRef = ref(null)
let barChart = null
let pieChart = null
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c']
const bgColors = ['#ecf5ff', '#f0f9eb', '#fdf6ec', '#fef0f0']
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#9b59b6']
const bgColors = ['#ecf5ff', '#f0f9eb', '#fdf6ec', '#fef0f0', '#f5eef8']
//
const factoryDefs = [
@ -199,10 +203,13 @@ const factoryDefs = [
{ name: '中联万吨', match: (t) => t === 'mplogx_xzzl' }, //
{ name: '铜梁水泥', match: (t) => t === 'mplogx_tlxn' }, //
{ name: '槐坎南方水泥', match: (t) => t.startsWith('mplogx_hknf') }, //
{ name: '瓮福烧结矿', match: (t) => t === 'mplogx_wengfu' }, //
]
const totalCount = ref(0)
const tables = ref([])
const loading = ref(true)
const isFullscreen = ref(false)
const factories = computed(() => {
const total = totalCount.value
@ -227,11 +234,31 @@ const maxFactory = computed(() => {
})
const formatNumber = (num) => {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return (num || 0).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
const goDetail = () => {
const toggleFullscreen = () => {
// isFullscreen CSS class
if (document.fullscreenElement) {
document.exitFullscreen()
} else {
document.documentElement.requestFullscreen()
}
}
const handleFullscreenChange = () => {
isFullscreen.value = !!document.fullscreenElement
//
nextTick(() => handleResize())
}
const goDetail = (item) => {
//
if (item && item.name === '瓮福烧结矿') {
router.push('/wengfu_mplogx')
} else {
router.push('/hfnf_mplogx')
}
}
const goStandard = () => {
@ -351,8 +378,11 @@ onMounted(() => {
initBarChart()
initPieChart()
})
}).finally(() => {
loading.value = false
})
window.addEventListener('resize', handleResize)
document.addEventListener('fullscreenchange', handleFullscreenChange)
// 使退 popstate
history.pushState(null, '', location.href)
window.addEventListener('popstate', handlePopState)
@ -360,6 +390,7 @@ onMounted(() => {
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
document.removeEventListener('fullscreenchange', handleFullscreenChange)
window.removeEventListener('popstate', handlePopState)
barChart?.dispose()
pieChart?.dispose()
@ -373,6 +404,21 @@ onBeforeUnmount(() => {
min-height: calc(100vh - 60px);
}
/* 全屏态dashboard 固定铺满屏幕、遮住左侧菜单与顶栏;退出移除 class 即恢复 */
.hfnf-dashboard.is-fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
min-height: 100%;
z-index: 9999;
overflow-y: auto;
background: #f5f7fa;
}
/* 顶部横幅 */
.summary-banner {
display: flex;
@ -422,6 +468,22 @@ onBeforeUnmount(() => {
.banner-right {
text-align: right;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12px;
}
.back-btn {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.4);
color: #fff;
}
.back-btn:hover {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.6);
color: #fff;
}
.total-block {

View File

@ -16,6 +16,7 @@
collapse-tags-tooltip
:max-collapse-tags="3"
@visible-change="onSelectVisible"
@change="handleQuery"
popper-class="mpoint-select-popper"
>
<el-option
@ -44,6 +45,7 @@
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width:380px;"
@change="handleQuery"
/>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button type="success" icon="el-icon-download" @click="handleExport" :loading="exporting">导出Excel</el-button>
@ -64,14 +66,9 @@
<el-table-column label="测点名称" prop="mpoint_name" show-overflow-tooltip></el-table-column>
<el-table-column label="测点ID" prop="mpoint_id" show-overflow-tooltip></el-table-column>
<el-table-column label="监测时间" prop="timex" align="center"></el-table-column>
<el-table-column label="监测值" prop="val" align="right">
<el-table-column label="监测值" align="center">
<template #default="{ row }">
{{ row.val != null ? Number(row.val).toFixed(4) : '' }}
</template>
</el-table-column>
<el-table-column label="布尔值" prop="val_bool" align="center">
<template #default="{ row }">
{{ row.val_bool != null ? (row.val_bool ? '是' : '否') : '' }}
{{ formatVal(row) }}
</template>
</el-table-column>
</scTable>
@ -90,6 +87,17 @@ const apiObj = ref(API.enm.wengfu_mplogx.list)
const query = ref({})
const table = ref(null)
// val() val_bool(/)
const formatVal = (row) => {
if (row.val != null && row.val !== '') {
return Number(row.val).toFixed(4)
}
if (row.val_bool != null) {
return row.val_bool ? '开' : '关'
}
return ''
}
const selectedMpoints = ref([])
const timeRange = ref([])
const mpointOptions = ref([])
@ -265,16 +273,12 @@ const handleExport = async () => {
}
const exportToExcel = (data) => {
const hasBool = data.some(item => item.val_bool != null)
const rows = data.map(item => {
const row = {
'测点名称': item.mpoint_name || '',
'测点ID': item.mpoint_id,
'监测时间': item.timex,
'监测值': item.val,
}
if (hasBool) {
row['布尔值'] = item.val_bool != null ? (item.val_bool ? '是' : '否') : ''
'监测值': formatVal(item),
}
return row
})

View File

@ -28,6 +28,13 @@ module.exports = defineConfig({
pathRewrite: {
'^/api': '/'
}
},
'/hfnf_stats': {
target: 'http://10.0.11.52:5900',
changeOrigin: true,
pathRewrite: {
'^/hfnf_stats': ''
}
}
},
client: {