| | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | <script setup name="dataHis"> |
| | | import { ref, reactive, computed, watch, nextTick, onMounted } from "vue"; |
| | | import useDevsRt from "@/hooks/useDevsRt.js"; |
| | | import { getTestList, getRecordInf, exportRecord } from "./api"; |
| | | import lineChart from "@/components/echarts/lineChart2.vue"; |
| | | import bar from "@/components/echarts/bar1.vue"; |
| | | import ycCard from "@/components/ycCard.vue"; |
| | | import formatSeconds from "@/assets/js/tools/formatSeconds.js"; |
| | | import getBarNum from "@/assets/js/tools/getBarNum.js"; |
| | | import toFixed from "@/assets/js/tools/toFixed.js"; |
| | | import useElement from "@/hooks/useElement.js"; |
| | | |
| | | const { $alert, $loading, $message, $confirm } = useElement(); |
| | | |
| | | const { list } = useDevsRt(); |
| | | |
| | | const devType = ref(1); |
| | | const keyWord = ref(""); |
| | | const currentDevId = ref(0); |
| | | const testRecordCode = ref(); |
| | | const monChart = ref(); |
| | | const groupChart = ref(); |
| | | const slideValue = ref(0); |
| | | const testDataFormated = reactive({ |
| | | labels: [], |
| | | datas: [], |
| | | }); |
| | | |
| | | const testRecordList = ref([]); |
| | | const testDatas = ref([]); |
| | | |
| | | const resList = computed(() => { |
| | | let _list = list.value[devType.value]; |
| | | return _list.filter((v) => ("" + v.devIdcode).indexOf(keyWord.value) > -1); |
| | | }); |
| | | |
| | | const testObj = computed(() => { |
| | | if (testRecordList.value) { |
| | | let [type, record] = testRecordCode.value || []; |
| | | if (!type) return {}; |
| | | |
| | | let list = testRecordList.value.filter((v) => v.value == type)[0].children; |
| | | return list.filter((v) => record == v.value)[0]; |
| | | } else { |
| | | return {}; |
| | | } |
| | | }); |
| | | |
| | | function chooseDev(params) { |
| | | currentDevId.value = params.devId; |
| | | testRecordCode.value = undefined; |
| | | testDatas.value = []; |
| | | updateChart([]); |
| | | getList(); |
| | | } |
| | | |
| | | const stopWatch = watch(resList, (newValue, oldValue) => { |
| | | console.log("resList", newValue, oldValue); |
| | | typeChange(); |
| | | stopWatch(); |
| | | }); |
| | | |
| | | function typeChange() { |
| | | chooseDev(resList.value[0]); |
| | | } |
| | | |
| | | function getList() { |
| | | getTestList(currentDevId.value) |
| | | .then((res) => { |
| | | let { code, data, data2 } = res.data; |
| | | let _list = []; |
| | | if (code && data) { |
| | | console.log(data); |
| | | _list = Object.keys(data2).map((v) => { |
| | | let arr = data2[v]; |
| | | let type = ""; |
| | | switch (v) { |
| | | case "chr": |
| | | type = devType.value == 1 ? "充电测试" : "充电"; |
| | | break; |
| | | case "dis": |
| | | type = devType.value == 1 ? "放电测试" : "放电"; |
| | | break; |
| | | case "jun": |
| | | type = "均衡"; |
| | | break; |
| | | } |
| | | let label = type; |
| | | let value = v; |
| | | let children = []; |
| | | let count = arr.length; |
| | | if (devType.value == 1) { |
| | | children = arr.map((vv) => ({ |
| | | ...vv, |
| | | label: vv.testStarttime, |
| | | value: vv.testRecordCount, |
| | | })); |
| | | } else { |
| | | // 添加一级 区分是组1还是组2 |
| | | let _obj = {}; |
| | | for (let i = arr.length; i--; ) { |
| | | let item = arr[i]; |
| | | let idx = item.battIdx; |
| | | _obj[idx] = _obj[idx] || []; |
| | | _obj[idx].push(item); |
| | | } |
| | | |
| | | Object.keys(_obj).forEach((vv) => { |
| | | let item = _obj[vv]; |
| | | let idx = `组${vv * 1 + 1}`; |
| | | let _count = item.length; |
| | | children.push({ |
| | | label: idx, |
| | | count: _count, |
| | | value: `group_${vv}`, |
| | | children: item.map((vvv) => ({ |
| | | ...vvv, |
| | | label: vvv.testStarttime, |
| | | value: vvv.testRecordCount, |
| | | })), |
| | | }); |
| | | }); |
| | | // children = arr.map((vv) => ({ |
| | | // ...vv, |
| | | // label: vv.testStarttime, |
| | | // value: vv.testRecordCount, |
| | | // })); |
| | | } |
| | | return { label, value, count, disabled: !arr.length, children }; |
| | | }); |
| | | } |
| | | testRecordList.value = _list; |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | } |
| | | |
| | | const canSlide = computed(() => !!testDatas.value.length); |
| | | |
| | | // const testInf = computed(() => { |
| | | // if (testDatas.value.length) { |
| | | // return testDatas.value[0]; |
| | | // } else { |
| | | // return {}; |
| | | // } |
| | | // }); |
| | | |
| | | function updateChart(data) { |
| | | let labels = [], |
| | | groupVols = [], |
| | | monVols = []; |
| | | let recordTime = -1; // 记录时间 |
| | | let recordNum = -100; // 记录笔数 |
| | | data.forEach((item) => { |
| | | let monNum = item.monNum; |
| | | let testTimeLong = formatSeconds(item.testTimelong); |
| | | // 获取组端电压,在线电压,组端电流的信息和开辟一个单体柱状图 |
| | | if (recordNum != item.recordNum) { |
| | | recordTime = item.recordTime; |
| | | recordNum = item.recordNum; |
| | | labels.push(testTimeLong); |
| | | groupVols.push(item.groupVol); |
| | | // 开辟空间 |
| | | monVols.push({ |
| | | labels: [], |
| | | datas: [], |
| | | }); |
| | | } |
| | | // 单体电压柱状图设置 |
| | | let monNum_text = "#" + monNum; |
| | | let monBarVol = monVols[monVols.length - 1]; |
| | | // monBarVol.push([monNum_text, item.monVol]); |
| | | monBarVol.labels.push(monNum_text); |
| | | monBarVol.datas.push(item.monVol); |
| | | }); |
| | | groupChart.value.updateChart(labels, groupVols); |
| | | testDataFormated.datas = monVols; |
| | | testDataFormated.labels = labels; |
| | | setBarChart(monVols); |
| | | } |
| | | // 根据百分比获取显示的数据的笔数 |
| | | function getDataIndex(num, percent) { |
| | | if (percent <= 0) { |
| | | return 0; |
| | | } |
| | | return Math.floor((num * percent) / 100) - 1; |
| | | } |
| | | |
| | | function handleSlider(params) { |
| | | // updateChart(testDatas.value); |
| | | setBarChart(testDataFormated.datas); |
| | | } |
| | | |
| | | // 设置柱状图 |
| | | function setBarChart(dataList) { |
| | | // if (!dataList.length) { |
| | | // return false; |
| | | // } |
| | | |
| | | let unit = "V"; |
| | | let fixed = 3; |
| | | |
| | | let title = `最大值=0${unit}; 最小值=0${unit}; 平均值=0${unit};`; |
| | | let index = getDataIndex(dataList.length, slideValue.value); |
| | | let labels = [], |
| | | datas = []; |
| | | if (dataList.length && index != -1) { |
| | | let data = dataList[index]; |
| | | let batNum = getBarNum(data); |
| | | title = |
| | | "最大值=" + |
| | | batNum.max + |
| | | unit + |
| | | ";最小值=" + |
| | | batNum.min + |
| | | unit + |
| | | ";平均值=" + |
| | | toFixed(batNum.avg, fixed) + |
| | | unit; |
| | | labels = data.labels; |
| | | datas = data.datas; |
| | | } |
| | | monChart.value.updateChart(title, labels, datas); |
| | | } |
| | | |
| | | function selectRecord() { |
| | | let res = testRecordCode.value[testRecordCode.value.length - 1]; |
| | | // console.log("res", res, "============="); |
| | | |
| | | getRecordInf(currentDevId.value, res) |
| | | .then((res) => { |
| | | let { code, data, data2 } = res.data; |
| | | let _list = []; |
| | | if (code && data) { |
| | | console.log(data); |
| | | |
| | | _list = data2; |
| | | updateChart(_list); |
| | | } |
| | | testDatas.value = _list; |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | } |
| | | |
| | | function exportData() { |
| | | if (!testRecordCode.value || !testRecordCode.value.length) { |
| | | $message.error("请选择要导出的测试记录"); |
| | | return false; |
| | | } |
| | | exportRecord(currentDevId.value, testRecordCode.value[1]) |
| | | .then((res) => { |
| | | let { data, status, headers } = res; |
| | | const matchRes = /filename=(.*)/.exec(headers["content-disposition"]); |
| | | const fileName = matchRes ? matchRes[1].trim() : "未知文件名.xls"; |
| | | if (200 == status && data) { |
| | | let url = window.URL.createObjectURL(data); |
| | | let link = document.createElement("a"); |
| | | link.style.display = "none"; |
| | | link.href = url; |
| | | link.download = decodeURI(fileName); |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | document.body.removeChild(link); |
| | | window.URL.revokeObjectURL(url); |
| | | // downloadLog(parentModel, subModel); |
| | | } else { |
| | | $message.error("下载失败"); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | } |
| | | |
| | | function formatTooltip(params) { |
| | | let testTimeLong = testDataFormated.labels; |
| | | let index = getDataIndex(testTimeLong.length, params); |
| | | let test_long = formatSeconds(0); |
| | | if (index != -1) { |
| | | // test_long = formatSeconds(testTimeLong[index]); |
| | | test_long = testTimeLong[index]; |
| | | } |
| | | return test_long; |
| | | } |
| | | |
| | | onMounted(() => { |
| | | if (resList.length) { |
| | | typeChange(); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | 历史数据 |
| | | <div class="page"> |
| | | <div class="p-left"> |
| | | <div class="filter"> |
| | | <el-input |
| | | v-model="keyWord" |
| | | class="key-word" |
| | | placeholder="请您输入您要查找的设备" |
| | | > |
| | | <template #suffix> |
| | | <el-icon class="el-input__icon" color="#42f4b5"><search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </div> |
| | | <div class="tabs"> |
| | | <el-radio-group |
| | | v-model="devType" |
| | | @change="typeChange" |
| | | size="default" |
| | | is-button |
| | | > |
| | | <el-radio-button :value="1">充放电测试仪</el-radio-button> |
| | | <el-radio-button :value="2">均衡测试仪</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <div class="list-wrap posR"> |
| | | <div class="list pos-full"> |
| | | <div |
| | | :class="['item', { active: item.devId == currentDevId }]" |
| | | v-for="item in resList" |
| | | :key="item.devId" |
| | | @click="chooseDev(item)" |
| | | > |
| | | {{ item.devIdcode }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="p-content"> |
| | | <yc-card class="item item-left" title="测试记录"> |
| | | <el-cascader |
| | | class="select" |
| | | v-model="testRecordCode" |
| | | :options="testRecordList" |
| | | @change="selectRecord" |
| | | ><template #default="{ node, data }"> |
| | | <span>{{ data.label }}</span> |
| | | <span v-if="!node.isLeaf"> ({{ data.count }}) </span> |
| | | </template></el-cascader |
| | | > |
| | | <div class="info-title"> |
| | | 测试概况 |
| | | <div |
| | | :class="['btn', { disabled: !testRecordCode }]" |
| | | @click="exportData" |
| | | > |
| | | 导出 |
| | | </div> |
| | | </div> |
| | | <div class="info"> |
| | | <div class="label">测试时间</div> |
| | | <div class="content">{{ testObj.testStarttime }}</div> |
| | | <div class="label">测试时长</div> |
| | | <div class="content">{{ formatSeconds(testObj.testTimelong) }}</div> |
| | | <div class="label">数据类型</div> |
| | | <div class="content"> |
| | | {{ |
| | | { 2: "充电数据", 3: "放电数据", 4: "均衡数据" }[testObj.testType] |
| | | }} |
| | | </div> |
| | | <div class="label">停止原因</div> |
| | | <div class="content">{{ testObj.stopTypeReason }}</div> |
| | | <!-- <div class="label">标称容量</div> |
| | | <div class="content">4444444</div> |
| | | <div class="label">单体数量</div> |
| | | <div class="content">33333</div> |
| | | <div class="label">测试电流</div> |
| | | <div class="content">4444444</div> |
| | | <div class="label">单体下限</div> |
| | | <div class="content">4444444</div> |
| | | <div class="label">测试时长</div> |
| | | <div class="content">4444444</div> |
| | | <div class="label">测试容量</div> |
| | | <div class="content">4444444</div> |
| | | <div class="label">停止原因</div> |
| | | <div class="content">4444444</div> --> |
| | | </div> |
| | | </yc-card> |
| | | <yc-card class="item" title="组端电压"> |
| | | <line-chart ref="groupChart" unit="V"></line-chart> |
| | | </yc-card> |
| | | <yc-card class="item" title="单体电压"> |
| | | <div class="card-content"> |
| | | <div class="chart-w"> |
| | | <bar ref="monChart" unit="V"></bar> |
| | | </div> |
| | | <el-slider |
| | | class="slider" |
| | | v-model="slideValue" |
| | | :format-tooltip="formatTooltip" |
| | | :disabled="!canSlide" |
| | | @input="handleSlider" |
| | | /> |
| | | </div> |
| | | </yc-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="less"> |
| | | .page { |
| | | height: 100%; |
| | | padding: 8px; |
| | | display: flex; |
| | | |
| | | </style> |
| | | .p-left { |
| | | width: 340px; |
| | | background: radial-gradient(#1a585d10, #1a585d80); |
| | | display: flex; |
| | | flex-direction: column; |
| | | .filter { |
| | | padding: 6px; |
| | | .key-word { |
| | | // margin: 6px; |
| | | } |
| | | :deep(.el-input__inner) { |
| | | background: transparent; |
| | | color: #fff; |
| | | &::placeholder { |
| | | color: #b0b7b4; |
| | | } |
| | | } |
| | | :deep(.el-input__wrapper) { |
| | | background-color: rgba(66, 244, 181, 0.1); |
| | | box-shadow: 0 0 0 1px #42f4b5 inset; |
| | | } |
| | | } |
| | | .tabs { |
| | | display: flex; |
| | | justify-content: center; |
| | | } |
| | | .list-wrap { |
| | | flex: 1; |
| | | margin: 6px; |
| | | .list { |
| | | border: 1px solid #0ff; |
| | | overflow-y: auto; |
| | | padding-left: 4em; |
| | | padding-top: 6px; |
| | | .item { |
| | | height: 38px; |
| | | display: flex; |
| | | align-items: center; |
| | | position: relative; |
| | | color: #0ff; |
| | | cursor: pointer; |
| | | &.active { |
| | | background: #007983; |
| | | } |
| | | &:hover { |
| | | background: rgba(100, 216, 216, 0.3); |
| | | } |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: -2em; |
| | | top: 50%; |
| | | width: 1.8em; |
| | | border-top: 1px solid #0ff; |
| | | } |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 50%; |
| | | left: -2em; |
| | | |
| | | height: 100%; |
| | | border-left: 1px solid #0ff; |
| | | } |
| | | &:last-of-type { |
| | | &::after { |
| | | content: none; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .p-content { |
| | | flex: 1; |
| | | margin-left: 10px; |
| | | // display: flex; |
| | | // flex-direction: column; |
| | | display: grid; |
| | | grid-template-columns: 300px 1fr; |
| | | grid-template-rows: 1fr 1fr; |
| | | grid-auto-flow: column; |
| | | gap: 8px; |
| | | // background: #1a585d; |
| | | .item-left { |
| | | grid-row: 1 e("/") 3; |
| | | :deep(.c-content) { |
| | | padding: 6px; |
| | | } |
| | | :deep(.select) { |
| | | width: 100%; |
| | | margin-bottom: 0.8em; |
| | | } |
| | | .info-title { |
| | | color: #0ff; |
| | | font-weight: bold; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | .btn { |
| | | cursor: pointer; |
| | | display: inline-block; |
| | | background: #0ff; |
| | | border-radius: 6px; |
| | | padding: 0 6px; |
| | | color: #000; |
| | | &.disabled { |
| | | background: #ccc; |
| | | } |
| | | } |
| | | } |
| | | .info { |
| | | display: grid; |
| | | grid-template-columns: 5em 1fr; |
| | | padding-left: 0.6em; |
| | | .label { |
| | | &::after { |
| | | content: ":"; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .item { |
| | | // background: #000; |
| | | background: radial-gradient(#1a585d10, #1a585d80); |
| | | } |
| | | .card-content { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | padding: 10px; |
| | | .chart-w { |
| | | flex: 1; |
| | | } |
| | | .slider { |
| | | padding-left: 10px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |