文件
fenleixinxi/addons/address/view/index/tencent.html

671 行
27 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>地址选择器</title>
<link rel="stylesheet" href="__CDN__/assets/css/frontend.min.css"/>
<link rel="stylesheet" href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css"/>
<style type="text/css">
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
}
#container {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.search {
position: absolute;
width: 440px;
max-width: 92%;
top: 12px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.14);
padding: 4px;
}
.search .form-control {
border: none;
box-shadow: none;
height: 36px;
font-size: 13px;
}
.search .btn-success {
background: #00d26a;
border-color: #00d26a;
height: 36px;
border-radius: 6px !important;
padding: 0 14px;
}
.search .btn-success:hover {
background: #00a854;
border-color: #00a854;
}
.map-tip-banner {
position: absolute;
top: 64px;
left: 50%;
transform: translateX(-50%);
z-index: 90;
background: rgba(15, 23, 42, 0.75);
color: #fff;
font-size: 12px;
padding: 5px 14px;
border-radius: 20px;
pointer-events: none;
white-space: nowrap;
}
.selected-panel {
position: absolute;
left: 16px;
right: 16px;
bottom: 16px;
z-index: 99;
background: #ffffff;
border-radius: 10px;
box-shadow: 0 6px 24px rgba(15, 23, 42, 0.16);
padding: 12px 16px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
}
.selected-panel__info {
flex: 1;
min-width: 0;
}
.selected-panel__addr {
font-size: 14px;
font-weight: 600;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 3px;
}
.selected-panel__coord {
font-size: 12px;
color: #64748b;
}
.confirm {
flex-shrink: 0;
height: 38px;
padding: 0 22px;
line-height: 38px;
font-size: 14px;
font-weight: 600;
text-align: center;
background: linear-gradient(135deg, #00e070, #00a854);
color: #ffffff;
border: none;
cursor: pointer;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 210, 106, 0.28);
transition: filter 0.15s ease;
}
.confirm:hover {
filter: brightness(0.96);
}
.autocomplete-search {
text-align: left;
cursor: default;
background: #fff;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.16);
position: absolute;
display: none;
z-index: 1036;
max-height: 280px;
overflow: hidden;
overflow-y: auto;
box-sizing: border-box;
margin-top: 4px;
}
.autocomplete-search .autocomplete-suggestion {
padding: 8px 12px;
font-size: 13px;
color: #1e293b;
border-bottom: 1px solid #f1f5f9;
cursor: pointer;
}
.autocomplete-search .autocomplete-suggestion:last-child {
border-bottom: none;
}
.autocomplete-search .autocomplete-suggestion:hover,
.autocomplete-search .autocomplete-suggestion.selected {
background: #f2fef7;
color: #00a854;
}
.autocomplete-suggestion__sub {
display: block;
font-size: 11px;
color: #94a3b8;
margin-top: 2px;
}
</style>
</head>
<body>
<div class="search">
<div class="input-group">
<input type="text" id="place" name="q" class="form-control" autocomplete="off" placeholder="搜索全国城市、区县、街道或地标(回车搜索)"/>
<span class="input-group-btn">
<button type="button" name="search" id="search-btn" class="btn btn-success">
<i class="fa fa-search"></i> 搜索
</button>
</span>
</div>
</div>
<div class="map-tip-banner"><i class="fa fa-hand-pointer-o"></i> 直接点击地图任意位置或拖动红标即可选点</div>
<div class="selected-panel">
<div class="selected-panel__info">
<div class="selected-panel__addr" id="selected-addr">点击地图选择位置...</div>
<div class="selected-panel__coord" id="selected-coord">坐标:--</div>
</div>
<button type="button" class="confirm"><i class="fa fa-check"></i> 确定选点</button>
</div>
<div id="container"></div>
<script charset="utf-8" src="//map.qq.com/api/js?v=2.exp&libraries=place&key={$config.tencentkey|default=''}"></script>
<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
<script src="__CDN__/assets/addons/address/js/gcoord.min.js"></script>
<script src="__CDN__/assets/addons/address/js/jquery.autocomplete.js"></script>
<script type="text/javascript">
$(function () {
var map, marker, geocoder, infoWin, xhr;
var initialAddress = "{$address|htmlentities}";
var hasUrlCoord = location.search.indexOf('lat=') !== -1 && location.search.indexOf('lng=') !== -1;
var lng = Number("{$lng}");
var lat = Number("{$lat}");
var fromtype = "GCJ02";
var totype = "{$config.coordtype|default='DEFAULT'}";
totype = totype === 'DEFAULT' ? "GCJ02" : totype;
if (lng && lat && fromtype !== totype) {
var converted = gcoord.transform([lng, lat], gcoord[totype], gcoord[fromtype]);
lng = converted[0] || lng;
lat = converted[1] || lat;
}
// 当前选点状态(确保无论逆地址解析是否成功,随时可点击确定返回所选点位)
var selectedState = {
lat: lat,
lng: lng,
address: initialAddress || '',
province: '',
city: '',
district: '',
rawParts: []
};
/**
* 提取 LatLng 对象的数值坐标
* @param {Object} latLng 腾讯地图 LatLng 或普通 {lat, lng} 对象
* @return {{lat: number, lng: number}}
*/
var extractLatLng = function (latLng) {
var parsedLat = typeof latLng.getLat === 'function' ? latLng.getLat() : Number(latLng.lat);
var parsedLng = typeof latLng.getLng === 'function' ? latLng.getLng() : Number(latLng.lng);
return {
lat: parsedLat,
lng: parsedLng
};
};
/**
* 更新底部状态栏与地图信息窗体展示
* @param {{lat: number, lng: number}} pos 坐标
* @param {string} title 地址文本
*/
var updateSelectedDisplay = function (pos, title) {
var addrText = title || selectedState.address || ('已选坐标 (' + pos.lat.toFixed(5) + ', ' + pos.lng.toFixed(5) + ')');
$('#selected-addr').text(addrText);
$('#selected-coord').text('纬度:' + pos.lat.toFixed(6) + ' ,经度:' + pos.lng.toFixed(6));
showInfoWin(pos, addrText);
};
/**
* 解析 Nominatim 返回的中文地址层级
* @param {Object} ret Nominatim 响应对象
* @return {{address: string, province: string, city: string, district: string, rawParts: Array}}
*/
var parseNominatimResult = function (ret) {
var displayName = (ret && ret.display_name) ? String(ret.display_name) : '';
var rawParts = displayName.split(',').map(function (s) {
return $.trim(s);
}).filter(function (s) {
return s !== '' && s !== '中国' && !/^\d{5,6}$/.test(s);
}).reverse();
var addrObj = (ret && ret.address) ? ret.address : {};
var province = addrObj.state || rawParts[0] || '';
var city = '';
var district = '';
// 从反转后的层级中提取省、市、区县
for (var i = 0; i < rawParts.length; i++) {
var part = rawParts[i];
if (!province && /(省|自治区|特别行政区)$/.test(part)) {
province = part;
} else if (!city && /(市|自治州|地区|盟)$/.test(part) && part !== province) {
city = part;
} else if (!district && /(区|县|旗|市)$/.test(part) && part !== province && part !== city) {
district = part;
}
}
if (!city && /^(北京市|上海市|天津市|重庆市|北京|上海|天津|重庆)/.test(province)) {
city = province.indexOf('市') === -1 ? (province + '市') : province;
}
if (!district && addrObj.county) {
district = addrObj.county;
}
var fullAddress = rawParts.length ? rawParts.join('') : displayName;
return {
address: fullAddress,
province: province,
city: city,
district: district,
rawParts: rawParts
};
};
/**
* 选中地图上某个坐标点:立即移动图标并异步解析中文地址
* @param {Object} latLng 坐标点
* @param {string} [presetAddress] 预设地址名(如搜索结果标题)
* @param {boolean} [panCenter] 是否移动地图中心
*/
var selectPoint = function (latLng, presetAddress, panCenter) {
var pos = extractLatLng(latLng);
if (!isFinite(pos.lat) || !isFinite(pos.lng)) {
return;
}
selectedState.lat = pos.lat;
selectedState.lng = pos.lng;
if (presetAddress) {
selectedState.address = presetAddress;
}
// 1. 立即放置/移动红标并显示坐标,不等待接口返回
showMarker(pos, presetAddress || '已选位置');
updateSelectedDisplay(pos, presetAddress || '正在解析地点名称...');
if (panCenter && map) {
map.setCenter(new qq.maps.LatLng(pos.lat, pos.lng));
}
// 2. 异步逆地址解析
geocoder.getAddress(pos, presetAddress);
};
/**
* 通过关键词搜索地点并自动定位到第一个匹配结果
* @param {string} keyword 搜索关键词
*/
var searchAndLocate = function (keyword) {
var kw = $.trim(String(keyword || ''));
if (!kw) {
return;
}
var wgs2gcj = function (wgsLng, wgsLat) {
var res = gcoord.transform([Number(wgsLng), Number(wgsLat)], gcoord.WGS84, gcoord.GCJ02);
return {
lng: res[0] || Number(wgsLng),
lat: res[1] || Number(wgsLat)
};
};
$.ajax({
url: "https://nominatim.openstreetmap.org/search",
dataType: "jsonp",
jsonp: "json_callback",
data: {
format: "json",
q: kw,
addressdetails: 1,
limit: 5,
"accept-language": "zh-CN",
countrycodes: "cn"
},
success: function (list) {
if ($.isArray(list) && list.length > 0) {
var first = list[0];
var gcj = wgs2gcj(first.lon, first.lat);
var parsed = parseNominatimResult(first);
selectedState.province = parsed.province;
selectedState.city = parsed.city;
selectedState.district = parsed.district;
selectedState.rawParts = parsed.rawParts;
selectPoint(gcj, parsed.address || first.name || kw, true);
}
}
});
};
/**
* 初始化腾讯地图与交互事件
*/
var init = function () {
var center = new qq.maps.LatLng(lat, lng);
map = new qq.maps.Map(document.getElementById('container'), {
center: center,
zoom: parseInt("{$config.zoom}") || 12
});
infoWin = new qq.maps.InfoWindow({
map: map
});
geocoder = {
/**
* 逆地址解析:优先尝试腾讯接口,未授权时自动降级为 Nominatim
* @param {{lat: number, lng: number}} pos GCJ02坐标
* @param {string} [fallbackTitle] 预设标题
*/
getAddress: function (pos, fallbackTitle) {
var fallbackToNominatim = function () {
var wgs = gcoord.transform([pos.lng, pos.lat], gcoord.GCJ02, gcoord.WGS84);
$.ajax({
url: "https://nominatim.openstreetmap.org/reverse",
dataType: "jsonp",
jsonp: "json_callback",
data: {
format: "json",
lat: wgs[1],
lon: wgs[0],
zoom: 18,
addressdetails: 1,
"accept-language": "zh-CN"
},
success: function (ret) {
if (ret && ret.display_name) {
var parsed = parseNominatimResult(ret);
selectedState.address = parsed.address || fallbackTitle || '';
selectedState.province = parsed.province;
selectedState.city = parsed.city;
selectedState.district = parsed.district;
selectedState.rawParts = parsed.rawParts;
updateSelectedDisplay(pos, selectedState.address);
} else {
updateSelectedDisplay(pos, fallbackTitle || ('坐标 (' + pos.lat.toFixed(5) + ', ' + pos.lng.toFixed(5) + ')'));
}
},
error: function () {
updateSelectedDisplay(pos, fallbackTitle || ('坐标 (' + pos.lat.toFixed(5) + ', ' + pos.lng.toFixed(5) + ')'));
}
});
};
$.ajax({
url: "https://apis.map.qq.com/ws/geocoder/v1/?location=" + pos.lat + "," + pos.lng + "&key={$config.tencentkey|default=''}&output=jsonp",
dataType: "jsonp",
type: 'GET',
cache: true,
crossDomain: true,
success: function (ret) {
if (ret && ret.status === 0 && ret.result) {
var component = ret.result.address_component || {};
var resolvedAddr = '';
if (ret.result.formatted_addresses && ret.result.formatted_addresses.recommend) {
var recommend = ret.result.formatted_addresses.recommend;
resolvedAddr = (component.province !== component.city ? (component.province + component.city) : component.province)
+ (recommend.indexOf(component.district) === 0 ? '' : component.district) + recommend;
} else {
resolvedAddr = ret.result.address || '';
}
selectedState.address = resolvedAddr || fallbackTitle || '';
selectedState.province = component.province || '';
selectedState.city = component.city || '';
selectedState.district = component.district || '';
updateSelectedDisplay(pos, selectedState.address);
} else {
fallbackToNominatim();
}
},
error: function () {
fallbackToNominatim();
}
});
}
};
// 初始选点渲染
selectPoint({lat: lat, lng: lng}, initialAddress, false);
// 若打开时未传自定义坐标但带有区域地址,自动定位到该区域
if (!hasUrlCoord && initialAddress) {
$('#place').val(initialAddress);
searchAndLocate(initialAddress);
}
// 搜索框自动联想(腾讯 Suggestion + Nominatim 双通道自动降级)
var place = $("#place");
place.autoComplete({
minChars: 1,
cache: 0,
menuClass: 'autocomplete-search',
source: function (term, response) {
try {
xhr && xhr.abort();
} catch (e) {
}
var searchByNominatim = function () {
xhr = $.ajax({
url: "https://nominatim.openstreetmap.org/search",
dataType: "jsonp",
jsonp: "json_callback",
data: {
format: "json",
q: term,
addressdetails: 1,
limit: 8,
"accept-language": "zh-CN",
countrycodes: "cn"
},
success: function (list) {
if (!$.isArray(list) || !list.length) {
$(".autocomplete-suggestions.autocomplete-search").html('');
response([]);
return;
}
var mapped = [];
for (var i = 0; i < list.length; i++) {
var row = list[i];
var gcj = gcoord.transform([Number(row.lon), Number(row.lat)], gcoord.WGS84, gcoord.GCJ02);
var parsed = parseNominatimResult(row);
mapped.push({
title: row.name || parsed.district || parsed.city || term,
address: parsed.address,
province: parsed.province,
city: parsed.city,
district: parsed.district,
rawParts: parsed.rawParts,
location: {
lat: gcj[1] || Number(row.lat),
lng: gcj[0] || Number(row.lon)
}
});
}
response(mapped);
}
});
};
xhr = $.ajax({
url: "https://apis.map.qq.com/ws/place/v1/suggestion?keyword=" + encodeURIComponent(term) + "&key={$config.tencentkey|default=''}&output=jsonp",
dataType: "jsonp",
type: 'GET',
cache: true,
success: function (ret) {
if (ret && ret.status === 0 && $.isArray(ret.data)) {
if (ret.data.length === 0) {
$(".autocomplete-suggestions.autocomplete-search").html('');
}
response(ret.data);
} else {
searchByNominatim();
}
},
error: function () {
searchByNominatim();
}
});
},
renderItem: function (item, search) {
var safeSearch = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var regexp = new RegExp("(" + safeSearch.replace(/[\,|\u3000|\uff0c]/, ' ').split(' ').join('|') + ")", "gi");
var sub = item.address ? ("<span class='autocomplete-suggestion__sub'>" + item.address + "</span>") : "";
var jsonStr = JSON.stringify(item).replace(/'/g, "&#39;");
return "<div class='autocomplete-suggestion' data-item='" + jsonStr + "' data-title='" + item.title + "' data-val='" + item.title + "'>" + item.title.replace(regexp, "<b>$1</b>") + sub + "</div>";
},
onSelect: function (e, term, sel) {
e.preventDefault();
var item = $(sel).data("item");
if (item && item.location) {
if (item.province) selectedState.province = item.province;
if (item.city) selectedState.city = item.city;
if (item.district) selectedState.district = item.district;
if (item.rawParts) selectedState.rawParts = item.rawParts;
selectPoint(item.location, item.address || item.title, true);
}
}
});
// 点击地图任意地点立即选点
qq.maps.event.addListener(map, 'click', function (event) {
if (event && event.latLng) {
selectPoint(event.latLng, '', false);
}
});
};
/**
* 显示地图信息气泡
* @param {{lat: number, lng: number}} latLng 坐标
* @param {string} title 显示文本
*/
var showInfoWin = function (latLng, title) {
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
infoWin.open();
infoWin.setContent('<div style="padding:2px 4px;font-size:12px;color:#1e293b;max-width:260px;">' + (title || '当前选点') + '</div>');
infoWin.setPosition(position);
};
/**
* 放置可拖拽红标并监听拖拽结束选点
* @param {{lat: number, lng: number}} latLng 坐标
* @param {string} title 提示文字
*/
var showMarker = function (latLng, title) {
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
marker && marker.setMap(null);
marker = new qq.maps.Marker({
map: map,
position: position,
draggable: true,
title: title || '点击地图或拖动图标选择位置'
});
qq.maps.event.addListener(marker, 'dragend', function (event) {
if (event && event.latLng) {
selectPoint(event.latLng, '', false);
}
});
};
/**
* 关闭弹窗并向父窗口回传选点数据
* @param {Object} data 选点数据
*/
var close = function (data) {
var index = parent.Layer.getFrameIndex(window.name);
var callback = parent.$("#layui-layer" + index).data("callback");
parent.Layer.close(index);
if (typeof callback === 'function') {
callback.call(undefined, data);
}
};
// 点击「确定选点」回传坐标与解析出的省市区信息
$(document).on('click', '.confirm', function () {
var zoom = map ? map.getZoom() : 12;
var outLat = Number(selectedState.lat);
var outLng = Number(selectedState.lng);
if (fromtype !== totype) {
var result = gcoord.transform([outLng, outLat], gcoord[fromtype], gcoord[totype]);
outLng = Number(result[0] || outLng);
outLat = Number(result[1] || outLat);
}
var data = {
lat: outLat.toFixed(6),
lng: outLng.toFixed(6),
zoom: zoom,
address: selectedState.address || '',
province: selectedState.province || '',
city: selectedState.city || '',
district: selectedState.district || '',
rawParts: selectedState.rawParts || []
};
close(data);
});
// 点击搜索按钮或回车搜索
$(document).on('click', '#search-btn', function () {
var kw = $.trim($("#place").val());
if (!kw) {
return;
}
var first = $(".autocomplete-search > .autocomplete-suggestion:first");
if (first.length && first.is(':visible')) {
var item = first.data("item");
if (item && item.location) {
if (item.province) selectedState.province = item.province;
if (item.city) selectedState.city = item.city;
if (item.district) selectedState.district = item.district;
if (item.rawParts) selectedState.rawParts = item.rawParts;
selectPoint(item.location, item.address || item.title, true);
$(".autocomplete-search").hide();
return;
}
}
searchAndLocate(kw);
});
$('#place').on('keydown', function (e) {
if (e.keyCode === 13) {
e.preventDefault();
$('#search-btn').trigger('click');
}
});
init();
});
</script>
</body>
</html>