├── cloudfunctions
├── vote_exe
│ ├── config.json
│ ├── package.json
│ └── index.js
├── vote_init
│ ├── config.json
│ ├── package.json
│ └── index.js
└── data.json
├── miniprogram
├── pages
│ └── vote
│ │ ├── vote.json
│ │ ├── vote.wxml
│ │ ├── vote.wxss
│ │ └── vote.js
├── app.js
├── sitemap.json
└── app.json
├── readme.md
├── webview
└── index.html
└── project.config.json
/cloudfunctions/vote_exe/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "permissions": {
3 | "openapi": [
4 | ]
5 | }
6 | }
--------------------------------------------------------------------------------
/cloudfunctions/vote_init/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "permissions": {
3 | "openapi": [
4 | ]
5 | }
6 | }
--------------------------------------------------------------------------------
/miniprogram/pages/vote/vote.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "navigationBarTitleText": "云开发投票"
4 | }
--------------------------------------------------------------------------------
/miniprogram/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | App({
3 | onLaunch: function () {
4 | wx.cloud.init({
5 | // env: '云开发环境ID',
6 | traceUser: true
7 | })
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/miniprogram/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/cloudfunctions/vote_exe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vote_exe",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "~2.3.2"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/vote_init/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vote_init",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "~2.3.2"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "_id": "b45a21d55ff30a39039278534bfe346e",
3 | "open": true,
4 | "options": [
5 | "小白:从来没有接触",
6 | "入门:正在学习和尝试DEMO",
7 | "了解:懂一些使用,敢于尝试小项目",
8 | "掌握:具备开发线上项目的实力",
9 | "熟悉:专业踩坑达人,多个线上项目"
10 | ],
11 | "title": "你的云开发使用经验如何?",
12 | "code": "SIN",
13 | "one": false
14 | }
--------------------------------------------------------------------------------
/miniprogram/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/vote/vote"
4 | ],
5 | "window": {
6 | "backgroundColor": "#FFFFFF",
7 | "backgroundTextStyle": "light",
8 | "navigationBarBackgroundColor": "#5586ff",
9 | "navigationBarTitleText": "云开发投票",
10 | "navigationBarTextStyle": "white"
11 | },
12 | "style": "v2",
13 | "sitemapLocation": "sitemap.json"
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/vote_init/index.js:
--------------------------------------------------------------------------------
1 | const cloud = require('wx-server-sdk')
2 | cloud.init({
3 | env: cloud.DYNAMIC_CURRENT_ENV
4 | })
5 | const db = cloud.database()
6 |
7 | exports.main = async (event, context) => {
8 | const openid = event.userInfo.openId;
9 | const result = (await db.collection('vote_user').where({
10 | openid:openid
11 | }).get()).data;
12 | if(result.length!=0){
13 | const code = event.code;
14 | let project = result[0].project[code];
15 | return project?project:{}
16 | }
17 | else{
18 | await db.collection('vote_user').add({
19 | data:{
20 | openid:openid,
21 | project:{}
22 | }
23 | })
24 | return {}
25 | }
26 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # 云开发投票小程序
2 |
3 | ## 项目介绍
4 |
5 | 云开发B站趣味项目直播【第1期】完整项目。
6 | - 投票小程序,可以进行单选投票,投票者可以实时查看各个选项的投票数;
7 | - 支持在线变更投票内容,自动更新到所有投票者;
8 | - 支持仅投一次,防止多次投票变更
9 | - 支持项目在线开启和关闭,自动更新到所有投票者;
10 | - 项目理论可以支撑多选投票类型【暂为实现】
11 | - 极简项目,无任何框架纯原生小程序、云开发
12 | - 极简不失典型,各种高效数据库的操作、云函数操作
13 |
14 | ## 项目来源
15 |
16 | 2021年1月6日,B站直播前,由于预定的投票项目太老旧,不能支撑趣味直播。提前花2小时拼出来一个前端虚拟化的投票小程序
17 | 直播时现场构建数据模型,对本地小程序进行改造,全面接入云开发。纯手工打造,热乎!
18 |
19 | 数据设计可以支撑CMS系统的使用。
20 |
21 | ## 部署步骤
22 |
23 | 1. 打开 app.js,替换 env 为自己的云开发环境ID,如果未开通则先开通云开发
24 | 2. 进入控制台,创建 vote_mess 和 vote_user 数据库集合,vote_mess 集合需要变更权限为所有人可读。
25 | 3. 进入 vote_mess 集合,导入 cloudfunctions 中的 data.json 数据。
26 | 3. 将cloudfunctions文件夹中两个云函数上传部署(云端安装)到云开发中,注意选择在第一步中的云开发环境上传。
27 | 4. 在开发者工具中选择编译模式为【有效进入】,即可开始体验之旅
28 |
29 | ## 作者
30 |
31 | - 腾讯云·云开发团队:zira
--------------------------------------------------------------------------------
/miniprogram/pages/vote/vote.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{info}}
5 | 关闭小程序
6 |
7 |
8 |
9 | {{project.title}}
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 | function hidden(user){
23 | return JSON.stringify(user)=="{}";
24 | }
25 | module.exports = {hidden:hidden}
26 |
--------------------------------------------------------------------------------
/cloudfunctions/vote_exe/index.js:
--------------------------------------------------------------------------------
1 | const cloud = require('wx-server-sdk')
2 | cloud.init({
3 | env: cloud.DYNAMIC_CURRENT_ENV
4 | })
5 | const db = cloud.database()
6 | const _ = db.command
7 |
8 | exports.main = async (event, context) => {
9 | const openid = event.userInfo.openId;
10 | const result = (await db.collection('vote_user').where({
11 | openid:openid
12 | }).get()).data;
13 | if(result.length!=0){
14 | const code = event.code;
15 | let project = result[0].project[code];
16 | let newData = event.select;
17 | let diff = {}
18 | for(let i in newData){
19 | diff[i] = _.inc(1)
20 | }
21 | for(let i in project){
22 | if(diff[i]) delete diff[i];
23 | else diff[i] = _.inc(-1)
24 | }
25 | if(Object.keys(diff).length==0){
26 | return newData;
27 | }
28 | await db.collection('vote_mess').where({
29 | code:code
30 | }).update({
31 | data:{
32 | number:diff
33 | }
34 | })
35 | await db.collection('vote_user').where({
36 | openid:openid
37 | }).update({
38 | data:{
39 | project:{
40 | [event.code]:_.set(newData)
41 | }
42 | }
43 | })
44 |
45 | return newData
46 | }
47 | }
--------------------------------------------------------------------------------
/webview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 投票实时展示
6 |
7 |
8 |
9 |
10 |
11 |
12 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "miniprogramRoot": "miniprogram/",
3 | "cloudfunctionRoot": "cloudfunctions/",
4 | "setting": {
5 | "urlCheck": true,
6 | "es6": true,
7 | "enhance": true,
8 | "postcss": true,
9 | "preloadBackgroundData": false,
10 | "minified": true,
11 | "newFeature": true,
12 | "coverView": true,
13 | "nodeModules": false,
14 | "autoAudits": false,
15 | "showShadowRootInWxmlPanel": true,
16 | "scopeDataCheck": false,
17 | "uglifyFileName": false,
18 | "checkInvalidKey": true,
19 | "checkSiteMap": true,
20 | "uploadWithSourceMap": true,
21 | "compileHotReLoad": false,
22 | "useMultiFrameRuntime": true,
23 | "useApiHook": true,
24 | "useApiHostProcess": true,
25 | "babelSetting": {
26 | "ignore": [],
27 | "disablePlugins": [],
28 | "outputPath": ""
29 | },
30 | "enableEngineNative": false,
31 | "bundle": false,
32 | "useIsolateContext": true,
33 | "useCompilerModule": true,
34 | "userConfirmedUseCompilerModuleSwitch": false,
35 | "userConfirmedBundleSwitch": false,
36 | "packNpmManually": false,
37 | "packNpmRelationList": [],
38 | "minifyWXSS": true
39 | },
40 | "appid": "",
41 | "projectname": "云开发投票小程序",
42 | "libVersion": "2.8.1",
43 | "condition": {
44 | "plugin": {
45 | "list": []
46 | },
47 | "game": {
48 | "list": []
49 | },
50 | "gamePlugin": {
51 | "list": []
52 | },
53 | "miniprogram": {
54 | "list": [
55 | {
56 | "name": "有效进入",
57 | "pathName": "pages/vote/vote",
58 | "query": "code=SIN",
59 | "scene": null
60 | },
61 | {
62 | "name": "无效进入",
63 | "pathName": "pages/vote/vote",
64 | "query": "code=SINA",
65 | "scene": null
66 | }
67 | ]
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/miniprogram/pages/vote/vote.wxss:
--------------------------------------------------------------------------------
1 | page{
2 | display: flex;
3 | background:#F3F4F8;
4 | width: 100%;
5 | height: 100%;
6 | }
7 | .container{
8 | display: flex;
9 | flex-direction: column;
10 | width: 100%;
11 | height: 100%;
12 | }
13 | .title {
14 | font-size:40rpx;
15 | font-weight: 900;
16 | width: calc(100% - 60rpx);
17 | padding: 80rpx 30rpx 40rpx;
18 | }
19 | .options {
20 | width: 100%;
21 | padding: 30rpx 0;
22 | display: flex;
23 | flex-direction: column;
24 | }
25 | .options button {
26 | display: flex;
27 | width: 100% !important;
28 | margin-bottom: 20rpx;
29 | background: #FFFFFF;
30 | border-radius: 0 !important;
31 | text-align: left;
32 | font-size: 27rpx;
33 | font-weight: 500;
34 | line-height: 70rpx;
35 | padding:15rpx 30rpx;
36 | height: 100rpx;
37 | }
38 | .options button:active{
39 | background:#ececec;
40 | color:inherit;
41 | }
42 | .options button[type='select']{
43 | background:#c5d6ff;
44 | }
45 | .options button[type='loading']{
46 | background:#ffd2d2;
47 | }
48 | .options button text {
49 | width: 80%;
50 | }
51 | .options button span {
52 | width: 20%;
53 | text-align: right;
54 | font-size: 25rpx;
55 | color: #545454;
56 | }
57 |
58 |
59 |
60 | .spinner {
61 | width: 200rpx;
62 | height: 200rpx;
63 | margin: auto;
64 | background-color: #5586ff;
65 | border-radius: 100%;
66 | -webkit-animation: scaleout 1.0s infinite ease-in-out;
67 | animation: scaleout 1.0s infinite ease-in-out;
68 | }
69 | @-webkit-keyframes scaleout {
70 | 0% { -webkit-transform: scale(0.0) }
71 | 100% {
72 | -webkit-transform: scale(1.0);
73 | opacity: 0;
74 | }
75 | }
76 | @keyframes scaleout {
77 | 0% {
78 | transform: scale(0.0);
79 | -webkit-transform: scale(0.0);
80 | } 100% {
81 | transform: scale(1.0);
82 | -webkit-transform: scale(1.0);
83 | opacity: 0;
84 | }
85 | }
86 |
87 |
88 | .showinfo{
89 | width: 80%;
90 | margin: 30% auto;
91 | text-align: center;
92 | }
93 | .showinfo text {
94 | font-size:30rpx;
95 | color:#343434;
96 | line-height: 50rpx;
97 | letter-spacing: 3rpx;
98 | }
99 | .showinfo navigator {
100 | height: 85rpx;
101 | width: 400rpx;
102 | margin: 80rpx auto;
103 | background: #5586ff;
104 | color:#FFFFFF;
105 | line-height: 85rpx;
106 | font-size: 32rpx;
107 | }
--------------------------------------------------------------------------------
/miniprogram/pages/vote/vote.js:
--------------------------------------------------------------------------------
1 | // 项目,通过实时
2 | // 用户,调用云函数
3 | // 投票,调用云函数,自己和项目票数自增
4 |
5 | // JS-SDK : 未登录,匿名登录,邮件登录,手机登录
6 | // WEB-SDK: 未登录、微信登录(服务号)
7 |
8 | //=================虚拟数据源====================
9 | var tempdata = {
10 | SIN: {
11 | title: "你的云开发使用经验如何?", //投票标题
12 | options: [ //投票选项
13 | "小白:从来没有接触",
14 | "入门:正在学习和尝试DEMO",
15 | "了解:懂一些使用,敢于尝试小项目",
16 | "掌握:具备开发线上项目的实力",
17 | "熟悉:专业踩坑达人,多个线上项目"
18 | ],
19 | code: "SIN", //进入路径
20 | open: true, //允许投票
21 | one: false, //只允许一次
22 | number: { //投票数
23 | 0:32,
24 | 1:56,
25 | 2:34,
26 | 3:78,
27 | 4:40
28 | }
29 | }
30 | }
31 | //=================页面逻辑区====================
32 | var that = null
33 |
34 | Page({
35 | data: {},
36 | onLoad(options) {
37 | that = this;
38 | if (options.code != null) {
39 | that.code = options.code;
40 | that.init_project()
41 | } else {
42 | setInfo('T_OPEN')
43 | }
44 | },
45 | init_project() {
46 | that.watch = wx.cloud.database().collection('vote_mess').where({
47 | code:that.code
48 | }).watch({
49 | onChange(res){
50 | if (res.docs.length != 0) {
51 | let result = res.docs[0];
52 | setProject(result)
53 | if (that.data.user == null || result.number == null) {
54 | that.init_user()
55 | }
56 | } else {
57 | setInfo('T_OPEN')
58 | }
59 | },
60 | onError(err){
61 | setInfo('T_NET')
62 | }
63 | })
64 | },
65 | init_user() {
66 | netCall({
67 | name:'vote_init',
68 | data:{
69 | code:that.code
70 | },
71 | success(res){
72 | setUser(res.result)
73 | that.one = JSON.stringify(res.result)
74 | }
75 | })
76 | },
77 | vote(e) {
78 | if (that.data.project.open != true) {
79 | showModel('S_CLOSE')
80 | return;
81 | }
82 | if (that.data.user[e.currentTarget.dataset.i] == true) {
83 | return;
84 | }
85 | if (that.data.project.one && that.one != '{}') {
86 | showModel('S_ONE')
87 | return;
88 | }
89 | let tempvote = {}
90 | if(e.currentTarget.dataset.mul){
91 | for(let i in that.data.user){
92 | if(that.data.user[i]==true){
93 | tempvote[i] = true
94 | }
95 | }
96 | if(Object.keys(tempvote).length==0){
97 | showModel('S_EMPTY')
98 | return;
99 | }
100 | }
101 | else{
102 | tempvote[e.currentTarget.dataset.i] = true
103 | }
104 | that.setData({
105 | load: tempvote
106 | })
107 | netCall({
108 | name:'vote_exe',
109 | data:{
110 | select:tempvote,
111 | code:that.code
112 | },
113 | success(res){
114 | setUser(res.result)
115 | that.one = JSON.stringify(res.result)
116 | }
117 | })
118 | },
119 | select(e) {
120 | let key = 'user.'+e.currentTarget.dataset.i
121 | that.setData({
122 | [key]:!that.data.user[e.currentTarget.dataset.i]
123 | })
124 | },
125 | onShareAppMessage() {
126 | return {
127 | title: `投票-${that.data.project.title}`,
128 | path: `/pages/vote/vote?code=${that.code}`
129 | }
130 | }
131 | })
132 | //=================功能封装区====================
133 | const INFO = {
134 | T_NET: '网络服务出现异常,请稍后再试\n如有问题请联系管理员处理',
135 | T_OPEN: '无法找到对应的投票项目,请重新尝试\n如有问题请联系管理员处理',
136 | S_ONE: ['项目设置只能投票一次', '提示'],
137 | S_CLOSE: ['当前不能投票,请等待投票开启', '提示'],
138 | S_LOAD: ['请等待本次投票操作完毕后再变更选择', '提示'],
139 | S_FAIL: ['在操作时遇到了一些网络问题,请稍后再试', '网络错误'],
140 | S_EMPTY: ['多选不能为空,请至少选择一个','提示']
141 | }
142 | function setInfo(info) {
143 | that.setData({
144 | info: INFO[info]
145 | })
146 | }
147 | function setUser(user) {
148 | that.setData({
149 | user,
150 | load: null
151 | })
152 | }
153 | function setProject(project) {
154 | that.setData({
155 | project
156 | })
157 | }
158 | function setLoad(load) {
159 | that.setData({
160 | load
161 | })
162 | }
163 | function showModel(info) {
164 | wx.showModal({
165 | title: INFO[info][1],
166 | content: INFO[info][0],
167 | showCancel: false
168 | })
169 | }
170 | function netCall(obj) {
171 | if (that.netload != true) {
172 | that.netload = true;
173 | wx.cloud.callFunction({
174 | name: obj.name,
175 | data: (obj.data ? obj.data : {}),
176 | success: (res) => {
177 | typeof obj.success == "function" ? obj.success(res) : null
178 | },
179 | fail: (err) => {
180 | showModel('S_FAIL')
181 | console.log(err)
182 | typeof obj.fail == "function" ? obj.fail(err) : null
183 | },
184 | complete() {
185 | that.netload = false;
186 | }
187 | })
188 | } else {
189 | showModel('S_LOAD')
190 | }
191 | }
--------------------------------------------------------------------------------