暂存用户打卡信息.md 8.08 KB

暂存用户打卡信息

背景

用户在“提交作业/打卡”页面输入了较长文字并上传了媒体,但在未点击提交时被中断(误触返回、微信进程被系统回收、来电/切后台等),再次进入页面内容丢失,导致体验断裂。

本规划目标是在不改动后端接口的前提下,在前端提供“草稿暂存(文本 + 已上传媒体信息)”能力,支持一周内自动过期清理,并在用户再次进入时提示恢复或删除。

涉及页面与核心逻辑参考:

需求拆解(逐条对齐)

  1. 缓存最多保存一周:写入时记录 saved_at,读写时执行过期清理(>7天删除)。
  2. 用户提交后清空缓存:提交成功(code===1)后删除对应草稿。
  3. 进入页面若存在未完成信息:弹框提示“继续/删除”,继续则回填,删除则清空。
  4. 功能开关放到 .env:新增 VITE_CHECKIN_DRAFT_CACHE(0/1),默认建议 1(也可先默认 0,灰度开启)。

范围与不做项(第一版)

  • 覆盖:文字内容、已上传成功的媒体(含 url/meta_id/file_type/name)。
  • 不覆盖:未上传完成的 File/Blob(localStorage 无法可靠持久化;要支持需 IndexedDB 存 Blob,复杂度与风险较高)。
  • 编辑模式(route.query.status===edit):第一版建议默认不启用草稿恢复,避免与“编辑回显(来自后端)”冲突;若要覆盖编辑场景,采用独立 key(见“扩展”)。

关键设计

1) 存储介质

  • 使用 localStorage:实现成本低,满足“一周”与“断网/切后台后仍可恢复”。
  • 数据量控制:只存“已上传成功”的附件元数据;不存 File 本体。

2) 草稿 Key 设计(避免串号)

建议 key 包含用户与作业上下文,确保不同用户/不同作业互不影响:

  • 前缀:CHECKIN_DRAFT_V1
  • 维度:user_id、task_id、date、task_type、status

示例:

  • CHECKIN_DRAFT_V1:{user_id}:{task_id}:{date}:{task_type}:{status}

其中:

  • user_id:来自 currentUser(contexts/auth.js 本地持久化)
  • task_id/date/task_type/status:来自路由 query(CheckinDetailPage 已使用 route.query.task_id/date/task_type/status)

3) 数据结构(建议)

{
  "version": 1,
  "saved_at": 1730000000000,
  "expires_at": 1730000000000,
  "context": {
    "user_id": "123",
    "task_id": "456",
    "date": "2026-01-25",
    "task_type": "upload",
    "status": "create"
  },
  "payload": {
    "message": "...",
    "active_type": "image",
    "subtask_id": "789",
    "file_list": [
      {
        "meta_id": "xxx",
        "url": "https://...",
        "name": "a.jpg",
        "file_type": "image"
      }
    ],
    "count": {
      "gratitude_count": 1,
      "gratitude_form_list": []
    }
  }
}

说明:

  • file_list:仅保存 useCheckin.afterRead 上传成功后写入的字段(item.status===done 且 meta_id 存在)。
  • count:来源于 CheckinDetailPage 的 selectedTargets/countValue(第一版可以先不存,或存但不影响非 count 类型)。

4) 触发保存的时机(自动暂存)

  • 文本变化:watch(message) debounce 500ms 保存。
  • 附件变化:watch(fileList) 深度监听 debounce 500ms 保存(仅保存 done 项)。
  • 作业选择变化:watch(selectedTaskValue) debounce 200ms 保存。
  • 页面离开兜底:beforeRouteLeave 或 window.pagehide/visibilitychange 时强制保存一次(避免最后一次变更没落盘)。

落盘时机要遵循开关:VITE_CHECKIN_DRAFT_CACHE === '1' 才启用。

5) 弹框提示与回填流程

进入 CheckinDetailPage.vue(且非 edit 模式)时:

  1. 读取 key 对应草稿;若不存在或已过期,直接进入正常流程。
  2. 若存在草稿且 payload 有实际内容(message 有非空或 file_list 非空):弹框提示。
  3. 用户选择:
    • 继续:将草稿回填到 message / activeType / fileList / selectedTaskValue(以及 count 数据如启用),并立刻再保存一次(避免回填后又丢)。
    • 删除:删除草稿并保持空表单。

弹框建议用 showConfirmDialog(Vant 4),取消分支需要 catch,避免控制台出现 Uncaught (in promise) cancel(Vant 文档:showConfirmDialog.then/catch)3

6) 清理策略(“定期清除一周前”)

采用“惰性清理 + 低频全量清理”组合:

  • 惰性清理:每次读/写草稿时,如果 expires_at < now 则删除。
  • 低频全量:进入打卡页时,扫描 localStorage 中以 CHECKIN_DRAFT_V1: 开头的 key,删除所有过期项。

说明:localStorage 没有内建 TTL,必须业务侧维护 expires_at。

7) 提交成功后清空

清空动作必须绑定到“真正提交成功”之后:

  • 建议在 useCheckin.onSubmit 中,当 add/edit API 返回 code===1 且后续逻辑准备 router.back 前,删除对应 key。

这样可覆盖“不同入口页复用 onSubmit”以及“提交后立即返回上一页”的场景。

开发步骤(可落地的实现顺序)

第 0 步:验证手段先行(TDD)

新增 Vitest 用例,先定义以下可验证点:

  • 写入后能读取同一 key 的草稿;过期后读取返回空且自动删除。
  • 仅保存 status===done 且含 meta_id 的附件。
  • 清理函数能删除所有过期 key,不误删其他业务 localStorage。
  • 提交成功时会调用清理(可通过 mock API 返回 code===1 验证)。

第 1 步:抽离草稿存储模块

位置建议:src/utils/checkinDraftCache.js(纯函数、无 UI 依赖)。

对外 API(示例):

  • is_enabled(): boolean(读取 env + 可选 query override)
  • build_key(context): string
  • save_draft(key, draft)
  • read_draft(key): draft|null(含 TTL 处理)
  • clear_draft(key)
  • cleanup_expired(prefix)

第 2 步:在 CheckinDetailPage 接入“检测 + 弹框 + 回填”

  • onMounted:初始化后读取草稿并弹框。
  • 回填时机:建议在任务详情/子任务列表加载完成后再回填 selectedTaskValue,避免 option 未加载导致显示异常。

第 3 步:在 CheckinDetailPage 接入“自动保存”

  • 对 message/fileList/selectedTaskValue/countValue/selectedTargets 建立 watch + debounce。
  • 页面离开事件兜底(pagehide/visibilitychange)。

第 4 步:在 useCheckin.onSubmit 接入“成功清理”

  • onSubmit 成功分支清除草稿。
  • 失败分支不清除,保留草稿以便重试。

边界条件与遗漏点梳理(建议补齐)

  1. 多用户切换:key 必须含 user_id,否则会串草稿。
  2. 多任务并存:key 必须含 task_id/date/task_type,否则会在不同作业之间误恢复。
  3. 附件未上传完成:
    • 仅保存已上传成功的项;如果用户退出时仍有 uploading 项,恢复后无法找回该 File。
    • 可在保存时统计未保存数量,并在恢复弹框里追加提示“有 X 个附件上传未完成未被暂存”。
  4. 关闭开关后的行为:
    • 关闭后不再读/写;建议仍执行一次 cleanup_expired,避免历史堆积。
  5. 版本升级/数据结构变更:draft.version 不匹配时丢弃并清除,避免解析异常。
  6. localStorage 配额:图片多但只存 url/meta_id 一般不会超;仍需 try/catch JSON 与 setItem 异常。
  7. 编辑模式:
    • 要支持“编辑中断恢复”,建议 key 加 post_id 维度,并在 initEditData 回显后再弹框询问是否覆盖当前表单。

环境变量(规划)

.env 增加:

  • VITE_CHECKIN_DRAFT_CACHE = 1

约定:

  • '1' 开启,'0' 关闭
  • 可选增加 URL 覆盖用于灰度测试:?enable_draft=1 / ?enable_draft=0(模式同 VITE_CHECKIN_MULTI_ATTACHMENT)