TodoList 任务详情子系统鸿蒙PC的Electron框架实现技术详解
欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/
atomgit仓库地址:https://atomgit.com/Math_teacher_fan/todolist


一、项目背景与设计理念
任务管理是每个人日常生活中都会面临的需求。从简单的购物清单到复杂的工作项目规划,任务管理工具无处不在。然而,传统的任务管理应用往往存在几个问题:界面复杂、使用繁琐、缺乏上下文感知能力、无法灵活适应用户的实际需求。
TodoList 任务详情子系统的设计初衷是解决这些痛点。应用采用"可选描述字段"的理念,任务标题是必填的,但描述是可选的。这种设计既保证了任务的清晰表达,又不会因为过于繁琐而增加用户负担。同时,应用具有"上下文感知渲染"能力,能够根据当前时间、任务状态等上下文信息,智能地展示相关信息,帮助用户更高效地管理任务。
从技术角度看,这个项目是Electron与OpenHarmony融合框架的一次实践。通过web_engine模块,开发者可以使用熟悉的Web技术栈开发鸿蒙桌面应用。应用的核心逻辑全部用JavaScript实现,界面采用响应式设计,这种技术选型让代码维护变得简单,也让更多开发者能够参与到项目中来。
二、系统架构设计
2.1 三层架构模型
TodoList应用采用经典的三层架构模型,将视图层、样式层和业务逻辑层分离。这种架构设计的好处是职责清晰、维护方便、扩展灵活。
视图层负责页面的结构布局和组件组织。HTML文件定义了应用的整体框架,包括头部导航、左侧的任务输入和任务列表区域、右侧的任务详情和上下文信息区域。左侧区域包含新建任务表单、任务列表、筛选器和统计信息四个核心模块。右侧区域包含任务详情面板、上下文信息面板和快捷操作面板三个辅助模块。这种左右分栏的布局设计让用户能够同时查看任务列表和任务详情,提高了操作效率。
样式层负责应用的视觉呈现。CSS文件定义了所有组件的样式,包括颜色、字体、间距、动画效果等。应用采用CSS变量系统,将主题色、背景色、边框色等统一定义在根选择器中,方便后续的主题切换和样式调整。紫色系配色方案从浅紫到深紫形成渐变层次,配合圆角边框和阴影效果,营造出专业而现代的视觉感受。
业务逻辑层负责应用的核心功能实现。JavaScript文件包含了任务管理、上下文感知渲染、数据持久化等所有业务逻辑。代码采用模块化的组织方式,每个功能模块都有独立的函数集合,通过状态对象统一管理应用数据。这种设计让代码结构清晰,便于后续的功能扩展和维护。
2.2 数据流设计
应用的数据流采用单向数据流模式。用户操作触发事件处理函数,函数更新状态对象,状态变化触发界面重新渲染。这种数据流设计保证了数据的一致性,避免了状态混乱的问题。
状态对象是整个应用的核心数据容器。它包含三个主要属性:tasks存储任务数组,selectedTaskId存储当前选中的任务ID,currentFilter存储当前的筛选条件。所有数据操作都通过这个状态对象进行,确保数据的集中管理和统一更新。
界面渲染函数负责将状态数据转换为可视化的DOM元素。每个功能模块都有对应的渲染函数,如renderTaskList负责渲染任务列表,renderTaskDetail负责渲染任务详情,updateContextInfo负责更新上下文信息。这些函数读取状态数据,生成HTML字符串,然后插入到对应的容器元素中。当状态数据发生变化时,只需调用对应的渲染函数,界面就会自动更新。
2.3 模块化设计
应用采用模块化设计,将不同功能封装到独立的函数中。主要模块包括任务管理模块、上下文感知模块、数据持久化模块和UI渲染模块。
任务管理模块负责任务的增删改查操作。包括addTask函数添加新任务,deleteTask函数删除任务,toggleTaskComplete函数切换任务完成状态,selectTask函数选择任务。这些函数修改appState.tasks数组,然后调用saveState和renderTaskList更新界面。
上下文感知模块负责根据上下文信息更新界面显示。包括updateContextInfo函数更新上下文信息面板,getDueDateInfo函数计算截止日期相关信息,startClock函数启动实时时钟。这些函数读取appState.tasks数组和当前时间,计算上下文信息,然后更新相应的DOM元素。
数据持久化模块负责应用数据的本地存储和读取。包括saveState函数保存状态到localStorage,loadState函数从localStorage读取状态。这些函数使用JSON序列化和反序列化,实现数据的持久化。
UI渲染模块负责生成HTML内容和更新DOM。包括renderTaskList函数渲染任务列表,renderTaskDetail函数渲染任务详情,updateTaskStats函数更新任务统计信息。这些函数使用模板字符串生成HTML内容,使用innerHTML属性更新DOM。
三、核心功能实现详解
3.1 任务数据结构设计
任务数据是整个应用的核心数据源。应用采用对象数组的形式组织任务数据,每个任务对象包含多个属性字段。
{
id: Date.now(),
title: '任务标题',
description: '任务描述',
priority: 'high',
category: 'work',
dueDate: '2026-06-01',
completed: false,
createdAt: new Date().toISOString()
}
这个数据结构设计有几个关键点。首先是id字段,作为任务的唯一标识符,使用Date.now()生成时间戳。其次是title字段,存储任务标题,这是必填字段。再次是description字段,存储任务描述,这是可选字段。priority字段存储优先级,有high、medium、low三个值。category字段存储分类标签,有work、personal、study、shopping、other五个值。dueDate字段存储截止日期,格式为YYYY-MM-DD。completed字段存储完成状态,布尔值。createdAt字段存储创建时间,使用ISO格式字符串。
这种数据结构设计的好处是信息完整、结构清晰、易于扩展。如果需要添加新的任务属性,只需在对象中添加新的字段,然后修改渲染函数即可。比如需要添加任务标签,只需添加tags字段。
3.2 任务添加功能实现
任务添加是应用的核心功能之一,handleAddTask函数实现了完整的添加逻辑。
function handleAddTask() {
const title = document.getElementById('taskTitle').value.trim();
const description = document.getElementById('taskDescription').value.trim();
const priority = document.getElementById('taskPriority').value;
const dueDate = document.getElementById('taskDueDate').value;
const category = appState.selectedCategory || 'other';
if (!title) {
showToast('请输入任务标题!');
document.getElementById('taskTitle').focus();
return;
}
const task = {
id: Date.now(),
title: title,
description: description,
priority: priority,
category: category,
dueDate: dueDate,
completed: false,
createdAt: new Date().toISOString()
};
appState.tasks.unshift(task);
saveState();
renderTaskList();
updateTaskStats();
updateContextInfo();
document.getElementById('taskTitle').value = '';
document.getElementById('taskDescription').value = '';
document.getElementById('taskDueDate').value = '';
document.getElementById('taskPriority').value = 'medium';
document.querySelectorAll('#categoryChips .chip').forEach(c => c.classList.remove('selected'));
appState.selectedCategory = null;
updateCharCount('taskTitle', 'titleCharCount', 100);
updateCharCount('taskDescription', 'descCharCount', 500);
showToast('任务添加成功!');
}
这段代码展示了任务添加的完整流程。首先读取表单输入的值,包括标题、描述、优先级、截止日期和分类。标题使用trim方法去除首尾空白,如果为空则显示提示并返回。
然后创建任务对象,使用Date.now()生成唯一ID。unshift方法将新任务添加到数组开头,保证最新添加的任务显示在最前面。
最后调用saveState保存状态,renderTaskList渲染列表,updateTaskStats更新统计,updateContextInfo更新上下文信息。同时清空表单,为下次添加做准备。
3.3 上下文感知渲染实现
上下文感知渲染是应用的核心亮点,updateContextInfo函数实现了这一功能。
function updateContextInfo() {
const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
const dateStr = now.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' });
document.getElementById('currentTime').textContent = timeStr;
document.getElementById('todayDate').textContent = dateStr;
const total = appState.tasks.length;
const completed = appState.tasks.filter(t => t.completed).length;
const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
document.getElementById('contextStats').textContent = `${progress}% 完成 (${completed}/${total})`;
document.getElementById('contextStats').className = progress >= 80 ? 'context-value success' : 'context-value';
const urgentTasks = appState.tasks.filter(t => {
if (t.completed || !t.dueDate) return false;
const due = new Date(t.dueDate);
const today = new Date();
today.setHours(0, 0, 0, 0);
const dueDay = new Date(due);
dueDay.setHours(0, 0, 0, 0);
const diffDays = Math.ceil((dueDay - today) / (1000 * 60 * 60 * 24));
return diffDays <= 2;
});
if (urgentTasks.length > 0) {
document.getElementById('contextTrend').textContent = `⚠️ ${urgentTasks.length}项紧急任务`;
document.getElementById('contextTrend').className = 'context-value urgent';
} else if (progress >= 80) {
document.getElementById('contextTrend').textContent = '🎉 进度优秀!';
document.getElementById('contextTrend').className = 'context-value success';
} else {
document.getElementById('contextTrend').textContent = '💪 继续加油!';
document.getElementById('contextTrend').className = 'context-value';
}
}
这段代码展示了上下文感知渲染的实现逻辑。函数首先获取当前时间,使用LocaleString方法格式化为中文显示。然后更新当前时间和日期的DOM元素。
接下来计算任务统计信息。使用filter方法分别统计总数和已完成数,计算完成百分比。根据百分比值设置contextStats的样式类,完成率达到80%以上时使用success样式。
然后计算紧急任务。筛选条件是:未完成、有截止日期、截止日期在2天以内。计算方法是将截止日期和今天都设置为零点,然后计算天数差。
最后根据紧急任务数量和完成进度更新趋势信息。紧急任务多于0时显示警告提示;完成进度达到80%以上时显示鼓励信息;否则显示加油提示。
3.4 截止日期智能判断实现
截止日期的智能判断是上下文感知的重要体现,getDueDateInfo函数实现了这一功能。
function getDueDateInfo(dueDate) {
if (!dueDate) return { display: '', isUrgent: false };
const due = new Date(dueDate);
const today = new Date();
today.setHours(0, 0, 0, 0);
const dueDay = new Date(due);
dueDay.setHours(0, 0, 0, 0);
const diffDays = Math.ceil((dueDay - today) / (1000 * 60 * 60 * 24));
let display = '';
let isUrgent = false;
if (diffDays === 0) {
display = '今天';
isUrgent = true;
} else if (diffDays === 1) {
display = '明天';
isUrgent = true;
} else if (diffDays === -1) {
display = '昨天(已过期)';
isUrgent = true;
} else if (diffDays < -1) {
display = `${Math.abs(diffDays)}天前(已过期)`;
isUrgent = true;
} else {
display = `${diffDays}天后`;
isUrgent = diffDays <= 3;
}
return { display, isUrgent };
}
这段代码展示了截止日期智能判断的逻辑。首先检查dueDate是否为空,如果为空返回空显示和非紧急状态。
然后计算天数差。将截止日期和今天都设置为零点(清除时间部分),计算两个日期的差值。Math.ceil确保结果为整数天数。
根据diffDays的值判断显示文本和紧急状态。0表示今天,1表示明天,-1表示昨天,小于-1表示多天前,大于1表示多天后。紧急状态的判断逻辑是:今天、明天、昨天、多天前都标记为紧急,大于1但小于等于3天也标记为紧急。
3.5 任务筛选功能实现
任务筛选功能允许用户查看不同状态的任务,renderTaskList函数结合筛选条件实现这一功能。
function renderTaskList() {
const container = document.getElementById('taskList');
let filteredTasks = appState.tasks;
if (appState.currentFilter === 'active') {
filteredTasks = appState.tasks.filter(task => !task.completed);
} else if (appState.currentFilter === 'completed') {
filteredTasks = appState.tasks.filter(task => task.completed);
}
if (filteredTasks.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-icon">📭</div>
<p>${appState.currentFilter === 'active' ? '暂无待办任务' :
appState.currentFilter === 'completed' ? '暂无已完成任务' : '暂无任务,添加一个开始吧!'}</p>
</div>
`;
return;
}
container.innerHTML = filteredTasks.map(task => {
const isSelected = task.id === appState.selectedTaskId;
const categoryIcon = getCategoryIcon(task.category);
const priorityBadge = getPriorityBadge(task.priority);
const dueDateInfo = getDueDateInfo(task.dueDate);
return `
<div class="task-card ${task.completed ? 'completed' : ''} ${isSelected ? 'selected' : ''}" data-id="${task.id}">
<div class="task-card-header">
<div class="task-title">${escapeHtml(task.title)}</div>
<span class="task-priority-badge ${task.priority}">${priorityBadge}</span>
</div>
${task.description ? `<div style="color: var(--text-muted); font-size: 0.9rem; margin-bottom: 8px;">${escapeHtml(task.description.substring(0, 80))}${task.description.length > 80 ? '...' : ''}</div>` : ''}
<div class="task-card-meta">
<span>${categoryIcon} ${getCategoryLabel(task.category)}</span>
${task.dueDate ? `<span class="${dueDateInfo.isUrgent ? 'task-priority-badge high' : ''}">📅 ${dueDateInfo.display}</span>` : ''}
</div>
<div class="task-actions">
<button class="task-action-btn" onclick="toggleTaskComplete(${task.id})" title="${task.completed ? '标记未完成' : '标记完成'}">
${task.completed ? '↩️' : '✓'}
</button>
<button class="task-action-btn" onclick="deleteTask(${task.id})" title="删除">
🗑️
</button>
</div>
</div>
`;
}).join('');
container.querySelectorAll('.task-card').forEach(card => {
card.addEventListener('click', (e) => {
if (e.target.closest('.task-action-btn')) return;
selectTask(parseInt(card.dataset.id));
});
});
}
这段代码展示了任务筛选和渲染的完整流程。首先根据currentFilter筛选任务数组。all显示全部任务,active显示未完成任务,completed显示已完成任务。
如果筛选结果为空,显示空状态提示,提示内容根据筛选条件动态变化。
如果筛选结果不为空,使用map方法遍历数组,为每个任务生成HTML元素。任务卡片包含完成状态样式、选中状态样式、优先级徽章、描述预览、分类和截止日期信息。
描述预览使用substring方法截取前80个字符,超过80字符的部分用省略号代替。任务卡片的点击事件绑定在父容器上,使用事件委托模式,避免为每个卡片单独绑定事件。
四、界面设计与交互优化
4.1 响应式布局设计
应用采用响应式布局设计,适配不同屏幕尺寸。布局系统基于CSS Grid实现,通过媒体查询调整网格配置。
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 1200px) {
.main-content {
grid-template-columns: 1fr;
}
.detail-meta-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 16px;
}
.row-fields {
grid-template-columns: 1fr;
}
.quick-actions {
grid-template-columns: 1fr;
}
}
这段CSS代码展示了响应式布局的核心配置。main-content容器使用grid布局,两栏等宽。当屏幕宽度小于1200px时,切换为单栏布局,详情元数据也改为单列。当屏幕宽度小于768px时,进一步调整内部元素的布局,头部内容改为垂直排列,表单字段改为单列,快捷操作改为单列。
响应式设计的关键是断点选择和布局调整。1200px断点针对中等屏幕,如平板设备。768px断点针对小屏幕,如手机设备。每个断点都有对应的布局调整,确保内容在不同屏幕上都能合理显示。
4.2 CSS变量系统
应用使用CSS变量系统管理主题色彩。所有颜色值定义在根选择器的变量中,其他样式规则通过var函数引用这些变量。
:root {
--primary: #6366f1;
--primary-light: #818cf8;
--primary-dark: #4f46e5;
--secondary: #10b981;
--accent: #f59e0b;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--info: #3b82f6;
--bg-primary: #f8fafc;
--bg-secondary: #ffffff;
--bg-tertiary: #f1f5f9;
--text-primary: #1e293b;
--text-secondary: #475569;
--text-muted: #94a3b8;
--border: #e2e8f0;
}
变量系统包含多个类别。primary系列定义主色调,从浅紫到深紫形成渐变层次。secondary定义辅助色。success、warning、danger、info定义状态色,用于不同类型的提示。bg系列定义背景色,从主背景到次背景形成层次。text系列定义文字色。border定义边框色。
这种变量系统的设计思路是语义化命名。变量名不是具体的颜色值,而是用途描述。比如primary表示主色调,success表示成功状态。这种命名方式让代码更易读,也让主题切换更方便。
4.3 动画效果设计
应用在多个交互点添加了动画效果,提升用户体验。动画效果包括按钮悬停、卡片选中、Toast提示等。
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.task-card:hover {
background: var(--border);
}
.task-card.selected {
border-left-color: var(--primary);
box-shadow: var(--shadow-sm);
}
.toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(100px);
opacity: 0;
transition: all 0.3s ease;
}
.toast.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
按钮悬停动画通过transform和box-shadow实现。当鼠标悬停在按钮上时,按钮向上移动2px,同时增加阴影效果,产生浮起的视觉感受。
任务卡片悬停动画通过背景色变化实现。当鼠标悬停时,背景色变为边框色,产生高亮效果。选中卡片通过左边框颜色和阴影区分,突出显示当前选中项。
Toast提示动画通过transform和opacity实现。Toast初始状态在屏幕底部之外,透明度为零。当显示时,Toast移动到屏幕内,透明度变为1。这种动画让提示信息自然滑入,不会突兀出现。
4.4 事件处理机制
应用的事件处理采用事件委托模式。不是为每个元素单独绑定事件,而是在父容器上绑定事件,通过事件对象的target属性判断触发元素。
container.querySelectorAll('.task-card').forEach(card => {
card.addEventListener('click', (e) => {
if (e.target.closest('.task-action-btn')) return;
selectTask(parseInt(card.dataset.id));
});
});
这段代码展示了事件委托的实现方式。事件监听器绑定在任务卡片上,而不是操作按钮上。当点击事件发生时,首先检查点击目标是否是操作按钮,如果是则不处理,否则调用selectTask函数。
事件委托的优势在于动态元素支持。当列表内容动态更新时,不需要重新绑定事件,新元素自动继承事件处理。这种设计简化了代码逻辑,也避免了内存泄漏问题。
五、数据持久化与状态管理
5.1 localStorage存储机制
应用使用localStorage实现数据持久化。localStorage是浏览器提供的本地存储API,可以存储键值对数据,数据在浏览器关闭后仍然保留。
function saveState() {
localStorage.setItem('todoState', JSON.stringify({
tasks: appState.tasks,
selectedTaskId: appState.selectedTaskId
}));
}
function loadState() {
const saved = localStorage.getItem('todoState');
if (saved) {
try {
const parsed = JSON.parse(saved);
appState = { ...appState, ...parsed };
} catch (e) {
console.error('加载状态失败:', e);
}
}
}
saveState函数将状态数据序列化为JSON字符串并存储。函数保存tasks和selectedTaskId两个属性,不保存currentFilter状态。currentFilter是UI状态,不需要持久化。
loadState函数在应用启动时读取存储数据并恢复状态。函数首先调用localStorage.getItem读取数据,然后使用JSON.parse解析字符串,最后使用对象展开运算符合并到状态对象中。try-catch块捕获可能的解析错误,防止数据格式异常导致应用崩溃。
localStorage的使用有几个注意事项。首先是数据格式,localStorage只能存储字符串,对象需要序列化。其次是数据大小,localStorage有容量限制,通常几MB,不适合存储大量数据。再次是数据安全,localStorage数据可以被用户清除,不适合存储关键数据。最后是数据同步,localStorage读写是同步操作,可能阻塞主线程。
5.2 状态对象设计
状态对象是应用的核心数据容器,所有业务数据都存储在这个对象中。状态对象采用集中管理模式,数据修改通过状态对象进行,界面渲染从状态对象读取数据。
let appState = {
tasks: [],
selectedTaskId: null,
currentFilter: 'all',
selectedCategory: null
};
状态对象包含四个主要属性。tasks存储任务数组,每个任务包含完整的属性信息。selectedTaskId存储当前选中的任务ID,用于详情显示。currentFilter存储当前的筛选条件。selectedCategory存储当前选择的分类。
这种集中式状态管理的好处是数据一致性。所有组件从同一个状态对象读取数据,避免了数据不一致的问题。数据修改也通过状态对象进行,修改后触发统一的保存和渲染流程。
状态对象的另一个好处是调试方便。开发者可以通过控制台查看状态对象,了解当前的数据状态。状态变化也可以通过日志记录,方便追踪问题。
5.3 初始数据加载
应用启动时,如果localStorage中没有数据,会自动添加示例任务,帮助用户快速体验应用功能。
if (appState.tasks.length === 0) {
addSampleTasks();
}
function addSampleTasks() {
const sampleTasks = [
{
id: Date.now() - 3,
title: '完成项目文档',
description: '撰写详细的技术实现文档,包括架构设计、核心功能说明和使用指南',
priority: 'high',
category: 'work',
dueDate: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
completed: false,
createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: Date.now() - 2,
title: '购买生活用品',
description: '',
priority: 'medium',
category: 'shopping',
dueDate: '',
completed: true,
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: Date.now() - 1,
title: '学习新技术',
description: '了解最新的前端框架和工具,探索如何提升开发效率',
priority: 'low',
category: 'study',
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
completed: false,
createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString()
}
];
appState.tasks = sampleTasks;
saveState();
renderTaskList();
updateTaskStats();
showToast('已添加示例任务!');
}
这段代码展示了示例任务的添加逻辑。函数创建三个示例任务,分别代表高优先级带描述的任务、可完成的购物任务、低优先级学习任务。这些任务覆盖了不同的优先级、分类、日期等场景,帮助用户快速了解应用的各项功能。
六、快捷操作功能实现
6.1 全选和取消全选
全选功能允许用户快速选择一个任务或取消选择。
function selectAllTasks() {
if (appState.selectedTaskId) {
appState.selectedTaskId = null;
} else if (appState.tasks.length > 0) {
let filteredTasks = appState.tasks;
if (appState.currentFilter === 'active') {
filteredTasks = appState.tasks.filter(task => !task.completed);
} else if (appState.currentFilter === 'completed') {
filteredTasks = appState.tasks.filter(task => task.completed);
}
if (filteredTasks.length > 0) {
appState.selectedTaskId = filteredTasks[0].id;
}
}
renderTaskList();
renderTaskDetail();
saveState();
showToast(appState.selectedTaskId ? '已选择任务' : '已取消选择');
}
这个函数实现了循环切换的选择逻辑。如果当前有选中项,则取消选择;如果当前没有选中项,则根据筛选条件选择第一个任务。这种设计让一个按钮实现了两个功能,简化了界面。
6.2 批量切换完成状态
批量切换功能允许用户一次切换多个任务的完成状态。
function toggleAllTasks() {
if (appState.tasks.length === 0) {
showToast('没有任务可操作');
return;
}
let filteredTasks = appState.tasks;
if (appState.currentFilter === 'active') {
filteredTasks = appState.tasks.filter(task => !task.completed);
} else if (appState.currentFilter === 'completed') {
filteredTasks = appState.tasks.filter(task => task.completed);
}
if (filteredTasks.length === 0) {
showToast('当前筛选下没有任务');
return;
}
const allCompleted = filteredTasks.every(t => t.completed);
filteredTasks.forEach(t => t.completed = !allCompleted);
saveState();
renderTaskList();
if (appState.selectedTaskId) {
renderTaskDetail();
}
updateTaskStats();
updateContextInfo();
showToast(allCompleted ? '全部标记为未完成' : '全部标记为完成');
}
这个函数实现了批量切换逻辑。首先根据筛选条件获取当前显示的任务数组。然后检查是否所有任务都已完成,如果都已完成则全部标记为未完成,否则全部标记为已完成。every方法用于检查是否所有任务都满足条件。
6.3 排序功能
排序功能允许用户按日期或优先级对任务进行排序。
function sortTasksByDate() {
appState.tasks.sort((a, b) => {
if (!a.dueDate) return 1;
if (!b.dueDate) return -1;
return new Date(a.dueDate) - new Date(b.dueDate);
});
saveState();
renderTaskList();
showToast('已按截止日期排序');
}
function sortTasksByPriority() {
const priorityOrder = { high: 0, medium: 1, low: 2 };
appState.tasks.sort((a, b) => {
return priorityOrder[a.priority] - priorityOrder[b.priority];
});
saveState();
renderTaskList();
showToast('已按优先级排序');
}
按日期排序时,没有截止日期的任务排在后面。sort方法接收一个比较函数,a.dueDate为空返回1表示a排在后面,b.dueDate为空返回-1表示b排在后面。
按优先级排序时,使用priorityOrder对象定义优先级顺序。high优先级值为0,medium优先级值为1,low优先级值为2。比较时用a的优先级值减去b的优先级值,实现从高优先级到低优先级的排序。
6.4 数据导出功能
数据导出功能允许用户将任务导出为文本文件。
function exportTasks() {
if (appState.tasks.length === 0) {
showToast('没有任务可导出');
return;
}
let text = 'TodoList 任务导出\n';
text += '='.repeat(50) + '\n\n';
const activeTasks = appState.tasks.filter(t => !t.completed);
const completedTasks = appState.tasks.filter(t => t.completed);
text += `【待办任务 (${activeTasks.length}项)】\n`;
activeTasks.forEach((task, index) => {
text += `${index + 1}. ${task.title}\n`;
if (task.description) {
text += ` ${task.description}\n`;
}
text += ` 分类: ${getCategoryLabel(task.category)} | 优先级: ${getPriorityLabel(task.priority)}`;
if (task.dueDate) {
text += ` | 截止: ${task.dueDate}`;
}
text += '\n\n';
});
text += `\n【已完成任务 (${completedTasks.length}项)】\n`;
completedTasks.forEach((task, index) => {
text += `${index + 1}. ✓ ${task.title}\n`;
});
text += '\n' + '='.repeat(50) + '\n';
text += '导出时间: ' + new Date().toLocaleString('zh-CN');
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const aElement = document.createElement('a');
aElement.href = url;
aElement.download = `todolist_${new Date().toISOString().split('T')[0]}.txt`;
aElement.click();
URL.revokeObjectURL(url);
showToast('任务已导出!');
}
导出功能使用Blob API生成文本文件。文件内容分为待办任务和已完成任务两部分,每部分列出任务编号、标题、描述、分类、优先级和截止日期。文件使用UTF-8编码,确保中文内容正确显示。
导出流程:创建Blob对象 → 生成临时URL → 创建下载链接 → 触发点击 → 释放URL资源。文件名包含当前日期,方便用户识别。
七、实时时钟功能
7.1 时钟启动
应用启动时,启动实时时钟,每秒更新一次上下文信息。
function startClock() {
setInterval(updateContextInfo, 1000);
}
startClock函数使用setInterval启动定时器,每隔1000毫秒(1秒)调用一次updateContextInfo函数。这种设计确保上下文信息始终显示最新时间,用户可以直观地看到当前时间。
7.2 时间格式化
时间格式化使用JavaScript的LocaleString方法,支持中文显示。
const timeStr = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
const dateStr = now.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' });
LocaleString方法接受两个参数:语言标签和格式化选项。'zh-CN’指定中文语言环境,格式化选项指定要显示的时间部分。时间显示小时和分钟,日期显示年、月、日和星期。
这种格式化方式的优势是自动处理本地化,不需要手动计算月份名称和星期名称。JavaScript引擎会根据系统语言环境选择合适的格式。
八、字符计数功能
8.1 输入框字符计数
字符计数功能实时显示输入框中的字符数量。
function updateCharCount(inputId, countId, maxLength) {
const input = document.getElementById(inputId);
const count = document.getElementById(countId);
count.textContent = `${input.value.length}/${maxLength}`;
if (input.value.length > maxLength) {
count.style.color = 'var(--danger)';
} else {
count.style.color = 'var(--text-muted)';
}
}
updateCharCount函数读取输入框的当前长度,显示"当前长度/最大长度"的格式。如果长度超过最大限制,颜色变为警告色,提醒用户超出限制。
8.2 事件绑定
字符计数通过input事件实时更新。
document.getElementById('taskTitle').addEventListener('input', () => {
updateCharCount('taskTitle', 'titleCharCount', 100);
});
input事件在用户输入时触发,比change事件更及时,能够实时反映输入内容的变化。这种设计让用户随时知道输入的长度,避免超过限制。
九、Electron与OpenHarmony融合框架
9.1 web_engine模块架构
web_engine模块是Electron与OpenHarmony融合的核心组件。这个模块实现了Web应用与鸿蒙原生环境的桥接,让HTML5应用能够运行在鸿蒙桌面环境中。
web_engine模块的架构分为三层。顶层是JavaScript绑定层,提供Web API接口。中层是适配器层,实现鸿蒙原生功能调用。底层是原生库层,提供底层能力支持。
JavaScript绑定层定义了Web应用可用的API接口。这些接口包括窗口管理、文件操作、网络请求、设备访问等。Web应用通过这些接口调用鸿蒙原生功能,实现跨平台能力。
适配器层实现了JavaScript接口与鸿蒙原生API的映射。每个适配器对应一个功能领域,如AppWindowAdapter处理窗口操作,FileManagerAdapter处理文件操作。适配器将JavaScript调用转换为鸿蒙API调用,处理参数转换和返回值封装。
原生库层提供了底层能力支持。libadapter.so是核心原生库,实现了Electron运行时的底层功能。这个库处理进程通信、窗口渲染、事件传递等核心操作。
9.2 应用启动流程
应用的启动流程涉及多个模块的协作。从用户点击应用图标到界面显示,经历了一系列初始化步骤。
首先是Ability启动。鸿蒙系统创建EntryAbility实例,调用onCreate方法初始化能力。EntryAbility继承自WebAbility,WebAbility继承自WebBaseAbility,形成能力继承链。
其次是窗口创建。WebBaseAbility的onWindowStageCreate方法创建应用窗口。窗口创建后,加载Index页面,Index页面包含WebWindow组件。
然后是WebWindow初始化。WebWindow组件创建WindowNodeController,WindowNodeController创建XComponent节点。XComponent是渲染载体,承载Web内容的显示。
最后是Web内容加载。XComponent的onLoad回调触发,调用nativeContext.runBrowser方法启动Electron运行时。Electron运行时加载main.js,main.js创建BrowserWindow并加载HTML应用。
这个启动流程的优势是分层清晰、职责明确。每个模块负责特定的功能,模块之间通过接口协作。这种设计让代码结构清晰,便于维护和扩展。
十、性能优化与最佳实践
10.1 渲染性能优化
界面渲染是应用性能的关键因素。应用通过几个策略优化渲染性能。
首先是批量更新。当状态数据变化时,不是立即更新DOM,而是调用对应的渲染函数。渲染函数使用innerHTML一次性更新整个列表,而不是逐个添加元素。
其次是条件渲染。任务列表为空时显示空状态,而不是显示空白的列表。这种设计避免了不必要的DOM节点创建。
再次是模板复用。任务卡片的HTML结构使用模板字符串定义,在map方法中复用。这种设计减少了代码重复,提高了可维护性。
最后是事件委托。列表项的事件监听器绑定在父容器上,而不是每个子元素上。这种设计减少了事件监听器的数量,提高了事件处理效率。
10.2 内存管理策略
内存管理是长期运行应用的重要考虑。应用通过几个策略管理内存。
首先是状态对象管理。状态对象只保存必要的数据,不保存大型数据结构。任务列表数组是必需的数据,其他状态如currentFilter是轻量级值。
其次是DOM引用缓存。频繁访问的DOM元素没有缓存,每次访问都重新查询。这种设计简化了代码,对于少量元素影响不大。
再次是定时器清理。虽然应用没有显式清理定时器,但页面关闭时定时器会自动停止。setInterval在页面卸载时会自动清理。
最后是Blob资源释放。文件导出后,调用revokeObjectURL释放Blob URL资源。如果不释放,URL资源会继续占用内存。
10.3 代码组织最佳实践
代码组织影响代码的可读性和可维护性。应用采用几个最佳实践组织代码。
首先是模块化设计。每个功能模块独立实现,模块之间通过函数调用协作。比如任务管理模块、上下文感知模块、数据持久化模块各自独立。
其次是函数单一职责。每个函数只做一件事,职责清晰。比如renderTaskList只负责渲染列表,handleAddTask只负责添加任务。
再次是命名规范。变量和函数使用语义化命名。比如updateCharCount表示更新字符计数,getDueDateInfo表示获取截止日期信息。
最后是注释适度。关键逻辑添加注释说明,简单代码不加注释。这种平衡让代码既清晰又不冗余。
十一、用户体验设计思考
11.1 信息架构设计
信息架构决定了用户如何浏览和理解应用内容。应用的信息架构基于任务管理流程设计,分为任务创建、任务查看、任务操作三个阶段。
任务创建阶段包含标题输入、描述输入、优先级选择、截止日期选择、分类选择五个步骤。这种分步设计让创建过程清晰,不会让用户感到信息过载。
任务查看阶段包含任务列表查看、任务详情查看、上下文信息查看三个视角。用户可以快速浏览任务列表,点击查看详情,同时参考上下文信息做出决策。
任务操作阶段包含完成切换、删除、编辑三个操作。操作按钮设计在任务卡片和详情面板中,方便用户随时执行。
这种信息架构的好处是流程清晰、步骤明确。用户能够按照自然流程使用应用,从创建任务到查看任务再到操作任务,形成完整的管理闭环。
11.2 视觉层次设计
视觉层次设计帮助用户快速理解页面结构,找到所需的功能。
页面分为左右两栏,左栏侧重任务输入和列表,右栏侧重任务详情和上下文。这种对称布局符合用户的阅读习惯,大脑可以自然地将页面分为操作区和信息区。
任务卡片采用卡片式设计,每个任务独立一块。卡片内部有清晰的层次结构,标题突出显示,元信息次要显示,操作按钮再次显示。这种层次设计让用户能够快速扫描定位。
优先级使用颜色徽章标识,高优先级红色、中优先级黄色、低优先级绿色。这种颜色编码符合直觉,用户一眼就能识别优先级高低。
11.3 错误处理策略
错误处理影响应用的稳定性。应用采用几个策略处理可能出现的错误。
首先是输入验证。任务标题必填,如果为空则显示提示并阻止添加。输入验证在客户端执行,即时反馈,用户体验好。
其次是确认机制。删除任务时弹出确认对话框,防止误删。confirm是浏览器原生的确认对话框,不需要额外实现。
再次是异常捕获。localStorage操作使用try-catch捕获异常,防止数据格式异常导致崩溃。异常发生时只是加载默认状态,不会导致应用崩溃。
最后是边界检查。数组操作前检查长度,日期计算前检查有效性。这些边界检查避免了运行时错误。
11.4 反馈机制设计
反馈机制让用户感知操作的响应。应用在多个交互点添加反馈效果。
首先是Toast提示。添加任务成功、删除任务、导出任务等操作完成后显示Toast提示。Toast在屏幕底部居中显示,几秒后自动消失,不干扰用户操作。
其次是视觉反馈。按钮悬停时变色和浮起,任务卡片悬停时高亮,选中任务时边框突出。这些视觉变化让用户知道操作已被识别。
再次是状态反馈。任务完成时文字加删除线,紧急任务截止日期高亮显示。这种状态反馈让用户快速了解任务情况。
最后是进度反馈。上下文信息实时更新,用户可以看到当前时间、完成进度、紧急任务数量。这种持续反馈帮助用户做出决策。
十二、未来扩展方向
12.1 功能扩展计划
应用有多个功能扩展方向,可以进一步提升实用性。
首先是任务编辑功能。当前版本只支持添加和删除,不支持编辑。添加编辑功能允许用户修改已创建的任务,完善任务管理能力。
其次是任务分组功能。当前版本只有分类标签,没有分组功能。添加分组功能允许用户将任务组织到不同的组中,比如按项目分组、按时间分组。
再次是提醒功能。当前版本只有截止日期显示,没有提醒功能。添加提醒功能允许用户设置提醒时间,系统在指定时间通知用户。
最后是数据同步功能。当前版本只有本地存储,没有云同步。添加数据同步功能允许用户在多个设备间同步任务数据。
12.2 技术升级方向
技术层面也有多个升级方向,可以提升应用性能和体验。
首先是框架升级。当前应用使用原生JavaScript,可以升级到Vue或React框架。框架提供了组件化开发、状态管理、路由控制等能力,能够简化开发流程。
其次是存储升级。当前使用localStorage,可以升级到IndexedDB。IndexedDB支持更大存储容量和更复杂查询,适合存储大量任务数据。
再次是样式升级。当前使用手写CSS,可以升级到Tailwind CSS或Styled Components。CSS框架提供了丰富的样式类和主题系统,能够加速UI开发。
最后是测试升级。当前版本没有自动化测试,可以添加单元测试和集成测试。测试覆盖关键函数,确保代码质量。
十三、总结与展望
TodoList 任务详情子系统是Electron与OpenHarmony融合框架的一次成功实践。应用通过数字化手段解决了任务管理中的实际问题,提供了可选描述字段、上下文感知渲染、分类管理、优先级设置等实用功能。技术实现采用Web技术栈,代码结构清晰,维护方便。
应用的核心价值在于实用性和智能化。实用性体现在功能设计围绕任务管理流程,每个功能都解决实际问题。智能化体现在上下文感知渲染,根据当前状态智能显示相关信息,帮助用户更高效地管理任务。
技术层面,应用展示了Web技术在桌面应用中的应用潜力。通过web_engine模块,Web应用能够运行在鸿蒙桌面环境中,享受原生平台的性能和功能。这种融合框架降低了跨平台开发的门槛,让更多开发者能够参与鸿蒙生态建设。
数据层面,应用展示了localStorage在本地存储中的应用。通过状态对象集中管理数据,实现了数据的持久化存储。这种存储方式简单可靠,适合小型应用。
交互层面,应用展示了现代Web应用的交互设计。通过Toast提示、视觉反馈、状态反馈等机制,提升了用户体验。这种交互设计让应用更加友好,用户愿意长期使用。
展望未来,应用有广阔的扩展空间。功能层面可以添加任务编辑、任务分组、提醒功能、数据同步等。技术层面可以升级框架、存储、样式、测试等。这些扩展将进一步提升应用的价值,服务更多用户。
最后,感谢开源鸿蒙PC社区的支持。社区提供了技术交流平台,让开发者能够分享经验、解决问题。希望这个应用能够为社区贡献一份力量,推动鸿蒙生态的发展。
技术要点总结:
| 技术点 | 实现方式 | 关键代码 |
|---|---|---|
| 任务数据结构 | 对象数组 | appState.tasks,包含id、title、description等属性 |
| 上下文感知 | 时间计算 | updateContextInfo函数,计算实时信息和紧急任务 |
| 截止日期判断 | 日期差计算 | getDueDateInfo函数,判断今天/明天/已过期等 |
| 任务筛选 | 数组过滤 | filter方法,根据completed状态筛选 |
| 数据持久化 | localStorage | saveState和loadState函数 |
| 字符计数 | input事件 | updateCharCount函数,实时显示输入长度 |
| 排序功能 | sort方法 | sortTasksByDate和sortTasksByPriority函数 |
| 数据导出 | Blob API | exportTasks函数,生成文本文件 |
| 实时时钟 | setInterval | startClock函数,每秒更新 |
| 事件委托 | 父容器监听 | click事件,检测目标元素 |
核心代码文件:
- [todolist_detail.html](file:///d:/save/systemIso/electron-openharmony-vue3/ohos_hap/web_engine/src/main/resources/resfile/resources/app/todolist_detail.html) - 页面结构和组件定义
- [todolist_detail_style.css](file:///d:/save/systemIso/electron-openharmony-vue3/ohos_hap/web_engine/src/main/resources/resfile/resources/app/todolist_detail_style.css) - 样式系统和动画效果
- [todolist_detail_app.js](file:///d:/save/systemIso/electron-openharmony-vue3/ohos_hap/web_engine/src/main/resources/resfile/resources/app/js/todolist_detail_app.js) - 核心逻辑和状态管理
- [main.js](file:///d:/save/systemIso/electron-openharmony-vue3/ohos_hap/web_engine/src/main/resources/resfile/resources/app/main.js) - Electron入口和窗口配置
参考资料:
更多推荐


所有评论(0)