hookehuyr

feat(路由): 新增签到模块相关页面和路由配置

添加签到模块的首页、扫码页和信息详情页
配置签到模块的路由路径
实现音频播放、图片预览等功能
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-02-10 16:19:29 4 + * @LastEditTime: 2025-08-26 15:46:05
5 * @FilePath: /map-demo/src/route.js 5 * @FilePath: /map-demo/src/route.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -112,4 +112,18 @@ export default [ ...@@ -112,4 +112,18 @@ export default [
112 title: '详情页', 112 title: '详情页',
113 }, 113 },
114 }, 114 },
115 + {
116 + path: '/checkin',
117 + component: () => import('@/views/checkin/map.vue'),
118 + meta: {
119 + title: '地图',
120 + },
121 + },
122 + {
123 + path: '/checkin/info',
124 + component: () => import('@/views/checkin/info.vue'),
125 + meta: {
126 + title: '详情页',
127 + },
128 + },
115 ]; 129 ];
......
1 +<!--
2 + * @Date: 2024-09-14 17:48:55
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-09-21 23:04:40
5 + * @FilePath: /map-demo/src/views/bieyuan/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="index-page">
10 + <div style="display: flex; flex-direction: column; align-items: center;">
11 + <van-image
12 + width="12rem"
13 + height="12rem"
14 + fit="contain"
15 + src="https://cdn.ipadbiz.cn/bieyuan/map/icon/index_logo@3x.png"
16 + />
17 + <div style="margin-top: 2rem; font-size: 0.95rem; letter-spacing: 5px; color: #47525F;">山水逢甘露,静心遇桃源</div>
18 + </div>
19 + <div @click="goTo" style="border: 1px solid #DD7850; padding: 0.8rem 5.5rem; border-radius: 5px; font-size: 1.15rem; color: #DD7850; background-color: white;">进&nbsp;入</div>
20 + </div>
21 +</template>
22 +
23 +<script setup>
24 +import { ref } from 'vue'
25 +import { useRoute, useRouter } from 'vue-router'
26 +
27 +//import { } from '@/utils/generateModules.js'
28 +//import { } from '@/utils/generateIcons.js'
29 +//import { } from '@/composables'
30 +const $route = useRoute();
31 +const $router = useRouter();
32 +
33 +const goTo = () => {
34 + $router.push({
35 + path: './map',
36 + query: {
37 + id: $route.query.id
38 + }
39 + });
40 + // 进入标记
41 + localStorage.setItem('first_in_bieyuan', 1);
42 +}
43 +
44 +onMounted(() => {
45 + // 记录第一次进入页面,之后判断是否第一次进入,直接跳转到MAP页面
46 + if (localStorage.getItem('first_in_bieyuan') === '1') {
47 + $router.push({
48 + path: './map',
49 + query: {
50 + id: $route.query.id
51 + }
52 + });
53 + }
54 +});
55 +</script>
56 +
57 +<style lang="less" scoped>
58 + .index-page {
59 + height: 100vh;
60 + display: flex;
61 + flex-direction: column;
62 + justify-content: space-evenly;
63 + align-items: center;
64 + background: linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)),
65 + url('https://cdn.ipadbiz.cn/bieyuan/map/MAP@3x.png');
66 + background-size: contain;
67 + }
68 +</style>
1 +<!--
2 + * @Date: 2024-09-15 22:08:49
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-03-22 21:33:41
5 + * @FilePath: /map-demo/src/views/by/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 v-if="page_details.show_audio" @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 @click="goToWalk()" class="info-btn">前往</div>
35 + </div>
36 + </div>
37 + <div class="info-sub-title">{{ page_details.note }}</div>
38 + </div>
39 + <div id="tab-wrapper" style="margin-top: 0.5rem;">
40 + <van-config-provider :theme-vars="themeVars">
41 + <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>
42 + <van-tab title="介 绍" v-if="page_details.introduction">
43 + <div class="info-content">
44 + <div id="introduction" v-html="page_details.introduction" style="padding: 0 1rem;"></div>
45 + </div>
46 + </van-tab>
47 + <van-tab title="故 事" v-if="page_details.story">
48 + <div class="info-content">
49 + <div id="story" v-html="page_details.story" style="padding: 0 1rem;"></div>
50 + </div>
51 + </van-tab>
52 + <van-tab title="体 验" v-if="page_details.experience">
53 + <div class="info-content">
54 + <div id="experience" v-html="page_details.experience" style="padding: 0 1rem;"></div>
55 + </div>
56 + <div v-if="page_details.experience_audio.length" class="audio-wrapper">
57 + <div @click="toggleHandleAudio(item, index)" :class="['audio-item', play_audio_index === index ? 'click' : '']" v-for="(item, index) in page_details.experience_audio" :key="index">
58 + <div>{{ item.description }}</div>
59 + <van-icon @click.stop="stopAudio(item, index)" v-if="item.play" size="2rem" name="stop-circle-o" color="#DD7850" />
60 + <van-icon v-else @click="playAudio(item, index)" size="2rem" name="https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png" />
61 + </div>
62 + </div>
63 + </van-tab>
64 + </van-tabs>
65 + </van-config-provider>
66 + </div>
67 + </div>
68 + <!-- <div class="info-logo" :style="{ marginBottom: audio_list_height ? `${audio_list_height * 1.5}px` : '3rem' }">
69 + <van-image width="3rem" height="3rem" fit="contain" src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_logo.png" />
70 + </div> -->
71 +
72 + <van-toast v-model:show="show_toast" style="padding: 0">
73 + <template #message>
74 + <p style="padding: 0.5rem 1rem;">{{ toast_text }}</p>
75 + </template>
76 + </van-toast>
77 +
78 + <van-image-preview v-model:show="show_preview" :images="preview_images" @change="onChange" doubleScale>
79 + <template v-slot:index>第{{ index + 1 }}张</template>
80 + </van-image-preview>
81 +
82 + <van-back-top />
83 +
84 + <audio-play-list :height="audio_list_height" :status="audio_status" @close="onCloseAudioList" @status="onStatusAudioList"></audio-play-list>
85 + </div>
86 +</template>
87 +
88 +<script setup>
89 +import { ref, watch, watchEffect } from 'vue'
90 +import { useRoute, useRouter } from 'vue-router'
91 +import { showImagePreview } from 'vant';
92 +import { storeToRefs } from 'pinia'
93 +import audioPlayList from '@/components/audioList.vue'
94 +import { mainStore, useTitle } from '@/utils/generatePackage'
95 +import wx from 'weixin-js-sdk';
96 +import $ from 'jquery';
97 +
98 +import { mapAPI, mapAudioAPI } from '@/api/map.js'
99 +
100 +const store = mainStore();
101 +const { audio_status, audio_entity, audio_list_status, audio_list_entity } = storeToRefs(store);
102 +
103 +const $route = useRoute();
104 +const $router = useRouter();
105 +
106 +const themeVars = ref({
107 + swipeIndicatorInactiveBackground: '#fff',
108 + swipeIndicatorMargin: '1.5rem',
109 + tabFontSize: '0.95rem',
110 +});
111 +
112 +const props = defineProps({
113 + info: Object,
114 + height: Number
115 +});
116 +
117 +const page_details = ref({});
118 +
119 +watch(
120 + () => props.info,
121 + (v) => {
122 + if (v.details.length) {
123 + page_details.value = { ...v.details[0], position: v.position, path: v.path };
124 + // 获取浏览器可视范围的高度
125 + $('.info-page').height(props.height + 'px');
126 + }
127 + }
128 +)
129 +
130 +const images = ref([
131 + 'https://cdn.ipadbiz.cn/bieyuan/map/swiper_img.png',
132 + 'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20230612_201951.png',
133 + 'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png',
134 +]);
135 +
136 +const active = ref(0);
137 +const play_audio_index = ref(null);
138 +
139 +// const audioList = ref([{
140 +// text: '5分钟观呼吸',
141 +// 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',
142 +// play: false,
143 +// }, {
144 +// text: '10分钟正念静坐',
145 +// 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',
146 +// play: false,
147 +// }, {
148 +// text: '15分钟正念静坐',
149 +// 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',
150 +// play: false,
151 +// }])
152 +const toggleHandleAudio = (item, index) => { // 切换播放或者暂停操作
153 + if (item.play) {
154 + stopAudio(item, index);
155 + } else {
156 + playAudio(item, index);
157 + }
158 +}
159 +
160 +const playAudio = (item, index) => {
161 + page_details.value.experience_audio.forEach(item => item.play = false);
162 + audio.value.src = item.src;
163 + // 后台有播放器运行时,先暂停
164 + if (audio_list_status.value === 'play'){
165 + audio_list_entity.value.pause();
166 + }
167 + play_audio_index.value = index;
168 + let play_status = audio.value.play() // 播放
169 + if (play_status) {
170 + play_status.then(() => {
171 + item.play = true;
172 + // 存放到pinia里面控制
173 + store.changeAudio(audio.value);
174 + store.changeAudioSrc(audio.value.src);
175 + store.changeAudioStatus('play');
176 + }).catch((e) => {
177 + // 失败
178 + console.log('Operation is too fast, audio play fails')
179 + })
180 + }
181 +}
182 +
183 +const stopAudio = (item, index) => {
184 + item.play = false;
185 + audio.value.pause();
186 +}
187 +
188 +const audio = ref(new Audio());
189 +const img_height = ref('15rem');
190 +const scrollTop = ref(0);
191 +
192 +onMounted(async () => {
193 + // 通过ID查询到标记点详情
194 + if (!props.info) {
195 + let id = $route.query.id;
196 + const { data } = await mapAPI({ i: id });
197 + const raw_list = data.list[0].list; // 获取标记点列表
198 + const marker_id = $route.query.marker_id;
199 + const current_marker = raw_list.filter(item => item.id == marker_id)[0];
200 + //
201 + page_details.value = { ...current_marker.details[0], position: current_marker.position, path: current_marker.path };
202 + // 富文本转义, 分割线样式转换
203 + page_details.value.introduction = page_details.value.introduction?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
204 + page_details.value.story = page_details.value.story?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
205 + page_details.value.experience = page_details.value.experience?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
206 + // 查询是否有音频列表,打标记点是否有音频
207 + const getAudioList = await mapAudioAPI({ mid: $route.query.id, bid: marker_id });
208 + if (getAudioList.data && getAudioList.data.length) {
209 + page_details.value.show_audio = true;
210 + }
211 + }
212 + // 介绍栏目,图片点击事件
213 + var imgs = $('#introduction').find('img');
214 + // 图片点击事件
215 + imgs.each(function(index, img) {
216 + $(img).on('click', function (e) {
217 + showImagePreview({
218 + images: [$(img).attr('src')],
219 + startPosition: 0,
220 + showIndex: false,
221 + onClose: () => {
222 + // console.log('close');
223 + }
224 + })
225 + });
226 + // 图片有2个像素的圆角
227 + $(img).css('border-radius', '5px');
228 + });
229 + // 获取屏幕宽度,设置高度4:3
230 + // 获取屏幕的宽度
231 + var screenWidth = $(window).width();
232 + // 计算4:3比例的高度
233 + var screenHeight = screenWidth * 3 / 4;
234 + // 设置容器的高度
235 + img_height.value = screenHeight + 'px';
236 + //
237 + nextTick(() => {
238 + $('.info-page').on('scroll', (evt) => {
239 + scrollTop.value = $(evt.currentTarget).scrollTop(); // 获取滚动的垂直距离
240 + })
241 + })
242 + // 如果是从浮动窗口点击进来音频播放,自动打开
243 + if ($route.query.source === 'click_audio') {
244 + setTimeout(() => {
245 + audio_list_height.value = (0.2 * window.innerHeight);
246 + // 修改当前路由参数,避免刷新再次播放
247 + $router.push({
248 + query: {
249 + ...$route.query,
250 + source: ''
251 + }
252 + });
253 + }, 500);
254 + }
255 + // 地图标题
256 + document.title = page_details.value.name;
257 + // 微信分享
258 + const shareData = {
259 + title: page_details.value.name, // 分享标题
260 + desc: '别院详情', // 分享描述
261 + link: location.origin + location.pathname + location.hash, // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
262 + imgUrl: '', // 分享图标
263 + success: function () {
264 + // console.warn('设置成功');
265 + }
266 + }
267 + // 分享好友(微信好友或qq好友)
268 + wx.updateAppMessageShareData(shareData);
269 + // 分享到朋友圈或qq空间
270 + wx.updateTimelineShareData(shareData);
271 + // 分享到腾讯微博
272 + wx.onMenuShareWeibo(shareData);
273 +});
274 +
275 +// watchEffect(
276 +// () => useTitle(page_details.value.name) // 地图标题
277 +// )
278 +
279 +onUnmounted(() => { // 离开页面时关闭音频播放
280 + audio.value.pause();
281 + store.changeAudioStatus('pause');
282 +})
283 +
284 +const audio_play = (src, index) => {
285 + audio.value.src = src;
286 +}
287 +
288 +const outerStopAudio = () => {
289 + audio.value.pause();
290 +}
291 +
292 +const emit = defineEmits(["closeFloat", 'route', 'walkRoute']);
293 +
294 +const show_toast = ref(false);
295 +const toast_text = ref('');
296 +
297 +const goTo = () => { // 打开标记地图显示
298 + // 没有关联导航提示
299 + if (page_details.value.path.length <= 1) {
300 + show_toast.value = true;
301 + toast_text.value = '该标记点没有关联导航';
302 + return;
303 + }
304 + //
305 + if ($router.currentRoute.value.path === '/by/info') { // 详情页
306 + $router.push({
307 + path: '/by',
308 + query: {
309 + id: $route.query.id,
310 + marker_id: $route.query.marker_id
311 + }
312 + })
313 + } else { // 地图页
314 + //
315 + emit("closeFloat", false);
316 + //
317 + emit("route", {name: '参观路径', path: page_details.value.path});
318 + }
319 +}
320 +
321 +const goToWalk = async () => { // 打开步行导航地图显示
322 + //
323 + if ($router.currentRoute.value.path === '/by/info') { // 详情页
324 + $router.push({
325 + path: '/by',
326 + query: {
327 + id: $route.query.id,
328 + marker_id: $route.query.marker_id
329 + }
330 + })
331 + } else { // 地图页
332 + //
333 + emit("closeFloat", false);
334 + //
335 + if (page_details.value?.position.length) {
336 + emit("walkRoute", {name: '步行导航', point: [+page_details.value?.position[0], +page_details.value?.position[1]]});
337 + } else {
338 + show_toast.value = true;
339 + toast_text.value = '该标记点没有关联导航';
340 + }
341 + }
342 +}
343 +
344 +const goBack = () => { // 返回首页
345 + $router.push({
346 + path: '/by',
347 + query: {
348 + id: $route.query.id,
349 + }
350 + })
351 +}
352 +
353 +const showBack = computed(() => $router.currentRoute.value.path === '/by/info');
354 +
355 +const voicePause = () => {
356 + audio.value.pause();
357 + store.changeAudioStatus('pause');
358 +}
359 +
360 +const tabsRef = ref(null);
361 +const clickTab = (evt) => { // 标签切换
362 + tabsRef.value.resize();
363 + nextTick(() => {
364 + if (evt.title === '介 绍') { // 介绍
365 + var imgs = $('#introduction').find('img');
366 + }
367 + if (evt.title === '故 事') { // 故事
368 + var imgs = $('#story').find('img');
369 + }
370 + if (evt.title === '体 验') { // 体验
371 + var imgs = $('#experience').find('img');
372 + }
373 + // 图片点击事件
374 + imgs.each(function(index, img) {
375 + $(img).on('click', function (e) {
376 + showImagePreview({
377 + images: [$(img).attr('src')],
378 + startPosition: 0,
379 + showIndex: false,
380 + onClose: () => {
381 + // console.log('close');
382 + }
383 + })
384 + })
385 + // 图片有5个像素的圆角
386 + $(img).css('border-radius', '5px');
387 + });
388 + // 滚动高度大于tabs高度后才滚动到指定高度
389 + let offsetTop = $('#tab-wrapper')[0].offsetTop;
390 + if (scrollTop.value >= offsetTop) {
391 + $('.info-page').scrollTop(offsetTop);
392 + }
393 + });
394 +}
395 +
396 +watch(
397 + () => audio_status.value,
398 + (v) => {
399 + if (v === 'pause') {
400 + voicePause();
401 + page_details.value.experience_audio?.forEach(item => item.play = false);
402 + }
403 + },
404 + { immediate: true }
405 +);
406 +
407 +defineExpose({
408 + outerStopAudio
409 +})
410 +
411 +
412 +const show_preview = ref(false);
413 +const index = ref(0);
414 +const preview_images = [];
415 +const onChange = (newIndex) => {
416 + index.value = newIndex;
417 +};
418 +
419 +const onClickImg = (idx) => {
420 + if ($('.info-page').height() >= $(window).height()) {
421 + showImagePreview({
422 + images: page_details.value.banner,
423 + startPosition: idx,
424 + showIndex: true,
425 + onClose: () => {
426 + // console.log('close');
427 + }
428 + })
429 + }
430 +};
431 +
432 +const show_shrink = computed(() => {
433 + // 统计非空字段的个数
434 + let filledFields = 0;
435 +
436 + if (page_details.value.introduction) filledFields++;
437 + if (page_details.value.story) filledFields++;
438 + if (page_details.value.experience) filledFields++;
439 +
440 + // 判断是否只有一个字段有值
441 + if (filledFields === 1) {
442 + return true;
443 + }
444 + return false;
445 +});
446 +
447 +const audio_list_height = ref(0);
448 +
449 +const onClickAudioList = () => {
450 + if ($('.info-page').height() < $(window).height()) { // 在浮动模式下点击音频列表
451 + // 打开页面
452 + $router.push({
453 + path: '/by/info',
454 + query: {
455 + id: $route.query.id,
456 + marker_id: props.info.id,
457 + source: 'click_audio'
458 + }
459 + });
460 + } else { // 详情页内点击
461 + audio_list_height.value = (0.2 * window.innerHeight);
462 + }
463 +}
464 +
465 +const onCloseAudioList = () => {
466 + audio_list_height.value = 0;
467 +}
468 +
469 +// const show_audio = ref(true);
470 +
471 +const onStatusAudioList = (status) => { // 音频列表组件,状态改变
472 + page_details.value.experience_audio?.forEach(item => item.play = false);
473 + audio.value.pause();
474 + play_audio_index.value = null;
475 + store.changeAudioStatus('pause');
476 + // // 反馈播放列表数量为空时,隐藏图标
477 + // if (status === 'none') {
478 + // show_audio.value = false;
479 + // }
480 +}
481 +</script>
482 +
483 +<style lang="less">
484 +.info-page {
485 + background-color: #EBEBEB;
486 + height: 100vh;
487 + overflow: scroll;
488 + position: relative;
489 + .info-header-wrapper {
490 + position: relative;
491 + min-height: 2rem;
492 + .header-z {
493 + position: absolute;
494 + bottom: 0;
495 + left: 0;
496 + right: 0;
497 + height: 1rem;
498 + // box-shadow: rgba(241, 242, 248, 0.6) 0px -3px 25px 15px;
499 + // background-color: #f7f7f7;
500 + background-color: #fff;
501 + margin: 0 1rem;
502 + border-top-left-radius: 0.5rem;
503 + border-top-right-radius: 0.5rem;
504 + }
505 + }
506 + .info-content-wrapper {
507 + box-shadow: 0px -3px 6px 0 rgba(241, 242, 248, 0.8);
508 + margin: 1rem;
509 + margin-top: 0;
510 + // padding: 1rem;
511 + border-bottom-left-radius: 0.5rem;
512 + border-bottom-right-radius: 0.5rem;
513 + background-color: white;
514 + .info-header {
515 + padding: 1rem 2rem 0;
516 + // display: flex;
517 + // justify-content: space-between;
518 + // align-items: center;
519 + .info-title {
520 + font-size: 1.25rem;
521 + margin-bottom: 0.5rem;
522 + }
523 + .info-sub-title {
524 + font-size: 0.85rem;
525 + color: #A0A8B1;
526 + line-height: 1.75;
527 + }
528 + .info-btn {
529 + width: 3rem;
530 + height: 1.5rem;
531 + border: 1px solid #DD7850;
532 + color: #DD7850;
533 + border-radius: 0.8rem;
534 + font-size: 0.85rem;
535 + text-align: center;
536 + line-height: 1.5rem;
537 + }
538 + }
539 + .info-content {
540 + color: #47525F;
541 + padding: 1rem;
542 + line-height: 1.75;
543 + p {
544 + line-height: 1.75;
545 + // padding: 0 0.85rem;
546 + text-align: justify;
547 + img {
548 + width: 100%;
549 + }
550 + }
551 + }
552 + .audio-wrapper {
553 + padding: 1rem;
554 + .audio-item {
555 + color: #47525F;
556 + display: flex;
557 + justify-content: space-between;
558 + align-items: center;
559 + padding: 1rem;
560 + background-color: #FFF;
561 + border-radius: 0.25rem;
562 + margin: 1rem;
563 + box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);
564 + &.click {
565 + border: 1px solid #DD7850;
566 + }
567 + .audio-icon {
568 + width: 2rem;
569 + height: 2rem;
570 + background-image: url('https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png'); /* 使用上传的图标 */
571 + background-size: cover;
572 + &.click {
573 + animation: pulse 1.5s infinite;
574 + }
575 + }
576 +
577 + @keyframes pulse {
578 + 0% {
579 + transform: scale(1);
580 + }
581 + 50% {
582 + transform: scale(1.2);
583 + }
584 + 100% {
585 + transform: scale(1);
586 + }
587 + }
588 + }
589 + }
590 + }
591 + .info-logo {
592 + display: flex;
593 + justify-content: center;
594 + margin: 3rem;
595 + }
596 +
597 + .van-tabs__wrap {
598 + border-bottom: 1px solid #F3F3F3;
599 + }
600 +
601 + .van-tabs__nav--line.van-tabs__nav--shrink {
602 + padding-left: 2rem;
603 + }
604 +}
605 +
606 +.van-back-top {
607 + background-color: #DD7850;
608 +}
609 +</style>
1 +<!--
2 + * @Date: 2024-09-15 22:08:49
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-02-10 16:11:41
5 + * @FilePath: /map-demo/src/views/by/info_w.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="info-page">
10 + <div>
11 + <van-config-provider :theme-vars="themeVars">
12 + <van-swipe class="my-swipe" indicator-color="#DD7850" lazy-render :autoplay="5000">
13 + <van-swipe-item v-for="image in images" :key="image">
14 + <van-image fit="cover" width="100%" height="13rem" :src="image" />
15 + </van-swipe-item>
16 + </van-swipe>
17 + </van-config-provider>
18 + </div>
19 + <div class="info-content-wrapper">
20 + <div class="info-header">
21 + <div>
22 + <p class="info-title">选佛场</p>
23 + <p class="info-sub-title">南楼2层</p>
24 + </div>
25 + <div @click="goTo()" class="info-btn">前往</div>
26 + </div>
27 + <div class="van-hairline--bottom">
28 + <van-tabs v-model:active="active" color="#DD7850" title-active-color="#DD7850" sticky>
29 + <van-tab title="介 绍">
30 + <div class="info-content">
31 + <p style="line-height: 1.75; padding: 0 0.85rem; color: #47525F;">选佛场是一个宗教活动场所,集禅堂与讲堂功能于一体。其设计仿制古代石窟样式,把古代人修行的场所“石窟”搬进室内。禅堂内的释迦牟尼佛像,仿麦积山石窟第44号特窟“东方的微笑”的造型。禅堂设计自然古朴,匠心独运,星光、烛光、月光三光辉映,营造出“一个人不孤单,一千人不喧闹”的宁静祥和的氛围。禅堂可容纳千人,开展讲经、禅修、法会等多种活动,提供礼佛、供灯、静坐等体验。</p>
32 + <div class="van-hairline--bottom" style="margin: 1rem 0;"></div>
33 + <div style="padding: 1rem;">
34 + <div style="color: #DD7850;">•&nbsp;五方塔</div>
35 + <div style="color: #47525F; margin-top: 1rem; line-height: 1.75;">禅堂外的草坪,安立着大小不一五座佛塔。信众可绕塔或悬挂风铃祝愿祈福,在肃静庄严的氛围中得到佛力加持,自净其意,心想事成。</div>
36 + </div>
37 + </div>
38 + </van-tab>
39 + <van-tab title="故 事">
40 + <div style="padding: 0 1rem;">
41 + <div style="padding: 1rem;">
42 + <div style="color: #DD7850;">•&nbsp;选官何如选佛</div>
43 + </div>
44 + <div style="padding: 0 1rem;">
45 + <van-image width="100%" height="11rem" fit="cover" src="https://cdn.ipadbiz.cn/bieyuan/map/Mix_20230612_201951.png" />
46 + </div>
47 + <div style="padding: 1rem;">
48 + <p style="color: #47525F; line-height: 1.75;">过去把禅堂叫作选佛场,意思是选择作佛的场所。这个典故与丹霞禅师有关。 <br />他原本是一个秀才,赴京赶考的途中遇到一位禅师,这位禅师跟他讲,选官何如选佛?考官还不如成佛利益更大。世间功名如过眼云烟,即便追求得到也是暂时利益,执著于此就会烦恼重重,甚至不断造业。而学佛修行,考佛就是要成佛,成就生命永恒的福祉。这是永久的利益,尽未来际的利益。每个生命原本具备觉悟的潜质,具有无尽的功德宝藏,取之不尽用之不竭。成佛,可以断除一切迷惑烦恼,可以彻底地开发我们生命的潜质,全然觉醒,这个利益无量无边。丹霞禅师深具慧根,一经点拨,马上出家。</p>
49 + </div>
50 + <div class="van-hairline--bottom" style="margin: 0 1rem;"></div>
51 + </div>
52 + <div style="padding: 0 1rem;">
53 + <div style="padding: 1rem;">
54 + <div style="color: #DD7850;">•&nbsp;把洞窟搬进讲堂</div>
55 + </div>
56 + <div style="padding: 0 1rem;">
57 + <van-image width="100%" height="11rem" fit="cover" src="https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png" />
58 + </div>
59 + <div style="padding: 1rem;">
60 + <p style="color: #47525F; line-height: 1.75;">洞窟,是传统的佛教建筑形式,最早在印度盛行,古代僧人喜欢在崇山峻岭的幽僻处开凿洞窟,遁世修行。选佛场集禅堂与讲堂的功能于一体,把洞窟搬进讲堂,既有回归佛教本怀的宁静温暖,又体现出融入泰宁岩穴文化的祥和之气。</p>
61 + </div>
62 + <div class="van-hairline--bottom" style="margin: 0 1rem;"></div>
63 + </div>
64 + </van-tab>
65 + <van-tab title="体 验">
66 + <div style="padding: 0 1rem;">
67 + <div style="padding: 1rem;">
68 + <div style="color: #DD7850;">•&nbsp;供灯</div>
69 + <div style="color: #47525F; margin-top: 1rem; line-height: 1.75;">禅堂内自助供灯。</div>
70 + </div>
71 + <div style="padding: 0 1rem;">
72 + <van-image width="100%" height="11rem" fit="cover" src="https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png" />
73 + </div>
74 + <div class="van-hairline--bottom" style="margin: 0 1rem;"></div>
75 + </div>
76 + <div class="audio-wrapper">
77 + <div :class="['audio-item', play_audio_index === index ? 'click' : '']" v-for="(item, index) in audioList" :key="index">
78 + <div>{{ item.text }}</div>
79 + <!-- <div :class="['audio-icon', play_audio_index === index ? 'click' : '']"></div> -->
80 + <van-icon @click="stopAudio(item, index)" v-if="item.play" size="2rem" name="stop-circle-o" color="#DD7850" />
81 + <van-icon v-else @click="playAudio(item, index)" size="2rem" name="https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png" />
82 + </div>
83 + </div>
84 + <div style="padding: 0 1rem;">
85 + <img src="https://cdn.ipadbiz.cn/bieyuan/map/xcx.png" style="width: 100%;">
86 + </div>
87 + </van-tab>
88 + </van-tabs>
89 + </div>
90 + </div>
91 + <!-- <div style="display: flex; justify-content: center; margin: 3rem;">
92 + <van-image
93 + width="3rem"
94 + height="3rem"
95 + fit="contain"
96 + src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_logo.png"
97 + />
98 + </div> -->
99 + </div>
100 +</template>
101 +
102 +<script setup>
103 +import { ref, watch } from 'vue'
104 +import { useRoute, useRouter } from 'vue-router'
105 +
106 +import { storeToRefs } from 'pinia'
107 +import { mainStore } from '@/store';
108 +
109 +const store = mainStore();
110 +const { audio_status, audio_entity } = storeToRefs(store);
111 +
112 +const $route = useRoute();
113 +const $router = useRouter();
114 +
115 +const themeVars = ref({
116 + swipeIndicatorInactiveBackground: '#fff',
117 +});
118 +
119 +const images = ref([
120 + 'https://cdn.ipadbiz.cn/bieyuan/map/swiper_img.png',
121 + 'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20230612_201951.png',
122 + 'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png',
123 +]);
124 +
125 +const active = ref(0);
126 +const play_audio_index = ref(null);
127 +
128 +const audioList = ref([{
129 + text: '5分钟观呼吸',
130 + 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',
131 + play: false,
132 +}, {
133 + text: '10分钟正念静坐',
134 + 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',
135 + play: false,
136 +}, {
137 + text: '15分钟正念静坐',
138 + 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',
139 + play: false,
140 +}])
141 +
142 +const playAudio = (item, index) => {
143 + audioList.value.forEach(item => item.play = false);
144 + audio.value.src = item.src;
145 + play_audio_index.value = index;
146 + let play_status = audio.value.play() // 播放
147 + if (play_status) {
148 + console.warn('start');
149 + // if (audio_status.value === 'play') {
150 + // audio_entity.value.pause();
151 + // }
152 + play_status.then(() => {
153 + console.warn('success');
154 + item.play = true;
155 + // 存放到pinia里面控制
156 + // store.changeAudioSrc(audio.value.src);
157 + // store.changeAudioStatus('play');
158 + }).catch((e) => {
159 + // 失败
160 + console.log('Operation is too fast, audio play fails')
161 + })
162 + }
163 +}
164 +
165 +const stopAudio = (item, index) => {
166 + item.play = false;
167 + audio.value.pause();
168 +}
169 +
170 +const audio = ref(new Audio());
171 +
172 +onMounted(() => {
173 + // 存放到pinia里面控制
174 + store.changeAudio(audio.value);
175 + // store.changeAudioStatus('pause');
176 +})
177 +
178 +onUnmounted(() => {
179 + audio.value.pause();
180 + store.changeAudioStatus('pause');
181 +})
182 +
183 +const audio_play = (src, index) => {
184 + audio.value.src = src;
185 +}
186 +
187 +const outerStopAudio = () => {
188 + audio.value.pause();
189 +}
190 +
191 +const emit = defineEmits(["closeFloat", 'route']);
192 +const goTo = () => { // 打开标记地图显示
193 + if ($router.currentRoute.value.path === '/by/info') { // 详情页
194 + $router.push({
195 + path: '/by',
196 + query: {
197 + id: $route.query.id,
198 + marker_id: '12345'
199 + }
200 + })
201 + } else { // 地图页
202 + //
203 + emit("closeFloat", false);
204 + //
205 + emit("route", 'marker_id');
206 + }
207 +}
208 +
209 +const voicePause = () => {
210 + audio.value.pause();
211 + store.changeAudioStatus('pause');
212 +}
213 +
214 +watch(
215 + () => audio_status.value,
216 + (v) => {
217 + if (v === 'pause') {
218 + voicePause();
219 + audioList.value.forEach(item => item.play = false);
220 + }
221 + },
222 + { immediate: true }
223 +);
224 +
225 +defineExpose({
226 + outerStopAudio
227 +})
228 +</script>
229 +
230 +<style lang="less">
231 +.info-page {
232 + background-color: #EBEBEB;
233 + height: 100vh;
234 + overflow: scroll;
235 + position: relative;
236 + .info-content-wrapper {
237 + // position: absolute;
238 + // top: 14.9rem;
239 + margin: 1rem;
240 + margin-top: 0;
241 + // padding: 1rem;
242 + border-radius: 0.5rem;
243 + background-color: white;
244 + .info-header {
245 + padding: 1rem 2rem 0;
246 + display: flex;
247 + justify-content: space-between;
248 + // align-items: center;
249 + .info-title {
250 + font-size: 1.25rem;
251 + margin-bottom: 0.5rem;
252 + }
253 + .info-sub-title {
254 + font-size: 0.85rem;
255 + color: #A0A8B1;
256 + }
257 + .info-btn {
258 + width: 3rem;
259 + height: 1.5rem;
260 + border: 1px solid #DD7850;
261 + color: #DD7850;
262 + border-radius: 0.8rem;
263 + font-size: 0.85rem;
264 + text-align: center;
265 + line-height: 1.5rem;
266 + }
267 + }
268 + .info-content {
269 + padding: 1rem;
270 + }
271 + .audio-wrapper {
272 + padding: 1rem;
273 + .audio-item {
274 + color: #47525F;
275 + display: flex;
276 + justify-content: space-between;
277 + align-items: center;
278 + padding: 1rem;
279 + background-color: #FFF;
280 + border-radius: 0.25rem;
281 + margin: 1rem;
282 + box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);
283 + &.click {
284 + border: 1px solid #DD7850;
285 + }
286 + .audio-icon {
287 + width: 2rem;
288 + height: 2rem;
289 + background-image: url('https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png'); /* 使用上传的图标 */
290 + background-size: cover;
291 + &.click {
292 + animation: pulse 1.5s infinite;
293 + }
294 + }
295 +
296 + @keyframes pulse {
297 + 0% {
298 + transform: scale(1);
299 + }
300 + 50% {
301 + transform: scale(1.2);
302 + }
303 + 100% {
304 + transform: scale(1);
305 + }
306 + }
307 + }
308 + }
309 + }
310 +}
311 +</style>
1 +<!--
2 + * @Date: 2023-05-19 14:54:27
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-08-08 14:51:23
5 + * @FilePath: /map-demo/src/views/by/map.vue
6 + * @Description: 公众地图主体页面
7 +-->
8 +<template>
9 + <div ref="root" style="height: 100vh; position: relative; overflow: hidden;">
10 + <div id="container"></div>
11 + <!-- 添加导航面板容器 -->
12 + <div id="walking-panel" style="position: absolute; bottom: 1rem; left: 1rem; padding: 1rem;"></div>
13 + <div style="position: absolute; top: 2rem; right: 1rem; display: flex; flex-direction: column;">
14 + <!-- <van-icon size="2rem" name="search" color="#DD7850" style="margin-bottom: 1rem;" /> -->
15 + <van-image
16 + width="2rem"
17 + height="2rem"
18 + fit="contain"
19 + src="https://cdn.ipadbiz.cn/bieyuan/map/icon/NAV@3x.png"
20 + />
21 + </div>
22 + <div v-if="data_logo" style="position: absolute; top: 2rem; left: calc(50% - 1.5rem); opacity: 0.5;">
23 + <van-image
24 + width="3rem"
25 + height="3rem"
26 + fit="contain"
27 + :src="data_logo"
28 + />
29 + </div>
30 + <div @click="scanQrcode" style="position: absolute; bottom: 1rem; left: calc(50% - 2.5rem);">
31 + <van-image
32 + width="5rem"
33 + height="5rem"
34 + fit="contain"
35 + src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan@3x.png"
36 + />
37 + </div>
38 +
39 + <van-config-provider :theme-vars="themeVars">
40 + <van-floating-panel v-model:height="info_height" :anchors="anchors" @height-change="onHeightChange">
41 + <!-- <template #header>
42 + <div class="custom-header">
43 + <h3>自定义标题</h3>
44 + <button @click="show = false">关闭</button>
45 + </div>
46 + </template> -->
47 + <page-info ref="pageInfo" :info="itemInfo" :height="info_height" @close-float="onCloseFloat" @route="onRoute" @walk-route="onWalkRoute"></page-info>
48 + <!-- <div v-if="showClose" @click="closeFloatPanel" class="close-float-panel">
49 + <van-icon name="arrow-left" color="#FFF" size="1.5rem" />
50 + </div> -->
51 + </van-floating-panel>
52 + </van-config-provider>
53 +
54 + <div v-if="!show_walk_route" @click="removeSafeRoute({ name: '参观路径' })" class="walk-nav-text">
55 + 关闭步行导航
56 + </div>
57 +
58 +
59 + <!-- 新增关闭导航按钮 -->
60 + <div v-if="walking && !show_walk_route" class="walk-nav-text" @click="closeWalkingRoute">
61 + 关闭步行导航
62 + </div>
63 +
64 + <van-dialog v-model:show="dialog_show" title="温馨提示">
65 + <div style="padding: 1rem; text-align: center;">{{ dialog_text }}</div>
66 + </van-dialog>
67 +
68 + <!-- 背景音乐控制 -->
69 + <!-- <audioBackground1></audioBackground1> -->
70 +
71 + <div class="operate-bar-wrapper">
72 + <div class="box-wrapper">
73 + <div v-if="open_current_location" class="item" @click="handleLocation(true)">
74 + <van-icon name="https://cdn.ipadbiz.cn/xys/map/%E5%AE%9A%E4%BD%8Dloc@2x.png" size="1.5rem"
75 + style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);;" />
76 + </div>
77 + <div v-else class="item" @click="handleLocation(false)">
78 + <van-icon name="https://cdn.ipadbiz.cn/xys/map/%E5%AE%9A%E4%BD%8Dloc@2x.png" size="1.5rem"
79 + style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);" />
80 + </div>
81 + </div>
82 + </div>
83 +
84 + <van-toast v-model:show="show_toast" style="padding: 0">
85 + <template #message>
86 + <p style="padding: 0.5rem 1rem;">{{ toast_text }}</p>
87 + </template>
88 + </van-toast>
89 + </div>
90 +</template>
91 +
92 +<script>
93 +import "@vant/touch-emulator";
94 +// import { mapState } from 'vuex'
95 +import coord from '@/common/map_data'
96 +import my_router from '@/common/my_router'
97 +import _ from 'lodash';
98 +import $ from 'jquery';
99 +import { useRect } from '@vant/use';
100 +import { mapAPI } from '@/api/map.js'
101 +import wx from 'weixin-js-sdk'
102 +import pageInfo from '@/views/by/info.vue'
103 +import audioBackground1 from '@/components/audioBackground1.vue'
104 +import { mapState, mapActions } from 'pinia'
105 +import { mainStore } from '@/store'
106 +import { parseQueryString } from '@/utils/tools'
107 +import AMapLoader from '@amap/amap-jsapi-loader'
108 +import { mapAudioAPI } from '@/api/map.js'
109 +
110 +const GPS = {
111 + PI: 3.14159265358979324,
112 + x_pi: 3.14159265358979324 * 3000.0 / 180.0,
113 + delta: function (lat, lon) {
114 + var a = 6378245.0; // a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
115 + var ee = 0.00669342162296594323; // ee: 椭球的偏心率。
116 + var dLat = this.transformLat(lon - 105.0, lat - 35.0);
117 + var dLon = this.transformLon(lon - 105.0, lat - 35.0);
118 + var radLat = lat / 180.0 * this.PI;
119 + var magic = Math.sin(radLat);
120 + magic = 1 - ee * magic * magic;
121 + var sqrtMagic = Math.sqrt(magic);
122 + dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * this.PI);
123 + dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * this.PI);
124 + return {
125 + 'lat': dLat,
126 + 'lon': dLon
127 + };
128 + },
129 + //WGS-84 to GCJ-02
130 + gcj_encrypt: function (wgsLat, wgsLon) {
131 + if (this.outOfChina(wgsLat, wgsLon))
132 + return {
133 + 'lat': wgsLat,
134 + 'lon': wgsLon
135 + };
136 +
137 + var d = this.delta(wgsLat, wgsLon);
138 + return {
139 + 'lat': wgsLat + d.lat,
140 + 'lon': wgsLon + d.lon
141 + };
142 + },
143 + outOfChina: function (lat, lon) {
144 + if (lon < 72.004 || lon > 137.8347)
145 + return true;
146 + if (lat < 0.8293 || lat > 55.8271)
147 + return true;
148 + return false;
149 + },
150 + transformLat: function (x, y) {
151 + var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
152 + ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
153 + ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0;
154 + ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0;
155 + return ret;
156 + },
157 + transformLon: function (x, y) {
158 + var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
159 + ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
160 + ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0;
161 + ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0;
162 + return ret;
163 + }
164 +};
165 +
166 +// 关键安全配置
167 +window._AMapSecurityConfig = {
168 + securityJsCode: 'ac1a3a5858d74b7d6c50b6858100aa12', // 替换为你的密钥
169 +}
170 +
171 +export default {
172 + components: { pageInfo, audioBackground1 },
173 + data() {
174 + return {
175 + map: '',
176 + geolocation: '',
177 + current_lng: '',
178 + current_lat: '',
179 + dialog_show: false,
180 + dialog_text: '',
181 + location_marker: '',
182 + itemInfo: {},
183 + navBarList: [],
184 + navList: [],
185 + navKey: '',
186 + markerSum: [], // marker合集
187 + mapTiles: [],
188 + data_center: [], // 接口获取-地图中心点
189 + data_zoom: '', // 接口获取-地图默认缩放
190 + data_zooms: '', // 接口获取-地图默认缩放范围
191 + data_rotation: 0, // 接口获取-地图旋转角度
192 + data_paths: {}, // 接口获取-地图导航路径
193 + data_path_list: [], // 接口获取-地图导航路径
194 + info_height: 0,
195 + anchors: [0, (0.65 * window.innerHeight), (1 * window.innerHeight)],
196 + themeVars: {
197 + floatingPanelHeaderHeight: 0,
198 + floatingPanelBorderRadius: '1.25rem'
199 + },
200 + showClose: false,
201 + markerStyle2: { // 选中
202 + //设置文本样式,Object 同 css 样式表
203 + "padding": ".5rem .2rem .5rem .2rem",
204 + // "margin-bottom": "1rem",
205 + "border-color": "#DD7850",
206 + "border-radius": ".25rem",
207 + "background-color": "#FFF",
208 + // "width": "1rem",
209 + // "border-width": 0,
210 + // "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
211 + // "text-align": "center",
212 + "font-size": "0.8rem",
213 + "color": "#DD7850",
214 + "writing-mode": "vertical-rl",
215 + "text-orientation": "mixed",
216 + "display": "flex",
217 + "justify-content": "center",
218 + "align-items": "center",
219 + },
220 + markerStyle1: { // 未选中
221 + //设置文本样式,Object 同 css 样式表
222 + "padding": ".5rem .2rem .5rem .2rem",
223 + // "margin-bottom": "1rem",
224 + "border-color": "#fcfbfa",
225 + "border-radius": ".25rem",
226 + "background-color": "#DD7850",
227 + // "width": "1rem",
228 + // "border-width": 0,
229 + // "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
230 + // "text-align": "center",
231 + "font-size": "0.8rem",
232 + "color": "white",
233 + "writing-mode": "vertical-rl",
234 + "text-orientation": "mixed",
235 + "display": "flex",
236 + "justify-content": "center",
237 + "align-items": "center",
238 + },
239 + markerStyle2_horizontal: { // 选中
240 + //设置文本样式,Object 同 css 样式表
241 + "padding": ".2rem .5rem .2rem .5rem",
242 + // "margin-bottom": "1rem",
243 + "border-color": "#DD7850",
244 + "border-radius": ".25rem",
245 + "background-color": "#FFF",
246 + // "width": "1rem",
247 + // "border-width": 0,
248 + // "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
249 + // "text-align": "center",
250 + "font-size": "0.8rem",
251 + "color": "#DD7850",
252 + "writing-mode": "horizontal",
253 + "text-orientation": "mixed",
254 + "display": "flex",
255 + "justify-content": "center",
256 + "align-items": "center",
257 + },
258 + markerStyle1_horizontal: { // 未选中
259 + //设置文本样式,Object 同 css 样式表
260 + "padding": ".2rem .5rem .2rem .5rem",
261 + // "margin-bottom": "1rem",
262 + "border-color": "#fcfbfa",
263 + "border-radius": ".25rem",
264 + "background-color": "#DD7850",
265 + // "width": "1rem",
266 + // "border-width": 0,
267 + // "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
268 + // "text-align": "center",
269 + "font-size": "0.8rem",
270 + "color": "white",
271 + "writing-mode": "horizontal",
272 + "text-orientation": "mixed",
273 + "display": "flex",
274 + "justify-content": "center",
275 + "align-items": "center",
276 + },
277 + current_safe_route: [],
278 + route_safe_marker: [],
279 + show_walk_route: true,
280 + open_current_location: true,
281 + show_toast: false,
282 + toast_text: '',
283 + data_logo: '',
284 + data_layers: [],
285 + point_range: [
286 + [117.044223,26.835105], [117.044227,26.842448], [117.0552,26.842452], [117.055195,26.8351]
287 + ],
288 + walking: '',
289 + }
290 + },
291 + async mounted() {
292 + const AMap = await AMapLoader.load({
293 + key: '17b8fc386104b89db88b60b049a6dbce', // 控制台获取
294 + version: '2.0', // 指定API版本
295 + plugins: ['AMap.ElasticMarker','AMap.ImageLayer','AMap.ToolBar','AMap.IndoorMap','AMap.Walking','AMap.Geolocation'] // 必须加载步行导航插件
296 + })
297 + const code = this.$route.query.id;
298 + const { data } = await mapAPI({ i: code });
299 + this.navBarList = data.list; // 底部导航条
300 + this.mapTiles = data.level; // 获取图层
301 + this.navKey = data.list.length ? data.list[0]['id'] : 0; // 默认选中 第一个 id
302 + this.navList = data.list.length ? data.list.filter(item => item.id === this.navKey)[0]['list'] : []; // 返回默认选中项的实体信息
303 + this.data_center = data.map.center.map(item => Number(item)); // 地图中心点
304 + this.data_zoom = data.map.zoom; // 地图默认缩放
305 + this.data_rotation = data.map.rotation; // 地图旋转角度
306 + this.data_zooms = data.map.zooms.map(item => Number(item)); // 地图默认缩放范围
307 + this.data_paths = data.map.path ? data.map.path : {}; // 地图默认导航路径
308 + this.data_logo = data.map.map_logo ? data.map.map_logo : ''; // 地图logo
309 + this.point_range = data.map.map_range ? data.map.map_range : []; // 地图定位范围
310 + if (data.map.map_layers) { // 地图默认图层
311 + if (data.map.map_layers === 'satellite') { // 卫星和路网
312 + this.data_layers = [new AMap.TileLayer.Satellite(), new AMap.TileLayer.RoadNet()]
313 + } else { // 平面图
314 + this.data_layers = [];
315 + }
316 + }
317 + if (data.map.path) {
318 + for (const key in data.map.path) {
319 + const element = data.map.path[key];
320 + this.data_path_list.push({
321 + name: key,
322 + path: element,
323 + status: true
324 + })
325 + }
326 + }
327 + // 地图标题
328 + document.title = data.map.map_title;
329 + // 微信分享
330 + const shareData = {
331 + title: data.map.map_title, // 分享标题
332 + desc: '别院地图', // 分享描述
333 + link: location.origin + location.pathname + location.hash, // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
334 + imgUrl: '', // 分享图标
335 + success: function () {
336 + // console.warn('设置成功');
337 + }
338 + }
339 + // 分享好友(微信好友或qq好友)
340 + wx.updateAppMessageShareData(shareData);
341 + // 分享到朋友圈或qq空间
342 + wx.updateTimelineShareData(shareData);
343 + // 分享到腾讯微博
344 + wx.onMenuShareWeibo(shareData);
345 + // 初始化地图
346 + this.initMap();
347 + // this.setMapBoundary();
348 + // 使用之前获取当前地址,判断当前是否能够获取经纬度
349 + // wx.getLocation({
350 + // type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
351 + // success: (res) => {
352 + // var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
353 + // var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
354 + // var speed = res.speed; // 速度,以米/每秒计
355 + // var accuracy = res.accuracy; // 位置精度
356 + // this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
357 + // this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
358 + // },
359 + // });
360 + // 设置贴片地图
361 + this.setTitleLayer();
362 + // 地图标题
363 + document.title = data.map.map_title;
364 + //
365 + // setTimeout(() => {
366 + // this.info_height = (0.5 * window.innerHeight);
367 + // // 浮动面板样式
368 + // $('.van-floating-panel__content').css('borderRadius', '1.5rem');
369 + // }, 2000);
370 + // 初始化步行导航插件时指定面板容器
371 + this.walking = new AMap.Walking({
372 + map: this.map,
373 + // panel: 'walking-panel', // 必须指定存在的DOM元素ID
374 + hideMarkers: false, // 设置隐藏路径规划的起始点图标
375 + isOutline: true, // 使用map属性时,绘制的规划线路是否显示描边
376 + autoFitView: true, // 是否自动调整地图视野到显示的路线
377 + });
378 + },
379 + watch: {
380 + // // 监听 $route 对象的 query 属性
381 + // '$route.query': {
382 + // handler(newQuery, oldQuery) {
383 + // if (newQuery.marker_id) {
384 +
385 + // }
386 + // },
387 + // immediate: true, // 设置为 true,确保在初始化时也执行一次 handler
388 + // deep: true // 如果 query 是嵌套对象,可以设置 deep 监听深层变化
389 + // }
390 + },
391 + computed: {
392 + ...mapState(mainStore, ['audio_entity', 'audio_src', 'audio_status'])
393 + },
394 + methods: {
395 + ...mapActions(mainStore, ['changeAudio', 'changeAudioSrc', 'changeAudioStatus']),
396 + initMap() {
397 + // 初始化地图
398 + this.map = new AMap.Map('container', {
399 + viewMode: '2D', // 设置地图模式
400 + turboMode: false,
401 + showIndoorMap: false,
402 + defaultCursor: 'pointer', // 地图默认鼠标样式
403 + showBuildingBlock: false, // 是否展示地图 3D 楼块
404 + zooms: this.data_zooms, // 地图显示的缩放级别范围, 默认为 [2, 20] ,取值范围 [2 ~ 30]
405 + showLabel: true, // 是否展示地图文字和 POI 信息
406 + zoom: this.data_zoom, // 设置地图显示的缩放级别
407 + pitch: 0, // 俯仰角度,默认 0,最大值根据地图当前 zoom 级别不断增大,2D地图下无效 。
408 + rotation: this.data_rotation, // 地图顺时针旋转角度,取值范围 [0-360] ,默认值:0
409 + center: this.data_center, // 设置地图中心点坐标
410 + forceVector: false,
411 + // rotateEnable: true,
412 + layers: this.data_layers,
413 + features: ['bg', 'road'], // 设置地图上显示的元素种类
414 + animateEnable: false, // 地图平移过程中是否使用动画
415 + resizeEnable: true,
416 + WebGLParams: { // 新增WebGL优化参数
417 + preserveDrawingBuffer: true,
418 + antialias: true,
419 + stencil: true,
420 + alpha: true
421 + },
422 + optimizeTileStrategy: true, // 开启瓦片优化
423 + autoRendering: false // 关闭自动渲染
424 + });
425 + // 添加地图点击事件
426 + this.map.on("click", this.showInfoClick);
427 + // 加载景点图层
428 + this.navKey && this.loadMaker(this.navKey);
429 + //
430 + this.map.setRotation(this.data_rotation, true);
431 + },
432 + loadMaker(id) {
433 + var zoomStyleMapping = { 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0 };
434 + const entity_info = this.navBarList.filter(item => item.id === id)[0]['list'];
435 + this.markerSum = [];
436 + _.each(entity_info, (x, i) => {
437 + let marker_icon = '';
438 + if (entity_info[i].window_type === 'warn' && entity_info[i].details.length === 1) { // 如果是预警类型并且内部预警项目只有一个取details第一个icon
439 + marker_icon = entity_info[i].details[0]['icon'];
440 + } else {
441 + marker_icon = entity_info[i].icon;
442 + }
443 + let text_direction = entity_info[i]?.writing_mode === 'vertical' ? 'vertical' : 'horizontal';
444 + let textMarker = new AMap.Text({
445 + zooms: [18, 20], // 点标记显示的层级范围,超过范围不显示。
446 + text: entity_info[i].name, //标记显示的文本内容
447 + anchor: "center", //设置文本标记锚点位置
448 + // draggable: true, //是否可拖拽
449 + // cursor: "pointer", //指定鼠标悬停时的鼠标样式。
450 + // angle: 10, //点标记的旋转角度
451 + style: text_direction === 'vertical' ? this.markerStyle1 : this.markerStyle1_horizontal,
452 + position: entity_info[i].position, //点标记在地图上显示的位置
453 + });
454 + textMarker.setMap(this.map); //将文本标记设置到地图上
455 + this.markerSum.push(textMarker);
456 + if (clickListener1) {
457 + textMarker.off('click', clickListener)
458 + }
459 + // 绑定景点的点击事件 - 文字出现才能触发
460 + var clickListener1 = textMarker.on('click', async (e) => {
461 + // 还原样式
462 + this.markerSum.forEach(item => {
463 + if (e.target.hS !== item.hS) {
464 + // 修改文本的样式
465 + item.setStyle(item._x['writing-mode'] === 'vertical-rl' ? this.markerStyle2 : this.markerStyle2_horizontal);
466 + }
467 + })
468 + // 修改文本的样式
469 + e.target.setStyle(text_direction === 'vertical' ? this.markerStyle1 : this.markerStyle1_horizontal);
470 +
471 + // 修改文本内容
472 + // textMarker.setText('样式已修改');
473 + //
474 +
475 + // 先获取音频信息
476 + const { data, code } = await mapAudioAPI({ mid: this.$route.query.id, bid: entity_info[i].id });
477 +
478 + // 创建新的对象并设置音频状态
479 + const updatedInfo = JSON.parse(JSON.stringify(entity_info[i]));
480 + if (data.length && updatedInfo.details && updatedInfo.details[0]) {
481 + updatedInfo.details[0].show_audio = true;
482 + }
483 +
484 + // 使用 nextTick 确保视图更新
485 + await this.$nextTick();
486 + this.itemInfo = updatedInfo;
487 +
488 + // 详情为空提示
489 + if (!this.itemInfo.details.length) {
490 + this.show_toast = true;
491 + this.toast_text = '该景点暂无详情'
492 + return;
493 + }
494 +
495 + // 打开浮动面板
496 + this.info_height = (0.65 * window.innerHeight);
497 + // 浮动面板样式
498 + $('.van-floating-panel__content').css('borderRadius', '1.25rem');
499 + $('.van-floating-panel').css('boxShadow', '0 0 15px black');
500 +
501 + // 定位到当前位置中心
502 + this.map.setZoomAndCenter(this.zoom, this.itemInfo.position);
503 + // 获取地图容器的高度
504 + const mapHeight = this.map.getSize().height;
505 +
506 + // 计算需要向上移动的像素值,比如向上移动地图高度的一半左右
507 + const offsetY = -mapHeight / 3.5;
508 +
509 + // 使用 panBy 方法进行视图偏移
510 + this.map.panBy(0, offsetY);
511 + })
512 + // if (entity_info[i]?.writing_mode === 'vertical') { // 标题文字垂直
513 + // let textMarker = new AMap.Text({
514 + // zooms: [18, 20], // 点标记显示的层级范围,超过范围不显示。
515 + // text: entity_info[i].name, //标记显示的文本内容
516 + // anchor: "center", //设置文本标记锚点位置
517 + // // draggable: true, //是否可拖拽
518 + // // cursor: "pointer", //指定鼠标悬停时的鼠标样式。
519 + // // angle: 10, //点标记的旋转角度
520 + // style: this.markerStyle1,
521 + // position: entity_info[i].position, //点标记在地图上显示的位置
522 + // });
523 + // textMarker.setMap(this.map); //将文本标记设置到地图上
524 + // this.markerSum.push(textMarker);
525 + // if (clickListener1) {
526 + // textMarker.off('click', clickListener)
527 + // }
528 + // // 绑定景点的点击事件 - 文字出现才能触发
529 + // var clickListener1 = textMarker.on('click', (e) => {
530 + // // 还原样式
531 + // this.markerSum.forEach(item => {
532 + // if (e.target.hS !== item.hS) {
533 + // // 修改文本的样式
534 + // item.setStyle(this.markerStyle2);
535 + // }
536 + // })
537 + // // 修改文本的样式
538 + // e.target.setStyle(this.markerStyle1);
539 +
540 + // // 修改文本内容
541 + // // textMarker.setText('样式已修改');
542 + // //
543 +
544 + // // console.warn(e);
545 + // this.itemInfo = entity_info[i];
546 +
547 + // // 详情为空提示
548 + // if (!this.itemInfo.details.length) {
549 + // this.show_toast = true;
550 + // this.toast_text = '该景点暂无详情'
551 + // return;
552 + // }
553 +
554 +
555 + // // 打开浮动面板
556 + // this.info_height = (0.65 * window.innerHeight);
557 + // // 浮动面板样式
558 + // $('.van-floating-panel__content').css('borderRadius', '1.25rem');
559 + // $('.van-floating-panel').css('boxShadow', '0 0 15px black');
560 +
561 + // // 定位到当前位置中心
562 + // this.map.setZoomAndCenter(this.zoom, this.itemInfo.position);
563 + // // 获取地图容器的高度
564 + // const mapHeight = this.map.getSize().height;
565 +
566 + // // 计算需要向上移动的像素值,比如向上移动地图高度的一半左右
567 + // const offsetY = -mapHeight / 3.5;
568 +
569 + // // 使用 panBy 方法进行视图偏移
570 + // this.map.panBy(0, offsetY);
571 + // })
572 + // }
573 + // TODO: 获取详情定位信息用来导航
574 + // 导航路径
575 + let marker_id = this.$route.query.marker_id;
576 + if (marker_id) {
577 + this.$nextTick(() => {
578 + let marker = this.navBarList[0]['list'].filter(item => item.id == marker_id)
579 + // let path = marker[0].path;
580 + // this.addSafeRoute({name: '参观路径', path});
581 + // TAG: 新增步行导航
582 + let position = marker[0].position;
583 + wx.getLocation({
584 + type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
585 + success: (res) => {
586 + var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
587 + var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
588 + var speed = res.speed; // 速度,以米/每秒计
589 + var accuracy = res.accuracy; // 位置精度
590 + this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
591 + this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
592 + this.onWalkRoute({point: position});
593 + },
594 + });
595 +
596 + // 获取当前 URL 的查询参数
597 + let query = { ...this.$route.query };
598 +
599 + // 删除 marker_id 参数
600 + delete query.marker_id;
601 +
602 + // 使用 Vue Router 更新 URL,并且不刷新页面
603 + this.$router.replace({ query });
604 + });
605 + }
606 + });
607 + this.map.add(this.markerSum);
608 + //
609 + // setTimeout(() => {
610 + // // 获取定位打标记
611 + // this.setLocation();
612 + // }, 1000);
613 + },
614 + isPointInRing() { // 是否在景区范围
615 + let isPointInRing = AMap.GeometryUtil.isPointInRing([this.current_lng, this.current_lat], this.point_range);
616 + return isPointInRing
617 + },
618 + setLocation() { // 开启定位服务
619 + // 获取失败
620 + // if (!this.current_lng || !this.current_lat) {
621 + // this.dialog_show = true;
622 + // this.dialog_text = '获取经纬度失败';
623 + // }
624 + this.getLocation();
625 + },
626 + getLocation() { // 获取经纬度
627 + // PC端无法获取定位
628 + // 微信获取地址
629 + wx.getLocation({
630 + type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
631 + success: (res) => {
632 + var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
633 + var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
634 + var speed = res.speed; // 速度,以米/每秒计
635 + var accuracy = res.accuracy; // 位置精度
636 + this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
637 + this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
638 + // 判断是否在范围内
639 + // if (!this.isPointInRing()) {
640 + // this.dialog_show = true;
641 + // this.dialog_text = '您不在景区范围内';
642 + // } else {
643 + // 使用纠正偏移后的地址,打一个定位标记
644 + this.location_marker = new AMap.LabelMarker({
645 + icon: {
646 + image: 'https://cdn.ipadbiz.cn/bieyuan/map/icon/Group%2034@3x.png',
647 + anchor: 'bottom-center',
648 + size: [65, 65],
649 + },
650 + position: new AMap.LngLat(this.current_lng, this.current_lat), // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
651 + });
652 + this.map.add(this.location_marker);
653 + // 定位到当前位置中心
654 + this.map.setZoomAndCenter(this.zoom, [this.current_lng, this.current_lat]);
655 + // }
656 + },
657 + complete: () => {
658 + // 获取失败
659 + if (!this.current_lng || !this.current_lat) {
660 + this.dialog_show = true;
661 + this.dialog_text = '获取经纬度失败';
662 + }
663 + },
664 + });
665 + },
666 + setZoom(type) { // 设置放大缩小地图
667 + const zoom = this.map.getZoom();
668 + if (type === 'plus') {
669 + this.map.setZoom(zoom + 1)
670 + }
671 + if (type === 'minus') {
672 + this.map.setZoom(zoom - 1)
673 + }
674 + },
675 + computedMapSource(x, y, z) { // 根据图层信息生成图层实际地址
676 + for (const id in this.mapTiles) {
677 + if (z == id) {
678 + const scope = this.mapTiles[id];
679 + return scope[`${x}-${y}`]
680 + }
681 + }
682 + },
683 + setTitleLayer() { // 生成瓦片图
684 + // 获取瓦片图渲染范围
685 + function getFirstProperty(obj) {
686 + for (var prop in obj) {
687 + return prop;
688 + }
689 + }
690 + function getLastProperty(obj) {
691 + var props = [];
692 + for (var prop in obj) {
693 + props.push(prop);
694 + }
695 + return props[props.length - 1];
696 + }
697 + let obj_scope = {};
698 + for (const key in this.mapTiles) {
699 + const element = this.mapTiles[key];
700 + let first = getFirstProperty(element).split('-');
701 + let last = getLastProperty(element).split('-');
702 + obj_scope[key] = {
703 + x: [first[0], last[0]],
704 + y: [first[1], last[1]]
705 + }
706 + }
707 + const _this = this;
708 + var layer = new AMap.TileLayer.Flexible({
709 + cacheSize: 50,
710 + opacity: 1,
711 + zIndex: 100,
712 + createTile: function (x, y, z, success, fail) {
713 + // 控制地图等级显示图片范围-过滤不显示的图层渲染
714 + for (const id in obj_scope) {
715 + if (z == id) {
716 + const scope = obj_scope[id];
717 + if (x < scope.x[0] || x > scope.x[1]) {
718 + fail()
719 + return;
720 + }
721 + if (y < scope.y[0] || y > scope.y[1]) {
722 + fail()
723 + return;
724 + }
725 + }
726 + }
727 +
728 + var img = document.createElement('img');
729 + img.onload = function () {
730 + success(img)
731 + };
732 + img.crossOrigin = "anonymous";// 必须添加,同时图片要有跨域头
733 + img.onerror = function () {
734 + fail()
735 + };
736 +
737 + // img.src = `images/tiles/${z}/${x}_${y}.png`;
738 + img.src = _this.computedMapSource(x, y, z);
739 + },
740 + });
741 +
742 + this.map.addLayer(layer);
743 +
744 + // Canvas作为切片
745 + var layer1 = new AMap.TileLayer.Flexible({
746 + // tileSize: 128,
747 + cacheSize: 300,
748 + zIndex: 200,
749 + createTile: function (x, y, z, success, fail) {
750 + var c = document.createElement('canvas');
751 + c.width = c.height = 256;
752 +
753 + var cxt = c.getContext("2d");
754 + cxt.font = "15px Verdana";
755 + cxt.fillStyle = "#ff0000";
756 + cxt.strokeStyle = "#FF0000";
757 + cxt.strokeRect(0, 0, 256, 256);
758 + cxt.fillText('(' + [x, y, z].join(',') + ')', 10, 30);
759 +
760 + // 通知API切片创建完成
761 + success(c);
762 + }
763 + });
764 +
765 + // layer1.setMap(this.map);
766 +
767 + // 只显示相应区域,移动会回到选定范围
768 + // this.lockMapBounds()
769 + },
770 + // 限制地图范围
771 + lockMapBounds() {
772 + // var bounds = this.map.getBounds();
773 + var myBounds = new AMap.Bounds( // 移动范围,对角线
774 + [117.04384,26.833629],
775 + [117.055975,26.843652]
776 + );
777 +
778 + this.map.setLimitBounds(myBounds);
779 +
780 + let list =[ // 四个角,覆盖填充范围
781 + [117.04421,26.833875],
782 + [117.045012,26.842089],
783 + [117.054749,26.84219],
784 + [117.056013,26.83387]
785 + ]
786 +
787 + // 隐藏边界以外的区域
788 + let outer = [
789 + new AMap.LngLat(-360, 90, true),
790 + new AMap.LngLat(-360, -90, true),
791 + new AMap.LngLat(360, -90, true),
792 + new AMap.LngLat(360, 90, true),
793 + ] // 遮盖填充反向
794 +
795 + let pathArray = [
796 + outer,
797 + list
798 + ]
799 +
800 + var polygon = new AMap.Polygon({
801 + pathL: pathArray,
802 + strokeColor: "#fcfbf9",
803 + strokeWeight: 2,
804 + fillColor: "#fcfbf9",
805 + fillOpacity: 1,
806 + })
807 +
808 + polygon.setPath(pathArray)
809 + this.map.add(polygon)
810 +
811 + },
812 + showInfoClick(e) {
813 + // console.log(e);
814 + var zoom = this.map.getZoom(); //获取当前地图级别
815 + // var text =
816 + // "您在 [" +
817 + // e.lnglat.getLng() +
818 + // "," +
819 + // e.lnglat.getLat() +
820 + // "] 的位置单击了地图!当前层级" +
821 + // zoom;
822 + var text =
823 + "[" +
824 + e.lnglat.getLng() +
825 + "," +
826 + e.lnglat.getLat() +
827 + "],"
828 + console.log(text);
829 + // 点击空白处,关闭弹框
830 + if (this.info_height) {
831 + // 关闭浮动面板
832 + this.info_height = 0;
833 + $('.van-floating-panel').css('boxShadow', 'none');
834 + // 还原样式
835 + this.resetMarkStyle();
836 + }
837 + },
838 + scanQrcode() { // 扫码跳转详情页
839 + wx.scanQRCode({
840 + needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
841 + scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
842 + success: (res) => {
843 + var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
844 + let id = parseQueryString(result).id;
845 + let marker_id = parseQueryString(result).marker_id;
846 + // 跳转详情页
847 + this.$router.push({
848 + path: '/by/info',
849 + query: {
850 + id,
851 + marker_id
852 + }
853 + })
854 + }
855 + });
856 + // 识别率太低
857 + // this.$router.push({
858 + // path: '/by/scan'
859 + // })
860 + },
861 + onHeightChange ({ height }) { // 监听浮动面板高度变化
862 + if (height > window.innerHeight * 0.6) {
863 + // // 浮动面板样式
864 + // $('.van-floating-panel__content').css('borderRadius', '0');
865 + // this.showClose = true;
866 + // 清空设置
867 + // this.changeAudio('');
868 + // this.changeAudioStatus('pause');
869 + //
870 + this.$router.push({
871 + path: '/by/info',
872 + query: {
873 + id: this.$route.query.id,
874 + marker_id: this.itemInfo.id
875 + }
876 + })
877 + } else {
878 + $('.van-floating-panel__content').css('borderRadius', '1.25rem');
879 + $('.van-floating-panel').css('boxShadow', 'none');
880 + this.showClose = false;
881 + }
882 + },
883 + closeFloatPanel () {
884 + this.info_height = (0.65 * window.innerHeight);
885 + $('.van-floating-panel__content').css('borderRadius', '1.25rem');
886 + this.showClose = false;
887 + // 关闭音频
888 + this.$refs.pageInfo.outerStopAudio();
889 + },
890 + resetMarkStyle () {
891 + this.markerSum.forEach(item => {
892 + item.setStyle(item._x['writing-mode'] === 'vertical-rl' ? this.markerStyle1 : this.markerStyle1_horizontal);
893 + })
894 + },
895 + onCloseFloat () {
896 + this.info_height = 0;
897 + $('.van-floating-panel__content').css('borderRadius', '1.25rem');
898 + $('.van-floating-panel').css('boxShadow', 'none');
899 + this.resetMarkStyle();
900 + },
901 + addSafeRoute({name, path}) { // 新增路径
902 + // 获取对象的第一个键和值
903 + // let firstKey = Object.keys(this.data_paths)[0];
904 + // let firstValue = this.data_paths[firstKey];
905 + // 行动路线
906 + // var path = [
907 + // [120.587645, 31.314833],
908 + // [120.587709, 31.314338],
909 + // [120.588211, 31.314377],
910 + // ];
911 + // console.warn(firstValue);
912 + // var path = firstValue;
913 + // 生成折线地图路径
914 + let current_safe_route = new AMap.Polyline({
915 + path,
916 + isOutline: true,
917 + outlineColor: '#179FB1',
918 + borderWeight: 1,
919 + strokeColor: '#179FB1',
920 + strokeOpacity: 1,
921 + strokeWeight: 3,
922 + // 折线样式还支持 'dashed'
923 + strokeStyle: 'solid',
924 + // strokeStyle是dashed时有效
925 + strokeDasharray: [10, 5],
926 + lineJoin: 'round',
927 + lineCap: 'round',
928 + zIndex: 50
929 + })
930 + this.map.add([current_safe_route]);
931 + this.current_safe_route.push({
932 + key: name,
933 + path: current_safe_route
934 + })
935 + // 设置起始点标记
936 + var marker1 = new AMap.Marker({
937 + icon: new AMap.Icon({
938 + image: 'https://cdn.ipadbiz.cn/bieyuan/map/icon/Ellipse%2013@3x.png',
939 + size: new AMap.Size(15, 15),
940 + // 图标所用图片大小
941 + imageSize: new AMap.Size(15, 15),
942 + // 图标取图偏移量
943 + imageOffset: new AMap.Pixel(0, 0)
944 + }),
945 + position: new AMap.LngLat(path[0][0], path[0][1]), // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
946 + anchor: 'bottom-center',
947 + offset: new AMap.Pixel(0, 0)
948 + });
949 + // marker1.setLabel({
950 + // direction: 'right',
951 + // offset: new AMap.Pixel(0, -10), //设置文本标注偏移量
952 + // content: "<div class='info'>起点</div>", //设置文本标注内容
953 + // });
954 + var marker2 = new AMap.Marker({
955 + icon: new AMap.Icon({
956 + image: 'https://cdn.ipadbiz.cn/bieyuan/map/icon/Ellipse%2013@3x.png',
957 + size: new AMap.Size(15, 15),
958 + // 图标所用图片大小
959 + imageSize: new AMap.Size(15, 15),
960 + // 图标取图偏移量
961 + imageOffset: new AMap.Pixel(0, 0)
962 + }),
963 + position: new AMap.LngLat(path[path.length - 1][0], path[path.length - 1][1]), // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
964 + anchor: 'bottom-center',
965 + offset: new AMap.Pixel(0, 0)
966 + });
967 + // marker2.setLabel({
968 + // direction: 'right',
969 + // offset: new AMap.Pixel(0, -10), //设置文本标注偏移量
970 + // content: "<div class='info'>终点</div>", //设置文本标注内容
971 + // });
972 + // 新增逃生路线标记
973 + // this.route_safe_marker = [marker1, marker2]
974 + // this.map.add(this.route_safe_marker);
975 + // 新增逃生路线标记
976 + let route_safe_marker = [marker1, marker2]
977 + this.map.add(route_safe_marker);
978 + this.route_safe_marker.push({
979 + key: name,
980 + path: route_safe_marker
981 + });
982 + // 关闭导航提示
983 + this.show_walk_route = false;
984 + },
985 + removeSafeRoute({name}) { // 移除地图路线
986 + this.current_safe_route.forEach(item => {
987 + if (item.key === name) {
988 + this.map.remove([item.path]); // 删除地图折线
989 + }
990 + });
991 + // this.map.remove(this.route_safe_marker); // 删除起始点标记
992 + this.route_safe_marker.forEach(item => {
993 + if (item.key === name) {
994 + this.map.remove(item.path); // 删除起始点标记
995 + }
996 + });
997 + // 关闭导航提示
998 + this.show_walk_route = true;
999 + },
1000 + onRoute (path) {
1001 + console.warn(path);
1002 + // 模拟新增路线
1003 + this.addSafeRoute(path);
1004 + // 定位到当前位置中心
1005 + this.map.setZoomAndCenter(this.zoom, this.data_center);
1006 + },
1007 + async onWalkRoute (position) {
1008 + await this.$nextTick(); // 等待DOM更新
1009 + // 步行导航
1010 + // let walking = new AMap.Walking({
1011 + // map: this.map,
1012 + // panel: "panel"
1013 + // });
1014 +
1015 + wx.getLocation({
1016 + type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
1017 + success: (res) => {
1018 + var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
1019 + var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
1020 + var speed = res.speed; // 速度,以米/每秒计
1021 + var accuracy = res.accuracy; // 位置精度
1022 + this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
1023 + this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
1024 +
1025 + // 确保参数格式正确
1026 + const startPoint = [this.current_lng, this.current_lat]; // 起点
1027 + const endPoint = position.point; // 终点
1028 +
1029 + // 参数检查
1030 + if (!startPoint[0] || !startPoint[1]) {
1031 + this.show_toast = true;
1032 + this.toast_text = '无法获取当前位置,请确保已开启定位功能';
1033 + return;
1034 + }
1035 +
1036 + if (!endPoint[0] || !endPoint[1]) {
1037 + this.show_toast = true;
1038 + this.toast_text = '目的地坐标不完整';
1039 + return;
1040 + }
1041 +
1042 + // 转换为 LngLat 对象
1043 + const start = new AMap.LngLat(startPoint[0], startPoint[1]);
1044 + const end = new AMap.LngLat(endPoint[0], endPoint[1]);
1045 +
1046 + // 规划步行路线
1047 + this.walking.search(start, end, (status, result) => {
1048 + if (status === 'complete') {
1049 + console.log('步行路线规划成功');
1050 + setTimeout(() =>{
1051 + // 定位到当前位置中心
1052 + this.getLocation();
1053 + this.show_walk_route = false;
1054 + },500)
1055 + } else {
1056 + console.error('步行路线规划失败:', status, result);
1057 + ElMessage.error('步行路线规划失败,请稍后重试');
1058 + }
1059 + });
1060 + },
1061 + });
1062 + },
1063 + handleLocation(status) { // 打开/关闭 当前定位
1064 + if (status) {
1065 + this.setLocation()
1066 + this.open_current_location = false;
1067 + } else {
1068 + this.removeLocation()
1069 + this.open_current_location = true;
1070 + }
1071 + },
1072 + removeLocation() { // 移除定位标记
1073 + this.current_lng = '';
1074 + this.current_lat = '';
1075 + this.map.remove(this.location_marker); // 删除当前定位标记
1076 + },
1077 + closeWalkingRoute() {
1078 + if (this.walking) {
1079 + this.walking.clear(); // 清除路线
1080 + this.show_walk_route = true; // 恢复状态
1081 + // 可选:移除面板内容
1082 + document.getElementById('walking-panel').innerHTML = '';
1083 +
1084 + // 显示提示
1085 + this.show_toast = true;
1086 + this.toast_text = '已关闭步行导航';
1087 + }
1088 + },
1089 + }
1090 +}
1091 +</script>
1092 +
1093 +<style lang="less">
1094 +#container {
1095 + position: absolute;
1096 + top: 0;
1097 + left: 0;
1098 + right: 0;
1099 + bottom: 0;
1100 + width: 100%;
1101 + height: 100%;
1102 +}
1103 +
1104 +// 遮挡地图logo
1105 +.amap-logo {
1106 + display: none!important;
1107 + visibility: hidden!important;
1108 +}
1109 +
1110 +.amap-copyright {
1111 + display: none!important;
1112 + visibility: hidden!important;
1113 +}
1114 +
1115 +/* 标记文字样式 */
1116 +.amap-marker-label {
1117 + padding: 0.25rem 0.5rem;
1118 + width: auto;
1119 + border: none;
1120 + border-radius: 2px;
1121 + background: rgba(86, 65, 23, 0.8);
1122 + color: white;
1123 +}
1124 +
1125 +.amap-marker {
1126 + .amap-icon {
1127 + margin-top: 0.25rem;
1128 + }
1129 +}
1130 +
1131 +.input-card {
1132 + display: flex;
1133 + flex-direction: column;
1134 + min-width: 0;
1135 + word-wrap: break-word;
1136 + background-color: #fff;
1137 + background-clip: border-box;
1138 + border-radius: .25rem;
1139 + width: 20rem;
1140 + border-width: 0;
1141 + border-radius: 0.4rem;
1142 + box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
1143 + position: fixed;
1144 + top: 4rem;
1145 + right: 1rem;
1146 + -ms-flex: 1 1 auto;
1147 + flex: 1 1 auto;
1148 + padding: 0.75rem 1.25rem;
1149 +}
1150 +
1151 +.tool-bar-wrapper {
1152 + position: absolute;
1153 + left: 20px;
1154 + bottom: 8rem;
1155 + width: 20px;
1156 +}
1157 +
1158 +.nav-bar-wrapper {
1159 + position: fixed;
1160 + bottom: 0;
1161 + left: 0;
1162 + height: 5.5rem;
1163 + width: 100%;
1164 + background-color: white;
1165 + text-align: center;
1166 + box-shadow: 0 -1px 0 rgba(80, 80, 80, 0.1);
1167 + z-index: 999;
1168 + // padding: 0.5rem 0;
1169 + padding-bottom: 0.5rem;
1170 +
1171 + .nav-bar-content {
1172 + display: flex;
1173 + overflow-x: scroll;
1174 + overflow-y: hidden;
1175 + -webkit-overflow-scrolling: touch;
1176 + position: relative;
1177 + }
1178 +
1179 + .item {
1180 + padding-top: 0.5rem;
1181 + color: #888;
1182 + width: 21.5%;
1183 + flex-shrink: 0;
1184 + padding-top: 1rem;
1185 + }
1186 +
1187 + .checked {
1188 + color: #965f13;
1189 + }
1190 +}
1191 +
1192 +.safe-route-wrapper {
1193 + position: absolute;
1194 + bottom: 2rem;
1195 + right: 1rem;
1196 + background-color: white;
1197 +}
1198 +
1199 +.operate-bar-wrapper {
1200 + position: fixed;
1201 + left: 20px;
1202 + bottom: 6rem;
1203 + width: 20px;
1204 + height: auto;
1205 + z-index: 100;
1206 +
1207 + .box-wrapper {
1208 + display: flex;
1209 + flex-direction: column;
1210 + align-items: center;
1211 + justify-content: center;
1212 +
1213 + .item {
1214 + position: relative;
1215 + text-align: center;
1216 + font-size: 0.85rem;
1217 + width: 2rem;
1218 + height: 2rem;
1219 + background-color: white;
1220 + margin-bottom: 1rem;
1221 + border-radius: 50%;
1222 + padding: 2.5px;
1223 + line-height: 2rem;
1224 + }
1225 + }
1226 +}
1227 +
1228 +.popup-wrapper {
1229 + margin-top: 1rem;
1230 +
1231 + .title {
1232 + font-size: 1.25rem;
1233 + margin-bottom: 0.85rem;
1234 + }
1235 +
1236 + .content {
1237 + line-height: 1.75;
1238 + font-size: 0.95rem;
1239 + }
1240 +}
1241 +
1242 +
1243 +.hideScrollBar::-webkit-scrollbar {
1244 + display: none;
1245 +}
1246 +
1247 +.hideScrollBar {
1248 + -ms-overflow-style: none;
1249 + overflow: -moz-scrollbars-none;
1250 +}
1251 +
1252 +.van-dialog__confirm,
1253 +.van-dialog__confirm:active {
1254 + color: #AB8F57;
1255 +}
1256 +
1257 +.walk-nav-text {
1258 + position: fixed;
1259 + bottom: 6rem;
1260 + left: 50%;
1261 + transform: translate(-50%, -50%);
1262 + z-index: 9;
1263 + background: rgba(86, 65, 23, 0.8);
1264 + color: white;
1265 + border-radius: 10px;
1266 + padding: 5px 12px;
1267 + font-size: 0.8rem;
1268 +}
1269 +
1270 +.close-float-panel {
1271 + position: absolute;
1272 + top: 1rem;
1273 + left: 1rem;
1274 +}
1275 +
1276 +.custom-header {
1277 + display: flex;
1278 + justify-content: space-between;
1279 + align-items: center;
1280 + padding: 10px;
1281 + background-color: #f7f8fa;
1282 +}
1283 +
1284 +.van-floating-panel__header-bar {
1285 + background: none;
1286 +}
1287 +</style>
1 +<!--
2 + * @Date: 2024-09-15 11:45:13
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-09-18 11:15:56
5 + * @FilePath: /map-demo/src/views/bieyuan/scan.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="scan-page">
10 + <!--<div class="scan_wrapper">
11 + <div class="scan_box">
12 + <!~~ 镜头区域 ~~>
13 + <video ref="video" id="video" class="scan-video" autoplay></video>
14 + </div>
15 + <div @click="openScan" class="scan_text">点击扫描二维码查看详情</div>
16 + </div>
17 + <div class="sys_logo">
18 + <van-image
19 + width="3rem"
20 + height="3rem"
21 + fit="contain"
22 + src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_logo.png"
23 + />
24 + </div>-->
25 +
26 + <video ref="video" id="video" class="video vjs-fluid" autoplay></video>
27 + <div v-show="tipShow" class="tip">{{tipMsg}}</div>
28 + </div>
29 +</template>
30 +
31 +<script setup>
32 +import { ref } from "vue";
33 +import { useRoute, useRouter } from "vue-router";
34 +
35 +const $route = useRoute();
36 +const $router = useRouter();
37 +
38 +import { BrowserMultiFormatReader } from "@zxing/library";
39 +// import axios from 'axios'
40 +const codeReader = ref(null);
41 +
42 +const scanText = ref("");
43 +// 初始化相机
44 +const openScan = () => {
45 + codeReader.value = new BrowserMultiFormatReader();
46 + codeReader.value
47 + .getVideoInputDevices()
48 + .then((videoDevices) => {
49 + let firstDeviceId = videoDevices[videoDevices.length - 1].deviceId;
50 + if (videoDevices.length > 1) {
51 + // 一般通过判断摄像头列表项里的 label 字段,'camera2 0, facing back' 字符串含有 'back' 和 '0',大部分机型是这样,如果有些机型没有,那就还是默认获取最后一个
52 + firstDeviceId = videoDevices.find((el) => {
53 + return el.label.indexOf("back") > -1 && el.label.indexOf("0") > -1;
54 + })
55 + ? videoDevices.find((el) => {
56 + return el.label.indexOf("back") > -1 && el.label.indexOf("0") > -1;
57 + }).deviceId
58 + : videoDevices[videoDevices.length - 1].deviceId;
59 + }
60 + decodeFromInputVideoFunc(firstDeviceId);
61 + })
62 + .catch((err) => {
63 + console.log(err);
64 + });
65 +};
66 +
67 +// 扫码
68 +const decodeFromInputVideoFunc = (firstDeviceId) => {
69 + // 使用摄像头扫描
70 + codeReader.value.reset(); // 重置
71 + codeReader.value.decodeFromInputVideoDeviceContinuously(
72 + firstDeviceId,
73 + "video",
74 + (result, err) => {
75 + if (result) {
76 + alert(result.text);
77 + console.log("扫码结果", result);
78 + scanText.value = result.text;
79 + if (scanText.value) {
80 + // 识别成功关闭摄像头
81 + codeReader.value.reset();
82 + codeReader.value.stopContinuousDecodeFromInputVideoDevice();
83 + }
84 + }
85 + }
86 + );
87 +};
88 +
89 +onMounted(() => {});
90 +</script>
91 +
92 +
93 +<style lang="less" scoped>
94 +.scan-page {
95 + position: relative;
96 + height: 100vh;
97 + .scan_wrapper {
98 + display: flex;
99 + padding: 1rem;
100 + flex-direction: column;
101 + align-items: center;
102 + padding-top: 5rem;
103 + .scan_box {
104 + padding: 1rem;
105 + background-image: url('https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_bg.png');
106 + background-size: contain;
107 + background-repeat: no-repeat;
108 + background-position: center center;
109 + height: 20rem;
110 + width: 90%;
111 + position: relative;
112 + overflow: hidden; /* 确保溢出内容被隐藏 */
113 + box-sizing: border-box; /* 包括 padding 在内的尺寸计算 */
114 + .scan-video {
115 + object-fit: cover; /* 保持视频内容的比例,同时填满容器 */
116 + position: absolute; /* 确保视频覆盖整个容器 */
117 + top: -1rem;
118 + left: -1rem;
119 + width: calc(100% - 2rem); /* 视频宽度减去左右1rem的padding */
120 + height: calc(100% - 2rem); /* 视频高度减去上下1rem的padding */
121 + padding: 2rem;
122 + object-fit: cover; /* 保持视频比例,同时填充容器 */
123 + }
124 + }
125 + .scan_text {
126 + font-size: 0.9rem;
127 + color: #DD7850;
128 + padding: 0.5rem 1rem;
129 + margin: 1rem;
130 + border: 1px solid #DD7850;
131 + border-radius: 5px;
132 + }
133 + }
134 + .sys_logo {
135 + position: absolute;
136 + bottom: 3rem;
137 + left: calc(50% - 1.5rem);
138 + }
139 +}
140 +</style>