打卡系统分析.md
10.5 KB
打卡系统分析
最后更新: 2026-02-09 相关文件:
-
src/api/checkin.js- 打卡 API -
src/views/checkin/- 打卡页面 -
src/views/bieyuan/scan.vue- 别院扫码 -
src/views/by/scan.vue- BY 扫码
系统架构
打卡流程
用户扫描二维码
↓
打开打卡页面(checkin/index.vue)
↓
获取打卡信息(detail_id)
↓
检查是否已打卡(isCheckedAPI)
↓
已打卡? → 显示打卡信息(info_w.vue)
未打卡? → 显示打卡按钮
↓
用户点击打卡
↓
提交打卡(checkinAPI)
↓
显示打卡成功(info_w.vue)
打卡类型
项目支持多种打卡场景:
-
别院打卡 (
bieyuan/)- 页面:
src/views/bieyuan/scan.vue - 信息页面:
src/views/bieyuan/info_w.vue
- 页面:
-
BY 打卡 (
by/)- 页面:
src/views/by/scan.vue - 信息页面:
src/views/by/info_w.vue
- 页面:
-
通用打卡 (
checkin/)- 页面:
src/views/checkin/scan.vue - 信息页面:
src/views/checkin/info_w.vue - 信息页面:
src/views/checkin/info.vue
- 页面:
API 接口
1. 检查是否已打卡
端点: /srv/?f=walk&a=map&t=is_checked
方法: GET
请求参数:
{
detail_id: String, // 打卡点 ID(必需)
openid: String // 微信 OpenID(必需)
}
响应数据:
{
code: 1,
msg: '',
data: {
is_checked: Number // 是否已打卡(0: 未打卡, 1: 已打卡)
}
}
API 调用:
import { isCheckedAPI } from '@/api/checkin.js';
const { data } = await isCheckedAPI({
detail_id: '123',
openid: 'wx_openid_123'
});
if (data.is_checked === 1) {
console.log('已打卡');
} else {
console.log('未打卡');
}
2. 提交打卡
端点: /srv/?f=walk&a=map&t=checkin
方法: POST
请求参数:
{
detail_id: String, // 打卡点 ID(必需)
openid: String // 微信 OpenID(必需)
}
响应数据:
{
code: 1,
msg: '打卡成功',
data: {
// 打卡信息(可能包含)
checkin_time: String, // 打卡时间
checkin_date: String, // 打卡日期
user_info: Object, // 用户信息
location_info: Object // 位置信息
}
}
API 调用:
import { checkinAPI } from '@/api/checkin.js';
const { data } = await checkinAPI({
detail_id: '123',
openid: 'wx_openid_123'
});
console.log('打卡成功:', data);
页面组件
1. 打卡首页 (checkin/index.vue)
功能:
- ✅ 显示打卡点信息
- ✅ 检查打卡状态
- ✅ 显示打卡按钮
- ✅ 跳转到扫码页面
使用示例:
<template>
<div class="checkin-page">
<h1>{{ locationName }}</h1>
<p>{{ locationDesc }}</p>
<van-button
v-if="!isChecked"
type="primary"
@click="handleCheckin"
>
立即打卡
</van-button>
<van-button
v-else
disabled
>
已打卡
</van-button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { isCheckedAPI, checkinAPI } from '@/api/checkin.js';
import { useUserStore } from '@/store/user';
const router = useRouter();
const userStore = useUserStore();
const locationName = ref('打卡点名称');
const locationDesc = ref('打卡点描述');
const isChecked = ref(false);
const checkCheckinStatus = async () => {
const { data } = await isCheckedAPI({
detail_id: router.currentRoute.value.query.id,
openid: userStore.openid,
});
isChecked.value = data.is_checked === 1;
};
const handleCheckin = async () => {
const { data } = await checkinAPI({
detail_id: router.currentRoute.value.query.id,
openid: userStore.openid,
});
// 跳转到打卡信息页面
router.push({
path: '/checkin/info_w',
query: { id: router.currentRoute.value.query.id }
});
};
onMounted(() => {
checkCheckinStatus();
});
</script>
2. 扫码页面 (scan.vue)
功能:
- ✅ 调用扫码功能
- ✅ 解析二维码
- ✅ 跳转到打卡页面
使用示例:
<template>
<div class="scan-page">
<button @click="handleScan">扫码打卡</button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
const handleScan = async () => {
// 调用微信扫码
wx.scanQRCode({
needResult: 1, // 1: 返回扫码结果
scanType: ['qrCode'], // 可以指定扫二维码还是一维码
success: (res) => {
// res.resultStr: 扫码结果
const detailId = extractDetailId(res.resultStr);
// 跳转到打卡页面
router.push({
path: '/checkin',
query: { id: detailId }
});
},
fail: (err) => {
console.error('扫码失败:', err);
}
});
};
const extractDetailId = (resultStr) => {
// 从扫码结果中提取 detail_id
// 例如: https://example.com/checkin?id=123
const url = new URL(resultStr);
return url.searchParams.get('id');
};
</script>
3. 打卡信息页面 (info.vue, info_w.vue)
功能:
- ✅ 显示打卡时间
- ✅ 显示用户信息
- ✅ 显示位置信息
- ✅ 显示打卡状态
区别:
-
info.vue: 普通信息页面 -
info_w.vue: 带警告样式的信息页面
路由配置
路由定义
// src/router/routes/modules/checkin/index.js(如果存在)
{
path: '/checkin',
name: 'Checkin',
component: () => import('@/views/checkin/index.vue'),
meta: {
title: '打卡',
requireAuth: true, // 需要登录
},
children: [
{
path: 'info',
name: 'CheckinInfo',
component: () => import('@/views/checkin/info.vue'),
},
{
path: 'info_w',
name: 'CheckinInfoWarn',
component: () => import('@/views/checkin/info_w.vue'),
},
{
path: 'scan',
name: 'CheckinScan',
component: () => import('@/views/checkin/scan.vue'),
},
],
}
数据流程
1. 用户扫码
微信扫码
↓
微信返回扫码结果
↓
解析 detail_id
↓
路由跳转(携带 detail_id)
2. 检查打卡状态
进入打卡页面
↓
获取 detail_id(路由参数)
↓
获取 openid(用户信息)
↓
调用 isCheckedAPI
↓
显示打卡状态
3. 提交打卡
用户点击打卡按钮
↓
调用 checkinAPI
↓
更新打卡状态
↓
显示打卡成功信息
微信集成
1. 扫码功能
微信 JS-SDK 配置:
// src/api/wx/jsApiList.js
export const jsApiList = [
'scanQRCode', // 扫码
// 其他 API...
];
使用示例:
import { jsApiList } from '@/api/wx/jsApiList';
wx.config({
debug: false,
appId: 'YOUR_APPID',
timestamp: timestamp,
nonceStr: nonceStr,
signature: signature,
jsApiList: jsApiList,
});
wx.ready(() => {
// 扫码功能就绪
});
wx.error((res) => {
console.error('微信配置失败:', res);
});
2. 用户信息
OpenID 获取:
// 从用户信息中获取 OpenID
import { useUserStore } from '@/store/user';
const userStore = useUserStore();
const openid = userStore.openid;
已知问题
1. 重复打卡
问题: 用户可能多次点击打卡按钮
解决方案:
// 防止重复提交
const isSubmitting = ref(false);
const handleCheckin = async () => {
if (isSubmitting.value) {
return;
}
isSubmitting.value = true;
try {
const { data } = await checkinAPI({
detail_id: detailId,
openid: openid,
});
// 打卡成功
} catch (err) {
console.error('打卡失败:', err);
} finally {
isSubmitting.value = false;
}
};
2. 网络异常
问题: 网络请求失败
解决方案:
const handleCheckin = async () => {
try {
const { data } = await checkinAPI({
detail_id: detailId,
openid: openid,
});
showToast('打卡成功');
} catch (err) {
showToast('打卡失败,请重试');
// 延迟重试
setTimeout(() => {
handleCheckin();
}, 1000);
}
};
3. OpenID 获取失败
问题: OpenID 未正确获取
解决方案:
// 检查 OpenID 是否存在
const checkOpenId = () => {
const openid = userStore.openid;
if (!openid) {
// 跳转到登录页面
router.push('/login');
return false;
}
return true;
};
const handleCheckin = async () => {
if (!checkOpenId()) {
return;
}
// 继续打卡流程
};
最佳实践
1. 错误处理
// ✅ 推荐:统一的错误处理
const checkin = async () => {
try {
const { data } = await checkinAPI(params);
if (data.code === 1) {
showToast('打卡成功');
return data;
} else {
showToast(data.msg || '打卡失败');
return null;
}
} catch (err) {
console.error('打卡异常:', err);
showToast('网络异常,请重试');
return null;
}
};
// ❌ 不推荐:不处理错误
const checkin = async () => {
const { data } = await checkinAPI(params);
return data;
};
2. 状态管理
// ✅ 推荐:使用 Pinia 管理打卡状态
import { useCheckinStore } from '@/store/checkin';
const checkinStore = useCheckinStore();
const handleCheckin = async () => {
const success = await checkinStore.checkin(detailId, openid);
if (success) {
showToast('打卡成功');
}
};
// ❌ 不推荐:在组件中管理状态
const isChecked = ref(false);
const handleCheckin = async () => {
const { data } = await checkinAPI(params);
isChecked.value = true;
};
3. 路由跳转
// ✅ 推荐:使用命名路由
router.push({
name: 'CheckinInfo',
query: { id: detailId }
});
// ❌ 不推荐:使用路径拼接
router.push(`/checkin/info?id=${detailId}`);
调试技巧
1. 模拟打卡
// 在控制台模拟打卡
import { checkinAPI } from '@/api/checkin.js';
checkinAPI({
detail_id: '123',
openid: 'test_openid'
}).then(res => console.log(res));
2. 查看打卡状态
// 查看当前打卡状态
console.log('打卡状态:', {
detailId: router.currentRoute.value.query.id,
openid: userStore.openid,
isChecked: isChecked.value
});
3. 清除打卡状态
// 清除本地打卡状态(测试用)
localStorage.removeItem('checkin_status');