1810 lines
74 KiB
Vue
1810 lines
74 KiB
Vue
<template>
|
||
<div class="running-status">
|
||
<el-row :gutter="20">
|
||
<el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="14" class="status-col">
|
||
<!-- 设备模式和OTA升级部分 -->
|
||
<el-row :gutter="20" class="mode-section">
|
||
<!-- 设备模式 -->
|
||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||
<el-card class="mode-card" shadow="hover">
|
||
<div class="mode-header">
|
||
<i class="el-icon-menu"></i>
|
||
<span class="mode-title">{{ $t('device.running-status.866086-0') }}</span>
|
||
</div>
|
||
<div class="mode-content">
|
||
<span class="title" :style="{ color: statusColor.background }">{{ title }}</span>
|
||
<el-button type="text" @click="printThingsModels" style="margin-left: 10px">
|
||
<i class="el-icon-printer"></i> 打印物模型
|
||
</el-button>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
|
||
<!-- 设备升级 -->
|
||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||
<el-card class="mode-card" shadow="hover">
|
||
<div class="mode-header">
|
||
<svg-icon icon-class="ota" />
|
||
<span class="mode-title">{{ $t('device.running-status.866086-1') }}</span>
|
||
</div>
|
||
<div class="mode-content">
|
||
<el-button type="primary" size="mini" :plain="true" @click="viewVersion()">
|
||
{{ $t('device.running-status.866086-44') }}
|
||
</el-button>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 声卡基础设置 -->
|
||
<el-card class="settings-card" shadow="hover">
|
||
<div slot="header" class="settings-header">
|
||
<span class="settings-title">基础设置</span>
|
||
</div>
|
||
<el-form :model="basicSettings" label-width="100px" style="margin-top: 20px;">
|
||
<el-form-item label="音频开关">
|
||
<el-switch v-model="basicSettings.audioEnabled" active-color="#13ce66"
|
||
inactive-color="#ff4949" @change="handleAudioSwitchChange">
|
||
</el-switch>
|
||
</el-form-item>
|
||
<el-form-item label="音量设置">
|
||
<el-slider v-model="basicSettings.volume" :min="0" :max="100" :format-tooltip="formatVolume"
|
||
@change="handleVolumeChange" style="width: 80%" :disabled="!basicSettings.audioEnabled">
|
||
</el-slider>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 音频列表 -->
|
||
<el-card class="audio-list-card" shadow="hover">
|
||
<div slot="header" class="audio-list-header">
|
||
<span class="audio-list-title">音频列表</span>
|
||
<el-button type="primary" size="mini" icon="el-icon-plus" @click="showAddAudioDialog">
|
||
添加音频
|
||
</el-button>
|
||
</div>
|
||
<el-table :data="audioList" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }"
|
||
border>
|
||
<el-table-column prop="id" label="序号" width="80" align="center">
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="音频名称" min-width="150">
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="120" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button type="text" icon="el-icon-delete" @click="handleDeleteAudio(scope.row)">
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
<template slot="empty">
|
||
<div style="padding: 20px 0;">
|
||
<el-empty description="暂无音频数据"></el-empty>
|
||
</div>
|
||
</template>
|
||
</el-table>
|
||
</el-card>
|
||
|
||
<!-- 默认列表 -->
|
||
<el-card class="default-list-card" shadow="hover">
|
||
<div slot="header" class="default-list-header">
|
||
<span class="default-list-title">播放列表</span>
|
||
<el-button type="primary" size="mini" icon="el-icon-plus" @click="showAddPlaylistDialog">
|
||
添加音频
|
||
</el-button>
|
||
</div>
|
||
<el-table :data="defaultList" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }"
|
||
border>
|
||
<el-table-column prop="id" label="序号" width="80" align="center">
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="音频名称" min-width="150">
|
||
</el-table-column>
|
||
<el-table-column prop="playTime" label="播放时间" width="120" align="center">
|
||
</el-table-column>
|
||
<el-table-column prop="weekdays" label="重复" width="200" align="center">
|
||
</el-table-column>
|
||
<el-table-column label="雷达" width="100" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag :type="scope.row.radarEnabled ? 'success' : 'info'">
|
||
{{ scope.row.radarEnabled ? '开启' : '关闭' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="120" align="center">
|
||
<template slot-scope="scope">
|
||
<el-switch v-model="scope.row.status" :active-value="'启用'" :inactive-value="'禁用'"
|
||
@change="handleStatusChange(scope.row)">
|
||
</el-switch>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="120" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button type="text" icon="el-icon-delete" @click="handleDeletePlaylist(scope.row)">
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
<template slot="empty">
|
||
<div style="padding: 20px 0;">
|
||
<el-empty description="暂无播放列表数据"></el-empty>
|
||
</div>
|
||
</template>
|
||
</el-table>
|
||
</el-card>
|
||
</el-col>
|
||
|
||
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
|
||
<!-- 远程喊话控制面板 -->
|
||
<el-card class="voice-control-card" shadow="hover">
|
||
<div slot="header" class="voice-control-header">
|
||
<span class="voice-control-title">远程喊话</span>
|
||
</div>
|
||
<div class="voice-control-content">
|
||
<div class="recorder-status">
|
||
<div class="status-indicator" :class="{ 'recording': isRecording }">
|
||
<i class="el-icon-microphone"></i>
|
||
</div>
|
||
<span class="status-text">{{ recordingStatus }}</span>
|
||
</div>
|
||
<div class="timer-display" v-if="isRecording">
|
||
{{ formatTime(recordingTime) }}
|
||
</div>
|
||
<div class="control-buttons">
|
||
<el-button type="primary" icon="el-icon-video-play" circle @click="startRecording"
|
||
:disabled="isRecording">
|
||
</el-button>
|
||
<el-button type="danger" icon="el-icon-video-pause" circle @click="stopRecording"
|
||
:disabled="!isRecording">
|
||
</el-button>
|
||
<el-button type="success" icon="el-icon-upload2" circle @click="uploadRecording"
|
||
:disabled="!hasRecording || isRecording">
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 录音预览 -->
|
||
<div class="recording-preview" v-if="hasRecording">
|
||
<div class="preview-title">录音预览</div>
|
||
<div class="audio-player">
|
||
<audio ref="audioPlayer" :src="audioUrl" controls></audio>
|
||
<div class="preview-controls">
|
||
<el-button type="text" icon="el-icon-refresh" @click="reRecord"
|
||
:disabled="isRecording">
|
||
重新录制
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="recording-list" v-if="recordings.length > 0">
|
||
<div class="list-title">最近录音</div>
|
||
<el-scrollbar style="height: 200px">
|
||
<div v-for="(recording, index) in recordings" :key="index" class="recording-item">
|
||
<span class="recording-name">{{ recording.name }}</span>
|
||
<span class="recording-time">{{ recording.time }}</span>
|
||
<el-button type="text" icon="el-icon-delete" @click="deleteRecording(index)">
|
||
</el-button>
|
||
</div>
|
||
</el-scrollbar>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 设备监测图表-->
|
||
<el-row :gutter="20" v-if="deviceInfo.chartList.length > 0">
|
||
<el-col :xs="24" :sm="12" :md="12" :lg="24" :xl="12" v-for="(item, index) in deviceInfo.chartList"
|
||
:key="index">
|
||
<el-card shadow="hover" style="border-radius: 8px; margin-bottom: 20px">
|
||
<div ref="map" style="height: 230px; width: 185px; margin: 0 auto; margin-bottom: 15px">
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 固件版本查看对话框 -->
|
||
<el-dialog :title="$t('device.running-status.866086-10')" :visible.sync="openVersion" width="550px"
|
||
append-to-body>
|
||
<el-form ref="firmwareForm" label-width="100px" :model="firmwareParams" :inline="true" :rules="rules">
|
||
<el-form-item :label="$t('device.running-status.866086-38')" prop="firmwareType">
|
||
<el-select v-model="deviceInfo.firmwareType" :placeholder="$t('firmware.index.222541-51')"
|
||
@change="handleVersionInputChange" style="width: 350px" disabled>
|
||
<el-option v-for="item in firmwareTypeList" :key="item.value" :label="item.label"
|
||
:value="item.value"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('device.running-status.866086-39')" prop="">
|
||
<el-input :placeholder="$t('device.running-status.866086-40')" v-model="deviceInfo.firmwareVersion"
|
||
style="width: 350px" disabled>
|
||
<template slot="prepend">Version</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer">
|
||
<el-tooltip effect="dark" :content="$t('device.running-status.866086-41')" placement="top-start">
|
||
<el-button type="primary" @click="getLatestFirmware" :disabled="device.status !== 3">{{
|
||
$t('device.running-status.866086-42') }}</el-button>
|
||
</el-tooltip>
|
||
<el-button @click="cancel1">{{ $t('cancel') }}</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 添加或修改产品固件对话框 -->
|
||
<el-dialog :title="$t('device.running-status.866086-10')" :visible.sync="openFirmware" width="600px"
|
||
append-to-body>
|
||
<div v-if="firmware == null" style="text-align: center; font-size: 16px">
|
||
<i class="el-icon-success" style="color: #67c23a"></i>
|
||
{{ $t('device.running-status.866086-11') }}
|
||
</div>
|
||
<el-descriptions :column="1" border size="large"
|
||
v-if="firmware != null && deviceInfo.firmwareVersion < firmware.version"
|
||
:labelStyle="{ width: '150px', 'font-weight': 'bold' }">
|
||
<template slot="title">
|
||
<el-link icon="el-icon-success" type="success" :underline="false">{{
|
||
$t('device.running-status.866086-12') }}</el-link>
|
||
</template>
|
||
<el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName
|
||
}}</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName
|
||
}}</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version
|
||
}}</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.running-status.866086-16')">
|
||
<el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{
|
||
getDownloadUrl(firmware.filePath) }}</el-link>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark
|
||
}}</el-descriptions-item>
|
||
</el-descriptions>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="success" @click="otaUpgrade"
|
||
v-if="firmware != null && deviceInfo.firmwareVersion < firmware.version">{{
|
||
$t('device.running-status.866086-18') }}</el-button>
|
||
<el-button @click="cancel">{{ $t('cancel') }}</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 添加音频对话框 -->
|
||
<el-dialog title="添加音频" :visible.sync="addAudioDialogVisible" width="600px" append-to-body>
|
||
<el-form :model="newAudio" :rules="audioRules" ref="audioForm" label-width="120px">
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="newAudio.remark" placeholder="请输入备注"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="主持人声音" prop="per">
|
||
<el-select v-model="newAudio.per" placeholder="请选择主持人声音" style="width: 100%">
|
||
<el-option label="小美" :value="0"></el-option>
|
||
<el-option label="小宇" :value="1"></el-option>
|
||
<el-option label="逍遥" :value="3"></el-option>
|
||
<el-option label="丫丫" :value="4"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="合成语速" prop="spd">
|
||
<el-slider v-model="newAudio.spd" :min="0" :max="15" :step="1" :format-tooltip="formatSpeed">
|
||
</el-slider>
|
||
</el-form-item>
|
||
<el-form-item label="合成音调" prop="pit">
|
||
<el-slider v-model="newAudio.pit" :min="0" :max="15" :step="1" :format-tooltip="formatPitch">
|
||
</el-slider>
|
||
</el-form-item>
|
||
<el-form-item label="合成音量" prop="vol">
|
||
<el-slider v-model="newAudio.vol" :min="0" :max="15" :step="1" :format-tooltip="formatVolume">
|
||
</el-slider>
|
||
</el-form-item>
|
||
<el-form-item label="合成文本" prop="tex_utf8">
|
||
<el-input type="textarea" :rows="4" v-model="newAudio.tex_utf8" placeholder="请输入需要合成的文本">
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="addAudioDialogVisible = false">取 消</el-button>
|
||
<el-button type="primary" @click="submitAudioForm">确 定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 添加播放列表对话框 -->
|
||
<el-dialog title="添加播放列表" :visible.sync="addPlaylistDialogVisible" width="600px" append-to-body>
|
||
<el-form :model="newPlaylist" :rules="playlistRules" ref="playlistForm" label-width="120px">
|
||
<el-form-item label="音频选择" prop="audioId">
|
||
<el-select v-model="newPlaylist.audioId" placeholder="请选择音频" style="width: 100%">
|
||
<el-option v-for="item in audioList" :key="item.id" :label="item.name" :value="item.id">
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="播放时间" prop="playTime">
|
||
<div class="time-range">
|
||
<el-time-picker v-model="newPlaylist.playTimeStart" format="HH:mm" placeholder="开始时间"
|
||
style="width: 45%">
|
||
</el-time-picker>
|
||
<span class="time-separator">至</span>
|
||
<el-time-picker v-model="newPlaylist.playTimeEnd" format="HH:mm" placeholder="结束时间"
|
||
style="width: 45%">
|
||
</el-time-picker>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="星期重复" prop="weekdays">
|
||
<el-checkbox-group v-model="newPlaylist.weekdays">
|
||
<el-checkbox label="1">周一</el-checkbox>
|
||
<el-checkbox label="2">周二</el-checkbox>
|
||
<el-checkbox label="3">周三</el-checkbox>
|
||
<el-checkbox label="4">周四</el-checkbox>
|
||
<el-checkbox label="5">周五</el-checkbox>
|
||
<el-checkbox label="6">周六</el-checkbox>
|
||
<el-checkbox label="0">周日</el-checkbox>
|
||
</el-checkbox-group>
|
||
</el-form-item>
|
||
<el-form-item label="雷达开关" prop="radarEnabled">
|
||
<el-switch v-model="newPlaylist.radarEnabled" active-color="#13ce66" inactive-color="#ff4949">
|
||
</el-switch>
|
||
</el-form-item>
|
||
<el-collapse-transition>
|
||
<div v-show="newPlaylist.radarEnabled" class="radar-settings">
|
||
<el-form-item label="速度范围" prop="radarSpeed">
|
||
<div class="speed-range">
|
||
<el-input-number v-model="newPlaylist.radarSpeedMin" :min="0"
|
||
:max="newPlaylist.radarSpeedMax" :step="1" placeholder="最小速度">
|
||
</el-input-number>
|
||
<span class="speed-separator">-</span>
|
||
<el-input-number v-model="newPlaylist.radarSpeedMax" :min="newPlaylist.radarSpeedMin"
|
||
:max="200" :step="1" placeholder="最大速度">
|
||
</el-input-number>
|
||
<span class="speed-unit">km/h</span>
|
||
</div>
|
||
</el-form-item>
|
||
</div>
|
||
</el-collapse-transition>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="addPlaylistDialogVisible = false">取 消</el-button>
|
||
<el-button type="primary" @click="submitPlaylistForm">确 定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getLatestFirmware } from '@/api/iot/firmware';
|
||
import { serviceInvoke, serviceInvokeReply } from '@/api/iot/runstatus';
|
||
import { getOrderControl } from '@/api/iot/control';
|
||
|
||
export default {
|
||
name: 'running-status',
|
||
props: {
|
||
device: {
|
||
type: Object,
|
||
default: null,
|
||
},
|
||
},
|
||
watch: {
|
||
device: {
|
||
handler(newVal) {
|
||
if (newVal && newVal.deviceId != 0) {
|
||
this.deviceInfo = newVal;
|
||
this.updateDeviceStatus(this.deviceInfo);
|
||
this.$nextTick(function () {
|
||
this.MonitorChart();
|
||
});
|
||
console.log("物模型", JSON.stringify(this.deviceInfo.thingsModels));
|
||
|
||
if (this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.length > 0) {
|
||
this.deviceInfo.thingsModels = this.device.thingsModels.sort((a, b) => b.order - a.order);
|
||
}
|
||
if (this.deviceInfo.chartList && this.deviceInfo.chartList.length > 0) {
|
||
this.deviceInfo.chartList = this.deviceInfo.chartList.sort((a, b) => b.order - a.order);
|
||
}
|
||
}
|
||
},
|
||
},
|
||
},
|
||
|
||
data() {
|
||
return {
|
||
title: '设备控制',
|
||
shadowUnEnable: false,
|
||
statusColor: {
|
||
background: '#67C23A',
|
||
color: '#fff',
|
||
maxWidth: '200px',
|
||
},
|
||
firmware: {},
|
||
openFirmware: false,
|
||
loading: true,
|
||
deviceInfo: {
|
||
deviceId: 0,
|
||
serialNumber: '',
|
||
productId: '',
|
||
productName: '',
|
||
status: 0,
|
||
isShadow: 0,
|
||
rssi: 0,
|
||
firmwareVersion: '',
|
||
wirelessVersion: '',
|
||
firmwareType: 1,
|
||
protocolCode: '',
|
||
thingsModels: [],
|
||
chartList: [],
|
||
},
|
||
firmwareParams: {
|
||
firmwareType: '',
|
||
versionInput: '',
|
||
},
|
||
monitorChart: [
|
||
{
|
||
chart: {},
|
||
data: {
|
||
id: '',
|
||
name: '',
|
||
value: '',
|
||
},
|
||
},
|
||
],
|
||
openVersion: false,
|
||
firmwareTypeList: [
|
||
{
|
||
label: this.$t('firmware.index.222541-52'),
|
||
value: 1,
|
||
},
|
||
{
|
||
label: 'HTTP',
|
||
value: 2,
|
||
},
|
||
],
|
||
rules: {
|
||
firmwareType: [
|
||
{
|
||
required: true,
|
||
message: this.$t('device.running-status.866086-43'),
|
||
trigger: 'blur',
|
||
},
|
||
],
|
||
},
|
||
// 声卡相关数据
|
||
basicSettings: {
|
||
volume: 50,
|
||
audioEnabled: true
|
||
},
|
||
audioList: [],
|
||
defaultList: [],
|
||
// 录音相关数据
|
||
isRecording: false,
|
||
recordingTime: 0,
|
||
recordingStatus: '准备就绪',
|
||
hasRecording: false,
|
||
mediaRecorder: null,
|
||
audioChunks: [],
|
||
recordings: [],
|
||
timer: null,
|
||
audioUrl: null,
|
||
// 音频列表相关数据
|
||
addAudioDialogVisible: false,
|
||
newAudio: {
|
||
remark: '',
|
||
per: 0,
|
||
spd: 5,
|
||
pit: 5,
|
||
vol: 5,
|
||
tex_utf8: '',
|
||
filename: ''
|
||
},
|
||
audioRules: {
|
||
remark: [
|
||
{ required: true, message: '请输入备注', trigger: 'blur' }
|
||
],
|
||
per: [
|
||
{ required: true, message: '请选择主持人声音', trigger: 'change' }
|
||
],
|
||
spd: [
|
||
{ required: true, message: '请设置合成语速', trigger: 'change' }
|
||
],
|
||
pit: [
|
||
{ required: true, message: '请设置合成音调', trigger: 'change' }
|
||
],
|
||
vol: [
|
||
{ required: true, message: '请设置合成音量', trigger: 'change' }
|
||
],
|
||
tex_utf8: [
|
||
{ required: true, message: '请输入合成文本', trigger: 'blur' }
|
||
]
|
||
},
|
||
// 播放列表相关数据
|
||
addPlaylistDialogVisible: false,
|
||
newPlaylist: {
|
||
name: '',
|
||
type: '用户',
|
||
status: '启用',
|
||
audioId: '',
|
||
playTimeStart: null,
|
||
playTimeEnd: null,
|
||
weekdays: [],
|
||
radarEnabled: false,
|
||
radarSpeedMin: 0,
|
||
radarSpeedMax: 120
|
||
},
|
||
playlistRules: {
|
||
audioId: [
|
||
{ required: true, message: '请选择音频', trigger: 'change' }
|
||
],
|
||
playTime: [
|
||
{
|
||
validator: (rule, value, callback) => {
|
||
if (!this.newPlaylist.playTimeStart || !this.newPlaylist.playTimeEnd) {
|
||
callback(new Error('请选择播放时间段'));
|
||
} else if (this.newPlaylist.playTimeStart >= this.newPlaylist.playTimeEnd) {
|
||
callback(new Error('开始时间必须小于结束时间'));
|
||
} else {
|
||
callback();
|
||
}
|
||
},
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
weekdays: [
|
||
{ required: true, message: '请选择重复日期', trigger: 'change' }
|
||
],
|
||
radarSpeed: [
|
||
{
|
||
validator: (rule, value, callback) => {
|
||
if (this.newPlaylist.radarEnabled) {
|
||
if (!this.newPlaylist.radarSpeedMin || !this.newPlaylist.radarSpeedMax) {
|
||
callback(new Error('请设置速度范围'));
|
||
} else if (this.newPlaylist.radarSpeedMin >= this.newPlaylist.radarSpeedMax) {
|
||
callback(new Error('最小速度必须小于最大速度'));
|
||
} else {
|
||
callback();
|
||
}
|
||
} else {
|
||
callback();
|
||
}
|
||
},
|
||
trigger: 'change'
|
||
}
|
||
]
|
||
},
|
||
};
|
||
},
|
||
mounted() {
|
||
if (this.device && this.device.deviceId) {
|
||
this.handleDeviceChange(this.device);
|
||
this.initDataStatus();
|
||
this.initData();
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
//发送指令
|
||
async mqttPublish(device, model) {
|
||
const command = {};
|
||
command[model.id] = model.shadow;
|
||
const params = {
|
||
deviceId: device.deviceId,
|
||
modelId: model.modelId,
|
||
};
|
||
const response = await getOrderControl(params);
|
||
if (response.code != 200) {
|
||
this.$message({
|
||
type: 'warning',
|
||
message: response.msg,
|
||
});
|
||
return;
|
||
}
|
||
const data = {
|
||
serialNumber: device.serialNumber,
|
||
productId: device.productId,
|
||
remoteCommand: command,
|
||
identifier: model.id,
|
||
modelName: model.name,
|
||
isShadow: device.status != 3,
|
||
type: model.type,
|
||
};
|
||
//设备在线状态判断
|
||
if (this.device.status !== 3 && this.device.isShadow !== 1) {
|
||
if (this.device.status === 1) {
|
||
title = this.$t('device.device-variable.930930-0');
|
||
} else if (this.device.status === 2) {
|
||
title = this.$t('device.device-variable.930930-1');
|
||
} else {
|
||
title = this.$t('device.device-variable.930930-2');
|
||
}
|
||
this.$message({
|
||
type: 'warning',
|
||
message: title,
|
||
});
|
||
return;
|
||
}
|
||
if ((this.deviceInfo.protocolCode === 'MODBUS-TCP' || this.deviceInfo.protocolCode === 'MODBUS-RTU') && this.device.status === 3) {
|
||
await serviceInvokeReply(data).then((response) => {
|
||
if (response.code === 200) {
|
||
this.$message({
|
||
type: 'success',
|
||
message: this.$t('device.running-status.866086-25'),
|
||
});
|
||
} else {
|
||
this.$message.error(response.msg);
|
||
}
|
||
});
|
||
} else {
|
||
await serviceInvoke(data).then((response) => {
|
||
if (response.code === 200) {
|
||
this.$message({
|
||
type: 'success',
|
||
message: this.$t('device.running-status.866086-25'),
|
||
});
|
||
} else {
|
||
this.$message.error(response.msg);
|
||
}
|
||
});
|
||
}
|
||
},
|
||
|
||
// 保留原有的设备状态相关方法
|
||
handleDeviceChange(device) {
|
||
if (device && device.deviceId != 0) {
|
||
const { firmwareVersion, wirelessVersion, firmwareType, ...res } = device;
|
||
const data = {
|
||
version: firmwareType === 1 ? firmwareVersion : wirelessVersion,
|
||
firmwareType,
|
||
...res,
|
||
};
|
||
this.deviceInfo = data;
|
||
this.updateDeviceStatus(this.deviceInfo);
|
||
this.$nextTick(() => {
|
||
this.MonitorChart();
|
||
});
|
||
if (this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.length > 0) {
|
||
this.deviceInfo.thingsModels = this.deviceInfo.thingsModels.sort((a, b) => b.order - a.order);
|
||
this.updateBasicSettings(); // 更新基础设置
|
||
this.printThingsModels();
|
||
}
|
||
if (this.deviceInfo.chartList && this.deviceInfo.chartList.length > 0) {
|
||
this.deviceInfo.chartList = this.deviceInfo.chartList.sort((a, b) => b.order - a.order);
|
||
}
|
||
}
|
||
},
|
||
|
||
// 更新基础设置
|
||
updateBasicSettings() {
|
||
if (!this.deviceInfo.thingsModels) return;
|
||
|
||
// 更新音频开关状态
|
||
const playEnModel = this.deviceInfo.thingsModels.find(model => model.id === 'play_en');
|
||
if (playEnModel) {
|
||
this.basicSettings.audioEnabled = playEnModel.shadow === '1';
|
||
}
|
||
|
||
// 更新音量设置
|
||
const volumeModel = this.deviceInfo.thingsModels.find(model => model.id === 'volume');
|
||
if (volumeModel) {
|
||
this.basicSettings.volume = parseInt(volumeModel.shadow) || 50;
|
||
}
|
||
|
||
// 更新音频列表
|
||
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === 'mp3_list');
|
||
if (mp3ListModel && mp3ListModel.shadow) {
|
||
try {
|
||
// 解析 JSON 字符串
|
||
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
|
||
const data = JSON.parse(jsonStr);
|
||
|
||
// 获取 mp3_list 数组
|
||
if (data.sound_card && data.sound_card.mp3_list) {
|
||
// 更新音频列表
|
||
this.audioList = data.sound_card.mp3_list.map((item, index) => {
|
||
// 从 "1_def" 格式中提取名称
|
||
const name = item.split('_')[1] || item;
|
||
return {
|
||
id: index + 1,
|
||
name: name
|
||
};
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('解析音频列表失败:', error);
|
||
}
|
||
}
|
||
|
||
// 更新播放列表
|
||
const playListModel = this.deviceInfo.thingsModels.find(model => model.id === 'play_list');
|
||
if (playListModel && playListModel.shadow) {
|
||
try {
|
||
const jsonStr = playListModel.shadow.replace('JSON=', '');
|
||
const data = JSON.parse(jsonStr);
|
||
|
||
if (data.sound_card && data.sound_card.play_list) {
|
||
this.defaultList = data.sound_card.play_list.map((item, index) => {
|
||
// 转换时间格式
|
||
const beginTime = this.formatSecondsToTime(item.time.begin);
|
||
const endTime = this.formatSecondsToTime(item.time.end);
|
||
|
||
// 转换星期格式
|
||
const weekdays = this.convertWeekToArray(item.time.week);
|
||
|
||
return {
|
||
id: index + 1,
|
||
name: item.play.filename,
|
||
playTime: `${beginTime} - ${endTime}`,
|
||
weekdays: weekdays.join(', '),
|
||
radarEnabled: item.speed.en === 1,
|
||
status: item.play.en === 1 ? '启用' : '禁用',
|
||
radarSpeed: item.speed.en === 1 ? `${item.speed.min}-${item.speed.max}km/h` : ''
|
||
};
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('解析播放列表失败:', error);
|
||
}
|
||
}
|
||
},
|
||
|
||
printThingsModels() {
|
||
console.log('当前物模型数据:', JSON.stringify(this.deviceInfo.thingsModels, null, 2));
|
||
this.$message({
|
||
message: '物模型数据已打印到控制台',
|
||
type: 'success'
|
||
});
|
||
},
|
||
|
||
// 声卡特有方法
|
||
formatVolume(val) {
|
||
return val + '%';
|
||
},
|
||
|
||
handleVolumeChange(val) {
|
||
const volumeModel = this.deviceInfo.thingsModels.find(model => model.id === 'volume');
|
||
if (volumeModel) {
|
||
volumeModel.shadow = val.toString();
|
||
this.mqttPublish(this.deviceInfo, volumeModel);
|
||
}
|
||
},
|
||
|
||
// 保留其他必要的方法
|
||
initData() {
|
||
this.$busEvent.$on('updateData', (params) => {
|
||
this.updateParam(params);
|
||
});
|
||
},
|
||
|
||
// 处理设备上报的数据更新
|
||
// updateParam(params) {
|
||
// console.log(1111111111)
|
||
// if (!params || !this.deviceInfo.thingsModels) return;
|
||
|
||
// const { serialNumber, productId, data } = params;
|
||
// if (data && this.deviceInfo.serialNumber === serialNumber) {
|
||
// // 更新物模型数据
|
||
// this.deviceInfo.thingsModels.forEach(model => {
|
||
// if (data[model.id] !== undefined) {
|
||
// model.shadow = data[model.id];
|
||
// }
|
||
// });
|
||
|
||
// // 更新基础设置
|
||
// this.updateBasicSettings();
|
||
// }
|
||
// },
|
||
|
||
|
||
//更新参数值
|
||
updateParam(params) {
|
||
let { serialNumber, productId, data } = params;
|
||
let isComplete = false;
|
||
data = data.message;
|
||
if (data) {
|
||
for (let j = 0; j < data.length; j++) {
|
||
for (let k = 0; k < this.deviceInfo.thingsModels.length && !isComplete; k++) {
|
||
if (this.deviceInfo.thingsModels[k].id == data[j].id) {
|
||
const variable = this.deviceInfo.thingsModels[k];
|
||
// 普通类型(小数/整数/字符串/布尔/枚举)
|
||
if (this.deviceInfo.thingsModels[k].datatype.type == 'decimal' || this.deviceInfo.thingsModels[k].datatype.type == 'integer') {
|
||
variable.shadow = Number(data[j].value);
|
||
} else {
|
||
variable.shadow = data[j].value;
|
||
}
|
||
}
|
||
if (this.deviceInfo.thingsModels[k].datatype.type == 'object') {
|
||
// 对象类型
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.params.length; n++) {
|
||
if (this.deviceInfo.thingsModels[k].datatype.params[n].id == data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.params[n].shadow = data[j].value;
|
||
isComplete = true;
|
||
break;
|
||
}
|
||
}
|
||
} else if (this.deviceInfo.thingsModels[k].datatype.type == 'array') {
|
||
// 数组类型
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayType == 'object') {
|
||
// 1.对象类型数组,id为数组中一个元素,例如:array_01_gateway_temperature
|
||
if (String(data[j].id).indexOf('array_') == 0) {
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayParams.length; n++) {
|
||
for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype.arrayParams[n].length; m++) {
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].id == data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].shadow = data[j].value;
|
||
isComplete = true;
|
||
break;
|
||
}
|
||
}
|
||
if (isComplete) {
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
// 2.对象类型数组,例如:gateway_temperature,消息ID添加前缀后匹配
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayParams.length; n++) {
|
||
for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype.arrayParams[n].length; m++) {
|
||
let index = n > 9 ? String(n) : '0' + k;
|
||
let prefix = 'array_' + index + '_';
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].id == prefix + data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].shadow = data[j].value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 整数、小数和字符串类型数组
|
||
for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayModel.length; n++) {
|
||
if (this.deviceInfo.thingsModels[k].datatype.arrayModel[n].id == data[j].id) {
|
||
this.deviceInfo.thingsModels[k].datatype.arrayModel[n].shadow = data[j].value;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 图表数据
|
||
for (let k = 0; k < this.deviceInfo.chartList.length; k++) {
|
||
if (this.deviceInfo.chartList[k].id.indexOf('array_') == 0) {
|
||
// 数组类型匹配,例如:array_00_gateway_temperature
|
||
if (this.deviceInfo.chartList[k].id == data[j].id) {
|
||
this.deviceInfo.chartList[k].shadow = data[j].value;
|
||
// 更新图表
|
||
for (let m = 0; m < this.monitorChart.length; m++) {
|
||
if (data[j].id == this.monitorChart[m].data.id) {
|
||
let data = [
|
||
{
|
||
value: this.deviceInfo.chartList[k].shadow,
|
||
name: this.monitorChart[m].data.name,
|
||
},
|
||
];
|
||
this.monitorChart[m].chart.setOption({
|
||
series: [
|
||
{
|
||
data: data,
|
||
},
|
||
],
|
||
});
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 普通类型匹配
|
||
if (this.deviceInfo.chartList[k].id == data[j].id) {
|
||
this.deviceInfo.chartList[k].shadow = data[j].value;
|
||
// 更新图表
|
||
for (let m = 0; m < this.monitorChart.length; m++) {
|
||
if (data[j].id == this.monitorChart[m].data.id) {
|
||
let data = [
|
||
{
|
||
value: this.deviceInfo.chartList[k].shadow,
|
||
name: this.monitorChart[m].data.name,
|
||
},
|
||
];
|
||
this.monitorChart[m].chart.setOption({
|
||
series: [
|
||
{
|
||
data: data,
|
||
},
|
||
],
|
||
});
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (isComplete) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
this.updateBasicSettings();
|
||
},
|
||
|
||
|
||
|
||
|
||
|
||
|
||
initDataStatus() {
|
||
this.$busEvent.$on('updateStatus', (status) => {
|
||
this.updateStatus(status);
|
||
});
|
||
},
|
||
|
||
updateStatus(status) {
|
||
let { serialNumber, productId, data } = status;
|
||
if (data) {
|
||
if (this.deviceInfo.serialNumber == serialNumber) {
|
||
this.deviceInfo.status = data.status;
|
||
this.deviceInfo.isShadow = data.isShadow;
|
||
this.deviceInfo.rssi = data.rssi;
|
||
this.updateDeviceStatus(this.deviceInfo);
|
||
}
|
||
}
|
||
},
|
||
|
||
updateDeviceStatus(device) {
|
||
if (device.status == 3) {
|
||
this.statusColor.background = '#12d09f';
|
||
this.title = this.$t('device.running-status.866086-26');
|
||
this.shadowUnEnable = false;
|
||
} else {
|
||
if (device.isShadow == 1) {
|
||
this.statusColor.background = '#486FF2';
|
||
this.title = this.$t('device.running-status.866086-27');
|
||
this.shadowUnEnable = false;
|
||
} else {
|
||
this.statusColor.background = '#909399';
|
||
this.title = this.$t('device.running-status.866086-28');
|
||
this.shadowUnEnable = true;
|
||
}
|
||
}
|
||
this.$emit('statusEvent', this.deviceInfo.status);
|
||
},
|
||
|
||
// 保留固件更新相关方法
|
||
viewVersion() {
|
||
this.openVersion = true;
|
||
this.firmwareParams.firmwareType = 1;
|
||
this.firmwareParams.versionInput = '';
|
||
this.handleVersionInputChange();
|
||
},
|
||
|
||
handleVersionInputChange() {
|
||
if (this.firmwareParams.firmwareType == 1) {
|
||
this.firmwareParams.versionInput = 'Version' + this.device.firmwareVersion;
|
||
} else {
|
||
this.firmwareParams.versionInput = 'Version' + this.device.wirelessVersion;
|
||
}
|
||
},
|
||
|
||
cancel1() {
|
||
this.openVersion = false;
|
||
},
|
||
|
||
getLatestFirmware() {
|
||
const { deviceId, firmwareType } = this.deviceInfo;
|
||
getLatestFirmware(deviceId, firmwareType).then((response) => {
|
||
if (response.code === 200) {
|
||
this.firmware = response.data;
|
||
this.openFirmware = true;
|
||
}
|
||
});
|
||
},
|
||
|
||
cancel() {
|
||
this.openFirmware = false;
|
||
},
|
||
|
||
getDownloadUrl(path) {
|
||
return window.location.origin + process.env.VUE_APP_BASE_API + path;
|
||
},
|
||
|
||
MonitorChart() {
|
||
for (let i = 0; i < this.deviceInfo.chartList.length; i++) {
|
||
this.monitorChart[i] = {
|
||
chart: this.$echarts.init(this.$refs.map[i]),
|
||
data: {
|
||
id: this.deviceInfo.chartList[i].id,
|
||
name: this.deviceInfo.chartList[i].name,
|
||
value: this.deviceInfo.chartList[i].shadow ? this.deviceInfo.chartList[i].shadow : this.deviceInfo.chartList[i].datatype.min,
|
||
},
|
||
};
|
||
var option;
|
||
option = {
|
||
tooltip: {
|
||
formatter: ' {b} <br/> {c}' + this.deviceInfo.chartList[i].datatype.unit,
|
||
},
|
||
series: [
|
||
{
|
||
name: this.deviceInfo.chartList[i].datatype.type,
|
||
type: 'gauge',
|
||
min: this.deviceInfo.chartList[i].datatype.min,
|
||
max: this.deviceInfo.chartList[i].datatype.max,
|
||
colorBy: 'data',
|
||
splitNumber: 10,
|
||
radius: '100%',
|
||
splitLine: {
|
||
distance: 4,
|
||
},
|
||
axisLabel: {
|
||
fontSize: 10,
|
||
distance: 10,
|
||
},
|
||
axisTick: {
|
||
distance: 4,
|
||
},
|
||
axisLine: {
|
||
lineStyle: {
|
||
width: 8,
|
||
color: [
|
||
[0.2, '#409EFF'],
|
||
[0.8, '#12d09f'],
|
||
[1, '#F56C6C'],
|
||
],
|
||
opacity: 0.3,
|
||
},
|
||
},
|
||
pointer: {
|
||
icon: 'triangle',
|
||
length: '60%',
|
||
width: 7,
|
||
},
|
||
progress: {
|
||
show: true,
|
||
width: 8,
|
||
},
|
||
detail: {
|
||
valueAnimation: true,
|
||
formatter: '{value}' + ' ' + this.deviceInfo.chartList[i].datatype.unit,
|
||
offsetCenter: [0, '80%'],
|
||
fontSize: 20,
|
||
},
|
||
data: [
|
||
{
|
||
value: this.deviceInfo.chartList[i].shadow ? this.deviceInfo.chartList[i].shadow : this.deviceInfo.chartList[i].datatype.min,
|
||
name: this.deviceInfo.chartList[i].name,
|
||
},
|
||
],
|
||
title: {
|
||
offsetCenter: [0, '115%'],
|
||
fontSize: 16,
|
||
},
|
||
},
|
||
],
|
||
};
|
||
option && this.monitorChart[i].chart.setOption(option);
|
||
}
|
||
},
|
||
|
||
// 录音相关方法
|
||
async startRecording() {
|
||
if (this.isRecording) return;
|
||
|
||
try {
|
||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||
this.mediaRecorder = new MediaRecorder(stream);
|
||
this.audioChunks = [];
|
||
|
||
this.mediaRecorder.ondataavailable = (event) => {
|
||
this.audioChunks.push(event.data);
|
||
};
|
||
|
||
this.mediaRecorder.onstop = () => {
|
||
const audioBlob = new Blob(this.audioChunks, { type: 'audio/mp3' });
|
||
this.hasRecording = true;
|
||
this.recordingStatus = '录音完成';
|
||
// 创建音频预览URL
|
||
this.audioUrl = URL.createObjectURL(audioBlob);
|
||
};
|
||
|
||
// 设置数据收集间隔为100ms
|
||
this.mediaRecorder.start(100);
|
||
this.isRecording = true;
|
||
this.recordingStatus = '正在录音...';
|
||
this.recordingTime = 0;
|
||
|
||
// 开始计时
|
||
this.timer = setInterval(() => {
|
||
this.recordingTime++;
|
||
}, 1000);
|
||
} catch (error) {
|
||
this.$message.error('无法访问麦克风');
|
||
console.error('录音错误:', error);
|
||
}
|
||
},
|
||
|
||
stopRecording() {
|
||
if (this.mediaRecorder && this.isRecording) {
|
||
this.mediaRecorder.stop();
|
||
this.isRecording = false;
|
||
clearInterval(this.timer);
|
||
|
||
// 停止所有音轨
|
||
this.mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
||
}
|
||
},
|
||
|
||
formatTime(seconds) {
|
||
const minutes = Math.floor(seconds / 60);
|
||
const remainingSeconds = seconds % 60;
|
||
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||
},
|
||
|
||
reRecord() {
|
||
// 释放之前的音频URL
|
||
if (this.audioUrl) {
|
||
URL.revokeObjectURL(this.audioUrl);
|
||
this.audioUrl = null;
|
||
}
|
||
this.hasRecording = false;
|
||
this.audioChunks = [];
|
||
this.recordingStatus = '准备就绪';
|
||
this.recordingTime = 0; // 重置录音时长
|
||
},
|
||
|
||
async uploadRecording() {
|
||
if (!this.hasRecording) return;
|
||
|
||
try {
|
||
const audioBlob = new Blob(this.audioChunks, { type: 'audio/mp3' });
|
||
const formData = new FormData();
|
||
formData.append('file', audioBlob, `recording_${Date.now()}.mp3`);
|
||
|
||
// TODO: 替换为实际的上传API
|
||
// const response = await uploadFile(formData);
|
||
|
||
this.$message.success('上传成功');
|
||
this.recordings.unshift({
|
||
name: `录音_${this.formatTime(this.recordingTime)}`,
|
||
time: new Date().toLocaleString()
|
||
});
|
||
|
||
// 重置录音状态
|
||
this.reRecord();
|
||
} catch (error) {
|
||
this.$message.error('上传失败');
|
||
console.error('上传错误:', error);
|
||
}
|
||
},
|
||
|
||
deleteRecording(index) {
|
||
this.recordings.splice(index, 1);
|
||
},
|
||
|
||
handleAudioSwitchChange(val) {
|
||
const playEnModel = this.deviceInfo.thingsModels.find(model => model.id === 'play_en');
|
||
if (playEnModel) {
|
||
playEnModel.shadow = val ? '1' : '0';
|
||
this.mqttPublish(this.deviceInfo, playEnModel);
|
||
}
|
||
},
|
||
|
||
showAddAudioDialog() {
|
||
this.addAudioDialogVisible = true;
|
||
this.newAudio = {
|
||
remark: '',
|
||
per: 0,
|
||
spd: 5,
|
||
pit: 5,
|
||
vol: 5,
|
||
tex_utf8: '',
|
||
filename: ''
|
||
};
|
||
},
|
||
|
||
// 获取未使用的最小ID
|
||
getNextAvailableId() {
|
||
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === 'mp3_list');
|
||
if (mp3ListModel) {
|
||
try {
|
||
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
|
||
const data = JSON.parse(jsonStr);
|
||
if (data.sound_card && data.sound_card.mp3_list) {
|
||
// 获取所有已使用的ID
|
||
const usedIds = data.sound_card.mp3_list.map(item => {
|
||
const id = parseInt(item.split('_')[0]);
|
||
return isNaN(id) ? 0 : id;
|
||
});
|
||
|
||
// 找到最小的未使用ID
|
||
let nextId = 1;
|
||
while (usedIds.includes(nextId)) {
|
||
nextId++;
|
||
}
|
||
return nextId;
|
||
}
|
||
} catch (error) {
|
||
console.error('解析mp3_list失败:', error);
|
||
}
|
||
}
|
||
return 1; // 如果出错,返回1
|
||
},
|
||
|
||
submitAudioForm() {
|
||
this.$refs.audioForm.validate((valid) => {
|
||
if (valid) {
|
||
// 找到mp3_list物模型
|
||
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === 'mp3_list');
|
||
if (mp3ListModel) {
|
||
try {
|
||
// 获取下一个可用的ID
|
||
const nextId = this.getNextAvailableId();
|
||
|
||
// 生成文件名
|
||
const filename = `${nextId}_${this.newAudio.remark}`;
|
||
|
||
// 构建TTS对象
|
||
const ttsData = {
|
||
JSON_id: 1,
|
||
sound_card: {
|
||
TTS: {
|
||
per: this.newAudio.per,
|
||
spd: this.newAudio.spd,
|
||
pit: this.newAudio.pit,
|
||
vol: this.newAudio.vol,
|
||
tex_utf8: this.newAudio.tex_utf8,
|
||
filename: filename
|
||
}
|
||
}
|
||
};
|
||
|
||
// 更新物模型shadow值
|
||
mp3ListModel.shadow = 'JSON=' + JSON.stringify(ttsData);
|
||
|
||
// 发送更新
|
||
this.mqttPublish(this.deviceInfo, mp3ListModel);
|
||
|
||
this.addAudioDialogVisible = false;
|
||
this.$message.success('添加成功');
|
||
} catch (error) {
|
||
console.error('添加音频失败:', error);
|
||
this.$message.error('添加失败');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
handleDeleteAudio(row) {
|
||
this.$confirm('确认删除该音频吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
// 找到mp3_list物模型
|
||
const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === 'mp3_list');
|
||
if (mp3ListModel) {
|
||
try {
|
||
// 解析当前JSON
|
||
const jsonStr = mp3ListModel.shadow.replace('JSON=', '');
|
||
const data = JSON.parse(jsonStr);
|
||
|
||
// 从音频名称中提取完整ID
|
||
const audioId = row.name;
|
||
|
||
// 从mp3_list中移除对应音频
|
||
if (data.sound_card && data.sound_card.mp3_list) {
|
||
data.sound_card.mp3_list = data.sound_card.mp3_list.filter(item => !item.endsWith(audioId));
|
||
|
||
// 更新物模型shadow值
|
||
const newShadow = 'JSON=' + JSON.stringify(data);
|
||
mp3ListModel.shadow = newShadow;
|
||
|
||
// 发送更新
|
||
this.mqttPublish(this.deviceInfo, mp3ListModel).then(() => {
|
||
this.$message.success('删除成功');
|
||
}).catch(error => {
|
||
console.error('发送删除命令失败:', error);
|
||
this.$message.error('删除失败');
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('解析或更新mp3_list失败:', error);
|
||
this.$message.error('删除失败');
|
||
}
|
||
}
|
||
}).catch(() => { });
|
||
},
|
||
|
||
showAddPlaylistDialog() {
|
||
this.addPlaylistDialogVisible = true;
|
||
this.newPlaylist = {
|
||
name: '',
|
||
type: '用户',
|
||
status: '启用',
|
||
audioId: '',
|
||
playTimeStart: null,
|
||
playTimeEnd: null,
|
||
weekdays: [],
|
||
radarEnabled: false,
|
||
radarSpeedMin: 0,
|
||
radarSpeedMax: 120
|
||
};
|
||
},
|
||
|
||
submitPlaylistForm() {
|
||
this.$refs.playlistForm.validate((valid) => {
|
||
if (valid) {
|
||
// 找到play_list物模型
|
||
const playListModel = this.deviceInfo.thingsModels.find(model => model.id === 'play_list');
|
||
if (playListModel) {
|
||
try {
|
||
// 解析当前JSON
|
||
const jsonStr = playListModel.shadow.replace('JSON=', '');
|
||
const data = JSON.parse(jsonStr);
|
||
|
||
// 获取选中的音频信息
|
||
const selectedAudio = this.audioList.find(audio => audio.id === this.newPlaylist.audioId);
|
||
if (!selectedAudio) {
|
||
this.$message.error('未找到选中的音频');
|
||
return;
|
||
}
|
||
|
||
// 构建新的播放项
|
||
const newPlayItem = {
|
||
play: {
|
||
en: 1,
|
||
num: this.defaultList.length + 1,
|
||
sou: 0,
|
||
filename: `${selectedAudio.id}_${selectedAudio.name}`,
|
||
play_time: 1,
|
||
pause_time: 0
|
||
},
|
||
time: {
|
||
en: 1,
|
||
begin: this.convertTimeToSeconds(this.newPlaylist.playTimeStart),
|
||
end: this.convertTimeToSeconds(this.newPlaylist.playTimeEnd),
|
||
week: this.convertWeekArrayToValue(this.newPlaylist.weekdays)
|
||
},
|
||
speed: {
|
||
en: this.newPlaylist.radarEnabled ? 1 : 0,
|
||
min: this.newPlaylist.radarEnabled ? this.newPlaylist.radarSpeedMin : 0,
|
||
max: this.newPlaylist.radarEnabled ? this.newPlaylist.radarSpeedMax : 0
|
||
}
|
||
};
|
||
|
||
// 添加到播放列表
|
||
if (!data.sound_card) {
|
||
data.sound_card = {};
|
||
}
|
||
if (!data.sound_card.play_list) {
|
||
data.sound_card.play_list = [];
|
||
}
|
||
data.sound_card.play_list.push(newPlayItem);
|
||
|
||
// 更新物模型shadow值
|
||
playListModel.shadow = 'JSON=' + JSON.stringify(data);
|
||
|
||
// 发送更新
|
||
this.mqttPublish(this.deviceInfo, playListModel).then(() => {
|
||
this.addPlaylistDialogVisible = false;
|
||
this.$message.success('添加成功');
|
||
}).catch(error => {
|
||
console.error('发送播放列表更新失败:', error);
|
||
this.$message.error('添加失败');
|
||
});
|
||
} catch (error) {
|
||
console.error('解析或更新播放列表失败:', error);
|
||
this.$message.error('添加失败');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 将时间转换为秒数
|
||
convertTimeToSeconds(time) {
|
||
if (!time) return 0;
|
||
return time.getHours() * 3600 + time.getMinutes() * 60;
|
||
},
|
||
|
||
// 将星期数组转换为位值
|
||
convertWeekArrayToValue(weekdays) {
|
||
let value = 0;
|
||
weekdays.forEach(day => {
|
||
value |= (1 << parseInt(day));
|
||
});
|
||
return value;
|
||
},
|
||
|
||
formatSpeed(val) {
|
||
return val;
|
||
},
|
||
|
||
formatPitch(val) {
|
||
return val;
|
||
},
|
||
|
||
formatVolume(val) {
|
||
return val;
|
||
},
|
||
|
||
// 将秒数转换为时间格式
|
||
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 weekMap = {
|
||
1: '周一',
|
||
2: '周二',
|
||
3: '周三',
|
||
4: '周四',
|
||
5: '周五',
|
||
6: '周六',
|
||
0: '周日'
|
||
};
|
||
const result = [];
|
||
for (let i = 0; i < 7; i++) {
|
||
if (week & (1 << i)) {
|
||
result.push(weekMap[i]);
|
||
}
|
||
}
|
||
return result;
|
||
},
|
||
|
||
handleStatusChange(row) {
|
||
const playListModel = this.deviceInfo.thingsModels.find(model => model.id === 'play_list');
|
||
if (playListModel) {
|
||
try {
|
||
const jsonStr = playListModel.shadow.replace('JSON=', '');
|
||
const data = JSON.parse(jsonStr);
|
||
|
||
if (data.sound_card && data.sound_card.play_list) {
|
||
// 找到对应的播放项
|
||
const playItem = data.sound_card.play_list[row.id - 1];
|
||
if (playItem) {
|
||
// 更新状态
|
||
playItem.play.en = row.status === '启用' ? 1 : 0;
|
||
|
||
// 更新物模型shadow值
|
||
playListModel.shadow = 'JSON=' + JSON.stringify(data);
|
||
|
||
// 发送更新
|
||
this.mqttPublish(this.deviceInfo, playListModel).then(() => {
|
||
this.$message.success('状态更新成功');
|
||
}).catch(error => {
|
||
console.error('发送状态更新失败:', error);
|
||
this.$message.error('状态更新失败');
|
||
});
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('解析或更新播放列表状态失败:', error);
|
||
this.$message.error('状态更新失败');
|
||
}
|
||
}
|
||
},
|
||
|
||
handleDeletePlaylist(row) {
|
||
this.$confirm('确认删除该播放项吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
const playListModel = this.deviceInfo.thingsModels.find(model => model.id === 'play_list');
|
||
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(row.id - 1, 1);
|
||
|
||
// 更新序号
|
||
data.sound_card.play_list.forEach((item, index) => {
|
||
item.play.num = index + 1;
|
||
});
|
||
|
||
// 更新物模型shadow值
|
||
playListModel.shadow = 'JSON=' + JSON.stringify(data);
|
||
|
||
// 发送更新
|
||
this.mqttPublish(this.deviceInfo, playListModel).then(() => {
|
||
this.$message.success('删除成功');
|
||
}).catch(error => {
|
||
console.error('发送删除命令失败:', error);
|
||
this.$message.error('删除失败');
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('解析或更新播放列表失败:', error);
|
||
this.$message.error('删除失败');
|
||
}
|
||
}
|
||
}).catch(() => {});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.running-status {
|
||
padding: 20px;
|
||
|
||
.status-col {
|
||
.title {
|
||
line-height: 28px;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.mode-section {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.mode-card {
|
||
margin-bottom: 20px;
|
||
transition: all 0.3s;
|
||
padding: 20px;
|
||
|
||
&:hover {
|
||
transform: translateY(-5px);
|
||
}
|
||
|
||
.mode-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
|
||
i,
|
||
.svg-icon {
|
||
font-size: 20px;
|
||
margin-right: 8px;
|
||
color: #409EFF;
|
||
}
|
||
|
||
.mode-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
.mode-content {
|
||
text-align: center;
|
||
padding: 10px 0;
|
||
|
||
.title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
}
|
||
|
||
.settings-card,
|
||
.audio-list-card,
|
||
.default-list-card {
|
||
margin-bottom: 20px;
|
||
transition: all 0.3s;
|
||
|
||
&:hover {
|
||
transform: translateY(-5px);
|
||
}
|
||
|
||
.settings-header,
|
||
.audio-list-header,
|
||
.default-list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.settings-title,
|
||
.audio-list-title,
|
||
.default-list-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #303133;
|
||
}
|
||
}
|
||
}
|
||
|
||
.el-table {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.el-slider {
|
||
margin-top: 10px;
|
||
}
|
||
}
|
||
|
||
.voice-control-card {
|
||
margin-bottom: 20px;
|
||
transition: all 0.3s;
|
||
|
||
&:hover {
|
||
transform: translateY(-5px);
|
||
}
|
||
|
||
.voice-control-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.voice-control-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
.voice-control-content {
|
||
padding: 20px;
|
||
text-align: center;
|
||
|
||
.recorder-status {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
|
||
.status-indicator {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 50%;
|
||
background: #f4f4f5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 10px;
|
||
transition: all 0.3s;
|
||
|
||
i {
|
||
font-size: 30px;
|
||
color: #909399;
|
||
}
|
||
|
||
&.recording {
|
||
background: #fef0f0;
|
||
animation: pulse 1.5s infinite;
|
||
|
||
i {
|
||
color: #f56c6c;
|
||
}
|
||
}
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
}
|
||
}
|
||
|
||
.timer-display {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #303133;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.control-buttons {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
|
||
.el-button {
|
||
width: 50px;
|
||
height: 50px;
|
||
font-size: 20px;
|
||
}
|
||
}
|
||
|
||
.recording-preview {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
|
||
.preview-title {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
margin-bottom: 10px;
|
||
text-align: left;
|
||
}
|
||
|
||
.audio-player {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
|
||
audio {
|
||
width: 100%;
|
||
height: 40px;
|
||
}
|
||
|
||
.preview-controls {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: 5px;
|
||
|
||
.el-button {
|
||
padding: 8px 15px;
|
||
|
||
i {
|
||
margin-right: 5px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.recording-list {
|
||
text-align: left;
|
||
border-top: 1px solid #ebeef5;
|
||
padding-top: 15px;
|
||
|
||
.list-title {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.recording-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #ebeef5;
|
||
|
||
.recording-name {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
color: #303133;
|
||
}
|
||
|
||
.recording-time {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-right: 10px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@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 10px rgba(245, 108, 108, 0);
|
||
}
|
||
|
||
100% {
|
||
transform: scale(1);
|
||
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0);
|
||
}
|
||
}
|
||
|
||
.radar-settings {
|
||
margin-top: -20px;
|
||
padding: 15px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 4px;
|
||
|
||
.speed-range {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
|
||
.el-input-number {
|
||
width: 120px;
|
||
}
|
||
|
||
.speed-separator {
|
||
color: #606266;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.speed-unit {
|
||
color: #606266;
|
||
margin-left: 5px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.time-range {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
|
||
.time-separator {
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
</style> |