meeting-point/static/js/app.js

1086 lines
36 KiB
JavaScript
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.

/**
* 会面点 - Meeting Point
* 前端应用主逻辑
*/
// ========================================
// 全局状态
// ========================================
const state = {
map: null,
amapKey: '', // Web服务API Key
amapJsKey: '', // JS API Key
amapJsSecret: '', // JS API 安全密钥
locations: [], // 参与者位置列表
markers: [], // 位置标记
centerMarker: null, // 中心点标记
poiMarkers: [], // POI标记
center: null, // 计算出的中心点
searchCircle: null, // 搜索范围圆
infoWindow: null, // 信息窗口
currentPOIs: [], // 当前搜索结果
colors: [
'#3b82f6', '#ef4444', '#10b981', '#8b5cf6',
'#f59e0b', '#ec4899', '#06b6d4', '#84cc16'
]
};
// ========================================
// 初始化
// ========================================
document.addEventListener('DOMContentLoaded', async () => {
await loadConfig();
initMap();
bindEvents();
initMobileUI();
});
// ========================================
// 移动端UI初始化
// ========================================
function initMobileUI() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const menuBtn = document.getElementById('menuBtn');
const mobileSearchBtn = document.getElementById('mobileSearchBtn');
const mobileActionBar = document.getElementById('mobileActionBar');
const mobileSearchInput = document.getElementById('mobileSearchInput');
const mobileSearchClear = document.getElementById('mobileSearchClear');
const mobileSearchTips = document.getElementById('mobileSearchTips');
const floatingResultBtn = document.getElementById('floatingResultBtn');
const floatingResultBadge = document.getElementById('floatingResultBadge');
if (!sidebar || !overlay) return;
// 切换侧边栏
function toggleSidebar() {
sidebar.classList.toggle('active');
overlay.classList.toggle('active');
// 打开侧边栏时隐藏浮动按钮
if (sidebar.classList.contains('active') && floatingResultBtn) {
floatingResultBtn.style.display = 'none';
}
}
// 关闭侧边栏
function closeSidebar() {
sidebar.classList.remove('active');
overlay.classList.remove('active');
// 关闭侧边栏时,如果有搜索结果且结果面板隐藏,显示浮动按钮
const panel = document.getElementById('floatingResults');
if (floatingResultBtn && state.currentPOIs && state.currentPOIs.length > 0 &&
(!panel || panel.style.display === 'none')) {
window.showFloatingResultBtn(state.currentPOIs.length);
}
}
// 菜单按钮点击
if (menuBtn) {
menuBtn.addEventListener('click', toggleSidebar);
}
// 遮罩点击关闭
overlay.addEventListener('click', closeSidebar);
// 移动端搜索按钮
if (mobileSearchBtn) {
mobileSearchBtn.addEventListener('click', handleSearch);
}
// 移动端地址搜索
if (mobileSearchInput) {
let searchTimeout;
mobileSearchInput.addEventListener('input', (e) => {
const value = e.target.value.trim();
mobileSearchClear.style.display = value ? 'block' : 'none';
clearTimeout(searchTimeout);
if (value.length >= 2) {
searchTimeout = setTimeout(() => fetchMobileSearchTips(value), 300);
} else {
mobileSearchTips.classList.remove('active');
}
});
mobileSearchInput.addEventListener('focus', () => {
if (mobileSearchInput.value.trim().length >= 2) {
mobileSearchTips.classList.add('active');
}
});
mobileSearchClear.addEventListener('click', () => {
mobileSearchInput.value = '';
mobileSearchClear.style.display = 'none';
mobileSearchTips.classList.remove('active');
});
// 点击外部关闭搜索提示
document.addEventListener('click', (e) => {
if (!e.target.closest('.mobile-search-bar')) {
mobileSearchTips.classList.remove('active');
}
});
}
// 浮动结果按钮点击 - 重新打开结果面板
if (floatingResultBtn) {
floatingResultBtn.addEventListener('click', () => {
const panel = document.getElementById('floatingResults');
if (panel && state.currentPOIs.length > 0) {
panel.style.display = 'flex';
floatingResultBtn.style.display = 'none';
if (mobileActionBar) {
mobileActionBar.style.display = 'none';
}
}
});
}
// 点击搜索结果后自动关闭侧边栏(移动端)
window.closeSidebarOnMobile = function() {
if (window.innerWidth <= 768) {
closeSidebar();
}
};
// 隐藏/显示移动端操作栏
window.toggleMobileActionBar = function(show) {
if (mobileActionBar && window.innerWidth <= 768) {
mobileActionBar.style.display = show ? 'flex' : 'none';
}
};
// 显示浮动结果按钮
window.showFloatingResultBtn = function(count) {
if (floatingResultBtn) {
// 清除所有内联样式让CSS控制
floatingResultBtn.style.cssText = 'display: flex;';
if (floatingResultBadge) {
floatingResultBadge.textContent = count;
}
console.log('显示浮动结果按钮,数量:', count);
}
};
// 隐藏浮动结果按钮
window.hideFloatingResultBtn = function() {
if (floatingResultBtn) {
floatingResultBtn.style.display = 'none';
}
};
// 监听窗口大小变化
window.addEventListener('resize', () => {
if (window.innerWidth > 768) {
closeSidebar();
if (mobileActionBar) {
mobileActionBar.style.display = 'none';
}
if (floatingResultBtn) {
floatingResultBtn.style.display = 'none';
}
} else {
const floatingResults = document.getElementById('floatingResults');
const resultsHidden = !floatingResults || floatingResults.style.display === 'none';
if (mobileActionBar && resultsHidden) {
mobileActionBar.style.display = 'flex';
}
// 如果有搜索结果且结果面板隐藏,显示浮动按钮
if (resultsHidden && state.currentPOIs && state.currentPOIs.length > 0) {
window.showFloatingResultBtn(state.currentPOIs.length);
}
}
});
}
// 移动端地址搜索提示
async function fetchMobileSearchTips(keywords) {
const mobileSearchTips = document.getElementById('mobileSearchTips');
try {
const response = await fetch(`/api/tips?keywords=${encodeURIComponent(keywords)}&city=`);
const data = await response.json();
if (data.tips && data.tips.length > 0) {
renderMobileSearchTips(data.tips);
} else {
mobileSearchTips.classList.remove('active');
}
} catch (error) {
console.error('获取搜索提示失败:', error);
}
}
function renderMobileSearchTips(tips) {
const mobileSearchTips = document.getElementById('mobileSearchTips');
const mobileSearchInput = document.getElementById('mobileSearchInput');
const mobileSearchClear = document.getElementById('mobileSearchClear');
mobileSearchTips.innerHTML = tips.map(tip => `
<div class="mobile-search-tip-item" data-lng="${tip.location.lng}" data-lat="${tip.location.lat}" data-name="${tip.name}" data-address="${tip.district}${tip.address}">
<div class="tip-name">${tip.name}</div>
<div class="tip-address">${tip.district}${tip.address}</div>
</div>
`).join('');
// 绑定点击事件
mobileSearchTips.querySelectorAll('.mobile-search-tip-item').forEach(item => {
item.addEventListener('click', () => {
const location = {
name: item.dataset.name,
address: item.dataset.address,
coordinate: {
lng: parseFloat(item.dataset.lng),
lat: parseFloat(item.dataset.lat)
}
};
addLocation(location);
// 清理搜索框
mobileSearchInput.value = '';
mobileSearchClear.style.display = 'none';
mobileSearchTips.classList.remove('active');
});
});
mobileSearchTips.classList.add('active');
}
async function loadConfig() {
try {
const response = await fetch('/api/config');
const data = await response.json();
state.amapKey = data.amap_key;
state.amapJsKey = data.amap_js_key || data.amap_key;
state.amapJsSecret = data.amap_js_secret;
// 配置安全密钥必须在加载JS API之前设置
if (state.amapJsSecret) {
window._AMapSecurityConfig = {
securityJsCode: state.amapJsSecret
};
}
// 动态加载高德地图JS API
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = `https://webapi.amap.com/maps?v=2.0&key=${state.amapJsKey}&plugin=AMap.PlaceSearch,AMap.Geocoder,AMap.AutoComplete`;
script.onload = resolve;
script.onerror = () => reject(new Error('加载高德地图JS API失败'));
document.head.appendChild(script);
});
} catch (error) {
console.error('加载配置失败:', error);
alert('加载配置失败,请检查后端服务是否启动');
}
}
function initMap() {
// 初始化地图
state.map = new AMap.Map('mapContainer', {
zoom: 12,
center: [116.397428, 39.90923], // 默认北京
mapStyle: 'amap://styles/dark', // 深色主题
viewMode: '2D'
});
// 初始化信息窗口
state.infoWindow = new AMap.InfoWindow({
isCustom: true,
autoMove: true,
offset: new AMap.Pixel(0, -40)
});
// 地图点击事件
state.map.on('click', handleMapClick);
// 先IP定位快速显示再尝试精确定位
AMap.plugin(['AMap.CitySearch', 'AMap.Geolocation'], () => {
// 1. 先IP定位快速
const citySearch = new AMap.CitySearch();
citySearch.getLocalCity((status, result) => {
if (status === 'complete' && result.info === 'OK') {
const bounds = result.bounds;
if (bounds) {
state.map.setBounds(bounds);
}
console.log('IP定位成功城市:', result.city);
}
});
// 2. 然后尝试精确定位(较慢但更准确)
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true,
timeout: 10000,
buttonPosition: 'RB',
buttonOffset: new AMap.Pixel(10, 20),
zoomToAccuracy: true
});
// 添加定位控件到地图
state.map.addControl(geolocation);
geolocation.getCurrentPosition((status, result) => {
if (status === 'complete' && result.position) {
state.map.setCenter(result.position);
state.map.setZoom(15);
console.log('精确定位成功:', result.formattedAddress);
} else {
console.log('精确定位失败使用IP定位结果');
}
});
});
}
// ========================================
// 事件绑定
// ========================================
function bindEvents() {
// 搜索输入
const searchInput = document.getElementById('searchInput');
const clearSearch = document.getElementById('clearSearch');
const searchTips = document.getElementById('searchTips');
let searchTimeout;
searchInput.addEventListener('input', (e) => {
const value = e.target.value.trim();
clearSearch.style.display = value ? 'block' : 'none';
clearTimeout(searchTimeout);
if (value.length >= 2) {
searchTimeout = setTimeout(() => fetchSearchTips(value), 300);
} else {
searchTips.classList.remove('active');
}
});
clearSearch.addEventListener('click', () => {
searchInput.value = '';
clearSearch.style.display = 'none';
searchTips.classList.remove('active');
});
// 点击外部关闭搜索提示
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-box')) {
searchTips.classList.remove('active');
}
});
// 快捷标签
document.querySelectorAll('.tag').forEach(tag => {
tag.addEventListener('click', () => {
document.querySelectorAll('.tag').forEach(t => t.classList.remove('active'));
tag.classList.add('active');
document.getElementById('poiKeywords').value = tag.dataset.keyword;
});
});
// 搜索半径滑块
const radiusSlider = document.getElementById('searchRadius');
const radiusValue = document.getElementById('radiusValue');
radiusSlider.addEventListener('input', () => {
const value = parseInt(radiusSlider.value);
radiusValue.textContent = value >= 1000 ? `${value / 1000} 公里` : `${value}`;
});
// 搜索按钮
document.getElementById('searchBtn').addEventListener('click', handleSearch);
// POI关键词输入
document.getElementById('poiKeywords').addEventListener('input', () => {
document.querySelectorAll('.tag').forEach(t => t.classList.remove('active'));
});
}
// ========================================
// 搜索提示
// ========================================
async function fetchSearchTips(keywords) {
const searchTips = document.getElementById('searchTips');
try {
const response = await fetch(`/api/tips?keywords=${encodeURIComponent(keywords)}&city=`);
const data = await response.json();
if (data.tips && data.tips.length > 0) {
renderSearchTips(data.tips);
} else {
searchTips.classList.remove('active');
}
} catch (error) {
console.error('获取搜索提示失败:', error);
}
}
function renderSearchTips(tips) {
const searchTips = document.getElementById('searchTips');
searchTips.innerHTML = tips.map(tip => `
<div class="search-tip-item" data-lng="${tip.location.lng}" data-lat="${tip.location.lat}" data-name="${tip.name}" data-address="${tip.district}${tip.address}">
<div class="name">${tip.name}</div>
<div class="address">${tip.district}${tip.address}</div>
</div>
`).join('');
// 绑定点击事件
searchTips.querySelectorAll('.search-tip-item').forEach(item => {
item.addEventListener('click', () => {
const location = {
name: item.dataset.name,
address: item.dataset.address,
coordinate: {
lng: parseFloat(item.dataset.lng),
lat: parseFloat(item.dataset.lat)
}
};
addLocation(location);
// 清理搜索框
document.getElementById('searchInput').value = '';
document.getElementById('clearSearch').style.display = 'none';
searchTips.classList.remove('active');
});
});
searchTips.classList.add('active');
}
// ========================================
// 地图点击添加位置
// ========================================
// 待确认的位置
let pendingLocation = null;
function handleMapClick(e) {
const lnglat = e.lnglat;
// 逆地理编码获取地址,然后直接添加
AMap.plugin('AMap.Geocoder', () => {
const geocoder = new AMap.Geocoder();
geocoder.getAddress([lnglat.lng, lnglat.lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const address = result.regeocode.formattedAddress;
const location = {
name: `位置 ${state.locations.length + 1}`,
address: address,
coordinate: {
lng: lnglat.lng,
lat: lnglat.lat
}
};
// 直接添加位置
addLocation(location);
}
});
});
}
// 显示地图点击确认弹框
function showMapClickConfirm(address) {
const confirmDialog = document.getElementById('mapClickConfirm');
const confirmAddress = document.getElementById('confirmAddress');
const confirmOk = document.getElementById('confirmOk');
const confirmCancel = document.getElementById('confirmCancel');
if (!confirmDialog) return;
confirmAddress.textContent = address;
confirmDialog.style.display = 'flex';
// 确认添加
confirmOk.onclick = () => {
if (pendingLocation) {
addLocation(pendingLocation);
pendingLocation = null;
}
confirmDialog.style.display = 'none';
};
// 取消
confirmCancel.onclick = () => {
pendingLocation = null;
confirmDialog.style.display = 'none';
};
// 点击背景关闭
confirmDialog.onclick = (e) => {
if (e.target === confirmDialog) {
pendingLocation = null;
confirmDialog.style.display = 'none';
}
};
}
// ========================================
// 位置管理
// ========================================
function addLocation(location) {
state.locations.push(location);
// 添加地图标记
const index = state.locations.length - 1;
const color = state.colors[index % state.colors.length];
const marker = new AMap.Marker({
position: [location.coordinate.lng, location.coordinate.lat],
content: `<div class="custom-marker" style="background: ${color}">${index + 1}</div>`,
offset: new AMap.Pixel(-18, -18)
});
marker.on('click', () => {
showInfoWindow(marker, location);
});
state.markers.push(marker);
state.map.add(marker);
// 调整视野
if (state.locations.length > 1) {
state.map.setFitView(state.markers);
} else {
state.map.setCenter([location.coordinate.lng, location.coordinate.lat]);
state.map.setZoom(14);
}
// 更新UI
updateLocationList();
updateSearchButton();
// 清除之前的搜索结果
clearSearchResults();
}
function removeLocation(index) {
// 移除标记
state.map.remove(state.markers[index]);
state.markers.splice(index, 1);
state.locations.splice(index, 1);
// 更新剩余标记的编号和颜色
state.markers.forEach((marker, i) => {
const color = state.colors[i % state.colors.length];
marker.setContent(`<div class="custom-marker" style="background: ${color}">${i + 1}</div>`);
});
// 更新UI
updateLocationList();
updateSearchButton();
// 调整视野
if (state.markers.length > 0) {
state.map.setFitView(state.markers);
}
// 清除之前的搜索结果
clearSearchResults();
}
function updateLocationList() {
const listEl = document.getElementById('locationList');
const countEl = document.getElementById('locationCount');
countEl.textContent = state.locations.length;
if (state.locations.length === 0) {
listEl.innerHTML = `
<li class="empty-state">
<span class="empty-icon">🗺️</span>
<span>还没有添加位置</span>
<span class="empty-hint">搜索地址或点击地图开始</span>
</li>
`;
return;
}
listEl.innerHTML = state.locations.map((loc, index) => {
const color = state.colors[index % state.colors.length];
return `
<li class="location-item">
<div class="location-marker" style="background: ${color}">${index + 1}</div>
<div class="location-info">
<div class="location-name">${loc.name}</div>
<div class="location-address">${loc.address || '暂无地址'}</div>
</div>
<button class="location-remove" onclick="removeLocation(${index})">×</button>
</li>
`;
}).join('');
}
function updateSearchButton() {
const btn = document.getElementById('searchBtn');
const mobileBtn = document.getElementById('mobileSearchBtn');
const mobileCount = document.getElementById('mobileLocationCount');
const count = state.locations.length;
const disabled = count < 2;
btn.disabled = disabled;
if (mobileBtn) {
mobileBtn.disabled = disabled;
// 更新按钮文字显示当前状态
if (count === 0) {
mobileBtn.textContent = '🔍 搜索会面点';
} else if (count === 1) {
mobileBtn.textContent = '🔍 再添加1个位置';
} else {
mobileBtn.textContent = '🔍 搜索会面点';
}
}
if (mobileCount) {
mobileCount.textContent = count;
}
}
// ========================================
// 搜索功能
// ========================================
async function handleSearch() {
const keywords = document.getElementById('poiKeywords').value.trim();
const radius = parseInt(document.getElementById('searchRadius').value);
if (!keywords) {
// 自动打开侧边栏设置面板
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
if (sidebar && window.innerWidth <= 768) {
sidebar.classList.add('active');
if (overlay) overlay.classList.add('active');
}
// 聚焦到关键词输入框
const poiInput = document.getElementById('poiKeywords');
if (poiInput) {
poiInput.focus();
poiInput.setAttribute('placeholder', '⚠️ 请选择或输入搜索类型...');
// 3秒后恢复原来的placeholder
setTimeout(() => {
poiInput.setAttribute('placeholder', '例如咖啡馆、餐厅、KTV...');
}, 3000);
}
return;
}
if (state.locations.length < 2) {
alert('请至少添加2个位置');
return;
}
showLoading(true);
try {
// 1. 计算中心点
const centerResponse = await fetch('/api/center', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ locations: state.locations })
});
const centerData = await centerResponse.json();
state.center = centerData.center;
// 显示中心点标记
showCenterMarker();
// 2. 搜索周边POI
const searchResponse = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
center: state.center,
keywords: keywords,
radius: radius
})
});
const searchData = await searchResponse.json();
// 显示搜索结果
showSearchResults(searchData.pois, radius);
// 移动端自动关闭侧边栏
if (window.closeSidebarOnMobile) {
window.closeSidebarOnMobile();
}
} catch (error) {
console.error('搜索失败:', error);
alert('搜索失败,请稍后重试');
} finally {
showLoading(false);
}
}
function showCenterMarker() {
// 移除旧的中心点标记
if (state.centerMarker) {
state.map.remove(state.centerMarker);
}
// 创建新的中心点标记
state.centerMarker = new AMap.Marker({
position: [state.center.lng, state.center.lat],
content: '<div class="center-marker">⭐</div>',
offset: new AMap.Pixel(-24, -24),
zIndex: 200
});
state.map.add(state.centerMarker);
// 不显示中心点浮动提示
// document.getElementById('centerInfo').style.display = 'block';
}
function showSearchResults(pois, radius) {
// 清除旧的POI标记
clearPOIMarkers();
// 显示搜索范围圆
if (state.searchCircle) {
state.map.remove(state.searchCircle);
}
state.searchCircle = new AMap.Circle({
center: [state.center.lng, state.center.lat],
radius: radius,
fillColor: '#f59e0b',
fillOpacity: 0.1,
strokeColor: '#f59e0b',
strokeWeight: 2,
strokeOpacity: 0.6
});
state.map.add(state.searchCircle);
// 添加POI标记
pois.forEach((poi, index) => {
const marker = new AMap.Marker({
position: [poi.location.lng, poi.location.lat],
content: `<div class="poi-marker">${index + 1}</div>`,
offset: new AMap.Pixel(-16, -16),
zIndex: 100
});
marker.on('click', () => {
showPOIInfoWindow(marker, poi);
highlightResult(index);
});
state.poiMarkers.push(marker);
state.map.add(marker);
});
// 渲染结果列表
renderResultList(pois);
// 调整视野(右侧留出空间给浮动面板)
const allMarkers = [...state.markers, state.centerMarker, ...state.poiMarkers];
state.map.setFitView(allMarkers, false, [80, 420, 80, 80]);
}
function renderResultList(pois) {
const panel = document.getElementById('floatingResults');
const list = document.getElementById('floatingResultList');
const count = document.getElementById('floatingResultCount');
const filterInput = document.getElementById('resultFilter');
// 保存POI数据供筛选使用
state.currentPOIs = pois;
count.textContent = pois.length;
if (pois.length === 0) {
list.innerHTML = `
<li style="display:flex;flex-direction:column;align-items:center;padding:32px;color:#64748b;text-align:center;">
<span style="font-size:2.5rem;opacity:0.5;">😕</span>
<span>未找到相关地点</span>
<span style="font-size:0.8rem;opacity:0.7;">尝试增大搜索半径或更换关键词</span>
</li>
`;
} else {
list.innerHTML = pois.map((poi, index) => {
const dist = parseDistance(poi.distance);
const rankColors = [
'background:linear-gradient(135deg,#ffd700,#ffb800);color:#0f0f1a',
'background:linear-gradient(135deg,#c0c0c0,#a0a0a0);color:#0f0f1a',
'background:linear-gradient(135deg,#cd7f32,#b87333);color:#0f0f1a'
];
const rankBg = rankColors[index] || 'background:#16213e;color:#64748b';
return `<li data-index="${index}" data-name="${poi.name}" onclick="focusPOI(${index})" style="
display: flex;
flex-direction: row;
align-items: center;
padding: 12px;
margin-bottom: 8px;
background: #0a0a12;
border: 1px solid #2d2d44;
border-radius: 10px;
cursor: pointer;
">
<span style="
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.85rem;
margin-right: 12px;
${rankBg};
">${index + 1}</span>
<span style="
flex: 1;
min-width: 0;
overflow: hidden;
margin-right: 10px;
">
<span style="display:block;font-weight:600;font-size:0.95rem;color:#f8fafc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${poi.name}</span>
<span style="display:block;font-size:0.8rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${poi.address || poi.type}</span>
${poi.tel ? `<span style="display:block;font-size:0.8rem;color:#3b82f6;">📞 ${poi.tel}</span>` : ''}
</span>
<span style="
flex-shrink: 0;
width: 55px;
padding: 6px 4px;
text-align: center;
background: rgba(16,185,129,0.12);
border-radius: 6px;
border: 1px solid rgba(16,185,129,0.25);
">
<span style="display:block;font-size:1rem;font-weight:700;color:#10b981;">${dist.value}</span>
<span style="display:block;font-size:0.7rem;color:#10b981;">${dist.unit}</span>
</span>
</li>`;
}).join('');
// 点击事件已通过 onclick 内联绑定
}
panel.style.display = 'flex';
filterInput.value = '';
// 隐藏浮动结果按钮(因为结果面板已显示)
if (window.hideFloatingResultBtn) {
window.hideFloatingResultBtn();
}
// 移动端隐藏操作栏
if (window.toggleMobileActionBar) {
window.toggleMobileActionBar(false);
}
// 绑定关闭按钮事件
document.getElementById('closeFloatingResults').onclick = () => {
panel.style.display = 'none';
// 移动端显示操作栏和浮动结果按钮
if (window.toggleMobileActionBar) {
window.toggleMobileActionBar(true);
}
if (window.showFloatingResultBtn && state.currentPOIs.length > 0) {
window.showFloatingResultBtn(state.currentPOIs.length);
}
};
// 绑定筛选事件
filterInput.oninput = (e) => {
const keyword = e.target.value.trim().toLowerCase();
filterResults(keyword);
};
}
// 解析距离为数值和单位
function parseDistance(distance) {
const d = parseInt(distance);
if (d >= 1000) {
return { value: (d / 1000).toFixed(1), unit: '公里' };
}
return { value: d, unit: '米' };
}
// 筛选结果
function filterResults(keyword) {
const items = document.querySelectorAll('.floating-result-item');
let visibleCount = 0;
items.forEach(item => {
const name = item.dataset.name?.toLowerCase() || '';
const address = item.querySelector('.address')?.textContent.toLowerCase() || '';
if (!keyword || name.includes(keyword) || address.includes(keyword)) {
item.classList.remove('hidden');
visibleCount++;
} else {
item.classList.add('hidden');
}
});
// 更新显示数量
document.getElementById('floatingResultCount').textContent = visibleCount;
}
function formatDistance(distance) {
const d = parseInt(distance);
if (d >= 1000) {
return (d / 1000).toFixed(1) + ' 公里';
}
return d + ' 米';
}
function focusPOI(index) {
const marker = state.poiMarkers[index];
const poi = state.currentPOIs[index];
if (marker && poi) {
// 先高亮结果
highlightResult(index);
// 关闭当前信息窗口
state.infoWindow.close();
// 使用 panTo 平滑移动到目标位置
const position = marker.getPosition();
state.map.panTo(position);
// 延迟设置缩放和打开信息窗口,确保地图移动完成
setTimeout(() => {
state.map.setZoom(16);
showPOIInfoWindow(marker, poi);
}, 300);
// 移动端:关闭结果面板,显示浮动按钮
if (window.innerWidth <= 768) {
const panel = document.getElementById('floatingResults');
if (panel) {
panel.style.display = 'none';
}
if (window.showFloatingResultBtn) {
window.showFloatingResultBtn(state.currentPOIs.length);
}
if (window.toggleMobileActionBar) {
window.toggleMobileActionBar(true);
}
}
}
}
function highlightResult(index) {
document.querySelectorAll('.floating-result-item').forEach((item, i) => {
item.classList.toggle('active', i === index);
});
// 滚动到对应项
const activeItem = document.querySelector(`.floating-result-item[data-index="${index}"]`);
if (activeItem) {
activeItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
// ========================================
// 信息窗口
// ========================================
function showInfoWindow(marker, location) {
const content = `
<div class="info-window">
<h3>${location.name}</h3>
<p>${location.address || '暂无地址'}</p>
</div>
`;
state.infoWindow.setContent(content);
state.infoWindow.open(state.map, marker.getPosition());
}
function showPOIInfoWindow(marker, poi) {
const distText = formatDistance(poi.distance);
const navUrl = `https://uri.amap.com/navigation?to=${poi.location.lng},${poi.location.lat},${encodeURIComponent(poi.name)}&mode=car&coordinate=gaode`;
const content = `
<div class="info-window">
<h3>${poi.name}</h3>
<p class="distance">🎯 距中心点: ${distText}</p>
${poi.address ? `<p>📍 ${poi.address}</p>` : ''}
${poi.tel ? `<p class="tel">📞 <a href="tel:${poi.tel}">${poi.tel}</a></p>` : ''}
<a href="${navUrl}" target="_blank" class="nav-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71L12 2z"/>
</svg>
导航前往
</a>
</div>
`;
state.infoWindow.setContent(content);
state.infoWindow.open(state.map, marker.getPosition());
}
// ========================================
// 工具函数
// ========================================
function clearSearchResults() {
// 隐藏结果区域
document.getElementById('floatingResults').style.display = 'none';
document.getElementById('centerInfo').style.display = 'none';
document.getElementById('resultFilter').value = '';
// 移动端显示操作栏,隐藏浮动按钮
if (window.toggleMobileActionBar) {
window.toggleMobileActionBar(true);
}
if (window.hideFloatingResultBtn) {
window.hideFloatingResultBtn();
}
// 清除标记
clearPOIMarkers();
if (state.centerMarker) {
state.map.remove(state.centerMarker);
state.centerMarker = null;
}
if (state.searchCircle) {
state.map.remove(state.searchCircle);
state.searchCircle = null;
}
state.center = null;
state.currentPOIs = [];
}
function clearPOIMarkers() {
state.poiMarkers.forEach(marker => {
state.map.remove(marker);
});
state.poiMarkers = [];
}
function showLoading(show) {
document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';
}
// ========================================
// 暴露全局函数
// ========================================
window.removeLocation = removeLocation;
window.focusPOI = focusPOI;