officialAccount/pages/map/index.vue

593 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<!-- 顶部搜索框 -->
<view class="search-bar">
<view class="search-bar-left">
<image class="left-imge" src="/static/index/chahao.png" />
<view style="margin-left: 15rpx;" @click="jumpBack">
取消
</view>
</view>
<!-- <view class="" @click="dingwei">复位</view> -->
<view class="search-bar-right" @click="jumpBackValue" v-show="listTarget!=-1">完成</view>
<view class="search-bar-right-bad" v-show="listTarget==-1">
请选择
</view>
</view>
<!-- 地图展示区 -->
<view id="map" class="map" @touchend.stop.prevent="onMapDragCancel">
<image class="map-dian" src="/static/index/tuding.png" />
</view>
<view class="goback" @click="dingwei">
<image class="goback-imge" src="/static/index/dingwei.png" />
</view>
<!-- 搜索结果列表 -->
<view class="result-list">
<view class="sousuo-bgc" @click="openinput = true;pois=[];listTarget=-1;" v-show="!openinput">
<image class="sousuo-imge" src="/static/index/fangda.png" />
<view class="">搜索地点</view>
</view>
<view class="input-father" v-show="openinput">
<input ref="inputRef" class="input" type="text" v-model="keyword" placeholder="搜索地点" @input="onInput" />
<uview class="input-button" @click="close">取消</uview>
</view>
<!-- <view class="search-bar">
<input type="text" v-model="keyword" placeholder="搜索地点" @confirm="onSearch" />
<button @click="onSearch">搜索</button>
</view> -->
<view class="poi-item">
<view v-for="(poi, idx) in pois" :key="idx" @click="selectPoi(poi,idx)">
<view class="poi-card">
<view class="card-title">{{poi.name}}</view>
<view class="card-text">
{{ poi.juli }} | {{ poi.where }}
</view>
<view class="target" v-if="idx==listTarget">
</view>
</view>
<!-- <text class="poi-name">{{ poi.name }}</text>
<text class="poi-address">{{ poi.address }}</text> -->
</view>
</view>
</view>
<!-- 默认提示区 -->
<!-- <view class="info" v-else>
<text>请选择或搜索地点</text>
</view> -->
</view>
</template>
<script setup>
import {
onMounted,
ref,
reactive,
onBeforeUnmount
} from 'vue';
import {
base_url
} from '@/request/index.js';
import {
proxy,
jsonp
} from '@/api/main.js'
// 腾讯地图默认中心
const defaultLat = 39.9042;
const defaultLng = 116.4074;
const inputRef = ref(null)
const keyword = ref('');
const pois = ref([]);
const openinput = ref(false);
let map = null;
let marker = null;
// 初始化地图
function initMap(lat, lng) {
const center = new qq.maps.LatLng(lat, lng);
map = new qq.maps.Map(document.getElementById('map'), {
center,
zoom: 15,
zoomControl: false, // 隐藏放大缩小按钮
panControl: false, // 隐藏平移控件
mapTypeControl: false // 隐藏右上角地图切换
});
marker = new qq.maps.Marker({
position: center,
map,
});
}
// 动态加载微信 JSSDK 脚本,加载完成后 resolve
function loadWxJSSDK() {
return new Promise(resolve => {
if (window.wx && typeof wx.config === 'function') {
return resolve();
}
const script = document.createElement('script');
script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js';
script.onload = () => {
// 确认 wx 对象已注入
const checkWx = () => {
if (window.wx && typeof wx.config === 'function') {
resolve();
} else {
setTimeout(checkWx, 50);
}
};
checkWx();
};
script.onerror = () => {
console.error('加载微信 JSSDK 脚本失败');
resolve();
};
document.head.appendChild(script);
});
}
// 使用微信 JSSDK 定位并初始化地图
async function initLocation() {
// 1. 等待 JSSDK 脚本加载完成
await loadWxJSSDK();
// 2. 获取签名并配置
try {
const res = await fetch(`${base_url}/weixin/getJsApiInfo`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: location.href.split('#')[0]
})
});
const data = await res.json();
if (window.wx && typeof wx.config === 'function') {
wx.config({
debug: false,
appId: 'wx8fc3e4305d2fbf0b',
timestamp: data.timestamp,
nonceStr: data.nonceStr,
signature: data.signature,
jsApiList: ['getLocation', 'openLocation']
});
wx.ready(() => {
wx.getLocation({
type: 'wgs84',
success(ret) {
initMap(ret.latitude, ret.longitude);
fujinGet(ret.latitude, ret.longitude)
},
fail(err) {
console.warn('微信定位失败,使用默认位置', err);
uni.showToast({
title: '定位失败,使用默认位置',
icon: 'none'
});
initMap(defaultLat, defaultLng);
fujinGet(defaultLat, defaultLng)
}
});
});
wx.error(err => {
console.error('wx.config 验证失败:', err);
initMap(defaultLat, defaultLng);
});
} else {
console.warn('wx.config 不可用,使用默认位置');
initMap(defaultLat, defaultLng);
}
} catch (err) {
console.error('获取 JSSDK 签名失败:', err);
initMap(defaultLat, defaultLng);
}
}
const dingwei = () => {
// wx.ready(() => {
wx.getLocation({
type: 'wgs84',
success(ret) {
initMap(ret.latitude, ret.longitude);
fujinGet(ret.latitude, ret.longitude)
},
fail(err) {
console.warn('微信定位失败,使用默认位置', err);
uni.showToast({
title: err.errMsg,
icon: 'none'
});
initMap(defaultLat, defaultLng);
fujinGet(defaultLat, defaultLng);
}
});
// });
}
const jumpBack = () => {
uni.navigateBack()
}
const jumpBackValue = () => {
const payload = {
name: pois.value[listTarget.value].name,
lat: pois.value[listTarget.value].lat,
lng: pois.value[listTarget.value].lng,
}
uni.setStorageSync('dingwei', payload)
uni.navigateBack()
}
// 搜索 POI
async function onSearch() {
const kw = keyword.value.trim();
if (!kw) {
uni.showToast({
title: '请输入搜索内容',
icon: 'none'
});
return;
}
pois.value = [];
const center = map.getCenter();
const lat = center.getLat();
const lng = center.getLng();
let inIt = {
apiUrl: `https://apis.map.qq.com/ws/place/v1/search`,
other: `keyword=${encodeURIComponent(kw)}%26boundary=nearby(${lat},${lng},1000)%26key=LOLBZ-Z2PKW-JJ6RO-3Y7Z7-BSKWT-DLFNC`
}
proxy(inIt).then(data => {
if (data.status === 0 && data.data.length) {
data.data.map(item => {
let element = {
name: item.title,
juli: `${item._distance}km`,
where: item.address,
lat: item.location.lat,
lng: item.location.lng,
}
pois.value.push(element)
})
} else {
uni.showToast({
title: '未搜索到结果',
icon: 'none'
});
}
})
}
const fujinGet = (lat, lng) => {
let inIt = {
apiUrl: `https://apis.map.qq.com/ws/geocoder/v1`,
other: `location=${lat},${lng}%26key=LOLBZ-Z2PKW-JJ6RO-3Y7Z7-BSKWT-DLFNC%26get_poi=1%26radius=1000%26poi_options=page_size=10;radius=1000;policy=distance;address_format=short`
}
proxy(inIt).then(data => {
if (data.status === 0) {
pois.value = [];
data.result.pois.map(item => {
let element = {
name: item.title,
juli: `${item._distance}km`,
where: item.address,
lat: item.location.lat,
lng: item.location.lng,
}
pois.value.push(element)
})
} else {
uni.showToast({
title: '未搜索到结果',
icon: 'none'
});
}
})
}
function focusInput() {
inputRef.value?.focus()
}
const listTarget = ref(0);
// 点击 POI 定位
function selectPoi(poi, ids) {
listTarget.value = ids
const pos = new qq.maps.LatLng(poi.lat, poi.lng);
map.setCenter(pos);
marker.setPosition(pos);
}
// 防抖函数delay 毫秒
function debounce(fn, delay = 500) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const onMapDragCancel = () => {
const center = map.getCenter();
const lat = center.getLat();
const lng = center.getLng();
listTarget.value = 0
fujinGet(lat, lng)
}
// 绑定的搜索关键词
const query = ref('')
// 防抖定时器 id
let timerId = null
// 防抖等待时长(毫秒)
const DEBOUNCE_DELAY = 500
// 真正的搜索请求方法
function doSearch(val) {
// 这里写你的请求逻辑
console.log('发起搜索请求:', val)
// uni.request({ url: '...', data: { q: val }, ... })
}
// 每次 input 都触发
function onInput(e) {
// uni-app H5 下取值
query.value = e.detail?.value ?? e.target.value
// 清除上一次的定时器
if (timerId) {
clearTimeout(timerId)
}
// 如果包含英文字母,则不再设置防抖,也不发请求
if (/[A-Za-z]/.test(query.value)) {
return
}
// 设置防抖定时器:只有用户停止输入 500ms 后才执行 doSearch
timerId = setTimeout(() => {
const val = query.value.trim()
if (val) {
onSearch()
}
}, DEBOUNCE_DELAY)
}
const close = () => {
openinput.value = false;
listTarget.value = -1;
if (!pois.value.length) {
onMapDragCancel()
}
}
onMounted(async () => {
await initLocation();
});
// 组件卸载时清理定时器
onBeforeUnmount(() => {
if (timerId) {
clearTimeout(timerId)
}
})
</script>
<style scoped lang="scss">
.container {
display: flex;
flex-direction: column;
height: 100vh;
position: relative;
}
.search-bar {
position: absolute;
top: 0;
left: 0;
display: flex;
width: 100%;
height: 120rpx;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 3;
/* background: #fff; */
}
.search-bar input {
flex: 1;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
.map {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
.map-dian {
// background-color: black;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
z-index: 2;
margin-bottom: 65rpx;
}
}
.goback {
position: fixed;
bottom: 43vh;
right: 40rpx;
width: 100rpx;
height: 100rpx;
background-color: #fff;
border-radius: 20rpx;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 4rpx 4rpx 8rpx rgba(0, 0, 0, 0.1);
.goback-imge {
width: 55rpx;
height: 55rpx;
}
}
.result-list {
position: fixed;
bottom: 0;
left: 0;
height: 40vh;
width: 94%;
margin-left: 3%;
border-top-right-radius: 35rpx;
border-top-left-radius: 35rpx;
// overflow-y: auto;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
}
.poi-item {
margin-top: 30rpx;
height: calc(40vh - 110rpx);
width: 100%;
overflow-y: auto;
}
.poi-name {
font-weight: bold;
}
.poi-address {
font-size: 12px;
color: #666;
}
.info {
padding: 16px;
background: #fff;
text-align: center;
color: #999;
}
.search-bar-left {
margin-left: 30rpx;
font-size: 32rpx;
font-weight: 600;
display: flex;
// margin-top: -10rpx;
align-items: center;
}
.search-bar-right {
margin-right: 30rpx;
width: 120rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 20rpx;
color: #fff;
background-color: rgb(1, 168, 255);
}
.search-bar-right-bad {
margin-right: 30rpx;
width: 120rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 20rpx;
color: rgb(238, 238, 238);
background-color: #fff;
}
.sousuo-bgc {
width: 90%;
background-color: rgb(238, 238, 238);
border-radius: 15rpx;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
margin-top: 30rpx;
color: rgb(153, 153, 153);
.sousuo-imge {
width: 50rpx;
height: 50rpx;
margin-right: 20rpx;
}
}
.input-father {
width: 100%;
display: flex;
align-items: center;
margin-top: 30rpx;
// justify-content: center;
.input {
margin: 0 5%;
padding-left: 5%;
width: 70%;
background-color: rgb(238, 238, 238);
border-radius: 15rpx;
height: 75rpx;
color: rgb(153, 153, 153);
}
.input-button {
font-size: 25rpx;
}
}
.left-imge {
width: 30rpx;
height: 30rpx;
}
.poi-card {
width: 100%;
height: 150rpx;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 50rpx;
position: relative;
.card-title {
font-size: 28rpx;
margin-bottom: 20rpx;
}
.card-text {
font-size: 25rpx;
color: rgb(167, 167, 167);
display: block;
width: calc(100% - 10rpx);
/* 视需要调整 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.target {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 80rpx;
font-size: 40rpx;
color: skyblue;
}
</style>