无Backend,无借口:构建不出卖你的Pain Tracker
发布: (2025年12月7日 GMT+8 16:16)
5 min read
原文: Dev.to
Source: Dev.to
概述
Pain Tracker 是一款完全运行在用户设备上的慢性疼痛管理应用。
它 没有账号、没有云同步,也没有遥测——你的健康数据仅保留在设备上,或根本不存在。
典型的“免费”健康应用会要求你创建账号并将数据同步到外部服务器,暴露你的疼痛程度、用药历史以及最糟糕的日子给保险公司、数据泄露或法律传票。最需要疼痛追踪的人——慢性病患者、残疾赔偿申请者、工伤赔偿争议者以及经历医疗创伤的人——往往也是最容易受到这些风险侵害的群体。
我之所以构建 Pain Tracker,是因为我亲身经历过健康数据在法庭上被用来对付我。这个应用让你掌控自己的数据。
架构
┌─────────────────────────────────────────────────────────────┐
│ YOUR DEVICE │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ React UI │ → │ Zustand │ → │ IndexedDB │ │
│ │ │ │ │ │ (Encrypted) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ PDF/CSV │ → Your doctor. Your call. │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
关键点
- 没有云、没有服务器、没有遥测。 数据永远不会离开设备,除非你手动导出。
- Zustand 与 Immer 提供不可变更新和完整的审计轨迹。
- IndexedDB 本地存储加密数据。
状态管理
import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
export const usePainTrackerStore = create()(
subscribeWithSelector(
persist(
devtools(
immer((set) => ({
entries: [],
addEntry: (entryData) =>
set((state) => {
state.entries.push({
id: crypto.randomUUID(),
timestamp: new Date().toISOString(),
version: 1,
...entryData,
});
}),
}))
),
{
name: 'pain-tracker-storage',
storage: createJSONStorage(() => localStorage),
}
)
)
);
- 每条记录都包含 版本号 和时间戳。
- 不可变更新使得在任意时间点证明数据的样子变得容易。
客户端加密
// Generate a fresh IV for each encryption
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
encryptionKey,
plaintextBytes
);
- 通过 Web Crypto API 实现 AES‑256‑GCM。
- 没有外部依赖。
- 解密时进行 HMAC 验证。
- 基于密码的密钥使用 150,000 次 PBKDF2 迭代(这是使暴力攻击不切实际的最低要求)。
完整的加密实现详见第 2 部分。
疼痛发作的自适应界面
const activateEmergencyMode = useCallback(() => {
updatePreferences({
simplifiedMode: true,
touchTargetSize: 'extra-large', // 72 px
autoSave: true,
showMemoryAids: true,
});
}, [updatePreferences]);
危机检测 监控以下情况:
- 疼痛等级突升(≥ 7)
- 高错误率(认知雾)
- 输入模式异常(压力)
当检测到发作时,UI 会自动:
- 切换到简化布局
- 增大按钮尺寸
- 减少选项
- 开启每次更改自动保存
所有数据仍然保留在设备上,且可以随时删除。
完整的 hooks 实现详见第 3 部分。
纤维肌痛评估(ACR 2016 标准)
export function calculateFibromyalgiaScore(
painLocations: string[],
symptomScores: SymptomScores
): FibromyalgiaAssessment {
const wpi = painLocations.length; // 0‑19
const sss =
symptomScores.fatigue +
symptomScores.wakingUnrefreshed +
symptomScores.cognitiveSymptoms; // 0‑12
const meetsCriteria =
(wpi >= 7 && sss >= 5) ||
(wpi >= 4 && wpi = 9);
return {
wpiScore: wpi,
sssScore: sss,
meetsFibromyalgiaCriteria: meetsCriteria,
assessmentDate: new Date().toISOString(),
};
}
- 提供 经过验证的评估(而非诊断),可生成供临床医生使用的文档。
- 用来取代“全身疼痛”之类的模糊描述。
为工伤赔偿(WCB)导出
interface WCBExportOptions {
format: 'csv' | 'json' | 'pdf';
dateRange: { start: Date; end: Date };
includeMetadata: boolean;
wcbClaimNumber?: string;
}
export async function exportForWCB(
entries: PainEntry[],
options: WCBExportOptions
): Promise {
const filtered = entries.filter(
(e) =>
new Date(e.timestamp) >= options.dateRange.start &&
new Date(e.timestamp)
值得阅读的文件
src/services/EmpathyIntelligenceEngine.ts– 启发式疼痛分析(2,076 行)src/services/EncryptionService.ts– 客户端加密实现src/components/accessibility/– 创伤知情 hookssrc/stores/pain-tracker-store.ts– 带审计轨迹的状态管理
结论
仍然处于住房不稳定的阶段。仍在持续发布。
取其有用之处,改进它,构建更好的东西。