soundRecorder.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. <template>
  2. <view class="sound-recorder">
  3. <view class="top-text">{{ topText }}</view>
  4. <view class="file-name">{{ dataInner.fileName }}</view>
  5. <view class="call-history">{{ callHistory }}</view>
  6. <view class="audio-wrap">
  7. <view class="play-btn" @click="togglePlay">
  8. <up-icon :name="isPlaying ? 'pause-circle-fill' : 'play-circle-fill'" size="44" color="#4c8afe"></up-icon>
  9. </view>
  10. <text class="play-tip">{{ isPlaying ? '播放中' : '点击播放' }}</text>
  11. </view>
  12. <view @click="handleDelete">
  13. <up-icon class="delectIcon" name="trash" size="22"></up-icon>
  14. </view>
  15. </view>
  16. </template>
  17. <script>
  18. export default {
  19. props: {
  20. data: {
  21. type: Object,
  22. default: () => ({})
  23. }
  24. },
  25. data() {
  26. return {
  27. dataInner: {},
  28. isPlaying: false,
  29. innerAudio: null
  30. }
  31. },
  32. watch: {
  33. data: {
  34. deep: true,
  35. immediate: true,
  36. handler(newVal) {
  37. this.dataInner = { ...newVal }
  38. if (this.innerAudio && this.isPlaying) {
  39. this.innerAudio.stop()
  40. this.isPlaying = false
  41. }
  42. }
  43. }
  44. },
  45. computed: {
  46. topText() {
  47. return `${this.dataInner.createTime || ''}号码(${this.dataInner.caller || ''})上传录音`
  48. },
  49. callHistory() {
  50. return `${this.dataInner.caller || ''} 打给 ${this.dataInner.callee || ''}`
  51. }
  52. },
  53. beforeUnmount() {
  54. if (this.innerAudio) {
  55. this.innerAudio.stop()
  56. this.innerAudio.destroy()
  57. this.innerAudio = null
  58. }
  59. },
  60. methods: {
  61. getInnerAudio() {
  62. if (this.innerAudio) return this.innerAudio
  63. const ctx = uni.createInnerAudioContext()
  64. ctx.onPlay(() => { this.isPlaying = true })
  65. ctx.onPause(() => { this.isPlaying = false })
  66. ctx.onStop(() => { this.isPlaying = false })
  67. ctx.onEnded(() => { this.isPlaying = false })
  68. ctx.onError((err) => {
  69. this.isPlaying = false
  70. console.error('soundRecorder audio error', err)
  71. uni.showToast({ title: '播放失败', icon: 'none' })
  72. })
  73. this.innerAudio = ctx
  74. return ctx
  75. },
  76. togglePlay() {
  77. const url = this.dataInner.fileUrl
  78. if (!url) {
  79. uni.showToast({ title: '暂无音频', icon: 'none' })
  80. return
  81. }
  82. const ctx = this.getInnerAudio()
  83. if (this.isPlaying) {
  84. ctx.pause()
  85. } else {
  86. ctx.src = url
  87. ctx.play()
  88. }
  89. },
  90. handleDelete() {
  91. if (this.innerAudio && this.isPlaying) {
  92. this.innerAudio.stop()
  93. this.isPlaying = false
  94. }
  95. this.$emit('handleDelectThisSoundRecord', this.dataInner)
  96. }
  97. }
  98. }
  99. </script>
  100. <style lang="scss" scoped>
  101. .sound-recorder {
  102. background: #ffffff;
  103. border-radius: 20rpx;
  104. box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.08);
  105. padding: 32rpx;
  106. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  107. border: 2rpx solid #f0f0f0;
  108. position: relative;
  109. margin-bottom: 10rpx;
  110. .top-text {
  111. font-size: 20rpx;
  112. color: #888;
  113. line-height: 1.4;
  114. }
  115. .call-history {
  116. font-size: 30rpx;
  117. color: #333;
  118. font-weight: 500;
  119. margin-bottom: 20rpx;
  120. line-height: 1.5;
  121. }
  122. .file-name {
  123. font-size: 20rpx;
  124. color: #666;
  125. line-height: 1.4;
  126. word-break: break-all;
  127. }
  128. .audio-wrap {
  129. display: flex;
  130. align-items: center;
  131. gap: 16rpx;
  132. margin-bottom: 8rpx;
  133. .play-btn { padding: 8rpx; }
  134. .play-tip { font-size: 24rpx; color: #4c8afe; }
  135. }
  136. .delectIcon {
  137. position: absolute;
  138. right: 32rpx;
  139. top: 32rpx;
  140. z-index: 1;
  141. }
  142. }
  143. </style>