简介:一个基于微信小程序的问卷调查项目源码包,面向需要快速搭建在线问卷的中高级小程序开发者、产品经理或市场调研人员。压缩包约9.77MB,内部通常包含.wxml结构文件、.wxss样式文件、.js逻辑文件、.json配置文件,以及图片图标等资源文件、数据库配置、测试数据和README文档,便于对照学习微信小程序的前后端协作模式。已有557人学习下载。通过研读源码,可掌握表单设计、数据采集与结果展示的核心实现,体验即用即走的轻量级应用特性,并学会利用社交分享、位置服务等功能提升问卷回收率。对于需要开展短期调研、课堂反馈或产品验证的团队,这套资源能帮助快速上手,避免从零搭建的重复劳动。
1. 这套问卷小程序源码包,值得你拆开改一遍
拿到手的是一个叫“问卷调查小程序.rar”的压缩包,里面不是成品演示,而是一套完整的微信小程序前端工程,外加数据库配置和示例数据。对这个包的正确打开方式,不是直接上传审核,而是把它当成一个可跑通的基线项目,把问卷设计、题目渲染、提交存储这条路走通,然后再替换成自己的业务逻辑。适合两类人:一是刚接触微信小程序、想知道一套正经项目文件怎么组织的开发者;二是做市场调研或教育研究、需要用小程序快速发问卷并且能拿到结构化数据的人。这套代码用的是原生小程序框架,不依赖 uniapp 或第三方 UI 库,意味着你只要会用微信开发者工具就能改,最大的收益是能摸清wxml怎么动态渲染表单、js里如何管理问卷状态,以及微信小程序项目实例里数据流的完整走向。下面从源码结构开始逐层拆。
2. 源码包里的文件体系:从wxml到json的职责划分
2.1 解压之后先看顶层目录
拿到.rar后,先别急着导入开发者工具,用rar解压出来,对照微信小程序的标准工程结构确认一下完整性。一个可用的小程序项目至少要包含pages、app.js、app.json、app.wxss和project.config.json,这套问卷源码基本也是这个骨架,只是一些资源包可能把wxss抽到了styles目录,或者引入了utils工具文件夹。先说顶层四个文件的分工,这是理解后面所有代码的前提:
| 文件 | 职责 | 该工程里对应的典型内容 |
|---|---|---|
app.js | 全局逻辑,注册小程序实例,挂载公共数据或方法 | 全局用户信息、问卷提交入口 |
app.json | 全局配置,定义页面路由、窗口样式、网络超时等 | 页面列表pages/index/index、pages/survey/survey |
app.wxss | 全局样式,作用于所有页面 | 通用按钮、表单控件样式 |
project.config.json | 开发者工具配置,含 appid、编译设置 | appid占位符,需替换为自己的 |
打开app.json你会看到类似这样的片段:
{ "pages": [ "pages/index/index", "pages/survey/survey", "pages/result/result" ], "window": { "navigationBarTitleText": "问卷调查", "navigationBarBackgroundColor": "#4A90E2" } }这里pages数组的第一项就是小程序的启动页面。这套源码把首页设为问卷列表页,survey是答题页,result用于提交后的展示。注意navigationBarTitleText在window里配置的是全局标题,如果你希望每个页面显示不同标题,需要在各页面的.json里单独设navigationBarTitleText,微信小程序页面配置优先于全局配置。这也是热搜里经常提到的“小程序动态设置标题”的一个入口,页面级配置是静态的,动态改标题得用wx.setNavigationBarTitle,后面章节会具体说。
2.2 一个页面的四件套:.wxml、.wxss、.js、.json
以答题页pages/survey/survey为例,一个页面由四个同名前缀文件组成。.wxml决定结构,类似 HTML 但用的是小程序标签,比如view、text、radio-group;.wxss决定样式,写法接近 CSS 但支持rpx单位,适配不同屏幕宽度;.js里写的是 Page 构造器,注册数据、生命周期函数和事件处理;.json则控制这个页面的窗口表现,比如是否允许下拉刷新、标题文字。
下面是从这套问卷源码里抽出来的动态题目渲染结构。问卷的题目不是写死在wxml里的,而是存在data数组里,通过wx:for循环输出,这样做的好处是新增一道题只需要改数据,不需要改页面:
<view class="question-card" wx:for="{{questions}}" wx:key="id"> <text class="q-title">{{index + 1}}. {{item.title}}</text> <view wx:if="{{item.type === 'radio'}}"> <radio-group bindchange="handleRadioChange">Page({ data: { questions: [ { id: 'q1', title: '你使用小程序进行问卷调查的频率是?', type: 'radio', options: [ { label: '每周多次', value: 'high' }, { label: '偶尔', value: 'medium' }, { label: '几乎没有', value: 'low' } ], answer: '' }, { id: 'q2', title: '你希望问卷支持哪些题型?', type: 'textarea', answer: '' } ] }, handleRadioChange(e) { const qid = e.currentTarget.dataset.qid; const value = e.detail.value; this.setData({ [`questions[${this._findIndex(qid)}].answer`]: value }); }, _findIndex(qid) { return this.data.questions.findIndex(q => q.id === qid); } });setData的 key 支持路径表达式,比如questions[0].answer,这套源码正是利用这个特性避免在循环里为每一道题单独绑定一个处理函数,而是通过dataset.qid动态定位题目。_findIndex前面的下划线不是语法要求,而是代码约定,表示内部方法。这里有一个新手常踩的坑:直接修改this.data.questions[0].answer = value不会触发视图更新,必须通过setData,并且路径中的数组索引要写进字符串里。
3. 问卷核心逻辑复现:题型渲染、选项联动与校验
3.1 单选、多选、填空三态的通用渲染方案
多数问卷小程序的题型逃不开单选、多选和文本填空。这套源码里用item.type字段做分支,radio走radio-group,checkbox走checkbox-group,textarea走textarea标签。一个更省代码的做法是把checkbox-group也归并到同一个渲染块里,因为小程序的checkbox-group事件回调里e.detail.value是一个数组,这意味着多选的存储结构应该设计成数组,而不是逗号拼接字符串。
看这段多选处理的示例:
handleCheckboxChange(e) { const qid = e.currentTarget.dataset.qid; const selectedValues = e.detail.value; // 数组 this.setData({ [`questions[${this._findIndex(qid)}].answer`]: selectedValues }); }对于填空类型,textarea的bindinput返回的e.detail.value是字符串,和单选多选的数据类型不同。如果你在提交时统一做toString()操作,会把数组转成a,b,c这样的字符串,但空数组会变成空字符串,这在需要区分“未作答”和“作答为空”的场景会丢失信息。比较稳妥的做法是提交前遍历题目,根据type判断是否符合预期类型,不符合的单独处理。
3.2 题目跳转逻辑:用showConditions控制可见性
问卷里经常出现“如果选 A 则跳到第 5 题”的需求。这套源码虽然定位是轻量级问卷,但作者用了一个字段visibleWhen来实现基础的条件展示。核心思路是:给每道题挂一个visibleWhen对象,值为{ questionId: 'q1', value: 'high' },表示“当 q1 题的答案是 high 时,本题才展示”。渲染层用wx:if判断这个条件是否成立,计算函数放在wxs里或提前在js里算好。
在wxml中控制可见性可能写成这样:
<view wx:if="{{item.visibleWhen && isVisible(item.visibleWhen)}}"> <!-- 题目内容 --> </view>但因为wxml里不能直接调用 js 方法,所以一般有两种做法:一是在wxs文件里写isVisible函数,二是在setData之前遍历题目,把visible字段算好写进data。后者更直白,也方便在提交时只收集可见题的答案。我倾向在js里维护一个_computeVisibility方法,每当任一题目的 answer 变化后就重新计算所有题目的visible状态,然后整体setData一次,避免在模板里堆逻辑。
3.3 必填校验与提交前的数据清洗
问卷不能允许空答案被提交,尤其单选和多选。这个源码包里应该有一个submitSurvey方法,大致流程是:
submitSurvey() { const incomplete = this.data.questions.filter(q => { if (q.required === false) return false; const a = q.answer; return a === '' || a === undefined || (Array.isArray(a) && a.length === 0); }); if (incomplete.length > 0) { wx.showToast({ title: `请完成第${this._findIndex(incomplete[0].id) + 1}题`, icon: 'none' }); return; } // 构造提交数据 const payload = this.data.questions .filter(q => q.visible !== false) .map(q => ({ id: q.id, title: q.title, answer: q.answer })); this._upload(payload); }这里有两个细节值得注意。第一,required字段可以单独控制某题是否必填,避免所有题都强制。第二,过滤visible !== false的目的是只提交用户实际看到的题,因为条件隐藏的题虽然不在界面上,但它的answer可能残留之前选择的值,也会被收集进数组,直接提交会造成脏数据。_upload是一个你可以替换成自己后端接口的占位函数,源码里可能用的是wx.request或云函数。
4. 从源码包到可运行项目:导入、配置与真机调试
4.1 用微信开发者工具导入源码
解压后打开微信开发者工具,选择“导入项目”,目录指向解压出的工程根目录(包含project.config.json的层级)。如果工具提示 appid 不合法,有两种解决办法:一是你自己的注册小程序 appid,二是在工具里选择“测试号”。测试号不能调用支付、获取手机号等敏感接口,但跑通问卷流程没问题。project.config.json里的"appid": "touristappid"是典型占位符,必须替换。
导入后先看三个地方:app.json里的页面路由是否存在对应文件,project.config.json里的libVersion基础库版本是否高于代码中用到的 API 要求,以及networkTimeout是否合理。如果编译报错module 'xxx' is not defined,多半是路径引用大小写不一致,因为微信小程序对文件路径大小写敏感,Windows 解压出来的文件名也许是Pages开头,但代码里写的是pages,导入前需要改成一致。
4.2 后台接口的几种接法
这套问卷小程序的提交目标可以是任意后端。源码里如果直接写了wx.request的 URL,大概率是http://localhost:8080之类的开发地址,真机上无法访问。你的改造方向取决于资源是什么形式:
| 后端方案 | 需要改的位置 | 适用场景 |
|---|---|---|
| 微信云开发 | wx.request换成wx.cloud.callFunction | 不想自己买服务器,数据量小 |
| 自建服务器 + HTTP | url改为线上 HTTPS 域名,并在控制台配置 request 合法域名 | 已有后台系统 |
| JSON 文件存储(学习用) | 提交结果写入wx.setStorageSync | 仅本地演示 |
以云开发为例,在app.js里初始化:
App({ onLaunch() { if (!wx.cloud) { console.error('请使用 2.2.3 或以上基础库'); } else { wx.cloud.init({ env: 'your-env-id', traceUser: true }); } } });然后在提交方法里调用云函数submit_answer,云函数在 Node 环境中接收event里的题目数组,写入云数据库集合surveys。这种方案的好处是不用配服务器域名,也不用管 SSL 证书,非常适合毕设和内部调研工具。注意云开发要求开通按量付费环境,但个人开发者有免费额度的套餐,足够支撑低流量问卷。
4.3 真机预览时必查的五个坑
真机预览比模拟器多出很多问题,尤其是网络和分享相关。这份源码里如果涉及微信小程序的转发功能,通常会用到wx.showShareMenu和页面内button open-type="share"。真机调试时常见的坑有五个:
第一,wx.request的 url 必须是 HTTPS 且已经在小程序管理后台的“开发管理-服务器域名”里添加,开发工具里可以勾选“不校验合法域名”绕过,但真机不行。第二,如果使用了wx.getLocation获取用户位置用于问卷附带地理信息,需要在app.json里声明permission并配置scope.userLocation的用途说明,否则调用会直接失败。第三,基础库版本过低会导致某些 API 不存在,比如wx.cloud在旧版微信里是 undefined。第四,在tabBar页面中调用wx.navigateTo跳转无效,必须用wx.switchTab,但问卷的场景很少用 tabBar,可以忽略。第五,分享出去的问卷页,在二次打开时需要利用页面参数options.q或自定义参数来区分来自分享还是直接进入,否则可能出现同一份数据反复提交的问题。
下面是一个带分享参数的页面加载与去重示例:
Page({ onLoad(query) { if (query.fromShare) { this.setData({ shareToken: query.fromShare }); } }, onShareAppMessage() { return { title: '帮我填一份问卷', path: `/pages/survey/survey?fromShare=${this.data.shareToken}` }; } });path里的shareToken可以在提交时传给后端,用于统计同一份问卷被多少人打开过,这也是微信小程序跳转链接从生成到触发的一种典型处理思路:通过页面路径携带业务标识,在onLoad的query中解析。注意onShareAppMessage返回值里的path必须以/开头,且不包含http前缀。
5. 把源码包改造成可商用问卷系统的三个进阶点
5.1 导出答卷数据到 Excel
问卷收集的数据如果只待在云数据库里,分析起来很不直观。一个实用的改造是在管理端用云函数导出 Excel。云函数里安装node-xlsx依赖,将查询到的记录转成二维数组,返回给小程序端保存为本地文件。关键代码如下:
const xlsx = require('node-xlsx'); exports.main = async (event) => { const db = cloud.database(); const { data } = await db.collection('surveys').limit(1000).get(); const rows = data.map(item => [ item.createTime, item.answerList.map(a => a.answer).join('|') ]); const buffer = xlsx.build([{ name: '问卷结果', data: rows }]); return buffer.toString('base64'); };小程序端收到 base64 后,用wx.getFileSystemManager().writeFile写入临时文件,再调用wx.openDocument打开,这样就能直接分享 Excel 文件给协作同事。注意云函数返回数据大小限制约 6MB,超过 1000 条问卷时要做分页拉取或直接生成 CSV。
5.2 用雷达图展示单选题分布
热搜词里提到“小程序雷达图”,问卷系统里最常用到雷达图的场景是 NPS 或能力自评。实现方式是用ec-canvas组件接入 ECharts,把单选题答案按维度聚合后渲染。核心配置在echarts.setOption中:
option = { radar: { indicator: questionList.map(q => ({ name: q.shortTitle, max: 5 })) }, series: [{ type: 'radar', data: [{ value: avgScores, name: '平均得分' }] }] };需要注意ec-canvas的canvasId在页面内必须唯一,否则图表不渲染。另外 ECharts 体积较大,建议按需引入,只打包雷达图模块,否则小程序包体积会超过主包 2MB 的上限。实际项目中可以把问卷结果页设为分包页面,雷达图独立成组件,这样主包大小不会影响审核。
5.3 动态修改导航栏标题
问卷页可能被不同项目复用,比如同一套题既用于“客户满意度调查”,又用于“员工内部调研”。此时不应每次改代码,而是通过页面参数来设置。在survey.json里不要写死标题,在onLoad中根据query.title调用wx.setNavigationBarTitle:
Page({ onLoad(query) { if (query.title) { wx.setNavigationBarTitle({ title: decodeURIComponent(query.title) }); } } });这里的decodeURIComponent很重要,因为从分享路径传递中文字符时,小程序不会自动解码,直接赋值会得到%E6%BB%A1%E6%84%8F%E5%BA%A6这样的乱码。配合onShareAppMessage里path参数拼接title=${encodeURIComponent('客户满意度调查')},就能让一份源码承载多重问卷主题,这也是把这套源码包从学习项目提升到可复用工具的最低成本优化。
本文还有配套的精品资源,点击获取