Browse Source

feat:实时数据

master
waibao2 3 days ago
parent
commit
84d5b525a5
  1. 311
      src/views/data/currdata/index-jd.vue
  2. 377
      src/views/data/currdata/index.vue

311
src/views/data/currdata/index-jd.vue

@ -0,0 +1,311 @@
<template>
<div class="app-container app-container-bg">
<el-card class="first-card" ref='firstCard' shadow="always">
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent>
<el-form-item label="测站属性" prop="prop">
<el-select v-model="queryParams.prop" placeholder="请选择测站属性" style="width:100%" @change="propChange">
<el-option v-for="dict in propsList" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="测站名称" prop="stnmId">
<el-select v-model="queryParams.stnmId" placeholder="请选择测站名称" style="width:100%" @change="handleQuery">
<el-option v-for="dict in stationList" :key="dict.stnmId" :label="dict.stnm" :value="dict.stnmId"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['data:currdata:export']">导出</el-button>
</el-col>
</el-row>
</el-card>
<div class="el-card-p card-shadow carder-border mt10 pad10 currdata" v-loading="fullscreenLoading">
<div class="table-container" v-if="processedTables.length > 0" v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="30">
<div class="table-grid" :style="{ gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)` }">
<div class="table-item" v-for="(table, index) in processedTables" :key="index">
<el-table :data="table.data" class="double-header-table" :show-header="true" :header-cell-style="{ backgroundColor: '#3F7DEC !important', }" style="width: 100%;" size="mini">
<el-table-column :label="table.stnm" width="120" show-overflow-tooltip>
<template #header #default="scope">
<div class="header-content">
<el-tooltip effect="dark" :content="table.stnm" placement="top-end" :open-delay="300">
<div class="item-station cursor">
<div>
{{ table.stnm }}
</div>
<div>{{ table.stcd }}</div>
</div>
</el-tooltip>
<div class="header-right ">
<span class="item-time">{{ table.tm }}</span>
</div>
</div>
</template>
<el-table-column prop="typeName" label="名称" show-overflow-tooltip />
<el-table-column prop="value" label="值" :class-name="value-name" />
<el-table-column prop="unit" label="单位" width="80" />
</el-table-column>
</el-table>
</div>
</div>
</div>
<el-empty class="table-container " v-else :image-size="200"></el-empty>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, } from 'vue'
import _ from 'lodash'
const { proxy } = getCurrentInstance()
const alignment = 'center'
const queryParams = reactive({
page: 1,
limit: 10,
stnmId: '',
prop: "",
mainType: "A"
})
const fullscreenLoading = ref(false)
let currdataList = []
let total = 0
const getList = async () => {
fullscreenLoading.value = true;
try {
const res = await proxy.axiosGet('/data/currdata/queryAll', queryParams)
if (res.code == 0) {
currdataList = res.data
total = currdataList.length
formatTableData()
}
} catch (error) {
} finally {
fullscreenLoading.value = false
}
}
let tableList = ref([])
const formatTableData = () => {
const result = _.chain(currdataList)
.groupBy('stnm')
.map((group, stnm) => ({
stnm,
stcd: group[0].stcd,
tm: group[0].latestTm,
data: group.map(item => ({
...item,
value: item.val !== null ? item.val : item.latestValue
}))
}))
.value();
tableList.value = result
}
//
const busy = ref(false)
const loadMore = async () => {
let all = total <= queryParams.page * queryParams.limit
if (busy.value && all) return; //
busy.value = true; //
queryParams.page += 1
let response = await proxy.axiosGet('/data/currdata/queryAll', queryParams);
if (response.code == 0) {
let rawData = response.data
let concatData = currdataList.concat(rawData)
currdataList = concatData
formatTableData()
}
busy.value = false;
}
const itemsPerRow = ref(5)
const processedTables = computed(() => {
let data = tableList.value
const result = [];
const rows = Math.ceil(data.length / itemsPerRow.value);
for (let i = 0; i < rows; i++) {
const startIndex = i * itemsPerRow.value;
const endIndex = Math.min(startIndex + itemsPerRow.value, data.length);
const rowTables = data.slice(startIndex, endIndex);
//
const maxRows = Math.max(...rowTables.map(table => table.data.length));
//
rowTables.forEach(table => {
const newTable = { ...table };
//
if (table.data.length < maxRows) {
const emptyRows = Array(maxRows - table.data.length).fill({});
newTable.data = [...table.data, ...emptyRows];
}
result.push(newTable);
});
}
return result;
})
//
const stationList = ref([])
const propChange = async () => {
try {
let res = await proxy.axiosGet('/basic/station/queryStnmandStnmId', queryParams);
if (res.code == 200) {
stationList.value = res.data;
}
} catch (error) {
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1
getList()
}
const resetQuery = () => {
proxy.resetForm("queryForm");
handleQuery();
}
//
const handleExport = async () => {
let p = {
page: 1,
limit: 9999,
stnm: queryParams.stnm,
mainType: queryParams.mainType,
}
let res = await proxy.axiosGet('/data/currdata/queryAll', p);
if (res.code === 0) {
let table = [];
table.push({
A: "站点名称",
B: "设备ID",
C: "类型",
D: "最新数值",
E: "最新上传时间",
});
res.data.forEach(d => {
let row = {
A: d.stnm,
B: d.stcd,
C: d.typeName,
D: d.latestValue,
E: d.latestTm
};
table.push(row);
});
let header = ["A", "B", "C", "D", "E"];
let fileName = "站点实时数据";
proxy.exportExcel(header, table, fileName);
}
}
onMounted(() => {
getList()
propChange()
})
const propsList = ref([
{ label: '水文测站', value: 0 },
{ label: '气象站', value: 1 }
])
</script>
<style lang="scss" scoped>
.currdata {
overflow-y: scroll;
}
.table-container {
width: 100%;
.table-grid {
display: grid;
grid-template-columns: repeat(var(--grid-columns), 1fr);
gap: 20px;
width: 100%;
.table-item {
min-width: 240px;
/* 防止表格溢出*/
}
}
.header-content {
display: flex;
justify-content: space-between;
flex-wrap: nowrap;
.item-station {
width: 150px;
white-space: nowrap !important;
text-overflow: ellipsis;
word-break: break-all;
overflow: hidden;
}
.item-station,
.item-icon,
.item-time {
font-size: 12px;
color: #fff;
/* flex-wrap: nowrap; */
}
.item-icon {
cursor: pointer;
margin-left: 2px;
}
}
}
/* 表格样式 */
:deep(.double-header-table) {
border-radius: 12px;
&::before {
display: none;
}
.el-table__fixed-right::before {
display: none;
}
.el-table__body-wrapper::-webkit-scrollbar {
display: none;
}
/* 统一行高 */
.el-table__row {
height: 39.6667px;
}
/* 空行样式 */
.el-table__row--empty {
background-color: #f5f7fa;
}
/* 隐藏子表头 */
.el-table__header tr:nth-child(2) {
display: none;
}
/* 调整主表头样式 */
.el-table__header tr:first-child th {
background: #f5f7fa;
font-weight: bold;
}
}
/** 取消表格滑上的背景色 */
:deep(.el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell) {
background: #fff !important;
}
</style>

377
src/views/data/currdata/index.vue

@ -2,14 +2,13 @@
<div class="app-container app-container-bg"> <div class="app-container app-container-bg">
<el-card class="first-card" ref='firstCard' shadow="always"> <el-card class="first-card" ref='firstCard' shadow="always">
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> <el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent>
<el-form-item label="测站属性" prop="prop"> <el-form-item label="测站名称" prop="stnm">
<el-select v-model="queryParams.prop" placeholder="请选择测站属性" style="width:100%" @change="propChange"> <el-input v-model="queryParams.stnm" placeholder="请输入测站名称" clearable @keyup.enter.native="handleQuery" />
<el-option v-for="dict in propsList" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="测站名称" prop="stnmId"> <el-form-item label="测站类型" prop="mainType">
<el-select v-model="queryParams.stnmId" placeholder="请选择测站名称" style="width:100%" @change="handleQuery"> <el-select v-model="queryParams.mainType" placeholder="请选择测站类型" @change="handleQuery">
<el-option v-for="dict in stationList" :key="dict.stnmId" :label="dict.stnm" :value="dict.stnmId"></el-option> <el-option v-for="item in mainTypes" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -23,143 +22,91 @@
</el-col> </el-col>
</el-row> </el-row>
</el-card> </el-card>
<div class="el-card-p card-shadow carder-border mt10 pad10 currdata" v-loading="fullscreenLoading"> <div class="el-card-p card-shadow carder-border mt10 pad10 ">
<div class="table-container" v-if="processedTables.length > 0" v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="30"> <el-table class="table-box" border v-table-height v-loading="loading" :data="currentData" :span-method="objectSpanMethod">
<div class="table-grid" :style="{ gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)` }"> <el-table-column label="名称" :align="alignment" prop="stnm" />
<div class="table-item" v-for="(table, index) in processedTables" :key="index"> <el-table-column label="设备ID" :align="alignment" prop="stcd" />
<el-table :data="table.data" class="double-header-table" :show-header="true" :header-cell-style="{ backgroundColor: '#3F7DEC !important', }" style="width: 100%;" size="mini"> <el-table-column label="最新数值" :align="alignment" prop="latestValue" />
<el-table-column :label="table.stnm" width="120" show-overflow-tooltip> <el-table-column label="最新上传时间" :align="alignment" prop="latestTm" width="180" />
<template #header #default="scope"> <el-table-column label="保证水位" :align="alignment" prop="bzsw" v-if="dataType == 'B' || dataType =='C' " />
<div class="header-content"> <el-table-column label="警戒水位" :align="alignment" prop="jjsw" v-if="dataType == 'B' || dataType =='C' " />
<el-tooltip effect="dark" :content="table.stnm" placement="top-end" :open-delay="300"> <el-table-column label="操作" :align="alignment" class-name="small-padding fixed-width">
<div class="item-station cursor"> <template #default="scope">
<div> <el-button type="text" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['data:currdata:edit']">修改</el-button>
{{ table.stnm }}
</div>
<div>{{ table.stcd }}</div>
</div>
</el-tooltip>
<div class="header-right ">
<span class="item-time">{{ table.tm }}</span>
</div>
</div>
</template> </template>
<el-table-column prop="typeName" label="名称" show-overflow-tooltip />
<el-table-column prop="value" label="值" :class-name="value-name" />
<el-table-column prop="unit" label="单位" width="80" />
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div> </div>
<el-dialog class="custom-dialog" :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
<el-form-item label="设备ID" prop="stcd">
<el-input v-model="form.stcd" :disabled="true" />
</el-form-item>
<el-form-item label="最新数值" prop="latestValue">
<el-input v-model="form.latestValue" placeholder="请输入最新数值" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm(formRef)" v-loading='btnLoading'> </el-button>
<el-button @click="cancel"> </el-button>
</div> </div>
</div> </template>
<el-empty class="table-container " v-else :image-size="200"></el-empty> </el-dialog>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, } from 'vue' import { ref, reactive, onMounted, } from 'vue'
import _ from 'lodash'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const alignment = 'center' const alignment = 'center'
//
const mainTypes = [
{
label: '雨量站',
value: 'A'
},
{
label: '河道站',
value: 'B'
},
{
label: '水库站',
value: 'C'
},
{
label: '潮位站',
value: 'D'
},
{
label: '流量站',
value: 'E'
}
]
const queryParams = reactive({ const queryParams = reactive({
page: 1, pageNum: 1,
limit: 10, pageSize: 10,
stnmId: '', stnm: '',
prop: "",
mainType:"A" mainType:"A"
}) })
const fullscreenLoading = ref(false) const loading = ref(false)
let currdataList = [] const total = ref(0)
let total = 0 const currentData = ref([])
const getList = async () => { const getList = async () => {
fullscreenLoading.value = true; loading.value = true;
try { try {
const res = await proxy.axiosGet('/data/currdata/queryAll', queryParams) const res = await proxy.axiosGet('/data/currdata/queryAll', queryParams)
if (res.code == 0) { if (res.code == 0) {
currdataList = res.data currentData.value = res.data
total = currdataList.length total.value = res.count
formatTableData()
} }
} catch (error) { } catch (error) {
} finally { } finally {
fullscreenLoading.value = false loading.value = false
}
}
let tableList = ref([])
const formatTableData = () => {
const result = _.chain(currdataList)
.groupBy('stnm')
.map((group, stnm) => ({
stnm,
stcd: group[0].stcd,
tm: group[0].latestTm,
data: group.map(item => ({
...item,
value: item.val !== null ? item.val : item.latestValue
}))
}))
.value();
tableList.value = result
}
//
const busy = ref(false)
const loadMore = async () => {
let all = total <= queryParams.page * queryParams.limit
if (busy.value && all) return; //
busy.value = true; //
queryParams.page += 1
let response = await proxy.axiosGet('/data/currdata/queryAll', queryParams);
if (response.code == 0) {
let rawData = response.data
let concatData = currdataList.concat(rawData)
currdataList = concatData
formatTableData()
}
busy.value = false;
}
const itemsPerRow = ref(5)
const processedTables = computed(() => {
let data = tableList.value
const result = [];
const rows = Math.ceil(data.length / itemsPerRow.value);
for (let i = 0; i < rows; i++) {
const startIndex = i * itemsPerRow.value;
const endIndex = Math.min(startIndex + itemsPerRow.value, data.length);
const rowTables = data.slice(startIndex, endIndex);
//
const maxRows = Math.max(...rowTables.map(table => table.data.length));
//
rowTables.forEach(table => {
const newTable = { ...table };
//
if (table.data.length < maxRows) {
const emptyRows = Array(maxRows - table.data.length).fill({});
newTable.data = [...table.data, ...emptyRows];
}
result.push(newTable);
});
}
return result;
})
//
const stationList = ref([])
const propChange = async () => {
try {
let res = await proxy.axiosGet('/basic/station/queryStnmandStnmId', queryParams);
if (res.code == 200) {
stationList.value = res.data;
}
} catch (error) {
} }
} }
/** 搜索按钮操作 */ /** 搜索按钮操作 */
@ -171,141 +118,145 @@
proxy.resetForm("queryForm"); proxy.resetForm("queryForm");
handleQuery(); handleQuery();
} }
const open = ref(false)
const title = ref('')
const form = ref({})
//
const handleUpdate = async (row) => {
reset();
let id = row.id
try {
let res = await proxy.axiosGet('/data/currdata/info/' + id);
if (res.code === 0) {
form.value = JSON.parse(JSON.stringify(res.data))
open.value = true;
title.value = "修改实时数据";
}
} catch (error) {
console.log(error)
}
}
// //
const handleExport = async () => { const handleExport = async () => {
let p = { let param = {
page: 1, page: 1,
limit: 9999, limit: 9999,
stnm: queryParams.stnm, stnm: queryParams.stnm,
mainType: queryParams.mainType, mainType: queryParams.mainType
} }
let res = await proxy.axiosGet('/data/currdata/queryAll', p); let res = await proxy.axiosGet('/data/currdata/queryAll', param);
if (res.code === 0) { if (res.code === 0) {
let table = []; let table = [];
table.push({ table.push({
A: "站点名称", A: "站点名称",
B: "设备ID", B: "设备ID",
C: "类型", C: "最新数值",
D: "最新数值", D: "最新上传时间",
E: "最新上传时间",
}); });
res.data.forEach(d => { res.data.forEach(d => {
let row = { let row = {
A: d.stnm, A: d.stnm,
B: d.stcd, B: d.stcd,
C: d.typeName, C: d.latestValue,
D: d.latestValue, D: d.latestTm,
E: d.latestTm
}; };
table.push(row); table.push(row);
}); });
let header = ["A", "B", "C", "D", "E"]; let header = ["A", "B", "C", "D"];
let fileName = "站点实时数据"; let fileName = "站点实时数据";
proxy.exportExcel(header, table, fileName); proxy.exportExcel(header, table, fileName);
} }
} }
/******************************** 弹窗 ***************************** */
onMounted(() => { const formRef = ref(null)
getList() const cancel = () => {
propChange() open.value = false
}) reset()
const propsList = ref([
{ label: '水文测站', value: 0 },
{ label: '气象站', value: 1 }
])
</script>
<style lang="scss" scoped>
.currdata {
overflow-y: scroll;
} }
.table-container { const reset = () => {
width: 100%; form.value = {
id: null,
.table-grid { stcd: "",
display: grid; latestValue: "",
grid-template-columns: repeat(var(--grid-columns), 1fr);
gap: 20px;
width: 100%;
.table-item {
min-width: 240px;
/* 防止表格溢出*/
} }
proxy.resetForm("formRef")
} }
const submitForm = async (formEl) => {
.header-content { if (!formEl) return
display: flex; await formEl.validate((valid, fields) => {
justify-content: space-between; if (valid) {
flex-wrap: nowrap; if (form.id != null) {
editMethod();
.item-station { } else {
width: 150px; addMethod();
white-space: nowrap !important;
text-overflow: ellipsis;
word-break: break-all;
overflow: hidden;
} }
.item-station,
.item-icon,
.item-time {
font-size: 12px;
color: #fff;
/* flex-wrap: nowrap; */
} }
})
.item-icon {
cursor: pointer;
margin-left: 2px;
} }
//
const addMethod = async () => {
btnLoading.value = true
try {
let res = await proxy.axiosPost('/data/currdata/add', form);
if (res.code === 0) {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList()
btnLoading.value = false
} }
} catch (error) {
btnLoading.value = false
} }
/* 表格样式 */
:deep(.double-header-table) {
border-radius: 12px;
&::before {
display: none;
} }
.el-table__fixed-right::before { //
display: none; const editMethod = async () => {
btnLoading.value = true
try {
let res = await proxy.axiosPost('/data/currdata/edit', form);
if (res.code === 0) {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList()
btnLoading.value = false
} }
} catch (error) {
.el-table__body-wrapper::-webkit-scrollbar { btnLoading.value = false
display: none;
} }
/* 统一行高 */
.el-table__row {
height: 39.6667px;
} }
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
/* 空行样式 */ if (column.property === 'stnm') {
.el-table__row--empty { // stnm
background-color: #f5f7fa; let spanCount = 0;
for (let i = 0; i <= rowIndex; i++) {
if (currentData.value[i].stnm === row.stnm) {
spanCount++;
}
} }
/* 隐藏子表头 */ // stnm
.el-table__header tr:nth-child(2) { if (spanCount > 1) {
display: none; return { rowspan: 0, colspan: 0 };
} }
/* 调整主表头样式 */ //
.el-table__header tr:first-child th { let rowspan = 1;
background: #f5f7fa; for (let i = rowIndex + 1; i < currentData.value.length; i++) {
font-weight: bold; if (currentData.value[i].stnm === row.stnm) {
rowspan++;
} else {
break;
} }
} }
/** 取消表格滑上的背景色 */ return { rowspan, colspan: 1 };
:deep(.el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell) {
background: #fff !important;
} }
</style> return { rowspan: 1, colspan: 1 };
};
onMounted(() => {
getList()
})
</script>
Loading…
Cancel
Save