系列文章目录
HarmonyOS期末项目——作业清单(一)项目介绍和应用主页面设计
HarmonyOS期末项目——作业清单(三)搭建关系型数据库
HarmonyOS期末项目——作业清单(四)通用工具类
HarmonyOS期末项目——作业清单(完结篇)组件与视图
目录
HarmonyOS期末项目——作业清单(一)项目介绍和应用主页面设计
HarmonyOS期末项目——作业清单(三)搭建关系型数据库
3.1 TaskItemDialog.ets (任务项对话框)
3.2 TaskItemDialog_ViewOnly.ets (只读任务项对话框)
前言
在前一篇文章中,我介绍了作业清单项目及其应用主页面的设计。作业清单是一个帮助学生和工作人员组织和管理任务的工具,能提高工作效率和时间管理能力。
项目介绍部分详细说明了作业清单的主要功能和特点,包括记录和安排任务,以及支持多种任务分类和标签功能,方便任务的组织和查找。
在应用主页面设计部分,我的设计建议是,强调界面要简洁明了,信息呈现清晰,并建议添加搜索和过滤功能,以及用户体验优化,如交互反馈和动画效果。
本篇文章将深入探讨如何实现作业清单的任务创建与编辑功能,包括添加任务、设置提醒、分类标签等,并分享优化用户体验的技巧。
一、关键技术
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. 数据持久化:确保任务数据在应用重启或设备重启后不会丢失。
通过这些功能的实现,作业清单提高了任务管理的便捷性和用户体验,成为学生和工作人员高效的任务管理工具。