
SpringbootVue3天地图 考勤地点配置考勤围栏选点、画圆与半径怎么配文档地址https://ruoyioffice.com源码1·GitHubhttps://github.com/yuqing2026/ruoyi-office源码2·GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office源码3·Giteehttps://gitee.com/yqzy1688/ruoyi-office微信17156169080备注「RuoYi Office」位置打卡最容易配错的不是班次而是圆心和半径。手填经纬度容易偏一条马路半径写 50 米会把大院门口挡在圈外写 5000 米又等于没围栏。RuoYi Office 在假勤配置里嵌天地图点地图回写经纬度用T.Circle按打卡半径画圈搜索走 LocalSearch失败再走地理编码。App 用球面距离判断在不在圈内迟到早退仍由服务端按班次重算。▲ 左打卡模式 / 经纬 / 半径 / 外勤中选点画圆与搜索兜底右球面距离、内网 WiFi、服务端重算班次引言围栏配不好打卡一定吵考勤规则可以很复杂大小周、节假日、弹性窗口。但员工投诉最多的往往是一句「我都到公司门口了为什么提示不在范围」。痛点只手填经纬度本次配法圆心偏了几十米从百度/高德复制坐标坐标系不对天地图点选WGS-84 落库半径和视觉不一致表单是 200地图不画圈punchRadius变化立刻重画 Circle搜不到楼名只接一种搜索LocalSearch 失败再 geocoder圈外能不能打直接禁打出差全卡死允许外勤则记外勤不允许才拒绝前端报迟到、后台报正常前端传 statusFlag提交不传状态班次由服务端算一句话地图负责把圆心和半径配准客户端负责「在不在圈里」服务端负责「迟到还是早退」。本文按已落地的假勤配置页和双端打卡页来拆不重复整套考勤引擎预生成、节假日、部门继承那篇长文只把天地图这一截讲清楚。和那篇长文的边界可以记成三句话长文管「哪天要上班、几点算迟到、记录怎么预生成」本文管「人站在哪里算在公司」分析中心管「这个月多少人异常」。三套口径叠在同一张打卡结果上但配置入口不是同一个菜单。一、先给出可直接抽取的定义1.1 什么是考勤围栏考勤围栏是部门考勤配置上的一个圆心加一段半径圆心是经纬度半径是米落在hrm_attendance_config。位置打卡模式下员工定位到的点与圆心的球面距离小于等于半径才算在范围内。默认半径 200 米表单限制 505000 米。小于 50 没有操作意义大于 5000 基本失去围栏作用。1.2 什么是位置打卡 / 内网打卡punchMode 1位置打卡必须有经纬度地图可见客户端比距离。punchMode 2内网打卡不比距离可选 WiFi BSSID 白名单。名单为空时能访问系统即可打兼容旧逻辑名单非空时仅 App / 小程序连上指定 WiFi 才能打PC 浏览器读不到 BSSID会被拒绝。两种模式互斥。切到内网后地图和经纬表单项隐藏避免管理员以为还在管围栏。内网模式下 WiFi 行是「SSID 选填 BSSID 必能用来匹配」。匹配键是 BSSID如ac:de:48:00:11:22SSID 只给人看。H5 / PC 读不到网卡地址所以白名单一旦非空这两端会被拒这是写在配置页 Alert 里的预期行为不是定位组件坏了。获取 BSSID 的说明在配置页「如何获取 WiFi 信息」里实施时让现场同事用 App 或系统设置抄物理地址不要让他们填中文 WiFi 名当唯一条件。1.3 什么是外勤打卡外勤是圈外仍然允许打卡、但状态记为外勤的开关。allowFieldPunch 1时客户端弹确认 0时直接提示无法打卡。外勤不是把迟到改成正常班次判定仍走服务端。二、先配哪几项一张表看完打开「人力 → 假勤管理 → 假勤配置」左侧选部门。规则优先本部门没有则沿用最近上级——和围栏无关但选错部门会把圆心配到总部。▲ 打卡模式选「位置打卡」后出现经度、纬度、打卡半径本例圆心 117.138160 / 36.665360半径 500 米允许外勤字段建议怎么配不要这样打卡模式外勤、工地、园区大门用位置纯办公楼内网 PC 用内网两种都开一半经度 / 纬度地图点选精度 6 位从百度地图右键复制后不转换打卡半径大门到工位实测再放宽 20%为图省事填 50 或 5000允许外勤销售、实施外勤多则开全员外勤等于取消围栏WiFi 白名单仅内网模式填 BSSID只填 SSID 不填 BSSID左树是组织不是围栏列表。搜索「研发部门」会过滤树点标题才会加载右侧表单。只展开节点、不触发 Tree 的 select右边会一直停在「请选择左侧部门」。这和列表页点行不是同一套交互。地图在表单下方备注后面点空白处更新标记和圆。大屏要把表单滚到底才能看到圆配半径时建议先改数字再滚下去对一下视觉或把浏览器窗口拉高。▲ 圆心落在园区建筑上蓝色圆为当前半径左下角可见天地图水印与比例尺配完点「保存配置」。只改地图不保存员工打卡仍用库里的旧圆心。三、和别的地图方案怎么选方案适合代价天地图 JS API 4.0国内政务/信创、考勤选点、围栏圆要申请 tk配额自己盯高德 / 百度生态组件多、 commuter 导航熟坐标系和天地图不一致混用必偏只填数字不嵌地图临时演示上线后投诉「门口打不了」后端画围栏多边形不规则厂区产品与采集成本高本系统先做圆本系统选天地图是因为考勤围栏只要「一个圆够用」并且前端已有统一的loadTiandituMapSdk。资产定位、轨迹回放若以后要上应复用同一 Key 和同一套 WGS-84不要再引入第二套底图。历史数据若从百度迁过来库里的点是 BD-09必须先转 WGS-84 再给天地图。工具函数bd09ToWgs84已放在地图工具里迁移脚本用它不要在页面里现场估偏移。四、PC 打卡页圈外长什么样员工端「考勤打卡」左侧是记录右侧是打卡卡片。浏览器拒绝定位时会提示去开启权限此时距离算不出来按钮会走到外勤或不可打取决于是否允许外勤。▲ 右侧卡片显示班次 08:00 / 17:00定位权限被拒绝后提示重新定位主按钮变为外勤打卡这张图说明两件事围栏判断在有坐标之前就会失败——没定位就没有距离isInRange为 false允许外勤时产品把「圈外」设计成可继续而不是静默改成正常打卡。PC 打开了 WiFi 限制时会直接提示去用 App不会假装浏览器能读 BSSID。五、前端Key、选点、画圆、搜索5.1 SDK 只加载一次天地图脚本地址带tk。Key 配在前端环境变量VITE_TIANDITU_MAP_KEY。加载函数若发现window.T已存在就直接 resolve并发调用共用同一个 Promise避免配置页反复挂载时插多条 script。exportconstloadTiandituMapSdk(timeout10_000):Promisevoid{if(window.T){returnPromise.resolve();}if(loadPromise){returnloadPromise;}loadPromisenewPromise((resolve,reject){consttimeoutIdsetTimeout((){loadPromisenull;reject(newError(天地图 SDK 加载超时));},timeout);constscriptdocument.createElement(script);script.srchttps://api.tianditu.gov.cn/api?v4.0tk${import.meta.env.VITE_TIANDITU_MAP_KEY};// load 后检查 window.Terror / 超时则清空 loadPromise 以便重试});returnloadPromise;};Key 暴露在前端是天地图浏览器 SDK 的常态。配额、Referer 限制在控制台配不要把 tk 再抄进后端当「更安全」——围栏坐标的权威在库表不在 Key。5.2 点击地图标记和圆一起换初始化中心默认 116.404 / 39.915无配置时的北京点有部门坐标则用部门的。关闭双击放大避免误触缩放被当成选点。点击后emit(locationChange)父表单写入longitude/latitude。functionupdateMarker(lng:number,lat:number){if(!state.map)return;constlnglatnewwindow.T.LngLat(lng,lat);if(state.mapMarker)state.map.removeOverLay(state.mapMarker);if(state.circle)state.map.removeOverLay(state.circle);state.mapMarkernewwindow.T.Marker(lnglat);state.map.addOverLay(state.mapMarker);constradiusprops.punchRadius||200;state.circlenewwindow.T.Circle(lnglat,radius,{color:#1890ff,weight:2,opacity:0.6,fillColor:#1890ff,fillOpacity:0.15,});state.map.addOverLay(state.circle);state.map.centerAndZoom(lnglat,15);}watch(punchRadius)会在半径输入变化时重画。管理员拖半径数字圆应该立刻变大变小这是「配得准不准」的唯一视觉反馈。5.3 搜索LocalSearch 失败再 geocoder关键字先走T.LocalSearch。POI 结构不统一要同时兼容lonlat/point/lnglat。都解析失败或结果为空再请求https://api.tianditu.gov.cn/geocoder?ds{keyWord}tk...返回的location.lon/location.lat同样回写标记。两级都失败才提示换更具体的词。不要把搜索失败理解成 Key 一定错了——也可能是词太宽只输入「公司」。5.4 卸载时销毁地图配置页会切换部门、反复挂载地图组件。onBeforeUnmount调用map.destroy()避免残留瓦片监听。SDK Promise 可以保留地图实例不要跨部门复用。经纬度输入框精度 6 位和点选回写一致。有人会在框里手动改小数——可以但改完必须触发一次updateMarkerwatch 经纬否则地图针还扎在旧点。推荐流程是搜索或点击 → 看圆 → 必要时微调半径 → 保存。不要先手填再期望地图自己飞过去却忘了 watch。默认无坐标时地图停在 116.404、39.915那是占位中心不是你们公司。新部门第一次配务必点到真实楼否则全员按「北京某个点」比距离外勤会铺开。六、客户端球面距离不信自己的迟到标记6.1 距离公式PC / App 同一套地球半径取 6_371_000 米Haversine 公式。PC 打卡页的data.ts和 UniApp 打卡页各有一份数字必须一致不要一边 6371 公里一边 6378。exportfunctioncalcDistance(lat1:number,lng1:number,lat2:number,lng2:number,):number{constR6_371_000;constdLat((lat2-lat1)*Math.PI)/180;constdLng((lng2-lng1)*Math.PI)/180;constaMath.sin(dLat/2)*Math.sin(dLat/2)Math.cos((lat1*Math.PI)/180)*Math.cos((lat2*Math.PI)/180)*Math.sin(dLng/2)*Math.sin(dLng/2);constc2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));returnR*c;}isInRange无配置则 false内网模式直接 true位置模式比较distance (punchRadius || 200)。6.2 提交时不传迟到早退打卡请求带punchType、isOvertime、isFieldPunch。注释写得很直白状态由后端规则引擎计算前端不再传statusFlag。返回后再根据checkInStatus/checkOutStatus提示「已记为迟到」。圈外且允许外勤Modal.confirm后executePunch(..., true)。圈外且不允许warning不发请求。6.3 App 还要处理坐标系和权限文案手机定位可能是 GCJ-02。UniApp 页对国内坐标做了转换后再和库里的 WGS-84 比距离避免「地图上看在圈里、App 显示 200 米外」。权限拒绝、超时、定位服务未开文案要分开不要统一成「定位失败」。国内点的转换不要抄一套不完整的偏移。页面里对「是否在中国境内」先判断境外坐标不做 GCJ 平移。转换常量和前端bd09ToWgs84用的椭球参数应能对上避免 PC 配置页和 App 各算各的。functiononLocationSuccess(latitude:number,longitude:number){userLatitude.valuelatitude userLongitude.valuelongitude distance.valuecalcDistance(latitude,longitude,punchInfo.value.latitude!,punchInfo.value.longitude!,)locationLoading.valuefalselocationError.value}定位成功只更新距离不在这里改打卡状态。状态等用户按下按钮、服务端返回后再提示迟到。圈外提示「未在考勤范围内距离 xx可外勤打卡」或「不可打卡」距离超过 1000 米改成 km方便实施人员判断是圆心错了还是人真的在路上。非工作日打卡会先确认「记为加班」。这和围栏无关但和按钮文案叠在一起时实施容易以为是定位问题。排查顺序建议先看模式位置还是内网再看有没有定位再看距离和半径最后才看班次与是否工作日。七、后端存圆不算圆配置保存在hrm_attendance_configlongitude、latitude用BigDecimalpunchRadius用整数米punchMode、allowFieldPunch、wifiListJSON并列。保存 VO 要求deptId非空位置模式下前端还应校验经纬已点选。保存走POST /hrm/attendance-config/save按部门读走GET /hrm/attendance-config/get?deptId。删除是按部门清规则清掉后该部门回到「继承上级」。不要把删除理解成删部门。打卡信息GET /hrm/attendance-punch/info给客户端的是生效后的规则本级或继承来的和配置页正在编辑的草稿不是同一回事。配置页改了没保存打卡页仍是旧圆。执行打卡POST /hrm/attendance-punch/do建议只带这些字段谁填说明punchType前端1 签到 2 签退isOvertime前端确认框非工作日点确认后为 trueisFieldPunch前端确认框圈外且允许外勤wifiBssid仅 App内网白名单模式statusFlag不要再传已废弃后端按班次算我的记录分页仍走my-page和右侧卡片不是同一请求。卡片看的是「今天能不能打」列表看的是历史行。打卡信息接口把圆心、半径、模式、是否允许外勤、WiFi 名单回给客户端供画圈和比距离。服务端mobilePunch/ 管理端打卡在写记录时接收前端算好的外勤标记迟到早退按当天生效班次重算不采信客户端自报的正常/迟到。这是刻意分工职责放哪原因选点、画圆、搜地址Vue 配置页 天地图要视觉反馈距离是否 ≤ 半径PC / App 客户端要立刻提示外勤是否迟到早退服务端班次引擎防改请求刷正常内网 WiFi 是否命中App 读 BSSID服务端可再校验名单PC 读不到就拒绝不要把 Haversine 再在 Java 里算一遍当「更权威」除非你要做服务端强制围栏防伪造定位。若以后要防伪造应单独做服务端距离校验并写进接口文档而不是默默改掉现有「前端判圈、后端判班次」。八、部门继承与多圆心假勤配置顶部黄条写明本单位有规则用本单位没有则用最近上级。围栏同样继承。子公司若只改班次、忘了改圆心员工会按总部园区打卡门口全部外勤。怎么配总部配一个大半径园区分公司各自点自己的楼半径按大门到最远工位不要在一个部门上幻想两个圆心——当前模型就是一个圆。两个办公区应拆部门或走外勤而不是把半径拉到 10 公里。大小周、节假日方案、精确/弹性窗口和围栏正交圆只管空间班次管时间。配围栏时不要顺手把上班时间改掉除非你本来就要改班次。半径实测可以按这个顺序站在员工最常走的大门内侧打一次定位看距离再站在马路对面看距离。内侧应明显小于半径外侧应大于半径。500 米适合科技园多栋楼共用一个大门临街店面 80150 米更常见。把半径当「防代打」的唯一手段不够代打还能把定位伪造成圆心围栏只解决「人确实不在附近还显示正常」的一部分。九、数据结构表hrm_attendance_config与围栏相关的列列类型说明dept_idbigint部门一条规则longitude / latitudedecimal圆心WGS-84punch_radiusint米默认 200punch_modeint1 位置 2 内网allow_field_punchint0/1wifi_listjson仅模式 2BSSID 列表打卡信息 VO 会把以上字段外加今日班次时刻发给客户端。打卡记录表记的是签到签退时刻与状态不存每次的经纬度——若合规要留轨迹那是另一张表不要塞进配置。设计要点经纬用小数而不是字符串避免116.40和116.404比较出坑半径用整数米和T.Circle第二参数单位一致WiFi 用 Jackson 处理 List空列表与 null 在「不校验」语义上要一致。十、技术亮点总结设计要点实现方式价值点选即回写地图 click → emit → 表单少手填少偏马路半径可视化T.Circle watch 半径配 200 还是 500 一眼能看搜索双通道LocalSearch geocoder楼名、路名都能落到点SDK 单例Promise 复用切部门不重复插脚本坐标系工具BD-09 → WGS-84迁百度历史点不漂模式互斥位置出地图内网出 WiFi管理员不会配一套废字段圈外策略外勤开关出差不死门禁仍在班次权威提交不传 statusFlag防前端刷正常双端同一公式R6371000 HaversinePC / App 距离一致PC 承认读不到 WiFi直接引导 App不假装 H5 能取 BSSID十一、快速体验在线演示https://ruoyioffice.com/web/账号 admin / admin123推荐路径打开「假勤配置」搜索并点「研发部门」或你环境里已有规则的部门确认打卡模式为位置打卡记下经纬和半径滚到地图看蓝圆是否罩住目标楼改半径看圆是否跟着变点附近再看经纬是否回写保存后打开「考勤打卡」看右侧卡片浏览器若拒定位会出现重新定位 / 外勤提示用手机 App 打开同一菜单对比距离文案是否和 PC 同一数量级把模式临时改成内网、加一条 WiFi确认 PC 提示去 App演示完改回避免全员打不了对照分析中心人力资源看板围栏配的是空间规则异常人数仍来自打卡结果两页不要对同一个「半径」数字。本地启动后端 48080前端pnpm dev:antd端口 5800。.env中VITE_TIANDITU_MAP_KEY必须有值否则地图容器是空白框容易误判成组件没挂上。源码仓库GitHubhttps://github.com/yuqing2026/ruoyi-office GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office Giteehttps://gitee.com/yqzy1688/ruoyi-office保存前可以按这张清单过一遍比上线后再改圆心便宜检查项通过标准部门点对了右侧标题是本部门不是误点的上级模式是位置打卡能看到经纬、半径和地图不是 WiFi 行圆心是本园区地图针不在默认北京点圆罩得住大门和最远工位改半径后圆跟着变外勤策略明确销售/实施开坐班关不要默认全开已点保存打卡信息接口返回的经纬与页面一致App 试距离大门内侧小于半径马路对面大于半径版本锚点本文描述的是 Vue3 天地图 JavaScript API 4.0 部门级圆形围栏 PC/UniApp 球面距离这一档。不规则多边形、服务端强制验距、轨迹回放不在本文范围。Key 轮换时只改环境变量并重启前端即可库里的经纬不用动。配额打满时地图会白围栏数据仍在员工端仍按旧坐标比距离——这和「没配圆心」是两种故障不要一起重配。结语考勤围栏的核心不是把地图嵌进表单显得完整而是让管理员用眼睛对准圆心和半径让员工端用同一套距离说话让服务端继续只对班次负责。天地图负责底图和选点库表负责权威坐标外勤开关负责「圈外还能不能打」。坐标系一旦混用所有半径都是错的。同一套选点组件还可以用在资产存放位置、会议外出地点只要坚持一个 Key、一种坐标、圆或点不要各写各的加载器。相关阅读建议双端考勤打卡长文看班次、预生成和部门继承分析中心那篇看异常人数怎么汇总。围栏是空间规则不是又一张统计图。欢迎在评论区说说你们半径一般定多少米以及有没有用过两个办公区共用一个部门的坑。若你们必须上多边形围栏也可以说说采集成本是否真的值得。常见问题FAQ天地图和高德、百度怎么选考勤围栏一定要用天地图吗围栏只要一个圆时用哪家底图都能做。RuoYi Office 统一天地图是为了和国内信创场景、已有 Key 加载器一致。不要在同一套经纬上混用高德点——坐标系不同圆会整体平移。地图是空白的第一步查什么先看VITE_TIANDITU_MAP_KEY是否有值、前端是否重启过再看浏览器控制台是否 SDK 超时或 403。Key 错时容器在、瓦片不来。部门若是内网模式地图根本不渲染先看打卡模式是不是位置打卡。搜索不到地址是不是 Key 废了不一定。先换更具体的关键词带市、路、大厦名。LocalSearch 无 POI 时会走 geocoder两级都空再怀疑配额或网络。解析 POI 时要兼容多种坐标字段名。为什么 App 显示在圈外、地图上看在圈里常见原因手机给的是 GCJ-02库里是 WGS-84没做转换或部门继承了总部圆心。先对一下打卡信息接口返回的经纬是否就是配置页上那一对再看 App 是否走了国内坐标转换。外勤打卡会不会把迟到改成正常不会。外勤只表示空间在圈外仍允许打。迟到早退由服务端按班次重算。前端提交不带 statusFlag。半径改了地图圆不动AttendanceMap必须 watchpunchRadius并调用updateMarker。若二开时只改了 InputNumber、没把半径传给地图组件圆会停在旧值。保存后员工端用的是库里的半径预览圆和落库值还要点一次保存才对齐。PC 开启 WiFi 限制后为什么打不了浏览器读不到 BSSID。这是预期不是围栏 bug。位置模式不依赖 WiFi内网模式加了白名单就只能走 App / 小程序。想要体验 RuoYi Office 的强大功能在线演示https://ruoyioffice.com/web/账号 admin / admin123源码仓库GitHubhttps://github.com/yuqing2026/ruoyi-office GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office Giteehttps://gitee.com/yqzy1688/ruoyi-office技术咨询添加微信17156169080备注「RuoYi Office」⭐如果觉得不错请给个 Star 支持一下