福报排行模块 Cordova 与 OpenHarmony 混合开发实战
开源鸿蒙跨平台开发者社区推出福报排行模块,实现用户排名可视化展示。该模块采用Cordova与OpenHarmony结合的技术方案,支持总排行、周排行、月排行等多维度排名,包含数据聚合、分页加载和实时更新功能。前端通过HTML/CSS构建响应式界面,采用虚拟滚动优化性能,原生层处理复杂SQL查询并实现数据缓存。模块包含排行榜列表、分类筛选、分页控件和用户个人排名卡片等组件,通过事件通知机制实现排名变
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

📌 概述
福报排行模块展示用户在社区中的排名情况,包括总排行、周排行、月排行等多个维度。用户可以查看自己的排名、福报值、成就等信息,并与其他用户进行对比。这个模块涉及数据聚合、排序、分页等复杂的数据处理逻辑,通过 Cordova 与 OpenHarmony 的结合实现了高效的排行榜系统。
🔗 完整流程
第一部分:数据聚合与排序
排行榜需要从数据库中聚合所有用户的福报数据,按照不同的维度(总排行、周排行、月排行)进行统计和排序。原生层负责复杂的 SQL 查询和数据聚合,Web 层负责数据展示和用户交互。数据聚合过程中需要考虑性能优化,使用数据库索引加速查询。
第二部分:分页加载与缓存
排行榜数据量可能很大,需要实现分页加载机制。原生层支持分页查询,Web 层实现虚拟滚动或分页按钮。为了提高性能,原生层会缓存排行榜数据,定期更新。Web 层也会缓存已加载的数据,减少网络请求。
第三部分:实时更新与通知
当用户记录新的福报时,排行榜数据需要实时更新。通过事件通知机制,原生层通知 Web 层数据已更新,Web 层刷新排行榜。同时,如果用户的排名发生变化,可以显示排名变化提示。
🔧 Web 代码实现
排行榜 HTML 结构
<div class="ranking-container">
<div class="ranking-header">
<h1 class="ranking-title">福报排行榜</h1>
<div class="ranking-tabs">
<button class="tab-btn active" data-type="total">总排行</button>
<button class="tab-btn" data-type="week">周排行</button>
<button class="tab-btn" data-type="month">月排行</button>
</div>
</div>
<div class="ranking-filters">
<div class="filter-item">
<label>分类筛选:</label>
<select id="categoryFilter" class="filter-select">
<option value="">全部分类</option>
<option value="charity">慈善捐赠</option>
<option value="help">帮助他人</option>
<option value="volunteer">志愿服务</option>
</select>
</div>
</div>
<div class="ranking-list" id="rankingList">
<!-- 排行榜项目将动态插入这里 -->
</div>
<div class="ranking-pagination">
<button class="pagination-btn" id="prevBtn">上一页</button>
<span class="pagination-info" id="pageInfo">第 1 页</span>
<button class="pagination-btn" id="nextBtn">下一页</button>
</div>
<div class="user-rank-card">
<h3 class="card-title">你的排名</h3>
<div class="rank-info">
<div class="rank-item">
<span class="rank-label">排名</span>
<span class="rank-value" id="userRank">--</span>
</div>
<div class="rank-item">
<span class="rank-label">福报值</span>
<span class="rank-value" id="userBlessings">0</span>
</div>
<div class="rank-item">
<span class="rank-label">成就</span>
<span class="rank-value" id="userAchievements">0</span>
</div>
</div>
</div>
</div>
HTML 结构包含选项卡切换不同的排行榜类型、筛选器、排行榜列表、分页控件和用户排名卡片。使用语义化的元素和清晰的 class 命名。
排行榜样式
.ranking-container {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.ranking-header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.ranking-title {
font-size: 32px;
font-weight: bold;
margin: 0 0 20px 0;
}
.ranking-tabs {
display: flex;
justify-content: center;
gap: 10px;
}
.tab-btn {
padding: 10px 20px;
border: 2px solid white;
background: transparent;
color: white;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: bold;
}
.tab-btn.active {
background: white;
color: #667eea;
}
.tab-btn:hover {
transform: scale(1.05);
}
.ranking-filters {
background: white;
padding: 15px;
border-radius: 12px;
margin-bottom: 20px;
display: flex;
gap: 20px;
}
.filter-item {
display: flex;
align-items: center;
gap: 10px;
}
.filter-item label {
font-weight: bold;
color: #333;
}
.filter-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
cursor: pointer;
}
.ranking-list {
background: white;
border-radius: 12px;
overflow: hidden;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.ranking-item {
display: flex;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
transition: background 0.3s ease;
}
.ranking-item:hover {
background: #f9f9f9;
}
.ranking-item:last-child {
border-bottom: none;
}
.ranking-position {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 15px;
flex-shrink: 0;
}
.ranking-position.top3 {
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
color: #333;
}
.ranking-info {
flex: 1;
}
.ranking-name {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.ranking-detail {
font-size: 12px;
color: #999;
}
.ranking-value {
text-align: right;
font-size: 18px;
font-weight: bold;
color: #667eea;
min-width: 80px;
}
.ranking-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-bottom: 20px;
}
.pagination-btn {
padding: 10px 20px;
background: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
}
.pagination-btn:hover:not(:disabled) {
background: #f0f0f0;
transform: scale(1.05);
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-info {
color: white;
font-weight: bold;
}
.user-rank-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-title {
font-size: 18px;
font-weight: bold;
color: #333;
margin: 0 0 15px 0;
}
.rank-info {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.rank-item {
text-align: center;
padding: 15px;
background: #f5f7fa;
border-radius: 8px;
}
.rank-label {
display: block;
font-size: 12px;
color: #999;
margin-bottom: 8px;
}
.rank-value {
display: block;
font-size: 24px;
font-weight: bold;
color: #667eea;
}
CSS 使用渐变背景、阴影效果和过渡动画。排行榜项目在 hover 时改变背景色。前三名使用特殊的金色背景。响应式布局适配不同屏幕。
排行榜 JavaScript 逻辑
class RankingModule {
constructor() {
this.currentType = 'total';
this.currentPage = 1;
this.pageSize = 20;
this.data = [];
this.init();
}
init() {
this.setupEventListeners();
this.loadRankingData();
}
setupEventListeners() {
// 选项卡切换
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
this.currentType = e.target.dataset.type;
this.currentPage = 1;
this.loadRankingData();
});
});
// 分类筛选
document.getElementById('categoryFilter').addEventListener('change', () => {
this.currentPage = 1;
this.loadRankingData();
});
// 分页按钮
document.getElementById('prevBtn').addEventListener('click', () => {
if (this.currentPage > 1) {
this.currentPage--;
this.loadRankingData();
}
});
document.getElementById('nextBtn').addEventListener('click', () => {
this.currentPage++;
this.loadRankingData();
});
}
loadRankingData() {
const category = document.getElementById('categoryFilter').value;
cordova.exec(
(result) => {
this.data = result.items;
this.renderRankingList();
this.updatePagination(result.total);
this.loadUserRank();
},
(error) => {
console.error('加载排行榜失败:', error);
},
'RankingPlugin',
'getRankingData',
[{
type: this.currentType,
category: category,
page: this.currentPage,
pageSize: this.pageSize
}]
);
}
renderRankingList() {
const list = document.getElementById('rankingList');
list.innerHTML = '';
this.data.forEach((item, index) => {
const position = (this.currentPage - 1) * this.pageSize + index + 1;
const div = document.createElement('div');
div.className = 'ranking-item';
const positionClass = position <= 3 ? 'top3' : '';
div.innerHTML = `
<div class="ranking-position ${positionClass}">${position}</div>
<div class="ranking-info">
<div class="ranking-name">${item.name}</div>
<div class="ranking-detail">${item.category} · ${item.achievement}个成就</div>
</div>
<div class="ranking-value">${item.blessings}</div>
`;
list.appendChild(div);
});
}
updatePagination(total) {
const totalPages = Math.ceil(total / this.pageSize);
document.getElementById('pageInfo').textContent = `第 ${this.currentPage} 页`;
document.getElementById('prevBtn').disabled = this.currentPage <= 1;
document.getElementById('nextBtn').disabled = this.currentPage >= totalPages;
}
loadUserRank() {
cordova.exec(
(result) => {
document.getElementById('userRank').textContent = result.rank || '--';
document.getElementById('userBlessings').textContent = result.blessings || 0;
document.getElementById('userAchievements').textContent = result.achievements || 0;
},
(error) => {
console.error('加载用户排名失败:', error);
},
'RankingPlugin',
'getUserRank',
[{ type: this.currentType }]
);
}
}
// 初始化排行榜模块
const rankingModule = new RankingModule();
JavaScript 代码实现了选项卡切换、分类筛选、分页加载等功能。通过 Cordova 插件获取排行榜数据。动态渲染排行榜列表。
🔌 原生代码实现
OpenHarmony 排行榜插件
// RankingPlugin.ets
export class RankingPlugin {
private context: Context;
private rankingCache: Map<string, any> = new Map();
constructor(context: Context) {
this.context = context;
}
// 获取排行榜数据
getRankingData(params: any, callback: (data: any) => void): void {
try {
const { type, category, page, pageSize } = params;
const cacheKey = `${type}_${category}_${page}`;
// 检查缓存
if (this.rankingCache.has(cacheKey)) {
callback(this.rankingCache.get(cacheKey));
return;
}
// 从数据库查询
const result = this.queryRankingData(type, category, page, pageSize);
// 缓存结果
this.rankingCache.set(cacheKey, result);
callback(result);
} catch (error) {
console.error('获取排行榜数据失败:', error);
callback({ items: [], total: 0 });
}
}
private queryRankingData(type: string, category: string, page: number, pageSize: number): any {
const db = this.getDatabase();
let sql = '';
let params: any[] = [];
// 根据类型构建查询语句
if (type === 'total') {
sql = `
SELECT u.id, u.name, u.total_blessings as blessings,
COUNT(DISTINCT a.id) as achievement,
b.category
FROM users u
LEFT JOIN achievements a ON u.id = a.user_id AND a.unlocked = 1
LEFT JOIN blessings b ON u.id = b.user_id
${category ? 'WHERE b.category = ?' : ''}
GROUP BY u.id
ORDER BY u.total_blessings DESC
LIMIT ? OFFSET ?
`;
if (category) params.push(category);
} else if (type === 'week') {
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
sql = `
SELECT u.id, u.name,
SUM(b.amount) as blessings,
COUNT(DISTINCT a.id) as achievement,
b.category
FROM users u
LEFT JOIN blessings b ON u.id = b.user_id AND b.created_at > ?
LEFT JOIN achievements a ON u.id = a.user_id AND a.unlocked = 1
${category ? 'WHERE b.category = ?' : ''}
GROUP BY u.id
ORDER BY blessings DESC
LIMIT ? OFFSET ?
`;
params.push(oneWeekAgo);
if (category) params.push(category);
} else if (type === 'month') {
const oneMonthAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
sql = `
SELECT u.id, u.name,
SUM(b.amount) as blessings,
COUNT(DISTINCT a.id) as achievement,
b.category
FROM users u
LEFT JOIN blessings b ON u.id = b.user_id AND b.created_at > ?
LEFT JOIN achievements a ON u.id = a.user_id AND a.unlocked = 1
${category ? 'WHERE b.category = ?' : ''}
GROUP BY u.id
ORDER BY blessings DESC
LIMIT ? OFFSET ?
`;
params.push(oneMonthAgo);
if (category) params.push(category);
}
params.push(pageSize);
params.push((page - 1) * pageSize);
const items = db.query(sql, params);
// 查询总数
const countSql = `
SELECT COUNT(DISTINCT u.id) as total
FROM users u
LEFT JOIN blessings b ON u.id = b.user_id
${category ? 'WHERE b.category = ?' : ''}
`;
const countParams = category ? [category] : [];
const total = db.query(countSql, countParams)[0]?.total || 0;
return { items, total };
}
// 获取用户排名
getUserRank(params: any, callback: (data: any) => void): void {
try {
const { type } = params;
const userId = this.getUserId();
const result = this.queryUserRank(type, userId);
callback(result);
} catch (error) {
console.error('获取用户排名失败:', error);
callback({ rank: 0, blessings: 0, achievements: 0 });
}
}
private queryUserRank(type: string, userId: string): any {
const db = this.getDatabase();
let sql = '';
let params: any[] = [userId];
if (type === 'total') {
sql = `
SELECT COUNT(*) as rank FROM users
WHERE total_blessings > (SELECT total_blessings FROM users WHERE id = ?)
`;
} else if (type === 'week') {
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
sql = `
SELECT COUNT(*) as rank FROM (
SELECT u.id, SUM(b.amount) as total
FROM users u
LEFT JOIN blessings b ON u.id = b.user_id AND b.created_at > ?
GROUP BY u.id
) t
WHERE total > (
SELECT SUM(b.amount) FROM blessings b
WHERE b.user_id = ? AND b.created_at > ?
)
`;
params = [oneWeekAgo, userId, oneWeekAgo];
}
const rankResult = db.query(sql, params)[0] || { rank: 0 };
const userResult = db.query(
'SELECT total_blessings as blessings FROM users WHERE id = ?',
[userId]
)[0] || { blessings: 0 };
const achievementResult = db.query(
'SELECT COUNT(*) as count FROM achievements WHERE user_id = ? AND unlocked = 1',
[userId]
)[0] || { count: 0 };
return {
rank: rankResult.rank + 1,
blessings: userResult.blessings,
achievements: achievementResult.count
};
}
private getDatabase(): any {
// 获取数据库实例
return null;
}
private getUserId(): string {
// 获取当前用户ID
return '';
}
}
原生代码实现了复杂的 SQL 查询,支持多种排行榜类型。使用缓存机制提高性能。数据库查询使用参数化防止注入。
📝 总结
福报排行模块展示了复杂数据处理的实现。通过 Cordova 与 OpenHarmony 的结合,实现了高效的排行榜系统。关键技术包括:多维度数据聚合、分页加载、缓存机制、实时排名计算等。这些技术的综合应用,为用户提供了一个功能完整、性能优异的排行榜体验。
更多推荐

所有评论(0)