鸿蒙PC Electron框架实战:儿童故事技术实现详解
开源鸿蒙PC儿童故事应用简介 本项目是一款专为3-8岁儿童设计的睡前故事应用,基于开源鸿蒙PC平台开发。应用提供8个精选儿童故事,涵盖动物、童话、寓言和科普四大类别,采用彩虹色系设计和分段阅读模式,确保儿童友好体验。 技术架构: 前端:HTML/CSS/JavaScript构建响应式界面 架构层:Electron+Preload实现IPC通信 原生层:HarmonyOS适配器支持 核心功能: 分类
欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/
atomgit仓库地址:https://atomgit.com/Math_teacher_fan/ertonggushi





一、项目概述
1.1 项目背景
儿童故事是一款专为3-8岁儿童设计的睡前故事应用。在数字化时代,越来越多的孩子习惯使用电子设备,但市面上很多应用内容良莠不齐,设计也过于成人化。本应用旨在为孩子们提供一个纯净、温馨、有教育意义的故事阅读平台。
应用收录了8个经典儿童故事,涵盖动物故事、童话故事、寓言故事和科普故事四大类别。每个故事都经过精心编排,分段清晰,适合儿童阅读或家长讲述。应用采用梦幻的彩虹色系设计,配合流畅的动画效果,让孩子们在温馨的氛围中进入甜美的梦乡。
1.2 设计理念
| 理念 | 说明 |
|---|---|
| 温馨安全 | 纯净内容,无广告干扰 |
| 简单易用 | 界面简洁,适合儿童操作 |
| 分段阅读 | 每页一段,避免视觉疲劳 |
| 教育意义 | 故事蕴含道理,寓教于乐 |
1.3 功能模块
| 模块 | 功能 |
|---|---|
| 故事库 | 浏览、筛选、收藏所有故事 |
| 阅读器 | 分页阅读、翻页导航 |
| 我的收藏 | 快速访问收藏故事 |
| 夜间模式 | 保护眼睛的暗色主题 |
| 音频控制 | 模拟播放控制 |
二、技术架构设计
2.1 整体架构
┌─────────────────────────────────────────────────────────────┐
│ 前端应用层 │
│ (HTML/CSS/JavaScript + 故事阅读系统) │
├─────────────────────────────────────────────────────────────┤
│ Electron + Preload 层 │
│ (IPC 通信、API 暴露) │
├─────────────────────────────────────────────────────────────┤
│ HarmonyOS 原生层 │
│ (libadapter.so + ETS Adapters) │
└─────────────────────────────────────────────────────────────┘
2.2 模块划分
| 模块 | 职责 | 文件 |
|---|---|---|
| 视图层 | HTML结构、页面组件 | index.html |
| 样式层 | 彩虹色系、CSS动画 | style.css |
| 业务层 | 故事逻辑、状态管理 | js/app.js |
| 数据层 | 故事内容定义 | js/app.js (内置) |
2.3 数据结构设计
故事数据结构:
const story = {
id: 1,
title: '小兔子学游泳',
emoji: '🐰',
category: 'animal',
categoryName: '🐾 动物故事',
age: '3-6岁',
desc: '小兔子米米害怕水,但在朋友的鼓励下学会了游泳',
pages: [
'森林里住着一只小兔子...',
'可是米米有一个大问题...',
// 更多段落...
]
};
应用状态管理:
let appState = {
currentView: 'library', // 当前视图
currentCategory: 'all', // 当前分类
currentStory: null, // 当前阅读的故事
currentPage: 0, // 当前页码
favorites: new Set(), // 收藏的故事ID集合
isPlaying: false, // 是否正在播放
nightMode: false // 夜间模式
};
三、核心功能实现详解
3.1 应用初始化流程
应用启动时执行完整的初始化操作:
function initApp() {
// 1. 从本地存储加载数据
loadData();
// 2. 渲染故事网格
renderStoryGrid();
// 3. 渲染收藏列表
renderFavorites();
// 4. 更新收藏数量
updateFavCount();
// 5. 设置事件监听
setupEventListeners();
}
数据加载函数:
function loadData() {
// 加载收藏列表
const savedFavs = localStorage.getItem('storyFavorites');
if (savedFavs) {
appState.favorites = new Set(JSON.parse(savedFavs));
}
// 加载夜间模式设置
const savedNightMode = localStorage.getItem('nightMode');
if (savedNightMode === 'true') {
appState.nightMode = true;
document.body.classList.add('night-mode');
}
}
3.2 故事数据管理
应用内置8个经典儿童故事:
const stories = [
{
id: 1,
title: '小兔子学游泳',
emoji: '🐰',
category: 'animal',
categoryName: '🐾 动物故事',
age: '3-6岁',
desc: '小兔子米米害怕水,但在朋友的鼓励下学会了游泳',
pages: [
'森林里住着一只小兔子,名叫米米...',
'可是米米有一个大问题——她特别害怕水...',
// 每段一段,分隔清晰
]
},
// 更多故事...
];
3.3 故事网格渲染
故事库页面的核心渲染函数:
function renderStoryGrid() {
const grid = document.getElementById('storyGrid');
let filteredStories = stories;
// 应用分类筛选
if (appState.currentCategory !== 'all') {
filteredStories = stories.filter(s => s.category === appState.currentCategory);
}
// 渲染故事卡片
grid.innerHTML = filteredStories.map(story => `
<div class="story-card" data-id="${story.id}">
<div class="story-card-cover">
<span class="cover-emoji">${story.emoji}</span>
<button class="story-card-fav ${appState.favorites.has(story.id) ? 'active' : ''}" data-id="${story.id}">
${appState.favorites.has(story.id) ? '❤️' : '🤍'}
</button>
</div>
<div class="story-card-content">
<div class="story-card-title">${story.title}</div>
<div class="story-card-desc">${story.desc}</div>
<div class="story-card-meta">
<span class="story-card-tag">${story.categoryName}</span>
<span class="story-card-tag">${story.age}</span>
</div>
</div>
</div>
`).join('');
// 绑定点击事件
bindCardEvents();
}
事件绑定函数:
function bindCardEvents() {
const grid = document.getElementById('storyGrid');
// 故事卡片点击 - 打开阅读器
grid.querySelectorAll('.story-card').forEach(card => {
card.addEventListener('click', (e) => {
if (!e.target.classList.contains('story-card-fav')) {
openStory(parseInt(card.dataset.id));
}
});
});
// 收藏按钮点击
grid.querySelectorAll('.story-card-fav').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
toggleFavorite(parseInt(btn.dataset.id));
});
});
}
3.4 分类筛选系统
分类切换是儿童类应用的重要功能:
function switchCategory(category) {
appState.currentCategory = category;
// 更新标签按钮状态
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.category === category);
});
// 重新渲染网格
renderStoryGrid();
}
HTML中的分类标签:
<div class="category-tabs" id="categoryTabs">
<button class="tab-btn active" data-category="all">全部</button>
<button class="tab-btn" data-category="animal">🐾 动物故事</button>
<button class="tab-btn" data-category="fairy">🧚 童话故事</button>
<button class="tab-btn" data-category="fable">📖 寓言故事</button>
<button class="tab-btn" data-category="science">🔬 科普故事</button>
</div>
3.5 阅读器核心功能
打开故事函数:
function openStory(storyId) {
const story = stories.find(s => s.id === storyId);
if (!story) return;
// 保存当前状态
appState.currentStory = story;
appState.currentPage = 0;
// 更新阅读器UI
document.getElementById('storyCover').innerHTML = `<span class="cover-emoji">${story.emoji}</span>`;
document.getElementById('storyTitle').textContent = story.title;
document.getElementById('storyCategory').textContent = story.categoryName;
document.getElementById('storyAge').textContent = story.age;
// 更新收藏按钮状态
const collectBtn = document.getElementById('collectBtn');
collectBtn.innerHTML = `<span>${appState.favorites.has(story.id) ? '❤️' : '🤍'}</span>`;
// 显示第一页
showPage(0);
// 切换到阅读视图
switchView('reader');
// 显示音频控制条
document.getElementById('audioControls').classList.add('active');
document.getElementById('audioTitle').textContent = story.title;
}
分页显示函数:
function showPage(pageIndex) {
if (!appState.currentStory) return;
const pages = appState.currentStory.pages;
if (pageIndex < 0 || pageIndex >= pages.length) return;
// 更新当前页码
appState.currentPage = pageIndex;
// 渲染故事内容
const storyBody = document.getElementById('storyBody');
storyBody.innerHTML = `<p>${pages[pageIndex]}</p>`;
// 更新页码显示
document.getElementById('currentPage').textContent = pageIndex + 1;
document.getElementById('totalPages').textContent = pages.length;
// 更新翻页按钮状态
document.getElementById('prevPage').disabled = pageIndex === 0;
document.getElementById('nextPage').disabled = pageIndex === pages.length - 1;
}
翻页控制:
function prevPage() {
showPage(appState.currentPage - 1);
}
function nextPage() {
showPage(appState.currentPage + 1);
}
3.6 收藏系统实现
收藏功能使用Set数据结构存储,Set的特性保证了收藏ID的唯一性:
function toggleFavorite(storyId) {
if (appState.favorites.has(storyId)) {
// 取消收藏
appState.favorites.delete(storyId);
showToast('已取消收藏');
} else {
// 添加收藏
appState.favorites.add(storyId);
showToast('已添加到收藏');
}
// 保存到本地
saveData();
// 更新UI
updateFavCount();
renderStoryGrid();
renderFavorites();
// 如果正在阅读当前故事,更新收藏按钮
if (appState.currentStory && appState.currentStory.id === storyId) {
const collectBtn = document.getElementById('collectBtn');
collectBtn.innerHTML = `<span>${appState.favorites.has(storyId) ? '❤️' : '🤍'}</span>`;
}
}
收藏列表渲染:
function renderFavorites() {
const list = document.getElementById('favoritesList');
// 空状态处理
if (appState.favorites.size === 0) {
list.innerHTML = `
<div class="empty-favorites">
<div class="empty-favorites-icon">📚</div>
<p>还没有收藏的故事哦</p>
<p>快去故事库看看吧!</p>
</div>
`;
return;
}
// 渲染收藏列表
list.innerHTML = [...appState.favorites].map(favId => {
const story = stories.find(s => s.id === favId);
if (!story) return '';
return `
<div class="favorite-item" data-id="${story.id}">
<div class="favorite-icon">${story.emoji}</div>
<div class="favorite-info">
<div class="favorite-title">${story.title}</div>
<div class="favorite-meta">${story.categoryName} · ${story.age}</div>
</div>
<button class="favorite-remove" data-id="${story.id}">移除</button>
</div>
`;
}).join('');
// 绑定事件
bindFavoriteEvents();
}
3.7 夜间模式切换
夜间模式通过CSS变量和body类名控制:
function toggleNightMode() {
appState.nightMode = !appState.nightMode;
document.body.classList.toggle('night-mode', appState.nightMode);
saveData();
showToast(appState.nightMode ? '已开启夜间模式' : '已关闭夜间模式');
}
CSS中的夜间模式样式:
body.night-mode {
--bg-primary: #1a1a2e;
--bg-secondary: #252542;
--bg-warm: #2d2d4a;
--text-primary: #e0e0f0;
--text-secondary: #a0a0c0;
--border: #3a3a5a;
}
body.night-mode .header,
body.night-mode .story-card,
body.night-mode .reader-content,
body.night-mode .favorite-item {
background: var(--bg-secondary);
}
3.8 音频播放模拟
虽然应用没有真正的音频文件,但实现了完整的播放控制界面:
function togglePlayPause() {
appState.isPlaying = !appState.isPlaying;
document.getElementById('playIcon').textContent = appState.isPlaying ? '⏸️' : '▶️';
// 模拟播放进度
if (appState.isPlaying) {
simulatePlayback();
}
}
let playbackInterval = null;
function simulatePlayback() {
if (playbackInterval) clearInterval(playbackInterval);
let progress = 0;
const duration = 60; // 模拟60秒
playbackInterval = setInterval(() => {
if (!appState.isPlaying) {
clearInterval(playbackInterval);
return;
}
progress += 0.5;
const percent = (progress / duration) * 100;
document.getElementById('progressFill').style.width = `${percent}%`;
document.getElementById('currentTime').textContent = formatTime(Math.floor(progress));
if (progress >= duration) {
clearInterval(playbackInterval);
stopPlayback();
}
}, 100);
}
function stopPlayback() {
appState.isPlaying = false;
document.getElementById('playIcon').textContent = '▶️';
document.getElementById('progressFill').style.width = '0%';
document.getElementById('currentTime').textContent = '0:00';
if (playbackInterval) clearInterval(playbackInterval);
}
3.9 数据持久化
使用localStorage保存用户数据:
function saveData() {
localStorage.setItem('storyFavorites', JSON.stringify([...appState.favorites]));
localStorage.setItem('nightMode', appState.nightMode);
}
四、前端样式设计
4.1 彩虹色系主题
:root {
/* 主色调 - 温暖粉色 */
--primary: #ff6b9d;
--primary-light: #ff8fb3;
--primary-dark: #e55a8a;
/* 彩虹辅助色 */
--rainbow-1: #ff6b6b; /* 红 */
--rainbow-2: #ffa94d; /* 橙 */
--rainbow-3: #ffd43b; /* 黄 */
--rainbow-4: #69db7c; /* 绿 */
--rainbow-5: #74c0fc; /* 蓝 */
--rainbow-6: #b197fc; /* 紫 */
/* 背景色 */
--bg-primary: #fff5f7;
--bg-secondary: #ffffff;
--bg-warm: #fff9f0;
}
4.2 Logo彩虹渐变
.logo h1 {
font-size: 1.5rem;
font-weight: 700;
background: linear-gradient(90deg, var(--rainbow-1), var(--rainbow-2), var(--rainbow-3), var(--rainbow-4), var(--rainbow-5), var(--rainbow-6));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
4.3 Banner浮动云朵动画
.cloud {
position: absolute;
font-size: 3rem;
opacity: 0.3;
animation: floatCloud 6s ease-in-out infinite;
}
.cloud-1 { top: 20%; left: 10%; animation-delay: 0s; }
.cloud-2 { top: 40%; right: 15%; animation-delay: 2s; }
.cloud-3 { bottom: 20%; left: 30%; animation-delay: 4s; }
@keyframes floatCloud {
0%, 100% { transform: translateX(0) translateY(0); }
50% { transform: translateX(20px) translateY(-10px); }
}
4.4 故事卡片悬浮效果
.story-card {
background: var(--bg-secondary);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: all var(--transition-normal);
}
.story-card:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-lg);
}
4.5 浮动装饰动画
.floating-decorations {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
overflow: hidden;
}
.float-item {
position: absolute;
left: var(--x);
top: var(--y);
font-size: 1.5rem;
opacity: 0.4;
animation: floatUp 4s ease-in-out infinite;
animation-delay: var(--delay);
}
@keyframes floatUp {
0%, 100% {
transform: translateY(0) rotate(0deg);
opacity: 0.4;
}
50% {
transform: translateY(-30px) rotate(180deg);
opacity: 0.7;
}
}
4.6 响应式布局
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: var(--spacing-md);
}
.story-grid {
grid-template-columns: 1fr;
}
.category-tabs {
overflow-x: auto;
flex-wrap: nowrap;
}
}
@media (max-width: 480px) {
.logo h1 {
font-size: 1.2rem;
}
.reader-content {
padding: var(--spacing-md);
}
}
五、事件监听系统
5.1 完整事件绑定
function setupEventListeners() {
// 分类标签点击
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => switchCategory(btn.dataset.category));
});
// 返回按钮
document.getElementById('backBtn').addEventListener('click', goBack);
// 收藏按钮
document.getElementById('collectBtn').addEventListener('click', () => {
if (appState.currentStory) {
toggleFavorite(appState.currentStory.id);
}
});
// 分页按钮
document.getElementById('prevPage').addEventListener('click', prevPage);
document.getElementById('nextPage').addEventListener('click', nextPage);
// 收藏页按钮
document.getElementById('favoritesBtn').addEventListener('click', () => {
switchView('favorites');
renderFavorites();
});
// 夜间模式按钮
document.getElementById('nightModeBtn').addEventListener('click', toggleNightMode);
// 音频控制
document.getElementById('playPauseBtn').addEventListener('click', togglePlayPause);
document.getElementById('stopBtn').addEventListener('click', stopPlayback);
}
六、数据管理
6.1 LocalStorage数据结构
| Key | 数据类型 | 说明 |
|---|---|---|
| storyFavorites | Array | 收藏的故事ID列表 |
| nightMode | String | 夜间模式开关 |
6.2 数据流转
用户点击收藏
↓
Set添加/删除ID
↓
保存到localStorage
↓
更新收藏数量
↓
重新渲染UI
七、代码优化建议
7.1 性能优化
使用DocumentFragment减少DOM操作:
function renderStoryGrid() {
const grid = document.getElementById('storyGrid');
const fragment = document.createDocumentFragment();
// 在片段中构建DOM
filteredStories.forEach(story => {
const card = createStoryCard(story);
fragment.appendChild(card);
});
// 一次性添加到DOM
grid.innerHTML = '';
grid.appendChild(fragment);
}
7.2 错误处理
function openStory(storyId) {
try {
const story = stories.find(s => s.id === storyId);
if (!story) {
console.error('故事不存在:', storyId);
return;
}
// 正常逻辑...
} catch (error) {
console.error('打开故事失败:', error);
showToast('加载失败,请重试');
}
}
7.3 扩展性设计
添加新故事只需在stories数组中添加对象:
const stories = [
// 现有故事...
{
id: 9,
title: '新故事名称',
emoji: '🆕',
category: 'animal',
categoryName: '🐾 动物故事',
age: '3-6岁',
desc: '故事简介',
pages: ['第1段内容', '第2段内容', '...']
}
];
八、总结与展望
8.1 项目成果
儿童故事应用成功实现了以下功能:
- 故事库系统:8个经典故事,四大分类筛选
- 分页阅读器:清晰的段落分隔,便捷的翻页导航
- 收藏系统:Set存储,一键收藏/取消
- 夜间模式:CSS变量切换,保护眼睛
- 音频控制界面:模拟播放进度展示
- 数据持久化:localStorage保存用户数据
8.2 技术亮点
- Set数据结构:收藏ID去重,操作高效
- CSS变量主题:夜间模式切换优雅
- 事件委托:高效的卡片点击处理
- 分段阅读:适合儿童注意力特点
- 动画效果:浮动装饰,云朵飘动
8.3 未来规划
| 优先级 | 功能 | 说明 |
|---|---|---|
| 高 | 语音朗读 | 接入TTS实现自动朗读 |
| 高 | 更多故事 | 扩充故事库内容 |
| 中 | 书签功能 | 记忆阅读进度 |
| 中 | 家长控制 | 设置使用时间 |
| 低 | 互动环节 | 故事问答游戏 |
| 低 | 音效系统 | 背景音乐和音效 |
8.4 技术拓展
未来可以考虑引入以下技术:
- Web Speech API:实现真正的语音朗读
- IndexedDB:存储更多故事数据
- Service Worker:实现离线阅读
- 动画库:如GSAP实现更复杂动画
- PWA:支持添加到桌面
附录
核心文件列表
| 文件 | 说明 |
|---|---|
| index.html | 页面结构,包含故事库、阅读器、收藏页 |
| style.css | 彩虹色系样式,包含所有动画 |
| js/app.js | 故事数据和应用逻辑 |
故事列表
| ID | 标题 | 分类 | 适龄 |
|---|---|---|---|
| 1 | 小兔子学游泳 | 动物故事 | 3-6岁 |
| 2 | 星星的愿望 | 童话故事 | 4-7岁 |
| 3 | 狼来了和牧童 | 寓言故事 | 5-8岁 |
| 4 | 月亮船旅行记 | 童话故事 | 3-6岁 |
| 5 | 小蜗牛的大房子 | 动物故事 | 4-7岁 |
| 6 | 种子的梦想 | 科普故事 | 5-8岁 |
| 7 | 三只小猪盖房子 | 寓言故事 | 4-7岁 |
| 8 | 小蝌蚪找妈妈 | 科普故事 | 3-6岁 |
浏览器兼容性
| 浏览器 | 最低版本 |
|---|---|
| Chrome | 80+ |
| Firefox | 75+ |
| Safari | 13+ |
| Edge | 80+ |
参考资料
- MDN Web Docs - CSS动画
- MDN Web Docs - localStorage
- MDN Web Docs - Set数据结构
- Electron官方文档
- HarmonyOS开发者文档
更多推荐


所有评论(0)