简介面向高校科研管理场景的微信小程序完整项目源码涵盖科研项目申报、经费管理、成果登记、人员协同、审批通知等典型模块适用于教学实训、课程设计与毕业设计参考帮助开发者快速理解高校科研管理的业务流程与系统实现。压缩包共1472个文件、约33.19MB主要包含前端小程序wxml/wxss/js页面、Vue后台管理界面、Java后端类与配置文件、JSON数据定义以及数据库SQL脚本类型覆盖全面结构分层清晰。从目录结构看系统兼顾移动端与后台管理端业务边界明确项目内置可运行的初始化、启动与构建脚本并保留多个页面样式备份文件便于前后端联调、排错和二次开发。目前已有90人学习下载适合具备基础Java和前端知识、希望直接研究完整系统或扩展功能的开发者使用。1. 高校科研管理系统用小程序做主入口先分清业务再谈页面高校科研管理的实际痛点不在录入效率而在审批链路过长教师提交申报书院系科研秘书初审科研处复审分管校领导终审整条链在每个节点都要“催一下”才往下走。把这条链路搬进微信小程序比做 App 或网页端更贴合真实使用频率——教师不会每天打开 App但每天都会用微信。拿到这份“高校科研管理系统设计与实现小程序”的 Zip 工程时我第一反应不是先跑通页面而是先确认业务边界项目申报、中期检查、结题验收、经费到账、成果统计这五类是高校科研管理最常见的主流程。后面的内容按这个边界拆解实现方案环境上以 uni-app 开发的微信小程序为前端、Spring Boot 提供接口、MySQL 存业务数据覆盖角色权限、数据表设计和调试期的高频坑适合正在做毕业设计、课程设计或小规模校内系统的开发者和工程团队参考。2. uni-app 搭微信小程序前端 Spring Boot 后端的选型理由与接口约定2.1 前端选 uni-app 而不是原生 WXML省的是多端复用的一致性成本单做微信小程序原生 WXML/WXSS 完全可行但高校科研系统通常还要在答辩或演示时跑一个 Web 版或者后期让科研处领导在手机浏览器直接打开。uni-app 用 Vue 语法编译到微信小程序页面逻辑可以同时输出 H5这正是大多数模板工程选它的原因。取舍在于uni-app 编译后的小程序包会比原生多出一层运行时首屏体积大概多 50 到 100KB对校内部系统来说完全能接受但微信小程序的「页面栈改动、API 行为差异」会偶尔暴露出来需要条件编译处理。做法上我会把涉及微信特性的调用登录、分享、导航栏统一封装到src/utils/wechat.js页面只调统一方法多端差异集中在这一层。具体目录习惯如下src/ ├── pages/ │ ├── login/ # 登录页 │ ├── index/ # 首页待办与科研动态 │ ├── project/ # 项目管理申报、中期、结题 │ ├── fund/ # 经费登记与到账明细 │ └── mine/ # 个人中心成果、消息 ├── utils/ │ ├── request.js # 封装 uni.request统一携带 token │ └── wechat.js # 登录、导航栏、分享的集中封装 └── static/pages.json里注册这些页面并配置 tabBar首页要放在第一个位置。request.js的核心逻辑是把uni.request包成 Promise拦截 401 状态做重新登录——这个点是后面第五章节踩坑的重点。2.2 后端用 Spring Boot 的原因和模块划分Spring Boot 在管理系统里的优势是生态完整任何一个小模块都能用 starter 快速引入MyBatis-Plus 做单表 CRUDSpring Security 或 Sa-Token 做认证授权。科研管理系统角色固定教师、科研秘书、科研处长、校领导我更推荐 Sa-Token 这类轻量方案注解式鉴权对多角色判定的可读性更好。后端包结构按业务域而不是按技术分层来切com.example.research ├── controller/ │ ├── ProjectController.java │ ├── FundController.java │ └── ReviewController.java ├── service/ ├── mapper/ ├── entity/ └── config/controller 只做参数接收和结果包装业务判定下沉到 service。这类项目规模不大但保持“controller 薄、service 厚”能避免后期把多角色状态判断堆在接口里后续加一个“批量审批”功能时不需要动 controller 签名。2.3 前后端联调先定好统一返回结构省掉改签名的返工前后端各写各的最先吵起来的通常不是接口地址而是返回格式。我一般会在第一个接口写好之前先定一个统一返回类public class RT { private Integer code; // 0 成功非 0 业务错误 private String msg; // 提示信息直接弹给用户 private T data; // 业务数据 }前端request.js里遇到code ! 0时统一弹msg后端不需要为每个异常单独写返回结构。另外还要定分页参数pageNum、pageSize从 1 开始返回total和records避免前端拿list.length当总数。这两个约定看似简单在团队协作里却是最常见的返工来源。约定项推荐值说明时间格式yyyy-MM-dd HH:mm:ss避免前端解析 ISO 字符串踩时区主键生成雪花ID比自增主键更适合迁移和分库文件上传OSS/本地磁盘接口返回 url前端不处理二进制状态变更单一接口 动作参数例如submit/reject/approve3. 解压 zip 后的项目骨架小程序页面目录与 MySQL 表怎么落库3.1 先跑通数据库再对小程序页面拿到 Zip 源码最容易犯的错是直接在小程序开发者工具里打开前端目录结果空白页。这套系统前后端分离正确顺序是先建库建表再启动后端接口最后编译小程序。核心业务表建议至少包括用户表t_user、科研项目申报表t_project、项目状态流转表t_project_log、经费到账表t_fund、成果登记表t_achievement再加一张t_role做角色字典。建表语句可以直接用 Navicat 执行也可以放到schema.sql里由 Spring Boot 启动时初始化我习惯后者方便换电脑重跑。3.2 核心表结构与初始化 SQL下面 SQL 是去掉冗余字段后的最小结构字段名用下划线和小程序端驼峰属性做好映射CREATE TABLE t_project ( id BIGINT PRIMARY KEY COMMENT 雪花ID, project_name VARCHAR(200) NOT NULL COMMENT 项目名称, project_type VARCHAR(50) COMMENT 类型纵向/横向/校级, applicant_id BIGINT COMMENT 申报人ID, status TINYINT DEFAULT 0 COMMENT 0草稿 1待院审 2待校审 3已立项 4结题中 5已结题, budget DECIMAL(12,2) DEFAULT 0 COMMENT 预算金额, create_time DATETIME, update_time DATETIME ); CREATE TABLE t_fund ( id BIGINT PRIMARY KEY, project_id BIGINT, amount DECIMAL(12,2) COMMENT 到账金额, arrive_time DATETIME COMMENT 到账时间, source VARCHAR(100) COMMENT 拨款来源, remark VARCHAR(255) );状态字段用 TINYINT 而不是 VARCHAR 存汉字前端用字典映射显示。预算用 DECIMAL 而不是 DOUBLE避免浮点误差——科研经费的统计口径很紧金额算错会直接波及结题审计。t_project_log的作用是记录谁在什么时候做了哪个动作下面的review接口会往这张表写数据。3.3 登录接口怎么对接小程序端小程序没有传统账号密码场景多数模板登录换成“微信授权 手机号绑定”。对接流程是前端wx.login拿code后端用code换openid首次登录自动建账号之后把openid换成自定义 token 返回。下面代码只写后端关键部分PostMapping(/login) public RString login(RequestBody LoginDTO dto) { String openid wxService.code2Session(dto.getCode()); User user userMapper.selectByOpenid(openid); if (user null) { user User.builder().openid(openid) .role(teacher).build(); userMapper.insert(user); } String token saTokenHelper.createToken(user.getId(), user.getRole()); return R.ok(token); }参数说明dto.getCode()是小程序端wx.login拿到的临时凭证五分钟有效且只能使用一次。后端拿到openid后不要直接作为 token 传给前端因为openid是敏感标识长度固定且容易被猜测。用 token 再映射用户身份后续每个接口都从 token 里取userId和role不再传输openid也能避免在日志里泄露用户标识。4. 科研项目申报、中期检查与结题成果登记的实现路径4.1 项目申报不能被表单验证带偏核心是状态机申报页面无非是填项目名、类型、预算、成员、立项依据难点是状态流转不能靠前端按钮硬切。数据库里status从 0 到 5 是单向推进但审批驳回时会从 2 退回 1。用状态机处理比散落的if/else好维护public void review(ReviewVO vo) { Project project projectMapper.selectById(vo.getProjectId()); // 校验当前状态是否允许执行该动作 Action action Action.of(vo.getAction()); if (!StateMachine.can(project.getStatus(), action)) { throw new BizException(当前状态不允许 action.getName()); } // 推进状态并记录操作日志 project.setStatus(StateMachine.next(project.getStatus(), action)); projectMapper.updateById(project); logMapper.insert(ProjectLog.of(project.getId(), vo.getAction(), vo.getUserId(), vo.getComment())); }StateMachine.can里用二维数组或 Map 维护「当前状态-动作-下一状态」的映射。审批动作只包括submit、pass、reject、recall四个配合五个状态组合有限写死的状态表在业务上反而最稳。关键是不能漏日志审计追踪在科研系统里是硬需求老师问“谁在什么时候退回了我的申报书”靠的就是t_project_log。4.2 中期检查和时间提醒中期检查可以复用申报表单的渲染把字段换成检查报告、阶段性成果、预算执行率。常见做法是在t_project里加midterm_deadline和last_remind_time后端定时任务每天扫一次到期项目给申报人发微信订阅消息。这里容易踩的坑是订阅消息模板需要用户在前端主动点一下“允许”不能当短信用所以前端要在项目详情页加一个“开启进度提醒”的授权按钮。4.3 经费登记与到账查询经费模块我会单独拆页面而不是塞在项目详情里。t_fund记录每次到账列表按月汇总前端用picker选年月后端响应按SUM(amount)分组。要特别注意一个统计口径“本年度到账”来自t_fund.arrive_time的between查询“预算剩余”是budget减累计到账两者都在后端算好前端不要在本地拼预算剩余。金额计算必须由后端出这是避免小账目查不平的原则。4.4 结题成果登记怎么和项目关联论文、专利、获奖统一成t_achievement一张表用type字段区分再建关联表t_project_achievement解决多对多CREATE TABLE t_achievement ( id BIGINT PRIMARY KEY, type TINYINT COMMENT 1论文 2专利 3获奖, title VARCHAR(255), publish_date DATETIME, level VARCHAR(20) ); CREATE TABLE t_project_achievement ( project_id BIGINT, achievement_id BIGINT );前端登记成果时先查这个成果是否已经在库里存在存在就只做关联不存在才新增防止同一篇论文被两个项目重复登记。导出报表时用group_concat或后端拼字符串把关联成果合并成一列给到科研处比逐条展示直观得多。5. 微信小程序调试期最容易翻车的四个点登录态、导航栏、动态标题和抓包定位5.1 登录态失效和 401 循环getStorageSync里的 token 过期request.js如果简单跳登录页会陷入“登录-请求-再过期”的循环。我一般这么处理401 时先清理本地 token再调用wx.login换新 token然后重放原请求只有重放仍失败才跳登录页。同时给存储加一个version字段升级后端 token 策略时手动 bump 掉旧缓存。给后端接口排错时先看请求头带没带Authorization这能快速过滤掉一半的 401 问题。5.2 自定义导航栏高度适配不同机型不同机型状态栏高度不同如果模板用了自定义导航栏不能写死 44px// 页面 onLoad 中计算 const menu wx.getMenuButtonBoundingClientRect(); const statusBarHeight wx.getSystemInfoSync().statusBarHeight; this.setData({ navBarHeight: menu.bottom menu.top - statusBarHeight });menu.bottom是胶囊按钮底部到屏幕顶部的距离menu.top是胶囊按钮顶部到状态栏的距离二者相加减去statusBarHeight正好是导航栏总高度。因为胶囊按钮在多数机型上是垂直居中的所以这段距离等于导航栏总高比固定 44px 稳。5.3 动态设置页面标题页面上方要显示「项目名称-详情」这类动态内容时用uni.setNavigationBarTitleuni.setNavigationBarTitle({ title: projectName -详情 });这个调用要放在接口拿到数据之后再执行如果先渲染页面再设置会看到标题闪跳。注意 tabBar 页面的标题只能在pages.json配置setNavigationBarTitle对 tabBar 页面不生效这是模板工程容易忽略的边界。5.4 真机调试配合抓包工具定位问题后端说没收到请求、前端说发了这时候别互相猜走真机调试加代理抓包。用 Charles 这类工具在 PC 端起代理手机代理指向同一台机器就能看到uni.request的 URL、请求头、响应体。重点看两个地方请求头有没有带Authorization响应体里的code是不是 0。绝大多数联调问题出在这两处。PC 端微信内置浏览器的流量需要在系统层面配代理不校验域名时才看得全这里单独走一套号段排查。6. 把 zip 项目交付前要做的 5 项收敛加载页改造、接口验证与启动失败排查6.1 修改刚进入的加载页面模板工程通常默认进 index 页交付前要确认首次进入路径。如果首页不需要登录就能看到数据要加路由守卫onLoad里检查 token没有就redirectTo到登录页。同时把加载页的“正在加载”文案和底图换成学校实际信息——系统演示时第一屏就是评审印象别让默认图片先入为主。6.2 启动失败排查清单小程序端白屏先看控制台报错重点关注app.js里请求的baseURL是不是用了127.0.0.1。真机访问不到 localhost必须换成局域网 IP。后端启动失败先看端口有没有被占macOS/Linux 用lsof -i:8080Windows 用netstat -ano | findstr 8080再检查application.yml里的数据库账号密码和jdbc:mysql://地址IPv6 环境下建议显式写127.0.0.1。6.3 接口冒烟验证交付前用 curl 过一遍核心链路比页面点一遍更快# 登录拿 token curl -X POST http://localhost:8080/api/login \ -H Content-Type: application/json \ -d {code:test_code} # 用 token 查项目列表 curl http://localhost:8080/api/project/list?page1size10 \ -H Authorization: Bearer xxx把登录、列表、详情、提交审批、驳回、重新提交这 6 步跑通核心状态机就没大问题。最后在微信开发者工具里点“上传”把体验版二维码给到验收人后续再按反馈迭代。6.4 公众号菜单跳转进入时的参数透传部分校内场景从公众号菜单直接进小程序此时weixin://dl/business这类 scheme 链接的生成需要后端配短链服务前端在onLoad的options里读scene参数再做路由。注意这类 scheme 触发依赖用户微信版本无法保证 100% 拉起所以模板工程里要配一个“在浏览器打开”的兜底说明。进入后的第一跳要带上 userId 和 role页面才不需要再拉一遍用户信息能省掉一次白屏等待。本文还有配套的精品资源点击获取