文章目录[隐藏]
实战教程:在WordPress网站中添加基于位置的附近门店与服务查询功能
引言:位置服务在互联网应用中的重要性
在移动互联网时代,基于位置的服务(LBS)已成为各类网站和应用程序的标配功能。无论是电商平台、服务型企业还是内容网站,为用户提供附近门店查询、地理位置搜索等功能,都能显著提升用户体验和商业转化率。根据谷歌的研究,超过80%的消费者在寻找本地服务时会使用“附近”搜索功能,而其中76%的用户会在一天内访问相关门店。
WordPress作为全球最流行的内容管理系统,占据了互联网上超过43%的网站份额。然而,许多WordPress网站所有者并未充分利用位置服务的潜力。本教程将详细介绍如何通过代码二次开发,在WordPress网站中实现专业的附近门店与服务查询功能,无需依赖昂贵的第三方插件,同时保持对功能的完全控制。
第一部分:前期准备与环境配置
1.1 功能需求分析与设计
在开始编码之前,我们需要明确功能需求。一个完整的附近门店查询系统应包含以下核心功能:
- 门店信息管理(名称、地址、坐标、联系方式、营业时间等)
- 用户位置获取(自动检测或手动输入)
- 距离计算与排序算法
- 地图可视化展示
- 筛选与搜索功能
- 响应式设计,支持移动设备
1.2 开发环境搭建
确保你的WordPress开发环境满足以下要求:
- WordPress 5.0以上版本
- PHP 7.4以上(推荐8.0+)
- MySQL 5.6以上或MariaDB 10.1以上
- 启用Apache/Nginx的rewrite模块
- 代码编辑器(VS Code、PHPStorm等)
1.3 获取地图API密钥
我们将使用Google Maps API作为地图服务,你也可以选择百度地图、高德地图等国内服务。以Google Maps为例:
- 访问Google Cloud Console(console.cloud.google.com)
- 创建新项目或选择现有项目
- 启用"Maps JavaScript API"、"Places API"和"Geocoding API"
- 创建API密钥并设置限制(推荐限制HTTP引用来源)
第二部分:数据库设计与门店管理
2.1 创建自定义数据表
虽然可以使用WordPress的自定义文章类型(CPT)存储门店信息,但为了更好的性能和查询效率,我们创建独立的数据表:
// 在插件激活时创建数据表
function create_store_locations_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'store_locations';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
store_name varchar(255) NOT NULL,
address text NOT NULL,
city varchar(100),
state varchar(100),
country varchar(100),
postal_code varchar(20),
latitude decimal(10,8) NOT NULL,
longitude decimal(11,8) NOT NULL,
phone varchar(30),
email varchar(100),
website varchar(255),
description text,
opening_hours text,
categories varchar(255),
featured tinyint(1) DEFAULT 0,
active tinyint(1) DEFAULT 1,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY latitude_longitude (latitude, longitude),
KEY city_state (city, state),
KEY categories (categories(191))
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
register_activation_hook(__FILE__, 'create_store_locations_table');
2.2 创建后台管理界面
为方便管理门店信息,我们需要在WordPress后台创建管理界面:
// 添加管理菜单
add_action('admin_menu', 'register_store_locations_menu');
function register_store_locations_menu() {
add_menu_page(
'门店管理',
'门店位置',
'manage_options',
'store-locations',
'store_locations_admin_page',
'dashicons-location-alt',
30
);
add_submenu_page(
'store-locations',
'添加新门店',
'添加新门店',
'manage_options',
'add-new-store',
'add_new_store_page'
);
add_submenu_page(
'store-locations',
'门店分类',
'分类管理',
'manage_options',
'store-categories',
'store_categories_page'
);
}
// 主管理页面
function store_locations_admin_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'store_locations';
// 处理批量操作
if (isset($_POST['action']) && $_POST['action'] == 'bulk_delete') {
// 验证nonce和权限
check_admin_referer('bulk_action_nonce');
if (!current_user_can('manage_options')) {
wp_die('权限不足');
}
if (isset($_POST['store_ids'])) {
$ids = array_map('intval', $_POST['store_ids']);
$ids_placeholder = implode(',', array_fill(0, count($ids), '%d'));
$wpdb->query($wpdb->prepare(
"DELETE FROM $table_name WHERE id IN ($ids_placeholder)",
$ids
));
echo '<div class="notice notice-success"><p>已删除选中的门店</p></div>';
}
}
// 分页查询
$per_page = 20;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
$stores = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table_name ORDER BY created_at DESC LIMIT %d OFFSET %d",
$per_page, $offset
));
// 显示管理界面
?>
<div class="wrap">
<h1 class="wp-heading-inline">门店管理</h1>
<a href="<?php echo admin_url('admin.php?page=add-new-store'); ?>" class="page-title-action">添加新门店</a>
<form method="post" action="">
<?php wp_nonce_field('bulk_action_nonce'); ?>
<input type="hidden" name="action" value="bulk_delete">
<div class="tablenav top">
<div class="alignleft actions bulkactions">
<select name="bulk_action">
<option value="-1">批量操作</option>
<option value="delete">删除</option>
</select>
<input type="submit" class="button action" value="应用">
</div>
<div class="tablenav-pages">
<?php
$total_pages = ceil($total_items / $per_page);
echo paginate_links(array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '«',
'next_text' => '»',
'total' => $total_pages,
'current' => $current_page
));
?>
</div>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<td class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all-1">
</td>
<th>ID</th>
<th>门店名称</th>
<th>地址</th>
<th>城市</th>
<th>电话</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($stores as $store): ?>
<tr>
<th scope="row" class="check-column">
<input type="checkbox" name="store_ids[]" value="<?php echo $store->id; ?>">
</th>
<td><?php echo $store->id; ?></td>
<td>
<strong><?php echo esc_html($store->store_name); ?></strong>
<?php if ($store->featured): ?>
<span class="dashicons dashicons-star-filled" style="color:#ffb900;"></span>
<?php endif; ?>
</td>
<td><?php echo esc_html($store->address); ?></td>
<td><?php echo esc_html($store->city); ?></td>
<td><?php echo esc_html($store->phone); ?></td>
<td>
<?php if ($store->active): ?>
<span class="status-active">启用</span>
<?php else: ?>
<span class="status-inactive">禁用</span>
<?php endif; ?>
</td>
<td>
<a href="<?php echo admin_url('admin.php?page=add-new-store&id=' . $store->id); ?>">编辑</a> |
<a href="#" class="delete-store" data-id="<?php echo $store->id; ?>">删除</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</form>
</div>
<script>
jQuery(document).ready(function($) {
$('.delete-store').on('click', function(e) {
e.preventDefault();
if (confirm('确定要删除这个门店吗?')) {
var storeId = $(this).data('id');
$.post(ajaxurl, {
action: 'delete_store',
store_id: storeId,
nonce: '<?php echo wp_create_nonce("delete_store_nonce"); ?>'
}, function(response) {
if (response.success) {
location.reload();
} else {
alert('删除失败:' + response.data);
}
});
}
});
});
</script>
<?php
}
第三部分:核心功能开发
3.1 地理位置编码与坐标获取
将地址转换为地理坐标(经纬度)是位置服务的基础:
class LocationGeocoder {
private $api_key;
private $cache_time = 604800; // 缓存7天
public function __construct($api_key) {
$this->api_key = $api_key;
}
public function geocode_address($address) {
// 检查缓存
$cache_key = 'geocode_' . md5($address);
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
// 调用Google Geocoding API
$url = 'https://maps.googleapis.com/maps/api/geocode/json';
$params = array(
'address' => urlencode($address),
'key' => $this->api_key,
'language' => 'zh-CN'
);
$response = wp_remote_get($url . '?' . http_build_query($params));
if (is_wp_error($response)) {
return array(
'success' => false,
'error' => $response->get_error_message()
);
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if ($data['status'] !== 'OK') {
return array(
'success' => false,
'error' => '地理编码失败:' . $data['status']
);
}
$result = $data['results'][0];
$location = array(
'success' => true,
'latitude' => $result['geometry']['location']['lat'],
'longitude' => $result['geometry']['location']['lng'],
'formatted_address' => $result['formatted_address'],
'address_components' => $this->parse_address_components($result['address_components'])
);
// 缓存结果
set_transient($cache_key, $location, $this->cache_time);
return $location;
}
private function parse_address_components($components) {
$parsed = array();
foreach ($components as $component) {
foreach ($component['types'] as $type) {
$parsed[$type] = $component['long_name'];
}
}
return $parsed;
}
public function calculate_distance($lat1, $lon1, $lat2, $lon2, $unit = 'km') {
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) +
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
switch ($unit) {
case 'km':
return $miles * 1.609344;
case 'm':
return $miles * 1.609344 * 1000;
default:
return $miles;
}
}
}
3.2 附近门店查询算法
实现高效的附近门店搜索算法:
class NearbyStoresFinder {
private $db;
private $earth_radius = 6371; // 地球半径,单位公里
public function __construct() {
global $wpdb;
$this->db = $wpdb;
}
public function find_nearby_stores($latitude, $longitude, $radius_km = 10, $limit = 20, $category = null) {
$table_name = $this->db->prefix . 'store_locations';
// 使用Haversine公式计算距离
$haversine = "(
$this->earth_radius * acos(
cos(radians(%f)) *
cos(radians(latitude)) *
cos(radians(longitude) - radians(%f)) +
sin(radians(%f)) *
sin(radians(latitude))
)
)";
$query = "SELECT *,
$haversine AS distance
FROM $table_name
WHERE active = 1";
$params = array($latitude, $longitude, $latitude);
// 添加分类筛选
if ($category) {
$query .= " AND FIND_IN_SET(%s, categories)";
$params[] = $category;
}
$query .= " HAVING distance <= %d
ORDER BY distance ASC
LIMIT %d";
$params[] = $radius_km;
$params[] = $limit;
$prepared_query = $this->db->prepare($query, $params);
return $this->db->get_results($prepared_query);
}
public function find_stores_by_bounds($north, $south, $east, $west, $category = null) {
$table_name = $this->db->prefix . 'store_locations';
$query = "SELECT * FROM $table_name
WHERE active = 1
AND latitude BETWEEN %f AND %f
AND longitude BETWEEN %f AND %f";
$params = array($south, $north, $west, $east);
if ($category) {
$query .= " AND FIND_IN_SET(%s, categories)";
$params[] = $category;
}
$query .= " ORDER BY featured DESC, store_name ASC
LIMIT 100";
$prepared_query = $this->db->prepare($query, $params);
return $this->db->get_results($prepared_query);
}
}
第四部分:前端界面与用户体验
4.1 创建搜索表单与结果展示
class StoreLocatorFrontend {
private $api_key;
public function __construct($api_key) {
$this->api_key = $api_key;
add_shortcode('store_locator', array($this, 'render_store_locator'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('wp_ajax_search_nearby_stores', array($this, 'ajax_search_nearby_stores'));
add_action('wp_ajax_nopriv_search_nearby_stores', array($this, 'ajax_search_nearby_stores'));
}
public function enqueue_scripts() {
global $post;
if (has_shortcode($post->post_content, 'store_locator')) {
// 加载Google Maps API
wp_enqueue_script(
'google-maps-api',
'https://maps.googleapis.com/maps/api/js?key=' . $this->api_key . '&libraries=places&language=zh-CN',
array(),
null,
true
);
// 加载自定义脚本
wp_enqueue_script(
'store-locator-frontend',
plugin_dir_url(__FILE__) . 'js/store-locator-frontend.js',
array('jquery', 'google-maps-api'),
'1.0.0',
true
);
// 加载样式
wp_enqueue_style(
'store-locator-style',
plugin_dir_url(__FILE__) . 'css/store-locator.css',
array(),
'1.0.0'
);
// 传递数据到前端
wp_localize_script('store-locator-frontend', 'storeLocatorData', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('store_locator_nonce'),
'default_lat' => 39.9042, // 默认坐标(北京)
'default_lng' => 116.4074,
'default_radius' => 10,
4.2 构建响应式前端界面
public function render_store_locator($atts) {
$atts = shortcode_atts(array(
'default_radius' => 10,
'max_results' => 50,
'show_categories' => true,
'show_filters' => true
), $atts);
// 获取所有门店分类
$categories = $this->get_store_categories();
ob_start();
?>
<div class="store-locator-container">
<div class="store-locator-header">
<h2>附近门店查询</h2>
<p>输入地址或使用当前位置查找附近的门店</p>
</div>
<div class="store-locator-main">
<!-- 搜索面板 -->
<div class="search-panel">
<div class="search-form">
<div class="form-group">
<label for="store-search-input">搜索地址或位置:</label>
<div class="search-input-wrapper">
<input type="text"
id="store-search-input"
class="search-input"
placeholder="输入地址、城市或邮政编码">
<button type="button" id="use-current-location" class="location-btn">
<span class="dashicons dashicons-location"></span>
使用当前位置
</button>
</div>
</div>
<?php if ($atts['show_categories'] && !empty($categories)): ?>
<div class="form-group">
<label>服务分类:</label>
<div class="categories-filter">
<select id="store-category" class="category-select">
<option value="">所有分类</option>
<?php foreach ($categories as $category): ?>
<option value="<?php echo esc_attr($category); ?>">
<?php echo esc_html($category); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<?php endif; ?>
<div class="form-group">
<label>搜索半径:</label>
<div class="radius-slider">
<input type="range"
id="search-radius"
min="1"
max="50"
value="<?php echo esc_attr($atts['default_radius']); ?>"
class="radius-range">
<span id="radius-value"><?php echo esc_attr($atts['default_radius']); ?> 公里</span>
</div>
</div>
<div class="form-actions">
<button type="button" id="search-stores" class="search-btn">
搜索附近门店
</button>
<button type="button" id="reset-search" class="reset-btn">
重置搜索
</button>
</div>
</div>
<!-- 搜索结果列表 -->
<div class="results-panel">
<div class="results-header">
<h3>搜索结果</h3>
<div class="results-count">
找到 <span id="results-count">0</span> 个门店
</div>
</div>
<div class="results-list" id="stores-results">
<div class="no-results">
<p>请输入位置开始搜索</p>
</div>
</div>
<div class="results-pagination" id="results-pagination" style="display: none;">
<button id="prev-page" class="pagination-btn" disabled>上一页</button>
<span id="page-info">第 1 页</span>
<button id="next-page" class="pagination-btn" disabled>下一页</button>
</div>
</div>
</div>
<!-- 地图显示区域 -->
<div class="map-panel">
<div id="store-locator-map"></div>
<div class="map-controls">
<button id="zoom-in" class="map-control-btn">+</button>
<button id="zoom-out" class="map-control-btn">-</button>
<button id="reset-map" class="map-control-btn">重置视图</button>
</div>
</div>
</div>
<!-- 门店详情模态框 -->
<div id="store-details-modal" class="store-modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="modal-store-name"></h3>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
<div class="store-info-grid">
<div class="info-item">
<span class="dashicons dashicons-location"></span>
<div>
<strong>地址:</strong>
<span id="modal-store-address"></span>
</div>
</div>
<div class="info-item">
<span class="dashicons dashicons-phone"></span>
<div>
<strong>电话:</strong>
<span id="modal-store-phone"></span>
</div>
</div>
<div class="info-item">
<span class="dashicons dashicons-clock"></span>
<div>
<strong>营业时间:</strong>
<div id="modal-store-hours"></div>
</div>
</div>
<div class="info-item">
<span class="dashicons dashicons-admin-site"></span>
<div>
<strong>距离:</strong>
<span id="modal-store-distance"></span>
</div>
</div>
</div>
<div class="store-description">
<h4>门店介绍</h4>
<p id="modal-store-description"></p>
</div>
<div class="store-actions">
<a href="#" id="modal-get-directions" class="action-btn" target="_blank">
<span class="dashicons dashicons-car"></span>
获取路线
</a>
<a href="#" id="modal-call-store" class="action-btn">
<span class="dashicons dashicons-phone"></span>
拨打电话
</a>
<a href="#" id="modal-visit-website" class="action-btn" target="_blank">
<span class="dashicons dashicons-external"></span>
访问网站
</a>
</div>
</div>
</div>
</div>
</div>
<!-- 加载状态 -->
<div id="loading-overlay" style="display: none;">
<div class="loading-spinner"></div>
<p>正在搜索附近门店...</p>
</div>
<?php
return ob_get_clean();
}
private function get_store_categories() {
global $wpdb;
$table_name = $wpdb->prefix . 'store_locations';
$categories = $wpdb->get_col(
"SELECT DISTINCT categories FROM $table_name WHERE active = 1 AND categories IS NOT NULL"
);
$all_categories = array();
foreach ($categories as $category_string) {
$cat_array = explode(',', $category_string);
foreach ($cat_array as $category) {
$category = trim($category);
if ($category && !in_array($category, $all_categories)) {
$all_categories[] = $category;
}
}
}
sort($all_categories);
return $all_categories;
}
4.3 前端JavaScript交互逻辑
// store-locator-frontend.js
(function($) {
'use strict';
class StoreLocator {
constructor() {
this.map = null;
this.markers = [];
this.infoWindow = null;
this.currentLocation = null;
this.currentRadius = parseInt(storeLocatorData.default_radius);
this.currentCategory = '';
this.currentPage = 1;
this.storesPerPage = 10;
this.allStores = [];
this.init();
}
init() {
this.initMap();
this.bindEvents();
this.initAutocomplete();
}
initMap() {
const defaultLocation = {
lat: parseFloat(storeLocatorData.default_lat),
lng: parseFloat(storeLocatorData.default_lng)
};
this.map = new google.maps.Map(document.getElementById('store-locator-map'), {
zoom: 12,
center: defaultLocation,
mapTypeControl: true,
streetViewControl: false,
fullscreenControl: true,
styles: this.getMapStyles()
});
this.infoWindow = new google.maps.InfoWindow({
maxWidth: 300
});
// 添加初始标记
this.addInitialMarker(defaultLocation);
}
getMapStyles() {
return [
{
featureType: 'poi.business',
stylers: [{ visibility: 'on' }]
},
{
featureType: 'transit',
elementType: 'labels.icon',
stylers: [{ visibility: 'off' }]
}
];
}
addInitialMarker(position) {
const marker = new google.maps.Marker({
position: position,
map: this.map,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
fillColor: '#4285F4',
fillOpacity: 1,
strokeColor: '#FFFFFF',
strokeWeight: 2
},
title: '中心位置'
});
this.markers.push(marker);
}
bindEvents() {
// 搜索按钮
$('#search-stores').on('click', () => this.searchStores());
// 使用当前位置
$('#use-current-location').on('click', () => this.getCurrentLocation());
// 半径滑块
$('#search-radius').on('input', (e) => {
const radius = parseInt(e.target.value);
$('#radius-value').text(radius + ' 公里');
this.currentRadius = radius;
});
// 分类选择
$('#store-category').on('change', (e) => {
this.currentCategory = e.target.value;
});
// 重置搜索
$('#reset-search').on('click', () => this.resetSearch());
// 分页按钮
$('#prev-page').on('click', () => this.changePage(-1));
$('#next-page').on('click', () => this.changePage(1));
// 地图控制
$('#zoom-in').on('click', () => this.map.setZoom(this.map.getZoom() + 1));
$('#zoom-out').on('click', () => this.map.setZoom(this.map.getZoom() - 1));
$('#reset-map').on('click', () => this.resetMapView());
// 模态框关闭
$('.modal-close, #store-details-modal').on('click', (e) => {
if (e.target === e.currentTarget) {
this.closeModal();
}
});
// 键盘事件
$(document).on('keyup', (e) => {
if (e.key === 'Escape') this.closeModal();
});
}
initAutocomplete() {
const input = document.getElementById('store-search-input');
const autocomplete = new google.maps.places.Autocomplete(input, {
types: ['geocode', 'establishment'],
componentRestrictions: { country: 'cn' }
});
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
if (!place.geometry) {
alert('未找到该位置,请尝试更详细的地址');
return;
}
this.currentLocation = {
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng()
};
this.updateMapCenter(this.currentLocation);
this.searchStores();
});
}
getCurrentLocation() {
if (!navigator.geolocation) {
alert('您的浏览器不支持地理位置功能');
return;
}
$('#use-current-location').prop('disabled', true).text('定位中...');
navigator.geolocation.getCurrentPosition(
(position) => {
this.currentLocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
// 反向地理编码获取地址
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: this.currentLocation }, (results, status) => {
if (status === 'OK' && results[0]) {
$('#store-search-input').val(results[0].formatted_address);
}
});
this.updateMapCenter(this.currentLocation);
this.searchStores();
$('#use-current-location').prop('disabled', false).html(
'<span class="dashicons dashicons-location"></span> 使用当前位置'
);
},
(error) => {
let errorMessage = '无法获取您的位置:';
switch(error.code) {
case error.PERMISSION_DENIED:
errorMessage += '用户拒绝了位置请求';
break;
case error.POSITION_UNAVAILABLE:
errorMessage += '位置信息不可用';
break;
case error.TIMEOUT:
errorMessage += '获取位置超时';
break;
default:
errorMessage += '未知错误';
}
alert(errorMessage);
$('#use-current-location').prop('disabled', false).html(
'<span class="dashicons dashicons-location"></span> 使用当前位置'
);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
}
updateMapCenter(location) {
this.map.setCenter(location);
// 更新中心标记
if (this.markers.length > 0) {
this.markers[0].setPosition(location);
}
}
async searchStores() {
if (!this.currentLocation) {
alert('请先选择或输入一个位置');
return;
}
this.showLoading(true);
try {
const response = await $.ajax({
url: storeLocatorData.ajax_url,
method: 'POST',
data: {
action: 'search_nearby_stores',
nonce: storeLocatorData.nonce,
latitude: this.currentLocation.lat,
longitude: this.currentLocation.lng,
radius: this.currentRadius,
category: this.currentCategory
}
});
if (response.success) {
this.allStores = response.data.stores;
this.currentPage = 1;
this.displayResults();
this.displayMarkers();
this.updatePagination();
} else {
throw new Error(response.data);
}
} catch (error) {
console.error('搜索失败:', error);
alert('搜索失败,请稍后重试');
} finally {
this.showLoading(false);
}
}
displayResults() {
const $resultsContainer = $('#stores-results');
const startIndex = (this.currentPage - 1) * this.storesPerPage;
const endIndex = startIndex + this.storesPerPage;
const currentStores = this.allStores.slice(startIndex, endIndex);
if (currentStores.length === 0) {
$resultsContainer.html(`
<div class="no-results">
<p>在指定范围内未找到门店</p>
<p>尝试扩大搜索半径或选择其他位置</p>
</div>
`);
return;
}
let html = '';
currentStores.forEach((store, index) => {
html += this.getStoreCardHtml(store, startIndex + index + 1);
});
$resultsContainer.html(html);
$('#results-count').text(this.allStores.length);
// 绑定卡片点击事件
$('.store-card').on('click', (e) => {
const storeId = $(e.currentTarget).data('store-id');
const store = this.allStores.find(s => s.id == storeId);
if (store) {
this.showStoreDetails(store);
}
});
}
getStoreCardHtml(store, index) {
const distance = parseFloat(store.distance).toFixed(1);
const featuredClass = store.featured ? 'featured' : '';
return `
<div class="store-card ${featuredClass}" data-store-id="${store.id}">
<div class="store-card-header">
<span class="store-index">${index}</span>
<h4 class="store-name">${this.escapeHtml(store.store_name)}</h4>
${store.featured ? '<span class="featured-badge">推荐</span>' : ''}
</div>
<div class="store-card-body">
<div class="store-info">
<p class="store-address">
<span class="dashicons dashicons-location"></span>
${this.escapeHtml(store.address)}
</p>
${store.phone ? `
<p class="store-phone">
<span class="dashicons dashicons-phone"></span>
${this.escapeHtml(store.phone)}
</p>
` : ''}
<p class="store-distance">
<span class="dashicons dashicons-admin-site"></span>
距离:${distance} 公里
</p>
</div>
<div class="store-actions">
<button class="btn-details" data-store-id="${store.id}">
查看详情
</button>
<button class="btn-directions" data-store-id="${store.id}">
路线规划
</button>
</div>
</div>
</div>
`;
}
displayMarkers() {
// 清除现有标记
this.clearMarkers();
// 添加新标记
this.allStores.forEach(store => {
const marker = new google.maps.Marker({
position: {
lat: parseFloat(store.latitude),
lng: parseFloat(store.longitude)
},
map: this.map,
title: store.store_name,
icon: this.getMarkerIcon(store.featured)
});
