diff --git a/main.go b/main.go index 1a57d19..e91f8fb 100644 --- a/main.go +++ b/main.go @@ -9,10 +9,14 @@ import ( "io/fs" "log" "math" + "net" "net/http" "net/url" "os" + "os/exec" + "runtime" "strings" + "time" "github.com/gin-gonic/gin" ) @@ -120,10 +124,65 @@ type AmapTipsResponse struct { var config Config +// checkAndKillPort 检查端口是否被占用,如果被占用则尝试杀死占用进程 +func checkAndKillPort(port string) { + addr := ":" + port + ln, err := net.Listen("tcp", addr) + if err == nil { + // 端口未被占用,关闭监听 + ln.Close() + return + } + + log.Printf("端口 %s 已被占用,正在尝试释放...", port) + + // 根据操作系统执行不同的命令 + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + // Windows: 查找并杀死占用端口的进程 + // 先查找 PID + findCmd := exec.Command("cmd", "/c", fmt.Sprintf("netstat -ano | findstr :%s | findstr LISTENING", port)) + output, err := findCmd.Output() + if err != nil { + log.Printf("查找端口占用进程失败: %v", err) + return + } + + // 解析 PID + lines := strings.Split(string(output), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 5 { + pid := fields[len(fields)-1] + if pid != "" && pid != "0" { + log.Printf("发现占用进程 PID: %s,正在终止...", pid) + killCmd := exec.Command("taskkill", "/F", "/PID", pid) + killCmd.Run() + } + } + } + } else { + // Linux/Mac: 使用 lsof 和 kill + cmd = exec.Command("sh", "-c", fmt.Sprintf("lsof -ti:%s | xargs -r kill -9", port)) + cmd.Run() + } + + // 等待端口释放 + time.Sleep(500 * time.Millisecond) + log.Printf("端口 %s 已释放", port) +} + func main() { // 加载配置 loadConfig() + // 检查并释放端口 + port := config.Port + if port == "" { + port = "8080" + } + checkAndKillPort(port) + // 设置Gin模式 gin.SetMode(gin.ReleaseMode) @@ -145,11 +204,6 @@ func main() { r.POST("/api/search", searchPOIHandler) r.GET("/api/tips", getTipsHandler) - port := config.Port - if port == "" { - port = "8080" - } - log.Printf("========================================") log.Printf(" 会面点 Meeting Point") log.Printf(" 服务启动在 http://localhost:%s", port) diff --git a/meeting-point.exe b/meeting-point.exe new file mode 100644 index 0000000..d20c2c6 Binary files /dev/null and b/meeting-point.exe differ diff --git a/static/css/style.css b/static/css/style.css index 0d1dde5..0a627f1 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -738,6 +738,276 @@ ul { height: 100%; } +/* 移动端搜索框 */ +.mobile-search-bar { + display: none; + position: absolute; + top: var(--spacing-md); + left: var(--spacing-md); + right: var(--spacing-md); + z-index: 50; +} + +.mobile-search-input-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.mobile-search-bar input { + width: 100%; + padding: 12px 40px 12px 16px; + background: rgba(26, 26, 46, 0.95); + backdrop-filter: blur(10px); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + color: var(--text-primary); + font-size: 0.9rem; + box-shadow: var(--shadow-md); +} + +.mobile-search-bar input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-glow); +} + +.mobile-search-bar input::placeholder { + color: var(--text-muted); +} + +.mobile-search-clear { + position: absolute; + right: 8px; + width: 28px; + height: 28px; + background: var(--bg-card-hover); + border: none; + border-radius: 50%; + color: var(--text-secondary); + font-size: 1.1rem; + cursor: pointer; +} + +.mobile-search-tips { + position: absolute; + top: 100%; + left: 0; + right: 0; + margin-top: 4px; + background: rgba(26, 26, 46, 0.98); + backdrop-filter: blur(10px); + border: 1px solid var(--border); + border-radius: var(--radius-md); + max-height: 250px; + overflow-y: auto; + display: none; + box-shadow: var(--shadow-lg); +} + +.mobile-search-tips.active { + display: block; +} + +.mobile-search-tip-item { + padding: 12px 16px; + border-bottom: 1px solid var(--border); + cursor: pointer; +} + +.mobile-search-tip-item:last-child { + border-bottom: none; +} + +.mobile-search-tip-item:active { + background: var(--bg-card-hover); +} + +.mobile-search-tip-item .tip-name { + font-weight: 500; + color: var(--text-primary); + font-size: 0.9rem; + margin-bottom: 2px; +} + +.mobile-search-tip-item .tip-address { + font-size: 0.8rem; + color: var(--text-muted); +} + +/* 地图点击确认弹框 */ +.map-click-confirm { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 200; + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-md); +} + +.map-click-confirm .confirm-content { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + max-width: 320px; + width: 100%; + box-shadow: var(--shadow-lg); + animation: popIn 0.2s ease; +} + +@keyframes popIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.map-click-confirm .confirm-title { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--spacing-md); + text-align: center; +} + +.map-click-confirm .confirm-address { + font-size: 0.9rem; + color: var(--text-secondary); + text-align: center; + padding: var(--spacing-md); + background: var(--bg-darker); + border-radius: var(--radius-md); + margin-bottom: var(--spacing-lg); + line-height: 1.5; + max-height: 80px; + overflow-y: auto; +} + +.map-click-confirm .confirm-actions { + display: flex; + gap: var(--spacing-md); +} + +.map-click-confirm .confirm-btn { + flex: 1; + padding: 12px; + border: none; + border-radius: var(--radius-md); + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition-fast); +} + +.map-click-confirm .confirm-btn.cancel { + background: var(--bg-elevated); + color: var(--text-secondary); + border: 1px solid var(--border); +} + +.map-click-confirm .confirm-btn.cancel:active { + background: var(--bg-card-hover); +} + +.map-click-confirm .confirm-btn.ok { + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); + color: var(--bg-dark); +} + +.map-click-confirm .confirm-btn.ok:active { + transform: scale(0.98); +} + +/* 浮动结果按钮 */ +.floating-result-btn { + position: fixed; + bottom: 24px; + right: 24px; + padding: 10px 16px; + background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); + border: none; + border-radius: 25px; + color: #0f0f1a; + font-size: 0.85rem; + font-weight: 700; + cursor: pointer; + z-index: 9999; + display: none; + align-items: center; + gap: 6px; + box-shadow: 0 4px 16px rgba(245, 158, 11, 0.5); +} + +.floating-result-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(245, 158, 11, 0.6); +} + +.floating-result-btn.visible { + display: flex !important; +} + +.floating-result-btn .result-icon { + display: none; +} + +.floating-result-btn .result-text { + display: none; +} + +.floating-result-btn .result-badge { + background: #0f0f1a; + color: #f59e0b; + font-weight: 700; + padding: 4px 8px; + border-radius: 10px; + font-size: 0.8rem; + min-width: 20px; + text-align: center; +} + +/* PC端显示为紧凑按钮:图标+数字 */ +.floating-result-btn::before { + content: '📋'; + font-size: 1rem; +} + +@media (max-width: 768px) { + .mobile-search-bar { + display: block; + } + + .floating-result-btn { + bottom: 90px; + left: 50%; + right: auto; + transform: translateX(-50%); + padding: 12px 20px; + font-size: 0.95rem; + box-shadow: 0 6px 24px rgba(245, 158, 11, 0.6); + } + + .floating-result-btn::before { + content: '📋 查看结果'; + font-size: 0.95rem; + } + + .floating-result-btn .result-badge { + padding: 4px 10px; + font-size: 0.85rem; + } +} + /* 中心点信息 */ .center-info { position: absolute; @@ -901,11 +1171,12 @@ ul { padding: var(--spacing-md) var(--spacing-lg); min-width: 220px; max-width: 300px; - background: rgba(22, 33, 62, 0.92); + background: rgba(22, 33, 62, 0.95); backdrop-filter: blur(12px); border: 1px solid rgba(245, 158, 11, 0.3); border-radius: var(--radius-md); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 20px rgba(245, 158, 11, 0.1); + overflow: visible; } .info-window h3 { @@ -982,32 +1253,571 @@ ul { } } +/* ======================================== + 移动端快捷操作栏 + ======================================== */ + +.mobile-action-bar { + display: none; + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: var(--spacing-sm) var(--spacing-md); + padding-bottom: calc(var(--spacing-sm) + env(safe-area-inset-bottom, 0)); + background: rgba(26, 26, 46, 0.95); + backdrop-filter: blur(10px); + border-top: 1px solid var(--border); + z-index: 60; + gap: var(--spacing-sm); + flex-direction: column; +} + +.mobile-action-bar .action-row { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.mobile-action-bar .action-info { + display: flex; + align-items: center; + gap: var(--spacing-sm); + color: var(--text-secondary); + font-size: 0.85rem; + padding: 0 var(--spacing-sm); +} + +.mobile-action-bar .location-badge { + background: var(--primary); + color: var(--bg-dark); + font-weight: 700; + padding: 2px 8px; + border-radius: 12px; + min-width: 20px; + text-align: center; + font-size: 0.8rem; +} + +.mobile-action-bar .action-search-btn { + flex: 1; + padding: 10px var(--spacing-md); + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); + border: none; + border-radius: var(--radius-md); + color: var(--bg-dark); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; +} + +.mobile-action-bar .action-search-btn:disabled { + opacity: 0.5; +} + +.mobile-action-bar .menu-btn { + width: 40px; + height: 40px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: 1.2rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +/* ======================================== + 移动端切换按钮(隐藏,改用底部菜单) + ======================================== */ + +.mobile-toggle { + display: none; +} + +/* 侧边栏遮罩 */ +.sidebar-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 99; + opacity: 0; + transition: opacity var(--transition-normal); + pointer-events: none; +} + +.sidebar-overlay.active { + opacity: 1; + pointer-events: auto; +} + +/* ======================================== + 平板端适配 (768px - 1024px) + ======================================== */ + +@media (max-width: 1024px) { + :root { + --sidebar-width: 360px; + } + + .floating-results { + width: 340px; + } +} + +/* ======================================== + 移动端适配 (< 768px) + ======================================== */ + @media (max-width: 768px) { + :root { + --spacing-md: 12px; + --spacing-lg: 16px; + } + .app-container { flex-direction: column; } + /* 移动端显示操作栏 */ + .mobile-action-bar { + display: flex; + } + + /* 侧边栏改为底部弹出面板 */ .sidebar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + top: auto; width: 100%; min-width: 100%; - max-height: 50vh; + max-height: 70vh; + z-index: 100; + transform: translateY(100%); + transition: transform var(--transition-normal); + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + overflow: hidden; } + .sidebar.active { + transform: translateY(0); + } + + .sidebar-overlay { + display: block; + } + + /* 地图全屏 */ .map-area { - height: 50vh; + width: 100%; + height: 100vh; } - .location-list { - max-height: 120px; + /* Logo 简化 */ + .logo { + padding: var(--spacing-md); + position: relative; + } + + /* 添加拖动指示条 */ + .logo::before { + content: ''; + position: absolute; + top: 8px; + left: 50%; + transform: translateX(-50%); + width: 40px; + height: 4px; + background: var(--border-light); + border-radius: 2px; + } + + .logo-icon { + width: 36px; + height: 36px; + } + + .logo-icon svg { + width: 22px; + height: 22px; + } + + .logo h1 { + font-size: 1.1rem; + margin-top: 4px; + } + + /* 侧边栏内容可滚动 */ + .sidebar-content { + padding: var(--spacing-md); + gap: var(--spacing-md); + max-height: calc(70vh - 80px); + overflow-y: auto; + padding-bottom: calc(var(--spacing-lg) + env(safe-area-inset-bottom, 0)); + } + + .section { + padding: var(--spacing-md); + } + + .section-header h2 { + font-size: 0.9rem; + } + + /* 位置列表 */ + .locations-section { + display: none; /* 移动端隐藏位置列表,只在地图上显示 */ + } + + .search-section .section-header { + display: none; /* 简化标题 */ + } + + /* 快捷标签 */ + .quick-tags { + gap: 6px; + margin-top: var(--spacing-sm); + } + + .tag { + padding: 8px 12px; + font-size: 0.85rem; + } + + .form-group { + margin-bottom: var(--spacing-md); + } + + .form-group label { + font-size: 0.85rem; + margin-bottom: 6px; + } + + /* 隐藏侧边栏中的搜索按钮(使用底部操作栏的按钮) */ + .btn-search { + display: none; + } + + /* 中心点信息 */ + .center-info { + top: var(--spacing-md); + left: 50%; + transform: translateX(-50%); + } + + .center-badge { + padding: 6px var(--spacing-md); + font-size: 0.8rem; + } + + /* 浮动结果面板 - 底部弹出,更紧凑 */ + .floating-results { + position: fixed; + top: auto; + bottom: 0; + left: 0; + right: 0; + width: 100%; + max-height: 75vh; + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + animation: slideUp 0.3s ease; + background: var(--bg-card) !important; + backdrop-filter: blur(16px); + display: flex; + flex-direction: column; + } + + @keyframes slideUp { + from { + opacity: 0; + transform: translateY(100%); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .floating-results-header { + padding: var(--spacing-sm) var(--spacing-md); + padding-top: var(--spacing-lg); + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + position: relative; + background: var(--bg-elevated); + } + + /* 拖动指示条 */ + .floating-results-header::before { + content: ''; + position: absolute; + top: 8px; + left: 50%; + transform: translateX(-50%); + width: 36px; + height: 4px; + background: var(--border-light); + border-radius: 2px; + } + + .floating-results-header h3 { + font-size: 1rem; + display: flex; + align-items: center; + gap: 8px; + } + + .floating-results-filter { + padding: 8px var(--spacing-md); + background: var(--bg-card); + } + + .floating-results-filter input { + padding: 10px var(--spacing-md); + font-size: 0.9rem; + } + + .floating-result-list { + padding: 10px 16px; + padding-bottom: calc(20px + env(safe-area-inset-bottom, 0)); + gap: 10px; + background: var(--bg-card); + flex: 1; + overflow-y: auto; + } + + /* 移动端列表项布局修复 - 使用 !important 覆盖基础样式 */ + .floating-result-list > .floating-result-item { + display: grid !important; + grid-template-columns: 36px 1fr 65px !important; + gap: 10px !important; + align-items: center !important; + padding: 12px !important; + background: var(--bg-darker) !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-md) !important; + } + + .floating-result-list > .floating-result-item > .rank { + grid-column: 1 !important; + width: 36px !important; + height: 36px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + } + + .floating-result-list > .floating-result-item > .info { + grid-column: 2 !important; + display: flex !important; + flex-direction: column !important; + gap: 4px !important; + min-width: 0 !important; + overflow: hidden !important; + } + + .floating-result-list > .floating-result-item > .info > .name { + font-size: 0.95rem !important; + font-weight: 600 !important; + color: var(--text-primary) !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + } + + .floating-result-list > .floating-result-item > .info > .address { + font-size: 0.8rem !important; + color: var(--text-muted) !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + } + + .floating-result-list > .floating-result-item > .info > .tel { + font-size: 0.8rem !important; + color: var(--info) !important; + } + + .floating-result-list > .floating-result-item > .distance-badge { + grid-column: 3 !important; + width: 65px !important; + min-width: 65px !important; + max-width: 65px !important; + padding: 8px 4px !important; + text-align: center !important; + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + background: rgba(16, 185, 129, 0.12) !important; + border-radius: var(--radius-sm) !important; + border: 1px solid rgba(16, 185, 129, 0.25) !important; + } + + .floating-result-list > .floating-result-item > .distance-badge > .num { + font-size: 1.1rem !important; + font-weight: 700 !important; + color: var(--success) !important; + display: block !important; + } + + .floating-result-list > .floating-result-item > .distance-badge > .unit { + font-size: 0.7rem !important; + color: var(--success) !important; + display: block !important; + } + + /* 信息窗口更紧凑 */ + .info-window { + min-width: 200px; + max-width: 280px; + padding: var(--spacing-md); + } + + .info-window h3 { + font-size: 0.95rem; + padding-bottom: 8px; + margin-bottom: 8px; + } + + .info-window p { + font-size: 0.82rem; + margin-bottom: 6px; + } + + .info-window .nav-btn { + padding: 10px 14px; + font-size: 0.85rem; + margin-top: 10px; + } + + .info-window .nav-btn svg { + width: 16px; + height: 16px; + } + + /* 地图标记更小 */ + .custom-marker { + width: 30px; + height: 30px; + font-size: 11px; + border-width: 2px; + } + + .center-marker { + width: 36px; + height: 36px; + font-size: 18px; + border-width: 2px; + } + + .poi-marker { + width: 26px; + height: 26px; + font-size: 11px; + border-width: 2px; + } +} + +/* ======================================== + 小屏手机适配 (< 375px) + ======================================== */ + +@media (max-width: 375px) { + .floating-results { + max-height: 60vh; + } + + .tag { + padding: 6px 10px; + font-size: 0.8rem; + } + + .mobile-action-bar .action-search-btn { + font-size: 0.85rem; + padding: 8px var(--spacing-md); + } +} + +/* ======================================== + 横屏模式 + ======================================== */ + +@media (max-width: 768px) and (orientation: landscape) { + .sidebar { + max-height: 85vh; + } + + .sidebar-content { + max-height: calc(85vh - 70px); } .floating-results { - width: calc(100% - 32px); - max-height: 40vh; - top: auto; - bottom: var(--spacing-md); - right: var(--spacing-md); - left: var(--spacing-md); + max-height: 80vh; + width: 55%; + right: 0; + left: auto; + border-radius: var(--radius-lg) 0 0 var(--radius-lg); + } + + .mobile-action-bar { + width: 45%; + left: 0; + right: auto; + border-radius: 0 var(--radius-lg) 0 0; + } +} + +/* ======================================== + 触摸优化 + ======================================== */ + +@media (hover: none) and (pointer: coarse) { + /* 增大触摸目标 */ + .tag { + min-height: 44px; + display: inline-flex; + align-items: center; + } + + .location-remove { + width: 36px; + height: 36px; + } + + .floating-result-item { + min-height: 64px; + } + + /* 移除hover效果,改用active */ + .floating-result-item:hover { + background: var(--bg-darker); + border-color: transparent; + transform: none; + } + + .floating-result-item:active { + background: var(--bg-card-hover); + border-color: var(--primary); + } + + .tag:hover { + background: var(--bg-card); + border-color: var(--border); + color: var(--text-secondary); + } + + .tag:active { + background: var(--bg-card-hover); + border-color: var(--primary); + color: var(--primary); } } @@ -1029,8 +1839,7 @@ ul { display: flex; flex-direction: column; animation: slideInRight 0.3s ease; - backdrop-filter: blur(10px); - background: rgba(26, 26, 46, 0.95); + overflow: hidden; } @keyframes slideInRight { @@ -1128,6 +1937,7 @@ ul { display: flex; flex-direction: column; gap: var(--spacing-sm); + background: var(--bg-card); } .floating-result-list::-webkit-scrollbar { @@ -1145,7 +1955,7 @@ ul { .floating-result-item { display: flex; - align-items: stretch; + align-items: flex-start; gap: var(--spacing-md); padding: var(--spacing-md); background: var(--bg-darker); @@ -1206,6 +2016,7 @@ ul { display: flex; flex-direction: column; gap: 4px; + overflow: visible; } .floating-result-item .name { @@ -1220,10 +2031,7 @@ ul { font-size: 0.8rem; color: var(--text-muted); line-height: 1.4; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; + word-break: break-word; } .floating-result-item .tel { @@ -1249,11 +2057,13 @@ ul { flex-direction: column; align-items: center; justify-content: center; - min-width: 52px; + min-width: 50px; + max-width: 55px; padding: var(--spacing-sm); background: rgba(16, 185, 129, 0.12); border-radius: var(--radius-sm); border: 1px solid rgba(16, 185, 129, 0.25); + align-self: center; } .floating-result-item .distance-badge .num { diff --git a/static/js/app.js b/static/js/app.js index ade3a56..29c8713 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -34,8 +34,224 @@ 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 => ` +