如何小程序制作教程活动签到

 发布时间:2026-10-14 12:01:47   热度:19
下面是一份基于**微信小程序云开发**的活动签到小程序完整制作教程,包含创建活动、生成签到二维码、扫码签到、防重复签到、统计记录等核心功能。无需自建服务器,适合快速上手。 --- ## 一、准备工作 1. **注册小程序账号** 前往[微信公众平台](https://mp.weixin.qq.com/)注册小程序,获取 **AppID**。 2. **安装开发者工具** 下载并安装[微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)。 3. **开通云开发** 在开发者工具中新建项目,填入 AppID,选择“微信云开发”模板。 点击工具栏“云开发”按钮,创建云开发环境(如 `signin-demo`)。 --- ## 二、数据库设计 在云开发控制台创建两个集合: ### 1. `activities` 活动集合 | 字段 | 类型 | 说明 | |--------------|--------|--------------------------| | name | string | 活动名称 | | time | string | 活动时间 | | location | string | 活动地点 | | code | string | 签到码(6位随机字符串) | | qrcodeFileID | string | 签到小程序码文件ID | | _openid | string | 创建者openid(自动/手动)| | createTime | date | 创建时间 | ### 2. `signins` 签到记录集合 | 字段 | 类型 | 说明 | |------------|--------|--------------------------| | activityId | string | 关联的活动ID | | _openid | string | 签到用户openid | | createTime | date | 签到时间 | > 集合权限建议设置为“**所有用户不可读写**”,因为所有数据库操作都通过云函数完成,云函数拥有管理员权限。 --- ## 三、项目结构 ``` project/ ├── cloudfunctions/ # 云函数目录 │ ├── createActivity/ # 创建活动+生成二维码 │ ├── signIn/ # 签到 │ ├── getMyActivities/ # 我的活动列表 │ ├── getActivityByCode/ # 根据签到码查活动 │ └── getSignins/ # 签到记录 └── miniprogram/ ├── pages/ │ ├── index/ # 活动列表 │ ├── create/ # 创建活动 │ ├── signin/ # 签到页 │ └── records/ # 签到记录 ├── app.json └── app.js ``` --- ## 四、云函数代码 ### 1. 创建活动云函数 `createActivity` **功能**:生成唯一签到码 → 调用微信接口生成小程序码 → 上传云存储 → 写入数据库。 ```js // cloudfunctions/createActivity/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() exports.main = async (event, context) => { const { OPENID } = cloud.getWXContext() const { name, time, location } = event if (!name || !time) { return { success: false, message: '活动名称和时间不能为空' } } // 生成唯一签到码(6位) let code let isUnique = false while (!isUnique) { code = Math.random().toString(36).slice(2, 8).toUpperCase() const exist = await db.collection('activities').where({ code }).count() if (exist.total === 0) isUnique = true } try { // 生成小程序码,scene 携带签到码 const qrResult = await cloud.openapi.wxacode.getUnlimited({ scene: code, page: 'pages/signin/signin', checkPath: false, envVersion: 'release' }) // 上传小程序码到云存储 const upload = await cloud.uploadFile({ cloudPath: `qrcode/${code}.png`, fileContent: qrResult.buffer }) // 写入活动记录 const res = await db.collection('activities').add({ data: { name, time, location, code, qrcodeFileID: upload.fileID, _openid: OPENID, createTime: db.serverDate() } }) return { success: true, activityId: res._id, code, qrcodeFileID: upload.fileID } } catch (err) { return { success: false, message: err.message } } } ``` **配置权限**:在 `createActivity` 目录下新建 `config.json`: ```json { "permissions": { "openapi": [ "wxacode.getUnlimited" ] } } ``` --- ### 2. 签到云函数 `signIn` **功能**:根据签到码查找活动 → 检查是否已签到 → 写入签到记录。 ```js // cloudfunctions/signIn/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() exports.main = async (event, context) => { const { OPENID } = cloud.getWXContext() const { code } = event if (!code) { return { success: false, message: '签到码不能为空' } } try { // 查找活动 const activityRes = await db.collection('activities').where({ code }).get() if (activityRes.data.length === 0) { return { success: false, message: '活动不存在或签到码错误' } } const activity = activityRes.data[0] // 防重复签到 const exist = await db.collection('signins').where({ activityId: activity._id, _openid: OPENID }).count() if (exist.total > 0) { return { success: false, message: '您已签到,请勿重复签到' } } // 写入签到记录 await db.collection('signins').add({ data: { activityId: activity._id, _openid: OPENID, createTime: db.serverDate() } }) return { success: true, message: '签到成功' } } catch (err) { return { success: false, message: err.message } } } ``` --- ### 3. 获取我的活动列表云函数 `getMyActivities` ```js // cloudfunctions/getMyActivities/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() exports.main = async (event, context) => { const { OPENID } = cloud.getWXContext() try { const activitiesRes = await db.collection('activities') .where({ _openid: OPENID }) .orderBy('createTime', 'desc') .get() const activities = activitiesRes.data // 统计每个活动的签到人数 for (let act of activities) { const countRes = await db.collection('signins') .where({ activityId: act._id }) .count() act.signinCount = countRes.total } return { success: true, data: activities } } catch (err) { return { success: false, message: err.message } } } ``` --- ### 4. 根据签到码查询活动 `getActivityByCode` ```js // cloudfunctions/getActivityByCode/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() exports.main = async (event, context) => { const { code } = event try { const res = await db.collection('activities').where({ code }).get() if (res.data.length === 0) { return { success: false, message: '活动不存在' } } return { success: true, data: res.data[0] } } catch (err) { return { success: false, message: err.message } } } ``` --- ### 5. 获取签到记录 `getSignins` ```js // cloudfunctions/getSignins/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() exports.main = async (event, context) => { const { OPENID } = cloud.getWXContext() const { activityId } = event try { // 验证是否为活动创建者 const activityRes = await db.collection('activities').doc(activityId).get() if (activityRes.data._openid !== OPENID) { return { success: false, message: '无权限查看' } } const signinsRes = await db.collection('signins') .where({ activityId }) .orderBy('createTime', 'asc') .get() return { success: true, data: signinsRes.data } } catch (err) { return { success: false, message: err.message } } } ``` --- ## 五、小程序页面代码 ### 1. 配置 `app.json` ```json { "pages": [ "pages/index/index", "pages/create/create", "pages/signin/signin", "pages/records/records" ], "window": { "navigationBarTitleText": "活动签到" }, "sitemapLocation": "sitemap.json" } ``` --- ### 2. 活动列表页 `pages/index` **index.wxml** ```xml {{item.name}} {{item.time}} {{item.location}} 签到码:{{item.code}} 已签到:{{item.signinCount}}人 ``` **index.js** ```js Page({ data: { activities: [] }, onShow() { this.loadActivities() }, async loadActivities() { const res = await wx.cloud.callFunction({ name: 'getMyActivities' }) if (res.result.success) { this.setData({ activities: res.result.data }) } }, goCreate() { wx.navigateTo({ url: '/pages/create/create' }) }, goRecords(e) { const id = e.currentTarget.dataset.id wx.navigateTo({ url: `/pages/records/records?activityId=${id}` }) } }) ``` --- ### 3. 创建活动页 `pages/create` **create.wxml** ```xml ``` **create.js** ```js Page({ data: { name: '', time: '', location: '' }, onInput(e) { const field = e.currentTarget.dataset.field this.setData({ [field]: e.detail.value }) }, async onSubmit() { if (!this.data.name || !this.data.time) { wx.showToast({ title: '请填写活动名称和时间', icon: 'none' }) return } wx.showLoading({ title: '创建中...' }) try { const res = await wx.cloud.callFunction({ name: 'createActivity', data: { name: this.data.name, time: this.data.time, location: this.data.location } }) wx.hideLoading() if (res.result.success) { wx.showToast({ title: '创建成功' }) setTimeout(() => wx.navigateBack(), 1500) } else { wx.showToast({ title: res.result.message, icon: 'none' }) } } catch (err) { wx.hideLoading() wx.showToast({ title: '创建失败', icon: 'none' }) } } }) ``` --- ### 4. 签到页 `pages/signin` **signin.wxml** ```xml {{activity.name}} {{activity.time}} {{activity.location}} ``` **signin.js** ```js Page({ data: { code: '', activity: null }, onLoad(options) { // 扫码进入:options.scene 携带签到码 if (options.scene) { this.setData({ code: decodeURIComponent(options.scene) }) } else if (options.code) { this.setData({ code: options.code }) } if (this.data.code) { this.loadActivity() } }, async loadActivity() { const res = await wx.cloud.callFunction({ name: 'getActivityByCode', data: { code: this.data.code } }) if (res.result.success) { this.setData({ activity: res.result.data }) } else { wx.showToast({ title: res.result.message, icon: 'none' }) } }, onInputCode(e) { this.setData({ code: e.detail.value }) }, async onSignIn() { if (!this.data.code) { wx.showToast({ title: '请输入签到码', icon: 'none' }) return } const res = await wx.cloud.callFunction({ name: 'signIn', data: { code: this.data.code } }) if (res.result.success) { wx.showToast({ title: '签到成功' }) } else { wx.showToast({ title: res.result.message, icon: 'none' }) } } }) ``` --- ### 5. 签到记录页 `pages/records` **records.wxml** ```xml 签到时间:{{item.createTime}} ``` **records.js** ```js Page({ data: { records: [] }, onLoad(options) { this.activityId = options.activityId this.loadRecords() }, async loadRecords() { const res = await wx.cloud.callFunction({ name: 'getSignins', data: { activityId: this.activityId } }) if (res.result.success) { this.setData({ records: res.result.data }) } } }) ``` --- ## 六、部署与运行 1. **上传云函数** 在开发者工具中右键每个云函数目录 → “上传并部署:云端安装依赖”。 2. **创建数据库集合** 在云开发控制台创建 `activities` 和 `signins` 集合,权限设为“所有用户不可读写”。 3. **编译运行** 点击“编译”,在小程序中创建活动,生成签到二维码。 4. **测试签到** 使用另一台手机扫描小程序码,自动跳转到签到页并完成签到。 --- ## 七、发布上线 1. 在开发者工具点击“上传”,填写版本号。 2. 登录[微信公众平台](https://mp.weixin.qq.com/) → 版本管理 → 提交审核。 3. 审核通过后点击“发布”。 --- ## 八、注意事项 - **签到码唯一性**:代码中使用循环查重,确保签到码全局唯一。 - **小程序码生成权限**:云函数目录下的 `config.json` 必须配置 `wxacode.getUnlimited` 权限,否则会报错。 - **防重复签到**:通过 `activityId + openid` 组合查询,确保每人只能签到一次。 - **隐私合规**:签到过程仅需 openid,不需要用户授权头像昵称,符合最小化收集原则。 - **免费额度**:云开发基础版每月有免费调用量,个人使用足够。 --- 通过以上步骤,你就可以制作一个完整的活动签到小程序。如果需要更丰富的功能(如签到名单导出、地理位置校验、签到表单等),可以在此基础上继续扩展。