GateWay/src/views/voice/voiceset.vue

1404 lines
51 KiB
Vue
Raw Normal View History

2024-11-13 11:26:59 +08:00
<template>
<div class="page-container">
<Header :title="currentTitle" />
<van-pull-refresh v-model="loading" @refresh="onRefresh" success-text="刷新成功" failed-text="刷新失败">
<div class="content">
<div class="home">
<p class="title">设备音量</p>
<van-cell-group inset>
<van-field v-model.number="voicevalue" type="number" label="设备音量" placeholder="请输入0-100"
max="100" />
<van-slider v-model.number="voicevalue" type="number" :step="1" :max="100" :min="0"
@change="onvoiceChange" class="slider" />
</van-cell-group>
<div class="button-container">
<!-- <van-button type="primary" size="large" @click="uploadSettings">上传</van-button> -->
</div>
</div>
<div class="home">
<p class="title">默认播放列表</p>
<van-icon class="icon" name="plus" @click="showDialogForPlaylist" />
<ul class="playlist">
<!-- 播放列表 -->
<li v-for="(item, index) in playlist" :key="index" class="playlist-item"
@click="editdefaultlist(index)">
<button class="delete-btn" @click.stop="removeItem(index, 'playlist')">删除</button>
<p class="remark">备注: {{ item.remark }}</p>
<div class="duration-info">
<p>播放时长: {{ item.runningtime }} </p>
<p>暂停时长: {{ item.pauseduration }} </p>
</div>
</li>
</ul>
<!-- <van-button v-if="playlist.length > 0" type="primary" size="normal" @click="sendPlaylistData">发送播放列表</van-button> -->
</div>
<div class="home">
<p class="title">事件播放列表</p>
<van-icon class="icon" name="plus" @click="showDialogForEventPlaylist" />
<ul class="playlist">
<!-- 事件播放列表 -->
<li v-for="(item, index) in eventplaylist" :key="index" class="playlist-item"
@click="editeventlist(index)">
<button class="delete-btn" @click.stop="removeItem(index, 'eventplaylist')">删除</button>
<p class="remark">备注: {{ item.remark }}</p>
<div class="duration-info">
<p>事件来源:
<span v-if="item.EventSourcename === '其他'">{{ item.EventSource }}</span>
<span v-else>{{ item.EventSourcename }}</span>
</p>
<p>事件码:
<span v-if="item.eventcodename === '其他'">{{ item.eventcode }}</span>
<span v-else>{{ item.eventcodename }}</span>
</p>
</div>
</li>
</ul>
</div>
</div>
</van-pull-refresh>
<!-- Dialog 组件 -->
<van-dialog v-model:show="showOptionsDialog" title="选择音频来源" @confirm="onDialogConfirm" show-cancel-button>
<van-radio-group v-model="audioSource">
<van-radio class="choice" name="online">在线合成音频</van-radio>
<van-radio class="choice" name="local">本地选择音频</van-radio>
</van-radio-group>
<!-- v-if="!isLocal" -->
</van-dialog>
<!-- 默认本地合成语音弹窗 -->
<van-dialog v-model:show="showlocal" title="本地语音合成" @confirm="addLocalAudio" show-cancel-button
@cancel="Reset_input()">
<van-cell-group inset>
<van-field v-model="filename" is-link readonly name="picker" label="文件名" placeholder="请选择文件名"
@click="showfilenamePicker = true" />
<van-field v-model="remark" label="备注" />
<van-field v-model="runningtime" label="播放时长" />
<van-field v-model="pauseduration" label="暂停时长" />
</van-cell-group>
</van-dialog>
<!-- 默认远程合成语音弹窗 -->
<van-dialog v-model:show="showonline" title="在线语音合成" @confirm="addOnlineAudio" show-cancel-button
@cancel="Reset_input()">
<van-cell-group inset>
<van-field v-model="filename" label="文件名" readonly />
<van-field v-model="remark" label="备注" />
<!-- <van-field v-model="Broadcast_sound" label="播报声音" /> -->
<van-field v-model="speed" label="合成语速" placeholder="语速0-15" />
<van-cell is-link title="播报声音" @click="showBroadcast_sound = true">
{{ Broadcast_sound }}
</van-cell>
<van-field v-model="text" label="合成文本" type="textarea" placeholder="请输入合成文本" class="custom-textarea" />
<van-field v-model="runningtime" label="播放时长" />
<van-field v-model="pauseduration" label="暂停时长" />
<!-- 强制合成开关 -->
<van-cell title="强制合成">
<template #right-icon>
<van-switch v-model="forceSynthesis" />
</template>
</van-cell>
</van-cell-group>
</van-dialog>
<!-- 事件本地合成语音弹窗 -->
<van-dialog v-model:show="showeventlocal" title="本地事件语音合成" @confirm="addEventLocalAudio" show-cancel-button
@cancel="Reset_input()">
<van-cell-group inset>
<!-- <div class="field">
<label for="eventFilename">选择文件名:</label>
<select id="eventFilename" v-model="filename">
<option value="" disabled selected>请选择文件名</option>
<option v-for="option in filenameOptions" :key="option" :value="option">{{ option }}</option>
</select>
</div> -->
<van-field v-model="filename" is-link readonly name="picker" label="文件名" placeholder="请选择文件名"
@click="showfilenamePicker = true" />
<van-field v-model="remark" label="备注" placeholder="请输入备注名" />
<!-- <van-field v-model="runningtime" label="事件来源" /> -->
<van-field v-model="sourcefieldValue" is-link readonly label="事件来源" placeholder="选择事件来源"
@click="showsourcePicker = true" />
<van-field v-if="sourcefieldValue == '其他'" v-model="jsonEventSource" label="事件来源"
placeholder="请输入事件来源码" />
<van-field v-model="fieldValue" is-link readonly label="事件码" placeholder="请输入事件码"
@click="showPicker = true" />
<van-field v-if="fieldValue == '其他'" v-model="jsoneventcode" label="事件码" placeholder="请输入事件码" />
<van-field v-model="priority" label="优先级" placeholder="请输入优先级" />
<!-- <van-field v-model="pauseduration" label="事件码" /> -->
</van-cell-group>
</van-dialog>
<!-- 事件远程合成语音弹窗 -->
<van-dialog v-model:show="showeventonline" title="在线事件语音合成" @confirm="addEventOnlineAudio" show-cancel-button
@cancel="Reset_input()">
<van-cell-group inset>
<van-field v-model="filename" label="文件名" readonly />
<van-field v-model="remark" label="备注" placeholder="请输入备注名" />
<van-cell is-link title="播报声音" @click="showBroadcast_sound = true">
{{ Broadcast_sound }}
</van-cell>
<van-field v-model="speed" label="合成语速" placeholder="语速范围取值0-15" />
<van-field v-model="text" label="合成文本" type="textarea" placeholder="请输入合成文本" class="custom-textarea" />
<van-field v-model="sourcefieldValue" is-link readonly label="事件来源" placeholder="选择事件来源"
@click="showsourcePicker = true" />
<van-field v-if="sourcefieldValue == '其他'" v-model="jsonEventSource" label="事件来源"
placeholder="请输入事件来源码" />
<van-field v-model="fieldValue" is-link readonly label="事件码" placeholder="请输入事件码"
@click="showPicker = true" />
<van-field v-if="fieldValue == '其他'" v-model="jsoneventcode" label="事件码" placeholder="请输入事件码" />
<van-field v-model="priority" label="优先级" placeholder="请输入优先级" />
<van-cell title="强制合成">
<template #right-icon>
<van-switch v-model="forceSynthesis" />
</template>
</van-cell>
</van-cell-group>
</van-dialog>
<van-action-sheet v-model:show="showBroadcast_sound" :actions="speedactions" v-model="Broadcast_sound"
@select="onSelect" />
<van-popup v-model:show="showPicker" round position="bottom">
<van-picker :columns="codecolumns" @cancel="showPicker = false" @confirm="onConfirm" />
</van-popup>
<van-popup v-model:show="showsourcePicker" round position="bottom">
<van-picker :columns="sourcecolumns" @cancel="showsourcePicker = false" @confirm="onConfirmsource" />
</van-popup>
<van-popup v-model:show="showfilenamePicker" position="bottom">
<van-picker :columns="filenamecolumns" @confirm="onConfirmfilename" @cancel="showfilenamePicker = false" />
</van-popup>
</div>
<Footer />
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from "vue";
import { useRouter } from "vue-router";
import Header from "../../components/Header.vue";
import Footer from "../../components/Footer.vue";
import axios from "axios";
// import { connectMQTT, client } from "../../components/MQTT/mqttclient";
import { showToast } from "vant";
export default {
components: {
Header,
Footer,
},
setup() {
const currentTitle = ref("声卡设置");
const isLocal = ref(true); // 定义 isLocal 变量并初始化为 false
const loading = ref(false);
const jsonId = ref(1); // 初始 JSON ID
const voicevalue = ref(0);
const showfilenamePicker = ref(false);
const showBroadcast_sound = ref(false);
const speedactions = [
{ name: '度小美(0)', value: '0' },
{ name: '度小宇(1)', value: '1' },
{ name: '度逍遥(3)', value: '3' },
{ name: '度丫丫(4)', value: '4' },
];
const filenamecolumns = ref([]);;
const flag = ref(false);
//false 表示默认的tts合成
const router = useRouter();
const filename = ref("");
const remark = ref("");
const priority = ref("0");
const speed = ref("5");
const Broadcast_sound = ref("0");
const text = ref("");
const eventcode = ref("");
const EventSource = ref("");
const runningtime = ref("1");
const pauseduration = ref("1");
const playlist = ref([]); // 播放列表数组
const eventplaylist = ref([]); // 事件播放列表数组
const showOptionsDialog = ref(false); // 控制 Dialog 显示状态
const selectedOption = ref(""); // 用于指示哪个列表要被添加
const audioSource = ref(""); // 保存用户选择的音频来源
const showlocal = ref(false);
const showonline = ref(false);
const showeventlocal = ref(false);
const showeventonline = ref(false);
const filenameOptions = ref([]); // 文件名选项
let editingIndex = ref(null);
let jsoneventcode = ref("0");
let jsonEventSource = ref("1");
const EventSourcename = ref("");
const eventcodename = ref("");
const isEditing = ref(false);
const forceSynthesis = ref(false);
const newFilename = ref('');
let currentIndex = ref("0");
let newItem_content = ref([]);
//事件码
const codecolumns = [
{ text: '默认显示', value: '0' },
{ text: '雷达触发(RS485雷达)', value: '1' },
{ text: '雷达超速(RS485雷达)', value: '2' },
{ text: '车牌触发(摄像头)', value: '3' },
{ text: '车牌超速(摄像头)', value: '4' },
{ text: '行人检测(相机)', value: '5' },
{ text: 'IN1下降沿', value: '0x51' },
{ text: 'IN2上升沿', value: '0x52' },
{ text: 'IN3下降沿', value: '0x53' },
{ text: 'IN4上升沿', value: '0x54' },
{ text: 'IN1低电平', value: '0x55' },
{ text: 'IN2高电平', value: '0x56' },
{ text: 'IN3低电平', value: '0x57' },
{ text: 'IN4高电平', value: '0x58' },
{ text: '其他', value: '-1' },
];
const combinedList = ref([]); // 定义为响应式变量
let fieldValue = ref('默认显示');
const showPicker = ref(false);
const onConfirm = ({ selectedOptions }) => {
showPicker.value = false;
fieldValue.value = selectedOptions[0].text;
jsoneventcode.value = selectedOptions[0].value;
};
//事件码
//事件来源
const sourcecolumns = [
{ text: '任意设备(除本身外)', value: '254' },
{ text: '任意设备', value: '255' },
{ text: '其他', value: '1' },
];
const onConfirmfilename = ({ selectedOptions }) => {
filename.value = selectedOptions[0]?.text;
showfilenamePicker.value = false;
};
let sourcefieldValue = ref('其他');
const showsourcePicker = ref(false);
const onConfirmsource = ({ selectedOptions }) => {
showsourcePicker.value = false;
sourcefieldValue.value = selectedOptions[0].text;
jsonEventSource.value = selectedOptions[0].value;
};
//事件来源
const checkEnvironment = () => {
if (window.location.hostname === '192.168.4.1') {
console.log('in local');
// 本地开发环境,设置 isLocal 为 true
isLocal.value = true;
} else {
// 生产环境,设置 isLocal 为 false
isLocal.value = false;
console.log('in online');
}
};
const onSelect = (item) => {
// 默认情况下点击选项时不会自动收起
// 可以通过 close-on-click-action 属性开启自动收起
showBroadcast_sound.value = false;
Broadcast_sound.value = item.value;
Broadcast_sound.name = item.name;
showToast(item.name);
};
const removeItem = (index, type) => {
if (type === "playlist") {
if (index >= 0 && index < playlist.value.length) {
playlist.value.splice(index, 1); // 从播放列表中删除
sendPlaylistData();
}
} else if (type === "eventplaylist") {
if (index >= 0 && index < eventplaylist.value.length) {
eventplaylist.value.splice(index, 1); // 从事件播放列表中删除
sendEventPlaylistData();
}
}
};
const editdefaultlist = (index) => {
// 进入编辑模式
isEditing.value = true;
// 获取所选的事件项目
const selectedEvent = playlist.value[index];
// 将所选事件的值赋给对应的输入框
filename.value = selectedEvent.filename;
remark.value = selectedEvent.remark;
runningtime.value = selectedEvent.runningtime;
pauseduration.value = selectedEvent.pauseduration;
// 设置原始值用于比较
// 根据需要设置其他输入框的值
Broadcast_sound.value = selectedEvent.Broadcast_sound || "";
speed.value = selectedEvent.speed || "";
text.value = selectedEvent.text || "";
if (selectedEvent.sou === 1) {
showonline.value = true;
} else {
showlocal.value = true; // 或者 showeventonline.value = true根据具体情况选择
}
// 设置当前编辑的索引
editingIndex.value = index;
console.log(editingIndex.value);
};
// 事件列表编辑函数
const editeventlist = (index) => {
// 进入编辑模式
isEditing.value = true;
// 获取所选的事件项目
const selectedEvent = eventplaylist.value[index];
// 将所选事件的值赋给对应的输入框
filename.value = selectedEvent.filename;
remark.value = selectedEvent.remark;
sourcefieldValue.value = selectedEvent.EventSourcename;
jsonEventSource.value = selectedEvent.EventSource;
jsoneventcode.value = selectedEvent.eventcode;
fieldValue.value = selectedEvent.eventcodename;
priority.value = selectedEvent.priority;
// 根据需要设置其他输入框的值
Broadcast_sound.value = selectedEvent.Broadcast_sound || "";
speed.value = selectedEvent.speed || "";
text.value = selectedEvent.text || "";
if (selectedEvent.sou === 1) {
showeventonline.value = true;
} else {
showeventlocal.value = true; // 或者 showeventonline.value = true根据具体情况选择
}
// 设置当前编辑的索引
editingIndex.value = index;
console.log(editingIndex.value);
};
// 在组件挂载时获取音频数据
onMounted(async () => {
// showToast("success")
window.MQTT_recv = MQTT_recv;
await checkEnvironment();
getallinfo();
// fetchAudioData(); // 获取音频数据
// fetchFilenameOptions();
});
const onvoiceChange = async () => {
const newVolume = voicevalue.value;
const payload = {
JSON_id: jsonId.value,
board_id: 103,
sound_card: {
param:
{
volume: newVolume
}
}
}
if (isLocal.value) {
axios.post(`/communication`, payload, {
headers: {
"content-type": "application/json"
}
})
.then(response => {
console.log('Response data:', response.data);
showToast('刷新成功');
jsonId.value++;
loading.value = false; // 结束刷新时设置 loading 为 false
})
.catch(error => {
// 处理错误
console.error('Upload error:', error);
showToast('刷新失败');
jsonId.value++;
loading.value = false; // 结束刷新时设置 loading 为 false
});
}
else {
MQTT_send(payload);
showToast('设置已上传');
}
};
const MQTT_send = (send_string) => {
console.log("MQTT 发送:" + JSON.stringify(send_string));
if (/xazn/.test(navigator.userAgent) || /uni-app/.test(navigator.userAgent)) {
uni.postMessage({
data: {
str: JSON.stringify(send_string)
}
});
} else {
window.parent.postMessage({ str: JSON.stringify(send_string) }, "*");
}
};
// MQTT 接收函数
const MQTT_recv = (string) => {
console.log("MQTT 接收的json:" + JSON.stringify(string));
const data = JSON.parse(string);
if (data.sound_card.TTS_state) {
if (data.sound_card.TTS_state == 'succeed' || data.sound_card.TTS_state == 'time_out') {
const index = currentIndex; // 这里假设你有一个 currentIndex 用于追踪编辑项的索引
if (flag.value) {
if (isEditing) {
// 如果是编辑模式,更新原有项
eventplaylist.value[index] = newItem_content;
} else {
// 如果是新建模式,添加新项
eventplaylist.value.push(newItem_content);
}
sendEventPlaylistData();
showToast('TTS 合成成功,已添加到事件播放列表');
} else {
if (isEditing) {
// 如果是编辑模式,更新原有项
playlist.value[index] = newItem_content;
} else {
// 如果是新建模式,添加新项
playlist.value.push(newItem_content);
}
sendPlaylistData();
showToast('TTS 合成成功,已添加到事件播放列表');
}
}
}
else {
console.log("执行解析函数");
console.log(JSON.stringify(data.sound_card));
handleJsonMessage(data.sound_card);
}
};
//文件名管理
const getNextTTSFilename = (list) => {
// 过滤出以 'TTS' 开头的 filename 属性的值
const usedTTSNumbers = list
.filter(item => item.filename && item.filename.startsWith('TTS'))
.map(item => parseInt(item.filename.replace('TTS', ''), 10))
.filter(number => !isNaN(number)); // 排除非数字的情况
// 找到未使用的最小编号
for (let i = 0; i <= 29; i++) {
if (!usedTTSNumbers.includes(i)) {
return `TTS${i}`;
}
}
return null; // 如果所有编号都被使用,返回 null
};
const onRefresh = () => {
getallinfo();
};
const showDialogForPlaylist = () => {
if (playlist.length > 10) {
showToast("添加数量已满");
} else {
selectedOption.value = "default"; // 设置选择选项为默认播放列表
showOptionsDialog.value = true; // 显示选项对话框
}
};
const showDialogForEventPlaylist = () => {
if (eventplaylist.length > 20) {
showToast("添加数量已满");
} else {
selectedOption.value = "event"; // 设置选择选项为事件播放列表
showOptionsDialog.value = true; // 显示选项对话框
}
};
const onDialogConfirm = () => {
console.log('New TTS Filename:', newFilename.value);
const source = audioSource.value; // 获取选择的音频来源
if (selectedOption.value === "default") {
// 判断选项是默认播放列表
if (source === "local") {
showlocal.value = true; // 显示本地合成语音弹窗
} else if (source === "online") {
showonline.value = true; // 显示在线合成语音弹窗
filename.value = newFilename.value;
}
} else if (selectedOption.value === "event") {
// 判断选项是事件播放列表
if (source === "local") {
showeventlocal.value = true; // 显示本地事件合成语音弹窗
} else if (source === "online") {
showeventonline.value = true; // 显示在线事件合成语音弹窗
filename.value = newFilename.value;
}
}
showOptionsDialog.value = false; // 关闭选项对话框
};
const addLocalAudio = () => {
if (!filename.value.trim()) {
showToast('文件名为空,请填写文件名');
return; // 退出函数,防止添加或更新操作
}
// 添加本地音频到播放列表
if (isEditing.value) {
// 更新现有项
playlist.value[editingIndex.value] = {
filename: filename.value,
remark: remark.value,
runningtime: runningtime.value,
pauseduration: pauseduration.value,
Broadcast_sound: Broadcast_sound.value,
speed: speed.value,
text: text.value,
sou: 0
};
// 退出编辑模式
Reset_input();
isEditing.value = false;
} else {
// 添加新项
playlist.value.push({
filename: filename.value,
remark: remark.value,
runningtime: runningtime.value,
pauseduration: pauseduration.value,
Broadcast_sound: Broadcast_sound.value,
speed: speed.value,
text: text.value,
sou: 0
});
Reset_input();
}
isEditing.value = false;
console.log(isEditing.value);
sendPlaylistData();
};
const addOnlineAudio = async () => {
console.log(newFilename.value);
const newItem = {
filename: newFilename.value,
remark: remark.value,
runningtime: runningtime.value,
pauseduration: pauseduration.value,
Broadcast_sound: Broadcast_sound.value,
speed: speed.value,
text: text.value,
sou: 1
};
newItem_content = newItem;
if (isEditing.value) {
// 获取当前编辑的项目
const originalItem = playlist.value[editingIndex.value];
// 检查是否需要进行 TTS 合成
const ttsRequired = forceSynthesis.value ||
originalItem.Broadcast_sound !== newItem.Broadcast_sound ||
originalItem.speed !== newItem.speed ||
originalItem.text !== newItem.text;
if (ttsRequired) {
await ttssend(newItem, editingIndex.value);
} else {
// 如果不需要 TTS 合成,直接更新现有项
playlist.value[editingIndex.value] = newItem;
}
} else {
// 非编辑模式,直接添加新项并进行 TTS 合成
const newIndex = playlist.value.length;
currentIndex = newIndex;
await ttssend(newItem, newIndex);
// playlist.value.push(newItem);
}
Reset_input();
isEditing.value = false;
// sendPlaylistData();
};
const addEventLocalAudio = () => {
// 检查文件名是否为空
if (!filename.value.trim()) {
showToast('文件名为空,请填写文件名');
return; // 退出函数,防止添加或更新操作
}
// 添加本地事件音频到事件播放列表
if (isEditing.value) {
eventplaylist.value[editingIndex.value] = {
sou: 0,
filename: filename.value,
remark: remark.value,
EventSource: jsonEventSource.value,
eventcode: jsoneventcode.value,
EventSourcename: sourcefieldValue.value,
eventcodename: fieldValue.value,
priority: priority.value
};
} else {
eventplaylist.value.push({
sou: 0,
filename: filename.value,
remark: remark.value,
EventSource: jsonEventSource.value,
eventcode: jsoneventcode.value,
EventSourcename: sourcefieldValue.value,
eventcodename: fieldValue.value,
priority: priority.value
});
}
// 重置输入框
Reset_input();
isEditing.value = false;
console.log(isEditing.value);
showeventlocal.value = false; // 关闭本地事件音频弹窗
sendEventPlaylistData();
};
const Reset_input = () => {
// 重置输入框
isEditing.value = false;
filename.value = "";
remark.value = "";
Broadcast_sound.value = "0";
speed.value = "5";
text.value = "";
sourcefieldValue.value = "";
fieldValue.value = "";
runningtime.value = "1";
pauseduration.value = "1";
priority.value = "0";
jsonEventSource.value = "1";
jsoneventcode.value = "0";
sourcefieldValue.value = '其他';
fieldValue.value = '默认显示';
}
const addEventOnlineAudio = async () => {
const newItem = {
sou: 1,
filename: newFilename.value,
remark: remark.value,
Broadcast_sound: Broadcast_sound.value,
speed: speed.value,
text: text.value,
EventSourcename: sourcefieldValue.value,
eventcodename: fieldValue.value,
EventSource: jsonEventSource.value,
eventcode: jsoneventcode.value,
priority: priority.value
};
newItem_content = newItem;
// 显示加载指示器
if (isEditing.value) {
// 获取当前编辑的项目
const originalItem = eventplaylist.value[editingIndex.value];
// 检查是否需要进行 TTS 合成
const ttsRequired = forceSynthesis.value ||
originalItem.Broadcast_sound !== newItem.Broadcast_sound ||
originalItem.speed !== newItem.speed ||
originalItem.text !== newItem.text;
if (ttsRequired) {
await ttssendevent(newItem, editingIndex.value, true);
} else {
// 如果不需要 TTS 合成,直接更新现有项
eventplaylist.value[editingIndex.value] = newItem;
}
} else {
// 非编辑模式,进行 TTS 合成并等待成功响应
const newIndex = eventplaylist.value.length;
currentIndex = newIndex;
await ttssendevent(newItem, null, false);
}
// 隐藏加载指示器
Reset_input();
isEditing.value = false;
console.log(isEditing.value);
showeventonline.value = false; // 关闭在线事件音频弹窗
// sendEventPlaylistData();
};
//组成json数据格式
// 将播放列表转换成 JSON 格式
const createPlaylistJSON = () => {
const play_list = playlist.value.map((item, index) => {
const baseItem = {
num: Number(index),
sou: Number(item.sou), // 假设 0 代表在线音频1 代表本地音频
remark: item.remark,
filename: item.filename,
play_time: Number(item.runningtime),
pause_time: Number(item.pauseduration),
};
if (item.Broadcast_sound) {
baseItem.TTS = {
per: Number(item.Broadcast_sound),
spd: Number(item.speed || 5), // 速度默认值 5
pit: Number(5), // 音调默认值
vol: Number(5), // 音量默认值
};
baseItem.tts_str = item.text;
}
return baseItem;
});
return {
JSON_id: jsonId.value,
board_id: 103,
sound_card: {
play_list,
},
};
};
const ttssend = async (newItem, index = null) => {
flag.value = false;
const ttsJson = {
JSON_id: jsonId.value,
board_id: 103,
sound_card: {
TTS: {
per: Number(newItem.Broadcast_sound),
spd: Number(newItem.speed),
pit: 5, // 根据你的需求设置
vol: 5, // 根据你的需求设置
tex_utf8: newItem.text,
filename: newItem.filename
},
TTS_state: "idle",
interrupt: 1
},
error_code: 0
};
console.log('Sending TTS request:', JSON.stringify(ttsJson));
// 使用 postMessage 发送消息
try {
MQTT_send(ttsJson);
console.log('Payload sent:', ttsJson);
showToast('合成TTS已请求');
} catch (error) {
console.error('PostMessage error:', error);
showToast('合成TTS失败');
}
};
//event
// tts
const ttssendevent = async (newItem, index, isEditing) => {
flag.value = true;
const ttsJson = {
JSON_id: jsonId.value,
board_id: 103,
sound_card: {
TTS: {
per: Number(newItem.Broadcast_sound),
spd: Number(newItem.speed),
pit: 5, // 根据你的需求设置
vol: 5, // 根据你的需求设置
tex_utf8: newItem.text,
filename: newItem.filename
},
TTS_state: "idle",
interrupt: 1
},
error_code: 0
};
console.log('Sending TTS request:', JSON.stringify(ttsJson));
// 使用 postMessage 发送消息
try {
MQTT_send(ttsJson);
console.log('Payload sent:', ttsJson);
showToast('合成TTS语音已请求');
} catch (error) {
console.error('PostMessage error:', error);
showToast('合成TTS语音失败');
}
};
// 将事件播放列表转换成 JSON 格式
const createEventPlaylistJSON = () => {
const event_play_list = eventplaylist.value.map((item, index) => {
const baseItem = {
num: Number(index),
sou: Number(item.sou), // 假设 0 代表在线音频1 代表本地音频
remark: item.remark,
filename: item.filename,
event: {
source: Number(item.EventSource),
code: Number(item.eventcode),
priority: Number(item.priority)
}
};
if (item.Broadcast_sound) {
baseItem.TTS = {
per: Number(item.Broadcast_sound),
spd: Number(item.speed || 5), // 速度默认值 5
pit: Number(5), // 音调默认值
vol: Number(5), // 音量默认值
};
baseItem.tts_str = item.text;
}
return baseItem;
});
return {
JSON_id: jsonId.value,
board_id: 103,
sound_card: {
event_play_list: event_play_list,
},
};
};
// 默认列表
const sendPlaylistData = async () => {
const data = createPlaylistJSON();
console.log(JSON.stringify(data));
if (isLocal.value) {
try {
const response = await axios.post(`/communication`, data, {
headers: {
"content-type": "application/json"
}
});
console.log('Response data:', response.data);
showToast('刷新成功');
jsonId.value++;
} catch (error) {
// 处理错误
console.error('Upload error:', error);
showToast('刷新失败');
jsonId.value++;
}
} else {
// 使用 postMessage 发送数据
try {
MQTT_send(data);
console.log('Payload sent:', data);
showToast('设置已上传');
} catch (error) {
console.error('PostMessage error:', error);
}
}
};
//事件列表发送部分
const sendEventPlaylistData = async () => {
const data = createEventPlaylistJSON();
console.log(JSON.stringify(data));
if (isLocal.value) {
try {
const response = await axios.post(`/communication`, data, {
headers: {
"content-type": "application/json"
}
});
console.log('Response data:', response.data);
showToast('刷新成功');
jsonId.value++;
loading.value = false; // 结束刷新时设置 loading 为 false
} catch (error) {
// 处理错误
console.error('Upload error:', error);
showToast('刷新失败');
jsonId.value++;
loading.value = false; // 结束刷新时设置 loading 为 false
}
} else {
// 使用 postMessage 发送数据
try {
MQTT_send(data);
console.log('Payload sent:', data);
showToast('设置已上传');
} catch (error) {
console.error('PostMessage error:', error);
}
}
};
const getallinfo = async () => {
loading.value = true; // 开始请求时设置 loading 为 true
const createRequest = (params) => ({
JSON_id: jsonId.value,
board_id: 103,
sound_card: params
});
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const sendRequest = async (request) => {
let timeoutId;
const handleTimeout = () => {
showToast('数据获取失败'); // 超时时显示错误提示
loading.value = false; // 超时后停止 loading
};
try {
// 设置 5 秒超时
timeoutId = setTimeout(handleTimeout, 5000);
if (isLocal.value) {
const response = await axios.post('/communication', request, {
headers: {
"content-type": "application/json"
}
});
clearTimeout(timeoutId); // 如果请求成功,清除超时
console.log('Response data:', response.data);
return response.data.sound_card;
} else {
await MQTT_send(request);
await delay(2000); // 等待一段时间模拟异步操作
clearTimeout(timeoutId); // 清除超时
}
} catch (error) {
console.error('Request error:', error);
clearTimeout(timeoutId); // 请求失败时清除超时
loading.value = false; // 失败时停止 loading
showToast('刷新失败'); // 显示错误提示
throw error;
}
};
try {
const requests = [
{ get_param: 1 },
{ get_mp3_list: 1 },
{ get_play_list: 1 },
{ get_event_play_list: 1 }
];
for (let i = 0; i < requests.length; i++) {
console.log('senddata:', JSON.stringify(requests[i]));
const data = await sendRequest(createRequest(requests[i]));
if (isLocal.value) {
handleJsonMessage(data); // 处理响应数据
}
jsonId.value++; // 更新 JSON_id
}
// showToast('刷新成功'); // 请求完成后显示成功提示
} catch (error) {
// 处理其他错误
} finally {
loading.value = false; // 不论成功或失败,最终都停止 loading
}
};
onBeforeUnmount(() => {
});
// json数据处理函数
function handleJsonMessage(data) {
// 如果存在新参数音量,则更新
if (data.param) {
voicevalue.value = data.param.volume;
}
// 更新文件名选项
if (data && Array.isArray(data.mp3_list)) {
newFilename.value = getNextTTSFilename(combinedList.value);
filenamecolumns.value = data.mp3_list.map(mp3 => ({
text: mp3,
value: mp3
}));
} else {
console.warn('No valid mp3_list found in data.sound_card');
}
// 更新播放列表并合并到 combinedList
if (data.play_list) {
const newPlaylistItems = data.play_list.map(item => ({
remark: item.remark,
filename: item.filename,
runningtime: item.play_time,
pauseduration: item.pause_time,
Broadcast_sound: item.TTS ? item.TTS.per : null,
speed: item.TTS ? item.TTS.spd : 0,
pit: item.TTS ? item.TTS.pit : 0,
vol: item.TTS ? item.TTS.vol : 0,
text: item.tts_str,
...item
}));
playlist.value = newPlaylistItems;
combinedList.value.push(...newPlaylistItems);
}
// 更新事件播放列表并合并到 combinedList
if (data.event_play_list) {
const newEventPlaylistItems = data.event_play_list.map(item => {
let eventType, eventsource;
switch (item.event.source) {
case 0:
eventsource = '本台设备';
break;
case 254:
eventsource = '除本身外任意设备';
break;
case 255:
eventsource = '任意设备';
break;
default:
eventsource = '其他';
}
switch (item.event.code) {
case 0:
eventType = '默认显示';
break;
case 1:
eventType = '雷达触发(RS485 雷达)';
break;
case 2:
eventType = '雷达超速(RS485 雷达)';
break;
case 3:
eventType = '车牌触发(摄像头)';
break;
case 4:
eventType = '车牌超速(摄像头)';
break;
case 5:
eventType = '行人检测(相机)';
break;
case 51:
eventType = 'IN1下降沿';
break;
case 52:
eventType = 'IN2上升沿';
break;
case 53:
eventType = 'IN3下降沿';
break;
case 54:
eventType = 'IN4上升沿';
break;
case 55:
eventType = 'IN1低电平';
break;
case 56:
eventType = 'IN2高电平';
break;
case 57:
eventType = 'IN3低电平';
break;
case 58:
eventType = 'IN4高电平';
break;
default:
eventType = '其他';
}
return {
remark: item.remark,
EventSource: item.event.source,
eventcode: item.event.code,
EventSourcename: eventsource,
eventcodename: eventType,
priority: item.event.priority,
Broadcast_sound: item.TTS ? item.TTS.per : null,
speed: item.TTS ? item.TTS.spd : 0,
pit: item.TTS ? item.TTS.pit : 0,
vol: item.TTS ? item.TTS.vol : 0,
text: item.tts_str,
...item
};
});
eventplaylist.value = newEventPlaylistItems;
combinedList.value.push(...newEventPlaylistItems);
}
console.log('combinedList:', JSON.stringify(combinedList.value));
}
return {
currentTitle,
isLocal,
loading,
voicevalue,
filename,
remark,
speed,
Broadcast_sound,
text,
runningtime,
pauseduration,
playlist,
eventplaylist,
onvoiceChange,
onRefresh,
showlocal,
showonline,
showeventlocal,
showeventonline,
showOptionsDialog,
audioSource,
filenameOptions,
removeItem,
showDialogForPlaylist,
showDialogForEventPlaylist,
onDialogConfirm,
addLocalAudio,
addOnlineAudio,
addEventLocalAudio,
addEventOnlineAudio,
eventcode,
EventSource,
showBroadcast_sound,
speedactions,
onSelect,
codecolumns,
onConfirm,
fieldValue,
showPicker,
showsourcePicker,
sourcecolumns,
onConfirmsource,
sourcefieldValue,
editingIndex,
createEventPlaylistJSON,
createPlaylistJSON,
sendPlaylistData,
sendEventPlaylistData,
jsoneventcode,
jsonEventSource,
EventSourcename,
eventcodename,
editdefaultlist,
isEditing,
Reset_input,
editeventlist,
checkEnvironment,
getallinfo,
forceSynthesis,
handleJsonMessage,
showfilenamePicker,
filenamecolumns,
onConfirmfilename,
priority,
ttssend,
ttssendevent,
MQTT_recv,
MQTT_send,
flag,
getNextTTSFilename,
newFilename,
currentIndex,
newItem_content,
combinedList
};
},
};
</script>
<style scoped>
.page-container {
display: flex;
flex-direction: column;
min-height: 100vh;
position: relative;
}
.content {
flex: 1;
overflow-y: auto;
padding: 20px;
border-radius: 20px !important;
margin-top: 80px;
margin-bottom: 40px;
min-height: 100vh;
}
.home {
display: flex;
flex-direction: column;
padding: 5px;
border-radius: 10px;
border: 2px solid #0668fc;
margin: 10px 10px 20px;
flex: 1;
}
.title {
text-align: center;
font-size: 30px;
}
.icon {
font-size: 34px;
align-self: flex-end;
/* 使用 flexbox 让图标靠右对齐 */
margin-top: 10px;
cursor: pointer;
}
.choice {
margin: 20px;
}
.slider {
margin-top: 20px;
margin-bottom: 15px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
.playlist {
list-style: none;
padding: 0;
margin: 20px 0;
}
.remark {
font-weight: bold;
font-size: 16px;
color: #0668fc;
text-align: center;
}
.duration-info {
display: flex;
justify-content: space-around;
margin-top: 10px;
font-size: 14px;
color: #0668fc;
}
.field {
margin-bottom: 15px;
}
.field label {
font-size: 16px;
/* color: #0668fc; */
}
.field select {
width: 100%;
padding: 8px;
font-size: 16px;
border: 1px solid #0668fc;
border-radius: 5px;
outline: none;
appearance: none;
}
.playlist-item {
position: relative;
/* 为子元素的绝对定位提供参照 */
background-color: #e8f4ff;
border: 2px solid #0668fc;
border-radius: 10px;
margin-bottom: 15px;
padding: 15px;
}
.delete-btn {
position: absolute;
top: 10px;
right: 10px;
background-color: #ff4d4f;
color: white;
border: none;
border-radius: 5px;
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
}
.delete-btn:hover {
background-color: #e60012;
}
::v-deep .van-field__control {
text-align: right !important;
}
::v-deep .custom-textarea .van-field__label {
align-self: center;
/* 标签垂直居中 */
line-height: normal;
/* 确保标签内的文字不会被拉伸 */
height: 100%;
/* 让标签的高度和输入框一致 */
display: flex;
align-items: center;
/* 在标签的容器中居中对齐 */
}
</style>