添加根据经纬度获取城市

添加后台统计看板
This commit is contained in:
2025-12-26 16:26:15 +08:00
parent 028886c1fe
commit 6963216a85
9 changed files with 1668 additions and 8 deletions

View File

@@ -0,0 +1,645 @@
{layout name="layout1" /}
<style>
.map-container {
padding: 15px;
min-height: calc(100vh - 60px);
}
.map-header {
background: #fff;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
display: flex;
align-items: center;
justify-content: space-between;
}
.map-header-left {
display: flex;
align-items: center;
gap: 15px;
}
.map-header-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.map-header-stats {
display: flex;
gap: 20px;
align-items: center;
}
.stat-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 15px;
background: #f8f9ff;
border-radius: 6px;
}
.stat-label {
font-size: 13px;
color: #666;
}
.stat-value {
font-size: 16px;
font-weight: bold;
color: #4A70F4;
}
.map-search-box {
display: flex;
gap: 10px;
align-items: center;
}
.map-search-input {
width: 300px;
}
#maplocation {
width: 100%;
height: calc(100vh - 200px);
min-height: 600px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
background: #f5f5f5;
}
/* 头像标记点样式 */
.tmap-marker-avatar {
border-radius: 50%;
border: 2px solid #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
object-fit: cover;
}
.info-window {
padding: 10px;
min-width: 150px;
}
.info-window-title {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
.info-window-item {
font-size: 12px;
color: #666;
margin: 5px 0;
line-height: 1.6;
}
.info-window-item strong {
color: #333;
margin-right: 5px;
}
.map-controls {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000;
background: #fff;
padding: 10px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.map-control-btn {
display: block;
width: 100%;
margin-bottom: 8px;
padding: 8px 15px;
text-align: center;
background: #4A70F4;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: all 0.3s;
}
.map-control-btn:hover {
background: #3a5fd4;
}
.map-control-btn:last-child {
margin-bottom: 0;
}
.map-legend {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 1000;
background: rgba(255,255,255,0.95);
padding: 12px 15px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
font-size: 12px;
color: #666;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
margin: 5px 0;
}
.legend-color {
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
</style>
<div class="map-container">
<!-- 地图头部 -->
<div class="map-header">
<div class="map-header-left">
<div class="map-header-title">用户位置分布图</div>
<div class="map-header-stats">
<div class="stat-item">
<span class="stat-label">总用户数:</span>
<span class="stat-value" id="total-users">0</span>
</div>
<div class="stat-item">
<span class="stat-label">已定位:</span>
<span class="stat-value" id="located-users">0</span>
</div>
</div>
</div>
<div class="map-search-box">
<input type="text" id="search_key" class="layui-input map-search-input" placeholder="请输入地名搜索">
<!-- <button class="layui-btn layui-btn-normal searchKey">搜索</button>-->
<button class="layui-btn layui-btn-primary" id="reset-map">重置视图</button>
</div>
</div>
<!-- 地图容器 -->
<div style="position: relative;">
<div id="maplocation"></div>
<!-- 地图控制按钮 -->
<div class="map-controls">
<button class="map-control-btn" id="zoom-in">放大</button>
<button class="map-control-btn" id="zoom-out">缩小</button>
<button class="map-control-btn" id="fit-bounds">适应所有标记</button>
<button class="map-control-btn" id="clear-markers">清除标记</button>
</div>
<!-- 图例 -->
<div class="map-legend">
<div style="font-weight: bold; margin-bottom: 8px; color: #333;">图例</div>
<div class="legend-item">
<div class="legend-color" style="background: #4A70F4;"></div>
<span>用户位置</span>
</div>
</div>
</div>
</div>
<script src="/static/webjs/jquery.min.js"></script>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service,tools&key=EVOBZ-VX7YU-QKJVR-BVESA-AVFT3-7JBWG" onload="console.log('腾讯地图API脚本加载完成')" onerror="console.error('腾讯地图API脚本加载失败')"></script>
<script>
layui.config({
version:"{$front_version}",
base: '/static/plug/layui-admin/dist/layuiadmin/'
}).extend({
index: 'lib/index'
}).use(['index'], function(){
var $ = layui.$;
// 用户数据(将从后端获取)
var users = [];
var markers = null;
var map = null;
var geocoder = null;
var infoWindows = [];
var defaultCenter = null; // 默认中心点(贵阳)
var defaultZoom = 12;
var markerClickHandler = null; // 保存点击事件处理函数
// 初始化地图
function initMap() {
try {
// 检查TMap是否已加载
if (typeof TMap === 'undefined') {
console.error('腾讯地图API未加载');
layer.msg('地图加载失败,请刷新页面重试', {icon: 2, time: 3000});
return;
}
// 初始化默认中心点
if (!defaultCenter) {
defaultCenter = new TMap.LatLng(26.647, 106.63); // 默认中心点(贵阳)
}
console.log('开始初始化地图...');
map = new TMap.Map('maplocation', {
zoom: defaultZoom,
center: defaultCenter
});
console.log('地图对象创建成功');
// 初始化标记点(样式将在渲染时动态创建)
markers = new TMap.MultiMarker({
map: map,
styles: {},
geometries: []
});
console.log('标记点对象创建成功');
// 初始化地理编码服务
geocoder = new TMap.service.Geocoder();
// 等待地图加载完成
map.on('complete', function() {
console.log('地图加载完成');
// 加载用户数据
setTimeout(function() {
if (typeof loadUsers === 'function') {
loadUsers();
} else {
console.error('loadUsers函数未定义');
}
}, 500);
});
// 备用方案如果complete事件没有触发延迟后直接初始化
setTimeout(function() {
console.log('备用方案延迟调用loadUsers()');
if (typeof loadUsers === 'function') {
loadUsers();
} else {
console.error('loadUsers函数未定义');
}
}, 3000);
// 监听地图错误
map.on('error', function(e) {
console.error('地图错误:', e);
layer.msg('地图加载出错,请刷新页面重试', {icon: 2, time: 3000});
});
} catch (error) {
console.error('地图初始化失败:', error);
layer.msg('地图初始化失败:' + error.message, {icon: 2, time: 3000});
}
}
// 加载用户数据
function loadUsers() {
if (typeof $ === 'undefined') {
console.error('jQuery未加载');
return;
}
$.ajax({
url: '{:url("BulletinBoard/getUserMap")}',
type: 'GET',
dataType: 'json',
success: function(res) {
console.log('用户数据响应:', res);
if (res.code === 1 && res.data) {
users = res.data;
console.log('获取到用户数据:', users.length, '条');
updateStats();
renderMarkers();
} else {
console.warn('获取用户数据失败:', res.msg);
layer.msg(res.msg || '获取用户数据失败', {icon: 2});
users = [];
updateStats();
}
},
error: function(xhr, status, error) {
console.error('请求失败:', xhr, status, error);
console.error('响应内容:', xhr.responseText);
layer.msg('请求失败,请稍后重试', {icon: 2});
users = [];
updateStats();
}
});
}
// 将loadUsers暴露到全局作用域方便调试
window.loadUsers = loadUsers;
// 更新统计信息
function updateStats() {
var totalUsers = users.length;
var locatedUsers = users.filter(function(u) {
return u.lat && u.lng && u.lat != 0 && u.lng != 0;
}).length;
$('#total-users').text(totalUsers);
$('#located-users').text(locatedUsers);
}
// 渲染标记点
function renderMarkers() {
if (!markers || !map) {
console.warn('标记点或地图未初始化');
return;
}
// 清除现有标记和信息窗口
clearMarkers();
var geometries = [];
var validUsers = [];
var markerStyles = {};
// 过滤有效坐标的用户
users.forEach(function(user, index) {
if (!user.lat || !user.lng || user.lat == 0 || user.lng == 0) {
return;
}
var lat = parseFloat(user.lat);
var lng = parseFloat(user.lng);
if (isNaN(lat) || isNaN(lng)) {
console.warn('无效的经纬度:', user);
return;
}
validUsers.push(user);
// 为每个用户创建使用头像的标记点样式
var styleId = 'avatar_' + (user.id || index);
// 如果没有头像,使用默认标记图标
var avatarUrl = user.avatar && user.avatar.trim() !== ''
? user.avatar
: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png';
// 创建头像标记样式(圆形头像,带边框)
markerStyles[styleId] = new TMap.MarkerStyle({
width: 40,
height: 40,
anchor: {x: 20, y: 20},
src: avatarUrl
});
// 创建标记点
geometries.push({
id: 'user_' + (user.id || index),
position: new TMap.LatLng(lat, lng),
styleId: styleId,
properties: {
title: user.contact || user.name || '用户' + (user.id || index),
user: user
}
});
});
console.log('有效用户数:', validUsers.length, '标记点数:', geometries.length);
// 更新标记样式和几何图形
if (geometries.length > 0) {
// 先更新样式
markers.setStyles(markerStyles);
// 再更新几何图形
markers.updateGeometries(geometries);
// 创建信息窗口(但不自动显示)
createInfoWindows(validUsers);
// 如果只有一个用户,居中显示
if (validUsers.length === 1) {
map.setCenter(new TMap.LatLng(validUsers[0].lat, validUsers[0].lng));
map.setZoom(15);
} else if (validUsers.length > 1) {
// 适应所有标记
fitBounds(validUsers);
}
} else {
console.warn('没有有效的用户位置数据');
}
}
// 创建信息窗口(但不立即添加到地图,只在点击时创建)
function createInfoWindows(users) {
// 先清除之前的事件监听器(如果存在)
if (markers && markerClickHandler) {
try {
markers.off('click', markerClickHandler);
} catch (e) {
console.warn('移除事件监听器失败:', e);
}
markerClickHandler = null;
}
// 点击标记显示信息窗口
markerClickHandler = function(evt) {
var geometry = evt.geometry;
if (geometry && geometry.properties) {
// 关闭所有信息窗口
infoWindows.forEach(function(item) {
if (item.window) {
try {
item.window.close();
if (item.window.destroy) {
item.window.destroy();
}
} catch (e) {
console.warn('关闭信息窗口失败:', e);
}
}
});
infoWindows = [];
// 从 properties 中直接获取用户数据
var user = geometry.properties.user;
// 如果没有,尝试从全局 users 数组查找
if (!user) {
var userId = geometry.id.replace('user_', '');
user = users.find(function(u) {
return (u.id || '').toString() === userId || users.indexOf(u).toString() === userId;
});
}
if (user) {
var lat = parseFloat(user.lat);
var lng = parseFloat(user.lng);
if (isNaN(lat) || isNaN(lng)) {
console.error('无效的经纬度:', user);
return;
}
var content = '<div class="info-window">' +
'<div class="info-window-title">' + (user.contact || user.name || '用户') + '</div>' +
'<div class="info-window-item"><strong>电话:</strong>' + (user.telephone || user.phone || '-') + '</div>' +
'<div class="info-window-item"><strong>地址:</strong>' + (user.address || '-') + '</div>';
content += '</div>';
// 只在点击时创建信息窗口
try {
var infoWindow = new TMap.InfoWindow({
map: map,
position: new TMap.LatLng(lat, lng),
content: content,
visible: true,
zIndex: 100
});
infoWindows.push({
window: infoWindow,
markerId: geometry.id
});
} catch (e) {
console.error('创建信息窗口失败:', e);
layer.msg('显示信息失败', {icon: 2});
}
} else {
console.warn('未找到用户数据:', geometry.id);
}
} else {
console.warn('标记点数据不完整:', geometry);
}
};
// 绑定事件监听器
if (markers && markerClickHandler) {
markers.on('click', markerClickHandler);
}
}
// 适应所有标记
function fitBounds(users) {
if (users.length === 0) return;
var bounds = new TMap.LatLngBounds();
users.forEach(function(user) {
bounds.extend(new TMap.LatLng(parseFloat(user.lat), parseFloat(user.lng)));
});
map.fitBounds(bounds);
}
// 清除标记
function clearMarkers() {
if (markers) {
markers.updateGeometries([]);
}
infoWindows.forEach(function(item) {
if (item.window) {
try {
item.window.close();
if (item.window.destroy) {
item.window.destroy();
} else if (item.window.setMap) {
item.window.setMap(null);
}
} catch (e) {
console.warn('清除信息窗口失败:', e);
}
}
});
infoWindows = [];
}
// 搜索功能
var searchService = {
search: function(name) {
if (!name || !name.trim()) {
layer.msg('请输入搜索关键词', {icon: 0});
return;
}
geocoder.getLocation({ address: name })
.then(function(result) {
if (result && result.result && result.result.location) {
var location = result.result.location;
map.setCenter(location);
map.setZoom(15);
// 添加临时标记
markers.updateGeometries([{
id: 'search_result',
position: location,
styleId: 'marker'
}]);
} else {
layer.msg('未找到该地点', {icon: 0});
}
})
.catch(function(error) {
layer.msg('搜索失败:' + (error.message || '未知错误'), {icon: 2});
});
}
};
// 事件绑定
$(document).on('click', '.searchKey', function() {
var searchKey = $("#search_key").val();
searchService.search(searchKey);
});
$('#search_key').on('keypress', function(e) {
if (e.which === 13) {
$('.searchKey').trigger('click');
}
});
$('#reset-map').on('click', function() {
map.setCenter(defaultCenter);
map.setZoom(defaultZoom);
renderMarkers();
});
$('#zoom-in').on('click', function() {
map.setZoom(map.getZoom() + 1);
});
$('#zoom-out').on('click', function() {
map.setZoom(map.getZoom() - 1);
});
$('#fit-bounds').on('click', function() {
var validUsers = users.filter(function(u) {
return u.lat && u.lng && u.lat != 0 && u.lng != 0;
});
if (validUsers.length > 0) {
fitBounds(validUsers);
} else {
layer.msg('没有可显示的用户位置', {icon: 0});
}
});
$('#clear-markers').on('click', function() {
clearMarkers();
layer.msg('已清除所有标记', {icon: 1});
});
// 等待腾讯地图API加载完成后再初始化
var retryCount = 0;
var maxRetries = 100; // 10秒
function waitForTMap() {
if (typeof TMap !== 'undefined' && typeof TMap.Map !== 'undefined') {
// 延迟一下确保API完全加载
setTimeout(function() {
initMap();
}, 300);
} else {
// 如果还没加载等待一段时间后重试最多等待10秒
if (retryCount < maxRetries) {
retryCount++;
setTimeout(waitForTMap, 100);
} else {
console.error('腾讯地图API加载超时');
layer.msg('地图API加载超时请刷新页面重试', {icon: 2, time: 3000});
}
}
}
// 初始化
$(function() {
// 等待地图API加载
waitForTMap();
});
});
</script>