Commit ebcf087e authored by liuzhaoh's avatar liuzhaoh

解决冲突

parents cc98dce3 dc39c40b
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
ref="tableRef" ref="tableRef"
:data="currentPageData" :data="currentPageData"
v-bind="$attrs" v-bind="$attrs"
:resizable="false"
border border
:height="tableHeight" :height="tableHeight"
> >
...@@ -221,6 +222,8 @@ watch( ...@@ -221,6 +222,8 @@ watch(
width: 100% !important; width: 100% !important;
} }
.el-table__header th { user-select: none; }
.el-table { .el-table {
// 设置表头样式 // 设置表头样式
th.el-table__cell { th.el-table__cell {
......
import request from "../index.js";
export function getMonitorRealtime(params) {
return request({
url: "/api/duster/monitor/chamber",
method: "get",
params,
});
}
export function getMonitorToday(params) {
return request({
url: "/api/duster/monitor/curve",
method: "get",
params,
});
}
export function getMonitorList(params) {
return request({
url: "/api/duster/monitor/list",
method: "get",
params,
});
}
...@@ -4,6 +4,12 @@ import Layout from '../layout/index.vue' ...@@ -4,6 +4,12 @@ import Layout from '../layout/index.vue'
// 检查路由是否在授权菜单中的函数 // 检查路由是否在授权菜单中的函数
const checkRoutePermission = (route, menuList) => { const checkRoutePermission = (route, menuList) => {
// bag-monitor 路由不需要权限验证
if (route.path === '/bag-monitor') {
return true;
}
// 检查当前路由 // 检查当前路由
const hasPermission = menuList.some(menu => const hasPermission = menuList.some(menu =>
menu.name === route.meta?.title && menu.name === route.meta?.title &&
......
...@@ -5,7 +5,7 @@ import NProgress from 'nprogress' ...@@ -5,7 +5,7 @@ import NProgress from 'nprogress'
import 'nprogress/nprogress.css' // progress bar style import 'nprogress/nprogress.css' // progress bar style
NProgress.configure({ showSpinner: false }) // NProgress Configuration NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist const whiteList = ['/login', '/auth-redirect', '/bag-monitor'] // no redirect whitelist
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
const store = useUsersStore() const store = useUsersStore()
const hasToken = getToken('TOKEN') const hasToken = getToken('TOKEN')
...@@ -50,19 +50,6 @@ router.beforeEach(async (to, from, next) => { ...@@ -50,19 +50,6 @@ router.beforeEach(async (to, from, next) => {
} }
} else { } else {
try { try {
// debugger
// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
// const roles = await store.getInfo()
// generate accessible routes map based on roles
// const menuLimitsObj = await store.menuLimitsObj
// const accessRoutes = await store.dispatch('permission/generateRoutes', { roles, menuLimitsObj })
// console.log("accessRoutes",accessRoutes)
// dynamically add accessible routes
// router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
// next({ ...to, replace: true })
next() next()
} catch (error) { } catch (error) {
console.log(error) console.log(error)
......
...@@ -132,6 +132,7 @@ export const getBagMonitoringChartOption = (xData = [], seriesData = []) => ({ ...@@ -132,6 +132,7 @@ export const getBagMonitoringChartOption = (xData = [], seriesData = []) => ({
top: "3%", top: "3%",
containLabel: true, containLabel: true,
}, },
// x轴的文字去除年月日
xAxis: { xAxis: {
type: "category", type: "category",
boundaryGap: false, boundaryGap: false,
...@@ -145,6 +146,9 @@ export const getBagMonitoringChartOption = (xData = [], seriesData = []) => ({ ...@@ -145,6 +146,9 @@ export const getBagMonitoringChartOption = (xData = [], seriesData = []) => ({
fontSize: 12, fontSize: 12,
interval: "auto", interval: "auto",
rotate: 0, rotate: 0,
formatter: function (value) {
return value.split(' ')[1];
}
}, },
axisTick: { axisTick: {
show: false, show: false,
...@@ -154,8 +158,8 @@ export const getBagMonitoringChartOption = (xData = [], seriesData = []) => ({ ...@@ -154,8 +158,8 @@ export const getBagMonitoringChartOption = (xData = [], seriesData = []) => ({
yAxis: { yAxis: {
type: "value", type: "value",
min: 0, min: 0,
max: 600, // max: 600,
interval: 200, // interval: 200,
axisLabel: { axisLabel: {
color: "rgba(0,0,0,0.6)", color: "rgba(0,0,0,0.6)",
formatter: "{value}", formatter: "{value}",
......
...@@ -150,6 +150,8 @@ import { useRoute, useRouter } from "vue-router"; ...@@ -150,6 +150,8 @@ import { useRoute, useRouter } from "vue-router";
import { getDataFun, postDataJSON } from "@/request/method.js"; import { getDataFun, postDataJSON } from "@/request/method.js";
import moment from "moment"; import moment from "moment";
const route = useRoute();
const router = useRouter(); const router = useRouter();
const formInline = ref({ const formInline = ref({
eventName: "", eventName: "",
...@@ -157,9 +159,20 @@ const formInline = ref({ ...@@ -157,9 +159,20 @@ const formInline = ref({
dusterName: "", dusterName: "",
deviceType: "", deviceType: "",
suspendFlag: "2", suspendFlag: "2",
date: "", date: route.query.startTime
? [
moment(new Date(route.query.startTime).getTime()).format(
"YYYY-MM-DD HH:mm:ss"
),
moment(new Date(route.query.endTime).getTime()).format(
"YYYY-MM-DD HH:mm:ss"
),
]
: [],
}); });
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(20); const pageSize = ref(20);
const equDialog = ref(false); const equDialog = ref(false);
......
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button class="default-btn" @click="onSearch">搜索</el-button> <el-button type="primary" class="default-btn" @click="onSearch">搜索</el-button>
<el-button type="primary" class="default-btn" @click="onReset">重置</el-button> <el-button class="default-btn" @click="onReset">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="table-box"> <div class="table-box">
......
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
<span class="label">描述:</span> <span class="label">描述:</span>
<div class="value"> <div class="value">
<span class="value-item" v-for="(item, index) in spot.description" :key="index">{{ item }}</span> <span class="value-item" v-for="(item, index) in spot.description" :key="index">{{ item }}</span>
<span class="value-item" v-if="spot.description.length === 0">暂无</span>
</div> </div>
</div> </div>
......
...@@ -2,432 +2,552 @@ ...@@ -2,432 +2,552 @@
<div class="page-container bag-monitoring-container"> <div class="page-container bag-monitoring-container">
<div class="header"> <div class="header">
<div class="select-container"> <div class="select-container">
<span>检测仪器11</span> <span>检测仪器</span>
<el-select v-model="selectedDevice" placeholder="1#布袋检测仪"> <el-select v-model="selectedDevice" placeholder="1#布袋检测仪" @change="handleDeviceChange">
<el-option label="1#布袋检测仪" value="1"></el-option> <el-option
v-for="item in deviceList"
:key="item.deviceNo"
:label="item.deviceName"
:value="item.deviceNo"
></el-option>
</el-select> </el-select>
</div> </div>
<div class="title">BME布袋监测</div> <div class="title">BME布袋监测 - {{ dusterName }}</div>
</div> </div>
<div class="chart-container"> <div class="chart-container">
<div class="chart-wrapper" ref="chartRef"></div> <div class="chart-wrapper" ref="chartRef"></div>
<div class="chart-wrapper no-data" v-if="chartData.xData.length === 0">
<div class="data-panel" :class="{ 'collapsed': !isPanelOpen }"> <div class="no-data-text">暂无数据</div>
<div class="panel-toggle" :class="{ 'collapsed-icon': !isPanelOpen, 'expanded-icon': isPanelOpen }" @click="togglePanel"> </div>
<el-icon v-if="isPanelOpen"><ArrowRightBold /></el-icon> </div>
<el-icon v-if="!isPanelOpen"><ArrowLeftBold /></el-icon>
<div class="data-panel" :class="{ collapsed: !isPanelOpen }">
<div
class="panel-toggle"
:class="{
'collapsed-icon': !isPanelOpen,
'expanded-icon': isPanelOpen,
}"
@click="togglePanel"
>
<el-icon v-if="isPanelOpen"><ArrowRightBold /></el-icon>
<el-icon v-if="!isPanelOpen"><ArrowLeftBold /></el-icon>
</div>
<div class="data-box">
<div class="data-item">
<div>时间: {{ currentData.time }}</div>
<div>仓室: {{ currentData.compartNo }}</div>
<div>反吹: {{ currentData.blowBack }}</div>
</div> </div>
<div class="data-box">
<div class="data-item"> <div class="data-item">
<div>时间: {{ currentData.time }}</div> <div>DI3: {{ currentData.di3 }}</div>
<div>仓室: {{ currentData.chamber }}</div> <div>排: {{ currentData.row }}</div>
<div>反吹: {{ currentData.backwash }}</div> <div>实时值: {{ currentData.realData }}</div>
</div>
<div class="data-item">
<div>DI3: {{ currentData.di3 }}</div>
<div>排: {{ currentData.row }}</div>
<div>实时值: {{ currentData.realValue }}</div>
</div>
<div class="data-item">
<div>基线值: {{ currentData.baseline }}</div>
<div>Delay: {{ currentData.delay }}</div>
<div>totaltime: {{ currentData.totaltime }}</div>
</div>
<div class="data-item">
<div>{{ currentData.next }}</div>
<div>valve:{{ currentData.valve }}</div>
<div>DI_TIME={{ currentData.di_time }}</div>
</div>
<div class="data-item">
<div>false:{{ currentData.false }}</div>
<div>peak={{ currentData.peak }}</div>
</div>
</div> </div>
<div class="data-item">
<div class="data-box second-box"> <div>基线值: {{ currentData.baseline }}</div>
<div>时间:{{ secondaryData.time }}, 仓室: {{ secondaryData.chamber }}, 反吹: {{ secondaryData.backwash }}, 谷值: {{ secondaryData.valleyValue }}, 仓室状态阀门{{ secondaryData.status }}False{{ secondaryData.falseVal }}</div> <div>Delay: {{ currentData.delay }}</div>
<div>totaltime: {{ currentData.totalTime }}</div>
</div>
<div class="data-item">
<div>next:{{ currentData.next }}</div>
<div>valve:{{ currentData.valveNo }}</div>
<div>DI_TIME={{ currentData.diTime }}</div>
</div>
<div class="data-item">
<div>peak={{ currentData.peak }}</div>
</div> </div>
</div> </div>
<!-- <div class="data-box second-box">
<div>时间:{{ secondaryData.time }}, 仓室: {{ secondaryData.chamber }}, 反吹: {{ secondaryData.backwash }}, 谷值: {{ secondaryData.valleyValue }}, 仓室状态阀门{{ secondaryData.status }}False{{ secondaryData.falseVal }}</div>
</div> -->
</div> </div>
<div class="data-table">
<div class="data-table">
<div class="time-controls"> <div class="time-controls">
<el-button @click="navigateBackward"> <div class="time-desc">
<el-icon><ArrowLeftBold />向前</el-icon> <span class="icon"></span>
</el-button> 当前实时信号:{{ currentData.compartNo }}仓 / {{ currentData.row }}
<el-button @click="navigateForward"> </div>
<el-icon><ArrowRightBold />向后</el-icon> <div>
</el-button> <el-button @click="navigateBackward">
<span class="time-label">间隔</span> <el-icon><ArrowLeftBold />向前</el-icon>
<el-input v-model="timeInterval" class="time-input"></el-input> </el-button>
<span class="time-unit">分钟</span> <el-button @click="navigateForward">
<el-icon><ArrowRightBold />向后</el-icon>
<el-button @click="reset">重置</el-button> </el-button>
<span class="time-label">间隔</span>
<el-input v-model="timeInterval" class="time-input"></el-input>
<span class="time-unit">分钟</span>
<el-button @click="reset">重置</el-button>
</div>
</div> </div>
<table> <table>
<thead> <thead>
<tr> <tr>
<th>名称</th> <th>名称</th>
<th v-for="i in 20" :key="i">{{ i }}</th> <th v-for="(value, index) in tableData" :key="index">
{{ value.compartName }}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>峰值_R</td> <td>峰值{{ currentData.row }}排_R</td>
<td v-for="(value, index) in peakValuesR" :key="'r'+index">{{ value }}</td> <td
v-for="(value, index) in tableData"
:key="'r' + index"
:class="{
'online-style': value.compartNo === currentData.compartNo,
}"
>
{{ value.peakValueR }}
</td>
</tr> </tr>
<tr> <tr>
<td>峰值_H</td> <td>峰值_H</td>
<td v-for="(value, index) in peakValuesH" :key="'h'+index">{{ value }}</td> <td v-for="(value, index) in tableData" :key="'h' + index">
{{ value.peakValueH }}
</td>
</tr> </tr>
<tr> <tr>
<td>状态</td> <td>状态</td>
<td v-for="(value, index) in statusValues" :key="'s'+index" :class="{ 'error': value === '故障' }">{{ value }}</td> <td
v-for="(value, index) in tableData"
:key="'s' + index"
:class="{ error: value.status === 1 }"
>
{{ value.status === 1 ? "故障" : "正常" }}
</td>
</tr> </tr>
<tr> <tr>
<td>反吹中</td> <td>反吹中</td>
<td v-for="(value, index) in backwashValues" :key="'b'+index">{{ value }}</td> <td v-for="(value, index) in tableData" :key="'b' + index">
{{ value.blowBack }}
</td>
</tr> </tr>
<tr> <tr>
<td>谷值</td> <td>谷值</td>
<td v-for="(value, index) in valleyValues" :key="'v'+index">{{ value }}</td> <td v-for="(value, index) in tableData" :key="'v' + index">
{{ value.valleyValue}}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue' import { ref, reactive, onMounted, onBeforeUnmount, watch } from "vue";
import * as echarts from 'echarts' import * as echarts from "echarts";
import { ArrowRightBold, ArrowLeftBold } from '@element-plus/icons-vue' import moment from "moment";
import { getBagMonitoringChartOption } from '@/utils/chart.js' import { ArrowRightBold, ArrowLeftBold } from "@element-plus/icons-vue";
import { getBagMonitoringChartOption } from "@/utils/chart.js";
import {
getMonitorToday,
getMonitorList,
getMonitorRealtime,
} from "@/request/api/bagMonitor";
import { useRoute } from "vue-router";
// 获取路由参数
const route = useRoute();
// 设备选择 // 设备选择
const selectedDevice = ref('1') const selectedDevice = ref(null);
const isPanelOpen = ref(true) const isPanelOpen = ref(true);
// 图表相关 // 图表相关
const chartRef = ref(null) const chartRef = ref(null);
let chartInstance = null let chartInstance = null;
let updateTimer = null // 定时器引用 let updateTimer = null; // 定时器引用
// 时间导航相关 let lastTimePoint = null;
let isRealTimeMode = true // 控制是否获取实时设备状态数据
let lastTimePoint = null let showRealtimeDeviceStatus = true;
// 从路由参数获取dusterNo
const dusterNo = ref(
route.params.dusterNo || route.query.dusterNo || ""
); // 默认值作为fallback
const dusterName = ref(
route.params.dusterName || route.query.dusterName || ""
);
// 图表数据 // 图表数据
const chartData = reactive({ const chartData = reactive({
xData: [], xData: [],
seriesData: [] seriesData: [],
}) });
const deviceList = ref([]);
const currentTime = ref(null);
// 当前数据面板 // 当前数据面板
const currentData = reactive({ const currentData = ref({
time: '641', realtime: "",
chamber: '5', time: "",
backwash: '0', compartNo: "",
di3: '0, 0,', blowBack: "",
row: '1,', di3: "",
realValue: '18.87,', row: "",
baseline: '10,', realData: "",
delay: '0,', baseline: "",
totaltime: '1837', delay: "",
next: 'next:765,', totalTime: "",
valve: '5', next: "",
di_time: '1848', valveNo: "",
false: '0:0,', diTime: "",
peak: '4, 1, 46' peak: "",
}) });
// 次要数据面板
const secondaryData = reactive({
time: '784',
chamber: '6',
backwash: '0',
valleyValue: '3',
status: '212',
falseVal: '0'
})
// 表格数据 // 表格数据
const peakValuesR = [0, 0, -30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const tableData = ref([
const peakValuesH = [30, 56, 60, 70, 18, 56, 18, 41, 41, 0, 41, 41, 41, 41, 41, 41, 500, 41, 31, 41] {
const statusValues = Array(20).fill('正常') compartName: "默认",
statusValues[2] = '故障' peakValueR: 0,
statusValues[16] = '故障' peakValueH: 0,
status: 0,
const backwashValues = [0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] blowBack: 0,
const valleyValues = [-8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0] valleyValue: 0,
},
]);
// 时间控制 // 时间控制
const timeInterval = ref('10') const timeInterval = ref("10");
const togglePanel = () => { const togglePanel = () => {
isPanelOpen.value = !isPanelOpen.value isPanelOpen.value = !isPanelOpen.value;
} };
// 格式化时间为 HH:MM:SS 格式 const handleDeviceChange = (value) => {
const formatTime = (date) => { console.log("value", value);
const hours = date.getHours().toString().padStart(2, '0') initChart();
const minutes = date.getMinutes().toString().padStart(2, '0') };
const seconds = date.getSeconds().toString().padStart(2, '0')
return `${hours}:${minutes}:${seconds}`
}
// 初始化图表 // 初始化图表
const initChart = () => { const initChart = () => {
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
} }
// 初始化图表数据 // 初始化图表数据
initChartData() initChartData();
};
chartInstance = echarts.init(chartRef.value)
// updateChart()
// 启动定时更新
startRealTimeUpdates()
}
// 初始化图表数据 // 初始化图表数据
const initChartData = () => { const initChartData = () => {
const now = new Date() if (!dusterNo.value || !selectedDevice.value) {
chartData.xData = [] console.warn("缺少dusterNo或deviceNo,无法获取数据");
chartData.seriesData = [] return;
// 创建初始数据点,从5分钟前开始
for (let i = 300; i >= 0; i--) {
const time = new Date(now.getTime() - i * 1000)
chartData.xData.push(formatTime(time))
chartData.seriesData.push(generateRandomValue())
} }
}
// 生成一个介于 20-300 之间的随机值,模拟实时数据 // 获取昨天这个之前的数据
const generateRandomValue = () => { const startTime = moment()
// 有10%的概率生成峰值 .subtract(5, "minutes")
if (Math.random() < 0.1) { .format("YYYY-MM-DD HH:mm:ss");
return Math.floor(Math.random() * 200) + 200 // 200-400之间的峰值 const endTime = moment().format("YYYY-MM-DD HH:mm:ss");
} chartData.xData = [];
return Math.floor(Math.random() * 130) + 20 // 20-150之间的基础值 chartData.seriesData = [];
} getMonitorToday({
dusterNo: dusterNo.value,
deviceNo: selectedDevice.value,
startTime: startTime,
// endTime: endTime,
})
.then((res) => {
// 将data中的date和realData数据添加到chartData中,要求大数据量下速度快
if (res.data && res.data.length > 0) {
res.data.forEach((item) => {
chartData.xData.push(item.date);
chartData.seriesData.push(item.realData);
});
// 当前时间
currentTime.value = res.data[res.data.length - 1].date;
chartInstance = echarts.init(chartRef.value);
// 启动定时更新
startRealTimeUpdates();
} else {
console.warn("未获取到初始数据");
}
})
.catch((err) => {
console.error("获取初始数据失败:", err);
});
};
// 更新图表 // 更新图表
const updateChart = () => { const updateChart = () => {
if (chartInstance) { if (chartInstance) {
chartInstance.setOption(getBagMonitoringChartOption(chartData.xData, chartData.seriesData)) chartInstance.setOption(
getBagMonitoringChartOption(chartData.xData, chartData.seriesData)
);
} }
} };
// 启动实时更新 // 启动实时更新
const startRealTimeUpdates = () => { const startRealTimeUpdates = () => {
if (updateTimer) { if (updateTimer) {
clearInterval(updateTimer) clearInterval(updateTimer);
} }
updateTimer = setInterval(() => {
// 添加新数据点
const now = new Date()
chartData.xData.push(formatTime(now))
chartData.seriesData.push(generateRandomValue())
// 移除最旧的数据点以保持固定长度
if (chartData.xData.length > 31) { // 保持30秒的数据
chartData.xData.shift()
chartData.seriesData.shift()
}
// 更新图表
updateChart()
updateDataPanels()
}, 1000)
isRealTimeMode = true
}
// 停止实时更新 updateTimer = setInterval(() => {
const stopRealTimeUpdates = () => { updateFunction();
if (updateTimer) { }, 1000);
clearInterval(updateTimer)
updateTimer = null showRealtimeDeviceStatus = true;
};
// 更新函数
const updateFunction = () => {
if (!dusterNo.value || !selectedDevice.value) return;
// 从API获取实时数据
getMonitorRealtime({
dusterNo: dusterNo.value,
deviceNo: selectedDevice.value,
})
.then((res) => {
if (res.data) {
// 更新数据面板
if (res.data.realtimeCompart) {
tableData.value = res.data.realtimeCompart;
}
if (res.data.realtimePanel) {
currentData.value = res.data.realtimePanel;
}
}
})
.catch((err) => {
console.error("获取实时数据失败:", err);
});
if (showRealtimeDeviceStatus) {
// 结束时间往后推一秒
let endTime = moment(new Date(currentTime.value))
.add(1, "seconds")
.format("YYYY-MM-DD HH:mm:ss");
getMonitorToday({
dusterNo: dusterNo.value,
deviceNo: selectedDevice.value,
startTime: currentTime.value,
endTime: endTime,
}).then((res) => {
if (res.data && res.data.length > 0) {
const item = res.data[res.data.length - 1];
chartData.xData.push(item.date);
chartData.seriesData.push(item.realData || 0);
// 移除最旧的数据点以保持固定长度
if (chartData.xData.length > 301) {
// 保持300秒的数据
chartData.xData.shift();
chartData.seriesData.shift();
}
currentTime.value = endTime;
// 更新图表
updateChart();
}
});
} }
};
isRealTimeMode = false
}
// 向前导航(查看更早的数据) // 向前导航(查看更早的数据)
const navigateBackward = () => { const navigateBackward = () => {
// 关闭实时更新 // 不完全关闭轮询,只禁用实时设备状态获取
stopRealTimeUpdates() showRealtimeDeviceStatus = false;
// 获取时间间隔(分钟) // 获取时间间隔(分钟)
const interval = parseInt(timeInterval.value) || 10 const interval = parseInt(timeInterval.value) || 10;
const intervalMs = interval * 60 * 1000 const intervalMs = interval * 60 * 1000;
// 如果是第一次点击,以当前最后一条数据的时间为参考 // 如果是第一次点击,以当前最后一条数据的时间为参考
if (lastTimePoint === null && chartData.xData.length > 0) { if (lastTimePoint === null && chartData.xData.length > 0) {
const lastTimeString = chartData.xData[chartData.xData.length - 1] const lastTimeString = chartData.xData[chartData.xData.length - 1];
const [hours, minutes, seconds] = lastTimeString.split(':').map(Number) lastTimePoint = new Date(lastTimeString);
const now = new Date()
lastTimePoint = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, seconds)
} else if (lastTimePoint === null) { } else if (lastTimePoint === null) {
// 如果没有数据,使用当前时间 // 如果没有数据,使用当前时间
lastTimePoint = new Date() lastTimePoint = new Date();
} }
// 计算新的时间范围 // 计算新的时间范围
const endTime = new Date(lastTimePoint.getTime() - intervalMs) const endTime = new Date(lastTimePoint.getTime() - intervalMs);
lastTimePoint = endTime lastTimePoint = endTime;
// 重新生成数据 // 重新生成数据
generateHistoricalData(endTime, interval) generateHistoricalData(endTime, interval);
} };
// 向后导航(查看更近的数据) // 向后导航(查看更近的数据)
const navigateForward = () => { const navigateForward = () => {
// 关闭实时更新 // 不完全关闭轮询,只禁用实时设备状态获取
stopRealTimeUpdates() showRealtimeDeviceStatus = false;
// 获取时间间隔(分钟) // 获取时间间隔(分钟)
const interval = parseInt(timeInterval.value) || 10 const interval = parseInt(timeInterval.value) || 10;
const intervalMs = interval * 60 * 1000 const intervalMs = interval * 60 * 1000;
// 如果是第一次点击,以当前最后一条数据的时间为参考 // 如果是第一次点击,以当前最后一条数据的时间为参考
if (lastTimePoint === null && chartData.xData.length > 0) { if (lastTimePoint === null && chartData.xData.length > 0) {
const lastTimeString = chartData.xData[chartData.xData.length - 1] const lastTimeString = chartData.xData[chartData.xData.length - 1];
const [hours, minutes, seconds] = lastTimeString.split(':').map(Number) lastTimePoint = new Date(lastTimeString);
const now = new Date()
lastTimePoint = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, seconds)
} else if (lastTimePoint === null) { } else if (lastTimePoint === null) {
// 如果没有数据,使用当前时间 // 如果没有数据,使用当前时间
lastTimePoint = new Date() lastTimePoint = new Date();
} }
// 计算新的时间范围 // 计算新的时间范围
const endTime = new Date(lastTimePoint.getTime() + intervalMs) let endTime = new Date(lastTimePoint.getTime() + intervalMs);
// // 如果新的结束时间超过当前时间,则切换回实时模式 // // 如果新的结束时间超过当前时间,则切换回实时模式
const now = new Date() const now = moment();
if (endTime > now) { if (endTime > now) {
lastTimePoint = null lastTimePoint = new Date(now);
// startRealTimeUpdates() endTime = new Date(now);
return // // showRealtimeDeviceStatus = true;
// return;
} else {
lastTimePoint = endTime;
} }
lastTimePoint = endTime
// 重新生成数据 // 重新生成数据
generateHistoricalData(endTime, interval) generateHistoricalData(endTime, interval);
} };
const reset = () => { const reset = () => {
// 清空现有数据 // 清空现有数据
chartData.xData = [] chartData.xData = [];
chartData.seriesData = [] chartData.seriesData = [];
lastTimePoint = null lastTimePoint = null;
// 恢复实时设备状态获取
showRealtimeDeviceStatus = true;
// 重新生成当前时间往前300秒的数据 // 重新生成当前时间往前300秒的数据
const now = new Date() const now = new Date();
generateHistoricalData(now, 5)
generateHistoricalData(now, 5);
// 开启实时更新 // 开启实时更新
startRealTimeUpdate() startRealTimeUpdate();
} };
// 开启实时更新(重置用) // 开启实时更新(重置用)
const startRealTimeUpdate = () => { const startRealTimeUpdate = () => {
// 确保之前的定时器已清除 // 确保之前的定时器已清除
stopRealTimeUpdates() if (updateTimer) {
clearInterval(updateTimer);
// 初始化实时模式标志 updateTimer = null;
isRealTimeMode = true }
// 启动定时更新 // 启动定时更新
updateTimer = setInterval(() => { updateTimer = setInterval(() => {
// 添加新数据点 updateFunction();
const now = new Date() }, 1000);
chartData.xData.push(formatTime(now)) };
chartData.seriesData.push(generateRandomValue())
// 移除最旧的数据点以保持固定长度
if (chartData.xData.length > 31) { // 保持30秒的数据
chartData.xData.shift()
chartData.seriesData.shift()
}
// 更新图表
updateChart()
updateDataPanels()
}, 1000)
}
// 生成历史数据 // 生成历史数据
const generateHistoricalData = (endTime, intervalMinutes) => { const generateHistoricalData = (endTime, intervalMinutes) => {
// 清空现有数据 if (!dusterNo.value || !selectedDevice.value) return;
chartData.xData = []
chartData.seriesData = []
// 计算开始时间(结束时间减去间隔)
const startTime = new Date(endTime.getTime() - intervalMinutes * 60 * 1000)
// 生成数据点(每秒一个)
for (let time = new Date(startTime); time <= endTime; time = new Date(time.getTime() + 1000)) {
chartData.xData.push(formatTime(time))
chartData.seriesData.push(generateRandomValue())
}
// 更新图表
updateChart()
}
// 更新数据面板 // 清空现有数据
const updateDataPanels = () => { chartData.xData = [];
// 随机更新当前数据面板的实时值 chartData.seriesData = [];
currentData.realValue = (Math.random() * 30 + 10).toFixed(2) + ',' const startTime = new Date(endTime.getTime() - intervalMinutes * 60 * 1000);
// 更新时间显示 const startTimeStr = moment(startTime).format("YYYY-MM-DD HH:mm:ss");
const now = new Date() const endTimeStr = moment(endTime).format("YYYY-MM-DD HH:mm:ss");
currentData.time = Math.floor((now.getTime() / 1000) % 1000).toString()
// 从API获取历史数据
// 每10秒更新一次次要数据面板 getMonitorToday({
if (now.getSeconds() % 1 === 0) { dusterNo: dusterNo.value,
secondaryData.time = Math.floor((now.getTime() / 1000) % 1000).toString() deviceNo: selectedDevice.value,
secondaryData.valleyValue = Math.floor(Math.random() * 10).toString() startTime: startTimeStr,
} endTime: endTimeStr,
} })
.then((res) => {
if (res.data && res.data.length > 0) {
res.data.forEach((item) => {
chartData.xData.push(item.date);
chartData.seriesData.push(item.realData);
});
// 更新图表
updateChart();
}
})
.catch((err) => {
console.error("获取历史数据失败:", err);
});
};
// 监听窗口大小变化,调整图表大小 // 监听窗口大小变化,调整图表大小
const handleResize = () => { const handleResize = () => {
if (chartInstance) { if (chartInstance) {
chartInstance.resize() chartInstance.resize();
} }
} };
const getDeviceList = () => {
if (!dusterNo.value) return;
getMonitorList({
dusterNo: dusterNo.value,
}).then((res) => {
if (res.data) {
deviceList.value = res.data.devices || [];
if (deviceList.value.length > 0) {
selectedDevice.value = deviceList.value[0].deviceNo;
}
tableData.value = Array.from(
{ length: res.data.compartCount },
(item, index) => ({
compartName: `${index + 1}仓`,
peakValueR: 0,
peakValueH: 0,
status: 0,
blowBack: 0,
})
);
initChart();
}
});
};
// 监听dusterNo变化
watch(dusterNo, (newVal) => {
if (newVal) {
// 重新获取设备列表
getDeviceList();
}
});
onMounted(() => { onMounted(() => {
initChart() if (dusterNo.value) {
window.addEventListener('resize', handleResize) getDeviceList();
}) }
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize) window.removeEventListener("resize", handleResize);
// 清除定时器 // 清除定时器
if (updateTimer) { if (updateTimer) {
clearInterval(updateTimer) clearInterval(updateTimer);
updateTimer = null updateTimer = null;
} }
// 销毁图表实例 // 销毁图表实例
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
chartInstance = null chartInstance = null;
} }
}) });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
...@@ -435,15 +555,16 @@ onBeforeUnmount(() => { ...@@ -435,15 +555,16 @@ onBeforeUnmount(() => {
width: 100%; width: 100%;
height: calc(100% - 14px); height: calc(100% - 14px);
box-sizing: border-box; box-sizing: border-box;
overflow-x: hidden; // overflow-x: hidden;
position: relative;
.header { .header {
position: relative; position: relative;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 16px;
.select-container { .select-container {
position: absolute; position: absolute;
left: 0; left: 0;
...@@ -452,28 +573,28 @@ onBeforeUnmount(() => { ...@@ -452,28 +573,28 @@ onBeforeUnmount(() => {
display: flex; display: flex;
align-items: center; align-items: center;
z-index: 99; z-index: 99;
span { span {
display: block; display: block;
width: 110px; width: 110px;
margin-right: 10px; margin-right: 10px;
} }
.device-number { .device-number {
background: #fcf0c2; background: #fcf0c2;
padding: 2px 8px; padding: 2px 8px;
margin-left: 10px; margin-left: 10px;
border-radius: 4px; border-radius: 6px;
} }
} }
.title { .title {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
color: #409EFF; color: #2182a0;
} }
} }
.chart-container { .chart-container {
width: 100%; width: 100%;
height: calc(100% - 350px); height: calc(100% - 350px);
...@@ -481,106 +602,127 @@ onBeforeUnmount(() => { ...@@ -481,106 +602,127 @@ onBeforeUnmount(() => {
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
.chart-wrapper { .chart-wrapper {
flex: 1; flex: 1;
width: 100%; width: 100%;
} .no-data-text {
width: 100%;
.data-panel { height: 100%;
position: absolute;
top: 80px;
right: 25px;
width: 320px;
z-index: 10;
transition: transform 0.3s ease-in-out;
&.collapsed {
transform: translateX(345px);
}
.panel-toggle {
position: absolute;
width: 25px;
height: 30px;
background: #fff;
top: 1px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; font-size: 14px;
color: #999;
background-color: #f5f7fa;
border-radius: 4px; border-radius: 4px;
} }
}
.no-data {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
}
}
.collapsed-icon { .data-panel {
left: -23px; position: absolute;
} top: 0px;
.expanded-icon { right: 0px;
right: -23px; width: 320px;
} z-index: 100;
transition: transform 0.3s ease-in-out;
.data-box {
padding: 10px; &.collapsed {
background-color: rgba(255, 255, 255, 0.9); transform: translateX(345px);
border-radius: 4px; }
border: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); .panel-toggle {
margin-bottom: 10px; position: absolute;
width: 25px;
.data-item { height: 30px;
display: flex; background: #fff;
justify-content: space-between; top: 1px;
margin-bottom: 5px; display: flex;
font-size: 14px; align-items: center;
justify-content: center;
div { cursor: pointer;
flex: 1; border-radius: 4px;
} }
.collapsed-icon {
left: -23px;
}
.expanded-icon {
right: -23px;
}
.data-box {
padding: 10px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 4px;
border: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
.data-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 14px;
div {
flex: 1;
} }
} }
}
.second-box {
font-size: 12px; .second-box {
} font-size: 12px;
} }
} }
.data-table { .data-table {
background-color: #fff; background-color: #fff;
border-radius: 4px; border-radius: 4px;
padding: 10px 0; padding: 10px 0;
box-sizing: border-box; box-sizing: border-box;
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
th, td { th,
border: 1px solid #EBEEF5; td {
border: 1px solid #ebeef5;
text-align: center; text-align: center;
padding: 8px; padding: 8px;
font-size: 14px; font-size: 14px;
} }
th { th {
background-color: #F5F7FA; background-color: #f5f7fa;
color: #606266; color: #606266;
} }
.error { .error {
color: #F56C6C; color: #f56c6c;
} }
.highlight { .highlight {
background-color: #FCF8E3; background-color: #fcf8e3;
position: relative; position: relative;
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
width: 16px; width: 16px;
height: 16px; height: 16px;
background-color: #E6A23C; background-color: #e6a23c;
border-radius: 50%; border-radius: 50%;
top: 50%; top: 50%;
left: 50%; left: 50%;
...@@ -588,18 +730,49 @@ onBeforeUnmount(() => { ...@@ -588,18 +730,49 @@ onBeforeUnmount(() => {
z-index: 1; z-index: 1;
} }
} }
.online-style {
background-color: rgba(33, 130, 160, 1);
color: #fff;
}
} }
.time-controls { .time-controls {
margin-bottom: 10px; margin-bottom: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: space-between;
.time-label, .time-unit { .el-button {
background-color: #2182a0;
border-color: #2182a0;
color: #fff;
&:hover {
background-color: #1a6980;
border-color: #1a6980;
}
}
.time-desc {
font-size: 14px;
color: #606266;
display: flex;
align-items: center;
.icon {
display: inline-block;
width: 12px;
height: 12px;
background-color: #05ea09;
border-radius: 50%;
margin-right: 5px;
}
}
.time-label,
.time-unit {
font-size: 14px;
margin: 0 5px; margin: 0 5px;
margin-right: 10px;
} }
.time-input { .time-input {
width: 60px; width: 60px;
} }
...@@ -607,4 +780,3 @@ onBeforeUnmount(() => { ...@@ -607,4 +780,3 @@ onBeforeUnmount(() => {
} }
} }
</style> </style>
\ No newline at end of file
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
class="healthy-progress-bar h-full transition-all duration-500 ease-out bg-gradient-to-r from-secondary to-green-400 diagonal-pattern diagonal-pattern-animation" class="healthy-progress-bar h-full transition-all duration-500 ease-out bg-gradient-to-r from-secondary to-green-400 diagonal-pattern diagonal-pattern-animation"
></div> ></div>
<div class="justify-between items-center mb-2 healthy-text-position"> <div class="justify-between items-center mb-2 healthy-text-position">
<span class="font-semibold">健康度</span> <span class="font-semibold">健康度:</span>
<span id="square-progress-value" class="text-lg font-bold"> 45%</span> <span id="square-progress-value" class="text-lg font-bold"> 45%</span>
</div> </div>
</div> </div>
......
<template> <template>
<div class="title layout1"> <div class="title layout1">
<span class="warn-title">{{ title }}</span> <span class="warn-title">告警</span>
<span class="jump-icon">>></span> <!-- <span class="jump-icon">>></span> -->
</div> </div>
<div class="content"> <div class="content">
<div class="item" v-for="item in listInfo" :key="item"> <div class="item" v-for="item in listInfo" :key="item">
...@@ -45,12 +45,15 @@ const props = defineProps({ ...@@ -45,12 +45,15 @@ const props = defineProps({
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 5px;
padding: 4px 0px 4px 2px; padding: 4px 0px 4px 2px;
box-shadow: rgba(53,58,62,0.08) 0px 1px 10px 0px,rgba(53,58,62,0.08) 0px 2px 4px 0px; box-shadow: rgba(53,58,62,0.04) 0px 1px 10px 0px,rgba(53,58,62,0.04) 0px 2px 4px 0px;
font-size: 14px;
color: #333;
img { img {
width: 20px; width: 20px;
height: auto; height: auto;
margin-right: 10px;
} }
} }
} }
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
}" }"
v-for="item in detailObj.compartHealthList[0]" v-for="item in detailObj.compartHealthList[0]"
:key="item" :key="item"
@click="handleStatusDotClick()"
></div> ></div>
</div> </div>
<div class="part"> <div class="part">
...@@ -131,6 +132,7 @@ ...@@ -131,6 +132,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted, watch, computed } from "vue"; import { ref, reactive, onMounted, onUnmounted, watch, computed } from "vue";
import { useRouter, useRoute } from "vue-router";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { getDataFun } from "@/request/method.js"; import { getDataFun } from "@/request/method.js";
import { useUsersStore } from "@/pinia/user.js"; import { useUsersStore } from "@/pinia/user.js";
...@@ -138,6 +140,7 @@ import moment from "moment"; ...@@ -138,6 +140,7 @@ import moment from "moment";
import warnCom from "./components/warn.vue"; import warnCom from "./components/warn.vue";
import healthyCom from "./components/healthyProgress.vue"; import healthyCom from "./components/healthyProgress.vue";
import { getLineOption2, getGaugeOption } from "@/utils/chart"; import { getLineOption2, getGaugeOption } from "@/utils/chart";
const userStore = useUsersStore(); const userStore = useUsersStore();
const form = reactive({ const form = reactive({
dusterNo: "", dusterNo: "",
...@@ -179,6 +182,8 @@ const option = { ...@@ -179,6 +182,8 @@ const option = {
}, },
], ],
}; };
const router = useRouter();
const route = useRoute();
const chartInstance = reactive({}); const chartInstance = reactive({});
const chartData = reactive([{}, {}, {}]); const chartData = reactive([{}, {}, {}]);
const initChart = (instance, option, domEl) => { const initChart = (instance, option, domEl) => {
...@@ -304,7 +309,7 @@ const getDusterNameEnum = () => { ...@@ -304,7 +309,7 @@ const getDusterNameEnum = () => {
.then((res) => { .then((res) => {
dusterList.value = (res && res.data) || []; dusterList.value = (res && res.data) || [];
// 判断除尘器名称是否有值 // 判断除尘器名称是否有值
form.dusterNo = dusterList.value[0].dusterNo; form.dusterNo = route.query.dusterNo || dusterList.value[0].dusterNo;
}) })
.catch(() => { .catch(() => {
dusterList.value = []; dusterList.value = [];
...@@ -583,6 +588,16 @@ const dusterName = computed(() => { ...@@ -583,6 +588,16 @@ const dusterName = computed(() => {
return dusterList.value.find((item) => item.dusterNo == form.dusterNo) return dusterList.value.find((item) => item.dusterNo == form.dusterNo)
?.dusterName; ?.dusterName;
}); });
const handleStatusDotClick = () => {
router.push({
path: "/bag-monitor",
query: {
dusterNo: form.dusterNo,
dusterName: dusterName.value,
}
});
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
$borderColor: #bbbdc391; $borderColor: #bbbdc391;
...@@ -627,7 +642,7 @@ $borderColor: #bbbdc391; ...@@ -627,7 +642,7 @@ $borderColor: #bbbdc391;
.part1 { .part1 {
width: 100%; width: 100%;
height: 6vh; height: 5vh;
border-radius: 6px; border-radius: 6px;
border: 1px solid rgba(13, 15, 18, 0.1); border: 1px solid rgba(13, 15, 18, 0.1);
box-shadow: 0px 3px 6px 0px rgba(13, 15, 18, 0.1); box-shadow: 0px 3px 6px 0px rgba(13, 15, 18, 0.1);
...@@ -642,7 +657,7 @@ $borderColor: #bbbdc391; ...@@ -642,7 +657,7 @@ $borderColor: #bbbdc391;
.part2 { .part2 {
margin-top: 10px; margin-top: 10px;
width: 100%; width: 100%;
height: calc(57vh - 10px); height: calc(58vh - 10px);
border-radius: 6px; border-radius: 6px;
border: 1px solid rgba(13, 15, 18, 0.1); border: 1px solid rgba(13, 15, 18, 0.1);
box-shadow: 0px 3px 6px 0px rgba(13, 15, 18, 0.1); box-shadow: 0px 3px 6px 0px rgba(13, 15, 18, 0.1);
......
<template> <template>
<el-dialog :model-value="modelValue" :title="editData ? '编辑除尘器' : '新增除尘器'" width="460px" :close-on-click-modal="false" <el-dialog :model-value="modelValue" :title="editData ? '编辑除尘器' : '新增除尘器'" width="500px" :close-on-click-modal="false"
:close-on-press-escape="false" @update:model-value="$emit('update:modelValue', $event)"> :close-on-press-escape="false" @update:model-value="cancel">
<div class="add-dust-form"> <div class="add-dust-form">
<div class="form-item"> <div class="form-item">
<div class="selector-wrap"> <div class="selector-wrap">
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</div> </div>
<div class="form-content"> <div class="form-content">
<el-form ref="formRef" :model="formData" label-width="100px" :rules="rules"> <el-form ref="formRef" :model="formData" label-width="120px" :rules="rules">
<el-form-item label="除尘器名称" prop="name"> <el-form-item label="除尘器名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入" /> <el-input v-model="formData.name" placeholder="请输入" />
</el-form-item> </el-form-item>
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
<el-input v-model.number="formData.valveCount" placeholder="请输入" /> <el-input v-model.number="formData.valveCount" placeholder="请输入" />
</el-form-item> </el-form-item>
<el-form-item label="合理压差范围"> <el-form-item label="合理压差范围" required>
<div class="range-input"> <div class="range-input">
<el-form-item prop="pressureMin" class="coordinate-item"> <el-form-item prop="pressureMin" class="coordinate-item">
<el-input v-model.number="formData.pressureMin" placeholder="请输入" /> <el-input v-model.number="formData.pressureMin" placeholder="请输入" />
...@@ -119,26 +119,12 @@ const formData = reactive({ ...@@ -119,26 +119,12 @@ const formData = reactive({
coordinateY: null coordinateY: null
}); });
// 监听editData变化,初始化表单数据
watch(() => props.editData, (newVal) => {
if (newVal) {
formData.name = newVal.deviceName;
formData.code = newVal.deviceNo;
formData.process = newVal.productionLineId;
formData.chamberCount = Number(newVal.compartNum);
formData.valveCount = Number(newVal.valveNum);
formData.pressureMin = Number(newVal.pressureDiffLow);
formData.pressureMax = Number(newVal.pressureDiffHigh);
formData.serverIp = newVal.serviceIp;
formData.coordinateX = Number(newVal.x);
formData.coordinateY = Number(newVal.y);
}
}, { immediate: true });
const rules = { const rules = {
name: [{ required: true, message: '请输入除尘器名称', trigger: 'blur' }], name: [{ required: true, message: '请输入除尘器名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入除尘器编号', trigger: 'blur' }], code: [{ required: true, message: '请输入除尘器编号', trigger: 'blur' }],
process: [{ required: true, message: '请选择所属工序', trigger: 'change' }], process: [{ required: true, message: '请选择所属工序', trigger: 'blur' }],
chamberCount: [ chamberCount: [
{ required: true, message: '请输入仓室数量', trigger: 'blur' }, { required: true, message: '请输入仓室数量', trigger: 'blur' },
{ type: 'number', min: 1, message: '仓室数量必须大于0', trigger: 'blur' } { type: 'number', min: 1, message: '仓室数量必须大于0', trigger: 'blur' }
...@@ -149,7 +135,7 @@ const rules = { ...@@ -149,7 +135,7 @@ const rules = {
], ],
pressureMin: [ pressureMin: [
{ required: true, message: '请输入最小压差', trigger: 'blur' }, { required: true, message: '请输入最小压差', trigger: 'blur' },
{ type: 'number', min: 1, message: '最小压差必须大于0', trigger: 'blur' }, { type: 'number', min: 0, message: '最小压差不能小于0', trigger: 'blur' },
{ {
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (value && formData.pressureMax && value >= formData.pressureMax) { if (value && formData.pressureMax && value >= formData.pressureMax) {
...@@ -163,7 +149,7 @@ const rules = { ...@@ -163,7 +149,7 @@ const rules = {
], ],
pressureMax: [ pressureMax: [
{ required: true, message: '请输入最大压差', trigger: 'blur' }, { required: true, message: '请输入最大压差', trigger: 'blur' },
{ type: 'number', min: 0, message: '最大压差必须大于0', trigger: 'blur' }, { type: 'number', min: 0, message: '最大压差不能小于0', trigger: 'blur' },
{ {
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (value && formData.pressureMin && value <= formData.pressureMin) { if (value && formData.pressureMin && value <= formData.pressureMin) {
...@@ -236,7 +222,6 @@ const productionLine = () => { ...@@ -236,7 +222,6 @@ const productionLine = () => {
onMounted(() => { onMounted(() => {
productionLine(); productionLine();
initDustOptions(); initDustOptions();
}); });
const selectDustType = () => { const selectDustType = () => {
...@@ -270,7 +255,6 @@ const submitForm = () => { ...@@ -270,7 +255,6 @@ const submitForm = () => {
if (props.editData) { if (props.editData) {
submitData.id = props.editData.id; submitData.id = props.editData.id;
} }
// 发送数据到父组件处理保存逻辑 // 发送数据到父组件处理保存逻辑
emit('save', submitData); emit('save', submitData);
// emit('update:modelValue', false); // emit('update:modelValue', false);
...@@ -279,10 +263,8 @@ const submitForm = () => { ...@@ -279,10 +263,8 @@ const submitForm = () => {
} }
}); });
}; };
const resetForm = () => {
const cancel = () => {
// 重置表单状态和数据 // 重置表单状态和数据
formRef.value.resetFields();
formData.dustType = ''; formData.dustType = '';
formData.name = ''; formData.name = '';
formData.code = ''; formData.code = '';
...@@ -294,9 +276,38 @@ const cancel = () => { ...@@ -294,9 +276,38 @@ const cancel = () => {
formData.serverIp = ''; formData.serverIp = '';
formData.coordinateX = null; formData.coordinateX = null;
formData.coordinateY = null; formData.coordinateY = null;
emit('update:modelValue', false); }
const cancel = () => {
resetForm();
formRef.value.resetFields();
emit('cancel', {});
}; };
// 监听editData变化,初始化表单数据
watch(() => props.editData, (newVal) => {
if (newVal) {
formData.name = newVal.deviceName;
formData.code = newVal.deviceNo;
formData.process = newVal.productionLineId;
formData.chamberCount = Number(newVal.compartNum);
formData.valveCount = Number(newVal.valveNum);
formData.pressureMin = Number(newVal.pressureDiffLow);
formData.pressureMax = Number(newVal.pressureDiffHigh);
formData.serverIp = newVal.serviceIp;
formData.coordinateX = Number(newVal.x);
formData.coordinateY = Number(newVal.y);
} else {
resetForm();
}
}, { immediate: true });
watch(() => props.modelValue, (newVal) => {
if (newVal === false) {
resetForm();
}
}, { immediate: true });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -343,7 +354,7 @@ const cancel = () => { ...@@ -343,7 +354,7 @@ const cancel = () => {
} }
.form-footer { .form-footer {
margin-top: 20px; margin-top: 30px;
display: flex; display: flex;
justify-content: center; justify-content: center;
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
style="width: 100%" style="width: 100%"
size="small" size="small"
border border
:resizable="false"
> >
<el-table-column prop="compartPositionRow" label="排" width="180"> <el-table-column prop="compartPositionRow" label="排" width="180">
<template #default="{ row }"> <template #default="{ row }">
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
width="800px" width="800px"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
@update:model-value="$emit('update:modelValue', $event)" @update:model-value="cancel"
> >
<div class="valve-setting"> <div class="valve-setting">
<div class="setting-row"> <div class="setting-row">
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
:min="0" :min="0"
controls-position="right" controls-position="right"
style="width: 120px" style="width: 120px"
@change="handleValveNumChange"
:class="{ 'is-error': errorMessage }" :class="{ 'is-error': errorMessage }"
/> />
</div> </div>
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
:min="0" :min="0"
controls-position="right" controls-position="right"
style="width: 120px" style="width: 120px"
@change="handleBagNumChange"
/> />
</div> </div>
</div> </div>
...@@ -57,14 +59,14 @@ ...@@ -57,14 +59,14 @@
{{ errorMessage }} {{ errorMessage }}
</div> </div>
<div> <!-- <div>
<el-button <el-button
type="primary" type="primary"
class="save-btn" class="save-btn"
@click="handleSave" @click="handleSave"
:disabled="!!errorMessage || valveForm.valveNum === null || valveForm.bagNum === null" :disabled="!!errorMessage || valveForm.valveNum === null || valveForm.bagNum === null"
>设置</el-button> >设置</el-button>
</div> </div> -->
</div> </div>
<!-- 分布表格 --> <!-- 分布表格 -->
...@@ -93,7 +95,7 @@ ...@@ -93,7 +95,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="$emit('update:modelValue', false)">取消</el-button> <el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="handleConfirm" :disabled="!!errorMessage"> <el-button type="primary" @click="handleConfirm" :disabled="!!errorMessage">
保存 保存
</el-button> </el-button>
...@@ -119,7 +121,7 @@ const emit = defineEmits(["update:modelValue", "confirm"]); ...@@ -119,7 +121,7 @@ const emit = defineEmits(["update:modelValue", "confirm"]);
const valveForm = ref({ const valveForm = ref({
total: 0, total: 0,
rows: 0, rows: 0,
frontCompartNo: 0, frontCompartNo: null,
valveNum: 0, valveNum: 0,
bagNum: 0 bagNum: 0
}); });
...@@ -174,14 +176,25 @@ const initializeState = () => { ...@@ -174,14 +176,25 @@ const initializeState = () => {
} }
}; };
const resetForm = () => {
statusData.value = [];
valveForm.value.frontCompartNo = null;
valveForm.value.valveNum = 0;
valveForm.value.bagNum = 0;
}
const getAllocation = (params) => { const getAllocation = (params) => {
getValveAllocation(params).then(res => { getValveAllocation(params).then(res => {
if (res.code === 1 && res.data.valveList.length > 0) { if (res.code === 1) {
statusData.value = res.data.valveList; if (res.data.valveList.length > 0) {
valveForm.value.frontCompartNo = statusData.value[0].frontCompartNo; statusData.value = res.data.valveList;
valveForm.value.valveNum = statusData.value[0].valveNum; valveForm.value.frontCompartNo = statusData.value[0].frontCompartNo;
valveForm.value.bagNum = statusData.value[0].bagNum; valveForm.value.valveNum = statusData.value[0].valveNum;
} valveForm.value.bagNum = statusData.value[0].bagNum;
} else {
resetForm();
}
}
}) })
} }
...@@ -197,6 +210,45 @@ const handleRoomChange = (roomIndex) => { ...@@ -197,6 +210,45 @@ const handleRoomChange = (roomIndex) => {
} }
}; };
// 监听电磁阀数量变化
const handleValveNumChange = () => {
// 验证是否超出限制
const validation = validateValveCount.value;
if (!validation.valid) {
errorMessage.value = validation.message;
return;
}
// 保存当前选中仓室的电磁阀分布数量
const selectedRoom = statusData.value.find(cell => cell.frontCompartNo === valveForm.value.frontCompartNo);
if (selectedRoom) {
selectedRoom.valveNum = valveForm.value.valveNum;
selectedRoom.bagNum = valveForm.value.bagNum;
// 更新错误信息
errorMessage.value = "";
}
}
// 监听布袋数量变化
const handleBagNumChange = () => {
// 验证是否超出限制
const validation = validateValveCount.value;
if (!validation.valid) {
errorMessage.value = validation.message;
return;
}
// 保存当前选中仓室的电磁阀分布数量
const selectedRoom = statusData.value.find(cell => cell.frontCompartNo === valveForm.value.frontCompartNo);
if (selectedRoom) {
selectedRoom.valveNum = valveForm.value.valveNum;
selectedRoom.bagNum = valveForm.value.bagNum;
// 更新错误信息
errorMessage.value = "";
}
}
watch(() => props.modelValue, (newVal) => { watch(() => props.modelValue, (newVal) => {
if (newVal) { if (newVal) {
initializeState(); initializeState();
...@@ -251,7 +303,6 @@ const handleSave = () => { ...@@ -251,7 +303,6 @@ const handleSave = () => {
const handleConfirm = () => { const handleConfirm = () => {
// 最终确认时再次验证总数是否符合要求 // 最终确认时再次验证总数是否符合要求
console.log(statusData.value);
const totalValves = statusData.value.reduce((sum, cell) => sum + cell.valveNum, 0); const totalValves = statusData.value.reduce((sum, cell) => sum + cell.valveNum, 0);
if (totalValves > valveForm.value.total) { if (totalValves > valveForm.value.total) {
errorMessage.value = `电磁阀总数超出限制 ${totalValves - valveForm.value.total} 个,请调整后再提交`; errorMessage.value = `电磁阀总数超出限制 ${totalValves - valveForm.value.total} 个,请调整后再提交`;
...@@ -261,6 +312,11 @@ const handleConfirm = () => { ...@@ -261,6 +312,11 @@ const handleConfirm = () => {
emit("confirm", statusData.value); emit("confirm", statusData.value);
// emit("update:modelValue", false); // emit("update:modelValue", false);
}; };
const cancel = () => {
resetForm();
emit("update:modelValue", false);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -334,6 +390,7 @@ const handleConfirm = () => { ...@@ -334,6 +390,7 @@ const handleConfirm = () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
min-height: 152px;
padding: 12px; padding: 12px;
background: #f5f7fa; background: #f5f7fa;
border-radius: 4px; border-radius: 4px;
...@@ -345,7 +402,7 @@ const handleConfirm = () => { ...@@ -345,7 +402,7 @@ const handleConfirm = () => {
} }
.valve-cell { .valve-cell {
width: 41px; min-width: 41px;
background: white; background: white;
padding: 4px 8px; padding: 4px 8px;
border-radius: 4px; border-radius: 4px;
...@@ -368,7 +425,7 @@ const handleConfirm = () => { ...@@ -368,7 +425,7 @@ const handleConfirm = () => {
.cell-value { .cell-value {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: end; align-items: baseline;
font-size: 16px; font-size: 16px;
color: #606266; color: #606266;
span { span {
......
<template> <template>
<div class="page-container dust-container"> <div class="page-container dust-container">
<div class="header"> <div class="header">
<div class="item-box"> <div class="item-box crusor-click" @click="handleDusterLeakNumClick">
<img src="@/assets/icons/warn.png" alt="dust" /> <img src="@/assets/icons/warn.png" alt="dust" />
<div class="title"> <div class="title">
<span>泄漏告警(条)</span> <span>泄漏告警(条)</span>
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div class="item-box"> <div class="item-box crusor-click" @click="handleCloseLoopNumClick">
<img src="@/assets/icons/close.png" alt="dust" /> <img src="@/assets/icons/close.png" alt="dust" />
<div class="title"> <div class="title">
<span>闭环(条)</span> <span>闭环(条)</span>
...@@ -102,18 +102,19 @@ ...@@ -102,18 +102,19 @@
<template #compartHealthList="{ row }"> <template #compartHealthList="{ row }">
<div class="status-matrix" v-if="row.compartHealthList.length > 0"> <div class="status-matrix" v-if="row.compartHealthList.length > 0">
<div <div
v-for="(row, rowIndex) in row.compartHealthList" v-for="(childRow, rowIndex) in row.compartHealthList"
:key="rowIndex" :key="rowIndex"
class="matrix-row" class="matrix-row"
> >
<div <div
v-for="(status, colIndex) in row" v-for="(status, colIndex) in childRow"
:key="colIndex" :key="colIndex"
class="status-dot" class="status-dot"
@click="handleStatusDotClick(row, colIndex)"
:class="{ :class="{
'status-normal': status.healthStatus === 1, 'status-normal': status.healthStatus === 1,
'status-warning': status.healthStatus === 2, 'status-warning': status.healthStatus === 3,
'status-error': status.healthStatus === 3, 'status-error': status.healthStatus === 2,
}" }"
></div> ></div>
</div> </div>
...@@ -158,6 +159,7 @@ ...@@ -158,6 +159,7 @@
v-model="isAddDustCollectorVisible" v-model="isAddDustCollectorVisible"
:edit-data="currentEditingDustCollector" :edit-data="currentEditingDustCollector"
@save="handleSaveDustCollector" @save="handleSaveDustCollector"
@cancel="handleCancel"
/> />
</div> </div>
</template> </template>
...@@ -175,12 +177,15 @@ import { ...@@ -175,12 +177,15 @@ import {
postAddCompartAllocation, postAddCompartAllocation,
postAddValveInfo, postAddValveInfo,
} from "@/request/api/dustOverview"; } from "@/request/api/dustOverview";
import { useRouter } from "vue-router";
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
import CommonTable from "@/components/commonTable/index.vue"; import CommonTable from "@/components/commonTable/index.vue";
import { ElMessageBox, ElMessage } from "element-plus"; import { ElMessageBox, ElMessage } from "element-plus";
import RoomSettingDialog from "./components/roomSettingDialog.vue"; import RoomSettingDialog from "./components/roomSettingDialog.vue";
import ValveSettingDialog from "./components/valveSettingDialog.vue"; import ValveSettingDialog from "./components/valveSettingDialog.vue";
import AddDustCollectorDialog from "./components/addDustCollectorDialog.vue"; import AddDustCollectorDialog from "./components/addDustCollectorDialog.vue";
import moment from "moment";
const router = useRouter();
const formInline = ref({ const formInline = ref({
deviceName: "", deviceName: "",
...@@ -280,19 +285,10 @@ const filterProductionLine = (query) => { ...@@ -280,19 +285,10 @@ const filterProductionLine = (query) => {
} }
}; };
const getStatusType = (status) => {
const statusMap = {
正常运行: "success",
轻微异常: "warning",
故障: "danger",
};
return statusMap[status] || "info";
};
const getHealthScoreColor = (score) => { const getHealthScoreColor = (score) => {
// 去除百分号 // 去除百分号
const value = parseInt(score.replace("%", "")); const value = parseInt(score.replace("%", ""));
if (value >= 90) return "#67C23A"; if (value == 100) return "#67C23A";
if (value >= 70) return "#E6A23C"; if (value >= 70) return "#E6A23C";
return "#F56C6C"; return "#F56C6C";
}; };
...@@ -302,7 +298,12 @@ const getIndex = (index) => { ...@@ -302,7 +298,12 @@ const getIndex = (index) => {
}; };
const handleView = (row) => { const handleView = (row) => {
console.log("查看详情", row); router.push({
path: "/monitor",
query: {
dusterNo: row.deviceNo,
},
});
}; };
const handleEdit = (row) => { const handleEdit = (row) => {
...@@ -357,6 +358,34 @@ const handleValveNumClick = (row) => { ...@@ -357,6 +358,34 @@ const handleValveNumClick = (row) => {
} }
}; };
const handleStatusDotClick = (row, colIndex) => {
router.push({
path: "/bag-monitor",
query: {
dusterNo: row.deviceNo,
dusterName: row.deviceName,
}
});
};
const handleDusterLeakNumClick = () => {
router.push({
path: "/alerts",
query: {
startTime: moment().format("YYYY-MM-DD HH:mm:ss"),
endTime: moment().subtract(24, 'hours').format("YYYY-MM-DD HH:mm:ss"),
},
});
};
const handleCloseLoopNumClick = () => {
router.push({
path: "/my-loop/myAgency",
});
};
// 确认电磁阀设置 // 确认电磁阀设置
const handleValveSettingConfirm = (updatedStatusData) => { const handleValveSettingConfirm = (updatedStatusData) => {
const list = updatedStatusData.map((item) => { const list = updatedStatusData.map((item) => {
...@@ -388,6 +417,10 @@ const setValveInfo = (data) => { ...@@ -388,6 +417,10 @@ const setValveInfo = (data) => {
}); });
}; };
const handleCancel = () => {
isAddDustCollectorVisible.value = false;
currentEditingDustCollector.value = null;
};
// 保存新增或编辑除尘器 // 保存新增或编辑除尘器
const handleSaveDustCollector = (data) => { const handleSaveDustCollector = (data) => {
if (data.id) { if (data.id) {
...@@ -412,6 +445,7 @@ const handleSaveDustCollector = (data) => { ...@@ -412,6 +445,7 @@ const handleSaveDustCollector = (data) => {
.then((res) => { .then((res) => {
if (res.code == 1) { if (res.code == 1) {
ElMessage.success("新增除尘器成功"); ElMessage.success("新增除尘器成功");
currentEditingDustCollector.value = null;
isAddDustCollectorVisible.value = false; isAddDustCollectorVisible.value = false;
refreshData(); refreshData();
} else { } else {
...@@ -540,6 +574,13 @@ onBeforeUnmount(() => {}); ...@@ -540,6 +574,13 @@ onBeforeUnmount(() => {});
margin-top: 10px; margin-top: 10px;
} }
} }
.crusor-click {
cursor: pointer;
transition: box-shadow 0.3s ease; // 添加过渡动画
&:hover {
box-shadow: 0px 1.33px 16px 0px rgba(83, 100, 170, 0.5);
}
}
} }
.content-box { .content-box {
margin-top: 24px; margin-top: 24px;
...@@ -736,4 +777,14 @@ onBeforeUnmount(() => {}); ...@@ -736,4 +777,14 @@ onBeforeUnmount(() => {});
} }
} }
} }
:deep(.el-table) {
.el-table__column-resize-proxy {
display: none !important;
}
.el-table__header th {
user-select: none;
}
}
</style> </style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment