<script setup>
|
import { ref, computed, watch, onMounted, nextTick } from "vue";
|
import ycCard from "@/components/ycCard.vue";
|
import jhyInfo from "./jhyInfo.vue";
|
import useWebSocket from "@/hooks/useWebSocket.js";
|
import bar from "@/components/echarts/bar2.vue";
|
import iconPower from "@/components/icons/iconPower.vue";
|
import formatSeconds from "@/assets/js/tools/formatSeconds.js";
|
import IconDot from "@/components/icons/IconDot.vue";
|
|
import toFixed from "@/assets/js/tools/toFixed.js";
|
import const_digits from "@/assets/js/const/const_digits";
|
const { VOL, GROUPVOL, CURR_YT, CURR_JH } = const_digits;
|
|
const infoTab = ref(0);
|
const { sendData, message } = useWebSocket("rtstateSocket");
|
|
const props = defineProps({
|
devId: {
|
type: [Number, String],
|
required: true,
|
},
|
onlyOneGroup: {
|
type: Boolean,
|
default: false,
|
},
|
});
|
|
const headers = [
|
{
|
prop: "monNum",
|
label: "编号",
|
width: "",
|
},
|
{
|
prop: "monVol",
|
label: "电压(V)",
|
width: "",
|
},
|
{
|
prop: "monCurr",
|
label: "电流(A)",
|
width: "",
|
},
|
{
|
prop: "monState",
|
label: "状态",
|
width: "",
|
},
|
{
|
prop: "monCap",
|
label: "容量(AH)",
|
width: "",
|
},
|
];
|
|
const battVolChart = ref();
|
|
const groupCount = computed(() => (props.onlyOneGroup ? 1 : 2));
|
|
const rtDatas = computed(() => {
|
if (message.value) {
|
let {
|
code,
|
data2: {
|
rtdataState0: { data2: monStates0 },
|
rtdataState1: { data2: monStates1 },
|
resActmState: { data2: devStates },
|
event: { data2: eventList },
|
},
|
} = JSON.parse(message.value);
|
|
if (!devStates.length) {
|
devStates = [{}, {}];
|
}
|
|
monStates0.forEach((v) => {
|
v.monState = v.needTest == 0 ? "--" : v.monState;
|
v.monVol = toFixed(v.monVol, VOL);
|
v.monCurr = toFixed(v.monCurr, CURR_JH);
|
});
|
|
monStates1.forEach((v) => {
|
v.monState = v.needTest == 0 ? "--" : v.monState;
|
v.monVol = toFixed(v.monVol, VOL);
|
v.monCurr = toFixed(v.monCurr, CURR_JH);
|
});
|
|
return { monStates0, monStates1, devStates, eventList };
|
} else {
|
return {
|
monStates0: [],
|
monStates1: [],
|
devStates: [{}, {}],
|
eventList: [],
|
};
|
}
|
});
|
|
const logList = computed(() => {
|
let _list = rtDatas.value.eventList;
|
let resObj = {};
|
_list.forEach((v) => {
|
let { num, devId, battIdx, lastWorkState, nowWorkState, recordTime } = v;
|
let [date, time] = recordTime.split(" ");
|
resObj[date] = resObj[date] || { date, list: [] };
|
v.time = time;
|
resObj[date].list.push(v);
|
});
|
return Object.keys(resObj).map((v) => resObj[v]);
|
});
|
|
watch(
|
() => props.devId,
|
(v) => {
|
// console.log('devId', v, '=============');
|
let reg = /^2/;
|
if (reg.test(v)) {
|
sendData(JSON.stringify({ devId: props.devId, devType: 2 }));
|
}
|
},
|
{ immediate: true }
|
);
|
|
watch(message, (val) => {
|
changeTab();
|
});
|
|
watch(
|
() => props.onlyOneGroup,
|
(val) => {
|
nextTick(() => {
|
if (battVolChart.value) {
|
battVolChart.value[0].resize();
|
battVolChart.value[1]?.resize();
|
}
|
});
|
}
|
);
|
|
function changeTab() {
|
if (infoTab.value == 2) {
|
updateChart();
|
}
|
}
|
function updateChart() {
|
let _data = {
|
mons0: [],
|
vols0: [],
|
mons1: [],
|
vols1: [],
|
};
|
[0, 1].forEach((i) => {
|
rtDatas.value["monStates" + i].forEach((v) => {
|
if (v.needTest == 1) {
|
_data["mons" + i].push(`#${v.monNum}`);
|
_data["vols" + i].push(v.monVol);
|
}
|
});
|
});
|
// console.log('data', _data.vols0, _data.vols1, '=============');
|
|
if (battVolChart.value) {
|
battVolChart.value[0].updateChart(_data.mons0, _data.vols0);
|
battVolChart.value[1]?.updateChart(_data.mons1, _data.vols1);
|
}
|
}
|
|
onMounted(() => {
|
// console.log('devId,' , props.devId, 'onMounted', '=============');
|
|
let reg = /^2/;
|
if (reg.test(props.devId)) {
|
sendData(JSON.stringify({ devId: props.devId, devType: 2 }));
|
}
|
});
|
</script>
|
|
<template>
|
<yc-card class="p-info" title="实时监测">
|
<template #tools>
|
<el-radio-group
|
v-model="infoTab"
|
@change="changeTab"
|
size="small"
|
is-button
|
>
|
<el-radio-button :value="0">测试信息</el-radio-button>
|
<el-radio-button :value="1">单体列表</el-radio-button>
|
<el-radio-button :value="2">单体电压图</el-radio-button>
|
<el-radio-button :value="3">状态日志</el-radio-button>
|
</el-radio-group>
|
</template>
|
<div class="tab-container">
|
<transition-group :duration="300" name="slide-left">
|
<div class="tab-content test-content" v-if="infoTab == 0">
|
<jhy-info
|
class="group"
|
:datas="rtDatas.devStates[0]"
|
:rtdata="rtDatas.monStates0"
|
:idx="0"
|
></jhy-info>
|
<jhy-info
|
class="group"
|
v-if="!onlyOneGroup"
|
:datas="rtDatas.devStates[1]"
|
:rtdata="rtDatas.monStates1"
|
:idx="1"
|
></jhy-info>
|
</div>
|
<!-- 单体列表 -->
|
<div class="tab-content tab-batt" v-if="infoTab == 1">
|
<!-- <template v-for="(i, idx) in 2" :key="'batt' + idx">
|
<div class="info">
|
<div class="label">最大值</div>
|
<div class="value max">
|
{{ rtDatas.devStates[idx].minBatteryVoltage }}V
|
</div>
|
<div class="label">最小值</div>
|
<div class="value min">
|
{{ rtDatas.devStates[idx].minBatteryVoltage }}V
|
</div>
|
<div class="label">平均值</div>
|
<div class="value">{{ rtDatas.devStates[idx].minBatteryVoltage }}V</div>
|
</div>
|
<div class="list-wrap posR">
|
<div class="pos-full scroll">
|
<div
|
class="item"
|
v-for="item in rtDatas['monStates' + idx]"
|
:key="'test_' + item.monNum"
|
>
|
<div class="label">#{{ item.monNum }}</div>
|
<div
|
:class="[
|
'value',
|
{ max: rtDatas.devStates[idx].minBatteryVoltage == item.monVol, min: rtDatas.devStates[idx].minBatteryVoltage == item.monVol },
|
]"
|
>
|
{{ item.monVol }} V
|
</div>
|
</div>
|
</div>
|
</div>
|
</template> -->
|
<div class="group" v-for="(i, idx) in groupCount" :key="'group' + i">
|
<div
|
:class="[
|
'title',
|
{ offline: 0 == rtDatas.devStates[idx].moduleStatusInt },
|
]"
|
>
|
<div class="index">
|
<!-- #{{ rtDatas.devStates[idx].batteryStorageIndex + 1 }} -->
|
#{{ i }}
|
</div>
|
<div class="state">
|
<el-icon class="ico"><icon-dot /></el-icon>
|
{{ rtDatas.devStates[idx].moduleStatus }}
|
</div>
|
</div>
|
<div class="table-wrap posR">
|
<div class="pos-full">
|
<el-table
|
class="yc-table"
|
stripe
|
height="100%"
|
size="small"
|
:data="rtDatas['monStates' + idx]"
|
style="width: 100%"
|
>
|
<el-table-column
|
v-for="header in headers"
|
:key="header.prop"
|
:prop="header.prop"
|
:label="header.label"
|
:min-width="header.width"
|
align="center"
|
></el-table-column>
|
</el-table>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="tab-content tab-chart" v-if="infoTab == 2">
|
<div
|
class="group-chart"
|
v-for="(i, idx) in groupCount"
|
:key="'chart' + i"
|
>
|
<div class="chart-info">
|
<div class="label">最大值</div>
|
<div class="value max">
|
{{ toFixed(rtDatas["devStates"][idx].maxBatteryVoltage, VOL) }}V
|
</div>
|
<div class="label">最小值</div>
|
<div class="value min">
|
{{ toFixed(rtDatas["devStates"][idx].minBatteryVoltage, VOL) }}V
|
</div>
|
<div class="label">平均值</div>
|
<div class="value">
|
{{ toFixed(rtDatas["devStates"][idx].avgMonVol, VOL) }}V
|
</div>
|
</div>
|
<div class="chart-wrap1">
|
<bar ref="battVolChart" unit="V"></bar>
|
</div>
|
</div>
|
</div>
|
<div class="tab-content scroll" v-if="infoTab == 3">
|
<el-timeline class="custom-timeline time-left" v-if="logList.length">
|
<el-timeline-item
|
v-for="item in logList"
|
:timestamp="item.date"
|
placement="top"
|
:key="item.date"
|
>
|
<div class="card">
|
<div class="row" v-for="row in item.list" :key="row.num">
|
<div class="time">{{ row.time }}</div>
|
<div class="battIdx">组{{ row.battIdx + 1 }}</div>
|
<div class="content">{{ row.eventStr }}</div>
|
</div>
|
</div>
|
</el-timeline-item>
|
</el-timeline>
|
<el-empty v-else description="暂无记录" />
|
</div>
|
</transition-group>
|
</div>
|
</yc-card>
|
</template>
|
|
<style scoped lang="less">
|
.custom-timeline :deep(.el-timeline-item__tail) {
|
// left: auto;
|
// right: 18px;
|
}
|
|
.custom-timeline :deep(.el-timeline-item__content) {
|
margin-left: 0;
|
margin-right: 30px;
|
}
|
|
.custom-timeline :deep(.el-timeline-item__timestamp) {
|
position: absolute;
|
left: 0;
|
transform: translateX(-100%);
|
padding-right: 20px;
|
text-align: right;
|
color: #fff;
|
}
|
.custom-timeline {
|
.card {
|
color: #fff;
|
padding-top: 10px;
|
.row {
|
display: flex;
|
height: 38px;
|
align-items: center;
|
background: rgba(0, 0, 0, 0.2);
|
border-radius: 6px;
|
margin-top: 6px;
|
padding-left: 2em;
|
.time {
|
margin-right: 0.8em;
|
color: #0ff;
|
font-weight: bold;
|
}
|
.battIdx {
|
margin-right: 0.8em;
|
}
|
.content {
|
flex: 1;
|
}
|
}
|
}
|
&.time-left {
|
padding-left: 160px;
|
}
|
}
|
.p-info {
|
.tab-chart {
|
height: 100%;
|
display: flex;
|
.group-chart {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
& ~ .group-chart {
|
margin-left: 8px;
|
}
|
}
|
.chart-info {
|
display: grid;
|
grid-template-columns: repeat(3, 4em 6em);
|
place-content: center center;
|
// place-items: center center;
|
gap: 6px;
|
.label {
|
display: flex;
|
justify-content: end;
|
align-items: center;
|
&::after {
|
content: ":";
|
}
|
}
|
.value {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
height: 36px;
|
text-align: center;
|
background: #02a7f0;
|
&.max {
|
background: #438D29;
|
}
|
&.min {
|
background: #DBD608;
|
}
|
}
|
}
|
.chart-wrap1 {
|
flex: 1;
|
// background: #000;
|
}
|
}
|
.tab-batt {
|
display: flex;
|
// flex-direction: column;
|
.info {
|
display: grid;
|
grid-template-columns: repeat(3, 4em 6em);
|
place-content: center center;
|
// place-items: center center;
|
gap: 6px;
|
.label {
|
display: flex;
|
justify-content: end;
|
align-items: center;
|
&::after {
|
content: ":";
|
}
|
}
|
.value {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
height: 36px;
|
text-align: center;
|
background: #02a7f0;
|
&.max {
|
background: #438D29;
|
}
|
&.min {
|
background: #DBD608;
|
}
|
}
|
}
|
.list-wrap {
|
margin-top: 8px;
|
flex: 1;
|
.scroll {
|
// min-height: 0;
|
display: grid;
|
place-content: start center;
|
grid-template-columns: repeat(auto-fill, 200px);
|
overflow-y: auto;
|
gap: 24px;
|
.item {
|
display: flex;
|
border-radius: 6px;
|
background: #1e6866;
|
height: 36px;
|
align-items: center;
|
padding: 0 0.4em 0 1em;
|
.label {
|
margin-right: 1em;
|
width: 3em;
|
text-align: right;
|
}
|
.value {
|
background: #02a7f0;
|
flex: 1;
|
padding: 2px 10px;
|
border-radius: 6px;
|
&.max {
|
background: #438D29;
|
}
|
&.min {
|
background: #DBD608;
|
}
|
}
|
}
|
}
|
}
|
.group {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
& ~ .group {
|
margin-left: 8px;
|
}
|
.title {
|
height: 36px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 0 20px;
|
border: 1px solid #0ff;
|
border-radius: 6px;
|
background: #02a7f0;
|
&.offline {
|
background: #999;
|
.state {
|
color: #fff;
|
}
|
}
|
.state {
|
display: flex;
|
align-items: center;
|
font-weight: bold;
|
color: #0f0;
|
.ico {
|
margin-right: 0.8em;
|
font-size: 12px;
|
}
|
}
|
}
|
}
|
.table-wrap {
|
flex: 1;
|
margin-top: 6px;
|
margin-bottom: 6px;
|
}
|
}
|
.test-content {
|
// background: #000;
|
display: flex;
|
.group {
|
flex: 1;
|
& ~ .group {
|
margin-left: 20px;
|
}
|
}
|
}
|
}
|
.tab-container {
|
height: 100%;
|
position: relative;
|
overflow: hidden;
|
.tab-content {
|
position: absolute;
|
left: 0;
|
top: 0;
|
right: 0;
|
bottom: 0;
|
height: 100%;
|
color: #fff;
|
padding: 6px;
|
&.scroll {
|
right: 6px;
|
bottom: 4px;
|
overflow-y: auto;
|
}
|
}
|
|
.slide-left-enter-active,
|
.slide-left-leave-active,
|
.slide-right-enter-active,
|
.slide-right-leave-active {
|
transition: all 0.3s;
|
}
|
|
.slide-left-enter-from {
|
transform: translateX(100%);
|
}
|
|
.slide-left-leave-to {
|
transform: translateX(-100%);
|
}
|
|
.slide-right-enter-from {
|
transform: translateX(-100%);
|
}
|
|
.slide-right-leave-to {
|
transform: translateX(100%);
|
}
|
|
.slide-left-enter-to,
|
.slide-right-enter-to {
|
transform: translateX(0);
|
}
|
}
|
</style>
|