This commit is contained in:
JayJiaJun 2025-03-14 09:37:40 +08:00
parent f43f1d57ed
commit 3c80f4a2f7
3 changed files with 378 additions and 160 deletions

View File

@ -140,7 +140,7 @@ export default {
//
this.sendInterval = setInterval(() => {
this.sendControlData();
}, 1000); // 100ms
}, 400); // 100ms
//
this.audioHandler = {

View File

@ -1,7 +1,9 @@
<template>
<div class="cone-control">
<van-nav-bar title="路锥控制" left-arrow @click-left="onClickLeft" fixed placeholder />
<van-nav-bar title="路锥控制" left-arrow @click-left="onClickLeft" fixed />
<!-- 添加下拉刷新组件 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<!-- 为模组概况添加边框 -->
<div class="module-container">
<div class="module-header">
@ -14,101 +16,91 @@
<div class="module-info">
<div class="info-row">
<span class="info-label">固件版本</span>
<span class="info-value" ref="firmwareVersionEl"></span>
<span class="info-value" ref="firmwareVersionEl">{{ firmwareVersion }}</span>
</div>
<div class="info-row">
<span class="info-label">MAC地址</span>
<span class="info-value" ref="macAddressEl"></span>
<span class="info-value" ref="macAddressEl">{{ macAddress }}</span>
</div>
<div class="info-title">网络参数</div>
<div class="info-row">
<span class="info-label">网络ID</span>
<van-field v-model="networkId" type="text" input-align="right" placeholder="请输入网络ID" maxlength="6"
:formatter="value => value.replace(/[^0-9]/g, '')" />
<van-field v-model="networkId" readonly input-align="right" />
</div>
<div class="info-row">
<span class="info-label">无线频段</span>
<van-field v-model="freqBand" is-link readonly input-align="right" placeholder="请选择频段"
@click="showFreqBandPopup" />
<van-field v-model="freqBand" readonly input-align="right" />
</div>
<div class="info-row">
<span class="info-label">信道</span>
<van-field v-model="channel" is-link readonly input-align="right" placeholder="请选择信道"
@click="showChannelPopup" />
<van-field v-model="channel" readonly input-align="right" />
</div>
<div class="info-row">
<span class="info-label">速率(SF)</span>
<van-field v-model="speedRate" is-link readonly input-align="right" placeholder="请选择速率"
@click="showSpeedRatePopup" />
<van-field v-model="speedRate" readonly input-align="right" />
</div>
<div class="info-row">
<span class="info-label">功率</span>
<van-field v-model="power" is-link readonly input-align="right" placeholder="请选择功率"
@click="showPowerPopup" />
<van-field v-model="power" readonly input-align="right" />
</div>
<div class="upload-button-container">
<div class="mode-container">
<div class="info-row mode-row">
<span class="info-label">网络通道</span>
<van-field v-model="currentMode" is-link readonly input-align="right" placeholder="请选择网络通道"
@click="showModePopup" class="mode-field" />
</div>
</div>
<!-- <div class="upload-button-container">
<van-button type="primary" size="small" block @click="uploadLoRaConfig">上传配置</van-button>
</div>
</div> -->
</div>
</template>
</van-cell>
</van-cell-group>
</div>
<!-- 指令控制面板 -->
<div class="command-container">
<div class="command-header">
<van-icon name="apps-o" size="18" />
<span>控制面板</span>
</div>
<div class="command-grid">
<div class="command-item" @click="sendCommand('command1')">
<div class="command-icon">
<van-icon name="play-circle-o" size="24" />
</div>
<div class="command-text">启动路锥</div>
</div>
<div class="command-item" @click="sendCommand('command2')">
<div class="command-icon">
<van-icon name="pause-circle-o" size="24" />
</div>
<div class="command-text">停止路锥</div>
</div>
<div class="command-item" @click="sendCommand('command3')">
<div class="command-icon">
<van-icon name="setting-o" size="24" />
</div>
<div class="command-text">设置模式</div>
</div>
<div class="command-item" @click="sendCommand('command4')">
<div class="command-icon">
<van-icon name="warning-o" size="24" />
</div>
<div class="command-text">紧急停止</div>
</div>
<!-- 状态和控制面板 -->
<div class="status-container">
<div class="module-header">
<van-icon name="info-o" size="18" />
<span>状态与控制</span>
</div>
<van-cell-group inset>
<!-- 报警状态只读 -->
<van-cell title="报警状态">
<template #value>
<span :style="{ color: alarmStatus === '正常' ? '#07c160' : '#ee0a24' }">{{ alarmStatus }}</span>
</template>
<template #right-icon>
<van-button v-if="alarmStatus !== '正常'" size="small" type="danger"
@click="sendCommand('clearAlarm')">清除报警</van-button>
</template>
</van-cell>
<!-- 灯光状态选择 -->
<van-cell title="灯光状态" :value="lightStatus" is-link @click="showLightStatusPicker" />
<!-- 布防开关 -->
<van-cell center title="布防开关">
<template #right-icon>
<van-switch v-model="defenseEnabled" size="24" @change="onDefenseChange" />
</template>
</van-cell>
</van-cell-group>
</div>
<!-- 添加选择器弹出层 -->
<van-popup v-model:show="freqBandPickerVisible" position="bottom">
<van-picker :columns="freqBandOptions" @confirm="onFreqBandConfirm" @cancel="freqBandPickerVisible = false"
show-toolbar />
<!-- 灯光状态选择器 -->
<van-popup v-model:show="lightStatusPickerVisible" position="bottom">
<van-picker :columns="lightStatusOptions" @confirm="onLightStatusConfirm"
@cancel="lightStatusPickerVisible = false" show-toolbar />
</van-popup>
<van-popup v-model:show="channelPickerVisible" position="bottom">
<van-picker :columns="channelOptions" @confirm="onChannelConfirm" @cancel="channelPickerVisible = false"
show-toolbar />
</van-popup>
<van-popup v-model:show="speedRatePickerVisible" position="bottom">
<van-picker :columns="speedRateOptions" @confirm="onSpeedRateConfirm" @cancel="speedRatePickerVisible = false"
show-toolbar />
</van-popup>
<van-popup v-model:show="powerPickerVisible" position="bottom">
<van-picker :columns="powerOptions" @confirm="onPowerConfirm" @cancel="powerPickerVisible = false"
show-toolbar />
<!-- 添加模式选择器 -->
<van-popup v-model:show="modePickerVisible" position="bottom">
<van-picker :columns="modeOptions" @confirm="onModeConfirm" @cancel="modePickerVisible = false" show-toolbar />
</van-popup>
</van-pull-refresh>
</div>
</template>
@ -135,6 +127,15 @@ export default {
const channelPickerVisible = ref(false);
const speedRatePickerVisible = ref(false);
const powerPickerVisible = ref(false);
const alarmStatus = ref('报警');
const lightStatus = ref('白灯亮');
const defenseEnabled = ref(false);
const lightStatusPickerVisible = ref(false);
const refreshing = ref(false);
const firmwareVersion = ref('');
const macAddress = ref('');
const modePickerVisible = ref(false);
const currentMode = ref('');
//
const freqBandOptions = [
@ -170,6 +171,24 @@ export default {
{ text: '2dbm', value: 7 }
];
//
const lightStatusOptions = [
{ text: '白光亮(高)', value: 0 },
{ text: '白光亮(中)', value: 1 },
{ text: '白光亮(低)', value: 2 },
{ text: '红蓝快闪', value: 3 },
{ text: '红蓝慢闪', value: 4 },
{ text: '投射灯亮', value: 5 },
{ text: '关闭', value: 6 }
];
const modeOptions = [
{ text: '通道一', value: 0, networkId: '6A6A00' },
{ text: '通道二', value: 1, networkId: '6A6A01' },
{ text: '通道三', value: 2, networkId: '6A6A02' },
{ text: '通道四', value: 3, networkId: '6A6A03' }
];
const onClickLeft = () => {
router.back();
};
@ -190,6 +209,14 @@ export default {
powerPickerVisible.value = true;
};
const showLightStatusPicker = () => {
lightStatusPickerVisible.value = true;
};
const showModePopup = () => {
modePickerVisible.value = true;
};
const onFreqBandConfirm = (value) => {
freqBand.value = value.selectedOptions[0].text;
freqBandPickerVisible.value = false;
@ -210,6 +237,68 @@ export default {
powerPickerVisible.value = false;
};
const onLightStatusConfirm = (value) => {
lightStatus.value = value.selectedOptions[0].text;
lightStatusPickerVisible.value = false;
sendLightStatusCommand(value.selectedOptions[0].value);
};
const sendLightStatusCommand = (value) => {
const lightStatusCommand = {
board_id: 106,
JSON_id: 1,
cones_card: {
traffic_cone: {
lamplight: value
}
},
error_code: 0
};
if (isLocal.value) {
axios.post('/communication', lightStatusCommand, {
headers: {
"content-type": "application/json"
}
})
.then(response => {
console.log('Light status response:', response.data);
})
.catch(error => {
console.error('Light status error:', error);
});
} else {
MQTT_send(lightStatusCommand);
}
};
const onDefenseChange = (value) => {
const defenseCommand = {
board_id: 106,
JSON_id: 1,
cones_card: {
traffic_cone: {
mode: value ? 1 : 0
}
},
error_code: 0
};
if (isLocal.value) {
axios.post('/communication', defenseCommand, {
headers: {
"content-type": "application/json"
}
})
.then(response => {
console.log('Defense mode response:', response.data);
})
.catch(error => {
console.error('Defense mode error:', error);
});
} else {
MQTT_send(defenseCommand);
}
};
const checkEnvironment = () => {
if (window.location.hostname === '192.168.4.1') {
console.log('in local');
@ -291,12 +380,130 @@ export default {
};
const sendCommand = (command) => {
//
showToast(`发送${command}`);
if (command === 'clearAlarm') {
const clearAlarmCommand = {
board_id: 106,
JSON_id: 1,
cones_card: {
traffic_cone: {
alarm: 0
}
},
error_code: 0
};
if (isLocal.value) {
axios.post('/communication', clearAlarmCommand, {
headers: {
"content-type": "application/json"
}
})
.then(response => {
console.log('Clear alarm response:', response.data);
alarmStatus.value = '正常';
})
.catch(error => {
console.error('Clear alarm error:', error);
});
} else {
MQTT_send(clearAlarmCommand);
}
}
};
const onModeConfirm = (value) => {
currentMode.value = value.selectedOptions[0].text;
networkId.value = value.selectedOptions[0].networkId;
modePickerVisible.value = false;
//
const configData = {
board_id: 106,
JSON_id: 1,
cones_card: {
LoRa_cfg: {
mesh_id: [
parseInt(networkId.value.slice(0, 2), 16),
parseInt(networkId.value.slice(2, 4), 16),
parseInt(networkId.value.slice(4, 6), 16)
],
fre_band: parseInt(freqBand.value),
channel: parseInt(channel.value),
SF: parseInt(speedRate.value),
power: parseInt(power.value),
mode: value.selectedOptions[0].value
}
},
error_code: 0
};
if (isLocal.value) {
axios.post('/communication', configData, {
headers: {
"content-type": "application/json"
}
})
.then(response => {
console.log('Mode update response:', response.data);
})
.catch(error => {
console.error('Mode update error:', error);
});
} else {
MQTT_send(configData);
}
};
const updateStateFromResponse = (data) => {
firmwareVersion.value = data.versions;
macAddress.value = data.mac_addr.join(':');
networkId.value = data.mesh_id.map(num => num.toString(16).padStart(2, '0').toUpperCase()).join('');
freqBand.value = freqBandOptions.find(option => option.value === data.fre_band)?.text || '';
channel.value = `信道${data.channel + 1}`;
speedRate.value = speedRateOptions.find(option => option.value === data.SF)?.text || '';
power.value = powerOptions.find(option => option.value === data.power)?.text || '';
//
const mode = modeOptions.find(option => option.networkId === networkId.value);
if (mode) {
currentMode.value = mode.text;
}
};
const onRefresh = () => {
const refreshCommand = {
board_id: 106,
JSON_id: 1,
cones_card: {
get_traffic_cone: 1,
get_LoRa_cfg: 1
},
error_code: 0
};
if (isLocal.value) {
axios.post('/communication', refreshCommand, {
headers: {
"content-type": "application/json"
}
})
.then(response => {
console.log('Refresh response:', response.data);
updateStateFromResponse(response.data.cones_card.LoRa_cfg);
refreshing.value = false;
})
.catch(error => {
console.error('Refresh error:', error);
refreshing.value = false;
});
} else {
MQTT_send(refreshCommand);
refreshing.value = false;
}
};
onMounted(() => {
checkEnvironment();
onRefresh();
});
return {
@ -326,6 +533,23 @@ export default {
channelOptions,
speedRateOptions,
powerOptions,
alarmStatus,
lightStatus,
defenseEnabled,
lightStatusPickerVisible,
lightStatusOptions,
showLightStatusPicker,
onLightStatusConfirm,
onDefenseChange,
refreshing,
onRefresh,
firmwareVersion,
macAddress,
modePickerVisible,
currentMode,
modeOptions,
showModePopup,
onModeConfirm,
};
}
};
@ -358,7 +582,7 @@ export default {
gap: 8px;
}
.command-container {
.status-container {
margin: 16px;
background: #ffffff;
border-radius: 12px;
@ -366,52 +590,6 @@ export default {
overflow: hidden;
}
.command-header {
padding: 16px;
font-size: 16px;
font-weight: 500;
color: #323233;
border-bottom: 1px solid #f5f5f5;
display: flex;
align-items: center;
gap: 8px;
}
.command-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 16px;
}
.command-item {
background: #f7f8fa;
border-radius: 8px;
padding: 16px;
text-align: center;
transition: all 0.3s;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.command-item:active {
background: #e8e8e8;
transform: scale(0.98);
}
.command-icon {
color: #1989fa;
margin-bottom: 4px;
}
.command-text {
font-size: 14px;
color: #323233;
}
.info-row {
display: flex;
align-items: center;
@ -459,4 +637,44 @@ export default {
.command-item:active::after {
opacity: 1;
}
/* 为清除报警按钮添加样式 */
:deep(.van-button--danger) {
margin-left: 8px;
}
.mode-container {
margin: 12px -16px;
padding: 12px 16px;
background-color: #f2f7ff;
/* 浅蓝色背景 */
border-left: 4px solid #1989fa;
/* 左边添加蓝色边框 */
}
.mode-row {
margin-bottom: 0;
/* 覆盖原来的 margin */
}
.mode-field {
font-weight: 500;
/* 字体加粗 */
color: #1989fa;
/* 使用主题蓝色 */
}
.mode-field :deep(.van-field__value) {
color: #1989fa;
}
.mode-field :deep(.van-field__placeholder) {
color: #1989fa;
opacity: 0.7;
}
/* 让其他网络参数稍微淡一点 */
.info-row:not(.mode-row) {
opacity: 0.85;
}
</style>

View File

@ -106,8 +106,8 @@
<van-field
v-if="currentEvent.type === '其他'"
v-model="customEventType"
label="自定义类型"
placeholder="请输入自定义事件类型"
label="时间码"
placeholder="请输入时间码"
input-align="right"
type="number"
/>
@ -118,8 +118,8 @@
<van-field
v-if="currentEvent.source === '其他'"
v-model="customEventSource"
label="自定义来源"
placeholder="请输入自定义事件来源"
label="来源"
placeholder="请输入来源"
input-align="right"
type="number"
/>
@ -468,7 +468,7 @@ export default {
pins: []
} : {}),
...(outputType === '其他' ? {
customTarget: '',
customTarget: '0',
customData1: '0',
customData2: '0',
customData3: '0'
@ -1044,7 +1044,7 @@ export default {
// setup
const uploadLoRaConfig = () => {
//
if (!networkId.value || !freqBand.value || !channel.value || !speedRate.value || !power.value) {
if (!networkId.value || !freqBand.value || !channel.value || !speedRate.value || !power.value || !customEventType.value || !customEventSource.value) {
showToast('请填写所有必填字段');
return;
}