You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

956 lines
25 KiB

<template>
<view class="switch-manage">
<store-name></store-name>
<view class="sm-tit">
<text>{{pageInfo.name || '-'}}</text>
<text>{{pageInfo.tips || ''}}</text>
</view>
<view class="slider-box">
<text>音量</text>
<slider :value="voiceLevel" activeColor="#009874" block-color="#009874" block-size="22" @change="sliderChange" min="0" max="100" />
<view class="">
<text>0%</text>
<text>100%</text>
</view>
</view>
<view class="sm-list">
<view class="sl-item" v-for="(e, i) in deviceList" :key="i">
<view class="si-top">
<image mode="aspectFit" :src="getIcon()"></image>
<view class="st-right">
<view class="sr-name">{{e.hardware_name || '-'}}</view>
<!-- 门闸没有状态查询 -->
<!-- 请求接口自定义字段设备状态 1->在线0->离线 -->
<view class="sr-bot" v-if="pageInfo.id !=5">
<view :class="[e.defineStatusCode == 1?'active':'']">
<text>{{ e.defineStatusCode == 1 ? '设备在线' : e.defineStatusCode == 0?'设备离线' : '-' }}</text>
</view>
<image mode="aspectFit" src="/subpackage/device/static/images/refresh.png"
@click="refreshStatusBtn({switchInfo:e, index:i})"></image>
</view>
</view>
</view>
<block v-if="pageInfo.name=='音响管理'">
<view class="si-bottom">
<view v-if="pageInfo.isOpen" @click="operateBtn({ switchInfo: e, status: 1 })">
<image mode="aspectFit" :src="pageInfo.openIcon || ''"></image>
<view>{{pageInfo.openName || '-'}}</view>
</view>
<view v-if="pageInfo.isClose" @click="operateBtn({ switchInfo: e, status: 0 })">
<image mode="aspectFit" :src="pageInfo.closeIcon || ''"></image>
<view>{{pageInfo.closeName || '-'}}</view>
</view>
</view>
</block>
</view>
</view>
<!-- 音频控制 -->
<view class="voice_control_pad" v-if="voicePadConfig.showVoicePad">
<!-- 文字转语音 -->
<view class="cover_bg" @click.stop="voicePadConfig.showVoicePad=false">
</view>
<view class="v_box" v-if="voicePadConfig.step==0">
<textarea value="" v-model="voicePadConfig.txt" placeholder="请输您要说的话,输入标点符号,智能语音效果更好哦!" />
<view class="v_btns">
<view class="voice_btn btn_white" @click="sendVoice()">发送</view>
<view class="voice_btn btn_green" @click="listenVoice()">试听</view>
</view>
</view>
<!-- 录音 -->
<view class="v_box" v-else>
<view class="voice_title">{{getVoiceTxt(voicePadConfig.step)}}</view>
<image class="voice_img" src="../../static/images/i_voice.png" :src="getVoiceIcon(voicePadConfig.step)"
:class="voicePadConfig.step==2||voicePadConfig.step==4?'voice_img_playing':''"></image>
<view class="voice_btn " @longpress="longPressHandle" @touchend="touchEndHandle"
hover-class="btn_active" v-if="voicePadConfig.step<3">按住说话</view>
<view class="v_btns" v-else-if="voicePadConfig.step==3">
<view class="voice_btn btn_white" @click="sendVoice()">发送</view>
<view class="voice_btn btn_green" @click="listenVoice()">听取录音</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 20231129
* fix 语音发送云中控参数 AC_filterCloudACData
* 新增 value.text
*
*
* */
const recorderManager = uni.getRecorderManager();
const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = true;
import store_name from '../../components/store_name/store_name';
import deviceServer from '../../js/device_server';
import deviceApi from '../../js/device_api';
import { mapState } from 'vuex';
import util from '../../../../utils/util';
import DEVICE_FUN from '../../js/device_fun.js';
const showArr = {
's14': {
id: 14,
name: '音响管理',
isOpen: true,
isClose: true,
openIcon: '/subpackage/device/static/images/i_txt.png',
closeIcon: '/subpackage/device/static/images/i_voice_1.png',
openName: '文转音',
closeName: '语音',
hardware_type: 'AudioPlayer',
tips: "点击对应音响的麦克风可以进行说话,说话内容将通过音响播放。",
},
}
export default {
components: {
'store-name': store_name
},
computed: {
...mapState({
curStoreInfo: state => state.device.curStoreInfo,
}),
getVoiceIcon() {
return i => {
return `../../static/images/i_voice_${i}.png`
}
},
getVoiceTxt() {
return i => {
return [,'请按住说话','录音中...','录音已完成','播放中...'][i]
}
},
},
watch: {
curStoreInfo(newVal, oldVal) {
this.deviceList = [];
this.getDeviceList({
stadium_id: newVal.id,
hardware_type: this.pageInfo.hardware_type
})
},
},
data() {
return {
pageInfo: {},
deviceList: [],
voicePadConfig: {
showVoicePad: false,
step: 1, // 0 文字转语音, 1 未录音, 2 录音中, 3 录音结束, 4 播放录音中,
txt: "",
voicePath: "",
voiceUrl: "",
},
voiceLevel:0,
// 20231129 fix 云中控判断
curSwitch: null,
}
},
onLoad(options) {
this.initAudio();
this.initPage(options);
},
methods: {
//初始化音频
initAudio(){
let self = this;
recorderManager.onStop(function(res) {
console.log('recorder stop' + JSON.stringify(res));
self.voicePadConfig.voicePath = res.tempFilePath;
});
innerAudioContext.onPlay(function(res) {
console.log('play voice onPlay' + JSON.stringify(res));
if(self.voicePadConfig.step==3)self.voicePadConfig.step = 4
});
innerAudioContext.onEnded(function(res) {
console.log('play voice onEnded' + JSON.stringify(res));
if(self.voicePadConfig.step == 4)self.voicePadConfig.step = 3
});
},
initPage(options){
let _pageInfo = showArr[`s${options.sid}`] || {};
this.pageInfo = _pageInfo;
uni.setNavigationBarTitle({
title: _pageInfo.name
});
this.getDeviceList({
stadium_id: this.curStoreInfo.id,
hardware_type: _pageInfo.hardware_type
})
this.updateVoiceSlider();
},
//滑动更改音响音量
sliderChange(e) {
console.log('value 发生变化:' + e.detail.value)
let _data = {
"device": this.curStoreInfo.device_name,
"data": {
"name": "audio-player-volume",
"value": {
"volume": e.detail.value.toString()
}
},
}
//发送命令到中控
this.operateReq({
data: _data,
isTip:true
})
},
//更新音响音量
updateVoiceSlider(){
let that = this
let _data = {
"device": this.curStoreInfo.device_name,
"data": {
"name": "audio-player-volume",
"value": {
"volume": "-1"
}
},
}
//发送命令到中控
this.operateReq({
data: _data,
succFun: (res) => {
console.log("操作结果:",res);
that.voiceLevel = res.data
},
isTip:false
})
},
longPressHandle(e) {
console.log("长按开始...");
this.voicePadConfig.step = 2
console.log('开始录音');
recorderManager.start({
format: "wav"
});
},
touchEndHandle(e) {
console.log("触摸结束...");
this.voicePadConfig.step = 3
console.log('录音结束');
recorderManager.stop();
},
//处理音响数据
HandleVoiceOperate({
switchInfo,
status
}) {
console.log(switchInfo, status);
if (status === 1) { //文字转语音
console.log("文字转语音");
this.voicePadConfig.showVoicePad = true
this.voicePadConfig.step = 0
} else { //语音发送
console.log("语音发送");
this.voicePadConfig.showVoicePad = true
this.voicePadConfig.step = 1
}
},
//发送语音到中控
async sendVoice() {
let that = this
console.log(deviceApi.ORIGIN)
//文字转语音
let _url = ""; //需上传给后台的语音地址
if(this.voicePadConfig.step==0){
let txt = this.voicePadConfig.txt
if(!txt)return util.showNone("请先输入您要说的话后重试")
_url = `${deviceApi.ORIGIN}/ouxuanac/tts/textToVoice.wav?text=${txt}&voice_type=4&speed=-1&volume=10`;
}
//已录音
if(this.voicePadConfig.step==3){
console.log("本地录音path:",that.voicePadConfig.voicePath);
let e = await deviceServer.uploadFile({
url: deviceApi.uploadAudio,
filePath: that.voicePadConfig.voicePath
});
let _res = util.jsonPar(e.data);
if (_res.code == 0) {
that.voicePadConfig.voiceUrl = _res.data.url
console.log("上传成功后path:", that.voicePadConfig.voiceUrl);
_url = _res.data.url // 上传后地址
} else {
console.error('上传录音失败--->', _res);
util.showNone(_res.message || '上传录音失败,请重试!')
return
}
}
//组装中控所需data
let _data = {
"device": this.curStoreInfo.device_name,
"data": {
"name": "audio-player",
"value": {
"url": _url,
"text": this.voicePadConfig?.txt || ''
}
},
// "token": "f0d5c19b-b87e-11eb-bc7d-5254005df464"
}
//发送命令到中控
if(_url)this.operateReq({
data: DEVICE_FUN.AC_filterCloudACData(_data, that.curSwitch),
})
},
//试听/播放 语音/录音
async listenVoice() {
let self = this
if(this.voicePadConfig.step==0){
//需要将语音下载到本地,然后播放
let txt = this.voicePadConfig.txt;
if(!txt)return util.showNone("请先输入您要说的话后重试")
let url =`${deviceApi.ORIGIN}/ouxuanac/tts/textToVoice.wav?text=${txt}&voice_type=4&speed=-1&volume=10`;
let updated_url = await this.getDownloadUrl(url)
innerAudioContext.src = updated_url
}
if(this.voicePadConfig.step==3){
innerAudioContext.src = this.voicePadConfig.voicePath;
}
console.log('播放录音文件:', innerAudioContext.src);
if(innerAudioContext.src)innerAudioContext.play();
},
getDownloadUrl(url) {
console.log('下载录音');
util.showLoad()
return new Promise((rs,rj)=>{
uni.downloadFile({
url: url, //资源路径e
success: (res) => {
if (res.statusCode === 200) {
console.log('下载成功',res);
util.hideLoad()
rs(res.tempFilePath)
}
},
fail: (e) => {
console.log('文字转录音文件下载失败',e);
util.hideLoad()
rj(e)
},
complete: (e) => {
util.hideLoad()
},
});
})
},
getDeviceList({
stadium_id,
hardware_type,
limit = 100,
page = 1
}) {
util.showLoad();
deviceServer.get({
url: deviceApi.hardwareList,
data: {
'filter[hardware_type]': hardware_type,
'filter[stadium_id]': stadium_id,
'limit': limit,
'page': page,
},
failMsg: '加载失败!'
})
.then(res => {
util.hideLoad();
let _list = res.list || [];
//直接拿中控在线状态 做下临时处理先用中控状态的数据填一下
let deviceInfo = uni.getStorageSync("deviceInfo");
_list = _list.map((e,i)=>{
e['defineStatusCode'] = deviceInfo.Online==1? 1 : 0;
return e
})
this.deviceList = _list;
console.log(res)
this.updateVoiceSlider();
})
.catch(util.hideLoad)
},
// 按钮操作, status 0 -> 关(左), 1 -> 开(右)
operateBtn: util.debounce(function({
switchInfo,
status
}) {
this.curSwitch = switchInfo || {};
if (switchInfo.hardware_type === "AudioPlayer") return this.HandleVoiceOperate({
switchInfo,
status
}); //单独处理音响操作0704
}, 300, 300),
// 获取接口参数结构
getOperateReqData({
switchInfo,
status
}) {
let {
curStoreInfo
} = this;
let _query = switchInfo.hardware_type === 'GateControl' ?
this.getGateQuery({
switchInfo,
status
}) :
this.getSwitchQuery({
switchInfo,
status
});
let _data = {
device: curStoreInfo.device_name, // 中控名,
data: _query, // 后端数据结构, 参考src\subpackage\device\js\ouxuanac.md
};
//针对门禁没有关按钮发两条命令->开&关 20201224 后端: 直接发两条 关的那条这里填5 然后你那边不用管返回
if (switchInfo.hardware_type === 'AccessControl' && status == 0) _data['delay'] = '5';
return _data;
},
// 操作接口请求
operateReq({
data,
succFun,
isTip = true,
isLoad = true
}) {
let that = this
if (isLoad) util.showLoad();
deviceServer.post({
url: deviceApi.ouxuanac,
data: data,
isDefaultGet: false,
})
.then(res => {
if (isLoad) util.hideLoad();
if (res.data.code == 0) {
succFun(res.data);
if (isTip) util.showNone(res.data.message || '操作成功!');
that.voicePadConfig.showVoicePad = false; //操作成功后关闭voicePad
} else {
if (isTip) util.showNone(res.data.message || '操作失败!');
}
})
.catch(err => {
if (isLoad) util.hideLoad()
})
},
// 门闸数据结构 // 门闸数据结构不统一
getGateQuery({
switchInfo,
status
}) {
let {
enter_id,
leave_id,
hardware_net_addr
} = switchInfo;
// 进出控制ID |进入-> enter_id 离开-> leave_id|
let _cid = status === 1 ? enter_id :
status === 0 ? leave_id : '';
return {
name: 'gate',
value: {
tcp: hardware_net_addr + '',
cid: _cid + ''
},
is_delay: true,
queue_group: 'gate'
}
},
//
refreshStatusBtn: util.debounce(function({
switchInfo,
index
}) {
// this.getStatusReq({
// data: this.getSwitchStatusQuery(switchInfo),
// index,
// })
//TODO 等待后续音响设备调试后,再开放更新设备状态功能
// _data = DEVICE_FUN.AC_filterCloudACData(data,switchInfo);
// 后台:就没这个功能...
util.showLoad();
setTimeout(()=>util.hideLoad(),1000)
}, 300, 300),
// 获取设备状态
getStatusReq({
data,
index
}) {
let _deviceList = this.deviceList.slice();
util.showLoad();
deviceServer.post({
url: deviceApi.ouxuanac,
data: data,
isDefaultGet: false,
})
.then(res => {
util.hideLoad();
let _data = res.data || {};
console.log(this.changeLowerCase(_data.data))
if (_data.code == 504 || this.changeLowerCase(_data.data).indexOf('timeout') != -1) {
_deviceList[index]['defineStatusCode'] = 0;
} else if (_data.code == 0 && this.changeLowerCase(_data.data).indexOf('timeout') == -1) {
_deviceList[index]['defineStatusCode'] = 1;
} else {
util.showNone(_data.message || '操作失败!');
}
this.deviceList = _deviceList;
// if(res.data.code == 0){
// if(isTip)util.showNone(res.data.message || '操作成功!');
// }else{
// if(isTip)util.showNone(res.data.message || '操作失败!');
// }
})
.catch(util.hideLoad)
},
// 咖啡机和门闸暂时没有状态
// 设备状态请求参数数据结构
getSwitchStatusQuery(switchInfo) {
let {
curStoreInfo
} = this;
let {
hardware_connect_method,
hardware_type,
hardware_id,
node_id,
hardware_net_addr
} = switchInfo;
const _query = {
name: this.getStatusQueryName(switchInfo),
value: {
id: hardware_id + '',
} // value 内值全为String
};
if (this.changeLowerCase(hardware_connect_method) === 'tcp') _query.value['tcp'] = hardware_net_addr + '';
let _flag = this.changeLowerCase(hardware_connect_method) === 'serialport485' || this.changeLowerCase(
hardware_connect_method) === 'tcp';
if (_flag) {
if (hardware_type === 'Air') { // 空调开关状态 key为 op
_query.value['op'] = 'status'
_query['name'] = this.getAirQueryName(switchInfo)
} else {
_query.value['p'] = node_id + ''; // 硬件子id
// postData.value['o'] = this.getRelayStatus(status); // 开关状态
}
}
return {
device: curStoreInfo.device_name, // 中控名,
data: _query, // 后端数据结构, 参考src\subpackage\device\js\ouxuanac.md
}
// this.getStatusReq({
// index,
// data: {
// device: curStoreInfo.device_name, // 中控名,
// data: _query, // 后端数据结构, 参考src\subpackage\device\js\ouxuanac.md
// }
// })
},
// switchInfo -> 当前开关信息
// status -> 开关状态 0 -> 关(右), 1 -> 开(左)
// 数据结构参考 src\subpackage\device\js\ouxuanac.md
// 空调开关数据结构独立判断处理 hardware_type === 'Air'
getSwitchQuery({
switchInfo,
status
}) {
let {
hardware_connect_method,
hardware_type,
hardware_id,
node_id,
hardware_net_addr
} = switchInfo;
const postData = {
name: this.getQueryName(switchInfo),
value: {
id: hardware_id + '',
} // value 内值全为String
};
if (this.changeLowerCase(hardware_connect_method) === 'gpio') postData.value['status'] = this
.getRelayStatus(status);
// tcp 连接需要 hardware_net_addr
if (this.changeLowerCase(hardware_connect_method) === 'tcp') postData.value['tcp'] = hardware_net_addr +
'';
let _flag = this.changeLowerCase(hardware_connect_method) === 'serialport485' || this.changeLowerCase(
hardware_connect_method) === 'tcp';
if (_flag) {
if (hardware_type === 'Air') { // 空调开关状态 key为 op
postData.value['op'] = this.getAirRelayStatus(status);
postData['name'] = this.getAirQueryName(switchInfo)
} else {
// 空调设备不需要以下两个字段
postData.value['p'] = node_id + ''; // 硬件子id
postData.value['o'] = this.getRelayStatus(status); // 开关状态
}
}
return postData;
},
changeLowerCase(str) {
return str.toString().toLocaleLowerCase();
},
// 常规开关状态
// Low = "low", // 低电位,为开启
// High = "high", // 高电位, 为关闭
getRelayStatus(status) {
return ['high', 'low'][status] || ''
},
// 空调状态
// status = "status",
// on = "on",
// off = "off",
getAirRelayStatus(status) {
return ['off', 'on'][status] || ''
},
// 非空调获取状态name
getStatusQueryName(switchInfo) {
let {
hardware_connect_method
} = switchInfo;
let _obj = {
'Gpio': 'get-rpio', // 全设备
'SerialPort485': 'zzio404d-gpio-status',
'Tcp': 'zzio404d-gpio-status-tcp',
'YZK':'zzio404d-gpio-status'
};
return _obj[hardware_connect_method] || ''
},
// 非空调获取设置name
getQueryName(switchInfo) {
let {
hardware_connect_method
} = switchInfo;
let _obj = {
'Gpio': 'set-rpio', // 全设备
'SerialPort485': 'zzio404d-gpio',
'Tcp': 'zzio404d-gpio-tcp',
'YZK':'zzio404d-gpio'
};
return _obj[hardware_connect_method] || ''
},
// 空调name获取
getAirQueryName(switchInfo) {
let {
hardware_connect_method,
hardware_model
} = switchInfo;
let _flag = this.changeLowerCase(hardware_connect_method) === 'tcp' && this.changeLowerCase(
hardware_model) === 'jianda';
if (_flag) return 'ray-air-rs-tcp';
let _obj = {
'acmelec': 'acmelec',
'zhongnan': 'zhongnan',
'jianda': 'ray-air-rs',
"XiaoHuiXiong":"xhx-lora-air-tcp"
};
return _obj[this.changeLowerCase(hardware_model)] || '';
},
getIcon() {
let {
pageInfo
} = this;
if (!pageInfo.id) return '';
return `/subpackage/device/static/images/devices/${pageInfo.id}.png`
}
}
}
</script>
<style lang="scss">
@import '~style/public.scss';
page{
background:url(../../static/images/page_bg.jpg) repeat-y;
background-size: 100%;
background-attachment: fixed;
}
.slider-box{
margin-bottom: 40rpx;
> text {
margin-left: 30rpx;
font-size: 24rpx;
color: #fff;
}
> view{
width: 100%;
color: #fff;
font-size: 28rpx;
@include centerFlex(space-between);
padding: 0 34rpx;
}
}
.sm-tit {
padding-left: 40upx;
padding-top: 52upx;
padding-bottom: 30upx;
display: flex;
justify-content: space-between;
flex-wrap: nowrap;
text:first-child {
line-height: 60upx;
font-size: 44upx;
font-weight: 500;
color: #1A1A1A;
}
text:last-child {
width: 466rpx;
color: #fff;
font-size: 24rpx;
}
}
.sm-list {
padding: 0 32upx;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.sl-item {
margin-bottom: 30upx;
width: 328upx;
border-radius: 10upx;
border: .5px solid #fff;
box-shadow: 0 4upx 12upx 0 #00987454, inset 0 0 40upx 0 #ffffff80;
background-image: linear-gradient(180deg, #eff6f44d 0%, #FFFFFF 100%);
.si-top {
padding: 20upx 20upx 30upx;
display: flex;
flex-wrap: nowrap;
>image {
flex-shrink: 0;
flex-grow: 0;
margin-right: 20upx;
width: 80upx;
height: 80upx;
}
.st-right {
flex-grow: 1;
>.sr-name {
margin-bottom: 8upx;
line-height: 44upx;
font-size: 32upx;
color: #1A1A1A;
@include textHide(1);
}
.sr-bot {
display: flex;
align-items: center;
>view {
margin-right: 8upx;
padding: 0 12upx;
font-size: 20upx;
line-height: 28upx;
border-radius: 28upx;
border: 2upx solid #9A9A9D;
color: #9A9A9D;
&::before {
content: '';
margin-right: 12upx;
margin-top: -4upx;
display: inline-block;
vertical-align: middle;
width: 8upx;
height: 8upx;
border-radius: 50%;
background-color: #9A9A9D;
}
&.active {
color: #333333;
&::before {
background-color: $themeColor;
}
}
}
>image {
flex-shrink: 0;
flex-grow: 0;
width: 32upx;
height: 32upx;
}
}
}
}
.si-bottom {
padding-top: 30upx;
padding-bottom: 30upx;
display: flex;
justify-content: center;
border-top: .5px solid #fff;
>view {
flex-shrink: 0;
flex-grow: 0;
width: 50%;
>image {
display: block;
margin: 0 auto 20upx;
width: 100upx;
height: 100upx;
}
>view {
font-size: 24upx;
line-height: 34upx;
text-align: center;
color: #9a9a9d;
}
}
}
}
}
//语音控制板
.voice_control_pad {
position: fixed;
top: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
z-index: 1;
.cover_bg {
position: fixed;
top: 0;
background: rgba($color: #000000, $alpha: .3);
z-index: 2;
width: 100%;
height: 100%;
}
.v_box {
z-index: 3;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
border-radius: 5rpx;
width: 620rpx;
height: 550rpx;
background-color: white;
textarea {
margin-top: 50rpx;
width: 560rpx;
height: 260rpx;
}
.voice_title {
max-width: 560rpx;
color: #1A1A1A;
font-size: 32rpx;
font-weight: 800;
margin-top: 68rpx;
}
.voice_img {
margin-top: 70rpx;
width: 100rpx;
height: 100rpx;
}
.voice_img_playing {
width: 206rpx;
height: 100rpx;
}
.voice_btn {
margin-top: 100rpx;
width: 392rpx;
height: 112rpx;
background: #009874;
border-radius: 5rpx;
text-align: center;
line-height: 112rpx;
color: white;
font-size: 32rpx;
}
.btn_active {
color: black;
background-color: rgba($color: #000000, $alpha: .4);
}
.btn_white {
width: 204rpx;
height: 88rpx;
background-color: white;
color: #009874;
border: 1rpx solid #009874;
line-height: 88rpx;
}
.btn_green {
width: 204rpx;
height: 88rpx;
background-color: #009874;
color: white;
line-height: 88rpx;
}
.v_btns {
width: 470rpx;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
}
}
</style>