在这里插入图片描述

📌 概述

月度统计模块提供了月度喝茶数据的统计分析功能。该模块集成了 Cordova 框架与 OpenHarmony 原生能力,实现了高效的月度数据统计和趋势分析。用户可以查看特定月份的喝茶记录总数、消费金额、平均评分等统计数据,并可以与其他月份进行对比。

🔗 完整流程

第一步:月份选择与数据加载

用户在月度统计页面选择要查看的月份。应用会从数据库中加载该月份的所有喝茶记录。应用会计算该月份的各项统计数据。

第二步:月度数据聚合

应用会对该月份的数据进行聚合。包括按日期、茶叶类型、产地等维度进行分组统计。这些计算在原生层进行,确保性能。

第三步:统计结果展示与对比

统计完成后,应用会将结果以卡片、图表和表格的形式展示。用户可以查看月度趋势和与其他月份的对比。

🔧 Web 代码实现

HTML 月度统计页面

<div id="monthly-stats-page" class="page">
    <div class="page-header">
        <h1>月度统计</h1>
    </div>
    
    <div class="stats-month-selector">
        <input type="month" id="stats-month" onchange="loadMonthlyStats()">
        <button class="btn btn-primary" onclick="loadMonthlyStats()">查询</button>
    </div>
    
    <div class="stats-cards">
        <div class="stat-card">
            <div class="stat-label">喝茶次数</div>
            <div class="stat-value" id="monthly-count">0</div>
        </div>
        
        <div class="stat-card">
            <div class="stat-label">消费金额</div>
            <div class="stat-value" id="monthly-expense">¥0</div>
        </div>
        
        <div class="stat-card">
            <div class="stat-label">平均消费</div>
            <div class="stat-value" id="monthly-avg-price">¥0</div>
        </div>
        
        <div class="stat-card">
            <div class="stat-label">平均评分</div>
            <div class="stat-value" id="monthly-avg-rating">0</div>
        </div>
    </div>
    
    <div class="stats-section">
        <h2>茶叶类型分布</h2>
        <div id="monthly-type-stats" class="stats-table">
            <!-- 类型统计表动态生成 -->
        </div>
    </div>
    
    <div class="stats-section">
        <h2>产地分布</h2>
        <div id="monthly-origin-stats" class="stats-table">
            <!-- 产地统计表动态生成 -->
        </div>
    </div>
</div>

月度统计页面包含月份选择器、统计卡片和分布统计表。

月度统计逻辑

async function loadMonthlyStats() {
    const selectedMonth = document.getElementById('stats-month').value;
    
    if (!selectedMonth) {
        showToast('请选择月份', 'warning');
        return;
    }
    
    try {
        // 解析月份
        const [year, month] = selectedMonth.split('-');
        const startDate = `${year}-${month}-01`;
        const endDate = new Date(year, parseInt(month), 0).toISOString().split('T')[0];
        
        // 获取该月份的所有记录
        const records = await db.getRecordsByDateRange(startDate, endDate);
        
        // 计算统计数据
        const stats = calculateMonthlyStats(records);
        
        // 更新统计卡片
        document.getElementById('monthly-count').textContent = stats.count;
        document.getElementById('monthly-expense').textContent = '¥' + stats.totalExpense.toFixed(2);
        document.getElementById('monthly-avg-price').textContent = '¥' + stats.avgPrice.toFixed(2);
        document.getElementById('monthly-avg-rating').textContent = stats.avgRating.toFixed(1);
        
        // 显示类型分布
        renderTypeStats(stats.typeStats);
        
        // 显示产地分布
        renderOriginStats(stats.originStats);
        
        if (window.cordova) {
            cordova.exec(
                null, null,
                'TeaLogger',
                'logEvent',
                ['monthly_stats_loaded', { month: selectedMonth, recordCount: records.length }]
            );
        }
    } catch (error) {
        console.error('Failed to load monthly stats:', error);
        showToast('加载统计数据失败', 'error');
    }
}

function calculateMonthlyStats(records) {
    if (records.length === 0) {
        return {
            count: 0,
            totalExpense: 0,
            avgPrice: 0,
            avgRating: 0,
            typeStats: {},
            originStats: {}
        };
    }
    
    const totalExpense = records.reduce((sum, r) => sum + (r.price || 0), 0);
    const totalRating = records.reduce((sum, r) => sum + (r.rating || 0), 0);
    
    // 按类型统计
    const typeStats = {};
    records.forEach(r => {
        if (!typeStats[r.teaType]) {
            typeStats[r.teaType] = { count: 0, expense: 0 };
        }
        typeStats[r.teaType].count++;
        typeStats[r.teaType].expense += r.price || 0;
    });
    
    // 按产地统计
    const originStats = {};
    records.forEach(r => {
        if (!originStats[r.origin]) {
            originStats[r.origin] = { count: 0, expense: 0 };
        }
        originStats[r.origin].count++;
        originStats[r.origin].expense += r.price || 0;
    });
    
    return {
        count: records.length,
        totalExpense: totalExpense,
        avgPrice: totalExpense / records.length,
        avgRating: totalRating / records.length,
        typeStats: typeStats,
        originStats: originStats
    };
}

function renderTypeStats(typeStats) {
    const container = document.getElementById('monthly-type-stats');
    container.innerHTML = '';
    
    const table = document.createElement('table');
    table.className = 'data-table';
    
    // 表头
    const thead = document.createElement('thead');
    thead.innerHTML = '<tr><th>茶叶类型</th><th>次数</th><th>消费金额</th><th>占比</th></tr>';
    table.appendChild(thead);
    
    // 表体
    const tbody = document.createElement('tbody');
    const totalExpense = Object.values(typeStats).reduce((sum, s) => sum + s.expense, 0);
    
    Object.entries(typeStats).forEach(([type, stats]) => {
        const percentage = totalExpense > 0 ? ((stats.expense / totalExpense) * 100).toFixed(1) : 0;
        const row = document.createElement('tr');
        row.innerHTML = `
            <td>${type}</td>
            <td>${stats.count}</td>
            <td>¥${stats.expense.toFixed(2)}</td>
            <td>${percentage}%</td>
        `;
        tbody.appendChild(row);
    });
    
    table.appendChild(tbody);
    container.appendChild(table);
}

function renderOriginStats(originStats) {
    const container = document.getElementById('monthly-origin-stats');
    container.innerHTML = '';
    
    const table = document.createElement('table');
    table.className = 'data-table';
    
    // 表头
    const thead = document.createElement('thead');
    thead.innerHTML = '<tr><th>产地</th><th>次数</th><th>消费金额</th><th>占比</th></tr>';
    table.appendChild(thead);
    
    // 表体
    const tbody = document.createElement('tbody');
    const totalExpense = Object.values(originStats).reduce((sum, s) => sum + s.expense, 0);
    
    Object.entries(originStats).forEach(([origin, stats]) => {
        const percentage = totalExpense > 0 ? ((stats.expense / totalExpense) * 100).toFixed(1) : 0;
        const row = document.createElement('tr');
        row.innerHTML = `
            <td>${origin}</td>
            <td>${stats.count}</td>
            <td>¥${stats.expense.toFixed(2)}</td>
            <td>${percentage}%</td>
        `;
        tbody.appendChild(row);
    });
    
    table.appendChild(tbody);
    container.appendChild(table);
}

// 初始化月份为当前月份
document.addEventListener('DOMContentLoaded', function() {
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const monthInput = document.getElementById('stats-month');
    if (monthInput) {
        monthInput.value = `${year}-${month}`;
    }
});

这段代码实现了月度统计功能。loadMonthlyStats() 加载指定月份的统计数据。calculateMonthlyStats() 计算月度统计指标和分布数据。renderTypeStats()renderOriginStats() 显示分布统计表。

🔌 OpenHarmony 原生代码

月度数据聚合

// entry/src/main/ets/plugins/MonthlyAggregation.ets
import { relationalStore } from '@kit.ArkData';

export class MonthlyAggregation {
    private store: relationalStore.RdbStore;
    
    async aggregateMonthlyData(year: number, month: number): Promise<MonthlyData> {
        const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
        const endDate = new Date(year, month, 0).toISOString().split('T')[0];
        
        const predicates = new relationalStore.RdbPredicates('tea_records');
        predicates.between('created_at', startDate, endDate);
        
        const resultSet = await this.store.query(predicates);
        const records: TeaRecord[] = [];
        
        while (resultSet.goToNextRow()) {
            records.push(this.parseRecord(resultSet));
        }
        
        resultSet.close();
        
        // 聚合数据
        return this.aggregateRecords(records);
    }
    
    private aggregateRecords(records: TeaRecord[]): MonthlyData {
        if (records.length === 0) {
            return {
                count: 0,
                totalExpense: 0,
                avgPrice: 0,
                avgRating: 0,
                typeDistribution: {},
                originDistribution: {}
            };
        }
        
        let totalExpense = 0;
        let totalRating = 0;
        const typeDistribution = new Map<string, { count: number; expense: number }>();
        const originDistribution = new Map<string, { count: number; expense: number }>();
        
        records.forEach(record => {
            totalExpense += record.price;
            totalRating += record.rating;
            
            // 类型分布
            if (!typeDistribution.has(record.teaType)) {
                typeDistribution.set(record.teaType, { count: 0, expense: 0 });
            }
            const typeData = typeDistribution.get(record.teaType);
            if (typeData) {
                typeData.count++;
                typeData.expense += record.price;
            }
            
            // 产地分布
            if (!originDistribution.has(record.origin)) {
                originDistribution.set(record.origin, { count: 0, expense: 0 });
            }
            const originData = originDistribution.get(record.origin);
            if (originData) {
                originData.count++;
                originData.expense += record.price;
            }
        });
        
        return {
            count: records.length,
            totalExpense: totalExpense,
            avgPrice: totalExpense / records.length,
            avgRating: totalRating / records.length,
            typeDistribution: Object.fromEntries(typeDistribution),
            originDistribution: Object.fromEntries(originDistribution)
        };
    }
    
    private parseRecord(resultSet: relationalStore.ResultSet): TeaRecord {
        return {
            id: resultSet.getColumnValue(resultSet.getColumnIndex('id')) as number,
            teaType: resultSet.getColumnValue(resultSet.getColumnIndex('tea_type')) as string,
            origin: resultSet.getColumnValue(resultSet.getColumnIndex('origin')) as string,
            price: resultSet.getColumnValue(resultSet.getColumnIndex('price')) as number,
            rating: resultSet.getColumnValue(resultSet.getColumnIndex('rating')) as number
        };
    }
}

interface MonthlyData {
    count: number;
    totalExpense: number;
    avgPrice: number;
    avgRating: number;
    typeDistribution: Record<string, { count: number; expense: number }>;
    originDistribution: Record<string, { count: number; expense: number }>;
}

interface TeaRecord {
    id: number;
    teaType: string;
    origin: string;
    price: number;
    rating: number;
}

这个类提供了月度数据聚合功能。aggregateMonthlyData() 聚合指定月份的数据。aggregateRecords() 对记录进行聚合计算。

📝 总结

月度统计模块展示了如何在 Cordova 框架中实现月度数据统计和分析功能。通过 Web 层的用户界面和交互,结合原生层的高效数据聚合,为用户提供了详细的月度分析。

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

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐