欢迎光临
我们一直在努力

HarmonyOs应用《日记本》开发第17篇 - 日记编辑页面 DiaryEdit 详解

本篇详细分析日记编辑页面的实现,包括表单输入、日期选择、天气心情标签选择以及数据保存逻辑。

在这里插入图片描述

一、页面功能概述

DiaryEdit 页面是日记应用的核心交互页面,承担了"新建"和"编辑"两种模式:

  • 新建模式:初始化空表单,保存时创建新日记
  • 编辑模式:加载已有日记数据,保存时更新内容
  • 日期选择:通过 DatePicker 选择日记日期
  • 天气/心情标签:从预设选项中选择
  • 内容输入:多行文本输入区域

二、页面状态设计

2.1 状态变量

@Entry
@Component
struct DiaryEdit {
@State diary: DiaryInfo = new DiaryInfo()
@State isEditMode: boolean = false
@State selectedDate: Date = new Date()
@State showDatePicker: boolean = false
@State showDeleteConfirm: boolean = false
@State contentInput: string = ''
@State selectedWeather: Weather = Weather.SUNNY
@State selectedMood: Mood = Mood.HAPPY

private store: DiaryStore = DiaryStore.getInstance()
private editId: string = ''
}

状态变量说明:

变量类型用途
diary DiaryInfo 当前编辑的日记对象
isEditMode boolean 区分新建/编辑模式
selectedDate Date 选中的日期
showDatePicker boolean 控制日期选择器显示
contentInput string 绑定文本输入框内容
selectedWeather Weather 当前选中的天气
selectedMood Mood 当前选中的心情

2.2 模式判断

aboutToAppear() {
const params = router.getParams() as Record<string, string>
if (params && params.mode === 'edit' && params.id) {
this.isEditMode = true
this.editId = params.id
this.loadDiary(params.id)
} else {
this.isEditMode = false
this.diary.date = this.formatDate(new Date())
this.selectedDate = new Date()
}
}

逻辑分析:

  • 从路由参数获取 mode 和 id
  • 若 mode === 'edit',进入编辑模式,加载已有数据
  • 否则进入新建模式,设置默认日期为今天
  • 三、页面布局结构

    3.1 整体布局

    ┌──────────────────────────────┐
    │ ← 返回 保存 │ 顶部导航栏
    ├──────────────────────────────┤
    │ 📅 2026-07-24 ▼ │ 日期选择行
    ├──────────────────────────────┤
    │ 天气: ☀️ ☁️ 🌧️ ⛅ 🌨️ │ 天气标签
    ├──────────────────────────────┤
    │ 心情: 😊 😐 😢 😡 😴 │ 心情标签
    ├──────────────────────────────┤
    │ │
    │ 今天发生了什么… │ 内容输入区
    │ │
    │ │
    │ │
    ├──────────────────────────────┤
    │ [删除日记] │ 仅编辑模式显示
    └──────────────────────────────┘

    3.2 顶部导航栏

    Row() {
    // 返回按钮
    Text('返回')
    .fontSize(16)
    .onClick(() => {
    router.back()
    })

    // 标题
    Text(this.isEditMode ? '编辑日记' : '新建日记')
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
    .layoutWeight(1)
    .textAlign(TextAlign.Center)

    // 保存按钮
    Text('保存')
    .fontSize(16)
    .fontColor('#007DFF')
    .onClick(() => {
    this.saveDiary()
    })
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })

    四、日期选择功能

    4.1 日期显示行

    Row() {
    Image($r('app.media.calendar'))
    .width(20)
    .height(20)
    Text(this.formatDate(this.selectedDate))
    .fontSize(16)
    .layoutWeight(1)
    Image($r('app.media.arrow_down'))
    .width(16)
    .height(16)
    }
    .padding(16)
    .onClick(() => {
    this.showDatePicker = true
    })

    4.2 DatePicker 弹窗

    if (this.showDatePicker) {
    DatePickerDialog.show({
    start: new Date('2020-01-01'),
    end: new Date(),
    selected: this.selectedDate,
    onAccept: (value: DatePickerResult) => {
    this.selectedDate = new Date(value.year, value.month, value.day)
    this.diary.date = this.formatDate(this.selectedDate)
    this.showDatePicker = false
    },
    onCancel: () => {
    this.showDatePicker = false
    }
    })
    }

    DatePickerDialog 参数说明:

    参数说明
    start 可选最早日期
    end 可选最晚日期
    selected 默认选中日期
    onAccept 确认选择回调
    onCancel 取消选择回调

    4.3 日期格式化

    private formatDate(date: Date): string {
    const year = date.getFullYear()
    const month = (date.getMonth() + 1).toString().padStart(2, '0')
    const day = date.getDate().toString().padStart(2, '0')
    return `${year}${month}${day}`
    }

    格式化要点:

    • 月份需要 +1(getMonth() 返回 0-11)
    • 使用 padStart(2, '0') 保证两位数格式
    • 最终输出 YYYY-MM-DD 格式

    五、天气与心情选择

    5.1 天气标签组

    Text('天气')
    .fontSize(14)
    .fontColor('#666')
    .margin({ top: 16, left: 16 })

    Flex({ wrap: FlexWrap.Wrap }) {
    ForEach(WeatherList, (item: WeatherItem, index: number) => {
    Text(item.label)
    .fontSize(16)
    .padding({ left: 16, right: 16, top: 8, bottom: 8 })
    .margin(4)
    .borderRadius(20)
    .backgroundColor(
    this.selectedWeather === item.value ? '#007DFF' : '#F5F5F5'
    )
    .fontColor(
    this.selectedWeather === item.value ? '#FFFFFF' : '#333333'
    )
    .onClick(() => {
    this.selectedWeather = item.value
    this.diary.weather = item.label
    })
    })
    }
    .padding({ left: 12, right: 12 })

    5.2 心情标签组

    Text('心情')
    .fontSize(14)
    .fontColor('#666')
    .margin({ top: 16, left: 16 })

    Flex({ wrap: FlexWrap.Wrap }) {
    ForEach(MoodList, (item: MoodItem, index: number) => {
    Text(item.label)
    .fontSize(16)
    .padding({ left: 16, right: 16, top: 8, bottom: 8 })
    .margin(4)
    .borderRadius(20)
    .backgroundColor(
    this.selectedMood === item.value ? '#007DFF' : '#F5F5F5'
    )
    .fontColor(
    this.selectedMood === item.value ? '#FFFFFF' : '#333333'
    )
    .onClick(() => {
    this.selectedMood = item.value
    this.diary.mood = item.label
    })
    })
    }

    标签选择设计模式:

  • 使用 Flex 组件实现自动换行布局
  • 选中状态通过背景色和文字颜色区分
  • 圆角胶囊形状符合现代标签设计风格
  • 点击即选中,支持切换
  • 六、内容输入区

    TextArea({ text: this.contentInput, placeholder: '今天发生了什么…' })
    .width('100%')
    .height(200)
    .fontSize(16)
    .padding(16)
    .borderRadius(12)
    .backgroundColor('#FAFAFA')
    .onChange((value: string) => {
    this.contentInput = value
    this.diary.content = value
    })

    TextArea 关键属性:

    属性说明
    text 输入框当前文本
    placeholder 占位提示文字
    onChange 文本变化回调
    height 输入区域高度

    七、数据保存逻辑

    7.1 表单验证

    private validateInput(): boolean {
    if (this.contentInput.trim().length === 0) {
    promptAction.showToast({
    message: '请输入日记内容',
    duration: 2000
    })
    return false
    }
    return true
    }

    7.2 保存方法

    private async saveDiary() {
    if (!this.validateInput()) {
    return
    }

    // 组装日记数据
    this.diary.content = this.contentInput
    this.diary.date = this.formatDate(this.selectedDate)

    try {
    if (this.isEditMode) {
    this.diary.id = this.editId
    await this.store.updateDiary(this.diary)
    promptAction.showToast({ message: '保存成功', duration: 1500 })
    } else {
    this.diary.id = this.generateId()
    this.diary.createTime = Date.now()
    await this.store.addDiary(this.diary)
    promptAction.showToast({ message: '创建成功', duration: 1500 })
    }
    router.back()
    } catch (error) {
    promptAction.showToast({
    message: '保存失败,请重试',
    duration: 2000
    })
    console.error('保存日记失败: ' + error)
    }
    }

    保存流程:

    用户点击保存


    表单验证 ──失败──→ 显示错误提示

    通过


    判断模式

    ├──新建──→ 生成ID → 添加日记 → Toast提示 → 返回

    └──编辑──→ 设置ID → 更新日记 → Toast提示 → 返回

    7.3 ID 生成

    private generateId(): string {
    return Date.now().toString() + Math.random().toString(36).substr(2, 9)
    }

    ID 生成策略:

    • 时间戳保证全局递增性
    • 随机字符串防止同一毫秒创建的日记冲突
    • 最终格式示例:1721845678901a3b7c2d

    八、删除功能

    仅在编辑模式下显示删除按钮:

    if (this.isEditMode) {
    Button('删除日记')
    .width('90%')
    .height(48)
    .fontSize(16)
    .fontColor('#FF3B30')
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .margin({ top: 24 })
    .onClick(() => {
    this.showDeleteConfirm = true
    })
    }

    删除确认弹窗将在后续章节详细讨论。

    九、加载已有数据

    private async loadDiary(id: string) {
    try {
    const diary = await this.store.getDiaryById(id)
    if (diary) {
    this.diary = diary
    this.contentInput = diary.content
    this.selectedDate = this.parseDate(diary.date)
    this.selectedWeather = this.getWeatherByLabel(diary.weather)
    this.selectedMood = this.getMoodByLabel(diary.mood)
    }
    } catch (error) {
    console.error('加载日记失败: ' + error)
    promptAction.showToast({ message: '加载失败', duration: 2000 })
    }
    }

    十、总结

    DiaryEdit 页面的设计要点:

  • 双模式复用:新建和编辑共用同一页面,通过状态变量区分
  • 表单数据绑定:@State 变量与 UI 组件双向同步
  • 标签选择器:Flex + 条件样式实现标签组
  • 异步保存:async/await 处理 Preferences 存储
  • 用户反馈:Toast 提示操作结果
  • 数据验证:前端验证防止空内容提交
  • 这些模式是鸿蒙表单页面的典型实现,适用于各类编辑场景。

    赞(0)
    未经允许不得转载:171主机测评 » HarmonyOs应用《日记本》开发第17篇 - 日记编辑页面 DiaryEdit 详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址