2025-07-03 08:57:13 +08:00

1502 lines
39 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="voice-control">
<!-- 基础信息 -->
<view class="card">
<view class="status-titletop">{{ title }}</view>
<view style="padding:20rpx;">
<u--form labelPosition="left" labelWidth="100"
:labelStyle="{ marginRight: '16px', lineHeight: '32px', width: '50px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', color: '#000000' }">
<view class="version-wrap">
<u-form-item :label="$tt('status.deviceVersion')">
<u-row>
<u-col span="8">
<u--text :text="'Version ' + device.firmwareVersion"></u--text>
</u-col>
</u-row>
</u-form-item>
</view>
</u--form>
</view>
</view>
<view class="card basic-info">
<view class="section-title">基础设置</view>
<view class="info-content">
<!-- 音量控制 -->
<view class="volume-slider">
<view class="volume-icon">
<image src="https://xaznkj.cn/doc/photo/brightness.png" mode="aspectFit" class="volume-svg">
</image>
</view>
<view class="slider-container">
<u-slider v-model="volume" :min="0" :max="100" :step="1" @change="volumeChange"
:disabled="device.status !== 3" height="4" activeColor="#2979ff" blockSize="18"
:showValue="false">
</u-slider>
<view class="volume-marks">
<text>0</text>
<text>50</text>
<text>100</text>
</view>
</view>
<view class="volume-value">{{ volume }}%</view>
</view>
<!-- 音频开关 -->
<view class="audio-switch">
<text>屏幕开关</text>
<u-switch v-model="audioEnabled" @change="audioSwitchChange" :disabled="device.status !== 3"
size="22"></u-switch>
</view>
<!-- 屏幕参数 -->
<view class="clear-screen-btn-wrap">
<u-button type="error" size="medium" shape="circle" @click="clearScreen"
customStyle="box-shadow:0 4rpx 12rpx rgba(255,77,79,0.15);font-weight:600;">
<u-icon name="trash" size="18" color="#fff" style="margin-right:8rpx;" />
清除屏幕
</u-button>
</view>
</view>
</view>
<!-- 屏幕参数卡片 -->
<view class="card screen-params">
<view class="section-title">屏幕参数</view>
<view class="screen-params-form">
<u-cell-group>
<u-cell @click="showPicker('template')" title="屏幕模板"
:value="screenParams.templateLabel || '请选择屏幕模板'" isLink></u-cell>
<u-cell @click="showPicker('screenRotate')" title="屏幕旋转"
:value="screenParams.screenRotateLabel || '请选择屏幕旋转'" isLink></u-cell>
</u-cell-group>
<u-form :model="screenParams" labelPosition="left" labelWidth="120">
<u-form-item label="OE极性" prop="oePolarity" borderBottom>
<view style="display:flex;align-items:center;">
<u-switch v-model="screenParams.oePolarity" :active-value="'1'" :inactive-value="'0'"
@change="val => screenParams.oePolarityLabel = val === '1' ? '1' : '0'" size="22"
style="margin-right:12rpx;" />
<u-input v-model="screenParams.oePolarityLabel" readonly border="none" style="flex:1;" />
</view>
</u-form-item>
<u-form-item label="DATA极性" prop="dataPolarity" borderBottom>
<view style="display:flex;align-items:center;">
<u-switch v-model="screenParams.dataPolarity" :active-value="'1'" :inactive-value="'0'"
@change="val => screenParams.dataPolarityLabel = val === '1' ? '1' : '0'" size="22"
style="margin-right:12rpx;" />
<u-input v-model="screenParams.dataPolarityLabel" readonly border="none" style="flex:1;" />
</view>
</u-form-item>
<u-form-item label="屏宽" prop="width" borderBottom>
<u-input v-model="screenParams.width" type="number" placeholder="请输入屏宽" />
</u-form-item>
<u-form-item label="屏高" prop="height" borderBottom>
<u-input v-model="screenParams.height" type="number" placeholder="请输入屏高" />
</u-form-item>
</u-form>
</view>
<!-- <view style="text-align:center;margin:20rpx 0;">
<u-button type="primary" size="mini" @click="testOpenPicker">测试弹窗</u-button>
</view> -->
</view>
<!-- 节目列表 -->
<view class="card audio-list">
<view class="section-title">
<text>节目列表</text>
<image src="https://xaznkj.cn/doc/photo/add.svg" mode="aspectFit" class="add-icon"
@click="showAddAudioModal"></image>
</view>
<view class="list-container">
<view class="empty-tip" v-if="audioList.length === 0">
<u-icon name="qzone" size="50" color="#c0c4cc"></u-icon>
<text>暂无节目文件</text>
</view>
<view class="audio-item" v-for="(item, index) in audioList" :key="index">
<view class="audio-info">
<u-icon name="play-right" size="18" color="#2979ff"></u-icon>
<text class="audio-name">{{ item.name }}</text>
</view>
<view class="audio-actions">
<u-icon name="trash" size="18" color="#ff4d4f" @click="deleteAudio(index)"></u-icon>
</view>
</view>
</view>
</view>
<!-- 虚拟遥控器 -->
<!-- <view class="card default-list">
<view class="section-title">
<text>虚拟遥控器</text>
</view>
<view class="list-container">
<view class="empty-tip" v-if="defaultList.length === 0">
<u-icon name="star" size="50" color="#c0c4cc"></u-icon>
<text>暂无节目</text>
</view>
<view class="audio-item" v-for="(item, index) in defaultList" :key="index">
<view class="audio-info">
<text class="audio-name">{{ item.name }}</text>
</view>
<view class="audio-actions">
<u-switch v-model="item.status" :active-value="'启用'" :inactive-value="'禁用'"
@change="(value) => handleStatusChange(index, value)" size="22"></u-switch>
</view>
</view>
</view>
</view> -->
<!-- 开机参数 -->
<view class="card remote-talk">
<view class="section-title">开机参数</view>
<view class="info-content">
<!-- 这里内容暂时空着如需表单可补充 -->
</view>
</view>
<!-- 测试按钮 -->
<!-- Pickers -->
<u-picker :show="pickerShow.template" :columns="[templateColumns]" keyName="label"
@confirm="onPickerConfirm('template')" @cancel="closePicker('template')" safe-area-inset-bottom></u-picker>
<u-picker :show="pickerShow.screenRotate" :columns="[screenRotateOptions]" keyName="label"
@confirm="onPickerConfirm('screenRotate')" @cancel="closePicker('screenRotate')"
safe-area-inset-bottom></u-picker>
</view>
</template>
<script>
import {
serviceInvoke
} from '@/apis/modules/runtime.js';
export default {
name: 'VoiceControl',
props: {
device: {
type: Object,
required: true
}
},
data() {
return {
title: '设备离线',
volume: 50,
audioEnabled: true,
showAddAudio: false,
showAddDefault: false,
showStartTimePicker: false,
showEndTimePicker: false,
showAudioPicker: false,
weekDays: ['日', '一', '二', '三', '四', '五', '六'],
audioIndex: -1,
newAudio: {
name: '',
per: '0',
spd: '5',
pit: '5',
vol: '5',
text: '',
file: null
},
deviceInfo: {
chartList: [],
},
newDefault: {
startTime: '',
endTime: '',
repeatDays: [],
radarEnabled: false,
minSpeed: '',
maxSpeed: '',
audioFile: null
},
audioFiles: [],
audioList: [],
defaultList: [],
audioUrl: '',
uploadFailed: false,
screenParams: {
template: '',
templateLabel: '',
oePolarity: '0',
oePolarityLabel: '0',
dataPolarity: '0',
dataPolarityLabel: '0',
screenRotate: '',
screenRotateLabel: '',
width: '',
height: ''
},
pickerShow: {
template: false,
screenRotate: false
},
templateColumns: [{
label: '自定义',
value: '1'
},
{
label: '伸缩屏',
value: '2'
},
{
label: '大屏',
value: '3'
},
{
label: '小屏',
value: '4'
}
],
screenRotateOptions: [{
label: '0°',
value: '0'
},
{
label: '90°',
value: '90'
},
{
label: '180°',
value: '180'
},
{
label: '270°',
value: '270'
}
],
};
},
created() {
if (this.device !== null && Object.keys(this.device).length !== 0) {
this.deviceInfo = this.device;
this.updateDeviceStatus(this.deviceInfo);
};
this.mqttCallback();
this.recorderManager = uni.getRecorderManager();
this.updateBasicSettings();
// 监听节目数据准备完成事件
uni.$on('programDataReady', this.handleProgramData);
},
beforeDestroy() {
if (this.recordingTimer) {
clearInterval(this.recordingTimer);
this.recordingTimer = null;
}
// 移除事件监听
uni.$off('programDataReady', this.handleProgramData);
},
methods: {
showPicker(type) {
this.pickerShow[type] = true
},
closePicker(type) {
this.pickerShow[type] = false
},
onPickerConfirm(type, e) {
console.log('Picker confirm:', type, e);
const value = e.value[0];
this.screenParams[type] = value.value;
this.screenParams[type + 'Label'] = value.label;
this.closePicker(type);
},
clearScreen() {
uni.showToast({
title: '已清除屏幕',
icon: 'success'
});
},
checkOnline(callback, ...args) {
if (this.device.status !== 3) {
uni.showToast({
title: '设备离线,无法操作',
icon: 'none'
});
return false;
}
if (typeof callback === 'function') {
return callback.apply(this, args);
}
return true;
},
async volumeChange(value) {
if (!this.checkOnline()) return;
try {
const volumeModel = this.device.thingsModels.find(item => item.id === 'volume');
if (volumeModel) {
volumeModel.shadow = value.toString();
await this.mqttPublish(this.device, volumeModel);
}
} catch (error) {
console.error('调整音量失败:', error);
uni.showToast({
title: '操作失败: ' + error.message,
icon: 'none'
});
}
},
mqttCallback() {
this.$mqttTool.client.on('message', (topic, message, buffer) => {
let topics = topic.split('/');
let productId = topics[1];
let deviceNum = topics[2];
message = JSON.parse(message.toString());
if (topics[3] == 'status') {
if (this.deviceInfo.serialNumber == deviceNum) {
this.deviceInfo.status = message.status;
this.deviceInfo.isShadow = message.isShadow;
this.deviceInfo.rssi = message.rssi;
this.updateDeviceStatus(this.deviceInfo);
this.updateBasicSettings();
}
}
if (topics[4] == 'reply') {
uni.showToast({
icon: 'none',
title: message,
})
}
if (topics[3] == 'property' || topics[3] == 'function' || topic.endsWith('ws/service')) {
if (this.deviceInfo.serialNumber == deviceNum) {
for (let j = 0; j < message.message.length; j++) {
let isComplete = false;
for (let k = 0; k < this.deviceInfo.thingsModels.length && !isComplete; k++) {
if (this.deviceInfo.thingsModels[k].id == message.message[j].id) {
this.deviceInfo.thingsModels[k].shadow = message.message[j].value;
isComplete = true;
break;
} else 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 == message
.message[j].id) {
this.deviceInfo.thingsModels[k].datatype.params[n].shadow = message
.message[j].value;
isComplete = true;
break;
}
}
} else if (this.deviceInfo.thingsModels[k].datatype.type == "array") {
if (this.deviceInfo.thingsModels[k].datatype.arrayType == "object") {
if (String(message.message[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 == message.message[j].id) {
this.deviceInfo.thingsModels[k].datatype.arrayParams[n]
[m].shadow = message.message[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 + message.message[j].id) {
this.deviceInfo.thingsModels[k].datatype.arrayParams[n]
[m].shadow = message.message[j].value;
isComplete = true;
}
}
if (isComplete) {
break;
}
}
}
} else {
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayModel
.length; n++) {
if (this.deviceInfo.thingsModels[k].datatype.arrayModel[n].id ==
message.message[j].id) {
this.deviceInfo.thingsModels[k].datatype.arrayModel[n].shadow =
message.message[j].value;
isComplete = true;
break;
}
}
}
}
};
for (let k = 0; k < this.deviceInfo.chartList.length && !isComplete; k++) {
if (this.deviceInfo.chartList[k].id.indexOf("array_") == 0) {
if (this.deviceInfo.chartList[k].id == message.message[j].id) {
this.deviceInfo.chartList[k].shadow = message.message[j].value;
isComplete = true;
break;
}
} else {
if (this.deviceInfo.chartList[k].id == message.message[j].id) {
this.deviceInfo.chartList[k].shadow = message.message[j].value;
isComplete = true;
break;
}
}
if (isComplete) {
break;
}
};
}
}
this.updateBasicSettings();
}
});
},
async mqttPublish(device, model) {
const command = {};
command[model.id] = model.shadow;
const data = {
serialNumber: device.serialNumber,
productId: device.productId,
remoteCommand: command,
identifier: model.id,
modelName: model.name,
isShadow: device.status != 3,
type: model.type
};
serviceInvoke(data).then(response => {
if (response.code === 200) {
uni.showToast({
icon: 'none',
title: this.$tt('status.service')
});
}
});
},
updateDeviceStatus(device) {
if (device.status === 3) {
this.title = this.$tt('status.online');
} else {
this.title = device.isShadow === 1 ? this.$tt('status.shadow') : this.$tt('status.deviceOffline');
}
},
showAddAudioModal() {
// 将设备信息存储到本地,供 addProgram 页面使用
uni.setStorageSync('currentDevice', this.device);
uni.setStorageSync('currentDeviceInfo', this.deviceInfo);
uni.navigateTo({
url: '/pagesA/home/device/status/addProgram'
});
console.log('页面跳转');
},
showAddDefaultModal() {
this.showAddDefault = true;
},
async confirmAddAudio() {
if (!this.checkOnline()) return;
try {
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id ===
'103#mp3List');
if (!mp3ListModel) {
throw new Error('未找到 mp3_list 模型');
}
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
const data = JSON.parse(jsonStr);
let maxId = 0;
if (data.sound_card && data.sound_card.mp3_list) {
data.sound_card.mp3_list.forEach(item => {
const id = parseInt(item.split('_')[0]);
if (!isNaN(id) && id > maxId) {
maxId = id;
}
});
}
const newId = maxId + 1;
const ttsData = {
JSON_id: 1,
sound_card: {
TTS: {
per: parseInt(this.newAudio.per),
spd: parseInt(this.newAudio.spd),
pit: parseInt(this.newAudio.pit),
vol: parseInt(this.newAudio.vol),
tex_utf8: this.newAudio.text,
filename: `${newId}_${this.newAudio.name}`
}
}
};
mp3ListModel.shadow = 'JSON=' + JSON.stringify(ttsData);
await this.mqttPublish(this.device, mp3ListModel);
this.newAudio = {
name: '',
per: '0',
spd: '5',
pit: '5',
vol: '5',
text: '',
file: null
};
this.showAddAudio = false;
uni.showToast({
title: '添加成功',
icon: 'success'
});
} catch (error) {
console.error('添加音频失败:', error);
uni.showToast({
title: '添加失败: ' + error.message,
icon: 'none'
});
}
},
async confirmAddDefault() {
if (!this.checkOnline()) return;
if (!this.newDefault.startTime || !this.newDefault.endTime || !this.newDefault.audioFile) {
uni.showToast({
title: '请填写完整信息',
icon: 'none'
});
return;
}
if (this.newDefault.radarEnabled) {
if (!this.newDefault.minSpeed || !this.newDefault.maxSpeed) {
uni.showToast({
title: '请填写速度范围',
icon: 'none'
});
return;
}
if (parseInt(this.newDefault.minSpeed) >= parseInt(this.newDefault.maxSpeed)) {
uni.showToast({
title: '最小速度必须小于最大速度',
icon: 'none'
});
return;
}
}
const playListModel = this.deviceInfo.thingsModels.find(model => model.id ===
'103#playList');
if (playListModel) {
try {
const jsonStr = playListModel.shadow.replace('JSON=', '');
const data = JSON.parse(jsonStr);
if (!data.sound_card) {
data.sound_card = {};
}
if (!data.sound_card.play_list) {
data.sound_card.play_list = [];
}
let maxNum = 0;
if (data.sound_card.play_list.length > 0) {
maxNum = Math.max(...data.sound_card.play_list.map(item => item.play.num ||
0));
}
const [startHour, startMinute] = this.newDefault.startTime.split(':').map(Number);
const [endHour, endMinute] = this.newDefault.endTime.split(':').map(Number);
const startSeconds = startHour * 3600 + startMinute * 60;
const endSeconds = endHour * 3600 + endMinute * 60;
let weekValue = 0;
this.newDefault.repeatDays.forEach(day => {
weekValue |= (1 << day);
});
const newPlayItem = {
play: {
num: maxNum + 1,
filename: this.newDefault.audioFile.name,
en: 1
},
time: {
begin: startSeconds,
end: endSeconds,
week: weekValue
},
speed: {
en: this.newDefault.radarEnabled ? 1 : 0,
min: this.newDefault.radarEnabled ? parseInt(this.newDefault
.minSpeed) : 0,
max: this.newDefault.radarEnabled ? parseInt(this.newDefault
.maxSpeed) : 0
}
};
data.sound_card.play_list.push(newPlayItem);
playListModel.shadow = 'JSON=' + JSON.stringify(data);
try {
await this.mqttPublish(this.device, playListModel);
uni.showToast({
title: '添加成功',
icon: 'success'
});
this.showAddDefault = false;
this.newDefault = {
startTime: '',
endTime: '',
repeatDays: [],
radarEnabled: false,
minSpeed: '',
maxSpeed: '',
audioFile: null
};
} catch (error) {
console.error('发送添加命令失败:', error);
uni.showToast({
title: '添加失败',
icon: 'none'
});
}
} catch (error) {
console.error('解析或更新播放列表失败:', error);
uni.showToast({
title: '添加失败',
icon: 'none'
});
}
}
},
async deleteAudio(index) {
if (!this.checkOnline()) return;
try {
uni.showModal({
title: '提示',
content: '确定要删除该音频吗?',
cancelText: '取消',
confirmText: '确定',
success: async (res) => {
if (res.confirm) {
const mp3ListModel = this.deviceInfo.thingsModels.find(
model =>
model
.id === '103#mp3List');
if (mp3ListModel && mp3ListModel.shadow) {
try {
const jsonStr = mp3ListModel.shadow.replace(
'JSON=',
'');
const data = JSON.parse(jsonStr);
if (data.sound_card && data.sound_card.mp3_list) {
data.sound_card.mp3_list.splice(index, 1);
mp3ListModel.shadow = 'JSON=' + JSON.stringify(
data);
await this.mqttPublish(this.device,
mp3ListModel);
this.audioList.splice(index, 1);
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
} catch (error) {
console.error('删除音频失败:', error);
uni.showToast({
title: '删除失败: ' + error.message,
icon: 'none'
});
}
}
}
}
});
} catch (error) {
console.error('删除音频失败:', error);
uni.showToast({
title: '删除失败: ' + error.message,
icon: 'none'
});
}
},
deleteDefault(index) {
uni.showModal({
title: '提示',
content: '确认删除该播放项吗?',
cancelText: '取消',
confirmText: '确定',
success: async (res) => {
if (res.confirm) {
const playListModel = this.deviceInfo.thingsModels.find(
model =>
model.id ===
'103#playList');
if (playListModel) {
try {
const jsonStr = playListModel.shadow.replace('JSON=',
'');
const data = JSON.parse(jsonStr);
if (data.sound_card && data.sound_card.play_list) {
data.sound_card.play_list.splice(index, 1);
data.sound_card.play_list.forEach((item,
index) => {
item.play.num = index + 1;
});
playListModel.shadow = 'JSON=' + JSON.stringify(
data);
try {
await this.mqttPublish(this.device,
playListModel);
uni.showToast({
title: '删除成功',
icon: 'success'
});
} catch (error) {
console.error('发送删除命令失败:', error);
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
}
} catch (error) {
console.error('解析或更新播放列表失败:', error);
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
}
}
}
});
},
afterAudioRead(file) {
this.audioFiles.push(file);
},
deleteAudioFile(index) {
this.audioFiles.splice(index, 1);
},
beforeAudioUpload(file) {
const isValidType = file.name.toLowerCase().endsWith('.mp3');
const isValidSize = file.size / 1024 / 1024 < 10;
if (!isValidType) {
uni.showToast({
title: '只能上传MP3格式',
icon: 'none'
});
return false;
}
if (!isValidSize) {
uni.showToast({
title: '文件大小不能超过10MB',
icon: 'none'
});
return false;
}
return true;
},
async audioSwitchChange() {
if (!this.checkOnline()) return;
try {
const playEnModel = this.device.thingsModels.find(item => item.id ===
'play_en');
if (playEnModel) {
playEnModel.shadow = this.audioEnabled ? '1' : '0';
await this.mqttPublish(this.device, playEnModel);
}
} catch (error) {
console.error('切换音频开关失败:', error);
uni.showToast({
title: '操作失败: ' + error.message,
icon: 'none'
});
}
},
formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
},
startTimeChange(e) {
this.newDefault.startTime = e.detail.value;
},
endTimeChange(e) {
this.newDefault.endTime = e.detail.value;
},
toggleWeekDay(index) {
const position = this.newDefault.repeatDays.indexOf(index);
if (position === -1) {
this.newDefault.repeatDays.push(index);
} else {
this.newDefault.repeatDays.splice(position, 1);
}
},
radarSwitchChange(value) {
this.newDefault.radarEnabled = value;
if (!value) {
this.newDefault.minSpeed = '';
this.newDefault.maxSpeed = '';
}
},
audioChange(e) {
this.audioIndex = e.detail.value;
this.newDefault.audioFile = this.audioList[this.audioIndex];
},
updateBasicSettings() {
if (!this.deviceInfo.thingsModels) return;
const playEnModel = this.deviceInfo.thingsModels.find(model => model.id ===
'103#playEn');
if (playEnModel) {
this.audioEnabled = playEnModel.shadow === '1';
}
const volumeModel = this.deviceInfo.thingsModels.find(model => model.id ===
'103#volume');
if (volumeModel) {
this.volume = parseInt(volumeModel.shadow) || 50;
}
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id ===
'103#mp3List');
if (mp3ListModel && mp3ListModel.shadow) {
try {
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
const data = JSON.parse(jsonStr);
if (data && data.mp3_list) {
this.audioList = data.mp3_list.map((item, index) => {
const name = item.split('_')[1] || item;
return {
id: index + 1,
name: name
};
});
}
} catch (error) {
console.error('解析音频列表失败:', error);
}
}
const playListModel = this.deviceInfo.thingsModels.find(model => model.id ===
'103#playList');
if (playListModel && playListModel.shadow) {
try {
const jsonStr = playListModel.shadow.replace('JSON=', '');
const data = JSON.parse(jsonStr);
if (data && data.play_list) {
this.defaultList = data.play_list.map((item, index) => {
const beginTime = this.formatSecondsToTime(item.time
.begin);
const endTime = this.formatSecondsToTime(item.time.end);
const weekdays = this.convertWeekToArray(item.time.week);
return {
id: index + 1,
name: item.play.filename,
playTime: `${beginTime} - ${endTime}`,
weekdays: weekdays.join(', '),
radarEnabled: item.speed.en === 1,
status: item.play.en === 1 ? '启用' : '禁用',
radarSpeed: item.speed.en === 1 ?
`${item.speed.min}-${item.speed.max}km/h` : ''
};
});
}
} catch (error) {
console.error('解析播放列表失败:', error);
}
}
},
formatSecondsToTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
},
convertWeekToArray(week) {
const weekdays = [];
for (let i = 0; i < 7; i++) {
if (week & (1 << i)) {
weekdays.push(this.weekDays[i]);
}
}
return weekdays;
},
async handleStatusChange(index, value) {
const playListModel = this.deviceInfo.thingsModels.find(model => model.id ===
'103#playList');
if (playListModel) {
try {
const jsonStr = playListModel.shadow.replace('JSON=', '');
const data = JSON.parse(jsonStr);
if (data.sound_card && data.sound_card.play_list) {
data.sound_card.play_list[index].play.en = value === '启用' ? 1 : 0;
playListModel.shadow = 'JSON=' + JSON.stringify(data);
try {
await this.mqttPublish(this.device, playListModel);
uni.showToast({
title: '更新成功',
icon: 'success'
});
} catch (error) {
console.error('发送状态更新命令失败:', error);
uni.showToast({
title: '更新失败',
icon: 'none'
});
this.defaultList[index].status = value === '启用' ? '禁用' : '启用';
}
}
} catch (error) {
console.error('解析或更新播放列表失败:', error);
uni.showToast({
title: '更新失败',
icon: 'none'
});
this.defaultList[index].status = value === '启用' ? '禁用' : '启用';
}
}
},
deleteRecording(index) {
uni.showModal({
title: '提示',
content: '确定要删除该录音吗?',
cancelText: '取消',
confirmText: '确定',
success: (res) => {
if (res.confirm) {
this.recordings.splice(index, 1);
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
}
});
},
// testOpenPicker(type) {
// this.pickerShow[type] = true;
// console.log('测试按钮pickerShow.' + type + ' =', this.pickerShow[type]);
// },
// 处理从 addProgram 页面传来的节目数据
async handleProgramData(data) {
try {
console.log('收到节目数据:', data);
console.log('设备模型列表:', this.device.thingsModels);
// 检查设备是否在线
if (!this.checkOnline()) return;
// 根据实际的设备模型ID来处理数据
// 这里需要根据你的设备实际模型来调整
// 示例1更新显示参数模型
const displayParamsModel = this.device.thingsModels.find(model =>
model.id === 'displayParams' || model.id === '103#displayParams'
);
if (displayParamsModel) {
const displayData = {
mode: data.form.mode,
duration: parseInt(data.form.duration) || 10,
zones: data.form.zones.map(zone => ({
playType: zone.playType,
displayText: zone.displayText,
font: zone.font,
fontSize: zone.fontSize,
fontColor: zone.fontColor,
effect: zone.effect,
speed: zone.speed,
stayTime: zone.stayTime,
hAlign: zone.hAlign,
vAlign: zone.vAlign,
x: parseInt(zone.x) || 0,
y: parseInt(zone.y) || 0,
width: parseInt(zone.width) || 32,
height: parseInt(zone.height) || 64
}))
};
displayParamsModel.shadow = JSON.stringify(displayData);
await this.mqttPublish(this.device, displayParamsModel);
}
// 示例2如果有专门的节目模型
const programModel = this.device.thingsModels.find(model =>
model.id === 'program' || model.id === '103#program'
);
if (programModel) {
programModel.shadow = JSON.stringify(data.form);
await this.mqttPublish(this.device, programModel);
}
// 示例3更新屏幕参数
const screenParamsModel = this.device.thingsModels.find(model =>
model.id === 'screenParams' || model.id === '103#screenParams'
);
if (screenParamsModel) {
const screenData = {
template: this.screenParams.template,
screenRotate: this.screenParams.screenRotate,
oePolarity: this.screenParams.oePolarity,
dataPolarity: this.screenParams.dataPolarity,
width: this.screenParams.width,
height: this.screenParams.height,
program: data.form
};
screenParamsModel.shadow = JSON.stringify(screenData);
await this.mqttPublish(this.device, screenParamsModel);
}
uni.showToast({
title: '节目设置成功',
icon: 'success'
});
// 刷新基础设置
this.updateBasicSettings();
} catch (error) {
console.error('处理节目数据失败:', error);
uni.showToast({
title: '设置失败: ' + error.message,
icon: 'none'
});
}
},
},
computed: {
startTimeLabel() {
return this.newDefault.startTime ? this.formatTime(this.newDefault.startTime) : '请选择开始时间';
},
endTimeLabel() {
return this.newDefault.endTime ? this.formatTime(this.newDefault.endTime) : '请选择结束时间';
},
selectedAudioName() {
return this.audioIndex >= 0 ? this.audioList[this.audioIndex].name : '';
}
}
};
</script>
<style lang="scss" scoped>
.status-titletop {
font-weight: bold;
font-size: 15px;
color: #333333;
line-height: 42rpx;
text-align: left;
padding: 15rpx 28rpx;
background-color: #eef6ff;
border-bottom: 1rpx solid #dceaff;
}
.voice-control {
padding: 24rpx;
background-color: #f5f7fa;
min-height: 100vh;
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
.card {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.1);
}
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
padding: 24rpx;
border-bottom: 1rpx solid #f0f2f5;
display: flex;
align-items: center;
background-color: #f9fbfd;
.add-icon {
width: 40rpx;
height: 40rpx;
margin-left: auto;
cursor: pointer;
transition: transform 0.2s;
&:active {
transform: scale(0.9);
}
}
}
.info-content {
padding: 24rpx;
}
/* 音量控制样式 */
.volume-slider {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 24rpx;
padding: 20rpx;
background-color: #f8fafc;
border-radius: 12rpx;
border: 1rpx solid #eaeef5;
.volume-icon {
width: 48rpx;
height: 48rpx;
display: flex;
justify-content: center;
align-items: center;
.volume-svg {
width: 100%;
height: 100%;
}
}
.slider-container {
flex: 1;
position: relative;
.volume-marks {
display: flex;
justify-content: space-between;
margin-top: 12rpx;
padding: 0 8rpx;
text {
font-size: 24rpx;
color: #909399;
font-weight: 500;
}
}
}
.volume-value {
min-width: 80rpx;
text-align: right;
color: #2979ff;
font-size: 28rpx;
font-weight: 600;
}
}
/* 开关样式 */
.audio-switch {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
background-color: #f8fafc;
border-radius: 12rpx;
margin-bottom: 24rpx;
border: 1rpx solid #eaeef5;
text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
/* 屏幕参数表单 */
.screen-params {
.screen-params-form {
padding: 20rpx 24rpx;
::v-deep .u-form-item {
margin-bottom: 20rpx;
&__body {
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f2f5;
&__left {
font-size: 28rpx;
color: #606266;
font-weight: 500;
}
&__right {
.u-input {
font-size: 28rpx;
color: #333;
text-align: right;
}
}
}
}
}
}
/* 列表样式 */
.audio-list,
.default-list {
.list-container {
padding: 0 24rpx 24rpx;
}
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
color: #c0c4cc;
font-size: 28rpx;
gap: 20rpx;
opacity: 0.8;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
opacity: 0.6;
}
}
.audio-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f5f5;
transition: all 0.2s ease;
&:last-child {
border-bottom: none;
}
&:active {
background-color: #f8f8f8;
}
.audio-info {
display: flex;
align-items: center;
gap: 16rpx;
flex: 1;
overflow: hidden;
.audio-name {
font-size: 28rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.audio-actions {
display: flex;
align-items: center;
gap: 24rpx;
margin-left: 16rpx;
.audio-duration {
font-size: 26rpx;
color: #909399;
}
}
}
}
/* 清除屏幕按钮 */
.clear-screen-btn-wrap {
display: flex;
justify-content: center;
margin-top: 24rpx;
::v-deep .u-button {
width: 60%;
height: 80rpx;
font-size: 28rpx;
}
}
/* 弹窗样式 */
.add-audio-modal {
width: 80vw;
max-width: 600rpx;
padding: 0;
border-radius: 16rpx;
background-color: #fff;
overflow: hidden;
.modal-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
padding: 28rpx;
text-align: center;
border-bottom: 1rpx solid #f5f5f5;
}
.modal-content {
padding: 0 28rpx;
margin: 20rpx 0;
max-height: 60vh;
overflow-y: auto;
::v-deep .u-form-item {
padding: 20rpx 0;
&__body {
padding: 0;
&__left {
width: 160rpx;
font-size: 28rpx;
color: #606266;
}
&__right {
.u-input {
font-size: 28rpx;
color: #333;
}
}
}
}
.slider-with-value {
display: flex;
align-items: center;
width: 100%;
.custom-slider {
flex: 1;
margin-right: 20rpx;
}
}
::v-deep .u-radio-group {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
}
.modal-footer {
display: flex;
justify-content: space-between;
padding: 16rpx 28rpx 28rpx;
border-top: 1rpx solid #f5f5f5;
.u-button {
width: 48%;
height: 80rpx;
font-size: 28rpx;
border-radius: 40rpx;
}
}
}
/* 周选择器 */
.week-picker {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
padding: 10rpx 0;
.week-item {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 36rpx;
background-color: #f5f5f5;
font-size: 28rpx;
color: #666;
transition: all 0.3s ease;
cursor: pointer;
&.active {
background-color: #2979ff;
color: #fff;
font-weight: 500;
}
}
}
/* 速度范围 */
.speed-range {
display: flex;
align-items: center;
gap: 10rpx;
.u-input {
flex: 1;
}
.separator {
color: #666;
padding: 0 10rpx;
}
.unit {
color: #666;
font-size: 26rpx;
margin-left: 10rpx;
}
}
/* 选择器值显示 */
.picker-value {
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
color: #333;
padding: 0 20rpx;
background-color: #f9f9f9;
border-radius: 8rpx;
}
/* 测试按钮 */
.test-card {
.test-buttons {
display: flex;
justify-content: space-between;
padding: 20rpx;
}
}
}
</style>