恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
微信小程序投票系统完整源码解析:从创建到发布实战指南
首页
资讯中心
/
微信小程序投票系统完整源码解析:从创建到发布实战指南
微信小程序投票系统完整源码解析:从创建到发布实战指南
发布时间:2026/10/14 2:27:52
简介这份资源是微信小程序投票系统的完整源码包面向希望学习小程序开发或快速搭建投票应用的开发者可用于意见收集、评选活动、市场调研等场景。压缩包共90个文件约153KB以49个wxss样式文件、15个png图片资源、8个js脚本、8个json配置和6个wxml结构文件为主另含php接口、sql建表脚本与说明文档覆盖页面结构、样式、逻辑与数据交互各层。源码按小程序标准目录组织包含全局配置、页面模块、工具函数、自定义组件、数据模型与模拟数据等部分投票创建、发布与结果展示流程完整便于理解页面生命周期、网络请求封装与数据可视化实现。目前已有3644人学习下载适合作为入门练手或二次开发的基础可在此基础上扩展登录、权限控制与防刷机制。1. 从一份投票 Demo 源码说起小程序里跑通「创建-发布-投票」到底要几步很多人第一次接到「做个微信小程序投票」的需求时第一反应是去搜现成模板结果下回来一堆跑不起来的半成品要么缺后端接口要么页面写死数据要么连app.json的页面注册都没配全。这份「微信小程序投票系统创建投票发布 demo 完整源码」的价值就在这儿——它把「创建投票 → 发布 → 参与投票 → 看结果」这条最短闭环完整跑通了前端页面、数据流转、页面跳转逻辑都在适合两类人一是刚接触小程序、想拿一个能跑的真实项目练手的开发者二是手里有投票类需求、想快速摸清页面结构和数据组织方式的从业者。它不解决高并发和复杂权限但能让你在半小时内看到一个投票小程序长什么样、每个页面之间怎么串起来。下面我按「先看懂结构、再动手跑、最后避坑」的顺序拆一遍。2. 拆开源码包目录结构、页面注册与数据流向拿到一个.zip源码包别急着双击导入开发者工具先解压看目录。小程序项目的骨架全在根目录那几个配置文件里页面逻辑则按pages分文件夹存放。这一章先把「它由什么组成」讲清楚你才知道后面改哪里。2.1 根目录三件套app.json、app.js、app.wxss小程序和普通 HTML 页面最大的区别是它有一套全局配置来管理页面路由和窗口表现。解压后根目录通常能看到这三个文件它们是整个项目的入口。app.json负责注册所有页面路径、设置导航栏和 tabBar。投票 Demo 一般至少有四个页面首页投票列表、创建投票页、投票详情页、结果页。页面没在这里注册跳转就会直接报page not found这是新手最常见的翻车点。{ pages: [ pages/index/index, pages/create/create, pages/detail/detail, pages/result/result ], window: { navigationBarTitleText: 投票 Demo, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black }, style: v2, sitemapLocation: sitemap.json }这段配置里pages数组的第一项就是小程序启动后默认打开的页面顺序不能随便调。window里的navigationBarTitleText是全局标题单个页面可以在自己的.json里覆盖。style: v2是新版组件样式老项目如果没有这行按钮圆角和间距会和新版基础库对不上视觉上会「怪怪的」。app.js里一般放全局数据比如投票列表的初始数据、用户信息占位。Demo 级别通常用globalData存一份内存数据页面之间靠getApp()取用。// app.js App({ globalData: { voteList: [], // 全局投票列表创建后往这里 push currentVote: null // 当前查看的投票对象 }, onLaunch() { // 启动时初始化一条示例数据方便直接看到效果 this.globalData.voteList [ { id: 1, title: 示例投票, options: [{ text: 选项A, count: 0 }, { text: 选项B, count: 0 }] } ]; } });globalData是内存级的小程序一关数据就没了。Demo 用它图省事但你要真上线必须换成wx.setStorageSync本地缓存或后端接口否则用户创建完投票、退出再进来就全没了——这个坑后面还会细说。2.2 pages 目录每个页面的四文件结构小程序每个页面是一个独立文件夹里面固定四类文件.js逻辑、.wxml结构、.wxss样式、.json页面配置。投票 Demo 的核心交互就藏在这几个文件里。以创建投票页为例.wxml负责渲染输入框和「添加选项」按钮.js负责收集用户输入、校验、写入globalData然后跳转到详情页。这里的关键是bindinput和bindtap两个事件绑定前者实时拿输入值后者触发按钮逻辑。!-- pages/create/create.wxml -- view classcontainer input classtitle-input placeholder请输入投票标题 bindinputonTitleInput / view wx:for{{options}} wx:keyindex classoption-row input placeholder选项{{index1}} bindinputonOptionInput>// pages/create/create.js 发布逻辑 publishVote() { const { title, options } this.data; if (!title || options.length 2) { wx.showToast({ title: 标题和至少两个选项不能为空, icon: none }); return; } const app getApp(); const newVote { id: Date.now(), // 用时间戳当临时 id避免重复 title, options: options.map(text ({ text, count: 0 })) }; app.globalData.voteList.push(newVote); wx.navigateTo({ url: /pages/detail/detail?id${newVote.id} }); }Date.now()生成时间戳做 id 是 Demo 常用做法简单但有个隐患同一毫秒内连续创建两条会撞 id。真实项目里应该用后端返回的唯一 id。wx.navigateTo只能跳转到非 tabBar 页面且页面层级最多十层跳太深要用redirectTo或navigateBack这也是后面避坑章要讲的点。3. 跑起来导入、编译与真机预览的完整步骤看懂结构之后下一步是让它真的在开发者工具里跑起来。这一章按顺序走一遍导入项目、检查基础库、编译预览、真机调试。每一步都有容易卡住的地方我按实际操作的顺序写。3.1 导入项目与 AppID 的选择打开开发者工具选择「导入项目」目录指向你解压后的文件夹。这里第一个岔路口是 AppID如果你有自己的小程序 AppID直接填如果没有选「测试号」也能跑但测试号不支持真机预览的部分能力比如某些需要登录态的接口。导入后工具会自动读取app.json并生成页面预览。如果报「未找到 app.json」说明你选的目录层级不对——要选到包含app.json的那一层而不是它的上一层。这个错误我见过太多次本质就是目录选深了一层或浅了一层。3.2 基础库版本与编译模式导入成功后看工具右上角的「详情」→「本地设置」里面有个「调试基础库」版本。投票 Demo 用到的是基础组件和globalData对基础库要求不高选 2.20 以上的稳定版基本都能跑。如果编译后页面白屏先看控制台有没有Component is not found或wx:for相关警告多半是基础库太低导致某些语法不识别。编译模式默认是「普通编译」从app.json的第一个页面启动。如果你想直接调试详情页可以在「添加编译模式」里指定启动页面和启动参数比如pages/detail/detail?id1这样省去每次手动点进去的麻烦。调试带参页面时这个功能特别省时间。3.3 真机预览与常见报错点「预览」生成二维码用微信扫码就能在手机上跑。真机预览最容易遇到两类问题一是网络请求失败如果 Demo 里有请求后端二是样式在真机上和模拟器不一致。样式不一致多半是单位问题。小程序推荐用rpx它按 750 设计稿宽度自适应模拟器窗口宽度和手机实际宽度不同用px写死就会错位。检查.wxss里有没有大量px有的话按 750 基准换算成rpx。/* 推荐用 rpx 做自适应 */ .container { padding: 32rpx; } .title-input { height: 88rpx; font-size: 32rpx; } /* 不推荐px 写死真机容易错位 */ .container { padding: 16px; }rpx的换算规则是屏幕宽度恒等于 750rpx所以 1rpx 在 375px 宽的屏幕上等于 0.5px。设计稿给多少 px乘以 2 就是 rpx 值。这个换算习惯一旦养成跨机型适配会省很多事。3.4 用本地缓存替换 globalData 的改造前面说过globalData一关就没如果你想让创建的投票在退出后还在最小改动是把写入和读取换成wx.setStorageSync/wx.getStorageSync。这不是必须的但改完你就能看到一个「数据能留住」的版本更接近真实可用状态。// 改造后的发布逻辑写入本地缓存 publishVote() { const { title, options } this.data; if (!title || options.length 2) { wx.showToast({ title: 标题和至少两个选项不能为空, icon: none }); return; } const voteList wx.getStorageSync(voteList) || []; const newVote { id: Date.now(), title, options: options.map(text ({ text, count: 0 })) }; voteList.push(newVote); wx.setStorageSync(voteList, voteList); // 同步写入简单直接 wx.navigateTo({ url: /pages/detail/detail?id${newVote.id} }); }wx.setStorageSync是同步写入Demo 数据量小用着没问题数据量大时同步会阻塞应该用异步的wx.setStorage。读取时用|| []兜底防止第一次进来缓存为空导致push报错。这个兜底写法是血泪经验不加的话首次运行必崩。4. 避坑与排查投票 Demo 最容易翻车的五个地方Demo 能跑不等于没问题下面这五个坑是我在拆这类源码时反复遇到的每个都按「现象 → 原因 → 解决」写清楚你对照排查能省不少时间。4.1 页面跳转报 page not found现象点「发布投票」后控制台报navigateTo:fail page pages/detail/detail is not found。原因app.json的pages数组里没注册目标页面或者路径拼写和实际文件夹名不一致大小写、单复数都算。解决打开app.json核对pages数组确保每个跳转目标都在里面且路径和文件夹层级完全对应。小程序路径区分大小写Detail和detail是两个东西。4.2 投票后票数不更新现象点了选项count加了但页面上的数字没变。原因直接改了globalData里的对象属性但没有调用this.setData触发视图刷新。小程序的数据绑定是单向的改了数据源不等于页面会重绘。解决把要展示的数据在onLoad时用setData同步到页面data投票后重新setData一次。记住一个原则凡是页面上要显示的数据都必须经过setData。onVote(e) { const index e.currentTarget.dataset.index; const vote this.data.vote; vote.options[index].count 1; this.setData({ vote }); // 必须重新 setData视图才会更新 }4.3 真机上输入框被键盘顶飞现象在创建页点输入框键盘弹起后页面整体上移输入框跑到屏幕外。原因小程序的输入框默认会随键盘调整页面位置如果页面用了固定定位或高度写死就会错位。解决给输入框外层加adjust-position属性控制或者用cursor-spacing设置光标与键盘的距离。简单做法是把输入区域放在页面顶部避免被键盘遮挡。4.4 选项数量没有上限导致页面卡死现象疯狂点「添加选项」加到几十个后页面明显卡顿。原因Demo 通常不限制选项数量wx:for渲染大量节点会拖慢渲染。解决在addOption里加一个上限判断比如最多 10 个超过就showToast提示。这是产品层面的约束也是性能保护。4.5 时间戳 id 重复导致投票串数据现象快速连续创建两个投票第二个覆盖了第一个或者详情页打开的是错的。原因Date.now()在同一毫秒内可能返回相同值两条数据 id 撞了。解决用Date.now() Math.random()拼接或者维护一个自增计数器存在缓存里。真实项目直接交给后端生成 id前端别自己造。5. 进阶把 Demo 改成能用的投票工具我固定走这三步Demo 跑通只是起点真要拿去用还得补上数据持久化、结果可视化和边界校验。这三步是我每次改造这类源码的固定动作按顺序做基本能把它从「能看」推到「能用」。第一步数据落缓存并加版本号。前面已经换成wx.setStorageSync但还要加一个version字段。以后数据结构变了靠版本号做迁移不然老用户缓存里的旧结构会让新代码报错。我一般会在app.js的onLaunch里检查版本不一致就清空重建。第二步结果页用进度条代替纯数字。纯数字票数不直观用view的宽度百分比做进度条视觉上立刻专业一截。计算方式是当前选项票数除以总票数乘以 100 得到百分比绑到style的width上。!-- pages/result/result.wxml -- view wx:for{{vote.options}} wx:keyindex classresult-row text{{item.text}}/text view classbar-bg view classbar-fill stylewidth: {{total ? (item.count / total * 100) : 0}}%/view /view text{{item.count}} 票/text /viewtotal是所有选项票数之和在onLoad里算好存进data。style里做三元判断是为了防止总票数为零时除以零得到NaN宽度变成NaN%浏览器会忽略进度条就不显示。这个除零保护是必须的不加的话新建投票的结果页进度条全是空的。第三步加输入校验和防重复提交。标题去空格后不能为空选项去空格后不能为空且不能重复发布按钮点一次后要禁用防止连点创建多条。防连点用一个isPublishing标志位进入函数先判断发完再置回。publishVote() { if (this.data.isPublishing) return; // 防连点 this.setData({ isPublishing: true }); const title this.data.title.trim(); const options this.data.options.map(t t.trim()).filter(Boolean); const unique [...new Set(options)]; if (!title || unique.length 2) { wx.showToast({ title: 标题和至少两个不重复选项不能为空, icon: none }); this.setData({ isPublishing: false }); return; } // ...写入缓存并跳转 this.setData({ isPublishing: false }); }trim()去首尾空格filter(Boolean)去掉空字符串new Set去重。这三板斧下去用户乱输基本都能拦住。isPublishing标志位在跳转前要记得置回false否则用户返回后再点就没反应了——这个后悔药我吃过不止一次后来养成习惯凡是加锁的地方成功和失败分支都要解锁。从那以后我每次拆这类 Demo 源码都强制自己先跑通主流程再按「缓存、校验、防连点」三件套过一遍最后才看样式。希望这份拆解帮到你少走点我踩过的弯路。本文还有配套的精品资源点击获取