十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue3+ThinkPHP+Laravel实战:课外阅读管理平台开发全记录

Vue3+ThinkPHP+Laravel实战:课外阅读管理平台开发全记录 前不久接了一个小学阅读管理类的项目需求是把学生的课外阅读从纸质登记卡搬到线上做成一个真正能记录、能统计、能帮助教师评估阅读能力的平台。项目前端选了Vue3后端在ThinkPHP和Laravel之间切换部署整体做下来思路和技术路线都比较清晰。这篇文章就把这个项目从功能拆解到环境搭建、接口实现、踩坑排查的过程完整写出来给准备做类似教育管理系统的朋友一个参考。先说清楚这东西能干什么学生登录后可以提交每日阅读记录、写读书笔记、查看推荐书单教师可以布置阅读任务、审批学生笔记、查看班级阅读数据家长能实时看到孩子的阅读进度系统本身还会根据阅读量、阅读速度、笔记质量这些维度给每个学生生成能力评估报告。适合两类人看一是要做学校信息化项目的开发人员二是关注阅读教学数据化的老师或管理人员。1. 项目背景与整体技术选型分析1.1 课外阅读管理场景里的真实痛点学校原有的做法一般是每学期发一本纸质阅读登记册学生每天把读了多少页、看了什么书写上去老师定期收上来盖章检查。这个方式有几个很实在的问题纸质记录容易丢、补写现象普遍数据到了期末汇总基本靠手工老师根本没时间逐本核对就算登记是真实的老师也只能看到一个学生“读了多少次”很难判断阅读能力有没有提升。所以这个系统的核心不是做一个漂亮的表单而是要把“阅读行为”变成“可分析的数据”。我一开始就明确三个目标记录要快学生每天花半分钟就能完成审批要轻老师不需要一条条审核所有内容评估要有依据系统能自动算出阅读量、连续打卡天数、笔记质量评分这些指标。这样期中、期末向家长反馈时不只是“你家孩子挺爱看书”这种模糊评价而是有数据支撑的成长报告。1.2 为什么前端选Vue3而不是React或小程序原生前端选型时我对比过UniApp和React Native最后决定主端用Vue3做Web应用。原因是学校这边的使用环境以Windows电脑和Android平板为主Web端直接用浏览器访问最省事不用走应用商店审核。Vue3相比Vue2的提升在实际项目里感受很明显Composition API 把同一个业务逻辑的代码聚在一起阅读记录这种多模块交互页面写起来比Options API清晰得多响应式系统改用Proxy之后数组下标操作和动态新增属性都不需要再额外调API配合Vite冷启动和热更新开发效率比Webpack时代提升很大。另一个考虑是生态。Element Plus、Vant这些组件库对Vue3支持已经很成熟后台管理页面直接套用Element Plus的表格、表单、弹窗组件能节省大量时间。ECharts的Vue3封装也很稳定阅读趋势图、班级对比图都能直接渲染。如果你是刚开始学Vue3这个项目的代码结构其实很适合当练习案例因为角色权限、列表分页、图表展示、表单校验这些通用场景都覆盖到了。1.3 ThinkPHP和Laravel在项目中怎么同时存在标题里同时出现ThinkPHP和Laravel可能有人会觉得奇怪。实际项目里这两套框架并不冲突原因很简单学校有一个旧的记录系统是ThinkPHP写的当时为了快速上线用ThinkPHP做了第一版数据采集接口后来要把系统升级成正式的阅读能力培养平台需要更完整的ORM关系映射、队列任务、权限中间件这些能力我又用Laravel重写了后台服务端。两套框架同时存在最需要注意的就是不要让业务代码和框架强耦合。我在设计时把数据表结构完全统一ThinkPHP版本负责轻量级的快速录入接口Laravel版本负责完整的后台管理、审批流和统计报表两者操作的是同一套MySQL数据库。这样既保留了旧系统的稳定接口又能在新框架里做复杂业务迁移压力也小。官方文档对这两者的定位差异也说明了很多ThinkPHP更强调简单、快速、贴合国内开发者习惯Laravel更强调设计模式、中间件和丰富的扩展生态。对比项ThinkPHPLaravel上手难度低中文文档友好中概念较多但思路规范路由与中间件有但相对简洁中间件体系很完善ORM继承自ThinkPHP自带ORM够用Eloquent功能更强关联模型方便适合场景快速原型、简单API复杂业务、高扩展需求本项目中角色录入接口、数据采集正式服务端、审批流管理2. 系统核心功能拆解与数据结构设计2.1 三种用户角色与权限思路阅读记录平台不是给一个人用的不同角色的视图差异非常大。我设计了四种身份学生、教师、家长、管理员。学生端看到的是“今日打卡”“我的书单”“我的报告”教师端是“班级阅读概览”“任务审批”“笔记评价”家长端以查看为主关注自家孩子的记录和周报管理员负责维护书目数据和班级结构。权限控制如果在后端每个接口都判断会比较散这里我采用了一个相对轻量的做法登录成功后返回角色ID每次请求时后端根据路由前缀做中间件过滤。Laravel的中间件写起来很顺手几行代码就能拦掉无权限请求。Vue3前端这边路由守卫里也做了角色判断菜单根据角色动态渲染。比如 /teacher 路由只有教师角色能进学生的JS包里虽然能看到对应组件代码但接口层没有权限也拿不到数据这就够了没有必要过度设计细粒度权限。2.2 阅读打卡闭环里的核心表设计数据表是整个系统的基础我在设计时参考了常见阅读平台的做法核心表有四张学生档案表、阅读记录表、书目表、笔记审批表。学生档案表关联班级、年级、姓名、学号并记录初始阅读水平等级。阅读记录表学生每次打卡一行字段包括书目ID、阅读页码、阅读时长、阅读字数、当日心得、记录时间。书目表维护书名、作者、出版社、适用年级、分类标签。笔记审批表学生提交阅读笔记后教师给出评价分数和评语状态分为待审批、已通过、已驳回。阅读时长这个字段在实现时要注意我让学生开始阅读时点一次“开始”结束再点“结束”前端计算间隔写入表单后端会做一次合理性校验单次阅读时长不超过3小时、当日打卡不超过5条超过就提示补充说明。书生读千字这个估算也不能乱填我根据学生实际阅读速度设了一个范围比如低年级每分钟150到250字后端会根据记录页码变化和阅读时长做交叉判断太离谱的数据直接打回。2.3 阅读能力评估的五维模型“阅读能力”是个比较抽象的东西系统里必须有一套可计算的评估公式。我参考了语文阅读能力结构的相关研究把它拆成五个维度阅读量、阅读理解、阅读速度、笔记质量、阅读习惯。每个维度按百分制打分最后按权重合成总分阅读量权重30%依据累计阅读字数对比同年级参考值换算得分。阅读理解权重20%来自阅读后的小测试题学生打卡时可选答根据正确率计分。阅读速度权重15%依据单次记录中的字数与用时比例结合年级正常范围给分。笔记质量权重20%教师审批笔记时的评分均值。阅读习惯权重15%以连续打卡天数为核心连续天数越长得分越高。举个例子某个四年级学生阅读量达到参考值的80%换算得分80分阅读理解测试正确率90%阅读速度略低于标准给60分笔记老师平均打分85分连续打卡20天习惯分75分。总分就是80×0.3 90×0.2 60×0.15 85×0.2 75×0.15 81.5分系统会对应到“良好”等级。这套公式不需要太复杂关键是把每个维度数据来源设计清楚教师端能看到每个维度的具体得分依据。3. 开发环境搭建与ThinkPHP、Laravel部署配置3.1 用Vite从零创建Vue3项目前端工程我是用Vite初始化的比Vue CLI创建的Webpack工程干净很多启动速度快模板也简单。命令就是常规那几条npm create vitelatest reading-front -- --template vue-ts cd reading-front npm install npm install vue-router4 pinia element-plus axios echarts npm run dev我用了Vue3官方的TypeScript模板因为项目里阅读报告的字段结构比较多类型约束能少踩很多坑。Element Plus推荐用完整引入的方式先开发后期再按需加载有一个地方容易忽略就是Element Plus的图标需要单独注册从 element-plus/icons-vue 里引入后挂到app上才能在表格操作列里正常显示编辑和删除图标。路由我用的Vue Router 4登录页、学生主页、教师后台都是单独模块加载。3.2 小皮面板里的ThinkPHP运行目录和二级域名设置ThinkPHP版本在本地开发时用的是小皮面板这里有一个很多新手都会卡住的问题网站运行目录必须指定到public目录否则访问根域名会直接把项目文件结构暴露出来不美观也不安全。在站点设置里把“运行目录”改为public删除入口文件索引就能正常用 thinkphp 风格的URL访问。二级域名设置也给学校部署时带来很大便利。实际需求是主域名指向正式后台reading二级域名指向学生阅读记录入口。在小皮面板里增加域名绑定然后在ThinkPHP的路由或者入口文件里做域名判断。我采用的是一个简单方案在应用配置里把student.xxx.com 和 teacher.xxx.com 分别解析前端两个入口共用一套组件代码通过host判断加载对应的APIBaseURL这样不用维护两套前端。3.3 Laravel正式环境部署与CORS配置Laravel版本部署到生产服务器时几个点必须处理不然跑起来一坑接一坑。一是storage目录的写权限不然日志、缓存、session都会报错二是php artisan key:generate生成APP_KEY三是路由缓存在每次修改路由之后要重新执行不然会出现“改了接口不生效”的假象。还有一个在前后端分离场景下最常见的报错就是跨域。Vue3前端跑在8080端口Laravel接口在80端口直接用axios请求会被浏览器的同源策略拦掉。解决方案我选择了加一个CORS中间件允许指定来源的请求携带Authorization头然后在Laravel的routes/api.php里统一注册。注意不要直接把allow_origin设置成*因为项目中带Token认证携带凭证的跨域请求不允许使用通配符。// app/Http/Middleware/Cors.php 示例片段 public function handle($request, Closure $next) { return $next($request) -header(Access-Control-Allow-Origin, config(app.frontend_url)) -header(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS) -header(Access-Control-Allow-Headers, Content-Type, Authorization); }4. 核心接口实现与Vue3页面交互细节4.1 登录态设计与JWT令牌登录是前后端分离系统里最基础也最容易出问题的环节。我采用的是常见的JWT方案学生输入账号密码Laravel验证通过后签发一个有效期7天的Token前端存在localStorage里请求拦截器统一在请求头加上Authorization: Bearer token响应拦截器检测到401状态码时自动清除Token并跳回登录页。写Vue3的axios封装时有一处容易被忽略响应拦截器里返回的是response.data还是response一定要全局统一。我在项目里用封装函数统一返回一个带code、data、msg结构的对象组件里直接判断code是否为0。如果封装不统一有的页面用res.data.list有的页面用res.list联调时满屏undefined排查起来非常痛苦。4.2 阅读记录提交和连续打卡天数统计阅读记录提交接口本身不复杂但连续打卡的天数统计需要一些SQL技巧。我的记录表里每个学生每天可以有多条记录但统计连续天数时只能把每天的记录去重。做法是先按学生分组查出最近几个月的打卡日期列表去重排序后在PHP里循环判断相邻日期是否差一天用一个计数器统计最大连续天数。直接在数据库层面用DATEDIFF和窗口函数也能做但考虑到学校的MySQL版本不一定支持窗口函数我选择了PHP遍历的方式数据量也不大性能完全够。这里要特别提醒阅读记录务必在后端做重复校验。学生在页面多点一次提交按钮如果前端没有做防重复后端又没查重就会插入两条相同时间段的记录连读天数和阅读时长全被污染。我加了两个校验同一本书同一时间段的记录不能重复距离上次提交间隔小于30秒的直接拦截。前端提交按钮在请求期间也置灰双保险。4.3 教师端审批流设计思路阅读笔记审批是最贴近教师工作流的功能我参考了Laravel生态中常见的审批流做法。老师从待审批列表进入详情页看学生笔记通过后填写评分和评语驳回时学生端会收到通知并可以修改后重新提交。审批记录会完整保留在审计表中后续生成学生成长档案时需要调用。数据库设计上审批状态字段是一个从0到2的整数枚举0待审批、1通过、2驳回。列表查询用where条件过滤状态配合分页就行。需要提的是审批意见不能是空字符串我后端校验至少5个字避免老师点一下“通过”就走流程学生也得不到有效反馈。4.4 Vue3 ECharts 阅读统计图表的适配问题阅读报告页需要展示最近30天阅读趋势图、班级阅读排名柱状图、能力维度雷达图这三个图全部用ECharts实现。Vue3里用ECharts有一个很常见的坑图表容器在数据还没渲染完或者页面display:none时初始化会导致图表宽度计算成0渲染出来一团模糊或者直接不显示。处理办法有两个一是在数据请求完成后调用nextTick再初始化图表二是监听容器尺寸变化调用chart.resize()。我项目中阅读报告是点击Tab后才切换显示所以在Tab切换事件里也手动触发了一次resize。另外做移动端适配时如果用了rem布局别直接把图表容器的宽高设置成rem单位ECharts内部是基于像素计算的最好用clientWidth作为宽度基准。5. 项目开发中的高频问题与排查笔记5.1 部署与配置问题速查表下列问题是这段时间里踩得最多也最容易被忽略的几类整理成速查表方便后续维护时直接对照参考。问题表现可能原因解决办法访问站点显示目录结构运行目录没指向public在站点设置里修改运行目录Laravel 接口500错误storage权限不对或APP_KEY缺失chmod -R 775 storage php artisan key:generate前端请求接口跨域CORS配置不对或allow_origin为通配符指定前端域名开启Authorization头支持API路由缓存导致新接口404修改路由后没刷新缓存执行 php artisan route:clear远程数据库连不上MySQL用户权限只允许localhost创建远程用户并授权5.2 路由跳转后组件不渲染或内容残留Vue3使用Vue Router 4时有一个情况很容易遇到在同一个路由地址下只改参数跳转比如从/book/1跳到/book/2组件会被复用旧的props不会自动更新页面看起来就是“内容没变、数据不对”。原因就是组件实例被缓存复用了不会重新走mounted生命周期。解决办法是在详情页里监听路由参数的变化手动重新拉取数据。我在Vue3里用watch来监听route.params.id变化时重置data并请求新数据。还有一个类似的问题是列表页切到详情页再返回列表位置和筛选条件全部丢失。我后来引入了keep-alive并把列表查询参数放在Pinia里暂存才解决了这个体验问题。5.3 Vue3中props不能直接给data赋值的问题在Vue3项目中很常见的场景是父组件通过props传一个对象进来子组件需要在这个对象基础上编辑再提交。直接写在const form ref(props.data)里会有坑因为ref只是拿到了初始值父组件数据变化后子组件不会同步更新。后来改成watch props深拷贝到本地form变量编辑完后再emit给父组件。记住千万别直接改props对象Vue官方也强调过单向数据流这样改会在严格模式下报错同时也会让数据流变得混乱。5.4 面试向进阶Diff算法在这个项目里的实际体现开发完这段时间我反倒对Vue3的虚拟DOM和Diff算法有了更接地气的理解。列表渲染时key能帮助Vue快速复用节点这在班级学生排名列表里体现很明显每次排序变化如果没有keyVue会复用旧的dom节点只更新内部文字速度慢且容易引发状态错乱。给了稳定且唯一的key比如学生IDDiff算法就能精确识别哪些节点被移动哪些需要重建。还有一个实际场景是图表组件。ECharts实例如果在组件更新时没有正确销毁新旧节点复用后图表可能保留上一次的数据。我使用v-if和key控制图表容器在切换学生查看报告时保证组件完全重建避免图例和数据残留。理解Diff算法的意义不是背八股文而是遇到这类复用性问题时能快速判断根源。5.5 浏览器兼容与小屏设备适配项目过程中还遇到过几个终端上的怪问题比如Edge浏览器下页面右上角极小概率出现按钮无响应的情况。排查后发现是浏览器扩展插件干扰了CSS事件切换到无痕模式后恢复正常。做这类系统不能只在一台主力机上测试我后来建立了一个简单的自测清单Chrome、Edge、安卓微信浏览器、iPad自带浏览器各跑一遍核心流程尤其关注表格横向滚动和数字键盘弹出对表单布局的影响。6. 从项目延伸出的几条实操建议如果让我重新做一遍这个项目我会在第一周就把数据字典和接口文档定完整而不是边开发边补。因为阅读记录、书目、审批流的字段一旦在前端页面写死后面就算只加一个字段也要动到好几个组件。第二个建议是ThinkPHP和Laravel两套逻辑尽量统一使用RESTful风格接口输出格式固定成code/data/msg三段式前端封装一层请求方法后后端框架替换对页面毫无感知。第三个建议是阅读能力评估模型最好找一线语文老师过一遍权重设计合理与否直接影响系统给出的报告是不是真的对教学有指导价值。做这类校园系统有一个自己的心得技术本身其实不复杂难点往往在理解业务怎么把“读了几本书”变成“能力提升了多少”才是系统真正的价值所在。上面这些踩坑和设计思路如果你正巧用得上不妨直接在你的项目里参考调整。
返回列表