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

在这里插入图片描述

📌 概述

福报排行模块展示用户在社区中的排名情况,包括总排行、周排行、月排行等多个维度。用户可以查看自己的排名、福报值、成就等信息,并与其他用户进行对比。这个模块涉及数据聚合、排序、分页等复杂的数据处理逻辑,通过 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 的结合,实现了高效的排行榜系统。关键技术包括:多维度数据聚合、分页加载、缓存机制、实时排名计算等。这些技术的综合应用,为用户提供了一个功能完整、性能优异的排行榜体验。

Logo

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

更多推荐