scale="mapScale"> </map>
<!-- 主弹出层 -->
<uni-popup ref="popup" mask-background-color="rgba(0,0,0,0)" type="bottom" :is-mask-click="false"
animation="false">
<view class="main-popup-content popup-content"><!-- 弹出层内容 -->
<view class="header">
<scroll-view class="nav-scroll" scroll-x>
<view class="nav-item" v-for="(item, index) in groups" :key="item.id"
class="{active: activeIndex === index}" @click="switchGroup(index)">
{{item.name}}
</view>
<view class="add-btn" @click="addGroup">
-
新增分组
</view>
</scroll-view>
<text class="edit-icon" @click="showGroupManager">✎</text>
</view><!-- 中间内容区域 --> <view class="content" v-if="currentPlaces.length"> <view class="place-item" v-for="item in currentPlaces" :key="item.id"> <view class="info"> <text class="name">{{item.name}}</text> <text class="distance">{{item.distance}}</text> </view> <text class="tag">{{item.tag}}</text> </view> </view> <!-- 底部栏 --> <view class="footer" v-if="currentPlaces.length"> <button class="btn">智能路线规划</button> <button class="btn add-btn">+ 添加地点</button> </view> <view class="empty" v-else> <image src="../../static/images/empty.jpg" mode="aspectFill"></image> <button @click="openAddLocationPopup"><uni-icons type="location" size="18" color="#007AFF"></uni-icons>添加地点</button> </view> </view>
</uni-popup>
<!-- 分组管理弹出层 -->
<uni-popup ref="manageGroupPopup" type="bottom">
<view class="manageGroupPopup-content popup-content">
<view class="group-title">
<text class="title">分组列表</text>
<uni-icons type="clear" size="28" color="#ddd" @click="closeManageGroupPopup"></uni-icons>
</view>
<view class="group-list">
<view class="group-item"
v-for="(item, index) in groups"
key="item.id">
<uni-icons type="minus-filled" size="26" color="red" v-if="groups.length > 1" @click="iconClick"></uni-icons>
<input class="uni-input" type="text" v-model="item.name"/>
<uni-icons type="bars" size="26"></uni-icons>
</view>
<view class="add-group" @click="addGroup">
<uni-icons type="plus-filled" size="26" color="blue"></uni-icons>
<text class="text">新增分组</text>
</view>
</view>
</view>
</uni-popup>
<!-- 添加地点弹出层 -->
<uni-popup ref="addLocationPopup" type="bottom" mask-background-color="rgba(0,0,0,0)" animation>
<view class="addLocationPopup-content popup-content">
<view class="head">
<view class="location">
<uni-icons type="location" size="22" color="white"></uni-icons>
<text class="text">{{area}}<uni-icons type="down" size="16" color="white"></uni-icons></text>
</view>
<text class="middle">添加新地点</text>
<view class="close">
<uni-icons type="clear" size="28" color="#969694" @click="closeAddLocationPopup"></uni-icons>
</view>
</view>
<view class="tabs">
<view class="tab-item" v-for="(item,index) in tabList" :key="item.icon" :class="{'tabActivited': activeTabIndex === index}">
<uni-icons :class="{'tabActivitedIcon': activeTabIndex === index}" :type=item.icon size="30" color="#91928c"></uni-icons>
<view class="text">{{item.text}}</view>
</view>
</view>
<view class="search">
<view class="search-input">
<uni-icons class="left-icon" type="search" size="26" color="#eee"></uni-icons>
<input class="input" type="text" placeholder="开始搜索地点" v-model="searchArea" @confirm="handleConfirm" focus/>
<uni-icons class="right-icon" v-if="searchArea" type="clear" size="20" @click="clearSearchArea" color="#91928c"></uni-icons>
</view>
<view v-if="!noSearch || !searchArea">
<view class="latestSearch" v-if="!searchArea || searchResult.length === 0">
<view class="title">最近搜索</view>
<view v-if="latestSearchList.length > 0" class="search-list">
<view class="search-item" v-for="(item,index) in latestSearchList" :key="index">
<view class="left-icon">
<uni-icons type="map-pin-ellipse" size="20" color="#ddd"></uni-icons>
</view>
<view class="name" @click="clickHistoryItem(item)">{{item}}</view>
<view class="right-icon">
<uni-icons type="close" size="20" color="#ddd" @click="deleteArea(index)"></uni-icons>
</view>
</view>
</view>
<view v-else class="empty">暂无搜索记录</view>
</view>
<!-- 搜索结果列表 -->
<scroll-view
scroll-y
class="result-list"
v-if="searchArea && searchResult.length > 0"
style="{ 'overflow: hidden; -webkit-overflow-scrolling: touch; }"
>
<view v-for="(item, index) in searchResult" :key="item.id" class="result-item" @click="handleSelectLocation(item)">
<view class="address">
<view class="name">{{item.name}}</view>
<view class="detail">{{item.address}}</view>
</view>
<view class="distance">{{item.distance}}km</view>
</view>
<view class="nosearch">
搜索不到新地点?试试使用<view class="mark" @click="console.log('1111')">地图选点</view>标注吧
</view>
<view class="empty" style="height: 200rpx;"></view>
</scroll-view>
</view>
<view v-if="noSearch && searchArea" class="no-result">
<image src="../../static/images/emptySearchArea.png" mode="aspectFill"></image>
<text>暂无搜索结果</text>
<view class="nosearch">
搜索不到新地点?试试使用<view class="mark" @click="console.log('1111')">地图选点</view>标注吧
</view>
</view>
</view>
</view>
</uni-popup>
<!-- 编辑选中地点详情弹出层 -->
<uni-popup ref="editLocationDetailPopup" type="bottom" mask-background-color="rgba(0,0,0,0)">
<view class="details-content popup-content">
<view class="head">
头头头头头头头头头头头头头头头头头头头头头头头头头头头
</view>
</view>
</uni-popup>>
<!-- 悬浮控件 -->
<view class="floating-controls popup-content">
<button @click="toggleFullscreen">{{isFullscreenText}}</text></button>
<button @click="openSettings" v-if="!isFullscreen">调节</button>
</view>
<!-- 设置弹出层 -->
<uni-popup ref="settingsPopup" type="bottom" v-bind:mask="false">
<view class="settings-content popup-content">
<text>风格调整</text>
<text>模式切换</text>
<text>设置</text>
<text>标记</text>
<text>路线</text>
<text>标题</text>
<text>VIP</text>
<text>经典图册</text>
<text>排序</text>
<text>EMOJI</text>
<button @click="closeSettings">取消</button>
<button @click="saveSettings">保存</button>
</view>
</uni-popup>
</view>
</template>
<script>
import { getCoordinateAPI, getSearchLocation } from '@/api/city/index.js'
export default {
data() {
return {
isOpen: true,
mapScale: 16,
latitude: 39.90469, // 地图纬度
longitude: 116.40717, // 地图经度
markers: [], // 地图标记
popupHeight: 800, // 弹出层高度
isFullscreen: false, // 是否全屏
isFullscreenText: '全屏',
area: '北京',
activeIndex: 0,
groups: [{
id: 1,
name: '分组1'
}],
places: [],
isSorting: true,
tabList: [
{icon: 'search', text: '搜索'},
{icon: 'shop-filled', text: '酒店特惠'},
{icon: 'scan', text: '智能解析'},
{icon: 'location-filled', text: '地图选点'}
],
activeTabIndex: 0,
searchArea: '',
searchResult: [],
noSearch: false,
latestSearchList: uni.getStorageSync("historySearch") || [],
selectedLocation: null, // 选中的地点信息
};
},
onLoad(e) {
let {
area = null
} = e
if (area) {
this.area = area
}
this.getCoordinate()
this.$nextTick(() => {
this.$refs.popup.open();
});
},
computed: {
currentPlaces() {
return this.places[this.groups[this.activeIndex].id] || []
}
},
methods: {
// 获取哪个地点的地图渲染
async getCoordinate() {
const res = await getCoordinateAPI(this.area)
this.latitude = res.data.latitude,
this.longitude = res.data.longitude
},
// 切换全屏
toggleFullscreen() {
console.log('1111')
this.isFullscreen = !this.isFullscreen;
if (this.isFullscreen) {
this.isFullscreenText = '退出'
this.$refs.popup.close();
} else {
this.isFullscreenText = '全屏'
this.$refs.popup.open();
}
},
// 打开设置弹出层
openSettings() {
this.$refs.settingsPopup.open();
},
// 关闭设置弹出层
closeSettings() {
this.$refs.settingsPopup.close();
},
// 保存设置
saveSettings() {
this.closeSettings();
},
// 切换添加地点
toggleAddLocation() {
this.popupHeight = this.popupHeight === 200 ? 400 : 200;
},
// 添加分组
addGroup() {
const newId = Date.now()
this.groups.push({
id: newId,
name: 分组${this.groups.length + 1}
})
this.places[newId] = []
},
// 切换分组
switchGroup(index) {
this.activeIndex = index
},
// 打开分组管理弹出层
showGroupManager () {
this.$refs.manageGroupPopup.open()
},
// 关闭分组管理弹出层
closeManageGroupPopup () {
this.$refs.manageGroupPopup.close()
},
// 点击删除分组的图标
iconClick(index) {
uni.showModal({
title:'提示',
content: '是否确认删除,删除分组后地点将无法恢复',
success: (res) => {
if (res.confirm) {
this.groups.splice(index,1)
}
}
})
},
// 打开添加地点弹出层
openAddLocationPopup () {
this.$refs.addLocationPopup.open()
},
// 关闭添加地点弹出层
closeAddLocationPopup () {
this.$refs.addLocationPopup.close()
},
// 清空搜索框内的内容
clearSearchArea () {
this.searchArea = ''
this.searchResult = ''
this.noSearch = false
},
// 确认搜索
async handleConfirm () {
if (this.searchArea.trim() !== '') {
this.latestSearchList = [...new Set([this.searchArea, ...this.latestSearchList])]
uni.setStorageSync("historySearch", this.latestSearchList)
}else {
uni.showToast({
title:"搜索的内容不能为空",
icon:"none"
})
}
const res = await getSearchLocation(this.searchArea, this.latitude, this.longitude)
this.searchResult = res.data.results
if (this.searchResult.length === 0) {
this.noSearch = true
console.log(this.noSearch)
}else {
this.noSearch = false
}
},
// 删除搜索历史中的某个记录
deleteArea (index) {
this.latestSearchList.splice(index,1)
uni.setStorageSync("historySearch", this.latestSearchList)
},
// 点击搜索历史中某条记录
clickHistoryItem(item) {
this.searchArea = item
this.latestSearchList = [...new Set([this.searchArea, ...this.latestSearchList])]
uni.setStorageSync("historySearch", this.latestSearchList)
this.handleConfirm()
},
// 点击搜索返回的结果列表中的某个地点
handleSelectLocation(item) {
console.log(item)
try {
this.latitude = item.poiLatitude
this.longitude = item.poiLongitude
this.makers = [{
id: Date.now(),
latitude: item.poiLatitude,
longitude: item.poiLongitude,
title: item.name,
iconPath: '../../static/redMaker.png'
}]
this.selectedLocation = item
this.$refs.addLocationPopup.close()
this.$refs.editLocationDetailPopup.open()
}catch{
}
}
}
}
</script>
<style scoped lang="scss">
.container {
position: relative;
height: 100vh;
isolation: isolate;
}
.map {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 0; /* 确保地图在底层 */
transition: all 0.3s;
transform: translateZ(0); /* 解决iOS滑动卡顿 */
}
.main-popup-content {
background-color: #fafafa;
border-radius: 20px 20px 0 0;
padding: 20px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
min-height: 600rpx;
// transition: height 0.3s ease;
z-index: 900;
position: fixed;
bottom: 0;
left: 0;
right: 0;
.header {
display: flex;
align-items: center;
justify-content: space-between;
background: #fafafa;
border-bottom: 1px solid #eee;
}
.nav-scroll {
white-space: nowrap;
width: 624rpx;
}
.nav-item {
flex: 1;
display: inline-block;
padding: 8px 15px;
margin-right: 10px;
border-radius: 16px;
background-color: #ddd;
color: #777;
&.active {
background: #007AFF;
color: white;
}
}
.add-btn {
display: inline-block;
border-radius: 16px;
padding: 8px 15px;
background-color: #fff;
border: 1px solid #ddd;
color: #555;
}
.edit-icon {
padding: 0 10px;
font-size: 18px;
}
.content {
flex: 1;
padding: 15px;
}
.footer {
padding: 10px;
background: #fff;
display: flex;
gap: 10px;
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
}
.btn {
flex: 1;
background: #007AFF;
color: white;
&.add-btn {
background: #4CD964;
}
}
.empty {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
button {
height: 70rpx;
width: 250rpx;
line-height: 70rpx;
background-color: #eee;
color: #007AFF;
border: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
.uni-icons {
margin-right: 5px;
}
}
}
}
.manageGroupPopup-content {
min-height: 800rpx;
z-index: 901;
background-color: #fff;
border-radius: 20px 20px 0 0;
padding: 20px;
position: fixed;
bottom: 0;
left: 0;
right: 0;
.group-title {
display: flex;
justify-content: space-between;
.title {
font-size: 20px;
font-weight: 600;
margin-bottom: 8rpx;
}
}
.group-list {
background-color: #fafafa;
border-radius: 10px;
padding: 0 10px;
.group-item {
width: 100%;
height: 100rpx;
align-items: center;
display: flex;
justify-content: space-between;
margin-top: 5px;
.uni-input {
width: 500rpx;
height: 50rpx;
border-bottom: 1px solid #eee;
}
}
.add-group {
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
.text {
margin-left: 8px;
}
}
}
}
.addLocationPopup-content {
background-color: #2a2a2a;
z-index: 902;
height: 1330rpx;
height: 80vh;
border-radius: 30px 30px 0 0;
padding: 10px;
color: #fff;
position: fixed;
bottom: 0;
left: 0;
right: 0;
.head {
display: flex;
align-items: center;
.location {
flex: 4;
.text {
margin-left: 2px;
overflow: hidden;
}
}
.middle {
font-size: 18px;
flex: 5;
}
.close {
flex: 1;
color: #91928c;
}
}
.tabs {
margin-top: 20px;
color: #91928c;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
border-bottom: 1px solid #91928d;
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
.text {
padding: 10px 0;
}
}
}
.search {
padding: 20px;
height: calc(100% - 120px);
display: flex;
flex-direction: column;
overflow: hidden;
.search-input {
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid #91928c;
width: 640rpx;
height: 90rpx;
border-radius: 20px;
padding: 0 10rpx;
.left-icon {
flex: 4;
}
.input {
flex: 20;
background-color: rgba(0,0,0,0);
}
.right-icon {
flex: 3;
}
}
.latestSearch {
margin-top: 40rpx;
.title {
font-size: 40rpx;
font-weight: 600;
color: #ddd;
}
.search-list {
.search-item {
margin: 15rpx 0;
display: flex;
align-items: center;
font-size: 35rpx;
.left-icon {
flex: 1;
}
.name {
flex: 8;
}
.right-icon {
flex: 1;
}
}
}
.empty {
margin-top: 50rpx;
font-size: 38rpx;
color: #ddd;
}
}
.result-list {
flex: 1;
overflow: hidden;
height: 100%;
.result-item {
padding: 5px;
width: 100%;
height: 150rpx;
border-bottom: 1px solid rgba(145,146,140,0.5);
display: flex;
align-items: center;
justify-content: space-around;
.address {
display: flex;
flex-direction: column;
color: #f4f4f4;
width: 440rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.name {
font-size: 44rpx;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.address {
font-size: 22rpx;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.distance {
color: #f4f4f4;
font-size: 28rpx;
}
}
.nosearch {
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
font-size: 26rpx;
margin-top: 50rpx;
.mark {
color: white;
font-size: 28rpx;
margin: 0 6rpx;
}
}
}
.no-result {
height: 900rpx;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
image {
width: 500rpx;
height: 500rpx;
}
text {
font-size: 40rpx;
color: #ddd;
}
.nosearch {
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
font-size: 26rpx;
margin-top: 50rpx;
.mark {
color: white;
font-size: 28rpx;
margin: 0 6rpx;
}
}
}
}
}
.details-content {
background-color: white;
z-index: 903;
height: 45vh;
border-radius: 30px 30px 0 0;
padding: 10px;
// position: fixed;
// bottom: 0;
// left: 0;
// right: 0;
}
.tabActivited {
color: white;
.text {
border-bottom: 2px solid white;
}
}
.tabActivitedIcon {
color: white !important;
}
.floating-controls {
position: fixed;
display: flex;
flex-direction: column;
justify-content: center;
top: 90px;
right: 20px;
gap: 5px;
z-index: 1;
}
.floating-controls button {
width: 110rpx;
height: 60rpx;
line-height: 60rpx;
font-size: 12px;
flex-direction: row;
background-color: rgb(250, 250, 250);
border: 1px solid #ccc;
border-radius: 10rpx;
}
.settings-content {
background-color: #fff;
padding: 20px;
border-radius: 20px 20px 0 0;
}
/* 隐藏遮罩层残留(某些版本需要) */
.uni-popup__mask {
display: none !important;
}
/* 允许地图穿透操作 */
.uni-popup {
pointer-events: none !important;
/* 整个弹出层不拦截事件 */
}
/* 内容区域需要单独响应点击 */
.popup-content {
pointer-events: auto;
/* 内容区域可交互 */
}
</style>