打卡系统分析.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)

打卡类型

项目支持多种打卡场景:

  1. 别院打卡 (bieyuan/)

    • 页面: src/views/bieyuan/scan.vue
    • 信息页面: src/views/bieyuan/info_w.vue
  2. BY 打卡 (by/)

    • 页面: src/views/by/scan.vue
    • 信息页面: src/views/by/info_w.vue
  3. 通用打卡 (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');

参考文档