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 => ` +
+
${tip.name}
+
${tip.district}${tip.address}
+
+ `).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'); @@ -84,16 +300,17 @@ function initMap() { // 地图点击事件 state.map.on('click', handleMapClick); - // 尝试定位到用户位置 - AMap.plugin('AMap.Geolocation', () => { - const geolocation = new AMap.Geolocation({ - enableHighAccuracy: true, - timeout: 10000 - }); - - geolocation.getCurrentPosition((status, result) => { - if (status === 'complete') { - state.map.setCenter([result.position.lng, result.position.lat]); + // 使用高德IP定位,只定位地图中心,不自动添加位置 + AMap.plugin('AMap.CitySearch', () => { + const citySearch = new AMap.CitySearch(); + citySearch.getLocalCity((status, result) => { + if (status === 'complete' && result.info === 'OK') { + // IP定位成功,将地图移动到当前城市 + const bounds = result.bounds; + if (bounds) { + state.map.setBounds(bounds); + } + console.log('已定位到城市:', result.city); } }); }); @@ -220,10 +437,13 @@ function renderSearchTips(tips) { // 地图点击添加位置 // ======================================== +// 待确认的位置 +let pendingLocation = null; + function handleMapClick(e) { const lnglat = e.lnglat; - // 逆地理编码获取地址 + // 逆地理编码获取地址,然后直接添加 AMap.plugin('AMap.Geocoder', () => { const geocoder = new AMap.Geocoder(); @@ -238,12 +458,50 @@ function handleMapClick(e) { 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'; + } + }; +} + // ======================================== // 位置管理 // ======================================== @@ -343,7 +601,27 @@ function updateLocationList() { function updateSearchButton() { const btn = document.getElementById('searchBtn'); - btn.disabled = state.locations.length < 2; + 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; + } } // ======================================== @@ -355,7 +633,23 @@ async function handleSearch() { const radius = parseInt(document.getElementById('searchRadius').value); if (!keywords) { - alert('请输入搜索关键词'); + // 自动打开侧边栏设置面板 + 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; } @@ -396,6 +690,11 @@ async function handleSearch() { // 显示搜索结果 showSearchResults(searchData.pois, radius); + // 移动端自动关闭侧边栏 + if (window.closeSidebarOnMobile) { + window.closeSidebarOnMobile(); + } + } catch (error) { console.error('搜索失败:', error); alert('搜索失败,请稍后重试'); @@ -420,8 +719,8 @@ function showCenterMarker() { state.map.add(state.centerMarker); - // 显示中心点信息 - document.getElementById('centerInfo').style.display = 'block'; + // 不显示中心点浮动提示 + // document.getElementById('centerInfo').style.display = 'block'; } function showSearchResults(pois, radius) { @@ -484,47 +783,92 @@ function renderResultList(pois) { if (pois.length === 0) { list.innerHTML = ` -
  • - 😕 +
  • + 😕 未找到相关地点 - 尝试增大搜索半径或更换关键词 + 尝试增大搜索半径或更换关键词
  • `; } else { list.innerHTML = pois.map((poi, index) => { const dist = parseDistance(poi.distance); - return ` -
  • -
    ${index + 1}
    -
    -
    ${poi.name}
    -
    ${poi.address || poi.type}
    - ${poi.tel ? `` : ''} -
    -
    - ${dist.value} - ${dist.unit} -
    -
  • - `}).join(''); + 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 `
  • + ${index + 1} + + ${poi.name} + ${poi.address || poi.type} + ${poi.tel ? `📞 ${poi.tel}` : ''} + + + ${dist.value} + ${dist.unit} + +
  • `; + }).join(''); - // 绑定点击事件 - list.querySelectorAll('.floating-result-item').forEach(item => { - item.addEventListener('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - const index = parseInt(item.dataset.index); - focusPOI(index); - }); - }); + // 点击事件已通过 onclick 内联绑定 } panel.style.display = 'flex'; filterInput.value = ''; + // 移动端隐藏操作栏 + 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); + } }; // 绑定筛选事件 @@ -592,6 +936,20 @@ function focusPOI(index) { 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); + } + } } } @@ -656,6 +1014,14 @@ function clearSearchResults() { document.getElementById('centerInfo').style.display = 'none'; document.getElementById('resultFilter').value = ''; + // 移动端显示操作栏,隐藏浮动按钮 + if (window.toggleMobileActionBar) { + window.toggleMobileActionBar(true); + } + if (window.hideFloatingResultBtn) { + window.hideFloatingResultBtn(); + } + // 清除标记 clearPOIMarkers(); diff --git a/templates/index.html b/templates/index.html index f6b1ac8..5a66215 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,17 +2,23 @@ - + + + + 会面点 - 寻找最佳聚会地点 - +
    + + + -