voice.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <template>
  2. <view class="page" :style="{'min-height':h+'px', 'padding-top':mt+'px'}">
  3. <cus-header title='音色'></cus-header>
  4. <div class="top adfacjb" v-if="voice">
  5. <div class="tl">
  6. <image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/07/04/a09113ef-34fe-4105-bc4e-84ab7351ffc8.gif"></image>
  7. <text>{{voice.name||''}}</text>
  8. </div>
  9. <div class="tr adfac">
  10. <div class="tr_btn qx" @tap="cancel">取消</div>
  11. <div class="tr_btn qr" @tap="confirm">确认</div>
  12. </div>
  13. </div>
  14. <template v-if="list.length">
  15. <div class="list">
  16. <div class="item adfacjb" :class="{'active':index===idx}" v-for="(item,index) in list" :key="index" @tap="selectVoice(item,index)">
  17. <div class="il adfac">
  18. <image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/07/04/6a79f5d7-2d41-4405-97e2-e93876797ec8.png" v-if="item.sex==1"></image>
  19. <image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/07/04/c4eaec06-7de9-4f1c-b76d-ead9a4a38373.png" v-else-if="item.sex==2"></image>
  20. <text>{{item.name}}</text>
  21. </div>
  22. <div class="ir">
  23. <image @tap.stop="playOnOff(0,item,index)" src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/07/04/fbfe67d5-a69b-4bb1-81a5-83d19403b88e.png" v-if="item.play"></image>
  24. <image @tap.stop="playOnOff(1,item,index)" src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/07/04/9ef5fc85-05b2-4681-b37e-47cf843f5b6c.png" v-else></image>
  25. </div>
  26. </div>
  27. </div>
  28. </template>
  29. <template v-else>
  30. <page-empty :height="'calc(100vh - 160rpx)'"></page-empty>
  31. </template>
  32. </view>
  33. </template>
  34. <script>
  35. import pageEmpty from '@/components/pageEmpty/index.vue'
  36. export default {
  37. components:{pageEmpty},
  38. data(){
  39. return {
  40. audioCtx: null,
  41. idx:'',
  42. list:[],
  43. voice:null,
  44. }
  45. },
  46. onLoad() {
  47. this.getModelVoiceList();
  48. },
  49. mounted() {
  50. this.audioCtx = uni.createInnerAudioContext()
  51. },
  52. methods:{
  53. getModelVoiceList(){
  54. this.$api.get(`/ttsVoice`,{
  55. ttsModelId:'TTS_EdgeTTS',
  56. page:1,
  57. limit:-1
  58. }).then(res=>{
  59. if(res.data.code!==0) return this.$showToast(res.data.msg)
  60. this.list = res.data.data.list;
  61. this.list.forEach((l,i)=>{
  62. if(l.name.indexOf('男声')>-1) l.sex=1
  63. else if(l.name.indexOf('女声')>-1) l.sex=2
  64. this.$set(this.list[i],'play',false);
  65. })
  66. })
  67. },
  68. selectVoice(item,index){
  69. this.voice = item;
  70. this.idx = index;
  71. },
  72. playOnOff(type,item,index){
  73. let idx = this.list.findIndex(l=>l.play);
  74. if(idx!==index) {
  75. this.audioCtx.pause();
  76. this.audioCtx.src = '';
  77. }
  78. this.voice = item;
  79. this.audioCtx.src = item.voiceDemo;
  80. this.list.forEach((l,i)=>{
  81. this.$set(this.list[i],'play',false);
  82. })
  83. this.$set(this.list[index],'play',type);
  84. uni.pageScrollTo({ scrollTop: 0, duration: 100});
  85. if(type==1) this.audioCtx.play();
  86. else if(type===0) this.audioCtx.pause();
  87. },
  88. cancel(){
  89. this.voice = null;
  90. this.list.forEach((l,i)=>{
  91. this.$set(this.list[i],'play',false);
  92. })
  93. },
  94. confirm() {
  95. if(!this.voice) return this.$showToast('请选择音色');
  96. this.getOpenerEventChannel().emit('selectVoice',this.voice)
  97. uni.navigateBack();
  98. }
  99. },
  100. beforeDestroy() {
  101. if (this.audioCtx) {
  102. this.audioCtx.destroy();
  103. this.audioCtx = null;
  104. }
  105. }
  106. }
  107. </script>
  108. <style scoped lang="less">
  109. .page{
  110. width: 100%;
  111. padding: 0 30rpx 140rpx;
  112. background: #FFFFFF;
  113. box-sizing: border-box;
  114. .top{
  115. margin-top: 18rpx;
  116. border-radius: 16rpx;
  117. border: 1rpx solid #EEEEEE;
  118. padding: 16rpx 24rpx;
  119. .tl{
  120. image{
  121. width: 40rpx;
  122. height: 22rpx;
  123. }
  124. text{
  125. font-family: PingFangSC, PingFang SC;
  126. font-weight: 400;
  127. font-size: 28rpx;
  128. color: #111111;
  129. line-height: 40rpx;
  130. margin-left: 9rpx;
  131. }
  132. }
  133. .tr{
  134. .tr_btn{
  135. width: 104rpx;
  136. height: 56rpx;
  137. border-radius: 14rpx;
  138. font-family: PingFangSC, PingFang SC;
  139. font-weight: 400;
  140. font-size: 28rpx;
  141. color: #111111;
  142. line-height: 56rpx;
  143. text-align: center;
  144. margin-left: 20rpx;
  145. &.qx{
  146. border: 1rpx solid #D1D5DB;
  147. }
  148. &.qr{
  149. border: 1rpx solid #D9F159;
  150. background: #D9F159;
  151. }
  152. }
  153. }
  154. }
  155. .list{
  156. margin-top: 35rpx;
  157. .item{
  158. box-shadow: inset 0rpx -1rpx 0rpx 0rpx #E2E2E2;
  159. padding: 24rpx 10rpx 24rpx 0;
  160. width: 100%;
  161. box-sizing: border-box;
  162. .il{
  163. image{
  164. width: 88rpx;
  165. height: 88rpx;
  166. }
  167. text{
  168. font-family: PingFang-SC, PingFang-SC;
  169. font-weight: bold;
  170. font-size: 32rpx;
  171. color: #111111;
  172. line-height: 32rpx;
  173. margin-left: 30rpx;
  174. }
  175. }
  176. .ir{
  177. image{
  178. width: 40rpx;
  179. height: 40rpx;
  180. }
  181. }
  182. }
  183. }
  184. }
  185. </style>