Ver Fonte

语音输入

zouling há 4 meses atrás
pai
commit
55ad379804

+ 6 - 0
property_ui/1.txt

@@ -10,3 +10,9 @@ SHA1: 2D:05:E4:2C:E2:00:0A:BF:03:58:2A:94:94:38:65:BE:5B:1C:F1:1B
 高德key:c99089913eec7e8472a6f7cb228c22eb
 
 极光appkey:
+
+百度语音
+
+语音识别: AppID:30799445
+	API Key :MuVtMTc6EHaLbrr38WEhqjml
+	Secret Key:rPGCiA3L41VwuTCBHkwkmcOt0IPTsi8z

+ 14 - 3
property_ui/manifest.json

@@ -8,9 +8,9 @@
     "app-plus" : {
         "usingComponents" : true,
         "nvueCompiler" : "uni-app",
-		"compatible" : {
-		    "ignoreVersion" : true //true表示忽略版本检查提示框,HBuilderX1.9.0及以上版本支持
-		},
+        "compatible" : {
+            "ignoreVersion" : true //true表示忽略版本检查提示框,HBuilderX1.9.0及以上版本支持
+        },
         "splashscreen" : {
             "alwaysShowBeforeRender" : true,
             "waiting" : true,
@@ -25,6 +25,8 @@
                 "permissions" : [
                     "<uses-feature android:name=\"android.hardware.camera\"/>",
                     "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
                     "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
@@ -33,11 +35,13 @@
                     "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
                     "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.INTERNET\"/>",
                     "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
                     "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
                     "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
                     "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
                     "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
                 ]
             },
@@ -52,6 +56,13 @@
                         "appkey_ios" : "c99089913eec7e8472a6f7cb228c22eb",
                         "appkey_android" : "c99089913eec7e8472a6f7cb228c22eb"
                     }
+                },
+                "speech" : {
+                    "baidu" : {
+                        "appid" : "30799445",
+                        "apikey" : "MuVtMTc6EHaLbrr38WEhqjml",
+                        "secretkey" : "rPGCiA3L41VwuTCBHkwkmcOt0IPTsi8z"
+                    }
                 }
             },
             "icons" : {

+ 3 - 2
property_ui/service/components/car/list.vue

@@ -15,8 +15,8 @@
 					<view class="clist"><view class="tit">巡检时间</view>:{{ite.checkTime}}</view>
 				</view>
 				<view class="patbtns">
-					<view class="btna" @click.stop="getDetail(ite.checkPointManageId)">修改</view>
-					<view  class="btnc" @click.stop="getDelFn(ite.checkPointManageId)">删除</view>
+					<view class="btna" v-if="checkPermi(['wuYe:checkPointManage:edit'])" @click.stop="getDetail(ite.checkPointManageId)">修改</view>
+					<view  class="btnc" v-if="checkPermi(['wuYe:checkPointManage:remove'])" @click.stop="getDelFn(ite.checkPointManageId)">删除</view>
 				</view>
 			</view>
 		</block>
@@ -40,6 +40,7 @@
 					<!-- v-else -->
 					<view class="btna" @click.stop="getDetail(ite.checkPointRecordId)" v-if="ite.checkStatus==2">查看</view>
 					<view  class="btnb" @click.stop="getAddFn(ite.checkPointRecordId)" v-else>去巡更</view>
+					<view  class="btnc" v-if="checkPermi(['wuYe:checkPointRecord:remove'])" @click.stop="getDelFn(ite.checkPointRecordId)">删除</view>
 				</view>
 			</view>
 		</block>

+ 14 - 2
property_ui/service/components/popup/popup.vue

@@ -50,7 +50,7 @@
 				<view class="asstit">回复</view>
 				<uni-easyinput maxlength='200' type="textarea" autoHeight v-model="text" :inputBorder='false' placeholder="请输入回复内容,最多不超过200字…" />
 				<view class="voice mb20">
-					<view class="flexc">
+					<view class="flexc" @click="getVoiceFn">
 						<image :src="voice"></image>
 						语音输入
 					</view>
@@ -76,6 +76,7 @@
 			
 			<image :src="closeimg" class="closeimg"  @click="getClose"></image>
 		</view>
+		<voice-input  :voiceflag="voiceflag" @getVoice="getVoice" @getClose="getvoiceClose"></voice-input>
 	</view>
 </template>
 
@@ -85,8 +86,9 @@
 	import { getToken } from '@/utils/auth'
 	import {uploadmore} from '@/utils/common.js'
 	import {selectDictValue} from "@/utils/common.js"
+	import voiceInput from "@/service/components/popup/voice.vue"
 	export default{
-		components:{},
+		components:{voiceInput},
 		props:{
 			type: {
 				type: String,
@@ -191,6 +193,7 @@
 				text:'',//文本框
 				peoinfo:'',
 				phofile:[],
+				voiceflag:false,
 			}
 		},
 		mounted() {
@@ -217,6 +220,15 @@
 				console.log(data,list)
 				return selectDictValue(list, data);
 			},
+			getVoiceFn(){
+				this.voiceflag=true;
+			},
+			getvoiceClose(){
+				this.voiceflag=false;
+			},
+			getVoice(data){
+				this.text=data;
+			},
 			bindDateChange(){
 				var val=e.detail.value;
 			},

+ 476 - 0
property_ui/service/components/popup/voice.vue

@@ -0,0 +1,476 @@
+<template>
+	<view>
+		<view class="bgbox"  v-if="voiceflag"  @click="getClose"></view>
+		<view class="voice" v-if="voiceflag">
+			<image :src="closeimg" class="choseimg" @click="getClose"></image>
+			<view v-if="isShow">
+				<view v-if="sendLock" class="tip">
+					<view class="txt">试试这样说</view>
+					<view class="txt-bt">科技</view>
+				</view>
+				<view v-else>
+					<!-- @click="resultClick" -->
+					<view class="res-txt" >
+						<text :style="{
+							color: (resultText == '正在识别中2...' || resultText == '未检测到语音,请重试') ? 
+							'#919098' : 
+							'#2979ff'}">
+							{{resultText}}
+						</text>
+						<!-- <image v-if="resultText != '正在识别中.1..' && resultText != '未检测到语音,请重试'"
+							src="/static/img/xiaoshou.png" mode="widthFix"></image> -->
+					</view>
+				</view>
+			</view>
+		 
+			<view v-else class="tip">
+				<view v-if="!sendLock" class="tipbox">{{voicetext}}</view>
+				<view v-html="text" class="txt" style="color: #8e8d9a;"></view>
+				<view v-if="!sendLock" class="prompt-loader">
+					<view class="em" :style="randomRgb()" v-for="(item,index) in 30" :key="index"></view>
+				</view>
+				<view v-else class="prompt-loader"></view>
+			</view>
+		 
+			<view class="btn" @longpress="handleRecordStart" @touchmove="handleTouchMove" @touchend="handleRecordStop">
+				<view class="btn-cont">
+					长按开始语音搜索
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	const recorderManager = uni.getRecorderManager();
+	//播放录音
+	const innerAudioContext = uni.createInnerAudioContext();
+	innerAudioContext.autoplay = true;
+	export default{
+		props:{
+			voiceflag:{
+				type: Boolean,
+				default () {
+					return false
+				}
+			}
+		},
+		watch:{
+			sendLock(newVal, oldVal) {
+				var that=this;
+				recorderManager.onStop(res => {
+					if (newVal) return //上锁不发送
+					//解锁发送网络请求
+					setTimeout(function(res){
+						if(!that.voicetext&&!that.partialResult){
+							that.resultText='未检测到语音,请重试';
+							that.sendLock=true;
+						}else{
+							that.resultText=that.voicetext||that.partialResult;
+							// console.log(that.resultText,85)
+							// that.xmmc=that.xmmc+that.resultText;
+							setTimeout(function(){
+								that.$emit('getVoice',that.resultText)
+								// that.voiceflag=false;
+								that.sendLock=true;
+							},1200)
+						}
+						
+					},1000)
+					// console.log(res.tempFilePath, '获取录音文件')
+				});
+			},
+		},
+		data(){
+			return {
+				// speechimg:require('@/static/images/index/speech.png'),
+				xmmc:'',
+				
+				closeimg:require("@/work/static/close.png"),
+				// voiceflag:true,
+				voiceToken: '',
+				timer: null,
+				text: '',
+				resultText: '正在识别中...',
+				startPoint: {},
+				sendLock: true,
+				isShow: true,
+				adioFileData: '',
+				adioSize: '',
+				resContent: '',
+				luyinStatus: true,
+				voicetext:'',
+				options: {}, // 语音转文字的设置
+				partialResult:'',//临时语音
+			}
+		},
+		mounted() {
+			// if(!this.xmmc){
+			// 	this.xmmc=this.name
+			// }
+			var that=this;
+			// #ifdef APP-PLUS
+					// 监听语音识别事件
+					plus.speech.addEventListener('start', this.ontStart, false);
+					plus.speech.addEventListener('volumeChange', this.onVolumeChange, false);
+					plus.speech.addEventListener('recognizing', this.onRecognizing, false);
+					plus.speech.addEventListener('recognition', this.onRecognition, false);
+					plus.speech.addEventListener('end', this.onEnd, false);
+			// #endif
+			
+			recorderManager.onStop(function(res) {
+				//录音后的回调函数
+				// console.log('recorder stop' + JSON.stringify(res));
+				// console.log(res.tempFilePath);
+				that.voicePath = res.tempFilePath;
+				// self.voicePath = 
+			});
+		},
+		onUnload() {
+		},
+		unmounted() {
+		},
+		methods:{
+			getClose(){
+				// this.voiceflag=false;
+				this.sendLock=true;
+				this.$emit('getClose')
+			},
+			onRecognizing(e){
+				this.partialResult=this.partialResult+e.partialResult;
+			},
+			// 录音转文字
+			handleVoice() {
+				// console.log('语音输入')
+				let _this = this;
+				this.options.engine = 'baidu'
+				// this.options.timeout = 60 * 1000; //超时时间
+				this.options.continue = true;//语音识别是否采用持续模式
+				this.options.punctuation = false; // 是否需要标点符号 
+				this.options.userInterface = false; // 是否显示语音界面
+				plus.speech.startRecognize(this.options, (s) => {
+					console.log(s,1)
+					_this.voicetext+=s;
+					// plus.speech.stopRecognize(); // 关
+				});
+			},
+			//长按录音方法
+			handleRecordStart(e) {
+				this.voicetext='';
+				this.partialResult='';
+				this.startPoint = e.touches[0]; //记录长按时开始点信息,后面用于计算上划取消时手指滑动的距离。
+				recorderManager.start({duration: 60000}); //开始录音
+				this.handleVoice()
+				this.text = `<text style="color:#333">上划取消识别</text>`;
+				this.sendLock = false; //长按时不上锁。
+				this.isShow = false;
+				this.resultText = '正在识别中...';
+				// 按钮
+			},
+			//结束录音 (手指松开)时触发
+			handleRecordStop(e) {
+				var that=this;
+				this.isShow = true;
+				setTimeout(function(){
+					plus.speech.stopRecognize();
+					recorderManager.stop(); //结束录音
+				},1200)
+				
+			},
+			//上划取消搜索
+			handleTouchMove(e) {
+				let moveLenght = e.touches[e.touches.length - 1].clientY - this.startPoint.clientY;
+				if (Math.abs(moveLenght) > 50) {
+					this.text = `松开手指,<text style="color:#333">取消搜索</text>`;
+					this.sendLock = true; //触发了上滑取消搜索,上锁
+					this.isShow = false;
+				} else {
+					this.text = `<text style="color:#333">上划取消搜索</text>`;
+					this.sendLock = false; //上划距离不足,可以搜索,不上锁
+					this.isShow = false;
+				}
+			},
+			//获取录音结果子传父
+			resultClick() {
+				if (this.resultText == '正在识别中...' || this.resultText == '未检测到语音,请重试') return;
+				this.$emit('getVoice',this.resultText)
+				// this.voiceflag=false;
+				this.sendLock=true;
+				// this.$emit('voiceResult', this.resultText)
+			},
+			//弹窗关闭之后的操作,点击遮罩层或关闭按钮
+			// afterHide() {
+			// 	this.sendLock = true;
+			// 	this.$emit('closePopup');
+			// 	clearInterval(this.timer);
+			// 	this.resultText = '正在识别中...';
+			// },
+			randomRgb() {
+				let R = Math.floor(Math.random() * 130 + 110);
+				let G = Math.floor(Math.random() * 130 + 110);
+				let B = Math.floor(Math.random() * 130 + 110);
+				return {
+					background: `rgb(${R},${G},${B}, 1)`
+				};
+			},
+			// 语音转文字
+			getSeep(){
+				this.voicetext='';
+				this.partialResult='';
+				this.voiceflag=true;		
+			},
+			onEnd() {
+				// let routes = getCurrentPages(); // 获取当前打开过的页面路由数组
+				//   let curRoute = routes[routes.length - 1].route //获取当前页面路由
+				//   if(curRoute=='pages/work/shprogress'){
+					  
+				//   }else{
+				// 	  if(this.xmmc){
+				// 	  	this.getVoice()
+				// 	  }
+				//   }
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	// 搜索
+.listtopa{border: 6rpx solid #FD5001;border-radius: 32rpx;height:64rpx;box-sizing: border-box;padding:0 140rpx 0 32rpx ;position: relative;
+ input{}
+ image{width: 20rpx;height: 30rpx;margin-right: 16rpx;}
+ .btn{background: #FA5F03;border-radius: 32rpx;width: 120rpx;position: absolute;right: -2rpx;top: -2rpx;bottom:-2rpx;}
+ }
+ 
+ .bgbox{z-index: 1900;}
+ .choseimg{width: 34rpx;height: 34rpx;position: absolute;left: 36rpx;top: 54rpx;}
+ .voice {
+		min-height: 500rpx;
+ 		padding: 100rpx 60rpx 0 60rpx;
+ 		position: relative;
+ 		background-color: #fff;
+ 		position: fixed;
+ 		left:0;right:0;bottom:0;z-index: 2000;
+		padding-bottom: 180rpx;
+ 		.res-txt {
+ 			text-align: center;
+ 			margin-top: 40rpx;
+ 			font-size: 36rpx;
+ 			color: #919098;
+  
+ 			image {
+ 				display: block;
+ 				margin: auto;
+ 				margin-top: 10rpx;
+ 				width: 60rpx;
+ 				animation: bounce-down 2.6s linear infinite;
+ 			}
+ 		}
+  
+ 		.tip {
+ 			margin-top: 15rpx;
+ 			text-align: center;
+  
+ 			.txt {
+ 				font-size: 36rpx;
+ 				color: #151823;
+ 			}
+  
+ 			.txt-bt {
+ 				margin-top: 20rpx;
+ 				color: #919098;
+ 			}
+ 		}
+  
+ 		.btn {
+ 			width: 50%;
+ 			height: 80rpx;
+ 			display: flex;
+ 			align-items: center;
+ 			justify-content: center;
+ 			color: #fff;
+ 			border-radius: 50rpx;
+ 			background: #3484fd;
+ 			position: absolute;
+			bottom: 80rpx;
+ 			left: 50%;
+ 			transform: translateX(-50%);
+  
+ 			.btn-cont {
+ 				display: flex;
+ 				align-items: center;
+ 			}
+ 		}
+ 	}
+  
+ 	@-webkit-keyframes bounce-down {
+ 		25% {
+ 			-webkit-transform: translateY(-10px);
+ 		}
+  
+ 		50%,
+ 		100% {
+ 			-webkit-transform: translateY(0);
+ 		}
+  
+ 		75% {
+ 			-webkit-transform: translateY(13px);
+ 		}
+ 	}
+ 	.content{background-color: #f5f5f5;position: fixed;left: 0;right: 0;bottom: 0;z-index: 10000;height: 500rpx;}
+ 	/* 语音动画 */
+ 	.prompt-loader {
+ 		width: 100%;
+ 		height: 35px;
+ 		display: flex;
+ 		align-items: center;
+ 		justify-content: space-between;
+ 		margin: 30rpx auto;
+ 	}
+  
+ 	.prompt-loader .em {
+ 		height: 15%;
+ 		width: 2px;
+ 		float: left;
+ 		display: block;
+ 		background: #333333;
+ 	}
+  
+ 	.prompt-loader .em:last-child {
+ 		margin-right: 0px;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(1) {
+ 		animation: load 1.3s 0.4s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(2) {
+ 		animation: load 1.3s 0.2s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(3) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(4) {
+ 		animation: load 1.3s 0.8s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(5) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(6) {
+ 		animation: load 1.3s 0.4s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(7) {
+ 		animation: load 1.3s 0.2s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(8) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(9) {
+ 		animation: load 1.3s 0.2s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(10) {
+ 		animation: load 1.3s 0.4s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(11) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(12) {
+ 		animation: load 1.3s 0.8s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(13) {
+ 		animation: load 1.3s 1s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(14) {
+ 		animation: load 1.3s 0.2s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(15) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(16) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(17) {
+ 		animation: load 1.3s 0.8s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(18) {
+ 		animation: load 1.3s 0.2s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(19) {
+ 		animation: load 1.3s 0.4s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(20) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(21) {
+ 		animation: load 1.3s 0.5s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(22) {
+ 		animation: load 1.3s 0.2s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(23) {
+ 		animation: load 1.3s 0.4s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(24) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(25) {
+ 		animation: load 1.3s 0.8s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(26) {
+ 		animation: load 1.3s 0.2s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(27) {
+ 		animation: load 1.3s 0.4s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(28) {
+ 		animation: load 1.3s 0.1s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(29) {
+ 		animation: load 1.3s 0.3s infinite linear;
+ 	}
+  
+ 	.prompt-loader .em:nth-child(30) {
+ 		animation: load 1.3s 0.6s infinite linear;
+ 	}
+  
+ 	@keyframes load {
+ 		0% {
+ 			height: 15%;
+ 		}
+  
+ 		50% {
+ 			height: 100%;
+ 		}
+  
+ 		100% {
+ 			height: 15%;
+ 		}
+ 	}
+</style>

+ 15 - 3
property_ui/service/pages/service/patroladd.vue

@@ -34,7 +34,7 @@
 					</uni-forms-item>
 					<uni-easyinput maxlength='-1' type="textarea" autoHeight v-model="datainfo.checkResultDetails" :inputBorder='false' placeholder="可详细描述巡检结果…" />
 					<view class="voice mb20">
-						<view class="flexc">
+						<view class="flexc" @click="getVoiceFn">
 							<image :src="voice"></image>
 							语音输入
 						</view>
@@ -61,6 +61,7 @@
 		
 
 		<loading></loading>
+		<voice-input  :voiceflag="voiceflag" @getVoice="getVoice" @getClose="getvoiceClose"></voice-input>
 	</view>
 </template>
 
@@ -71,8 +72,9 @@
 	import {getDictionaryFn} from "@/api/system/user.js"
 	import {xgdjlDet,xgdjlPut} from "@/api/work/service.js"
 	import {selectDictValue,selectValueKey} from "@/utils/common.js"
+	import voiceInput from "@/service/components/popup/voice.vue"
 	export default {
-		components: {},
+		components: {voiceInput},
 		data() {
 			return {
 				voice: require('@/service/static/service/voice.png'),
@@ -100,7 +102,8 @@
 				xgjgidx:'',
 				xglxList:[],
 				xgjgList:[],
-				phofile:[]
+				phofile:[],
+				voiceflag:false,
 			}
 		},
 		onLoad: function(e) {
@@ -112,6 +115,15 @@
 		methods: {
 			checkPermi,
 			checkRole,
+			getVoiceFn(){
+				this.voiceflag=true;
+			},
+			getvoiceClose(){
+				this.voiceflag=false;
+			},
+			getVoice(data){
+				this.datainfo.checkResultDetails=data;
+			},
 			init(){
 				// 巡更类型
 				getDictionaryFn('patrol_type').then(res=>{

+ 1 - 1
property_ui/work/pages/manage/car.vue

@@ -40,7 +40,7 @@
 			})
 			this.houseId=e.id
 			this.init()
-			this.getrefreshData()
+			this.getDataFn()
 		},
 		// 上拉触底加载更多触发事件
 		onReachBottom() {