hookehuyr

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

添加签到模块的首页、扫码页和信息详情页
配置签到模块的路由路径
实现音频播放、图片预览等功能
/*
* @Date: 2023-05-29 11:10:19
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-02-10 16:19:29
* @LastEditTime: 2025-08-26 15:46:05
* @FilePath: /map-demo/src/route.js
* @Description: 文件描述
*/
......@@ -112,4 +112,18 @@ export default [
title: '详情页',
},
},
{
path: '/checkin',
component: () => import('@/views/checkin/map.vue'),
meta: {
title: '地图',
},
},
{
path: '/checkin/info',
component: () => import('@/views/checkin/info.vue'),
meta: {
title: '详情页',
},
},
];
......
<!--
* @Date: 2024-09-14 17:48:55
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-09-21 23:04:40
* @FilePath: /map-demo/src/views/bieyuan/index.vue
* @Description: 文件描述
-->
<template>
<div class="index-page">
<div style="display: flex; flex-direction: column; align-items: center;">
<van-image
width="12rem"
height="12rem"
fit="contain"
src="https://cdn.ipadbiz.cn/bieyuan/map/icon/index_logo@3x.png"
/>
<div style="margin-top: 2rem; font-size: 0.95rem; letter-spacing: 5px; color: #47525F;">山水逢甘露,静心遇桃源</div>
</div>
<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>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
const $route = useRoute();
const $router = useRouter();
const goTo = () => {
$router.push({
path: './map',
query: {
id: $route.query.id
}
});
// 进入标记
localStorage.setItem('first_in_bieyuan', 1);
}
onMounted(() => {
// 记录第一次进入页面,之后判断是否第一次进入,直接跳转到MAP页面
if (localStorage.getItem('first_in_bieyuan') === '1') {
$router.push({
path: './map',
query: {
id: $route.query.id
}
});
}
});
</script>
<style lang="less" scoped>
.index-page {
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
background: linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)),
url('https://cdn.ipadbiz.cn/bieyuan/map/MAP@3x.png');
background-size: contain;
}
</style>
<!--
* @Date: 2024-09-15 22:08:49
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-03-22 21:33:41
* @FilePath: /map-demo/src/views/by/info.vue
* @Description: 文件描述
-->
<template>
<div class="info-page">
<div class="info-header-wrapper">
<div v-if="showBack && page_details.banner?.length" style="position: absolute; top: 1rem; left: 0.5rem; z-index: 9;">
<van-icon name="arrow-left" color="white" size="1.75rem" @click="goBack()" />
</div>
<van-config-provider :theme-vars="themeVars">
<van-swipe class="my-swipe" indicator-color="#DD7850" lazy-render :autoplay="5000">
<van-swipe-item v-for="(image, index) in page_details.banner" :key="index" style="position: relative;">
<van-image fit="cover" width="100%" :height="img_height" :src="image" @click="onClickImg(index)" />
<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="">
</van-swipe-item>
</van-swipe>
</van-config-provider>
<div class="header-z"></div>
</div>
<div class="info-content-wrapper">
<div class="info-header">
<div style="display: flex; justify-content: space-between;">
<p class="info-title">{{ page_details.name }}</p>
<div style="display: flex;">
<div v-if="page_details.show_audio" @click="onClickAudioList" style="margin-right: 0.75rem;">
<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" />
<van-icon v-else name="https://cdn.ipadbiz.cn/bieyuan/map/icon/%E8%AF%AD%E9%9F%B32@3x.png" size="1.65rem" />
</div>
<div v-if="page_details.path?.length > 1" @click="goTo()" class="info-btn">前往</div>
<div @click="goToWalk()" class="info-btn">前往</div>
</div>
</div>
<div class="info-sub-title">{{ page_details.note }}</div>
</div>
<div id="tab-wrapper" style="margin-top: 0.5rem;">
<van-config-provider :theme-vars="themeVars">
<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>
<van-tab title="介 绍" v-if="page_details.introduction">
<div class="info-content">
<div id="introduction" v-html="page_details.introduction" style="padding: 0 1rem;"></div>
</div>
</van-tab>
<van-tab title="故 事" v-if="page_details.story">
<div class="info-content">
<div id="story" v-html="page_details.story" style="padding: 0 1rem;"></div>
</div>
</van-tab>
<van-tab title="体 验" v-if="page_details.experience">
<div class="info-content">
<div id="experience" v-html="page_details.experience" style="padding: 0 1rem;"></div>
</div>
<div v-if="page_details.experience_audio.length" class="audio-wrapper">
<div @click="toggleHandleAudio(item, index)" :class="['audio-item', play_audio_index === index ? 'click' : '']" v-for="(item, index) in page_details.experience_audio" :key="index">
<div>{{ item.description }}</div>
<van-icon @click.stop="stopAudio(item, index)" v-if="item.play" size="2rem" name="stop-circle-o" color="#DD7850" />
<van-icon v-else @click="playAudio(item, index)" size="2rem" name="https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png" />
</div>
</div>
</van-tab>
</van-tabs>
</van-config-provider>
</div>
</div>
<!-- <div class="info-logo" :style="{ marginBottom: audio_list_height ? `${audio_list_height * 1.5}px` : '3rem' }">
<van-image width="3rem" height="3rem" fit="contain" src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_logo.png" />
</div> -->
<van-toast v-model:show="show_toast" style="padding: 0">
<template #message>
<p style="padding: 0.5rem 1rem;">{{ toast_text }}</p>
</template>
</van-toast>
<van-image-preview v-model:show="show_preview" :images="preview_images" @change="onChange" doubleScale>
<template v-slot:index>第{{ index + 1 }}张</template>
</van-image-preview>
<van-back-top />
<audio-play-list :height="audio_list_height" :status="audio_status" @close="onCloseAudioList" @status="onStatusAudioList"></audio-play-list>
</div>
</template>
<script setup>
import { ref, watch, watchEffect } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { showImagePreview } from 'vant';
import { storeToRefs } from 'pinia'
import audioPlayList from '@/components/audioList.vue'
import { mainStore, useTitle } from '@/utils/generatePackage'
import wx from 'weixin-js-sdk';
import $ from 'jquery';
import { mapAPI, mapAudioAPI } from '@/api/map.js'
const store = mainStore();
const { audio_status, audio_entity, audio_list_status, audio_list_entity } = storeToRefs(store);
const $route = useRoute();
const $router = useRouter();
const themeVars = ref({
swipeIndicatorInactiveBackground: '#fff',
swipeIndicatorMargin: '1.5rem',
tabFontSize: '0.95rem',
});
const props = defineProps({
info: Object,
height: Number
});
const page_details = ref({});
watch(
() => props.info,
(v) => {
if (v.details.length) {
page_details.value = { ...v.details[0], position: v.position, path: v.path };
// 获取浏览器可视范围的高度
$('.info-page').height(props.height + 'px');
}
}
)
const images = ref([
'https://cdn.ipadbiz.cn/bieyuan/map/swiper_img.png',
'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20230612_201951.png',
'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png',
]);
const active = ref(0);
const play_audio_index = ref(null);
// const audioList = ref([{
// text: '5分钟观呼吸',
// 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',
// play: false,
// }, {
// text: '10分钟正念静坐',
// 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',
// play: false,
// }, {
// text: '15分钟正念静坐',
// 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',
// play: false,
// }])
const toggleHandleAudio = (item, index) => { // 切换播放或者暂停操作
if (item.play) {
stopAudio(item, index);
} else {
playAudio(item, index);
}
}
const playAudio = (item, index) => {
page_details.value.experience_audio.forEach(item => item.play = false);
audio.value.src = item.src;
// 后台有播放器运行时,先暂停
if (audio_list_status.value === 'play'){
audio_list_entity.value.pause();
}
play_audio_index.value = index;
let play_status = audio.value.play() // 播放
if (play_status) {
play_status.then(() => {
item.play = true;
// 存放到pinia里面控制
store.changeAudio(audio.value);
store.changeAudioSrc(audio.value.src);
store.changeAudioStatus('play');
}).catch((e) => {
// 失败
console.log('Operation is too fast, audio play fails')
})
}
}
const stopAudio = (item, index) => {
item.play = false;
audio.value.pause();
}
const audio = ref(new Audio());
const img_height = ref('15rem');
const scrollTop = ref(0);
onMounted(async () => {
// 通过ID查询到标记点详情
if (!props.info) {
let id = $route.query.id;
const { data } = await mapAPI({ i: id });
const raw_list = data.list[0].list; // 获取标记点列表
const marker_id = $route.query.marker_id;
const current_marker = raw_list.filter(item => item.id == marker_id)[0];
//
page_details.value = { ...current_marker.details[0], position: current_marker.position, path: current_marker.path };
// 富文本转义, 分割线样式转换
page_details.value.introduction = page_details.value.introduction?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
page_details.value.story = page_details.value.story?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
page_details.value.experience = page_details.value.experience?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
// 查询是否有音频列表,打标记点是否有音频
const getAudioList = await mapAudioAPI({ mid: $route.query.id, bid: marker_id });
if (getAudioList.data && getAudioList.data.length) {
page_details.value.show_audio = true;
}
}
// 介绍栏目,图片点击事件
var imgs = $('#introduction').find('img');
// 图片点击事件
imgs.each(function(index, img) {
$(img).on('click', function (e) {
showImagePreview({
images: [$(img).attr('src')],
startPosition: 0,
showIndex: false,
onClose: () => {
// console.log('close');
}
})
});
// 图片有2个像素的圆角
$(img).css('border-radius', '5px');
});
// 获取屏幕宽度,设置高度4:3
// 获取屏幕的宽度
var screenWidth = $(window).width();
// 计算4:3比例的高度
var screenHeight = screenWidth * 3 / 4;
// 设置容器的高度
img_height.value = screenHeight + 'px';
//
nextTick(() => {
$('.info-page').on('scroll', (evt) => {
scrollTop.value = $(evt.currentTarget).scrollTop(); // 获取滚动的垂直距离
})
})
// 如果是从浮动窗口点击进来音频播放,自动打开
if ($route.query.source === 'click_audio') {
setTimeout(() => {
audio_list_height.value = (0.2 * window.innerHeight);
// 修改当前路由参数,避免刷新再次播放
$router.push({
query: {
...$route.query,
source: ''
}
});
}, 500);
}
// 地图标题
document.title = page_details.value.name;
// 微信分享
const shareData = {
title: page_details.value.name, // 分享标题
desc: '别院详情', // 分享描述
link: location.origin + location.pathname + location.hash, // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
imgUrl: '', // 分享图标
success: function () {
// console.warn('设置成功');
}
}
// 分享好友(微信好友或qq好友)
wx.updateAppMessageShareData(shareData);
// 分享到朋友圈或qq空间
wx.updateTimelineShareData(shareData);
// 分享到腾讯微博
wx.onMenuShareWeibo(shareData);
});
// watchEffect(
// () => useTitle(page_details.value.name) // 地图标题
// )
onUnmounted(() => { // 离开页面时关闭音频播放
audio.value.pause();
store.changeAudioStatus('pause');
})
const audio_play = (src, index) => {
audio.value.src = src;
}
const outerStopAudio = () => {
audio.value.pause();
}
const emit = defineEmits(["closeFloat", 'route', 'walkRoute']);
const show_toast = ref(false);
const toast_text = ref('');
const goTo = () => { // 打开标记地图显示
// 没有关联导航提示
if (page_details.value.path.length <= 1) {
show_toast.value = true;
toast_text.value = '该标记点没有关联导航';
return;
}
//
if ($router.currentRoute.value.path === '/by/info') { // 详情页
$router.push({
path: '/by',
query: {
id: $route.query.id,
marker_id: $route.query.marker_id
}
})
} else { // 地图页
//
emit("closeFloat", false);
//
emit("route", {name: '参观路径', path: page_details.value.path});
}
}
const goToWalk = async () => { // 打开步行导航地图显示
//
if ($router.currentRoute.value.path === '/by/info') { // 详情页
$router.push({
path: '/by',
query: {
id: $route.query.id,
marker_id: $route.query.marker_id
}
})
} else { // 地图页
//
emit("closeFloat", false);
//
if (page_details.value?.position.length) {
emit("walkRoute", {name: '步行导航', point: [+page_details.value?.position[0], +page_details.value?.position[1]]});
} else {
show_toast.value = true;
toast_text.value = '该标记点没有关联导航';
}
}
}
const goBack = () => { // 返回首页
$router.push({
path: '/by',
query: {
id: $route.query.id,
}
})
}
const showBack = computed(() => $router.currentRoute.value.path === '/by/info');
const voicePause = () => {
audio.value.pause();
store.changeAudioStatus('pause');
}
const tabsRef = ref(null);
const clickTab = (evt) => { // 标签切换
tabsRef.value.resize();
nextTick(() => {
if (evt.title === '介 绍') { // 介绍
var imgs = $('#introduction').find('img');
}
if (evt.title === '故 事') { // 故事
var imgs = $('#story').find('img');
}
if (evt.title === '体 验') { // 体验
var imgs = $('#experience').find('img');
}
// 图片点击事件
imgs.each(function(index, img) {
$(img).on('click', function (e) {
showImagePreview({
images: [$(img).attr('src')],
startPosition: 0,
showIndex: false,
onClose: () => {
// console.log('close');
}
})
})
// 图片有5个像素的圆角
$(img).css('border-radius', '5px');
});
// 滚动高度大于tabs高度后才滚动到指定高度
let offsetTop = $('#tab-wrapper')[0].offsetTop;
if (scrollTop.value >= offsetTop) {
$('.info-page').scrollTop(offsetTop);
}
});
}
watch(
() => audio_status.value,
(v) => {
if (v === 'pause') {
voicePause();
page_details.value.experience_audio?.forEach(item => item.play = false);
}
},
{ immediate: true }
);
defineExpose({
outerStopAudio
})
const show_preview = ref(false);
const index = ref(0);
const preview_images = [];
const onChange = (newIndex) => {
index.value = newIndex;
};
const onClickImg = (idx) => {
if ($('.info-page').height() >= $(window).height()) {
showImagePreview({
images: page_details.value.banner,
startPosition: idx,
showIndex: true,
onClose: () => {
// console.log('close');
}
})
}
};
const show_shrink = computed(() => {
// 统计非空字段的个数
let filledFields = 0;
if (page_details.value.introduction) filledFields++;
if (page_details.value.story) filledFields++;
if (page_details.value.experience) filledFields++;
// 判断是否只有一个字段有值
if (filledFields === 1) {
return true;
}
return false;
});
const audio_list_height = ref(0);
const onClickAudioList = () => {
if ($('.info-page').height() < $(window).height()) { // 在浮动模式下点击音频列表
// 打开页面
$router.push({
path: '/by/info',
query: {
id: $route.query.id,
marker_id: props.info.id,
source: 'click_audio'
}
});
} else { // 详情页内点击
audio_list_height.value = (0.2 * window.innerHeight);
}
}
const onCloseAudioList = () => {
audio_list_height.value = 0;
}
// const show_audio = ref(true);
const onStatusAudioList = (status) => { // 音频列表组件,状态改变
page_details.value.experience_audio?.forEach(item => item.play = false);
audio.value.pause();
play_audio_index.value = null;
store.changeAudioStatus('pause');
// // 反馈播放列表数量为空时,隐藏图标
// if (status === 'none') {
// show_audio.value = false;
// }
}
</script>
<style lang="less">
.info-page {
background-color: #EBEBEB;
height: 100vh;
overflow: scroll;
position: relative;
.info-header-wrapper {
position: relative;
min-height: 2rem;
.header-z {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1rem;
// box-shadow: rgba(241, 242, 248, 0.6) 0px -3px 25px 15px;
// background-color: #f7f7f7;
background-color: #fff;
margin: 0 1rem;
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
}
.info-content-wrapper {
box-shadow: 0px -3px 6px 0 rgba(241, 242, 248, 0.8);
margin: 1rem;
margin-top: 0;
// padding: 1rem;
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
background-color: white;
.info-header {
padding: 1rem 2rem 0;
// display: flex;
// justify-content: space-between;
// align-items: center;
.info-title {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
.info-sub-title {
font-size: 0.85rem;
color: #A0A8B1;
line-height: 1.75;
}
.info-btn {
width: 3rem;
height: 1.5rem;
border: 1px solid #DD7850;
color: #DD7850;
border-radius: 0.8rem;
font-size: 0.85rem;
text-align: center;
line-height: 1.5rem;
}
}
.info-content {
color: #47525F;
padding: 1rem;
line-height: 1.75;
p {
line-height: 1.75;
// padding: 0 0.85rem;
text-align: justify;
img {
width: 100%;
}
}
}
.audio-wrapper {
padding: 1rem;
.audio-item {
color: #47525F;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #FFF;
border-radius: 0.25rem;
margin: 1rem;
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);
&.click {
border: 1px solid #DD7850;
}
.audio-icon {
width: 2rem;
height: 2rem;
background-image: url('https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png'); /* 使用上传的图标 */
background-size: cover;
&.click {
animation: pulse 1.5s infinite;
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
}
}
}
.info-logo {
display: flex;
justify-content: center;
margin: 3rem;
}
.van-tabs__wrap {
border-bottom: 1px solid #F3F3F3;
}
.van-tabs__nav--line.van-tabs__nav--shrink {
padding-left: 2rem;
}
}
.van-back-top {
background-color: #DD7850;
}
</style>
<!--
* @Date: 2024-09-15 22:08:49
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-02-10 16:11:41
* @FilePath: /map-demo/src/views/by/info_w.vue
* @Description: 文件描述
-->
<template>
<div class="info-page">
<div>
<van-config-provider :theme-vars="themeVars">
<van-swipe class="my-swipe" indicator-color="#DD7850" lazy-render :autoplay="5000">
<van-swipe-item v-for="image in images" :key="image">
<van-image fit="cover" width="100%" height="13rem" :src="image" />
</van-swipe-item>
</van-swipe>
</van-config-provider>
</div>
<div class="info-content-wrapper">
<div class="info-header">
<div>
<p class="info-title">选佛场</p>
<p class="info-sub-title">南楼2层</p>
</div>
<div @click="goTo()" class="info-btn">前往</div>
</div>
<div class="van-hairline--bottom">
<van-tabs v-model:active="active" color="#DD7850" title-active-color="#DD7850" sticky>
<van-tab title="介 绍">
<div class="info-content">
<p style="line-height: 1.75; padding: 0 0.85rem; color: #47525F;">选佛场是一个宗教活动场所,集禅堂与讲堂功能于一体。其设计仿制古代石窟样式,把古代人修行的场所“石窟”搬进室内。禅堂内的释迦牟尼佛像,仿麦积山石窟第44号特窟“东方的微笑”的造型。禅堂设计自然古朴,匠心独运,星光、烛光、月光三光辉映,营造出“一个人不孤单,一千人不喧闹”的宁静祥和的氛围。禅堂可容纳千人,开展讲经、禅修、法会等多种活动,提供礼佛、供灯、静坐等体验。</p>
<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>
<div style="padding: 1rem;">
<div style="color: #DD7850;">•&nbsp;五方塔</div>
<div style="color: #47525F; margin-top: 1rem; line-height: 1.75;">禅堂外的草坪,安立着大小不一五座佛塔。信众可绕塔或悬挂风铃祝愿祈福,在肃静庄严的氛围中得到佛力加持,自净其意,心想事成。</div>
</div>
</div>
</van-tab>
<van-tab title="故 事">
<div style="padding: 0 1rem;">
<div style="padding: 1rem;">
<div style="color: #DD7850;">•&nbsp;选官何如选佛</div>
</div>
<div style="padding: 0 1rem;">
<van-image width="100%" height="11rem" fit="cover" src="https://cdn.ipadbiz.cn/bieyuan/map/Mix_20230612_201951.png" />
</div>
<div style="padding: 1rem;">
<p style="color: #47525F; line-height: 1.75;">过去把禅堂叫作选佛场,意思是选择作佛的场所。这个典故与丹霞禅师有关。 <br />他原本是一个秀才,赴京赶考的途中遇到一位禅师,这位禅师跟他讲,选官何如选佛?考官还不如成佛利益更大。世间功名如过眼云烟,即便追求得到也是暂时利益,执著于此就会烦恼重重,甚至不断造业。而学佛修行,考佛就是要成佛,成就生命永恒的福祉。这是永久的利益,尽未来际的利益。每个生命原本具备觉悟的潜质,具有无尽的功德宝藏,取之不尽用之不竭。成佛,可以断除一切迷惑烦恼,可以彻底地开发我们生命的潜质,全然觉醒,这个利益无量无边。丹霞禅师深具慧根,一经点拨,马上出家。</p>
</div>
<div class="van-hairline--bottom" style="margin: 0 1rem;"></div>
</div>
<div style="padding: 0 1rem;">
<div style="padding: 1rem;">
<div style="color: #DD7850;">•&nbsp;把洞窟搬进讲堂</div>
</div>
<div style="padding: 0 1rem;">
<van-image width="100%" height="11rem" fit="cover" src="https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png" />
</div>
<div style="padding: 1rem;">
<p style="color: #47525F; line-height: 1.75;">洞窟,是传统的佛教建筑形式,最早在印度盛行,古代僧人喜欢在崇山峻岭的幽僻处开凿洞窟,遁世修行。选佛场集禅堂与讲堂的功能于一体,把洞窟搬进讲堂,既有回归佛教本怀的宁静温暖,又体现出融入泰宁岩穴文化的祥和之气。</p>
</div>
<div class="van-hairline--bottom" style="margin: 0 1rem;"></div>
</div>
</van-tab>
<van-tab title="体 验">
<div style="padding: 0 1rem;">
<div style="padding: 1rem;">
<div style="color: #DD7850;">•&nbsp;供灯</div>
<div style="color: #47525F; margin-top: 1rem; line-height: 1.75;">禅堂内自助供灯。</div>
</div>
<div style="padding: 0 1rem;">
<van-image width="100%" height="11rem" fit="cover" src="https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png" />
</div>
<div class="van-hairline--bottom" style="margin: 0 1rem;"></div>
</div>
<div class="audio-wrapper">
<div :class="['audio-item', play_audio_index === index ? 'click' : '']" v-for="(item, index) in audioList" :key="index">
<div>{{ item.text }}</div>
<!-- <div :class="['audio-icon', play_audio_index === index ? 'click' : '']"></div> -->
<van-icon @click="stopAudio(item, index)" v-if="item.play" size="2rem" name="stop-circle-o" color="#DD7850" />
<van-icon v-else @click="playAudio(item, index)" size="2rem" name="https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png" />
</div>
</div>
<div style="padding: 0 1rem;">
<img src="https://cdn.ipadbiz.cn/bieyuan/map/xcx.png" style="width: 100%;">
</div>
</van-tab>
</van-tabs>
</div>
</div>
<!-- <div style="display: flex; justify-content: center; margin: 3rem;">
<van-image
width="3rem"
height="3rem"
fit="contain"
src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_logo.png"
/>
</div> -->
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { mainStore } from '@/store';
const store = mainStore();
const { audio_status, audio_entity } = storeToRefs(store);
const $route = useRoute();
const $router = useRouter();
const themeVars = ref({
swipeIndicatorInactiveBackground: '#fff',
});
const images = ref([
'https://cdn.ipadbiz.cn/bieyuan/map/swiper_img.png',
'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20230612_201951.png',
'https://cdn.ipadbiz.cn/bieyuan/map/Mix_20240815_211927.png',
]);
const active = ref(0);
const play_audio_index = ref(null);
const audioList = ref([{
text: '5分钟观呼吸',
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',
play: false,
}, {
text: '10分钟正念静坐',
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',
play: false,
}, {
text: '15分钟正念静坐',
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',
play: false,
}])
const playAudio = (item, index) => {
audioList.value.forEach(item => item.play = false);
audio.value.src = item.src;
play_audio_index.value = index;
let play_status = audio.value.play() // 播放
if (play_status) {
console.warn('start');
// if (audio_status.value === 'play') {
// audio_entity.value.pause();
// }
play_status.then(() => {
console.warn('success');
item.play = true;
// 存放到pinia里面控制
// store.changeAudioSrc(audio.value.src);
// store.changeAudioStatus('play');
}).catch((e) => {
// 失败
console.log('Operation is too fast, audio play fails')
})
}
}
const stopAudio = (item, index) => {
item.play = false;
audio.value.pause();
}
const audio = ref(new Audio());
onMounted(() => {
// 存放到pinia里面控制
store.changeAudio(audio.value);
// store.changeAudioStatus('pause');
})
onUnmounted(() => {
audio.value.pause();
store.changeAudioStatus('pause');
})
const audio_play = (src, index) => {
audio.value.src = src;
}
const outerStopAudio = () => {
audio.value.pause();
}
const emit = defineEmits(["closeFloat", 'route']);
const goTo = () => { // 打开标记地图显示
if ($router.currentRoute.value.path === '/by/info') { // 详情页
$router.push({
path: '/by',
query: {
id: $route.query.id,
marker_id: '12345'
}
})
} else { // 地图页
//
emit("closeFloat", false);
//
emit("route", 'marker_id');
}
}
const voicePause = () => {
audio.value.pause();
store.changeAudioStatus('pause');
}
watch(
() => audio_status.value,
(v) => {
if (v === 'pause') {
voicePause();
audioList.value.forEach(item => item.play = false);
}
},
{ immediate: true }
);
defineExpose({
outerStopAudio
})
</script>
<style lang="less">
.info-page {
background-color: #EBEBEB;
height: 100vh;
overflow: scroll;
position: relative;
.info-content-wrapper {
// position: absolute;
// top: 14.9rem;
margin: 1rem;
margin-top: 0;
// padding: 1rem;
border-radius: 0.5rem;
background-color: white;
.info-header {
padding: 1rem 2rem 0;
display: flex;
justify-content: space-between;
// align-items: center;
.info-title {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
.info-sub-title {
font-size: 0.85rem;
color: #A0A8B1;
}
.info-btn {
width: 3rem;
height: 1.5rem;
border: 1px solid #DD7850;
color: #DD7850;
border-radius: 0.8rem;
font-size: 0.85rem;
text-align: center;
line-height: 1.5rem;
}
}
.info-content {
padding: 1rem;
}
.audio-wrapper {
padding: 1rem;
.audio-item {
color: #47525F;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #FFF;
border-radius: 0.25rem;
margin: 1rem;
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);
&.click {
border: 1px solid #DD7850;
}
.audio-icon {
width: 2rem;
height: 2rem;
background-image: url('https://cdn.ipadbiz.cn/bieyuan/map/icon/audio_icon.png'); /* 使用上传的图标 */
background-size: cover;
&.click {
animation: pulse 1.5s infinite;
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
}
}
}
}
</style>
<!--
* @Date: 2023-05-19 14:54:27
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-08 14:51:23
* @FilePath: /map-demo/src/views/by/map.vue
* @Description: 公众地图主体页面
-->
<template>
<div ref="root" style="height: 100vh; position: relative; overflow: hidden;">
<div id="container"></div>
<!-- 添加导航面板容器 -->
<div id="walking-panel" style="position: absolute; bottom: 1rem; left: 1rem; padding: 1rem;"></div>
<div style="position: absolute; top: 2rem; right: 1rem; display: flex; flex-direction: column;">
<!-- <van-icon size="2rem" name="search" color="#DD7850" style="margin-bottom: 1rem;" /> -->
<van-image
width="2rem"
height="2rem"
fit="contain"
src="https://cdn.ipadbiz.cn/bieyuan/map/icon/NAV@3x.png"
/>
</div>
<div v-if="data_logo" style="position: absolute; top: 2rem; left: calc(50% - 1.5rem); opacity: 0.5;">
<van-image
width="3rem"
height="3rem"
fit="contain"
:src="data_logo"
/>
</div>
<div @click="scanQrcode" style="position: absolute; bottom: 1rem; left: calc(50% - 2.5rem);">
<van-image
width="5rem"
height="5rem"
fit="contain"
src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan@3x.png"
/>
</div>
<van-config-provider :theme-vars="themeVars">
<van-floating-panel v-model:height="info_height" :anchors="anchors" @height-change="onHeightChange">
<!-- <template #header>
<div class="custom-header">
<h3>自定义标题</h3>
<button @click="show = false">关闭</button>
</div>
</template> -->
<page-info ref="pageInfo" :info="itemInfo" :height="info_height" @close-float="onCloseFloat" @route="onRoute" @walk-route="onWalkRoute"></page-info>
<!-- <div v-if="showClose" @click="closeFloatPanel" class="close-float-panel">
<van-icon name="arrow-left" color="#FFF" size="1.5rem" />
</div> -->
</van-floating-panel>
</van-config-provider>
<div v-if="!show_walk_route" @click="removeSafeRoute({ name: '参观路径' })" class="walk-nav-text">
关闭步行导航
</div>
<!-- 新增关闭导航按钮 -->
<div v-if="walking && !show_walk_route" class="walk-nav-text" @click="closeWalkingRoute">
关闭步行导航
</div>
<van-dialog v-model:show="dialog_show" title="温馨提示">
<div style="padding: 1rem; text-align: center;">{{ dialog_text }}</div>
</van-dialog>
<!-- 背景音乐控制 -->
<!-- <audioBackground1></audioBackground1> -->
<div class="operate-bar-wrapper">
<div class="box-wrapper">
<div v-if="open_current_location" class="item" @click="handleLocation(true)">
<van-icon name="https://cdn.ipadbiz.cn/xys/map/%E5%AE%9A%E4%BD%8Dloc@2x.png" size="1.5rem"
style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);;" />
</div>
<div v-else class="item" @click="handleLocation(false)">
<van-icon name="https://cdn.ipadbiz.cn/xys/map/%E5%AE%9A%E4%BD%8Dloc@2x.png" size="1.5rem"
style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);" />
</div>
</div>
</div>
<van-toast v-model:show="show_toast" style="padding: 0">
<template #message>
<p style="padding: 0.5rem 1rem;">{{ toast_text }}</p>
</template>
</van-toast>
</div>
</template>
<script>
import "@vant/touch-emulator";
// import { mapState } from 'vuex'
import coord from '@/common/map_data'
import my_router from '@/common/my_router'
import _ from 'lodash';
import $ from 'jquery';
import { useRect } from '@vant/use';
import { mapAPI } from '@/api/map.js'
import wx from 'weixin-js-sdk'
import pageInfo from '@/views/by/info.vue'
import audioBackground1 from '@/components/audioBackground1.vue'
import { mapState, mapActions } from 'pinia'
import { mainStore } from '@/store'
import { parseQueryString } from '@/utils/tools'
import AMapLoader from '@amap/amap-jsapi-loader'
import { mapAudioAPI } from '@/api/map.js'
const GPS = {
PI: 3.14159265358979324,
x_pi: 3.14159265358979324 * 3000.0 / 180.0,
delta: function (lat, lon) {
var a = 6378245.0; // a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
var ee = 0.00669342162296594323; // ee: 椭球的偏心率。
var dLat = this.transformLat(lon - 105.0, lat - 35.0);
var dLon = this.transformLon(lon - 105.0, lat - 35.0);
var radLat = lat / 180.0 * this.PI;
var magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
var sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * this.PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * this.PI);
return {
'lat': dLat,
'lon': dLon
};
},
//WGS-84 to GCJ-02
gcj_encrypt: function (wgsLat, wgsLon) {
if (this.outOfChina(wgsLat, wgsLon))
return {
'lat': wgsLat,
'lon': wgsLon
};
var d = this.delta(wgsLat, wgsLon);
return {
'lat': wgsLat + d.lat,
'lon': wgsLon + d.lon
};
},
outOfChina: function (lat, lon) {
if (lon < 72.004 || lon > 137.8347)
return true;
if (lat < 0.8293 || lat > 55.8271)
return true;
return false;
},
transformLat: function (x, y) {
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));
ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0;
return ret;
},
transformLon: function (x, y) {
var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0;
return ret;
}
};
// 关键安全配置
window._AMapSecurityConfig = {
securityJsCode: 'ac1a3a5858d74b7d6c50b6858100aa12', // 替换为你的密钥
}
export default {
components: { pageInfo, audioBackground1 },
data() {
return {
map: '',
geolocation: '',
current_lng: '',
current_lat: '',
dialog_show: false,
dialog_text: '',
location_marker: '',
itemInfo: {},
navBarList: [],
navList: [],
navKey: '',
markerSum: [], // marker合集
mapTiles: [],
data_center: [], // 接口获取-地图中心点
data_zoom: '', // 接口获取-地图默认缩放
data_zooms: '', // 接口获取-地图默认缩放范围
data_rotation: 0, // 接口获取-地图旋转角度
data_paths: {}, // 接口获取-地图导航路径
data_path_list: [], // 接口获取-地图导航路径
info_height: 0,
anchors: [0, (0.65 * window.innerHeight), (1 * window.innerHeight)],
themeVars: {
floatingPanelHeaderHeight: 0,
floatingPanelBorderRadius: '1.25rem'
},
showClose: false,
markerStyle2: { // 选中
//设置文本样式,Object 同 css 样式表
"padding": ".5rem .2rem .5rem .2rem",
// "margin-bottom": "1rem",
"border-color": "#DD7850",
"border-radius": ".25rem",
"background-color": "#FFF",
// "width": "1rem",
// "border-width": 0,
// "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
// "text-align": "center",
"font-size": "0.8rem",
"color": "#DD7850",
"writing-mode": "vertical-rl",
"text-orientation": "mixed",
"display": "flex",
"justify-content": "center",
"align-items": "center",
},
markerStyle1: { // 未选中
//设置文本样式,Object 同 css 样式表
"padding": ".5rem .2rem .5rem .2rem",
// "margin-bottom": "1rem",
"border-color": "#fcfbfa",
"border-radius": ".25rem",
"background-color": "#DD7850",
// "width": "1rem",
// "border-width": 0,
// "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
// "text-align": "center",
"font-size": "0.8rem",
"color": "white",
"writing-mode": "vertical-rl",
"text-orientation": "mixed",
"display": "flex",
"justify-content": "center",
"align-items": "center",
},
markerStyle2_horizontal: { // 选中
//设置文本样式,Object 同 css 样式表
"padding": ".2rem .5rem .2rem .5rem",
// "margin-bottom": "1rem",
"border-color": "#DD7850",
"border-radius": ".25rem",
"background-color": "#FFF",
// "width": "1rem",
// "border-width": 0,
// "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
// "text-align": "center",
"font-size": "0.8rem",
"color": "#DD7850",
"writing-mode": "horizontal",
"text-orientation": "mixed",
"display": "flex",
"justify-content": "center",
"align-items": "center",
},
markerStyle1_horizontal: { // 未选中
//设置文本样式,Object 同 css 样式表
"padding": ".2rem .5rem .2rem .5rem",
// "margin-bottom": "1rem",
"border-color": "#fcfbfa",
"border-radius": ".25rem",
"background-color": "#DD7850",
// "width": "1rem",
// "border-width": 0,
// "box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
// "text-align": "center",
"font-size": "0.8rem",
"color": "white",
"writing-mode": "horizontal",
"text-orientation": "mixed",
"display": "flex",
"justify-content": "center",
"align-items": "center",
},
current_safe_route: [],
route_safe_marker: [],
show_walk_route: true,
open_current_location: true,
show_toast: false,
toast_text: '',
data_logo: '',
data_layers: [],
point_range: [
[117.044223,26.835105], [117.044227,26.842448], [117.0552,26.842452], [117.055195,26.8351]
],
walking: '',
}
},
async mounted() {
const AMap = await AMapLoader.load({
key: '17b8fc386104b89db88b60b049a6dbce', // 控制台获取
version: '2.0', // 指定API版本
plugins: ['AMap.ElasticMarker','AMap.ImageLayer','AMap.ToolBar','AMap.IndoorMap','AMap.Walking','AMap.Geolocation'] // 必须加载步行导航插件
})
const code = this.$route.query.id;
const { data } = await mapAPI({ i: code });
this.navBarList = data.list; // 底部导航条
this.mapTiles = data.level; // 获取图层
this.navKey = data.list.length ? data.list[0]['id'] : 0; // 默认选中 第一个 id
this.navList = data.list.length ? data.list.filter(item => item.id === this.navKey)[0]['list'] : []; // 返回默认选中项的实体信息
this.data_center = data.map.center.map(item => Number(item)); // 地图中心点
this.data_zoom = data.map.zoom; // 地图默认缩放
this.data_rotation = data.map.rotation; // 地图旋转角度
this.data_zooms = data.map.zooms.map(item => Number(item)); // 地图默认缩放范围
this.data_paths = data.map.path ? data.map.path : {}; // 地图默认导航路径
this.data_logo = data.map.map_logo ? data.map.map_logo : ''; // 地图logo
this.point_range = data.map.map_range ? data.map.map_range : []; // 地图定位范围
if (data.map.map_layers) { // 地图默认图层
if (data.map.map_layers === 'satellite') { // 卫星和路网
this.data_layers = [new AMap.TileLayer.Satellite(), new AMap.TileLayer.RoadNet()]
} else { // 平面图
this.data_layers = [];
}
}
if (data.map.path) {
for (const key in data.map.path) {
const element = data.map.path[key];
this.data_path_list.push({
name: key,
path: element,
status: true
})
}
}
// 地图标题
document.title = data.map.map_title;
// 微信分享
const shareData = {
title: data.map.map_title, // 分享标题
desc: '别院地图', // 分享描述
link: location.origin + location.pathname + location.hash, // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
imgUrl: '', // 分享图标
success: function () {
// console.warn('设置成功');
}
}
// 分享好友(微信好友或qq好友)
wx.updateAppMessageShareData(shareData);
// 分享到朋友圈或qq空间
wx.updateTimelineShareData(shareData);
// 分享到腾讯微博
wx.onMenuShareWeibo(shareData);
// 初始化地图
this.initMap();
// this.setMapBoundary();
// 使用之前获取当前地址,判断当前是否能够获取经纬度
// wx.getLocation({
// type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
// success: (res) => {
// var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
// var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
// var speed = res.speed; // 速度,以米/每秒计
// var accuracy = res.accuracy; // 位置精度
// this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
// this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
// },
// });
// 设置贴片地图
this.setTitleLayer();
// 地图标题
document.title = data.map.map_title;
//
// setTimeout(() => {
// this.info_height = (0.5 * window.innerHeight);
// // 浮动面板样式
// $('.van-floating-panel__content').css('borderRadius', '1.5rem');
// }, 2000);
// 初始化步行导航插件时指定面板容器
this.walking = new AMap.Walking({
map: this.map,
// panel: 'walking-panel', // 必须指定存在的DOM元素ID
hideMarkers: false, // 设置隐藏路径规划的起始点图标
isOutline: true, // 使用map属性时,绘制的规划线路是否显示描边
autoFitView: true, // 是否自动调整地图视野到显示的路线
});
},
watch: {
// // 监听 $route 对象的 query 属性
// '$route.query': {
// handler(newQuery, oldQuery) {
// if (newQuery.marker_id) {
// }
// },
// immediate: true, // 设置为 true,确保在初始化时也执行一次 handler
// deep: true // 如果 query 是嵌套对象,可以设置 deep 监听深层变化
// }
},
computed: {
...mapState(mainStore, ['audio_entity', 'audio_src', 'audio_status'])
},
methods: {
...mapActions(mainStore, ['changeAudio', 'changeAudioSrc', 'changeAudioStatus']),
initMap() {
// 初始化地图
this.map = new AMap.Map('container', {
viewMode: '2D', // 设置地图模式
turboMode: false,
showIndoorMap: false,
defaultCursor: 'pointer', // 地图默认鼠标样式
showBuildingBlock: false, // 是否展示地图 3D 楼块
zooms: this.data_zooms, // 地图显示的缩放级别范围, 默认为 [2, 20] ,取值范围 [2 ~ 30]
showLabel: true, // 是否展示地图文字和 POI 信息
zoom: this.data_zoom, // 设置地图显示的缩放级别
pitch: 0, // 俯仰角度,默认 0,最大值根据地图当前 zoom 级别不断增大,2D地图下无效 。
rotation: this.data_rotation, // 地图顺时针旋转角度,取值范围 [0-360] ,默认值:0
center: this.data_center, // 设置地图中心点坐标
forceVector: false,
// rotateEnable: true,
layers: this.data_layers,
features: ['bg', 'road'], // 设置地图上显示的元素种类
animateEnable: false, // 地图平移过程中是否使用动画
resizeEnable: true,
WebGLParams: { // 新增WebGL优化参数
preserveDrawingBuffer: true,
antialias: true,
stencil: true,
alpha: true
},
optimizeTileStrategy: true, // 开启瓦片优化
autoRendering: false // 关闭自动渲染
});
// 添加地图点击事件
this.map.on("click", this.showInfoClick);
// 加载景点图层
this.navKey && this.loadMaker(this.navKey);
//
this.map.setRotation(this.data_rotation, true);
},
loadMaker(id) {
var zoomStyleMapping = { 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0 };
const entity_info = this.navBarList.filter(item => item.id === id)[0]['list'];
this.markerSum = [];
_.each(entity_info, (x, i) => {
let marker_icon = '';
if (entity_info[i].window_type === 'warn' && entity_info[i].details.length === 1) { // 如果是预警类型并且内部预警项目只有一个取details第一个icon
marker_icon = entity_info[i].details[0]['icon'];
} else {
marker_icon = entity_info[i].icon;
}
let text_direction = entity_info[i]?.writing_mode === 'vertical' ? 'vertical' : 'horizontal';
let textMarker = new AMap.Text({
zooms: [18, 20], // 点标记显示的层级范围,超过范围不显示。
text: entity_info[i].name, //标记显示的文本内容
anchor: "center", //设置文本标记锚点位置
// draggable: true, //是否可拖拽
// cursor: "pointer", //指定鼠标悬停时的鼠标样式。
// angle: 10, //点标记的旋转角度
style: text_direction === 'vertical' ? this.markerStyle1 : this.markerStyle1_horizontal,
position: entity_info[i].position, //点标记在地图上显示的位置
});
textMarker.setMap(this.map); //将文本标记设置到地图上
this.markerSum.push(textMarker);
if (clickListener1) {
textMarker.off('click', clickListener)
}
// 绑定景点的点击事件 - 文字出现才能触发
var clickListener1 = textMarker.on('click', async (e) => {
// 还原样式
this.markerSum.forEach(item => {
if (e.target.hS !== item.hS) {
// 修改文本的样式
item.setStyle(item._x['writing-mode'] === 'vertical-rl' ? this.markerStyle2 : this.markerStyle2_horizontal);
}
})
// 修改文本的样式
e.target.setStyle(text_direction === 'vertical' ? this.markerStyle1 : this.markerStyle1_horizontal);
// 修改文本内容
// textMarker.setText('样式已修改');
//
// 先获取音频信息
const { data, code } = await mapAudioAPI({ mid: this.$route.query.id, bid: entity_info[i].id });
// 创建新的对象并设置音频状态
const updatedInfo = JSON.parse(JSON.stringify(entity_info[i]));
if (data.length && updatedInfo.details && updatedInfo.details[0]) {
updatedInfo.details[0].show_audio = true;
}
// 使用 nextTick 确保视图更新
await this.$nextTick();
this.itemInfo = updatedInfo;
// 详情为空提示
if (!this.itemInfo.details.length) {
this.show_toast = true;
this.toast_text = '该景点暂无详情'
return;
}
// 打开浮动面板
this.info_height = (0.65 * window.innerHeight);
// 浮动面板样式
$('.van-floating-panel__content').css('borderRadius', '1.25rem');
$('.van-floating-panel').css('boxShadow', '0 0 15px black');
// 定位到当前位置中心
this.map.setZoomAndCenter(this.zoom, this.itemInfo.position);
// 获取地图容器的高度
const mapHeight = this.map.getSize().height;
// 计算需要向上移动的像素值,比如向上移动地图高度的一半左右
const offsetY = -mapHeight / 3.5;
// 使用 panBy 方法进行视图偏移
this.map.panBy(0, offsetY);
})
// if (entity_info[i]?.writing_mode === 'vertical') { // 标题文字垂直
// let textMarker = new AMap.Text({
// zooms: [18, 20], // 点标记显示的层级范围,超过范围不显示。
// text: entity_info[i].name, //标记显示的文本内容
// anchor: "center", //设置文本标记锚点位置
// // draggable: true, //是否可拖拽
// // cursor: "pointer", //指定鼠标悬停时的鼠标样式。
// // angle: 10, //点标记的旋转角度
// style: this.markerStyle1,
// position: entity_info[i].position, //点标记在地图上显示的位置
// });
// textMarker.setMap(this.map); //将文本标记设置到地图上
// this.markerSum.push(textMarker);
// if (clickListener1) {
// textMarker.off('click', clickListener)
// }
// // 绑定景点的点击事件 - 文字出现才能触发
// var clickListener1 = textMarker.on('click', (e) => {
// // 还原样式
// this.markerSum.forEach(item => {
// if (e.target.hS !== item.hS) {
// // 修改文本的样式
// item.setStyle(this.markerStyle2);
// }
// })
// // 修改文本的样式
// e.target.setStyle(this.markerStyle1);
// // 修改文本内容
// // textMarker.setText('样式已修改');
// //
// // console.warn(e);
// this.itemInfo = entity_info[i];
// // 详情为空提示
// if (!this.itemInfo.details.length) {
// this.show_toast = true;
// this.toast_text = '该景点暂无详情'
// return;
// }
// // 打开浮动面板
// this.info_height = (0.65 * window.innerHeight);
// // 浮动面板样式
// $('.van-floating-panel__content').css('borderRadius', '1.25rem');
// $('.van-floating-panel').css('boxShadow', '0 0 15px black');
// // 定位到当前位置中心
// this.map.setZoomAndCenter(this.zoom, this.itemInfo.position);
// // 获取地图容器的高度
// const mapHeight = this.map.getSize().height;
// // 计算需要向上移动的像素值,比如向上移动地图高度的一半左右
// const offsetY = -mapHeight / 3.5;
// // 使用 panBy 方法进行视图偏移
// this.map.panBy(0, offsetY);
// })
// }
// TODO: 获取详情定位信息用来导航
// 导航路径
let marker_id = this.$route.query.marker_id;
if (marker_id) {
this.$nextTick(() => {
let marker = this.navBarList[0]['list'].filter(item => item.id == marker_id)
// let path = marker[0].path;
// this.addSafeRoute({name: '参观路径', path});
// TAG: 新增步行导航
let position = marker[0].position;
wx.getLocation({
type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: (res) => {
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
var speed = res.speed; // 速度,以米/每秒计
var accuracy = res.accuracy; // 位置精度
this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
this.onWalkRoute({point: position});
},
});
// 获取当前 URL 的查询参数
let query = { ...this.$route.query };
// 删除 marker_id 参数
delete query.marker_id;
// 使用 Vue Router 更新 URL,并且不刷新页面
this.$router.replace({ query });
});
}
});
this.map.add(this.markerSum);
//
// setTimeout(() => {
// // 获取定位打标记
// this.setLocation();
// }, 1000);
},
isPointInRing() { // 是否在景区范围
let isPointInRing = AMap.GeometryUtil.isPointInRing([this.current_lng, this.current_lat], this.point_range);
return isPointInRing
},
setLocation() { // 开启定位服务
// 获取失败
// if (!this.current_lng || !this.current_lat) {
// this.dialog_show = true;
// this.dialog_text = '获取经纬度失败';
// }
this.getLocation();
},
getLocation() { // 获取经纬度
// PC端无法获取定位
// 微信获取地址
wx.getLocation({
type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: (res) => {
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
var speed = res.speed; // 速度,以米/每秒计
var accuracy = res.accuracy; // 位置精度
this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
// 判断是否在范围内
// if (!this.isPointInRing()) {
// this.dialog_show = true;
// this.dialog_text = '您不在景区范围内';
// } else {
// 使用纠正偏移后的地址,打一个定位标记
this.location_marker = new AMap.LabelMarker({
icon: {
image: 'https://cdn.ipadbiz.cn/bieyuan/map/icon/Group%2034@3x.png',
anchor: 'bottom-center',
size: [65, 65],
},
position: new AMap.LngLat(this.current_lng, this.current_lat), // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
});
this.map.add(this.location_marker);
// 定位到当前位置中心
this.map.setZoomAndCenter(this.zoom, [this.current_lng, this.current_lat]);
// }
},
complete: () => {
// 获取失败
if (!this.current_lng || !this.current_lat) {
this.dialog_show = true;
this.dialog_text = '获取经纬度失败';
}
},
});
},
setZoom(type) { // 设置放大缩小地图
const zoom = this.map.getZoom();
if (type === 'plus') {
this.map.setZoom(zoom + 1)
}
if (type === 'minus') {
this.map.setZoom(zoom - 1)
}
},
computedMapSource(x, y, z) { // 根据图层信息生成图层实际地址
for (const id in this.mapTiles) {
if (z == id) {
const scope = this.mapTiles[id];
return scope[`${x}-${y}`]
}
}
},
setTitleLayer() { // 生成瓦片图
// 获取瓦片图渲染范围
function getFirstProperty(obj) {
for (var prop in obj) {
return prop;
}
}
function getLastProperty(obj) {
var props = [];
for (var prop in obj) {
props.push(prop);
}
return props[props.length - 1];
}
let obj_scope = {};
for (const key in this.mapTiles) {
const element = this.mapTiles[key];
let first = getFirstProperty(element).split('-');
let last = getLastProperty(element).split('-');
obj_scope[key] = {
x: [first[0], last[0]],
y: [first[1], last[1]]
}
}
const _this = this;
var layer = new AMap.TileLayer.Flexible({
cacheSize: 50,
opacity: 1,
zIndex: 100,
createTile: function (x, y, z, success, fail) {
// 控制地图等级显示图片范围-过滤不显示的图层渲染
for (const id in obj_scope) {
if (z == id) {
const scope = obj_scope[id];
if (x < scope.x[0] || x > scope.x[1]) {
fail()
return;
}
if (y < scope.y[0] || y > scope.y[1]) {
fail()
return;
}
}
}
var img = document.createElement('img');
img.onload = function () {
success(img)
};
img.crossOrigin = "anonymous";// 必须添加,同时图片要有跨域头
img.onerror = function () {
fail()
};
// img.src = `images/tiles/${z}/${x}_${y}.png`;
img.src = _this.computedMapSource(x, y, z);
},
});
this.map.addLayer(layer);
// Canvas作为切片
var layer1 = new AMap.TileLayer.Flexible({
// tileSize: 128,
cacheSize: 300,
zIndex: 200,
createTile: function (x, y, z, success, fail) {
var c = document.createElement('canvas');
c.width = c.height = 256;
var cxt = c.getContext("2d");
cxt.font = "15px Verdana";
cxt.fillStyle = "#ff0000";
cxt.strokeStyle = "#FF0000";
cxt.strokeRect(0, 0, 256, 256);
cxt.fillText('(' + [x, y, z].join(',') + ')', 10, 30);
// 通知API切片创建完成
success(c);
}
});
// layer1.setMap(this.map);
// 只显示相应区域,移动会回到选定范围
// this.lockMapBounds()
},
// 限制地图范围
lockMapBounds() {
// var bounds = this.map.getBounds();
var myBounds = new AMap.Bounds( // 移动范围,对角线
[117.04384,26.833629],
[117.055975,26.843652]
);
this.map.setLimitBounds(myBounds);
let list =[ // 四个角,覆盖填充范围
[117.04421,26.833875],
[117.045012,26.842089],
[117.054749,26.84219],
[117.056013,26.83387]
]
// 隐藏边界以外的区域
let outer = [
new AMap.LngLat(-360, 90, true),
new AMap.LngLat(-360, -90, true),
new AMap.LngLat(360, -90, true),
new AMap.LngLat(360, 90, true),
] // 遮盖填充反向
let pathArray = [
outer,
list
]
var polygon = new AMap.Polygon({
pathL: pathArray,
strokeColor: "#fcfbf9",
strokeWeight: 2,
fillColor: "#fcfbf9",
fillOpacity: 1,
})
polygon.setPath(pathArray)
this.map.add(polygon)
},
showInfoClick(e) {
// console.log(e);
var zoom = this.map.getZoom(); //获取当前地图级别
// var text =
// "您在 [" +
// e.lnglat.getLng() +
// "," +
// e.lnglat.getLat() +
// "] 的位置单击了地图!当前层级" +
// zoom;
var text =
"[" +
e.lnglat.getLng() +
"," +
e.lnglat.getLat() +
"],"
console.log(text);
// 点击空白处,关闭弹框
if (this.info_height) {
// 关闭浮动面板
this.info_height = 0;
$('.van-floating-panel').css('boxShadow', 'none');
// 还原样式
this.resetMarkStyle();
}
},
scanQrcode() { // 扫码跳转详情页
wx.scanQRCode({
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
success: (res) => {
var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
let id = parseQueryString(result).id;
let marker_id = parseQueryString(result).marker_id;
// 跳转详情页
this.$router.push({
path: '/by/info',
query: {
id,
marker_id
}
})
}
});
// 识别率太低
// this.$router.push({
// path: '/by/scan'
// })
},
onHeightChange ({ height }) { // 监听浮动面板高度变化
if (height > window.innerHeight * 0.6) {
// // 浮动面板样式
// $('.van-floating-panel__content').css('borderRadius', '0');
// this.showClose = true;
// 清空设置
// this.changeAudio('');
// this.changeAudioStatus('pause');
//
this.$router.push({
path: '/by/info',
query: {
id: this.$route.query.id,
marker_id: this.itemInfo.id
}
})
} else {
$('.van-floating-panel__content').css('borderRadius', '1.25rem');
$('.van-floating-panel').css('boxShadow', 'none');
this.showClose = false;
}
},
closeFloatPanel () {
this.info_height = (0.65 * window.innerHeight);
$('.van-floating-panel__content').css('borderRadius', '1.25rem');
this.showClose = false;
// 关闭音频
this.$refs.pageInfo.outerStopAudio();
},
resetMarkStyle () {
this.markerSum.forEach(item => {
item.setStyle(item._x['writing-mode'] === 'vertical-rl' ? this.markerStyle1 : this.markerStyle1_horizontal);
})
},
onCloseFloat () {
this.info_height = 0;
$('.van-floating-panel__content').css('borderRadius', '1.25rem');
$('.van-floating-panel').css('boxShadow', 'none');
this.resetMarkStyle();
},
addSafeRoute({name, path}) { // 新增路径
// 获取对象的第一个键和值
// let firstKey = Object.keys(this.data_paths)[0];
// let firstValue = this.data_paths[firstKey];
// 行动路线
// var path = [
// [120.587645, 31.314833],
// [120.587709, 31.314338],
// [120.588211, 31.314377],
// ];
// console.warn(firstValue);
// var path = firstValue;
// 生成折线地图路径
let current_safe_route = new AMap.Polyline({
path,
isOutline: true,
outlineColor: '#179FB1',
borderWeight: 1,
strokeColor: '#179FB1',
strokeOpacity: 1,
strokeWeight: 3,
// 折线样式还支持 'dashed'
strokeStyle: 'solid',
// strokeStyle是dashed时有效
strokeDasharray: [10, 5],
lineJoin: 'round',
lineCap: 'round',
zIndex: 50
})
this.map.add([current_safe_route]);
this.current_safe_route.push({
key: name,
path: current_safe_route
})
// 设置起始点标记
var marker1 = new AMap.Marker({
icon: new AMap.Icon({
image: 'https://cdn.ipadbiz.cn/bieyuan/map/icon/Ellipse%2013@3x.png',
size: new AMap.Size(15, 15),
// 图标所用图片大小
imageSize: new AMap.Size(15, 15),
// 图标取图偏移量
imageOffset: new AMap.Pixel(0, 0)
}),
position: new AMap.LngLat(path[0][0], path[0][1]), // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
anchor: 'bottom-center',
offset: new AMap.Pixel(0, 0)
});
// marker1.setLabel({
// direction: 'right',
// offset: new AMap.Pixel(0, -10), //设置文本标注偏移量
// content: "<div class='info'>起点</div>", //设置文本标注内容
// });
var marker2 = new AMap.Marker({
icon: new AMap.Icon({
image: 'https://cdn.ipadbiz.cn/bieyuan/map/icon/Ellipse%2013@3x.png',
size: new AMap.Size(15, 15),
// 图标所用图片大小
imageSize: new AMap.Size(15, 15),
// 图标取图偏移量
imageOffset: new AMap.Pixel(0, 0)
}),
position: new AMap.LngLat(path[path.length - 1][0], path[path.length - 1][1]), // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
anchor: 'bottom-center',
offset: new AMap.Pixel(0, 0)
});
// marker2.setLabel({
// direction: 'right',
// offset: new AMap.Pixel(0, -10), //设置文本标注偏移量
// content: "<div class='info'>终点</div>", //设置文本标注内容
// });
// 新增逃生路线标记
// this.route_safe_marker = [marker1, marker2]
// this.map.add(this.route_safe_marker);
// 新增逃生路线标记
let route_safe_marker = [marker1, marker2]
this.map.add(route_safe_marker);
this.route_safe_marker.push({
key: name,
path: route_safe_marker
});
// 关闭导航提示
this.show_walk_route = false;
},
removeSafeRoute({name}) { // 移除地图路线
this.current_safe_route.forEach(item => {
if (item.key === name) {
this.map.remove([item.path]); // 删除地图折线
}
});
// this.map.remove(this.route_safe_marker); // 删除起始点标记
this.route_safe_marker.forEach(item => {
if (item.key === name) {
this.map.remove(item.path); // 删除起始点标记
}
});
// 关闭导航提示
this.show_walk_route = true;
},
onRoute (path) {
console.warn(path);
// 模拟新增路线
this.addSafeRoute(path);
// 定位到当前位置中心
this.map.setZoomAndCenter(this.zoom, this.data_center);
},
async onWalkRoute (position) {
await this.$nextTick(); // 等待DOM更新
// 步行导航
// let walking = new AMap.Walking({
// map: this.map,
// panel: "panel"
// });
wx.getLocation({
type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: (res) => {
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
var speed = res.speed; // 速度,以米/每秒计
var accuracy = res.accuracy; // 位置精度
this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
// 确保参数格式正确
const startPoint = [this.current_lng, this.current_lat]; // 起点
const endPoint = position.point; // 终点
// 参数检查
if (!startPoint[0] || !startPoint[1]) {
this.show_toast = true;
this.toast_text = '无法获取当前位置,请确保已开启定位功能';
return;
}
if (!endPoint[0] || !endPoint[1]) {
this.show_toast = true;
this.toast_text = '目的地坐标不完整';
return;
}
// 转换为 LngLat 对象
const start = new AMap.LngLat(startPoint[0], startPoint[1]);
const end = new AMap.LngLat(endPoint[0], endPoint[1]);
// 规划步行路线
this.walking.search(start, end, (status, result) => {
if (status === 'complete') {
console.log('步行路线规划成功');
setTimeout(() =>{
// 定位到当前位置中心
this.getLocation();
this.show_walk_route = false;
},500)
} else {
console.error('步行路线规划失败:', status, result);
ElMessage.error('步行路线规划失败,请稍后重试');
}
});
},
});
},
handleLocation(status) { // 打开/关闭 当前定位
if (status) {
this.setLocation()
this.open_current_location = false;
} else {
this.removeLocation()
this.open_current_location = true;
}
},
removeLocation() { // 移除定位标记
this.current_lng = '';
this.current_lat = '';
this.map.remove(this.location_marker); // 删除当前定位标记
},
closeWalkingRoute() {
if (this.walking) {
this.walking.clear(); // 清除路线
this.show_walk_route = true; // 恢复状态
// 可选:移除面板内容
document.getElementById('walking-panel').innerHTML = '';
// 显示提示
this.show_toast = true;
this.toast_text = '已关闭步行导航';
}
},
}
}
</script>
<style lang="less">
#container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
// 遮挡地图logo
.amap-logo {
display: none!important;
visibility: hidden!important;
}
.amap-copyright {
display: none!important;
visibility: hidden!important;
}
/* 标记文字样式 */
.amap-marker-label {
padding: 0.25rem 0.5rem;
width: auto;
border: none;
border-radius: 2px;
background: rgba(86, 65, 23, 0.8);
color: white;
}
.amap-marker {
.amap-icon {
margin-top: 0.25rem;
}
}
.input-card {
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border-radius: .25rem;
width: 20rem;
border-width: 0;
border-radius: 0.4rem;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
position: fixed;
top: 4rem;
right: 1rem;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
padding: 0.75rem 1.25rem;
}
.tool-bar-wrapper {
position: absolute;
left: 20px;
bottom: 8rem;
width: 20px;
}
.nav-bar-wrapper {
position: fixed;
bottom: 0;
left: 0;
height: 5.5rem;
width: 100%;
background-color: white;
text-align: center;
box-shadow: 0 -1px 0 rgba(80, 80, 80, 0.1);
z-index: 999;
// padding: 0.5rem 0;
padding-bottom: 0.5rem;
.nav-bar-content {
display: flex;
overflow-x: scroll;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
position: relative;
}
.item {
padding-top: 0.5rem;
color: #888;
width: 21.5%;
flex-shrink: 0;
padding-top: 1rem;
}
.checked {
color: #965f13;
}
}
.safe-route-wrapper {
position: absolute;
bottom: 2rem;
right: 1rem;
background-color: white;
}
.operate-bar-wrapper {
position: fixed;
left: 20px;
bottom: 6rem;
width: 20px;
height: auto;
z-index: 100;
.box-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.item {
position: relative;
text-align: center;
font-size: 0.85rem;
width: 2rem;
height: 2rem;
background-color: white;
margin-bottom: 1rem;
border-radius: 50%;
padding: 2.5px;
line-height: 2rem;
}
}
}
.popup-wrapper {
margin-top: 1rem;
.title {
font-size: 1.25rem;
margin-bottom: 0.85rem;
}
.content {
line-height: 1.75;
font-size: 0.95rem;
}
}
.hideScrollBar::-webkit-scrollbar {
display: none;
}
.hideScrollBar {
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
}
.van-dialog__confirm,
.van-dialog__confirm:active {
color: #AB8F57;
}
.walk-nav-text {
position: fixed;
bottom: 6rem;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9;
background: rgba(86, 65, 23, 0.8);
color: white;
border-radius: 10px;
padding: 5px 12px;
font-size: 0.8rem;
}
.close-float-panel {
position: absolute;
top: 1rem;
left: 1rem;
}
.custom-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #f7f8fa;
}
.van-floating-panel__header-bar {
background: none;
}
</style>
<!--
* @Date: 2024-09-15 11:45:13
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-09-18 11:15:56
* @FilePath: /map-demo/src/views/bieyuan/scan.vue
* @Description: 文件描述
-->
<template>
<div class="scan-page">
<!--<div class="scan_wrapper">
<div class="scan_box">
<!~~ 镜头区域 ~~>
<video ref="video" id="video" class="scan-video" autoplay></video>
</div>
<div @click="openScan" class="scan_text">点击扫描二维码查看详情</div>
</div>
<div class="sys_logo">
<van-image
width="3rem"
height="3rem"
fit="contain"
src="https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_logo.png"
/>
</div>-->
<video ref="video" id="video" class="video vjs-fluid" autoplay></video>
<div v-show="tipShow" class="tip">{{tipMsg}}</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
const $route = useRoute();
const $router = useRouter();
import { BrowserMultiFormatReader } from "@zxing/library";
// import axios from 'axios'
const codeReader = ref(null);
const scanText = ref("");
// 初始化相机
const openScan = () => {
codeReader.value = new BrowserMultiFormatReader();
codeReader.value
.getVideoInputDevices()
.then((videoDevices) => {
let firstDeviceId = videoDevices[videoDevices.length - 1].deviceId;
if (videoDevices.length > 1) {
// 一般通过判断摄像头列表项里的 label 字段,'camera2 0, facing back' 字符串含有 'back' 和 '0',大部分机型是这样,如果有些机型没有,那就还是默认获取最后一个
firstDeviceId = videoDevices.find((el) => {
return el.label.indexOf("back") > -1 && el.label.indexOf("0") > -1;
})
? videoDevices.find((el) => {
return el.label.indexOf("back") > -1 && el.label.indexOf("0") > -1;
}).deviceId
: videoDevices[videoDevices.length - 1].deviceId;
}
decodeFromInputVideoFunc(firstDeviceId);
})
.catch((err) => {
console.log(err);
});
};
// 扫码
const decodeFromInputVideoFunc = (firstDeviceId) => {
// 使用摄像头扫描
codeReader.value.reset(); // 重置
codeReader.value.decodeFromInputVideoDeviceContinuously(
firstDeviceId,
"video",
(result, err) => {
if (result) {
alert(result.text);
console.log("扫码结果", result);
scanText.value = result.text;
if (scanText.value) {
// 识别成功关闭摄像头
codeReader.value.reset();
codeReader.value.stopContinuousDecodeFromInputVideoDevice();
}
}
}
);
};
onMounted(() => {});
</script>
<style lang="less" scoped>
.scan-page {
position: relative;
height: 100vh;
.scan_wrapper {
display: flex;
padding: 1rem;
flex-direction: column;
align-items: center;
padding-top: 5rem;
.scan_box {
padding: 1rem;
background-image: url('https://cdn.ipadbiz.cn/bieyuan/map/icon/scan_bg.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
height: 20rem;
width: 90%;
position: relative;
overflow: hidden; /* 确保溢出内容被隐藏 */
box-sizing: border-box; /* 包括 padding 在内的尺寸计算 */
.scan-video {
object-fit: cover; /* 保持视频内容的比例,同时填满容器 */
position: absolute; /* 确保视频覆盖整个容器 */
top: -1rem;
left: -1rem;
width: calc(100% - 2rem); /* 视频宽度减去左右1rem的padding */
height: calc(100% - 2rem); /* 视频高度减去上下1rem的padding */
padding: 2rem;
object-fit: cover; /* 保持视频比例,同时填充容器 */
}
}
.scan_text {
font-size: 0.9rem;
color: #DD7850;
padding: 0.5rem 1rem;
margin: 1rem;
border: 1px solid #DD7850;
border-radius: 5px;
}
}
.sys_logo {
position: absolute;
bottom: 3rem;
left: calc(50% - 1.5rem);
}
}
</style>