feat(enm): 新增瓮福数据页面(API + 前端)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shijing 2026-07-21 04:19:10 -04:00
parent 806eb80491
commit 74da00c70e
2 changed files with 331 additions and 0 deletions

View File

@ -252,6 +252,35 @@ export default {
); );
} }
}, },
wengfu_mplogx: {
list: {
name: "瓮福采集数据",
req: async function(data){
return await http.get(
`${config.API_URL}/enm/wengfu_mplogx/`,
data
);
}
},
export: {
name: "瓮福数据导出",
req: async function(data){
return await http.post(
`${config.API_URL}/enm/wengfu_mplogx/`,
data
);
}
},
},
wengfu_mpoints: {
name: "瓮福测点列表",
req: async function(data){
return await http.get(
`${config.API_URL}/enm/wengfu_mpoints/`,
data
);
}
},
enstat2: { enstat2: {
name: "全厂统计记录", name: "全厂统计记录",
req: async function(data){ req: async function(data){

View File

@ -0,0 +1,302 @@
<template>
<el-container>
<el-header style="height:auto; padding-bottom:10px;">
<div style="display:flex; flex-wrap:wrap; align-items:center; gap:10px;">
<el-select
v-model="selectedMpoints"
multiple
filterable
remote
reserve-keyword
placeholder="搜索并选择测点"
:remote-method="searchMpoints"
:loading="mpointLoading"
style="min-width:360px;"
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="3"
@visible-change="onSelectVisible"
popper-class="mpoint-select-popper"
>
<el-option
v-for="item in mpointOptions"
:key="item.mpoint_id"
:label="`${item.mpoint_id} - ${item.point_name || ''}`"
:value="item.mpoint_id"
/>
<div v-if="mpointTotal > mpointPageSize" style="text-align:center; padding:6px 0; border-top:1px solid #ebeef5;">
<el-pagination
small
layout="prev, pager, next"
:total="mpointTotal"
:page-size="mpointPageSize"
v-model:current-page="mpointPage"
@current-change="onMpointPageChange"
@click.stop
/>
</div>
</el-select>
<el-date-picker
v-model="timeRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width:380px;"
/>
<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>
<el-button :type="showChart ? 'warning' : 'info'" icon="el-icon-data-line" @click="toggleChart">
{{ showChart ? '隐藏图表' : '显示图表' }}
</el-button>
</div>
</el-header>
<el-main class="nopadding">
<div v-if="showChart && chartData.length > 0" ref="chartRef" style="width:100%; height:350px; margin-bottom:10px;"></div>
<scTable
ref="table"
:apiObj="apiObj"
row-key="mpoint_id"
stripe
:query="query"
>
<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">
<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 ? '是' : '否') : '' }}
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
</template>
<script setup>
import { ref, onMounted, nextTick, watch } from 'vue'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
import API from '@/api'
import { ElMessage } from 'element-plus'
const apiObj = ref(API.enm.wengfu_mplogx.list)
const query = ref({})
const table = ref(null)
const selectedMpoints = ref([])
const timeRange = ref([])
const mpointOptions = ref([])
const mpointLoading = ref(false)
const mpointTotal = ref(0)
const mpointPage = ref(1)
const mpointPageSize = 50
let mpointKeyword = ''
const showChart = ref(false)
const chartData = ref([])
const chartRef = ref(null)
let chartInstance = null
const exporting = ref(false)
const fetchMpoints = async (keyword, page) => {
mpointLoading.value = true
try {
const res = await API.enm.wengfu_mpoints.req({ keyword: keyword, page: page, page_size: mpointPageSize })
mpointOptions.value = res.results || []
mpointTotal.value = res.count || 0
} finally {
mpointLoading.value = false
}
}
const searchMpoints = (keyword) => {
mpointKeyword = keyword || ''
mpointPage.value = 1
fetchMpoints(mpointKeyword, 1)
}
const onMpointPageChange = (page) => {
fetchMpoints(mpointKeyword, page)
}
const onSelectVisible = (visible) => {
if (visible && mpointOptions.value.length === 0) {
searchMpoints('')
}
}
onMounted(() => {
searchMpoints('')
})
const handleQuery = () => {
const q = {}
if (selectedMpoints.value.length > 0) {
q.mpoint_ids = selectedMpoints.value.join(',')
}
if (timeRange.value && timeRange.value.length === 2) {
q.timex__gte = timeRange.value[0]
q.timex__lte = timeRange.value[1]
}
query.value = q
nextTick(() => {
table.value.refresh()
})
if (showChart.value && selectedMpoints.value.length > 0 && timeRange.value?.length === 2) {
loadChartData()
}
}
const toggleChart = () => {
showChart.value = !showChart.value
if (showChart.value && selectedMpoints.value.length > 0 && timeRange.value?.length === 2) {
loadChartData()
}
}
const loadChartData = async () => {
if (!timeRange.value || timeRange.value.length < 2 || selectedMpoints.value.length === 0) {
ElMessage.warning('请选择测点和时间范围后查看图表')
return
}
try {
const res = await API.enm.wengfu_mplogx.export.req({
mpoint_ids: selectedMpoints.value,
timex__gte: timeRange.value[0],
timex__lte: timeRange.value[1],
})
chartData.value = res
nextTick(() => { renderChart() })
} catch (e) {
ElMessage.error('加载图表数据失败')
}
}
const renderChart = () => {
if (!chartRef.value) return
if (chartInstance) chartInstance.dispose()
chartInstance = echarts.init(chartRef.value)
const groupedVal = {}
const groupedBool = {}
let hasBool = false
chartData.value.forEach(item => {
const key = item.mpoint_name || item.mpoint_id
if (!groupedVal[key]) groupedVal[key] = []
groupedVal[key].push([item.timex, item.val])
if (item.val_bool != null) {
hasBool = true
if (!groupedBool[key]) groupedBool[key] = []
groupedBool[key].push([item.timex, item.val_bool ? 1 : 0])
}
})
const series = Object.entries(groupedVal).map(([name, data]) => ({
name,
type: 'line',
data,
smooth: true,
symbol: 'none',
yAxisIndex: 0,
}))
Object.entries(groupedBool).forEach(([name, data]) => {
series.push({
name: name + '(布尔)',
type: 'line',
data,
yAxisIndex: 1,
step: 'end',
symbol: 'none',
lineStyle: { width: 2 },
areaStyle: { opacity: 0.15 },
})
})
chartInstance.setOption({
tooltip: {
trigger: 'axis',
valueFormatter: (v) => v != null ? Number(v).toFixed(4) : '',
},
legend: { type: 'scroll', top: 0 },
grid: { top: 40, right: hasBool ? 60 : 20, bottom: 30, left: 60 },
xAxis: { type: 'time' },
yAxis: [
{ type: 'value', name: '监测值' },
{ type: 'value', name: '布尔值', min: 0, max: 1, splitNumber: 1, show: hasBool,
axisLabel: { formatter: v => v === 1 ? '是' : '否' } },
],
dataZoom: [{ type: 'inside' }],
series,
})
}
const handleExport = async () => {
if (!timeRange.value || timeRange.value.length < 2 || selectedMpoints.value.length === 0) {
ElMessage.warning('请选择测点和时间范围后导出')
return
}
exporting.value = true
try {
const res = await API.enm.wengfu_mplogx.export.req({
mpoint_ids: selectedMpoints.value,
timex__gte: timeRange.value[0],
timex__lte: timeRange.value[1],
})
if (!res || res.length === 0) {
ElMessage.warning('没有数据可导出')
return
}
exportToExcel(res)
ElMessage.success('导出成功')
} catch (e) {
ElMessage.error('导出失败')
} finally {
exporting.value = false
}
}
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 ? '是' : '否') : ''
}
return row
})
const ws = XLSX.utils.json_to_sheet(rows)
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, '瓮福数据')
XLSX.writeFile(wb, `瓮福数据_${timeRange.value[0].slice(0,10)}_${timeRange.value[1].slice(0,10)}.xlsx`)
}
watch(showChart, (val) => {
if (!val && chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style>
.mpoint-select-popper .el-select-dropdown__list {
max-height: 400px !important;
}
.mpoint-select-popper .el-scrollbar__wrap {
max-height: 400px !important;
}
</style>