
简介一份面向招聘场景的微信小程序全开源模板源码包名为 dawn_banana 1.0.2适合需要快速搭建招聘小程序或学习小程序开发的初学者、独立开发者与中小企业覆盖职位发布、简历投递、企业信息展示等典型业务模块。压缩包共 449 个文件包含 113 个 JavaScript 逻辑文件、88 个 JSON 配置、84 个 WXSS 样式、83 个 WXML 页面结构以及 31 个 PNG 图片资源另有 PHP、HTML 等辅助接口与说明文件整体大小仅 894KB目录层级清晰前端页面、数据交互与后端接口组织合理。目前已有 470 人学习下载适合用于理解和二次开发。源码中页面组件、数据管理、API 接口和配置文件分层明确可对照 WXML/WXSS 与 JavaScript 学习前端渲染、用户交互和数据请求逻辑招聘模块化的设计也有助于快速改造出其他垂直行业小程序开源授权同时允许自由查看、修改与再分发是完整的全开源实战样例。1. 拿到 dawn_banana 1.0.2先别急着往微信开发者工具里拖凌晨一点我把 zip 解出来第一眼看到的不是一堆 .wxml而是 position_add.html、category_edit.html、business_edit.html 等 HTML 文件心里先是一愣这是微信小程序模板源码还是 Web 后台模板看完目录里的页面命名才反应过来dawn_banana 做的是整套招聘闭环——求职者进来用微信小程序找工作招聘方在后台 HTML 页面里维护职位和企业信息两边共用同一套数据模型。和市面上那些只给几个页面壳子的模板不同它把「小程序端 后台管理端」都放了进来适合一个人想快速搭起招聘类小程序、又懒得从零写管理界面的开发者。以下内容默认你至少用过一次微信开发者工具知道 app.json 和 pages 目录大概长什么样。2. 拆解 dawn_banana 的页面结构先从后台 HTML 模板读起多数模板源码会把小程序页面拆成 index、list、detail 三件套dawn_banana 却在根目录放了一组 HTMLsettings.html、position.html、resume_detail.html、category_edit.html、position_edit.html、position_detail.html、user_edit.html、position_add.html、category.html、business_edit.html。一眼看过去会以为下错了包其实这套文件对应招聘方使用的 Web 后台小程序端负责求职者入口后台模板负责商家维护职位。一个小程序要形成招聘业务的数据闭环这两端缺一不可。2.1 小程序页面与后台模板的对应表如果项目按常见微信小程序模板源码组织pages/目录下应当能找到与 HTML 功能对上号的 WXML 页面。两边渲染同一套数据小程序端简历投递后台端能立即看到这是源码包最值钱的地方。业务模块小程序端WXML/WXSS/JS后台管理模板HTML对应数据表首页/企业信息pages/indexbusiness_edit.htmlbusiness职位列表pages/position/listposition.htmlposition职位详情pages/position/detailposition_detail.htmlposition职位发布/编辑pages/position/editposition_add.html / position_edit.htmlposition分类管理pages/categorycategory.html / category_edit.htmlcategory简历详情pages/resume/detailresume_detail.htmlresume用户中心pages/useruser_edit.htmluserresume_detail.html是两端都会出现的页面求职者在简历投递后需要回看记录招聘方也要在后台查看候选人资料。实现时一般共用一条/resume/detail接口用权限字段区分返回数据的颗粒度避免把手机号、期望薪资差异显示到错误的一端。2.2 职位从发布到展示的数据链路后台position_add.html发布职位表单提交到后端 API随后小程序端列表页用wx.request拉取。完整链路有四条消息后台添加职位 → 数据库写入小程序进入列表页 → 请求/api/position/list下拉刷新 → 按分页拉新进入详情页 → 请求/api/position/detail。微信小程序端的基础实现我一般会写成下面这样兼容 dawn_banana 这类早期模板的写法// pages/position/list.js Page({ data: { list: [], page: 1, hasMore: true, }, onLoad() { this.loadPositions(); }, loadPositions() { if (!this.data.hasMore) return; wx.request({ url: https://api.example.com/api/position/list, data: { page: this.data.page, pageSize: 10 }, header: { Authorization: Bearer wx.getStorageSync(token) }, success: (res) { if (res.statusCode 200) { const rows res.data.data || []; this.setData({ list: this.data.list.concat(rows), hasMore: rows.length 10, }); } else { wx.showToast({ title: 加载失败, icon: none }); } }, }); }, });这里的page和pageSize是后台列表页最常见的分页参数position.html里的分页组件大概率也是往服务端传这两个字段Authorization从本地缓存读取与后台user_edit.html里的登录态保持一致。需要注意字符串拼接 url 时https://前缀不能写错后续在开发者工具里开着“不校验合法域名”时看的是明文 URL线上则必须换成 HTTPS 白名单域名。setData里用this.data.list.concat(rows)而不是直接覆盖否则分页加载时旧数据会丢失。2.3 WXML 模板语法中隐藏的招聘业务逻辑列表页和详情页的渲染依赖微信小程序的模板语法wx:for循环职位卡片wx:if控制“急招”“已下线”状态bindtap绑定投递事件。view wx:for{{list}} wx:keyid classposition-card view wx:if{{item.status 1}} classtag hot急招/view view classtitle{{item.title}}/view view classsalary{{item.salary_min}}-{{item.salary_max}}K/view button>{ appid: touristappid, compileType: miniprogram, libVersion: 3.2.0, setting: { urlCheck: false, es6: true, enhance: true } }关键参数含义urlCheck控制是否校验合法域名开发阶段改成falsees6开启后可以编译箭头函数、async/awaitdawn_banana 里的接口封装如果用了 Promise就必须依赖这项libVersion尽量不低于 2.20太老的基础库会让部分生命周期和组件 API 失效。如果用的是可视化界面就在“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。真机和模拟器的行为差异很大。模拟器默认放宽域名限制真机预览时微信客户端仍然强制校验所以上线前别忘把https://api.example.com替换成自己的正式域名并在微信公众平台后台配置 request 合法域名。3.2 wx.login 换取 token替换后台 user_edit.html 里的账号体系后台 HTML 模板通常用表单登录维持会话小程序不同它是先wx.login拿到 code再用 code 换服务端的 openId 和 token。这是一个可复用的封装// utils/auth.js function login() { return new Promise((resolve, reject) { wx.login({ success: (res) { wx.request({ url: https://api.example.com/auth/login, method: POST, data: { code: res.code }, success: (resp) { if (resp.data.code 0) { wx.setStorageSync(token, resp.data.data.token); resolve(resp.data.data); } else { reject(resp); } }, fail: reject, }); }, fail: reject, }); }); } module.exports { login };这段代码包含三层回调是微信小程序最常见的异步写法。注意它和浏览器环境不同拿不到localStorage要用wx.setStorageSync写 token。code五分钟内有效且只能消费一次同一个页面反复调wx.login会造成 401。后台user_edit.html里的用户 ID 字段在接口返回里一般叫userId或uidtoken 里带上用户信息后后续列表页请求职位接口时后台才能区分“我发布的”和“全部公开的”。3.3 职位列表的分页、下拉刷新与参数说明后台position.html大概率是服务端分页前端传page和pageSize后端返回total和rows。小程序端沿用同一套接口即可只需要把两个页面生命周期函数接上onPullDownRefresh() { this.setData({ page: 1, list: [] }); this.loadPositions().finally(() wx.stopPullDownRefresh()); }, onReachBottom() { this.setData({ page: this.data.page 1 }); this.loadPositions(); }对应的position-list.json里要声明enablePullDownRefresh: true、onReachBottomDistance: 80否则这两个生命周期不会触发。这里有几个参数值得说明参数类型说明pageNumber当前页码从 1 开始下拉重置为 1pageSizeNumber每页条数后台模板默认 10 或 20tokenString请求头里的登录态后台用它过滤上下架职位onReachBottomDistanceNumber距离底部多少像素时提前触发加载onReachBottomDistance设成 80 而不是 0是为了让加载动作早于“滚到底白屏”的感觉触发。调试时可以在开发者工具的事件面板直接模拟下拉刷新不用真机操作真机预览时记得打开右上角菜单里的“开发调试”vConsole 里能看到完整的请求头和返回体。4. 后台 HTML 模板与小程序 API 对接从 category_edit.html 到 WXML4.1 category_edit.html 决定前端下拉框的数据源招聘后台里的“职位分类”通常由category.html和category_edit.html维护小程序端只读。分类表最少要有id、parent_id、name、status四个字段。后台改了分类后小程序端不用发版也能拿到新数据前提是接口查询逻辑正确// pages/position/add.js 中加载职位分类 wx.request({ url: https://api.example.com/api/category/list, method: GET, data: { status: 1 }, success: (res) { const cats res.data.data || []; this.setData({ categoryNames: cats.map((c) c.name), categoryIds: cats.map((c) c.id), }); }, });这里的categoryNames和categoryIds必须同时存入data因为微信小程序的picker组件只返回索引不直接返回 id。把两个数组按下标对齐是避免二次请求的标准做法。category_edit.html里通常还有sort字段后端返回分类时默认按sort升序前端就不需要再写一次排序逻辑。有些后端会把分类做成两级parent_id小程序端如果只需要一级分类请求时加parent_id0过滤如果需要三级联动前端就要在bindcolumnchange里动态请求下一级列表单纯一次拉全量数据会拖慢首屏。4.2 对接接口时的参数边界表真正花时间的不是写 WXML而是把后台模板里每个表单字段对应到接口参数。以下是从 dawn_banana 这类“H5 后台 小程序前台”结构里归纳的最小接口集合操作接口请求方式参数返回码约定登录/auth/loginPOSTcodecode0 成功职位列表/position/listGETpage,pageSize,tokenrows 数组职位详情/position/detailGETpositionIdposition 对象简历投递/resume/submitPOSTpositionId,name,phone,resumeTextid分类列表/category/listGETstatuscategory 数组这里的返回码约定是整个小程序源码里最重要的一环。code0表示成功code401表示登录态过期。理解这两个码就能理解后台user_edit.html的管理员会话和小程序 token 为什么可以并存在同一套用户表里后台用 session cookie小程序用 Header Authorization两边走到/auth/login之后生成的会话互不干扰只是拿到同一份 userId。如果后端把返回码定义成code200那么前端判断就要跟着改如果code和 HTTPstatusCode混用排查起来最费劲。建议拿到源码后第一件事是在utils/request.js里统一好判断逻辑不要在每个页面里各自写一套。4.3 统一 request 封装避免页面里重复判断为了让 dawn_banana 里所有页面都走同一套鉴权和错误处理我会把wx.request包成一个 Promise 方法// utils/request.js function request(url, data {}, method GET) { return new Promise((resolve, reject) { wx.request({ url, data, method, timeout: 8000, header: { Authorization: Bearer wx.getStorageSync(token), Content-Type: application/json, }, success(res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data); } else if (res.statusCode 401) { wx.redirectTo({ url: /pages/login/login }); reject(res); } else { reject(res); } }, fail: reject, }); }); } module.exports request;这个方法把接口地址、参数、返回结构全部集中在调度层排错时打开 network 面板扫一眼就知道是 Session 过期还是参数写错。timeout参数很关键开发者工具里的请求可以很快真机网络抖一抖默认 60 秒超时就会让用户误以为卡死8 到 10 秒是招聘业务里比较合适的值。还有一个隐藏坑Content-Type用application/json时如果后端模板接收的是表单格式需要改成application/x-www-form-urlencoded否则后台position_edit.html接收到的请求体是空对象职位怎么发都失败。5. 真机调试时被问疯的三个问题请求域名、骨架屏、简历参数5.1 token 失效不再白屏真机预览微信小程序时最常遇到的不一定是代码逻辑错而是域名校验和缓存问题。真机微信客户端不像开发者工具那样默认放行必须登录微信公众平台把接口域名HTTPS加到 request 合法域名中否则所有wx.request都会直接 fail。验证方式手机打开小程序的“开发-调试-vConsole”在 network 面板里看到fail url not in domain list说明就是域名没配看到401就去检查 token 是否过期并在 request 封装里统一跳转登录页。5.2 职位列表的骨架屏替代方案dawn_banana 的职位列表用wx:for渲染一旦接口响应慢用户会先看到一整片白屏。一个很轻量的做法是在data初始化时预置几条loading字段为 true 的占位数据接口回来后再用setData替换Page({ data: { list: [ { id: 0, loading: true }, { id: 1, loading: true }, ], }, })然后在 WXML 里对item.loading做条件渲染显示灰色占位块。记住让占位数组先渲染出来再发请求不然 setData 回来之前页面还是空的。这样做的代价很小但首屏体感会明显改善。5.3 简历详情页的参数一致性排查简历投递链路时重点看/resume/detail的调用次数和参数名。从position_detail跳转到resume_detail时如果路径参数传的是id而不是positionId后端经常查不到数据。建议在resume_detail.js的onLoad(options)里统一兜底onLoad(options) { const positionId options.id || options.positionId; this.setData({ positionId }); }验证这个逻辑是否生效最简单的方法是连点两次“投递简历”看 vConsole 的 network 面板里是否只有一次请求。重复点击会把同一份简历重复写入接口侧必须加幂等判断比如用userId positionId做唯一索引否则后台resume_detail.html里会看到同一个人投了三次相同岗位。本文还有配套的精品资源点击获取