1335 lines
72 KiB
Vue
1335 lines
72 KiB
Vue
<template>
|
||
<div class="running-status">
|
||
<el-row :gutter="120">
|
||
<el-col :xs="24" :sm="24" :md="24" :lg="14" :xl="10" style="margin-bottom: 50px">
|
||
<el-descriptions :column="1" border style="margin-bottom: 50px">
|
||
<!-- 设备模式-->
|
||
<el-descriptions-item :labelStyle="statusColor">
|
||
<template slot="label">
|
||
<i class="el-icon-menu"></i>
|
||
{{ $t('device.running-status.866086-0') }}
|
||
</template>
|
||
<el-link :underline="false" style="line-height: 28px; font-size: 16px; padding-right: 10px">{{ title }}</el-link>
|
||
</el-descriptions-item>
|
||
<!-- 设备升级-->
|
||
<el-descriptions-item :labelStyle="statusColor">
|
||
<template slot="label">
|
||
<svg-icon icon-class="ota" />
|
||
{{ $t('device.running-status.866086-1') }}
|
||
</template>
|
||
<el-link :underline="false" style="line-height: 28px; font-size: 16px; padding-right: 10px">Version {{ deviceInfo.firmwareVersion }}</el-link>
|
||
<el-button type="success" size="mini" style="float: right" @click="getLatestFirmware(deviceInfo.deviceId)" :disabled="deviceInfo.status != 3">{{ $t('device.running-status.866086-2') }}</el-button>
|
||
</el-descriptions-item>
|
||
|
||
<!-- IMEI / ICCID(蜂窝信息) -->
|
||
<el-descriptions-item :labelStyle="statusColor">
|
||
<template slot="label">
|
||
<i class="el-icon-postcard"></i>
|
||
IMEI
|
||
</template>
|
||
<div class="cell-flex">
|
||
<el-input v-model="deviceInfo.imei" size="mini" placeholder="请输入IMEI" style="max-width: 260px;" @keyup.enter.native="saveImeiIccid('imei')" />
|
||
<el-popover placement="bottom" width="360" trigger="click" popper-class="atcmd-popover">
|
||
<div class="atcmd-title">编辑下发指令</div>
|
||
<el-input v-model="imeiCmd" size="mini" placeholder="例如:AT+IMEI\\r\\n" />
|
||
<el-button slot="reference" size="mini" icon="el-icon-edit" title="编辑指令"></el-button>
|
||
</el-popover>
|
||
<el-button type="primary" size="mini" :disabled="deviceInfo.status != 3" @click="sendAt('imei')" :loading="imeiLoading" title="默认指令:AT+IMEI\r\n">从设备读取</el-button>
|
||
</div>
|
||
</el-descriptions-item>
|
||
|
||
<el-descriptions-item :labelStyle="statusColor">
|
||
<template slot="label">
|
||
<i class="el-icon-postcard"></i>
|
||
ICCID
|
||
</template>
|
||
<div class="cell-flex">
|
||
<el-input v-model="deviceInfo.iccid" size="mini" placeholder="请输入ICCID" style="max-width: 260px;" @keyup.enter.native="saveImeiIccid('iccid')"/>
|
||
<el-popover placement="bottom" width="360" trigger="click" popper-class="atcmd-popover">
|
||
<div class="atcmd-title">编辑下发指令</div>
|
||
<el-input v-model="iccidCmd" size="mini" placeholder="例如:AT+ICCID\\r\\n" />
|
||
<el-button slot="reference" size="mini" icon="el-icon-edit" title="编辑指令"></el-button>
|
||
</el-popover>
|
||
<el-button type="primary" size="mini" :disabled="deviceInfo.status != 3" @click="sendAt('iccid')" :loading="iccidLoading" title="默认指令:AT+ICCID\r\n">从设备读取</el-button>
|
||
</div>
|
||
</el-descriptions-item>
|
||
|
||
<!-- 设备物模型-->
|
||
<el-descriptions-item v-for="(item, index) in deviceInfo.thingsModels" :key="index" :labelStyle="statusColor">
|
||
<template slot="label">
|
||
<i class="el-icon-open"></i>
|
||
{{ item.name }}
|
||
</template>
|
||
<div v-if="item.datatype.type == 'bool'">
|
||
<el-switch
|
||
v-model="item.shadow"
|
||
@change="mqttPublish(deviceInfo, item)"
|
||
active-text=""
|
||
inactive-text=""
|
||
active-value="1"
|
||
inactive-value="0"
|
||
style="min-width: 100px"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
/>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'enum'">
|
||
<div v-if="item.datatype.showWay && item.datatype.showWay == 'button'">
|
||
<el-button
|
||
style="margin: 5px"
|
||
size="mini"
|
||
@click="enumButtonClick(deviceInfo, item, subItem.value)"
|
||
v-for="subItem in item.datatype.enumList"
|
||
:key="subItem.value"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
:class="{ 'is-active-btn': subItem.value === item.shadow }"
|
||
>
|
||
{{ subItem.text }}
|
||
</el-button>
|
||
</div>
|
||
<el-select v-else v-model="item.shadow" :placeholder="$t('device.running-status.866086-3')" @change="mqttPublish(deviceInfo, item)" :disabled="shadowUnEnable || item.isReadonly == 1">
|
||
<el-option v-for="subItem in item.datatype.enumList" :key="subItem.value" :label="subItem.text" :value="subItem.value" />
|
||
</el-select>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'string'">
|
||
<el-input
|
||
v-model="item.shadow"
|
||
:placeholder="item.datatype.unit ? $t('device.running-status.866086-5', [item.datatype.unit]) : $t('device.running-status.866086-4')"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
>
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, item)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && item.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'decimal'">
|
||
<div style="width: 80%; float: left">
|
||
<el-slider
|
||
v-model="item.shadow"
|
||
:min="item.datatype.min"
|
||
:max="item.datatype.max"
|
||
:step="item.datatype.step"
|
||
:format-tooltip="(x) => x + ' ' + item.datatype.unit"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
></el-slider>
|
||
</div>
|
||
<div style="width: 20%; float: left">
|
||
<el-button
|
||
icon="el-icon-s-promotion"
|
||
type="info"
|
||
@click="mqttPublish(deviceInfo, item)"
|
||
style="font-size: 16px; padding: 1px 8px; margin: 2px 0 0 5px; border-radius: 3px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && item.isReadonly == 0"
|
||
></el-button>
|
||
</div>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'integer'">
|
||
<div style="width: 80%; float: left">
|
||
<el-slider
|
||
v-model="item.shadow"
|
||
:min="item.datatype.min"
|
||
:max="item.datatype.max"
|
||
:step="item.datatype.step"
|
||
:format-tooltip="(x) => x + ' ' + item.datatype.unit"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
></el-slider>
|
||
</div>
|
||
<div style="width: 20%; float: left">
|
||
<el-button
|
||
icon="el-icon-s-promotion"
|
||
type="info"
|
||
@click="mqttPublish(deviceInfo, item)"
|
||
style="font-size: 16px; padding: 1px 8px; margin: 4px 0 0 10px; border-radius: 3px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && item.isReadonly == 0"
|
||
></el-button>
|
||
</div>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'object'">
|
||
<el-descriptions :column="1" size="mini" border>
|
||
<el-descriptions-item v-for="(param, index) in item.datatype.params" :key="index" :label="param.name">
|
||
<div v-if="param.datatype.type == 'bool'">
|
||
<el-switch
|
||
v-model="param.shadow"
|
||
@change="mqttPublish(deviceInfo, param)"
|
||
active-text=""
|
||
inactive-text=""
|
||
active-value="1"
|
||
inactive-value="0"
|
||
style="min-width: 100px"
|
||
:disabled="shadowUnEnable || param.isReadonly == 1"
|
||
/>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'enum'">
|
||
<div v-if="param.datatype.showWay && param.datatype.showWay == 'button'">
|
||
<el-button
|
||
style="margin: 5px"
|
||
size="mini"
|
||
@click="enumButtonClick(deviceInfo, param, subItem.value)"
|
||
v-for="subItem in param.datatype.enumList"
|
||
:key="subItem.value"
|
||
:disabled="shadowUnEnable || param.isReadonly == 1"
|
||
:class="{ 'is-active-btn': subItem.value === param.shadow }"
|
||
>
|
||
{{ subItem.text }}
|
||
</el-button>
|
||
</div>
|
||
<el-select
|
||
size="small"
|
||
v-else
|
||
v-model="param.shadow"
|
||
:placeholder="$t('device.running-status.866086-3')"
|
||
@change="mqttPublish(deviceInfo, param)"
|
||
:disabled="shadowUnEnable || param.isReadonly == 1"
|
||
>
|
||
<el-option v-for="subItem in param.datatype.enumList" :key="subItem.value" :label="subItem.text" :value="subItem.value" />
|
||
</el-select>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'string'">
|
||
<el-input v-model="param.shadow" :placeholder="$t('device.running-status.866086-4')" :disabled="shadowUnEnable || param.isReadonly == 1">
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, param)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && param.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'decimal'">
|
||
<el-input v-model="param.shadow" type="number" :placeholder="$t('device.running-status.866086-7')" :disabled="shadowUnEnable || param.isReadonly == 1">
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, param)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && param.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'integer'">
|
||
<el-input v-model="param.shadow" type="integer" :placeholder="$t('device.running-status.866086-8')" :disabled="shadowUnEnable || param.isReadonly == 1">
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, param)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && param.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'array'">
|
||
<el-descriptions :column="1" size="mini" border v-if="item.datatype.arrayType != 'object'">
|
||
<el-descriptions-item v-for="(model, index) in item.datatype.arrayModel" :key="index" :label="item.name + (index + 1)">
|
||
<div v-if="item.datatype.arrayType == 'string'">
|
||
<el-input
|
||
:placeholder="$t('device.running-status.866086-4')"
|
||
size="mini"
|
||
v-model="model.shadow"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
@input="arrayItemChange($event, item)"
|
||
>
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, model)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable || item.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="item.datatype.arrayType == 'decimal'">
|
||
<el-input
|
||
type="number"
|
||
:placeholder="$t('device.running-status.866086-7')"
|
||
size="mini"
|
||
v-model="model.shadow"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
@input="arrayItemChange($event, item)"
|
||
>
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, model)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable || item.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="item.datatype.arrayType == 'integer'">
|
||
<el-input
|
||
type="integer"
|
||
:placeholder="$t('device.running-status.866086-8')"
|
||
size="mini"
|
||
v-model="model.shadow"
|
||
:disabled="shadowUnEnable || item.isReadonly == 1"
|
||
@input="arrayItemChange($event, item)"
|
||
>
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, model)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable || item.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<el-collapse v-if="item.datatype.arrayType == 'object'">
|
||
<el-collapse-item v-for="(arrayParam, index) in item.datatype.arrayParams" :key="index">
|
||
<template slot="title">
|
||
<span style="color: #666">
|
||
<i class="el-icon-tickets"></i>
|
||
{{ item.name + (index + 1) }}
|
||
</span>
|
||
</template>
|
||
<el-descriptions :column="1" size="mini" border>
|
||
<el-descriptions-item v-for="(param, index) in arrayParam" :key="index" :label="param.name">
|
||
<div v-if="param.datatype.type == 'bool'">
|
||
<el-switch
|
||
v-model="param.shadow"
|
||
@change="mqttPublish(deviceInfo, param)"
|
||
active-text=""
|
||
inactive-text=""
|
||
active-value="1"
|
||
inactive-value="0"
|
||
style="min-width: 100px"
|
||
:disabled="shadowUnEnable || param.isReadonly == 1"
|
||
/>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'enum'">
|
||
<div v-if="param.datatype.showWay && param.datatype.showWay == 'button'">
|
||
<el-button
|
||
style="margin: 5px"
|
||
size="mini"
|
||
@click="enumButtonClick(deviceInfo, param, subItem.value)"
|
||
v-for="subItem in param.datatype.enumList"
|
||
:key="subItem.value"
|
||
:disabled="shadowUnEnable || param.isReadonly == 1"
|
||
:class="{ 'is-active-btn': subItem.value === param.shadow }"
|
||
>
|
||
{{ subItem.text }}
|
||
</el-button>
|
||
</div>
|
||
<el-select
|
||
v-else
|
||
v-model="param.shadow"
|
||
:placeholder="$t('device.running-status.866086-3')"
|
||
size="small"
|
||
@change="mqttPublish(deviceInfo, param)"
|
||
:disabled="shadowUnEnable || param.isReadonly == 1"
|
||
>
|
||
<el-option v-for="subItem in param.datatype.enumList" :key="subItem.value" :label="subItem.text" :value="subItem.value" />
|
||
</el-select>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'string'">
|
||
<el-input v-model="param.shadow" :placeholder="$t('device.running-status.866086-4')" :disabled="shadowUnEnable || param.isReadonly == 1">
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, param)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && param.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'decimal'">
|
||
<el-input v-model="param.shadow" type="number" :placeholder="$t('device.running-status.866086-7')" :disabled="shadowUnEnable || param.isReadonly == 1">
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, param)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && param.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'integer'">
|
||
<el-input v-model="param.shadow" type="integer" :placeholder="$t('device.running-status.866086-8')" :disabled="shadowUnEnable || param.isReadonly == 1">
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-s-promotion"
|
||
@click="mqttPublish(deviceInfo, param)"
|
||
style="font-size: 20px"
|
||
:title="$t('device.running-status.866086-6')"
|
||
v-if="!shadowUnEnable && param.isReadonly == 0"
|
||
></el-button>
|
||
</el-input>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<!---设备状态(影子模式,value值不会更新)-->
|
||
<el-descriptions :column="1" border size="mini" v-if="deviceInfo.isShadow == 1 && deviceInfo.status != 3">
|
||
<template slot="title">
|
||
<span style="font-size: 14px; color: #606266">{{ $t('device.running-status.866086-9') }}</span>
|
||
</template>
|
||
|
||
<!-- 设备物模型-->
|
||
<el-descriptions-item v-for="(item, index) in deviceInfo.thingsModels" :key="index">
|
||
<template slot="label">
|
||
<i class="el-icon-open"></i>
|
||
{{ item.name }}
|
||
</template>
|
||
<div v-if="item.datatype.type == 'bool'">
|
||
<el-switch v-model="item.value" @change="mqttPublish(deviceInfo, item)" active-text="" inactive-text="" active-value="1" inactive-value="0" style="min-width: 100px" disabled />
|
||
</div>
|
||
<div v-if="item.datatype.type == 'enum'">
|
||
<div v-if="item.datatype.showWay && item.datatype.showWay == 'button'">
|
||
<el-button style="margin: 5px" size="mini" disabled v-for="subItem in item.datatype.enumList" :key="subItem.value">{{ subItem.text }}</el-button>
|
||
</div>
|
||
<el-select v-else v-model="item.value" :placeholder="$t('device.running-status.866086-3')" @change="mqttPublish(deviceInfo, item)" disabled size="mini">
|
||
<el-option v-for="subItem in item.datatype.enumList" :key="subItem.value" :label="subItem.text" :value="subItem.value" />
|
||
</el-select>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'string'">
|
||
<el-input v-model="item.value" :placeholder="$t('device.running-status.866086-4')" disabled size="mini"></el-input>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'decimal'">
|
||
<el-input v-model="item.value" type="number" :placeholder="$t('device.running-status.866086-7')" disabled size="mini"></el-input>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'integer'">
|
||
<el-input v-model="item.value" type="integer" :placeholder="$t('device.running-status.866086-8')" disabled size="mini"></el-input>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'object'">
|
||
<el-descriptions :column="1" size="mini" border>
|
||
<el-descriptions-item v-for="(param, index) in item.datatype.params" :key="index" :label="param.name">
|
||
<div v-if="param.datatype.type == 'bool'">
|
||
<el-switch
|
||
v-model="param.value"
|
||
size="mini"
|
||
@change="mqttPublish(deviceInfo, param)"
|
||
active-text=""
|
||
inactive-text=""
|
||
active-value="1"
|
||
inactive-value="0"
|
||
style="min-width: 100px"
|
||
disabled
|
||
/>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'enum'">
|
||
<el-select v-model="param.value" :placeholder="$t('device.running-status.866086-3')" @change="mqttPublish(deviceInfo, param)" disabled size="mini">
|
||
<el-option v-for="subItem in param.datatype.enumList" :key="subItem.value" :label="subItem.text" :value="subItem.value" />
|
||
</el-select>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'string'">
|
||
<el-input v-model="param.value" :placeholder="$t('device.running-status.866086-4')" disabled size="mini"></el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'decimal'">
|
||
<el-input v-model="param.value" type="number" :placeholder="$t('device.running-status.866086-7')" disabled size="mini"></el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'integer'">
|
||
<el-input v-model="param.value" type="integer" :placeholder="$t('device.running-status.866086-8')" disabled size="mini"></el-input>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
<div v-if="item.datatype.type == 'array'">
|
||
<el-descriptions :column="1" size="mini" border v-if="item.datatype.arrayType != 'object'">
|
||
<el-descriptions-item v-for="(model, index) in item.datatype.arrayModel" :key="index" :label="item.name + (index + 1)">
|
||
<div v-if="item.datatype.arrayType == 'string'">
|
||
<el-input v-model="model.value" :placeholder="$t('device.running-status.866086-4')" size="mini" disabled></el-input>
|
||
</div>
|
||
<div v-if="item.datatype.arrayType == 'decimal'">
|
||
<el-input v-model="model.value" type="number" :placeholder="$t('device.running-status.866086-7')" size="mini" disabled></el-input>
|
||
</div>
|
||
<div v-if="item.datatype.arrayType == 'integer'">
|
||
<el-input v-model="model.value" type="integer" :placeholder="$t('device.running-status.866086-8')" size="mini" disabled></el-input>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<el-collapse v-if="item.datatype.arrayType == 'object'">
|
||
<el-collapse-item v-for="(arrayParam, index) in item.datatype.arrayParams" :key="index">
|
||
<template slot="title">
|
||
<span style="color: #666">
|
||
<i class="el-icon-tickets"></i>
|
||
{{ item.name + (index + 1) }}
|
||
</span>
|
||
</template>
|
||
<el-descriptions :column="1" size="mini" border>
|
||
<el-descriptions-item v-for="(param, index) in arrayParam" :key="index" :label="param.name">
|
||
<div v-if="param.datatype.type == 'bool'">
|
||
<el-switch
|
||
v-model="param.value"
|
||
@change="mqttPublish(deviceInfo, param)"
|
||
active-text=""
|
||
inactive-text=""
|
||
active-value="1"
|
||
inactive-value="0"
|
||
style="min-width: 100px"
|
||
disabled
|
||
/>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'enum'">
|
||
<el-select v-model="param.value" :placeholder="$t('device.running-status.866086-3')" @change="mqttPublish(deviceInfo, param)" disabled size="mini">
|
||
<el-option v-for="subItem in param.datatype.enumList" :key="subItem.value" :label="subItem.text" :value="subItem.value" />
|
||
</el-select>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'string'">
|
||
<el-input v-model="param.value" :placeholder="$t('device.running-status.866086-4')" disabled size="mini"></el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'decimal'">
|
||
<el-input v-model="param.value" type="number" :placeholder="$t('device.running-status.866086-7')" disabled size="mini"></el-input>
|
||
</div>
|
||
<div v-if="param.datatype.type == 'integer'">
|
||
<el-input v-model="param.value" type="integer" :placeholder="$t('device.running-status.866086-8')" disabled size="mini"></el-input>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 设备监测图表(全宽响应式矩阵布局,尽量一屏看完,减少长滚动)-->
|
||
<div class="monitor-grid-wrap" v-if="deviceInfo.chartList.length > 0">
|
||
<div class="monitor-grid">
|
||
<el-card v-for="(item, index) in deviceInfo.chartList" :key="index" shadow="hover" class="monitor-card">
|
||
<div ref="map" class="monitor-chart"></div>
|
||
</el-card>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加或修改产品固件对话框 -->
|
||
<el-dialog :title="$t('device.running-status.866086-10')" :visible.sync="openFirmware" width="600px" append-to-body>
|
||
<div v-if="firmware == null" style="text-align: center; font-size: 16px">
|
||
<i class="el-icon-success" style="color: #67c23a"></i>
|
||
{{ $t('device.running-status.866086-11') }}
|
||
</div>
|
||
<el-descriptions :column="1" border size="large" v-if="firmware != null && deviceInfo.firmwareVersion < firmware.version" :labelStyle="{ width: '150px', 'font-weight': 'bold' }">
|
||
<template slot="title">
|
||
<el-link icon="el-icon-success" type="success" :underline="false">{{ $t('device.running-status.866086-12') }}</el-link>
|
||
</template>
|
||
<el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName }}</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName }}</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version }}</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.running-status.866086-16')">
|
||
<el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{ getDownloadUrl(firmware.filePath) }}</el-link>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="success" @click="otaUpgrade" v-if="firmware != null && deviceInfo.firmwareVersion < firmware.version">{{ $t('device.running-status.866086-18') }}</el-button>
|
||
<el-button @click="cancel">{{ $t('cancel') }}</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getLatestFirmware } from '@/api/iot/firmware';
|
||
|
||
import { serviceInvoke } from '@/api/iot/runstatus';
|
||
import { messagePost } from '@/api/iot/mqttTest';
|
||
import { getDevice, updateDevice } from '@/api/iot/device';
|
||
|
||
export default {
|
||
name: 'running-status',
|
||
props: {
|
||
device: {
|
||
type: Object,
|
||
default: null,
|
||
},
|
||
},
|
||
watch: {
|
||
// 获取到父组件传递的device后,刷新列表
|
||
device: {
|
||
handler(newVal) {
|
||
if (newVal && newVal.deviceId != 0) {
|
||
if (newVal && newVal.deviceId != 0) {
|
||
this.deviceInfo = newVal;
|
||
this.updateDeviceStatus(this.deviceInfo);
|
||
this.$nextTick(function () {
|
||
this.MonitorChart();
|
||
});
|
||
// 添加message事件监听器
|
||
// this.mqttCallback();
|
||
}
|
||
}
|
||
},
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
// 控制模块标题
|
||
title: '设备控制 ',
|
||
// 未启用设备影子
|
||
shadowUnEnable: false,
|
||
// 控制项标题背景
|
||
statusColor: {
|
||
background: '#67C23A',
|
||
color: '#fff',
|
||
minWidth: '100px',
|
||
},
|
||
// 最新固件信息
|
||
firmware: {},
|
||
// 打开固件对话框
|
||
openFirmware: false,
|
||
// 遮罩层
|
||
loading: true,
|
||
// 设备信息
|
||
deviceInfo: {
|
||
boolList: [],
|
||
enumList: [],
|
||
stringList: [],
|
||
integerList: [],
|
||
decimalList: [],
|
||
arrayList: [],
|
||
thingsModels: [],
|
||
chartList: [],
|
||
},
|
||
// 监测图表
|
||
monitorChart: [
|
||
{
|
||
chart: {},
|
||
data: {
|
||
id: '',
|
||
name: '',
|
||
value: '',
|
||
},
|
||
},
|
||
],
|
||
resizeObserver: null,
|
||
remoteCommand: {},
|
||
imeiCmd: 'AT+IMEI\\r\\n',
|
||
iccidCmd: 'AT+ICCID\\r\\n',
|
||
imeiLoading: false,
|
||
iccidLoading: false,
|
||
saveLoadingImei: false,
|
||
saveLoadingIccid: false,
|
||
};
|
||
},
|
||
mounted() {
|
||
const { deviceId, serialNumber } = this.device;
|
||
if (deviceId) {
|
||
this.initDataStatus();
|
||
this.initData();
|
||
}
|
||
window.addEventListener('resize', this.handleResize);
|
||
},
|
||
beforeDestroy() {
|
||
window.removeEventListener('resize', this.handleResize);
|
||
if (this.resizeObserver) {
|
||
this.resizeObserver.disconnect();
|
||
this.resizeObserver = null;
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
async refreshDeviceBaseInfo() {
|
||
if (!this.deviceInfo || !this.deviceInfo.deviceId) return;
|
||
const res = await getDevice(this.deviceInfo.deviceId);
|
||
if (res && res.code === 200 && res.data) {
|
||
// 只刷新这两个字段,避免影响运行态数据
|
||
this.deviceInfo.imei = res.data.imei;
|
||
this.deviceInfo.iccid = res.data.iccid;
|
||
}
|
||
},
|
||
|
||
async sendAt(type) {
|
||
try {
|
||
const cmd = type === 'imei' ? this.imeiCmd : this.iccidCmd;
|
||
if (!cmd) {
|
||
this.$message({ type: 'warning', message: '指令不能为空' });
|
||
return;
|
||
}
|
||
if (type === 'imei') this.imeiLoading = true;
|
||
if (type === 'iccid') this.iccidLoading = true;
|
||
|
||
// FUNCTION_GET 下发链路最终会走 ByteUtils.convertToHexByteArray(command)。
|
||
// 因此这里必须传“十六进制字节串”,而不是原始 ASCII。
|
||
// 同时兼容两种输入:
|
||
// 1) 用户输入 AT+IMEI\\r\\n 这类带转义的 ASCII:会先还原成真实 \r\n,再转 hex(不带空格)
|
||
// 2) 用户直接输入 41 54 ... 或 41542b...:会清理空格后直接使用(后端也会再次清理)
|
||
const trimmed = String(cmd).trim();
|
||
const hexMessage = this.isHexLike(trimmed)
|
||
? this.normalizeHexString(trimmed)
|
||
: this.asciiCommandToHex(this.normalizeAtEscapes(trimmed));
|
||
|
||
await messagePost({
|
||
message: hexMessage,
|
||
serialNumber: this.deviceInfo.serialNumber,
|
||
topicName: '/function/get',
|
||
dataType: 'hex',
|
||
});
|
||
this.$message({ type: 'success', message: '已下发指令,等待设备回复解析…' });
|
||
|
||
// 后端收到回复后会自动解析并写入数据库,这里做一次轻量轮询刷新
|
||
setTimeout(() => this.refreshDeviceBaseInfo(), 1200);
|
||
setTimeout(() => this.refreshDeviceBaseInfo(), 2500);
|
||
} catch (e) {
|
||
this.$message({ type: 'error', message: e && e.message ? e.message : '指令下发失败' });
|
||
} finally {
|
||
this.imeiLoading = false;
|
||
this.iccidLoading = false;
|
||
}
|
||
},
|
||
|
||
normalizeAtEscapes(str) {
|
||
// 输入框里用户通常会键入:AT+IMEI\\r\\n
|
||
// 但你当前的设备/链路期望的不是“真实 CR/LF(0d0a)”,而是把转义串当作占位符:
|
||
// 把 \\r\\n 期望转成 "/r/n"(即字符 '/','r','/','n',十六进制为 2f 72 2f 6e)。
|
||
if (!str) return '';
|
||
return String(str)
|
||
.replace(/\\r\\n/g, '/r/n')
|
||
.replace(/\\r/g, '/r')
|
||
.replace(/\\n/g, '/n');
|
||
},
|
||
|
||
isHexLike(hexStr) {
|
||
if (!hexStr) return false;
|
||
const cleaned = this.normalizeHexString(hexStr);
|
||
if (!cleaned) return false;
|
||
return /^[0-9a-fA-F]+$/.test(cleaned);
|
||
},
|
||
|
||
normalizeHexString(hexStr) {
|
||
if (!hexStr) return '';
|
||
return String(hexStr)
|
||
.trim()
|
||
.replace(/\s+/g, '')
|
||
.replace(/^0x/i, '');
|
||
},
|
||
|
||
asciiCommandToHex(asciiStr) {
|
||
// 把字符串按字节(低8位)转成纯16进制(无空格)
|
||
if (!asciiStr) return '';
|
||
let hex = '';
|
||
for (let i = 0; i < asciiStr.length; i++) {
|
||
const c = asciiStr.charCodeAt(i) & 0xff;
|
||
let hexByte = c.toString(16).toUpperCase();
|
||
if (hexByte.length === 1) hexByte = `0${hexByte}`;
|
||
hex += hexByte;
|
||
}
|
||
return hex;
|
||
},
|
||
|
||
async saveImeiIccid(type) {
|
||
try {
|
||
if (!this.deviceInfo || !this.deviceInfo.deviceId) return;
|
||
if (type === 'imei') this.saveLoadingImei = true;
|
||
if (type === 'iccid') this.saveLoadingIccid = true;
|
||
|
||
const payload = {
|
||
deviceId: this.deviceInfo.deviceId,
|
||
serialNumber: this.deviceInfo.serialNumber,
|
||
};
|
||
if (type === 'imei') payload.imei = this.deviceInfo.imei;
|
||
if (type === 'iccid') payload.iccid = this.deviceInfo.iccid;
|
||
|
||
const res = await updateDevice(payload);
|
||
if (res && res.code === 200) {
|
||
this.$message({ type: 'success', message: '保存成功' });
|
||
await this.refreshDeviceBaseInfo();
|
||
} else {
|
||
this.$message({ type: 'error', message: (res && res.msg) || '保存失败' });
|
||
}
|
||
} catch (e) {
|
||
this.$message({ type: 'error', message: e && e.message ? e.message : '保存失败' });
|
||
} finally {
|
||
this.saveLoadingImei = false;
|
||
this.saveLoadingIccid = false;
|
||
}
|
||
},
|
||
|
||
initResizeObserver() {
|
||
if (this.resizeObserver) return;
|
||
if (typeof window.ResizeObserver !== 'function') return;
|
||
|
||
const el = this.$el && this.$el.querySelector ? this.$el.querySelector('.monitor-grid-wrap') : null;
|
||
if (!el) return;
|
||
|
||
this.resizeObserver = new ResizeObserver(() => {
|
||
this.handleResize();
|
||
});
|
||
this.resizeObserver.observe(el);
|
||
},
|
||
handleResize() {
|
||
this.$nextTick(() => {
|
||
if (!this.monitorChart || !this.monitorChart.length) return;
|
||
this.monitorChart.forEach((item) => {
|
||
if (item && item.chart && typeof item.chart.resize === 'function') {
|
||
item.chart.resize();
|
||
}
|
||
});
|
||
});
|
||
},
|
||
scheduleChartResize() {
|
||
// grid / card 可能会在首次渲染后继续“撑开”,多次兜底 resize,确保不缩在左上角
|
||
this.handleResize();
|
||
setTimeout(() => this.handleResize(), 50);
|
||
setTimeout(() => this.handleResize(), 200);
|
||
setTimeout(() => this.handleResize(), 500);
|
||
},
|
||
initData() {
|
||
// 监听值的实时更新
|
||
this.$busEvent.$on('updateData', (params) => {
|
||
this.updateParam(params);
|
||
});
|
||
},
|
||
initDataStatus() {
|
||
// 监听值的实时更新
|
||
this.$busEvent.$on('updateStatus', (status) => {
|
||
this.updateStatus(status);
|
||
});
|
||
},
|
||
updateStatus(status) {
|
||
let { serialNumber, productId, data } = status;
|
||
if (data) {
|
||
if (this.deviceInfo.serialNumber == status.serialNumber) {
|
||
this.deviceInfo.status = data.status;
|
||
this.deviceInfo.isShadow = data.isShadow;
|
||
this.deviceInfo.rssi = data.rssi;
|
||
this.updateDeviceStatus(this.deviceInfo);
|
||
}
|
||
}
|
||
},
|
||
//更新参数值
|
||
updateParam(params) {
|
||
let { serialNumber, productId, data } = params;
|
||
if (data) {
|
||
data = data.message;
|
||
data.forEach((msg) => {
|
||
this.deviceInfo.thingsModels.some((item, index) => {
|
||
if (msg.id === item.id) {
|
||
const variable = this.deviceInfo.thingsModels[index];
|
||
// 普通类型(小数/整数/字符串/布尔/枚举)
|
||
if (item.datatype.type == 'decimal' || item.datatype.type == 'integer') {
|
||
variable.shadow = Number(msg.value);
|
||
} else {
|
||
variable.shadow = msg.value;
|
||
}
|
||
this.$set(this.deviceInfo.thingsModels, index, variable);
|
||
return true;
|
||
}
|
||
});
|
||
let isComplete = false;
|
||
for (let j = 0; j < data.length; j++) {
|
||
// 设备状态
|
||
for (let k = 0; k < this.deviceInfo.thingsModels.length && !isComplete; k++) {
|
||
if (this.deviceInfo.thingsModels[k].datatype.type == 'object') {
|
||
// 对象类型
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.params.length; n++) {
|
||
if (this.deviceInfo.thingsModels[k].datatype.params[n].id == data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.params[n].shadow = data[j].value;
|
||
isComplete = true;
|
||
break;
|
||
}
|
||
}
|
||
} else if (this.deviceInfo.thingsModels[k].datatype.type == 'array') {
|
||
// 数组类型
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayType == 'object') {
|
||
// 1.对象类型数组,id为数组中一个元素,例如:array_01_gateway_temperature
|
||
if (String(data[j].id).indexOf('array_') == 0) {
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayParams.length; n++) {
|
||
for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype.arrayParams[n].length; m++) {
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].id == data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].shadow = data[j].value;
|
||
isComplete = true;
|
||
break;
|
||
}
|
||
}
|
||
if (isComplete) {
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
// 2.对象类型数组,例如:gateway_temperature,消息ID添加前缀后匹配
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayParams.length; n++) {
|
||
for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype.arrayParams[n].length; m++) {
|
||
let index = n > 9 ? String(n) : '0' + k;
|
||
let prefix = 'array_' + index + '_';
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].id == prefix + data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].shadow = data[j].value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 整数、小数和字符串类型数组
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayModel.length; n++) {
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayModel[n].id == data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.arrayModel[n].shadow = data[j].value;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 图表数据
|
||
for (let k = 0; k < this.deviceInfo.chartList.length; k++) {
|
||
if (this.deviceInfo.chartList[k].id.indexOf('array_') == 0) {
|
||
// 数组类型匹配,例如:array_00_gateway_temperature
|
||
if (this.deviceInfo.chartList[k].id == data[j].id) {
|
||
this.deviceInfo.chartList[k].shadow = data[j].value;
|
||
// 更新图表
|
||
for (let m = 0; m < this.monitorChart.length; m++) {
|
||
if (data[j].id == this.monitorChart[m].data.id) {
|
||
// 更新数据对象
|
||
this.monitorChart[m].data.value = data[j].value;
|
||
|
||
// 确保数值是数字类型
|
||
const numValue = Number(data[j].value) || 0;
|
||
|
||
// 构建图表数据
|
||
const chartData = [
|
||
{
|
||
value: numValue,
|
||
name: this.monitorChart[m].data.name,
|
||
},
|
||
];
|
||
|
||
// 更新图表配置,包括detail部分
|
||
this.monitorChart[m].chart.setOption({
|
||
series: [
|
||
{
|
||
data: chartData,
|
||
detail: {
|
||
formatter: '{value}' + ' ' + (this.deviceInfo.chartList[k].datatype.unit || ''),
|
||
},
|
||
},
|
||
],
|
||
}, false); // false表示不合并,完全替换
|
||
|
||
// 强制刷新图表
|
||
this.$nextTick(() => {
|
||
if (this.monitorChart[m] && this.monitorChart[m].chart) {
|
||
this.monitorChart[m].chart.resize();
|
||
}
|
||
});
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 普通类型匹配
|
||
if (this.deviceInfo.chartList[k].id == data[j].id) {
|
||
this.deviceInfo.chartList[k].shadow = data[j].value;
|
||
// 更新图表
|
||
for (let m = 0; m < this.monitorChart.length; m++) {
|
||
if (data[j].id == this.monitorChart[m].data.id) {
|
||
// 更新数据对象
|
||
this.monitorChart[m].data.value = data[j].value;
|
||
|
||
// 确保数值是数字类型
|
||
const numValue = Number(data[j].value) || 0;
|
||
|
||
// 构建图表数据
|
||
const chartData = [
|
||
{
|
||
value: numValue,
|
||
name: this.monitorChart[m].data.name,
|
||
},
|
||
];
|
||
|
||
// 更新图表配置,包括detail部分
|
||
this.monitorChart[m].chart.setOption({
|
||
series: [
|
||
{
|
||
data: chartData,
|
||
detail: {
|
||
formatter: '{value}' + ' ' + (this.deviceInfo.chartList[k].datatype.unit || ''),
|
||
},
|
||
},
|
||
],
|
||
}, false); // false表示不合并,完全替换
|
||
|
||
// 强制刷新图表
|
||
this.$nextTick(() => {
|
||
if (this.monitorChart[m] && this.monitorChart[m].chart) {
|
||
this.monitorChart[m].chart.resize();
|
||
}
|
||
});
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (isComplete) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
// }
|
||
});
|
||
}
|
||
},
|
||
|
||
//发送指令
|
||
mqttPublish(device, model) {
|
||
const command = {};
|
||
command[model.id] = model.shadow;
|
||
const data = {
|
||
serialNumber: device.serialNumber,
|
||
productId: device.productId,
|
||
remoteCommand: command,
|
||
identifier: model.id,
|
||
modelName: model.name,
|
||
isShadow: device.status != 3,
|
||
type: model.type,
|
||
};
|
||
serviceInvoke(data).then((response) => {
|
||
if (response.code === 200) {
|
||
this.$message({
|
||
type: 'success',
|
||
message: this.$t('device.running-status.866086-25'),
|
||
});
|
||
}
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Mqtt发布消息
|
||
* @device 设备
|
||
* @model 物模型(id/name/type/name/isReadonly/value/shadow),type 类型(1=属性,2=功能,3=OTA升级,4=实时监测)
|
||
* */
|
||
// mqttPublish(device, model) {
|
||
// let topic = "";
|
||
// let message = ""
|
||
// if (model.type == 1) {
|
||
// if (device.status == 3) {
|
||
// // 属性,在线模式
|
||
// topic = "/" + device.productId + "/" + device.serialNumber + "/property-online/get";
|
||
// } else if (device.isShadow) {
|
||
// // 属性,离线模式
|
||
// topic = "/" + device.productId + "/" + device.serialNumber + "/property-offline/post";
|
||
// }
|
||
// message = '[{"id":"' + model.id + '","value":"' + model.shadow + '"}]';
|
||
// } else if (model.type == 2) {
|
||
// if (device.status == 3) {
|
||
// // 功能,在线模式
|
||
// topic = "/" + device.productId + "/" + device.serialNumber + "/function-online/get";
|
||
//
|
||
// } else if (device.isShadow) {
|
||
// // 功能,离线模式
|
||
// topic = "/" + device.productId + "/" + device.serialNumber + "/function-offline/post";
|
||
// }
|
||
// message = '[{"id":"' + model.id + '","value":"' + model.shadow + '"}]';
|
||
// } else if (model.type == 3) {
|
||
// // OTA升级
|
||
// topic = "/" + device.productId + "/" + device.serialNumber + "/ota/get";
|
||
// message = '{"version":' + this.firmware.version + ',"downloadUrl":"' + this.getDownloadUrl(this.firmware.filePath) + '"}';
|
||
// } else {
|
||
// return;
|
||
// }
|
||
// if (topic != "") {
|
||
// // 发布
|
||
// this.$mqttTool.publish(topic, message, model.name).then(res => {
|
||
// this.$modal.notifySuccess(res);
|
||
// }).catch(res => {
|
||
// this.$modal.notifyError(res);
|
||
// });
|
||
// }
|
||
// },
|
||
|
||
/** 枚举类型按钮单击 */
|
||
enumButtonClick(device, model, value) {
|
||
model.shadow = value;
|
||
this.mqttPublish(device, model);
|
||
},
|
||
|
||
/** 更新设备状态 */
|
||
updateDeviceStatus(device) {
|
||
if (device.status == 3) {
|
||
this.statusColor.background = '#12d09f';
|
||
this.title = this.$t('device.running-status.866086-26');
|
||
this.shadowUnEnable = false;
|
||
} else {
|
||
if (device.isShadow == 1) {
|
||
this.statusColor.background = '#409EFF';
|
||
this.title = this.$t('device.running-status.866086-27');
|
||
} else {
|
||
this.statusColor.background = '#909399';
|
||
this.title = this.$t('device.running-status.866086-28');
|
||
this.shadowUnEnable = true;
|
||
}
|
||
}
|
||
this.$emit('statusEvent', this.deviceInfo.status);
|
||
},
|
||
/** 物模型数组元素值改变事件 */
|
||
arrayItemChange(value, thingsModel) {
|
||
let shadow = '';
|
||
for (let i = 0; i < thingsModel.datatype.arrayCount; i++) {
|
||
shadow += thingsModel.datatype.arrayModel[i].shadow + ',';
|
||
}
|
||
shadow = shadow.substring(0, shadow.length - 1);
|
||
thingsModel.shadow = shadow;
|
||
},
|
||
/** 物模型中数组值改变事件 */
|
||
arrayInputChange(value, thingsModel) {
|
||
let arrayModels = value.split(',');
|
||
if (arrayModels.length != thingsModel.datatype.arrayCount) {
|
||
this.$modal.alertWarning(this.$t('device.running-status.866086-29') + thingsModel.datatype.arrayCount + this.$t('device.running-status.866086-30'));
|
||
} else {
|
||
for (let i = 0; i < thingsModel.datatype.arrayCount; i++) {
|
||
thingsModel.datatype.arrayModel[i].shadow = arrayModels[i];
|
||
}
|
||
}
|
||
},
|
||
/**用户是否拥有分享设备权限*/
|
||
// hasShrarePerm(permission) {
|
||
// if (this.deviceInfo.isOwner == 0) {
|
||
// // 分享设备权限
|
||
// if (this.deviceInfo.userPerms.indexOf(permission) == -1) {
|
||
// return false;
|
||
// }
|
||
// }
|
||
// return true;
|
||
// },
|
||
/** 设备升级 */
|
||
otaUpgrade() {
|
||
// OTA升级
|
||
let topic = '/' + this.deviceInfo.productId + '/' + this.deviceInfo.serialNumber + '/ota/get';
|
||
let message = '{"version":' + this.firmware.version + ',"downloadUrl":"' + this.getDownloadUrl(this.firmware.filePath) + '"}';
|
||
// 发布
|
||
this.$mqttTool
|
||
.publish(topic, message, this.$t('device.running-status.866086-31'))
|
||
.then((res) => {
|
||
this.$modal.notifySuccess(res);
|
||
})
|
||
.catch((res) => {
|
||
this.$modal.notifyError(res);
|
||
});
|
||
this.openFirmware = false;
|
||
},
|
||
/** 获取最新固件 */
|
||
getLatestFirmware(deviceId) {
|
||
getLatestFirmware(deviceId).then((response) => {
|
||
this.firmware = response.data;
|
||
this.openFirmware = true;
|
||
});
|
||
},
|
||
// 取消按钮
|
||
cancel() {
|
||
this.openFirmware = false;
|
||
},
|
||
// 获取下载路径前缀
|
||
getDownloadUrl(path) {
|
||
return window.location.origin + process.env.VUE_APP_BASE_API + path;
|
||
},
|
||
/**图表展示*/
|
||
MonitorChart() {
|
||
// 释放旧图表实例,避免重复 init 导致内存泄漏
|
||
if (this.monitorChart && this.monitorChart.length) {
|
||
this.monitorChart.forEach((item) => {
|
||
if (item && item.chart && typeof item.chart.dispose === 'function') {
|
||
item.chart.dispose();
|
||
}
|
||
});
|
||
}
|
||
for (let i = 0; i < this.deviceInfo.chartList.length; i++) {
|
||
this.monitorChart[i] = {
|
||
chart: this.$echarts.init(this.$refs.map[i]),
|
||
data: {
|
||
id: this.deviceInfo.chartList[i].id,
|
||
name: this.deviceInfo.chartList[i].name,
|
||
value: this.deviceInfo.chartList[i].shadow ? this.deviceInfo.chartList[i].shadow : this.deviceInfo.chartList[i].datatype.min,
|
||
},
|
||
};
|
||
var option;
|
||
option = {
|
||
tooltip: {
|
||
formatter: ' {b} <br/> {c}' + this.deviceInfo.chartList[i].datatype.unit,
|
||
},
|
||
series: [
|
||
{
|
||
name: this.deviceInfo.chartList[i].datatype.type,
|
||
type: 'gauge',
|
||
min: this.deviceInfo.chartList[i].datatype.min,
|
||
max: this.deviceInfo.chartList[i].datatype.max,
|
||
colorBy: 'data',
|
||
splitNumber: 10,
|
||
// 留一点空间给数值和标题,视觉上更“占满”
|
||
radius: '95%',
|
||
center: ['50%', '55%'],
|
||
// 分割线
|
||
splitLine: {
|
||
distance: 4,
|
||
},
|
||
axisLabel: {
|
||
fontSize: 10,
|
||
distance: 10,
|
||
},
|
||
// 刻度线
|
||
axisTick: {
|
||
distance: 4,
|
||
},
|
||
// 仪表盘轴线
|
||
axisLine: {
|
||
lineStyle: {
|
||
width: 8,
|
||
color: [
|
||
[0.2, '#409EFF'], // 0~20%
|
||
[0.8, '#12d09f'], // 40~60%
|
||
[1, '#F56C6C'], // 80~100%
|
||
],
|
||
opacity: 0.3,
|
||
},
|
||
},
|
||
pointer: {
|
||
icon: 'triangle',
|
||
length: '60%',
|
||
width: 7,
|
||
},
|
||
progress: {
|
||
show: true,
|
||
width: 8,
|
||
},
|
||
detail: {
|
||
valueAnimation: true,
|
||
formatter: '{value}' + ' ' + this.deviceInfo.chartList[i].datatype.unit,
|
||
offsetCenter: [0, '68%'],
|
||
fontSize: 22,
|
||
},
|
||
data: [
|
||
{
|
||
value: this.deviceInfo.chartList[i].shadow ? this.deviceInfo.chartList[i].shadow : this.deviceInfo.chartList[i].datatype.min,
|
||
name: this.deviceInfo.chartList[i].name,
|
||
},
|
||
],
|
||
title: {
|
||
offsetCenter: [0, '88%'],
|
||
fontSize: 16,
|
||
},
|
||
},
|
||
],
|
||
};
|
||
option && this.monitorChart[i].chart.setOption(option);
|
||
// grid/card 尺寸变化时,强制适配容器尺寸,避免表盘“缩在左上角”
|
||
this.monitorChart[i].chart.resize();
|
||
}
|
||
// 监听容器尺寸变化 + 多次兜底 resize:确保布局稳定后都 resize 到位
|
||
this.$nextTick(() => {
|
||
this.initResizeObserver();
|
||
this.scheduleChartResize();
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style>
|
||
.running-status {
|
||
padding-left: 20px;
|
||
}
|
||
|
||
.cell-flex {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.atcmd-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.monitor-grid-wrap {
|
||
background-color: #f5f7fa;
|
||
padding: 20px 10px;
|
||
border-radius: 15px;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.monitor-grid {
|
||
display: grid;
|
||
/* 默认:中等屏幕 3 列(避免媒体查询未命中时“没效果”) */
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
/* 让宽屏更“和谐”:优先 4 列(内容区一般达不到 1600px,这里下调阈值) */
|
||
@media (min-width: 1400px) {
|
||
.monitor-grid {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1100px) and (max-width: 1399px) {
|
||
.monitor-grid {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (min-width: 800px) and (max-width: 1099px) {
|
||
.monitor-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 799px) {
|
||
.monitor-grid {
|
||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
.monitor-card {
|
||
border-radius: 18px;
|
||
}
|
||
|
||
.monitor-card .el-card__body {
|
||
padding: 8px;
|
||
}
|
||
|
||
.monitor-chart {
|
||
height: 320px;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 重写滑动块样式 */
|
||
.el-slider__bar {
|
||
height: 18px;
|
||
}
|
||
|
||
.el-slider__runway {
|
||
height: 18px;
|
||
margin: 5px 0;
|
||
}
|
||
|
||
.el-slider__button {
|
||
height: 18px;
|
||
width: 18px;
|
||
border-radius: 10%;
|
||
}
|
||
|
||
.el-slider__button-wrapper {
|
||
top: -9px;
|
||
}
|
||
|
||
.is-active-btn {
|
||
color: #1890ff;
|
||
border-color: #badeff;
|
||
background-color: #e8f4ff;
|
||
}
|
||
</style>
|