|
@@ -1,390 +0,0 @@
|
|
-<template>
|
|
|
|
- <view>
|
|
|
|
- <selectTypesVue activeSelect="3"></selectTypesVue>
|
|
|
|
- <view class="words-xuan-box">
|
|
|
|
- <!-- 单词区 -->
|
|
|
|
- <view class="show-words-box"> {{data.name}} </view>
|
|
|
|
- <!-- 音标区 -->
|
|
|
|
- <view class="yb-play-box">
|
|
|
|
- <text>{{data.yinbiao}}</text>
|
|
|
|
- <icon class="yb-play-btn"></icon>
|
|
|
|
- <icon class="yb-playing-btn" v-if="false"></icon>
|
|
|
|
- </view>
|
|
|
|
-
|
|
|
|
- <view style="text-align: center;">
|
|
|
|
- <text v-if="data.jianyi&&data.jianyi.length>0" v-for="(item,index) in data.jianyi" :key="index">
|
|
|
|
- {{item}}
|
|
|
|
- </text>
|
|
|
|
- </view>
|
|
|
|
-
|
|
|
|
- <view>
|
|
|
|
- <view class="audio-play-btn"></view>
|
|
|
|
- <view class="audio-playing-btn" v-if="false"></view>
|
|
|
|
- </view>
|
|
|
|
- <view class="container">
|
|
|
|
- <view class="status">{{ recordingStatus }}</view>
|
|
|
|
- <view class="duration" v-if="isRecording">录音时长: {{ Math.floor(duration) }}秒</view>
|
|
|
|
-
|
|
|
|
- <button class="record-btn" :class="{ recording: isRecording, disabled: isPlaying }"
|
|
|
|
- @touchstart="handleTouchStart" @touchend="handleTouchEnd" @touchcancel="handleTouchEnd"
|
|
|
|
- @mousedown="handleMouseDown" @mouseup="handleMouseUp" @mouseleave="handleMouseLeave"
|
|
|
|
- :disabled="isPlaying">
|
|
|
|
- {{ isRecording ? '松开结束' : '按住录音' }}
|
|
|
|
- <view v-if="isPlaying" class="disabled-mask">播放中不可录音</view>
|
|
|
|
- </button>
|
|
|
|
-
|
|
|
|
- <button class="play-btn" :class="{ disabled: isRecording || !voicePath }" @click="playVoice"
|
|
|
|
- :disabled="isRecording || !voicePath">
|
|
|
|
- {{ isPlaying ? '播放中...' : '播放录音' }}
|
|
|
|
- <view v-if="isRecording" class="disabled-mask">录音中不可播放</view>
|
|
|
|
- </button>
|
|
|
|
-
|
|
|
|
- <view class="tips">提示:长按按钮录音,松开结束(需3秒以上)</view>
|
|
|
|
- </view>
|
|
|
|
- </view>
|
|
|
|
- </view>
|
|
|
|
-</template>
|
|
|
|
-
|
|
|
|
-<script setup>
|
|
|
|
- import selectTypesVue from './selectTypes.vue';
|
|
|
|
- import {
|
|
|
|
- onLoad
|
|
|
|
- } from "@dcloudio/uni-app"
|
|
|
|
- import {
|
|
|
|
- reactive,
|
|
|
|
- ref,
|
|
|
|
- onMounted,
|
|
|
|
- onUnmounted
|
|
|
|
- } from 'vue';
|
|
|
|
-
|
|
|
|
- const props = defineProps({
|
|
|
|
- activeWord: {
|
|
|
|
- type: Object,
|
|
|
|
- },
|
|
|
|
- pageData: {
|
|
|
|
- type: Object,
|
|
|
|
- },
|
|
|
|
- activeWords: {
|
|
|
|
- type: Array
|
|
|
|
- },
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- let tabFlag = ref(1)
|
|
|
|
- const audioInfo = ref(null)
|
|
|
|
- const data = reactive({
|
|
|
|
- name: '',
|
|
|
|
- yinbiao: '',
|
|
|
|
- jianyi: []
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- // 录音相关状态
|
|
|
|
- const isRecording = ref(false)
|
|
|
|
- const isPlaying = ref(false)
|
|
|
|
- const voicePath = ref('')
|
|
|
|
- const duration = ref(0)
|
|
|
|
- const timer = ref(null)
|
|
|
|
- const recordingStatus = ref('准备就绪')
|
|
|
|
- const startTime = ref(0)
|
|
|
|
- const longPressTimer = ref(null) // 长按计时器
|
|
|
|
- const MIN_RECORD_TIME = 3 // 最小录音时间3秒
|
|
|
|
-
|
|
|
|
- // 获取录音和音频播放管理器
|
|
|
|
- const recorderManager = uni.getRecorderManager()
|
|
|
|
- const innerAudioContext = uni.createInnerAudioContext()
|
|
|
|
-
|
|
|
|
- // 初始化录音器
|
|
|
|
- const initRecorder = () => {
|
|
|
|
- recorderManager.onStart(() => {
|
|
|
|
- console.log('recorder start')
|
|
|
|
- isRecording.value = true
|
|
|
|
- recordingStatus.value = '录音中...'
|
|
|
|
- startTime.value = Date.now()
|
|
|
|
- startTimer()
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- recorderManager.onStop((res) => {
|
|
|
|
- console.log('recorder stop', res)
|
|
|
|
- const recordTime = (Date.now() - startTime.value) / 1000
|
|
|
|
-
|
|
|
|
- stopTimer()
|
|
|
|
- isRecording.value = false
|
|
|
|
-
|
|
|
|
- if (recordTime < MIN_RECORD_TIME) {
|
|
|
|
- recordingStatus.value = '录音时间太短(需3秒以上)'
|
|
|
|
- uni.showToast({
|
|
|
|
- title: '录音时间太短,需3秒以上',
|
|
|
|
- icon: 'none'
|
|
|
|
- })
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (res.tempFilePath) {
|
|
|
|
- voicePath.value = res.tempFilePath
|
|
|
|
- recordingStatus.value = `录音完成,时长: ${Math.floor(recordTime)}秒`
|
|
|
|
- uni.showToast({
|
|
|
|
- title: '录音成功',
|
|
|
|
- icon: 'success'
|
|
|
|
- })
|
|
|
|
- } else {
|
|
|
|
- recordingStatus.value = '录音失败'
|
|
|
|
- uni.showToast({
|
|
|
|
- title: '录音失败',
|
|
|
|
- icon: 'none'
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- recorderManager.onError((res) => {
|
|
|
|
- console.error('recorder error', res)
|
|
|
|
- stopTimer()
|
|
|
|
- isRecording.value = false
|
|
|
|
- recordingStatus.value = `录音错误: ${res.errMsg}`
|
|
|
|
- uni.showToast({
|
|
|
|
- title: `录音出错: ${res.errMsg}`,
|
|
|
|
- icon: 'none'
|
|
|
|
- })
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- innerAudioContext.onPlay(() => {
|
|
|
|
- isPlaying.value = true
|
|
|
|
- recordingStatus.value = '播放中...'
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- innerAudioContext.onEnded(() => {
|
|
|
|
- isPlaying.value = false
|
|
|
|
- recordingStatus.value = '播放完成'
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- innerAudioContext.onError((res) => {
|
|
|
|
- isPlaying.value = false
|
|
|
|
- console.error('play error', res)
|
|
|
|
- uni.showToast({
|
|
|
|
- title: '播放失败',
|
|
|
|
- icon: 'none'
|
|
|
|
- })
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 检查权限
|
|
|
|
- const checkPermission = () => {
|
|
|
|
- uni.authorize({
|
|
|
|
- scope: 'scope.record',
|
|
|
|
- success: () => {
|
|
|
|
- console.log('已授权录音权限')
|
|
|
|
- },
|
|
|
|
- fail: (err) => {
|
|
|
|
- console.log('未授权录音权限', err)
|
|
|
|
- uni.showModal({
|
|
|
|
- title: '提示',
|
|
|
|
- content: '需要录音权限才能使用此功能',
|
|
|
|
- confirmText: '去设置',
|
|
|
|
- success: (res) => {
|
|
|
|
- if (res.confirm) {
|
|
|
|
- uni.openSetting()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 处理触摸开始(移动端)
|
|
|
|
- const handleTouchStart = (e) => {
|
|
|
|
- e.preventDefault()
|
|
|
|
- if (isPlaying.value) return
|
|
|
|
-
|
|
|
|
- // 设置长按计时器,500ms后才开始录音
|
|
|
|
- longPressTimer.value = setTimeout(() => {
|
|
|
|
- startRecording()
|
|
|
|
- }, 400)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 处理触摸结束(移动端)
|
|
|
|
- const handleTouchEnd = (e) => {
|
|
|
|
- e.preventDefault()
|
|
|
|
- clearTimeout(longPressTimer.value)
|
|
|
|
- if (isRecording.value) {
|
|
|
|
- endRecording()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 处理触摸取消(移动端,如被系统中断)
|
|
|
|
- const handleTouchCancel = (e) => {
|
|
|
|
- handleTouchEnd(e) // 与touchend同样处理
|
|
|
|
- }
|
|
|
|
- // 处理鼠标按下(桌面端)
|
|
|
|
- const handleMouseDown = (e) => {
|
|
|
|
- e.preventDefault()
|
|
|
|
- if (isPlaying.value) return
|
|
|
|
-
|
|
|
|
- longPressTimer.value = setTimeout(() => {
|
|
|
|
- startRecording()
|
|
|
|
- }, 500)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 处理鼠标抬起(桌面端)
|
|
|
|
- const handleMouseUp = (e) => {
|
|
|
|
- e.preventDefault()
|
|
|
|
- clearTimeout(longPressTimer.value)
|
|
|
|
- if (isRecording.value) {
|
|
|
|
- endRecording()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 处理鼠标移出按钮区域
|
|
|
|
- const handleMouseLeave = (e) => {
|
|
|
|
- clearTimeout(longPressTimer.value)
|
|
|
|
- if (isRecording.value) {
|
|
|
|
- endRecording()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 开始录音
|
|
|
|
- const startRecording = () => {
|
|
|
|
- if (isRecording.value) return
|
|
|
|
-
|
|
|
|
- console.log('开始录音')
|
|
|
|
- recordingStatus.value = '准备录音...'
|
|
|
|
- isRecording.value = true // 提前设置状态,避免延迟
|
|
|
|
- startTime.value = Date.now()
|
|
|
|
- startTimer()
|
|
|
|
-
|
|
|
|
- const options = {
|
|
|
|
- duration: 60000,
|
|
|
|
- sampleRate: 44100,
|
|
|
|
- numberOfChannels: 1,
|
|
|
|
- encodeBitRate: 192000,
|
|
|
|
- format: uni.getSystemInfoSync().platform === 'android' ? 'amr' : 'mp3',
|
|
|
|
- frameSize: 50
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- recorderManager.start(options)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 结束录音
|
|
|
|
- const endRecording = () => {
|
|
|
|
- if (!isRecording.value) return
|
|
|
|
-
|
|
|
|
- console.log('停止录音')
|
|
|
|
- isRecording.value = false
|
|
|
|
- stopTimer()
|
|
|
|
- recorderManager.stop()
|
|
|
|
- }
|
|
|
|
- // 播放录音
|
|
|
|
- const playVoice = () => {
|
|
|
|
- if (isRecording.value || !voicePath.value) return
|
|
|
|
-
|
|
|
|
- console.log('播放录音:', voicePath.value)
|
|
|
|
- innerAudioContext.src = voicePath.value
|
|
|
|
- innerAudioContext.play()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 计时器相关
|
|
|
|
- const startTimer = () => {
|
|
|
|
- duration.value = 0
|
|
|
|
- timer.value = setInterval(() => {
|
|
|
|
- duration.value += 1 // 每秒增加1,而不是0.1
|
|
|
|
- }, 1000) // 间隔改为1000ms
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const stopTimer = () => {
|
|
|
|
- if (timer.value) {
|
|
|
|
- clearInterval(timer.value)
|
|
|
|
- timer.value = null
|
|
|
|
- }
|
|
|
|
- duration.value = 0
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 初始化单词数据
|
|
|
|
- const initItem = () => {
|
|
|
|
- data.name = props.activeWord.name;
|
|
|
|
- data.yinbiao = props.activeWord.yinbiao;
|
|
|
|
- data.jianyi = props.activeWord.jianyi;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- onMounted(() => {
|
|
|
|
- initRecorder()
|
|
|
|
- checkPermission()
|
|
|
|
- initItem()
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- onUnmounted(() => {
|
|
|
|
- stopTimer()
|
|
|
|
- clearTimeout(longPressTimer.value)
|
|
|
|
- recorderManager.stop()
|
|
|
|
- innerAudioContext.stop()
|
|
|
|
- })
|
|
|
|
-</script>
|
|
|
|
-
|
|
|
|
-<style>
|
|
|
|
- .container {
|
|
|
|
- padding: 20px;
|
|
|
|
- display: flex;
|
|
|
|
- flex-direction: column;
|
|
|
|
- align-items: center;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .status {
|
|
|
|
- margin: 20px 0;
|
|
|
|
- color: #666;
|
|
|
|
- font-size: 16px;
|
|
|
|
- min-height: 24px;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .duration {
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
- color: #333;
|
|
|
|
- font-weight: bold;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .record-btn {
|
|
|
|
- background-color: #4CAF50;
|
|
|
|
- color: white;
|
|
|
|
- margin: 10px 0;
|
|
|
|
- width: 80%;
|
|
|
|
- padding: 15px 0;
|
|
|
|
- border: none;
|
|
|
|
- border-radius: 5px;
|
|
|
|
- position: relative;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .record-btn.recording {
|
|
|
|
- background-color: #F44336;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .play-btn {
|
|
|
|
- background-color: #2196F3;
|
|
|
|
- color: white;
|
|
|
|
- margin: 10px 0;
|
|
|
|
- width: 80%;
|
|
|
|
- padding: 15px 0;
|
|
|
|
- border: none;
|
|
|
|
- border-radius: 5px;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .disabled {
|
|
|
|
- opacity: 0.7;
|
|
|
|
- position: relative;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .disabled-mask {
|
|
|
|
- position: absolute;
|
|
|
|
- top: 0;
|
|
|
|
- left: 0;
|
|
|
|
- right: 0;
|
|
|
|
- bottom: 0;
|
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
|
- color: white;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: center;
|
|
|
|
- font-size: 14px;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .tips {
|
|
|
|
- margin-top: 20px;
|
|
|
|
- color: #999;
|
|
|
|
- font-size: 12px;
|
|
|
|
- text-align: center;
|
|
|
|
- }
|
|
|
|
-</style>
|
|
|