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

在这里插入图片描述

📌 概述

回收站功能允许用户安全地删除旅行记录。被删除的旅行不会立即从数据库中移除,而是被标记为已删除,存放在回收站中。用户可以在回收站中查看已删除的旅行,并选择恢复或永久删除。这个功能提供了数据安全保护,防止用户误删重要数据。在 Cordova 与 OpenHarmony 的混合开发框架中,回收站功能需要实现软删除机制、恢复功能和永久删除功能。

🔗 完整流程

第一步:软删除机制与标记管理

回收站功能基于软删除机制。当用户删除旅行时,不是直接从数据库中删除记录,而是将旅行的 isDeleted 标志设置为 true,并记录删除时间。这样可以保留用户的删除历史,并允许用户恢复已删除的数据。

软删除机制需要在所有查询操作中考虑 isDeleted 标志,确保已删除的旅行不会显示在正常列表中。

第二步:回收站列表展示与管理

回收站页面需要展示所有已删除的旅行。用户可以在回收站中查看旅行的详情,也可以选择恢复或永久删除。回收站列表通常按删除时间排序,最近删除的旅行显示在最前面。

回收站还可以实现自动清空功能,删除超过一定时间(如 30 天)的旅行。

第三步:原生层删除历史记录与清理机制

OpenHarmony 原生层可以实现删除历史的记录和管理。原生层可以维护一个删除历史日志,记录所有删除操作的时间和用户信息。原生层还可以实现自动清理机制,定期清理过期的已删除旅行。

🔧 Web 代码实现

回收站页面 HTML 结构

<div id="trash-page" class="page">
    <div class="page-header">
        <h1>回收站</h1>
        <button class="btn-icon" onclick="emptyTrash()">🗑️</button>
    </div>
    
    <div class="trash-container">
        <div class="trash-list" id="trashList">
            <!-- 已删除旅行列表动态加载 -->
        </div>
        <div class="empty-state" id="emptyState" style="display: none;">
            <div class="empty-icon">🗑️</div>
            <p>回收站为空</p>
        </div>
    </div>
</div>

HTML 结构包含已删除旅行列表和清空回收站按钮。用户可以一键清空所有已删除的旅行。

软删除函数

async function deleteTrip(tripId) {
    if (!confirm('确定要删除这个旅行吗?')) {
        return;
    }
    
    try {
        // 获取旅行数据
        const trip = await db.getTrip(tripId);
        
        if (trip) {
            // 标记为已删除
            trip.isDeleted = true;
            trip.deletedAt = new Date().toISOString();
            
            // 保存到数据库
            await db.updateTrip(trip);
            
            showToast('旅行已删除,可在回收站中恢复');
            
            // 刷新当前页面
            if (window.currentPage === 'all-trips') {
                loadTrips(1);
            }
            
            // 通知原生层
            if (window.cordova) {
                cordova.exec(
                    (result) => console.log('Trip deleted:', result),
                    (error) => console.error('Delete error:', error),
                    'TrashPlugin',
                    'onTripDeleted',
                    [{ tripId: tripId, timestamp: Date.now() }]
                );
            }
        }
    } catch (error) {
        console.error('Error deleting trip:', error);
        showToast('删除失败,请重试');
    }
}

软删除函数将旅行标记为已删除,并记录删除时间。函数不会从数据库中移除旅行,而是保留数据以便恢复。

加载回收站列表函数

async function loadTrash() {
    try {
        // 从数据库查询所有旅行
        const allTrips = await db.getAllTrips();
        
        // 筛选出已删除的旅行
        const trashedTrips = allTrips
            .filter(trip => trip.isDeleted === true)
            .sort((a, b) => {
                const timeA = new Date(a.deletedAt).getTime();
                const timeB = new Date(b.deletedAt).getTime();
                return timeB - timeA;
            });
        
        // 处理空状态
        if (trashedTrips.length === 0) {
            document.getElementById('emptyState').style.display = 'block';
            document.getElementById('trashList').innerHTML = '';
            return;
        }
        
        document.getElementById('emptyState').style.display = 'none';
        
        // 渲染回收站列表
        renderTrashList(trashedTrips);
    } catch (error) {
        console.error('Error loading trash:', error);
        showToast('加载回收站失败');
    }
}

这个函数从数据库查询所有已删除的旅行,按删除时间降序排序。函数处理空状态,当回收站为空时显示提示信息。

回收站列表渲染函数

function renderTrashList(trips) {
    const container = document.getElementById('trashList');
    container.innerHTML = '';
    
    trips.forEach(trip => {
        const trashElement = document.createElement('div');
        trashElement.className = 'trash-item';
        trashElement.id = `trash-${trip.id}`;
        
        const deletedTime = new Date(trip.deletedAt);
        const timeAgo = getTimeAgoString(deletedTime);
        
        trashElement.innerHTML = `
            <div class="trash-header">
                <h3>${trip.destination}</h3>
                <span class="deleted-time">${timeAgo}删除</span>
            </div>
            <div class="trash-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="trash-footer">
                <button class="btn-small" onclick="restoreTrip(${trip.id})">
                    恢复
                </button>
                <button class="btn-small btn-danger" onclick="permanentlyDeleteTrip(${trip.id})">
                    永久删除
                </button>
            </div>
        `;
        container.appendChild(trashElement);
    });
}

回收站列表渲染函数为每个已删除的旅行创建一个 DOM 元素。每个元素包含恢复和永久删除两个操作按钮。

恢复旅行函数

async function restoreTrip(tripId) {
    try {
        // 获取旅行数据
        const trip = await db.getTrip(tripId);
        
        if (trip) {
            // 取消删除标记
            trip.isDeleted = false;
            trip.deletedAt = null;
            
            // 保存到数据库
            await db.updateTrip(trip);
            
            showToast('旅行已恢复');
            
            // 从回收站移除该项
            const element = document.getElementById(`trash-${tripId}`);
            if (element) {
                element.remove();
            }
            
            // 重新加载回收站列表
            loadTrash();
            
            // 通知原生层
            if (window.cordova) {
                cordova.exec(
                    (result) => console.log('Trip restored:', result),
                    (error) => console.error('Restore error:', error),
                    'TrashPlugin',
                    'onTripRestored',
                    [{ tripId: tripId, timestamp: Date.now() }]
                );
            }
        }
    } catch (error) {
        console.error('Error restoring trip:', error);
        showToast('恢复失败,请重试');
    }
}

恢复函数将旅行的 isDeleted 标志设置为 false,使旅行重新出现在正常列表中。函数还通知原生层恢复操作。

永久删除函数

async function permanentlyDeleteTrip(tripId) {
    if (!confirm('确定要永久删除这个旅行吗?此操作无法撤销。')) {
        return;
    }
    
    try {
        // 从数据库永久删除
        await db.deleteTrip(tripId);
        
        showToast('旅行已永久删除');
        
        // 从回收站移除该项
        const element = document.getElementById(`trash-${tripId}`);
        if (element) {
            element.remove();
        }
        
        // 重新加载回收站列表
        loadTrash();
        
        // 通知原生层
        if (window.cordova) {
            cordova.exec(
                (result) => console.log('Trip permanently deleted:', result),
                (error) => console.error('Delete error:', error),
                'TrashPlugin',
                'onTripPermanentlyDeleted',
                [{ tripId: tripId, timestamp: Date.now() }]
            );
        }
    } catch (error) {
        console.error('Error permanently deleting trip:', error);
        showToast('永久删除失败,请重试');
    }
}

永久删除函数直接从数据库中删除旅行记录。这个操作是不可逆的,需要用户确认。

清空回收站函数

async function emptyTrash() {
    if (!confirm('确定要清空回收站吗?此操作无法撤销。')) {
        return;
    }
    
    try {
        // 获取所有已删除的旅行
        const allTrips = await db.getAllTrips();
        const trashedTrips = allTrips.filter(trip => trip.isDeleted === true);
        
        // 永久删除所有已删除的旅行
        for (let trip of trashedTrips) {
            await db.deleteTrip(trip.id);
        }
        
        showToast('回收站已清空');
        
        // 重新加载回收站列表
        loadTrash();
        
        // 通知原生层
        if (window.cordova) {
            cordova.exec(
                (result) => console.log('Trash emptied:', result),
                (error) => console.error('Empty error:', error),
                'TrashPlugin',
                'onTrashEmptied',
                [{ timestamp: Date.now() }]
            );
        }
    } catch (error) {
        console.error('Error emptying trash:', error);
        showToast('清空回收站失败');
    }
}

清空回收站函数永久删除所有已删除的旅行。这个操作需要用户确认,确保用户不会误操作。

🔌 OpenHarmony 原生代码实现

回收站管理插件

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

export class TrashPlugin {
    private deleteHistory: Map<number, number> = new Map(); // tripId -> deleteTime
    private autoCleanupInterval: number = 30 * 24 * 60 * 60 * 1000; // 30天
    
    // 处理旅行删除事件
    onTripDeleted(args: any, callback: Function): void {
        try {
            const tripId = args[0].tripId;
            const timestamp = args[0].timestamp;
            
            // 记录删除时间
            this.deleteHistory.set(tripId, timestamp);
            
            console.log(`[Trash] Trip ${tripId} deleted at ${timestamp}`);
            
            callback({ success: true, message: '删除记录已保存' });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
    
    // 处理旅行恢复事件
    onTripRestored(args: any, callback: Function): void {
        try {
            const tripId = args[0].tripId;
            
            // 删除删除记录
            this.deleteHistory.delete(tripId);
            
            console.log(`[Trash] Trip ${tripId} restored`);
            
            callback({ success: true, message: '恢复记录已更新' });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
    
    // 处理永久删除事件
    onTripPermanentlyDeleted(args: any, callback: Function): void {
        try {
            const tripId = args[0].tripId;
            
            // 删除删除记录
            this.deleteHistory.delete(tripId);
            
            console.log(`[Trash] Trip ${tripId} permanently deleted`);
            
            callback({ success: true, message: '永久删除记录已更新' });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
    
    // 处理回收站清空事件
    onTrashEmptied(args: any, callback: Function): void {
        try {
            this.deleteHistory.clear();
            
            console.log('[Trash] Trash emptied');
            
            callback({ success: true, message: '回收站已清空' });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
    
    // 自动清理过期的已删除旅行
    performAutoCleanup(callback: Function): void {
        try {
            const now = Date.now();
            const toDelete: number[] = [];
            
            this.deleteHistory.forEach((deleteTime, tripId) => {
                if (now - deleteTime > this.autoCleanupInterval) {
                    toDelete.push(tripId);
                }
            });
            
            toDelete.forEach(tripId => {
                this.deleteHistory.delete(tripId);
            });
            
            console.log(`[Trash] Auto cleanup: ${toDelete.length} trips deleted`);
            
            callback({ success: true, deletedCount: toDelete.length });
        } catch (error) {
            callback({ success: false, error: error.message });
        }
    }
}

回收站管理插件在原生层维护删除历史记录。插件实现了自动清理机制,定期清理超过 30 天的已删除旅行。

🛡️ 软删除 vs 硬删除

软删除和硬删除是两种不同的删除策略。硬删除直接从数据库中删除数据,无法恢复。软删除只是标记数据为已删除,数据仍然保存在数据库中。

软删除的优点是可以恢复数据,提供了数据安全保护。缺点是会占用更多的存储空间,并且需要在所有查询中考虑 isDeleted 标志。硬删除的优点是节省存储空间,缺点是无法恢复数据。

在实际应用中,通常使用软删除来保护用户数据。用户可以在回收站中恢复误删的数据。经过一段时间后(如 30 天),再进行硬删除,彻底清理数据库。

🔄 删除流程的设计

删除流程应该分为两个阶段:软删除和硬删除。在软删除阶段,用户可以随时恢复数据。在硬删除阶段,数据被彻底删除,无法恢复。

在软删除和硬删除之间应该有一个缓冲期,给用户足够的时间来决定是否恢复数据。这个缓冲期通常是 30 天。

📋 回收站的管理

回收站需要展示所有已删除的数据。用户可以在回收站中查看删除的数据,也可以选择恢复或永久删除。回收站应该按删除时间排序,最近删除的数据显示在最前面。

回收站还应该显示每个数据的删除时间和剩余的恢复时间。例如,如果一个旅行在 25 天前被删除,还有 5 天就会被永久删除,应该显示"5 天后永久删除"。这样可以提醒用户及时恢复重要数据。

🔐 删除权限与审计

在多用户应用中,删除操作需要权限控制。只有数据的所有者或管理员才能删除数据。删除操作应该被记录在审计日志中,用于追踪谁删除了什么数据。

对于重要的数据,可以实现二次确认机制。用户需要输入密码或进行其他验证才能删除数据。这样可以防止误删。

💾 删除历史的记录

删除操作应该被记录在删除历史中。删除历史包括删除的数据、删除时间、删除者等信息。这些信息可以用于审计和恢复。

删除历史应该被保存在一个单独的表中,与正常的数据分离。这样可以避免删除历史被意外删除。

🧹 自动清理机制

回收站应该实现自动清理机制。超过一定时间(如 30 天)的已删除数据应该被自动永久删除。这样可以避免回收站无限增长。

自动清理应该在后台进行,不影响用户的正常使用。可以在应用启动时或定期执行清理任务。

📊 删除统计与分析

删除数据可以提供一些有趣的统计信息。例如,可以统计每天有多少数据被删除,哪些数据最容易被删除等。这些统计信息可以帮助改进应用的设计。

如果发现某个功能的数据经常被删除,可能说明这个功能有问题,需要改进。

🔔 删除通知

当用户删除数据时,应该显示一个通知,告诉用户数据已被删除,可以在回收站中恢复。这样可以防止用户误删数据后不知道如何恢复。

对于重要的删除操作,可以发送一个更显眼的通知,甚至可以提供一个快速恢复按钮。

📝 总结

回收站管理功能展示了如何在 Cordova 与 OpenHarmony 框架中实现一个安全的删除机制。通过软删除机制,用户可以安全地删除数据,并在需要时恢复。原生层的自动清理机制确保了数据库的整洁。这个功能提供了数据安全保护,提升了用户体验。在实现过程中,需要重点关注删除流程设计、权限控制、自动清理等方面,确保回收站功能能够提供最佳的用户体验。

Logo

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

更多推荐