hookehuyr

✨ feat: 新增详情页音频播放列表组件

...@@ -11,6 +11,7 @@ declare module '@vue/runtime-core' { ...@@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
11 export interface GlobalComponents { 11 export interface GlobalComponents {
12 AudioBackground: typeof import('./src/components/audioBackground.vue')['default'] 12 AudioBackground: typeof import('./src/components/audioBackground.vue')['default']
13 AudioBackground1: typeof import('./src/components/audioBackground1.vue')['default'] 13 AudioBackground1: typeof import('./src/components/audioBackground1.vue')['default']
14 + AudioList: typeof import('./src/components/audioList.vue')['default']
14 Floor: typeof import('./src/components/Floor/index.vue')['default'] 15 Floor: typeof import('./src/components/Floor/index.vue')['default']
15 InfoPopup: typeof import('./src/components/InfoPopup.vue')['default'] 16 InfoPopup: typeof import('./src/components/InfoPopup.vue')['default']
16 InfoPopupLite: typeof import('./src/components/InfoPopupLite.vue')['default'] 17 InfoPopupLite: typeof import('./src/components/InfoPopupLite.vue')['default']
......
1 +<template>
2 + <div class="audio-list-page">
3 + <van-config-provider :theme-vars="themeVars">
4 + <van-floating-panel v-model:height="info_height" :anchors="anchors" @height-change="onHeightChange">
5 + <template #header>
6 + <div style="display: flex; align-items: center; justify-content: space-between; padding: 1.25rem 1rem 0.5rem 1rem;">
7 + <van-icon name="https://cdn.ipadbiz.cn/bieyuan/map/icon/Group%2054@3x.png" color="#DD7850" size="1.25rem" />
8 + <van-icon @click="onClose" name="cross" color="#DD7850" size="1.25rem" />
9 + </div>
10 + </template>
11 + <!-- <page-info ref="pageInfo" :info="itemInfo" :height="info_height" @close-float="onCloseFloat" @route="onRoute"></page-info> -->
12 + <!-- <div v-if="showClose" @click="closeFloatPanel" class="close-float-panel">
13 + <van-icon name="arrow-left" color="#FFF" size="1.5rem" />
14 + </div> -->
15 + <div class="van-hairline--top" style="padding: 1rem;">
16 + <div v-for="(item, index) in audio_list" :key="index" class="van-hairline--bottom audio-item">
17 + <div :class="['point', audio_index === index ? 'checked' : '']"></div>
18 + <div :class="['text', audio_index === index ? 'checked' : '', 'van-ellipsis']">
19 + {{ index + 1 }}. {{ item.title }}<span v-if="item.play" class="text-center">正在播放</span>
20 +
21 + </div>
22 + <!-- <div style="border: 1px solid #DD7850; border-radius: 50%; padding: 0.25rem;">
23 + <van-icon v-if="audio_index === index" name="https://cdn.ipadbiz.cn/xys/map/%E6%92%AD%E6%94%BE@2x.png" size="1.75rem" />
24 + <van-icon v-else name="https://cdn.ipadbiz.cn/xys/map/%E6%92%AD%E6%94%BE%E6%9A%82%E5%81%9C@2x.png" size="1.75rem" />
25 + </div> -->
26 + <div class="progress-ring">
27 + <div class="circle">
28 + <div class="pause-icon">
29 + <van-icon @click="handleAudioPause(item, index)" v-if="item.play" name="https://cdn.ipadbiz.cn/xys/map/%E6%92%AD%E6%94%BE@2x.png" size="2.25rem" />
30 + <van-icon @click="handleAudioPlay(item, index)" v-else name="https://cdn.ipadbiz.cn/xys/map/%E6%92%AD%E6%94%BE%E6%9A%82%E5%81%9C@2x.png" size="2.25rem" />
31 + </div>
32 + <div :class="['progress', item.play ? 'checked' : '']"></div>
33 + </div>
34 + </div>
35 + </div>
36 + </div>
37 + </van-floating-panel>
38 + </van-config-provider>
39 + </div>
40 +</template>
41 +
42 +<script setup>
43 +import { ref } from 'vue'
44 +import { useRoute, useRouter } from 'vue-router';
45 +import $ from 'jquery';
46 +
47 +const props = defineProps({
48 + height: Number,
49 +});
50 +
51 +const themeVars = ref({
52 + floatingPanelHeaderHeight: 0,
53 + floatingPanelBorderRadius: '1.25rem'
54 +})
55 +
56 +const anchors = ref([0, (0.2 * window.innerHeight), (0.5 * window.innerHeight)]);
57 +
58 +const onHeightChange = ({ height }) => {
59 + if (!height) {
60 + onClose()
61 + }
62 +}
63 +
64 +const info_height = ref(0);
65 +
66 +watch(
67 + () => props.height,
68 + (v) => {
69 + info_height.value = v;
70 + }
71 +)
72 +
73 +const onClose = () => {
74 + audio.value.pause();
75 + pauseProgress();
76 + // 播放进度条
77 + $('.progress').css('background', 'conic-gradient(#f07142 0%, transparent 0%)');
78 + audio_list.value.forEach(item => item.play = false);
79 + emit('close');
80 +}
81 +
82 +const emit = defineEmits(['close']);
83 +
84 +const audio_index = ref();
85 +
86 +const audio_list = ref([]);
87 +
88 +onMounted(() => {
89 + audio_list.value = [
90 + {
91 + title: '3',
92 + src: 'https://img.tukuppt.com/newpreview_music/01/62/01/63b515415b482633.mp3',
93 + play: false
94 + },
95 + {
96 + title: '2',
97 + src: 'https://img.tukuppt.com/newpreview_music/01/66/20/63c0c3db3f8de739.mp3',
98 + play: false
99 + },
100 + {
101 + title: '风纺声(测试)(风的声音)风纺声(测试)(风的声音)',
102 + src: 'https://img.tukuppt.com/newpreview_music/01/65/86/63c0264040bd4441.mp3',
103 + play: false
104 + },
105 + ];
106 +
107 + let audioDuration = 60; // 音频总时长 (秒)
108 + let elapsedTime = 0; // 已播放的时间
109 +
110 + // nextTick(() => {
111 + // setPlayProgress(30)
112 + // })
113 +
114 +});
115 +
116 +const audio = ref(new Audio());
117 +const duration = ref(0); // 音频总时长
118 +const currentTime = ref(0); // 当前播放时间
119 +// let audioDuration = 30; // 音频总时长 (秒)
120 +let elapsedTime = 0; // 已播放的时间
121 +let intervalId = null; // 保存setInterval的ID
122 +
123 +
124 +// 函数:更新进度条
125 +function updateProgress(audioDuration, index) {
126 + let progressPercent = (elapsedTime / audioDuration) * 100;
127 + document.querySelector('.progress.checked').style.background =
128 + `conic-gradient(#f07142 ${progressPercent}%, transparent ${progressPercent}%)`;
129 +
130 + if (elapsedTime >= audioDuration) {
131 + clearInterval(intervalId); // 音频播放完成,停止更新
132 + let idx = index + 1;
133 + if (idx >= audio_list.value.length) {
134 + idx = 0;
135 + }
136 + handleAudioPlay(audio_list.value[idx], idx)
137 + }
138 +}
139 +
140 +// 函数:启动进度更新
141 +function startProgress(audioDuration, index) {
142 + intervalId = setInterval(() => {
143 + elapsedTime++;
144 + updateProgress(audioDuration, index);
145 + }, 1000);
146 +}
147 +
148 +// 函数:暂停进度更新
149 +function pauseProgress() {
150 + clearInterval(intervalId);
151 +}
152 +
153 +// 初始化进度条
154 +// startProgress();
155 +
156 +// const handleAudio = (item, index) => {
157 +// audio_index.value = index;
158 +// document.querySelector('.progress').style.background =
159 +// `conic-gradient(#f07142 0%, transparent 0%)`;
160 +// clearInterval(progressInterval); // 音频播放完成
161 +// setPlayProgress(item.duration);
162 +// }
163 +
164 +const handleAudioPause = (item, index) => {
165 + item.play = false;
166 + audio.value.pause();
167 + pauseProgress();
168 +}
169 +
170 +const updateDuration = () => {
171 + duration.value = Math.floor(audio.value.duration); // 获取音频时长
172 +};
173 +
174 +const updateTime = () => {
175 + currentTime.value = Math.floor(audio.value.currentTime); // 获取当前播放时间
176 +};
177 +
178 +// 监听 'loadedmetadata' 事件,确保在音频元数据加载后获取时长
179 +audio.value.addEventListener('loadedmetadata', updateDuration);
180 +
181 +// 监听 'timeupdate' 事件,实时更新当前播放时间
182 +audio.value.addEventListener('timeupdate', updateTime);
183 +
184 +const handleAudioPlay = (item, index) => {
185 + // 播放状态
186 + audio_list.value.forEach(item => item.play = false);
187 + if (audio_index.value !== index) {
188 + audio.value.src = item.src;
189 + }
190 + // 播放进度条
191 + $('.progress').css('background', 'conic-gradient(#f07142 0%, transparent 0%)')
192 + clearInterval(intervalId);
193 + let play_status = audio.value.play() // 播放
194 + if (play_status) {
195 + play_status.then(() => {
196 + item.play = true;
197 + // 存放到pinia里面控制
198 + // store.changeAudio(audio.value);
199 + // store.changeAudioSrc(audio.value.src);
200 + // store.changeAudioStatus('play');
201 + //
202 + startProgress(duration.value, index);
203 + if (audio_index.value === index) { // 点击同一音频
204 + } else {
205 + audio_index.value = index;
206 + elapsedTime = 0
207 + }
208 + }).catch((e) => {
209 + // 失败
210 + console.log('Operation is too fast, audio play fails')
211 + })
212 + }
213 +}
214 +</script>
215 +
216 +<style lang="less" scoped>
217 +.audio-list-page {
218 + .audio-item {
219 + display: flex;
220 + align-items: center;
221 + justify-content: space-between;
222 + padding: 1rem 0;
223 + .point {
224 + padding: 0.25rem;
225 + width: 1rem;
226 + height: 1rem;
227 + border-radius: 50%;
228 + display: flex;
229 + align-items: center;
230 + justify-content: center;
231 + margin-right: 1rem;
232 + &.checked {
233 + border: 1px solid rgba(221, 120, 80, 0.5);
234 + }
235 + &::after {
236 + content: '';
237 + width: 1rem;
238 + height: 1rem;
239 + border-radius: 50%;
240 + background-color: #DD7850;
241 + }
242 + }
243 + .text {
244 + flex: 1;
245 + // display: flex;
246 + align-items: center;
247 + color: #47525F99;
248 + &.checked {
249 + color: #47525F;
250 + }
251 + .text-center {
252 + color: #DD7850;
253 + font-size: 0.85rem;
254 + margin-left: 1rem;
255 + line-height: 1.25;
256 + }
257 + }
258 + }
259 +
260 + .progress-ring {
261 + position: relative;
262 + width: 2.5rem;
263 + height: 2.5rem;
264 +}
265 +
266 +.circle {
267 + position: relative;
268 + width: 100%;
269 + height: 100%;
270 + border-radius: 50%;
271 + // background-color: lightgray;
272 +}
273 +
274 +.pause-icon {
275 + z-index: 10;
276 + position: absolute;
277 + left:0.15rem;
278 + top: 0.15rem;
279 +}
280 +
281 +.progress {
282 + position: absolute;
283 + width: 100%;
284 + height: 100%;
285 + top: 0;
286 + left: 0;
287 + border-radius: 50%;
288 + background: conic-gradient(#f07142 0%, #f07142 0%, transparent 0%);
289 + z-index: 5;
290 + transform: rotate(0deg); /* 让进度条从顶部开始 */
291 +}
292 +}
293 +</style>
1 <!-- 1 <!--
2 * @Date: 2024-09-15 22:08:49 2 * @Date: 2024-09-15 22:08:49
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2024-10-08 10:30:17 4 + * @LastEditTime: 2024-10-08 17:37:42
5 * @FilePath: /map-demo/src/views/bieyuan/info.vue 5 * @FilePath: /map-demo/src/views/bieyuan/info.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -24,7 +24,13 @@ ...@@ -24,7 +24,13 @@
24 <div class="info-header"> 24 <div class="info-header">
25 <div style="display: flex; justify-content: space-between;"> 25 <div style="display: flex; justify-content: space-between;">
26 <p class="info-title">{{ page_details.name }}</p> 26 <p class="info-title">{{ page_details.name }}</p>
27 - <div v-if="page_details.path?.length > 1" @click="goTo()" class="info-btn">前往</div> 27 + <div style="display: flex;">
28 + <div @click="onClickAudioList" style="margin-right: 0.75rem;">
29 + <van-icon v-if="!audio_list_height" name="https://cdn.ipadbiz.cn/bieyuan/map/icon/%E8%AF%AD%E9%9F%B31@3x.png" size="1.65rem" />
30 + <van-icon v-else name="https://cdn.ipadbiz.cn/bieyuan/map/icon/%E8%AF%AD%E9%9F%B32@3x.png" size="1.65rem" />
31 + </div>
32 + <div v-if="page_details.path?.length > 1" @click="goTo()" class="info-btn">前往</div>
33 + </div>
28 </div> 34 </div>
29 <div class="info-sub-title">{{ page_details.note }}</div> 35 <div class="info-sub-title">{{ page_details.note }}</div>
30 </div> 36 </div>
...@@ -73,6 +79,8 @@ ...@@ -73,6 +79,8 @@
73 </van-image-preview> 79 </van-image-preview>
74 80
75 <van-back-top /> 81 <van-back-top />
82 +
83 + <audio-play-list :height="audio_list_height" @close="onCloseAudioList"></audio-play-list>
76 </div> 84 </div>
77 </template> 85 </template>
78 86
...@@ -82,6 +90,7 @@ import { useRoute, useRouter } from 'vue-router' ...@@ -82,6 +90,7 @@ import { useRoute, useRouter } from 'vue-router'
82 import { showImagePreview } from 'vant'; 90 import { showImagePreview } from 'vant';
83 import { storeToRefs } from 'pinia' 91 import { storeToRefs } from 'pinia'
84 import { mainStore } from '@/store'; 92 import { mainStore } from '@/store';
93 +import audioPlayList from '@/components/audioList.vue'
85 94
86 import $ from 'jquery'; 95 import $ from 'jquery';
87 96
...@@ -350,7 +359,17 @@ const show_shrink = computed(() => { ...@@ -350,7 +359,17 @@ const show_shrink = computed(() => {
350 return true; 359 return true;
351 } 360 }
352 return false; 361 return false;
353 -}) 362 +});
363 +
364 +const audio_list_height = ref(0);
365 +
366 +const onClickAudioList = () => {
367 + audio_list_height.value = (0.2 * window.innerHeight);
368 +}
369 +
370 +const onCloseAudioList = () => {
371 + audio_list_height.value = 0;
372 +}
354 </script> 373 </script>
355 374
356 <style lang="less"> 375 <style lang="less">
......