HarmonyOS期末项目——作业清单(二)任务创建与编辑

系列文章目录

HarmonyOS期末项目——作业清单(一)项目介绍和应用主页面设计
HarmonyOS期末项目——作业清单(三)搭建关系型数据库
HarmonyOS期末项目——作业清单(四)通用工具类
HarmonyOS期末项目——作业清单(完结篇)组件与视图

目录

系列文章目录

HarmonyOS期末项目——作业清单(一)项目介绍和应用主页面设计

HarmonyOS期末项目——作业清单(三)搭建关系型数据库

HarmonyOS期末项目——作业清单(四)通用工具类

HarmonyOS期末项目——作业清单(完结篇)组件与视图

前言

一、关键技术

1.1 任务和时间管理

1.2 友好的用户交互设计

二、交互示意图

三、任务创建于编辑

1.功能概述

2.任务列表

2.1 功能简介

2.2 代码文件

3. 任务编辑

3.1 TaskItemDialog.ets (任务项对话框)

3.1.1 功能简介

3.1.2 代码文件

3.2 TaskItemDialog_ViewOnly.ets (只读任务项对话框)

3.2.1 功能简介

3.2.2 代码文件

四、遇到的问题及解决方案

问题一:任务添加功能无法正常保存新任务

问题二:任务分类删除后仍显示在列表中

总结


前言

在前一篇文章中,我介绍了作业清单项目及其应用主页面的设计。作业清单是一个帮助学生和工作人员组织和管理任务的工具,能提高工作效率和时间管理能力。

项目介绍部分详细说明了作业清单的主要功能和特点,包括记录和安排任务,以及支持多种任务分类和标签功能,方便任务的组织和查找。

在应用主页面设计部分,我的设计建议是,强调界面要简洁明了,信息呈现清晰,并建议添加搜索和过滤功能,以及用户体验优化,如交互反馈和动画效果。

本篇文章将深入探讨如何实现作业清单的任务创建与编辑功能,包括添加任务、设置提醒、分类标签等,并分享优化用户体验的技巧。


一、关键技术

1.1 任务和时间管理

  • 任务管理:提供任务名称、截止日期、完成情况等分组查看功能,帮助用户高效管理任务。
  • 时间管理:通过颜色和进度条直观展示任务距离截止日期的时间,帮助用户快速了解任务的紧迫性,合理安排时间。

1.2 友好的用户交互设计

在界面设计上注重用户交互体验,如在编辑和删除任务类别时提供了友好的提示和确认对话框,确保用户操作的安全性和可控性。

二、交互示意图

三、任务创建于编辑

1.功能概述

任务创建与编辑功能允许用户添加新任务、修改现有任务以及删除任务。

2.任务列表

TaskList.ets (任务列表组件)

该组件用于展示用户的任务列表,并提供了一系列的操作功能,包括添加、编辑、删除和查询等。

2.1 功能简介

  • 任务列表组件通过获取任务数据和分类数据,以及偏好设置等信息,展示了用户的所有任务列表。
  • 用户可以在任务列表中进行任务的增加、编辑和删除操作,以及查询任务信息。
  • 支持任务的勾选操作,用户可以标记任务是否已完成,并实时更新任务状态。
  • 提供了编辑弹窗 Dialog,用于用户编辑任务信息,包括任务名称、截止日期等,并可以选择任务分类。
  • 支持查看任务详情,用户可以点击任务列表中的任务项,查看任务的详细信息。
  • 用户可以通过右下角的 Fab 按钮快速添加新的任务,提高了用户的操作效率。
  • 支持对任务列表进行排序,根据任务的优先级或截止日期等条件进行排序展示。
  • 提供了分类列表弹窗,用户可以查看并管理任务的分类信息,包括添加新的分类、删除分类等操作。

通过以上功能,用户可以在任务列表页面方便地管理自己的任务,提高了任务管理的效率和便捷性。

2.2 代码文件



@Component

export default struct TaskList {

  @Link taskList: Array<TaskListItemData>
  @Link taskTable: TaskTable
  @Link prefData: PreferenceData
  @Link categoryList: TaskCategoryData[]
  @Link categoryTable: CategoryTable
  @State isEditing: boolean = false
  @State dialogIsAddNew: boolean = true
  @State taskItem: TaskListItemData = new TaskListItemData()
  private dialogSelectedIndex: number = -1
  private deleteTarget: Array<TaskListItemData> = []

  // UI 编辑弹窗 Dialog
  dialogController: CustomDialogController = new CustomDialogController({
    builder: TaskItemDialog({
      dialogIsAddNew: $dialogIsAddNew,
      taskItem: $taskItem,
      confirm: (dialogIsAddNew: boolean, taskItem: TaskListItemData) => {
        if (dialogIsAddNew) {
          this.submitNewTask(taskItem)
        } else {
          this.updateExistingTask(taskItem)
        }
        dialogIsAddNew = true
      },
      prefData: $prefData,
      categoryList: $categoryList
    }),
    alignment: DialogAlignment.Bottom,
    customStyle: true
  })
  dialogController_view: CustomDialogController = new CustomDialogController({
    builder: TaskItemDialog_ViewOnly({
      taskItem: $taskItem,
      categoryList: $categoryList
    }),
    alignment: DialogAlignment.Bottom,
    customStyle: true
  })
  dialogCategoryList: CustomDialogController = new CustomDialogController({
    builder: CategoryListDialog({
      categoryList: $categoryList,
      categoryTable: $categoryTable,
      safeDeleteCheckID: (id) => {
        for (let i = 0; i < this.taskList.length; ++i) {
          if (this.taskList[i].category == id) return false;
        }
        return true
      },
      openNewCategoryDialog: () => {
        this.dialogNewCategory.open()
      },
      queryCategory: (id) => {
        this.queryCategory(id)
      }
    })
  })
  dialogNewCategory: CustomDialogController = new CustomDialogController({
    builder: NewCategoryDialog({
      confirm: (newCategory: TaskCategoryData) => {
        this.categoryTable.insertData(newCategory, (id) => {
          newCategory.id = id;
          this.categoryList.push(newCategory)
          Logger.debug(`Category Added. Now: ${JSON.stringify(this.categoryList)}`)
        })
      }
    }),
  })


  @Builder TaskList_ListItem(item: TaskListItemData, index: number) {
    Row() {
      if (!this.isEditing) {
        Checkbox()
          .select(item.is_completed)
          .onChange((value: boolean) => {
            animateTo({duration: 300, curve: Curve.EaseInOut}, () => {
              item.is_completed = value
              this.dialogSelectedIndex = index
              this.updateExistingTask(item)
            })
          })
          .margin({ right: 10 })
      }
      Text(item.task_name)
        .textOverflow({overflow: TextOverflow.Ellipsis })
        .maxLines(1)
        .width('40%')
      Blank()
        .layoutWeight(1)
      Row() {
        Text(getTimeString(item.due_date_stamp))
          .margin({right: 10})

        Button() {
          Text(item.categoryName)
            .fontSize(14)
        }.backgroundColor('#f1f2f3')
        .margin({right: 4})
        .padding({left: 6, right: 6, top: 4, bottom: 4})
        if (!item.is_completed)
        Button({type: ButtonType.Circle, }) {
          Text(' ')
        }
        .backgroundColor(CommonConstants.DDL_COLOR[item.getDDLState()])
        .height(12)
        .width(12)
      }.margin({right: 8})
      if (this.isEditing) {
        Toggle({ type: ToggleType.Checkbox, isOn: false })
          .onChange((isOn: Boolean) => {
            if (isOn) this.deleteTarget.push(item)
            else {
              let this_id_index = this.deleteTarget.indexOf(item)
              this.deleteTarget.splice(this_id_index, 1)
            }
          })
      }
    }.justifyContent(FlexAlign.SpaceBetween)
    .height(56)
    .width('100%')
    .padding({ left: 12, right: 12 })
    .backgroundColor(item.topped ? '#80B6C5D1' : '#ffffff')
    .gesture(
      LongPressGesture()
        .onAction(() => {
          Logger.debug(`Drag Start`)
          item.topped = !item.topped
          this.dialogSelectedIndex = index
          this.updateExistingTask(item)
        })
    )
  }

  sortItems() {
    this.taskList.sort((a, b) => {
      return Number(b.topped == true ? 2 : 0) - Number(a.topped == true ? 2 : 0)
    })
  }

  // 数据库操作封装
  submitNewTask(taskData: TaskListItemData) {
    this.taskTable.insertData(taskData, (id: number) => {
      taskData.id = id
      taskData.setAll(this.categoryList)
      this.taskList.push(taskData)
    })
  }
  updateExistingTask(taskData: TaskListItemData) {
    this.taskTable.updateData(taskData, () => {})
    taskData.setAll(this.categoryList)
    let new_task = this.taskList
    this.taskList = new Array<TaskListItemData>()
    new_task[this.dialogSelectedIndex] = taskData
    new_task.sort((a, b) => {
      return Number(b.topped == true ? 2 : 0) - Number(a.topped == true ? 2 : 0)
    })
    new_task.forEach((val) => {
      this.taskList.push((val))
    })
    this.dialogSelectedIndex = -1
  }
  deleteSelectedTasks() {
    if (this.deleteTarget.length == 0) return;
    for (let i = 0; i < this.deleteTarget.length; ++i) {
      let target = this.deleteTarget[i]
      this.taskTable.deleteData(target, (ret) => {
        if (ret) {
          let targetIndex = this.taskList.indexOf(target)
          this.taskList.splice(targetIndex, 1)
        }
      })
    }
    this.deleteTarget = []
  }
  querySearchTask(searchVal: string, callback?: Function) {
    // 暂时只写了task_name
    this.taskTable.queryTaskName(`%${searchVal}%`, (searchRes: TaskListItemData[]) => {
      this.taskList = new Array<TaskListItemData>()
      searchRes.sort((a, b) => {
        return Number(b.topped == true ? 2 : 0) - Number(a.topped == true ? 2 : 0)
      })
      searchRes.forEach(item => {this.taskList.push(item)})
      for (let i = 0; i < this.taskList.length; ++i) {
        this.taskList[i].setAll(this.categoryList)
      }
      Logger.debug(`TaskListItemData = ${JSON.stringify(this.taskList)}`)
      if (callback !== undefined) callback()
    }, (searchVal == ''))
  }
  queryCategory(searchVal: number, callback?: Function) {
    let tmp: TaskListItemData = new TaskListItemData()
    tmp.category = searchVal
    this.taskTable.basic_query('category', tmp, (searchRes: TaskListItemData[]) => {
      this.taskList = new Array<TaskListItemData>()
      searchRes.sort((a, b) => {
        return Number(b.topped == true ? 2 : 0) - Number(a.topped == true ? 2 : 0)
      })
      searchRes.forEach(item => {this.taskList.push(item)})
      for (let i = 0; i < this.taskList.length; ++i) {
        this.taskList[i].setAll(this.categoryList)
      }
      Logger.debug(`TaskListItemData = ${JSON.stringify(this.taskList)}`)
      if (callback !== undefined) callback()
    }, (searchVal == 0))
  }
  // 数据库操作封装 结束

  // UI
  build() {
    Stack({alignContent: Alignment.Bottom}) {
      Stack({ alignContent: (this.isEditing ? Alignment.Bottom : Alignment.BottomEnd) }) { // 主界面与悬浮按钮叠放
        Navigation() { // UI 标题栏
          // UI 搜索
          if (!this.isEditing)
            Search()
              .onSubmit((value: string) => {
                this.querySearchTask(value)
              })

          Column() {
            Column() {
              // UI 待办事项界面
              // issue: 不满一屏仍然滚动
              Scroll() {
                Column() {
                  // UI 待办事项列表
                  Text('未办事项')
                    .textTitle()

                  List() {
                    // 未办事项
                    ForEach(this.taskList, (item: TaskListItemData, index) => {
                      if (!item.is_completed) ListItem() {
                        this.TaskList_ListItem(item, index)
                      }.width('100%')
                      .backgroundColor(0xffffff)
                      .onClick(() => {
                        if (this.isEditing) {
                          this.taskItem = item
                          this.dialogIsAddNew = false
                          this.dialogSelectedIndex = this.taskList.indexOf(item)
                          this.dialogController.open()
                        } else {
                          this.taskItem = item
                          this.dialogController_view.open()
                        }
                      })
                    })
                  }
                  .width('100%')
                  .borderRadius(20)
                  .divider({ strokeWidth: 1 })
                  .margin({ bottom: 20 })


                  Text('已办事项')
                    .textTitle()

                  List() {
                    // 已办事项
                    ForEach(this.taskList, (item: TaskListItemData, index) => {
                      if (item.is_completed) ListItem() {
                        this.TaskList_ListItem(item, index)
                      }.width('100%')
                      .backgroundColor(0xffffff)
                      .onClick(() => {
                        if (this.isEditing) {
                          this.taskItem = item
                          this.dialogIsAddNew = false
                          this.dialogSelectedIndex = this.taskList.indexOf(item)
                          this.dialogController.open()
                        } else {
                          this.taskItem = item
                          this.dialogController_view.open()
                        }
                      })
                    })
                  }
                  .width('100%')
                  .borderRadius(24)
                  .divider({ strokeWidth: 1 })
                  .margin({bottom: 40})
                }
                // .justifyContent(FlexAlign.Start)
                .margin({top: 0})
              }
              .edgeEffect(EdgeEffect.Spring)
              .scrollBar(BarState.Off)
            }
          }.height('100%')
        }.titleMode(NavigationTitleMode.Full)
        .title('作业清单')
        .menus([{
          value: "",
          icon: '../../../resources/base/media/ic_public_folder.svg',
          action: () => {
            this.dialogCategoryList.open()
          }
        },{
          value: "",
          icon: (this.isEditing ? '../../../resources/base/media/ic_public_edit_filled.svg' : '../../../resources/base/media/ic_public_edit.svg'),
          action: () => {
            animateTo({duration: 200, curve: Curve.EaseOut}, () => {
              this.deleteTarget = []
              this.isEditing = !this.isEditing
            })
          }
        }])
        .height('100%')
        .mode(NavigationMode.Stack)

        // UI 右下角的 Fab
        Button({ type: ButtonType.Circle }) {
          if (!this.isEditing) Image($r('app.media.ic_public_list_add_light'))
          else {
            Image($r('app.media.ic_public_delete'))
              .height(28)
              .width(28)
          }
        }
        .backgroundColor(!this.isEditing ? 0x0A59F7 : 0xE84026)
        .width(60)
        .height(60)
        .margin({ bottom: 12, right: (this.isEditing ? 0 : 12) })
        .onClick(() => {
          animateTo({duration: 200, curve: Curve.EaseOut}, () => {
            if (!this.isEditing) {
              this.dialogIsAddNew = true
              this.taskItem = new TaskListItemData()
              this.dialogController.open()
            } else {
              if (this.deleteTarget.length != 0) {
                this.deleteSelectedTasks()
                this.isEditing = false
              }
            }
          })
        })
      }.padding(CommonUI.DEFAULT_PADDING)
      
    }
  }
}

@Extend(Text) function textTitle() {
  .fontSize(20)
  .width('100%')
  .margin({ left: 12, bottom: 10, top: 20 })
  .textAlign(TextAlign.Start)
}

function getTimeString(date_stamp: number): string {
  let date: Date = new Date()
  date.setTime(date_stamp)
  Logger.debug(`TaskList: datestamp = ${date_stamp}, date = ${date.toLocaleDateString()}`)
  let ds: string[] = date.toLocaleDateString().split('/')
  let res: string = `${ds[2]}/${ds[0]}/${ds[1]}`
  return res
}

3. 任务编辑

提供添加或编辑任务信息界面,提供了一系列的输入框和选择器,用户可以设置任务的名称、截止日期、紧急程度等信息。

3.1 TaskItemDialog.ets (任务项对话框)

3.1.1 功能简介
  • 用户可以在弹窗中输入任务的名称和详细信息,并设置任务的截止日期和开始日期。
  • 提供了分类选择器,用户可以选择任务所属的分类,以便更好地管理任务。
  • 支持设置任务的紧急程度,用户可以根据任务的重要性选择不同的紧急程度,用以标识任务的优先级。
  • 提供了时间选择器,用户可以方便地设置任务的开始日期和截止日期,并实时预览日期和时间的变化。
  • 用户可以通过弹窗中的按钮进行保存或取消操作,保存后可以将编辑的任务信息提交至任务列表。

3.1.2 代码文件


@CustomDialog
export struct TaskItemDialog {
  @Link taskItem: TaskListItemData
  @Link dialogIsAddNew: boolean
  @Link prefData: PreferenceData
  @Link categoryList: TaskCategoryData[]
  controller: CustomDialogController
  confirm: (dialogIsAddNew: boolean, newTask: TaskListItemData) => void

  private taskName: string
  private taskSubject: number
  private startDate: Date
  private taskDate: Date
  private taskDetailText: string
  private taskDdlDetail: string = '0.3-0.5-0.9'
  private taskCategoryID: number

  @State startTask: string[] = ['', '']
  @State endTask: string[] = ['', '']
  @State showedTaskDDL: string = '正常'
  @State showedTaskCategory: string = '默认清单'
  @State taskCategoryColor: string = '#000000'
  @State handlePopup: boolean = false

  @Builder DatePickerMenu($$:{date: Date, str: string[]}) {
    Menu() {
      MenuItem() {
        DatePicker({
          start: new Date('1970-01-01'),
          selected: $$.date,
          end: new Date('2100-01-01')
        })
          .onChange((value: DatePickerResult) => {
            Logger.debug(`TaskItemDialog date = ${JSON.stringify(value)}`)
            $$.date.setFullYear(value.year, value.month, value.day)
            Logger.debug(`TaskItemDialog res = ${JSON.stringify($$.date)}, str = ${$$.date.toLocaleString()}`)
            $$.str[0] = getDateString($$.date.getTime())
          })
          .backgroundColor(0xffffff)
      }
    }
  }
  @Builder TimePickerMenu($$:{date: Date, str: string[]}) {
    Menu() {
      MenuItem() {
        TimePicker({
          // start: new Date('1970-01-01'),
          selected: $$.date,
          // end: new Date('2100-01-01')
        })
          .onChange((value: TimePickerResult) => {
            Logger.debug(`TaskItemDialog date = ${JSON.stringify(value)}`)
            $$.date.setHours(value.hour, value.minute)
            Logger.debug(`TaskItemDialog res = ${JSON.stringify($$.date)}, str = ${$$.date.toLocaleString()}`)
            $$.str[1] = getTimeString($$.date.getTime())
          })
          .backgroundColor(0xffffff)
          .width('80%')
      }
    }
  }
  @Builder DDLDetailPickerMenu() {
    Menu() {
      ForEach(CommonConstants.DDL_DETAIL_PRESET, (preset) => {
        MenuItem({content: preset.hint})
          .onClick(() => {
            this.taskDdlDetail = preset.detail
            this.showedTaskDDL = preset.hint
          })
      })
    }
  }
  @Builder CategoryPickerMenu() {
    Menu() {
      ForEach(this.categoryList, (cate) => {
        MenuItem({content: cate.name}) {
          Button({type: ButtonType.Circle}) {
            Text(' ').fontSize(50)
          }
          .backgroundColor(cate.color)
        }
        .onClick(() => {
          this.taskCategoryID = cate.id
          this.taskCategoryColor = cate.color
          this.showedTaskCategory = cate.name
        })
      })
    }
  }

  aboutToAppear() {
    this.taskName = this.taskItem.task_name
    this.taskSubject = this.taskItem.subject
    this.taskDetailText = this.taskItem.detail
    this.taskDdlDetail = this.taskItem.ddl_detail
    this.taskDate = new Date()
    this.startDate = new Date()
    this.taskDate.setSeconds(0)
    this.startDate.setSeconds(0)
    Logger.debug('TaskItemDialog new date = ' + this.taskDate.toLocaleString())
    if (this.taskItem.start_date_stamp) this.startDate.setTime(this.taskItem.start_date_stamp)
    if (this.taskItem.due_date_stamp) this.taskDate.setTime(this.taskItem.due_date_stamp)
    this.startTask[0] = getDateString(this.startDate.getTime())
    this.startTask[1] = getTimeString(this.startDate.getTime())
    this.endTask[0] = getDateString(this.taskDate.getTime())
    this.endTask[1] = getTimeString(this.taskDate.getTime())
    this.showedTaskDDL = getDDLPresetFromDetail(this.taskDdlDetail)
    this.taskCategoryID = this.taskItem.category
    for (let i = 0; i < this.categoryList.length; ++i) {
      if (this.categoryList[i].id == this.taskCategoryID) {
        this.showedTaskCategory = this.categoryList[i].name
        this.taskCategoryColor = this.categoryList[i].color
        break;
      }
      if (i == this.categoryList.length - 1) {
        this.taskCategoryID = this.categoryList[0].id
        this.showedTaskCategory = this.categoryList[0].name
        this.taskCategoryColor = this.categoryList[0].color
      }
    }
  }

  build() {
    Column() {
      Image($r('app.media.half'))
        .width(64)
        .height(24)
        .onClick(() => {
          this.controller?.close();
        })
        .margin({top: 5})
      Column() {
        Column() {
          Row() {
            Text((this.dialogIsAddNew ? '添加' : '修改') + '事项')
              .fontSize(20)
              .alignSelf(ItemAlign.Start)
            Button() {
              Text(this.showedTaskCategory)
            }.backgroundColor('#f1f2f3')
            .bindMenu(this.CategoryPickerMenu)
            .padding({left: 12, right: 12, top: 8, bottom: 8})
          }
          .margin({ left: 24, bottom: 20, right: 24 })
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)

          TextInput({
            placeholder: '作业名称',
            text: this.taskName
          })
            .onChange((value: string) => {
              this.taskName = value
            })
            .backgroundColor(0xf1f2f3)
            .margin({ bottom: 10 })
            .height(50)
          TextArea({ placeholder: '详细信息', text: this.taskDetailText })
            .onChange((value: string) => {
              this.taskDetailText = value
            })
            .height(50 * 3)
            .margin({ bottom: 10 })
          Row(){
            Button('任务紧急程度:' + this.showedTaskDDL)
              .bindMenu(this.DDLDetailPickerMenu)
              .width('60%')
              .margin({ right: 15 })
              .backgroundColor(0xf1f2f3)
              .fontColor(0x000000)
            Button({type: ButtonType.Circle}) {
              Image($r('app.media.ic_public_help_filled'))
                .height(25)
                .width(25)
            }
            .onClick(() => {this.handlePopup = !this.handlePopup})
            .height(25)
            .width(25)
            .backgroundColor(0xffffff)
            .bindPopup(this.handlePopup, {
              message: '任务紧急程度会决定指示颜色变化的快慢,任务紧急程度越高,指示颜色越早变红。',
              placementOnTop: true,
              primaryButton: {
                value: '明白了',
                action: () => {this.handlePopup = false}
              }
            })
          }
        }.padding({ left: 16, right: 16})
        .margin({ bottom: 16})
        Row() {
          Button('布置日期:' + this.startTask[0])
            .bindMenu(this.DatePickerMenu({date: this.startDate, str: this.startTask}))
            .backgroundColor(0xf1f2f3)
            .fontColor(0x000000)
            .margin({right: 10})

          Button(this.startTask[1])
            .bindMenu(this.TimePickerMenu({date: this.startDate, str: this.startTask}))
            .backgroundColor(0xf1f2f3)
            .fontColor(0x000000)
        }.margin({bottom: 8})
        Row() {
          Button('截止日期:' + this.endTask[0])
            .bindMenu(this.DatePickerMenu({date: this.taskDate, str: this.endTask}))
            .backgroundColor(0xf1f2f3)
            .fontColor(0x000000)
            .margin({right: 10})

          Button(this.endTask[1])
            .bindMenu(this.TimePickerMenu({date: this.taskDate, str: this.endTask}))
            .backgroundColor(0xf1f2f3)
            .fontColor(0x000000)
        }
      }.margin({bottom: 16})
      // .margin({ bottom: 10 })
      Row() {
        Button('取消')
          .margin({ right: 10 })
          .onClick(() => {
            this.controller.close()
          })
        Button('保存')
          .onClick(() => {
            if (this.taskName == '') {
              promptAction.showToast({message: '任务名称不能为空'})
              return
            }
            if (this.taskDate.getTime() < this.startDate.getTime()) {
              promptAction.showToast({message: '结束日期不能在开始日期之前'})
            }
            this.taskItem.subject = this.taskSubject
            this.taskItem.task_name = this.taskName
            this.taskItem.detail = this.taskDetailText
            this.taskItem.due_date_stamp = this.taskDate.getTime()
            this.taskItem.start_date_stamp = this.startDate.getTime()
            this.taskItem.ddl_detail = this.taskDdlDetail
            this.taskItem.category = this.taskCategoryID
            Logger.debug(`TaskItemDialog ddlDetail = ${this.taskItem.ddl_detail}`)
            // Logger.debug(`TaskItemDialog due_date(stamp): ${this.taskItem.due_date_stamp}, date: ${this.taskDate.toDateString()}`)
            this.confirm(this.dialogIsAddNew, this.taskItem)
            this.controller.close()
          })
      }.margin({ bottom: 20 })
    }.backgroundColor(0xffffff)
    .width('100%')
    // .height('60%')
    .borderRadius(24)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

function getTimeString(date_stamp: number): string {
  let date: Date = new Date()
  date.setTime(date_stamp)
  Logger.debug(`TaskList: datestamp = ${date_stamp}, time = ${date.toLocaleTimeString()}`)
  return date.toLocaleTimeString()
}

function getDateString(date_stamp: number): string {
  let date: Date = new Date()
  date.setTime(date_stamp)
  Logger.debug(`TaskList: datestamp = ${date_stamp}, date = ${date.toLocaleDateString()}`)
  let ds: string[] = date.toLocaleDateString().split('/')
  let res: string = `${ds[2]}/${ds[0]}/${ds[1]}`
  return res
}

function getDDLPresetFromDetail(detail: string): string {
  for (let i = 0; i < 5; ++i) {
    Logger.debug('getDDL: ' + CommonConstants.DDL_DETAIL_PRESET[i].detail)
    if (CommonConstants.DDL_DETAIL_PRESET[i].detail == detail) return CommonConstants.DDL_DETAIL_PRESET[i].hint
  } return ''
}

3.2 TaskItemDialog_ViewOnly.ets (只读任务项对话框)

仅用于查看任务项的对话框。

3.2.1 功能简介
  • 展示了任务的名称和详细内容,用户可以清楚地了解任务的具体内容。
  • 显示了任务的截止日期和布置日期,以便用户知晓任务的时间安排。
  • 根据任务的截止日期计算并显示了任务的进度,用户可以直观地了解任务的完成情况。
  • 提供了一个确定按钮,用户点击后可以关闭弹窗。

3.2.2 代码文件


@CustomDialog
export struct TaskItemDialog_ViewOnly {
  @Link taskItem: TaskListItemData
  @Link categoryList: TaskCategoryData[]
  controller: CustomDialogController

  private taskName: string
  private startDate: Date
  private taskDate: Date
  private taskDetailText: string
  private showedTaskCategory: string
  private taskCategoryID: number

  @State showedTaskDate: string = ''
  @State showedStartDate: string = ''

  aboutToAppear() {
    this.taskName = this.taskItem.task_name
    this.taskDetailText = this.taskItem.detail
    this.taskDate = new Date()
    this.startDate = new Date()
    Logger.debug('TaskItemDialog new date = ' + this.taskDate.toLocaleString())
    this.taskDate.setTime(this.taskItem.due_date_stamp)
    this.startDate.setTime(this.taskItem.start_date_stamp)
    this.showedTaskDate = getTimeString(this.taskDate.getTime())
    this.showedStartDate = getTimeString(this.startDate.getTime())
    this.taskCategoryID = this.taskItem.category
    this.showedTaskCategory = this.taskItem.categoryName
  }

  build() {
    Column() {
      Image($r('app.media.half'))
        .width(64)
        .height(24)
        .onClick(() => {
          this.controller?.close();
        })
        .margin({top: 5})
      Column() {
        Column() {
          Row() {
            Text('任务名称')
              .fontSize(18)
              .alignSelf(ItemAlign.Start)
              .margin({ top: 12, bottom: 12 })

            Button() {
              Text(this.showedTaskCategory)
            }.backgroundColor('#f1f2f3')
            .padding({left: 12, right: 12, top: 8, bottom: 8})
          }.width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          Text(this.taskName)
            .TextStyle(50, TextAlign.Start)
            .width('90%')
          Text('详细内容')
            .fontSize(18)
            .alignSelf(ItemAlign.Start)
            .margin({top: 12, bottom: 12})
          Text(this.taskDetailText)
            .height(50 * 3)
            .width('90%')
            .borderRadius(24)
            .backgroundColor(0xf1f2f3)
            .padding({ left: 12, right: 12, top: 12, bottom: 12 })
            .align(Alignment.TopStart)
        }.padding({ left: 16, right: 16})
        .margin({ bottom: 20})
        Text(`布置日期:${this.showedStartDate}`)
          .TextStyle(40, TextAlign.Center)
          .margin({bottom: 8})
        Text(`截止日期:${this.showedTaskDate}`)
          .TextStyle(40, TextAlign.Center)

        Progress({value: 100 - this.taskItem.getDDLPer()})
          .margin({bottom: 10, top: 10})
          .color(CommonConstants.DDL_COLOR[this.taskItem.getDDLState()])
      }
      Row() {
        Button('确定')
          .onClick(() => {
            this.controller.close()
          })
      }.margin({ bottom: 20 })
    }.backgroundColor(0xffffff)
    .width('100%')
    .borderRadius(24)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

@Extend(Text) function TextStyle(height: number, align: TextAlign) {
  .width('80%')
  .height(height)
  .padding({ left: 12, right: 12 })
  .borderRadius(24)
  .backgroundColor(0xf1f2f3)
  .fontColor(0x000000)
  .textAlign(align)
}

function getTimeString(date_stamp: number): string {
  let date: Date = new Date()
  date.setTime(date_stamp)
  Logger.debug(`TaskList: datestamp = ${date_stamp}, date = ${date.toLocaleDateString()}`)
  let ds: string[] = date.toLocaleDateString().split('/')
  let res: string = `${ds[2]}/${ds[0]}/${ds[1]} `
  return res + date.toLocaleTimeString()
}

四、遇到的问题及解决方案

问题一:任务添加功能无法正常保存新任务

问题描述
在实现任务添加功能时,用户在对话框中输入新任务信息并提交后,新任务未能正确保存到任务列表中,也未显示在应用界面上。

解决方案
1. 确保任务数据正确保存:
   检查 `submitNewTask` 方法,确保新任务数据成功插入到数据库中,并且 `taskList` 数组被正确更新。
 

    submitNewTask(taskData: TaskListItemData) {
      this.taskTable.insertData(taskData, (id: number) => {
        taskData.id = id;
        taskData.setAll(this.categoryList);
        this.taskList.push(taskData);
        this.sortItems();  // 确保任务列表排序更新
      });
    }

2. 验证对话框提交逻辑:
   确认对话框中提交按钮的 `confirm` 方法正确调用了 `submitNewTask`。

  
    builder: TaskItemDialog({
      dialogIsAddNew: $dialogIsAddNew,
      taskItem: $taskItem,
      confirm: (dialogIsAddNew: boolean, taskItem: TaskListItemData) => {
        if (dialogIsAddNew) {
          this.submitNewTask(taskItem);
        } else {
          this.updateExistingTask(taskItem);
        }
        this.dialogIsAddNew = true;  // 重置对话框状态
      },
      prefData: $prefData,
      categoryList: $categoryList
    })

问题二:任务分类删除后仍显示在列表中

问题描述
用户尝试删除某个任务分类,但该分类下的任务仍然显示在任务列表中,导致数据不一致的问题。

解决方案
1. 添加安全删除检查:
   在删除分类前,先检查该分类下是否还有任务,确保没有任务再进行删除操作。

    builder: CategoryListDialog({
      categoryList: $categoryList,
      categoryTable: $categoryTable,
      safeDeleteCheckID: (id) => {
        for (let i = 0; i < this.taskList.length; ++i) {
          if (this.taskList[i].category == id) return false;
        }
        return true;
      },
      openNewCategoryDialog: () => {
        this.dialogNewCategory.open();
      },
      queryCategory: (id) => {
        this.queryCategory(id);
      }
    })


 

2. 删除后更新任务列表:
   在删除分类后,刷新任务列表,移除已删除分类下的所有任务。

    confirm: (newCategory: TaskCategoryData) => {
      this.categoryTable.insertData(newCategory, (id) => {
        newCategory.id = id;
        this.categoryList.push(newCategory);
        Logger.debug(`Category Added. Now: ${JSON.stringify(this.categoryList)}`);
      });
    },
    safeDeleteCheckID: (id) => {
      let safeToDelete = true;
      this.taskList.forEach(task => {
        if (task.category === id) {
          safeToDelete = false;
        }
      });
      return safeToDelete;
    },
    onDeleteCategory: (id) => {
      this.taskTable.deleteTasksByCategory(id, (success) => {
        if (success) {
          this.taskList = this.taskList.filter(task => task.category !== id);
          this.categoryList = this.categoryList.filter(category => category.id !== id);
          Logger.debug(`Category and related tasks deleted. Updated taskList: ${JSON.stringify(this.taskList)}`);
        }
      });
    }
  

通过以上方法,可以有效解决任务添加功能和任务分类删除后数据不一致的问题,确保用户在使用过程中的体验更加流畅和可靠。


总结

在本次实验中,我实现了作业清单的任务创建与编辑功能,具体如下:

1. 任务创建:用户可以添加新任务,并设置任务名称、截止日期、提醒时间和分类标签。

2. 任务编辑:用户可以随时修改任务信息,包括名称、截止日期、提醒时间和分类标签。

3. 分类标签:实现了任务分类标签功能,方便用户按任务性质和优先级进行管理。

4. 交互反馈:增加了交互反馈和动画效果,提升了操作流畅性和用户体验。

5. 数据持久化:确保任务数据在应用重启或设备重启后不会丢失。

通过这些功能的实现,作业清单提高了任务管理的便捷性和用户体验,成为学生和工作人员高效的任务管理工具。

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个关于电影主题的HTML网页设计作业的示例,以千与千寻为例: ```html <!DOCTYPE html> <html> <head> <title>千与千寻</title> <style> /* CSS样式 */ body { font-family: Arial, sans-serif; background-color: #f2f2f2; } h1 { color: #333333; text-align: center; } .container { max-width: 800px; margin: 0 auto; padding: 20px; background-color: #ffffff; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .movie-info { display: flex; align-items: center; margin-bottom: 20px; } .movie-info img { width: 150px; height: auto; margin-right: 20px; } .movie-info h2 { margin: 0; } .movie-info p { margin: 0; } </style> </head> <body> <div class="container"> <h1>千与千寻</h1> <div class="movie-info"> <img src="千与千寻海报.jpg" alt="千与千寻海报"> <div> <h2>电影信息</h2> <p>导演:宫崎骏</p> <p>主演:柊瑠美、入野自由</p> <p>上映日期:2001年7月20日</p> <p>片长:125分钟</p> </div> </div> <h2>剧情简介</h2> <p>《千与千寻》是宫崎骏执导的一部动画电影,讲述了一个小女孩千寻在神秘的世界中寻找父母的故事。影片以细腻的画面和深刻的寓意赢得了广大观众的喜爱。</p> <h2>影评</h2> <p>这是一部非常有意义的电影,它通过一个小女孩的冒险故事,探讨了成长、勇气和爱的主题。宫崎骏的动画技巧和故事情节都非常出色,让人流连忘返。</p> </div> </body> </html> ``` 这个示例包含了一个简单的电影主题网页,其中包括电影的基本信息、剧情简介和影评。你可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值