Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/H5/threeSide
| | |
| | | <template> |
| | | <u-popup :show="show" mode="bottom" :closeOnClickOverlay="false" @close="closePopup" zIndex="10071"> |
| | | <u-popup :show="show" mode="bottom" :closeOnClickOverlay="false" @close="closePopup" :zIndex="9999" :overlay="true" |
| | | :safeAreaInsetBottom="true"> |
| | | <view class="voice-popup"> |
| | | <view class="header"> |
| | | <text class="pl-30">语音输入</text> |
| | | <image src="/static/Appeal/close.png" class="w-34 h-34 mr-30" mode="" @click="closePopup"></image> |
| | | </view> |
| | | <view class="record-anim"> |
| | | <image src="/static/Appeal/step.png" class="w-153 h-119" mode="" @click="onPlay"></image> |
| | | <image src="/static/Appeal/step.png" class="w-153 h-119" mode=""></image> |
| | | </view> |
| | | <view class="timer">{{ time }}</view> |
| | | <view class="btns"> |
| | | <image src="/static/Appeal/start.png" class="w-153 h-153 mr-14" mode="" @click="onPlay"></image> |
| | | <image src="/static/Appeal/stop.png" class="w-153 h-153 mr-14" mode="" @click="onPause"></image> |
| | | <image :src="getRecordButtonSrc" class="w-153 h-153 mr-14" mode="" @click="handlerOnCahnger"></image> |
| | | <image src="/static/Appeal/cancel.png" class="w-153 h-153 mr-14" mode="" @click="onStop"></image> |
| | | </view> |
| | | </view> |
| | | <mumu-recorder ref="mumuRecorder" @success="handlerSuccess" @error="handleError"></mumu-recorder> |
| | | </u-popup> |
| | | </template> |
| | | |
| | | <script> |
| | | import { getSignature } from '../pages/index/service' |
| | | import MumuRecorder from '@/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue' |
| | | export default { |
| | | name: 'VoiceInputPopup', |
| | | components: { |
| | | MumuRecorder |
| | | }, |
| | | props: { |
| | | show: { |
| | | type: Boolean, |
| | |
| | | data() { |
| | | return { |
| | | time: '00:00:00', |
| | | barHeights: [20, 30, 40, 30, 20], // 可做动画 |
| | | isRecording: false, |
| | | isPaused: false, |
| | | tempFilePath: '', |
| | | timer: null, |
| | | seconds: 0, |
| | | minutes: 0, |
| | | hours: 0, |
| | | recordSegments: [], // 存储录音片段 |
| | | status: false, |
| | | recorder: null, |
| | | currentSegment: null // 当前录音片段 |
| | | } |
| | | }, |
| | | computed: { |
| | | getRecordButtonSrc() { |
| | | if (this.isPaused) { |
| | | return '/static/Appeal/start.png' |
| | | } |
| | | return this.isRecording ? '/static/Appeal/stop.png' : '/static/Appeal/start.png' |
| | | } |
| | | }, |
| | | mounted() { |
| | | }, |
| | | methods: { |
| | | closePopup() { |
| | | this.$emit('update:show', false) |
| | | handlerSave() { |
| | | let tag = document.createElement('a') |
| | | tag.href = this.recorder.localUrl |
| | | tag.download = '录音' |
| | | tag.click() |
| | | }, |
| | | onPlay() {}, |
| | | onPause() {}, |
| | | onStop() {}, |
| | | handlerOnCahnger() { |
| | | if (!this.isRecording && !this.isPaused) { |
| | | // 开始新的录音 |
| | | this.startNewRecording() |
| | | } else if (this.isRecording) { |
| | | // 暂停当前录音 |
| | | this.pauseRecording() |
| | | } else if (this.isPaused) { |
| | | // 继续录音 |
| | | this.resumeRecording() |
| | | } |
| | | }, |
| | | startNewRecording() { |
| | | console.log('开始新的录音') |
| | | this.isRecording = true |
| | | this.isPaused = false |
| | | this.startTimer() |
| | | this.$refs.mumuRecorder.start() |
| | | }, |
| | | pauseRecording() { |
| | | console.log('暂停录音') |
| | | this.isRecording = false |
| | | this.isPaused = true |
| | | this.stopTimer() |
| | | this.$refs.mumuRecorder.stop() |
| | | }, |
| | | resumeRecording() { |
| | | console.log('继续录音') |
| | | this.isRecording = true |
| | | this.isPaused = false |
| | | this.startTimer() |
| | | this.$refs.mumuRecorder.start() |
| | | }, |
| | | handlerSuccess(res) { |
| | | console.log('录音成功回调:', res) |
| | | this.recorder = res |
| | | // 保存当前录音片段 |
| | | if (res.localUrl) { |
| | | console.log('当前录音片段URL:', res.localUrl) |
| | | console.log('当前已存储的片段数:', this.recordSegments.length) |
| | | console.log('当前片段:', this.currentSegment) |
| | | |
| | | // 只有当当前片段与上一个不同时才添加 |
| | | if (!this.currentSegment || this.currentSegment.url !== res.localUrl) { |
| | | console.log('添加新的录音片段') |
| | | this.currentSegment = { |
| | | url: res.localUrl, |
| | | data: res.data, |
| | | duration: this.seconds + this.minutes * 60 + this.hours * 3600 |
| | | } |
| | | this.recordSegments.push(this.currentSegment) |
| | | console.log('更新后的片段列表:', this.recordSegments) |
| | | } else { |
| | | console.log('跳过重复的录音片段') |
| | | } |
| | | } |
| | | }, |
| | | handlerError(code) { |
| | | switch (code) { |
| | | case '101': |
| | | uni.showModal({ |
| | | content: '当前浏览器版本较低,请更换浏览器使用,推荐在微信中打开。' |
| | | }) |
| | | break; |
| | | case '201': |
| | | uni.showModal({ |
| | | content: '麦克风权限被拒绝,请刷新页面后授权麦克风权限。' |
| | | }) |
| | | break |
| | | default: |
| | | uni.showModal({ |
| | | content: '未知错误,请刷新页面重试' |
| | | }) |
| | | break |
| | | } |
| | | }, |
| | | closePopup() { |
| | | // 清空录音片段 |
| | | this.recordSegments = [] |
| | | this.currentSegment = null |
| | | // 重置计时器 |
| | | this.seconds = 0 |
| | | this.minutes = 0 |
| | | this.hours = 0 |
| | | this.updateTimeDisplay() |
| | | // 关闭弹窗 |
| | | this.$emit('close') |
| | | }, |
| | | startTimer() { |
| | | this.stopTimer(); |
| | | this.timer = setInterval(() => { |
| | | this.seconds++; |
| | | if (this.seconds >= 60) { |
| | | this.seconds = 0; |
| | | this.minutes++; |
| | | if (this.minutes >= 60) { |
| | | this.minutes = 0; |
| | | this.hours++; |
| | | } |
| | | } |
| | | this.updateTimeDisplay(); |
| | | }, 1000); |
| | | }, |
| | | stopTimer() { |
| | | if (this.timer) { |
| | | clearInterval(this.timer); |
| | | this.timer = null; |
| | | } |
| | | }, |
| | | updateTimeDisplay() { |
| | | this.time = `${String(this.hours).padStart(2, '0')}:${String(this.minutes).padStart(2, '0')}:${String(this.seconds).padStart(2, '0')}`; |
| | | }, |
| | | // 合并音频文件 |
| | | async mergeAudioFiles(audioUrls) { |
| | | try { |
| | | const audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| | | const audioBuffers = []; |
| | | |
| | | // 加载所有音频文件 |
| | | for (const url of audioUrls) { |
| | | const response = await fetch(url); |
| | | const arrayBuffer = await response.arrayBuffer(); |
| | | const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); |
| | | audioBuffers.push(audioBuffer); |
| | | } |
| | | |
| | | // 计算总时长 |
| | | const totalLength = audioBuffers.reduce((acc, buffer) => acc + buffer.length, 0); |
| | | |
| | | // 创建新的音频缓冲区 |
| | | const mergedBuffer = audioContext.createBuffer( |
| | | audioBuffers[0].numberOfChannels, |
| | | totalLength, |
| | | audioBuffers[0].sampleRate |
| | | ); |
| | | |
| | | // 合并音频数据 |
| | | let offset = 0; |
| | | for (const buffer of audioBuffers) { |
| | | for (let channel = 0; channel < buffer.numberOfChannels; channel++) { |
| | | const channelData = buffer.getChannelData(channel); |
| | | mergedBuffer.copyToChannel(channelData, channel, offset); |
| | | } |
| | | offset += buffer.length; |
| | | } |
| | | |
| | | // 将合并后的音频转换为 Blob |
| | | const wavBlob = await this.audioBufferToWav(mergedBuffer); |
| | | const mergedUrl = URL.createObjectURL(wavBlob); |
| | | |
| | | return mergedUrl; |
| | | } catch (error) { |
| | | console.error('合并音频失败:', error); |
| | | throw error; |
| | | } |
| | | }, |
| | | |
| | | // 将 AudioBuffer 转换为 WAV 格式 |
| | | audioBufferToWav(buffer) { |
| | | const numOfChan = buffer.numberOfChannels; |
| | | const length = buffer.length * numOfChan * 2; |
| | | const buffer2 = new ArrayBuffer(44 + length); |
| | | const view = new DataView(buffer2); |
| | | const channels = []; |
| | | let sample; |
| | | let offset = 0; |
| | | let pos = 0; |
| | | |
| | | // 写入 WAV 文件头 |
| | | setUint32(0x46464952); // "RIFF" |
| | | setUint32(36 + length); // 文件长度 |
| | | setUint32(0x45564157); // "WAVE" |
| | | setUint32(0x20746d66); // "fmt " chunk |
| | | setUint32(16); // 长度 = 16 |
| | | setUint16(1); // PCM (uncompressed) |
| | | setUint16(numOfChan); |
| | | setUint32(buffer.sampleRate); |
| | | setUint32(buffer.sampleRate * 2 * numOfChan); // avg. bytes/sec |
| | | setUint16(numOfChan * 2); // block-align |
| | | setUint16(16); // 16-bit |
| | | setUint32(0x61746164); // "data" - chunk |
| | | setUint32(length); // chunk length |
| | | |
| | | // 写入音频数据 |
| | | for (let i = 0; i < buffer.numberOfChannels; i++) { |
| | | channels.push(buffer.getChannelData(i)); |
| | | } |
| | | |
| | | while (pos < buffer.length) { |
| | | for (let i = 0; i < numOfChan; i++) { |
| | | sample = Math.max(-1, Math.min(1, channels[i][pos])); |
| | | sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; |
| | | view.setInt16(44 + offset, sample, true); |
| | | offset += 2; |
| | | } |
| | | pos++; |
| | | } |
| | | |
| | | return new Blob([buffer2], { type: 'audio/wav' }); |
| | | |
| | | function setUint16(data) { |
| | | view.setUint16(pos, data, true); |
| | | pos += 2; |
| | | } |
| | | |
| | | function setUint32(data) { |
| | | view.setUint32(pos, data, true); |
| | | pos += 4; |
| | | } |
| | | }, |
| | | |
| | | // 修改 onStop 方法 |
| | | async onStop() { |
| | | console.log('停止录音') |
| | | // 如果正在录音,先停止当前录音 |
| | | if (this.isRecording) { |
| | | this.$refs.mumuRecorder.stop() |
| | | } |
| | | |
| | | // 停止计时 |
| | | this.stopTimer() |
| | | |
| | | // 重置状态 |
| | | this.isRecording = false |
| | | this.isPaused = false |
| | | this.currentSegment = null |
| | | |
| | | // 处理录音片段 |
| | | if (this.recordSegments.length > 0) { |
| | | try { |
| | | uni.showLoading({ |
| | | title: '正在处理音频...' |
| | | }) |
| | | |
| | | // 获取所有录音片段的URL |
| | | const audioUrls = this.recordSegments.map(segment => segment.url) |
| | | console.log('准备合并的音频片段列表:', audioUrls) |
| | | console.log('音频片段数量:', audioUrls.length) |
| | | |
| | | // 合并音频文件 |
| | | const mergedUrl = await this.mergeAudioFiles(audioUrls) |
| | | |
| | | // 创建合并后的音频数据对象 |
| | | const mergedAudioData = { |
| | | url: this.recordSegments[this.recordSegments.length - 1].url, |
| | | data: this.recordSegments[this.recordSegments.length - 1].data, |
| | | duration: this.seconds + this.minutes * 60 + this.hours * 3600 |
| | | } |
| | | |
| | | uni.showToast({ |
| | | title: '录制成功', |
| | | icon: 'success', |
| | | duration: 2000 |
| | | }) |
| | | |
| | | setTimeout(() => { |
| | | this.$emit('submit', mergedAudioData) |
| | | }, 2000) |
| | | |
| | | uni.hideLoading() |
| | | this.closePopup() |
| | | } catch (error) { |
| | | console.error('音频合并失败:', error) |
| | | uni.hideLoading() |
| | | uni.showToast({ |
| | | title: '音频合并失败,请重试', |
| | | icon: 'none', |
| | | duration: 2000 |
| | | }) |
| | | } |
| | | } else { |
| | | uni.showToast({ |
| | | title: '未检测到录音文件', |
| | | icon: 'none', |
| | | duration: 2000 |
| | | }) |
| | | } |
| | | } |
| | | }, |
| | | beforeDestroy() { |
| | | this.stopTimer(); |
| | | } |
| | | } |
| | | </script> |
| | |
| | | background: #fff; |
| | | border-radius: 24rpx 24rpx 0 0; |
| | | padding: 40rpx 0 30rpx 0; |
| | | position: relative; |
| | | z-index: 9999; |
| | | |
| | | .header { |
| | | display: flex; |
| | | justify-content: center; |
| | |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | margin-bottom: 80rpx; |
| | | |
| | | text { |
| | | flex: 1; |
| | | text-align: center; |
| | | } |
| | | |
| | | .u-icon { |
| | | position: absolute; |
| | | right: 30rpx; |
| | | top: 0; |
| | | } |
| | | } |
| | | |
| | | .record-anim { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | margin-bottom: 57rpx; |
| | | |
| | | .bars { |
| | | display: flex; |
| | | align-items: flex-end; |
| | | height: 60rpx; |
| | | |
| | | .bar { |
| | | width: 10rpx; |
| | | margin: 0 4rpx; |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | .timer { |
| | | text-align: center; |
| | | font-size: 28rpx; |
| | | color: #888; |
| | | // margin-bottom: 30rpx; |
| | | } |
| | | |
| | | .btns { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 40rpx 67rpx 76rpx 67rpx; |
| | | // .u-button { |
| | | // width: 100rpx; |
| | | // height: 100rpx; |
| | | // border-radius: 50%; |
| | | // display: flex; |
| | | // justify-content: center; |
| | | // align-items: center; |
| | | // margin: 0 10rpx; |
| | | // } |
| | | padding: 40rpx 67rpx 6rpx 67rpx; |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | ::v-deep .uni-toast { |
| | | z-index: 10099 !important; |
| | | } |
| | | |
| | | .uni-sample-toast { |
| | | z-index: 10099 !important; |
| | | } |
| | | |
| | | ::v-deep .uni-toast .uni-sample-toast { |
| | | z-index: 10099 !important; |
| | | } |
| | | |
| | | /deep/ .u-transition.u-fade-enter-to.u-fade-enter-active { |
| | | z-index: 997 !important; |
| | | } |
| | | </style> |
| | |
| | | export default { |
| | | BASE_URL: 'https://huacheng.psciio.com', |
| | | // BASE_URL: 'http://192.168.110.188:6194', |
| | | // BASE_URL: 'https://huacheng.psciio.com', |
| | | BASE_URL: 'http://192.168.110.188:6194', |
| | | imageUrl: 'https://huacheng.psciio.com/api/huacheng-applets/common/uploadimages', |
| | | } |
| | |
| | | { |
| | | "name" : "“三个身边”群众工作机制", |
| | | "appid" : "__UNI__DB035F5", |
| | | "description" : "", |
| | | "versionName" : "1.0.0", |
| | | "versionCode" : "100", |
| | | "transformPx" : false, |
| | | /* 5+App特有相关 */ |
| | | "app-plus" : { |
| | | "usingComponents" : true, |
| | | "nvueStyleCompiler" : "uni-app", |
| | | "compilerVersion" : 3, |
| | | "splashscreen" : { |
| | | "alwaysShowBeforeRender" : true, |
| | | "waiting" : true, |
| | | "autoclose" : true, |
| | | "delay" : 0 |
| | | }, |
| | | /* 模块配置 */ |
| | | "modules" : {}, |
| | | /* 应用发布信息 */ |
| | | "distribute" : { |
| | | /* android打包配置 */ |
| | | "android" : { |
| | | "permissions" : [ |
| | | "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", |
| | | "<uses-permission android:name=\"android.permission.VIBRATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", |
| | | "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CAMERA\"/>", |
| | | "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", |
| | | "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
| | | "<uses-feature android:name=\"android.hardware.camera\"/>", |
| | | "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" |
| | | ] |
| | | }, |
| | | /* ios打包配置 */ |
| | | "ios" : {}, |
| | | /* SDK配置 */ |
| | | "sdkConfigs" : {} |
| | | } |
| | | }, |
| | | "sassImplementationName" : "node-sass", |
| | | /* 快应用特有相关 */ |
| | | "quickapp" : {}, |
| | | /* 小程序特有相关 */ |
| | | "mp-weixin" : { |
| | | "appid" : "", |
| | | "setting" : { |
| | | "urlCheck" : false |
| | | }, |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-alipay" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-baidu" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-toutiao" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "uniStatistics" : { |
| | | "enable" : false |
| | | }, |
| | | "vueVersion" : "2", |
| | | "h5" : { |
| | | "template" : "index.html", |
| | | "title" : "“三个身边”群众工作机制", |
| | | "optimization" : { |
| | | "treeShaking" : { |
| | | "enable" : false |
| | | } |
| | | }, |
| | | "router" : { |
| | | "base" : "./" |
| | | } |
| | | } |
| | | } |
| | | "name": "“三个身边”群众工作机制", |
| | | "appid": "__UNI__DB035F5", |
| | | "description": "", |
| | | "versionName": "1.0.0", |
| | | "versionCode": "100", |
| | | "transformPx": false, |
| | | /* 5+App特有相关 */ |
| | | "app-plus": { |
| | | "usingComponents": true, |
| | | "nvueStyleCompiler": "uni-app", |
| | | "compilerVersion": 3, |
| | | "splashscreen": { |
| | | "alwaysShowBeforeRender": true, |
| | | "waiting": true, |
| | | "autoclose": true, |
| | | "delay": 0 |
| | | }, |
| | | /* 模块配置 */ |
| | | "modules": {}, |
| | | /* 应用发布信息 */ |
| | | "distribute": { |
| | | /* android打包配置 */ |
| | | "android": { |
| | | "permissions": [ |
| | | "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", |
| | | "<uses-permission android:name=\"android.permission.VIBRATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", |
| | | "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CAMERA\"/>", |
| | | "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", |
| | | "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
| | | "<uses-feature android:name=\"android.hardware.camera\"/>", |
| | | "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" |
| | | ] |
| | | }, |
| | | /* ios打包配置 */ |
| | | "ios": {}, |
| | | /* SDK配置 */ |
| | | "sdkConfigs": {} |
| | | } |
| | | }, |
| | | "sassImplementationName": "node-sass", |
| | | /* 快应用特有相关 */ |
| | | "quickapp": {}, |
| | | /* 小程序特有相关 */ |
| | | "mp-weixin": { |
| | | "appid": "", |
| | | "setting": { |
| | | "urlCheck": false |
| | | }, |
| | | "usingComponents": true |
| | | }, |
| | | "mp-alipay": { |
| | | "usingComponents": true |
| | | }, |
| | | "mp-baidu": { |
| | | "usingComponents": true |
| | | }, |
| | | "mp-toutiao": { |
| | | "usingComponents": true |
| | | }, |
| | | "uniStatistics": { |
| | | "enable": false |
| | | }, |
| | | "vueVersion": "2", |
| | | "h5": { |
| | | "template": "index.html", |
| | | "title": "“三个身边”群众工作机制", |
| | | "optimization": { |
| | | "treeShaking": { |
| | | "enable": false |
| | | } |
| | | }, |
| | | "devServer": { |
| | | "https": true |
| | | }, |
| | | "router": { |
| | | "base": "./" |
| | | } |
| | | } |
| | | } |
| | |
| | | { |
| | | "dependencies": { |
| | | "echarts": "^5.6.0" |
| | | }, |
| | | "permission": { |
| | | "scope.record": { |
| | | "desc": "录音功能需要使用您的录音权限" |
| | | } |
| | | } |
| | | } |
| | |
| | | <view class="flex a-center j-between"> |
| | | <text class="w-108 fs-27 font-bold mr-85">问题描述</text> |
| | | <view class="flex a-center" @click="voiceInput"> |
| | | |
| | | <image src="/static/Appeal/yuyin.png" class="w-30 h-30 mr-11" mode=""></image> |
| | | <text class="fs-23 red">语音输入</text> |
| | | </view> |
| | | </view> |
| | |
| | | import config from '@/config/index.js' |
| | | import voiceInputPopup from '@/components/voiceInputPopup.vue' |
| | | import { |
| | | mapActions, |
| | | mapState |
| | | } from "vuex"; |
| | | mapActions, |
| | | mapState |
| | | } from "vuex"; |
| | | |
| | | export default { |
| | | components: { |
| | |
| | | detailedAddress: '', |
| | | descriptionTitle: '', |
| | | descriptionContent: '', |
| | | videoContent: [ |
| | | { url: 'xxx1', playing: false }, |
| | | { url: 'xxx2', playing: false } |
| | | ], |
| | | videoContent: [], |
| | | latitude: '', |
| | | longitude: '', |
| | | images: [], |
| | | videos: [], |
| | | voiceFile: '',//语音文件多个逗号拼接 |
| | | userInfo: uni.getStorageSync('userInfo'), //个人信息 |
| | | voiceInputShow: false, |
| | | }; |
| | |
| | | this.getproblem() |
| | | this.time = dayjs().format('YYYY-MM-DD') |
| | | }, |
| | | |
| | | |
| | | methods: { |
| | | ...mapActions(["playRecording", "pausePlaying"]), |
| | | onPlayRecording(index) { |
| | |
| | | this.voiceInputShow = false; |
| | | }, |
| | | submitVoiceInput(e) { |
| | | this.videoContent.push(e); |
| | | console.log('eeeeeeeeeeeeeeeeeee', e) |
| | | this.videoContent.push({ url: e.url, data: e.data, playing: false }); |
| | | this.voiceInputShow = false; |
| | | }, |
| | | previewImage(index) { |
| | |
| | | })] |
| | | })) |
| | | }, |
| | | submit(type) { |
| | | async submit(type) { |
| | | if (this.videoContent.length > 0) { |
| | | uni.showLoading({ |
| | | title: '正在上传语音文件...' |
| | | }); |
| | | |
| | | const uploadPromises = this.videoContent.map(item => { |
| | | return new Promise((resolve, reject) => { |
| | | console.log('item.data', item.data) |
| | | uni.uploadFile({ |
| | | url: config.imageUrl, |
| | | file: item.data, |
| | | name: 'file', |
| | | // fileType: 'audio/mpeg', |
| | | // filePath: item.url, |
| | | // name: 'file', |
| | | header: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | 'Authorization': uni.getStorageSync('token') |
| | | }, |
| | | success: (uploadFileRes) => { |
| | | const response = JSON.parse(uploadFileRes.data); |
| | | if (response.code === 200) { |
| | | resolve(response.data); |
| | | } else { |
| | | reject(new Error('上传失败')); |
| | | } |
| | | }, |
| | | fail: (error) => { |
| | | reject(error); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | try { |
| | | const uploadedUrls = await Promise.all(uploadPromises); |
| | | this.voiceFile = uploadedUrls.join(','); |
| | | console.log('this.voiceFile', this.voiceFile) |
| | | uni.hideLoading(); |
| | | } catch (error) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '语音文件上传失败', |
| | | icon: 'error' |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | const preciseRegex = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/; |
| | | if (!this.time) { |
| | | uni.showToast({ |
| | |
| | | }) |
| | | return |
| | | } |
| | | |
| | | const data = { |
| | | time: this.time, |
| | | problemType: this.problemType, |
| | |
| | | descriptionContent: this.descriptionContent, |
| | | images: this.images.join(','), |
| | | videos: this.videos.join(','), |
| | | voiceFile: this.voiceFile, |
| | | } |
| | | // 问题上报 |
| | | if (type == 1) { |
| | |
| | | success: (res) => { |
| | | |
| | | uni.showLoading() |
| | | console.log('res.tempFilePaths[0]', res.tempFilePaths[0]) |
| | | uni.uploadFile({ |
| | | url: config.imageUrl, |
| | | filePath: res.tempFilePaths[0], |
| | |
| | | timeout: 1000 * 45, |
| | | name: 'file', |
| | | header: { |
| | | Authorization: cuni.getStorageSync('token') |
| | | Authorization: uni.getStorageSync('token') |
| | | }, |
| | | success: (res) => { |
| | | if (JSON.parse(res.data).code == 200) { |
| | |
| | | </view> |
| | | </view> |
| | | <view @click.stop="submit" class="mt-38 fs-35 lh-96 br-48 txt-center font-bold bgcolor6 color4 fixed" |
| | | style="width: calc(100% - 62rpx);bottom: calc(env(safe-area-inset-bottom) + 10rpx);">确认添加</view> |
| | | style="width: calc(100% - 62rpx);bottom: calc(env(safe-area-inset-bottom) + 10rpx);">确认</view> |
| | | <view class="btn-box"></view> |
| | | <view class="safe-box"></view> |
| | | </view> |
| | |
| | | |
| | | <script> |
| | | import { |
| | | saveProcess, |
| | | editProgress, |
| | | getComplaintDetail |
| | | } from './service' |
| | | import config from '@/config/index.js' |
| | |
| | | video: [], |
| | | againCklicFlag: true, |
| | | rulsFlag: false, |
| | | progressId: '' |
| | | } |
| | | }, |
| | | onLoad(params) { |
| | | this.complaintId = params.id |
| | | this.describe = JSON.parse(params.data).describe |
| | | this.progressId = JSON.parse(params.data).id |
| | | this.video = JSON.parse(params.data).video.split(',') |
| | | this.imgUrls = JSON.parse(params.data).imgUrl.split(',') |
| | | this.localImageUrls = JSON.parse(params.data).imgUrl.split(',') |
| | | getComplaintDetail({ |
| | | id: params.id |
| | | }).then(res => { |
| | |
| | | }) |
| | | } |
| | | let obj = { |
| | | id: this.progressId, |
| | | complaintId: this.complaintId, |
| | | describe: this.describe, |
| | | imgUrl: this.imgUrls.join(','), |
| | | video: this.video.join(',') |
| | | } |
| | | saveProcess(obj).then(res => { |
| | | editProgress(obj).then(res => { |
| | | if (res.code == 200) { |
| | | uni.showToast({ |
| | | title: '提交成功', |
| | |
| | | import request from '@/utils/request.js' |
| | | |
| | | // 工单列表 |
| | | export const getList = (params) => { |
| | | return request.post(`/api/huacheng-sangeshenbian/applet/complaint/list`, params) |
| | | // 处理记录 |
| | | export const getProcessingList = (data) => { |
| | | return request.post(`/api/huacheng-sangeshenbian/applet/supervise/processing-record-page`, data) |
| | | } |
| | | // 问题驳回池 |
| | | export const getRejectList = (data) => { |
| | | return request.post(`/api/huacheng-sangeshenbian/applet/supervise/reject-record-page`, data) |
| | | } |
| | | |
| | | // 工单详情 |
| | | // 详情 |
| | | export const getComplaintDetail = (params) => { |
| | | return request.get('/api/huacheng-sangeshenbian/applet/complaint/detail', params) |
| | | } |
| | | |
| | | // 编辑办理进度 |
| | | export const editProgress = (params) => { |
| | | return request.put('/api/huacheng-sangeshenbian/applet/complaint-progress/edit', params) |
| | | } |
| | | |
| | | // 删除办理进度 |
| | | export const delProgress = (id) => { |
| | | return request.delete(`/api/huacheng-sangeshenbian/applet/complaint-progress/${id}`) |
| | | } |
| | |
| | | <script> |
| | | import dayjs from '../../uni_modules/uview-ui/libs/util/dayjs' |
| | | import { |
| | | getComplaintDetail |
| | | getComplaintDetail, |
| | | delProgress |
| | | } from './service' |
| | | export default { |
| | | data() { |
| | |
| | | getComplaintDetail({ |
| | | id: this.id |
| | | }).then(res => { |
| | | |
| | | this.info = { |
| | | ...res.data |
| | | } |
| | |
| | | }, |
| | | methods: { |
| | | deleteProgress() { |
| | | |
| | | delProgress(this.row.id).then(res => { |
| | | uni.showToast({ |
| | | icon: 'none', |
| | | mask: true, |
| | | title: '删除成功' |
| | | }) |
| | | this.$refs.customPopup.show = false |
| | | setTimeout(() => { |
| | | getComplaintDetail({ |
| | | id: this.id |
| | | }).then(res => { |
| | | this.info = { |
| | | ...res.data |
| | | } |
| | | }) |
| | | }, 1500) |
| | | }) |
| | | }, |
| | | toDelete(item) { |
| | | this.row = item |
| | |
| | | }, |
| | | toEdit(item) { |
| | | uni.navigateTo({ |
| | | url: `/pages/supervision/edit-supervision-progress?id=${this.id}` |
| | | url: `/pages/supervision/edit-supervision-progress?id=${this.id}&data=${JSON.stringify(item)}` |
| | | }) |
| | | }, |
| | | callPhone(phoneNumber) { |
| | |
| | | <view class="content"> |
| | | <view class="bgColor2 shadow1 pb-19" style="position: sticky;top: 0;z-index: 999;"> |
| | | <view class="h-96 flex a-center fs-27 j-between txt-center font-w400 color1"> |
| | | <view @click="changeType('1')" class="flex1 bgColor2 relative" |
| | | :class="searchParams.type == '1' && 'color2 font-bold'"> |
| | | <view @click="changeType(1)" class="flex1 bgColor2 relative" :class="type == 1 && 'color2 font-bold'"> |
| | | <view class="relative zIndex1000"> |
| | | 处理记录 |
| | | </view> |
| | | <view v-if="searchParams.type == '1'" class="bgColor1"></view> |
| | | <view v-if="type == 1" class="bgColor1"></view> |
| | | </view> |
| | | <view @click="changeType('2')" class="flex1 bgColor2 relative" |
| | | :class="searchParams.type == '2' && 'color2 font-bold'"> |
| | | <view @click="changeType(2)" class="flex1 bgColor2 relative" :class="type == 2 && 'color2 font-bold'"> |
| | | <view class="relative zIndex1000"> |
| | | 问题驳回池 |
| | | </view> |
| | | <view v-if="searchParams.type == '2'" class="bgColor1"></view> |
| | | <view v-if="type == 2" class="bgColor1"></view> |
| | | </view> |
| | | </view> |
| | | <view class="flex a-center j-between mx-27 pl-38 border2"> |
| | | <view class="flex a-center flex1"> |
| | | <image src="../../static/search.png" mode="widthFix" class="w-31 h-31 shrink0 mr-13" /> |
| | | <input v-model="searchParams.searchStr" class="fs-27 flex1" placeholder="输入姓名、联系电话关键字搜索" /> |
| | | <input v-model="searchParams.keyword" class="fs-27 flex1" placeholder="输入姓名、联系电话关键字搜索" /> |
| | | </view> |
| | | <view @click="searchList" class="fs-23 lh-69 txt-center px-29 br-48 my-4 mx-4 bgcolor88">搜索</view> |
| | | </view> |
| | |
| | | <view class="mt-31 border1"> |
| | | <view class="left"></view> |
| | | <view class="right"></view> |
| | | <view v-if="searchParams.type == '1'"> |
| | | <view v-if="type == 1"> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>添加办理进度数</view> |
| | | <view class="font-bold">10</view> |
| | | <view class="font-bold">{{item.progressCount}}</view> |
| | | </view> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>已下派次数</view> |
| | | <view class="font-bold">10</view> |
| | | <view class="font-bold">{{item.assignmentCount}}</view> |
| | | </view> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>已上报次数</view> |
| | | <view class="font-bold">10</view> |
| | | <view class="font-bold">{{item.reportCount}}</view> |
| | | </view> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>创建时间</view> |
| | |
| | | <view v-else> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>申请时间</view> |
| | | <view class="font-bold">2025-09-09 11:09:09</view> |
| | | <view class="font-bold">{{ item.applyTime | formatTime }}</view> |
| | | </view> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>申请人</view> |
| | | <view class="flex a-center"> |
| | | <view class="font-bold">李雷 13987654321</view> |
| | | <image @click.stop="callPhone(item.contactNumber)" src="../../static/tell.png" |
| | | <view class="font-bold">{{item.reporter}} {{item.reporterPhone}}</view> |
| | | <image @click.stop="callPhone(item.reporterPhone)" src="../../static/tell.png" |
| | | class="w-58 h-58 shrink0 ml-19" /> |
| | | </view> |
| | | </view> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>审批时间</view> |
| | | <view class="font-bold">2025-09-09 11:09:09</view> |
| | | <view class="font-bold">{{ item.auditTime | formatTime }}</view> |
| | | </view> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view>审批人</view> |
| | | <view class="flex a-center"> |
| | | <view class="font-bold">李雷 13987654321</view> |
| | | <image @click.stop="callPhone(item.contactNumber)" src="../../static/tell.png" |
| | | <view class="font-bold">{{item.auditorName}} {{item.auditorPhone}}</view> |
| | | <image @click.stop="callPhone(item.auditorPhone)" src="../../static/tell.png" |
| | | class="w-58 h-58 shrink0 ml-19" /> |
| | | </view> |
| | | </view> |
| | | <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view class="flex j-between lh-38 mt-29 ml-38 mr-23 fs-27"> |
| | | <view class="shrink0">驳回理由</view> |
| | | <view style="max-width: 458rpx; text-align: right;font-weight: bold;">不同意当前申请不同意当前申请不同意当前申请 |
| | | <view style="max-width: 458rpx; text-align: right;font-weight: bold;"> |
| | | {{item.rejectReason}} |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | <script> |
| | | import dayjs from '@/uni_modules/uview-ui/libs/util/dayjs.js' |
| | | import { |
| | | getList |
| | | getProcessingList, |
| | | getRejectList |
| | | } from './service.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | active: 1, |
| | | type: 1, |
| | | searchParams: { |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | type: '1', |
| | | searchStr: '' |
| | | keyword: '' |
| | | }, |
| | | list: [], |
| | | status: 'loadMore', |
| | |
| | | '6': '上级驳回', |
| | | '7': '延期待审核', |
| | | '8': '已办结' |
| | | } |
| | | }, |
| | | } |
| | | }, |
| | | onReachBottom() { |
| | |
| | | }, |
| | | methods: { |
| | | searchList() { |
| | | if (this.searchParams.keyword == '') { |
| | | uni.showToast({ |
| | | title: '请输入关键字搜索', |
| | | icon: 'none', |
| | | mask: true |
| | | }) |
| | | return |
| | | } |
| | | this.searchParams.pageNum = 1 |
| | | this.fetchList(this.searchParams, (e) => { |
| | | this.list = e.records || [] |
| | |
| | | }, |
| | | fetchList(params, callback) { |
| | | if (this.status == 'loading') return |
| | | this.status = 'loading' |
| | | getList(params).then(res => { |
| | | this.status = 'loading'; |
| | | [getProcessingList, getRejectList][this.type - 1](params).then(res => { |
| | | if (res.code == 200) { |
| | | res.data.records.map(item => { |
| | | if (item.images) { |
| | | item.images = item.images.split(',') |
| | | } |
| | | }) |
| | | callback(res.data) |
| | | } |
| | | }) |
| | | }, |
| | | // 切换状态筛选 |
| | | changeType(type) { |
| | | this.type = type |
| | | this.searchParams = { |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | type, //全部:不传,上报待审核:0,正在办理:1, 办结:2 |
| | | } |
| | | this.fetchList(this.searchParams, (e) => { |
| | | this.list = e.records || [] |
| | |
| | | <!-- 问题描述 --> |
| | | <view class="problem"> |
| | | <view class="title">问题描述</view> |
| | | <view v-for="(item,index) in 2" :key="index" |
| | | <view v-for="(item,index) in getVoiceFile(orderInfo.voiceFile)" :key="index" |
| | | class="flex a-center j-between py-17 px-19 br-8 bgcolor1 mb-19"> |
| | | <view class="fs-27 lh-38">语音名字</view> |
| | | <view class="fs-27 lh-38">语音名字{{(index + 1) | numToWords}}</view> |
| | | <image v-if="!playFlag" @click.stop="playRecording(item)" src="../../static/24gf-playCircle@2x.png" |
| | | class="w-27 h-27 shrink0" /> |
| | | <image v-else @click.stop="pausePlaying" src="../../static/pausePlaying.png" |
| | |
| | | formatTime(val) { |
| | | if (!val) return '' |
| | | return dayjs(val).format('YYYY-MM-DD HH:mm:ss') |
| | | }, |
| | | numToWords(val) { |
| | | const words = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] |
| | | // 处理 0-10 |
| | | if (val >= 0 && val <= 10) return words[val]; |
| | | // 处理 11-99 |
| | | if (val > 10 && val < 100) { |
| | | const ten = Math.floor(val / 10); |
| | | const unit = val % 10; |
| | | return `${ten > 1 ? words[ten] : ''}十${unit > 0 ? words[unit] : ''}`; |
| | | } |
| | | return val; // 超过99返回原数字 |
| | | } |
| | | }, |
| | | onShow() { |
| | |
| | | }, |
| | | methods: { |
| | | ...mapActions(["playRecording", "pausePlaying"]), |
| | | getVoiceFile(voiceFile) { |
| | | if (!voiceFile || voiceFile.length == 0) return [] |
| | | return voiceFile.split(',') |
| | | }, |
| | | callPhone(phoneNumber) { |
| | | uni.makePhoneCall({ |
| | | phoneNumber |
New file |
| | |
| | | ## 1.0.1(2022-06-11) |
| | | 修复苹果手机在微信中无法获取音频长度问题 |
| | | ## 1.0.0(2022-06-10) |
| | | 版本上线 |
New file |
| | |
| | | <template> |
| | | <view class="recorder"> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | data() { |
| | | return { |
| | | isUserMedia: false, |
| | | stream: null, |
| | | audio: null, |
| | | recorder: null, |
| | | chunks: [], |
| | | startTime: 0 |
| | | } |
| | | }, |
| | | mounted() { |
| | | /** |
| | | * error 事件的返回状态 |
| | | * 100: 请在HTTPS环境中使用 |
| | | * 101: 浏览器不支持 |
| | | * 201: 用户拒绝授权 |
| | | * 500: 未知错误 |
| | | * */ |
| | | if (origin.indexOf('https') === -1) { |
| | | this.$emit('error', '100') |
| | | throw '请在 https 环境中使用本插件。' |
| | | } |
| | | if (!navigator.mediaDevices || !window.MediaRecorder) { |
| | | this.$emit('error', '101') |
| | | throw '当前浏览器不支持' |
| | | } |
| | | |
| | | this.getRecorderManager() |
| | | }, |
| | | methods: { |
| | | getRecorderManager() { |
| | | this.audio = document.createElement('audio') |
| | | navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { |
| | | this.isUserMedia = true |
| | | stream.getTracks().forEach((track) => { |
| | | track.stop() |
| | | }) |
| | | }).catch(err => { |
| | | this.onErrorHandler(err) |
| | | }) |
| | | }, |
| | | start() { |
| | | if (!this.isUserMedia) return console.log('设备不支持') |
| | | |
| | | navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { |
| | | this.startTime = new Date().getTime() |
| | | this.stream = stream |
| | | this.recorder = new MediaRecorder(stream) |
| | | this.recorder.ondataavailable = this.getRecordingData |
| | | this.recorder.onstop = this.saveRecordingData |
| | | this.recorder.start() |
| | | }).catch(err => { |
| | | this.onErrorHandler(err) |
| | | }) |
| | | }, |
| | | stop() { |
| | | this.recorder.stop() |
| | | this.stream.getTracks().forEach((track) => { |
| | | track.stop() |
| | | }) |
| | | }, |
| | | getRecordingData(e) { |
| | | this.chunks.push(e.data) |
| | | }, |
| | | saveRecordingData() { |
| | | const blob = new Blob(this.chunks, { 'type': 'audio/mpeg' }), |
| | | localUrl = URL.createObjectURL(blob) |
| | | |
| | | const endTime = new Date().getTime() |
| | | |
| | | let duration = (endTime - this.startTime).toString().split('') |
| | | duration.splice(duration.length - 2) |
| | | duration.splice(duration.length - 1, 0, '.') |
| | | duration = parseFloat(duration.join('')) |
| | | |
| | | const recorder = { |
| | | data: blob, |
| | | duration: duration, |
| | | localUrl: localUrl |
| | | } |
| | | this.$emit('success', recorder) |
| | | }, |
| | | onErrorHandler(err) { |
| | | console.log(err) |
| | | if (err.name === 'NotAllowedError') { |
| | | this.$emit('error', '201') |
| | | throw '用户拒绝了当前的浏览器实例的访问请求' |
| | | } |
| | | |
| | | if (err.name === 'NotReadableError') { |
| | | this.$emit('error', '101') |
| | | throw '当前浏览器不支持' |
| | | } |
| | | |
| | | this.$emit('error', '500') |
| | | throw '调用失败,原因不详' |
| | | |
| | | } |
| | | }, |
| | | destroyed() { |
| | | this.stop() |
| | | } |
| | | } |
| | | </script> |
| | | <style> |
| | | </style> |
New file |
| | |
| | | { |
| | | "id": "mumu-recorder", |
| | | "displayName": "h5录音组件,调用H5原生功能使用麦克风进行录音", |
| | | "version": "1.0.1", |
| | | "description": "演示案例中模仿了微信的长按发送语音,与普通录音demo。", |
| | | "keywords": [ |
| | | "录音", |
| | | "麦克风", |
| | | "模仿微信" |
| | | ], |
| | | "repository": "", |
| | | "engines": { |
| | | "HBuilderX": "^3.1.0" |
| | | }, |
| | | "dcloudext": { |
| | | "category": [ |
| | | "前端组件", |
| | | "通用组件" |
| | | ], |
| | | "sale": { |
| | | "regular": { |
| | | "price": "0.00" |
| | | }, |
| | | "sourcecode": { |
| | | "price": "0.00" |
| | | } |
| | | }, |
| | | "contact": { |
| | | "qq": "" |
| | | }, |
| | | "declaration": { |
| | | "ads": "无", |
| | | "data": "无", |
| | | "permissions": "麦克风" |
| | | }, |
| | | "npmurl": "" |
| | | }, |
| | | "uni_modules": { |
| | | "dependencies": [], |
| | | "encrypt": [], |
| | | "platforms": { |
| | | "cloud": { |
| | | "tcb": "y", |
| | | "aliyun": "y" |
| | | }, |
| | | "client": { |
| | | "Vue": { |
| | | "vue2": "y", |
| | | "vue3": "y" |
| | | }, |
| | | "App": { |
| | | "app-vue": "n", |
| | | "app-nvue": "n" |
| | | }, |
| | | "H5-mobile": { |
| | | "Safari": "y", |
| | | "Android Browser": "y", |
| | | "微信浏览器(Android)": "y", |
| | | "QQ浏览器(Android)": "y" |
| | | }, |
| | | "H5-pc": { |
| | | "Chrome": "y", |
| | | "IE": "n", |
| | | "Edge": "y", |
| | | "Firefox": "y", |
| | | "Safari": "y" |
| | | }, |
| | | "小程序": { |
| | | "微信": "u", |
| | | "阿里": "u", |
| | | "百度": "u", |
| | | "字节跳动": "u", |
| | | "QQ": "u", |
| | | "钉钉": "u", |
| | | "快手": "u", |
| | | "飞书": "u", |
| | | "京东": "u", |
| | | "小红书": "u" |
| | | }, |
| | | "快应用": { |
| | | "华为": "u", |
| | | "联盟": "u" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | ## 插件简绍 |
| | | |
| | | ### 实现原理 |
| | | |
| | | > 通过 navigator.mediaDevices.getUserMedia(需要https环境) 这个api调用麦克风,获取到到音频流数据。 |
| | | > |
| | | > 通过 MediaRecorder 这个构造函数对音频流进行接收,完成录制后会返回一个存储`Blob`内容的录制数据。 |
| | | |
| | | |
| | | ### 使用环境 |
| | | |
| | | 需要https环境才能使用,本地测试可以在 manifest.json 中点击源码展示,找到h5 ,添加:"devServer" : { "https" : true} |
| | | |
| | | **请勿使用 UC浏览器 与 夸克等阿里旗下的浏览器,发现他们使用的内核都较低,无法正常获取音频流,并且都有对接音频流截取的插件,导致无法正常获取音频流的数据。在微信中可以正常使用,推荐在微信内打开演示案例 ** |
| | | |
| | | 需要https环境才能使用!!! |
| | | |
| | | 需要https环境才能使用!!! |
| | | |
| | | 需要https环境才能使用!!! |
| | | |
| | | ### 插件使用 |
| | | |
| | | **插件已支持 uni_modules 支持组件easycom,以下代码演示的是普通使用** |
| | | |
| | | ``` html |
| | | <!-- HTML --> |
| | | <view> |
| | | <audio :src='recorder.localUrl' v-if='recorder' name='本地录音' controls="true"></audio> |
| | | <view @click='handlerOnCahnger'> |
| | | {{!status?'开始录音':'结束录音'}} |
| | | </view> |
| | | <mumu-recorder ref='recorder' @success='handlerSuccess' @error='handlerError'></mumu-recorder> |
| | | </view> |
| | | ``` |
| | | |
| | | ``` javascript |
| | | // js |
| | | import MumuRecorder from '@/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue' |
| | | export default { |
| | | components: { MumuRecorder }, |
| | | data() { |
| | | return { |
| | | status: false, |
| | | recorder: null |
| | | } |
| | | }, |
| | | onLoad() { |
| | | |
| | | }, |
| | | methods: { |
| | | handlerSave() { |
| | | let tag = document.createElement('a') |
| | | tag.href = this.recorder.localUrl |
| | | tag.download = '录音' |
| | | tag.click() |
| | | }, |
| | | handlerOnCahnger() { |
| | | if (this.status) { |
| | | this.$refs.recorder.stop() |
| | | } else { |
| | | this.$refs.recorder.start() |
| | | } |
| | | this.status = !this.status |
| | | }, |
| | | handlerSuccess(res) { |
| | | console.log(res) |
| | | this.recorder = res |
| | | }, |
| | | handlerError(code) { |
| | | switch (code) { |
| | | case '101': |
| | | uni.showModal({ |
| | | content: '当前浏览器版本较低,请更换浏览器使用,推荐在微信中打开。' |
| | | }) |
| | | break; |
| | | case '201': |
| | | uni.showModal({ |
| | | content: '麦克风权限被拒绝,请刷新页面后授权麦克风权限。' |
| | | }) |
| | | break |
| | | default: |
| | | uni.showModal({ |
| | | content: '未知错误,请刷新页面重试' |
| | | }) |
| | | break |
| | | } |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 相关API |
| | | |
| | | ##### 组件内部方法($refs 调用) |
| | | |
| | | | 方法名 | 说明 | 参数 | |
| | | | ------ | -------- | ---- | |
| | | | start | 开始录音 | 无 | |
| | | | stop | 结束录音 | 无 | |
| | | |
| | | |
| | | |
| | | ##### 事件(Events) |
| | | |
| | | | 事件名 | 说明 | 回调参数 | |
| | | | ------- | -------------------- | ------------------------------------------------------------ | |
| | | | success | 停止录音后调用此事件 | 返回录音数据,是一个对象<br />{ data: 音频的 blob 数据,上传请使用这个 <br />duration: 当前音频长度<br/>localUrl: 当前音频的本地链接,可直接通过 audio 标签进行播放 } | |
| | | | error | 组件内部发生错误 | 错误码:<100 当前不是https环境> <101 浏览器不支持> <201 麦克风权限被拒绝> <500 未知错误> | |
| | | |
| | | ### 案例演示 |
| | | |
| | |  |
| | | |
| | | ## 支持作者 |
| | | |
| | |  |
| | |
| | | |
| | | return new Promise(function(resolve, reject) { |
| | | let token = uni.getStorageSync('token') |
| | | // token = |
| | | // 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjE2MjA0LCJ0eXBlIjoxLCJleHAiOjE3NDM1NTY3NzYsImNyZWF0ZWQiOjE3NDIyNjA3NzY2NTR9.SAmdlprtz_Z1cNSNB4ANYsKJFC7E7Jfxo6XbT9vpbO6zHBpTRfq_oyp9UlDQtHNiYgv-MuyqCBPw1-x88C_-8A' |
| | | // uni.setStorageSync('token', token) |
| | | token = |
| | | 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjE4OTU1MjI1Nzk1MDcwODEyMTgsInR5cGUiOjEsImV4cCI6MTc0Nzk4MzIyMiwiY3JlYXRlZCI6MTc0NjY4NzIyMjAxMX0.pmUfTkxkbBirDMbnMR1IaLsbSiiwHc366_yyAetCzTOWYxTNgmQlmvw26_W62NHLOebB_ZAEgZsPvkJcaLOoPg' |
| | | uni.setStorageSync('token', token) |
| | | let header = { |
| | | 'content-type': type ? 'application/x-www-form-urlencoded;charset=UTF-8' : 'application/json', |
| | | 'Authorization': token, |
| | |
| | | export default { |
| | | dev: { |
| | | SERVER_URL: 'http://192.168.110.111:6194', |
| | | SERVER_URL: 'http://192.168.110.106:6194', |
| | | // SERVER_URL: 'https://huacheng.psciio.com', |
| | | }, |
| | | test: { |
| | |
| | | import { getList } from './service'; |
| | | |
| | | const Account = () => { |
| | | const actionRef = useRef(); |
| | | const access = useAccess(); |
| | | const formRef = useRef(); |
| | | |
| | | const columns = [ |
| | | { |
| | | title: '述求号', |
| | | dataIndex: 'reportUserName', |
| | | dataIndex: 'serialNumber', |
| | | order: 8, |
| | | }, |
| | | { |
| | |
| | | }, |
| | | { |
| | | title: '申请人', |
| | | dataIndex: 'name', |
| | | dataIndex: 'applyUserName', |
| | | order: 5, |
| | | }, |
| | | { |
| | | title: '申请时间', |
| | | dataIndex: 'name', |
| | | dataIndex: 'applyTime', |
| | | hideInTable: true, |
| | | valueType: 'dateRange', |
| | | order: 3, |
| | | }, |
| | | { |
| | | title: '审批时间', |
| | | dataIndex: 'name', |
| | | dataIndex: 'examineTime', |
| | | valueType: 'dateRange', |
| | | order: 2, |
| | | }, |
| | | { |
| | | title: '审批人', |
| | | dataIndex: 'name', |
| | | dataIndex: 'examineUserName', |
| | | order: 4, |
| | | }, |
| | | { |
| | | title: '驳回理由', |
| | | dataIndex: 'contactNumber', |
| | | dataIndex: 'remark', |
| | | hideInSearch: true, |
| | | }, |
| | | { |
| | |
| | | 1: '延期办理', |
| | | 2: '超时办理', |
| | | 3: '已办结', |
| | | 4: '上报待审核', |
| | | 4: '群众撤销', |
| | | 5: '上报待审核', |
| | | 6: '上级驳回', |
| | | 7: '延期待审核', |
| | | 8: '已评价', |
| | | 9: '延期驳回', |
| | | }, |
| | | render: (text, record) => { |
| | | return Number(record.status) == 0 ? '正在办理' : record.status == 1 ? '延期办理' : record.status == 2 ? '超时办理' : record.status == 3 ? '已办结' : record.status == 4 ? '上报待审核' : '已办结'; |
| | | } |
| | | }, |
| | | { |
| | | title: '操作', |
| | |
| | | > |
| | | <ProTable |
| | | rowKey="id" |
| | | actionRef={actionRef} |
| | | columns={columns} |
| | | formRef={formRef} |
| | | request={async (params) => { |
| | | |
| | | if (params.time && params.time.length > 0) { |
| | | params.startTime = moment(params.time[0]).format('YYYY-MM-DD HH:mm:ss'); |
| | | params.endTime = moment(params.time[1]).format('YYYY-MM-DD 23:59:59'); |
| | | delete params.time |
| | | } else { |
| | | delete params.startTime |
| | | delete params.endTime |
| | | if (params.applyTime && params.applyTime.length > 0) { |
| | | params.applyTime = moment(params.applyTime[0]).format('YYYY-MM-DD') + |
| | | ' - ' + moment(params.applyTime[1]).format('YYYY-MM-DD') |
| | | } |
| | | |
| | | if (params.examineTime && params.examineTime.length > 0) { |
| | | params.examineTime = moment(params.examineTime[0]).format('YYYY-MM-DD') + |
| | | ' - ' + moment(params.examineTime[1]).format('YYYY-MM-DD') |
| | | } |
| | | |
| | | return buildProTableDataSource(getList, params); |
| | | }} |
| | |
| | | import { request } from '@umijs/max'; |
| | | |
| | | // 获取诉求列表 |
| | | export const getList = async (data) => { |
| | | return request(`/api/huacheng-sangeshenbian/complaint/page`, { |
| | | method: 'POST', |
| | | data |
| | | }); |
| | | } |
| | | |
| | | // 获取述求详情 |
| | | export const getDetail = async (data) => { |
| | | return request(`/api/huacheng-sangeshenbian/complaint/detail/${data.id}`, { |
| | | export const getList = async (params) => { |
| | | return request(`/api/huacheng-sangeshenbian/complaint-reject/list`, { |
| | | method: 'GET', |
| | | data |
| | | params |
| | | }); |
| | | } |
| | |
| | | const columns = [ |
| | | { |
| | | title: '操作时间', |
| | | dataIndex: 'name', |
| | | dataIndex: 'createTime', |
| | | hideInTable: true, |
| | | valueType: 'dateRange', |
| | | order: 1, |
| | | }, |
| | | { |
| | | title: '操作用户', |
| | | dataIndex: 'reportUserName', |
| | | dataIndex: 'operatorName', |
| | | order: 5, |
| | | }, |
| | | { |
| | | title: '联系电话', |
| | | dataIndex: 'reportUserName', |
| | | dataIndex: 'operatorPhone', |
| | | order: 4, |
| | | }, |
| | | { |
| | | title: '操作类型', |
| | | dataIndex: 'reportUserPhone', |
| | | dataIndex: 'operatorCategory', |
| | | order: 3, |
| | | valueEnum: { |
| | | 1: '登录', |
| | |
| | | }, |
| | | { |
| | | title: '对象名称', |
| | | dataIndex: 'name', |
| | | dataIndex: 'targetName', |
| | | hideInSearch: true, |
| | | }, |
| | | { |
| | | title: '所在IP', |
| | | dataIndex: 'name', |
| | | dataIndex: 'ip', |
| | | order: 2, |
| | | }, |
| | | ]; |
| | | return ( |
| | | <div> |
| | | <PageContainer header={{ |
| | | breadcrumb: {}, |
| | | }} |
| | | title={'日志记录'} |
| | | > |
| | | <ProTable |
| | | rowKey="id" |
| | | actionRef={actionRef} |
| | | columns={columns} |
| | | formRef={formRef} |
| | | request={async (params) => { |
| | | if (params.time && params.time.length > 0) { |
| | | params.startTime = moment(params.time[0]).format('YYYY-MM-DD HH:mm:ss'); |
| | | params.endTime = moment(params.time[1]).format('YYYY-MM-DD 23:59:59'); |
| | | delete params.time |
| | | } else { |
| | | delete params.startTime |
| | | delete params.endTime |
| | | } |
| | | |
| | | |
| | | return buildProTableDataSource(getList, params); |
| | | }} |
| | | search={{ labelWidth: 'auto', defaultCollapsed: false }} |
| | | /> |
| | | </PageContainer> |
| | | </div> |
| | | <PageContainer header={{ |
| | | breadcrumb: {}, |
| | | }} |
| | | title={'日志记录'} |
| | | > |
| | | <ProTable |
| | | rowKey="id" |
| | | actionRef={actionRef} |
| | | columns={columns} |
| | | formRef={formRef} |
| | | request={async (params) => { |
| | | if (params.time && params.time.length > 0) { |
| | | params.startTime = moment(params.time[0]).format('YYYY-MM-DD HH:mm:ss'); |
| | | params.endTime = moment(params.time[1]).format('YYYY-MM-DD 23:59:59'); |
| | | delete params.time |
| | | } else { |
| | | delete params.startTime |
| | | delete params.endTime |
| | | } |
| | | return buildProTableDataSource(getList, params); |
| | | }} |
| | | search={{ labelWidth: 'auto', defaultCollapsed: false }} |
| | | /> |
| | | </PageContainer> |
| | | ); |
| | | }; |
| | | |
| | | export default Account; |
| | | export default Account; |
| | |
| | | import { request } from '@umijs/max'; |
| | | |
| | | // 获取诉求列表 |
| | | export const getList = async (data) => { |
| | | return request(`/api/huacheng-sangeshenbian/complaint/page`, { |
| | | method: 'POST', |
| | | data |
| | | }); |
| | | } |
| | | |
| | | // 获取述求详情 |
| | | export const getDetail = async (data) => { |
| | | return request(`/api/huacheng-sangeshenbian/complaint/detail/${data.id}`, { |
| | | export const getList = async (params) => { |
| | | return request(`/api/huacheng-sangeshenbian/system-log/list`, { |
| | | method: 'GET', |
| | | data |
| | | params |
| | | }); |
| | | } |