如何小程序制作教程活动签到
发布时间: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,不需要用户授权头像昵称,符合最小化收集原则。
- **免费额度**:云开发基础版每月有免费调用量,个人使用足够。
---
通过以上步骤,你就可以制作一个完整的活动签到小程序。如果需要更丰富的功能(如签到名单导出、地理位置校验、签到表单等),可以在此基础上继续扩展。
