db = MyTrack_Plugin::getDbConnection(); $this->options = Typecho_Widget::widget('Widget_Options')->plugin('MyTrack'); // 初始化缓存相关属性 $this->initCache(); } /** * 初始化缓存 * * @access private * @return void */ private function initCache() { $this->cacheDir = __DIR__ . '/cache'; // 确保缓存目录存在 if (!is_dir($this->cacheDir)) { mkdir($this->cacheDir, 0755, true); } // 从插件配置中获取缓存时间,默认为7天 try { if ($this->options && isset($this->options->cacheExpire)) { $cacheDays = $this->options->cacheExpire; // 确保是有效的数字,如果不是则使用默认值7天 $cacheDays = is_numeric($cacheDays) && $cacheDays >= 0 ? intval($cacheDays) : 7; // 将天数转换为秒数,0表示关闭缓存 $this->cacheExpire = $cacheDays * 24 * 60 * 60; } else { // 如果获取配置失败,使用默认值7天 $this->cacheExpire = 7 * 24 * 60 * 60; } } catch (Exception $e) { // 如果获取配置失败,使用默认值7天 $this->cacheExpire = 7 * 24 * 60 * 60; } } /** * 获取缓存键名 * * @access private * @param string $key 缓存键 * @return string 完整的缓存文件路径 */ private function getCacheFile($key) { return $this->cacheDir . '/' . md5($key) . '.cache'; } /** * 获取缓存数据 * * @access private * @param string $key 缓存键 * @return mixed|null 缓存数据或null */ private function getCache($key) { // 如果缓存时间为0,表示关闭缓存 if ($this->cacheExpire === 0) { return null; } $cacheFile = $this->getCacheFile($key); if (!file_exists($cacheFile)) { return null; } // 检查缓存是否过期 if (filemtime($cacheFile) + $this->cacheExpire < time()) { unlink($cacheFile); return null; } $data = file_get_contents($cacheFile); return unserialize($data); } /** * 设置缓存数据 * * @access private * @param string $key 缓存键 * @param mixed $data 要缓存的数据 * @return bool 是否成功 */ private function setCache($key, $data) { // 如果缓存时间为0,表示关闭缓存 if ($this->cacheExpire === 0) { return false; } $cacheFile = $this->getCacheFile($key); $data = serialize($data); return file_put_contents($cacheFile, $data) !== false; } /** * 清除缓存 * * @access private * @param string|null $key 特定缓存键,null表示清除所有缓存 * @return bool 是否成功 */ private function clearCache($key = null) { if ($key === null) { // 清除所有缓存 $files = glob($this->cacheDir . '/*.cache'); foreach ($files as $file) { unlink($file); } return true; } else { // 清除特定缓存 $cacheFile = $this->getCacheFile($key); if (file_exists($cacheFile)) { return unlink($cacheFile); } return true; } } /** * 执行函数 * * @access public * @return void */ public function execute() { // 检查是否启用前台显示 if (!$this->options || !$this->options->enableDisplay) { return; } } /** * 动作处理 * * @access public * @return void */ public function action() { // 处理前台API请求 $action = $this->request->get('do'); if ($action) { switch ($action) { case 'getAll': $this->getAll(); break; case 'clearCache': $this->clearAllCache(); break; default: $this->response->throwJson(array( 'success' => false, 'message' => '未知操作' )); } } else { $this->response->throwJson(array( 'success' => false, 'message' => '缺少操作参数' )); } } /** * 清除所有缓存 * * @access private * @return void */ private function clearAllCache() { try { $this->clearCache(); $this->response->throwJson(array( 'success' => true, 'message' => '缓存已清除' )); } catch (Exception $e) { $this->response->throwJson(array( 'success' => false, 'message' => '清除缓存失败: ' . $e->getMessage() )); } } /** * 输出足迹地图 * * @access public * @param string|null $externalTheme 外部传入的主题参数 * @return void */ public function render($externalTheme = null) { // 检查是否启用前台显示 if (!$this->options || !$this->options->enableDisplay) { return; } // 获取API密钥 - 使用JS API密钥 $apiKey = isset($this->options->jsApiKey) ? $this->options->jsApiKey : ''; if (empty($apiKey)) { echo '
请先在插件设置中配置高德地图JS API密钥
'; return; } // 获取配置 $zoomLevel = $this->options->zoomLevel ?: '15'; $viewMode = $this->options->viewMode ?: '2D'; // 优先使用外部传入的主题参数,如果没有则使用后台设置 $mapTheme = $externalTheme ?: ($this->options->mapTheme ?: 'normal'); $enableCluster = isset($this->options->enableCluster) ? $this->options->enableCluster : 0; $emptyMarkerColor = $this->options->emptyMarkerColor ?: '#2196F3'; $contentMarkerColor = $this->options->contentMarkerColor ?: '#4CAF50'; $clusterMarkerColor1 = $this->options->clusterMarkerColor1 ?: '#2196F3'; $clusterMarkerColor2 = $this->options->clusterMarkerColor2 ?: '#FF9800'; $clusterMarkerColor3 = $this->options->clusterMarkerColor3 ?: '#FF5722'; // 输出地图容器和筛选器容器 echo <<
分散聚合
HTML; // 输出JavaScript $this->outputMapScript($apiKey, $zoomLevel, $viewMode, $mapTheme, $enableCluster, $emptyMarkerColor, $contentMarkerColor, $clusterMarkerColor1, $clusterMarkerColor2, $clusterMarkerColor3); } /** * 获取所有足迹数据 * * @access public * @return void */ public function getAll() { try { // 先检查缓存 $cacheKey = 'mytrack_footprints_data'; $cachedData = $this->getCache($cacheKey); if ($cachedData !== null) { // 缓存命中,直接返回缓存数据 $this->response->throwJson(array( 'success' => true, 'data' => $cachedData, 'from_cache' => true )); return; } // 缓存未命中,查询数据库 $stmt = $this->db->query("SELECT id, latitude, longitude, name, address, location_type, rating_level, categories, review, description, article_cid, urlLabel, url, photos, tags, date, markerColor, related_articles, highlights, created_at, updated_at FROM plugin_track_footprint ORDER BY date ASC, created_at ASC"); $footprints = $stmt->fetchAll(PDO::FETCH_ASSOC); // 过滤掉经纬度无效的数据 $validFootprints = array_filter($footprints, function($footprint) { $longitude = is_numeric($footprint['longitude']) ? floatval($footprint['longitude']) : null; $latitude = is_numeric($footprint['latitude']) ? floatval($footprint['latitude']) : null; return $longitude !== null && $latitude !== null && !is_nan($longitude) && !is_nan($latitude) && $longitude >= -180 && $longitude <= 180 && $latitude >= -90 && $latitude <= 90; }); // 重新索引数组 $validFootprints = array_values($validFootprints); // 处理每个足迹 foreach ($validFootprints as &$footprint) { if (!empty($footprint['article_cid'])) { $articleInfo = MyTrack_Plugin::getArticleInfo($footprint['article_cid']); if ($articleInfo) { if (empty($footprint['urlLabel']) && !empty($articleInfo['title'])) { $footprint['urlLabel'] = $articleInfo['title']; } if (empty($footprint['url']) && !empty($articleInfo['link'])) { $footprint['url'] = $articleInfo['link']; } } } if (!empty($footprint['related_articles'])) { $relatedArticlesInfo = MyTrack_Plugin::getRelatedArticlesInfo($footprint['related_articles']); if (!empty($relatedArticlesInfo)) { $simpleArticles = array(); foreach ($relatedArticlesInfo as $cid => $info) { $simpleArticles[] = array( 'cid' => $cid, 'title' => $info['title'] ?? '', 'link' => $info['link'] ?? '' ); } $footprint['related_articles_info'] = $simpleArticles; } else { $footprint['related_articles_info'] = array(); } } else { $footprint['related_articles_info'] = array(); } // 确保字段有默认值 if (!isset($footprint['address'])) $footprint['address'] = ''; if (!isset($footprint['location_type'])) $footprint['location_type'] = ''; if (!isset($footprint['rating_level'])) $footprint['rating_level'] = 0; if (!isset($footprint['categories'])) $footprint['categories'] = ''; if (!isset($footprint['review'])) $footprint['review'] = ''; if (!isset($footprint['markerColor'])) $footprint['markerColor'] = ''; if (!isset($footprint['related_articles'])) $footprint['related_articles'] = ''; if (!isset($footprint['highlights'])) $footprint['highlights'] = ''; $footprint['rating_level'] = intval($footprint['rating_level']); // 重要:保持categories为英文数组,不翻译 if (!empty($footprint['categories'])) { $categoriesArray = explode(',', $footprint['categories']); $footprint['categories'] = array_map('trim', $categoriesArray); } else { $footprint['categories'] = array(); } } // 将数据存入缓存 if ($this->cacheExpire > 0) { $this->setCache($cacheKey, $validFootprints); } $this->response->throwJson(array( 'success' => true, 'data' => $validFootprints, 'from_cache' => false )); } catch (Exception $e) { $this->response->throwJson(array( 'success' => false, 'message' => '查询失败: ' . $e->getMessage() )); } } /** * 将分类从英文翻译为中文 * * @access private * @param string $category 英文分类 * @return string 中文分类 */ private function translateCategoryToChinese($category) { $translations = array( 'plan' => '计划', 'Plan' => '计划', 'want' => '向往', 'Want' => '向往', 'visited' => '已玩', 'Visited' => '已玩', 'wish' => '想去', 'Wish' => '想去', 'todo' => '待做', 'Todo' => '待做', 'done' => '完成', 'Done' => '完成', 'travel' => '旅行', 'Travel' => '旅行', 'food' => '美食', 'Food' => '美食', 'shopping' => '购物', 'Shopping' => '购物', 'entertainment' => '娱乐', 'Entertainment' => '娱乐', 'culture' => '文化', 'Culture' => '文化', 'nature' => '自然', 'Nature' => '自然', 'cafe' => '咖啡', 'Cafe' => '咖啡', 'restaurant' => '餐厅', 'Restaurant' => '餐厅', 'hotel' => '酒店', 'Hotel' => '酒店', 'shop' => '商店', 'Shop' => '商店', 'attraction' => '景点', 'Attraction' => '景点', 'park' => '公园', 'Park' => '公园', 'museum' => '博物馆', 'Museum' => '博物馆' ); $categoryLower = strtolower(trim($category)); foreach ($translations as $key => $value) { if (strtolower($key) === $categoryLower) { return $value; } } return $category; } /** * 输出内容 * * @access public * @param string|null $externalTheme 外部传入的主题参数 * @return void */ public static function output($externalTheme = null) { $widget = new self( Typecho_Widget::widget('Widget_Options')->request, Typecho_Widget::widget('Widget_Options')->response ); $widget->render($externalTheme); } /** * 获取足迹数据(服务器端获取) * * @access private * @return array 足迹数据数组 */ private function getFootprintsData() { try { $cacheKey = 'mytrack_footprints_data'; $cachedData = $this->getCache($cacheKey); if ($cachedData !== null) { return array( 'success' => true, 'data' => $cachedData, 'from_cache' => true ); } $stmt = $this->db->query("SELECT id, latitude, longitude, name, address, location_type, rating_level, categories, review, description, article_cid, urlLabel, url, photos, tags, date, markerColor, related_articles, highlights, created_at, updated_at FROM plugin_track_footprint ORDER BY date ASC, created_at ASC"); $footprints = $stmt->fetchAll(PDO::FETCH_ASSOC); $validFootprints = array_filter($footprints, function($footprint) { $longitude = is_numeric($footprint['longitude']) ? floatval($footprint['longitude']) : null; $latitude = is_numeric($footprint['latitude']) ? floatval($footprint['latitude']) : null; return $longitude !== null && $latitude !== null && !is_nan($longitude) && !is_nan($latitude) && $longitude >= -180 && $longitude <= 180 && $latitude >= -90 && $latitude <= 90; }); $validFootprints = array_values($validFootprints); foreach ($validFootprints as &$footprint) { if (!empty($footprint['article_cid'])) { $articleInfo = MyTrack_Plugin::getArticleInfo($footprint['article_cid']); if ($articleInfo) { if (empty($footprint['urlLabel']) && !empty($articleInfo['title'])) { $footprint['urlLabel'] = $articleInfo['title']; } if (empty($footprint['url']) && !empty($articleInfo['link'])) { $footprint['url'] = $articleInfo['link']; } } } if (!empty($footprint['related_articles'])) { $relatedArticlesInfo = MyTrack_Plugin::getRelatedArticlesInfo($footprint['related_articles']); if (!empty($relatedArticlesInfo)) { $simpleArticles = array(); foreach ($relatedArticlesInfo as $cid => $info) { $simpleArticles[] = array( 'cid' => $cid, 'title' => $info['title'] ?? '', 'link' => $info['link'] ?? '' ); } $footprint['related_articles_info'] = $simpleArticles; } else { $footprint['related_articles_info'] = array(); } } else { $footprint['related_articles_info'] = array(); } if (!isset($footprint['address'])) $footprint['address'] = ''; if (!isset($footprint['location_type'])) $footprint['location_type'] = ''; if (!isset($footprint['rating_level'])) $footprint['rating_level'] = 0; if (!isset($footprint['categories'])) $footprint['categories'] = ''; if (!isset($footprint['review'])) $footprint['review'] = ''; if (!isset($footprint['markerColor'])) $footprint['markerColor'] = ''; if (!isset($footprint['related_articles'])) $footprint['related_articles'] = ''; if (!isset($footprint['highlights'])) $footprint['highlights'] = ''; $footprint['rating_level'] = intval($footprint['rating_level']); // 重要:保持categories为英文数组,不翻译 if (!empty($footprint['categories'])) { $categoriesArray = explode(',', $footprint['categories']); $footprint['categories'] = array_map('trim', $categoriesArray); } else { $footprint['categories'] = array(); } } if ($this->cacheExpire > 0) { $this->setCache($cacheKey, $validFootprints); } return array( 'success' => true, 'data' => $validFootprints, 'from_cache' => false ); } catch (Exception $e) { return array( 'success' => false, 'message' => '查询失败: ' . $e->getMessage(), 'data' => array(), 'from_cache' => false ); } } /** * 输出地图脚本 * * @access private * @param string $apiKey API密钥 * @param string $zoomLevel 缩放级别 * @param string $viewMode 视图模式 * @param string $mapTheme 地图主题 * @param int $enableCluster 是否启用点聚合 * @param string $emptyMarkerColor 空内容标记点颜色 * @param string $contentMarkerColor 有内容标记点颜色 * @param string $clusterMarkerColor1 聚合标记点颜色1 * @param string $clusterMarkerColor2 聚合标记点颜色2 * @param string $clusterMarkerColor3 聚合标记点颜色3 * @return void */ private function outputMapScript($apiKey, $zoomLevel, $viewMode, $mapTheme, $enableCluster, $emptyMarkerColor, $contentMarkerColor, $clusterMarkerColor1, $clusterMarkerColor2, $clusterMarkerColor3) { $version = MyTrack_Plugin::getVersion(); $emptyMarkerRgb = $this->hexToRgb($emptyMarkerColor); $contentMarkerRgb = $this->hexToRgb($contentMarkerColor); $clusterMarkerRgb1 = $this->hexToRgb($clusterMarkerColor1); $clusterMarkerRgb2 = $this->hexToRgb($clusterMarkerColor2); $clusterMarkerRgb3 = $this->hexToRgb($clusterMarkerColor3); $footprintsData = $this->getFootprintsData(); $footprintsJson = json_encode($footprintsData, JSON_UNESCAPED_UNICODE); // 输出CSS - 移植深色模式适配 echo << /* AMap Overrides */ .amap-logo, .amap-copyright { display: none !important; } /* Reset InfoWindow Styles */ .amap-info-content, .amap-info-outer, .amap-info-inner { background: transparent !important; border: none !important; box-shadow: none !important; padding: 0 !important; } .amap-info-sharp, .amap-info-shadow { display: none !important; } .amap-scale-text, .amap-scalecontrol { background: none !important; border: none !important; box-shadow: none !important; } /* 主容器 */ .mytrack-map-container { width: 100%; min-height: 420px; border-radius: 12px; overflow: hidden; margin: 1.5rem 0; position: relative; background: #fff; z-index: 1; } .mytrack-map { width: 100%; height: 100%; outline: none; } .mytrack-map-container.is-fullscreen { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; z-index: 99999 !important; margin: 0 !important; border-radius: 0 !important; background: #fff; } /* 分类筛选器样式 - 重构 */ .mytrack-filters { position: absolute; top: 6px; left: 6px; display: flex; flex-wrap: wrap; gap: 8px; padding: 8px; border-radius: 24px; background: rgba(255, 255, 255, 0.9); box-shadow: 0 4px 18px rgba(0, 0, 0, 0.12); z-index: 3; max-width: calc(100% - 24px); } .mytrack-filter-btn { border: none; background: transparent; color: #333; font-size: 0.85rem; padding: 6px 12px; border-radius: 18px; cursor: pointer; transition: background 0.2s ease, color 0.2s ease; white-space: nowrap; } .mytrack-filter-btn:hover { background: rgba(0, 0, 0, 0.05); } .mytrack-filter-btn.is-active { background: #f15a22; color: #fff; } /* 右侧工具栏样式 - 优化 */ .mytrack-toolbar { position: absolute; top: 20px; right: 20px; z-index: 9; display: flex; flex-direction: column; gap: 12px; } .mytrack-toolbar-group { display: flex; flex-direction: column; gap: 8px; background: rgba(255, 255, 255, 0.95); border-radius: 10px; padding: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .mytrack-toolbar-btn { background: white; border: 1px solid #e0e0e0; border-radius: 6px; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; padding: 0; } .mytrack-toolbar-btn:hover { background: #f5f5f5; border-color: #ccc; transform: translateY(-1px); } .mytrack-toolbar-btn svg { width: 20px; height: 20px; fill: #666; } .mytrack-toolbar-btn:hover svg { fill: #333; } /* 集群开关样式 */ .mytrack-cluster-toggle { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 9; display: flex; align-items: center; gap: 8px; padding: 8px 16px; background: rgba(255, 255, 255, 0.95); border-radius: 20px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); font-size: 13px; user-select: none; transition: all 0.3s ease; } .mytrack-cluster-label { font-weight: 500; color: #333; transition: color 0.3s ease; } .mytrack-cluster-switch { position: relative; width: 44px; height: 24px; border: none; border-radius: 12px; cursor: pointer; transition: background 0.3s ease; outline: none; background: #f15a22; padding: 0; } .mytrack-cluster-switch.is-off { background: #ccc; } .mytrack-cluster-knob { position: absolute; top: 2px; left: 22px; width: 20px; height: 20px; background: white; border-radius: 50%; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: left 0.3s ease; } .mytrack-cluster-switch.is-off .mytrack-cluster-knob { left: 2px; } /* 信息面板样式 */ .mytrack-info-tags{display:none;} .mytrack-map-container { width: 100%; height: 500px; margin: 20px 0; position: relative; } .mytrack-map { width: 100%; height: 100%; border-radius:10px;} .mytrack-info-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500px; max-width: 90%; background: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 1000; } .mytrack-info-header { padding: 25px 20px 5px 20px; display: flex; justify-content: space-between; align-items: center; } .mytrack-info-header h3 { margin: 0; font-size: 18px; font-weight: 600; color: #333; flex: 1; } .mytrack-close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; } .mytrack-info-content { padding:0 20px 20px; max-height: 400px; overflow-y: auto; } .mytrack-card-info { flex: 1; min-width: 0; padding:0 20px!important; } .mytrack-card-info > div { margin-bottom: 10px; font-size: 14px; line-height: 1.5; } .dark .mytrack-card-highlights{ color:#ccc; } .mytrack-card-info strong { color: #555; font-weight: 600; min-width: 40px; display: inline-block; } .mytrack-card-title { margin: 0; font-size: 18px; font-weight: 600; color: #333; flex: 1; margin-left:20px!important; } .mytrack-card-address { color: #666; } .mytrack-card-type { color: #666; } .mytrack-card-categories { display: flex; flex-wrap: wrap; } .mytrack-categories-badge { display: inline-block; font-size: 14px; margin-right: 5px; margin-bottom: 5px; color:#666; } .mytrack-categories-cafe-badge { background-color: #e8f4fd; color: #0d6efd; border: 1px solid #b3d7ff; } .dark .mytrack-categories-badge{ color:#ccc; background: #333; } .mytrack-categories-restaurant-badge { background-color: #f8f9fa; color: #6c757d; border: 1px solid #dee2e6; } .mytrack-categories-hotel-badge { background-color: #fff3cd; color: #856404; border: 1px solid #ffeaa7; } .mytrack-categories-shop-badge { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .mytrack-categories-attraction-badge { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } .mytrack-categories-food-badge { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .mytrack-categories-travel-badge { background-color: #cce5ff; color: #004085; border: 1px solid #b8daff; } .mytrack-card-rating { display: flex; align-items: center; } .mytrack-stars-inline { display: inline-flex; align-items: center; } .mytrack-rating-display { display: inline-flex; align-items: center; } .mytrack-rating-star { color: #ffc107; font-size: 14px; } .mytrack-rating-empty-star { color: #e4e5e9; font-size: 14px; } .mytrack-related-articles-section { margin-top: 10px; } .mytrack-related-articles-list { margin: 5px 0 0 0; padding: 0; } .mytrack-related-article-item { margin-bottom: 5px; font-size: 13px; } .mytrack-related-article-item a { color: #3b82f6; text-decoration: none; } .mytrack-related-article-item a:hover { text-decoration: underline; } .mytrack-card-review { color: #444; border-radius: 6px; } .mytrack-info-images { display: none !important; } .mytrack-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 999; display: none; } .mytrack-lightbox { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1001; display: none; justify-content: center; align-items: center; cursor: pointer; } .mytrack-lightbox img { max-width: 90%; max-height: 90%; object-fit: contain; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); } .mytrack-lightbox-close { position: absolute; top: 20px; right: 30px; color: white; font-size: 40px; font-weight: bold; cursor: pointer; z-index: 1002; transition: color 0.3s ease; } .mytrack-lightbox-close:hover { color: #ccc; } .mytrack-lightbox-nav { position: absolute; top: 50%; transform: translateY(-50%); color: white; font-size: 30px; font-weight: bold; cursor: pointer; padding: 15px; transition: background-color 0.3s ease; border-radius: 50%; width: 30px; height: 30px;line-height: 23px;text-align: center;} .mytrack-lightbox-nav:hover { background-color: rgba(255,255,255,0.1); } .mytrack-lightbox-prev { left: 20px; } .mytrack-lightbox-next { right: 20px; } .mytrack-info-panel.is-hidden { display: none !important; } .mytrack-error { padding: 15px; background: #f8d7da; color: #721c24; border-radius: 4px; margin: 20px 0; } .mytrack-marker-custom { width: 20px; height: 20px; border-radius: 50%; box-shadow: 0 2px 5px rgba(0,0,0,0.3); cursor: pointer; transition: transform 0.2s ease-out; border: 2px solid #fff; } .mytrack-marker-custom:hover { transform: scale(2.05); } .mytrack-marker-sunset { background: linear-gradient(135deg, rgb(255, 179, 71), rgb(255, 111, 97)); } .mytrack-marker-ocean { background: linear-gradient(135deg, rgb(6, 190, 182), rgb(72, 177, 191)); } .mytrack-marker-forest { background: linear-gradient(135deg, rgb(94, 231, 223), rgb(57, 163, 124)); } .mytrack-marker-amber { background: linear-gradient(135deg, rgb(246, 211, 101), rgb(253, 160, 133)); } .mytrack-marker-violet { background: linear-gradient(135deg, rgb(161, 140, 209), rgb(251, 194, 235)); } .mytrack-marker-citrus { background: linear-gradient(135deg, rgb(253, 251, 143), rgb(161, 255, 206)); } .mytrack-marker-empty { width: 20px; height: 20px; border-radius: 50%; background-color: rgba({$emptyMarkerRgb['r']}, {$emptyMarkerRgb['g']}, {$emptyMarkerRgb['b']}, 0.7); border: 2px solid {$emptyMarkerColor}; box-shadow: 0 2px 5px rgba(0,0,0,0.3); cursor: pointer; } .mytrack-marker-with-content { width: 20px; height: 20px; border-radius: 50%; background: linear-gradient(180deg, #cf6609, #c00); box-shadow: 0 2px 5px rgba(0,0,0,0.3); cursor: pointer; transition: transform 0.2s ease-out; border: 2px solid #fff; } .mytrack-marker-with-content:hover { transform: scale(2.05); } /* ========================================================================== 深色模式适配 - 移植自正常工作版本 ========================================================================== */ .dark .mytrack-filters { background: rgba(40, 40, 40, 0.95); border-color: #555; color: #e0e0e0; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); } .dark .mytrack-filter-btn { background: transparent; color: #999; border: none; } .dark .mytrack-filter-btn:hover { background: rgba(255, 255, 255, 0.1); } .dark .mytrack-filter-btn.is-active { background:#f15a22;; color: #fff; } .dark .mytrack-toolbar-group { background: rgba(40, 40, 40, 0.95); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } .dark .mytrack-toolbar-btn { background: #333; border-color: #555; } .dark .mytrack-toolbar-btn:hover { background: #444; border-color: #666; } .dark .mytrack-toolbar-btn svg { fill: #ccc; } .dark .mytrack-toolbar-btn:hover svg { fill: white; } .dark .mytrack-cluster-toggle { background: rgba(40, 40, 40, 0.95); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); } .dark .mytrack-cluster-label { color: #e0e0e0; } .dark .mytrack-cluster-switch { background: #555; } .dark .mytrack-cluster-switch.is-off { background: #444; } .dark .mytrack-cluster-switch.is-on { background: #f15a22; } /* 信息面板深色模式 */ .dark .mytrack-info-panel { background-color: #333; } .dark .mytrack-info-panel .mytrack-card-title { color: #fff; } .dark .mytrack-info-panel strong { color: #b5b5b5; } .dark .mytrack-card-review{ color: #ccc;} /**.dark .mytrack-info-panel .mytrack-c { background: #444; color: #ddd; border-left-color: #4a90e2; }**/ .dark .mytrack-info-panel .mytrack-card-address, .dark .mytrack-info-panel .mytrack-card-type { color: #ccc; } .dark .mytrack-info-panel .mytrack-related-article-item a { color: #64b5f6; } /* 分类徽章深色模式适配 */ .dark .mytrack-info-panel .mytrack-categories-cafe-badge { background-color: rgba(232, 244, 253, 0.2); color: #b3d7ff; border-color: rgba(179, 215, 255, 0.3); } /* 移动端适配 */ @media (max-width: 640px) { .mytrack-filters { top: 10px; left: 10px; max-width: calc(100% - 100px); max-height: 120px; overflow-y: auto; border-radius: 12px; padding: 6px; gap: 6px; } .mytrack-filter-btn { padding: 4px 12px; font-size: 12px; } .mytrack-toolbar { top: 10px; right: 10px; gap: 8px; } .mytrack-toolbar-group { padding: 6px; gap: 6px; } .mytrack-toolbar-btn { width: 32px; height: 32px; } .mytrack-toolbar-btn svg { width: 18px; height: 18px; } .mytrack-cluster-toggle { bottom: 10px; padding: 6px 12px; font-size: 12px; } .mytrack-cluster-switch { width: 40px; height: 22px; } .mytrack-cluster-knob { width: 18px; height: 18px; top: 2px; } .mytrack-cluster-switch .mytrack-cluster-knob { left: 20px; } .mytrack-cluster-switch.is-off .mytrack-cluster-knob { left: 2px; } } HTML; echo '
'; echo '
× 大图预览
'; // 输出JavaScript - 添加地图主题切换功能 echo << (function() { var mapContainer = document.getElementById("mytrack-map"); if (!mapContainer) return; // 全局变量 var allFootprints = []; var currentFilter = 'all'; var markers = []; var cluster = null; var allCategories = []; var markersCache = {}; var clusterEnabled = {$enableCluster} ? true : false; var themeObserver = null; var registeredMaps = new Set(); // 从之前正常的代码中移植的:获取当前主题 function getCurrentTheme() { var lightTheme = 'amap://styles/whitesmoke'; var darkTheme = 'amap://styles/dark'; return document.documentElement.classList.contains('dark') ? darkTheme : lightTheme; } // 从之前正常的代码中移植的:注册主题同步 function registerThemeSync(map) { registeredMaps.add(map); if (themeObserver) return; themeObserver = new MutationObserver(() => { var style = getCurrentTheme(); registeredMaps.forEach(m => { try { m.setMapStyle(style); } catch (e) {} }); }); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); } // 加载高德地图API function loadAMapScript() { if (window.AMap) { initMap(); return; } if (typeof AMapLoader !== 'undefined') { var plugins = ['AMap.Scale', 'AMap.ToolBar', 'AMap.ControlBar']; if (clusterEnabled) { plugins.push('AMap.MarkerCluster', 'AMap.Adaptor'); } AMapLoader.load({ key: '{$apiKey}', version: '2.0', plugins: plugins }).then((AMap) => { initMap(); }).catch((e) => { console.error("加载高德地图API失败:", e); showError("加载高德地图API失败,请检查网络连接或API密钥配置"); }); } else { var loaderScript = document.createElement("script"); loaderScript.type = "text/javascript"; loaderScript.src = "https://webapi.amap.com/loader.js"; loaderScript.onload = function() { loadAMapScript(); }; loaderScript.onerror = function() { console.error("加载AMapLoader失败"); showError("加载地图加载器失败,请检查网络连接"); }; document.head.appendChild(loaderScript); } } loadAMapScript(); function initMap() { try { // 修改:使用getCurrentTheme()设置地图样式 window.mytrackMap = new AMap.Map("mytrack-map", { zoom: parseInt({$zoomLevel}), viewMode: "{$viewMode}", mapStyle: getCurrentTheme(), // 添加这一行 resizeEnable: true }); // 添加:注册主题同步 registerThemeSync(window.mytrackMap); // 初始化工具栏事件 initToolbarEvents(); // 初始化集群开关 initClusterToggle(); // 等待地图完全加载 window.mytrackMap.on('complete', function() { processFootprintsData(); console.info('%cMyTrack v{$version}%chttps://wangdaodao.com/', 'color: #013821; background: #43bb88; padding:5px; font-size: 12px; font-weight: bold;','color: #fadfa3; background: #030307; padding:5px; font-size: 12px; font-weight: bold;'); }); } catch (error) { console.error("初始化地图失败:", error); } } function initToolbarEvents() { var fullscreenBtn = document.getElementById("mytrack-fullscreen-btn"); var resetBtn = document.getElementById("mytrack-reset-btn"); var zoomInBtn = document.getElementById("mytrack-zoom-in-btn"); var zoomOutBtn = document.getElementById("mytrack-zoom-out-btn"); if (fullscreenBtn) fullscreenBtn.addEventListener("click", toggleFullscreen); if (resetBtn) resetBtn.addEventListener("click", resetView); if (zoomInBtn) zoomInBtn.addEventListener("click", zoomIn); if (zoomOutBtn) zoomOutBtn.addEventListener("click", zoomOut); } function initClusterToggle() { var clusterSwitch = document.getElementById("mytrack-cluster-switch"); if (!clusterSwitch) return; // 设置初始状态 if (!clusterEnabled) { clusterSwitch.classList.remove("is-on"); clusterSwitch.classList.add("is-off"); } clusterSwitch.addEventListener("click", function() { clusterEnabled = !clusterEnabled; if (clusterEnabled) { clusterSwitch.classList.remove("is-off"); clusterSwitch.classList.add("is-on"); } else { clusterSwitch.classList.remove("is-on"); clusterSwitch.classList.add("is-off"); } // 更新地图显示 updateClusters(); }); } function toggleFullscreen() { var container = document.getElementById("mytrack-map-container"); if (!container) return; var isFullscreen = container.classList.contains("is-fullscreen"); if (!isFullscreen) { if (container.requestFullscreen) { container.requestFullscreen(); } else if (container.mozRequestFullScreen) { container.mozRequestFullScreen(); } else if (container.webkitRequestFullscreen) { container.webkitRequestfullscreen(); } else if (container.msRequestFullscreen) { container.msRequestfullscreen(); } container.classList.add("is-fullscreen"); } else { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } container.classList.remove("is-fullscreen"); } setTimeout(function() { if (window.mytrackMap) { window.mytrackMap.resize(); } }, 100); } function resetView() { if (window.mytrackMap && markers && markers.length > 0) { try { window.mytrackMap.setFitView(markers, false, [80, 80, 80, 120]); } catch (e) { console.error("重置视图失败:", e); } } } function zoomIn() { if (window.mytrackMap) { var currentZoom = window.mytrackMap.getZoom(); window.mytrackMap.setZoom(currentZoom + 1); } } function zoomOut() { if (window.mytrackMap) { var currentZoom = window.mytrackMap.getZoom(); window.mytrackMap.setZoom(currentZoom - 1); } } document.addEventListener("fullscreenchange", handleFullscreenChange); document.addEventListener("webkitfullscreenchange", handleFullscreenChange); document.addEventListener("mozfullscreenchange", handleFullscreenChange); document.addEventListener("MSFullscreenChange", handleFullscreenChange); function handleFullscreenChange() { var container = document.getElementById("mytrack-map-container"); if (!container) return; var isFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (isFullscreen) { container.classList.add("is-fullscreen"); } else { container.classList.remove("is-fullscreen"); } setTimeout(function() { if (window.mytrackMap) { window.mytrackMap.resize(); } }, 100); } function processFootprintsData() { try { var serverData = {$footprintsJson}; console.log('足迹数据:', serverData); if (serverData.success) { allFootprints = serverData.data; console.log('处理后的足迹数据:', allFootprints.length, '条'); // 提取所有分类 - 使用之前的逻辑 extractCategories(allFootprints); console.log('提取到的分类:', allCategories); // 渲染筛选按钮 - 使用之前的逻辑 renderFilters(); // 显示所有足迹 displayFootprints(allFootprints); } else { console.error("获取足迹数据失败:", serverData.message); showError("获取足迹数据失败: " + serverData.message); } } catch (error) { console.error("处理足迹数据出错:", error); showError("处理足迹数据出错: " + error.message); } } // 提取所有分类 - 之前的逻辑 function extractCategories(footprints) { var categoriesSet = new Set(); footprints.forEach(function(footprint) { if (footprint.categories && Array.isArray(footprint.categories)) { footprint.categories.forEach(function(category) { category = category.trim(); if (category) { categoriesSet.add(category); } }); } }); allCategories = Array.from(categoriesSet).sort(); } // 渲染筛选按钮 - 修改:在显示时翻译为中文 function renderFilters() { var filtersContainer = document.getElementById("mytrack-filters"); if (!filtersContainer) return; console.log('渲染筛选器,分类数量:', allCategories.length); // 只有当有分类时才显示筛选器 if (allCategories.length === 0) { console.log('没有分类,隐藏筛选器'); filtersContainer.style.display = 'none'; return; } // 清空容器 filtersContainer.innerHTML = ''; // 创建"全部"按钮 var allButton = createFilterButton('全部足迹', 'all', true); filtersContainer.appendChild(allButton); // 创建分类按钮 - 显示时翻译为中文 allCategories.forEach(function(category) { // 在显示时翻译为中文,但内部逻辑仍使用英文分类 var displayName = translateCategoryToChinese(category); var button = createFilterButton(displayName, category, false); filtersContainer.appendChild(button); }); console.log('筛选器渲染完成,按钮数量:', filtersContainer.children.length); } // 创建筛选按钮 - 之前的逻辑 function createFilterButton(label, value, isActive) { var button = document.createElement("button"); button.type = "button"; button.className = "mytrack-filter-btn"; if (isActive) { button.classList.add("is-active"); } button.textContent = label; button.dataset.filter = value; button.addEventListener("click", function() { if (button.classList.contains("is-active")) return; // 更新活动状态 document.querySelectorAll(".mytrack-filter-btn").forEach(function(btn) { btn.classList.remove("is-active"); }); button.classList.add("is-active"); // 更新筛选 - 注意:这里使用的是英文分类值 currentFilter = value; filterFootprints(); }); return button; } // 筛选足迹 - 之前的逻辑 function filterFootprints() { var filteredFootprints = []; if (currentFilter === 'all') { filteredFootprints = allFootprints; } else { filteredFootprints = allFootprints.filter(function(footprint) { if (!footprint.categories || !Array.isArray(footprint.categories)) { return false; } return footprint.categories.some(function(category) { return category.trim() === currentFilter; }); }); } console.log('筛选结果:', currentFilter, '=>', filteredFootprints.length, '条'); // 显示筛选后的足迹 displayFootprints(filteredFootprints); } function showError(message) { var mapContainer = document.getElementById("mytrack-map"); if (mapContainer) { var errorDiv = document.createElement("div"); errorDiv.className = "mytrack-error"; errorDiv.textContent = message; errorDiv.style.position = "absolute"; errorDiv.style.top = "50%"; errorDiv.style.left = "50%"; errorDiv.style.transform = "translate(-50%, -50%)"; errorDiv.style.zIndex = "10"; errorDiv.style.maxWidth = "80%"; mapContainer.appendChild(errorDiv); } } // 显示足迹 - 修复集群问题 function displayFootprints(footprints) { if (!footprints || footprints.length === 0) { clearMap(); return; } // 清空地图 clearMap(); var validFootprints = []; var newMarkers = []; var hasContentCache = {}; footprints.forEach(function(footprint) { var longitude = parseFloat(footprint.longitude); var latitude = parseFloat(footprint.latitude); if (isNaN(longitude) || isNaN(latitude) || longitude < -180 || longitude > 180 || latitude < -90 || latitude > 90) { console.warn("发现无效的经纬度数据:", footprint); return; } var hasAddress = !!(footprint.address && footprint.address.trim()); var hasLocationType = !!(footprint.location_type && footprint.location_type.trim()); var hasRatingLevel = !!(footprint.rating_level && footprint.rating_level > 0); var hasCategories = !!(footprint.categories && footprint.categories.length > 0); var hasReview = !!(footprint.review && footprint.review.trim()); var hasHighlights = !!(footprint.highlights && footprint.highlights.trim()); var hasRelatedArticles = false; if (footprint.related_articles_info && Array.isArray(footprint.related_articles_info)) { hasRelatedArticles = footprint.related_articles_info.length > 0; } var hasContent = hasAddress || hasLocationType || hasRatingLevel || hasCategories || hasReview || hasHighlights || hasRelatedArticles; hasContentCache[footprint.id || JSON.stringify(footprint)] = { hasAddress: hasAddress, hasLocationType: hasLocationType, hasRatingLevel: hasRatingLevel, hasCategories: hasCategories, hasReview: hasReview, hasHighlights: hasHighlights, hasRelatedArticles: hasRelatedArticles, hasContent: hasContent }; validFootprints.push(footprint); var position = new AMap.LngLat(longitude, latitude); try { var marker = new AMap.Marker({ position: position, title: footprint.name || "足迹点", animation: "AMAP_ANIMATION_DROP", anchor: 'bottom-center' }); marker.setExtData({ footprint: footprint, hasAddress: hasAddress, hasLocationType: hasLocationType, hasRatingLevel: hasRatingLevel, hasCategories: hasCategories, hasReview: hasReview, hasHighlights: hasHighlights, hasRelatedArticles: hasRelatedArticles, hasContent: hasContent }); var markerContent = ''; if (footprint.markerColor) { markerContent = '
'; } else if (hasContent) { markerContent = '
'; } else { markerContent = '
'; } marker.setContent(markerContent); marker.on("click", function(e) { var data = e.target.getExtData(); if (data.hasContent) { showFootprintInfo(data.footprint); } }); newMarkers.push(marker); markersCache[footprint.id || JSON.stringify(footprint)] = marker; } catch (error) { console.error("创建标记点失败:", error, "数据:", footprint); } }); markers = newMarkers; // 更新集群显示 - 简化逻辑 updateClusters(); if (validFootprints.length > 0) { if (markers && markers.length > 0) { try { window.mytrackMap.setFitView(markers, false, [80, 80, 80, 120]); } catch (e) { console.error("自动调整视图失败:", e); var firstFootprint = validFootprints[0]; var centerPosition = new AMap.LngLat( parseFloat(firstFootprint.longitude), parseFloat(firstFootprint.latitude) ); window.mytrackMap.setCenter(centerPosition); } } else { var firstFootprint = validFootprints[0]; var centerPosition = new AMap.LngLat( parseFloat(firstFootprint.longitude), parseFloat(firstFootprint.latitude) ); window.mytrackMap.setCenter(centerPosition); } } else { console.warn("没有有效的足迹数据可以显示"); } } // 更新集群显示 - 简化版本 function updateClusters() { // 移除现有的集群 if (cluster) { try { cluster.setMap(null); } catch (e) { console.error("移除集群时出错:", e); } cluster = null; } // 移除现有的标记点 if (markers && markers.length > 0) { try { window.mytrackMap.remove(markers); } catch (e) { console.error("移除标记点时出错:", e); } } // 如果启用了点聚合,创建聚合对象 if (clusterEnabled && markers.length > 0) { try { if (typeof AMap.MarkerCluster !== 'undefined') { cluster = new AMap.MarkerCluster(window.mytrackMap, markers, { gridSize: 80, maxZoom: 10, averageCenter: true, minClusterSize: 2, styles: [ { url: 'data:image/svg+xml;base64,' + btoa(''), size: new AMap.Size(40, 40), offset: new AMap.Pixel(-20, -20), textColor: 'white', textSize: 14 }, { url: 'data:image/svg+xml;base64,' + btoa(''), size: new AMap.Size(45, 45), offset: new AMap.Pixel(-22.5, -22.5), textColor: 'white', textSize: 16 }, { url: 'data:image/svg+xml;base64,' + btoa(''), size: new AMap.Size(50, 50), offset: new AMap.Pixel(-25, -25), textColor: 'white', textSize: 16 } ] }); cluster.on('click', function(e) { var clusterData = e.clusterData; if (clusterData && clusterData.length > 1) { var currentZoom = window.mytrackMap.getZoom(); var newZoom = currentZoom + 3; newZoom = Math.min(newZoom, 18); window.mytrackMap.setZoom(newZoom); window.mytrackMap.setCenter(e.lnglat); return; } var marker = e.target; if (marker && marker.getExtData) { var data = marker.getExtData(); if (data.hasContent) { showFootprintInfo(data.footprint); } } }); console.log('集群已启用,标记点数量:', markers.length); } else { console.warn("AMap.MarkerCluster未加载,使用普通标记点"); window.mytrackMap.add(markers); } } catch (error) { console.error("创建点聚合失败:", error); window.mytrackMap.add(markers); } } else if (markers.length > 0) { window.mytrackMap.add(markers); console.log('集群已禁用,使用普通标记点,数量:', markers.length); } } function clearMap() { if (cluster) { cluster.setMap(null); cluster = null; } if (markers && markers.length > 0) { window.mytrackMap.remove(markers); markers = []; } markersCache = {}; } function showFootprintInfo(footprint) { var infoPanel = document.getElementById("mytrack-info-panel"); var overlay = document.getElementById("mytrack-overlay"); var metaDiv = document.getElementById("mytrack-info-meta"); var reviewDiv = document.getElementById("mytrack-info-review"); var imagesDiv = document.getElementById("mytrack-info-images"); var titleElement = document.getElementById("mytrack-info-title"); var hasAddress = !!(footprint.address && footprint.address.trim()); var hasLocationType = !!(footprint.location_type && footprint.location_type.trim()); var hasRatingLevel = !!(footprint.rating_level && footprint.rating_level > 0); var hasCategories = !!(footprint.categories && footprint.categories.length > 0); var hasReview = !!(footprint.review && footprint.review.trim()); var hasHighlights = !!(footprint.highlights && footprint.highlights.trim()); var hasRelatedArticles = false; var relatedArticlesInfo = null; if (footprint.related_articles_info && Array.isArray(footprint.related_articles_info)) { relatedArticlesInfo = footprint.related_articles_info; hasRelatedArticles = relatedArticlesInfo.length > 0; } if (!hasAddress && !hasLocationType && !hasRatingLevel && !hasCategories && !hasReview && !hasHighlights && !hasRelatedArticles) { return; } metaDiv.innerHTML = ''; reviewDiv.style.display = 'none'; imagesDiv.style.display = 'none'; titleElement.textContent = footprint.name || "足迹信息"; // 1. 地址信息 if (hasAddress) { var addressDiv = document.createElement("div"); addressDiv.className = "mytrack-card-address"; addressDiv.innerHTML = '地址:' + footprint.address; metaDiv.appendChild(addressDiv); } // 2. 类型信息 if (hasLocationType) { var typeDiv = document.createElement("div"); typeDiv.className = "mytrack-card-type"; typeDiv.innerHTML = '类型:' + footprint.location_type; metaDiv.appendChild(typeDiv); } // 3. 分类信息 - 翻译为中文显示 if (hasCategories) { var categoriesDiv = document.createElement("div"); categoriesDiv.className = "mytrack-card-categories"; categoriesDiv.innerHTML = '分类:'; if (Array.isArray(footprint.categories)) { footprint.categories.forEach(function(category) { category = category.trim(); var className = getCategoryClass(category); var text = translateCategoryToChinese(category); var badge = document.createElement("span"); badge.className = 'mytrack-categories-badge ' + className; badge.textContent = text; categoriesDiv.appendChild(badge); }); } metaDiv.appendChild(categoriesDiv); } // 4. 推荐星级 if (hasRatingLevel) { var ratingDiv = document.createElement("div"); ratingDiv.className = "mytrack-card-rating"; ratingDiv.innerHTML = '推荐:'; var starsContainer = document.createElement("span"); starsContainer.className = "mytrack-rating-display"; for (var i = 1; i <= 5; i++) { var star = document.createElement("span"); if (i <= footprint.rating_level) { star.className = "mytrack-rating-star"; star.textContent = "★"; } else { star.className = "mytrack-rating-empty-star"; star.textContent = "☆"; } starsContainer.appendChild(star); } ratingDiv.appendChild(starsContainer); metaDiv.appendChild(ratingDiv); } // 5. 亮点信息(新增) if (hasHighlights) { var highlightsDiv = document.createElement("div"); highlightsDiv.className = "mytrack-card-highlights"; highlightsDiv.innerHTML = '亮点:'; // 按逗号分隔亮点 var highlights = footprint.highlights.split(',').map(function(highlight) { return highlight.trim(); }).filter(function(highlight) { return highlight !== ''; }); if (highlights.length > 0) { highlights.forEach(function(highlight, index) { if (index > 0) { highlightsDiv.appendChild(document.createTextNode(', ')); } var highlightSpan = document.createElement("span"); highlightSpan.className = "mytrack-highlight-tag"; highlightSpan.textContent = highlight; highlightsDiv.appendChild(highlightSpan); }); } metaDiv.appendChild(highlightsDiv); } // 6. 简评 if (hasReview) { var reviewDivInMeta = document.createElement("div"); reviewDivInMeta.className = "mytrack-card-review"; reviewDivInMeta.innerHTML = '简评:' + footprint.review; metaDiv.appendChild(reviewDivInMeta); } // 7. 关联文章 if (hasRelatedArticles && relatedArticlesInfo) { var relatedArticlesDiv = document.createElement("div"); relatedArticlesDiv.className = "mytrack-related-articles-section"; relatedArticlesDiv.innerHTML = '这些文章提到了本地点:'; var articlesList = document.createElement("div"); articlesList.className = "mytrack-related-articles-list"; relatedArticlesInfo.forEach(function(articleInfo) { if (articleInfo && articleInfo.title && articleInfo.link) { var articleItem = document.createElement("div"); articleItem.className = "mytrack-related-article-item"; var link = document.createElement("a"); link.href = articleInfo.link; link.target = "_blank"; link.textContent = articleInfo.title; articleItem.appendChild(link); articlesList.appendChild(articleItem); } }); if (articlesList.children.length > 0) { relatedArticlesDiv.appendChild(articlesList); metaDiv.appendChild(relatedArticlesDiv); } } infoPanel.style.display = "block"; overlay.style.display = "block"; } function getCategoryClass(category) { var categoryLower = category.toLowerCase(); if (categoryLower === 'visited' || categoryLower === '已玩') { return 'mytrack-categories-visited-badge'; } else if (categoryLower === 'want' || categoryLower === '向往') { return 'mytrack-categories-want-badge'; } else if (categoryLower === 'plan' || categoryLower === '计划') { return 'mytrack-categories-plan-badge'; } else if (categoryLower === 'cafe' || categoryLower === '咖啡') { return 'mytrack-categories-cafe-badge'; } else if (categoryLower === 'restaurant' || categoryLower === '餐厅') { return 'mytrack-categories-restaurant-badge'; } else if (categoryLower === 'hotel' || categoryLower === '酒店') { return 'mytrack-categories-hotel-badge'; } else if (categoryLower === 'shop' || categoryLower === '商店') { return 'mytrack-categories-shop-badge'; } else if (categoryLower === 'attraction' || categoryLower === '景点') { return 'mytrack-categories-attraction-badge'; } else if (categoryLower === 'food' || categoryLower === '美食') { return 'mytrack-categories-food-badge'; } else if (categoryLower === 'travel' || categoryLower === '旅行') { return 'mytrack-categories-travel-badge'; } return 'mytrack-categories-badge'; } // 翻译分类为中文(用于显示) function translateCategoryToChinese(category) { var translations = { 'plan': '计划', 'Plan': '计划', 'want': '向往', 'Want': '向往', 'visited': '已玩', 'Visited': '已玩', 'wish': '想去', 'Wish': '想去', 'todo': '待做', 'Todo': '待做', 'done': '完成', 'Done': '完成', 'travel': '旅行', 'Travel': '旅行', 'food': '美食', 'Food': '美食', 'shopping': '购物', 'Shopping': '购物', 'entertainment': '娱乐', 'Entertainment': '娱乐', 'culture': '文化', 'Culture': '文化', 'nature': '自然', 'Nature': '自然', 'cafe': '咖啡', 'Cafe': '咖啡', 'restaurant': '餐厅', 'Restaurant': '餐厅', 'hotel': '酒店', 'Hotel': '酒店', 'shop': '商店', 'Shop': '商店', 'attraction': '景点', 'Attraction': '景点', 'park': '公园', 'Park': '公园', 'museum': '博物馆', 'Museum': '博物馆' }; var categoryLower = category.toLowerCase(); for (var key in translations) { if (key.toLowerCase() === categoryLower) { return translations[key]; } } return category; } function closeInfoPanel() { var infoPanel = document.getElementById("mytrack-info-panel"); var overlay = document.getElementById("mytrack-overlay"); if (infoPanel) infoPanel.style.display = "none"; if (overlay) overlay.style.display = "none"; } var currentImageIndex = 0; var currentImages = []; function openLightbox(imageSrc, images, index) { currentImages = images; currentImageIndex = index; var lightbox = document.getElementById("mytrack-lightbox"); var lightboxImg = document.getElementById("mytrack-lightbox-img"); var infoPanel = document.getElementById("mytrack-info-panel"); if (lightboxImg) lightboxImg.src = imageSrc; if (lightbox) lightbox.style.display = "flex"; if (infoPanel) { infoPanel.classList.add("is-hidden"); } updateNavigationButtons(); } function closeLightbox() { var lightbox = document.getElementById("mytrack-lightbox"); var infoPanel = document.getElementById("mytrack-info-panel"); if (lightbox) lightbox.style.display = "none"; if (infoPanel) { infoPanel.classList.remove("is-hidden"); } } function updateNavigationButtons() { var prevBtn = document.querySelector(".mytrack-lightbox-prev"); var nextBtn = document.querySelector(".mytrack-lightbox-next"); if (!prevBtn || !nextBtn) return; if (currentImages.length <= 1) { prevBtn.style.display = "none"; nextBtn.style.display = "none"; } else { prevBtn.style.display = currentImageIndex === 0 ? "none" : "block"; nextBtn.style.display = currentImageIndex === currentImages.length - 1 ? "none" : "block"; } } function showPrevImage() { if (currentImageIndex > 0) { currentImageIndex--; var lightboxImg = document.getElementById("mytrack-lightbox-img"); if (lightboxImg) lightboxImg.src = currentImages[currentImageIndex]; updateNavigationButtons(); } } function showNextImage() { if (currentImageIndex < currentImages.length - 1) { currentImageIndex++; var lightboxImg = document.getElementById("mytrack-lightbox-img"); if (lightboxImg) lightboxImg.src = currentImages[currentImageIndex]; updateNavigationButtons(); } } var infoCloseBtn = document.getElementById("mytrack-info-close"); var overlay = document.getElementById("mytrack-overlay"); if (infoCloseBtn) infoCloseBtn.addEventListener("click", closeInfoPanel); if (overlay) overlay.addEventListener("click", closeInfoPanel); var lightbox = document.getElementById("mytrack-lightbox"); if (lightbox) { lightbox.addEventListener("click", function(e) { if (e.target === this) { closeLightbox(); } }); } var lightboxClose = document.querySelector(".mytrack-lightbox-close"); var lightboxPrev = document.querySelector(".mytrack-lightbox-prev"); var lightboxNext = document.querySelector(".mytrack-lightbox-next"); if (lightboxClose) lightboxClose.addEventListener("click", closeLightbox); if (lightboxPrev) lightboxPrev.addEventListener("click", function(e) { e.stopPropagation(); showPrevImage(); }); if (lightboxNext) lightboxNext.addEventListener("click", function(e) { e.stopPropagation(); showNextImage(); }); document.addEventListener("keydown", function(e) { var lightbox = document.getElementById("mytrack-lightbox"); if (lightbox && lightbox.style.display === "flex") { if (e.key === "Escape") { closeLightbox(); } else if (e.key === "ArrowLeft") { showPrevImage(); } else if (e.key === "ArrowRight") { showNextImage(); } } }); })(); HTML; } /** * 将十六进制颜色转换为RGB格式 * * @access private * @param string $hex 十六进制颜色值 * @return array RGB值数组 */ private function hexToRgb($hex) { $hex = ltrim($hex, '#'); if (strlen($hex) == 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } $r = hexdec(substr($hex, 0, 2)); $g = hexdec(substr($hex, 2, 2)); $b = hexdec(substr($hex, 4, 2)); return array('r' => $r, 'g' => $g, 'b' => $b); } }