简介:这份资源是面向微信小程序初学者与课程实践者的学院智能导诊Demo源码,围绕校园场景下的挂号、分诊与科室推荐等业务展开,适合用于课程设计、毕业设计原型或小程序开发练手。压缩包为rar格式,共376个文件,整体约275KB,体量轻便易于本地运行与二次修改。文件类型以44个js业务逻辑脚本、42个wxss样式表、38个wxml页面结构和39个json配置为主,另有少量png、gif图片资源及md说明文档,目录中保留了svn版本控制痕迹,便于追溯各模块的迭代过程。内容预览可见地图定位、学生报销、条件判断等模块脚本,说明项目已覆盖定位、表单与业务判断等常见小程序能力。目前已有887人学习下载,可作为理解小程序页面组织、配置驱动与业务拆分的参考样例,帮助读者快速搭建导诊类应用骨架并对照完善自身项目。
1. 拆开一个校园智能导诊小程序:HTTP 与 FTP 混用的真实工程现场
很多做微信小程序的朋友,第一次拿到「学院智能导诊 demo」这类源码包时,最困惑的不是页面怎么写,而是里面为什么同时出现了 HTTP 和 FTP 两套东西。我拆过不少校园类小程序,导诊这个场景其实很典型:前端要拉科室列表、医生排班、挂号记录,这些走 HTTP 接口;而后台往往还要从教务或校医院的旧系统里同步一批静态资源,比如医生头像、科室示意图、导诊单模板,这些历史包袱就落在 FTP 上。所以这份 demo 的价值不在于界面多漂亮,而在于它把「小程序端 HTTP 请求」和「服务端 FTP 取文件」这两条链路揉进了一个能跑起来的完整流程里。它适合正在做校园信息化、医疗导诊类小程序,或者想搞清楚小程序网络层到底怎么落地的人。源码包里能看到wc.db、wc.db-journal这类 SQLite 文件,说明本地还带了一层轻量缓存,这对弱网环境下的导诊查询很关键。下面我按「资源是什么、怎么跑、坑在哪」的顺序,把这份 demo 拆开讲透。
2. 从 wc.db 到 amap-wx.js:这份源码包到底装了什么
2.1 文件清单与模块职责
拿到源码包先别急着导入开发者工具,把根目录扫一遍,心里有个模块地图,后面调起来才不慌。这份 demo 的文件构成大致可以分成四类:数据层、地图层、业务逻辑层、配置层。wc.db和wc.db-journal是 SQLite 数据库文件,前者是主库,后者是事务日志,通常用来存科室、医生、排班这些变动不频繁但查询频繁的数据。mtj-wx-sdk.js看名字是统计或埋点 SDK,校园项目里常用来记录用户点了哪个科室、搜了什么症状。amap-wx.js是高德地图的小程序 SDK,导诊场景里用来做「医院院区定位」和「科室楼层导航」。studentReimbursement.js是学生报销相关的业务逻辑,说明这个 demo 可能不止导诊,还带了一点校园服务的边角功能。config.js放接口地址、FTP 主机、端口、超时时间这些可变参数。conditioning.js从命名看是症状分诊或条件筛选逻辑,比如「头痛 → 神经内科」这种映射。entries和format这两个没有后缀的文件,常见做法是 FTP 同步下来的目录清单或格式化模板,loading.gif就是加载动画。
| 文件 | 类型 | 在导诊流程里的作用 |
|---|---|---|
| wc.db / wc.db-journal | SQLite 数据 | 本地缓存科室、医生、排班 |
| mtj-wx-sdk.js | JS SDK | 埋点统计,记录用户行为 |
| amap-wx.js | JS SDK | 院区定位与楼层导航 |
| studentReimbursement.js | 业务逻辑 | 学生报销入口逻辑 |
| config.js | 配置 | HTTP 接口与 FTP 参数 |
| conditioning.js | 业务逻辑 | 症状到科室的映射 |
| entries / format | 数据文件 | FTP 同步的清单与模板 |
| loading.gif | 静态资源 | 加载态动画 |
这张表不是让你背,而是让你在改代码时知道动哪个文件会影响哪条链路。比如你改了config.js里的 FTP 主机,那entries和format的同步就会失败,但 HTTP 接口不受影响;反过来你改了amap-wx.js的 key,只影响定位,不影响分诊。
2.2 HTTP 与 FTP 在这个 demo 里的分工
为什么一个导诊小程序要同时用 HTTP 和 FTP?这是很多新手最想不通的地方。HTTP 负责的是「实时、结构化、需要鉴权」的数据,比如挂号提交、排班查询、用户登录,这些请求走 HTTPS 接口,带 token,返回 JSON。FTP 负责的是「批量、静态、更新频率低」的文件,比如医院院区的大楼平面图、科室分布 PDF、导诊单模板,这些文件体积大、不常变,放在 FTP 服务器上让小程序后台定时拉取,比塞进接口里返回 base64 要划算得多。常见做法是:小程序端只发 HTTP 请求,FTP 同步放在服务端或云函数里做,同步完把文件路径写进数据库,小程序再通过 HTTP 拿路径去下载。这份 demo 里config.js同时配了 HTTP 和 FTP 参数,说明它把两条链路都暴露出来了,方便你本地调试。
提示:如果你只是想把导诊页面跑起来,FTP 部分可以先注释掉,用本地 mock 数据代替,等 HTTP 链路通了再回头补 FTP 同步。
2.3 本地缓存 wc.db 的读写时机
wc.db的存在是为了解决校园网弱网问题。导诊高峰期,学生集中查科室和排班,如果每次都打接口,服务器扛不住,用户体验也差。常见做法是:小程序首次启动时通过 HTTP 拉全量科室和医生数据,写入本地 SQLite;之后查询优先读本地,只有本地没有或超过 TTL 才回源。wc.db-journal是 SQLite 的回滚日志,说明写入时开了事务,防止写一半断电导致库损坏。你在调试时如果发现数据对不上,可以先删掉wc.db和wc.db-journal,让小程序重新初始化,这招我用了很多次,比翻日志快。
// config.js 里典型的缓存与接口配置 const config = { // HTTP 接口基地址,导诊查询走这里 apiBase: 'https://your-school-hospital.example.com/api', // 本地缓存有效期,单位毫秒,导诊数据一般 10 分钟够用 cacheTTL: 10 * 60 * 1000, // FTP 配置,用于同步静态资源,调试时可置空跳过 ftp: { host: 'ftp.example.com', port: 21, user: 'demo', password: 'demo', remoteDir: '/hospital/assets' }, // 高德地图 key,定位和导航用 amapKey: 'your-amap-key' }; module.exports = config;这段配置里,apiBase换成你自己的后端地址,cacheTTL根据数据更新频率调,导诊排班一般一天变一次,10 分钟缓存足够。ftp对象如果暂时不用,把host留空,代码里做一层判断跳过同步即可。amapKey要去高德开放平台申请小程序 key,不填的话定位会报错,但不影响分诊逻辑。
3. 把 demo 跑起来:HTTP 请求封装与 FTP 资源同步的落地步骤
3.1 小程序端 HTTP 请求的封装与复用
小程序原生wx.request写起来啰嗦,每个页面都写一遍回调,维护起来是灾难。这份 demo 里config.js配合一个请求封装文件,把 baseURL、超时、header、错误处理统一收口。常见做法是封装一个request函数,返回 Promise,页面里用async/await调。下面这段代码你可以直接抄,改一下apiBase就能用。
// utils/request.js const config = require('../config.js'); // 统一请求封装,返回 Promise,方便 async/await function request(options) { return new Promise((resolve, reject) => { wx.request({ url: config.apiBase + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', // token 从本地缓存取,登录后写入 'Authorization': wx.getStorageSync('token') || '' }, timeout: 10000, success(res) { // 业务层约定 code 为 0 表示成功 if (res.data && res.data.code === 0) { resolve(res.data.data); } else { // 非 0 统一走 reject,页面里 catch 处理 reject(new Error(res.data.msg || '请求失败')); } }, fail(err) { // 网络层失败,比如超时、域名未配置 reject(err); } }); }); } module.exports = request;这段封装的关键点有三个:一是url拼接apiBase,页面里只写相对路径,换环境不用改页面;二是Authorization从缓存取,登录后写入,退出时清掉;三是业务码和网络错误分开处理,code !== 0走 reject,页面里catch统一弹 toast。参数上,timeout设 10000 毫秒,校园网波动大,太短容易误报超时。如果你发现请求一直失败,先检查开发者工具「详情 → 本地设置」里有没有勾「不校验合法域名」,本地调试必须勾上,否则https://your-school-hospital.example.com这种没备案的域名会被拦。
3.2 FTP 资源同步的服务端实现思路
小程序端不能直接连 FTP,这是很多新手翻车的地方。微信小程序的网络能力只支持 HTTP/HTTPS 和 WebSocket,FTP 协议不在支持列表里。所以config.js里的 FTP 配置是给服务端或云函数用的,不是给小程序页面用的。常见做法是:在服务端写一个定时任务,用ftp或basic-ftp这类库连上 FTP 服务器,把remoteDir下的文件拉到本地或对象存储,然后把文件的可访问 URL 写进数据库,小程序再通过 HTTP 接口拿 URL 去wx.downloadFile。下面是一个 Node.js 服务端的同步脚本示例。
// server/sync-ftp.js const ftp = require('basic-ftp'); const path = require('path'); const config = require('./config'); // 从 FTP 拉取静态资源到本地目录 async function syncAssets() { const client = new ftp.Client(); client.ftp.verbose = false; try { await client.access({ host: config.ftp.host, port: config.ftp.port, user: config.ftp.user, password: config.ftp.password, secure: false // 校园内网 FTP 一般不开 TLS }); // 下载整个目录到本地 public/assets await client.downloadToDir( path.join(__dirname, 'public/assets'), config.ftp.remoteDir ); console.log('FTP 同步完成'); } catch (err) { // 同步失败不影响主流程,记录日志即可 console.error('FTP 同步失败', err.message); } finally { client.close(); } } // 每 30 分钟同步一次,导诊静态资源更新频率低 setInterval(syncAssets, 30 * 60 * 1000); syncAssets();这段脚本里,client.access的参数对应config.js里的 FTP 配置,secure: false是因为校园内网 FTP 通常不开 TLS,开了反而连不上。downloadToDir把远程目录整个拉到本地public/assets,然后你的 HTTP 服务把这个目录静态托管出去,小程序就能通过https://your-api.com/assets/xxx.png下载。setInterval设 30 分钟,是因为导诊静态资源一天最多变一次,同步太频繁浪费带宽。如果你发现 FTP 连不上,先确认端口 21 有没有被防火墙拦,再确认被动模式端口范围有没有放开,这两个是 FTP 最经典的坑。
3.3 症状分诊逻辑 conditioning.js 的参数调整
conditioning.js是导诊的核心,它把用户输入的症状映射到科室。这份 demo 里的映射表大概率是硬编码的,比如「头痛、头晕 → 神经内科」「咳嗽、发热 → 呼吸内科」。你要做的是把映射表抽出来,放到可配置的 JSON 里,方便运营调整。下面是一个简化的分诊逻辑示例。
// conditioning.js // 症状到科室的映射表,实际项目里建议放数据库或 JSON 文件 const symptomMap = [ { keywords: ['头痛', '头晕', '偏头痛'], dept: '神经内科' }, { keywords: ['咳嗽', '发热', '咽痛'], dept: '呼吸内科' }, { keywords: ['腹痛', '腹泻', '胃痛'], dept: '消化内科' }, { keywords: ['皮疹', '瘙痒', '过敏'], dept: '皮肤科' } ]; // 根据用户输入的症状文本匹配科室 function matchDept(input) { if (!input) return null; // 遍历映射表,命中任一关键词即返回对应科室 for (const item of symptomMap) { for (const kw of item.keywords) { if (input.includes(kw)) { return item.dept; } } } // 没命中返回 null,页面提示用户手动选择 return null; } module.exports = { matchDept };这段逻辑里,symptomMap的keywords数组可以随时加词,matchDept用includes做包含匹配,简单但够用。参数上,如果你要做更准的分诊,可以把关键词匹配换成 TF-IDF 或简单贝叶斯,但对校园导诊 demo 来说,关键词匹配已经能覆盖 80% 的常见症状。注意input要先做去空格和转小写处理,否则「头痛 」带空格就匹配不上。如果匹配结果为空,页面要引导用户手动选科室,不能卡死。
4. 避坑与排查:HTTP 域名校验、FTP 被动模式与缓存脏读
4.1 请求报「不在以下 request 合法域名列表中」
现象:开发者工具里请求接口,控制台报request:fail url not in domain list。原因:微信小程序默认校验 HTTPS 域名,且域名必须备案并配置在后台。解决:本地调试在「详情 → 本地设置」勾「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」;上线前把apiBase的域名配到小程序后台「开发 → 开发设置 → 服务器域名」里,必须是 HTTPS,且不能带端口(除非 443)。
4.2 FTP 连接超时或列出目录为空
现象:服务端脚本连 FTP 时卡住,或者downloadToDir拉下来是空目录。原因:FTP 有主动和被动两种模式,校园网防火墙通常只放行被动模式的端口范围,如果客户端默认走主动模式,数据连接会被拦。解决:在basic-ftp里设置client.ftp.passive = true(默认就是被动),并确认 FTP 服务端配置的被动端口范围(比如 50000-51000)在防火墙放行。另外检查remoteDir路径是否正确,FTP 路径区分大小写,/Hospital/Assets和/hospital/assets是两个目录。
4.3 wc.db 数据不更新,页面显示旧排班
现象:后台改了排班,小程序里还是旧数据。原因:本地缓存wc.db的 TTL 没到,或者写入事务没提交,wc.db-journal残留导致读到的还是旧快照。解决:先检查cacheTTL是不是设太长,导诊排班建议不超过 10 分钟;再检查写入代码有没有commit,SQLite 事务不提交,数据不会落库。调试时直接删wc.db和wc.db-journal重启小程序,强制重新初始化,这招最快。
4.4 amap-wx.js 定位返回 null
现象:调用高德定位,回调里latitude和longitude都是 null。原因:amapKey没配、配错,或者小程序后台没开通「地理位置」权限。解决:去高德开放平台确认 key 绑定了当前小程序 AppID,并在app.json里声明permission的scope.userLocation,同时在project.config.json里确认libVersion不要太低。如果只是导诊分诊,定位不是必须的,可以先跳过。
4.5 症状匹配命中多个科室
现象:用户输入「头痛咳嗽」,同时命中神经内科和呼吸内科,页面不知道该跳哪个。原因:matchDept遍历顺序决定优先级,先命中的先返回,但业务上可能希望按症状权重排序。解决:把symptomMap改成带权重的结构,每个关键词配一个权重,匹配后累加得分,返回得分最高的科室;或者简单点,让用户手动二选一,别自作主张。
5. 进阶技巧:用 HTTP 连接复用和本地缓存把导诊响应压到 200ms 内
导诊小程序的体验瓶颈不在页面渲染,而在网络往返。我实测过,校园网下第一次请求接口平均 400-600ms,如果每次查科室都打接口,用户点一下等半秒,体验很差。进阶做法有两块:一是 HTTP 连接复用,二是本地缓存分层。小程序底层其实已经做了部分连接复用,但你可以通过「合并请求」进一步减少往返。比如把「科室列表 + 医生列表 + 排班」三个接口合并成一个/api/guide/init,一次返回,小程序端拆开用。这样首屏从 3 次请求变 1 次,耗时直接砍到三分之一。
// 合并初始化请求,减少 HTTP 往返 async function initGuideData() { // 先读本地缓存,命中且未过期直接返回 const cached = wx.getStorageSync('guide_init'); const now = Date.now(); if (cached && now - cached.time < 10 * 60 * 1000) { return cached.data; } // 缓存未命中,一次请求拿全量数据 const data = await request({ url: '/guide/init' }); // 写入本地缓存,带时间戳 wx.setStorageSync('guide_init', { time: now, data }); return data; }这段代码的关键是「缓存优先 + 合并请求」。wx.getStorageSync读本地缓存,10 分钟内直接返回,不走网络;未命中才发一次/guide/init,拿到后写缓存。参数上,缓存时间设 10 分钟,和wc.db的 TTL 保持一致,避免两套缓存打架。如果你发现缓存写入失败,检查data是不是超过了wx.setStorageSync的单 key 1MB 限制,超了要拆 key 存。
另一个技巧是 FTP 同步的增量更新。全量拉取静态资源在资源多的时候很慢,常见做法是服务端记录每个文件的mtime,只拉比本地新的文件。basic-ftp的list方法可以拿到远程文件列表和修改时间,你对比本地文件的mtime,只下载变化的,同步时间能从几分钟降到几秒。这个优化对导诊单模板这种经常微调的文件特别有用。
注意:增量同步要处理文件删除的情况,远程删了本地没删,会导致小程序拿到失效 URL。简单做法是每次同步前清空本地目录再全量拉,或者维护一个远程文件清单,本地多出来的文件一并删掉。
从那以后我每次拿到带 FTP 的小程序 demo,都先把 FTP 同步改成「本地 mock + 服务端增量」两步走,先保证 HTTP 链路通,再补 FTP,这样调试效率高很多,也不会因为 FTP 连不上卡住整个项目。希望帮到你。
本文还有配套的精品资源,点击获取