readContent3.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <template>
  2. <view>
  3. <selectTypesVue activeSelect="3"></selectTypesVue>
  4. <view class="words-xuan-box">
  5. <!-- 单词区 -->
  6. <view class="show-words-box"> {{data.name}} </view>
  7. <!-- 音标区 -->
  8. <view class="yb-play-box">
  9. <text>{{data.yinbiao}}</text>
  10. <!-- active -->
  11. <icon class="yb-play-btn"></icon>
  12. <icon class="yb-playing-btn" v-if="false"></icon>
  13. </view>
  14. <view style="text-align: center;">
  15. <text v-if="data.jianyi&&data.jianyi.length>0" v-for="(item,index) in data.jianyi" :key="index">
  16. {{item}}
  17. </text>
  18. </view>
  19. <view>
  20. <view class="audio-play-btn"></view>
  21. <!-- 播放中 -->
  22. <view class="audio-playing-btn" v-if="false"></view>
  23. </view>
  24. <view class="container">
  25. <view class="status">{{ recordingStatus }}</view>
  26. <view class="duration" v-if="isRecording">录音时长: {{ duration.toFixed(1) }}秒</view>
  27. <button class="record-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
  28. @touchend="endRecord" @mousedown="startRecord" @mouseup="endRecord" @mouseleave="handleMouseLeave">
  29. {{ isRecording ? '松开结束' : '按住录音' }}
  30. </button>
  31. <button class="play-btn" :disabled="!voicePath || isPlaying" @click="playVoice">
  32. {{ isPlaying ? '播放中...' : '播放录音' }}
  33. </button>
  34. <view class="tips">提示:按住按钮录音,松开结束(需3秒以上)</view>
  35. </view>
  36. </view>
  37. </view>
  38. </template>
  39. <script setup>
  40. import selectWordsVue from './selectWords.vue';
  41. import selectTypesVue from './selectTypes.vue';
  42. import {
  43. onLoad
  44. } from "@dcloudio/uni-app"
  45. import {
  46. reactive,
  47. ref,
  48. onMounted,
  49. onUnmounted
  50. } from 'vue';
  51. const props = defineProps({
  52. activeWord: {
  53. type: Object,
  54. },
  55. pageData: {
  56. type: Object,
  57. },
  58. activeWords: {
  59. type: Array
  60. },
  61. })
  62. let tabFlag = ref(1)
  63. const audioInfo = ref(null)
  64. const data = reactive({
  65. name: '',
  66. yinbiao: '',
  67. jianyi: []
  68. })
  69. // 状态定义
  70. const isRecording = ref(false)
  71. const isPlaying = ref(false)
  72. const voicePath = ref('')
  73. const duration = ref(0)
  74. const timer = ref(null)
  75. const recordingStatus = ref('准备就绪')
  76. const startTime = ref(0)
  77. // 获取录音和音频播放管理器
  78. const recorderManager = uni.getRecorderManager()
  79. const innerAudioContext = uni.createInnerAudioContext()
  80. // 初始化录音器
  81. const initRecorder = () => {
  82. // 录音开始回调
  83. recorderManager.onStart(() => {
  84. console.log('recorder start')
  85. isRecording.value = true
  86. recordingStatus.value = '录音中...'
  87. startTime.value = Date.now()
  88. startTimer()
  89. })
  90. // 录音停止回调
  91. recorderManager.onStop((res) => {
  92. console.log('recorder stop', res)
  93. const recordTime = (Date.now() - startTime.value) / 1000
  94. stopTimer()
  95. isRecording.value = false
  96. if (recordTime < 3) {
  97. recordingStatus.value = '录音时间太短(需3秒以上)'
  98. uni.showToast({
  99. title: '录音时间太短,需3秒以上',
  100. icon: 'none'
  101. })
  102. return
  103. }
  104. if (res.tempFilePath) {
  105. voicePath.value = res.tempFilePath
  106. recordingStatus.value = `录音完成,时长: ${recordTime.toFixed(1)}秒`
  107. uni.showToast({
  108. title: '录音成功',
  109. icon: 'success'
  110. })
  111. } else {
  112. recordingStatus.value = '录音失败'
  113. uni.showToast({
  114. title: '录音失败',
  115. icon: 'none'
  116. })
  117. }
  118. })
  119. // 录音错误回调
  120. recorderManager.onError((res) => {
  121. console.error('recorder error', res)
  122. stopTimer()
  123. isRecording.value = false
  124. recordingStatus.value = `录音错误: ${res.errMsg}`
  125. uni.showToast({
  126. title: `录音出错: ${res.errMsg}`,
  127. icon: 'none'
  128. })
  129. })
  130. // 音频播放相关回调
  131. innerAudioContext.onPlay(() => {
  132. isPlaying.value = true
  133. recordingStatus.value = '播放中...'
  134. })
  135. innerAudioContext.onEnded(() => {
  136. isPlaying.value = false
  137. recordingStatus.value = '播放完成'
  138. })
  139. innerAudioContext.onError((res) => {
  140. isPlaying.value = false
  141. console.error('play error', res)
  142. uni.showToast({
  143. title: '播放失败',
  144. icon: 'none'
  145. })
  146. })
  147. }
  148. // 检查权限
  149. const checkPermission = () => {
  150. uni.authorize({
  151. scope: 'scope.record',
  152. success: () => {
  153. console.log('已授权录音权限')
  154. },
  155. fail: (err) => {
  156. console.log('未授权录音权限', err)
  157. uni.showModal({
  158. title: '提示',
  159. content: '需要录音权限才能使用此功能',
  160. confirmText: '去设置',
  161. success: (res) => {
  162. if (res.confirm) {
  163. uni.openSetting()
  164. }
  165. }
  166. })
  167. }
  168. })
  169. }
  170. // 开始录音
  171. const startRecord = (e) => {
  172. // 阻止默认行为,防止触摸事件触发其他行为
  173. e.preventDefault()
  174. if (isRecording.value) return
  175. console.log('开始录音')
  176. recordingStatus.value = '准备录音...'
  177. // 适配不同平台的录音参数
  178. const options = {
  179. duration: 60000, // 最长60秒
  180. sampleRate: 44100,
  181. numberOfChannels: 1,
  182. encodeBitRate: 192000,
  183. format: 'mp3',
  184. frameSize: 50 // iOS特定配置
  185. }
  186. // Android可能需要不同的配置
  187. if (uni.getSystemInfoSync().platform === 'android') {
  188. options.format = 'amr' // Android上amr格式兼容性更好
  189. }
  190. recorderManager.start(options)
  191. }
  192. // 结束录音
  193. const endRecord = (e) => {
  194. // 阻止默认行为
  195. e.preventDefault()
  196. if (!isRecording.value) return
  197. console.log('停止录音')
  198. recorderManager.stop()
  199. }
  200. // 处理鼠标移出按钮区域的情况
  201. const handleMouseLeave = (e) => {
  202. if (isRecording.value) {
  203. endRecord(e)
  204. }
  205. }
  206. // 计时器相关
  207. const startTimer = () => {
  208. duration.value = 0
  209. timer.value = setInterval(() => {
  210. duration.value += 0.1
  211. }, 100)
  212. }
  213. const stopTimer = () => {
  214. if (timer.value) {
  215. clearInterval(timer.value)
  216. timer.value = null
  217. }
  218. }
  219. // 播放录音
  220. const playVoice = () => {
  221. if (!voicePath.value) {
  222. uni.showToast({
  223. title: '没有可播放的录音',
  224. icon: 'none'
  225. })
  226. return
  227. }
  228. console.log('播放录音:', voicePath.value)
  229. innerAudioContext.src = voicePath.value
  230. innerAudioContext.play()
  231. }
  232. // 生命周期钩子
  233. onMounted(() => {
  234. initRecorder()
  235. checkPermission()
  236. initItem()
  237. })
  238. onUnmounted(() => {
  239. // 组件卸载时清理资源
  240. stopTimer()
  241. recorderManager.stop()
  242. innerAudioContext.stop()
  243. })
  244. function initItem() {
  245. console.log('data', data);
  246. data.name = props.activeWord.name;
  247. data.yinbiao = props.activeWord.yinbiao;
  248. data.jianyi = props.activeWord.jianyi;
  249. }
  250. </script>
  251. <style>
  252. </style>