心情日记的安全编辑流程:输入校验、删除确认与状态回收

记录类页面除了保存,还需要认真处理"不该保存"和"不该误删"。心情日记在输入为空时阻止创建记录,删除旧记录则要求同一条目连续点击两次,避免一次误触直接移除内容。

心情和颜色通过函数映射。保存时只记录心情名称与最终颜色,列表渲染不再重复判断当前主题。

typescript 复制代码
private moodColor(mood: string): string {
  if (mood === '开心') { return '#F0A24F' }
  if (mood === '平静') { return '#4E9B86' }
  if (mood === '疲惫') { return '#6F7FC2' }
  return '#8A6C91'
}

保存前对正文 trim()。只有空白字符时同样被拦截;成功记录放在顶部,随后清空输入框。

typescript 复制代码
private save(): void {
  const text: string = this.diaryText.trim()
  if (text.length === 0) {
    this.message = '写下一点内容再保存'
    return
  }
  const entry: MoodEntry = {
    id: Date.now(),
    mood: this.selectedMood,
    text,
    date: '今天',
    color: this.moodColor(this.selectedMood)
  }
  this.entries = [entry, ...this.entries]
  this.diaryText = ''
  this.message = '已经留在今天的日记里'
}

删除确认通过 pendingDeleteId 完成。第一次点击只保存目标ID并更改提示;第二次点击同一ID才过滤数组。点击另一条记录时,待确认目标会转移,不会误删上一条。

typescript 复制代码
private requestDelete(id: number): void {
  if (this.pendingDeleteId === id) {
    this.entries = this.entries.filter(
      (item: MoodEntry) => item.id !== id
    )
    this.pendingDeleteId = 0
    this.message = '旧记录已删除'
  } else {
    this.pendingDeleteId = id
    this.message = '再点一次删除,确认移除这条记录'
  }
}

四种心情按钮共用一组数据,图标、文字色和背景色都根据 selectedMood 派生。保存按钮也使用当前心情颜色,选择变化后不需要额外同步主题状态。

typescript 复制代码
ForEach(this.moods, (mood: string) => {
  Column({ space: 5 }) {
    Text(mood)
      .fontColor(
        this.selectedMood === mood
          ? Color.White
          : this.moodColor(mood)
      )
  }
  .backgroundColor(
    this.selectedMood === mood
      ? this.moodColor(mood)
      : '#EEF1F5'
  )
  .onClick(() => {
    this.selectedMood = mood
  })
})

当前二次删除确认没有超时,也没有在其他操作后清除。例如第一次点击删除后去保存新日记,原记录仍处于待确认状态。

更完整的交互应在选择心情、保存或经过一定时间后重置 pendingDeleteId,避免很久以后一次点击就直接删除。

当前记录只在内存中,二次确认也没有超时。正式保存个人内容时需要接入本地持久化,并评估加密与应用锁;在这些能力完成前,页面只能作为交互原型,不能宣称已经具备隐私保护。

typescript 复制代码
private entryById(id: number): MoodEntry | undefined {
  return this.entries.find((entry: MoodEntry) =>
    entry.id === id
  )
}

private requireEntry(id: number): MoodEntry {
  const target: MoodEntry | undefined = this.entryById(id)
  if (!target) {
    throw new Error('Entry not found: ' + id)
  }
  return target
}

最近记录列表把待删除ID转换成按钮文案。只有目标条目显示"确认删除",其他记录仍显示普通"删除"。

typescript 复制代码
Text(this.pendingDeleteId === item.id ? '确认删除' : '删除')
  .fontColor(
    this.pendingDeleteId === item.id ? '#C05052' : '#9AA2B0'
  )
  .onClick(() => {
    this.requestDelete(item.id)
  })

这个反馈位于被操作的卡片内部,比只在页面顶部显示提示更容易确认目标。若加入撤销条,应在真正删除后暂存被移除对象和原索引,超时后再清理,而不是立即丢失恢复信息。

情绪选择和正文输入组成一份草稿

页面用 selectedMood 保存当前情绪,content 保存文字。选择按钮只改变情绪,不提前创建日记;保存时统一校验并生成记录:

typescript 复制代码
private moodColor(mood: string): string {
  const target: MoodOption | undefined = this.moods.find(
    (item: MoodOption) => item.name === mood
  )
  return target ? target.color : '#E8ECF4'
}

private save(): void {
  const text: string = this.content.trim()
  if (text.length === 0) {
    this.message = '写下一点内容后再保存'
    return
  }
  this.entries = [{
    id: Date.now(),
    mood: this.selectedMood,
    content: text,
    date: '今天'
  }, ...this.entries]
  this.content = ''
  this.message = '日记已保存'
}

保存失败时不清空正文,用户可以继续编辑;成功后才清空草稿。使用头部插入让最新记录置顶。真实时间应保存时间戳,"今天"由展示层计算,这样第二天重新打开时旧记录会自动显示实际日期。

情绪颜色由配置数组统一映射,按钮、卡片标记和详情页可以共用同一来源。如果将颜色分别写死在多个组件中,新增情绪时容易遗漏某处。情绪名称是展示文字,持久化时更适合保存稳定枚举值,避免改文案后历史记录无法匹配颜色。

两次点击确认删除的状态边界

pendingDeleteId 指向等待确认的唯一卡片。第一次点击只进入确认态,第二次点击同一 ID 才删除;点击另一条会把确认目标切过去。这比全局"是否确认"布尔值更安全,因为布尔值无法说明待删除的是哪条记录。

typescript 复制代码
private requestDelete(id: number): void {
  if (this.pendingDeleteId !== id) {
    this.pendingDeleteId = id
    this.message = '再次点击确认删除'
    return
  }
  this.entries = this.entries.filter((item: MoodEntry) => item.id !== id)
  this.pendingDeleteId = -1
  this.message = '记录已删除'
}

删除完成后必须复位待确认 ID。若列表支持搜索和排序,确认目标仍然依靠 ID,而不能保存可变索引。还可以在几秒后自动取消确认,定时器在组件销毁时清理;当前双击流程不依赖定时器,行为更直接。

撤销删除与确认删除可以同时存在:先确认目标,再把对象从数组移出并暂存在撤销槽,用户点击恢复时按原索引插回。只有撤销时限结束后才真正从持久化层删除。这样能防止误触,又不会让一次确认对话打断记录浏览。

长文本、隐私和持久化

正文增加后,列表只显示摘要,点击卡片再进入完整内容。摘要应在字符边界截取,不能简单按字节切割;标签、图片等富内容则需要独立数据结构。输入长度上限和剩余字数也应在编辑区即时展示,保存时再做最终校验。

typescript 复制代码
private replaceEntry(
  id: number,
  updater: (source: MoodEntry) => MoodEntry
): void {
  this.entries = this.entries.map((entry: MoodEntry) =>
    entry.id === id ? updater(entry) : entry
  )
}

private updateEntryMessage(id: number): void {
  const target: MoodEntry | undefined = this.entryById(id)
  this.message = target ? '状态已更新' : '目标记录不存在'
}

日期筛选、情绪统计都应从记录数组派生。统计某类情绪时按稳定枚举聚合,不能依赖颜色;颜色属于主题表现,用户更换配色不应改变历史统计结果。

保存前返回结构化校验结果

错误信息和有效草稿通过同一个函数产生:

typescript 复制代码
interface MoodDraftResult {
  valid: boolean
  content: string
  error: string
}

private validateDraft(): MoodDraftResult {
  const content: string = this.content.trim()
  if (content.length === 0) {
    return { valid: false, content: '', error: '内容不能为空' }
  }
  if (content.length > 2000) {
    return { valid: false, content, error: '内容超过 2000 字' }
  }
  return { valid: true, content, error: '' }
}

保存方法只接收通过校验的数据,失败时保留输入并把错误显示在编辑区附近。

相关推荐
wazmlp00188736941 分钟前
控制器管理
linux·运维·服务器
薛定猫AI1 小时前
【深度解析】DeepSeek Harness:可插拔智能体运行时如何连接大模型与真实计算机
java·运维·网络
学linux的QQ蛋1 小时前
Linux 文件 IO:系统调用 open/read/write 完整总结
linux·运维·算法
Dola_Zou1 小时前
深入拆解 CmActLicense: 工业级“软授权”的架构设计与安全实践
安全·软件工程·软件加密
宋拾壹1 小时前
php对接携程验票核销
android·php·android studio
落魄大学生之流水线上谋生计1 小时前
Python Literal[] 类型提示详解
开发语言·python
sg_knight1 小时前
openCode 安装与初始化配置(Windows / macOS / Linux)
linux·windows·macos·llm·agent·ai编程·opencode
WongKyunban1 小时前
Go语言的简洁并发编程
开发语言·后端·golang
Rei22481 小时前
使用llama.cpp的Qwen3.6-27B-Q8本地部署详解【Linux GPU】
linux·运维·llama