Files
gitfjn 6963216a85 添加根据经纬度获取城市
添加后台统计看板
2025-12-26 16:26:15 +08:00

646 lines
20 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{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>