欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

在这里插入图片描述

📌 概述

最近旅行页面展示用户最近查看或编辑过的旅行记录。这个页面提供了快速访问常用旅行的便利,提升了用户体验。最近旅行功能需要记录用户的访问历史,并按照时间顺序排序。在 Cordova 与 OpenHarmony 的混合开发框架中,我们需要实现访问历史的记录、查询和展示功能。

🔗 完整流程

第一步:访问历史记录与时间戳管理

最近旅行功能需要记录用户每次访问旅行的时间。当用户查看或编辑旅行时,需要更新该旅行的最后访问时间。这个时间戳用于排序最近旅行列表,确保最近访问的旅行显示在最前面。

访问历史的记录可以在数据库中维护一个 lastViewedAt 字段,每次用户访问旅行时更新这个字段。

第二步:最近旅行列表的加载与排序

最近旅行页面需要从数据库中查询所有旅行,然后按照 lastViewedAt 字段进行降序排序,取前 N 条记录作为最近旅行列表。这个过程需要高效的数据库查询和排序算法。

最近旅行列表通常显示的数量有限(如前 20 条),这样可以减少 DOM 节点数量,提高渲染性能。

第三步:原生层访问历史优化与缓存

OpenHarmony 原生层可以实现访问历史的缓存和优化。原生层可以维护一个内存缓存,存储最近访问的旅行列表,避免频繁的数据库查询。原生层还可以实现访问历史的清理机制,定期清理过期的访问记录。

🔧 Web 代码实现

最近旅行页面 HTML 结构

<div id="recent-page" class="page">
    <div class="page-header">
        <h1>最近旅行</h1>
        <button class="btn-icon" onclick="clearRecentHistory()">🗑️</button>
    </div>
    
    <div class="recent-container">
        <div class="recent-list" id="recentList">
            <!-- 最近旅行列表动态加载 -->
        </div>
        <div class="empty-state" id="emptyState" style="display: none;">
            <div class="empty-icon">🕐</div>
            <p>还没有浏览过任何旅行</p>
        </div>
    </div>
</div>

HTML 结构包含最近旅行列表和清空历史按钮。用户可以一键清空所有访问历史。

记录访问时间函数

async function recordTripView(tripId) {
    try {
        // 获取旅行数据
        const trip = await db.getTrip(tripId);
        
        if (trip) {
            // 更新最后访问时间
            trip.lastViewedAt = new Date().toISOString();
            
            // 保存到数据库
            await db.updateTrip(trip);
            
            // 更新最近旅行列表
            loadRecentTrips();
            
            // 通知原生层
            if (window.cordova) {
                cordova.exec(
                    (result) => console.log('View recorded:', result),
                    (error) => console.error('Record error:', error),
                    'RecentPlugin',
                    'onTripViewed',
                    [{ tripId: tripId, timestamp: Date.now() }]
                );
            }
        }
    } catch (error) {
        console.error('Error recording trip view:', error);
    }
}

这个函数在用户访问旅行时被调用,更新旅行的最后访问时间。函数还通知原生层记录访问事件,用于原生层的缓存更新。

加载最近旅行列表函数

async function loadRecentTrips() {
    try {
        // 从数据库查询所有旅行
        const allTrips = await db.getAllTrips();
        
        // 按最后访问时间排序(降序)
        const recentTrips = allTrips
            .filter(trip => trip.lastViewedAt)
            .sort((a, b) => {
                const timeA = new Date(a.lastViewedAt).getTime();
                const timeB = new Date(b.lastViewedAt).getTime();
                return timeB - timeA;
            })
            .slice(0, 20); // 只取前20条
        
        // 处理空状态
        if (recentTrips.length === 0) {
            document.getElementById('emptyState').style.display = 'block';
            document.getElementById('recentList').innerHTML = '';
            return;
        }
        
        document.getElementById('emptyState').style.display = 'none';
        
        // 渲染最近旅行列表
        renderRecentList(recentTrips);
    } catch (error) {
        console.error('Error loading recent trips:', error);
        showToast('加载最近旅行失败');
    }
}

这个函数从数据库查询所有旅行,筛选出有访问时间的旅行,然后按访问时间降序排序。函数只取前 20 条记录,避免过多的 DOM 节点。

最近旅行列表渲染函数

function renderRecentList(trips) {
    const container = document.getElementById('recentList');
    container.innerHTML = '';
    
    trips.forEach(trip => {
        const recentElement = document.createElement('div');
        recentElement.className = 'recent-item';
        
        // 计算距离现在的时间差
        const lastViewedTime = new Date(trip.lastViewedAt);
        const timeAgo = getTimeAgoString(lastViewedTime);
        
        recentElement.innerHTML = `
            <div class="recent-header">
                <h3>${trip.destination}</h3>
                <span class="time-ago">${timeAgo}</span>
            </div>
            <div class="recent-body">
                <p class="trip-description">${trip.description || '暂无描述'}</p>
                <div class="trip-meta">
                    <span>📅 ${formatDate(trip.startDate)}</span>
                    <span>💰 ¥${trip.expense}</span>
                </div>
            </div>
            <div class="recent-footer">
                <button class="btn-small" onclick="navigateTo('trip-detail', ${trip.id})">
                    查看详情
                </button>
                <button class="btn-small" onclick="editTrip(${trip.id})">
                    编辑
                </button>
            </div>
        `;
        container.appendChild(recentElement);
    });
}

最近旅行列表渲染函数为每个旅行创建一个 DOM 元素,并显示距离现在的时间差(如"2小时前"、"3天前"等)。这样用户可以快速了解旅行的访问时间。

时间差计算函数

function getTimeAgoString(date) {
    const now = new Date();
    const diffMs = now.getTime() - date.getTime();
    const diffMins = Math.floor(diffMs / 60000);
    const diffHours = Math.floor(diffMs / 3600000);
    const diffDays = Math.floor(diffMs / 86400000);
    
    if (diffMins < 1) {
        return '刚刚';
    } else if (diffMins < 60) {
        return `${diffMins}分钟前`;
    } else if (diffHours < 24) {
        return `${diffHours}小时前`;
    } else if (diffDays < 30) {
        return `${diffDays}天前`;
    } else {
        return formatDate(date);
    }
}

时间差计算函数将时间戳转换为人类可读的格式,如"2小时前"、"3天前"等。这样提升了用户体验,用户可以快速了解旅行的访问时间。

清空访问历史函数

async function clearRecentHistory() {
    if (!confirm('确定要清空所有访问历史吗?')) {
        return;
    }
    
    try {
        // 获取所有旅行
        const allTrips = await db.getAllTrips();
        
        // 清空所有旅行的访问时间
        for (let trip of allTrips) {
            trip.lastViewedAt = null;
            await db.updateTrip(trip);
        }
        
        // 重新加载列表
        loadRecentTrips();
        
        showToast('访问历史已清空');
        
        // 通知原生层
        if (window.cordova) {
            cordova.exec(
                (result) => console.log('History cleared:', result),
                (error) => console.error('Clear error:', error),
                'RecentPlugin',
                'onHistoryCleared',
                [{ timestamp: Date.now() }]
            );
        }
    } catch (error) {
        console.error('Error clearing history:', error);
        showToast('清空历史失败');
    }
}

清空访问历史函数将所有旅行的 lastViewedAt 字段设置为 null,然后重新加载列表。函数还通知原生层清空缓存。

🔌 OpenHarmony 原生代码实现

最近旅行缓存插件

// RecentPlugin.ets
import { BusinessError } from '@ohos.base';

export class RecentPlugin {
    private recentCache: Map<number, number> = new Map(); // tripId -> timestamp
    private maxRecentCount: number = 20;
    
    // 处理旅行访问事件
    onTripViewed(args: any, callback: Function): void {
        try {
            const tripId = args[0].tripId;
            const timestamp = args[0].timestamp;
            
            // 更新缓存
            this.recentCache.set(tripId, timestamp);
            
            // 如果缓存超过限制,删除最旧的记录
            if (this.recentCache.size > this.maxRecentCount) {
                let oldestKey = null;
                let oldestTime = Infinity;
                
                this.recentCache.forEach((time, key) => {
                    if (time < oldestTime) {
                        oldestTime = time;
                        oldestKey = key;
                    }
                });
                
                if (oldestKey !== null) {
                    this.recentCache.delete(oldestKey);
                }
            }
            
            callback({ success: true, message: '访问记录已更新' });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
    
    // 处理历史清空事件
    onHistoryCleared(args: any, callback: Function): void {
        try {
            this.recentCache.clear();
            console.log('[Recent] History cleared');
            callback({ success: true, message: '缓存已清空' });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
    
    // 获取最近旅行列表
    getRecentTrips(callback: Function): void {
        try {
            const recentTrips = Array.from(this.recentCache.entries())
                .sort((a, b) => b[1] - a[1])
                .slice(0, this.maxRecentCount)
                .map(([tripId, timestamp]) => ({ tripId, timestamp }));
            
            callback({ success: true, data: recentTrips });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
}

最近旅行缓存插件在原生层维护一个访问历史缓存。缓存使用 Map 数据结构,存储旅行 ID 和访问时间戳。当缓存超过限制时,自动删除最旧的记录。

🕐 访问历史的应用场景

访问历史功能在很多应用中都很常见,如浏览器的历史记录、文件管理器的最近文件等。在旅行记录应用中,访问历史可以帮助用户快速回到最近查看的旅行。这对于经常编辑同一个旅行的用户特别有用。

访问历史还可以用于分析用户的行为。通过分析用户的访问历史,可以了解用户最关注的旅行,从而提供更好的推荐。

📈 时间戳的管理与优化

时间戳是访问历史的核心。每次用户访问旅行时,需要更新该旅行的最后访问时间。但是频繁的数据库更新会影响性能。可以使用批量更新或异步更新来优化性能。

时间戳的精度也需要考虑。如果精度太高,会导致数据库中有很多相似的时间戳。如果精度太低,用户无法区分最近访问的旅行。通常使用秒级精度就足够了。

🎯 最近旅行列表的个性化

最近旅行列表可以根据用户的偏好进行个性化。例如,可以根据用户的访问频率调整列表的长度。经常访问旅行的用户可能需要更长的列表,而很少访问旅行的用户可能只需要较短的列表。

最近旅行列表也可以根据用户的访问模式进行排序。除了按访问时间排序,还可以按访问频率排序,让用户最常访问的旅行显示在最前面。

🔄 访问历史的同步

如果应用支持多设备同步,访问历史也需要同步。用户在一个设备上访问旅行后,其他设备上的最近旅行列表应该相应更新。这需要实现跨设备的数据同步机制。

访问历史的同步也需要考虑冲突解决。如果用户在两个设备上同时访问不同的旅行,应该如何处理?通常可以使用时间戳来解决冲突,最新的访问时间优先。

🗑️ 访问历史的清理

访问历史会不断增长,需要定期清理。可以实现自动清理机制,删除超过一定时间(如 90 天)的访问记录。用户也应该能够手动清理访问历史。

清理访问历史时,需要小心不要删除重要的数据。应该只删除访问时间戳,而不是删除旅行本身。

💾 访问历史的存储

访问历史可以存储在本地数据库中,也可以存储在云端。本地存储速度快,但容量有限。云端存储容量大,但需要网络连接。

可以使用混合方案,将最近的访问历史存储在本地,较旧的访问历史存储在云端。这样可以兼顾性能和容量。

🔐 访问历史的隐私

访问历史可能包含用户的隐私信息。用户应该能够控制访问历史的可见性。例如,用户可以选择不记录某些旅行的访问历史,或者选择定期自动清理访问历史。

如果应用支持多用户,访问历史应该是用户私有的,其他用户无法看到。

📝 总结

最近旅行页面展示了如何在 Cordova 与 OpenHarmony 框架中实现一个访问历史管理系统。Web 层负责 UI 渲染和时间戳管理,原生层负责缓存管理和性能优化。通过时间戳和缓存机制,实现了高效的最近旅行列表展示。在实现过程中,需要重点关注时间戳管理、性能优化、隐私保护等方面,确保访问历史功能能够提供最佳的用户体验。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐