1842 lines
92 KiB
Vue
1842 lines
92 KiB
Vue
<template>
|
||
<div class="data-center-history">
|
||
<el-card>
|
||
<div class="history-device-filter">
|
||
<el-form size="mini" :inline="true" label-width="80px" @submit.native.prevent>
|
||
<el-form-item :label="$t('dataCenter.history.filterDept')" prop="deptId" class="history-device-filter__item">
|
||
<el-cascader
|
||
v-model="historyDeviceFilter.deptId"
|
||
:options="deptOptions"
|
||
:props="deptCascaderProps"
|
||
:placeholder="$t('dataCenter.history.filterDeptPlaceholder')"
|
||
clearable
|
||
filterable
|
||
size="mini"
|
||
:show-all-levels="false"
|
||
class="history-filter-control"
|
||
popper-class="history-dept-cascader-dropdown"
|
||
@visible-change="onHistoryDeptCascaderVisible"
|
||
@change="handleHistoryDeviceFilterChange"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('dataCenter.history.filterGroup')" class="history-device-filter__item">
|
||
<el-select
|
||
v-model="historyDeviceFilter.groupId"
|
||
class="history-filter-control"
|
||
:placeholder="$t('dataCenter.history.filterGroupPlaceholder')"
|
||
clearable
|
||
size="mini"
|
||
@change="handleHistoryDeviceFilterChange"
|
||
>
|
||
<el-option v-for="group in myGroupList" :key="group.groupId" :label="group.groupName" :value="group.groupId" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item class="history-device-filter__item history-device-filter__item--checkbox">
|
||
<el-checkbox v-model="historyDeviceFilter.showChild" @change="handleHistoryDeviceFilterChange">
|
||
{{ $t('device.index.105953-18') }}
|
||
</el-checkbox>
|
||
<el-tooltip :content="$t('device.index.105953-19')" placement="top">
|
||
<i class="el-icon-question history-device-filter__help" />
|
||
</el-tooltip>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<el-tabs v-model="activeTab">
|
||
<el-tab-pane :label="$t('dataCenter.history.384934-0')" name="device">
|
||
<div class="device-wrap">
|
||
<el-form @submit.native.prevent :model="devQueryParams" ref="devQueryForm" size="mini" :inline="true" label-width="68px">
|
||
<el-form-item :label="$t('dataCenter.history.384934-1')" prop="deviceId">
|
||
<el-select style="width: 100%" v-model="devQueryParams.deviceId" :placeholder="$t('dataCenter.history.384934-2')" filterable @change="handleDevDeviceChange">
|
||
<el-option v-for="(item, index) in devDeviceListNonGw" :key="index" :label="item.deviceName" :value="item.deviceId"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item v-if="devSubDeviceList.length > 0" label="子设备" prop="subDeviceId">
|
||
<el-select style="width: 100%" v-model="devQueryParams.subDeviceId" placeholder="请选择子设备" filterable @change="handleDevSubDeviceChange">
|
||
<el-option v-for="item in devSubDeviceList" :key="item.subDeviceId" :label="item.subDeviceName + ' (从机' + item.slaveId + ')'" :value="item.subDeviceId"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('dataCenter.history.384934-3')" prop="identifiers">
|
||
<el-select style="width: 100%" v-model="devQueryParams.identifiers" :placeholder="$t('dataCenter.history.384934-4')" filterable multiple collapse-tags @change="handleDevIdentifierChange">
|
||
<el-option label="全部勾选/反选" value="__selectAll__">
|
||
<span style="font-weight: bold; color: #409EFF;">{{ devIsAllSelected ? '全部反选' : '全部勾选' }}</span>
|
||
</el-option>
|
||
<el-option v-for="(item, index) in devIdentifierList" :key="index" :label="item.modelName" :value="item.identifier"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('dataCenter.history.384934-5')" prop="dayDaterange">
|
||
<el-date-picker
|
||
style="width: 315px"
|
||
v-model="devQueryParams.dayDaterange"
|
||
value-format="yyyy-MM-dd HH:mm:ss"
|
||
type="datetimerange"
|
||
range-separator="-"
|
||
:start-placeholder="$t('dataCenter.history.384934-6')"
|
||
:end-placeholder="$t('dataCenter.history.384934-7')"
|
||
:picker-options="pickerOptions"
|
||
></el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item style="float: right; margin-right: 0">
|
||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleDevQuery">{{ $t('dataCenter.history.384934-8') }}</el-button>
|
||
<el-button icon="el-icon-refresh" size="mini" @click="handleDevResetQuery">{{ $t('dataCenter.history.384934-9') }}</el-button>
|
||
<el-button type="success" icon="el-icon-download" size="mini" :disabled="devDatas.length === 0" @click="handleDevExport" v-loading="exportLoading">{{ $t('dataCenter.history.384934-24') }}</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-row>
|
||
<el-col :span="24">
|
||
<el-card v-loading="loading" shadow="never">
|
||
<div slot="header" style="display: flex; align-items: center; justify-content: space-between;">
|
||
<span>{{ $t('dataCenter.history.384934-10') }}</span>
|
||
<el-switch v-model="independentYAxis" active-text="独立Y轴" inactive-text="共享Y轴" @change="handleYAxisModeChange" />
|
||
</div>
|
||
<div class="el-table--enable-row-hover el-table--medium">
|
||
<div v-show="devDatas.length !== 0" ref="devLineChart" :style="{ width: '100%', height: devChartHeight + 'px', background: '#fff' }"></div>
|
||
<el-empty v-if="devDatas.length === 0" style="height: 480px" :description="$t('dataCenter.history.384934-12')"></el-empty>
|
||
<el-table v-show="devTotal > 0" style="margin-top: 50px" :data="devTableList" :border="false">
|
||
<el-table-column :label="$t('dataCenter.history.384934-13')" prop="time" width="200" />
|
||
<el-table-column v-for="item in this.devTableHeaderTemp" :key="item.value" :label="item.name" :prop="item.value" />
|
||
</el-table>
|
||
<pagination
|
||
style="margin-bottom: 20px"
|
||
v-show="devTotal > 0"
|
||
:autoScroll="false"
|
||
:total="devTotal"
|
||
:page.sync="devPageNum"
|
||
:limit.sync="devPageSize"
|
||
@pagination="getDevTableList"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="网关设备" name="gateway">
|
||
<div class="gateway-wrap">
|
||
<!-- 统一 4 步:1 选网关 2 选物模型 3 选子设备(可选) 4 选时间 -->
|
||
<el-form @submit.native.prevent :model="gwQueryParams" ref="gwQueryForm" size="mini" :inline="true" label-width="90px">
|
||
<el-form-item label="网关设备" prop="gwDeviceId">
|
||
<el-select style="width: 200px" v-model="gwQueryParams.gwDeviceId" placeholder="请选择网关设备" filterable @change="handleGwDeviceChange">
|
||
<el-option v-for="item in gwDeviceList" :key="item.deviceId" :label="item.deviceName" :value="item.deviceId"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="物模型" prop="identifiers">
|
||
<el-select style="width: 320px" v-model="gwQueryParams.identifiers" placeholder="请选择变量(可多选)" filterable multiple collapse-tags @change="handleGwIdentifierChange">
|
||
<el-option label="全部勾选/反选" value="__selectAll__">
|
||
<span style="font-weight: bold; color: #409EFF;">{{ gwGwIsAllSelected ? '全部反选' : '全部勾选' }}</span>
|
||
</el-option>
|
||
<el-option-group label="计算量(网关纯计算,无需选子设备)" v-if="gwVariableListCalcUnique.length">
|
||
<el-option v-for="item in gwVariableListCalcUnique" :key="'calc-' + item.identifier" :label="item.modelName + ' (计算)'" :value="item.identifier"></el-option>
|
||
</el-option-group>
|
||
<el-option-group label="子设备变量" v-if="gwVariableListSubUnique.length">
|
||
<el-option v-for="item in gwVariableListSubUnique" :key="'sub-' + item.identifier" :label="item.modelName" :value="item.identifier"></el-option>
|
||
</el-option-group>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="子设备" prop="subDeviceIds" v-if="gwShowSubDeviceSelect">
|
||
<el-select
|
||
style="width: 280px"
|
||
v-model="gwSubDeviceSelectValue"
|
||
placeholder="不选则全部子设备对比,可多选指定子设备"
|
||
filterable
|
||
multiple
|
||
collapse-tags
|
||
clearable
|
||
>
|
||
<el-option label="全部子设备(多设备对比)" value="__all__"></el-option>
|
||
<el-option v-for="item in gwSubDeviceList" :key="item.subDeviceId" :label="item.subDeviceName + ' (从机' + item.slaveId + ')'" :value="item.subDeviceId"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="时间范围" prop="dayDaterange">
|
||
<el-date-picker
|
||
style="width: 315px"
|
||
v-model="gwQueryParams.dayDaterange"
|
||
value-format="yyyy-MM-dd HH:mm:ss"
|
||
type="datetimerange"
|
||
range-separator="-"
|
||
start-placeholder="开始时间"
|
||
end-placeholder="结束时间"
|
||
:picker-options="pickerOptions"
|
||
></el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleGwQuery">查询</el-button>
|
||
<el-button icon="el-icon-refresh" size="mini" @click="handleGwReset">重置</el-button>
|
||
<el-button type="success" icon="el-icon-download" size="mini" :disabled="!gwHasResult" @click="handleGwExport" v-loading="exportLoading">导出Excel</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-card v-loading="loading" shadow="never">
|
||
<div slot="header" style="display: flex; align-items: center; justify-content: space-between;">
|
||
<span>{{ gwResultTitle }}</span>
|
||
<el-switch v-model="independentYAxis" active-text="独立Y轴" inactive-text="共享Y轴" @change="handleYAxisModeChange" />
|
||
</div>
|
||
<!-- 计算量结果 -->
|
||
<template v-if="gwResultMode === 'calc'">
|
||
<div v-show="gwCalcDatas.length !== 0" ref="gwCalcLineChart" :style="{ width: '100%', height: gwCalcChartHeight + 'px' }"></div>
|
||
<el-empty v-if="gwCalcDatas.length === 0 && !loading" style="height: 480px" description="请选择网关、物模型(计算量)和时间范围后查询"></el-empty>
|
||
<el-table v-show="gwCalcTotal > 0" style="margin-top: 20px" :data="gwCalcTableList" border>
|
||
<el-table-column label="更新时间" prop="time" width="180" />
|
||
<el-table-column v-for="item in gwCalcTableHeaderTemp" :key="item.value" :label="item.name" :prop="item.value" />
|
||
</el-table>
|
||
<pagination v-show="gwCalcTotal > 0" :total="gwCalcTotal" :page.sync="gwCalcPageNum" :limit.sync="gwCalcPageSize" @pagination="getGwCalcTableList" />
|
||
</template>
|
||
<!-- 多设备对比结果 -->
|
||
<template v-else-if="gwResultMode === 'compare'">
|
||
<div v-show="subCompareDatas.length !== 0" ref="subCompareLineChart" :style="{ width: '100%', height: subCompareChartHeight + 'px' }"></div>
|
||
<el-empty v-if="subCompareDatas.length === 0 && !loading" style="height: 480px" description="请选择网关、物模型(子设备变量)和时间范围,子设备可不选(多设备对比)"></el-empty>
|
||
<el-table v-show="subCompareTotal > 0" style="margin-top: 20px" :data="subCompareTableList" border>
|
||
<el-table-column v-for="col in subCompareTableColumns" :key="col.prop" :label="col.label" :prop="col.prop" :min-width="col.prop === 'time' ? 180 : 100" />
|
||
</el-table>
|
||
<pagination v-show="subCompareTotal > 0" :total="subCompareTotal" :page.sync="subComparePageNum" :limit.sync="subComparePageSize" @pagination="getSubCompareTableList" />
|
||
</template>
|
||
<!-- 单子设备历史结果 -->
|
||
<template v-else-if="gwResultMode === 'single'">
|
||
<div v-show="gwSingleDatas.length !== 0" ref="gwSingleLineChart" :style="{ width: '100%', height: gwSingleChartHeight + 'px' }"></div>
|
||
<el-empty v-if="gwSingleDatas.length === 0 && !loading" style="height: 480px" description="请选择网关、物模型、子设备和时间范围后查询"></el-empty>
|
||
<el-table v-show="gwSingleTotal > 0" style="margin-top: 20px" :data="gwSingleTableList" border>
|
||
<el-table-column label="更新时间" prop="time" width="180" />
|
||
<el-table-column v-for="item in gwSingleTableHeaderTemp" :key="item.value" :label="item.name" :prop="item.value" />
|
||
</el-table>
|
||
<pagination v-show="gwSingleTotal > 0" :total="gwSingleTotal" :page.sync="gwSinglePageNum" :limit.sync="gwSinglePageSize" @pagination="getGwSingleTableList" />
|
||
</template>
|
||
<el-empty v-else style="height: 480px" description="请按顺序选择:1.网关设备 2.物模型 3.子设备(可选) 4.时间范围,然后查询"></el-empty>
|
||
</el-card>
|
||
</div>
|
||
</el-tab-pane>
|
||
<el-tab-pane :label="$t('dataCenter.history.384934-14')" name="scene">
|
||
<div class="scene-wrap">
|
||
<el-form @submit.native.prevent :model="sceneQueryParams" ref="sceneQueryForm" size="mini" :inline="true" label-width="68px">
|
||
<el-form-item :label="$t('dataCenter.history.384934-15')" prop="sceneModelId">
|
||
<el-select style="width: 100%" v-model="sceneQueryParams.sceneModelId" :placeholder="$t('dataCenter.history.384934-16')" @change="handleSceneModelChange">
|
||
<el-option v-for="(item, index) in sceneModelList" :key="index" :label="item.sceneModelName" :value="item.sceneModelId"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('dataCenter.history.384934-17')" prop="sceneModelDeviceId">
|
||
<el-select style="width: 100%" v-model="sceneQueryParams.sceneModelDeviceId" :placeholder="$t('dataCenter.history.384934-18')" @change="handleSceneDeviceChange">
|
||
<el-option v-for="(item, index) in sceneDeviceList" :key="index" :label="item.name" :value="item.id"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('dataCenter.history.384934-3')" prop="identifiers">
|
||
<el-select style="width: 100%" v-model="sceneQueryParams.identifiers" :placeholder="$t('dataCenter.history.384934-4')" multiple collapse-tags @change="handleSceneIdentifierChange">
|
||
<el-option label="全部勾选/反选" value="__selectAll__">
|
||
<span style="font-weight: bold; color: #409EFF;">{{ sceneIsAllSelected ? '全部反选' : '全部勾选' }}</span>
|
||
</el-option>
|
||
<el-option v-for="(item, index) in sceneIdentifierList" :key="index" :label="item.sourceName" :value="item.identifier"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<template v-if="sceneSearchShow">
|
||
<el-form-item :label="$t('dataCenter.history.384934-5')" prop="dayDaterange">
|
||
<el-date-picker
|
||
style="width: 315px"
|
||
v-model="sceneQueryParams.dayDaterange"
|
||
value-format="yyyy-MM-dd HH:mm:ss"
|
||
type="datetimerange"
|
||
range-separator="-"
|
||
:start-placeholder="$t('dataCenter.history.384934-6')"
|
||
:end-placeholder="$t('dataCenter.history.384934-7')"
|
||
:picker-options="pickerOptions"
|
||
></el-date-picker>
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<el-form-item style="float: right; margin-right: 0">
|
||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleSceneQuery">{{ $t('dataCenter.history.384934-8') }}</el-button>
|
||
<el-button icon="el-icon-refresh" size="mini" @click="handleSceneResetQuery">{{ $t('dataCenter.history.384934-9') }}</el-button>
|
||
<el-button type="text" @click="handleSceneSearchChange">
|
||
{{ sceneSearchShow ? $t('template.index.891112-113') : $t('template.index.891112-112') }}
|
||
<i :class="{ 'el-icon-arrow-down': !sceneSearchShow, 'el-icon-arrow-up': sceneSearchShow }"></i>
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-row>
|
||
<el-col :span="24">
|
||
<el-card v-loading="loading" shadow="never">
|
||
<div slot="header" style="display: flex; align-items: center; justify-content: space-between;">
|
||
<span>{{ $t('dataCenter.history.384934-10') }}</span>
|
||
<el-switch v-model="independentYAxis" active-text="独立Y轴" inactive-text="共享Y轴" @change="handleYAxisModeChange" />
|
||
</div>
|
||
<div class="el-table--enable-row-hover el-table--medium">
|
||
<div v-show="sceneDatas.length !== 0" ref="sceneLineChart" :style="{ width: '100%', height: sceneChartHeight + 'px', background: '#fff' }"></div>
|
||
<el-empty v-if="sceneDatas.length === 0" style="height: 480px" :description="$t('dataCenter.history.384934-12')"></el-empty>
|
||
<el-table v-show="sceneTotal > 0" style="margin-top: 50px" :data="sceneTableList" :border="false">
|
||
<el-table-column :label="$t('dataCenter.history.384934-13')" prop="time" width="200" />
|
||
<el-table-column v-for="item in this.sceneTableHeaderTemp" :key="item.value" :label="item.name" :prop="item.value" />
|
||
</el-table>
|
||
<pagination
|
||
style="margin-bottom: 20px"
|
||
v-show="sceneTotal > 0"
|
||
:autoScroll="false"
|
||
:total="sceneTotal"
|
||
:page.sync="scenePageNum"
|
||
:limit.sync="scenePageSize"
|
||
@pagination="getSceneTableList"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import moment from 'moment';
|
||
import { listDeviceShort, listThingsModel } from '@/api/iot/device.js';
|
||
import { listGroup } from '@/api/iot/group';
|
||
import { deptsTreeSelect } from '@/api/system/user';
|
||
import { listGateway } from '@/api/iot/gateway.js';
|
||
import { getSceneModelList, getSceneModelDetail, getSceneModelDataList } from '@/api/scene/list.js';
|
||
import {
|
||
getDataCenterDeviceHistory,
|
||
getDataCenterSceneHistory,
|
||
exportDeviceHistory,
|
||
getGatewayCalculatedModels,
|
||
getDeviceHistoryCalculated,
|
||
exportDeviceHistoryCalculated,
|
||
getSubDeviceComparativeHistory,
|
||
exportSubDeviceComparativeHistory,
|
||
} from '@/api/data/center.js';
|
||
import { saveAs } from 'file-saver';
|
||
import { blobValidate } from '@/utils/ruoyi';
|
||
|
||
export default {
|
||
name: 'dataCenterHistory',
|
||
data() {
|
||
return {
|
||
activeTab: 'device',
|
||
/** 与 /system/user/deptTree 返回的 TreeSelect(id/label/children)一致,任意层级可选 */
|
||
deptCascaderProps: {
|
||
value: 'id',
|
||
label: 'label',
|
||
children: 'children',
|
||
checkStrictly: true,
|
||
emitPath: false,
|
||
},
|
||
/** 设备/网关下拉共用:机构、分组、含下级(与设备管理 shortList 参数一致) */
|
||
historyDeviceFilter: {
|
||
deptId: null,
|
||
showChild: true,
|
||
groupId: null,
|
||
},
|
||
deptOptions: [],
|
||
myGroupList: [],
|
||
devDeviceList: [], // 设备-设备列表
|
||
devSubDeviceList: [], // 网关子设备列表
|
||
devIdentifierList: [],
|
||
pickerOptions: {
|
||
shortcuts: [
|
||
{
|
||
text: this.$t('dataCenter.history.384934-19'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 2);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-26'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 4);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-27'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 8);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-28'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 12);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-20'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.analysis.384934-25'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 2);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-29'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-30'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 5);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-21'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-31'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 15);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
{
|
||
text: this.$t('dataCenter.history.384934-22'),
|
||
onClick(picker) {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||
picker.$emit('pick', [start, end]);
|
||
},
|
||
},
|
||
],
|
||
},
|
||
// 查询参数
|
||
devQueryParams: {
|
||
deviceId: null,
|
||
subDeviceId: null,
|
||
identifiers: [],
|
||
dayDaterange: [new Date(new Date().getTime() - 3600 * 1000 * 2), new Date()],
|
||
},
|
||
loading: false,
|
||
devDatas: [], // 设备列表数据
|
||
devTableComTemp: [], // 设备列表table数据
|
||
devTableHeaderTemp: [], // 设备列表table数据
|
||
devPageNum: 1,
|
||
devPageSize: 10,
|
||
devTotal: 0,
|
||
sceneSearchShow: false, //搜索展开
|
||
sceneModelList: [], // 场景列表
|
||
sceneDeviceList: [],
|
||
sceneIdentifierList: [], // 场景变量
|
||
sceneQueryParams: {
|
||
sceneModelId: null,
|
||
sceneModelDeviceId: null,
|
||
identifiers: [],
|
||
dayDaterange: [new Date(new Date().getTime() - 3600 * 1000 * 2), new Date()],
|
||
},
|
||
sceneDatas: [], // 场景列表数据
|
||
sceneTableComTemp: [], // 设备列表table数据
|
||
sceneTableHeaderTemp: [], // 设备列表table数据
|
||
scenePageNum: 1,
|
||
scenePageSize: 10,
|
||
sceneTotal: 0,
|
||
exportLoading: false,
|
||
// 网关设备 tab 统一 4 步(物模型支持多选)
|
||
gwQueryParams: {
|
||
gwDeviceId: null,
|
||
identifiers: [],
|
||
/** 子设备:null=全部,[id]=单设备,[id1,id2,...]=多设备对比指定 */
|
||
subDeviceIds: null,
|
||
dayDaterange: [new Date(Date.now() - 3600 * 1000 * 2), new Date()],
|
||
},
|
||
gwSubDeviceList: [],
|
||
gwCalcIdentifierList: [],
|
||
subCompareIdentifierList: [],
|
||
gwResultMode: '', // 'calc' | 'compare' | 'single'
|
||
// 计算量结果(复用原结构)
|
||
gwCalcDatas: [],
|
||
gwCalcTableHeaderTemp: [],
|
||
gwCalcTableComTemp: [],
|
||
gwCalcTotal: 0,
|
||
gwCalcPageNum: 1,
|
||
gwCalcPageSize: 10,
|
||
// 多设备对比结果
|
||
subCompareDatas: [],
|
||
subCompareTableColumns: [],
|
||
subCompareTableComTemp: [],
|
||
subCompareTotal: 0,
|
||
subComparePageNum: 1,
|
||
subComparePageSize: 10,
|
||
// 单子设备历史结果
|
||
gwSingleDatas: [],
|
||
gwSingleTableHeaderTemp: [],
|
||
gwSingleTableComTemp: [],
|
||
gwSingleTotal: 0,
|
||
gwSinglePageNum: 1,
|
||
gwSinglePageSize: 10,
|
||
independentYAxis: true,
|
||
devChartHeight: 480,
|
||
gwCalcChartHeight: 480,
|
||
gwSingleChartHeight: 480,
|
||
subCompareChartHeight: 480,
|
||
sceneChartHeight: 480,
|
||
};
|
||
},
|
||
beforeDestroy() {
|
||
this.teardownHistoryDeptCascaderLabelClick();
|
||
},
|
||
mounted() {
|
||
this.getDeptTree();
|
||
this.getGroupList();
|
||
this.getDevDeviceList();
|
||
this.getSceneModelListDatas();
|
||
const { activeName, deviceId, identifier, sceneModelId, sceneModelDeviceId } = this.$route.query;
|
||
if (activeName) {
|
||
this.activeTab = activeName || 'device';
|
||
// 设置默认值
|
||
if (activeName === 'device') {
|
||
this.devQueryParams.deviceId = Number(deviceId);
|
||
this.getDevIdentifierList(Number(deviceId));
|
||
this.devQueryParams.identifiers = [identifier];
|
||
setTimeout(() => {
|
||
this.handleDevQuery();
|
||
}, 500);
|
||
} else {
|
||
this.sceneQueryParams.sceneModelId = Number(sceneModelId);
|
||
this.getSceneModelDetailDatas(Number(sceneModelId));
|
||
this.sceneQueryParams.sceneModelDeviceId = Number(sceneModelDeviceId);
|
||
this.getSceneIdentifierList(Number(sceneModelDeviceId));
|
||
this.sceneQueryParams.identifiers = [identifier];
|
||
setTimeout(() => {
|
||
this.handleSceneQuery();
|
||
}, 500);
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
devTableList() {
|
||
const start = (this.devPageNum - 1) * this.devPageSize;
|
||
const end = start + this.devPageSize;
|
||
return this.devTableComTemp.slice(start, end);
|
||
},
|
||
sceneTableList() {
|
||
const start = (this.scenePageNum - 1) * this.scenePageSize;
|
||
const end = start + this.scenePageSize;
|
||
return this.sceneTableComTemp.slice(start, end);
|
||
},
|
||
// 判断设备变量是否全部选中
|
||
devIsAllSelected() {
|
||
if (this.devIdentifierList.length === 0) return false;
|
||
const allIdentifiers = this.devIdentifierList.map(item => item.identifier);
|
||
return allIdentifiers.length > 0 &&
|
||
allIdentifiers.every(id => this.devQueryParams.identifiers.includes(id));
|
||
},
|
||
// 判断场景变量是否全部选中
|
||
sceneIsAllSelected() {
|
||
if (this.sceneIdentifierList.length === 0) return false;
|
||
const allIdentifiers = this.sceneIdentifierList.map(item => item.identifier);
|
||
return allIdentifiers.length > 0 &&
|
||
allIdentifiers.every(id => this.sceneQueryParams.identifiers.includes(id));
|
||
},
|
||
/** 设备 tab 仅展示非网关设备 */
|
||
devDeviceListNonGw() {
|
||
return (this.devDeviceList || []).filter((d) => d.deviceType !== 2 && d.deviceType !== 4);
|
||
},
|
||
/** 网关设备 tab 仅展示网关设备 */
|
||
gwDeviceList() {
|
||
return (this.devDeviceList || []).filter((d) => d.deviceType === 2);
|
||
},
|
||
gwCalcTableList() {
|
||
const start = (this.gwCalcPageNum - 1) * this.gwCalcPageSize;
|
||
return (this.gwCalcTableComTemp || []).slice(start, start + this.gwCalcPageSize);
|
||
},
|
||
subCompareTableList() {
|
||
const start = (this.subComparePageNum - 1) * this.subComparePageSize;
|
||
return (this.subCompareTableComTemp || []).slice(start, start + this.subComparePageSize);
|
||
},
|
||
// 网关统一:计算量变量列表(用于下拉分组)
|
||
gwVariableListCalc() {
|
||
return this.gwCalcIdentifierList || [];
|
||
},
|
||
// 计算量按 identifier 去重,避免重复 key
|
||
gwVariableListCalcUnique() {
|
||
const list = this.gwCalcIdentifierList || [];
|
||
const seen = new Set();
|
||
return list.filter((i) => {
|
||
if (seen.has(i.identifier)) return false;
|
||
seen.add(i.identifier);
|
||
return true;
|
||
});
|
||
},
|
||
// 网关统一:子设备变量列表
|
||
gwVariableListSub() {
|
||
return this.subCompareIdentifierList || [];
|
||
},
|
||
// 子设备变量按 identifier 去重,且排除计算量(跨设备计算量不展示在子设备变量中)
|
||
gwVariableListSubUnique() {
|
||
const list = this.subCompareIdentifierList || [];
|
||
const calcSet = new Set((this.gwCalcIdentifierList || []).map((i) => i.identifier));
|
||
const seen = new Set();
|
||
return list.filter((i) => {
|
||
if (seen.has(i.identifier) || calcSet.has(i.identifier)) return false;
|
||
seen.add(i.identifier);
|
||
return true;
|
||
});
|
||
},
|
||
// 当前选中的物模型是否全部为计算量(计算量时不展示子设备选择)
|
||
gwSelectedIsCalculated() {
|
||
const ids = this.gwQueryParams.identifiers || [];
|
||
if (ids.length === 0) return false;
|
||
const calcSet = new Set((this.gwCalcIdentifierList || []).map((i) => i.identifier));
|
||
return ids.every((id) => calcSet.has(id));
|
||
},
|
||
// 选中的计算量 identifier 列表
|
||
gwSelectedCalcIdentifiers() {
|
||
const ids = this.gwQueryParams.identifiers || [];
|
||
const calcSet = new Set((this.gwCalcIdentifierList || []).map((i) => i.identifier));
|
||
return ids.filter((id) => calcSet.has(id));
|
||
},
|
||
// 选中的子设备变量 identifier 列表(排除同时属于计算量的,避免仅选计算量时报混合选择)
|
||
gwSelectedSubIdentifiers() {
|
||
const ids = this.gwQueryParams.identifiers || [];
|
||
const subSet = new Set((this.subCompareIdentifierList || []).map((i) => i.identifier));
|
||
const calcSet = new Set((this.gwCalcIdentifierList || []).map((i) => i.identifier));
|
||
return ids.filter((id) => subSet.has(id) && !calcSet.has(id));
|
||
},
|
||
// 网关物模型“全部勾选”仅针对子设备变量(不包含计算量)
|
||
gwGwIsAllSelected() {
|
||
const ids = this.gwQueryParams.identifiers || [];
|
||
const allSub = this.gwVariableListSubUnique.map((i) => i.identifier);
|
||
return allSub.length > 0 && allSub.every((id) => ids.includes(id));
|
||
},
|
||
gwShowSubDeviceSelect() {
|
||
return (this.gwQueryParams.identifiers || []).length > 0 && !this.gwSelectedIsCalculated;
|
||
},
|
||
/** 子设备多选绑定:null 显示为“全部”,选具体设备时去掉“全部” */
|
||
gwSubDeviceSelectValue: {
|
||
get() {
|
||
const ids = this.gwQueryParams.subDeviceIds;
|
||
return ids === null || (Array.isArray(ids) && ids.length === 0) ? ['__all__'] : ids;
|
||
},
|
||
set(val) {
|
||
const ids = (val || []).filter((v) => v !== '__all__');
|
||
this.gwQueryParams.subDeviceIds = ids.length === 0 ? null : ids;
|
||
},
|
||
},
|
||
gwResultTitle() {
|
||
if (this.gwResultMode === 'calc') return '曲线趋势图(计算量)';
|
||
if (this.gwResultMode === 'compare') return '多设备同一变量对比(仅显示有数据的时间点)';
|
||
if (this.gwResultMode === 'single') return '单子设备历史';
|
||
return '曲线趋势图';
|
||
},
|
||
gwHasResult() {
|
||
if (this.gwResultMode === 'calc') return this.gwCalcDatas.length > 0;
|
||
if (this.gwResultMode === 'compare') return this.subCompareDatas.length > 0;
|
||
if (this.gwResultMode === 'single') return this.gwSingleDatas.length > 0;
|
||
return false;
|
||
},
|
||
gwSingleTableList() {
|
||
const start = (this.gwSinglePageNum - 1) * this.gwSinglePageSize;
|
||
return (this.gwSingleTableComTemp || []).slice(start, start + this.gwSinglePageSize);
|
||
},
|
||
},
|
||
methods: {
|
||
getDeptTree() {
|
||
deptsTreeSelect().then((response) => {
|
||
this.deptOptions = response.data || [];
|
||
});
|
||
},
|
||
getGroupList() {
|
||
listGroup({
|
||
pageSize: 30,
|
||
pageNum: 1,
|
||
userId: this.$store.state.user.userId,
|
||
}).then((response) => {
|
||
this.myGroupList = response.rows || [];
|
||
});
|
||
},
|
||
handleHistoryDeviceFilterChange() {
|
||
this.getDevDeviceList();
|
||
},
|
||
/**
|
||
* Element UI Cascader 在 checkStrictly 下仅 radio 会触发选中,行点击会走展开。
|
||
* 在面板捕获阶段对「文字区域」模拟 radio 点击,右侧展开箭头仍走原逻辑。
|
||
*/
|
||
onHistoryDeptCascaderVisible(visible) {
|
||
if (!visible) {
|
||
this.teardownHistoryDeptCascaderLabelClick();
|
||
return;
|
||
}
|
||
this.$nextTick(() => {
|
||
setTimeout(() => this.setupHistoryDeptCascaderLabelClick(), 0);
|
||
});
|
||
},
|
||
setupHistoryDeptCascaderLabelClick() {
|
||
this.teardownHistoryDeptCascaderLabelClick();
|
||
const drop = document.querySelector('.history-dept-cascader-dropdown');
|
||
const panel = drop && drop.querySelector('.el-cascader-panel');
|
||
if (!panel) return;
|
||
this._historyDeptCascPanel = panel;
|
||
this._historyDeptCascPanelHandler = (e) => {
|
||
const li = e.target.closest('.el-cascader-node.is-selectable');
|
||
if (!li || !panel.contains(li)) return;
|
||
if (e.target.closest('.el-cascader-node__postfix')) return;
|
||
if (e.target.closest('.el-radio')) return;
|
||
const radio = li.querySelector('.el-radio__original');
|
||
if (radio && !radio.disabled) {
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
radio.click();
|
||
}
|
||
};
|
||
panel.addEventListener('click', this._historyDeptCascPanelHandler, true);
|
||
},
|
||
teardownHistoryDeptCascaderLabelClick() {
|
||
if (this._historyDeptCascPanel && this._historyDeptCascPanelHandler) {
|
||
this._historyDeptCascPanel.removeEventListener('click', this._historyDeptCascPanelHandler, true);
|
||
}
|
||
this._historyDeptCascPanel = null;
|
||
this._historyDeptCascPanelHandler = null;
|
||
},
|
||
/** 筛选后若当前选中设备不在列表中,清空下游状态 */
|
||
clearStaleSelectionsAfterDeviceListLoad() {
|
||
const ids = new Set((this.devDeviceList || []).map((d) => d.deviceId));
|
||
if (this.devQueryParams.deviceId && !ids.has(this.devQueryParams.deviceId)) {
|
||
this.devQueryParams.deviceId = null;
|
||
this.devQueryParams.subDeviceId = null;
|
||
this.devSubDeviceList = [];
|
||
this.devIdentifierList = [];
|
||
this.devQueryParams.identifiers = [];
|
||
this.devDatas = [];
|
||
this.devTableComTemp = [];
|
||
this.devTotal = 0;
|
||
}
|
||
if (this.gwQueryParams.gwDeviceId && !ids.has(this.gwQueryParams.gwDeviceId)) {
|
||
this.handleGwReset();
|
||
}
|
||
},
|
||
// 获取设备列表
|
||
getDevDeviceList() {
|
||
const params = {
|
||
showChild: this.historyDeviceFilter.showChild,
|
||
entrance: 2,
|
||
pageNum: 1,
|
||
pageSize: 9999,
|
||
};
|
||
if (this.historyDeviceFilter.deptId != null && this.historyDeviceFilter.deptId !== undefined) {
|
||
params.deptId = this.historyDeviceFilter.deptId;
|
||
}
|
||
if (this.historyDeviceFilter.groupId) {
|
||
params.groupId = this.historyDeviceFilter.groupId;
|
||
}
|
||
listDeviceShort(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.devDeviceList = res.rows;
|
||
this.$nextTick(() => this.clearStaleSelectionsAfterDeviceListLoad());
|
||
}
|
||
});
|
||
},
|
||
// 选择设备后(设备 tab 仅非网关,直接拉取物模型)
|
||
handleDevDeviceChange(val) {
|
||
this.devQueryParams.identifiers = [];
|
||
this.devQueryParams.subDeviceId = null;
|
||
this.devSubDeviceList = [];
|
||
this.devIdentifierList = [];
|
||
if (val) this.getDevIdentifierList(val);
|
||
},
|
||
// 选择子设备后
|
||
handleDevSubDeviceChange(val) {
|
||
this.devQueryParams.identifiers = [];
|
||
this.devIdentifierList = [];
|
||
if (val) {
|
||
this.getDevIdentifierList(val);
|
||
}
|
||
},
|
||
// 获取网关子设备列表
|
||
getDevSubDeviceList(gwDeviceId) {
|
||
listGateway({ gwDeviceId: gwDeviceId, pageNum: 1, pageSize: 9999 }).then((res) => {
|
||
if (res.code === 200) {
|
||
this.devSubDeviceList = res.rows || [];
|
||
if (this.devSubDeviceList.length === 0) {
|
||
this.getDevIdentifierList(gwDeviceId);
|
||
}
|
||
}
|
||
});
|
||
},
|
||
// 设备变量选择变化
|
||
handleDevIdentifierChange(val) {
|
||
// 如果选择了"全部勾选",则切换全选/全不选
|
||
const index = val.indexOf('__selectAll__');
|
||
if (index > -1) {
|
||
const allIdentifiers = this.devIdentifierList.map(item => item.identifier);
|
||
// 判断当前是否已经全部选中(基于当前值,不是新值)
|
||
const isAllSelected = allIdentifiers.length > 0 &&
|
||
allIdentifiers.every(id => this.devQueryParams.identifiers.includes(id));
|
||
|
||
this.$nextTick(() => {
|
||
if (isAllSelected) {
|
||
// 如果已经全部选中,则清空选择(反选)
|
||
this.devQueryParams.identifiers = [];
|
||
} else {
|
||
// 如果没有全部选中,则选中所有(勾选)
|
||
this.devQueryParams.identifiers = [...allIdentifiers];
|
||
}
|
||
});
|
||
}
|
||
},
|
||
getDevIdentifierList(deviceId) {
|
||
const params = {
|
||
deviceId: deviceId,
|
||
pageNum: 1,
|
||
pageSize: 9999,
|
||
};
|
||
listThingsModel(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.devIdentifierList = res.rows || [];
|
||
}
|
||
});
|
||
},
|
||
// 获取实际查询用的设备信息(子设备优先)
|
||
getQueryDevice() {
|
||
const device = this.devDeviceList.find((item) => item.deviceId === this.devQueryParams.deviceId);
|
||
if (!device) return null;
|
||
if (this.devSubDeviceList.length > 0 && this.devQueryParams.subDeviceId) {
|
||
const subDevice = this.devSubDeviceList.find((item) => item.subDeviceId === this.devQueryParams.subDeviceId);
|
||
if (subDevice) {
|
||
return { deviceId: subDevice.subDeviceId, serialNumber: subDevice.subDeviceNo };
|
||
}
|
||
}
|
||
return { deviceId: device.deviceId, serialNumber: device.serialNumber };
|
||
},
|
||
// 获取设备图形数据
|
||
getDevChartDatas() {
|
||
this.loading = true;
|
||
const queryDevice = this.getQueryDevice();
|
||
const identifierList = this.devQueryParams.identifiers.map((item) => {
|
||
const identifiers = this.devIdentifierList.find((chil) => chil.identifier === item);
|
||
return { identifier: identifiers.identifier, type: identifiers.type };
|
||
});
|
||
const params = {
|
||
deviceId: queryDevice.deviceId,
|
||
serialNumber: queryDevice.serialNumber,
|
||
identifierList: identifierList,
|
||
beginTime: moment(this.devQueryParams.dayDaterange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||
endTime: moment(this.devQueryParams.dayDaterange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||
};
|
||
getDataCenterDeviceHistory(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.devDatas = res.data;
|
||
this.formatDevTableDatas();
|
||
if (this.devDatas.length !== 0) {
|
||
setTimeout(() => {
|
||
this.drawDevLine();
|
||
}, 500);
|
||
}
|
||
}
|
||
this.loading = false;
|
||
});
|
||
},
|
||
// 搜索按钮操作
|
||
handleDevQuery() {
|
||
if (!this.devQueryParams.deviceId || this.devQueryParams.identifiers.length === 0 || !this.devQueryParams.dayDaterange || this.devQueryParams.dayDaterange.length === 0) {
|
||
return;
|
||
}
|
||
if (this.devSubDeviceList.length > 0 && !this.devQueryParams.subDeviceId) {
|
||
return;
|
||
}
|
||
this.getDevChartDatas();
|
||
},
|
||
// 重置按钮操作
|
||
handleDevResetQuery() {
|
||
this.resetForm('devQueryForm');
|
||
this.devQueryParams.identifiers = [];
|
||
this.devQueryParams.subDeviceId = null;
|
||
this.devSubDeviceList = [];
|
||
this.devIdentifierList = [];
|
||
this.devDatas = [];
|
||
this.devTableComTemp = [];
|
||
this.devTotal = 0;
|
||
},
|
||
// 获取设备列表数据
|
||
getDevTableList(e) {
|
||
this.devPageNum = e.page;
|
||
this.devPageSize = e.limit;
|
||
},
|
||
// 搜索展开隐藏
|
||
handleSceneSearchChange() {
|
||
this.sceneSearchShow = !this.sceneSearchShow;
|
||
},
|
||
// 获取场景列表
|
||
getSceneModelListDatas() {
|
||
const params = {
|
||
pageNum: 1,
|
||
pageSize: 9999,
|
||
};
|
||
getSceneModelList(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.sceneModelList = res.rows;
|
||
}
|
||
});
|
||
},
|
||
// 场景选择后
|
||
handleSceneModelChange(val) {
|
||
this.sceneQueryParams.sceneModelDeviceId = null;
|
||
this.sceneQueryParams.identifiers = [];
|
||
this.getSceneModelDetailDatas(val);
|
||
},
|
||
getSceneModelDetailDatas(sceneModelId) {
|
||
getSceneModelDetail(sceneModelId).then((res) => {
|
||
if (res.code === 200) {
|
||
this.sceneDeviceList = res.data.sceneModelDeviceList;
|
||
}
|
||
});
|
||
},
|
||
// 场景-设备选择后
|
||
handleSceneDeviceChange(val) {
|
||
this.sceneQueryParams.identifiers = [];
|
||
this.getSceneIdentifierList(val);
|
||
},
|
||
// 场景变量选择变化
|
||
handleSceneIdentifierChange(val) {
|
||
// 如果选择了"全部勾选",则切换全选/全不选
|
||
const index = val.indexOf('__selectAll__');
|
||
if (index > -1) {
|
||
const allIdentifiers = this.sceneIdentifierList.map(item => item.identifier);
|
||
// 判断当前是否已经全部选中(基于当前值,不是新值)
|
||
const isAllSelected = allIdentifiers.length > 0 &&
|
||
allIdentifiers.every(id => this.sceneQueryParams.identifiers.includes(id));
|
||
|
||
this.$nextTick(() => {
|
||
if (isAllSelected) {
|
||
// 如果已经全部选中,则清空选择(反选)
|
||
this.sceneQueryParams.identifiers = [];
|
||
} else {
|
||
// 如果没有全部选中,则选中所有(勾选)
|
||
this.sceneQueryParams.identifiers = [...allIdentifiers];
|
||
}
|
||
});
|
||
}
|
||
},
|
||
// 获取变量情况列表
|
||
getSceneIdentifierList(sceneModelDeviceId) {
|
||
const params = {
|
||
sceneModelId: this.sceneQueryParams.sceneModelId,
|
||
sceneModelDeviceId: sceneModelDeviceId,
|
||
pageNum: 1,
|
||
pageSize: 9999,
|
||
};
|
||
getSceneModelDataList(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.sceneIdentifierList = res.rows;
|
||
}
|
||
});
|
||
},
|
||
// 获取设备图形数据
|
||
getSceneChartDatas() {
|
||
this.loading = true;
|
||
const variableType = this.sceneDeviceList.find((item) => item.id === this.sceneQueryParams.sceneModelDeviceId).variableType;
|
||
const ids = this.sceneQueryParams.identifiers.map((item) => this.sceneIdentifierList.find((chil) => chil.identifier === item).id).join(',');
|
||
const params = {
|
||
sceneModelId: this.sceneQueryParams.sceneModelId,
|
||
sceneModelDeviceId: this.sceneQueryParams.sceneModelDeviceId,
|
||
variableType: variableType,
|
||
ids: ids,
|
||
beginTime: moment(this.sceneQueryParams.dayDaterange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||
endTime: moment(this.sceneQueryParams.dayDaterange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||
};
|
||
getDataCenterSceneHistory(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.sceneDatas = res.data;
|
||
this.formatSceneTableDatas();
|
||
if (this.sceneDatas.length !== 0) {
|
||
setTimeout(() => {
|
||
this.drawSceneLine();
|
||
}, 500);
|
||
}
|
||
}
|
||
this.loading = false;
|
||
});
|
||
},
|
||
// 搜索按钮操作
|
||
handleSceneQuery() {
|
||
const isField = this.areAllFields(this.sceneQueryParams);
|
||
if (isField) {
|
||
this.getSceneChartDatas();
|
||
}
|
||
},
|
||
// 重置按钮操作
|
||
handleSceneResetQuery() {
|
||
this.resetForm('sceneQueryForm');
|
||
this.sceneQueryParams.identifiers = [];
|
||
this.sceneDatas = [];
|
||
this.sceneTableComTemp = [];
|
||
this.sceneTotal = 0;
|
||
this.handleSceneQuery();
|
||
},
|
||
// 获取场景列表数据
|
||
getSceneTableList(e) {
|
||
this.scenePageNum = e.page;
|
||
this.scenePageSize = e.limit;
|
||
},
|
||
// 判断对象是否都有值
|
||
areAllFields(obj) {
|
||
for (const key in obj) {
|
||
if (obj.hasOwnProperty(key)) {
|
||
if (!obj[key] || obj[key] === '' || obj[key].length === 0) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
calcChartHeight(seriesCount) {
|
||
return Math.min(1200, Math.max(480, seriesCount * 14 + 360));
|
||
},
|
||
applyIndependentScale(series) {
|
||
series.forEach((s) => {
|
||
const nums = (s.data || []).filter(v => v != null && v !== '').map(Number).filter(n => !isNaN(n));
|
||
if (nums.length === 0) return;
|
||
const min = Math.min(...nums);
|
||
const max = Math.max(...nums);
|
||
const range = max - min;
|
||
s.data = s.data.map(v => {
|
||
if (v == null || v === '') return null;
|
||
const num = Number(v);
|
||
if (isNaN(num)) return null;
|
||
// 映射到 -100 ~ 100,range=0 时固定为 0(居中水平线)
|
||
return {
|
||
value: range === 0 ? 0 : ((num - min) / range) * 200 - 100,
|
||
_original: v,
|
||
};
|
||
});
|
||
});
|
||
return {
|
||
type: 'value',
|
||
min: -100,
|
||
max: 100,
|
||
splitNumber: 4,
|
||
axisLine: { show: true },
|
||
axisTick: { show: true },
|
||
axisLabel: {
|
||
formatter: (val) => val + '%',
|
||
},
|
||
splitLine: {
|
||
show: true,
|
||
lineStyle: { type: 'dashed', opacity: 0.5 },
|
||
},
|
||
};
|
||
},
|
||
independentScaleTooltip() {
|
||
return {
|
||
trigger: 'axis',
|
||
formatter: (params) => {
|
||
if (!params || !params.length) return '';
|
||
let html = '<div style="font-size:12px">' + params[0].axisValueLabel + '</div>';
|
||
params.forEach((p) => {
|
||
if (p.data == null) return;
|
||
const val = (typeof p.data === 'object' && p.data._original != null)
|
||
? p.data._original : p.value;
|
||
html += p.marker + ' ' + p.seriesName + ': <b>' + val + '</b><br/>';
|
||
});
|
||
return html;
|
||
},
|
||
};
|
||
},
|
||
handleYAxisModeChange() {
|
||
this.$nextTick(() => {
|
||
if (this.activeTab === 'device') {
|
||
if (this.devDatas.length) this.drawDevLine();
|
||
} else if (this.activeTab === 'gateway') {
|
||
if (this.gwResultMode === 'calc' && this.gwCalcDatas.length) this.drawGwCalcLine();
|
||
if (this.gwResultMode === 'compare' && this.subCompareDatas.length) this.drawSubCompareLine();
|
||
if (this.gwResultMode === 'single' && this.gwSingleDatas.length) this.drawGwSingleLine();
|
||
} else if (this.activeTab === 'scene') {
|
||
if (this.sceneDatas.length) this.drawSceneLine();
|
||
}
|
||
});
|
||
},
|
||
drawDevLine() {
|
||
const series = this.getDevSeries();
|
||
this.devChartHeight = this.calcChartHeight(series.length);
|
||
this.$nextTick(() => {
|
||
this.charts = this.$echarts.init(this.$refs.devLineChart);
|
||
this.charts.clear();
|
||
const yAxis = this.independentYAxis
|
||
? this.applyIndependentScale(series)
|
||
: { type: 'value', scale: true };
|
||
const tooltip = this.independentYAxis
|
||
? this.independentScaleTooltip()
|
||
: { trigger: 'axis' };
|
||
this.charts.setOption({
|
||
tooltip,
|
||
legend: {
|
||
align: 'right',
|
||
left: '3%',
|
||
top: '15%',
|
||
},
|
||
grid: {
|
||
top: '30%',
|
||
left: '5%',
|
||
right: '5%',
|
||
bottom: '5%',
|
||
containLabel: true,
|
||
},
|
||
toolbox: {
|
||
feature: {
|
||
dataView: {},
|
||
restore: {},
|
||
saveAsImage: {},
|
||
},
|
||
},
|
||
dataZoom: [
|
||
{ type: 'inside', start: 0, end: 100 },
|
||
{ start: 0, end: 100 },
|
||
],
|
||
xAxis: {
|
||
type: 'category',
|
||
boundaryGap: true,
|
||
axisTick: { alignWithLabel: true },
|
||
data: this.devDatas.length !== 0 && this.devDatas.map((item) => Object.keys(item)[0]),
|
||
},
|
||
yAxis,
|
||
series,
|
||
});
|
||
this.charts.resize();
|
||
});
|
||
},
|
||
// 获取设备的series
|
||
getDevSeries() {
|
||
return this.devQueryParams.identifiers.map((item) => {
|
||
return {
|
||
name: this.devIdentifierList.find((chil) => chil.identifier === item).modelName,
|
||
type: 'line',
|
||
smooth: true,
|
||
data: this.devDatas.map((d) => {
|
||
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
||
return Object.values(ide)[0];
|
||
}),
|
||
};
|
||
});
|
||
},
|
||
// 网关统一:选网关后拉取计算量变量、子设备变量、子设备列表
|
||
handleGwDeviceChange(val) {
|
||
this.gwQueryParams.identifiers = [];
|
||
this.gwQueryParams.subDeviceIds = null;
|
||
this.gwCalcIdentifierList = [];
|
||
this.subCompareIdentifierList = [];
|
||
this.gwSubDeviceList = [];
|
||
this.gwResultMode = '';
|
||
if (!val) return;
|
||
Promise.all([
|
||
getGatewayCalculatedModels(val).then((res) => (res.code === 200 ? res.data || [] : [])),
|
||
listThingsModel({ deviceId: val, pageNum: 1, pageSize: 9999 }).then((res) => {
|
||
if (res.code === 200) {
|
||
const rows = res.rows || [];
|
||
return rows.filter((r) => r.type === 1);
|
||
}
|
||
return [];
|
||
}),
|
||
listGateway({ gwDeviceId: val, pageNum: 1, pageSize: 9999 }).then((res) => (res.code === 200 ? res.rows || [] : [])),
|
||
]).then(([calcList, subList, subDevices]) => {
|
||
this.gwCalcIdentifierList = calcList;
|
||
this.subCompareIdentifierList = subList;
|
||
this.gwSubDeviceList = subDevices;
|
||
});
|
||
},
|
||
handleGwIdentifierChange(val) {
|
||
const raw = (val || []).filter((v) => v !== '__selectAll__');
|
||
const index = (val || []).indexOf('__selectAll__');
|
||
if (index > -1) {
|
||
// 全部勾选/反选:仅作用于子设备变量,不勾选计算量
|
||
const allSub = this.gwVariableListSubUnique.map((i) => i.identifier);
|
||
this.$nextTick(() => {
|
||
this.gwQueryParams.identifiers = this.gwGwIsAllSelected ? [] : [...allSub];
|
||
});
|
||
return;
|
||
}
|
||
// 计算量与子设备变量均支持多选,保留用户选择的全部项(混合选择在查询时校验)
|
||
this.gwQueryParams.identifiers = raw;
|
||
if (this.gwSelectedIsCalculated) this.gwQueryParams.subDeviceIds = null;
|
||
},
|
||
handleGwQuery() {
|
||
const { gwDeviceId, identifiers, subDeviceIds, dayDaterange } = this.gwQueryParams;
|
||
if (!gwDeviceId || !identifiers || identifiers.length === 0 || !dayDaterange || !dayDaterange.length) {
|
||
this.$message.warning('请选择网关设备、物模型和时间范围');
|
||
return;
|
||
}
|
||
const calcIds = this.gwSelectedCalcIdentifiers;
|
||
const subIds = this.gwSelectedSubIdentifiers;
|
||
if (calcIds.length > 0 && subIds.length > 0) {
|
||
this.$message.warning('不能混合选择计算量与子设备变量,请只选其中一类');
|
||
return;
|
||
}
|
||
const device = this.devDeviceList.find((d) => d.deviceId === gwDeviceId);
|
||
if (!device) return;
|
||
const isCalc = this.gwSelectedIsCalculated;
|
||
const singleDeviceId = Array.isArray(subDeviceIds) && subDeviceIds.length === 1 ? subDeviceIds[0] : null;
|
||
if (!isCalc && this.gwSubDeviceList.length > 0 && singleDeviceId) {
|
||
// 单子设备历史(多变量)
|
||
const sub = this.gwSubDeviceList.find((s) => s.subDeviceId === singleDeviceId);
|
||
if (!sub) {
|
||
this.$message.warning('请选择子设备');
|
||
return;
|
||
}
|
||
this.loading = true;
|
||
this.gwResultMode = 'single';
|
||
const identifierList = subIds.map((id) => {
|
||
const item = this.subCompareIdentifierList.find((i) => i.identifier === id) || {};
|
||
return { identifier: id, type: item.type || 1 };
|
||
});
|
||
const params = {
|
||
deviceId: sub.subDeviceId,
|
||
serialNumber: sub.subDeviceNo,
|
||
identifierList,
|
||
beginTime: moment(dayDaterange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||
endTime: moment(dayDaterange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||
};
|
||
getDataCenterDeviceHistory(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.gwSingleDatas = res.data || [];
|
||
this.formatGwSingleTableDatas();
|
||
this.$nextTick(() => {
|
||
if (this.gwSingleDatas.length) this.drawGwSingleLine();
|
||
});
|
||
}
|
||
this.loading = false;
|
||
});
|
||
return;
|
||
}
|
||
if (!isCalc) {
|
||
// 多设备对比(支持多变量、指定子设备或全部)
|
||
this.loading = true;
|
||
this.gwResultMode = 'compare';
|
||
const params = {
|
||
gwDeviceId,
|
||
identifierList: subIds,
|
||
type: 1,
|
||
beginTime: moment(dayDaterange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||
endTime: moment(dayDaterange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||
};
|
||
if (Array.isArray(subDeviceIds) && subDeviceIds.length > 0) {
|
||
params.subDeviceIds = subDeviceIds;
|
||
}
|
||
getSubDeviceComparativeHistory(params).then((res) => {
|
||
if (res.code === 200) {
|
||
this.subCompareDatas = res.data || [];
|
||
this.formatSubCompareTableDatas();
|
||
this.$nextTick(() => {
|
||
if (this.subCompareDatas.length) this.drawSubCompareLine();
|
||
});
|
||
}
|
||
this.loading = false;
|
||
});
|
||
return;
|
||
}
|
||
// 计算量(多变量)
|
||
this.loading = true;
|
||
this.gwResultMode = 'calc';
|
||
const identifierList = calcIds.map((id) => {
|
||
const item = this.gwCalcIdentifierList.find((i) => i.identifier === id) || {};
|
||
return { identifier: id, type: item.type || 1 };
|
||
});
|
||
const calcParams = {
|
||
deviceId: device.deviceId,
|
||
serialNumber: device.serialNumber,
|
||
identifierList,
|
||
beginTime: moment(dayDaterange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||
endTime: moment(dayDaterange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||
};
|
||
getDeviceHistoryCalculated(calcParams).then((res) => {
|
||
if (res.code === 200) {
|
||
this.gwCalcDatas = res.data || [];
|
||
this.gwCalcTableHeaderTemp = calcIds.map((id) => {
|
||
const m = this.gwCalcIdentifierList.find((i) => i.identifier === id);
|
||
return { name: (m && m.modelName) || id, value: id };
|
||
});
|
||
this.formatGwCalcTableDatas();
|
||
this.$nextTick(() => {
|
||
if (this.gwCalcDatas.length) this.drawGwCalcLine();
|
||
});
|
||
}
|
||
this.loading = false;
|
||
});
|
||
},
|
||
handleGwReset() {
|
||
this.gwQueryParams.gwDeviceId = null;
|
||
this.gwQueryParams.identifiers = [];
|
||
this.gwQueryParams.subDeviceIds = null;
|
||
this.gwQueryParams.dayDaterange = [new Date(Date.now() - 3600 * 1000 * 2), new Date()];
|
||
this.gwCalcIdentifierList = [];
|
||
this.subCompareIdentifierList = [];
|
||
this.gwSubDeviceList = [];
|
||
this.gwResultMode = '';
|
||
this.gwCalcDatas = [];
|
||
this.gwCalcTableComTemp = [];
|
||
this.gwCalcTotal = 0;
|
||
this.subCompareDatas = [];
|
||
this.subCompareTableComTemp = [];
|
||
this.subCompareTotal = 0;
|
||
this.gwSingleDatas = [];
|
||
this.gwSingleTableComTemp = [];
|
||
this.gwSingleTotal = 0;
|
||
},
|
||
async handleGwExport() {
|
||
const { gwDeviceId, identifiers, subDeviceIds, dayDaterange } = this.gwQueryParams;
|
||
if (!gwDeviceId || !identifiers || identifiers.length === 0 || !dayDaterange?.length) {
|
||
this.$message.warning('请先选择网关、物模型和时间范围');
|
||
return;
|
||
}
|
||
const device = this.devDeviceList.find((d) => d.deviceId === gwDeviceId);
|
||
if (!device) return;
|
||
const calcIds = this.gwSelectedCalcIdentifiers;
|
||
const subIds = this.gwSelectedSubIdentifiers;
|
||
if (calcIds.length > 0 && subIds.length > 0) {
|
||
this.$message.warning('不能混合选择计算量与子设备变量');
|
||
return;
|
||
}
|
||
try {
|
||
this.exportLoading = true;
|
||
const beginTime = moment(dayDaterange[0]).format('YYYY-MM-DD HH:mm:ss');
|
||
const endTime = moment(dayDaterange[1]).format('YYYY-MM-DD HH:mm:ss');
|
||
const singleId = Array.isArray(subDeviceIds) && subDeviceIds.length === 1 ? subDeviceIds[0] : null;
|
||
if (this.gwResultMode === 'single' && singleId) {
|
||
const sub = this.gwSubDeviceList.find((s) => s.subDeviceId === singleId);
|
||
if (!sub || this.gwSingleDatas.length === 0) {
|
||
this.$message.warning('没有数据可导出');
|
||
return;
|
||
}
|
||
const identifierList = subIds.map((id) => {
|
||
const item = this.subCompareIdentifierList.find((i) => i.identifier === id) || {};
|
||
return { identifier: id, type: item.type || 1 };
|
||
});
|
||
const params = { deviceId: sub.subDeviceId, serialNumber: sub.subDeviceNo, identifierList, beginTime, endTime };
|
||
const res = await exportDeviceHistory(params);
|
||
if (res instanceof Blob) {
|
||
const clonedBlob = res.slice();
|
||
const isValidBlob = await blobValidate(clonedBlob);
|
||
if (!isValidBlob) {
|
||
try {
|
||
const text = await res.text();
|
||
const err = JSON.parse(text);
|
||
this.$message.error(err.msg || '导出失败');
|
||
return;
|
||
} catch (e) {
|
||
this.$message.error('导出失败');
|
||
return;
|
||
}
|
||
}
|
||
saveAs(res, `${sub.subDeviceNo}_${subIds.join('-')}_${moment().format('YYYYMMDDHHmmss')}.xlsx`);
|
||
this.$message.success('导出成功');
|
||
}
|
||
return;
|
||
}
|
||
if (this.gwResultMode === 'compare' && this.subCompareDatas.length > 0) {
|
||
const params = { gwDeviceId, identifierList: subIds, type: 1, beginTime, endTime };
|
||
if (Array.isArray(subDeviceIds) && subDeviceIds.length > 0) params.subDeviceIds = subDeviceIds;
|
||
const res = await exportSubDeviceComparativeHistory(params);
|
||
if (res instanceof Blob) {
|
||
const clonedBlob = res.slice();
|
||
const isValidBlob = await blobValidate(clonedBlob);
|
||
if (!isValidBlob) {
|
||
try {
|
||
const text = await res.text();
|
||
const err = JSON.parse(text);
|
||
this.$message.error(err.msg || '导出失败');
|
||
return;
|
||
} catch (e) {
|
||
this.$message.error('导出失败');
|
||
return;
|
||
}
|
||
}
|
||
const idPart = subIds.length ? subIds.join('-') : 'compare';
|
||
saveAs(res, `sub_compare_${idPart}_${moment().format('YYYYMMDDHHmmss')}.xlsx`);
|
||
this.$message.success('导出成功');
|
||
}
|
||
return;
|
||
}
|
||
if (this.gwResultMode === 'calc' && this.gwCalcDatas.length > 0) {
|
||
const identifierList = calcIds.map((id) => {
|
||
const item = this.gwCalcIdentifierList.find((i) => i.identifier === id) || {};
|
||
return { identifier: id, type: item.type || 1 };
|
||
});
|
||
const params = { deviceId: device.deviceId, serialNumber: device.serialNumber, identifierList, beginTime, endTime };
|
||
const res = await exportDeviceHistoryCalculated(params);
|
||
if (res instanceof Blob) {
|
||
const clonedBlob = res.slice();
|
||
const isValidBlob = await blobValidate(clonedBlob);
|
||
if (!isValidBlob) {
|
||
try {
|
||
const text = await res.text();
|
||
const err = JSON.parse(text);
|
||
this.$message.error(err.msg || '导出失败');
|
||
return;
|
||
} catch (e) {
|
||
this.$message.error('导出失败');
|
||
return;
|
||
}
|
||
}
|
||
saveAs(res, `${device.serialNumber}_calculated_${moment().format('YYYYMMDDHHmmss')}.xlsx`);
|
||
this.$message.success('导出成功');
|
||
}
|
||
return;
|
||
}
|
||
this.$message.warning('没有数据可导出');
|
||
} catch (e) {
|
||
this.$message.error(e.message || '导出失败');
|
||
} finally {
|
||
this.exportLoading = false;
|
||
}
|
||
},
|
||
getGwCalcTableList(e) {
|
||
if (e) {
|
||
this.gwCalcPageNum = e.page;
|
||
this.gwCalcPageSize = e.limit;
|
||
}
|
||
},
|
||
formatGwCalcTableDatas() {
|
||
this.gwCalcTableComTemp = (this.gwCalcDatas || []).map((item) => {
|
||
const time = Object.keys(item)[0];
|
||
const vals = Object.values(item)[0] || [];
|
||
const obj = { time };
|
||
vals.forEach((v) => {
|
||
const k = Object.keys(v)[0];
|
||
obj[k] = Object.values(v)[0];
|
||
});
|
||
return obj;
|
||
});
|
||
this.gwCalcTotal = this.gwCalcTableComTemp.length;
|
||
const ids = this.gwSelectedCalcIdentifiers;
|
||
this.gwCalcTableHeaderTemp = ids.map((id) => {
|
||
const m = this.gwCalcIdentifierList.find((i) => i.identifier === id);
|
||
return { name: (m && m.modelName) || id, value: id };
|
||
});
|
||
},
|
||
drawGwCalcLine() {
|
||
if (!this.$refs.gwCalcLineChart) return;
|
||
const ids = this.gwSelectedCalcIdentifiers;
|
||
const times = this.gwCalcDatas.map((d) => Object.keys(d)[0]);
|
||
const series = ids.map((id) => {
|
||
const m = this.gwCalcIdentifierList.find((i) => i.identifier === id);
|
||
return {
|
||
name: (m && m.modelName) || id,
|
||
type: 'line',
|
||
smooth: true,
|
||
data: this.gwCalcDatas.map((d) => {
|
||
const arr = Object.values(d)[0] || [];
|
||
const o = arr.find((x) => Object.keys(x)[0] === id);
|
||
return o ? Object.values(o)[0] : null;
|
||
}),
|
||
};
|
||
});
|
||
this.gwCalcChartHeight = this.calcChartHeight(series.length);
|
||
this.$nextTick(() => {
|
||
const chart = this.$echarts.init(this.$refs.gwCalcLineChart);
|
||
chart.clear();
|
||
const yAxis = this.independentYAxis
|
||
? this.applyIndependentScale(series)
|
||
: { type: 'value', scale: true };
|
||
const tooltip = this.independentYAxis
|
||
? this.independentScaleTooltip()
|
||
: { trigger: 'axis' };
|
||
chart.setOption({
|
||
tooltip,
|
||
legend: { align: 'right', left: '3%', top: '3%' },
|
||
grid: { top: '30%', left: '5%', right: '5%', bottom: '15%', containLabel: true },
|
||
toolbox: { feature: { dataView: {}, restore: {}, saveAsImage: {} } },
|
||
dataZoom: [
|
||
{ type: 'inside', start: 0, end: 100 },
|
||
{ start: 0, end: 100 },
|
||
],
|
||
xAxis: {
|
||
type: 'category',
|
||
boundaryGap: true,
|
||
axisTick: { alignWithLabel: true },
|
||
axisLabel: { rotate: 30 },
|
||
data: times,
|
||
},
|
||
yAxis,
|
||
series,
|
||
});
|
||
chart.resize();
|
||
});
|
||
},
|
||
getGwSingleTableList(e) {
|
||
if (e) {
|
||
this.gwSinglePageNum = e.page;
|
||
this.gwSinglePageSize = e.limit;
|
||
}
|
||
},
|
||
formatGwSingleTableDatas() {
|
||
const ids = this.gwSelectedSubIdentifiers;
|
||
this.gwSingleTableComTemp = (this.gwSingleDatas || []).map((item) => {
|
||
const time = Object.keys(item)[0];
|
||
const vals = Object.values(item)[0] || [];
|
||
const obj = { time };
|
||
vals.forEach((v) => {
|
||
const k = Object.keys(v)[0];
|
||
obj[k] = Object.values(v)[0];
|
||
});
|
||
return obj;
|
||
});
|
||
this.gwSingleTotal = this.gwSingleTableComTemp.length;
|
||
this.gwSingleTableHeaderTemp = ids.map((id) => {
|
||
const m = this.subCompareIdentifierList.find((i) => i.identifier === id);
|
||
return { name: (m && m.modelName) || id, value: id };
|
||
});
|
||
},
|
||
drawGwSingleLine() {
|
||
if (!this.$refs.gwSingleLineChart || !this.gwSingleDatas.length) return;
|
||
const ids = this.gwSelectedSubIdentifiers;
|
||
const times = this.gwSingleDatas.map((d) => Object.keys(d)[0]);
|
||
const series = ids.map((id) => {
|
||
const m = this.subCompareIdentifierList.find((i) => i.identifier === id);
|
||
return {
|
||
name: (m && m.modelName) || id,
|
||
type: 'line',
|
||
smooth: true,
|
||
data: this.gwSingleDatas.map((d) => {
|
||
const arr = Object.values(d)[0] || [];
|
||
const o = arr.find((x) => Object.keys(x)[0] === id);
|
||
return o ? Object.values(o)[0] : null;
|
||
}),
|
||
};
|
||
});
|
||
this.gwSingleChartHeight = this.calcChartHeight(series.length);
|
||
this.$nextTick(() => {
|
||
const chart = this.$echarts.init(this.$refs.gwSingleLineChart);
|
||
chart.clear();
|
||
const yAxis = this.independentYAxis
|
||
? this.applyIndependentScale(series)
|
||
: { type: 'value', scale: true };
|
||
const tooltip = this.independentYAxis
|
||
? this.independentScaleTooltip()
|
||
: { trigger: 'axis' };
|
||
chart.setOption({
|
||
tooltip,
|
||
legend: { align: 'right', left: '3%', top: '3%' },
|
||
grid: { top: '30%', left: '5%', right: '5%', bottom: '15%', containLabel: true },
|
||
toolbox: { feature: { dataView: {}, restore: {}, saveAsImage: {} } },
|
||
dataZoom: [
|
||
{ type: 'inside', start: 0, end: 100 },
|
||
{ start: 0, end: 100 },
|
||
],
|
||
xAxis: {
|
||
type: 'category',
|
||
boundaryGap: true,
|
||
axisTick: { alignWithLabel: true },
|
||
axisLabel: { rotate: 30 },
|
||
data: times,
|
||
},
|
||
yAxis,
|
||
series,
|
||
});
|
||
chart.resize();
|
||
});
|
||
},
|
||
getSubCompareTableList(e) {
|
||
if (e) {
|
||
this.subComparePageNum = e.page;
|
||
this.subComparePageSize = e.limit;
|
||
}
|
||
},
|
||
/** 多设备对比列名分隔符,与后端一致 */
|
||
subCompareColSep() {
|
||
return ':::';
|
||
},
|
||
/** 多设备对比表格/图例列展示名:后端返回 "设备标签:::identifier" 时解析为 "设备标签 - 变量名" */
|
||
getSubCompareColumnLabel(key) {
|
||
if (key === 'time') return '更新时间';
|
||
const sep = this.subCompareColSep();
|
||
if (key && key.includes(sep)) {
|
||
const idx = key.indexOf(sep);
|
||
const deviceLabel = key.substring(0, idx);
|
||
const identifier = key.substring(idx + sep.length);
|
||
const model = (this.subCompareIdentifierList || []).find((i) => i.identifier === identifier);
|
||
return `${deviceLabel} - ${(model && model.modelName) || identifier}`;
|
||
}
|
||
return key;
|
||
},
|
||
formatSubCompareTableDatas() {
|
||
this.subCompareTableComTemp = this.subCompareDatas || [];
|
||
this.subCompareTotal = this.subCompareTableComTemp.length;
|
||
const cols = [];
|
||
if (this.subCompareDatas.length) {
|
||
Object.keys(this.subCompareDatas[0]).forEach((k) => {
|
||
cols.push({ prop: k, label: this.getSubCompareColumnLabel(k) });
|
||
});
|
||
}
|
||
this.subCompareTableColumns = cols;
|
||
},
|
||
drawSubCompareLine() {
|
||
if (!this.$refs.subCompareLineChart || !this.subCompareDatas.length) return;
|
||
const times = this.subCompareDatas.map((d) => d.time);
|
||
const keys = Object.keys(this.subCompareDatas[0]).filter((k) => k !== 'time');
|
||
const series = keys.map((k) => ({
|
||
name: this.getSubCompareColumnLabel(k),
|
||
type: 'line',
|
||
smooth: true,
|
||
data: this.subCompareDatas.map((d) => d[k]),
|
||
}));
|
||
this.subCompareChartHeight = this.calcChartHeight(series.length);
|
||
this.$nextTick(() => {
|
||
const chart = this.$echarts.init(this.$refs.subCompareLineChart);
|
||
chart.clear();
|
||
const yAxis = this.independentYAxis
|
||
? this.applyIndependentScale(series)
|
||
: { type: 'value', scale: true };
|
||
const tooltip = this.independentYAxis
|
||
? this.independentScaleTooltip()
|
||
: { trigger: 'axis' };
|
||
chart.setOption({
|
||
tooltip,
|
||
legend: { align: 'right', left: '3%', top: '3%' },
|
||
grid: { top: '30%', left: '5%', right: '5%', bottom: '15%', containLabel: true },
|
||
toolbox: { feature: { dataView: {}, restore: {}, saveAsImage: {} } },
|
||
dataZoom: [
|
||
{ type: 'inside', start: 0, end: 100 },
|
||
{ start: 0, end: 100 },
|
||
],
|
||
xAxis: {
|
||
type: 'category',
|
||
boundaryGap: true,
|
||
axisTick: { alignWithLabel: true },
|
||
axisLabel: { rotate: 30 },
|
||
data: times,
|
||
},
|
||
yAxis,
|
||
series,
|
||
});
|
||
chart.resize();
|
||
});
|
||
},
|
||
// 格式化设备列表数据
|
||
formatDevTableDatas() {
|
||
this.devTableComTemp = this.devDatas.map((item) => {
|
||
const time = Object.keys(item)[0];
|
||
let obj = {};
|
||
Object.values(item)[0].forEach((chil) => {
|
||
obj[Object.keys(chil)[0]] = Object.values(chil)[0];
|
||
});
|
||
return { time, ...obj };
|
||
});
|
||
this.devTotal = this.devDatas.length;
|
||
this.devTableHeaderTemp = this.devQueryParams.identifiers.map((item) => ({
|
||
name: this.devIdentifierList.find((chil) => chil.identifier === item).modelName,
|
||
value: item,
|
||
}));
|
||
},
|
||
// 导出设备历史数据
|
||
async handleDevExport() {
|
||
if (!this.devQueryParams.deviceId || this.devQueryParams.identifiers.length === 0 || !this.devQueryParams.dayDaterange || this.devQueryParams.dayDaterange.length === 0) {
|
||
this.$message.warning('请先选择设备、变量和时间范围');
|
||
return;
|
||
}
|
||
if (this.devSubDeviceList.length > 0 && !this.devQueryParams.subDeviceId) {
|
||
this.$message.warning('请选择子设备');
|
||
return;
|
||
}
|
||
if (this.devDatas.length === 0) {
|
||
this.$message.warning('没有数据可导出');
|
||
return;
|
||
}
|
||
try {
|
||
this.exportLoading = true;
|
||
const queryDevice = this.getQueryDevice();
|
||
const identifierList = this.devQueryParams.identifiers.map((item) => {
|
||
const identifiers = this.devIdentifierList.find((chil) => chil.identifier === item);
|
||
return { identifier: identifiers.identifier, type: identifiers.type };
|
||
});
|
||
const params = {
|
||
deviceId: queryDevice.deviceId,
|
||
serialNumber: queryDevice.serialNumber,
|
||
identifierList: identifierList,
|
||
beginTime: moment(this.devQueryParams.dayDaterange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||
endTime: moment(this.devQueryParams.dayDaterange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||
};
|
||
const res = await exportDeviceHistory(params);
|
||
|
||
if (res instanceof Blob) {
|
||
const clonedBlob = res.slice();
|
||
const isValidBlob = await blobValidate(clonedBlob);
|
||
if (!isValidBlob) {
|
||
try {
|
||
const text = await res.text();
|
||
const errorObj = JSON.parse(text);
|
||
this.$message.error(errorObj.msg || '导出失败');
|
||
return;
|
||
} catch (e) {
|
||
this.$message.error('导出失败,请稍后重试');
|
||
return;
|
||
}
|
||
}
|
||
const deviceSerialNumber = queryDevice.serialNumber || '设备数据';
|
||
const fileName = `${deviceSerialNumber}_${moment().format('YYYYMMDDHHmmss')}.xlsx`;
|
||
saveAs(res, fileName);
|
||
this.$message.success('导出成功');
|
||
} else {
|
||
this.$message.error('导出失败:服务器返回格式错误');
|
||
}
|
||
} catch (err) {
|
||
console.error('导出失败:', err);
|
||
this.$message.error(err.message || '导出失败');
|
||
} finally {
|
||
this.exportLoading = false;
|
||
}
|
||
},
|
||
drawSceneLine() {
|
||
const series = this.getSceneSeries();
|
||
this.sceneChartHeight = this.calcChartHeight(series.length);
|
||
this.$nextTick(() => {
|
||
this.charts = this.$echarts.init(this.$refs.sceneLineChart);
|
||
this.charts.clear();
|
||
const yAxis = this.independentYAxis
|
||
? this.applyIndependentScale(series)
|
||
: { type: 'value', scale: true };
|
||
const tooltip = this.independentYAxis
|
||
? this.independentScaleTooltip()
|
||
: { trigger: 'axis' };
|
||
this.charts.setOption({
|
||
tooltip,
|
||
legend: { align: 'right', left: '3%', top: '15%' },
|
||
grid: { top: '30%', left: '5%', right: '5%', bottom: '5%', containLabel: true },
|
||
toolbox: { feature: { dataView: {}, restore: {}, saveAsImage: {} } },
|
||
dataZoom: [
|
||
{ type: 'inside', start: 0, end: 100 },
|
||
{ start: 0, end: 100 },
|
||
],
|
||
xAxis: {
|
||
type: 'category',
|
||
boundaryGap: true,
|
||
axisTick: { alignWithLabel: true },
|
||
data: this.sceneDatas.length !== 0 && this.sceneDatas.map((item) => Object.keys(item)[0]),
|
||
},
|
||
yAxis,
|
||
series,
|
||
});
|
||
this.charts.resize();
|
||
});
|
||
},
|
||
// 获取设备的series
|
||
getSceneSeries() {
|
||
return this.sceneQueryParams.identifiers.map((item) => {
|
||
return {
|
||
name: this.sceneIdentifierList.find((chil) => chil.identifier === item).sourceName,
|
||
type: 'line',
|
||
smooth: true,
|
||
data: this.sceneDatas.map((d) => {
|
||
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
||
return Object.values(ide)[0];
|
||
}),
|
||
};
|
||
});
|
||
},
|
||
// 格式化设备列表数据
|
||
formatSceneTableDatas() {
|
||
this.sceneTableComTemp = this.sceneDatas.map((item) => {
|
||
const time = Object.keys(item)[0];
|
||
let obj = {};
|
||
Object.values(item)[0].forEach((chil) => {
|
||
obj[Object.keys(chil)[0]] = Object.values(chil)[0];
|
||
});
|
||
return { time, ...obj };
|
||
});
|
||
this.sceneTotal = this.sceneDatas.length;
|
||
this.sceneTableHeaderTemp = this.sceneQueryParams.identifiers.map((item) => ({
|
||
name: this.sceneIdentifierList.find((chil) => chil.identifier === item).sourceName,
|
||
value: item,
|
||
}));
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.data-center-history {
|
||
padding: 20px;
|
||
|
||
.history-device-filter {
|
||
margin-bottom: 12px;
|
||
padding: 12px 14px 4px;
|
||
background: #f5f7fa;
|
||
border-radius: 4px;
|
||
border: 1px solid #ebeef5;
|
||
|
||
&__item {
|
||
margin-right: 20px;
|
||
margin-bottom: 10px;
|
||
vertical-align: middle;
|
||
|
||
&--checkbox {
|
||
margin-left: 4px;
|
||
}
|
||
}
|
||
|
||
&__help {
|
||
margin-left: 6px;
|
||
color: #909399;
|
||
cursor: help;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 与 Tab 内 el-select 同为 Element mini,统一高度与边框 */
|
||
.history-filter-control {
|
||
width: 340px;
|
||
min-width: 280px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
::v-deep .history-filter-control.el-cascader {
|
||
line-height: normal;
|
||
}
|
||
::v-deep .history-filter-control .el-input__inner {
|
||
height: 28px;
|
||
line-height: 28px;
|
||
font-size: 12px;
|
||
}
|
||
::v-deep .history-filter-control .el-input__icon {
|
||
line-height: 28px;
|
||
}
|
||
}
|
||
|
||
.device-wrap {
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.scene-wrap {
|
||
margin-top: 5px;
|
||
}
|
||
.gateway-wrap {
|
||
margin-top: 5px;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<style lang="scss">
|
||
/* 下拉层挂到 body,需单独写:提示整段文字可点选 */
|
||
.history-dept-cascader-dropdown .el-cascader-node.is-selectable .el-cascader-node__label {
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
</style>
|