月度统计模块 Cordova 与 OpenHarmony 混合开发实战
月度统计模块通过集成Cordova和OpenHarmony技术,实现用户喝茶数据的月度分析功能。该功能允许用户选择月份查看喝茶记录、消费金额、平均评分等统计指标,并支持按茶叶类型和产地进行分类统计。系统通过HTML页面展示统计卡片和分布表格,JavaScript代码实现数据加载、计算和可视化呈现,同时记录操作日志。模块提供直观的数据聚合和对比分析能力,帮助用户了解喝茶习惯和消费情况。

📌 概述
月度统计模块提供了月度喝茶数据的统计分析功能。该模块集成了 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
更多推荐
所有评论(0)