更新记录

1.0.0(2026-09-15) 下载此版本

初版


平台兼容性

其他

多语言 暗黑模式 宽屏模式

SL-WX-RankList 横向滚动排行榜组件

组件简介

基于 scroll-view 实现的横向滚动分页式排行榜组件。内置「主列 + peek 提示」布局:每列宽度等于容器宽度 × leftRatio(默认 2/3),右侧自然露出 1 - leftRatio(1/3)部分作为"还有更多"的视觉提示。滑动松手后自动吸附到最近列边界(snap)。H5 / 微信小程序通用。

设计说明:组件只负责横向分页骨架(按 rowsPerColumn 自动分列、列宽计算、snap 吸附),每张卡片的内容完全由调用方通过默认插槽自定义,灵活适配「小说榜」「主播榜」「热销榜」等不同业务场景。

目录结构

components/SL-WX-RankList
└── SL-WX-RankList.vue

核心特性

  • 主列 + peek 提示布局:每列宽度 = 容器实际宽度 × leftRatio,右侧露出 1 - leftRatio 部分暗示后续内容,无需额外配置即可呈现"分页浏览"的视觉。

  • 滑动后自动吸附:松手停止滚动后 150ms 防抖,计算 Math.round(scrollLeft / columnWidth) 得到最近列索引,通过 scroll-into-view 平滑滚动到目标列。动画期间锁住 scroll 事件 450ms,避免抖动。

  • 自动分列:根据 rowsPerColumnlist 自动切成多列(每列 N 项),无需业务侧预处理。

  • 统一卡片高度:所有卡片高度一致(cardHeight),列内卡片纵向排列,列间通过 gap 控制间距。

  • 内容完全自定义:默认插槽透传 item / index / columnIndex / rowIndex 四个作用域参数,调用方自由渲染封面、排名徽章、标题、分类标签、热度数据等。

  • 标题栏可选title / subtitle 可选,传入则在顶部渲染标题与副标题。

  • 外部跳转 API:暴露 scrollToColumn(index) 方法,可通过 $refs 控制跳转到指定列。

Props

属性 类型 默认值 说明
list Array [] 排行榜数据列表,每项结构由业务自定义(通常含 cover / title / value 等字段)
title String '' 标题,传入则在顶部渲染标题栏(不传则不渲染标题栏)
subtitle String '' 副标题(右上角灰色小字,如更新时间)
cardHeight Number 280 卡片高度(rpx),所有卡片统一高度
gap Number 16 列间距(rpx)
rowsPerColumn Number 4 每列行数,组件据此自动分列
leftRatio Number 2/3 主列占比(0~1),主列宽度 = 容器宽度 × leftRatio,右侧露出 1 - leftRatio
snap Boolean true 是否启用滑动后吸附到最近列

Events

事件名 回调参数 说明
card-tap { item, index } 点击卡片时触发,返回当前项数据与全局索引
onCardTap({ item, index }) {
  console.log('第' + (index + 1) + '位', item)
}

Slots

插槽名 作用域参数 说明
default { item, index, columnIndex, rowIndex } 自定义每张卡片内容。item 为当前项数据,index 为全局索引(0 起),columnIndex 为列索引,rowIndex 为列内行索引

对外方法

<sl-wx-rank-list ref="rank" :list="list" />
方法名 参数 说明
scrollToColumn index 跳转到指定列(0 起),带平滑动画
this.$refs.rank.scrollToColumn(2)  // 跳到第 3 列

使用示例

示例 1:基础排行榜(封面 + 排名徽章 + 标题 + 热度)

<template>
    <sl-wx-rank-list
        :list="list"
        title="热读榜 · 网文精选"
        subtitle="09-12 更新"
        :card-height="300"
        :rows-per-column="4"
        :left-ratio="2/3"
        @card-tap="onCardTap"
    >
        <template v-slot:default="{ item, index }">
            <view class="rank-card">
                <view class="rank-cover-wrap">
                    <image class="rank-cover" :src="item.cover" mode="aspectFill" />
                    <view class="rank-badge" :class="badgeClass(index)">
                        <text>{{ index + 1 }}</text>
                    </view>
                </view>
                <view class="rank-info">
                    <text class="rank-title">{{ item.title }}</text>
                    <view class="rank-meta">
                        <view class="rank-tag">{{ item.category }}</view>
                        <text class="rank-value">{{ item.value }}</text>
                    </view>
                </view>
            </view>
        </template>
    </sl-wx-rank-list>
</template>

<script>
import SlWxRankList from '@/components/SL-WX-RankList/SL-WX-RankList.vue'

export default {
    components: { SlWxRankList },
    data() {
        return {
            list: [
                { cover: 'https://picsum.photos/seed/n1/300/400', title: '开局5000万,让我办学校??', category: '都市脑洞', value: '9262万在读' },
                { cover: 'https://picsum.photos/seed/n2/300/400', title: '重生S2:调教电竞圈!', category: '游戏体育', value: '9066万在读' },
                { cover: 'https://picsum.photos/seed/n3/300/400', title: '破产三轮车厂,被我干成万亿巨头', category: '都市种田', value: '8971万在读' },
                { cover: 'https://picsum.photos/seed/n4/300/400', title: '全城围捕:我真不是罪犯啊', category: '悬疑灵异', value: '24个老书虫在看' },
                { cover: 'https://picsum.photos/seed/n5/300/400', title: '诸神要我献祭反手献祭神', category: '都市脑洞', value: '87 追读' },
                { cover: 'https://picsum.photos/seed/n6/300/400', title: '玩家每日一戏出完整版', category: '都市脑洞', value: '84 追读' },
                { cover: 'https://picsum.photos/seed/n7/300/400', title: '修仙:从黑始崛起', category: '东方仙侠', value: '81 追读' },
                { cover: 'https://picsum.photos/seed/n8/300/400', title: '全民部落求活着就能变', category: '游戏体育', value: '81 追读' }
            ]
        }
    },
    methods: {
        onCardTap({ item, index }) {
            uni.showToast({ title: `第 ${index + 1} 位 · ${item.title}`, icon: 'none' })
        },
        badgeClass(index) {
            if (index === 0) return 'gold'
            if (index === 1) return 'silver'
            if (index === 2) return 'bronze'
            return ''
        }
    }
}
</script>

示例 2:调整主列占比(1/2 / 3/4)

<!-- 主列占 1/2,右侧露出 1/2 -->
<sl-wx-rank-list :list="list" :left-ratio="0.5" />

<!-- 主列占 3/4,右侧露出 1/4 -->
<sl-wx-rank-list :list="list" :left-ratio="0.75" />

示例 3:关闭吸附(自由滚动)

<sl-wx-rank-list :list="list" :snap="false" />

示例 4:外部控制跳转

<template>
    <view>
        <view class="btns">
            <button @tap="$refs.rank.scrollToColumn(0)">第 1 列</button>
            <button @tap="$refs.rank.scrollToColumn(1)">第 2 列</button>
            <button @tap="$refs.rank.scrollToColumn(2)">第 3 列</button>
        </view>
        <sl-wx-rank-list ref="rank" :list="list" :rows-per-column="4" />
    </view>
</template>

示例 5:每列 3 行 + 紧凑布局

<sl-wx-rank-list
    :list="list"
    :rows-per-column="3"
    :card-height="240"
    :gap="12"
    :left-ratio="0.6"
/>

布局原理简述

  1. 容器宽度测量mounted 时通过 uni.createSelectorQuery 测量 .sl-ranklist__scroll 的实际宽度(px),兜底使用 windowWidth

  2. 列宽计算columnWidth = floor(containerWidth × leftRatio),保证右列露出 1 - leftRatio 部分。

  3. 自动分列computed.columnsrowsPerColumnlist 切成二维数组,每列 N 项,最后一列可能不满。

  4. snap 吸附@scroll 防抖 150ms,停止后 nearestIndex = round(scrollLeft / columnWidth),通过 scroll-into-view + 列 id(sl-ranklist-col-N)平滑滚动。动画期间 isAnimating 锁住 scroll 事件 450ms 避免冲突。

  5. 数据变化回首页watch list 触发后 $nextTick 自动 snapToIndex(0),回到第一列。

注意事项

  1. 容器宽度依赖测量:组件 mounted 后测量容器宽度,若父级容器宽度延迟生效(如 v-if 切换、动画展开),需在宽度就绪后调用 scrollToColumn(0) 重新吸附。

  2. leftRatio 取值范围:建议 0.5 ~ 0.8。过小(如 0.3)主列太窄内容拥挤,过大(如 0.9)右侧 peek 提示不明显失去"还有更多"的视觉意义。

  3. rowsPerColumn 与数据量:列数 = ceil(list.length / rowsPerColumn)。数据量少时列数少,横向滚动距离短;数据量大时建议配合 rowsPerColumn = 4 ~ 6 控制列数。

  4. snap 与自由滚动snap = false 时组件不再吸附,可自由停在任意位置;适合"连续浏览"场景。

  5. 卡片高度统一:所有卡片高度一致(cardHeight),如需差异化高度,请在插槽内通过内部布局(如封面固定高度 + 信息区 flex 撑满)实现视觉差异,不要试图让卡片本身高度不一致。

  6. scroll-into-view 限制:吸附依赖 scroll-into-view + 元素 id,小程序要求 id 不能以数字开头,组件已统一使用 sl-ranklist-col- 前缀。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。