GateWay/src/views/voice/voiceset.vue
2025-05-22 15:34:21 +08:00

1408 lines
52 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>
<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 () => {
window.addEventListener('message', function (event) {
console.log("接受的原始数据",JSON.stringify(event.data))
if (event.data) {
MQTT_recv(JSON.stringify(event.data)); // 保持与原 MQTT_recv 兼容
}
});
// 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(" 向父页面发送消息:", JSON.stringify(send_string));
// 使用 postMessage 发送数据给父页面
window.parent.postMessage(
{
data: send_string
},
'*' // 目标 origin生产环境应替换为具体的父页面域名
);
};
// 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>