欢迎加入开源鸿蒙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 项目成果

儿童故事应用成功实现了以下功能:

  1. 故事库系统:8个经典故事,四大分类筛选
  2. 分页阅读器:清晰的段落分隔,便捷的翻页导航
  3. 收藏系统:Set存储,一键收藏/取消
  4. 夜间模式:CSS变量切换,保护眼睛
  5. 音频控制界面:模拟播放进度展示
  6. 数据持久化: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+

参考资料

  1. MDN Web Docs - CSS动画
  2. MDN Web Docs - localStorage
  3. MDN Web Docs - Set数据结构
  4. Electron官方文档
  5. HarmonyOS开发者文档
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐