2025-06-06 06:23:49 +08:00
|
|
|
|
<template>
|
|
|
|
|
<view class="voice-control">
|
|
|
|
|
<!-- 设备状态卡片 -->
|
|
|
|
|
<view class="card">
|
|
|
|
|
<view class="status-titletop">{{ title }}</view>
|
|
|
|
|
<view style="padding:20rpx;">
|
|
|
|
|
<u--form labelPosition="left" labelWidth="100"
|
|
|
|
|
:labelStyle="{ marginRight: '16px', lineHeight: '32px', width: '50px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', color: '#000000' }">
|
|
|
|
|
<view class="version-wrap">
|
|
|
|
|
<u-form-item :label="$tt('status.deviceVersion')">
|
|
|
|
|
<u-row>
|
|
|
|
|
<u-col span="8">
|
|
|
|
|
<u--text :text="'Version ' + device.firmwareVersion"></u--text>
|
|
|
|
|
</u-col>
|
|
|
|
|
</u-row>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
</view>
|
|
|
|
|
</u--form>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<!-- <button @click="print()">测试打印</button -->
|
2025-06-06 06:23:49 +08:00
|
|
|
|
<!-- 基础信息 -->
|
|
|
|
|
<view class="card basic-info">
|
|
|
|
|
<view class="section-title">基础信息</view>
|
|
|
|
|
<view class="info-content">
|
|
|
|
|
<!-- 音量控制 -->
|
|
|
|
|
<view class="volume-slider">
|
|
|
|
|
<view class="volume-icon">
|
2025-07-03 08:57:13 +08:00
|
|
|
|
<image src="https://xaznkj.cn/doc/photo/voice.svg" mode="aspectFit" class="volume-svg">
|
2025-06-06 06:23:49 +08:00
|
|
|
|
</image>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="slider-container">
|
|
|
|
|
<u-slider v-model="volume" :min="0" :max="100" :step="1" @change="volumeChange"
|
|
|
|
|
:disabled="device.status !== 3" height="4" activeColor="#2979ff" blockSize="18"
|
|
|
|
|
:showValue="false">
|
|
|
|
|
</u-slider>
|
|
|
|
|
<view class="volume-marks">
|
|
|
|
|
<text>0</text>
|
|
|
|
|
<text>50</text>
|
|
|
|
|
<text>100</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="volume-value">{{ volume }}%</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 音频开关 -->
|
|
|
|
|
<view class="audio-switch">
|
|
|
|
|
<text>音频开关</text>
|
|
|
|
|
<u-switch v-model="audioEnabled" @change="audioSwitchChange" :disabled="device.status !== 3"
|
|
|
|
|
size="22"></u-switch>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 音频列表 -->
|
|
|
|
|
<view class="card audio-list">
|
|
|
|
|
<view class="section-title">
|
|
|
|
|
<text>音频列表</text>
|
2025-07-03 08:57:13 +08:00
|
|
|
|
<image src="https://xaznkj.cn/doc/photo/add.svg" mode="aspectFit" class="add-icon"
|
2025-06-06 06:23:49 +08:00
|
|
|
|
@click="showAddAudioModal"></image>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="list-container">
|
|
|
|
|
<view class="empty-tip" v-if="audioList.length === 0">
|
|
|
|
|
<u-icon name="music" size="50" color="#c0c4cc"></u-icon>
|
|
|
|
|
<text>暂无音频文件</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="audio-item" v-for="(item, index) in audioList" :key="index">
|
|
|
|
|
<view class="audio-info">
|
|
|
|
|
<u-icon name="play-right" size="18" color="#2979ff"></u-icon>
|
|
|
|
|
<text class="audio-name">{{ item.name }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="audio-actions">
|
|
|
|
|
<text class="audio-duration">{{ item.duration }}</text>
|
|
|
|
|
<u-icon name="trash" size="18" color="#ff4d4f" @click="deleteAudio(index)"></u-icon>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 默认音频列表 -->
|
|
|
|
|
<view class="card default-list">
|
|
|
|
|
<view class="section-title">
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<text>播放列表</text>
|
2025-07-03 08:57:13 +08:00
|
|
|
|
<image src="https://xaznkj.cn/doc/photo/add.svg" mode="aspectFit" class="add-icon"
|
2025-06-06 06:23:49 +08:00
|
|
|
|
@click="showAddDefaultModal"></image>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="list-container">
|
|
|
|
|
<view class="empty-tip" v-if="defaultList.length === 0">
|
|
|
|
|
<u-icon name="star" size="50" color="#c0c4cc"></u-icon>
|
|
|
|
|
<text>暂无默认音频</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="audio-item" v-for="(item, index) in defaultList" :key="index">
|
|
|
|
|
<view class="audio-info">
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<u-icon :name="item.status === '启用' ? 'star-fill' : 'star'" size="18"
|
|
|
|
|
:color="item.status === '启用' ? '#ff9900' : '#c0c4cc'"></u-icon>
|
|
|
|
|
<view class="audio-details">
|
|
|
|
|
<text class="audio-name">{{ item.name }}</text>
|
|
|
|
|
<view class="audio-meta">
|
|
|
|
|
<text class="time-info">{{ item.playTime }}</text>
|
|
|
|
|
<text class="week-info">{{ item.weekdays }}</text>
|
|
|
|
|
<text v-if="item.radarEnabled" class="radar-info">{{ item.radarSpeed }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
2025-06-06 06:23:49 +08:00
|
|
|
|
</view>
|
|
|
|
|
<view class="audio-actions">
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<u-switch v-model="item.status" :active-value="'启用'" :inactive-value="'禁用'"
|
|
|
|
|
@change="(value) => handleStatusChange(index, value)" size="22"></u-switch>
|
2025-06-06 06:23:49 +08:00
|
|
|
|
<u-icon name="trash" size="18" color="#ff4d4f" @click="deleteDefault(index)"></u-icon>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 远程喊话 -->
|
|
|
|
|
<view class="card remote-talk">
|
|
|
|
|
<view class="talk-container">
|
2025-06-24 10:07:51 +08:00
|
|
|
|
<!-- 录音状态显示 -->
|
|
|
|
|
<view class="recorder-status">
|
|
|
|
|
<view class="status-indicator" :class="{ recording: isRecording }">
|
|
|
|
|
<image
|
2025-07-03 08:57:13 +08:00
|
|
|
|
:src="isRecording ? 'https://xaznkj.cn/doc/photo/recording.png' : 'https://xaznkj.cn/doc/photo/record.png'"
|
2025-06-24 10:07:51 +08:00
|
|
|
|
class="mic-img" mode="aspectFit" />
|
|
|
|
|
</view>
|
|
|
|
|
<text class="status-text">{{ recordingStatus }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 录音时长显示 -->
|
|
|
|
|
<view class="timer-display" v-if="isRecording">
|
|
|
|
|
{{ formatTime(recordingTime) }}
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 录音控制按钮 -->
|
|
|
|
|
<view class="control-buttons">
|
|
|
|
|
<view class="talk-button" :class="{ recording: isRecording }" @touchstart="startRecording"
|
|
|
|
|
@touchend="stopRecording" @touchcancel="cancelRecording">
|
|
|
|
|
<image
|
2025-07-03 08:57:13 +08:00
|
|
|
|
:src="isRecording ? 'https://xaznkj.cn/doc/photo/micred.png' : 'https://xaznkj.cn/doc/photo/mic.png'"
|
2025-06-24 10:07:51 +08:00
|
|
|
|
class="mic-img" mode="aspectFit" />
|
|
|
|
|
<text>{{ isRecording ? '录音中...' : '按住说话' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 录音状态提示 -->
|
|
|
|
|
<view class="recording-tips" v-if="!hasRecording && !isRecording">
|
|
|
|
|
<text class="tip-text">按住按钮开始录音,松开自动结束并上传</text>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 录音预览(仅在上传失败时显示) -->
|
2025-06-26 14:55:08 +08:00
|
|
|
|
<!-- <view class="recording-preview" v-if="hasRecording && uploadFailed">
|
2025-06-24 10:07:51 +08:00
|
|
|
|
<view class="preview-title">录音预览</view>
|
|
|
|
|
<view class="audio-player">
|
|
|
|
|
<audio :src="audioUrl" controls style="width: 100%; height: 40px;"></audio>
|
|
|
|
|
<view class="preview-controls">
|
|
|
|
|
<u-button type="primary" size="small" @click="uploadRecording">
|
|
|
|
|
重新上传
|
|
|
|
|
</u-button>
|
|
|
|
|
<u-button type="text" size="small" @click="reRecord">
|
|
|
|
|
重新录制
|
|
|
|
|
</u-button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
2025-06-26 14:55:08 +08:00
|
|
|
|
</view> -->
|
2025-06-24 10:07:51 +08:00
|
|
|
|
|
|
|
|
|
<!-- 最近录音列表 -->
|
2025-06-26 14:55:08 +08:00
|
|
|
|
<!-- <view class="recording-list" v-if="recordings.length > 0">
|
2025-06-24 10:07:51 +08:00
|
|
|
|
<view class="list-title">最近录音</view>
|
|
|
|
|
<scroll-view scroll-y style="height: 200px;">
|
|
|
|
|
<view v-for="(recording, index) in recordings" :key="index" class="recording-item">
|
|
|
|
|
<view class="recording-info">
|
|
|
|
|
<text class="recording-name">{{ recording.name }}</text>
|
|
|
|
|
<text class="recording-time">{{ recording.time }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<u-icon name="trash" size="18" color="#ff4d4f" @click="deleteRecording(index)"></u-icon>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
2025-06-26 14:55:08 +08:00
|
|
|
|
</view> -->
|
2025-06-06 06:23:49 +08:00
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 添加音频弹窗 -->
|
|
|
|
|
<u-popup :show="showAddAudio" mode="center" @close="showAddAudio = false" round="8">
|
|
|
|
|
<view class="add-audio-modal">
|
|
|
|
|
<view class="modal-title">添加音频</view>
|
|
|
|
|
<view class="modal-content">
|
|
|
|
|
<u-form :model="newAudio" ref="audioForm">
|
|
|
|
|
<u-form-item label="备注名" prop="name" borderBottom>
|
|
|
|
|
<u-input v-model="newAudio.name" placeholder="请输入备注名"></u-input>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="主持人" prop="per" borderBottom>
|
|
|
|
|
<u-radio-group v-model="newAudio.per">
|
|
|
|
|
<u-radio label="小美" name="0"></u-radio>
|
|
|
|
|
<u-radio label="小宇" name="1"></u-radio>
|
|
|
|
|
<u-radio label="逍遥" name="3"></u-radio>
|
|
|
|
|
<u-radio label="丫丫" name="4"></u-radio>
|
|
|
|
|
</u-radio-group>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="合成语速" prop="spd" borderBottom>
|
|
|
|
|
<view class="slider-with-value">
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<u-slider v-model="newAudio.spd" :min="0" :max="15" :step="1" :showValue="true"
|
|
|
|
|
class="custom-slider"></u-slider>
|
2025-06-06 06:23:49 +08:00
|
|
|
|
</view>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="合成音调" prop="pit" borderBottom>
|
|
|
|
|
<view class="slider-with-value">
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<u-slider v-model="newAudio.pit" :min="0" :max="15" :step="1" :showValue="true"
|
|
|
|
|
class="custom-slider"></u-slider>
|
2025-06-06 06:23:49 +08:00
|
|
|
|
</view>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="合成音量" prop="vol" borderBottom>
|
|
|
|
|
<view class="slider-with-value">
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<u-slider v-model="newAudio.vol" :min="0" :max="15" :step="1" :showValue="true"
|
|
|
|
|
class="custom-slider"></u-slider>
|
2025-06-06 06:23:49 +08:00
|
|
|
|
</view>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="合成文本" prop="text" borderBottom>
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<u-input v-model="newAudio.text" type="textarea" placeholder="请输入合成文本"
|
|
|
|
|
height="100"></u-input>
|
2025-06-06 06:23:49 +08:00
|
|
|
|
</u-form-item>
|
|
|
|
|
</u-form>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="modal-footer">
|
|
|
|
|
<u-button @click="showAddAudio = false" plain size="small">取消</u-button>
|
|
|
|
|
<u-button type="primary" @click="confirmAddAudio" size="small">确定</u-button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</u-popup>
|
|
|
|
|
|
|
|
|
|
<!-- 添加默认音频弹窗 -->
|
|
|
|
|
<u-popup :show="showAddDefault" mode="center" @close="showAddDefault = false" round="8">
|
|
|
|
|
<view class="add-audio-modal">
|
2025-06-18 10:18:15 +08:00
|
|
|
|
<view class="modal-title">添加播放列表</view>
|
2025-06-06 06:23:49 +08:00
|
|
|
|
<view class="modal-content">
|
|
|
|
|
<u-form :model="newDefault" ref="defaultForm">
|
|
|
|
|
<u-form-item label="开始时间" prop="startTime" borderBottom>
|
|
|
|
|
<picker mode="time" :value="newDefault.startTime" start="00:00" end="23:59"
|
|
|
|
|
@change="startTimeChange">
|
|
|
|
|
<view class="picker-value">{{ newDefault.startTime || '请选择开始时间' }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="结束时间" prop="endTime" borderBottom>
|
|
|
|
|
<picker mode="time" :value="newDefault.endTime" start="00:00" end="23:59"
|
|
|
|
|
@change="endTimeChange">
|
|
|
|
|
<view class="picker-value">{{ newDefault.endTime || '请选择结束时间' }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="重复" prop="repeat" borderBottom>
|
|
|
|
|
<view class="week-picker">
|
|
|
|
|
<view class="week-item" v-for="(day, index) in weekDays" :key="index"
|
|
|
|
|
:class="{ active: newDefault.repeatDays.includes(index) }"
|
|
|
|
|
@click="toggleWeekDay(index)">
|
|
|
|
|
{{ day }}
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<u-form-item label="雷达开关" prop="radarEnabled" borderBottom>
|
|
|
|
|
<u-switch v-model="newDefault.radarEnabled" @change="radarSwitchChange"></u-switch>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
<template v-if="newDefault.radarEnabled">
|
|
|
|
|
<u-form-item label="速度范围" prop="speedRange" borderBottom>
|
|
|
|
|
<view class="speed-range">
|
|
|
|
|
<u-input v-model="newDefault.minSpeed" type="number" placeholder="最小速度"></u-input>
|
|
|
|
|
<text class="separator">-</text>
|
|
|
|
|
<u-input v-model="newDefault.maxSpeed" type="number" placeholder="最大速度"></u-input>
|
|
|
|
|
<text class="unit">km/h</text>
|
|
|
|
|
</view>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
</template>
|
|
|
|
|
<u-form-item label="选择音频" prop="audioFile" borderBottom>
|
|
|
|
|
<picker :range="audioList" range-key="name" :value="audioIndex" @change="audioChange">
|
|
|
|
|
<view class="picker-value">{{ selectedAudioName || '请选择音频' }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
</u-form-item>
|
|
|
|
|
</u-form>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="modal-footer">
|
|
|
|
|
<u-button @click="showAddDefault = false" plain size="small">取消</u-button>
|
|
|
|
|
<u-button type="primary" @click="confirmAddDefault" size="small">确定</u-button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</u-popup>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import {
|
|
|
|
|
serviceInvoke
|
|
|
|
|
} from '@/apis/modules/runtime.js';
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'VoiceControl',
|
|
|
|
|
props: {
|
|
|
|
|
device: {
|
|
|
|
|
type: Object,
|
|
|
|
|
required: true
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
device: function(newVal, oldVal) {
|
|
|
|
|
if (newVal.deviceName !== '') {
|
|
|
|
|
this.deviceInfo = newVal;
|
|
|
|
|
if (this.deviceInfo.deviceType != 3) {
|
|
|
|
|
this.operateList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter((
|
2025-06-24 10:07:51 +08:00
|
|
|
|
item) => item.isReadonly == '0');
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.attributeList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter((
|
2025-06-24 10:07:51 +08:00
|
|
|
|
item) => item.isReadonly == '1');
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.attributeList = this.attributeList.sort((a, b) => b.order - a.order);
|
|
|
|
|
this.operateList = this.operateList.sort((a, b) => b.order - a.order);
|
|
|
|
|
}
|
|
|
|
|
this.updateDeviceStatus(this.deviceInfo);
|
|
|
|
|
this.updateBasicSettings();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
title: '设备离线',
|
|
|
|
|
volume: 50,
|
|
|
|
|
audioEnabled: true,
|
|
|
|
|
isRecording: false,
|
|
|
|
|
recorderManager: null,
|
2025-06-24 10:07:51 +08:00
|
|
|
|
recordingTime: 0,
|
|
|
|
|
recordingTimer: null,
|
2025-06-06 06:23:49 +08:00
|
|
|
|
tempFilePath: '',
|
2025-06-24 10:07:51 +08:00
|
|
|
|
hasRecording: false,
|
|
|
|
|
audioUrl: '',
|
|
|
|
|
uploadFailed: false,
|
|
|
|
|
recordings: [],
|
|
|
|
|
recordingStatus: '准备录音',
|
2025-06-06 06:23:49 +08:00
|
|
|
|
showAddAudio: false,
|
|
|
|
|
showAddDefault: false,
|
|
|
|
|
showStartTimePicker: false,
|
|
|
|
|
showEndTimePicker: false,
|
|
|
|
|
showAudioPicker: false,
|
|
|
|
|
weekDays: ['日', '一', '二', '三', '四', '五', '六'],
|
|
|
|
|
audioIndex: -1,
|
|
|
|
|
newAudio: {
|
|
|
|
|
name: '',
|
|
|
|
|
per: '0',
|
|
|
|
|
spd: '5',
|
|
|
|
|
pit: '5',
|
|
|
|
|
vol: '5',
|
|
|
|
|
text: '',
|
|
|
|
|
file: null
|
|
|
|
|
},
|
|
|
|
|
deviceInfo: {
|
|
|
|
|
chartList: [],
|
|
|
|
|
},
|
|
|
|
|
newDefault: {
|
|
|
|
|
startTime: '',
|
|
|
|
|
endTime: '',
|
|
|
|
|
repeatDays: [],
|
|
|
|
|
radarEnabled: false,
|
|
|
|
|
minSpeed: '',
|
|
|
|
|
maxSpeed: '',
|
|
|
|
|
audioFile: null
|
|
|
|
|
},
|
|
|
|
|
audioFiles: [],
|
2025-06-24 10:07:51 +08:00
|
|
|
|
audioList: [],
|
|
|
|
|
defaultList: [],
|
|
|
|
|
audioUrl: '',
|
|
|
|
|
uploadFailed: false
|
2025-06-06 06:23:49 +08:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
if (this.device !== null && Object.keys(this.device).length !== 0) {
|
|
|
|
|
this.deviceInfo = this.device;
|
|
|
|
|
this.updateDeviceStatus(this.deviceInfo);
|
|
|
|
|
};
|
|
|
|
|
this.mqttCallback();
|
|
|
|
|
this.recorderManager = uni.getRecorderManager();
|
|
|
|
|
this.initRecorder();
|
|
|
|
|
this.updateBasicSettings();
|
2025-06-24 10:07:51 +08:00
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
if (this.recordingTimer) {
|
|
|
|
|
clearInterval(this.recordingTimer);
|
|
|
|
|
this.recordingTimer = null;
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
print() {
|
|
|
|
|
console.log("测试打印", JSON.stringify(this.deviceInfo.thingsModels))
|
|
|
|
|
},
|
|
|
|
|
checkOnline(callback, ...args) {
|
|
|
|
|
if (this.device.status !== 3) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '设备离线,无法操作',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (typeof callback === 'function') {
|
|
|
|
|
return callback.apply(this, args);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
2025-06-24 10:07:51 +08:00
|
|
|
|
async uploadRecording() {
|
|
|
|
|
if (!this.tempFilePath) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
this.recordingStatus = '上传中...';
|
|
|
|
|
|
|
|
|
|
// 在 uni-app 中直接使用 uni.uploadFile,不需要 FormData
|
2025-07-03 08:57:13 +08:00
|
|
|
|
const uploadUrl = 'https://xaznkj.cn/common/upload/audio';
|
2025-06-24 10:07:51 +08:00
|
|
|
|
const token =
|
|
|
|
|
'Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImU3MWM2OTg4LTNlMzMtNDYyMy05M2M3LWE4YzZmMTNlMjZkZSJ9.wgsL8b3WDmyuesG8JTA3LcNFp2FigkB90h6Inwxt7OFadH6rc5np5TjAyU1pzU2_b5cmG8BYXMEdAqEdJzoDcA';
|
|
|
|
|
|
|
|
|
|
// 使用 uni.uploadFile 上传录音文件
|
|
|
|
|
const [error, res] = await new Promise((resolve) => {
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
url: uploadUrl,
|
|
|
|
|
filePath: this.tempFilePath,
|
|
|
|
|
name: 'file',
|
|
|
|
|
header: {
|
|
|
|
|
'Authorization': token
|
|
|
|
|
},
|
|
|
|
|
success: (res) => resolve([null, res]),
|
|
|
|
|
fail: (err) => resolve([err, null])
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (error) throw error;
|
|
|
|
|
|
|
|
|
|
if (res.statusCode === 200) {
|
|
|
|
|
const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
|
|
|
|
|
if (result.code === 200) {
|
|
|
|
|
this.recordingStatus = '上传成功';
|
|
|
|
|
this.uploadFailed = false;
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '上传成功',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 提取返回的 URL 并通过 MQTT 下发到 103#onlinePlay
|
|
|
|
|
if (result.url || result.resourcePath) {
|
|
|
|
|
const onlinePlayModel = this.deviceInfo.thingsModels?.find(model => model.id ===
|
|
|
|
|
'103#onlinePlay');
|
|
|
|
|
if (onlinePlayModel) {
|
|
|
|
|
// 构建包含 interrupt 参数的 JSON 数据
|
|
|
|
|
const playData = 'JSON=' + JSON.stringify({
|
|
|
|
|
online_play: {
|
|
|
|
|
url: "http://1.94.62.14:8080" + (result.resourcePath || result
|
|
|
|
|
.url),
|
|
|
|
|
},
|
|
|
|
|
interrupt: 98
|
|
|
|
|
});
|
|
|
|
|
onlinePlayModel.shadow = playData;
|
|
|
|
|
await this.mqttPublish(this.deviceInfo, onlinePlayModel);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '音频已下发到设备',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '未找到 103#onlinePlay 物模型',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(result.msg || '上传失败');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error('上传失败,状态码: ' + res.statusCode);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('上传失败:', error);
|
|
|
|
|
this.recordingStatus = '上传失败';
|
|
|
|
|
this.uploadFailed = true;
|
|
|
|
|
|
|
|
|
|
let errorMsg = '上传失败';
|
|
|
|
|
if (error.message && error.message.includes('Mixed Content')) {
|
|
|
|
|
errorMsg = '由于安全策略,无法在 HTTPS 页面访问 HTTP 接口。请联系管理员配置接口支持 HTTPS。';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: errorMsg || error.message || '上传失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
reRecord() {
|
|
|
|
|
this.hasRecording = false;
|
|
|
|
|
this.uploadFailed = false;
|
|
|
|
|
this.recordingStatus = '准备录音';
|
|
|
|
|
},
|
2025-06-06 06:23:49 +08:00
|
|
|
|
async volumeChange(value) {
|
|
|
|
|
if (!this.checkOnline()) return;
|
|
|
|
|
try {
|
|
|
|
|
const volumeModel = this.device.thingsModels.find(item => item.id === 'volume');
|
|
|
|
|
if (volumeModel) {
|
|
|
|
|
volumeModel.shadow = value.toString();
|
|
|
|
|
await this.mqttPublish(this.device, volumeModel);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('调整音量失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '操作失败: ' + error.message,
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
mqttCallback() {
|
|
|
|
|
this.$mqttTool.client.on('message', (topic, message, buffer) => {
|
|
|
|
|
let topics = topic.split('/');
|
|
|
|
|
let productId = topics[1];
|
|
|
|
|
let deviceNum = topics[2];
|
|
|
|
|
message = JSON.parse(message.toString());
|
|
|
|
|
if (topics[3] == 'status') {
|
|
|
|
|
if (this.deviceInfo.serialNumber == deviceNum) {
|
|
|
|
|
this.deviceInfo.status = message.status;
|
|
|
|
|
this.deviceInfo.isShadow = message.isShadow;
|
|
|
|
|
this.deviceInfo.rssi = message.rssi;
|
|
|
|
|
this.updateDeviceStatus(this.deviceInfo);
|
|
|
|
|
this.updateBasicSettings();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (topics[4] == 'reply') {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
icon: 'none',
|
|
|
|
|
title: message,
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (topics[3] == 'property' || topics[3] == 'function' || topic.endsWith('ws/service')) {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (this.deviceInfo.serialNumber == deviceNum) {
|
|
|
|
|
for (let j = 0; j < message.message.length; j++) {
|
|
|
|
|
let isComplete = false;
|
2025-06-24 10:07:51 +08:00
|
|
|
|
for (let k = 0; k < this.deviceInfo.thingsModels.length && !isComplete; k++) {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (this.deviceInfo.thingsModels[k].id == message.message[j].id) {
|
|
|
|
|
this.deviceInfo.thingsModels[k].shadow = message.message[j].value;
|
|
|
|
|
isComplete = true;
|
|
|
|
|
break;
|
2025-06-24 10:07:51 +08:00
|
|
|
|
} else if (this.deviceInfo.thingsModels[k].datatype.type == "object") {
|
|
|
|
|
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.params
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.length; n++) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (this.deviceInfo.thingsModels[k].datatype.params[n].id == message
|
|
|
|
|
.message[j].id) {
|
|
|
|
|
this.deviceInfo.thingsModels[k].datatype.params[n].shadow = message
|
|
|
|
|
.message[j].value;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
isComplete = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-24 10:07:51 +08:00
|
|
|
|
} else if (this.deviceInfo.thingsModels[k].datatype.type == "array") {
|
|
|
|
|
if (this.deviceInfo.thingsModels[k].datatype.arrayType == "object") {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (String(message.message[j].id).indexOf("array_") == 0) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.arrayParams.length; n++) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.arrayParams[n].length; m++) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n]
|
2025-06-06 06:23:49 +08:00
|
|
|
|
[m].id == message.message[j].id) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
this.deviceInfo.thingsModels[k].datatype.arrayParams[n]
|
2025-06-06 06:23:49 +08:00
|
|
|
|
[m].shadow = message.message[j].value;
|
|
|
|
|
isComplete = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (isComplete) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.arrayParams.length; n++) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.arrayParams[n].length; m++) {
|
|
|
|
|
let index = n > 9 ? String(n) : '0' + k;
|
|
|
|
|
let prefix = 'array_' + index + '_';
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n]
|
2025-06-06 06:23:49 +08:00
|
|
|
|
[m].id == prefix + message.message[j].id) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
this.deviceInfo.thingsModels[k].datatype.arrayParams[n]
|
2025-06-06 06:23:49 +08:00
|
|
|
|
[m].shadow = message.message[j].value;
|
|
|
|
|
isComplete = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (isComplete) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayModel
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.length; n++) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (this.deviceInfo.thingsModels[k].datatype.arrayModel[n].id ==
|
2025-06-06 06:23:49 +08:00
|
|
|
|
message.message[j].id) {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
this.deviceInfo.thingsModels[k].datatype.arrayModel[n].shadow =
|
2025-06-06 06:23:49 +08:00
|
|
|
|
message.message[j].value;
|
|
|
|
|
isComplete = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-06-24 10:07:51 +08:00
|
|
|
|
for (let k = 0; k < this.deviceInfo.chartList.length && !isComplete; k++) {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (this.deviceInfo.chartList[k].id.indexOf("array_") == 0) {
|
|
|
|
|
if (this.deviceInfo.chartList[k].id == message.message[j].id) {
|
|
|
|
|
this.deviceInfo.chartList[k].shadow = message.message[j].value;
|
|
|
|
|
isComplete = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (this.deviceInfo.chartList[k].id == message.message[j].id) {
|
|
|
|
|
this.deviceInfo.chartList[k].shadow = message.message[j].value;
|
|
|
|
|
isComplete = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (isComplete) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.updateBasicSettings();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
async mqttPublish(device, model) {
|
|
|
|
|
const command = {};
|
|
|
|
|
command[model.id] = model.shadow;
|
|
|
|
|
const data = {
|
|
|
|
|
serialNumber: device.serialNumber,
|
|
|
|
|
productId: device.productId,
|
|
|
|
|
remoteCommand: command,
|
|
|
|
|
identifier: model.id,
|
|
|
|
|
modelName: model.name,
|
|
|
|
|
isShadow: device.status != 3,
|
|
|
|
|
type: model.type
|
|
|
|
|
};
|
|
|
|
|
serviceInvoke(data).then(response => {
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
icon: 'none',
|
|
|
|
|
title: this.$tt('status.service')
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
updateDeviceStatus(device) {
|
|
|
|
|
if (device.status === 3) {
|
|
|
|
|
this.title = this.$tt('status.online');
|
|
|
|
|
} else {
|
|
|
|
|
this.title = device.isShadow === 1 ? this.$tt('status.shadow') : this.$tt('status.deviceOffline');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
initRecorder() {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (!this.recorderManager) return;
|
|
|
|
|
this.recorderManager.onStart(() => {
|
|
|
|
|
this.recordingStatus = '正在录音...';
|
|
|
|
|
});
|
|
|
|
|
this.recorderManager.onStop((res) => {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.tempFilePath = res.tempFilePath;
|
2025-06-24 10:07:51 +08:00
|
|
|
|
this.audioUrl = res.tempFilePath;
|
|
|
|
|
this.hasRecording = true;
|
|
|
|
|
this.recordingStatus = '录音完成,正在上传...';
|
|
|
|
|
this.recordings.unshift({
|
|
|
|
|
name: `录音_${this.formatTime(res.duration ? Math.floor(res.duration/1000) : this.recordingTime)}`,
|
|
|
|
|
time: new Date().toLocaleString(),
|
|
|
|
|
path: res.tempFilePath,
|
|
|
|
|
duration: res.duration ? Math.floor(res.duration / 1000) : this.recordingTime
|
|
|
|
|
});
|
|
|
|
|
this.uploadRecording();
|
2025-06-06 06:23:49 +08:00
|
|
|
|
});
|
2025-06-24 10:07:51 +08:00
|
|
|
|
this.recorderManager.onError((res) => {
|
|
|
|
|
this.isRecording = false;
|
|
|
|
|
this.recordingStatus = '录音失败';
|
2025-06-06 06:23:49 +08:00
|
|
|
|
uni.showToast({
|
2025-06-24 10:07:51 +08:00
|
|
|
|
title: res.errMsg || '录音失败',
|
|
|
|
|
icon: 'none',
|
2025-06-06 06:23:49 +08:00
|
|
|
|
});
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (this.recordingTimer) {
|
|
|
|
|
clearInterval(this.recordingTimer);
|
|
|
|
|
this.recordingTimer = null;
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
startRecording() {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (!this.checkOnline() || !this.recorderManager) return;
|
|
|
|
|
try {
|
|
|
|
|
this.isRecording = true;
|
|
|
|
|
this.recordingStatus = '正在录音...';
|
|
|
|
|
this.recordingTime = 0;
|
|
|
|
|
this.recordingTimer = setInterval(() => {
|
|
|
|
|
this.recordingTime++;
|
|
|
|
|
}, 1000);
|
|
|
|
|
this.recorderManager.start({
|
|
|
|
|
duration: 60000,
|
|
|
|
|
sampleRate: 16000,
|
|
|
|
|
numberOfChannels: 1,
|
|
|
|
|
encodeBitRate: 48000,
|
|
|
|
|
format: 'mp3',
|
|
|
|
|
frameSize: 1,
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.isRecording = false;
|
|
|
|
|
this.recordingStatus = '录音启动失败';
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '录音启动失败,请检查权限',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
},
|
|
|
|
|
stopRecording() {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (!this.isRecording || !this.recorderManager) return;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.isRecording = false;
|
2025-06-24 10:07:51 +08:00
|
|
|
|
this.recordingStatus = '录音完成,正在上传...';
|
|
|
|
|
if (this.recordingTimer) {
|
|
|
|
|
clearInterval(this.recordingTimer);
|
|
|
|
|
this.recordingTimer = null;
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.recorderManager.stop();
|
|
|
|
|
},
|
|
|
|
|
cancelRecording() {
|
2025-06-24 10:07:51 +08:00
|
|
|
|
if (!this.isRecording || !this.recorderManager) return;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.isRecording = false;
|
2025-06-24 10:07:51 +08:00
|
|
|
|
this.recordingStatus = '准备录音';
|
|
|
|
|
if (this.recordingTimer) {
|
|
|
|
|
clearInterval(this.recordingTimer);
|
|
|
|
|
this.recordingTimer = null;
|
|
|
|
|
}
|
|
|
|
|
this.recordingTime = 0;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.recorderManager.stop();
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '已取消录音',
|
2025-06-24 10:07:51 +08:00
|
|
|
|
icon: 'none',
|
2025-06-06 06:23:49 +08:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
showAddAudioModal() {
|
|
|
|
|
this.showAddAudio = true;
|
|
|
|
|
},
|
|
|
|
|
showAddDefaultModal() {
|
|
|
|
|
this.showAddDefault = true;
|
|
|
|
|
},
|
|
|
|
|
async confirmAddAudio() {
|
|
|
|
|
if (!this.checkOnline()) return;
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
try {
|
2025-06-23 09:15:23 +08:00
|
|
|
|
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#mp3List');
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (!mp3ListModel) {
|
|
|
|
|
throw new Error('未找到 mp3_list 模型');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
|
|
|
|
|
const data = JSON.parse(jsonStr);
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
let maxId = 0;
|
|
|
|
|
if (data.sound_card && data.sound_card.mp3_list) {
|
|
|
|
|
data.sound_card.mp3_list.forEach(item => {
|
|
|
|
|
const id = parseInt(item.split('_')[0]);
|
|
|
|
|
if (!isNaN(id) && id > maxId) {
|
|
|
|
|
maxId = id;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
const newId = maxId + 1;
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
const ttsData = {
|
|
|
|
|
JSON_id: 1,
|
|
|
|
|
sound_card: {
|
|
|
|
|
TTS: {
|
|
|
|
|
per: parseInt(this.newAudio.per),
|
|
|
|
|
spd: parseInt(this.newAudio.spd),
|
|
|
|
|
pit: parseInt(this.newAudio.pit),
|
|
|
|
|
vol: parseInt(this.newAudio.vol),
|
|
|
|
|
tex_utf8: this.newAudio.text,
|
|
|
|
|
filename: `${newId}_${this.newAudio.name}`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mp3ListModel.shadow = 'JSON=' + JSON.stringify(ttsData);
|
|
|
|
|
await this.mqttPublish(this.device, mp3ListModel);
|
|
|
|
|
|
|
|
|
|
this.newAudio = {
|
|
|
|
|
name: '',
|
|
|
|
|
per: '0',
|
|
|
|
|
spd: '5',
|
|
|
|
|
pit: '5',
|
|
|
|
|
vol: '5',
|
|
|
|
|
text: '',
|
|
|
|
|
file: null
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.showAddAudio = false;
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '添加成功',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('添加音频失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '添加失败: ' + error.message,
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-06-18 10:18:15 +08:00
|
|
|
|
async confirmAddDefault() {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (!this.checkOnline()) return;
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
|
|
|
|
if (!this.newDefault.startTime || !this.newDefault.endTime || !this.newDefault.audioFile) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '请填写完整信息',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.newDefault.radarEnabled) {
|
|
|
|
|
if (!this.newDefault.minSpeed || !this.newDefault.maxSpeed) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '请填写速度范围',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (parseInt(this.newDefault.minSpeed) >= parseInt(this.newDefault.maxSpeed)) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '最小速度必须小于最大速度',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 09:15:23 +08:00
|
|
|
|
const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList');
|
2025-06-18 10:18:15 +08:00
|
|
|
|
if (playListModel) {
|
|
|
|
|
try {
|
|
|
|
|
const jsonStr = playListModel.shadow.replace('JSON=', '');
|
|
|
|
|
const data = JSON.parse(jsonStr);
|
|
|
|
|
|
|
|
|
|
if (!data.sound_card) {
|
|
|
|
|
data.sound_card = {};
|
|
|
|
|
}
|
|
|
|
|
if (!data.sound_card.play_list) {
|
|
|
|
|
data.sound_card.play_list = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let maxNum = 0;
|
|
|
|
|
if (data.sound_card.play_list.length > 0) {
|
|
|
|
|
maxNum = Math.max(...data.sound_card.play_list.map(item => item.play.num || 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [startHour, startMinute] = this.newDefault.startTime.split(':').map(Number);
|
|
|
|
|
const [endHour, endMinute] = this.newDefault.endTime.split(':').map(Number);
|
|
|
|
|
const startSeconds = startHour * 3600 + startMinute * 60;
|
|
|
|
|
const endSeconds = endHour * 3600 + endMinute * 60;
|
|
|
|
|
|
|
|
|
|
let weekValue = 0;
|
|
|
|
|
this.newDefault.repeatDays.forEach(day => {
|
|
|
|
|
weekValue |= (1 << day);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const newPlayItem = {
|
|
|
|
|
play: {
|
|
|
|
|
num: maxNum + 1,
|
|
|
|
|
filename: this.newDefault.audioFile.name,
|
|
|
|
|
en: 1
|
|
|
|
|
},
|
|
|
|
|
time: {
|
|
|
|
|
begin: startSeconds,
|
|
|
|
|
end: endSeconds,
|
|
|
|
|
week: weekValue
|
|
|
|
|
},
|
|
|
|
|
speed: {
|
|
|
|
|
en: this.newDefault.radarEnabled ? 1 : 0,
|
|
|
|
|
min: this.newDefault.radarEnabled ? parseInt(this.newDefault.minSpeed) : 0,
|
|
|
|
|
max: this.newDefault.radarEnabled ? parseInt(this.newDefault.maxSpeed) : 0
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
data.sound_card.play_list.push(newPlayItem);
|
|
|
|
|
|
|
|
|
|
playListModel.shadow = 'JSON=' + JSON.stringify(data);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await this.mqttPublish(this.device, playListModel);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '添加成功',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
this.showAddDefault = false;
|
|
|
|
|
|
|
|
|
|
this.newDefault = {
|
|
|
|
|
startTime: '',
|
|
|
|
|
endTime: '',
|
|
|
|
|
repeatDays: [],
|
|
|
|
|
radarEnabled: false,
|
|
|
|
|
minSpeed: '',
|
|
|
|
|
maxSpeed: '',
|
|
|
|
|
audioFile: null
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('发送添加命令失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '添加失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('解析或更新播放列表失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '添加失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
},
|
|
|
|
|
async deleteAudio(index) {
|
|
|
|
|
if (!this.checkOnline()) return;
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
try {
|
|
|
|
|
uni.showModal({
|
|
|
|
|
title: '提示',
|
|
|
|
|
content: '确定要删除该音频吗?',
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
confirmText: '确定',
|
|
|
|
|
success: async (res) => {
|
|
|
|
|
if (res.confirm) {
|
2025-06-18 10:18:15 +08:00
|
|
|
|
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model
|
2025-06-24 10:07:51 +08:00
|
|
|
|
.id === '103#mp3List');
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (mp3ListModel && mp3ListModel.shadow) {
|
|
|
|
|
try {
|
|
|
|
|
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
|
|
|
|
|
const data = JSON.parse(jsonStr);
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (data.sound_card && data.sound_card.mp3_list) {
|
|
|
|
|
data.sound_card.mp3_list.splice(index, 1);
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
mp3ListModel.shadow = 'JSON=' + JSON.stringify(data);
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
await this.mqttPublish(this.device, mp3ListModel);
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
this.audioList.splice(index, 1);
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '删除成功',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除音频失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '删除失败: ' + error.message,
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除音频失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '删除失败: ' + error.message,
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
deleteDefault(index) {
|
|
|
|
|
uni.showModal({
|
|
|
|
|
title: '提示',
|
2025-06-18 10:18:15 +08:00
|
|
|
|
content: '确认删除该播放项吗?',
|
2025-06-06 06:23:49 +08:00
|
|
|
|
cancelText: '取消',
|
|
|
|
|
confirmText: '确定',
|
2025-06-18 10:18:15 +08:00
|
|
|
|
success: async (res) => {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (res.confirm) {
|
2025-06-18 10:18:15 +08:00
|
|
|
|
const playListModel = this.deviceInfo.thingsModels.find(model => model.id ===
|
2025-06-23 09:15:23 +08:00
|
|
|
|
'103#playList');
|
2025-06-18 10:18:15 +08:00
|
|
|
|
if (playListModel) {
|
|
|
|
|
try {
|
|
|
|
|
const jsonStr = playListModel.shadow.replace('JSON=', '');
|
|
|
|
|
const data = JSON.parse(jsonStr);
|
|
|
|
|
|
|
|
|
|
if (data.sound_card && data.sound_card.play_list) {
|
|
|
|
|
data.sound_card.play_list.splice(index, 1);
|
|
|
|
|
|
|
|
|
|
data.sound_card.play_list.forEach((item, index) => {
|
|
|
|
|
item.play.num = index + 1;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
playListModel.shadow = 'JSON=' + JSON.stringify(data);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await this.mqttPublish(this.device, playListModel);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '删除成功',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('发送删除命令失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '删除失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('解析或更新播放列表失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '删除失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
afterAudioRead(file) {
|
|
|
|
|
this.audioFiles.push(file);
|
|
|
|
|
},
|
|
|
|
|
deleteAudioFile(index) {
|
|
|
|
|
this.audioFiles.splice(index, 1);
|
|
|
|
|
},
|
|
|
|
|
beforeAudioUpload(file) {
|
|
|
|
|
const isValidType = file.name.toLowerCase().endsWith('.mp3');
|
|
|
|
|
const isValidSize = file.size / 1024 / 1024 < 10;
|
|
|
|
|
|
|
|
|
|
if (!isValidType) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '只能上传MP3格式',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!isValidSize) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '文件大小不能超过10MB',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
async audioSwitchChange() {
|
|
|
|
|
if (!this.checkOnline()) return;
|
|
|
|
|
try {
|
|
|
|
|
const playEnModel = this.device.thingsModels.find(item => item.id === 'play_en');
|
|
|
|
|
if (playEnModel) {
|
|
|
|
|
playEnModel.shadow = this.audioEnabled ? '1' : '0';
|
|
|
|
|
await this.mqttPublish(this.device, playEnModel);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('切换音频开关失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '操作失败: ' + error.message,
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-06-24 10:07:51 +08:00
|
|
|
|
formatTime(seconds) {
|
|
|
|
|
const minutes = Math.floor(seconds / 60);
|
|
|
|
|
const remainingSeconds = seconds % 60;
|
|
|
|
|
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
},
|
|
|
|
|
startTimeChange(e) {
|
|
|
|
|
this.newDefault.startTime = e.detail.value;
|
|
|
|
|
},
|
|
|
|
|
endTimeChange(e) {
|
|
|
|
|
this.newDefault.endTime = e.detail.value;
|
|
|
|
|
},
|
|
|
|
|
toggleWeekDay(index) {
|
|
|
|
|
const position = this.newDefault.repeatDays.indexOf(index);
|
|
|
|
|
if (position === -1) {
|
|
|
|
|
this.newDefault.repeatDays.push(index);
|
|
|
|
|
} else {
|
|
|
|
|
this.newDefault.repeatDays.splice(position, 1);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
radarSwitchChange(value) {
|
|
|
|
|
this.newDefault.radarEnabled = value;
|
|
|
|
|
if (!value) {
|
|
|
|
|
this.newDefault.minSpeed = '';
|
|
|
|
|
this.newDefault.maxSpeed = '';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
audioChange(e) {
|
|
|
|
|
this.audioIndex = e.detail.value;
|
|
|
|
|
this.newDefault.audioFile = this.audioList[this.audioIndex];
|
|
|
|
|
},
|
|
|
|
|
updateBasicSettings() {
|
|
|
|
|
if (!this.deviceInfo.thingsModels) return;
|
|
|
|
|
|
2025-06-23 09:15:23 +08:00
|
|
|
|
const playEnModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playEn');
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (playEnModel) {
|
|
|
|
|
this.audioEnabled = playEnModel.shadow === '1';
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 09:15:23 +08:00
|
|
|
|
const volumeModel = this.deviceInfo.thingsModels.find(model => model.id === '103#volume');
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (volumeModel) {
|
|
|
|
|
this.volume = parseInt(volumeModel.shadow) || 50;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 10:07:51 +08:00
|
|
|
|
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#mp3List');
|
2025-06-06 06:23:49 +08:00
|
|
|
|
if (mp3ListModel && mp3ListModel.shadow) {
|
|
|
|
|
try {
|
|
|
|
|
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
|
|
|
|
|
const data = JSON.parse(jsonStr);
|
|
|
|
|
|
2025-06-23 09:15:23 +08:00
|
|
|
|
if (data && data.mp3_list) {
|
|
|
|
|
this.audioList = data.mp3_list.map((item, index) => {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
const name = item.split('_')[1] || item;
|
|
|
|
|
return {
|
|
|
|
|
id: index + 1,
|
|
|
|
|
name: name
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('解析音频列表失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-23 09:15:23 +08:00
|
|
|
|
const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList');
|
2025-06-18 10:18:15 +08:00
|
|
|
|
if (playListModel && playListModel.shadow) {
|
|
|
|
|
try {
|
|
|
|
|
const jsonStr = playListModel.shadow.replace('JSON=', '');
|
|
|
|
|
const data = JSON.parse(jsonStr);
|
|
|
|
|
|
2025-06-23 09:15:23 +08:00
|
|
|
|
if (data && data.play_list) {
|
|
|
|
|
this.defaultList = data.play_list.map((item, index) => {
|
2025-06-18 10:18:15 +08:00
|
|
|
|
const beginTime = this.formatSecondsToTime(item.time.begin);
|
|
|
|
|
const endTime = this.formatSecondsToTime(item.time.end);
|
|
|
|
|
const weekdays = this.convertWeekToArray(item.time.week);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: index + 1,
|
|
|
|
|
name: item.play.filename,
|
|
|
|
|
playTime: `${beginTime} - ${endTime}`,
|
|
|
|
|
weekdays: weekdays.join(', '),
|
|
|
|
|
radarEnabled: item.speed.en === 1,
|
|
|
|
|
status: item.play.en === 1 ? '启用' : '禁用',
|
|
|
|
|
radarSpeed: item.speed.en === 1 ? `${item.speed.min}-${item.speed.max}km/h` :
|
|
|
|
|
''
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('解析播放列表失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
formatSecondsToTime(seconds) {
|
|
|
|
|
const hours = Math.floor(seconds / 3600);
|
|
|
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
|
|
|
|
},
|
|
|
|
|
convertWeekToArray(week) {
|
|
|
|
|
const weekdays = [];
|
|
|
|
|
for (let i = 0; i < 7; i++) {
|
|
|
|
|
if (week & (1 << i)) {
|
|
|
|
|
weekdays.push(this.weekDays[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return weekdays;
|
|
|
|
|
},
|
|
|
|
|
async handleStatusChange(index, value) {
|
2025-06-23 09:15:23 +08:00
|
|
|
|
const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList');
|
2025-06-18 10:18:15 +08:00
|
|
|
|
if (playListModel) {
|
|
|
|
|
try {
|
|
|
|
|
const jsonStr = playListModel.shadow.replace('JSON=', '');
|
|
|
|
|
const data = JSON.parse(jsonStr);
|
|
|
|
|
|
|
|
|
|
if (data.sound_card && data.sound_card.play_list) {
|
|
|
|
|
data.sound_card.play_list[index].play.en = value === '启用' ? 1 : 0;
|
|
|
|
|
|
|
|
|
|
playListModel.shadow = 'JSON=' + JSON.stringify(data);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await this.mqttPublish(this.device, playListModel);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '更新成功',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('发送状态更新命令失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '更新失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
this.defaultList[index].status = value === '启用' ? '禁用' : '启用';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('解析或更新播放列表失败:', error);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '更新失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
this.defaultList[index].status = value === '启用' ? '禁用' : '启用';
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-24 10:07:51 +08:00
|
|
|
|
},
|
|
|
|
|
deleteRecording(index) {
|
|
|
|
|
uni.showModal({
|
|
|
|
|
title: '提示',
|
|
|
|
|
content: '确定要删除该录音吗?',
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
confirmText: '确定',
|
|
|
|
|
success: (res) => {
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
this.recordings.splice(index, 1);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: '删除成功',
|
|
|
|
|
icon: 'success'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-06-06 06:23:49 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
startTimeLabel() {
|
|
|
|
|
return this.newDefault.startTime ? this.formatTime(this.newDefault.startTime) : '请选择开始时间';
|
|
|
|
|
},
|
|
|
|
|
endTimeLabel() {
|
|
|
|
|
return this.newDefault.endTime ? this.formatTime(this.newDefault.endTime) : '请选择结束时间';
|
|
|
|
|
},
|
|
|
|
|
selectedAudioName() {
|
|
|
|
|
return this.audioIndex >= 0 ? this.audioList[this.audioIndex].name : '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
// @import "@/uni_modules/uview-ui/scss/index.scss";
|
|
|
|
|
|
|
|
|
|
.voice-control {
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
background-color: #f7f8fa;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
|
|
|
|
.card {
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-titletop {
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
color: #333333;
|
|
|
|
|
line-height: 42rpx;
|
|
|
|
|
text-align: left;
|
|
|
|
|
padding: 15rpx 28rpx;
|
|
|
|
|
background-color: #eef6ff;
|
|
|
|
|
border-bottom: 1rpx solid #dceaff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.version-wrap {
|
|
|
|
|
background-color: #F7F7F7;
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
padding: 0 42rpx;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
color: #000000;
|
|
|
|
|
line-height: 42rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #333;
|
|
|
|
|
padding: 24rpx;
|
|
|
|
|
border-bottom: 1rpx solid #f5f5f5;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
.add-icon {
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-content {
|
|
|
|
|
padding: 16rpx 24rpx 24rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.volume-slider {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
padding: 16rpx 0;
|
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
|
|
|
|
.volume-icon {
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
.volume-svg {
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-container {
|
|
|
|
|
flex: 1;
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
.volume-marks {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
margin-top: 8rpx;
|
|
|
|
|
padding: 0 8rpx;
|
|
|
|
|
|
|
|
|
|
text {
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.volume-value {
|
|
|
|
|
min-width: 72rpx;
|
|
|
|
|
text-align: right;
|
|
|
|
|
color: #2979ff;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.audio-switch {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 16rpx 0;
|
|
|
|
|
|
|
|
|
|
text {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.audio-list,
|
|
|
|
|
.default-list {
|
|
|
|
|
.list-container {
|
|
|
|
|
padding: 0 24rpx 16rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-tip {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 60rpx 0;
|
|
|
|
|
color: #c0c4cc;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
transform: scale(0.98);
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.audio-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
border-bottom: 1rpx solid #f5f5f5;
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.audio-info {
|
|
|
|
|
display: flex;
|
2025-06-18 10:18:15 +08:00
|
|
|
|
align-items: flex-start;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
gap: 16rpx;
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
2025-06-18 10:18:15 +08:00
|
|
|
|
.audio-details {
|
|
|
|
|
flex: 1;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
overflow: hidden;
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
|
|
|
|
.audio-name {
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.audio-meta {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 12rpx;
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
color: #666;
|
|
|
|
|
|
|
|
|
|
.time-info,
|
|
|
|
|
.week-info,
|
|
|
|
|
.radar-info {
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
padding: 4rpx 12rpx;
|
|
|
|
|
border-radius: 4rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.audio-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 24rpx;
|
|
|
|
|
margin-left: 16rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.remote-talk {
|
|
|
|
|
.talk-container {
|
|
|
|
|
padding: 24rpx;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
|
2025-06-24 10:07:51 +08:00
|
|
|
|
.recorder-status {
|
2025-06-06 06:23:49 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
2025-06-24 10:07:51 +08:00
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
|
|
|
|
.status-indicator {
|
|
|
|
|
width: 60rpx;
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: #f4f4f5;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
&.recording {
|
|
|
|
|
background: #fef0f0;
|
|
|
|
|
animation: pulse 1.5s infinite;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-text {
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
color: #606266;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.timer-display {
|
|
|
|
|
font-size: 48rpx;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #303133;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.control-buttons {
|
|
|
|
|
display: flex;
|
2025-06-06 06:23:49 +08:00
|
|
|
|
justify-content: center;
|
2025-06-24 10:07:51 +08:00
|
|
|
|
gap: 20rpx;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
|
|
|
|
.talk-button {
|
|
|
|
|
width: 120rpx;
|
|
|
|
|
height: 120rpx;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background-color: #f0f6ff;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 10rpx;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
border: 2rpx solid #e1e8ff;
|
|
|
|
|
box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.2);
|
|
|
|
|
|
|
|
|
|
&.recording {
|
|
|
|
|
background-color: #fff1f0;
|
|
|
|
|
border-color: #ff4d4f;
|
|
|
|
|
transform: scale(1.05);
|
|
|
|
|
box-shadow: 0 6rpx 16rpx rgba(245, 77, 79, 0.3);
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
|
2025-06-24 10:07:51 +08:00
|
|
|
|
&:active {
|
|
|
|
|
transform: scale(0.95);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text {
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
color: #666;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
}
|
2025-06-24 10:07:51 +08:00
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
|
2025-06-24 10:07:51 +08:00
|
|
|
|
.recording-tips {
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
padding: 24rpx;
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
|
|
.tip-text {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #606266;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.recording-preview {
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
padding: 24rpx;
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
|
|
.preview-title {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #606266;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.audio-player {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
|
|
|
|
|
audio {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 80rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preview-controls {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin-top: 10rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.recording-list {
|
|
|
|
|
text-align: left;
|
|
|
|
|
border-top: 1rpx solid #ebeef5;
|
|
|
|
|
padding-top: 20rpx;
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
|
|
.list-title {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #606266;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.recording-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 16rpx 0;
|
|
|
|
|
border-bottom: 1rpx solid #ebeef5;
|
|
|
|
|
|
|
|
|
|
.recording-info {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 8rpx;
|
|
|
|
|
|
|
|
|
|
.recording-name {
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
color: #303133;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.recording-time {
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
color: #909399;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 10:07:51 +08:00
|
|
|
|
@keyframes pulse {
|
|
|
|
|
0% {
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0.4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
70% {
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
box-shadow: 0 0 0 10rpx rgba(245, 108, 108, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
100% {
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.add-audio-modal {
|
|
|
|
|
width: 80vw;
|
|
|
|
|
max-width: 600rpx;
|
|
|
|
|
padding: 0;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
.modal-title {
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #333;
|
|
|
|
|
padding: 28rpx 28rpx 20rpx;
|
|
|
|
|
text-align: center;
|
|
|
|
|
border-bottom: 1rpx solid #f5f5f5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.modal-content {
|
|
|
|
|
padding: 0 28rpx;
|
|
|
|
|
margin: 20rpx 0;
|
|
|
|
|
max-height: 60vh;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
|
|
|
|
.u-form-item {
|
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
|
|
|
|
|
:deep(.u-form-item__body) {
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.u-form-item__body__left) {
|
|
|
|
|
width: 160rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.u-form-item__body__right) {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider-with-value {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
width: 100%;
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
.custom-slider {
|
|
|
|
|
flex: 1;
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-18 10:18:15 +08:00
|
|
|
|
|
2025-06-06 06:23:49 +08:00
|
|
|
|
:deep(.u-radio-group) {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.modal-footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 16rpx 28rpx 28rpx;
|
|
|
|
|
border-top: 1rpx solid #f5f5f5;
|
|
|
|
|
|
|
|
|
|
.u-button {
|
|
|
|
|
width: 48%;
|
|
|
|
|
height: 72rpx;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
border-radius: 40rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.week-picker {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 10rpx;
|
|
|
|
|
padding: 10rpx 0;
|
|
|
|
|
|
|
|
|
|
.week-item {
|
|
|
|
|
width: 60rpx;
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
border-radius: 30rpx;
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
color: #666;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
background-color: #2979ff;
|
|
|
|
|
color: #fff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.speed-range {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10rpx;
|
|
|
|
|
|
|
|
|
|
.u-input {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.separator {
|
|
|
|
|
color: #666;
|
|
|
|
|
padding: 0 10rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.unit {
|
|
|
|
|
color: #666;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
margin-left: 10rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.picker-value {
|
|
|
|
|
height: 80rpx;
|
|
|
|
|
line-height: 80rpx;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
padding: 0 20rpx;
|
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
}
|
2025-06-24 10:07:51 +08:00
|
|
|
|
|
|
|
|
|
.mic-img {
|
|
|
|
|
width: 56rpx;
|
|
|
|
|
height: 56rpx;
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
}
|
2025-06-06 06:23:49 +08:00
|
|
|
|
}
|
|
|
|
|
</style>
|