ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

uniapp+vue开发学生考勤签到请假系统:技术选型、核心实现与踩坑记录

uniapp+vue开发学生考勤签到请假系统:技术选型、核心实现与踩坑记录 1. 选型前想清楚的事为什么考勤系统用uniappvue来做1.1 做这套系统之前我面对的是什么局面先说背景。我接到的是一个典型的高校/中职场景改造需求原来学生考勤靠纸质签到表班长课间拿着表格挨个打勾老师课后收上来人工统计月底再汇总旷课、迟到、请假记录。这套流程的问题不用我多说了——统计靠肉眼、追溯靠翻纸、请假审批靠微信聊天记录老师和辅导员经常为“这个学生到底有没有来上课”扯皮。所以当需求方找到我时他们提了几点要求学生端必须能在手机上完成签到老师端要有考勤报表请假审批不能再用聊天记录最好能在微信里打开免安装。这时技术选型就摆在面前了。一开始也考虑过做纯微信小程序原生开发或者直接用H5套壳做安卓App但后来综合评估下来还是选了uniapp vue这套组合。选uniapp的核心原因很直接一次编写多端运行。同一个代码仓库既能编译成微信小程序也能打包成安卓APK还能跑H5。考勤系统这种业务学生群体分布很杂有的习惯用微信小程序有的学校要求必须有独立App如果用原生安卓写一套、小程序再写一套双倍开发量不说后面改一个签到规则要同步改两个端维护成本直接翻倍。uniapp的Vue语法对我来说足够熟悉拿vue2/vue3那套组件的思路直接上手开发效率比原生高不少。还有一个容易被忽略的选型理由团队的技能复用。如果你是一个Vue背景的前端选了uniapp基本等于零成本迁移。安卓原生开发需要Java/Kotlin那套工具链小程序原生又要学WXML/WXSS那套语法而uniapp允许你用标准的Vue单文件组件写业务逻辑框架帮你做平台适配。这套系统的核心价值是业务逻辑而不是炫技所以快速交付才是第一位的。1.2 技术栈的具体构成与版本选型确定了uniapp vue之后还要把具体的技术栈定下来这里我踩过版本匹配的坑后面详细说。先说最终确定的方案技术项我的选择备注前端框架uniapp vue2选vue2而不是vue3主要是因为当时项目依赖的部分插件还没完全适配vue3UI组件库uView 2.0表单、弹窗、时间选择器直接用现成组件省了很多样式工作状态管理Vuex全局存用户信息、token、当前定位地图与定位高德地图uni-app插件考勤签到必须校验地理位置用官方插件比原生SDK省事图表uCharts考勤统计报表需要用柱状图/饼图展示出勤率后端接口统一RESTful风格我这边是前端为主后端接口由合作方提供前端只做联调打包方式Android离线打包 微信小程序云端编译离线打包是后来踩坑后换的方案这里想提醒一句uniapp的版本更新非常频繁HBuilderX的版本、vue版本、插件版本三者之间经常出现兼容性问题。选型的核心原则是求稳不求新——用团队熟悉的、社区生态成熟的组合而不是追最新版本。我们当初就把大量时间花在“升级到vue3结果插件不兼容又降级回vue2”的痛苦上这个教训后面会单独写一节。2. 系统功能地图签到、请假、考勤统计三块主线的梳理2.1 学生端核心流程从打开应用签到到查看考勤记录这套系统的业务其实不复杂核心就是围绕“学生—课程—考勤状态”三个实体的流转。学生端的完整流程是这样的登录后进入今日课程列表每门课展示当前时间和签到状态。点击“签到”按钮系统先调定位接口获取学生当前经纬度再把这个经纬度与老师预先设置的允许签到范围比如以教室坐标为中心、半径200米做比对在范围内才允许签到。签到成功后在列表里标记“已签到”并把时间戳记录下来。但业务细节比这多一点。比如签到窗口怎么设计我们实际用的方案是“上课前15分钟到上课后10分钟”内可以签“正常”超过这个窗口签到就算“迟到”完全超过窗口则标记“未签”。这个逻辑在代码里就是几个时间比较// 拿到课程开始时间计算签到窗口 let courseStart new Date(course.startTime).getTime() let currentTime new Date().getTime() let offSet currentTime - courseStart // 提前15分钟 ~ 开课后10分钟内正常 if (offSet -15 * 60 * 1000 offSet 10 * 60 * 1000) { attendanceStatus 正常 } // 迟到窗口开课后10分钟 ~ 20分钟 else if (offSet 10 * 60 * 1000 offSet 20 * 60 * 1000) { attendanceStatus 迟到 }这种时间窗口逻辑建议放到前端做一部分、后端兜底校验。前端做了可以让学生秒级得到反馈但后端也必须重新校验时间防止有人改系统时间绕过签到限制。安卓上改系统时间太容易了后端的校验才是最后一道防线。2.2 请假流程的状态流转从提交到审批的几个环节请假模块看起来简单做好不容易。学生的请假类型有病假、事假、公假比如代表学校参加比赛不同类型的审批链不一致。比如病假需要上传医院证明图片公假需要上传活动通知文件。审批流是一个典型的状态机待审批 → 辅导员通过/驳回 → 任课老师确认更复杂的场景是辅导员通过了但任课老师那边还没确认这时候考勤统计该怎么算我们的处理方式是只有走到终态“任课老师已确认”请假状态才会写入考勤报表否则一律按“请假中”单独标记不计入旷课也不计入出勤等终态出来后再回填。这块业务逻辑如果在数据库设计阶段没想清楚后面写统计接口时会特别痛苦。前端部分请假表单需要做好“图片上传”和“提交确认”两个交互细节。uniapp里上传图片用uni.chooseImage拿到临时路径再通过uni.uploadFile上传到服务器。有一个常踩的坑是部分安卓手机上chooseImage选择的图片体积过大直接上传会超时所以我们处理的时候会先做一次压缩保证单张图片不超过300KB。2.3 角色权限与数据隔离不同的登录者看到不同的世界考勤系统天然有三种角色学生、任课老师、辅导员/管理员。每个角色的菜单和操作范围完全不同学生今日课程、签到打卡、请假申请、我的考勤记录任课老师我的课程列表、班级学生名单、考勤实时统计、审批请假辅导员/管理员全校/全系课程管理、学生管理、考勤总报表导出因为这些角色差异非常明显我用的是最简单的路由拦截按钮权限双重控制。uniapp的路由跳转统一封装在uni.navigateTo外面套一层权限校验函数角色不匹配直接跳回首页。按钮级权限则用Vue的v-if控制。没有引入复杂的前端权限框架因为这套系统的角色固定、权限静态做重了反而消耗开发时间。数据隔离方面接口请求都会带上用户的userId和role字段后端根据角色返回对应数据。学生看自己的课程表老师看自己名下的课程和学生这套数据边界在设计接口时就要和后端对齐清楚否则联调阶段会不断扯皮。3. 核心模块的实现细节定位打卡、时间处理、数据交互3.1 定位签到uniapp集成高德地图的完整步骤考勤签到系统的技术核心第一个肯定是定位。如果定位不准学生明明在教室却签不上或者人在宿舍就能签上到系统就没意义了。我用的方案是高德地图uni-app插件具体集成步骤给大家拆解一下。第一步在manifest.json的“App模块配置”里勾选Maps并填写高德地图的key。这个key需要在高德开放平台申请平台类型选择“Android平台”。注意申请key时填的包名和安全码SHA1必须和最终打包时完全一致如果先用云打包测试、后来又改成离线打包包名变了key就失效地图就会白屏。这个坑我踩过后面详细说。第二步在需要定位的页面引入插件const amap require(/js_sdk/amap/amap-wx.js) export default { data() { return { amapPlugin: null } }, onLoad() { this.amapPlugin new amap.AMapWX({ key: 你的高德key }) }, methods: { getLocation() { this.amapPlugin.getRegeo({ success: (data) { console.log(当前位置, data) }, fail: (err) { console.log(定位失败, err) } }) } } }这样拿到的是经纬度和具体地址描述后端接口会计算学生经纬度和教室坐标的距离。具体的距离计算可以用高德的api也可以直接用Haversin公式在前端先算一遍给学生即时反馈。我这里是前端用公式先算出一个“预估距离”如果预估距离明显超范围就直接提醒否则提交后端做最终校验。关于定位精度的调优说几个真实体验优先用GPS基站WiFi混合定位在教室这种GPS信号偏弱的室内场景纯GPS很容易定位失败或漂移。定位需要时间必须在UI上有loading状态。我见过不少考勤系统学生点签到后界面没反应连点三下结果提交了三次签到。我们的方案是点击后立即置灰按钮并显示“正在定位中”。定位成功后缓存最近一次坐标网络慢的时候可以先显示“已获取位置”避免反复调SDK。3.2 时间处理考勤系统的灵魂是时间而不是定位很多人以为考勤系统的核心是定位其实我认为时间比定位更关键。定位不准顶多签不到位时间不准直接导致整个考勤数据不可信。这里的“时间不准”有两层含义一是学生手机本地时间被修改二是服务器时间和客户端时间有偏差。处理方案是客户端只提交操作行为时间一律以服务器返回的时间为准。具体做法是用户在页面进入时先调一个时间接口拿到服务器标准时间并缓存下来。前端展示的倒计时、签到窗口判断全部基于这个服务器时间计算而不是new Date()。学生点击签到时前端只把自己的userId、courseId、经纬度提交给后端后端记录时间戳并完成窗口判断。有人可能会说只要后端做判断不就行了前端为什么还要做窗口提示因为用户体验问题。如果前端不提示“距离签到开始还有多少时间”学生就会一直点按钮然后被后端反复拒绝影响体验。用服务器时间做前端倒计时既能保证一致性又能让用户清晰感知签到窗口。这里有个细节要提醒服务器返回的时间建议用时间戳而不是格式化字符串。时间戳没有时区概念前后端处理都方便。我们用getTime()拿毫秒级时间戳做运算格式化展示时才转成“2024-12-18 08:30”这样的字符串。3.3 接口与数据交互request封装与token鉴权管理整个系统所有网络请求都通过uniapp的uni.request但直接到处写uni.request会让代码非常难维护。我的做法是封装一个统一的request.js模块const BASE_URL https://api.xxx.com/v1 export function request(options) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, token: uni.getStorageSync(token) || }, success: (res) { // 后端统一返回结构{ code: 0, data: xxx, msg: ok } if (res.data.code 0) { resolve(res.data.data) } else if (res.data.code 401) { // token过期跳转登录页 uni.removeStorageSync(token) uni.navigateTo({ url: /pages/login/login }) reject(res.data) } else { uni.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络异常请检查网络, icon: none }) reject(err) } }) }) }我把这个模块放在/utils/request.js业务代码里直接import { request } from /utils/request然后调用。封装的好处是所有接口鉴权、错误提示、loading弹窗都在一个地方处理不会出现一个接口一个写法的混乱局面。Token过期处理这块特别值得说一下。考勤系统学生可能放暑假一个多月再回来用token早就过期了。我们的处理逻辑是公共请求模块拦截所有401响应清理本地token并跳转登录页。用户重新登录后又可以正常使用整个链路是顺畅的。但有个细节不要用uni.reLaunch跳登录页否则会把页面栈清空导致返回失效用uni.navigateTo更合理。4. 从uniapp代码到安卓安装包打包发布链路全记录4.1 manifest.json里的关键配置项uniapp项目要打包成安卓App第一步是检查manifest.json的配置。这个文件是uniapp应用的“门面”很多配置错误都出在这里。首先是基础配置里的“应用名称”和“AppID”。AppID是DCloud平台分配的需要登录HBuilderX后获取。然后“图标配置”可以上传自动生成所有尺寸的图标用一张1024x1024的png图就行。最关键的是“App模块配置”和“App权限配置”两块。考勤系统需要用的模块有Maps高德地图、定位、相机拍照、文件上传。权限配置里要勾选android.permission.ACCESS_FINE_LOCATION精确定位、android.permission.ACCESS_COARSE_LOCATION粗略定位、android.permission.CAMERA相机、android.permission.INTERNET网络。这里有个容易忽略的点权限说明文字。现在安卓应用市场上架时要求提供权限用途说明比如“定位权限用于完成考勤签到时的地理位置校验”。如果manifest.json里的说明写得含糊上架审核会被驳回。我们在每个权限后面都写了清晰的中文说明这在上架时真的省了很多事。4.2 离线打包和云端打包怎么选uniapp提供两种安卓打包方式云打包和离线打包。云打包就是在HBuilderX里直接选择“发行—原生App云打包”不需要配置安卓SDK上传一个打包证书就能等结果。优点是真的省事缺点也很明显云打包的产物是标准APK但如果你需要集成特殊原生插件流程会更麻烦。云打包还需要排队高峰期等半小时甚至更久。云打包实际上是在DCloud服务器上编译你的高德key必须和云打包签名一致否则地图功能直接失效。我当时先用了云打包结果地图白屏查了半天最后发现是高德的key和云打包的默认签名对不上。后来干脆转成离线打包自己控制签名和key虽然配置麻烦但胜在可控。离线打包需要下载Android Studio和uniapp离线SDK用Android Studio打开SDK里的工程模板把自己的uniapp资源通过HBuilderX的“发行—本地打包—生成本地打包App资源”生成放到对应目录然后修改必要的配置再编译。这个流程对不熟悉安卓开发的纯前端有一套学习成本但只要你打算长期维护一个App端绕不开这条路的。4.3 安卓应用市场上架前的准备与常见驳回原因如果是学校内部使用可以直接发APK让用户安装。但如果需要上架应用市场比如各个学校的应用商店或者第三方市场就需要准备软著证书安卓应用市场上架基本都要求提供“计算机软件著作权登记证书”这项需要提前准备通常要1-2个月不要等开发完了才开始办。隐私政策应用内必须在“关于”页面展示一份完整的隐私政策说明采集了哪些信息、如何使用、如何处理。考勤系统涉及位置信息和个人信息隐私政策审核会比较严格。隐私检测报告部分应用市场要求第三方检测机构出具的隐私合规检测报告重点检查有没有违规收集个人信息、超范围申请权限等。这些材料里隐私政策是上架被驳回的最高频原因。我第一次提审就是在“未提供用户撤回同意的方式”上被卡住后来在设置页面里加了“注销账号”“撤回同意”入口才通过。做考勤系统上架前建议先把隐私合规这些细节想清楚否则开发完成后会被流程卡到怀疑人生。5. 开发过程实录六个典型坑与排查思路5.1 手机软键盘遮挡查询输入框开发考勤记录查询页面时遇到一个非常经典的uniapp安卓端问题页面上的输入框聚焦弹起软键盘后键盘把下方的查询按钮和内容列表遮住了用户输入关键字后看不到查询结果。问题的本质是安卓软键盘弹出时默认会压缩WebView的高度但如果页面底部有固定定位的元素或者布局里有position: fixed的组件键盘就不会自动把内容顶上去。我们的解决方案分两步第一步在页面根节点上用adjust-positiontrueuniapp的默认行为让WebView随键盘自动调整。第二步在需要滚动查看结果的区域用onKeyboardHeightChange监听键盘高度变化动态给列表容器加一个padding-bottom。伪代码长这样onKeyboardHeightChange(res) { this.keyboardHeight res.height // 在template里给底部查询按钮设置 margin-bottom: {{ keyboardHeight }}px }实测这个方案在安卓和微信小程序上都能稳定工作。这个问题的排查链路建议先从adjust-position入手不要一开始就手动监听键盘因为安卓不同机型对键盘高度的回调时机差异很大过度手动处理反而容易出现闪烁。5.2 uniapp运行到微信开发者工具没反应联调微信小程序端时我点HBuilderX的“运行—运行到小程序模拟器—微信开发者工具”开发者工具长时间没有反应Console里也不报错。当时第一反应是“代码有问题”查了半天发现并不是。这个问题的排查链路是这样的先确认微信开发者工具是否开启了“服务端口”。具体路径是微信开发者工具→设置→安全设置→服务端口必须打开。这是HBuilderX推送代码到微信开发者工具的通道不打开就会没反应。确认微信开发者工具登录的微信号是否有权限打开项目。确认uniapp项目的manifest.json里微信小程序配置的AppID是否正确。如果你没有微信小程序账号可以选“测试号”。我那次第1步就没做对端口没开HBuilderX推送的数据全部被微信开发者工具丢弃了。这个坑几乎是每个uniapp新手都会遇到的但报错信息很含糊不直观。5.3 scancode扫码扫出来是一串数字考勤系统里我加了一个扫码签到功能——老师上课时展示一个二维码学生用uniapp的uni.scanCode扫码完成签到。实测发现扫普通的文本二维码没问题但扫某些特殊编码的二维码时返回的result是一串数字而不是网址或文本内容。排查后发现原因uni.scanCode默认情况下会把一些二维码内容当作二进制数据解析特别是二维码内容超过一定长度或者包含特殊字符时返回的可能是UTF-8的字节码数组转成的字符串。解决方案是设置scanType参数uni.scanCode({ scanType: [qrCode, barCode], // 明确指定扫描类型 success: (res) { let result res.result // 如果是数字字符串尝试还原 if (/^\d$/.test(result)) { result decodeURIComponent(escape(result)) } } })这个问题的关键是要意识到uniapp扫码返回的内容并不是“所见即所得”的解码逻辑受二维码编码方式影响。如果遇到一堆数字优先考虑是编码格式问题而不是二维码本身坏了。5.4 manifest配置改了不生效还有一个让我白等一下午的坑修改了manifest.json里的权限配置后重新云打包安装到手机上发现还是没有弹出对应权限。问题在于uniapp的manifest.json不是修改后立即生效的——改了manifest.json后需要先重新编译一次项目再执行打包。如果直接在没编译的状态下点“发行”HBuilderX读取到的还是旧的配置缓存。所以后来我养成一个习惯每次改manifest配置就执行一次“运行到浏览器”让项目重新编译一遍确认没有语法错误后再执行打包。另外如果用了自定义基座需要把自定义基座重新打包安装否则调试时用的还是旧基座权限配置自然也不会生效。5.5 高德地图在H5端正常、App端白屏我们的考勤签到页面有地图选点功能在H5端调试时地图显示正常打包成安卓App安装后地图区域白屏连网格底图都没有。排查思路确认高德key是否绑定正确包名和签名。这是个高概率原因但验证时发现不是它。确认manifest.json的App模块配置里是否勾选了Maps。这个也是重点怀疑对象但如果忘勾选H5端也会出问题。最终定位到问题高德地图的uni-app插件需要先初始化直接在onLoad里实例化会由于WebView没渲染完成导致白屏。我们的修复方案是把地图初始化从onLoad挪到onReady之后或者加一个setTimeout延迟100ms再初始化。安卓端的白屏问题90%是初始化时机和key配置这两类原因。如果你也遇到可以按这个顺序排查。5.6 自定义分享好友与动态设置标题这虽然不是核心考勤功能但属于体验加分项。学生在分享“我的考勤记录”给家长或辅导员时用uniapp自带的分享功能默认分享的是小程序当前页面标题是页面标题。我们做了两个优化动态设置标题用uni.setNavigationBarTitleuni.setNavigationBarTitle({ title: 张三 - 考勤详情 })自定义分享好友用onShareAppMessage生命周期onShareAppMessage() { return { title: 考勤统计报告, path: /pages/attendance/detail?userId${this.userId}courseId${this.courseId}, imageUrl: https://xxx.com/share-bg.png } }这两个能力看着小但对用户体验的提升很明显。分享出去的卡片不再是光秃秃的路径链接而是带标题、带预览图的正式报告卡片。6. 关于这套系统的后续优化方向我在实际巡检和迭代中发现考勤系统的价值不只是“打了卡”而是数据积累后的分析能力。下面几个方向是我们在第一版跑通后做的小改进也是我比较推荐的功能优先级。第一考勤数据的批量导入导出。学校老师最常用的功能不是实时打卡而是月底导出报表。我们在接口设计时跟后端对齐了“导入课程表Excel”“导出考勤统计Excel”两个能力前端用uni.downloadFile下载文件安卓端下载完的文件通常存在download目录里再通过系统分享发送给老师。第二异常考勤的自动提醒。学生连续两节课未签到系统自动给辅导员发一条提醒学生请假审批超时未处理系统提醒老师及时审核。这类功能我们是后端定时任务实现的前端只需要在消息中心里拉取消息列表体验提升非常明显。第三多端数据的同步与容灾。考勤数据很重要最怕学生在教室签到了但数据没提交上去。我们在前端实现了“签到数据本地暂存”机制签到动作先写入本地Storage再异步提交到服务器提交成功后才清除本地记录。网络差的时候学生也可以先签到等网络恢复后再自动上报。这个机制实现成本不高但对考勤系统的稳定性帮助很大强烈推荐。7. 写在最后的几条实战建议整套系统从开发到上线用了三个月左右回头总结有几条经验觉得值得单独拎出来说第一考勤系统的时间、定位、数据一致性三个维度要在设计阶段想清楚别等联调了再改。前端本地时间不可信、定位有误差是必然的必须在交互设计和接口设计上就做好容错。比如定位签到正确的容忍区间应该是由后端根据教室半径加一定缓冲来计算的而不是前端精确到米。第二uniapp多端开发时优先在安卓真机上做兼容性验证而不是只在微信开发者工具里调试。我们遇到的很多坑都是安卓真机专属的比如软键盘遮挡、地图白屏、扫码乱码。微信开发者工具里显示正常真机上表现可能完全是两回事。有条件的话从第一天起就准备一台安卓真机做日常验证。第三请假审批流程一定要设计好终态判定规则。请假状态如果定义不清楚直接影响考勤报表的正确性。“辅导员通过”和“任课老师确认”之间本来就有时间差这个时间差内学生的考勤状态怎么算必须在需求阶段和后端、业务方对齐。我们因为这个问题在联调阶段返工过一次很痛苦。第四uniapp项目要养成定期编译验证的习惯。因为HBuilderX的缓存机制修改manifest、插件配置后不重新编译直接打包经常会出现“配置不生效”的灵异问题。每次改完配置老老实实重新编译一次再跑一遍核心流程这个习惯能帮你省掉大量排查时间。这套学生考勤签到请假系统的技术方案整体并不复杂难点在于把细节做扎实。如果你也在用uniappvue做类似的系统希望这篇文章能帮你避开我踩过的那些坑。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进