拍照打卡小程序

举报
yd_229854089 发表于 2026/09/04 16:17:52 2026/09/04
【摘要】 用纯前端实现一个拍照打卡小工具:从想法到上架华为云开发者作品展览馆> 一台浏览器摄像头,一个 localStorage,852 行代码,零依赖。这篇文章记录了「拍照打卡小工具」从构思到开发、再到发布至华为云开发者作品展览馆的全过程。## 一、为什么做这个工具打卡这件事,大家都不陌生。健身打卡、学习打卡、早起打卡……市面上的打卡 App 很多,但大多数都需要注册账号、下载安装、还要忍受各种广...
 用纯前端实现一个拍照打卡小工具:从想法到上架华为云开发者作品展览馆

> 一台浏览器摄像头,一个 localStorage,852 行代码,零依赖。这篇文章记录了「拍照打卡小工具」从构思到开发、再到发布至华为云开发者作品展览馆的全过程。

## 一、为什么做这个工具

打卡这件事,大家都不陌生。健身打卡、学习打卡、早起打卡……市面上的打卡 App 很多,但大多数都需要注册账号、下载安装、还要忍受各种广告。有时候你只是想随手拍张照片,记一笔备注,留个时间戳,就这么简单。

于是我想:能不能做一个**零安装、零注册、打开即用**的拍照打卡工具?答案是肯定的——浏览器原生就提供了摄像头 API,localStorage 可以做本地持久化,不需要任何后端服务。

## 二、功能设计

在动手之前,我先梳理了核心功能:

| 功能 | 说明 |
|------|------|
| **拍照打卡** | 调用浏览器摄像头,拍照截图,支持前后摄像头切换 |
| **备注记录** | 每次拍照后可添加文字备注,自动记录时间戳 |
| **历史记录** | 展示所有打卡记录,含照片缩略图、时间、备注,按时间倒序 |
| **数据持久化** | 使用 localStorage 保存,照片以 Base64 存储,刷新不丢失 |
| **记录管理** | 单条删除(带确认弹窗)、一键清空所有记录 |
| **统计信息** | 总打卡次数 + 今日打卡次数 |

界面设计上,我选择了**移动端优先**的思路:

- 底部 Tab 导航:「打卡」和「记录」两个页面
- 打卡页:摄像头预览区域 + 大圆形拍照按钮 + 备注输入框
- 记录页:统计卡片 + 打卡历史列表
- 配色用清新的**蓝绿色系**(teal/cyan),卡片式布局

## 三、技术实现

### 3.1 整体架构

应用采用三层架构,简洁清晰:

```
┌─────────────────────────────────────────┐
│           用户界面层 (UI Layer)            │
│  打卡页面 | 记录页面 | 底部Tab导航          │
├─────────────────────────────────────────┤
│          功能逻辑层 (Logic Layer)          │
│  拍照模块 | 记录管理 | 统计模块             │
├─────────────────────────────────────────┤
│         数据存储层 (Storage Layer)         │
│  localStorage | Base64 编码               │
└─────────────────────────────────────────┘
```

### 3.2 拍照功能:getUserMedia + Canvas

拍照的核心是浏览器的 `getUserMedia` API。请求摄像头权限后,将视频流绑定到 `<video>` 元素上实时预览。拍照时,把当前视频帧绘制到 `<canvas>` 上,再通过 `canvas.toDataURL()` 转为 Base64 图片数据。

```javascript
// 请求摄像头
async function startCamera(facingMode = 'environment') {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode: facingMode }
  });
  videoElement.srcObject = stream;
}

// 拍照截图
function capturePhoto() {
  const canvas = document.createElement('canvas');
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
  canvas.getContext('2d').drawImage(videoElement, 0, 0);
  return canvas.toDataURL('image/jpeg', 0.8); // Base64 图片
}
```

**前后摄像头切换**通过 `facingMode` 参数实现:`environment` 为后摄(默认),`user` 为前摄。移动端切换时需要先停止当前视频流,再重新请求新方向的流。

### 3.3 数据持久化:localStorage + Base64

每条打卡记录的数据结构如下:

```javascript
{
  id: "checkin-1725350400000",   // 唯一 ID(时间戳生成)
  photo: "data:image/jpeg;base64,/9j/4AAQ...",  // Base64 照片
  note: "今天完成了 5 公里跑步",   // 备注文字
  timestamp: 1725350400000        // 时间戳
}
```

所有记录以 JSON 数组形式存入 `localStorage`:

```javascript
// 保存记录
function saveRecord(record) {
  const records = JSON.parse(localStorage.getItem('checkin-records') || '[]');
  records.unshift(record); // 最新的排前面
  localStorage.setItem('checkin-records', JSON.stringify(records));
}

// 读取记录
function loadRecords() {
  return JSON.parse(localStorage.getItem('checkin-records') || '[]');
}
```

**为什么用 Base64 而不是 Blob?** 因为 localStorage 只能存储字符串。Base64 编码后照片直接嵌在 JSON 里,读写简单,无需额外管理 Blob URL。代价是存储体积增大约 33%,但对于打卡场景的小尺寸照片完全可接受。

### 3.4 统计信息

统计逻辑很轻量——遍历记录数组,对比日期即可:

```javascript
function getStats() {
  const records = loadRecords();
  const today = new Date().toDateString();
  const todayCount = records.filter(r =>
    new Date(r.timestamp).toDateString() === today
  ).length;
  return { total: records.length, today: todayCount };
}
```

### 3.5 响应式布局

使用 CSS Flexbox + 媒体查询实现移动端优先的响应式布局:

```css
/* 移动端优先 */
.container {
  display: flex;
  flex-direction: column;
  max-width: 480px;  /* 手机宽度 */
  margin: 0 auto;
}

/* 平板及以上 */
@media (min-width: 768px) {
  .container {
    max-width: 720px;
  }
}
```

底部 Tab 导航固定在视口底部,拍照按钮居中大圆形设计,这些都是移动端的经典交互模式。

## 四、开发体验:华为云 CodeArts

这个项目是在 **华为云 AI DevSpace** 的 CodeArts 沙箱中开发的。整个开发流程非常顺畅:

1. **沙箱环境即开即用**:不需要本地配置开发环境,浏览器打开就能写代码
2. **实时预览**:代码写完后直接在沙箱里启动 HTTP 服务,通过预览链接查看效果
3. **一键部署**:开发完成后可以直接通过 DevBridge 隧道分享给外部用户访问

CodeArts 沙箱的工作目录在宿主机上直接可读写,git 操作也没有任何障碍,和本地开发体验几乎一致。

## 五、发布到开发者作品展览馆

开发完成后,我使用 **publish-work-to-gallery** 技能将作品发布到了华为云高校运营平台的开发者作品展览馆。发布流程设计得很周到:

### 5.1 身份认证:STS 自委托

发布前需要验证身份,这里用到了一个很巧妙的设计——**IAM 自委托(SELF_VERIFY)**:

- 创建一个委托方和被委托方都是自己账号的 IAM 委托
- 通过 `STS AssumeAgency` 生成最小权限临时凭证(仅允许 `GetCallerIdentity` 和 `listAuthDomains`)
- 临时凭证有效期 900 秒,过期可自动刷新
- **永久 AK/SK 始终留在本地**,只传递临时凭证,安全可靠

### 5.2 作品元数据生成

发布需要几样东西:

- **作品封面**:自动截图 + PPT 风格合成,支持多种色调方案
- **作品简介**:15-50 字的一句话描述
- **详情文章**:Markdown 格式,含架构图,打包成 zip 上传
- **Git 仓库地址**:代码需先推送到 GitCode

### 5.3 门禁检查

发布前有多道门禁,确保质量:

| 门禁 | 检查内容 |
|------|---------|
| 渲染门禁 | CJK + Emoji 字体安装、fontconfig 中文映射 |
| 编码门禁 | HTML 声明编码与实际编码一致(UTF-8) |
| 截图门禁 | 页面内字形自检 + 字体兜底注入 |
| 像素级字形终检 | 比对文字行墨量,防止渲染出豆腐块 |

这些门禁由脚本强制执行,不依赖开发者自觉,有效防止了中文乱码、字体缺失等问题混入发布。

### 5.4 最终结果

作品成功发布,获得了作品陈列馆页面和 200 成长积分:

- **作品链接**:https://gallery.developer.huaweicloud.com/gallery/002c354ad0c00000
- **代码仓库**:https://gitcode.com/gcw_O7wBDy8J/photo-checkin.git

## 六、总结与思考

### 做对了什么

- **纯前端零依赖**:单个 HTML 文件,不需要构建工具、不需要框架、不需要后端,部署成本为零
- **移动端优先**:打卡场景天然适合手机,从交互到布局都针对触摸操作优化
- **隐私安全**:所有数据存在本地浏览器,不上传任何服务器,用户完全掌控自己的数据

### 可以改进的方向

- **存储容量限制**:localStorage 通常有 5-10MB 限制,大量打卡记录可能超限。可以考虑用 IndexedDB 替代,或提供导出/导入功能
- **PWA 离线支持**:加一个 Service Worker 和 manifest,就能实现离线访问和「添加到主屏幕」
- **照片压缩**:目前直接存 JPEG Base64,可以在 Canvas 绘制时调整尺寸和质量,进一步减小存储占用
- **多设备同步**:如果需要跨设备同步,可以接入华为云 OBS 或 FunctionGraph 做后端存储

### 关于华为云开发体验

这次体验了华为云 AI DevSpace + CodeArts + GitCode + 开发者作品展览馆的完整链路,整体感受是:

- **AI DevSpace** 沙箱环境开箱即用,省去了环境配置的麻烦
- **CodeArts** 代码编写体验流畅,实时预览很方便
- **GitCode** 代码托管与 GitCode OAuth 登录工具链完整
- **作品展览馆** 发布流程设计严谨,门禁检查保证了作品质量,STS 自委托的身份认证方式兼顾了安全与便捷

如果你也想快速开发并发布一个小工具,这条链路值得试试。

---

*本文涉及的完整代码已开源至 GitCode:[photo-checkin](https://gitcode.com/gcw_O7wBDy8J/photo-checkin.git),欢迎 Star ⭐*
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。