logo替换以及显卡页面图片
@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 芯程物联网系统
|
||||
VUE_APP_TITLE = 信安智能
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 芯程物联云系统
|
||||
VUE_APP_TITLE = 信安智能
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 芯程物联
|
||||
VUE_APP_TITLE = 信安智能
|
||||
|
||||
NODE_ENV = production
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "fastbee",
|
||||
"version": "2.5.2",
|
||||
"description": "芯程物联",
|
||||
"description": "信安智能",
|
||||
"author": "kerwincui",
|
||||
"license": "AGPL3.0",
|
||||
"scripts": {
|
||||
|
BIN
src/assets/traffic/上箭头.png
Normal file
After Width: | Height: | Size: 372 B |
BIN
src/assets/traffic/两侧道路变窄.png
Normal file
After Width: | Height: | Size: 509 B |
BIN
src/assets/traffic/停车.png
Normal file
After Width: | Height: | Size: 618 B |
BIN
src/assets/traffic/减速慢行.png
Normal file
After Width: | Height: | Size: 512 B |
BIN
src/assets/traffic/减速让行.png
Normal file
After Width: | Height: | Size: 500 B |
BIN
src/assets/traffic/前方事故.png
Normal file
After Width: | Height: | Size: 507 B |
BIN
src/assets/traffic/前方施工.png
Normal file
After Width: | Height: | Size: 516 B |
BIN
src/assets/traffic/右下箭头.png
Normal file
After Width: | Height: | Size: 342 B |
BIN
src/assets/traffic/右侧道路变窄.png
Normal file
After Width: | Height: | Size: 511 B |
BIN
src/assets/traffic/右箭头.png
Normal file
After Width: | Height: | Size: 288 B |
BIN
src/assets/traffic/向右.png
Normal file
After Width: | Height: | Size: 475 B |
BIN
src/assets/traffic/向右转弯.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
src/assets/traffic/向左.png
Normal file
After Width: | Height: | Size: 476 B |
BIN
src/assets/traffic/向左向右转弯.png
Normal file
After Width: | Height: | Size: 495 B |
BIN
src/assets/traffic/向左转弯.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
src/assets/traffic/左下箭头.png
Normal file
After Width: | Height: | Size: 342 B |
BIN
src/assets/traffic/左侧道路变窄.png
Normal file
After Width: | Height: | Size: 511 B |
BIN
src/assets/traffic/左右两侧绕行.png
Normal file
After Width: | Height: | Size: 501 B |
BIN
src/assets/traffic/左箭头.png
Normal file
After Width: | Height: | Size: 288 B |
BIN
src/assets/traffic/掉头.png
Normal file
After Width: | Height: | Size: 647 B |
BIN
src/assets/traffic/检查.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
src/assets/traffic/注意安全.png
Normal file
After Width: | Height: | Size: 422 B |
BIN
src/assets/traffic/禁止临时停车.png
Normal file
After Width: | Height: | Size: 615 B |
BIN
src/assets/traffic/禁止停车(P).png
Normal file
After Width: | Height: | Size: 631 B |
BIN
src/assets/traffic/禁止停车(停).png
Normal file
After Width: | Height: | Size: 646 B |
BIN
src/assets/traffic/禁止右转.png
Normal file
After Width: | Height: | Size: 627 B |
BIN
src/assets/traffic/禁止左转.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
src/assets/traffic/禁止掉头.png
Normal file
After Width: | Height: | Size: 648 B |
BIN
src/assets/traffic/禁止驶入.png
Normal file
After Width: | Height: | Size: 384 B |
BIN
src/assets/traffic/禁止鸣笛.png
Normal file
After Width: | Height: | Size: 598 B |
BIN
src/assets/traffic/箭头向上.png
Normal file
After Width: | Height: | Size: 505 B |
BIN
src/assets/traffic/箭头向下.png
Normal file
After Width: | Height: | Size: 505 B |
BIN
src/assets/traffic/解除限速20.png
Normal file
After Width: | Height: | Size: 694 B |
BIN
src/assets/traffic/解除限速40.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
src/assets/traffic/解除限速60.png
Normal file
After Width: | Height: | Size: 706 B |
BIN
src/assets/traffic/限速100.png
Normal file
After Width: | Height: | Size: 625 B |
BIN
src/assets/traffic/限速110.png
Normal file
After Width: | Height: | Size: 612 B |
BIN
src/assets/traffic/限速120.png
Normal file
After Width: | Height: | Size: 612 B |
BIN
src/assets/traffic/限速15.png
Normal file
After Width: | Height: | Size: 618 B |
BIN
src/assets/traffic/限速20.png
Normal file
After Width: | Height: | Size: 648 B |
BIN
src/assets/traffic/限速30.png
Normal file
After Width: | Height: | Size: 655 B |
BIN
src/assets/traffic/限速40.png
Normal file
After Width: | Height: | Size: 658 B |
BIN
src/assets/traffic/限速50.png
Normal file
After Width: | Height: | Size: 645 B |
BIN
src/assets/traffic/限速60.png
Normal file
After Width: | Height: | Size: 662 B |
BIN
src/assets/traffic/限速70.png
Normal file
After Width: | Height: | Size: 644 B |
BIN
src/assets/traffic/限速80.png
Normal file
After Width: | Height: | Size: 669 B |
BIN
src/assets/traffic/限速90.png
Normal file
After Width: | Height: | Size: 663 B |
BIN
src/assets/traffic/靠右行驶.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
src/assets/traffic/靠左行驶.png
Normal file
After Width: | Height: | Size: 442 B |
@ -53,7 +53,7 @@ export default {
|
||||
if (data.logoName) {
|
||||
this.title = data.logoName.length > 8 ? data.logoName.slice(0, 8) : data.logoName;
|
||||
} else {
|
||||
this.title = '芯程物联';
|
||||
this.title = '信安智能';
|
||||
}
|
||||
if (data.deptLogo) {
|
||||
this.logo = this.baseUrl + data.deptLogo;
|
||||
|
@ -195,6 +195,8 @@
|
||||
ref="voicecard" :device="form" @statusEvent="getDeviceStatusData($event)" />
|
||||
<display v-else-if="form.productName && form.productName.toLowerCase().includes('显卡产品')"
|
||||
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"
|
||||
@statusEvent="getDeviceStatusData($event)" />
|
||||
|
||||
@ -462,6 +464,7 @@ import gatewaypre from './gatewaypre.vue'
|
||||
import acousto_optic from'./acousto_optic.vue'
|
||||
import voicecard from './voicecard.vue';
|
||||
import display from './display.vue'
|
||||
import gateway from './gateway.vue'
|
||||
export default {
|
||||
name: 'DeviceEdit',
|
||||
dicts: ['iot_device_status', 'iot_location_way'],
|
||||
@ -498,7 +501,8 @@ export default {
|
||||
gatewaypre,
|
||||
acousto_optic,
|
||||
voicecard,
|
||||
display
|
||||
display,
|
||||
gateway
|
||||
},
|
||||
watch: {
|
||||
activeName(val) {
|
||||
|
@ -447,8 +447,30 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="19" v-if="zone.playType === 1">
|
||||
<el-form-item label="图片路径" label-width="70px">
|
||||
<el-input v-model="zone.image" placeholder="请输入图片路径或选择图片" style="width:100%" />
|
||||
<el-form-item label="图片选择" label-width="70px">
|
||||
<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-col>
|
||||
</el-row>
|
||||
@ -843,6 +865,8 @@ export default {
|
||||
fontBold: 0,
|
||||
fontStretch: 0,
|
||||
image: '',
|
||||
imageSize: 1, // 32x32
|
||||
imageColor: 0, // Red
|
||||
effect: 0,
|
||||
speed: 4,
|
||||
stayTime: 5,
|
||||
@ -884,15 +908,19 @@ export default {
|
||||
addProgramStayTimes: ['不停顿', '100毫秒', '500毫秒', '1秒', '2秒', '3秒', '4秒', '5秒', '6秒', '8秒', '10秒'],
|
||||
addProgramHorizontalAlign: ['居左对齐', '居中对齐', '居右对齐'],
|
||||
addProgramVerticalAlign: ['顶部对齐', '居中对齐', '底部对齐'],
|
||||
addProgramImageSizes: ['16x16', '32x32', '48x48', '64x64'],
|
||||
addProgramImageColors: ['红色', '绿色', '黄色'],
|
||||
// 自动分页相关
|
||||
addProgramPreviewPage: [0], // 每个分区当前页
|
||||
addProgramPreviewTimer: [], // 每个分区定时器
|
||||
addProgramPreviewAssets: [], // 每个分区的分行分页缓存
|
||||
addProgramPreviewAnimState: [], // 每个分区动画状态
|
||||
addProgramPreviewAnimFrame: null, // 动画主循环ID
|
||||
addProgramTrafficImages: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadTrafficImages();
|
||||
if (this.device && this.device.deviceId) {
|
||||
this.handleDeviceChange(this.device);
|
||||
this.initDataStatus();
|
||||
@ -905,6 +933,22 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadTrafficImages() {
|
||||
try {
|
||||
// 使用webpack的require.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) {
|
||||
const command = {};
|
||||
@ -2245,6 +2289,8 @@ export default {
|
||||
fontBold: 0,
|
||||
fontStretch: 0,
|
||||
image: '',
|
||||
imageSize: 1,
|
||||
imageColor: 0,
|
||||
effect: 0,
|
||||
speed: 4,
|
||||
stayTime: 5,
|
||||
@ -2277,6 +2323,8 @@ export default {
|
||||
fontBold: 0,
|
||||
fontStretch: 0,
|
||||
image: '',
|
||||
imageSize: 1,
|
||||
imageColor: 0,
|
||||
effect: 0,
|
||||
speed: 4,
|
||||
stayTime: 5,
|
||||
@ -2486,8 +2534,71 @@ export default {
|
||||
|
||||
ctx.fillText(line, startX + animOffsetX, startY + lineIdx * lineHeight + animOffsetY);
|
||||
});
|
||||
} else if (zone.playType === 1) {
|
||||
} else if (zone.playType === 1 && asset.isImage) {
|
||||
// 图片类型分区
|
||||
if (asset.loaded && asset.img.complete) {
|
||||
const img = asset.img;
|
||||
|
||||
// 获取用户选择的目标尺寸
|
||||
const sizeValues = [16, 32, 48, 64];
|
||||
const targetSize = sizeValues[zone.imageSize] || 32;
|
||||
|
||||
// 根据原始宽高比计算缩放后的尺寸
|
||||
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);
|
||||
|
||||
@ -2495,7 +2606,8 @@ export default {
|
||||
ctx.font = `${Math.round(12 * scale)}px Arial`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('图片', zoneX + zoneWidth / 2, zoneY + zoneHeight / 2);
|
||||
ctx.fillText('加载中...', zoneX + zoneWidth / 2, zoneY + zoneHeight / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复裁剪区域
|
||||
@ -2510,6 +2622,21 @@ export default {
|
||||
const scale = Math.min(renderWidth / 32, 120 / 64);
|
||||
|
||||
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) {
|
||||
return { pages: [[]], isText: false };
|
||||
}
|
||||
|
962
src/views/iot/device/gateway.vue
Normal 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>
|
@ -1269,7 +1269,7 @@ export default {
|
||||
formData.append('file', audioBlob, `recording_${Date.now()}.mp3`);
|
||||
|
||||
// 强制使用 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';
|
||||
|
||||
// 调用上传接口
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="login-wrap">
|
||||
<div class="logo-wrap">
|
||||
<!-- <img class="icon" src="@/assets/images/logo-no-word-blue.png" /> -->
|
||||
<span class="text">{{ "芯程物联" }}</span>
|
||||
<span class="text">{{ "信安智能" }}</span>
|
||||
</div>
|
||||
<!-- <pre class="introduce-text">{{ $t('login.989807-38') }}</pre> -->
|
||||
<div class="img-wrap">
|
||||
@ -74,11 +74,11 @@
|
||||
<span class="text" @click="handleGotoOS">{{ $t('login.989807-41') }}</span>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="other-login">
|
||||
<div class="other-login">
|
||||
<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="envelope" style="color: #486ff2" @click="handleGotoSmsLogin" />
|
||||
</div> -->
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
|