mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-14 04:32:49 +00:00
perf: 添加定位签到
This commit is contained in:
parent
2fc329a403
commit
3b9c9872ca
@ -1362,6 +1362,63 @@ class DialogController extends AbstractController
|
|||||||
], $botUser->userid, true);
|
], $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. 获取消息阅读情况
|
* @api {get} api/dialog/msg/readlist 27. 获取消息阅读情况
|
||||||
*
|
*
|
||||||
|
|||||||
@ -407,6 +407,9 @@ class SystemController extends AbstractController
|
|||||||
'face_upload',
|
'face_upload',
|
||||||
'face_remark',
|
'face_remark',
|
||||||
'face_retip',
|
'face_retip',
|
||||||
|
'locat_remark',
|
||||||
|
'locat_bd_lbs_key',
|
||||||
|
'locat_bd_lbs_point', // 格式:{"lng":116.404, "lat":39.915, "radius":500}
|
||||||
'manual_remark',
|
'manual_remark',
|
||||||
'modes',
|
'modes',
|
||||||
'key',
|
'key',
|
||||||
@ -422,9 +425,21 @@ class SystemController extends AbstractController
|
|||||||
if (!$botUser) {
|
if (!$botUser) {
|
||||||
return Base::retError('创建签到机器人失败');
|
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']) {
|
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));
|
$setting = Base::setting('checkinSetting', Base::newTrim($all));
|
||||||
} else {
|
} else {
|
||||||
@ -444,6 +459,8 @@ class SystemController extends AbstractController
|
|||||||
$setting['face_upload'] = $setting['face_upload'] ?: 'close';
|
$setting['face_upload'] = $setting['face_upload'] ?: 'close';
|
||||||
$setting['face_remark'] = $setting['face_remark'] ?: Doo::translate('考勤机');
|
$setting['face_remark'] = $setting['face_remark'] ?: Doo::translate('考勤机');
|
||||||
$setting['face_retip'] = $setting['face_retip'] ?: 'open';
|
$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['manual_remark'] = $setting['manual_remark'] ?: Doo::translate('手动签到');
|
||||||
$setting['time'] = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
|
$setting['time'] = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
|
||||||
$setting['advance'] = intval($setting['advance']) ?: 120;
|
$setting['advance'] = intval($setting['advance']) ?: 120;
|
||||||
|
|||||||
@ -408,7 +408,7 @@ class IndexController extends InvokeController
|
|||||||
], 'inline');
|
], 'inline');
|
||||||
}
|
}
|
||||||
// EEUI App 直接在线预览查看
|
// EEUI App 直接在线预览查看
|
||||||
if (str_contains($userAgent, 'eeui') && Base::judgeClientVersion("0.34.47")) {
|
if (Base::isEEUIApp() && Base::judgeClientVersion("0.34.47")) {
|
||||||
if ($browser === 'safari-mobile') {
|
if ($browser === 'safari-mobile') {
|
||||||
$redirectUrl = Base::fillUrl($path);
|
$redirectUrl = Base::fillUrl($path);
|
||||||
return <<<EOF
|
return <<<EOF
|
||||||
|
|||||||
@ -91,63 +91,86 @@ class UserBot extends AbstractModel
|
|||||||
*/
|
*/
|
||||||
public static function quickMsgs($email)
|
public static function quickMsgs($email)
|
||||||
{
|
{
|
||||||
return match ($email) {
|
switch ($email) {
|
||||||
'check-in@bot.system' => [
|
case 'check-in@bot.system':
|
||||||
[
|
$menu = [
|
||||||
'key' => 'checkin',
|
/*[
|
||||||
'label' => Doo::translate('我要打卡')
|
'key' => 'it',
|
||||||
], /*[
|
'label' => Doo::translate('IT资讯')
|
||||||
'key' => 'it',
|
], [
|
||||||
'label' => Doo::translate('IT资讯')
|
'key' => '36ke',
|
||||||
], [
|
'label' => Doo::translate('36氪')
|
||||||
'key' => '36ke',
|
], [
|
||||||
'label' => Doo::translate('36氪')
|
'key' => '60s',
|
||||||
], [
|
'label' => Doo::translate('60s读世界')
|
||||||
'key' => '60s',
|
], [
|
||||||
'label' => Doo::translate('60s读世界')
|
'key' => 'joke',
|
||||||
], [
|
'label' => Doo::translate('开心笑话')
|
||||||
'key' => 'joke',
|
], [
|
||||||
'label' => Doo::translate('开心笑话')
|
'key' => 'soup',
|
||||||
], [
|
'label' => Doo::translate('心灵鸡汤')
|
||||||
'key' => 'soup',
|
]*/
|
||||||
'label' => Doo::translate('心灵鸡汤')
|
];
|
||||||
]*/
|
$setting = Base::setting('checkinSetting');
|
||||||
],
|
if ($setting['open'] !== 'open') {
|
||||||
'anon-msg@bot.system' => [
|
return $menu;
|
||||||
[
|
}
|
||||||
'key' => 'help',
|
if (in_array('locat', $setting['modes']) && Base::isEEUIApp()) {
|
||||||
'label' => Doo::translate('使用说明')
|
$menu[] = [
|
||||||
], [
|
'key' => 'locat-checkin',
|
||||||
'key' => 'privacy',
|
'label' => $setting['locat_remark'] ?: Doo::translate('定位签到'),
|
||||||
'label' => Doo::translate('隐私说明')
|
'config' => [
|
||||||
],
|
'key' => $setting['locat_bd_lbs_key'],
|
||||||
],
|
'lng' => $setting['locat_bd_lbs_point']['lng'],
|
||||||
'bot-manager@bot.system' => [
|
'lat' => $setting['locat_bd_lbs_point']['lat'],
|
||||||
[
|
'radius' => $setting['locat_bd_lbs_point']['radius'],
|
||||||
'key' => '/help',
|
]
|
||||||
'label' => Doo::translate('帮助指令')
|
];
|
||||||
], [
|
}
|
||||||
'key' => '/api',
|
if (in_array('manual', $setting['modes'])) {
|
||||||
'label' => Doo::translate('API接口文档')
|
$menu[] = [
|
||||||
], [
|
'key' => 'manual-checkin',
|
||||||
'key' => '/list',
|
'label' => $setting['manual_remark'] ?: Doo::translate('手动打卡')
|
||||||
'label' => Doo::translate('我的机器人')
|
];
|
||||||
],
|
}
|
||||||
],
|
return $menu;
|
||||||
'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 => [],
|
|
||||||
};
|
|
||||||
|
|
||||||
|
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());
|
Cache::put("UserBot::checkinBotQuickMsg:{$userid}", "yes", Carbon::now()->addSecond());
|
||||||
//
|
//
|
||||||
if ($command === 'checkin') {
|
if ($command === 'manual-checkin') {
|
||||||
$setting = Base::setting('checkinSetting');
|
$setting = Base::setting('checkinSetting');
|
||||||
if ($setting['open'] !== 'open') {
|
if ($setting['open'] !== 'open') {
|
||||||
return '暂未开启签到功能。';
|
return '暂未开启签到功能。';
|
||||||
@ -182,7 +205,9 @@ class UserBot extends AbstractModel
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 签到机器人签到
|
* 签到机器人签到
|
||||||
* @param $mac
|
* @param mixed $mac
|
||||||
|
* - 多个使用,分隔
|
||||||
|
* - 支持:mac地址、userid、checkin-userid
|
||||||
* @param $time
|
* @param $time
|
||||||
* @param bool $alreadyTip 签到过是否提示
|
* @param bool $alreadyTip 签到过是否提示
|
||||||
* @return string|null 返回string表示错误信息,返回null表示签到成功
|
* @return string|null 返回string表示错误信息,返回null表示签到成功
|
||||||
|
|||||||
@ -957,6 +957,7 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
/**
|
/**
|
||||||
* 发送消息、修改消息
|
* 发送消息、修改消息
|
||||||
* @param string $action 动作
|
* @param string $action 动作
|
||||||
|
* - null:发送消息
|
||||||
* - reply-98:回复消息ID=98
|
* - reply-98:回复消息ID=98
|
||||||
* - update-99:更新消息ID=99(标记修改)
|
* - update-99:更新消息ID=99(标记修改)
|
||||||
* - change-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'])) {
|
if (in_array($msg['ext'], ['jpg', 'jpeg', 'webp', 'png', 'gif'])) {
|
||||||
$mtype = 'image';
|
$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) {
|
if ($push_silence === null) {
|
||||||
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
|
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
|
||||||
|
|||||||
@ -2039,6 +2039,16 @@ class Base
|
|||||||
return $platform;
|
return $platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是App移动端
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isEEUIApp()
|
||||||
|
{
|
||||||
|
$userAgent = strtolower(Request::server('HTTP_USER_AGENT'));
|
||||||
|
return str_contains($userAgent, 'kuaifan_eeui');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回根据距离sql排序语句
|
* 返回根据距离sql排序语句
|
||||||
* @param $lat
|
* @param $lat
|
||||||
|
|||||||
@ -384,7 +384,7 @@ LDAP 用户禁止修改邮箱
|
|||||||
匿名消息
|
匿名消息
|
||||||
系统管理员
|
系统管理员
|
||||||
我要签到
|
我要签到
|
||||||
我要打卡
|
手动打卡
|
||||||
关键词不能为空
|
关键词不能为空
|
||||||
LICENSE 格式错误
|
LICENSE 格式错误
|
||||||
LICENSE 保存失败
|
LICENSE 保存失败
|
||||||
@ -714,3 +714,5 @@ webhook地址最长仅支持255个字符。
|
|||||||
动画表情
|
动画表情
|
||||||
每日开心:(*)
|
每日开心:(*)
|
||||||
心灵鸡汤:(*)
|
心灵鸡汤:(*)
|
||||||
|
|
||||||
|
定位签到
|
||||||
|
|||||||
@ -22967,18 +22967,6 @@
|
|||||||
"id": "Saya ingin check-in",
|
"id": "Saya ingin check-in",
|
||||||
"ru": "Я хочу зарегистрироваться"
|
"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": "关键词不能为空",
|
"key": "关键词不能为空",
|
||||||
"zh": "",
|
"zh": "",
|
||||||
@ -26027,4 +26015,4 @@
|
|||||||
"id": "Lokasi Gagal",
|
"id": "Lokasi Gagal",
|
||||||
"ru": "Не удалось определить местоположение"
|
"ru": "Не удалось определить местоположение"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -17,7 +17,14 @@
|
|||||||
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
|
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div id="address-list" class="address-list">
|
||||||
<h3 id="address-label"></h3>
|
<h3 id="address-label"></h3>
|
||||||
<ul id="poi-list"></ul>
|
<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() {
|
constructor() {
|
||||||
this.map = null;
|
this.constructor.init();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
static async init() {
|
||||||
// 先初始化参数
|
while (typeof requireModuleJs !== "function") {
|
||||||
this.initParams();
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
// 如果没有 key,直接返回
|
|
||||||
if (!this.config.key) {
|
|
||||||
console.error('未提供百度地图 API Key');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待地图 JS 加载完成
|
|
||||||
await this.loadBaiduMapScript();
|
|
||||||
// 初始化地图
|
|
||||||
this.initMap();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载百度地图失败:', error);
|
|
||||||
}
|
}
|
||||||
|
this.#eeui = requireModuleJs("eeui");
|
||||||
|
this.#geolocation = requireModuleJs("eeui/geolocation");
|
||||||
}
|
}
|
||||||
|
|
||||||
initParams() {
|
static async setVariate(key, value) {
|
||||||
// 获取当前URL的查询参数
|
while (!this.#eeui) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
// 遍历 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;
|
|
||||||
}
|
}
|
||||||
|
await this.#eeui.setVariate(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
initMap() {
|
static async getLocation() {
|
||||||
// 初始化地图
|
while (!this.#geolocation) {
|
||||||
this.map = new BMap.Map('map-container');
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
}
|
||||||
// 创建定位控件
|
return new Promise(resolve => {
|
||||||
const locationControl = new BMap.GeolocationControl({
|
this.#geolocation.get((res) => {
|
||||||
anchor: BMAP_ANCHOR_BOTTOM_RIGHT,
|
resolve(res);
|
||||||
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() {
|
static closePage() {
|
||||||
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() {
|
|
||||||
try {
|
try {
|
||||||
// 方法1: 如果是在 eeui 环境中
|
// 方法1: 如果是在 eeui 环境中
|
||||||
if (typeof requireModuleJs === "function") {
|
if (this.#eeui) {
|
||||||
const eeui = requireModuleJs("eeui");
|
this.#eeui.closePage();
|
||||||
eeui.closePage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法2: 如果是从其他页面打开的,可以关闭当前窗口
|
// 方法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) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 如果已经加载过,直接返回
|
// 如果已经加载过,直接返回
|
||||||
if (window.BMap) {
|
if (window.BMap) {
|
||||||
@ -384,7 +558,7 @@ class LocationPicker {
|
|||||||
// 创建script标签
|
// 创建script标签
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.type = 'text/javascript';
|
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 = () => {
|
window.initBaiduMap = () => {
|
||||||
@ -405,5 +579,6 @@ class LocationPicker {
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.theme-dark {
|
html.theme-dark {
|
||||||
@ -67,11 +68,40 @@ body {
|
|||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
#map-container {
|
#map-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 240px;
|
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 {
|
.address-list {
|
||||||
display: none;
|
display: none;
|
||||||
min-height: 300px;
|
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
|
* @param item
|
||||||
*/
|
*/
|
||||||
sendQuick(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>`)
|
this.sendMsg(`<p><span data-quick-key="${item.key}">${item.label}</span></p>`)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -71,13 +71,13 @@
|
|||||||
<CheckboxGroup v-model="formData.modes">
|
<CheckboxGroup v-model="formData.modes">
|
||||||
<Checkbox label="face">{{$L('人脸签到')}}</Checkbox>
|
<Checkbox label="face">{{$L('人脸签到')}}</Checkbox>
|
||||||
<Checkbox label="auto">{{$L('WiFi签到')}}</Checkbox>
|
<Checkbox label="auto">{{$L('WiFi签到')}}</Checkbox>
|
||||||
|
<Checkbox label="locat">{{$L('定位签到')}}</Checkbox>
|
||||||
<Checkbox label="manual">{{$L('手动签到')}}</Checkbox>
|
<Checkbox label="manual">{{$L('手动签到')}}</Checkbox>
|
||||||
<Checkbox v-if="false" label="location">{{$L('定位签到')}}</Checkbox>
|
|
||||||
</CheckboxGroup>
|
</CheckboxGroup>
|
||||||
<div v-if="formData.modes.includes('face')" class="form-tip">{{$L('人脸签到')}}: {{$L('通过人脸识别机签到')}}</div>
|
<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('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('manual')" class="form-tip">{{$L('手动签到')}}: {{$L('通过在签到打卡机器人发送指令签到')}}</div>
|
||||||
<div v-if="formData.modes.includes('location')" class="form-tip">{{$L('定位签到')}}: {{$L('通过在签到打卡机器人发送位置签到')}}</div>
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -117,6 +117,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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')">
|
<template v-if="formData.modes.includes('manual')">
|
||||||
<div class="block-setting-space"></div>
|
<div class="block-setting-space"></div>
|
||||||
<div class="block-setting-box">
|
<div class="block-setting-box">
|
||||||
@ -145,17 +168,49 @@
|
|||||||
:size="1380">
|
:size="1380">
|
||||||
<TeamManagement v-if="allUserShow" checkin-mode/>
|
<TeamManagement v-if="allUserShow" checkin-mode/>
|
||||||
</DrawerOverlay>
|
</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>
|
</div>
|
||||||
</template>
|
</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>
|
<script>
|
||||||
import DrawerOverlay from "../../../../components/DrawerOverlay";
|
import DrawerOverlay from "../../../../components/DrawerOverlay";
|
||||||
import TeamManagement from "../../components/TeamManagement";
|
import TeamManagement from "../../components/TeamManagement";
|
||||||
import CheckinExport from "../../components/CheckinExport";
|
import CheckinExport from "../../components/CheckinExport";
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
|
import IFrame from "../../components/IFrame.vue";
|
||||||
export default {
|
export default {
|
||||||
name: "SystemCheckin",
|
name: "SystemCheckin",
|
||||||
components: {CheckinExport, TeamManagement, DrawerOverlay},
|
components: {IFrame, CheckinExport, TeamManagement, DrawerOverlay},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loadIng: 0,
|
loadIng: 0,
|
||||||
@ -169,11 +224,17 @@ export default {
|
|||||||
face_remark: '',
|
face_remark: '',
|
||||||
face_retip: '',
|
face_retip: '',
|
||||||
manual_remark: '',
|
manual_remark: '',
|
||||||
|
locat_remark: '',
|
||||||
|
locat_bd_lbs_point: {},
|
||||||
},
|
},
|
||||||
ruleData: {},
|
ruleData: {},
|
||||||
|
|
||||||
allUserShow: false,
|
allUserShow: false,
|
||||||
exportShow: false,
|
exportShow: false,
|
||||||
|
|
||||||
|
bdSelectShow: false,
|
||||||
|
bdSelectPoint: {},
|
||||||
|
bdSelectUrl: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -227,6 +288,39 @@ export default {
|
|||||||
this.$refs.cmd.focus({cursor:'all'});
|
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>
|
</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",
|
selectclose: "true",
|
||||||
channel: $A.randomString(6)
|
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', {
|
dispatch('openAppChildPage', {
|
||||||
pageType: 'app',
|
pageType: 'app',
|
||||||
pageTitle: params.title,
|
pageTitle: params.title,
|
||||||
|
|||||||
@ -17,7 +17,14 @@
|
|||||||
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
|
<input type="text" id="search-input" placeholder="" enterkeyhint="search">
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div id="address-list" class="address-list">
|
||||||
<h3 id="address-label"></h3>
|
<h3 id="address-label"></h3>
|
||||||
<ul id="poi-list"></ul>
|
<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() {
|
constructor() {
|
||||||
this.map = null;
|
this.constructor.init();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
static async init() {
|
||||||
// 先初始化参数
|
while (typeof requireModuleJs !== "function") {
|
||||||
this.initParams();
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
// 如果没有 key,直接返回
|
|
||||||
if (!this.config.key) {
|
|
||||||
console.error('未提供百度地图 API Key');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待地图 JS 加载完成
|
|
||||||
await this.loadBaiduMapScript();
|
|
||||||
// 初始化地图
|
|
||||||
this.initMap();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载百度地图失败:', error);
|
|
||||||
}
|
}
|
||||||
|
this.#eeui = requireModuleJs("eeui");
|
||||||
|
this.#geolocation = requireModuleJs("eeui/geolocation");
|
||||||
}
|
}
|
||||||
|
|
||||||
initParams() {
|
static async setVariate(key, value) {
|
||||||
// 获取当前URL的查询参数
|
while (!this.#eeui) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
// 遍历 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;
|
|
||||||
}
|
}
|
||||||
|
await this.#eeui.setVariate(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
initMap() {
|
static async getLocation() {
|
||||||
// 初始化地图
|
while (!this.#geolocation) {
|
||||||
this.map = new BMap.Map('map-container');
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
}
|
||||||
// 创建定位控件
|
return new Promise(resolve => {
|
||||||
const locationControl = new BMap.GeolocationControl({
|
this.#geolocation.get((res) => {
|
||||||
anchor: BMAP_ANCHOR_BOTTOM_RIGHT,
|
resolve(res);
|
||||||
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() {
|
static closePage() {
|
||||||
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() {
|
|
||||||
try {
|
try {
|
||||||
// 方法1: 如果是在 eeui 环境中
|
// 方法1: 如果是在 eeui 环境中
|
||||||
if (typeof requireModuleJs === "function") {
|
if (this.#eeui) {
|
||||||
const eeui = requireModuleJs("eeui");
|
this.#eeui.closePage();
|
||||||
eeui.closePage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法2: 如果是从其他页面打开的,可以关闭当前窗口
|
// 方法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) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 如果已经加载过,直接返回
|
// 如果已经加载过,直接返回
|
||||||
if (window.BMap) {
|
if (window.BMap) {
|
||||||
@ -384,7 +558,7 @@ class LocationPicker {
|
|||||||
// 创建script标签
|
// 创建script标签
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.type = 'text/javascript';
|
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 = () => {
|
window.initBaiduMap = () => {
|
||||||
@ -405,5 +579,6 @@ class LocationPicker {
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.theme-dark {
|
html.theme-dark {
|
||||||
@ -67,11 +68,40 @@ body {
|
|||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
#map-container {
|
#map-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 240px;
|
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 {
|
.address-list {
|
||||||
display: none;
|
display: none;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user