hookehuyr

把别院的详情做搬出来作为通用的详情页-Bob需求

1 /* 1 /*
2 * @Date: 2023-05-29 11:10:19 2 * @Date: 2023-05-29 11:10:19
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-01-23 16:22:10 4 + * @LastEditTime: 2025-01-26 09:55:58
5 * @FilePath: /map-demo/src/route.js 5 * @FilePath: /map-demo/src/route.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -70,6 +70,13 @@ export default [ ...@@ -70,6 +70,13 @@ export default [
70 }, 70 },
71 }, 71 },
72 { 72 {
73 + path: '/info',
74 + component: () => import('@/views/info.vue'),
75 + meta: {
76 + title: '详情页',
77 + },
78 + },
79 + {
73 path: '/map_cutter', 80 path: '/map_cutter',
74 name: '瓦片切图工具', 81 name: '瓦片切图工具',
75 component: () => import('@/views/mapCutter.vue'), 82 component: () => import('@/views/mapCutter.vue'),
......
1 +<!--
2 + * @Date: 2024-09-15 22:08:49
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-11-06 11:49:42
5 + * @FilePath: /map-demo/src/views/bieyuan/info.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="info-page">
10 + <div class="info-header-wrapper">
11 + <div v-if="showBack && page_details.banner?.length" style="position: absolute; top: 1rem; left: 0.5rem; z-index: 9;">
12 + <van-icon name="arrow-left" color="white" size="1.75rem" @click="goBack()" />
13 + </div>
14 + <van-config-provider :theme-vars="themeVars">
15 + <van-swipe class="my-swipe" indicator-color="#DD7850" lazy-render :autoplay="5000">
16 + <van-swipe-item v-for="(image, index) in page_details.banner" :key="index" style="position: relative;">
17 + <van-image fit="cover" width="100%" :height="img_height" :src="image" @click="onClickImg(index)" />
18 + <img src="https://cdn.ipadbiz.cn/bieyuan/map/icon/pageShade@3x.png" style="width: 100%; height: 5rem; position: absolute; right: 0; left: 0; bottom: 0;" alt="">
19 + </van-swipe-item>
20 + </van-swipe>
21 + </van-config-provider>
22 + <div class="header-z"></div>
23 + </div>
24 + <div class="info-content-wrapper">
25 + <div class="info-header">
26 + <div style="display: flex; justify-content: space-between;">
27 + <p class="info-title">{{ page_details.name }}</p>
28 + <div style="display: flex;">
29 + <div @click="onClickAudioList" style="margin-right: 0.75rem;">
30 + <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" />
31 + <van-icon v-else name="https://cdn.ipadbiz.cn/bieyuan/map/icon/%E8%AF%AD%E9%9F%B32@3x.png" size="1.65rem" />
32 + </div>
33 + <div v-if="page_details.path?.length > 1" @click="goTo()" class="info-btn">前往</div>
34 + </div>
35 + </div>
36 + <div class="info-sub-title">{{ page_details.note }}</div>
37 + </div>
38 + <div id="tab-wrapper" style="margin-top: 0.5rem;">
39 + <van-config-provider :theme-vars="themeVars">
40 + <van-tabs ref="tabsRef" v-model:active="active" @click-tab="clickTab" color="#DD7850" title-active-color="#DD7850" title-inactive-color="#DD7850" :shrink="show_shrink" sticky animated>
41 + <van-tab title="介 绍" v-if="page_details.introduction">
42 + <div class="info-content">
43 + <div id="introduction" v-html="page_details.introduction" style="padding: 0 1rem;"></div>
44 + </div>
45 + </van-tab>
46 + <van-tab title="故 事" v-if="page_details.story">
47 + <div class="info-content">
48 + <div id="story" v-html="page_details.story" style="padding: 0 1rem;"></div>
49 + </div>
50 + </van-tab>
51 + <van-tab title="体 验" v-if="page_details.experience">
52 + <div class="info-content">
53 + <div id="experience" v-html="page_details.experience" style="padding: 0 1rem;"></div>
54 + </div>
55 + <div v-if="page_details.experience_audio.length" class="audio-wrapper">
56 + <div @click="toggleHandleAudio(item, index)" :class="['audio-item', play_audio_index === index ? 'click' : '']" v-for="(item, index) in page_details.experience_audio" :key="index">
57 + <div>{{ item.description }}</div>
58 + <van-icon @click.stop="stopAudio(item, index)" v-if="item.play" size="2rem" name="stop-circle-o" color="#DD7850" />
59 + <van-icon v-else @click="playAudio(item, index)" size="2rem" name="https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png" />
60 + </div>
61 + </div>
62 + </van-tab>
63 + </van-tabs>
64 + </van-config-provider>
65 + </div>
66 + </div>
67 + <!-- <div class="info-logo" :style="{ marginBottom: audio_list_height ? `${audio_list_height * 1.5}px` : '3rem' }">
68 + <van-image width="3rem" height="3rem" fit="contain" src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_logo.png" />
69 + </div> -->
70 +
71 + <van-toast v-model:show="show_toast" style="padding: 0">
72 + <template #message>
73 + <p style="padding: 0.5rem 1rem;">{{ toast_text }}</p>
74 + </template>
75 + </van-toast>
76 +
77 + <van-image-preview v-model:show="show_preview" :images="preview_images" @change="onChange" doubleScale>
78 + <template v-slot:index>第{{ index + 1 }}张</template>
79 + </van-image-preview>
80 +
81 + <van-back-top />
82 +
83 + <audio-play-list :height="audio_list_height" :status="audio_status" @close="onCloseAudioList" @status="onStatusAudioList"></audio-play-list>
84 + </div>
85 +</template>
86 +
87 +<script setup>
88 +import { ref, watch, watchEffect } from 'vue'
89 +import { useRoute, useRouter } from 'vue-router'
90 +import { showImagePreview } from 'vant';
91 +import { storeToRefs } from 'pinia'
92 +import audioPlayList from '@/components/audioList.vue'
93 +import { mainStore, useTitle } from '@/utils/generatePackage'
94 +import wx from 'weixin-js-sdk';
95 +import $ from 'jquery';
96 +
97 +import { mapAPI } from '@/api/map.js'
98 +
99 +const store = mainStore();
100 +const { audio_status, audio_entity, audio_list_status, audio_list_entity } = storeToRefs(store);
101 +
102 +const $route = useRoute();
103 +const $router = useRouter();
104 +
105 +const themeVars = ref({
106 + swipeIndicatorInactiveBackground: '#fff',
107 + swipeIndicatorMargin: '1.5rem',
108 + tabFontSize: '0.95rem',
109 +});
110 +
111 +const props = defineProps({
112 + info: Object,
113 + height: Number
114 +});
115 +
116 +const page_details = ref({});
117 +
118 +watch(
119 + () => props.info,
120 + (v) => {
121 + if (v.details.length) {
122 + page_details.value = { ...v.details[0], position: v.position, path: v.path };
123 + // 获取浏览器可视范围的高度
124 + $('.info-page').height(props.height + 'px');
125 + }
126 + }
127 +)
128 +
129 +const images = ref([
130 + 'https://cdn.ipadbiz.cn/bieyuan/map/swiper_img.png',
131 + 'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20230612_201951.png',
132 + 'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png',
133 +]);
134 +
135 +const active = ref(0);
136 +const play_audio_index = ref(null);
137 +
138 +// const audioList = ref([{
139 +// text: '5分钟观呼吸',
140 +// src: 'https://cdn.ipadbiz.cn/bieyuan/map/audio/%E6%AD%A3%E5%BF%B5%E5%91%BC%E5%90%B8%EF%BC%8810%E5%88%86%E9%92%9F%EF%BC%89.mp3',
141 +// play: false,
142 +// }, {
143 +// text: '10分钟正念静坐',
144 +// src: 'https://cdn.ipadbiz.cn/bieyuan/map/audio/%E5%8D%81%E5%88%86%E9%92%9F%E6%AD%A3%E5%BF%B5%E9%9D%99%E5%9D%9020210510.mp3',
145 +// play: false,
146 +// }, {
147 +// text: '15分钟正念静坐',
148 +// src: 'https://cdn.ipadbiz.cn/bieyuan/map/audio/%E5%8D%81%E5%88%86%E9%92%9F%E6%AD%A3%E5%BF%B5%E9%9D%99%E5%9D%9020210510.mp3',
149 +// play: false,
150 +// }])
151 +const toggleHandleAudio = (item, index) => { // 切换播放或者暂停操作
152 + if (item.play) {
153 + stopAudio(item, index);
154 + } else {
155 + playAudio(item, index);
156 + }
157 +}
158 +
159 +const playAudio = (item, index) => {
160 + page_details.value.experience_audio.forEach(item => item.play = false);
161 + audio.value.src = item.src;
162 + // 后台有播放器运行时,先暂停
163 + if (audio_list_status.value === 'play'){
164 + audio_list_entity.value.pause();
165 + }
166 + play_audio_index.value = index;
167 + let play_status = audio.value.play() // 播放
168 + if (play_status) {
169 + play_status.then(() => {
170 + item.play = true;
171 + // 存放到pinia里面控制
172 + store.changeAudio(audio.value);
173 + store.changeAudioSrc(audio.value.src);
174 + store.changeAudioStatus('play');
175 + }).catch((e) => {
176 + // 失败
177 + console.log('Operation is too fast, audio play fails')
178 + })
179 + }
180 +}
181 +
182 +const stopAudio = (item, index) => {
183 + item.play = false;
184 + audio.value.pause();
185 +}
186 +
187 +const audio = ref(new Audio());
188 +const img_height = ref('15rem');
189 +const scrollTop = ref(0);
190 +
191 +onMounted(async () => {
192 + // 通过ID查询到标记点详情
193 + if (!props.info) {
194 + let id = $route.query.id;
195 + const { data } = await mapAPI({ i: id });
196 + const raw_list = data.list[0].list; // 获取标记点列表
197 + const marker_id = $route.query.marker_id;
198 + const current_marker = raw_list.filter(item => item.id == marker_id)[0];
199 + //
200 + page_details.value = { ...current_marker.details[0], position: current_marker.position, path: current_marker.path };
201 + // 富文本转义, 分割线样式转换
202 + page_details.value.introduction = page_details.value.introduction?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
203 + page_details.value.story = page_details.value.story?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
204 + page_details.value.experience = page_details.value.experience?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
205 + }
206 + // 介绍栏目,图片点击事件
207 + var imgs = $('#introduction').find('img');
208 + // 图片点击事件
209 + imgs.each(function(index, img) {
210 + $(img).on('click', function (e) {
211 + showImagePreview({
212 + images: [$(img).attr('src')],
213 + startPosition: 0,
214 + showIndex: false,
215 + onClose: () => {
216 + // console.log('close');
217 + }
218 + })
219 + });
220 + // 图片有2个像素的圆角
221 + $(img).css('border-radius', '5px');
222 + });
223 + // 获取屏幕宽度,设置高度4:3
224 + // 获取屏幕的宽度
225 + var screenWidth = $(window).width();
226 + // 计算4:3比例的高度
227 + var screenHeight = screenWidth * 3 / 4;
228 + // 设置容器的高度
229 + img_height.value = screenHeight + 'px';
230 + //
231 + nextTick(() => {
232 + $('.info-page').on('scroll', (evt) => {
233 + scrollTop.value = $(evt.currentTarget).scrollTop(); // 获取滚动的垂直距离
234 + })
235 + })
236 + // 如果是从浮动窗口点击进来音频播放,自动打开
237 + if ($route.query.source === 'click_audio') {
238 + setTimeout(() => {
239 + audio_list_height.value = (0.2 * window.innerHeight);
240 + // 修改当前路由参数,避免刷新再次播放
241 + $router.push({
242 + query: {
243 + ...$route.query,
244 + source: ''
245 + }
246 + });
247 + }, 500);
248 + }
249 + // 地图标题
250 + document.title = page_details.value.name;
251 + // 微信分享
252 + const shareData = {
253 + title: page_details.value.name, // 分享标题
254 + desc: '别院详情', // 分享描述
255 + link: location.origin + location.pathname + location.hash, // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
256 + imgUrl: '', // 分享图标
257 + success: function () {
258 + console.warn('设置成功');
259 + }
260 + }
261 + // 分享好友(微信好友或qq好友)
262 + wx.updateAppMessageShareData(shareData);
263 + // 分享到朋友圈或qq空间
264 + wx.updateTimelineShareData(shareData);
265 + // 分享到腾讯微博
266 + wx.onMenuShareWeibo(shareData);
267 +});
268 +
269 +// watchEffect(
270 +// () => useTitle(page_details.value.name) // 地图标题
271 +// )
272 +
273 +onUnmounted(() => { // 离开页面时关闭音频播放
274 + audio.value.pause();
275 + store.changeAudioStatus('pause');
276 +})
277 +
278 +const audio_play = (src, index) => {
279 + audio.value.src = src;
280 +}
281 +
282 +const outerStopAudio = () => {
283 + audio.value.pause();
284 +}
285 +
286 +const emit = defineEmits(["closeFloat", 'route']);
287 +
288 +const show_toast = ref(false);
289 +const toast_text = ref('');
290 +
291 +const goTo = () => { // 打开标记地图显示
292 + // 没有关联导航提示
293 + if (page_details.value.path.length <= 1) {
294 + show_toast.value = true;
295 + toast_text.value = '该标记点没有关联导航';
296 + return;
297 + }
298 + //
299 + if ($router.currentRoute.value.path === '/bieyuan/info') { // 详情页
300 + $router.push({
301 + path: '/bieyuan/map',
302 + query: {
303 + id: $route.query.id,
304 + marker_id: $route.query.marker_id
305 + }
306 + })
307 + } else { // 地图页
308 + //
309 + emit("closeFloat", false);
310 + //
311 + emit("route", {name: '参观路径', path: page_details.value.path});
312 + }
313 +}
314 +
315 +const goBack = () => { // 返回首页
316 + $router.push({
317 + path: '/bieyuan/map',
318 + query: {
319 + id: $route.query.id,
320 + }
321 + })
322 +}
323 +
324 +const showBack = computed(() => $router.currentRoute.value.path === '/bieyuan/info');
325 +
326 +const voicePause = () => {
327 + audio.value.pause();
328 + store.changeAudioStatus('pause');
329 +}
330 +
331 +const tabsRef = ref(null);
332 +const clickTab = (evt) => { // 标签切换
333 + tabsRef.value.resize();
334 + nextTick(() => {
335 + if (evt.title === '介 绍') { // 介绍
336 + var imgs = $('#introduction').find('img');
337 + }
338 + if (evt.title === '故 事') { // 故事
339 + var imgs = $('#story').find('img');
340 + }
341 + if (evt.title === '体 验') { // 体验
342 + var imgs = $('#experience').find('img');
343 + }
344 + // 图片点击事件
345 + imgs.each(function(index, img) {
346 + $(img).on('click', function (e) {
347 + showImagePreview({
348 + images: [$(img).attr('src')],
349 + startPosition: 0,
350 + showIndex: false,
351 + onClose: () => {
352 + // console.log('close');
353 + }
354 + })
355 + })
356 + // 图片有5个像素的圆角
357 + $(img).css('border-radius', '5px');
358 + });
359 + // 滚动高度大于tabs高度后才滚动到指定高度
360 + let offsetTop = $('#tab-wrapper')[0].offsetTop;
361 + if (scrollTop.value >= offsetTop) {
362 + $('.info-page').scrollTop(offsetTop);
363 + }
364 + });
365 +}
366 +
367 +watch(
368 + () => audio_status.value,
369 + (v) => {
370 + if (v === 'pause') {
371 + voicePause();
372 + page_details.value.experience_audio?.forEach(item => item.play = false);
373 + }
374 + },
375 + { immediate: true }
376 +);
377 +
378 +defineExpose({
379 + outerStopAudio
380 +})
381 +
382 +
383 +const show_preview = ref(false);
384 +const index = ref(0);
385 +const preview_images = [];
386 +const onChange = (newIndex) => {
387 + index.value = newIndex;
388 +};
389 +
390 +const onClickImg = (idx) => {
391 + showImagePreview({
392 + images: page_details.value.banner,
393 + startPosition: idx,
394 + showIndex: true,
395 + onClose: () => {
396 + // console.log('close');
397 + }
398 + })
399 +};
400 +
401 +const show_shrink = computed(() => {
402 + // 统计非空字段的个数
403 + let filledFields = 0;
404 +
405 + if (page_details.value.introduction) filledFields++;
406 + if (page_details.value.story) filledFields++;
407 + if (page_details.value.experience) filledFields++;
408 +
409 + // 判断是否只有一个字段有值
410 + if (filledFields === 1) {
411 + return true;
412 + }
413 + return false;
414 +});
415 +
416 +const audio_list_height = ref(0);
417 +
418 +const onClickAudioList = () => {
419 + if ($('.info-page').height() < $(window).height()) { // 在浮动模式下点击音频列表
420 + // 打开页面
421 + $router.push({
422 + path: '/bieyuan/info',
423 + query: {
424 + id: $route.query.id,
425 + marker_id: props.info.id,
426 + source: 'click_audio'
427 + }
428 + });
429 + } else { // 详情页内点击
430 + audio_list_height.value = (0.2 * window.innerHeight);
431 + }
432 +}
433 +
434 +const onCloseAudioList = () => {
435 + audio_list_height.value = 0;
436 +}
437 +
438 +// const show_audio = ref(true);
439 +
440 +const onStatusAudioList = (status) => { // 音频列表组件,状态改变
441 + page_details.value.experience_audio?.forEach(item => item.play = false);
442 + audio.value.pause();
443 + play_audio_index.value = null;
444 + store.changeAudioStatus('pause');
445 + // // 反馈播放列表数量为空时,隐藏图标
446 + // if (status === 'none') {
447 + // show_audio.value = false;
448 + // }
449 +}
450 +</script>
451 +
452 +<style lang="less">
453 +.info-page {
454 + background-color: #EBEBEB;
455 + height: 100vh;
456 + overflow: scroll;
457 + position: relative;
458 + .info-header-wrapper {
459 + position: relative;
460 + min-height: 2rem;
461 + .header-z {
462 + position: absolute;
463 + bottom: 0;
464 + left: 0;
465 + right: 0;
466 + height: 1rem;
467 + // box-shadow: rgba(241, 242, 248, 0.6) 0px -3px 25px 15px;
468 + // background-color: #f7f7f7;
469 + background-color: #fff;
470 + margin: 0 1rem;
471 + border-top-left-radius: 0.5rem;
472 + border-top-right-radius: 0.5rem;
473 + }
474 + }
475 + .info-content-wrapper {
476 + box-shadow: 0px -3px 6px 0 rgba(241, 242, 248, 0.8);
477 + margin: 1rem;
478 + margin-top: 0;
479 + // padding: 1rem;
480 + border-bottom-left-radius: 0.5rem;
481 + border-bottom-right-radius: 0.5rem;
482 + background-color: white;
483 + .info-header {
484 + padding: 1rem 2rem 0;
485 + // display: flex;
486 + // justify-content: space-between;
487 + // align-items: center;
488 + .info-title {
489 + font-size: 1.25rem;
490 + margin-bottom: 0.5rem;
491 + }
492 + .info-sub-title {
493 + font-size: 0.85rem;
494 + color: #A0A8B1;
495 + line-height: 1.75;
496 + }
497 + .info-btn {
498 + width: 3rem;
499 + height: 1.5rem;
500 + border: 1px solid #DD7850;
501 + color: #DD7850;
502 + border-radius: 0.8rem;
503 + font-size: 0.85rem;
504 + text-align: center;
505 + line-height: 1.5rem;
506 + }
507 + }
508 + .info-content {
509 + color: #47525F;
510 + padding: 1rem;
511 + line-height: 1.75;
512 + p {
513 + line-height: 1.75;
514 + // padding: 0 0.85rem;
515 + text-align: justify;
516 + img {
517 + width: 100%;
518 + }
519 + }
520 + }
521 + .audio-wrapper {
522 + padding: 1rem;
523 + .audio-item {
524 + color: #47525F;
525 + display: flex;
526 + justify-content: space-between;
527 + align-items: center;
528 + padding: 1rem;
529 + background-color: #FFF;
530 + border-radius: 0.25rem;
531 + margin: 1rem;
532 + box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);
533 + &.click {
534 + border: 1px solid #DD7850;
535 + }
536 + .audio-icon {
537 + width: 2rem;
538 + height: 2rem;
539 + background-image: url('https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png'); /* 使用上传的图标 */
540 + background-size: cover;
541 + &.click {
542 + animation: pulse 1.5s infinite;
543 + }
544 + }
545 +
546 + @keyframes pulse {
547 + 0% {
548 + transform: scale(1);
549 + }
550 + 50% {
551 + transform: scale(1.2);
552 + }
553 + 100% {
554 + transform: scale(1);
555 + }
556 + }
557 + }
558 + }
559 + }
560 + .info-logo {
561 + display: flex;
562 + justify-content: center;
563 + margin: 3rem;
564 + }
565 +
566 + .van-tabs__wrap {
567 + border-bottom: 1px solid #F3F3F3;
568 + }
569 +
570 + .van-tabs__nav--line.van-tabs__nav--shrink {
571 + padding-left: 2rem;
572 + }
573 +}
574 +
575 +.van-back-top {
576 + background-color: #DD7850;
577 +}
578 +</style>