6 changed files with 1748 additions and 405 deletions
@ -0,0 +1,73 @@ |
|||||
|
<template> |
||||
|
<Dialog v-model="modelVisible" title="设备操作(新设备)" width="600px" style="z-index:1200"> |
||||
|
<DeviceParam v-model="modelVisible" ref="DeviceParam" :deviceId="deviceId" :id="id" :deviceType="deviceType" |
||||
|
@update:submitting="(val) => { submitting = val }"> |
||||
|
</DeviceParam> |
||||
|
|
||||
|
<template #footer> |
||||
|
<Button style="background-color: rgba(0, 179, 204, 0.3)" |
||||
|
@click.native="(modelVisible = false), (submitting = false)"> |
||||
|
取消 |
||||
|
</Button> |
||||
|
<Button @click.native="handleSubmit" :loading="submitting"> 确定 </Button> |
||||
|
</template> |
||||
|
</Dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import Dialog from "@screen/components/Dialog/index.vue"; |
||||
|
import Button from "@screen/components/Buttons/Button.vue"; |
||||
|
import NewDeviceParam from "./NewDeviceParam.vue"; |
||||
|
|
||||
|
export default { |
||||
|
name: "NewDeviceControlDialog", |
||||
|
components: { |
||||
|
Dialog, |
||||
|
Button, |
||||
|
DeviceParam: NewDeviceParam, |
||||
|
}, |
||||
|
model: { |
||||
|
prop: "visible", |
||||
|
event: "update:value", |
||||
|
}, |
||||
|
props: { |
||||
|
visible: Boolean, |
||||
|
deviceId: String, |
||||
|
id: Number, |
||||
|
deviceType: String, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
submitting: false, |
||||
|
}; |
||||
|
}, |
||||
|
computed: { |
||||
|
modelVisible: { |
||||
|
get() { |
||||
|
return this.visible; |
||||
|
}, |
||||
|
set(val) { |
||||
|
this.$emit("update:value", val); |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
methods: { |
||||
|
handleSubmit() { |
||||
|
this.$refs.DeviceParam.handleSubmit(); |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
||||
|
<style lang="scss" scoped> |
||||
|
.NewDeviceControlDialog { |
||||
|
width: 450px; |
||||
|
height: 250px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 15px; |
||||
|
|
||||
|
.tips { |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,730 @@ |
|||||
|
<template> |
||||
|
<div class="DeviceControlDialog" :style="{ |
||||
|
'margin-top': isMultiControl ? '15px' : undefined, |
||||
|
'width': isMultiControl ? '100%' : undefined, |
||||
|
}"> |
||||
|
<Form v-model="formData" class="form" ref="FormConfigRef" :formList="formList" column="1" labelWidth="120px" /> |
||||
|
|
||||
|
<div class="tips" v-if="formData.deviceMode == 1"> |
||||
|
说明: 阈值开关开启时,设备将按照设定的开灯时间和关灯时间自动控制 |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import Dialog from "@screen/components/Dialog/index.vue"; |
||||
|
import Button from "@screen/components/Buttons/Button.vue"; |
||||
|
import Form from "@screen/components/FormConfig"; |
||||
|
|
||||
|
import { Message } from "element-ui"; |
||||
|
import request from "@/utils/request"; |
||||
|
import { delay } from "@screen/utils/common.js"; |
||||
|
|
||||
|
const workStatus = [ |
||||
|
{ |
||||
|
key: "00", |
||||
|
label: "不更新状态", |
||||
|
disabled: false, |
||||
|
}, |
||||
|
{ |
||||
|
key: "01", |
||||
|
label: "常亮", |
||||
|
}, |
||||
|
{ |
||||
|
key: "02", |
||||
|
label: "流水", |
||||
|
}, |
||||
|
{ |
||||
|
key: "03", |
||||
|
label: "闪烁", |
||||
|
}, |
||||
|
{ |
||||
|
key: "04", |
||||
|
label: "关闭", |
||||
|
disabled: false, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
export default { |
||||
|
name: "NewDeviceParam", |
||||
|
components: { |
||||
|
Dialog, |
||||
|
Button, |
||||
|
Form, |
||||
|
}, |
||||
|
model: { |
||||
|
prop: "visible", |
||||
|
event: "update:value", |
||||
|
}, |
||||
|
props: { |
||||
|
visible: Boolean, |
||||
|
deviceId: String, |
||||
|
id: [String, Number], |
||||
|
deviceType: String, |
||||
|
isMultiControl: Boolean, |
||||
|
selectItems: { |
||||
|
type: Array, |
||||
|
default: () => [] |
||||
|
}, |
||||
|
|
||||
|
deviceParams: { |
||||
|
type: Object, |
||||
|
default: () => ({}) |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
formData: { |
||||
|
onTimePicker: null, // 用于时间选择器绑定 |
||||
|
offTimePicker: null, // 用于时间选择器绑定 |
||||
|
}, |
||||
|
// 用于存储从接口获取的设备参数 |
||||
|
apiDeviceParams: {}, |
||||
|
formList: [ |
||||
|
{ |
||||
|
label: "模式选择:", |
||||
|
key: "cmd", |
||||
|
type: "RadioGroup", |
||||
|
default: 1, |
||||
|
options: { |
||||
|
type: "circle", |
||||
|
options: [ |
||||
|
{ |
||||
|
key: 1, |
||||
|
label: "同步", |
||||
|
}, |
||||
|
{ |
||||
|
key: 2, |
||||
|
label: "流水", |
||||
|
}, |
||||
|
{ |
||||
|
key: 3, |
||||
|
label: "尾迹", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "亮灯模式:", |
||||
|
key: "mode", |
||||
|
type: "RadioGroup", |
||||
|
default: 0, |
||||
|
options: { |
||||
|
type: "circle", |
||||
|
options: [ |
||||
|
{ |
||||
|
key: 0, |
||||
|
label: "常亮", |
||||
|
}, |
||||
|
{ |
||||
|
key: 1, |
||||
|
label: "单闪", |
||||
|
}, |
||||
|
{ |
||||
|
key: 2, |
||||
|
label: "双闪", |
||||
|
}, |
||||
|
{ |
||||
|
key: 3, |
||||
|
label: "三闪", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "设备开关:", |
||||
|
key: "sw", |
||||
|
type: "RadioGroup", |
||||
|
default: true, |
||||
|
options: { |
||||
|
type: "circle", |
||||
|
options: [ |
||||
|
{ |
||||
|
key: true, |
||||
|
label: "开启", |
||||
|
}, |
||||
|
{ |
||||
|
key: false, |
||||
|
label: "关闭", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "闪灯频率:", |
||||
|
key: "ff", |
||||
|
type: "Input", |
||||
|
required: true, |
||||
|
options: { |
||||
|
placeholder: "请输入闪灯频率 (1-30)", |
||||
|
tip: "数值×5表示一分钟闪烁次数(例如:输入6表示每分钟闪烁30次)" |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "亮度:", |
||||
|
key: "lum", |
||||
|
type: "Input", |
||||
|
required: true, |
||||
|
options: { |
||||
|
placeholder: "请输入亮度 (0-100)", |
||||
|
tip: "亮度值范围0-100,数值越大亮度越高" |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "亮灯时长:", |
||||
|
key: "ontime", |
||||
|
type: "Input", |
||||
|
required: true, |
||||
|
options: { |
||||
|
placeholder: "请输入亮灯时长 (50-500)", |
||||
|
tip: "必须为50的倍数,单位为毫秒(例如:50、100、150...500)" |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "灯光颜色:", |
||||
|
key: "sg", |
||||
|
type: "RadioGroup", |
||||
|
default: "AA", |
||||
|
options: { |
||||
|
type: "circle", |
||||
|
options: [ |
||||
|
{ |
||||
|
key: "AA", |
||||
|
label: "红色", |
||||
|
}, |
||||
|
{ |
||||
|
key: "55", |
||||
|
label: "黄色", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "灵敏度:", |
||||
|
key: "cth", |
||||
|
type: "Input", |
||||
|
required: true, |
||||
|
options: { |
||||
|
placeholder: "请输入灵敏度 (0-255)", |
||||
|
tip: "值越高灵敏度越低,范围0-255" |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "阈值开关:", |
||||
|
key: "deviceMode", |
||||
|
type: "RadioGroup", |
||||
|
default: 0, |
||||
|
options: { |
||||
|
type: "circle", |
||||
|
options: [ |
||||
|
{ |
||||
|
key: 0, |
||||
|
label: "关闭", |
||||
|
}, |
||||
|
{ |
||||
|
key: 1, |
||||
|
label: "开启", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "开灯时间:", |
||||
|
key: "onTimePicker", |
||||
|
type: "TimePicker", |
||||
|
required: true, |
||||
|
visible: (data) => data.deviceMode === 1, |
||||
|
options: { |
||||
|
format: "HH:mm", |
||||
|
valueFormat: "HH:mm", |
||||
|
placeholder: "选择开灯时间", |
||||
|
tip: "选择开灯时间(格式:HH:mm,保存时会转换为数字格式如1800)" |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: "关灯时间:", |
||||
|
key: "offTimePicker", |
||||
|
type: "TimePicker", |
||||
|
required: true, |
||||
|
visible: (data) => data.deviceMode === 1, |
||||
|
options: { |
||||
|
format: "HH:mm", |
||||
|
valueFormat: "HH:mm", |
||||
|
placeholder: "选择关灯时间", |
||||
|
tip: "选择关灯时间(格式:HH:mm,保存时会转换为数字格式如0600)" |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
|
}, |
||||
|
mounted() { |
||||
|
console.log('NewDeviceParam 组件 mounted,deviceId:', this.deviceId); |
||||
|
this.$emit("update:submitting", false); |
||||
|
|
||||
|
// 优先使用 props 中的 deviceParams,如果没有则从接口获取 |
||||
|
if (this.deviceParams && this.hasActualData(this.deviceParams)) { |
||||
|
console.log('使用 props 中的 deviceParams 进行回显'); |
||||
|
this.setFormDataFromParams(this.deviceParams); |
||||
|
} else if (!this.isMultiControl) { |
||||
|
console.log('从接口获取设备参数进行回显'); |
||||
|
this.reDisplay(); |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
// 监听 deviceParams 变化 |
||||
|
deviceParams: { |
||||
|
handler(newVal) { |
||||
|
console.log('deviceParams props 变化:', newVal); |
||||
|
if (newVal && this.hasActualData(newVal)) { |
||||
|
this.setFormDataFromParams(newVal); |
||||
|
} |
||||
|
}, |
||||
|
immediate: true, |
||||
|
deep: true |
||||
|
}, |
||||
|
// 监听时间选择器变化,转换为数字格式 |
||||
|
'formData.onTimePicker': { |
||||
|
handler(newVal) { |
||||
|
console.log('onTimePicker 变化:', newVal); |
||||
|
if (newVal) { |
||||
|
this.formData.onTime = this.timeStringToNumber(newVal); |
||||
|
} else { |
||||
|
this.formData.onTime = null; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
'formData.offTimePicker': { |
||||
|
handler(newVal) { |
||||
|
console.log('offTimePicker 变化:', newVal); |
||||
|
if (newVal) { |
||||
|
this.formData.offTime = this.timeStringToNumber(newVal); |
||||
|
} else { |
||||
|
this.formData.offTime = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// 检查对象是否有实际数据(排除Vue响应式属性) |
||||
|
hasActualData(obj) { |
||||
|
if (!obj || typeof obj !== 'object') return false; |
||||
|
|
||||
|
// 检查是否有非 __ 开头的属性 |
||||
|
for (let key in obj) { |
||||
|
if (obj.hasOwnProperty(key) && !key.startsWith('__')) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
}, |
||||
|
|
||||
|
// 重新显示方法 |
||||
|
reDisplay() { |
||||
|
console.log('reDisplay 被调用,deviceId:', this.deviceId); |
||||
|
this.fetchDeviceProperty(); |
||||
|
}, |
||||
|
|
||||
|
// 获取设备参数 |
||||
|
async fetchDeviceProperty() { |
||||
|
try { |
||||
|
console.log('开始从接口获取设备参数,deviceId:', this.deviceId); |
||||
|
|
||||
|
// 按照要求的格式构建请求体 |
||||
|
const requestBody = { |
||||
|
params: { |
||||
|
list: [this.deviceId] // 使用组件的 deviceId prop |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
console.log('发送请求体:', requestBody); |
||||
|
|
||||
|
const response = await request.post( |
||||
|
`business/device/functions/${this.deviceId}/getProperty`, |
||||
|
requestBody |
||||
|
); |
||||
|
|
||||
|
console.log('接口响应:', response); |
||||
|
|
||||
|
if (response.code === 200 && response.data) { |
||||
|
console.log('获取设备参数成功:', response.data); |
||||
|
this.parseResponseData(response.data); |
||||
|
} else { |
||||
|
console.warn('获取设备参数失败:', response); |
||||
|
Message.error('获取设备参数失败'); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('获取设备参数异常:', error); |
||||
|
Message.error('获取设备参数异常'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
ensureBoolean(value) { |
||||
|
if (value === true || value === 'true') return true; |
||||
|
if (value === false || value === 'false') return false; |
||||
|
return value; |
||||
|
}, |
||||
|
|
||||
|
// 解析响应数据 |
||||
|
parseResponseData(data) { |
||||
|
try { |
||||
|
console.log('开始解析数据,数据类型:', typeof data, '值:', data); |
||||
|
|
||||
|
// 如果返回的是数组,取第一个元素 |
||||
|
let rawData = data; |
||||
|
if (Array.isArray(data) && data.length > 0) { |
||||
|
rawData = data[0]; |
||||
|
} |
||||
|
|
||||
|
// 如果数据是字符串,解析为JSON |
||||
|
if (typeof rawData === 'string') { |
||||
|
try { |
||||
|
rawData = JSON.parse(rawData); |
||||
|
} catch (parseError) { |
||||
|
console.error('JSON解析失败:', parseError); |
||||
|
console.error('原始字符串:', rawData); |
||||
|
Message.error('数据解析失败'); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (rawData && rawData.data) { |
||||
|
const deviceData = rawData.data; |
||||
|
console.log("设备属性数据:", deviceData); |
||||
|
|
||||
|
// 创建对象存储解析后的设备参数 |
||||
|
const parsedParams = {}; |
||||
|
|
||||
|
// 处理 deviceProperty |
||||
|
if (deviceData.deviceProperty && Array.isArray(deviceData.deviceProperty) && deviceData.deviceProperty.length > 0) { |
||||
|
const property = deviceData.deviceProperty[0]; |
||||
|
console.log('设备属性数据:', { |
||||
|
csq: property.csq, |
||||
|
latitude: property.lat, |
||||
|
longitude: property.lng, |
||||
|
voltage: property.vol |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 处理 lampParam |
||||
|
if (deviceData.lampParam && Array.isArray(deviceData.lampParam) && deviceData.lampParam.length > 0) { |
||||
|
const lampParam = deviceData.lampParam[0]; |
||||
|
|
||||
|
// 存储 lampParam 数据 |
||||
|
Object.assign(parsedParams, { |
||||
|
cmd: lampParam.cmd || 1, |
||||
|
mode: lampParam.mode || 0, |
||||
|
ff: lampParam.ff || 1, |
||||
|
lum: lampParam.lum || 50, |
||||
|
ontime: lampParam.ontime || 50, |
||||
|
sw: this.ensureBoolean(lampParam.sw), |
||||
|
sg: lampParam.sg || 'AA', |
||||
|
cth: lampParam.cth || 128 |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 处理 deviceSetting |
||||
|
if (deviceData.deviceSetting && Array.isArray(deviceData.deviceSetting) && deviceData.deviceSetting.length > 0) { |
||||
|
const deviceSetting = deviceData.deviceSetting[0]; |
||||
|
|
||||
|
// 合并 deviceSetting 数据 |
||||
|
Object.assign(parsedParams, { |
||||
|
deviceMode: deviceSetting.mode || 0, |
||||
|
onTime: deviceSetting.onTime || 1800, |
||||
|
offTime: deviceSetting.offTime || 600 |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
console.log('解析后的设备参数:', parsedParams); |
||||
|
|
||||
|
// 保存到本地,然后进行回显 |
||||
|
this.apiDeviceParams = parsedParams; |
||||
|
this.setFormDataFromParams(parsedParams); |
||||
|
|
||||
|
} else { |
||||
|
console.warn('数据格式不正确,没有 data 字段:', rawData); |
||||
|
Message.warning('设备参数数据格式不正确'); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('解析数据失败:', error); |
||||
|
console.error('原始数据:', data); |
||||
|
console.error('错误堆栈:', error.stack); |
||||
|
Message.error('解析设备参数失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 从参数设置表单数据(回显) |
||||
|
setFormDataFromParams(params) { |
||||
|
|
||||
|
const sourceParams = params || this.deviceParams || this.apiDeviceParams; |
||||
|
|
||||
|
|
||||
|
if (!sourceParams || !this.hasActualData(sourceParams)) { |
||||
|
console.log('没有有效的设备参数,跳过回显'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// 逐个字段设置,确保每个字段都有值 |
||||
|
const updates = {}; |
||||
|
|
||||
|
// cmd |
||||
|
if ('cmd' in sourceParams) { |
||||
|
updates.cmd = sourceParams.cmd; |
||||
|
} |
||||
|
|
||||
|
// mode |
||||
|
if ('mode' in sourceParams) { |
||||
|
updates.mode = sourceParams.mode; |
||||
|
} |
||||
|
|
||||
|
// sw |
||||
|
if ('sw' in sourceParams) { |
||||
|
updates.sw = this.ensureBoolean(sourceParams.sw); |
||||
|
} |
||||
|
|
||||
|
// ff |
||||
|
if ('ff' in sourceParams) { |
||||
|
updates.ff = sourceParams.ff !== undefined ? sourceParams.ff.toString() : ''; |
||||
|
} |
||||
|
|
||||
|
// lum |
||||
|
if ('lum' in sourceParams) { |
||||
|
updates.lum = sourceParams.lum !== undefined ? sourceParams.lum.toString() : ''; |
||||
|
} |
||||
|
|
||||
|
// ontime |
||||
|
if ('ontime' in sourceParams) { |
||||
|
updates.ontime = sourceParams.ontime !== undefined ? sourceParams.ontime.toString() : ''; |
||||
|
} |
||||
|
|
||||
|
// sg |
||||
|
if ('sg' in sourceParams) { |
||||
|
updates.sg = sourceParams.sg; |
||||
|
} |
||||
|
|
||||
|
// cth |
||||
|
if ('cth' in sourceParams) { |
||||
|
updates.cth = sourceParams.cth !== undefined ? sourceParams.cth.toString() : ''; |
||||
|
} |
||||
|
|
||||
|
// deviceMode |
||||
|
if ('deviceMode' in sourceParams) { |
||||
|
updates.deviceMode = sourceParams.deviceMode; |
||||
|
} |
||||
|
|
||||
|
// 应用更新 |
||||
|
Object.assign(this.formData, updates); |
||||
|
|
||||
|
|
||||
|
// 处理时间字段 |
||||
|
if ('onTime' in sourceParams && sourceParams.onTime !== undefined && sourceParams.onTime !== null) { |
||||
|
this.formData.onTime = sourceParams.onTime; |
||||
|
this.formData.onTimePicker = this.numberToTimeString(sourceParams.onTime); |
||||
|
} |
||||
|
|
||||
|
if ('offTime' in sourceParams && sourceParams.offTime !== undefined && sourceParams.offTime !== null) { |
||||
|
this.formData.offTime = sourceParams.offTime; |
||||
|
this.formData.offTimePicker = this.numberToTimeString(sourceParams.offTime); |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
// 将数字格式转换为时间字符串(1800 → "18:00") |
||||
|
numberToTimeString(timeNum) { |
||||
|
if (timeNum === undefined || timeNum === null) { |
||||
|
console.log('numberToTimeString: 输入为空', timeNum); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// 将数字转换为4位字符串,不足补0 |
||||
|
const numStr = timeNum.toString().padStart(4, '0'); |
||||
|
|
||||
|
// 提取小时和分钟 |
||||
|
const hours = numStr.substring(0, 2); |
||||
|
const minutes = numStr.substring(2, 4); |
||||
|
|
||||
|
const result = `${hours}:${minutes}`; |
||||
|
console.log('numberToTimeString 转换:', timeNum, '->', result); |
||||
|
return result; |
||||
|
}, |
||||
|
|
||||
|
// 将时间字符串转换为数字格式("18:00" → 1800) |
||||
|
timeStringToNumber(timeStr) { |
||||
|
if (!timeStr) { |
||||
|
console.log('timeStringToNumber: 输入为空'); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// 分割小时和分钟 |
||||
|
const [hours, minutes] = timeStr.split(':'); |
||||
|
|
||||
|
// 转换为数字格式 |
||||
|
const result = parseInt(hours) * 100 + parseInt(minutes); |
||||
|
return result; |
||||
|
}, |
||||
|
|
||||
|
handleSubmit() { |
||||
|
const formData = this.$refs.FormConfigRef?.formData || this.formData; |
||||
|
|
||||
|
console.log('提交的表单数据:', formData); |
||||
|
|
||||
|
// 验证闪灯频率(1-30) |
||||
|
if (formData.ff) { |
||||
|
const ffValue = parseInt(formData.ff); |
||||
|
if (isNaN(ffValue) || ffValue < 1 || ffValue > 30) { |
||||
|
return Message.error('闪灯频率必须在1-30范围内!'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 验证亮度(0-100) |
||||
|
if (formData.lum) { |
||||
|
const lumValue = parseInt(formData.lum); |
||||
|
if (isNaN(lumValue) || lumValue < 0 || lumValue > 100) { |
||||
|
return Message.error('亮度必须在0-100范围内!'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 验证亮灯时长(50-500且必须是50的倍数) |
||||
|
if (formData.ontime) { |
||||
|
const ontimeValue = parseInt(formData.ontime); |
||||
|
if (isNaN(ontimeValue) || ontimeValue < 50 || ontimeValue > 500) { |
||||
|
return Message.error('亮灯时长必须在50-500范围内!'); |
||||
|
} |
||||
|
if (ontimeValue % 50 !== 0) { |
||||
|
return Message.error('亮灯时长必须是50的倍数!'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 验证灵敏度(0-255) |
||||
|
if (formData.cth) { |
||||
|
const cthValue = parseInt(formData.cth); |
||||
|
if (isNaN(cthValue) || cthValue < 0 || cthValue > 255) { |
||||
|
return Message.error('灵敏度必须在0-255范围内!'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 验证时间格式 |
||||
|
if (formData.deviceMode == 1) { |
||||
|
// 验证时间选择器是否已选择 |
||||
|
if (!formData.onTimePicker || !formData.offTimePicker) { |
||||
|
return Message.error('开灯时间和关灯时间不能为空!'); |
||||
|
} |
||||
|
|
||||
|
// 确保数字格式已生成 |
||||
|
if (!formData.onTime) { |
||||
|
formData.onTime = this.timeStringToNumber(formData.onTimePicker); |
||||
|
} |
||||
|
|
||||
|
if (!formData.offTime) { |
||||
|
formData.offTime = this.timeStringToNumber(formData.offTimePicker); |
||||
|
} |
||||
|
|
||||
|
if (formData.onTime < 0 || formData.onTime > 2359 || |
||||
|
formData.offTime < 0 || formData.offTime > 2359) { |
||||
|
return Message.error('时间必须在0-2359范围内!'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 验证其他必填字段 |
||||
|
const requiredFields = ['cmd', 'mode', 'sw', 'ff', 'lum', 'ontime', 'sg', 'cth', 'deviceMode']; |
||||
|
for (const field of requiredFields) { |
||||
|
const value = formData[field]; |
||||
|
if (value === undefined || value === null || value === '') { |
||||
|
return Message.error(`${this.getFieldLabel(field)}不能为空!`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.$emit("update:submitting", true); |
||||
|
|
||||
|
// 准备lampParam数据 |
||||
|
const lampParamData = { |
||||
|
cmd: parseInt(formData.cmd), |
||||
|
mode: parseInt(formData.mode), |
||||
|
ff: parseInt(formData.ff), |
||||
|
lum: parseInt(formData.lum), |
||||
|
ontime: parseInt(formData.ontime), |
||||
|
sw: formData.sw, |
||||
|
sg: formData.sg, |
||||
|
cth: parseInt(formData.cth) |
||||
|
}; |
||||
|
|
||||
|
// 准备deviceSetting数据 |
||||
|
const deviceSettingData = { |
||||
|
mode: parseInt(formData.deviceMode), |
||||
|
onTime: formData.onTime || 0, |
||||
|
offTime: formData.offTime || 0 |
||||
|
}; |
||||
|
|
||||
|
console.log('发送的数据:', { |
||||
|
lampParam: lampParamData, |
||||
|
deviceSetting: deviceSettingData |
||||
|
}); |
||||
|
|
||||
|
const requestBody = { |
||||
|
|
||||
|
params: { |
||||
|
list: [this.deviceId], |
||||
|
params: { |
||||
|
lampParam: lampParamData, |
||||
|
deviceSetting: deviceSettingData |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
// 发送控制命令 |
||||
|
request.post( |
||||
|
`business/device/functions/${this.deviceId}/setDesired`, |
||||
|
requestBody |
||||
|
) |
||||
|
.then(res => { |
||||
|
if (res.code === 200) { |
||||
|
this.$emit("update:value", false); |
||||
|
Message.success('设备操作成功'); |
||||
|
} else { |
||||
|
Message.error(res.message || '设备操作失败'); |
||||
|
} |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
console.error('设备操作失败:', err); |
||||
|
Message.error('设备操作失败'); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
this.$emit("update:submitting", false); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
getFieldLabel(field) { |
||||
|
const labelMap = { |
||||
|
cmd: '模式选择', |
||||
|
mode: '亮灯模式', |
||||
|
sw: '设备开关', |
||||
|
ff: '闪灯频率', |
||||
|
lum: '亮度', |
||||
|
ontime: '亮灯时长', |
||||
|
sg: '灯光颜色', |
||||
|
cth: '灵敏度', |
||||
|
deviceMode: '阈值开关', |
||||
|
onTime: '开灯时间', |
||||
|
offTime: '关灯时间' |
||||
|
}; |
||||
|
return labelMap[field] || field; |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.DeviceControlDialog { |
||||
|
width: 450px; |
||||
|
height: 500px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 15px; |
||||
|
|
||||
|
.tips { |
||||
|
font-size: 12px; |
||||
|
color: #909399; |
||||
|
margin-top: 10px; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,542 @@ |
|||||
|
<template> |
||||
|
<Dialog v-model="visibleModel" title="行车诱导(新设备)" width="470px"> |
||||
|
<div class="DrivingGuidance"> |
||||
|
<Video class="video-stream" :camId="camId" img="智能行车诱导" /> |
||||
|
|
||||
|
<ElTabs v-model="activeName" @tab-click="handleClickTabs" class="tabs"> |
||||
|
<ElTabPane label="基本信息" name="first"> |
||||
|
<div class="device-info"> |
||||
|
<div class="info-grid"> |
||||
|
<div class="info-item" v-for="(item, index) in infoList" :key="index"> |
||||
|
<div class="info-label">{{ item.label }}:</div> |
||||
|
<div class="info-value">{{ getInfoValue(item) }}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 新设备特有参数 --> |
||||
|
<div class="device-setting-section" v-if="deviceParams.deviceMode !== undefined"> |
||||
|
<div class="section-title">设备设置</div> |
||||
|
<div class="setting-grid"> |
||||
|
<div class="setting-item"> |
||||
|
<span class="setting-label">阈值开关:</span> |
||||
|
<span class="setting-value" :class="deviceParams.deviceMode === 1 ? 'on' : 'off'"> |
||||
|
{{ deviceParams.deviceMode === 1 ? '开启' : '关闭' }} |
||||
|
</span> |
||||
|
</div> |
||||
|
<div class="setting-item"> |
||||
|
<span class="setting-label">开灯时间:</span> |
||||
|
<span class="setting-value">{{ formatTime(deviceParams.onTime) }}</span> |
||||
|
</div> |
||||
|
<div class="setting-item"> |
||||
|
<span class="setting-label">关灯时间:</span> |
||||
|
<span class="setting-value">{{ formatTime(deviceParams.offTime) }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ElTabPane> |
||||
|
|
||||
|
<ElTabPane label="设备参数" name="second"> |
||||
|
<div class="device-params"> |
||||
|
<div class="param-section"> |
||||
|
<div class="section-title">灯控参数</div> |
||||
|
<div class="param-grid"> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">模式:</span> |
||||
|
<span class="param-value">{{ getModeText(deviceParams.cmd) }}</span> |
||||
|
</div> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">亮灯模式:</span> |
||||
|
<span class="param-value">{{ getLightModeText(deviceParams.mode) }}</span> |
||||
|
</div> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">闪灯频率:</span> |
||||
|
<span class="param-value">{{ deviceParams.ff ? `${deviceParams.ff * 5}次/分钟` : '-' }}</span> |
||||
|
</div> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">亮度:</span> |
||||
|
<span class="param-value">{{ deviceParams.lum !== undefined ? `${deviceParams.lum}%` : '-' }}</span> |
||||
|
</div> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">亮灯时长:</span> |
||||
|
<span class="param-value">{{ deviceParams.ontime ? `${deviceParams.ontime}ms` : '-' }}</span> |
||||
|
</div> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">开关:</span> |
||||
|
<span class="param-value" :class="{ 'on': deviceParams.sw, 'off': !deviceParams.sw }"> |
||||
|
{{ deviceParams.sw ? '开启' : '关闭' }} |
||||
|
</span> |
||||
|
</div> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">颜色:</span> |
||||
|
<span class="param-value" :style="{ color: getColorStyle(deviceParams.sg) }"> |
||||
|
{{ getColorText(deviceParams.sg) }} |
||||
|
</span> |
||||
|
</div> |
||||
|
<div class="param-item"> |
||||
|
<span class="param-label">灵敏度:</span> |
||||
|
<span class="param-value">{{ deviceParams.cth !== undefined ? deviceParams.cth : '-' }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ElTabPane> |
||||
|
|
||||
|
<ElTabPane label="在线率统计" name="third"> |
||||
|
<LineChart v-if="activeName === 'third'" :productId="dialogData.id" style="height: 180px" /> |
||||
|
</ElTabPane> |
||||
|
</ElTabs> |
||||
|
|
||||
|
<div class="bottom"> |
||||
|
<Button v-hasPermi="['business:home:drivingGuidance']" |
||||
|
@click.native="onControlClick" |
||||
|
:style="dialogData.deviceState ? '' : 'background-color:grey'">设备操作</Button> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 新设备控制对话框 --> |
||||
|
<NewDeviceControlDialog v-model="deviceControlVisible" |
||||
|
:deviceId="dialogData.iotDeviceId" |
||||
|
:id="dialogData.id" |
||||
|
:deviceType="dialogData.deviceType" |
||||
|
:deviceParams="deviceParams" /> |
||||
|
</Dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import Dialog from "@screen/components/Dialog/index.vue"; |
||||
|
import Button from "@screen/components/Buttons/Button.vue"; |
||||
|
import Video from "@screen/components/Video"; |
||||
|
import LineChart from "../LineChart/index.vue"; |
||||
|
import NewDeviceControlDialog from "../Dialogs/DrivingGuidance/components/NewDeviceControlDialog.vue"; |
||||
|
import request from "@/utils/request"; |
||||
|
import { CameraDirectionEnum, DeviceTypeEnum } from "@screen/utils/enum.js"; |
||||
|
|
||||
|
export default { |
||||
|
name: "NewDeviceDrivingGuidance", |
||||
|
components: { |
||||
|
Dialog, |
||||
|
Button, |
||||
|
NewDeviceControlDialog, |
||||
|
Video, |
||||
|
LineChart, |
||||
|
}, |
||||
|
props: { |
||||
|
visible: { |
||||
|
type: Boolean, |
||||
|
default: true, |
||||
|
}, |
||||
|
data: { |
||||
|
type: Object, |
||||
|
default: null, |
||||
|
}, |
||||
|
}, |
||||
|
emits: ["close"], |
||||
|
computed: { |
||||
|
visibleModel: { |
||||
|
get() { |
||||
|
return this.visible; |
||||
|
}, |
||||
|
set(bool) { |
||||
|
this.$emit("close", bool); |
||||
|
}, |
||||
|
}, |
||||
|
dialogData() { |
||||
|
return this.data || {}; |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
camId: '', |
||||
|
activeName: "first", |
||||
|
deviceControlVisible: false, |
||||
|
deviceParams: {}, |
||||
|
|
||||
|
infoList: [ |
||||
|
{ label: "设备名称", key: "deviceName" }, |
||||
|
{ label: "设备桩号", key: "stakeMark" }, |
||||
|
{ label: "道路名称", key: "roadName" }, |
||||
|
{ label: "设备方向", key: "direction", enum: "CameraDirectionEnum" }, |
||||
|
{ label: "设备状态", key: "deviceState", enum: "DeviceTypeEnum" }, |
||||
|
{ label: "设备厂商", key: "manufacturer" }, |
||||
|
] |
||||
|
}; |
||||
|
}, |
||||
|
async created() { |
||||
|
// 解析camId |
||||
|
if (typeof this.dialogData.otherConfig === 'string') { |
||||
|
try { |
||||
|
const oConfig = JSON.parse(this.dialogData.otherConfig); |
||||
|
if (oConfig?.camId) { |
||||
|
this.camId = oConfig.camId; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('解析otherConfig失败:', e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 获取设备属性(基本信息) |
||||
|
await this.fetchDeviceProperty(); |
||||
|
}, |
||||
|
methods: { |
||||
|
|
||||
|
|
||||
|
// 获取设备参数 |
||||
|
async fetchDeviceProperty() { |
||||
|
|
||||
|
// 按照要求的格式构建请求体 |
||||
|
const requestBody = { |
||||
|
params: { |
||||
|
list: [this.dialogData.iotDeviceId] |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
console.log('发送请求体:', requestBody); |
||||
|
|
||||
|
const lampResponse = await request.post( |
||||
|
`business/device/functions/${this.dialogData.iotDeviceId}/getProperty`, |
||||
|
|
||||
|
requestBody |
||||
|
); |
||||
|
|
||||
|
if (lampResponse.code === 200 && lampResponse.data) { |
||||
|
console.log('lampParam获取成功:', lampResponse.data); |
||||
|
this.parseResponseData(lampResponse.data); |
||||
|
} else { |
||||
|
console.warn('获取lampParam失败:', lampResponse); |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
onControlClick() { |
||||
|
|
||||
|
if (this.dialogData.deviceState) { |
||||
|
// 直接使用已经获取的 deviceParams |
||||
|
// 确保 deviceParams 已经有数据 |
||||
|
if (Object.keys(this.deviceParams).length === 0) { |
||||
|
console.log('deviceParams 为空,尝试重新获取'); |
||||
|
// 可以尝试重新获取数据 |
||||
|
this.fetchDeviceProperty().then(() => { |
||||
|
this.deviceControlVisible = true; |
||||
|
}); |
||||
|
} else { |
||||
|
console.log('deviceParams 已有数据,打开对话框'); |
||||
|
this.deviceControlVisible = true; |
||||
|
} |
||||
|
} else { |
||||
|
this.$message.warning('设备状态不可用,无法操作'); |
||||
|
} |
||||
|
}, |
||||
|
ensureBoolean(value) { |
||||
|
if (value === true || value === 'true') return true; |
||||
|
if (value === false || value === 'false') return false; |
||||
|
return value; |
||||
|
}, |
||||
|
|
||||
|
// 解析响应数据 |
||||
|
parseResponseData(data) { |
||||
|
try { |
||||
|
console.log('开始解析数据,数据类型:', typeof data, '值:', data); |
||||
|
|
||||
|
// 如果返回的是数组,取第一个元素 |
||||
|
let rawData = data; |
||||
|
if (Array.isArray(data) && data.length > 0) { |
||||
|
rawData = data[0]; |
||||
|
} |
||||
|
|
||||
|
// 如果数据是字符串,解析为JSON |
||||
|
if (typeof rawData === 'string') { |
||||
|
try { |
||||
|
rawData = JSON.parse(rawData); |
||||
|
} catch (parseError) { |
||||
|
console.error('JSON解析失败:', parseError); |
||||
|
console.error('原始字符串:', rawData); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
if (rawData && rawData.data) { |
||||
|
const deviceData = rawData.data; |
||||
|
console.log("设备属性数据") |
||||
|
console.log(deviceData) |
||||
|
// 处理 deviceProperty |
||||
|
if (deviceData.deviceProperty && Array.isArray(deviceData.deviceProperty) && deviceData.deviceProperty.length > 0) { |
||||
|
const property = deviceData.deviceProperty[0]; |
||||
|
|
||||
|
console.log('设备属性数据:', { |
||||
|
csq: property.csq, |
||||
|
latitude: property.lat, |
||||
|
longitude: property.lng, |
||||
|
voltage: property.vol |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 处理 lampParam |
||||
|
if (deviceData.lampParam && Array.isArray(deviceData.lampParam) && deviceData.lampParam.length > 0) { |
||||
|
const lampParam = deviceData.lampParam[0]; |
||||
|
|
||||
|
// 存储 lampParam 数据 |
||||
|
this.deviceParams = { |
||||
|
cmd: lampParam.cmd || 1, |
||||
|
mode: lampParam.mode || 0, |
||||
|
ff: lampParam.ff || 1, |
||||
|
lum: lampParam.lum || 50, |
||||
|
ontime: lampParam.ontime || 50, |
||||
|
sw: this.ensureBoolean(lampParam.sw), |
||||
|
sg: lampParam.sg || 'AA', |
||||
|
cth: lampParam.cth || 128 // 默认值 |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 处理 deviceSetting |
||||
|
if (deviceData.deviceSetting && Array.isArray(deviceData.deviceSetting) && deviceData.deviceSetting.length > 0) { |
||||
|
const deviceSetting = deviceData.deviceSetting[0]; |
||||
|
|
||||
|
// 合并 deviceSetting 数据 |
||||
|
this.deviceParams = { |
||||
|
...this.deviceParams, |
||||
|
deviceMode: deviceSetting.mode || 0, |
||||
|
onTime: deviceSetting.onTime || 1800, |
||||
|
offTime: deviceSetting.offTime || 600 |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 设置设备参数类型 |
||||
|
this.deviceParamType = 'lampParam'; |
||||
|
|
||||
|
|
||||
|
// 强制更新视图 |
||||
|
this.$forceUpdate(); |
||||
|
} else { |
||||
|
console.warn('数据格式不正确,没有 data 字段:', rawData); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('解析数据失败:', error); |
||||
|
console.error('原始数据:', data); |
||||
|
console.error('错误堆栈:', error.stack); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
handleClickTabs(tab) { |
||||
|
// tab切换处理 |
||||
|
}, |
||||
|
|
||||
|
getInfoValue(item) { |
||||
|
const value = this.dialogData[item.key]; |
||||
|
|
||||
|
if (item.enum) { |
||||
|
if (item.enum === 'CameraDirectionEnum') { |
||||
|
return CameraDirectionEnum[value]?.text || value; |
||||
|
} else if (item.enum === 'DeviceTypeEnum') { |
||||
|
return DeviceTypeEnum[value]?.text || value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return value || '-'; |
||||
|
}, |
||||
|
|
||||
|
getModeText(cmd) { |
||||
|
const modeMap = { 1: '同步', 2: '流水', 3: '尾迹' }; |
||||
|
return modeMap[cmd] || '-'; |
||||
|
}, |
||||
|
|
||||
|
getLightModeText(mode) { |
||||
|
const lightModeMap = { 0: '常亮', 1: '单闪', 2: '双闪', 3: '三闪' }; |
||||
|
return lightModeMap[mode] || '-'; |
||||
|
}, |
||||
|
|
||||
|
getColorText(color) { |
||||
|
const colorMap = { 'AA': '红色', '55': '黄色' }; |
||||
|
return colorMap[color] || '-'; |
||||
|
}, |
||||
|
|
||||
|
getColorStyle(color) { |
||||
|
const colorStyleMap = { 'AA': '#ff0000', '55': '#ffff00' }; |
||||
|
return colorStyleMap[color] ? { color: colorStyleMap[color] } : {}; |
||||
|
}, |
||||
|
|
||||
|
formatTime(timeValue) { |
||||
|
if (timeValue === undefined || timeValue === null) return '-'; |
||||
|
|
||||
|
const timeStr = String(timeValue).padStart(4, '0'); |
||||
|
const hour = timeStr.substring(0, 2); |
||||
|
const minute = timeStr.substring(2, 4); |
||||
|
|
||||
|
return `${hour}:${minute}`; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.DrivingGuidance { |
||||
|
width: 420px; |
||||
|
color: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
.video-stream { |
||||
|
height: 200px; |
||||
|
margin-bottom: 15px; |
||||
|
} |
||||
|
|
||||
|
.tabs { |
||||
|
flex: 1; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
::v-deep { |
||||
|
.el-tabs__content { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
|
||||
|
.el-tab-pane { |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.device-info { |
||||
|
padding: 10px; |
||||
|
|
||||
|
.info-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(2, 1fr); |
||||
|
gap: 12px; |
||||
|
margin-bottom: 20px; |
||||
|
|
||||
|
.info-item { |
||||
|
display: flex; |
||||
|
flex-direction: row; // 改为行布局 |
||||
|
align-items: center; // 垂直居中 |
||||
|
padding: 8px; |
||||
|
background: rgba(255, 255, 255, 0.05); |
||||
|
border-radius: 4px; |
||||
|
|
||||
|
.info-label { |
||||
|
font-size: 13px; |
||||
|
color: #72b6ef; |
||||
|
margin-right: 8px; // 改为右间距 |
||||
|
margin-bottom: 0; // 移除底部间距 |
||||
|
} |
||||
|
|
||||
|
.info-value { |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.device-setting-section { |
||||
|
margin-top: 15px; |
||||
|
padding: 12px; |
||||
|
background: rgba(255, 255, 255, 0.03); |
||||
|
border-radius: 6px; |
||||
|
border-left: 3px solid #409EFF; |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 15px; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 12px; |
||||
|
color: #409EFF; |
||||
|
} |
||||
|
|
||||
|
.setting-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 12px; |
||||
|
|
||||
|
.setting-item { |
||||
|
flex-direction: column; |
||||
|
|
||||
|
.setting-label { |
||||
|
font-size: 13px; |
||||
|
color: #a0a0a0; |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
|
||||
|
.setting-value { |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
|
||||
|
&.on { |
||||
|
color: #67c23a; |
||||
|
} |
||||
|
|
||||
|
&.off { |
||||
|
color: #f56c6c; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.device-params { |
||||
|
padding: 10px; |
||||
|
|
||||
|
.param-section { |
||||
|
.section-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 15px; |
||||
|
color: #409EFF; |
||||
|
border-left: 4px solid #409EFF; |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
|
||||
|
.param-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(2, 1fr); |
||||
|
gap: 12px; |
||||
|
|
||||
|
.param-item { |
||||
|
flex-direction: column; |
||||
|
padding: 10px; |
||||
|
background: rgba(255, 255, 255, 0.05); |
||||
|
border-radius: 4px; |
||||
|
|
||||
|
.param-label { |
||||
|
font-size: 13px; |
||||
|
color: #72b6ef; |
||||
|
margin-bottom: 5px; |
||||
|
margin-right: 8px; // 改为右间距 |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.param-value { |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
|
||||
|
&.on { |
||||
|
color: #67c23a; |
||||
|
} |
||||
|
|
||||
|
&.off { |
||||
|
color: #f56c6c; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.bottom { |
||||
|
margin-top: 12px; |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
|
||||
|
::v-deep button { |
||||
|
font-size: 14px; |
||||
|
padding: 8px 20px; |
||||
|
|
||||
|
&:disabled { |
||||
|
background-color: #666 !important; |
||||
|
cursor: not-allowed; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,323 @@ |
|||||
|
<template> |
||||
|
<Dialog v-model="visibleModel" title="行车诱导" width="470px"> |
||||
|
<div class="DrivingGuidance"> |
||||
|
<Video class="video-stream" :camId="camId" img="智能行车诱导" /> |
||||
|
|
||||
|
<ElTabs v-model="activeName" @tab-click="handleClickTabs" class="tabs"> |
||||
|
<ElTabPane label="基本信息" name="first"> |
||||
|
<Descriptions :list="list" :data="deviceInfoData" style="gap: 18px" /> |
||||
|
</ElTabPane> |
||||
|
<ElTabPane label="在线率统计" name="third"> |
||||
|
<LineChart v-if="activeName === 'third'" :productId="deviceInfoData.id" style="height: 180px" /> |
||||
|
</ElTabPane> |
||||
|
</ElTabs> |
||||
|
|
||||
|
<div class="bottom"> |
||||
|
<Button v-hasPermi="['business:home:drivingGuidance']" @click.native="onControlClick" :style="deviceInfoData.useState ? '' : 'background-color:grey'">设备操作</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<DeviceControlDialog v-model="deviceControlVisible" :deviceId="deviceInfoData.iotDeviceId" :id="deviceInfoData.id" |
||||
|
:deviceType="deviceInfoData.deviceType" /> |
||||
|
</Dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import Dialog from "@screen/components/Dialog/index.vue"; |
||||
|
import Button from "@screen/components/Buttons/Button.vue"; |
||||
|
import Descriptions from "@screen/components/Descriptions.vue"; |
||||
|
import DeviceControlDialog from "../Dialogs/DrivingGuidance/components/DeviceControlDialog.vue"; |
||||
|
import { |
||||
|
getRoadInfoByStakeMark, |
||||
|
getProduct, |
||||
|
} from "@screen/pages/Home/components/RoadAndEvents/utils/httpList.js"; |
||||
|
import Video from "@screen/components/Video"; |
||||
|
import request from "@/utils/request"; |
||||
|
import { delay } from "@screen/utils/common.js"; |
||||
|
import { handle3CResult } from "@screen/utils/deviceControl.js"; |
||||
|
import { inducerModeDic, inducerWorkTypeDic } from "@screen/utils/enum.js"; |
||||
|
import { dialogDelayVisible } from "../Dialogs/mixin.js"; |
||||
|
import LineChart from "../LineChart/index.vue"; |
||||
|
|
||||
|
export default { |
||||
|
name: "OldDeviceDrivingGuidance", |
||||
|
mixins: [dialogDelayVisible], |
||||
|
provide() { |
||||
|
return { |
||||
|
requestURL: this.requestURL, |
||||
|
updateFormData: this.updateFormData |
||||
|
}; |
||||
|
}, |
||||
|
components: { |
||||
|
Dialog, |
||||
|
Button, |
||||
|
Descriptions, |
||||
|
DeviceControlDialog, |
||||
|
Video, |
||||
|
LineChart, |
||||
|
}, |
||||
|
props: { |
||||
|
visible: { |
||||
|
type: Boolean, |
||||
|
default: true, |
||||
|
}, |
||||
|
data: { // 从父组件接收的数据 |
||||
|
type: Object, |
||||
|
default: () => ({}) |
||||
|
}, |
||||
|
}, |
||||
|
emits: ["close"], |
||||
|
computed: { |
||||
|
visibleModel: { |
||||
|
get() { |
||||
|
if (this.visible) this.getData(); |
||||
|
return this.visible; |
||||
|
}, |
||||
|
set(bool) { |
||||
|
console.log('子组件准备关闭,emit close'); |
||||
|
this.$emit('close'); |
||||
|
this.$emit("close", bool); |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
camId: '', |
||||
|
activeName: "first", |
||||
|
deviceControlVisible: false, |
||||
|
// 重命名为 deviceInfoData,避免与 props.data 冲突 |
||||
|
deviceInfoData: { |
||||
|
deviceType: "行车诱导", |
||||
|
deviceStation: "k094+079", |
||||
|
roadName: "济菏高速", |
||||
|
direction: "1", |
||||
|
deviceState: "0", |
||||
|
deviceVendors: "XXX厂家", |
||||
|
}, |
||||
|
list: [ |
||||
|
{ |
||||
|
label: "设备名称", |
||||
|
key: "deviceName", |
||||
|
}, |
||||
|
{ |
||||
|
label: "设备桩号", |
||||
|
key: "stakeMark", |
||||
|
}, |
||||
|
{ |
||||
|
label: "道路名称", |
||||
|
key: "roadName", |
||||
|
}, |
||||
|
{ |
||||
|
label: "设备方向", |
||||
|
key: "direction", |
||||
|
enum: "CameraDirectionEnum", |
||||
|
}, |
||||
|
{ |
||||
|
label: "设备状态", |
||||
|
key: "deviceState", |
||||
|
enum: "DeviceTypeEnum", |
||||
|
}, |
||||
|
{ |
||||
|
label: "设备厂商", |
||||
|
key: "manufacturer", |
||||
|
}, |
||||
|
{ |
||||
|
label: "工作模式", |
||||
|
key: "workMode", |
||||
|
}, |
||||
|
{ |
||||
|
label: "上行工作状态", |
||||
|
key: "onWorkStatus", |
||||
|
}, |
||||
|
{ |
||||
|
label: "下行工作状态", |
||||
|
key: "inWorkStatus", |
||||
|
}, |
||||
|
] |
||||
|
}; |
||||
|
}, |
||||
|
watch: { |
||||
|
// 监听 props.data 的变化 |
||||
|
data: { |
||||
|
immediate: true, |
||||
|
deep: true, |
||||
|
handler(newData) { |
||||
|
console.log('收到设备数据:', newData); |
||||
|
this.updateDeviceInfoData(newData); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
async created() { |
||||
|
// 初始化设备信息数据 |
||||
|
this.updateDeviceInfoData(this.data); |
||||
|
|
||||
|
// 等待数据加载完成 |
||||
|
await delay(100); |
||||
|
|
||||
|
// 解析camId |
||||
|
if (typeof this.deviceInfoData.otherConfig === 'string') { |
||||
|
try { |
||||
|
const oConfig = JSON.parse(this.deviceInfoData.otherConfig); |
||||
|
if (oConfig && oConfig.camId) { |
||||
|
this.camId = oConfig.camId; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('解析otherConfig失败:', e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 获取产品信息 - 修复 productId 访问 |
||||
|
if (this.deviceInfoData.productId) { |
||||
|
console.log('获取产品信息,productId:', this.deviceInfoData.productId); |
||||
|
getProduct(this.deviceInfoData.productId) |
||||
|
.then((data) => { |
||||
|
console.log('产品信息获取成功:', data); |
||||
|
if (data && data.brand) { |
||||
|
this.$set(this.deviceInfoData, "brand", data.brand); |
||||
|
} |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
console.error('获取产品信息失败:', err); |
||||
|
}); |
||||
|
} else { |
||||
|
console.warn('未找到 productId,跳过获取产品信息'); |
||||
|
} |
||||
|
|
||||
|
// 获取设备状态信息 |
||||
|
this.requestURL().then(async (data) => { |
||||
|
await delay(0); |
||||
|
const formData = {}; |
||||
|
formData.controlType = data.mode; |
||||
|
await handle3CResult(data, formData, this.requestURL); |
||||
|
this.updateFormData({ ...formData, workMode: data.mode }); |
||||
|
}).catch(err => { |
||||
|
console.error('获取设备状态失败:', err); |
||||
|
}); |
||||
|
}, |
||||
|
methods: { |
||||
|
// 更新设备信息数据 |
||||
|
updateDeviceInfoData(newData) { |
||||
|
if (!newData) return; |
||||
|
|
||||
|
console.log('更新设备信息数据:', newData); |
||||
|
|
||||
|
// 合并数据,保持原有的默认值 |
||||
|
this.deviceInfoData = { |
||||
|
...this.deviceInfoData, |
||||
|
...newData, |
||||
|
roadName: '济菏高速' // 保持默认的道路名称 |
||||
|
}; |
||||
|
|
||||
|
// 确保 iotDeviceId 是有效的 |
||||
|
if (!this.deviceInfoData.iotDeviceId) { |
||||
|
console.error('警告:设备ID为空'); |
||||
|
} else { |
||||
|
console.log('设备ID有效:', this.deviceInfoData.iotDeviceId); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getData() {}, |
||||
|
|
||||
|
onControlClick() { |
||||
|
if (this.deviceInfoData.useState) { |
||||
|
this.deviceControlVisible = true; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
updateFormData(formData) { |
||||
|
console.log('更新表单数据:', formData); |
||||
|
|
||||
|
if (formData.workMode) { |
||||
|
this.$set(this.deviceInfoData, "workMode", inducerModeDic[formData.workMode]); |
||||
|
} |
||||
|
if (formData.inWorkStatus) { |
||||
|
this.$set(this.deviceInfoData, "inWorkStatus", inducerWorkTypeDic[formData.inWorkStatus]); |
||||
|
} |
||||
|
if (formData.onWorkStatus) { |
||||
|
this.$set(this.deviceInfoData, "onWorkStatus", inducerWorkTypeDic[formData.onWorkStatus]); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
handleClickTabs() { }, |
||||
|
|
||||
|
requestURL(functionId = 52, options = {}) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 检查设备ID |
||||
|
if (!this.deviceInfoData.iotDeviceId) { |
||||
|
console.error('请求失败:设备ID为空'); |
||||
|
reject(new Error('设备ID为空')); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 确保 options 是对象,即使是空对象 |
||||
|
const requestBody = options && Object.keys(options).length > 0 ? options : {}; |
||||
|
|
||||
|
console.log(`发送请求:`, { |
||||
|
url: `business/device/functions/${this.deviceInfoData.iotDeviceId}/${functionId}`, |
||||
|
deviceId: this.deviceInfoData.iotDeviceId, |
||||
|
functionId: functionId, |
||||
|
body: requestBody |
||||
|
}); |
||||
|
|
||||
|
request |
||||
|
.post( |
||||
|
`business/device/functions/${this.deviceInfoData.iotDeviceId}/${functionId}`, |
||||
|
requestBody // 确保传递请求体 |
||||
|
) |
||||
|
.then((result) => { |
||||
|
if (result.code != 200) { |
||||
|
console.warn('请求返回非200状态:', result); |
||||
|
return reject(result); |
||||
|
} |
||||
|
resolve(result.data[0]); |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
console.error('请求失败:', err); |
||||
|
reject(err); |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.DrivingGuidance { |
||||
|
width: 420px; |
||||
|
color: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
.camera-video { |
||||
|
flex: 1.5; |
||||
|
} |
||||
|
|
||||
|
.tabs { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
::v-deep { |
||||
|
.el-tabs__content { |
||||
|
flex: 1; |
||||
|
|
||||
|
.el-tab-pane { |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.bottom { |
||||
|
margin-top: 12px; |
||||
|
display: flex; |
||||
|
gap: 9px; |
||||
|
align-items: center; |
||||
|
justify-content: end; |
||||
|
|
||||
|
>div { |
||||
|
font-size: 16px; |
||||
|
padding: 6px 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue