feat(enm): 新增瓮福数据页面(API + 前端)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
806eb80491
commit
74da00c70e
|
|
@ -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: {
|
||||
name: "全厂统计记录",
|
||||
req: async function(data){
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue