在这里插入图片描述

📌 概述

趋势分析模块提供了喝茶消费趋势的分析功能。该模块集成了 Cordova 框架与 OpenHarmony 原生能力,实现了高效的时间序列数据处理和趋势可视化。用户可以查看不同时间周期(日、周、月)的消费趋势、喝茶频率趋势等。模块支持趋势预测和对比分析。

🔗 完整流程

第一步:时间序列数据准备

应用会从数据库中加载指定时间范围的所有喝茶记录。按时间周期(日、周、月)进行聚合,生成时间序列数据。这个过程在原生层进行,确保性能。

第二步:趋势计算与平滑

应用会对时间序列数据进行处理,计算趋势线。使用移动平均等算法平滑数据,消除噪声。计算趋势的上升或下降方向。

第三步:趋势展示与分析

趋势以折线图的形式展示。用户可以查看消费金额趋势、喝茶次数趋势等。应用提供了趋势分析的文字说明,帮助用户理解趋势。

🔧 Web 代码实现

HTML 趋势分析页面

<div id="trend-analysis-page" class="page">
    <div class="page-header">
        <h1>趋势分析</h1>
    </div>
    
    <div class="trend-controls">
        <select id="trend-period" onchange="loadTrendData()">
            <option value="daily">日趋势</option>
            <option value="weekly">周趋势</option>
            <option value="monthly">月趋势</option>
        </select>
        
        <select id="trend-metric" onchange="loadTrendData()">
            <option value="expense">消费金额</option>
            <option value="count">喝茶次数</option>
            <option value="rating">平均评分</option>
        </select>
        
        <button class="btn btn-primary" onclick="loadTrendData()">刷新</button>
    </div>
    
    <div id="trend-chart" class="trend-chart">
        <!-- 趋势图表动态生成 -->
    </div>
    
    <div class="trend-analysis">
        <h2>趋势分析</h2>
        <div id="trend-summary" class="trend-summary">
            <!-- 趋势分析文字说明 -->
        </div>
    </div>
</div>

趋势分析页面包含周期和指标选择、趋势图表和分析说明。

趋势分析逻辑

async function loadTrendData() {
    const period = document.getElementById('trend-period').value;
    const metric = document.getElementById('trend-metric').value;
    
    try {
        // 获取所有记录
        const records = await db.getAllRecords();
        
        // 按周期聚合数据
        const trendData = aggregateTrendData(records, period, metric);
        
        // 渲染趋势图表
        renderTrendChart(trendData, metric);
        
        // 生成趋势分析
        const analysis = generateTrendAnalysis(trendData, metric);
        document.getElementById('trend-summary').innerHTML = analysis;
        
        if (window.cordova) {
            cordova.exec(
                null, null,
                'TeaLogger',
                'logEvent',
                ['trend_loaded', { period: period, metric: metric }]
            );
        }
    } catch (error) {
        console.error('Failed to load trend data:', error);
        showToast('加载趋势数据失败', 'error');
    }
}

function aggregateTrendData(records, period, metric) {
    const data = {};
    
    records.forEach(record => {
        const date = new Date(record.createdAt);
        let key;
        
        if (period === 'daily') {
            key = date.toISOString().split('T')[0];
        } else if (period === 'weekly') {
            const weekStart = new Date(date);
            weekStart.setDate(date.getDate() - date.getDay());
            key = weekStart.toISOString().split('T')[0];
        } else {
            key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
        }
        
        if (!data[key]) {
            data[key] = { count: 0, expense: 0, totalRating: 0 };
        }
        
        data[key].count++;
        data[key].expense += record.price || 0;
        data[key].totalRating += record.rating || 0;
    });
    
    // 计算平均评分
    Object.values(data).forEach(d => {
        d.avgRating = d.count > 0 ? d.totalRating / d.count : 0;
    });
    
    return data;
}

function renderTrendChart(trendData, metric) {
    const container = document.getElementById('trend-chart');
    container.innerHTML = '';
    
    const dates = Object.keys(trendData).sort();
    const values = dates.map(date => {
        switch(metric) {
            case 'expense':
                return trendData[date].expense;
            case 'count':
                return trendData[date].count;
            case 'rating':
                return trendData[date].avgRating;
            default:
                return 0;
        }
    });
    
    // 简单的ASCII图表
    const maxValue = Math.max(...values);
    const chartHeight = 10;
    
    let chart = '<pre>';
    for (let i = chartHeight; i > 0; i--) {
        let line = '';
        values.forEach(v => {
            const height = Math.round((v / maxValue) * chartHeight);
            line += height >= i ? '█ ' : '  ';
        });
        chart += line + '\n';
    }
    chart += '</pre>';
    
    container.innerHTML = chart;
}

function generateTrendAnalysis(trendData, metric) {
    const dates = Object.keys(trendData).sort();
    const values = dates.map(date => {
        switch(metric) {
            case 'expense':
                return trendData[date].expense;
            case 'count':
                return trendData[date].count;
            case 'rating':
                return trendData[date].avgRating;
            default:
                return 0;
        }
    });
    
    if (values.length < 2) {
        return '<p>数据不足,无法进行趋势分析</p>';
    }
    
    const firstValue = values[0];
    const lastValue = values[values.length - 1];
    const change = lastValue - firstValue;
    const changePercent = ((change / firstValue) * 100).toFixed(1);
    
    const trend = change > 0 ? '上升' : '下降';
    const metricName = metric === 'expense' ? '消费金额' : metric === 'count' ? '喝茶次数' : '平均评分';
    
    return `
        <p>最近${metricName}${trend}趋势,变化幅度为${Math.abs(changePercent)}%。</p>
        <p>初始值: ${firstValue.toFixed(2)}, 最终值: ${lastValue.toFixed(2)}</p>
    `;
}

这段代码实现了趋势分析功能。loadTrendData() 加载趋势数据。aggregateTrendData() 按周期聚合数据。renderTrendChart() 显示趋势图表。generateTrendAnalysis() 生成趋势分析说明。

🔌 OpenHarmony 原生代码

时间序列处理

// entry/src/main/ets/plugins/TimeSeriesProcessor.ets
export class TimeSeriesProcessor {
    static aggregateByPeriod(records: TeaRecord[], period: string): Map<string, AggregatedData> {
        const data = new Map<string, AggregatedData>();
        
        records.forEach(record => {
            const date = new Date(record.createdAt);
            let key: string;
            
            if (period === 'daily') {
                key = this.formatDate(date);
            } else if (period === 'weekly') {
                const weekStart = new Date(date);
                weekStart.setDate(date.getDate() - date.getDay());
                key = this.formatDate(weekStart);
            } else {
                key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
            }
            
            if (!data.has(key)) {
                data.set(key, { count: 0, expense: 0, totalRating: 0 });
            }
            
            const agg = data.get(key);
            if (agg) {
                agg.count++;
                agg.expense += record.price;
                agg.totalRating += record.rating;
            }
        });
        
        return data;
    }
    
    static calculateMovingAverage(values: number[], windowSize: number): number[] {
        const result: number[] = [];
        
        for (let i = 0; i < values.length; i++) {
            const start = Math.max(0, i - Math.floor(windowSize / 2));
            const end = Math.min(values.length, i + Math.ceil(windowSize / 2));
            const window = values.slice(start, end);
            const avg = window.reduce((a, b) => a + b, 0) / window.length;
            result.push(avg);
        }
        
        return result;
    }
    
    private static formatDate(date: Date): string {
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        return `${year}-${month}-${day}`;
    }
}

interface AggregatedData {
    count: number;
    expense: number;
    totalRating: number;
}

interface TeaRecord {
    createdAt: string;
    price: number;
    rating: number;
}

这个类处理时间序列数据。aggregateByPeriod() 按周期聚合数据。calculateMovingAverage() 计算移动平均以平滑数据。

📝 总结

趋势分析模块展示了如何在 Cordova 框架中实现趋势分析功能。通过 Web 层的用户界面和交互,结合原生层的高效时间序列处理,为用户提供了直观的趋势展示和分析。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐