mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-14 12:42:51 +00:00
perf: 添加定位签到
This commit is contained in:
parent
2fc329a403
commit
3b9c9872ca
@ -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. 获取消息阅读情况
|
||||
*
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -91,63 +91,86 @@ class UserBot extends AbstractModel
|
||||
*/
|
||||
public static function quickMsgs($email)
|
||||
{
|
||||
return match ($email) {
|
||||
'check-in@bot.system' => [
|
||||
[
|
||||
'key' => 'checkin',
|
||||
'label' => Doo::translate('我要打卡')
|
||||
], /*[
|
||||
'key' => 'it',
|
||||
'label' => Doo::translate('IT资讯')
|
||||
], [
|
||||
'key' => '36ke',
|
||||
'label' => Doo::translate('36氪')
|
||||
], [
|
||||
'key' => '60s',
|
||||
'label' => Doo::translate('60s读世界')
|
||||
], [
|
||||
'key' => 'joke',
|
||||
'label' => Doo::translate('开心笑话')
|
||||
], [
|
||||
'key' => 'soup',
|
||||
'label' => Doo::translate('心灵鸡汤')
|
||||
]*/
|
||||
],
|
||||
'anon-msg@bot.system' => [
|
||||
[
|
||||
'key' => 'help',
|
||||
'label' => Doo::translate('使用说明')
|
||||
], [
|
||||
'key' => 'privacy',
|
||||
'label' => Doo::translate('隐私说明')
|
||||
],
|
||||
],
|
||||
'bot-manager@bot.system' => [
|
||||
[
|
||||
'key' => '/help',
|
||||
'label' => Doo::translate('帮助指令')
|
||||
], [
|
||||
'key' => '/api',
|
||||
'label' => Doo::translate('API接口文档')
|
||||
], [
|
||||
'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' => [
|
||||
[
|
||||
'key' => '%3A.clear',
|
||||
'label' => Doo::translate('清空上下文')
|
||||
]
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
switch ($email) {
|
||||
case 'check-in@bot.system':
|
||||
$menu = [
|
||||
/*[
|
||||
'key' => 'it',
|
||||
'label' => Doo::translate('IT资讯')
|
||||
], [
|
||||
'key' => '36ke',
|
||||
'label' => Doo::translate('36氪')
|
||||
], [
|
||||
'key' => '60s',
|
||||
'label' => Doo::translate('60s读世界')
|
||||
], [
|
||||
'key' => 'joke',
|
||||
'label' => Doo::translate('开心笑话')
|
||||
], [
|
||||
'key' => 'soup',
|
||||
'label' => Doo::translate('心灵鸡汤')
|
||||
]*/
|
||||
];
|
||||
$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('使用说明')
|
||||
], [
|
||||
'key' => 'privacy',
|
||||
'label' => Doo::translate('隐私说明')
|
||||
],
|
||||
];
|
||||
|
||||
case 'bot-manager@bot.system':
|
||||
return [
|
||||
[
|
||||
'key' => '/help',
|
||||
'label' => Doo::translate('帮助指令')
|
||||
], [
|
||||
'key' => '/api',
|
||||
'label' => Doo::translate('API接口文档')
|
||||
], [
|
||||
'key' => '/list',
|
||||
'label' => Doo::translate('我的机器人')
|
||||
],
|
||||
];
|
||||
|
||||
default:
|
||||
if (preg_match('/^ai-(.*?)@bot.system$/', $email)) {
|
||||
return [
|
||||
[
|
||||
'key' => '%3A.clear',
|
||||
'label' => Doo::translate('清空上下文')
|
||||
]
|
||||
];
|
||||
}
|
||||
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表示签到成功
|
||||
|
||||
@ -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"]);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -384,7 +384,7 @@ LDAP 用户禁止修改邮箱
|
||||
匿名消息
|
||||
系统管理员
|
||||
我要签到
|
||||
我要打卡
|
||||
手动打卡
|
||||
关键词不能为空
|
||||
LICENSE 格式错误
|
||||
LICENSE 保存失败
|
||||
@ -714,3 +714,5 @@ webhook地址最长仅支持255个字符。
|
||||
动画表情
|
||||
每日开心:(*)
|
||||
心灵鸡汤:(*)
|
||||
|
||||
定位签到
|
||||
|
||||
@ -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": "",
|
||||
|
||||
@ -17,7 +17,14 @@
|
||||
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
|
||||
</div>
|
||||
</div>
|
||||
<div id="map-container"></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>
|
||||
|
||||
857
public/tools/map/main.js
vendored
857
public/tools/map/main.js
vendored
@ -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;
|
||||
}
|
||||
|
||||
try {
|
||||
// 等待地图 JS 加载完成
|
||||
await this.loadBaiduMapScript();
|
||||
// 初始化地图
|
||||
this.initMap();
|
||||
} catch (error) {
|
||||
console.error('加载百度地图失败:', error);
|
||||
static async init() {
|
||||
while (typeof requireModuleJs !== "function") {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
this.#eeui = requireModuleJs("eeui");
|
||||
this.#geolocation = requireModuleJs("eeui/geolocation");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 设置主题风格
|
||||
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;
|
||||
static async setVariate(key, value) {
|
||||
while (!this.#eeui) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
await this.#eeui.setVariate(key, value);
|
||||
}
|
||||
|
||||
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
|
||||
static async getLocation() {
|
||||
while (!this.#geolocation) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.#geolocation.get((res) => {
|
||||
resolve(res);
|
||||
});
|
||||
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();
|
||||
});
|
||||
|
||||
272
public/tools/map/select.html
Normal file
272
public/tools/map/select.html
Normal 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>
|
||||
30
public/tools/map/style.css
vendored
30
public/tools/map/style.css
vendored
@ -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;
|
||||
|
||||
@ -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>`)
|
||||
},
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
2
resources/assets/js/store/actions.js
vendored
2
resources/assets/js/store/actions.js
vendored
@ -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,
|
||||
|
||||
@ -17,7 +17,14 @@
|
||||
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
|
||||
</div>
|
||||
</div>
|
||||
<div id="map-container"></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>
|
||||
|
||||
857
resources/assets/statics/public/tools/map/main.js
vendored
857
resources/assets/statics/public/tools/map/main.js
vendored
@ -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;
|
||||
}
|
||||
|
||||
try {
|
||||
// 等待地图 JS 加载完成
|
||||
await this.loadBaiduMapScript();
|
||||
// 初始化地图
|
||||
this.initMap();
|
||||
} catch (error) {
|
||||
console.error('加载百度地图失败:', error);
|
||||
static async init() {
|
||||
while (typeof requireModuleJs !== "function") {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
this.#eeui = requireModuleJs("eeui");
|
||||
this.#geolocation = requireModuleJs("eeui/geolocation");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 设置主题风格
|
||||
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;
|
||||
static async setVariate(key, value) {
|
||||
while (!this.#eeui) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
await this.#eeui.setVariate(key, value);
|
||||
}
|
||||
|
||||
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
|
||||
static async getLocation() {
|
||||
while (!this.#geolocation) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.#geolocation.get((res) => {
|
||||
resolve(res);
|
||||
});
|
||||
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();
|
||||
});
|
||||
|
||||
272
resources/assets/statics/public/tools/map/select.html
Normal file
272
resources/assets/statics/public/tools/map/select.html
Normal 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>
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user