| | |
| | | <script setup name="hisTest"> |
| | | import { ref, reactive } from "vue"; |
| | | import { ref, reactive, onMounted, watch, nextTick, computed } from "vue"; |
| | | import lineChart from '@/components/echarts/line4.vue'; |
| | | import barChart from '@/components/echarts/bar7.vue'; |
| | | |
| | | import formatSeconds from '@/utils/formatSeconds'; |
| | | import useElement from "@/hooks/useElement.js"; |
| | | const { $loading, $message, $confirm } = useElement(); |
| | | |
| | | import * as echarts from 'echarts'; |
| | | |
| | | import { |
| | | toFixed, |
| | | digits, |
| | | } from '@/utils/toFixed'; |
| | | |
| | | import { |
| | | getBattTinf, |
| | | getTinfDataWithTestRecordCount, |
| | | } from "@/api/history.js"; |
| | | import { format } from "echarts"; |
| | | |
| | | const props = defineProps({ |
| | | battgroupId: { |
| | | type: [String, Number], |
| | | }, |
| | | num: { |
| | | type: [String, Number], |
| | | } |
| | | }); |
| | | |
| | | const testTypes = [ |
| | | { |
| | |
| | | }, |
| | | { |
| | | prop: 'jkfd', |
| | | label: '监控放电' |
| | | label: '监测放电' |
| | | }, |
| | | { |
| | | prop: 'jkcd', |
| | | label: '监控充电' |
| | | label: '监测充电' |
| | | }, |
| | | { |
| | | prop: 'tdfd', |
| | |
| | | hrcd: [], |
| | | jkfd: [], |
| | | jkcd: [], |
| | | tdfd: [] |
| | | tdfd: [], |
| | | }); |
| | | |
| | | const selectValues = reactive({ |
| | | hrfd: '', |
| | | hrcd: '', |
| | | jkfd: '', |
| | | jkcd: '', |
| | | tdfd: '', |
| | | }); |
| | | |
| | | const slider = ref(100); |
| | | const chart0 = ref(null); |
| | | const chart1 = ref(null); |
| | | const chart2 = ref(null); |
| | | const chart3 = ref(null); |
| | | |
| | | const statusList = reactive([ |
| | | { label: '电池状态', prop: 'battStateName', unit: '' }, |
| | | { label: '在线电压', prop: 'onlineVol', unit: 'V' }, |
| | | { label: '电池电流', prop: 'groupCurr', unit: 'A' }, |
| | | { label: '测试时长', prop: 'testTimeLongStr', unit: '' }, |
| | | { label: '测试时间', prop: 'testStarttime', unit: '' }, |
| | | { label: '测试容量', prop: 'testCap', unit: 'Ah' }, |
| | | { label: '预估容量', prop: 'realCap', unit: 'Ah' }, |
| | | { label: '预估续航', prop: 'restTimeStr', unit: '' }, |
| | | ]); |
| | | |
| | | const infoList = reactive([ |
| | | { label: '标称容量', prop: 'moncapstd', unit: 'Ah' }, |
| | | { label: '蓄电池数量', prop: 'monCount', unit: '' }, |
| | | { label: '标称电压', prop: 'monvolstd', unit: 'V' }, |
| | | { label: '浮充电压', prop: 'floatchartVol', unit: 'V' }, |
| | | ]) |
| | | |
| | | const monBarTitle = ref("最大值=0V;最小值=0V;平均值=0V"); |
| | | const chartType = ref("vol"); |
| | | const chartTypes = [ |
| | | { |
| | | label: "单体电压", |
| | | value: "vol", |
| | | unit: "V", |
| | | fixed: digits.VOL, |
| | | }, |
| | | { |
| | | label: "单体温度", |
| | | value: "temp", |
| | | unit: "℃", |
| | | fixed: digits.TEMP, |
| | | }, |
| | | { |
| | | label: "单体实际容量", |
| | | value: "realCap", |
| | | unit: "AH", |
| | | fixed: digits.CAP, |
| | | }, |
| | | { |
| | | label: "单体剩余容量", |
| | | value: "resCap", |
| | | unit: "AH", |
| | | fixed: digits.CAP, |
| | | }, |
| | | { |
| | | label: "单体容量百分比", |
| | | value: "preCap", |
| | | unit: "%", |
| | | fixed: 0, |
| | | }, |
| | | ]; |
| | | |
| | | const chartData = reactive({ |
| | | vol: [], |
| | | temp: [], |
| | | realCap: [], |
| | | resCap: [], |
| | | preCap: [], |
| | | }); |
| | | |
| | | const testRecordCount = ref(''); |
| | | const currProp = ref(''); |
| | | function filterChange(prop) { |
| | | testTypes.forEach(v => { |
| | | if (v.prop !== prop) { |
| | | selectValues[v.prop] = ''; |
| | | } |
| | | }); |
| | | |
| | | testRecordCount.value = selectValues[prop]; |
| | | currProp.value = prop; |
| | | getTestInfo(); |
| | | } |
| | | |
| | | const battStatus = computed(() => { |
| | | return currProp.value ? testTypes.filter(v => v.prop == currProp.value)[0].label : '--'; |
| | | }); |
| | | |
| | | const testInfo = computed(() => { |
| | | let obj = currProp.value ? testRecordList[currProp.value].filter(v => v.value == testRecordCount.value)[0] : {}; |
| | | obj.battStateName = battStatus.value; |
| | | console.log('obj', obj, '============='); |
| | | |
| | | return obj; |
| | | }); |
| | | |
| | | |
| | | // 历史测试记录 |
| | | async function getLists() { |
| | | let res = await getBattTinf(props.battgroupId); |
| | | let { code, data, data2 } = res; |
| | | if (code && data) { |
| | | testRecordList.hrfd = data2['核容放电'].map(v => ({ |
| | | ...v, |
| | | testCap: toFixed(v.testCap, digits.CAP), |
| | | restTimeStr: formatSeconds(v.restTime), |
| | | testTimeLongStr: formatSeconds(v.testTimeLong), |
| | | value: `${v.testRecordCount}-${v.recordNum}`, |
| | | label: v.testStarttime |
| | | })); |
| | | testRecordList.hrfd = data2['核容放电'].map(v => ({ |
| | | ...v, |
| | | testCap: toFixed(v.testCap, digits.CAP), |
| | | restTimeStr: formatSeconds(v.restTime), |
| | | testTimeLongStr: formatSeconds(v.testTimeLong), |
| | | value: `${v.testRecordCount}-${v.recordNum}`, |
| | | label: v.testStarttime |
| | | })); |
| | | testRecordList.hrfd = data2['核容放电'].map(v => ({ |
| | | ...v, |
| | | testCap: toFixed(v.testCap, digits.CAP), |
| | | restTimeStr: formatSeconds(v.restTime), |
| | | testTimeLongStr: formatSeconds(v.testTimeLong), |
| | | value: `${v.testRecordCount}-${v.recordNum}`, |
| | | label: v.testStarttime |
| | | })); |
| | | testRecordList.hrfd = data2['核容放电'].map(v => ({ |
| | | ...v, |
| | | testCap: toFixed(v.testCap, digits.CAP), |
| | | restTimeStr: formatSeconds(v.restTime), |
| | | testTimeLongStr: formatSeconds(v.testTimeLong), |
| | | value: `${v.testRecordCount}-${v.recordNum}`, |
| | | label: v.testStarttime |
| | | })); |
| | | testRecordList.hrfd = data2['核容放电'].map(v => ({ |
| | | ...v, |
| | | testCap: toFixed(v.testCap, digits.CAP), |
| | | restTimeStr: formatSeconds(v.restTime), |
| | | testTimeLongStr: formatSeconds(v.testTimeLong), |
| | | value: `${v.testRecordCount}-${v.recordNum}`, |
| | | label: v.testStarttime |
| | | })); |
| | | } |
| | | } |
| | | |
| | | // 查询测试信息 |
| | | async function getTestInfo() { |
| | | let [recordCount, recordNum] = testRecordCount.value.split('-'); |
| | | let loading = $loading(); |
| | | let res = await getTinfDataWithTestRecordCount(props.battgroupId, props.num, recordNum, recordCount); |
| | | loading.close(); |
| | | let { code, data, data2 } = res; |
| | | if (code && data) { |
| | | // testInfo.value = data2; |
| | | formatData(data2); |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.num, |
| | | () => { |
| | | if (!currProp.value) { |
| | | return false; |
| | | } |
| | | getTestInfo(); |
| | | } |
| | | ); |
| | | |
| | | const chartOptions = reactive({ |
| | | chart0: { |
| | | color: ["#0081ff", "#df9d02"], |
| | | title: { |
| | | show: false, |
| | | text: "端电压折线图(V)", |
| | | x: "left", |
| | | textStyle: { |
| | | fontSize: "14", |
| | | }, |
| | | }, |
| | | legend: { |
| | | show: false, |
| | | data: ["在线电压", "组端电压"], |
| | | right: 0, |
| | | orient: "vertical", |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "在线电压", |
| | | data: [], |
| | | }, |
| | | { |
| | | name: "组端电压", |
| | | data: [], |
| | | }, |
| | | ], |
| | | }, |
| | | chart1: { |
| | | name: '', |
| | | data: [], |
| | | }, |
| | | chart2: { |
| | | title: { |
| | | show: false, |
| | | text: "电池电流折线图(A)", |
| | | x: "center", |
| | | textStyle: { |
| | | fontSize: "14", |
| | | }, |
| | | }, |
| | | legend: { |
| | | show: false, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "电池电流", |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: "#00feff", |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: "transparent", |
| | | }, |
| | | ]), |
| | | }, |
| | | data: [], |
| | | }, |
| | | ], |
| | | }, |
| | | chart3: { |
| | | title: { |
| | | show: false, |
| | | text: "单体电压(V)", |
| | | x: "center", |
| | | textStyle: { |
| | | fontSize: "14", |
| | | }, |
| | | }, |
| | | legend: { |
| | | show: false, |
| | | }, |
| | | series: [], |
| | | }, |
| | | }); |
| | | |
| | | const testTLong = ref([]); |
| | | const barDatas = ref({}); |
| | | const bgDatas = ref({}); |
| | | const monNames = ref([]); |
| | | |
| | | // 格式化数据 |
| | | function formatData(data) { |
| | | let recordNum = -1; |
| | | let chartData = { |
| | | groupVol: [], |
| | | onlineVol: [], |
| | | testCurr: [], |
| | | monVolLine: [], |
| | | }; |
| | | let monBarData = { |
| | | vol: [], |
| | | temp: [], |
| | | realCap: [], |
| | | resCap: [], |
| | | preCap: [], |
| | | }; |
| | | let xLabels = []; |
| | | let firestData = { |
| | | vol: [], |
| | | temp: [], |
| | | realCap: [], |
| | | resCap: [], |
| | | preCap: [], |
| | | }; |
| | | let firstRecordNum = -1; |
| | | for (let i = 0, len = data.length; i < len; i++) { |
| | | let item = data[i]; |
| | | let monNum = item.monNum; |
| | | let testTimeLong = formatSeconds(item.testTimelong); |
| | | if (recordNum != item.recordNum) { |
| | | recordNum = item.recordNum; |
| | | if (firstRecordNum == -1) { |
| | | firstRecordNum = item.recordNum; |
| | | } |
| | | chartData.groupVol.push([testTimeLong, item.groupVol]); |
| | | chartData.onlineVol.push([testTimeLong, item.onlineVol]); |
| | | chartData.testCurr.push([testTimeLong, item.testCurr]); |
| | | monBarData.vol.push([]); |
| | | monBarData.temp.push([]); |
| | | monBarData.realCap.push([]); |
| | | monBarData.resCap.push([]); |
| | | monBarData.preCap.push([]); |
| | | testTLong.value.push(testTimeLong); |
| | | } |
| | | let monName; |
| | | if (firstRecordNum == recordNum) { |
| | | monName = '#' + monNum; |
| | | firestData.vol.push(item.monVol); |
| | | firestData.temp.push(item.monTmp); |
| | | firestData.realCap.push(item.realCap); |
| | | firestData.resCap.push(item.restCap); |
| | | firestData.preCap.push(item.percentCap); |
| | | xLabels.push(monName); |
| | | } |
| | | monBarData.vol[monBarData.vol.length - 1].push(item.monVol); |
| | | monBarData.temp[monBarData.temp.length - 1].push(item.monTmp); |
| | | monBarData.realCap[monBarData.realCap.length - 1].push(item.realCap); |
| | | monBarData.resCap[monBarData.resCap.length - 1].push(item.restCap); |
| | | monBarData.preCap[monBarData.preCap.length - 1].push(item.percentCap); |
| | | |
| | | let monIdx = monNum - 1; |
| | | chartData.monVolLine[monIdx] = chartData.monVolLine[monIdx] || []; |
| | | chartData.monVolLine[monIdx].push([testTimeLong, item.monVol]); |
| | | } |
| | | // return { chartData, monBarData, xLabels }; |
| | | |
| | | chartOptions.chart0.series[0].data = chartData.onlineVol; |
| | | chartOptions.chart0.series[1].data = chartData.groupVol; |
| | | |
| | | chartOptions.chart2.series[0].data = chartData.testCurr; |
| | | chartOptions.chart3.series = chartData.monVolLine.map((item, idx) => ({ |
| | | name: '#' + (idx + 1), |
| | | data: item |
| | | })); |
| | | |
| | | barDatas.value = monBarData; |
| | | bgDatas.value = firestData; |
| | | monNames.value = xLabels; |
| | | updateChart(); |
| | | } |
| | | |
| | | function updateChart() { |
| | | chart0.value.updateChart(chartOptions.chart0); |
| | | updateBarChart(); |
| | | chart2.value.updateChart(chartOptions.chart2); |
| | | chart3.value.updateChart(chartOptions.chart3); |
| | | // chart2.value.updateChart(xLabels, monBarData.vol, monBarData.temp, monBarData.realCap, monBarData.resCap, monBarData.preCap); |
| | | } |
| | | |
| | | function getBarNum(data) { |
| | | let sum = 0; |
| | | data.map(v => v * 1).forEach((item) => { |
| | | sum += item; |
| | | }); |
| | | let max = data.length > 0 ? Math.max(...data) : 0; |
| | | let min = data.length > 0 ? Math.min(...data) : 0; |
| | | let avg = data.length > 0 ? sum / data.length : 0; |
| | | return { |
| | | max, |
| | | min, |
| | | avg, |
| | | }; |
| | | } |
| | | |
| | | function updateBarChart() { |
| | | let dataList = barDatas.value[chartType.value]; |
| | | let opt = chartTypes.find((item) => item.value == chartType.value); |
| | | let fixed = opt.fixed; |
| | | let unit = opt.unit; |
| | | let name = opt.label; |
| | | let index = getDataIndex(dataList.length, slider.value); |
| | | let data = []; |
| | | if (index != -1) { |
| | | data = dataList[index]; |
| | | let batNum = getBarNum(data); |
| | | monBarTitle.value = |
| | | "最大值=" + |
| | | batNum.max + |
| | | unit + |
| | | ";最小值=" + |
| | | batNum.min + |
| | | unit + |
| | | ";平均值=" + |
| | | toFixed(batNum.avg, fixed) + |
| | | unit; |
| | | } else { |
| | | console.log("未获取到值"); |
| | | } |
| | | |
| | | // 设置柱状图 |
| | | chart1.value.updateChart(monNames.value, { name, data }, bgDatas.value[chartType.value]); |
| | | } |
| | | |
| | | |
| | | // 格式化滑块显示的内容 |
| | | function formatTooltip(value) { |
| | | let index = getDataIndex(testTLong.value.length, value); |
| | | let test_long = formatSeconds(0); |
| | | if (index != -1) { |
| | | test_long = formatSeconds(testTLong[index]); |
| | | } |
| | | return test_long; |
| | | } |
| | | // 拖动滑块时触发 |
| | | function sliderInput() { |
| | | updateBarChart(); |
| | | } |
| | | // 根据百分比获取显示的数据的笔数 |
| | | function getDataIndex(num, percent) { |
| | | if (percent <= 0) { |
| | | return 0; |
| | | } |
| | | return Math.floor((num * percent) / 100) - 1; |
| | | } |
| | | |
| | | function changeChartType() { |
| | | updateBarChart(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | if (chart0.value && chart2.value && chart3.value) { |
| | | echarts.connect([chart0.value.getChart(), chart2.value.getChart(), chart3.value.getChart()]); |
| | | |
| | | } |
| | | getLists(); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="page-his"> |
| | | <div class="filter"> |
| | | <div class="filter page-filter"> |
| | | <div class="item" v-for="(item, idx) in testTypes" :key="'list0_' + idx"> |
| | | <div class="label">{{ item.label }}</div> |
| | | <div class="value"> |
| | | <el-select |
| | | v-model="item.prop" |
| | | size="small" |
| | | clearable |
| | | placeholder="请选择" |
| | | > |
| | | <el-option |
| | | v-for="(item, idx) in testRecordList[item.prop]" |
| | | :key="'list5_' + idx" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | > |
| | | <el-select v-model="selectValues[item.prop]" size="small" placeholder="请选择" @change="filterChange(item.prop)"> |
| | | <el-option v-for="(item, idx) in testRecordList[item.prop]" :key="'list5_' + idx" :label="item.label" |
| | | :value="item.value"> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="info"> |
| | | <div :class="['status-item',]" v-for="(item, index) in statusList" :key="'status_' + index"> |
| | | <div class="item-value" v-if="item.prop == 'captestTimelong' || item.prop == 'restTime'"> |
| | | {{ testInfo[item.prop] ? |
| | | formatSeconds(testInfo[item.prop]) : '--' }} |
| | | </div> |
| | | <div class="item-value time" v-else-if="item.prop == 'testStarttime'"> |
| | | {{ testInfo[item.prop] || '--' }} |
| | | </div> |
| | | <div class="item-value" v-else> |
| | | {{ testInfo[item.prop]}}{{ item.unit }} |
| | | </div> |
| | | <div class="item-name">{{ item.label }}</div> |
| | | </div> |
| | | <div class="info-item" v-for="(item, index) in infoList" :key="'info_' + index"> |
| | | <div class="label">{{ item.label }}({{ item.unit }})</div> |
| | | <div class="value">{{ testInfo[item.prop] }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="main"> |
| | | <div class="grid"> |
| | | <div class="g-item"> |
| | | <card title="端电压折线图(V)"> |
| | | <line-chart ref="chart0"></line-chart> |
| | | </card> |
| | | </div> |
| | | <div class="g-item"> |
| | | <card :title="monBarTitle"> |
| | | <template #tools> |
| | | <el-select v-model="chartType" :disabled="!currProp" size="small" @change="changeChartType"> |
| | | <el-option v-for="item in chartTypes" :key="item.value" :label="item.label" |
| | | :value="item.value"></el-option> |
| | | </el-select> |
| | | </template> |
| | | <bar-chart ref="chart1"></bar-chart> |
| | | </card> |
| | | </div> |
| | | <div class="g-item"> |
| | | <card title="电池电流折线图(A)"> |
| | | <line-chart ref="chart2"></line-chart> |
| | | </card> |
| | | </div> |
| | | <div class="g-item"> |
| | | <card title="单体电压(V)"> |
| | | <line-chart ref="chart3"></line-chart> |
| | | </card> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="footer"> |
| | | <el-slider v-model="slider" size="small" :format-tooltip="formatTooltip" @input="sliderInput" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="less"> |
| | | .page-his { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .filter { |
| | | background: #073451; |
| | | display: flex; |
| | | // flex-wrap: wrap; |
| | | align-items: center; |
| | | padding: 8px; |
| | | font-size: 16px; |
| | | color: #45beea; |
| | | |
| | | .item { |
| | | flex: 1; |
| | | display: flex; |
| | | |
| | | &~.item { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .label { |
| | | margin-right: 10px; |
| | | |
| | | &::after { |
| | | content: ":"; |
| | | } |
| | | } |
| | | |
| | | .value { |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .info { |
| | | height: 72px; |
| | | display: flex; |
| | | flex-direction: row; |
| | | |
| | | .status-item { |
| | | flex: 1; |
| | | font-size: 14px; |
| | | font-weight: 700; |
| | | color: #50c7f1; |
| | | // border: 1px solid #0F6B79; |
| | | border-radius: 6px; |
| | | margin: 4px 2px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | align-items: center; |
| | | box-shadow: inset 0 0 30px -10px #0F6B79; |
| | | background: radial-gradient(circle at -10% center, #1F6571 5%, transparent 20%) no-repeat, |
| | | radial-gradient(circle at 110% center, #1F6571 5%, transparent 20%) no-repeat, |
| | | radial-gradient(circle at center -72%, #1F6571 12%, transparent 50%) no-repeat, |
| | | radial-gradient(circle at center 138%, #1F6571 12%, transparent 50%) no-repeat; |
| | | |
| | | .item-value { |
| | | margin-bottom: 6px; |
| | | color: #ff0; |
| | | font-size: 20px; |
| | | |
| | | &.time { |
| | | white-space: nowrap; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | |
| | | .item-name { |
| | | color: #6DCCE6; |
| | | } |
| | | } |
| | | |
| | | .info-item { |
| | | flex: 1.2; |
| | | display: flex; |
| | | align-items: center; |
| | | margin-left: 2px; |
| | | |
| | | .label { |
| | | color: #6DCCE6; |
| | | font-size: 12px; |
| | | margin-bottom: 4px; |
| | | |
| | | &::after { |
| | | content: ":"; |
| | | } |
| | | } |
| | | |
| | | .value { |
| | | color: #6DCCE6; |
| | | font-size: 14px; |
| | | background: #134263; |
| | | flex: 1; |
| | | border-radius: 4px; |
| | | padding-left: 6px; |
| | | margin-left: 4px; |
| | | margin-right: 6px; |
| | | min-height: 1.8em; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .main { |
| | | flex: 1; |
| | | padding: 8px; |
| | | |
| | | .grid { |
| | | height: 100%; |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | grid-template-rows: repeat(2, 1fr); |
| | | grid-gap: 10px 8px; |
| | | place-content: stretch; |
| | | |
| | | // place-items: center stretch; |
| | | // justify-items: stretch; |
| | | // align-items: stretch; |
| | | .g-item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | :deep(.card) { |
| | | width: 100%; |
| | | |
| | | .el-select { |
| | | width: 140px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer { |
| | | padding: 0 8px; |
| | | } |
| | | </style> |