logo替换以及显卡页面图片

This commit is contained in:
1 2025-07-03 08:59:20 +08:00
parent bf9ffb7bca
commit a3a25ae99f
60 changed files with 1121 additions and 28 deletions

View File

@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 芯程物联网系统 VUE_APP_TITLE = 信安智能
# 开发环境配置 # 开发环境配置
ENV = 'development' ENV = 'development'

View File

@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 芯程物联云系统 VUE_APP_TITLE = 信安智能
# 生产环境配置 # 生产环境配置
ENV = 'production' ENV = 'production'

View File

@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 芯程物联 VUE_APP_TITLE = 信安智能
NODE_ENV = production NODE_ENV = production

BIN
dist.zip

Binary file not shown.

View File

@ -1,7 +1,7 @@
{ {
"name": "fastbee", "name": "fastbee",
"version": "2.5.2", "version": "2.5.2",
"description": "芯程物联", "description": "信安智能",
"author": "kerwincui", "author": "kerwincui",
"license": "AGPL3.0", "license": "AGPL3.0",
"scripts": { "scripts": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

View File

@ -53,7 +53,7 @@ export default {
if (data.logoName) { if (data.logoName) {
this.title = data.logoName.length > 8 ? data.logoName.slice(0, 8) : data.logoName; this.title = data.logoName.length > 8 ? data.logoName.slice(0, 8) : data.logoName;
} else { } else {
this.title = '芯程物联'; this.title = '信安智能';
} }
if (data.deptLogo) { if (data.deptLogo) {
this.logo = this.baseUrl + data.deptLogo; this.logo = this.baseUrl + data.deptLogo;

View File

@ -195,6 +195,8 @@
ref="voicecard" :device="form" @statusEvent="getDeviceStatusData($event)" /> ref="voicecard" :device="form" @statusEvent="getDeviceStatusData($event)" />
<display v-else-if="form.productName && form.productName.toLowerCase().includes('显卡产品')" <display v-else-if="form.productName && form.productName.toLowerCase().includes('显卡产品')"
ref="display" :device="form" @statusEvent="getDeviceStatusData($event)" /> ref="display" :device="form" @statusEvent="getDeviceStatusData($event)" />
<gateway v-else-if="form.productName && form.productName.toLowerCase().includes('网关')"
ref="gateway" :device="form" @statusEvent="getDeviceStatusData($event)" />
<running-status v-else ref="runningStatus" :device="form" <running-status v-else ref="runningStatus" :device="form"
@statusEvent="getDeviceStatusData($event)" /> @statusEvent="getDeviceStatusData($event)" />
@ -462,6 +464,7 @@ import gatewaypre from './gatewaypre.vue'
import acousto_optic from'./acousto_optic.vue' import acousto_optic from'./acousto_optic.vue'
import voicecard from './voicecard.vue'; import voicecard from './voicecard.vue';
import display from './display.vue' import display from './display.vue'
import gateway from './gateway.vue'
export default { export default {
name: 'DeviceEdit', name: 'DeviceEdit',
dicts: ['iot_device_status', 'iot_location_way'], dicts: ['iot_device_status', 'iot_location_way'],
@ -498,7 +501,8 @@ export default {
gatewaypre, gatewaypre,
acousto_optic, acousto_optic,
voicecard, voicecard,
display display,
gateway
}, },
watch: { watch: {
activeName(val) { activeName(val) {

View File

@ -266,17 +266,17 @@
$t('device.running-status.866086-12') }}</el-link> $t('device.running-status.866086-12') }}</el-link>
</template> </template>
<el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName <el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName <el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version <el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item :label="$t('device.running-status.866086-16')"> <el-descriptions-item :label="$t('device.running-status.866086-16')">
<el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{ <el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{
getDownloadUrl(firmware.filePath) }}</el-link> getDownloadUrl(firmware.filePath) }}</el-link>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark <el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark
}}</el-descriptions-item> }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="success" @click="otaUpgrade" <el-button type="success" @click="otaUpgrade"
@ -447,8 +447,30 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="19" v-if="zone.playType === 1"> <el-col :span="19" v-if="zone.playType === 1">
<el-form-item label="图片路径" label-width="70px"> <el-form-item label="图片选择" label-width="70px">
<el-input v-model="zone.image" placeholder="请输入图片路径或选择图片" style="width:100%" /> <el-select v-model="zone.image" placeholder="请选择图片" style="width:100%">
<el-option v-for="item in addProgramTrafficImages" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 新增图片尺寸和颜色 -->
<el-row :gutter="12" v-if="zone.playType === 1" style="margin-bottom:2px;">
<el-col :span="8">
<el-form-item label="图片尺寸" label-width="70px">
<el-select v-model="zone.imageSize" placeholder="请选择尺寸" style="width:100%">
<el-option v-for="(item, idx) in addProgramImageSizes" :key="idx" :label="item"
:value="idx" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="图片颜色" label-width="70px">
<el-select v-model="zone.imageColor" placeholder="请选择颜色" style="width:100%">
<el-option v-for="(item, idx) in addProgramImageColors" :key="idx" :label="item"
:value="idx" />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -843,6 +865,8 @@ export default {
fontBold: 0, fontBold: 0,
fontStretch: 0, fontStretch: 0,
image: '', image: '',
imageSize: 1, // 32x32
imageColor: 0, // Red
effect: 0, effect: 0,
speed: 4, speed: 4,
stayTime: 5, stayTime: 5,
@ -884,15 +908,19 @@ export default {
addProgramStayTimes: ['不停顿', '100毫秒', '500毫秒', '1秒', '2秒', '3秒', '4秒', '5秒', '6秒', '8秒', '10秒'], addProgramStayTimes: ['不停顿', '100毫秒', '500毫秒', '1秒', '2秒', '3秒', '4秒', '5秒', '6秒', '8秒', '10秒'],
addProgramHorizontalAlign: ['居左对齐', '居中对齐', '居右对齐'], addProgramHorizontalAlign: ['居左对齐', '居中对齐', '居右对齐'],
addProgramVerticalAlign: ['顶部对齐', '居中对齐', '底部对齐'], addProgramVerticalAlign: ['顶部对齐', '居中对齐', '底部对齐'],
addProgramImageSizes: ['16x16', '32x32', '48x48', '64x64'],
addProgramImageColors: ['红色', '绿色', '黄色'],
// //
addProgramPreviewPage: [0], // addProgramPreviewPage: [0], //
addProgramPreviewTimer: [], // addProgramPreviewTimer: [], //
addProgramPreviewAssets: [], // addProgramPreviewAssets: [], //
addProgramPreviewAnimState: [], // addProgramPreviewAnimState: [], //
addProgramPreviewAnimFrame: null, // ID addProgramPreviewAnimFrame: null, // ID
addProgramTrafficImages: [],
}; };
}, },
mounted() { mounted() {
this.loadTrafficImages();
if (this.device && this.device.deviceId) { if (this.device && this.device.deviceId) {
this.handleDeviceChange(this.device); this.handleDeviceChange(this.device);
this.initDataStatus(); this.initDataStatus();
@ -905,6 +933,22 @@ export default {
}, },
methods: { methods: {
loadTrafficImages() {
try {
// 使webpackrequire.context
const files = require.context('@/assets/traffic', false, /\.(png|jpe?g|svg|bmp)$/);
this.addProgramTrafficImages = files.keys().map(key => {
const label = key.replace('./', '');
return {
label: label, // : '1.png'
value: files(key) // Webpack
};
});
} catch (e) {
console.error("加载交通标志图片失败, 请确认 'src/assets/traffic' 目录下有图片文件。", e);
this.addProgramTrafficImages = [];
}
},
// //
async mqttPublish(device, model) { async mqttPublish(device, model) {
const command = {}; const command = {};
@ -2245,6 +2289,8 @@ export default {
fontBold: 0, fontBold: 0,
fontStretch: 0, fontStretch: 0,
image: '', image: '',
imageSize: 1,
imageColor: 0,
effect: 0, effect: 0,
speed: 4, speed: 4,
stayTime: 5, stayTime: 5,
@ -2277,6 +2323,8 @@ export default {
fontBold: 0, fontBold: 0,
fontStretch: 0, fontStretch: 0,
image: '', image: '',
imageSize: 1,
imageColor: 0,
effect: 0, effect: 0,
speed: 4, speed: 4,
stayTime: 5, stayTime: 5,
@ -2486,16 +2534,80 @@ export default {
ctx.fillText(line, startX + animOffsetX, startY + lineIdx * lineHeight + animOffsetY); ctx.fillText(line, startX + animOffsetX, startY + lineIdx * lineHeight + animOffsetY);
}); });
} else if (zone.playType === 1) { } else if (zone.playType === 1 && asset.isImage) {
// //
ctx.fillStyle = '#222'; if (asset.loaded && asset.img.complete) {
ctx.fillRect(zoneX + 4, zoneY + 4, zoneWidth - 8, zoneHeight - 8); const img = asset.img;
ctx.fillStyle = '#fff'; //
ctx.font = `${Math.round(12 * scale)}px Arial`; const sizeValues = [16, 32, 48, 64];
ctx.textAlign = 'center'; const targetSize = sizeValues[zone.imageSize] || 32;
ctx.textBaseline = 'middle';
ctx.fillText('图片', zoneX + zoneWidth / 2, zoneY + zoneHeight / 2); //
let newWidth, newHeight;
if (img.width > img.height) {
newWidth = targetSize;
newHeight = (img.height / img.width) * targetSize;
} else {
newWidth = (img.width / img.height) * targetSize;
newHeight = targetSize;
}
//
const tempCanvas = document.createElement('canvas');
tempCanvas.width = newWidth;
tempCanvas.height = newHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
//
tempCtx.drawImage(img, 0, 0, newWidth, newHeight);
//
const imageData = tempCtx.getImageData(0, 0, newWidth, newHeight);
const data = imageData.data;
const colors = [[255, 0, 0], [0, 255, 0], [255, 255, 0]]; // Red, Green, Yellow
const selectedColor = colors[zone.imageColor];
if (selectedColor) {
for (let i = 0; i < data.length; i += 4) {
// 线使
const grayscale = (data[i] + data[i + 1] + data[i + 2]) / 3;
const isLine = grayscale < 128 && data[i + 3] > 50;
if (isLine) {
// 线
data[i] = selectedColor[0]; // R
data[i + 1] = selectedColor[1]; // G
data[i + 2] = selectedColor[2]; // B
data[i + 3] = 255; // 线
} else {
//
data[i + 3] = 0;
}
}
}
//
tempCtx.putImageData(imageData, 0, 0);
//
const finalWidth = newWidth * scale;
const finalHeight = newHeight * scale;
const dX = zoneX + (zoneWidth - finalWidth) / 2;
const dY = zoneY + (zoneHeight - finalHeight) / 2;
//
ctx.drawImage(tempCanvas, dX, dY, finalWidth, finalHeight);
} else {
// Image not loaded yet, draw placeholder
ctx.fillStyle = '#222';
ctx.fillRect(zoneX + 4, zoneY + 4, zoneWidth - 8, zoneHeight - 8);
ctx.fillStyle = '#fff';
ctx.font = `${Math.round(12 * scale)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('加载中...', zoneX + zoneWidth / 2, zoneY + zoneHeight / 2);
}
} }
// //
@ -2510,6 +2622,21 @@ export default {
const scale = Math.min(renderWidth / 32, 120 / 64); const scale = Math.min(renderWidth / 32, 120 / 64);
this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => { this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => {
if (zone.playType === 1 && zone.image) {
//
const img = new window.Image();
img.crossOrigin = 'Anonymous';
const asset = { img, isImage: true, loaded: false };
img.onload = () => {
asset.loaded = true;
this.drawAddProgramPreview();
};
img.onerror = (e) => {
console.error("Preview image failed to load:", e);
};
img.src = zone.image;
return asset;
}
if (zone.playType !== 0 || !zone.displayText) { if (zone.playType !== 0 || !zone.displayText) {
return { pages: [[]], isText: false }; return { pages: [[]], isText: false };
} }

View File

@ -0,0 +1,962 @@
<template>
<div class="gateway-container">
<!-- 网关设备状态头部 -->
<el-row :gutter="20" class="gateway-header">
<el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="14">
<el-card class="status-card" shadow="hover">
<div class="status-header">
<div class="status-icon-wrapper">
<i class="el-icon-monitor"></i>
<div class="status-indicator" :class="getStatusClass()"></div>
</div>
<span class="status-title">网关设备状态</span>
</div>
<div class="status-content">
<span class="title" :style="{ color: statusColor.background }">{{ title }}</span>
<el-button type="text" @click="printThingsModels" style="margin-left: 10px" class="print-btn">
<i class="el-icon-printer"></i> 打印物模型
</el-button>
</div>
<!-- <div class="status-details">
<div class="detail-item">
<span class="label">设备ID:</span>
<span class="value">{{ deviceInfo.deviceId || 'N/A' }}</span>
</div>
<div class="detail-item">
<span class="label">序列号:</span>
<span class="value">{{ deviceInfo.serialNumber || 'N/A' }}</span>
</div>
<div class="detail-item">
<span class="label">信号强度:</span>
<span class="value">{{ deviceInfo.rssi || 0 }} dBm</span>
</div>
</div> -->
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<el-card class="ota-card" shadow="hover">
<div class="ota-header">
<div class="ota-icon-wrapper">
<svg-icon icon-class="ota" />
<div class="update-indicator"></div>
</div>
<span class="ota-title">设备升级</span>
</div>
<div class="ota-content">
<el-button type="primary" size="mini" :plain="true" @click="viewVersion()" class="version-btn">
<i class="el-icon-refresh"></i> 查看版本
</el-button>
</div>
<!-- <div class="ota-info">
<div class="info-item">
<span class="label">固件版本:</span>
<span class="value">{{ deviceInfo.firmwareVersion || 'N/A' }}</span>
</div>
<div class="info-item">
<span class="label">无线版本:</span>
<span class="value">{{ deviceInfo.wirelessVersion || 'N/A' }}</span>
</div>
</div> -->
</el-card>
</el-col>
</el-row>
<!-- 子设备标题 -->
<el-row :gutter="20" style="margin-bottom: 20px;">
<el-col :span="24">
<div class="sub-devices-title">
<i class="el-icon-connection"></i>
<span>子设备管理</span>
</div>
</el-col>
</el-row>
<!-- 子产品Tab标签页 -->
<el-card class="sub-products-card" shadow="hover">
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
<el-tab-pane label="显示屏控制" name="display">
<div class="tab-content">
<display-component :device="device" ref="displayComponent" v-if="activeTab === 'display'">
</display-component>
</div>
</el-tab-pane>
<el-tab-pane label="声卡控制" name="voicecard">
<div class="tab-content">
<voicecard-component :device="device" ref="voicecardComponent" v-if="activeTab === 'voicecard'">
</voicecard-component>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 固件版本查看对话框 -->
<el-dialog :title="$t('device.running-status.866086-10')" :visible.sync="openVersion" width="550px"
append-to-body>
<el-form ref="firmwareForm" label-width="100px" :model="firmwareParams" :inline="true" :rules="rules">
<el-form-item :label="$t('device.running-status.866086-38')" prop="firmwareType">
<el-select v-model="deviceInfo.firmwareType" :placeholder="$t('firmware.index.222541-51')"
style="width: 350px" disabled>
<el-option v-for="item in firmwareTypeList" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('device.running-status.866086-39')" prop="">
<el-input :placeholder="$t('device.running-status.866086-40')" v-model="deviceInfo.firmwareVersion"
style="width: 350px" disabled>
<template slot="prepend">Version</template>
</el-input>
</el-form-item>
</el-form>
<div slot="footer">
<el-tooltip effect="dark" :content="$t('device.running-status.866086-41')" placement="top-start">
<el-button type="primary" @click="getLatestFirmware" :disabled="device.status !== 3">{{
$t('device.running-status.866086-42') }}</el-button>
</el-tooltip>
<el-button @click="cancel1">{{ $t('cancel') }}</el-button>
</div>
</el-dialog>
<!-- 添加或修改产品固件对话框 -->
<el-dialog :title="$t('device.running-status.866086-10')" :visible.sync="openFirmware" width="600px"
append-to-body>
<div v-if="!firmware" style="text-align: center; font-size: 16px">
<i class="el-icon-success" style="color: #67c23a"></i>
{{ $t('device.running-status.866086-11') }}
</div>
<div v-else-if="!compareVersions(deviceInfo.firmwareVersion, firmware.version)"
style="text-align: center; font-size: 16px">
<i class="el-icon-warning" style="color: #E6A23C"></i>
{{ $t('device.running-status.866086-47') }}
</div>
<el-descriptions v-else :column="1" border size="large"
: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 && compareVersions(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, serviceInvokeReply } from '@/api/iot/runstatus';
import { getOrderControl } from '@/api/iot/control';
import DisplayComponent from './display.vue';
import VoicecardComponent from './voicecard.vue';
export default {
name: 'gateway',
components: {
DisplayComponent,
VoicecardComponent
},
props: {
device: {
type: Object,
default: null,
},
},
data() {
return {
activeTab: 'display', //
title: '网关设备控制',
shadowUnEnable: false,
statusColor: {
background: '#67C23A',
color: '#fff',
maxWidth: '200px',
},
firmware: {},
openFirmware: false,
loading: true,
deviceInfo: {
deviceId: 0,
serialNumber: '',
productId: '',
productName: '',
status: 0,
isShadow: 0,
rssi: 0,
firmwareVersion: '',
wirelessVersion: '',
firmwareType: 1,
protocolCode: '',
thingsModels: [],
chartList: [],
},
firmwareParams: {
firmwareType: '',
versionInput: '',
},
openVersion: false,
firmwareTypeList: [
{
label: this.$t('firmware.index.222541-52'),
value: 1,
},
{
label: 'HTTP',
value: 2,
},
],
rules: {
firmwareType: [
{
required: true,
message: this.$t('device.running-status.866086-43'),
trigger: 'blur',
},
],
},
};
},
watch: {
device: {
handler(newVal) {
if (newVal && newVal.deviceId != 0) {
this.deviceInfo = newVal;
this.updateDeviceStatus(this.deviceInfo);
console.log("网关物模型", JSON.stringify(this.deviceInfo.thingsModels));
if (this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.length > 0) {
this.deviceInfo.thingsModels = this.device.thingsModels.sort((a, b) => b.order - a.order);
}
if (this.deviceInfo.chartList && this.deviceInfo.chartList.length > 0) {
this.deviceInfo.chartList = this.deviceInfo.chartList.sort((a, b) => b.order - a.order);
}
}
},
},
},
mounted() {
if (this.device && this.device.deviceId) {
this.handleDeviceChange(this.device);
this.initDataStatus();
this.initData();
}
},
methods: {
// Tab
handleTabClick(tab) {
console.log('切换到:', tab.name);
//
},
//
printThingsModels() {
console.log('当前网关物模型数据:', JSON.stringify(this.deviceInfo.thingsModels, null, 2));
this.$message({
message: '网关物模型数据已打印到控制台',
type: 'success'
});
},
//
handleDeviceChange(device) {
if (device && device.deviceId != 0) {
const { firmwareVersion, wirelessVersion, firmwareType, ...res } = device;
const data = {
version: firmwareType === 1 ? firmwareVersion : wirelessVersion,
firmwareType,
...res,
};
this.deviceInfo = data;
this.updateDeviceStatus(this.deviceInfo);
}
},
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 = '#486FF2';
this.title = this.$t('device.running-status.866086-27');
this.shadowUnEnable = false;
} else {
this.statusColor.background = '#909399';
this.title = this.$t('device.running-status.866086-28');
this.shadowUnEnable = true;
}
}
this.$emit('statusEvent', this.deviceInfo.status);
},
//
viewVersion() {
this.openVersion = true;
this.firmwareParams.firmwareType = 1;
this.firmwareParams.versionInput = '';
this.handleVersionInputChange();
},
handleVersionInputChange() {
if (this.firmwareParams.firmwareType == 1) {
this.firmwareParams.versionInput = 'Version' + this.device.firmwareVersion;
} else {
this.firmwareParams.versionInput = 'Version' + this.device.wirelessVersion;
}
},
cancel1() {
this.openVersion = false;
},
compareVersions(version1, version2) {
version1 = String(version1).replace('Version', '').trim();
version2 = String(version2).replace('Version', '').trim();
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
const v1 = v1Parts[i] || 0;
const v2 = v2Parts[i] || 0;
if (v1 < v2) return true;
if (v1 > v2) return false;
}
return false;
},
getLatestFirmware() {
const { deviceId, firmwareType } = this.deviceInfo;
if (!deviceId || !firmwareType) {
this.$message.error('设备信息不完整');
return;
}
getLatestFirmware(deviceId, firmwareType).then((response) => {
if (response.code === 200) {
if (response.data) {
this.firmware = response.data;
console.log('当前版本:', this.deviceInfo.firmwareVersion);
console.log('新版本:', this.firmware.version);
console.log('版本比较结果:', this.compareVersions(this.deviceInfo.firmwareVersion, this.firmware.version));
this.openFirmware = true;
} else {
this.$message.info('当前已是最新版本');
this.openFirmware = true;
}
} else {
this.$message.error(response.msg || '获取固件信息失败');
}
}).catch(error => {
console.error('获取固件信息失败:', error);
this.$message.error('获取固件信息失败');
});
},
otaUpgrade() {
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;
},
cancel() {
this.openFirmware = false;
},
getDownloadUrl(path) {
return window.location.origin + process.env.VUE_APP_BASE_API + path;
},
initData() {
this.$busEvent.$on('updateData', (params) => {
this.updateParam(params);
});
},
initDataStatus() {
this.$busEvent.$on('updateStatus', (status) => {
this.updateStatus(status);
});
},
updateParam(params) {
let { serialNumber, productId, data } = params;
let isComplete = false;
data = data.message;
if (data) {
for (let j = 0; j < data.length; j++) {
for (let k = 0; k < this.deviceInfo.thingsModels.length && !isComplete; k++) {
if (this.deviceInfo.thingsModels[k].id == data[j].id) {
const variable = this.deviceInfo.thingsModels[k];
if (this.deviceInfo.thingsModels[k].datatype.type == 'decimal' || this.deviceInfo.thingsModels[k].datatype.type == 'integer') {
variable.shadow = Number(data[j].value);
} else {
variable.shadow = data[j].value;
}
}
// ...
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') {
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 {
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;
}
}
}
}
}
}
}
},
updateStatus(status) {
let { serialNumber, productId, data } = status;
if (data) {
if (this.deviceInfo.serialNumber == serialNumber) {
this.deviceInfo.status = data.status;
this.deviceInfo.isShadow = data.isShadow;
this.deviceInfo.rssi = data.rssi;
this.updateDeviceStatus(this.deviceInfo);
}
}
},
getStatusClass() {
if (this.deviceInfo.status == 3) {
return 'status-online';
} else if (this.deviceInfo.isShadow == 1) {
return 'status-shadow';
} else {
return 'status-offline';
}
},
},
};
</script>
<style lang="scss" scoped>
.gateway-container {
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
.gateway-header {
margin-bottom: 30px;
padding: 20px;
.status-card,
.ota-card {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: none;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.9);
&:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.status-header,
.ota-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 0 5px;
.status-icon-wrapper,
.ota-icon-wrapper {
position: relative;
margin-right: 12px;
i,
.svg-icon {
font-size: 24px;
color: #ffffff;
background: linear-gradient(45deg, #409EFF, #67C23A);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.status-indicator,
.update-indicator {
position: absolute;
top: -2px;
right: -2px;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid #fff;
animation: pulse 2s infinite;
}
.status-indicator {
&.status-online {
background: #67C23A;
box-shadow: 0 0 10px rgba(103, 194, 58, 0.5);
}
&.status-shadow {
background: #E6A23C;
box-shadow: 0 0 10px rgba(230, 162, 60, 0.5);
}
&.status-offline {
background: #F56C6C;
box-shadow: 0 0 10px rgba(245, 108, 108, 0.5);
}
}
.update-indicator {
background: #409EFF;
box-shadow: 0 0 10px rgba(64, 158, 255, 0.5);
}
}
.status-title,
.ota-title {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
letter-spacing: 0.5px;
}
}
.status-content,
.ota-content {
text-align: center;
padding: 15px 0;
.title {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin-right: 15px;
}
.el-button {
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
&.print-btn {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
border: none;
padding: 8px 16px;
&:hover {
background: linear-gradient(45deg, #5a6fd8, #6a4190);
}
}
&.version-btn {
background: linear-gradient(45deg, #409EFF, #67C23A);
color: white;
border: none;
padding: 8px 16px;
&:hover {
background: linear-gradient(45deg, #337ecc, #5daf34);
}
}
}
}
.status-details,
.ota-info {
margin-top: 15px;
padding: 15px;
background: rgba(255, 255, 255, 0.5);
border-radius: 8px;
border-left: 4px solid #409EFF;
animation: slideInRight 0.6s ease-out 0.5s both;
.detail-item,
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 4px 0;
transition: all 0.3s ease;
&:hover {
background: rgba(64, 158, 255, 0.1);
border-radius: 4px;
padding-left: 8px;
padding-right: 8px;
}
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.value {
font-size: 14px;
color: #2c3e50;
font-weight: 600;
}
}
}
}
}
.sub-devices-title {
background: #ffffff;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
margin-bottom: 20px;
border-left: 4px solid #409EFF;
i {
font-size: 24px;
color: #409EFF;
margin-right: 12px;
}
span {
font-size: 20px;
font-weight: 600;
color: #2c3e50;
letter-spacing: 0.5px;
}
}
.sub-products-card {
border: none;
border-radius: 20px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.95);
overflow: hidden;
.tab-content {
min-height: 500px;
padding: 30px 0;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
// Tab
::v-deep .el-tabs__header {
margin-bottom: 0;
background: #ffffff;
padding: 0 20px;
.el-tabs__nav-wrap {
&::after {
display: none;
}
}
.el-tabs__item {
font-size: 16px;
font-weight: 500;
padding: 0 35px;
// height: 50px;
// line-height: 50px;
border: 1px solid #e4e7ed;
border-bottom: none;
background: #f5f7fa;
color: #606266;
transition: all 0.3s ease;
position: relative;
&:hover {
color: #409EFF;
background: #ecf5ff;
}
&.is-active {
background: #ffffff;
color: #409EFF;
border-bottom: 2px solid #409EFF;
}
}
.el-tabs__active-bar {
background: #409EFF;
height: 2px;
}
}
::v-deep .el-tabs__content {
padding: 0;
background: transparent;
border: none;
}
}
//
&::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
z-index: 1000;
}
}
//
@media (max-width: 768px) {
.gateway-container {
padding: 15px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
.gateway-header {
margin-bottom: 20px;
.status-card,
.ota-card {
margin-bottom: 15px;
border-radius: 12px;
.status-header,
.ota-header {
.status-title,
.ota-title {
font-size: 16px;
}
}
.status-content,
.ota-content {
.title {
font-size: 16px;
}
}
}
}
.sub-products-card {
border-radius: 16px;
::v-deep .el-tabs__header {
padding: 0 10px;
.el-tabs__item {
padding: 0 20px;
font-size: 14px;
height: 50px;
// line-height: 50px;
}
}
.tab-content {
min-height: 400px;
padding: 20px 0;
}
}
}
}
//
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.7;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.gateway-container {
animation: fadeInUp 0.6s ease-out;
.gateway-header {
animation: fadeInUp 0.6s ease-out 0.1s both;
.status-card {
animation: fadeInUp 0.6s ease-out 0.2s both;
}
.ota-card {
animation: fadeInUp 0.6s ease-out 0.3s both;
}
}
.sub-products-card {
animation: fadeInUp 0.6s ease-out 0.4s both;
}
}
//
.gateway-container {
::v-deep ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::v-deep ::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::v-deep ::-webkit-scrollbar-thumb {
background: linear-gradient(45deg, #667eea, #764ba2);
border-radius: 4px;
&:hover {
background: linear-gradient(45deg, #5a6fd8, #6a4190);
}
}
}
//
.tab-content {
::v-deep .running-status {
background: transparent;
padding: 0;
.mode-section {
.mode-card {
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
}
}
}
.settings-card,
.audio-list-card,
.default-list-card {
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
}
}
.voice-control-card {
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
}
}
}
}
</style>

View File

@ -254,17 +254,17 @@
$t('device.running-status.866086-12') }}</el-link> $t('device.running-status.866086-12') }}</el-link>
</template> </template>
<el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName <el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName <el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version <el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item :label="$t('device.running-status.866086-16')"> <el-descriptions-item :label="$t('device.running-status.866086-16')">
<el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{ <el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{
getDownloadUrl(firmware.filePath) }}</el-link> getDownloadUrl(firmware.filePath) }}</el-link>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark <el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark
}}</el-descriptions-item> }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="success" @click="otaUpgrade" <el-button type="success" @click="otaUpgrade"
@ -1269,7 +1269,7 @@ export default {
formData.append('file', audioBlob, `recording_${Date.now()}.mp3`); formData.append('file', audioBlob, `recording_${Date.now()}.mp3`);
// 使 HTTP HTTP // 使 HTTP HTTP
const uploadUrl = 'https://iot-xcwl.cn/common/upload/audio'; const uploadUrl = 'https://xaznkj.cn/common/upload/audio';
// const uploadUrl = 'http://1.94.62.14:8080/common/upload/audio'; // const uploadUrl = 'http://1.94.62.14:8080/common/upload/audio';
// //

View File

@ -2,7 +2,7 @@
<div class="login-wrap"> <div class="login-wrap">
<div class="logo-wrap"> <div class="logo-wrap">
<!-- <img class="icon" src="@/assets/images/logo-no-word-blue.png" /> --> <!-- <img class="icon" src="@/assets/images/logo-no-word-blue.png" /> -->
<span class="text">{{ "芯程物联" }}</span> <span class="text">{{ "信安智能" }}</span>
</div> </div>
<!-- <pre class="introduce-text">{{ $t('login.989807-38') }}</pre> --> <!-- <pre class="introduce-text">{{ $t('login.989807-38') }}</pre> -->
<div class="img-wrap"> <div class="img-wrap">
@ -74,11 +74,11 @@
<span class="text" @click="handleGotoOS">{{ $t('login.989807-41') }}</span> <span class="text" @click="handleGotoOS">{{ $t('login.989807-41') }}</span>
</div> --> </div> -->
<!-- <div class="other-login"> <div class="other-login">
<span class="text">{{ $t('login.989807-42') }}</span> <span class="text">{{ $t('login.989807-42') }}</span>
<svg-icon class="icon" icon-class="wechat" style="color: #07c160" @click="handleGotoWeChatLogin" /> <svg-icon class="icon" icon-class="wechat" style="color: #07c160" @click="handleGotoWeChatLogin" />
<svg-icon class="icon" icon-class="envelope" style="color: #486ff2" @click="handleGotoSmsLogin" /> <svg-icon class="icon" icon-class="envelope" style="color: #486ff2" @click="handleGotoSmsLogin" />
</div> --> </div>
</el-form> </el-form>
</div> </div>