1406 lines
34 KiB
Vue
1406 lines
34 KiB
Vue
<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://iot-xcwl.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://iot-xcwl.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();
|
||
},
|
||
beforeDestroy() {
|
||
if (this.recordingTimer) {
|
||
clearInterval(this.recordingTimer);
|
||
this.recordingTimer = null;
|
||
}
|
||
},
|
||
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() {
|
||
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]);
|
||
// },
|
||
},
|
||
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> |