1408 lines
52 KiB
Vue
1408 lines
52 KiB
Vue
<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> |