Files
smart_storage_web/src/views/surveillance/battery-analysis/index.vue

1050 lines
27 KiB
Vue
Raw Normal View History

2025-06-30 10:17:15 +08:00
<template>
<section class="realtime-wrapper">
<el-form id="searchForm">
<el-row :gutter="5" class="search-row">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="6">
<el-form-item :label="$t('surveillance.indicators') + ':'" label-width="80px">
<el-input
v-model="allColName"
:placeholder="$t('surveillance.placeHolderCol')"
class="input-with-select"
readonly
@focus="openDevDialog"
>
<el-button
slot="append"
icon="el-icon-setting"
:loading="dev_loading"
@click="openDevDialog"
/>
</el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="6">
<el-form-item :label="$t('surveillance.timeRange') + ':'" label-width="100px">
<el-date-picker
v-model="time"
:picker-options="pickerOptionsStart"
style="width: 100%"
type="datetimerange"
range-separator="-"
:start-placeholder="$t('surveillance.startTime')"
:end-placeholder="$t('surveillance.endTime')"
value-format="yyyy-MM-dd HH:mm:ss"
@change="changeTime"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="3">
<el-button
type="primary"
icon="el-icon-search"
:loading="search_loading"
@click="on_refresh"
>{{ $t('surveillance.query') }}</el-button>
<el-button
type="primary"
icon="el-icon-refresh"
class="reset-btn"
@click="handleSearchClear"
>{{ $t('surveillance.reset') }}</el-button>
</el-col>
<el-col
:xs="24"
:sm="12"
:md="12"
:lg="12"
:xl="9"
style="text-align: right"
>
<el-button
v-show="isLine"
type="primary"
class="reset-btn"
icon="el-icon-s-grid"
@click="isLine = false"
>{{ $t('surveillance.list') }}</el-button>
<el-button
v-show="!isLine"
type="primary"
class="reset-btn"
icon="el-icon-s-data"
@click="isLine = true"
>{{ $t('surveillance.chart') }}</el-button>
<el-button
v-show="!isOriginal"
type="primary"
class="reset-btn"
icon="el-icon-notebook-1"
@click="handleIsOriginal('原始值')"
>{{ $t('surveillance.original') }}</el-button>
<el-button
v-show="isOriginal"
type="primary"
class="reset-btn"
icon="el-icon-notebook-2"
@click="handleIsOriginal('处理值')"
>{{ $t('surveillance.dispose') }}</el-button>
<el-button
type="primary"
class="reset-btn"
@click="selectTemplate"
>{{ $t('surveillance.selectTemPlates') }}</el-button>
<el-button
v-permission="['data:hisCurve:export']"
type="primary"
class="reset-btn"
icon="el-icon-upload2"
:loading="export_loading"
@click="handleDownExcel"
>{{ $t('surveillance.export') }}</el-button>
<el-button
v-permission="['data:hisCurve:newExport']"
type="primary"
class="reset-btn"
icon="el-icon-upload2"
:loading="new_export_loading"
@click="handleNewDownExcel"
>{{ $t('surveillance.listExport') }}</el-button>
</el-col>
</el-row>
</el-form>
<colSelectDialog
ref="dialog"
:is-save="true"
:is-show="devDialog"
:dev-tree-data="devTreeData"
:col-tree-data="colTreeData"
@changeSensType="changeSensType"
@GetDeviceId="GetDeviceId"
@GetQueryPoint="GetQueryPoint"
@GetResultData="GetResultData"
@SaveTemplate="SaveTemplate"
@Close="Close"
/>
<el-dialog
:append-to-body="false"
:title="$t('surveillance.selectTemPlates')"
:visible.sync="templateVisible"
width="800px"
:close-on-click-modal="false"
center
@close="closeTemplate"
>
<el-table
ref="table_data"
highlight-current-row
:data="table_data"
height="400"
:loading="table_load"
@current-change="handleTemSelection"
>
<el-table-column prop="modelName" :label="$t('surveillance.templateName')" />
<el-table-column :label="$t('surveillance.indicators')" align="center">
<template slot-scope="scope">
<div v-for="item in scope.row.list" :key="item.id">
<el-tag size="normal" effect="dark">{{ item.colName }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('surveillance.operation')" align="center" width="150">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
icon="el-icon-delete"
class="btn-delete-table-text"
@click="handleDel(scope.row)"
>{{ $t('surveillance.delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div>
<Pagingbar>
<div slot="page">
<el-pagination
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="pageSizes"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</Pagingbar>
</div>
<div slot="footer" style="width: 100%; text-align: right">
<el-button @click="closeTemplate">{{ $t('surveillance.cancel') }}</el-button>
<el-button type="primary" @click="sureTemplate">{{ $t('surveillance.sure') }}</el-button>
</div>
</el-dialog>
<div v-show="isLine" v-loading="loading" class="table-wrapper">
<chart :show="isLine" :option="chartData" :time="time" />
</div>
<div v-show="!isLine" v-loading="loading" class="table-wrapper">
<Card :card-list="cardList" :params="cardParams" :is-show="isLine" />
</div>
<ModelKuang :is-model-show="showModel" :percentage="percentage" />
</section>
</template>
<script>
import chart from './components/chart.vue'
import Card from './components/Card.vue'
import { handleDownExcel } from '@/utils'
import XLSX from 'xlsx'
import {
GetPointCurve,
GetPointTable,
GetPageSelectAll,
DeleteTemplate
} from '@/api/surveillance/battery-analysis'
import { GetQueryPoint } from '@/api/surveillance/battery-analysis'
2025-06-30 10:17:15 +08:00
import ModelKuang from '@/components/ModelKuang/index.vue'
import { pageSize, pageSizes } from '@/config'
import Pagingbar from '@/components/Pagingbar'
import { TemplateAdd } from '@/api/surveillance/battery-analysis'
import { GetTreeVirtualDevices } from '@/api/surveillance/energy-storage/index'
export default {
components: { chart, Card, Pagingbar, ModelKuang },
data() {
return {
isLine: true,
allColName: '',
dev_loading: false,
search_loading: false,
export_loading: false,
devDialog: false,
loading: false,
devTreeData: [],
colTreeData: [],
time: null,
pickerOptionsStart: {
// 时间不能大于当前时间
disabledDate: (time) => {
return time.getTime() > Date.now()
}
},
filters: {
deviceIdList: null,
col: null,
sensType: 2,
stationId: null,
beginTime: null,
endTime: null
},
cardParams: {},
chartData: [],
result: null,
defaultTable: [],
defaultCol: [],
templateVisible: false,
table_data: [],
cardList: [], // card
currentPage: 1,
// 数据总条目
total: 0,
// 每页显示多少条数据
pageSize: pageSize,
pageSizes: pageSizes,
table_load: false,
templateValue: [],
isOriginal: false,
jsonData: [],
showModel: false,
percentage: 0,
new_export_loading: false
}
},
computed: {
stations: function() {
return this.$store.getters.stations || []
},
currentStation() {
return this.$store.getters.currentStation || undefined
},
language() {
return this.$store.getters.language || undefined
}
},
watch: {
currentStation: {
async handler(val) {
if (val && val.id) {
this.filters.stationId = val.id
this.FindIntegratedCabinets(this.filters.stationId)
this.colTreeData = []
if (this.$refs.dialog) {
this.filters.endTime = ''
this.filters.beginTime = ''
this.time = []
this.allColName = ''
this.$refs.dialog.clearAll()
this.chartData = []
}
}
},
deep: true,
immediate: true
},
language: {
handler(val) {
this.chartData = []
},
deep: true
}
},
created() {},
mounted() {},
methods: {
async SaveTemplate(params) {
try {
await TemplateAdd(params)
this.$notify({
title: '成功',
message: '保存成功',
type: 'success',
duration: 2000
})
} catch (error) {
this.$notify({
title: '失败',
message: '保存失败',
type: 'warning',
duration: 2000
})
} finally {
this.$refs.dialog.TemplateVisable = false
this.$refs.dialog.temLoading = false
}
},
handleIsOriginal(val) {
if (val === '原始值') {
this.isOriginal = true
} else {
this.isOriginal = false
}
this.on_refresh()
},
changeRadio(val) {
this.isOriginal = val
},
handleTemSelection(val) {
this.templateValue = val
},
async GetPageSelectAll() {
this.table_load = true
try {
const res = await GetPageSelectAll({
pageNum: this.currentPage,
pageSize: this.pageSize,
stationId: this.filters.stationId
})
this.table_data = res.data.list
this.total = res.data.totalRows
} catch (error) {
// console.log(error)
} finally {
this.table_load = false
}
},
// 删除模板
async handleDel(row) {
this.$confirm(`${this.$t('surveillance.deleteTip')}`, `${this.$t('surveillance.tip')}`, {
confirmButtonText: `${this.$t('surveillance.sure')}`,
cancelButtonText: `${this.$t('surveillance.cancel')}`,
type: 'warning',
showClose: false,
center: true
}).then(async() => {
const params = {
modelId: row.modelId
}
DeleteTemplate(params)
.then(() => {
this.$message.success(this.$t('surveillance.delSuccess'))
this.GetPageSelectAll()
})
.catch(() => {
this.$message.warning(this.$t('surveillance.delFail'))
})
})
},
// 关闭弹出框
closeTemplate() {
this.templateVisible = false
},
// 打开选择模板弹出框
selectTemplate() {
this.GetPageSelectAll()
this.templateVisible = true
},
// 确认模板
sureTemplate() {
const dialogTable = []
const dialogCol = []
if (this.templateValue && this.templateValue.list.length > 0) {
this.templateValue.list.forEach((el) => {
dialogTable.push({
srcId: el.srcId,
col: el.col,
id: el.srcId + el.col,
colName: el.colName,
deviceType: el.deviceType
})
dialogCol.push(el.srcId + el.col)
this.allColName += el.colName + ','
})
this.$refs.dialog.tableData = dialogTable
this.$refs.dialog.col = dialogCol
this.$refs.dialog.submit()
}
this.templateVisible = false
},
handleCurrentChange(val) {
this.currentPage = val
},
handleSizeChange(val) {
this.currentPage = 1
this.pageSize = val
},
// 关闭弹出框
Close() {
this.devDialog = false
},
changeSensType(val) {
this.filters.sensType = val
this.GetQueryPoint()
},
splitByNameToArray(arr) {
const result = []
const map = new Map()
// 遍历每个对象
arr.forEach((item) => {
// 遍历每个 list
item.list.forEach((entry) => {
const key = entry.row_name_digital0
if (!map.has(key)) {
map.set(key, [])
}
map.get(key).push(entry)
})
})
// 将 map 中的值转化为二维数组
map.forEach((value) => {
result.push(value)
})
return result
},
exportFile() {
const groupedArrays = this.jsonData.reduce((acc, obj) => {
const key = obj.row_name_digital0
if (!acc[key]) {
acc[key] = []
}
acc[key].push(obj)
return acc
}, {})
const result = Object.values(groupedArrays)
const wb = XLSX.utils.book_new()
const arr = []
result.forEach((item, index) => {
arr.push([])
item.forEach((item2, index2) => {
arr[index].push({
时间: item2.date0,
名称: item2.row_name_digital0,
: item2.digital0
})
})
})
arr.forEach((item, index) => {
const ws = XLSX.utils.json_to_sheet(item)
XLSX.utils.book_append_sheet(wb, ws, `${item[0]['名称']}`)
})
XLSX.writeFile(wb, `${this.currentStation.name}.xlsx`)
},
getPageExport(i, params, size) {
return new Promise((resolve) => {
this.showModel = true
if (i !== size) {
this.percentage = +((i / size) * 100).toFixed(2)
} else {
this.percentage = 95
}
setTimeout(async() => {
try {
const { data } = await GetPointTable(this.cardParams)
console.log(this.jsonData.length)
data.forEach((item, index) => {
this.jsonData.push(...data[index].list)
if (this.jsonData.length >= 100000) {
this.exportFile()
this.jsonData = []
}
})
if (i === size) {
this.exportFile()
this.showModel = false
this.new_export_loading = false
this.percentage = 0
}
resolve(`请求${i}的数据`)
} catch (error) {
console.log(error)
this.new_export_loading = false
this.showModel = false
this.percentage = 0
this.$message({
title: this.$t('surveillance.tip'),
message: this.$t('surveillance.exportFail'),
type: 'warning'
})
}
}, 200)
})
},
// 导出
handleDownExcel() {
this.export_loading = true
if (this.chartData.length > 0) {
const params = this.result
const stationName = this.currentStation.name + `${this.$t('surveillance.historyData')}.xls`
if (this.isOriginal) {
params.isOriginal = 1
} else {
params.isOriginal = 0
}
if (this.isLine) {
params.pageType = 0
} else {
params.pageType = 1
}
params.beginTime = this.filters.beginTime
params.endTime = this.filters.endTime
params.title = stationName
handleDownExcel(
'/business/point/hisExportExcel',
params,
(callback) => {
this.export_loading = false
}
)
} else {
this.export_loading = false
this.$message({
message: this.$t('surveillance.exportBefore'),
type: 'warning'
})
}
},
parseDate(dateStr) {
const parts = dateStr.split(/[- :]/)
return new Date(
parts[0],
parts[1] - 1,
parts[2],
parts[3],
parts[4],
parts[5]
)
},
generateDateRanges(startDateStr, endDateStr) {
const dateRanges = []
// 解析 startDateStr 和 endDateStr 为 Date 对象
const startDate = new Date(startDateStr)
const endDate = new Date(endDateStr)
// 生成每日时间段
let currentStart = new Date(startDate)
// 获取结束日期的日期部分,用于处理结束日期的边界情况
const endDateOnly = new Date(endDate)
endDateOnly.setHours(0, 0, 0, 0)
while (currentStart < endDate) {
// 计算当前日期的结束时间
const currentEnd = new Date(currentStart)
currentEnd.setDate(currentEnd.getDate() + 1)
currentEnd.setHours(0, 0, 0, 0)
// 如果当前日期的结束时间超过了最终结束日期,调整
const rangeEnd = currentEnd > endDate ? endDate : currentEnd
dateRanges.push({
beginTime: this.formatDate(currentStart),
endTime: this.formatDate(rangeEnd)
})
currentStart = currentEnd
}
return dateRanges
},
formatDate(date) {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
const seconds = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
},
// 导出
async handleNewDownExcel() {
const arr = this.generateDateRanges(
this.filters.beginTime,
this.filters.endTime
)
const params = {
...this.result,
isOriginal: this.isOriginal ? 1 : 0,
beginTime: this.filters.beginTime,
endTime: this.filters.endTime
}
this.cardParams = {
...params,
pageNum: 1,
pageSize: 2000
}
this.new_export_loading = true
for (let i = 0; i < arr.length; i++) {
this.cardParams.beginTime = arr[i].beginTime
this.cardParams.endTime = arr[i].endTime
await this.getPageExport(i, this.cardParams, arr.length - 1)
}
},
// 打开弹出框
openDevDialog() {
this.devDialog = true
this.$refs.dialog.setDefault(this.defaultCol, this.defaultTable)
},
changeTime(val) {
if (val) {
this.filters.beginTime = val[0]
this.filters.endTime = val[1]
} else {
this.filters.beginTime = ''
this.filters.endTime = ''
}
},
// 获取指标
async GetQueryPoint() {
this.$refs.dialog.load_data_org2 = true
const params = {
sensType: this.filters.sensType,
srcIdList: this.deviceIdList,
stationId: this.filters.stationId
}
try {
const res = await GetQueryPoint(params)
2025-06-30 10:17:15 +08:00
const option = res.data
// 将指标的数据处理成树
if (option.length > 0) {
option.forEach((el) => {
el.colName = el.deviceName
el.col = el.srcId
})
option[0].deviceTypeColList.forEach((item) => {
item.flgId = this.deviceIdList[0] + item.col
})
this.colTreeData = option[0].deviceTypeColList
}
} catch (error) {
// console.log(error);
} finally {
this.$refs.dialog.load_data_org2 = false
}
},
// 点击弹出框的设备获取srcId
GetDeviceId(srcId) {
this.deviceIdList = [srcId]
},
// 将弹出框的数据获取并处理
GetResultData(col, table, colName) {
this.defaultCol = col
this.defaultTable = table
this.allColName = colName
const params = {
stationId: this.filters.stationId,
deviceIdList: []
}
const result = col.reduce((acc, curr, index, array) => {
const same = array.filter((item) => item[0] === curr[0])
if (same.length >= 1 && !acc.some((item) => item[0][0] === curr[0])) {
acc.push(same)
}
return acc
}, [])
const arr = []
const deviceIdList = []
for (let i = 0; i < table.length; i++) {
arr.push([])
if (result[i]) {
for (let j = 0; j < result[i].length; j++) {
arr[i].push(result[i][j][1])
}
}
if (!deviceIdList.includes(table[i].srcId)) {
deviceIdList.push(table[i].srcId)
}
}
const colOptionArr = []
deviceIdList.forEach((item, index) => {
colOptionArr[index] = [{ srcId: item }]
colOptionArr[index][0].col = []
result.forEach((item1, index1) => {
item1.forEach((item2) => {
if (+colOptionArr[index][0].srcId === +item2[0]) {
colOptionArr[index][0].col.push(item2[1])
}
})
})
})
colOptionArr.forEach((el, index) => {
params.deviceIdList.push({
srcId: el[0].srcId,
cols: el[0].col
})
})
this.result = params
},
// 查询
async on_refresh() {
const params = {
...this.result,
isOriginal: 0
}
if (this.isOriginal) {
params.isOriginal = 1
} else {
params.isOriginal = 0
}
params.beginTime = this.filters.beginTime
params.endTime = this.filters.endTime
if (!params.beginTime || !params.endTime) {
this.$message({
title: this.$t('surveillance.tip'),
message: this.$t('surveillance.placeholderDate'),
type: 'warning'
})
} else if (!params.deviceIdList?.length) {
this.$message({
title: this.$t('surveillance.tip'),
message: this.$t('surveillance.placeHolderCol'),
type: 'warning'
})
} else {
this.search_loading = true
this.loading = true
this.cardParams = {
...params,
pageNum: 1,
isOriginal: params.isOriginal,
pageSize: pageSize
}
try {
const res = await GetPointCurve(params)
this.chartData = res.data
} catch (error) {
this.chartData = []
this.$notify({
title: this.$t('surveillance.fail'),
message: this.$t('surveillance.queryFail'),
type: 'warning',
duration: 2000
})
} finally {
this.loading = false
this.search_loading = false
}
try {
const cardList = await GetPointTable(this.cardParams)
this.cardList = cardList.data
} catch (error) {
this.cardList = []
this.$notify({
title: this.$t('surveillance.fail'),
message: this.$t('surveillance.queryListFail'),
type: 'warning',
duration: 2000
})
} finally {
this.loading = false
this.search_loading = false
}
}
},
// 获取设备
async FindIntegratedCabinets(id) {
this.dev_loading = true
try {
const res = await GetTreeVirtualDevices({ stationId: id })
this.devTreeData = res.data
} catch (error) {
// console.log(error);
} finally {
this.dev_loading = false
}
},
// 清空
handleSearchClear() {
this.filters.endTime = ''
this.filters.beginTime = ''
this.time = []
this.allColName = ''
if (this.$refs.dialog) {
this.$refs.dialog.clearAll()
this.$refs.dialog.setKeys([], [])
this.$refs.dialog.tableData = []
this.$refs.dialog.col = []
}
this.chartData = []
}
}
}
</script>
<style lang="scss" scoped>
.realtime-wrapper {
width: 100%;
height: 100%;
background:var(--table-bg);
padding: 10px;
box-shadow: inset 0px 2px 16px 0px rgba(0, 148, 255, 0.15);
height: 100%;
overflow: auto;
display: flex;
flex-direction: column;
::v-deep .el-tag.el-tag--info {
color: #fff;
}
/deep/.el-select-dropdown__wrap {
max-height: 600px !important;
}
/deep/.el-upload--picture-card {
background-color: #091a31 !important;
}
.main-btns {
margin-left: auto;
text-align: right;
padding: 10px 10px;
button {
color: #fff;
}
}
.top-search {
width: 100%;
height: 50px;
margin-bottom: 10px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 10px;
.serach-item {
display: flex;
align-items: center;
color: $text-color;
.input-box {
width: 60%;
}
}
.btns-box {
text-align: right;
.upload-btn {
background: $tiffany;
}
.download-btn {
background: $green;
}
}
}
.table-wrapper {
height: calc(100% - 60px);
padding: 10px;
overflow: hidden;
}
.filter-tree {
margin-top: 10px;
}
.left-wrap {
height: 100%;
overflow: auto;
padding-bottom: 60px;
margin-top: 10px;
.right-item-wrap {
width: 100%;
height: 100%;
display: flex;
align-items: center;
.header-select {
width: 100%;
height: 40px;
display: flex;
align-items: center;
font-size: 14px;
color: #fff;
.xuhao {
width: 60px;
height: 40px;
line-height: 40px;
text-align: center;
}
.select_name {
flex: 1;
height: 40px;
line-height: 40px;
text-align: center;
}
}
.item-select {
width: 100%;
height: 40px;
display: flex;
align-items: center;
font-size: 14px;
color: #fff;
.xuhao {
width: 60px;
height: 40px;
}
.select_name {
flex: 1;
height: 40px;
}
}
}
}
}
.label {
white-space: nowrap;
}
.sn-box {
display: flex;
flex-direction: row;
align-items: center;
.operate-i {
font-size: 16px;
color: #fff;
margin-left: 5px;
}
}
/deep/ .el-cascader__search-input {
display: none;
}
.el-cascader .el-input .el-input__inner:focus,
.el-cascader .el-input.is-focus .el-input__inner {
height: 33px !important; //这里高度根据需求自己设定
}
/deep/.el-cascader__tags {
display: inline-flex;
margin-right: 10px;
flex-wrap: nowrap !important;
}
/deep/ .el-cascader__tags .el-tag:not(.is-hit) {
border-color: #909399 !important;
}
.reset-btn {
background: rgba(0, 148, 255, 0.15);
border-color: rgba(0, 148, 255, 0.15);
}
.dialog-label {
font-size: 16px;
color: #fff;
}
/deep/.el-scrollbar {
}
// /deep/ .vue-treeselect__placeholder{
// color: #a5a5a5!important;
// font-size: 14px;
// display: flex;
// align-items: center;
// }
/deep/.vue-treeselect__control {
border: 1px solid #4f6f84;
}
</style>
<style lang="scss">
.history-data-page {
.el-cascader-panel {
height: 585px;
.el-scrollbar__wrap {
height: 100%;
}
.el-cascader-menu__wrap {
max-height: 585px;
}
}
.el-select-dropdown.el-popper.is-multiple.multi-cascader-style
.el-select-dropdown__item {
background-color: #025388;
}
}
</style>