文章目录
-
- 在当今数字化时代,网站已经不仅仅是信息展示的平台,更是提供实用工具和服务的重要渠道。对于城市生活类网站、旅游博客、本地商业网站或社区门户而言,集成实时公共交通信息查询功能可以显著提升用户体验和网站价值。 WordPress作为全球最流行的内容管理系统,其强大的扩展性使得开发者能够通过代码二次开发实现各种互联网小工具功能。本教程将详细介绍如何在WordPress中连接开放API,实现实时公共交通信息查询功能,为访问者提供便捷的出行规划服务。 通过本教程,您将学习到如何: 选择合适的公共交通API 在WordPress中安全地集成API 设计用户友好的查询界面 处理并展示实时交通数据 优化功能性能和用户体验
-
- 在开始开发之前,首先需要选择一个合适的公共交通API。以下是一些国内外常用的选择: 国际通用API: Google Maps Directions API:提供全球范围内的公共交通路线规划 Transitland:开源公共交通数据平台,覆盖多个国家和地区 OpenRouteService:基于开放数据提供路线规划服务 中国地区API: 高德地图API:提供全面的公共交通查询功能 百度地图API:包含公交、地铁等公共交通路线规划 腾讯地图API:公共交通查询功能完善 本教程将以高德地图API为例,因为其对中国公共交通数据的覆盖较为全面,且提供免费的开发额度。
- 访问高德开放平台官网(https://lbs.amap.com/) 注册开发者账号并登录 进入控制台,创建新应用 获取您的API密钥(Key)
- 确保您的WordPress环境满足以下条件: WordPress 5.0或更高版本 PHP 7.2或更高版本 已安装并激活一个适合开发的主题 具备基本的WordPress插件开发知识
- 在WordPress的wp-content/plugins/目录下创建新插件文件夹public-transit-query,并建立以下结构: public-transit-query/ ├── public-transit-query.php ├── includes/ │ ├── class-api-handler.php │ ├── class-shortcode.php │ └── class-admin-settings.php ├── assets/ │ ├── css/ │ │ └── style.css │ └── js/ │ └── script.js └── templates/ └── transit-form.php
-
- 打开public-transit-query.php,添加以下基础插件代码: <?php /** * Plugin Name: 实时公共交通查询 * Plugin URI: https://yourwebsite.com/public-transit-query * Description: 在WordPress网站中集成实时公共交通查询功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: public-transit-query */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('PTQ_VERSION', '1.0.0'); define('PTQ_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('PTQ_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class_name) { if (strpos($class_name, 'PTQ_') === 0) { $class_file = PTQ_PLUGIN_DIR . 'includes/' . 'class-' . strtolower(str_replace('_', '-', $class_name)) . '.php'; if (file_exists($class_file)) { require_once $class_file; } } }); // 初始化插件 function ptq_init() { // 检查依赖 if (!function_exists('curl_init')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>公共交通查询插件需要cURL扩展支持,请确保您的PHP已启用cURL。</p></div>'; }); return; } // 初始化各个组件 PTQ_API_Handler::get_instance(); PTQ_Shortcode::get_instance(); PTQ_Admin_Settings::get_instance(); } add_action('plugins_loaded', 'ptq_init'); // 激活插件时的操作 register_activation_hook(__FILE__, 'ptq_activate'); function ptq_activate() { // 创建必要的数据库表或选项 if (!get_option('ptq_settings')) { $default_settings = array( 'api_key' => '', 'default_city' => '北京', 'cache_duration' => 300, 'enable_cache' => true ); update_option('ptq_settings', $default_settings); } } // 停用插件时的操作 register_deactivation_hook(__FILE__, 'ptq_deactivate'); function ptq_deactivate() { // 清理临时数据 delete_transient('ptq_api_status'); }
- 在includes/class-api-handler.php中创建API处理类: <?php class PTQ_API_Handler { private static $instance = null; private $api_key; private $api_base = 'https://restapi.amap.com/v3/'; private $settings; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->settings = get_option('ptq_settings', array()); $this->api_key = isset($this->settings['api_key']) ? $this->settings['api_key'] : ''; add_action('wp_ajax_ptq_get_transit', array($this, 'ajax_get_transit')); add_action('wp_ajax_nopriv_ptq_get_transit', array($this, 'ajax_get_transit')); } /** * 获取公共交通路线 */ public function get_transit_route($origin, $destination, $city = null) { if (empty($this->api_key)) { return new WP_Error('no_api_key', 'API密钥未配置'); } // 使用缓存减少API调用 $cache_key = 'ptq_route_' . md5($origin . $destination . $city); $cached_result = get_transient($cache_key); if ($cached_result !== false && isset($this->settings['enable_cache']) && $this->settings['enable_cache']) { return $cached_result; } // 构建API请求参数 $city = $city ?: (isset($this->settings['default_city']) ? $this->settings['default_city'] : '北京'); $params = array( 'key' => $this->api_key, 'origin' => $origin, 'destination' => $destination, 'city' => $city, 'extensions' => 'all', 'output' => 'JSON' ); $url = $this->api_base . 'direction/transit/integrated?' . http_build_query($params); // 发送API请求 $response = $this->make_request($url); if (is_wp_error($response)) { return $response; } // 解析响应数据 $result = $this->parse_transit_response($response); // 缓存结果 $cache_duration = isset($this->settings['cache_duration']) ? $this->settings['cache_duration'] : 300; set_transient($cache_key, $result, $cache_duration); return $result; } /** * 发送HTTP请求 */ private function make_request($url) { $args = array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/json' ) ); $response = wp_remote_get($url, $args); if (is_wp_error($response)) { return $response; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { return new WP_Error('json_parse_error', '解析API响应失败'); } if ($data['status'] != '1') { return new WP_Error('api_error', $data['info'] ?? 'API请求失败'); } return $data; } /** * 解析公共交通响应数据 */ private function parse_transit_response($data) { if (!isset($data['route']) || !isset($data['route']['transits'])) { return array(); } $transits = $data['route']['transits']; $parsed_routes = array(); foreach ($transits as $index => $transit) { if ($index >= 5) break; // 只显示前5条路线 $route = array( 'cost' => $transit['cost'] ?? '未知', 'duration' => $this->format_duration($transit['duration'] ?? 0), 'walking_distance' => $transit['walking_distance'] ?? 0, 'distance' => $transit['distance'] ?? 0, 'segments' => array() ); // 解析路线段 if (isset($transit['segments'])) { foreach ($transit['segments'] as $segment) { $segment_info = array( 'walking' => isset($segment['walking']) ? $segment['walking'] : null, 'bus' => isset($segment['bus']) ? $segment['bus'] : null, 'railway' => isset($segment['railway']) ? $segment['railway'] : null, 'taxi' => isset($segment['taxi']) ? $segment['taxi'] : null ); $route['segments'][] = $segment_info; } } $parsed_routes[] = $route; } return array( 'origin' => $data['route']['origin'] ?? '', 'destination' => $data['route']['destination'] ?? '', 'routes' => $parsed_routes, 'count' => count($parsed_routes) ); } /** * 格式化持续时间 */ private function format_duration($seconds) { $hours = floor($seconds / 3600); $minutes = floor(($seconds % 3600) / 60); if ($hours > 0) { return sprintf('%d小时%d分钟', $hours, $minutes); } else { return sprintf('%d分钟', $minutes); } } /** * AJAX处理函数 */ public function ajax_get_transit() { // 验证nonce if (!check_ajax_referer('ptq_ajax_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } $origin = sanitize_text_field($_POST['origin'] ?? ''); $destination = sanitize_text_field($_POST['destination'] ?? ''); $city = sanitize_text_field($_POST['city'] ?? ''); if (empty($origin) || empty($destination)) { wp_send_json_error('请输入起点和终点'); } $result = $this->get_transit_route($origin, $destination, $city); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } wp_send_json_success($result); } }
-
- 在includes/class-shortcode.php中创建短代码处理类: <?php class PTQ_Shortcode { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_shortcode('public_transit_query', array($this, 'render_shortcode')); add_action('wp_enqueue_scripts', array($this, 'enqueue_assets')); } /** * 渲染短代码 */ public function render_shortcode($atts) { $atts = shortcode_atts(array( 'title' => '公共交通查询', 'default_city' => '', 'show_history' => 'true' ), $atts, 'public_transit_query'); ob_start(); include PTQ_PLUGIN_DIR . 'templates/transit-form.php'; return ob_get_clean(); } /** * 加载前端资源 */ public function enqueue_assets() { global $post; // 只在包含短代码的页面加载资源 if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'public_transit_query')) { wp_enqueue_style( 'ptq-frontend-style', PTQ_PLUGIN_URL . 'assets/css/style.css', array(), PTQ_VERSION ); wp_enqueue_script( 'ptq-frontend-script', PTQ_PLUGIN_URL . 'assets/js/script.js', array('jquery'), PTQ_VERSION, true ); wp_localize_script('ptq-frontend-script', 'ptq_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ptq_ajax_nonce'), 'loading_text' => '查询中...', 'error_text' => '查询失败,请重试', 'no_results_text' => '未找到相关路线' )); } } }
- 在templates/transit-form.php中创建查询表单: <div class="ptq-container"> <div class="ptq-header"> <h2><?php echo esc_html($atts['title']); ?></h2> <p class="ptq-description">查询实时公共交通路线,规划您的出行</p> </div> <div class="ptq-form-container"> <form id="ptq-query-form" class="ptq-form"> <div class="ptq-form-row"> <div class="ptq-form-group"> <label for="ptq-origin">起点</label> <input type="text" id="ptq-origin" name="origin" placeholder="例如:天安门广场" required> <div class="ptq-example">可输入地址、地标或公交站名</div> </div> <div class="ptq-form-group"> <label for="ptq-destination">终点</label> <input type="text" id="ptq-destination" name="destination" placeholder="例如:北京西站" required> <div class="ptq-example">可输入地址、地标或公交站名</div> </div> </div> <div class="ptq-form-row"> <div class="ptq-form-group"> <label for="ptq-city">城市</label> <input type="text" id="ptq-city" name="city" placeholder="<?php echo esc_attr($atts['default_city'] ?: '北京'); ?>" value="<?php echo esc_attr($atts['default_city'] ?: ''); ?>"> <div class="ptq-example">默认为<?php echo esc_html($atts['default_city'] ?: '北京'); ?></div> </div> <div class="ptq-form-group ptq-submit-group"> <button type="submit" id="ptq-submit" class="ptq-submit-btn"> <span class="ptq-btn-text">查询路线</span> <span class="ptq-loading-spinner" style="display:none;"></span> </button> </div> </div> </form> </div> <div id="ptq-results-container" class="ptq-results-container" style="display:none;"> <div class="ptq-results-header"> <h3>查询结果</h3> <div class="ptq-route-summary"> <span id="ptq-route-origin"></span> → <span id="ptq-route-destination"></span> </div> </div> <div id="ptq-results" class="ptq-results"> <!-- 结果将通过JavaScript动态加载 --> </div> <div id="ptq-no-results" class="ptq-no-results" style="display:none;"> <p>未找到相关公共交通路线,请尝试调整查询条件。</p> </div> <div id="ptq-error" class="ptq-error" style="display:none;"> <p>查询过程中出现错误,请稍后重试。</p> </div> </div> <?php if ($atts['show_history'] === 'true') : ?> <div class="ptq-history-container"> <h4>最近查询</h4> <div id="ptq-history-list" class="ptq-history-list"> <!-- 查询历史将通过JavaScript动态加载 --> </div> </div> <?php endif; ?> <div class="ptq-footer"> <p class="ptq-disclaimer">数据来源:高德地图开放平台 | 更新时间:<span id="ptq-update-time"></span></p> </div>
-
- 在assets/js/script.js中添加前端交互逻辑: (function($) { 'use strict'; // 公共交通查询对象 var PTQ = { init: function() { this.cacheElements(); this.bindEvents(); this.loadHistory(); }, cacheElements: function() { this.$form = $('#ptq-query-form'); this.$origin = $('#ptq-origin'); this.$destination = $('#ptq-destination'); this.$city = $('#ptq-city'); this.$submitBtn = $('#ptq-submit'); this.$submitText = $('.ptq-btn-text'); this.$loadingSpinner = $('.ptq-loading-spinner'); this.$resultsContainer = $('#ptq-results-container'); this.$results = $('#ptq-results'); this.$noResults = $('#ptq-no-results'); this.$error = $('#ptq-error'); this.$historyList = $('#ptq-history-list'); this.$updateTime = $('#ptq-update-time'); }, bindEvents: function() { var self = this; // 表单提交事件 this.$form.on('submit', function(e) { e.preventDefault(); self.submitQuery(); }); // 输入框自动完成建议 this.setupAutocomplete(); // 更新当前时间 this.updateCurrentTime(); setInterval(function() { self.updateCurrentTime(); }, 60000); // 每分钟更新一次 }, submitQuery: function() { var self = this; var origin = this.$origin.val().trim(); var destination = this.$destination.val().trim(); var city = this.$city.val().trim(); // 验证输入 if (!origin || !destination) { this.showError('请输入起点和终点'); return; } // 显示加载状态 this.setLoading(true); // 隐藏之前的错误和结果 this.$noResults.hide(); this.$error.hide(); // 发送AJAX请求 $.ajax({ url: ptq_ajax.ajax_url, type: 'POST', dataType: 'json', data: { action: 'ptq_get_transit', nonce: ptq_ajax.nonce, origin: origin, destination: destination, city: city }, success: function(response) { self.setLoading(false); if (response.success) { self.displayResults(response.data); self.saveToHistory(origin, destination, city); } else { self.showError(response.data || ptq_ajax.error_text); } }, error: function() { self.setLoading(false); self.showError(ptq_ajax.error_text); } }); }, displayResults: function(data) { // 显示结果容器 this.$resultsContainer.show(); // 更新路线摘要 $('#ptq-route-origin').text(data.origin); $('#ptq-route-destination').text(data.destination); // 清空之前的结果 this.$results.empty(); if (!data.routes || data.routes.length === 0) { this.$noResults.show(); return; } // 显示路线结果 $.each(data.routes, function(index, route) { var routeHtml = self.buildRouteHtml(route, index + 1); self.$results.append(routeHtml); }); // 滚动到结果区域 $('html, body').animate({ scrollTop: self.$resultsContainer.offset().top - 100 }, 500); }, buildRouteHtml: function(route, index) { var html = '<div class="ptq-route-card">'; html += '<div class="ptq-route-header">'; html += '<span class="ptq-route-index">方案' + index + '</span>'; html += '<span class="ptq-route-stats">'; html += '<span class="ptq-stat"><i class="ptq-icon-time"></i>' + route.duration + '</span>'; html += '<span class="ptq-stat"><i class="ptq-icon-cost"></i>' + route.cost + '元</span>'; html += '<span class="ptq-stat"><i class="ptq-icon-distance"></i>' + (route.distance / 1000).toFixed(1) + '公里</span>'; html += '</span>'; html += '</div>'; html += '<div class="ptq-route-details">'; // 构建路线详情 if (route.segments && route.segments.length > 0) { $.each(route.segments, function(segmentIndex, segment) { html += self.buildSegmentHtml(segment, segmentIndex); }); } html += '</div>'; html += '</div>'; return html; }, buildSegmentHtml: function(segment, index) { var html = '<div class="ptq-segment">'; // 步行段 if (segment.walking && segment.walking.distance > 0) { html += '<div class="ptq-segment-walking">'; html += '<span class="ptq-segment-icon"><i class="ptq-icon-walk"></i></span>'; html += '<span class="ptq-segment-text">步行' + (segment.walking.distance / 1000).toFixed(1) + '公里</span>'; html += '</div>'; } // 公交段 if (segment.bus && segment.bus.buslines && segment.bus.buslines.length > 0) { $.each(segment.bus.buslines, function(i, busline) { html += '<div class="ptq-segment-bus">'; html += '<span class="ptq-segment-icon"><i class="ptq-icon-bus"></i></span>'; html += '<span class="ptq-segment-text">'; html += busline.name + ' (' + busline.departure_stop.name + ' → ' + busline.arrival_stop.name + ')'; html += '</span>'; html += '</div>'; }); } // 地铁段 if (segment.railway && segment.railway.name) { html += '<div class="ptq-segment-railway">'; html += '<span class="ptq-segment-icon"><i class="ptq-icon-subway"></i></span>'; html += '<span class="ptq-segment-text">' + segment.railway.name + '</span>'; html += '</div>'; } html += '</div>'; return html; }, setupAutocomplete: function() { // 这里可以集成高德地图的输入提示API // 由于篇幅限制,这里只提供基本思路 var self = this; // 使用高德地图的输入提示功能 // 需要额外引入高德地图JavaScript API if (typeof AMap !== 'undefined') { // 创建输入提示实例 var originAuto = new AMap.Autocomplete({ input: 'ptq-origin' }); var destAuto = new AMap.Autocomplete({ input: 'ptq-destination' }); // 监听选择事件 AMap.event.addListener(originAuto, 'select', function(e) { self.$origin.val(e.poi.name); }); AMap.event.addListener(destAuto, 'select', function(e) { self.$destination.val(e.poi.name); }); } }, saveToHistory: function(origin, destination, city) { var history = this.getHistory(); var query = { origin: origin, destination: destination, city: city, timestamp: new Date().getTime() }; // 添加到历史记录开头 history.unshift(query); // 只保留最近10条记录 if (history.length > 10) { history = history.slice(0, 10); } // 保存到localStorage localStorage.setItem('ptq_query_history', JSON.stringify(history)); // 更新显示 this.loadHistory(); }, getHistory: function() { var history = localStorage.getItem('ptq_query_history'); return history ? JSON.parse(history) : []; }, loadHistory: function() { var history = this.getHistory(); var self = this; this.$historyList.empty(); if (history.length === 0) { this.$historyList.append('<p class="ptq-no-history">暂无查询历史</p>'); return; } $.each(history, function(index, query) { var time = new Date(query.timestamp); var timeStr = time.getHours() + ':' + (time.getMinutes() < 10 ? '0' : '') + time.getMinutes(); var historyHtml = '<div class="ptq-history-item">'; historyHtml += '<div class="ptq-history-route">'; historyHtml += '<span class="ptq-history-origin">' + query.origin + '</span>'; historyHtml += ' → '; historyHtml += '<span class="ptq-history-destination">' + query.destination + '</span>'; historyHtml += '</div>'; historyHtml += '<div class="ptq-history-meta">'; historyHtml += '<span class="ptq-history-time">' + timeStr + '</span>'; historyHtml += '<button class="ptq-history-redo" data-origin="' + query.origin + '" data-destination="' + query.destination + '" data-city="' + (query.city || '') + '">再次查询</button>'; historyHtml += '</div>'; historyHtml += '</div>'; self.$historyList.append(historyHtml); }); // 绑定重新查询事件 $('.ptq-history-redo').on('click', function() { var $btn = $(this); self.$origin.val($btn.data('origin')); self.$destination.val($btn.data('destination')); self.$city.val($btn.data('city')); self.$form.submit(); }); }, setLoading: function(isLoading) { if (isLoading) { this.$submitText.text(ptq_ajax.loading_text); this.$loadingSpinner.show(); this.$submitBtn.prop('disabled', true); } else { this.$submitText.text('查询路线'); this.$loadingSpinner.hide(); this.$submitBtn.prop('disabled', false); } }, showError: function(message) { this.$error.find('p').text(message); this.$error.show(); }, updateCurrentTime: function() { var now = new Date(); var timeStr = now.getHours() + ':' + (now.getMinutes() < 10 ? '0' : '') + now.getMinutes(); this.$updateTime.text(timeStr); } }; // 初始化 $(document).ready(function() { PTQ.init(); }); // 暴露到全局 window.PTQ = PTQ; })(jQuery);
- 在assets/css/style.css中添加样式: /* 公共交通查询插件样式 */ .ptq-container { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: #f8f9fa; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); } .ptq-header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #e9ecef; } .ptq-header h2 { color: #2c3e50; margin: 0 0 10px 0; font-size: 28px; font-weight: 600; } .ptq-description { color: #6c757d; font-size: 16px; margin: 0; } /* 表单样式 */ .ptq-form-container { background: white; padding: 25px; border-radius: 10px; margin-bottom: 25px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .ptq-form-row { display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 20px; } .ptq-form-group { flex: 1; min-width: 250px; } .ptq-form-group label { display: block; margin-bottom: 8px; color: #495057; font-weight: 500; font-size: 14px; } .ptq-form-group input { width: 100%; padding: 12px 15px; border: 2px solid #dee2e6; border-radius: 6px; font-size: 16px; transition: all 0.3s ease; box-sizing: border-box; } .ptq-form-group input:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); } .ptq-example { font-size: 12px; color: #6c757d; margin-top: 5px; font-style: italic; } .ptq-submit-group { display: flex; align-items: flex-end; } .ptq-submit-btn { background: linear-gradient(135deg, #3498db, #2980b9); color: white; border: none; padding: 14px 30px; border-radius: 6px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; min-width: 150px; height: 48px; } .ptq-submit-btn:hover:not(:disabled) { background: linear-gradient(135deg, #2980b9, #1c6ea4); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3); } .ptq-submit-btn:disabled { opacity: 0.7; cursor: not-allowed; } .ptq-loading-spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.3); border-radius: 50%; border-top-color: white; animation: ptq-spin 1s ease-in-out infinite; margin-left: 10px; } @keyframes ptq-spin { to { transform: rotate(360deg); } } /* 结果区域样式 */ .ptq-results-container { background: white; padding: 25px; border-radius: 10px; margin-bottom: 25px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .ptq-results-header { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #e9ecef; } .ptq-results-header h3 { color: #2c3e50; margin: 0 0 10px 0; font-size: 22px; } .ptq-route-summary { color: #6c757d; font-size: 16px; font-weight: 500; } /* 路线卡片样式 */ .ptq-route-card { border: 1px solid #e9ecef; border-radius: 8px; padding: 20px; margin-bottom: 20px; transition: all 0.3s ease; } .ptq-route-card:hover { border-color: #3498db; box-shadow: 0 4px 15px rgba(52, 152, 219, 0.1); } .ptq-route-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #f1f3f4; } .ptq-route-index { font-size: 18px; font-weight: 600; color: #2c3e50; } .ptq-route-stats { display: flex; gap: 20px; } .ptq-stat { display: flex; align-items: center; color: #6c757d; font-size: 14px; } .ptq-icon-time, .ptq-icon-cost, .ptq-icon-distance, .ptq-icon-walk, .ptq-icon-bus, .ptq-icon-subway { display: inline-block; width: 16px; height: 16px; margin-right: 6px; background-size: contain; background-repeat: no-repeat; }
在当今数字化时代,网站已经不仅仅是信息展示的平台,更是提供实用工具和服务的重要渠道。对于城市生活类网站、旅游博客、本地商业网站或社区门户而言,集成实时公共交通信息查询功能可以显著提升用户体验和网站价值。
WordPress作为全球最流行的内容管理系统,其强大的扩展性使得开发者能够通过代码二次开发实现各种互联网小工具功能。本教程将详细介绍如何在WordPress中连接开放API,实现实时公共交通信息查询功能,为访问者提供便捷的出行规划服务。
通过本教程,您将学习到如何:
- 选择合适的公共交通API
- 在WordPress中安全地集成API
- 设计用户友好的查询界面
- 处理并展示实时交通数据
- 优化功能性能和用户体验
在开始开发之前,首先需要选择一个合适的公共交通API。以下是一些国内外常用的选择:
国际通用API:
- Google Maps Directions API:提供全球范围内的公共交通路线规划
- Transitland:开源公共交通数据平台,覆盖多个国家和地区
- OpenRouteService:基于开放数据提供路线规划服务
中国地区API:
- 高德地图API:提供全面的公共交通查询功能
- 百度地图API:包含公交、地铁等公共交通路线规划
- 腾讯地图API:公共交通查询功能完善
本教程将以高德地图API为例,因为其对中国公共交通数据的覆盖较为全面,且提供免费的开发额度。
- 访问高德开放平台官网(https://lbs.amap.com/)
- 注册开发者账号并登录
- 进入控制台,创建新应用
- 获取您的API密钥(Key)
确保您的WordPress环境满足以下条件:
- WordPress 5.0或更高版本
- PHP 7.2或更高版本
- 已安装并激活一个适合开发的主题
- 具备基本的WordPress插件开发知识
在WordPress的wp-content/plugins/目录下创建新插件文件夹public-transit-query,并建立以下结构:
public-transit-query/
├── public-transit-query.php
├── includes/
│ ├── class-api-handler.php
│ ├── class-shortcode.php
│ └── class-admin-settings.php
├── assets/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── script.js
└── templates/
└── transit-form.php
打开public-transit-query.php,添加以下基础插件代码:
<?php
/**
* Plugin Name: 实时公共交通查询
* Plugin URI: https://yourwebsite.com/public-transit-query
* Description: 在WordPress网站中集成实时公共交通查询功能
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
* Text Domain: public-transit-query
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('PTQ_VERSION', '1.0.0');
define('PTQ_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('PTQ_PLUGIN_URL', plugin_dir_url(__FILE__));
// 自动加载类文件
spl_autoload_register(function ($class_name) {
if (strpos($class_name, 'PTQ_') === 0) {
$class_file = PTQ_PLUGIN_DIR . 'includes/' . 'class-' . strtolower(str_replace('_', '-', $class_name)) . '.php';
if (file_exists($class_file)) {
require_once $class_file;
}
}
});
// 初始化插件
function ptq_init() {
// 检查依赖
if (!function_exists('curl_init')) {
add_action('admin_notices', function() {
echo '<div class="notice notice-error"><p>公共交通查询插件需要cURL扩展支持,请确保您的PHP已启用cURL。</p></div>';
});
return;
}
// 初始化各个组件
PTQ_API_Handler::get_instance();
PTQ_Shortcode::get_instance();
PTQ_Admin_Settings::get_instance();
}
add_action('plugins_loaded', 'ptq_init');
// 激活插件时的操作
register_activation_hook(__FILE__, 'ptq_activate');
function ptq_activate() {
// 创建必要的数据库表或选项
if (!get_option('ptq_settings')) {
$default_settings = array(
'api_key' => '',
'default_city' => '北京',
'cache_duration' => 300,
'enable_cache' => true
);
update_option('ptq_settings', $default_settings);
}
}
// 停用插件时的操作
register_deactivation_hook(__FILE__, 'ptq_deactivate');
function ptq_deactivate() {
// 清理临时数据
delete_transient('ptq_api_status');
}
在includes/class-api-handler.php中创建API处理类:
<?php
class PTQ_API_Handler {
private static $instance = null;
private $api_key;
private $api_base = 'https://restapi.amap.com/v3/';
private $settings;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->settings = get_option('ptq_settings', array());
$this->api_key = isset($this->settings['api_key']) ? $this->settings['api_key'] : '';
add_action('wp_ajax_ptq_get_transit', array($this, 'ajax_get_transit'));
add_action('wp_ajax_nopriv_ptq_get_transit', array($this, 'ajax_get_transit'));
}
/**
* 获取公共交通路线
*/
public function get_transit_route($origin, $destination, $city = null) {
if (empty($this->api_key)) {
return new WP_Error('no_api_key', 'API密钥未配置');
}
// 使用缓存减少API调用
$cache_key = 'ptq_route_' . md5($origin . $destination . $city);
$cached_result = get_transient($cache_key);
if ($cached_result !== false && isset($this->settings['enable_cache']) && $this->settings['enable_cache']) {
return $cached_result;
}
// 构建API请求参数
$city = $city ?: (isset($this->settings['default_city']) ? $this->settings['default_city'] : '北京');
$params = array(
'key' => $this->api_key,
'origin' => $origin,
'destination' => $destination,
'city' => $city,
'extensions' => 'all',
'output' => 'JSON'
);
$url = $this->api_base . 'direction/transit/integrated?' . http_build_query($params);
// 发送API请求
$response = $this->make_request($url);
if (is_wp_error($response)) {
return $response;
}
// 解析响应数据
$result = $this->parse_transit_response($response);
// 缓存结果
$cache_duration = isset($this->settings['cache_duration']) ? $this->settings['cache_duration'] : 300;
set_transient($cache_key, $result, $cache_duration);
return $result;
}
/**
* 发送HTTP请求
*/
private function make_request($url) {
$args = array(
'timeout' => 15,
'headers' => array(
'Accept' => 'application/json'
)
);
$response = wp_remote_get($url, $args);
if (is_wp_error($response)) {
return $response;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return new WP_Error('json_parse_error', '解析API响应失败');
}
if ($data['status'] != '1') {
return new WP_Error('api_error', $data['info'] ?? 'API请求失败');
}
return $data;
}
/**
* 解析公共交通响应数据
*/
private function parse_transit_response($data) {
if (!isset($data['route']) || !isset($data['route']['transits'])) {
return array();
}
$transits = $data['route']['transits'];
$parsed_routes = array();
foreach ($transits as $index => $transit) {
if ($index >= 5) break; // 只显示前5条路线
$route = array(
'cost' => $transit['cost'] ?? '未知',
'duration' => $this->format_duration($transit['duration'] ?? 0),
'walking_distance' => $transit['walking_distance'] ?? 0,
'distance' => $transit['distance'] ?? 0,
'segments' => array()
);
// 解析路线段
if (isset($transit['segments'])) {
foreach ($transit['segments'] as $segment) {
$segment_info = array(
'walking' => isset($segment['walking']) ? $segment['walking'] : null,
'bus' => isset($segment['bus']) ? $segment['bus'] : null,
'railway' => isset($segment['railway']) ? $segment['railway'] : null,
'taxi' => isset($segment['taxi']) ? $segment['taxi'] : null
);
$route['segments'][] = $segment_info;
}
}
$parsed_routes[] = $route;
}
return array(
'origin' => $data['route']['origin'] ?? '',
'destination' => $data['route']['destination'] ?? '',
'routes' => $parsed_routes,
'count' => count($parsed_routes)
);
}
/**
* 格式化持续时间
*/
private function format_duration($seconds) {
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
if ($hours > 0) {
return sprintf('%d小时%d分钟', $hours, $minutes);
} else {
return sprintf('%d分钟', $minutes);
}
}
/**
* AJAX处理函数
*/
public function ajax_get_transit() {
// 验证nonce
if (!check_ajax_referer('ptq_ajax_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
$origin = sanitize_text_field($_POST['origin'] ?? '');
$destination = sanitize_text_field($_POST['destination'] ?? '');
$city = sanitize_text_field($_POST['city'] ?? '');
if (empty($origin) || empty($destination)) {
wp_send_json_error('请输入起点和终点');
}
$result = $this->get_transit_route($origin, $destination, $city);
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
wp_send_json_success($result);
}
}
在includes/class-shortcode.php中创建短代码处理类:
<?php
class PTQ_Shortcode {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
add_shortcode('public_transit_query', array($this, 'render_shortcode'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_assets'));
}
/**
* 渲染短代码
*/
public function render_shortcode($atts) {
$atts = shortcode_atts(array(
'title' => '公共交通查询',
'default_city' => '',
'show_history' => 'true'
), $atts, 'public_transit_query');
ob_start();
include PTQ_PLUGIN_DIR . 'templates/transit-form.php';
return ob_get_clean();
}
/**
* 加载前端资源
*/
public function enqueue_assets() {
global $post;
// 只在包含短代码的页面加载资源
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'public_transit_query')) {
wp_enqueue_style(
'ptq-frontend-style',
PTQ_PLUGIN_URL . 'assets/css/style.css',
array(),
PTQ_VERSION
);
wp_enqueue_script(
'ptq-frontend-script',
PTQ_PLUGIN_URL . 'assets/js/script.js',
array('jquery'),
PTQ_VERSION,
true
);
wp_localize_script('ptq-frontend-script', 'ptq_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ptq_ajax_nonce'),
'loading_text' => '查询中...',
'error_text' => '查询失败,请重试',
'no_results_text' => '未找到相关路线'
));
}
}
}
在templates/transit-form.php中创建查询表单:
<div class="ptq-container">
<div class="ptq-header">
<h2><?php echo esc_html($atts['title']); ?></h2>
<p class="ptq-description">查询实时公共交通路线,规划您的出行</p>
</div>
<div class="ptq-form-container">
<form id="ptq-query-form" class="ptq-form">
<div class="ptq-form-row">
<div class="ptq-form-group">
<label for="ptq-origin">起点</label>
<input type="text" id="ptq-origin" name="origin"
placeholder="例如:天安门广场" required>
<div class="ptq-example">可输入地址、地标或公交站名</div>
</div>
<div class="ptq-form-group">
<label for="ptq-destination">终点</label>
<input type="text" id="ptq-destination" name="destination"
placeholder="例如:北京西站" required>
<div class="ptq-example">可输入地址、地标或公交站名</div>
</div>
</div>
<div class="ptq-form-row">
<div class="ptq-form-group">
<label for="ptq-city">城市</label>
<input type="text" id="ptq-city" name="city"
placeholder="<?php echo esc_attr($atts['default_city'] ?: '北京'); ?>"
value="<?php echo esc_attr($atts['default_city'] ?: ''); ?>">
<div class="ptq-example">默认为<?php echo esc_html($atts['default_city'] ?: '北京'); ?></div>
</div>
<div class="ptq-form-group ptq-submit-group">
<button type="submit" id="ptq-submit" class="ptq-submit-btn">
<span class="ptq-btn-text">查询路线</span>
<span class="ptq-loading-spinner" style="display:none;"></span>
</button>
</div>
</div>
</form>
</div>
<div id="ptq-results-container" class="ptq-results-container" style="display:none;">
<div class="ptq-results-header">
<h3>查询结果</h3>
<div class="ptq-route-summary">
<span id="ptq-route-origin"></span> → <span id="ptq-route-destination"></span>
</div>
</div>
<div id="ptq-results" class="ptq-results">
<!-- 结果将通过JavaScript动态加载 -->
</div>
<div id="ptq-no-results" class="ptq-no-results" style="display:none;">
<p>未找到相关公共交通路线,请尝试调整查询条件。</p>
</div>
<div id="ptq-error" class="ptq-error" style="display:none;">
<p>查询过程中出现错误,请稍后重试。</p>
</div>
</div>
<?php if ($atts['show_history'] === 'true') : ?>
<div class="ptq-history-container">
<h4>最近查询</h4>
<div id="ptq-history-list" class="ptq-history-list">
<!-- 查询历史将通过JavaScript动态加载 -->
</div>
</div>
<?php endif; ?>
<div class="ptq-footer">
<p class="ptq-disclaimer">数据来源:高德地图开放平台 | 更新时间:<span id="ptq-update-time"></span></p>
</div>
在assets/js/script.js中添加前端交互逻辑:
(function($) {
'use strict';
// 公共交通查询对象
var PTQ = {
init: function() {
this.cacheElements();
this.bindEvents();
this.loadHistory();
},
cacheElements: function() {
this.$form = $('#ptq-query-form');
this.$origin = $('#ptq-origin');
this.$destination = $('#ptq-destination');
this.$city = $('#ptq-city');
this.$submitBtn = $('#ptq-submit');
this.$submitText = $('.ptq-btn-text');
this.$loadingSpinner = $('.ptq-loading-spinner');
this.$resultsContainer = $('#ptq-results-container');
this.$results = $('#ptq-results');
this.$noResults = $('#ptq-no-results');
this.$error = $('#ptq-error');
this.$historyList = $('#ptq-history-list');
this.$updateTime = $('#ptq-update-time');
},
bindEvents: function() {
var self = this;
// 表单提交事件
this.$form.on('submit', function(e) {
e.preventDefault();
self.submitQuery();
});
// 输入框自动完成建议
this.setupAutocomplete();
// 更新当前时间
this.updateCurrentTime();
setInterval(function() {
self.updateCurrentTime();
}, 60000); // 每分钟更新一次
},
submitQuery: function() {
var self = this;
var origin = this.$origin.val().trim();
var destination = this.$destination.val().trim();
var city = this.$city.val().trim();
// 验证输入
if (!origin || !destination) {
this.showError('请输入起点和终点');
return;
}
// 显示加载状态
this.setLoading(true);
// 隐藏之前的错误和结果
this.$noResults.hide();
this.$error.hide();
// 发送AJAX请求
$.ajax({
url: ptq_ajax.ajax_url,
type: 'POST',
dataType: 'json',
data: {
action: 'ptq_get_transit',
nonce: ptq_ajax.nonce,
origin: origin,
destination: destination,
city: city
},
success: function(response) {
self.setLoading(false);
if (response.success) {
self.displayResults(response.data);
self.saveToHistory(origin, destination, city);
} else {
self.showError(response.data || ptq_ajax.error_text);
}
},
error: function() {
self.setLoading(false);
self.showError(ptq_ajax.error_text);
}
});
},
displayResults: function(data) {
// 显示结果容器
this.$resultsContainer.show();
// 更新路线摘要
$('#ptq-route-origin').text(data.origin);
$('#ptq-route-destination').text(data.destination);
// 清空之前的结果
this.$results.empty();
if (!data.routes || data.routes.length === 0) {
this.$noResults.show();
return;
}
// 显示路线结果
$.each(data.routes, function(index, route) {
var routeHtml = self.buildRouteHtml(route, index + 1);
self.$results.append(routeHtml);
});
// 滚动到结果区域
$('html, body').animate({
scrollTop: self.$resultsContainer.offset().top - 100
}, 500);
},
buildRouteHtml: function(route, index) {
var html = '<div class="ptq-route-card">';
html += '<div class="ptq-route-header">';
html += '<span class="ptq-route-index">方案' + index + '</span>';
html += '<span class="ptq-route-stats">';
html += '<span class="ptq-stat"><i class="ptq-icon-time"></i>' + route.duration + '</span>';
html += '<span class="ptq-stat"><i class="ptq-icon-cost"></i>' + route.cost + '元</span>';
html += '<span class="ptq-stat"><i class="ptq-icon-distance"></i>' + (route.distance / 1000).toFixed(1) + '公里</span>';
html += '</span>';
html += '</div>';
html += '<div class="ptq-route-details">';
// 构建路线详情
if (route.segments && route.segments.length > 0) {
$.each(route.segments, function(segmentIndex, segment) {
html += self.buildSegmentHtml(segment, segmentIndex);
});
}
html += '</div>';
html += '</div>';
return html;
},
buildSegmentHtml: function(segment, index) {
var html = '<div class="ptq-segment">';
// 步行段
if (segment.walking && segment.walking.distance > 0) {
html += '<div class="ptq-segment-walking">';
html += '<span class="ptq-segment-icon"><i class="ptq-icon-walk"></i></span>';
html += '<span class="ptq-segment-text">步行' + (segment.walking.distance / 1000).toFixed(1) + '公里</span>';
html += '</div>';
}
// 公交段
if (segment.bus && segment.bus.buslines && segment.bus.buslines.length > 0) {
$.each(segment.bus.buslines, function(i, busline) {
html += '<div class="ptq-segment-bus">';
html += '<span class="ptq-segment-icon"><i class="ptq-icon-bus"></i></span>';
html += '<span class="ptq-segment-text">';
html += busline.name + ' (' + busline.departure_stop.name + ' → ' + busline.arrival_stop.name + ')';
html += '</span>';
html += '</div>';
});
}
// 地铁段
if (segment.railway && segment.railway.name) {
html += '<div class="ptq-segment-railway">';
html += '<span class="ptq-segment-icon"><i class="ptq-icon-subway"></i></span>';
html += '<span class="ptq-segment-text">' + segment.railway.name + '</span>';
html += '</div>';
}
html += '</div>';
return html;
},
setupAutocomplete: function() {
// 这里可以集成高德地图的输入提示API
// 由于篇幅限制,这里只提供基本思路
var self = this;
// 使用高德地图的输入提示功能
// 需要额外引入高德地图JavaScript API
if (typeof AMap !== 'undefined') {
// 创建输入提示实例
var originAuto = new AMap.Autocomplete({
input: 'ptq-origin'
});
var destAuto = new AMap.Autocomplete({
input: 'ptq-destination'
});
// 监听选择事件
AMap.event.addListener(originAuto, 'select', function(e) {
self.$origin.val(e.poi.name);
});
AMap.event.addListener(destAuto, 'select', function(e) {
self.$destination.val(e.poi.name);
});
}
},
saveToHistory: function(origin, destination, city) {
var history = this.getHistory();
var query = {
origin: origin,
destination: destination,
city: city,
timestamp: new Date().getTime()
};
// 添加到历史记录开头
history.unshift(query);
// 只保留最近10条记录
if (history.length > 10) {
history = history.slice(0, 10);
}
// 保存到localStorage
localStorage.setItem('ptq_query_history', JSON.stringify(history));
// 更新显示
this.loadHistory();
},
getHistory: function() {
var history = localStorage.getItem('ptq_query_history');
return history ? JSON.parse(history) : [];
},
loadHistory: function() {
var history = this.getHistory();
var self = this;
this.$historyList.empty();
if (history.length === 0) {
this.$historyList.append('<p class="ptq-no-history">暂无查询历史</p>');
return;
}
$.each(history, function(index, query) {
var time = new Date(query.timestamp);
var timeStr = time.getHours() + ':' + (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
var historyHtml = '<div class="ptq-history-item">';
historyHtml += '<div class="ptq-history-route">';
historyHtml += '<span class="ptq-history-origin">' + query.origin + '</span>';
historyHtml += ' → ';
historyHtml += '<span class="ptq-history-destination">' + query.destination + '</span>';
historyHtml += '</div>';
historyHtml += '<div class="ptq-history-meta">';
historyHtml += '<span class="ptq-history-time">' + timeStr + '</span>';
historyHtml += '<button class="ptq-history-redo" data-origin="' + query.origin + '" data-destination="' + query.destination + '" data-city="' + (query.city || '') + '">再次查询</button>';
historyHtml += '</div>';
historyHtml += '</div>';
self.$historyList.append(historyHtml);
});
// 绑定重新查询事件
$('.ptq-history-redo').on('click', function() {
var $btn = $(this);
self.$origin.val($btn.data('origin'));
self.$destination.val($btn.data('destination'));
self.$city.val($btn.data('city'));
self.$form.submit();
});
},
setLoading: function(isLoading) {
if (isLoading) {
this.$submitText.text(ptq_ajax.loading_text);
this.$loadingSpinner.show();
this.$submitBtn.prop('disabled', true);
} else {
this.$submitText.text('查询路线');
this.$loadingSpinner.hide();
this.$submitBtn.prop('disabled', false);
}
},
showError: function(message) {
this.$error.find('p').text(message);
this.$error.show();
},
updateCurrentTime: function() {
var now = new Date();
var timeStr = now.getHours() + ':' + (now.getMinutes() < 10 ? '0' : '') + now.getMinutes();
this.$updateTime.text(timeStr);
}
};
// 初始化
$(document).ready(function() {
PTQ.init();
});
// 暴露到全局
window.PTQ = PTQ;
})(jQuery);
在assets/css/style.css中添加样式:
/* 公共交通查询插件样式 */
.ptq-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f8f9fa;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.ptq-header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e9ecef;
}
.ptq-header h2 {
color: #2c3e50;
margin: 0 0 10px 0;
font-size: 28px;
font-weight: 600;
}
.ptq-description {
color: #6c757d;
font-size: 16px;
margin: 0;
}
/* 表单样式 */
.ptq-form-container {
background: white;
padding: 25px;
border-radius: 10px;
margin-bottom: 25px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.ptq-form-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.ptq-form-group {
flex: 1;
min-width: 250px;
}
.ptq-form-group label {
display: block;
margin-bottom: 8px;
color: #495057;
font-weight: 500;
font-size: 14px;
}
.ptq-form-group input {
width: 100%;
padding: 12px 15px;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 16px;
transition: all 0.3s ease;
box-sizing: border-box;
}
.ptq-form-group input:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
.ptq-example {
font-size: 12px;
color: #6c757d;
margin-top: 5px;
font-style: italic;
}
.ptq-submit-group {
display: flex;
align-items: flex-end;
}
.ptq-submit-btn {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
border: none;
padding: 14px 30px;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 150px;
height: 48px;
}
.ptq-submit-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #2980b9, #1c6ea4);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
}
.ptq-submit-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.ptq-loading-spinner {
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: ptq-spin 1s ease-in-out infinite;
margin-left: 10px;
}
@keyframes ptq-spin {
to { transform: rotate(360deg); }
}
/* 结果区域样式 */
.ptq-results-container {
background: white;
padding: 25px;
border-radius: 10px;
margin-bottom: 25px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.ptq-results-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e9ecef;
}
.ptq-results-header h3 {
color: #2c3e50;
margin: 0 0 10px 0;
font-size: 22px;
}
.ptq-route-summary {
color: #6c757d;
font-size: 16px;
font-weight: 500;
}
/* 路线卡片样式 */
.ptq-route-card {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
transition: all 0.3s ease;
}
.ptq-route-card:hover {
border-color: #3498db;
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.1);
}
.ptq-route-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #f1f3f4;
}
.ptq-route-index {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
}
.ptq-route-stats {
display: flex;
gap: 20px;
}
.ptq-stat {
display: flex;
align-items: center;
color: #6c757d;
font-size: 14px;
}
.ptq-icon-time,
.ptq-icon-cost,
.ptq-icon-distance,
.ptq-icon-walk,
.ptq-icon-bus,
.ptq-icon-subway {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
background-size: contain;
background-repeat: no-repeat;
}


