perf: 添加定位签到

This commit is contained in:
kuaifan 2024-11-08 21:46:07 +08:00
parent 2fc329a403
commit 3b9c9872ca
19 changed files with 1997 additions and 763 deletions

View File

@ -1362,6 +1362,63 @@ class DialogController extends AbstractController
], $botUser->userid, true);
}
/**
* @api {post} api/dialog/msg/sendlocation 24. 发送位置消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendlocation
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} type 位置类型
* - bd: 百度地图
* @apiParam {Number} lng 经度
* @apiParam {Number} lat 纬度
* @apiParam {String} title 位置名称
* @apiParam {String} [address] 位置地址
* @apiParam {String} [preview] 预览图片url
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__sendlocation()
{
$user = User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
$type = strtolower(trim(Request::input('type')));
$lng = floatval(Request::input('lng'));
$lat = floatval(Request::input('lat'));
$title = trim(Request::input('title'));
$address = trim(Request::input('address'));
$preview = trim(Request::input('preview'));
//
if (empty($lng) || $lng < -180 || $lng > 180
|| empty($lat) || $lat < -90 || $lat > 90) {
return Base::retError('经纬度错误');
}
if (empty($title)) {
return Base::retError('位置名称不能为空');
}
//
WebSocketDialog::checkDialog($dialog_id);
//
if ($type == 'bd') {
$msgData = [
'type' => $type,
'lng' => $lng,
'lat' => $lat,
'title' => $title,
'address' => $address,
'preview' => $preview,
];
return WebSocketDialogMsg::sendMsg(null, $dialog_id, 'location', $msgData, $user->userid);
}
return Base::retError('位置类型错误');
}
/**
* @api {get} api/dialog/msg/readlist 27. 获取消息阅读情况
*

View File

@ -407,6 +407,9 @@ class SystemController extends AbstractController
'face_upload',
'face_remark',
'face_retip',
'locat_remark',
'locat_bd_lbs_key',
'locat_bd_lbs_point', // 格式:{"lng":116.404, "lat":39.915, "radius":500}
'manual_remark',
'modes',
'key',
@ -422,9 +425,21 @@ class SystemController extends AbstractController
if (!$botUser) {
return Base::retError('创建签到机器人失败');
}
if (in_array('locat', $all['modes'])) {
if (empty($all['locat_bd_lbs_key'])) {
return Base::retError('请填写百度地图LBS Key');
}
if (!is_array($all['locat_bd_lbs_point'])) {
return Base::retError('请选择允许签到位置');
}
$all['locat_bd_lbs_point']['radius'] = intval($all['locat_bd_lbs_point']['radius']);
if (empty($all['locat_bd_lbs_point']['lng']) || empty($all['locat_bd_lbs_point']['lat']) || empty($all['locat_bd_lbs_point']['radius'])) {
return Base::retError('请选择有效的签到位置');
}
}
}
if ($all['modes']) {
$all['modes'] = array_intersect($all['modes'], ['auto', 'manual', 'location', 'face']);
$all['modes'] = array_intersect($all['modes'], ['auto', 'manual', 'locat', 'face']);
}
$setting = Base::setting('checkinSetting', Base::newTrim($all));
} else {
@ -444,6 +459,8 @@ class SystemController extends AbstractController
$setting['face_upload'] = $setting['face_upload'] ?: 'close';
$setting['face_remark'] = $setting['face_remark'] ?: Doo::translate('考勤机');
$setting['face_retip'] = $setting['face_retip'] ?: 'open';
$setting['locat_remark'] = $setting['locat_remark'] ?: Doo::translate('定位签到');
$setting['locat_bd_lbs_point'] = is_array($setting['locat_bd_lbs_point']) ? $setting['locat_bd_lbs_point'] : ['radius' => 500];
$setting['manual_remark'] = $setting['manual_remark'] ?: Doo::translate('手动签到');
$setting['time'] = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
$setting['advance'] = intval($setting['advance']) ?: 120;

View File

@ -408,7 +408,7 @@ class IndexController extends InvokeController
], 'inline');
}
// EEUI App 直接在线预览查看
if (str_contains($userAgent, 'eeui') && Base::judgeClientVersion("0.34.47")) {
if (Base::isEEUIApp() && Base::judgeClientVersion("0.34.47")) {
if ($browser === 'safari-mobile') {
$redirectUrl = Base::fillUrl($path);
return <<<EOF

View File

@ -91,12 +91,10 @@ class UserBot extends AbstractModel
*/
public static function quickMsgs($email)
{
return match ($email) {
'check-in@bot.system' => [
[
'key' => 'checkin',
'label' => Doo::translate('我要打卡')
], /*[
switch ($email) {
case 'check-in@bot.system':
$menu = [
/*[
'key' => 'it',
'label' => Doo::translate('IT资讯')
], [
@ -112,8 +110,33 @@ class UserBot extends AbstractModel
'key' => 'soup',
'label' => Doo::translate('心灵鸡汤')
]*/
],
'anon-msg@bot.system' => [
];
$setting = Base::setting('checkinSetting');
if ($setting['open'] !== 'open') {
return $menu;
}
if (in_array('locat', $setting['modes']) && Base::isEEUIApp()) {
$menu[] = [
'key' => 'locat-checkin',
'label' => $setting['locat_remark'] ?: Doo::translate('定位签到'),
'config' => [
'key' => $setting['locat_bd_lbs_key'],
'lng' => $setting['locat_bd_lbs_point']['lng'],
'lat' => $setting['locat_bd_lbs_point']['lat'],
'radius' => $setting['locat_bd_lbs_point']['radius'],
]
];
}
if (in_array('manual', $setting['modes'])) {
$menu[] = [
'key' => 'manual-checkin',
'label' => $setting['manual_remark'] ?: Doo::translate('手动打卡')
];
}
return $menu;
case 'anon-msg@bot.system':
return [
[
'key' => 'help',
'label' => Doo::translate('使用说明')
@ -121,8 +144,10 @@ class UserBot extends AbstractModel
'key' => 'privacy',
'label' => Doo::translate('隐私说明')
],
],
'bot-manager@bot.system' => [
];
case 'bot-manager@bot.system':
return [
[
'key' => '/help',
'label' => Doo::translate('帮助指令')
@ -133,21 +158,19 @@ class UserBot extends AbstractModel
'key' => '/list',
'label' => Doo::translate('我的机器人')
],
],
'ai-openai@bot.system',
'ai-claude@bot.system',
'ai-wenxin@bot.system',
'ai-gemini@bot.system',
'ai-zhipu@bot.system',
'ai-qianwen@bot.system' => [
];
default:
if (preg_match('/^ai-(.*?)@bot.system$/', $email)) {
return [
[
'key' => '%3A.clear',
'label' => Doo::translate('清空上下文')
]
],
default => [],
};
];
}
return [];
}
}
/**
@ -163,7 +186,7 @@ class UserBot extends AbstractModel
}
Cache::put("UserBot::checkinBotQuickMsg:{$userid}", "yes", Carbon::now()->addSecond());
//
if ($command === 'checkin') {
if ($command === 'manual-checkin') {
$setting = Base::setting('checkinSetting');
if ($setting['open'] !== 'open') {
return '暂未开启签到功能。';
@ -182,7 +205,9 @@ class UserBot extends AbstractModel
/**
* 签到机器人签到
* @param $mac
* @param mixed $mac
* - 多个使用,分隔
* - 支持mac地址、userid、checkin-userid
* @param $time
* @param bool $alreadyTip 签到过是否提示
* @return string|null 返回string表示错误信息返回null表示签到成功

View File

@ -957,6 +957,7 @@ class WebSocketDialogMsg extends AbstractModel
/**
* 发送消息、修改消息
* @param string $action 动作
* - null:发送消息
* - reply-98回复消息ID=98
* - update-99更新消息ID=99(标记修改)
* - change-99更新消息ID=99(不标记修改)
@ -994,6 +995,19 @@ class WebSocketDialogMsg extends AbstractModel
if (in_array($msg['ext'], ['jpg', 'jpeg', 'webp', 'png', 'gif'])) {
$mtype = 'image';
}
} elseif ($type === 'location') {
if (preg_match('/^https*:\/\//', $msg['preview'])) {
$preview = file_get_contents($msg['preview']);
if (empty($preview)) {
throw new ApiException('获取地图快照失败');
}
$filePath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/" . md5s($msg['preview']) . ".jpg";
Base::makeDir(dirname(public_path($filePath)));
if (!Base::saveContentImage(public_path($filePath), $preview, 90)) {
throw new ApiException('保存地图快照失败');
}
$msg['preview'] = $filePath;
}
}
if ($push_silence === null) {
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);

View File

@ -2039,6 +2039,16 @@ class Base
return $platform;
}
/**
* 是否是App移动端
* @return bool
*/
public static function isEEUIApp()
{
$userAgent = strtolower(Request::server('HTTP_USER_AGENT'));
return str_contains($userAgent, 'kuaifan_eeui');
}
/**
* 返回根据距离sql排序语句
* @param $lat

View File

@ -384,7 +384,7 @@ LDAP 用户禁止修改邮箱
匿名消息
系统管理员
我要签到
我要打卡
手动打卡
关键词不能为空
LICENSE 格式错误
LICENSE 保存失败
@ -714,3 +714,5 @@ webhook地址最长仅支持255个字符。
动画表情
每日开心:(*)
心灵鸡汤:(*)
定位签到

View File

@ -22967,18 +22967,6 @@
"id": "Saya ingin check-in",
"ru": "Я хочу зарегистрироваться"
},
{
"key": "我要打卡",
"zh": "",
"zh-CHT": "我要打卡",
"en": "I want to clock in",
"ko": "출근하고 싶습니다",
"ja": "出勤したい",
"de": "Ich möchte einstempeln",
"fr": "Je veux pointer",
"id": "Saya ingin absen",
"ru": "Я хочу отметиться"
},
{
"key": "关键词不能为空",
"zh": "",

View File

@ -17,7 +17,14 @@
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
</div>
</div>
<div class="map-box">
<div id="map-container"></div>
<div id="map-location">
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 0a64 64 0 0 1 62.208 48.896 467.456 467.456 0 0 1 400.896 400.938667 64 64 0 0 1 0 124.373333 467.456 467.456 0 0 1-400.938667 400.896 64 64 0 0 1-124.373333 0 467.456 467.456 0 0 1-400.896-400.938667 64 64 0 0 1 0-124.373333 467.456 467.456 0 0 1 400.938667-400.896A63.914667 63.914667 0 0 1 512 0z m64 178.816V196.266667a64 64 0 0 1-127.701333 6.144L448 196.266667v-17.450667a339.541333 339.541333 0 0 0-269.184 269.226667L196.266667 448a64 64 0 0 1 6.144 127.701333L196.266667 576h-17.450667a339.584 339.584 0 0 0 269.226667 269.184L448 827.733333a64 64 0 0 1 127.701333-6.144l0.298667 6.144v17.450667A339.584 339.584 0 0 0 845.226667 576L827.733333 576a64 64 0 0 1-6.144-127.701333l6.144-0.298667h17.450667A339.584 339.584 0 0 0 576 178.816zM512.512 448a64 64 0 0 1 6.186667 127.701333L512 576a64 64 0 0 1-6.144-127.701333l6.613333-0.298667z" fill="#717171"></path>
</svg>
</div>
</div>
<div id="address-list" class="address-list">
<h3 id="address-label"></h3>
<ul id="poi-list"></ul>

View File

@ -1,356 +1,42 @@
class LocationPicker {
class App {
static #eeui = null;
static #geolocation = null;
constructor() {
this.map = null;
this.marker = null;
this.geolocation = null;
this.localSearch = null;
this.currentPoint = null;
this.loadNum = 0;
this.config = {
theme: 'light', // 主题风格light|dark
key: null, // 百度地图 API Key
title: null, // 页面标题,如:选择打卡地点
label: null, // 搜索列表标签,如:附近的地点
placeholder: null, // 搜索框占位符,如:搜索附近的地点
point: null, // 初始坐标116.404,39.915
noresult: null, // 无搜索结果提示,如:附近没有找到地点
radius: 300, // 搜索半径单位300
zoom: 16, // 地图缩放级别
errtip: null, // 定位失败提示
errclose: false, // 定位失败是否关闭页面
channel: null, // 回传数据通道
selectclose: false, // 选择地点是否关闭页面
};
this.init();
this.constructor.init();
}
async init() {
// 先初始化参数
this.initParams();
// 如果没有 key直接返回
if (!this.config.key) {
console.error('未提供百度地图 API Key');
return;
static async init() {
while (typeof requireModuleJs !== "function") {
await new Promise(resolve => setTimeout(resolve, 500));
}
this.#eeui = requireModuleJs("eeui");
this.#geolocation = requireModuleJs("eeui/geolocation");
}
try {
// 等待地图 JS 加载完成
await this.loadBaiduMapScript();
// 初始化地图
this.initMap();
} catch (error) {
console.error('加载百度地图失败:', error);
static async setVariate(key, value) {
while (!this.#eeui) {
await new Promise(resolve => setTimeout(resolve, 500));
}
await this.#eeui.setVariate(key, value);
}
initParams() {
// 获取当前URL的查询参数
const urlParams = new URLSearchParams(window.location.search);
// 遍历 config 对象的所有属性
Object.keys(this.config).forEach(key => {
// 从 URL 参数中获取值
const value = urlParams.get(key);
if (value !== null) {
// 根据参数类型进行转换
switch (key) {
case 'radius':
// 转换为数字
this.config[key] = parseInt(value) || 300;
break;
case 'point':
// 转换为坐标数组
const [lng, lat] = value.replace(/[|-]/, ',').split(',').map(parseFloat);
if (lng && lat) {
this.config[key] = {lng, lat};
}
break;
default:
// 字符串类型直接赋值
this.config[key] = value;
}
static async getLocation() {
while (!this.#geolocation) {
await new Promise(resolve => setTimeout(resolve, 500));
}
return new Promise(resolve => {
this.#geolocation.get((res) => {
resolve(res);
});
// 设置主题风格
document.documentElement.classList.add(`theme-${this.config.theme}`);
document.body.style.backgroundColor = "#ffffff";
// 设置标题
if (this.config.title) {
document.title = this.config.title;
}
// 设置搜索框占位符
if (this.config.placeholder) {
document.getElementById('search-input').placeholder = this.config.placeholder;
}
// 设置label
if (this.config.label) {
document.getElementById('address-label').innerText = this.config.label;
}
}
initMap() {
// 初始化地图
this.map = new BMap.Map('map-container');
// 创建定位控件
const locationControl = new BMap.GeolocationControl({
anchor: BMAP_ANCHOR_BOTTOM_RIGHT,
showAddressBar: false,
enableAutoLocation: false,
locationIcon: new BMap.Icon("empty.svg", new BMap.Size(0, 0))
});
// 监听定位事件
locationControl.addEventListener("locationSuccess", (e) => {
// 定位成功事件
this.updateCurrentPoint(e.point);
});
locationControl.addEventListener("locationError", (e) => {
// 定位失败事件
console.error('定位失败:', e.message);
this.locationError();
});
// 添加定位控件到地图
this.map.addControl(locationControl);
// 初始化本地搜索,移除地图渲染
this.localSearch = new BMap.LocalSearch(this.map, {
renderOptions: {
autoViewport: false // 关闭自动视野调整
}
});
// 设置地图中心点
if (this.config.point) {
const {lng, lat} = this.config.point;
this.config.point = new BMap.Point(lng, lat);
// 设置地图中心点和缩放级别
this.map.centerAndZoom(this.config.point, this.config.zoom);
// 创建圆形区域
const circle = new BMap.Circle(this.config.point, this.config.radius, {
fillColor: "#333333",
fillOpacity: 0.1,
strokeColor: "#333333",
strokeWeight: 1,
strokeOpacity: 0.3
});
this.map.addOverlay(circle);
}
// 绑定事件
this.bindEvents();
// 初始化时自动定位
this.getCurrentLocation();
}
bindEvents() {
const searchInput = document.getElementById('search-input');
// 监听回车键
searchInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
searchInput.blur();
}
});
// 监听失去焦点
searchInput.addEventListener('blur', () => {
this.searchAddress();
});
}
getCurrentLocation() {
this.loaderShow();
this.geolocation = new BMap.Geolocation();
this.geolocation.getCurrentPosition((result) => {
this.loaderHide();
if (result && result.point) {
this.updateCurrentPoint(result.point)
} else {
console.error('定位失败');
this.locationError();
}
}, {enableHighAccuracy: true});
}
updateCurrentPoint(point) {
this.currentPoint = point;
this.map.centerAndZoom(this.currentPoint, this.config.zoom);
this.updateMarker(this.currentPoint);
this.searchNearby();
}
updateMarker(point) {
if (this.marker) {
this.marker.setPosition(point);
} else {
this.marker = new BMap.Marker(point);
this.map.addOverlay(this.marker);
}
}
searchAddress() {
const keyword = document.getElementById('search-input').value;
this.searchNearby(keyword ? [keyword] : []);
}
searchNearby(keywords = [], retryCount = 0) {
// 当前位置未获取
if (this.currentPoint === null) {
return;
}
// 清除之前的搜索结果
this.localSearch.clearResults();
// 搜索附近的关键词
if (keywords.length === 0) {
keywords = ["写字楼", "公司", "银行", "餐馆", "商场", "超市", "学校", "医院", "公交站", "地铁站"]
}
// 定义一个随机数,用于判断定时器是否过期
this.searchRandom = Math.random();
// 设置搜索完成回调
this.loaderShow();
this.localSearch.setSearchCompleteCallback((results) => {
this.loaderHide();
if (this.localSearch.getStatus() !== BMAP_STATUS_SUCCESS) {
// 搜索失败
if (retryCount < 60) {
const tmpRand = this.searchRandom;
this.loaderShow();
setTimeout(() => {
this.loaderHide();
tmpRand === this.searchRandom && this.searchNearby(keywords, ++retryCount);
}, 1000)
return;
}
}
// 搜索结果
document.getElementById('address-list').style.display = 'block';
const array = [];
if (results instanceof Array) {
results.some(result => {
if (!result) {
return false;
}
for (let i = 0; i < result.getCurrentNumPois(); i++) {
const poi = result.getPoi(i);
poi._distance = this.config.point ? this.map.getDistance(this.config.point, poi.point) : null;
array.push(poi);
}
});
}
this.updatePoiList(array);
});
// 执行搜索
this.localSearch.searchNearby(keywords, this.currentPoint, this.config.radius);
}
updatePoiList(results) {
const poiList = document.getElementById('poi-list');
poiList.innerHTML = '';
// 如果没有搜索结果
if (results.length === 0 && this.config.noresult) {
poiList.innerHTML = '<li>' + this.config.noresult + '</li>';
return;
}
// 按距离排序(如果有距离信息)
results.sort((a, b) => {
if (a._distance && b._distance) {
return a._distance - b._distance;
}
return 0;
});
results = results.slice(0, 20);
// 创建列表项
results.forEach(poi => {
const li = document.createElement('li');
const distance = poi._distance ? `<div class="address-distance">${this.convertDistance(Math.round(poi._distance))}</div>` : '';
li.innerHTML = `
<div class="address-name">${poi.title}</div>
<div class="address-detail">${poi.address || ""}${distance}</div>
`;
li.addEventListener('click', () => {
const point = poi.point;
this.updateMarker(point);
this.map.setCenter(point);
//
if (typeof requireModuleJs === "function") {
const eeui = requireModuleJs("eeui");
eeui.setVariate("location::" + this.config.channel, JSON.stringify(poi))
}
if (this.config.selectclose) {
this.closePage();
}
});
poiList.appendChild(li);
});
// 列表更新后,重新将当前标记点居中显示
setTimeout(() => {
if (this.marker) {
this.map.setCenter(this.marker.getPosition());
}
}, 100); // 添加小延时确保DOM已更新
}
convertDistance(d) {
if (d > 1000) {
return (d / 1000).toFixed(1) + 'km';
}
return d.toFixed(0) + 'm';
}
locationError() {
if (this.config.errtip) {
alert(this.config.errtip);
}
if (this.config.errclose) {
this.closePage();
}
}
loaderShow() {
this.loadNum++;
this.loaderJudge();
}
loaderHide() {
setTimeout(() => {
this.loadNum--;
this.loaderJudge();
}, 100)
}
loaderJudge() {
if (this.loadNum > 0) {
document.querySelector('.loading').classList.add('show');
} else if (this.loadNum <= 0) {
document.querySelector('.loading').classList.remove('show');
}
}
closePage() {
static closePage() {
try {
// 方法1: 如果是在 eeui 环境中
if (typeof requireModuleJs === "function") {
const eeui = requireModuleJs("eeui");
eeui.closePage();
if (this.#eeui) {
this.#eeui.closePage();
}
// 方法2: 如果是从其他页面打开的,可以关闭当前窗口
@ -373,7 +59,495 @@ class LocationPicker {
}
}
loadBaiduMapScript() {
static isArray(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number";
}
static isJson(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined";
}
}
class CoordTransform {
// 私有静态常量
static #x_PI = 3.14159265358979324 * 3000.0 / 180.0;
static #PI = 3.1415926535897932384626;
static #a = 6378245.0;
static #ee = 0.00669342162296594323;
/**
* WGS84 BD09
* @param {number} lng WGS84 经度
* @param {number} lat WGS84 纬度
* @returns {[number, number]} BD09 坐标 [经度, 纬度]
*/
static wgs84toBd09(lng, lat) {
const gcj = CoordTransform.wgs84ToGcj02(lng, lat);
return CoordTransform.gcj02ToBd09(gcj[0], gcj[1]);
}
/**
* WGS84 GCJ02
* @private
*/
static wgs84ToGcj02(lng, lat) {
if (CoordTransform.outOfChina(lng, lat)) {
return [lng, lat];
}
let dlat = CoordTransform.transformLat(lng - 105.0, lat - 35.0);
let dlng = CoordTransform.transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * CoordTransform.#PI;
let magic = Math.sin(radLat);
magic = 1 - CoordTransform.#ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((CoordTransform.#a * (1 - CoordTransform.#ee)) / (magic * sqrtMagic) * CoordTransform.#PI);
dlng = (dlng * 180.0) / (CoordTransform.#a / sqrtMagic * Math.cos(radLat) * CoordTransform.#PI);
const mgLat = lat + dlat;
const mgLng = lng + dlng;
return [mgLng, mgLat];
}
/**
* GCJ02 BD09
* @private
*/
static gcj02ToBd09(lng, lat) {
const z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * CoordTransform.#x_PI);
const theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * CoordTransform.#x_PI);
const bdLng = z * Math.cos(theta) + 0.0065;
const bdLat = z * Math.sin(theta) + 0.006;
return [bdLng, bdLat];
}
/**
* 判断坐标是否在中国境内
* @private
*/
static outOfChina(lng, lat) {
return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271);
}
/**
* 转换纬度
* @private
*/
static transformLat(lng, lat) {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat +
0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * CoordTransform.#PI) + 20.0 *
Math.sin(2.0 * lng * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * CoordTransform.#PI) + 40.0 *
Math.sin(lat / 3.0 * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * CoordTransform.#PI) + 320 *
Math.sin(lat * CoordTransform.#PI / 30.0)) * 2.0 / 3.0;
return ret;
}
/**
* 转换经度
* @private
*/
static transformLng(lng, lat) {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng +
0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * CoordTransform.#PI) + 20.0 *
Math.sin(2.0 * lng * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * CoordTransform.#PI) + 40.0 *
Math.sin(lng / 3.0 * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * CoordTransform.#PI) + 300.0 *
Math.sin(lng / 30.0 * CoordTransform.#PI)) * 2.0 / 3.0;
return ret;
}
}
class Loader {
static #num = 0;
static show() {
this.#num++;
this.judge();
}
static hide() {
setTimeout(() => {
this.#num--;
this.judge();
}, 100)
}
static judge() {
if (this.#num > 0) {
document.querySelector('.loading').classList.add('show');
} else if (this.#num <= 0) {
document.querySelector('.loading').classList.remove('show');
}
}
}
class BaiduMapPicker {
constructor() {
this.map = null;
this.marker = null;
this.localSearch = null;
this.currentPoint = null;
this.params = {
theme: 'light', // 主题风格light|dark
key: null, // 百度地图 API Key
title: null, // 页面标题,如:选择打卡地点
label: null, // 搜索列表标签,如:附近的地点
placeholder: null, // 搜索框占位符,如:搜索附近的地点
point: null, // 初始坐标116.404,39.915
noresult: null, // 无搜索结果提示,如:附近没有找到地点
radius: 300, // 搜索半径单位300
zoom: 16, // 地图缩放级别
errtip: null, // 定位失败提示
errclose: false, // 定位失败是否关闭页面
channel: null, // 回传数据通道
selectclose: false, // 选择地点是否关闭页面
};
this.init();
}
async init() {
// 先初始化参数
this.initParams();
// 如果没有 key直接返回
if (!this.params.key) {
console.error('未提供百度地图 API Key');
return;
}
try {
// 等待地图 JS 加载完成
await this.loadMapScript();
// 初始化地图
this.initMap();
} catch (error) {
console.error('加载百度地图失败:', error);
}
}
/**
* 初始化参数
*/
initParams() {
// 获取当前URL的查询参数
const urlParams = new URLSearchParams(window.location.search);
// 遍历 params 对象的所有属性
Object.keys(this.params).forEach(key => {
// 从 URL 参数中获取值
const value = urlParams.get(key);
if (value !== null) {
// 根据参数类型进行转换
switch (key) {
case 'radius':
// 转换为数字
this.params[key] = parseInt(value) || 300;
break;
case 'point':
// 转换为坐标数组
const [lng, lat] = value.replace(/[|-]/, ',').split(',').map(parseFloat);
if (lng && lat) {
this.params[key] = {lng, lat};
}
break;
default:
// 字符串类型直接赋值
this.params[key] = value;
}
}
});
// 设置主题风格
if (!['dark', 'light'].includes(this.params.theme)) {
this.params.theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.classList.add(`theme-${this.params.theme}`);
document.body.style.backgroundColor = "#ffffff";
// 设置标题
if (this.params.title) {
document.title = this.params.title;
}
// 设置搜索框占位符
if (this.params.placeholder) {
document.getElementById('search-input').placeholder = this.params.placeholder;
}
// 设置label
if (this.params.label) {
document.getElementById('address-label').innerText = this.params.label;
}
}
/**
* 初始化地图
*/
initMap() {
// 初始化地图
this.map = new BMap.Map('map-container');
// 初始化本地搜索,移除地图渲染
this.localSearch = new BMap.LocalSearch(this.map, {
renderOptions: {
autoViewport: false // 关闭自动视野调整
}
});
// 设置地图中心点
if (this.params.point) {
const {lng, lat} = this.params.point;
this.params.point = new BMap.Point(lng, lat);
// 设置地图中心点和缩放级别
this.map.centerAndZoom(this.params.point, this.params.zoom);
// 创建圆形区域
const circle = new BMap.Circle(this.params.point, this.params.radius, {
fillColor: "#333333",
fillOpacity: 0.1,
strokeColor: "#333333",
strokeWeight: 1,
strokeOpacity: 0.3
});
this.map.addOverlay(circle);
}
// 绑定事件
this.bindEvents();
// 初始化时自动定位
Loader.show();
this.getCurrentLocation().then((point) => {
Loader.hide();
if (point === null) {
this.locationError();
}
document.getElementById('map-location').style.display = 'block';
});
}
/**
* 绑定事件
*/
bindEvents() {
// 输入框事件
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
searchInput.blur();
}
});
searchInput.addEventListener('blur', () => {
this.searchAddress();
});
// 地图定位点击事件
const mapLocation = document.getElementById('map-location');
mapLocation.addEventListener('click', () => {
Loader.show()
this.getCurrentLocation().then(() => {
Loader.hide()
});
});
}
/**
* 获取当前位置
* @returns {Promise<unknown>}
*/
getCurrentLocation() {
return new Promise(resolve => {
App.getLocation().then(res => {
if (App.isJson(res) && res.longitude && res.latitude) {
const bd09_coord = CoordTransform.wgs84toBd09(res.longitude, res.latitude);
const point = new BMap.Point(bd09_coord[0], bd09_coord[1]);
this.updateCurrentPoint(point)
resolve(point);
} else {
console.error('定位失败');
resolve(null);
}
})
})
}
/**
* 更新当前位置
* @param point
*/
updateCurrentPoint(point) {
this.currentPoint = point;
this.map.centerAndZoom(this.currentPoint, this.params.zoom);
this.updateMarker(this.currentPoint);
this.searchNearby();
}
/**
* 更新标记点
* @param point
*/
updateMarker(point) {
if (this.marker) {
this.marker.setPosition(point);
} else {
this.marker = new BMap.Marker(point);
this.map.addOverlay(this.marker);
}
}
/**
* 搜索地址
*/
searchAddress() {
const keyword = document.getElementById('search-input').value;
this.searchNearby(keyword ? [keyword] : []);
}
/**
* 搜索附近的地点
* @param keywords
* @param retryCount
*/
searchNearby(keywords = [], retryCount = 0) {
// 当前位置未获取
if (this.currentPoint === null) {
return;
}
// 清除之前的搜索结果
this.localSearch.clearResults();
// 搜索附近的关键词
if (keywords.length === 0) {
keywords = ["写字楼", "公司", "银行", "餐馆", "商场", "超市", "学校", "医院", "公交站", "地铁站"]
}
// 定义一个随机数,用于判断定时器是否过期
this.searchRandom = Math.random();
// 设置搜索完成回调
Loader.show();
this.localSearch.setSearchCompleteCallback((results) => {
Loader.hide();
if (this.localSearch.getStatus() !== BMAP_STATUS_SUCCESS) {
// 搜索失败
if (retryCount < 60) {
const tmpRand = this.searchRandom;
Loader.show();
setTimeout(() => {
Loader.hide();
tmpRand === this.searchRandom && this.searchNearby(keywords, ++retryCount);
}, 1000)
return;
}
}
// 搜索结果
document.getElementById('address-list').style.display = 'block';
const array = [];
if (results instanceof Array) {
results.some(result => {
if (!result) {
return false;
}
for (let i = 0; i < result.getCurrentNumPois(); i++) {
const poi = result.getPoi(i);
poi.distance = this.params.point ? this.map.getDistance(this.params.point, poi.point) : null;
array.push(poi);
}
});
}
this.updatePoiList(array);
});
// 执行搜索
this.localSearch.searchNearby(keywords, this.currentPoint, this.params.radius);
}
/**
* 更新搜索结果列表
* @param results
*/
updatePoiList(results) {
const poiList = document.getElementById('poi-list');
poiList.innerHTML = '';
// 如果没有搜索结果
if (results.length === 0 && this.params.noresult) {
poiList.innerHTML = '<li>' + this.params.noresult + '</li>';
return;
}
// 按距离排序(如果有距离信息)
results.sort((a, b) => {
if (a.distance && b.distance) {
return a.distance - b.distance;
}
return 0;
});
results = results.slice(0, 20);
// 创建列表项
results.forEach(poi => {
const li = document.createElement('li');
const distanceFormat = poi.distance ? `<div class="address-distance">${this.convertDistance(Math.round(poi.distance))}</div>` : '';
li.innerHTML = `
<div class="address-name">${poi.title}</div>
<div class="address-detail">${poi.address || ""}${distanceFormat}</div>
`;
li.addEventListener('click', () => {
const point = poi.point;
this.updateMarker(point);
this.map.setCenter(point);
//
App.setVariate("location::" + this.params.channel, JSON.stringify(poi));
if (this.params.selectclose) {
App.closePage();
}
});
poiList.appendChild(li);
});
// 列表更新后,重新将当前标记点居中显示
setTimeout(() => {
if (this.marker) {
this.map.setCenter(this.marker.getPosition());
}
}, 100); // 添加小延时确保DOM已更新
}
/**
* 转换距离显示
* @param d
* @returns {string}
*/
convertDistance(d) {
if (d > 1000) {
return (d / 1000).toFixed(1) + 'km';
}
return d.toFixed(0) + 'm';
}
/**
* 定位失败提示
*/
locationError() {
if (this.params.errtip) {
alert(this.params.errtip);
}
if (this.params.errclose) {
App.closePage();
}
}
/**
* 加载百度地图脚本
* @returns {Promise<unknown>}
*/
loadMapScript() {
return new Promise((resolve, reject) => {
// 如果已经加载过,直接返回
if (window.BMap) {
@ -384,7 +558,7 @@ class LocationPicker {
// 创建script标签
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${this.config.key}&callback=initBaiduMap`;
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${this.params.key}&callback=initBaiduMap`;
// 添加回调函数
window.initBaiduMap = () => {
@ -405,5 +579,6 @@ class LocationPicker {
// 初始化
document.addEventListener('DOMContentLoaded', () => {
new LocationPicker();
new App();
new BaiduMapPicker();
});

View File

@ -0,0 +1,272 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
html.theme-dark {
-webkit-filter: invert(100%) hue-rotate(180deg) contrast(90%) !important;
filter: invert(100%) hue-rotate(180deg) contrast(90%) !important;
}
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-size: 14px;
color: #303133;
}
#map-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
<script>
class Pickup {
constructor(containerId) {
this.containerId = containerId;
this.map = null;
this.circle = null;
this.marker = null;
this.centerPoint = null;
this.isFirstClick = true;
this.params = {
theme: 'light', // 主题风格light|dark
key: null, // 百度地图 API Key
point: null, // 初始坐标116.404,39.915
radius: 300, // 圆形半径
zoom: 16, // 地图缩放级别
}
this.init();
}
async init() {
// 先初始化参数
this.initParams();
// 如果没有 key直接返回
if (!this.params.key) {
console.error('未提供百度地图 API Key');
return;
}
try {
// 等待地图 JS 加载完成
await this.loadMapScript();
// 初始化地图
this.initMap();
} catch (error) {
console.error('加载百度地图失败:', error);
}
}
initParams() {
// 获取当前URL的查询参数
const urlParams = new URLSearchParams(window.location.search);
// 遍历 params 对象的所有属性
Object.keys(this.params).forEach(key => {
// 从 URL 参数中获取值
const value = urlParams.get(key);
if (value !== null) {
// 根据参数类型进行转换
switch (key) {
case 'radius':
// 转换为数字
this.params[key] = parseInt(value) || 300;
break;
case 'point':
// 转换为坐标数组
const [lng, lat] = value.replace(/[|-]/, ',').split(',').map(parseFloat);
if (lng && lat) {
this.params[key] = {lng, lat};
}
break;
default:
// 字符串类型直接赋值
this.params[key] = value;
}
}
});
// 设置主题风格
if (!['dark', 'light'].includes(this.params.theme)) {
this.params.theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.classList.add(`theme-${this.params.theme}`);
document.body.style.backgroundColor = "#ffffff";
}
// 初始化地图
initMap() {
this.map = new BMap.Map(this.containerId, {
enableMapClick: false
});
if (this.params.point) {
this.centerPoint = new BMap.Point(this.params.point.lng, this.params.point.lat);
this.handleMapClick({point: this.centerPoint});
} else {
this.centerPoint = new BMap.Point(116.404, 39.915);
}
this.map.centerAndZoom(this.centerPoint, 15);
this.initMapControls();
this.bindEvents();
}
initMapControls() {
this.map.enableScrollWheelZoom();
this.map.enableDragging();
this.map.addControl(new BMap.NavigationControl());
this.map.addControl(new BMap.ScaleControl());
}
bindEvents() {
this.map.addEventListener("click", (e) => this.handleMapClick(e));
}
handleMapClick(e) {
this.centerPoint = e.point;
if (this.isFirstClick) {
this.createMarkerAndCircle();
this.isFirstClick = false;
} else {
this.params.radius = this.circle.getRadius();
this.updateMarkerAndCircle();
}
this.updateInfoPanel();
}
createMarkerAndCircle() {
this.createMarker();
this.createCircle();
}
createMarker() {
this.marker = new BMap.Marker(this.centerPoint, {
enableClicking: false
});
this.map.addOverlay(this.marker);
}
createCircle() {
this.circle = new BMap.Circle(this.centerPoint, this.params.radius, {
strokeColor: "#FF0000",
strokeWeight: 2,
strokeOpacity: 0.5,
fillColor: "#FF0000",
fillOpacity: 0.2,
enableClicking: false
});
this.map.addOverlay(this.circle);
this.circle.enableEditing();
this.circle.addEventListener("lineupdate", () => {
this.centerPoint = this.circle.getCenter();
this.marker?.setPosition(this.centerPoint);
this.params.radius = this.circle.getRadius();
this.updateInfoPanel();
});
}
updateMarkerAndCircle() {
this.map.removeOverlay(this.marker);
this.map.removeOverlay(this.circle);
this.createMarker();
this.createCircle();
}
updateInfoPanel() {
const data = {
longitude: this.centerPoint.lng.toFixed(6),
latitude: this.centerPoint.lat.toFixed(6),
radius: this.circle ? this.circle.getRadius().toFixed(0) : '-'
}
window.parent.postMessage(Object.assign(data, {
action: "bd_lbs_select_point",
}), "*");
}
// 获取当前选择的数据
getData() {
if (!this.circle) return null;
return {
center: {
lng: this.centerPoint.lng,
lat: this.centerPoint.lat
},
radius: this.circle.getRadius()
};
}
// 设置中心点和半径
setData(lng, lat, radius) {
this.centerPoint = new BMap.Point(lng, lat);
this.params.radius = radius || this.params.radius;
if (this.isFirstClick) {
this.createMarkerAndCircle();
this.isFirstClick = false;
} else {
this.updateMarkerAndCircle();
}
this.updateInfoPanel();
this.map.panTo(this.centerPoint);
}
loadMapScript() {
return new Promise((resolve, reject) => {
// 如果已经加载过,直接返回
if (window.BMap) {
resolve();
return;
}
// 创建script标签
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${this.params.key}&callback=initBaiduMap`;
// 添加回调函数
window.initBaiduMap = () => {
resolve();
delete window.initBaiduMap;
};
// 处理加载错误
script.onerror = () => {
reject(new Error('百度地图脚本加载失败'));
};
// 添加到页面
document.body.appendChild(script);
});
}
}
// 页面加载完成后初始化
window.onload = function () {
window.pickup = new Pickup("map-container");
};
</script>
</head>
<body>
<div id="map-container"></div>
</body>
</html>

View File

@ -2,6 +2,7 @@
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
html.theme-dark {
@ -67,11 +68,40 @@ body {
-webkit-user-select: text;
}
.map-box {
position: relative;
display: flex;
flex: 1;
}
#map-container {
flex: 1;
min-height: 240px;
}
#map-location {
display: none;
position: absolute;
bottom: 16px;
right: 16px;
z-index: 100;
background: #fff;
border-radius: 4px;
cursor: pointer;
width: 36px;
height: 36px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
#map-location .icon {
width: 24px;
height: 24px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.address-list {
display: none;
min-height: 300px;

View File

@ -1722,11 +1722,70 @@ export default {
}
},
/**
* 发送位置消息
* @param data
*/
sendLocationMsg(data) {
this.$store.dispatch("call", {
url: 'dialog/msg/sendlocation',
data: Object.assign(data, {
dialog_id: this.dialogId,
}),
spinner: true,
method: 'post',
}).then(({data}) => {
this.sendSuccess(data)
}).catch(({msg}) => {
$A.modalConfirm({
icon: 'error',
title: '发送失败',
content: msg,
cancelText: '取消发送',
okText: '重新发送',
onOk: _ => {
this.sendLocationMsg(data)
},
})
});
},
/**
* 发送快捷消息
* @param item
*/
sendQuick(item) {
if (item.key === "locat-checkin") {
this.$store.dispatch('openAppMapPage', {
key: item.config.key,
point: `${item.config.lng},${item.config.lat}`,
}).then(data => {
if (!$A.isJson(data)) {
return
}
if (data.distance > item.config.radius) {
$A.modalError(`你选择的位置「${data.title}」不在签到范围内`)
return
}
const preview = $A.urlAddParams('https://api.map.baidu.com/staticimage/v2', {
ak: item.config.key,
center: `${item.config.lng},${item.config.lat}`,
width: 800,
height: 480,
zoom: 17,
copyright: 1
})
this.sendLocationMsg({
type: 'bd',
lng: data.point.lng,
lat: data.point.lat,
title: data.title,
address: data.address || '',
preview
})
})
return;
}
this.sendMsg(`<p><span data-quick-key="${item.key}">${item.label}</span></p>`)
},

View File

@ -71,13 +71,13 @@
<CheckboxGroup v-model="formData.modes">
<Checkbox label="face">{{$L('人脸签到')}}</Checkbox>
<Checkbox label="auto">{{$L('WiFi签到')}}</Checkbox>
<Checkbox label="locat">{{$L('定位签到')}}</Checkbox>
<Checkbox label="manual">{{$L('手动签到')}}</Checkbox>
<Checkbox v-if="false" label="location">{{$L('定位签到')}}</Checkbox>
</CheckboxGroup>
<div v-if="formData.modes.includes('face')" class="form-tip">{{$L('人脸签到')}}: {{$L('通过人脸识别机签到')}}</div>
<div v-if="formData.modes.includes('auto')" class="form-tip">{{$L('WiFi签到')}}: {{$L('详情看下文安装说明')}}</div>
<div v-if="formData.modes.includes('locat')" class="form-tip">{{$L('定位签到')}}: {{$L('通过在签到打卡机器人发送位置签到')}}</div>
<div v-if="formData.modes.includes('manual')" class="form-tip">{{$L('手动签到')}}: {{$L('通过在签到打卡机器人发送指令签到')}}</div>
<div v-if="formData.modes.includes('location')" class="form-tip">{{$L('定位签到')}}: {{$L('通过在签到打卡机器人发送位置签到')}}</div>
</FormItem>
</template>
</div>
@ -117,6 +117,29 @@
</div>
</div>
</template>
<template v-if="formData.modes.includes('locat')">
<div class="block-setting-space"></div>
<div class="block-setting-box">
<h3>{{ $L('定位签到') }}</h3>
<div class="form-box">
<FormItem :label="$L('签到备注')" prop="locat_remark">
<Input :maxlength="30" v-model="formData.locat_remark"/>
</FormItem>
<FormItem :label="$L('百度地图AK')" prop="locat_bd_lbs_key">
<Input :maxlength="100" v-model="formData.locat_bd_lbs_key"/>
<div class="form-tip">{{$L('获取AK流程')}}: <a href="https://lbs.baidu.com/faq/search?id=299&title=677" target="_blank">https://lbs.baidu.com/faq/search?id=299&title=677</a></div>
</FormItem>
<FormItem :label="$L('允许签到位置')" prop="locat_bd_allow_point">
<ETooltip v-if="formData.locat_bd_lbs_point.lng" :content="$L('点击修改')">
<a href="javascript:void(0)" @click="openBdSelect">
{{ $L(`经度:${formData.locat_bd_lbs_point.lng},纬度:${formData.locat_bd_lbs_point.lat},半径:${formData.locat_bd_lbs_point.radius}`) }}
</a>
</ETooltip>
<a v-else href="javascript:void(0)" @click="openBdSelect">{{$L('点击设置')}}</a>
</FormItem>
</div>
</div>
</template>
<template v-if="formData.modes.includes('manual')">
<div class="block-setting-space"></div>
<div class="block-setting-box">
@ -145,17 +168,49 @@
:size="1380">
<TeamManagement v-if="allUserShow" checkin-mode/>
</DrawerOverlay>
<!--百度选择签到位置-->
<Modal
v-model="bdSelectShow"
:title="$L('允许签到位置')"
:mask-closable="false"
width="800">
<div>
<div v-if="bdSelectPoint.radius" class="bd-select-point-tip">{{ $L(`签到半径${bdSelectPoint.radius}`) }}</div>
<div v-else class="bd-select-point-tip">{{ $L('请点击地图选择签到位置') }}</div>
<IFrame v-if="bdSelectShow" class="bd-select-point-iframe" :src="bdSelectUrl" @on-message="onBdMessage"/>
</div>
<div slot="footer" class="adaption">
<Button type="default" @click="bdSelectShow=false">{{$L('关闭')}}</Button>
<Button type="primary" @click="onBdSelect">{{$L('确定')}}</Button>
</div>
</Modal>
</div>
</template>
<style lang="scss" scoped>
.bd-select-point-tip {
font-size: 16px;
text-align: center;
margin-bottom: 12px;
margin-top: -12px;
}
.bd-select-point-iframe {
width: 100%;
height: 500px;
border: 0;
border-radius: 12px;
}
</style>
<script>
import DrawerOverlay from "../../../../components/DrawerOverlay";
import TeamManagement from "../../components/TeamManagement";
import CheckinExport from "../../components/CheckinExport";
import {mapState} from "vuex";
import IFrame from "../../components/IFrame.vue";
export default {
name: "SystemCheckin",
components: {CheckinExport, TeamManagement, DrawerOverlay},
components: {IFrame, CheckinExport, TeamManagement, DrawerOverlay},
data() {
return {
loadIng: 0,
@ -169,11 +224,17 @@ export default {
face_remark: '',
face_retip: '',
manual_remark: '',
locat_remark: '',
locat_bd_lbs_point: {},
},
ruleData: {},
allUserShow: false,
exportShow: false,
bdSelectShow: false,
bdSelectPoint: {},
bdSelectUrl: '',
}
},
@ -227,6 +288,39 @@ export default {
this.$refs.cmd.focus({cursor:'all'});
});
},
openBdSelect() {
if (!this.formData.locat_bd_lbs_key) {
$A.messageError('请先填写百度地图AK');
return;
}
const url = $A.urlAddParams($A.mainUrl('tools/map/select.html'), {
key: this.formData.locat_bd_lbs_key,
point: this.formData.locat_bd_lbs_point.lng + ',' + this.formData.locat_bd_lbs_point.lat,
radius: this.formData.locat_bd_lbs_point.radius,
})
this.$store.dispatch('userUrl', url).then(newUrl => {
this.bdSelectUrl = newUrl;
this.bdSelectPoint = this.formData.locat_bd_lbs_point;
this.bdSelectShow = true;
});
},
onBdMessage(data) {
if (data.action !== 'bd_lbs_select_point') {
return;
}
this.bdSelectPoint = {
lng: data.longitude,
lat: data.latitude,
radius: data.radius,
}
},
onBdSelect() {
this.formData.locat_bd_lbs_point = this.bdSelectPoint;
this.bdSelectShow = false;
},
}
}
</script>

View File

@ -986,7 +986,7 @@ export default {
selectclose: "true",
channel: $A.randomString(6)
}
const url = $A.urlAddParams($A.mainUrl("tools/map/index.html"), Object.assign(params, objects))
const url = $A.urlAddParams($A.eeuiAppRewriteUrl('../public/tools/map/index.html'), Object.assign(params, objects || {}))
dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: params.title,

View File

@ -17,7 +17,14 @@
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
</div>
</div>
<div class="map-box">
<div id="map-container"></div>
<div id="map-location">
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 0a64 64 0 0 1 62.208 48.896 467.456 467.456 0 0 1 400.896 400.938667 64 64 0 0 1 0 124.373333 467.456 467.456 0 0 1-400.938667 400.896 64 64 0 0 1-124.373333 0 467.456 467.456 0 0 1-400.896-400.938667 64 64 0 0 1 0-124.373333 467.456 467.456 0 0 1 400.938667-400.896A63.914667 63.914667 0 0 1 512 0z m64 178.816V196.266667a64 64 0 0 1-127.701333 6.144L448 196.266667v-17.450667a339.541333 339.541333 0 0 0-269.184 269.226667L196.266667 448a64 64 0 0 1 6.144 127.701333L196.266667 576h-17.450667a339.584 339.584 0 0 0 269.226667 269.184L448 827.733333a64 64 0 0 1 127.701333-6.144l0.298667 6.144v17.450667A339.584 339.584 0 0 0 845.226667 576L827.733333 576a64 64 0 0 1-6.144-127.701333l6.144-0.298667h17.450667A339.584 339.584 0 0 0 576 178.816zM512.512 448a64 64 0 0 1 6.186667 127.701333L512 576a64 64 0 0 1-6.144-127.701333l6.613333-0.298667z" fill="#717171"></path>
</svg>
</div>
</div>
<div id="address-list" class="address-list">
<h3 id="address-label"></h3>
<ul id="poi-list"></ul>

View File

@ -1,356 +1,42 @@
class LocationPicker {
class App {
static #eeui = null;
static #geolocation = null;
constructor() {
this.map = null;
this.marker = null;
this.geolocation = null;
this.localSearch = null;
this.currentPoint = null;
this.loadNum = 0;
this.config = {
theme: 'light', // 主题风格light|dark
key: null, // 百度地图 API Key
title: null, // 页面标题,如:选择打卡地点
label: null, // 搜索列表标签,如:附近的地点
placeholder: null, // 搜索框占位符,如:搜索附近的地点
point: null, // 初始坐标116.404,39.915
noresult: null, // 无搜索结果提示,如:附近没有找到地点
radius: 300, // 搜索半径单位300
zoom: 16, // 地图缩放级别
errtip: null, // 定位失败提示
errclose: false, // 定位失败是否关闭页面
channel: null, // 回传数据通道
selectclose: false, // 选择地点是否关闭页面
};
this.init();
this.constructor.init();
}
async init() {
// 先初始化参数
this.initParams();
// 如果没有 key直接返回
if (!this.config.key) {
console.error('未提供百度地图 API Key');
return;
static async init() {
while (typeof requireModuleJs !== "function") {
await new Promise(resolve => setTimeout(resolve, 500));
}
this.#eeui = requireModuleJs("eeui");
this.#geolocation = requireModuleJs("eeui/geolocation");
}
try {
// 等待地图 JS 加载完成
await this.loadBaiduMapScript();
// 初始化地图
this.initMap();
} catch (error) {
console.error('加载百度地图失败:', error);
static async setVariate(key, value) {
while (!this.#eeui) {
await new Promise(resolve => setTimeout(resolve, 500));
}
await this.#eeui.setVariate(key, value);
}
initParams() {
// 获取当前URL的查询参数
const urlParams = new URLSearchParams(window.location.search);
// 遍历 config 对象的所有属性
Object.keys(this.config).forEach(key => {
// 从 URL 参数中获取值
const value = urlParams.get(key);
if (value !== null) {
// 根据参数类型进行转换
switch (key) {
case 'radius':
// 转换为数字
this.config[key] = parseInt(value) || 300;
break;
case 'point':
// 转换为坐标数组
const [lng, lat] = value.replace(/[|-]/, ',').split(',').map(parseFloat);
if (lng && lat) {
this.config[key] = {lng, lat};
}
break;
default:
// 字符串类型直接赋值
this.config[key] = value;
}
static async getLocation() {
while (!this.#geolocation) {
await new Promise(resolve => setTimeout(resolve, 500));
}
return new Promise(resolve => {
this.#geolocation.get((res) => {
resolve(res);
});
// 设置主题风格
document.documentElement.classList.add(`theme-${this.config.theme}`);
document.body.style.backgroundColor = "#ffffff";
// 设置标题
if (this.config.title) {
document.title = this.config.title;
}
// 设置搜索框占位符
if (this.config.placeholder) {
document.getElementById('search-input').placeholder = this.config.placeholder;
}
// 设置label
if (this.config.label) {
document.getElementById('address-label').innerText = this.config.label;
}
}
initMap() {
// 初始化地图
this.map = new BMap.Map('map-container');
// 创建定位控件
const locationControl = new BMap.GeolocationControl({
anchor: BMAP_ANCHOR_BOTTOM_RIGHT,
showAddressBar: false,
enableAutoLocation: false,
locationIcon: new BMap.Icon("empty.svg", new BMap.Size(0, 0))
});
// 监听定位事件
locationControl.addEventListener("locationSuccess", (e) => {
// 定位成功事件
this.updateCurrentPoint(e.point);
});
locationControl.addEventListener("locationError", (e) => {
// 定位失败事件
console.error('定位失败:', e.message);
this.locationError();
});
// 添加定位控件到地图
this.map.addControl(locationControl);
// 初始化本地搜索,移除地图渲染
this.localSearch = new BMap.LocalSearch(this.map, {
renderOptions: {
autoViewport: false // 关闭自动视野调整
}
});
// 设置地图中心点
if (this.config.point) {
const {lng, lat} = this.config.point;
this.config.point = new BMap.Point(lng, lat);
// 设置地图中心点和缩放级别
this.map.centerAndZoom(this.config.point, this.config.zoom);
// 创建圆形区域
const circle = new BMap.Circle(this.config.point, this.config.radius, {
fillColor: "#333333",
fillOpacity: 0.1,
strokeColor: "#333333",
strokeWeight: 1,
strokeOpacity: 0.3
});
this.map.addOverlay(circle);
}
// 绑定事件
this.bindEvents();
// 初始化时自动定位
this.getCurrentLocation();
}
bindEvents() {
const searchInput = document.getElementById('search-input');
// 监听回车键
searchInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
searchInput.blur();
}
});
// 监听失去焦点
searchInput.addEventListener('blur', () => {
this.searchAddress();
});
}
getCurrentLocation() {
this.loaderShow();
this.geolocation = new BMap.Geolocation();
this.geolocation.getCurrentPosition((result) => {
this.loaderHide();
if (result && result.point) {
this.updateCurrentPoint(result.point)
} else {
console.error('定位失败');
this.locationError();
}
}, {enableHighAccuracy: true});
}
updateCurrentPoint(point) {
this.currentPoint = point;
this.map.centerAndZoom(this.currentPoint, this.config.zoom);
this.updateMarker(this.currentPoint);
this.searchNearby();
}
updateMarker(point) {
if (this.marker) {
this.marker.setPosition(point);
} else {
this.marker = new BMap.Marker(point);
this.map.addOverlay(this.marker);
}
}
searchAddress() {
const keyword = document.getElementById('search-input').value;
this.searchNearby(keyword ? [keyword] : []);
}
searchNearby(keywords = [], retryCount = 0) {
// 当前位置未获取
if (this.currentPoint === null) {
return;
}
// 清除之前的搜索结果
this.localSearch.clearResults();
// 搜索附近的关键词
if (keywords.length === 0) {
keywords = ["写字楼", "公司", "银行", "餐馆", "商场", "超市", "学校", "医院", "公交站", "地铁站"]
}
// 定义一个随机数,用于判断定时器是否过期
this.searchRandom = Math.random();
// 设置搜索完成回调
this.loaderShow();
this.localSearch.setSearchCompleteCallback((results) => {
this.loaderHide();
if (this.localSearch.getStatus() !== BMAP_STATUS_SUCCESS) {
// 搜索失败
if (retryCount < 60) {
const tmpRand = this.searchRandom;
this.loaderShow();
setTimeout(() => {
this.loaderHide();
tmpRand === this.searchRandom && this.searchNearby(keywords, ++retryCount);
}, 1000)
return;
}
}
// 搜索结果
document.getElementById('address-list').style.display = 'block';
const array = [];
if (results instanceof Array) {
results.some(result => {
if (!result) {
return false;
}
for (let i = 0; i < result.getCurrentNumPois(); i++) {
const poi = result.getPoi(i);
poi._distance = this.config.point ? this.map.getDistance(this.config.point, poi.point) : null;
array.push(poi);
}
});
}
this.updatePoiList(array);
});
// 执行搜索
this.localSearch.searchNearby(keywords, this.currentPoint, this.config.radius);
}
updatePoiList(results) {
const poiList = document.getElementById('poi-list');
poiList.innerHTML = '';
// 如果没有搜索结果
if (results.length === 0 && this.config.noresult) {
poiList.innerHTML = '<li>' + this.config.noresult + '</li>';
return;
}
// 按距离排序(如果有距离信息)
results.sort((a, b) => {
if (a._distance && b._distance) {
return a._distance - b._distance;
}
return 0;
});
results = results.slice(0, 20);
// 创建列表项
results.forEach(poi => {
const li = document.createElement('li');
const distance = poi._distance ? `<div class="address-distance">${this.convertDistance(Math.round(poi._distance))}</div>` : '';
li.innerHTML = `
<div class="address-name">${poi.title}</div>
<div class="address-detail">${poi.address || ""}${distance}</div>
`;
li.addEventListener('click', () => {
const point = poi.point;
this.updateMarker(point);
this.map.setCenter(point);
//
if (typeof requireModuleJs === "function") {
const eeui = requireModuleJs("eeui");
eeui.setVariate("location::" + this.config.channel, JSON.stringify(poi))
}
if (this.config.selectclose) {
this.closePage();
}
});
poiList.appendChild(li);
});
// 列表更新后,重新将当前标记点居中显示
setTimeout(() => {
if (this.marker) {
this.map.setCenter(this.marker.getPosition());
}
}, 100); // 添加小延时确保DOM已更新
}
convertDistance(d) {
if (d > 1000) {
return (d / 1000).toFixed(1) + 'km';
}
return d.toFixed(0) + 'm';
}
locationError() {
if (this.config.errtip) {
alert(this.config.errtip);
}
if (this.config.errclose) {
this.closePage();
}
}
loaderShow() {
this.loadNum++;
this.loaderJudge();
}
loaderHide() {
setTimeout(() => {
this.loadNum--;
this.loaderJudge();
}, 100)
}
loaderJudge() {
if (this.loadNum > 0) {
document.querySelector('.loading').classList.add('show');
} else if (this.loadNum <= 0) {
document.querySelector('.loading').classList.remove('show');
}
}
closePage() {
static closePage() {
try {
// 方法1: 如果是在 eeui 环境中
if (typeof requireModuleJs === "function") {
const eeui = requireModuleJs("eeui");
eeui.closePage();
if (this.#eeui) {
this.#eeui.closePage();
}
// 方法2: 如果是从其他页面打开的,可以关闭当前窗口
@ -373,7 +59,495 @@ class LocationPicker {
}
}
loadBaiduMapScript() {
static isArray(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number";
}
static isJson(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined";
}
}
class CoordTransform {
// 私有静态常量
static #x_PI = 3.14159265358979324 * 3000.0 / 180.0;
static #PI = 3.1415926535897932384626;
static #a = 6378245.0;
static #ee = 0.00669342162296594323;
/**
* WGS84 BD09
* @param {number} lng WGS84 经度
* @param {number} lat WGS84 纬度
* @returns {[number, number]} BD09 坐标 [经度, 纬度]
*/
static wgs84toBd09(lng, lat) {
const gcj = CoordTransform.wgs84ToGcj02(lng, lat);
return CoordTransform.gcj02ToBd09(gcj[0], gcj[1]);
}
/**
* WGS84 GCJ02
* @private
*/
static wgs84ToGcj02(lng, lat) {
if (CoordTransform.outOfChina(lng, lat)) {
return [lng, lat];
}
let dlat = CoordTransform.transformLat(lng - 105.0, lat - 35.0);
let dlng = CoordTransform.transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * CoordTransform.#PI;
let magic = Math.sin(radLat);
magic = 1 - CoordTransform.#ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((CoordTransform.#a * (1 - CoordTransform.#ee)) / (magic * sqrtMagic) * CoordTransform.#PI);
dlng = (dlng * 180.0) / (CoordTransform.#a / sqrtMagic * Math.cos(radLat) * CoordTransform.#PI);
const mgLat = lat + dlat;
const mgLng = lng + dlng;
return [mgLng, mgLat];
}
/**
* GCJ02 BD09
* @private
*/
static gcj02ToBd09(lng, lat) {
const z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * CoordTransform.#x_PI);
const theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * CoordTransform.#x_PI);
const bdLng = z * Math.cos(theta) + 0.0065;
const bdLat = z * Math.sin(theta) + 0.006;
return [bdLng, bdLat];
}
/**
* 判断坐标是否在中国境内
* @private
*/
static outOfChina(lng, lat) {
return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271);
}
/**
* 转换纬度
* @private
*/
static transformLat(lng, lat) {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat +
0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * CoordTransform.#PI) + 20.0 *
Math.sin(2.0 * lng * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * CoordTransform.#PI) + 40.0 *
Math.sin(lat / 3.0 * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * CoordTransform.#PI) + 320 *
Math.sin(lat * CoordTransform.#PI / 30.0)) * 2.0 / 3.0;
return ret;
}
/**
* 转换经度
* @private
*/
static transformLng(lng, lat) {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng +
0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * CoordTransform.#PI) + 20.0 *
Math.sin(2.0 * lng * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * CoordTransform.#PI) + 40.0 *
Math.sin(lng / 3.0 * CoordTransform.#PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * CoordTransform.#PI) + 300.0 *
Math.sin(lng / 30.0 * CoordTransform.#PI)) * 2.0 / 3.0;
return ret;
}
}
class Loader {
static #num = 0;
static show() {
this.#num++;
this.judge();
}
static hide() {
setTimeout(() => {
this.#num--;
this.judge();
}, 100)
}
static judge() {
if (this.#num > 0) {
document.querySelector('.loading').classList.add('show');
} else if (this.#num <= 0) {
document.querySelector('.loading').classList.remove('show');
}
}
}
class BaiduMapPicker {
constructor() {
this.map = null;
this.marker = null;
this.localSearch = null;
this.currentPoint = null;
this.params = {
theme: 'light', // 主题风格light|dark
key: null, // 百度地图 API Key
title: null, // 页面标题,如:选择打卡地点
label: null, // 搜索列表标签,如:附近的地点
placeholder: null, // 搜索框占位符,如:搜索附近的地点
point: null, // 初始坐标116.404,39.915
noresult: null, // 无搜索结果提示,如:附近没有找到地点
radius: 300, // 搜索半径单位300
zoom: 16, // 地图缩放级别
errtip: null, // 定位失败提示
errclose: false, // 定位失败是否关闭页面
channel: null, // 回传数据通道
selectclose: false, // 选择地点是否关闭页面
};
this.init();
}
async init() {
// 先初始化参数
this.initParams();
// 如果没有 key直接返回
if (!this.params.key) {
console.error('未提供百度地图 API Key');
return;
}
try {
// 等待地图 JS 加载完成
await this.loadMapScript();
// 初始化地图
this.initMap();
} catch (error) {
console.error('加载百度地图失败:', error);
}
}
/**
* 初始化参数
*/
initParams() {
// 获取当前URL的查询参数
const urlParams = new URLSearchParams(window.location.search);
// 遍历 params 对象的所有属性
Object.keys(this.params).forEach(key => {
// 从 URL 参数中获取值
const value = urlParams.get(key);
if (value !== null) {
// 根据参数类型进行转换
switch (key) {
case 'radius':
// 转换为数字
this.params[key] = parseInt(value) || 300;
break;
case 'point':
// 转换为坐标数组
const [lng, lat] = value.replace(/[|-]/, ',').split(',').map(parseFloat);
if (lng && lat) {
this.params[key] = {lng, lat};
}
break;
default:
// 字符串类型直接赋值
this.params[key] = value;
}
}
});
// 设置主题风格
if (!['dark', 'light'].includes(this.params.theme)) {
this.params.theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.classList.add(`theme-${this.params.theme}`);
document.body.style.backgroundColor = "#ffffff";
// 设置标题
if (this.params.title) {
document.title = this.params.title;
}
// 设置搜索框占位符
if (this.params.placeholder) {
document.getElementById('search-input').placeholder = this.params.placeholder;
}
// 设置label
if (this.params.label) {
document.getElementById('address-label').innerText = this.params.label;
}
}
/**
* 初始化地图
*/
initMap() {
// 初始化地图
this.map = new BMap.Map('map-container');
// 初始化本地搜索,移除地图渲染
this.localSearch = new BMap.LocalSearch(this.map, {
renderOptions: {
autoViewport: false // 关闭自动视野调整
}
});
// 设置地图中心点
if (this.params.point) {
const {lng, lat} = this.params.point;
this.params.point = new BMap.Point(lng, lat);
// 设置地图中心点和缩放级别
this.map.centerAndZoom(this.params.point, this.params.zoom);
// 创建圆形区域
const circle = new BMap.Circle(this.params.point, this.params.radius, {
fillColor: "#333333",
fillOpacity: 0.1,
strokeColor: "#333333",
strokeWeight: 1,
strokeOpacity: 0.3
});
this.map.addOverlay(circle);
}
// 绑定事件
this.bindEvents();
// 初始化时自动定位
Loader.show();
this.getCurrentLocation().then((point) => {
Loader.hide();
if (point === null) {
this.locationError();
}
document.getElementById('map-location').style.display = 'block';
});
}
/**
* 绑定事件
*/
bindEvents() {
// 输入框事件
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
searchInput.blur();
}
});
searchInput.addEventListener('blur', () => {
this.searchAddress();
});
// 地图定位点击事件
const mapLocation = document.getElementById('map-location');
mapLocation.addEventListener('click', () => {
Loader.show()
this.getCurrentLocation().then(() => {
Loader.hide()
});
});
}
/**
* 获取当前位置
* @returns {Promise<unknown>}
*/
getCurrentLocation() {
return new Promise(resolve => {
App.getLocation().then(res => {
if (App.isJson(res) && res.longitude && res.latitude) {
const bd09_coord = CoordTransform.wgs84toBd09(res.longitude, res.latitude);
const point = new BMap.Point(bd09_coord[0], bd09_coord[1]);
this.updateCurrentPoint(point)
resolve(point);
} else {
console.error('定位失败');
resolve(null);
}
})
})
}
/**
* 更新当前位置
* @param point
*/
updateCurrentPoint(point) {
this.currentPoint = point;
this.map.centerAndZoom(this.currentPoint, this.params.zoom);
this.updateMarker(this.currentPoint);
this.searchNearby();
}
/**
* 更新标记点
* @param point
*/
updateMarker(point) {
if (this.marker) {
this.marker.setPosition(point);
} else {
this.marker = new BMap.Marker(point);
this.map.addOverlay(this.marker);
}
}
/**
* 搜索地址
*/
searchAddress() {
const keyword = document.getElementById('search-input').value;
this.searchNearby(keyword ? [keyword] : []);
}
/**
* 搜索附近的地点
* @param keywords
* @param retryCount
*/
searchNearby(keywords = [], retryCount = 0) {
// 当前位置未获取
if (this.currentPoint === null) {
return;
}
// 清除之前的搜索结果
this.localSearch.clearResults();
// 搜索附近的关键词
if (keywords.length === 0) {
keywords = ["写字楼", "公司", "银行", "餐馆", "商场", "超市", "学校", "医院", "公交站", "地铁站"]
}
// 定义一个随机数,用于判断定时器是否过期
this.searchRandom = Math.random();
// 设置搜索完成回调
Loader.show();
this.localSearch.setSearchCompleteCallback((results) => {
Loader.hide();
if (this.localSearch.getStatus() !== BMAP_STATUS_SUCCESS) {
// 搜索失败
if (retryCount < 60) {
const tmpRand = this.searchRandom;
Loader.show();
setTimeout(() => {
Loader.hide();
tmpRand === this.searchRandom && this.searchNearby(keywords, ++retryCount);
}, 1000)
return;
}
}
// 搜索结果
document.getElementById('address-list').style.display = 'block';
const array = [];
if (results instanceof Array) {
results.some(result => {
if (!result) {
return false;
}
for (let i = 0; i < result.getCurrentNumPois(); i++) {
const poi = result.getPoi(i);
poi.distance = this.params.point ? this.map.getDistance(this.params.point, poi.point) : null;
array.push(poi);
}
});
}
this.updatePoiList(array);
});
// 执行搜索
this.localSearch.searchNearby(keywords, this.currentPoint, this.params.radius);
}
/**
* 更新搜索结果列表
* @param results
*/
updatePoiList(results) {
const poiList = document.getElementById('poi-list');
poiList.innerHTML = '';
// 如果没有搜索结果
if (results.length === 0 && this.params.noresult) {
poiList.innerHTML = '<li>' + this.params.noresult + '</li>';
return;
}
// 按距离排序(如果有距离信息)
results.sort((a, b) => {
if (a.distance && b.distance) {
return a.distance - b.distance;
}
return 0;
});
results = results.slice(0, 20);
// 创建列表项
results.forEach(poi => {
const li = document.createElement('li');
const distanceFormat = poi.distance ? `<div class="address-distance">${this.convertDistance(Math.round(poi.distance))}</div>` : '';
li.innerHTML = `
<div class="address-name">${poi.title}</div>
<div class="address-detail">${poi.address || ""}${distanceFormat}</div>
`;
li.addEventListener('click', () => {
const point = poi.point;
this.updateMarker(point);
this.map.setCenter(point);
//
App.setVariate("location::" + this.params.channel, JSON.stringify(poi));
if (this.params.selectclose) {
App.closePage();
}
});
poiList.appendChild(li);
});
// 列表更新后,重新将当前标记点居中显示
setTimeout(() => {
if (this.marker) {
this.map.setCenter(this.marker.getPosition());
}
}, 100); // 添加小延时确保DOM已更新
}
/**
* 转换距离显示
* @param d
* @returns {string}
*/
convertDistance(d) {
if (d > 1000) {
return (d / 1000).toFixed(1) + 'km';
}
return d.toFixed(0) + 'm';
}
/**
* 定位失败提示
*/
locationError() {
if (this.params.errtip) {
alert(this.params.errtip);
}
if (this.params.errclose) {
App.closePage();
}
}
/**
* 加载百度地图脚本
* @returns {Promise<unknown>}
*/
loadMapScript() {
return new Promise((resolve, reject) => {
// 如果已经加载过,直接返回
if (window.BMap) {
@ -384,7 +558,7 @@ class LocationPicker {
// 创建script标签
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${this.config.key}&callback=initBaiduMap`;
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${this.params.key}&callback=initBaiduMap`;
// 添加回调函数
window.initBaiduMap = () => {
@ -405,5 +579,6 @@ class LocationPicker {
// 初始化
document.addEventListener('DOMContentLoaded', () => {
new LocationPicker();
new App();
new BaiduMapPicker();
});

View File

@ -0,0 +1,272 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
html.theme-dark {
-webkit-filter: invert(100%) hue-rotate(180deg) contrast(90%) !important;
filter: invert(100%) hue-rotate(180deg) contrast(90%) !important;
}
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-size: 14px;
color: #303133;
}
#map-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
<script>
class Pickup {
constructor(containerId) {
this.containerId = containerId;
this.map = null;
this.circle = null;
this.marker = null;
this.centerPoint = null;
this.isFirstClick = true;
this.params = {
theme: 'light', // 主题风格light|dark
key: null, // 百度地图 API Key
point: null, // 初始坐标116.404,39.915
radius: 300, // 圆形半径
zoom: 16, // 地图缩放级别
}
this.init();
}
async init() {
// 先初始化参数
this.initParams();
// 如果没有 key直接返回
if (!this.params.key) {
console.error('未提供百度地图 API Key');
return;
}
try {
// 等待地图 JS 加载完成
await this.loadMapScript();
// 初始化地图
this.initMap();
} catch (error) {
console.error('加载百度地图失败:', error);
}
}
initParams() {
// 获取当前URL的查询参数
const urlParams = new URLSearchParams(window.location.search);
// 遍历 params 对象的所有属性
Object.keys(this.params).forEach(key => {
// 从 URL 参数中获取值
const value = urlParams.get(key);
if (value !== null) {
// 根据参数类型进行转换
switch (key) {
case 'radius':
// 转换为数字
this.params[key] = parseInt(value) || 300;
break;
case 'point':
// 转换为坐标数组
const [lng, lat] = value.replace(/[|-]/, ',').split(',').map(parseFloat);
if (lng && lat) {
this.params[key] = {lng, lat};
}
break;
default:
// 字符串类型直接赋值
this.params[key] = value;
}
}
});
// 设置主题风格
if (!['dark', 'light'].includes(this.params.theme)) {
this.params.theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.classList.add(`theme-${this.params.theme}`);
document.body.style.backgroundColor = "#ffffff";
}
// 初始化地图
initMap() {
this.map = new BMap.Map(this.containerId, {
enableMapClick: false
});
if (this.params.point) {
this.centerPoint = new BMap.Point(this.params.point.lng, this.params.point.lat);
this.handleMapClick({point: this.centerPoint});
} else {
this.centerPoint = new BMap.Point(116.404, 39.915);
}
this.map.centerAndZoom(this.centerPoint, 15);
this.initMapControls();
this.bindEvents();
}
initMapControls() {
this.map.enableScrollWheelZoom();
this.map.enableDragging();
this.map.addControl(new BMap.NavigationControl());
this.map.addControl(new BMap.ScaleControl());
}
bindEvents() {
this.map.addEventListener("click", (e) => this.handleMapClick(e));
}
handleMapClick(e) {
this.centerPoint = e.point;
if (this.isFirstClick) {
this.createMarkerAndCircle();
this.isFirstClick = false;
} else {
this.params.radius = this.circle.getRadius();
this.updateMarkerAndCircle();
}
this.updateInfoPanel();
}
createMarkerAndCircle() {
this.createMarker();
this.createCircle();
}
createMarker() {
this.marker = new BMap.Marker(this.centerPoint, {
enableClicking: false
});
this.map.addOverlay(this.marker);
}
createCircle() {
this.circle = new BMap.Circle(this.centerPoint, this.params.radius, {
strokeColor: "#FF0000",
strokeWeight: 2,
strokeOpacity: 0.5,
fillColor: "#FF0000",
fillOpacity: 0.2,
enableClicking: false
});
this.map.addOverlay(this.circle);
this.circle.enableEditing();
this.circle.addEventListener("lineupdate", () => {
this.centerPoint = this.circle.getCenter();
this.marker?.setPosition(this.centerPoint);
this.params.radius = this.circle.getRadius();
this.updateInfoPanel();
});
}
updateMarkerAndCircle() {
this.map.removeOverlay(this.marker);
this.map.removeOverlay(this.circle);
this.createMarker();
this.createCircle();
}
updateInfoPanel() {
const data = {
longitude: this.centerPoint.lng.toFixed(6),
latitude: this.centerPoint.lat.toFixed(6),
radius: this.circle ? this.circle.getRadius().toFixed(0) : '-'
}
window.parent.postMessage(Object.assign(data, {
action: "bd_lbs_select_point",
}), "*");
}
// 获取当前选择的数据
getData() {
if (!this.circle) return null;
return {
center: {
lng: this.centerPoint.lng,
lat: this.centerPoint.lat
},
radius: this.circle.getRadius()
};
}
// 设置中心点和半径
setData(lng, lat, radius) {
this.centerPoint = new BMap.Point(lng, lat);
this.params.radius = radius || this.params.radius;
if (this.isFirstClick) {
this.createMarkerAndCircle();
this.isFirstClick = false;
} else {
this.updateMarkerAndCircle();
}
this.updateInfoPanel();
this.map.panTo(this.centerPoint);
}
loadMapScript() {
return new Promise((resolve, reject) => {
// 如果已经加载过,直接返回
if (window.BMap) {
resolve();
return;
}
// 创建script标签
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${this.params.key}&callback=initBaiduMap`;
// 添加回调函数
window.initBaiduMap = () => {
resolve();
delete window.initBaiduMap;
};
// 处理加载错误
script.onerror = () => {
reject(new Error('百度地图脚本加载失败'));
};
// 添加到页面
document.body.appendChild(script);
});
}
}
// 页面加载完成后初始化
window.onload = function () {
window.pickup = new Pickup("map-container");
};
</script>
</head>
<body>
<div id="map-container"></div>
</body>
</html>

View File

@ -2,6 +2,7 @@
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
html.theme-dark {
@ -67,11 +68,40 @@ body {
-webkit-user-select: text;
}
.map-box {
position: relative;
display: flex;
flex: 1;
}
#map-container {
flex: 1;
min-height: 240px;
}
#map-location {
display: none;
position: absolute;
bottom: 16px;
right: 16px;
z-index: 100;
background: #fff;
border-radius: 4px;
cursor: pointer;
width: 36px;
height: 36px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
#map-location .icon {
width: 24px;
height: 24px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.address-list {
display: none;
min-height: 300px;