123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- <template>
- <view class="min-h-[100vh]" :style="themeColor()">
- <!-- #ifdef MP-WEIXIN -->
- <top-tabbar :data="param" :isFill="false" />
- <!-- #endif -->
- <!-- 顶部图片 -->
- <view class="rank-head">
- <image class="w-[100%] h-[435rpx]" :src="img(rankConfig.rank_images)" mode="aspectFill"></image>
- <view class="content-box">
- <!-- 榜单分类按钮 -->
- <scroll-view scroll-x="true" class="category-slider" scroll-with-animation :scroll-into-view="'id' + activeIndex">
- <view class="category-con" :style="{ justifyContent: centered ? 'center' : 'flex-start' }">
- <view class="category-btn" v-for="(item, index) in rankList" :key="index" :id="'id' + index" @click="selectCategory(item, index)"
- :style="{ color: activeIndex === index ? rankConfig.select_color : rankConfig.no_color, background: activeIndex === index ? `linear-gradient(to right, ${rankConfig.select_bg_color_start}, ${rankConfig.select_bg_color_end})`: 'transparent'}">
- <view>{{ item.name }}</view>
- </view>
- </view>
- </scroll-view>
- <!-- <view class="content">
- <text class="text-[26rpx]">{{rankConfig.rank_name}}</text>
- </view> -->
- </view>
- <view class="side-tab" :style="{ top: topStyle }" @click="rankPopup = true" v-if="rankConfig.rank_remark">
- <text class="iconfont icona-paihangbangpc30 icon"></text>
- <text class="desc">{{ t('rankingRules') }}</text>
- </view>
- </view>
- <view class="rank-list p-[20rpx] relative -mt-[42rpx]">
- <!-- 列表 -->
- <mescroll-body ref="mescrollRef" :height="listHeight" @init="mescrollInit" :down="{ use: false }" @up="getRankGoodsListFn">
- <view class="bg-[#fff] flex rounded-[var(--rounded-mid)] p-[20rpx]"
- v-for="(item,index) in rankGoodsList" :key="item.goods_id"
- :class="{'mb-[20rpx]': (rankGoodsList.length-1) != index}" v-if="rankGoodsList.length"
- @click="toLink(item.goods_id)">
- <view class="w-[240rpx] h-[240rpx] flex items-center justify-center relative">
- <!-- 榜单排名图片 -->
- <image v-if="index < 5" class="absolute top-[7rpx] left-[10rpx] w-[50rpx] h-[58rpx]" :style="{ zIndex:9 }" :src="getRankBadge(item.rank_num)" mode="aspectFill"></image>
- <view class="absolute top-[15rpx] left-[10rpx] flex items-center justify-center w-[50rpx] h-[50rpx]" v-if="index < 5" :style="{ zIndex: 10 }">
- <text class="text-[24rpx] font-bold text-[#fff]">{{ index + 1 }}</text>
- </view>
- <image v-if="item.goods_cover_thumb_mid" class="w-[250rpx] h-[250rpx] rounded-[var(--rounded-mid)]"
- :src="img(item.goods_cover_thumb_mid)" :mode="'aspectFill'"
- @error="item.goods_cover_thumb_mid='static/resource/images/diy/shop_default.jpg'" />
- <image class="w-[240rpx] h-[240rpx] rounded-[var(--rounded-mid)]" v-else
- :src="img('static/resource/images/diy/shop_default.jpg')" :mode="'aspectFill'" />
- </view>
- <view class="flex flex-col flex-1 justify-between ml-[20rpx] pt-[4rpx]">
- <view class="text-[28rpx] text-[#333] leading-[40rpx] multi-hidden mb-[10rpx]">
- <view class="brand-tag" v-if="item.goods_brand" :style="diyGoods.baseTagStyle(item.goods_brand)">
- {{ item.goods_brand.brand_name }}
- </view>
- {{ item.goods_name }}
- </view>
- <view v-if="item.goods_label_name && item.goods_label_name.length" class="flex flex-wrap">
- <template v-for="(tagItem, tagIndex) in item.goods_label_name">
- <image class="img-tag" v-if="tagItem.style_type == 'icon' && tagItem.icon" :src="img(tagItem.icon)" mode="heightFix" @error="diyGoods.error(tagItem,'icon')" />
- <view class="base-tag" v-else-if="tagItem.style_type == 'diy' || !tagItem.icon" :style="diyGoods.baseTagStyle(tagItem)">{{ tagItem.label_name }}</view>
- </template>
- </view>
- <view class="flex items-center justify-between">
- <view class="text-[var(--price-text-color)] price-font flex items-baseline">
- <text class="text-[24rpx] font-500">¥</text>
- <text class="text-[40rpx] font-500">{{ diyGoods.goodsPrice(item).toFixed(2).split('.')[0] }}</text>
- <text class="text-[24rpx] font-500">.{{ diyGoods.goodsPrice(item).toFixed(2).split('.')[1] }}</text>
- </view>
- <view :id="'itemCart' + index" class="w-[102rpx] box-border text-center text-[#fff] primary-btn-bg h-[46rpx] text-[22rpx] leading-[46rpx] rounded-[100rpx]">去购买</view>
- </view>
- </view>
- </view><mescroll-empty v-if="!rankGoodsList.length && loading" :option="{tip : '暂无商品', btnText:'去逛逛'}"
- @emptyclick="redirect({ url: '/addon/mall/pages/goods/list'})"></mescroll-empty>
- </mescroll-body>
- <view @touchmove.prevent.stop>
- <u-popup :show="rankPopup" @close="closeFn" mode="center" round="var(--rounded-big)">
- <view class="w-[570rpx] px-[32rpx] popup-common center">
- <view class="title">{{ t('rankingRules') }}</view>
- <scroll-view :scroll-y="true" class="px-[30rpx] box-border max-h-[260rpx]">
- <view class="text-[28rpx] leading-[40rpx] mb-[20rpx]">{{ rankConfig.rank_remark }}</view>
- </scroll-view>
- <view class="btn-wrap !pt-[40rpx]">
- <button class="primary-btn-bg w-[480rpx] h-[70rpx] text-[26rpx] leading-[70rpx] rounded-[35rpx] !text-[#fff] font-500" @click="rankPopup = false">我知道了</button>
- </view>
- </view>
- </u-popup>
- </view>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { reactive, ref, computed, nextTick } from 'vue'
- import { t } from '@/locale'
- import { redirect, img, pxToRpx } from '@/utils/common';
- import { getRankList, getRankGoodsList, getRankConfig } from '@/addon/mall/api/rank';
- import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue';
- import useMescroll from '@/components/mescroll/hooks/useMescroll.js';
- import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue'
- import { onLoad, onPageScroll, onReachBottom } from '@dcloudio/uni-app';
- import { topTabar } from '@/utils/topTabbar'
- import { useGoods } from '@/addon/mall/hooks/useGoods'
- const diyGoods = useGoods();
- const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom);
- const mescrollRef = ref(null);
- const loading = ref<boolean>(false);
- // 获取系统状态栏的高度
- let menuButtonInfo: any = {};
- // 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容)
- // #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
- menuButtonInfo = uni.getMenuButtonBoundingClientRect();
- // #endif
- /********* 自定义头部 - start ***********/
- const topTabarObj = topTabar()
- let param = topTabarObj.setTopTabbarParam({ title: '' })
- const topStyle = computed(() => {
- let style = pxToRpx(Number(menuButtonInfo.height) + menuButtonInfo.top + 8) + 30 + 'rpx;'
- return style
- })
- /********* 自定义头部 - end ***********/
- // 获取系统信息
- const systemInfo = uni.getSystemInfoSync();
- const topImageHeight = 450;
- const screenHeight = systemInfo.windowHeight;
- // 将屏幕高度转换为 rpx
- const screenHeightInRpx = (screenHeight / systemInfo.screenWidth) * 750;
- // 计算列表高度
- const listHeight = computed(() => {
- const listHeightValue = screenHeightInRpx - topImageHeight;
- return `${ listHeightValue }rpx`;
- });
- /**************** 榜单规则 ********************/
- const rankPopup = ref(false)
- const closeFn = () => {
- rankPopup.value = false
- }
- const rankList = ref<Array<any>>([]);
- const rankGoodsList = ref<Array<any>>([]);
- const centered = ref(false); // 是否居中
- const calculateCentered = () => {
- nextTick(() => {
- const query = uni.createSelectorQuery();
- query.selectAll('.category-btn').boundingClientRect((rects) => {
- if (rects && rects.length > 0) {
- const totalWidth = rects.reduce((sum, rect) => sum + rect.width, 0);
- const screenWidth = uni.getSystemInfoSync().windowWidth;
- centered.value = totalWidth <= screenWidth; // 判断是否需要居中
- } else {
- console.error('Failed to get .category-btn elements.');
- }
- })
- .exec();
- });
- };
- // 加载分类数据
- const getRankListFn = (isFirstLoad = false) => {
- getRankList().then((res) => {
- rankList.value = res.data
- // 仅在首次加载时选择第一个分类
- if (rankId.value) {
- for (let i = 0; i < rankList.value.length; i++) {
- if (rankId.value == rankList.value[i].rank_id) {
- selectCategory(rankList.value[i], i);
- break;
- }
- }
- } else if (isFirstLoad && rankList.value && rankList.value.length) {
- selectCategory(rankList.value[0], 0);
- } else if (!rankList.value.length) {
- loading.value = true;
- }
- calculateCentered();
- }).catch((error) => {
- console.error("加载分类数据失败", error);
- })
- };
- const rankConfig = reactive({});
- // 榜单设置
- const getRankConfigFn = () => {
- getRankConfig().then((res: any) => {
- Object.assign(rankConfig, res.data); // 合并新数据
- });
- };
- // 当前选中的分类的索引
- const activeIndex = ref(0)
- // 当前选中的分类的rank_id
- const rankId = ref(0)
- // 当前选中的分类的goods_source
- const goodsSource = ref()
- // 点击分类按钮时,更新选中的分类
- function selectCategory(rank: any, index: any) {
- loading.value = false;
- // 清空之前选中的商品列表
- rankGoodsList.value = [];
- activeIndex.value = index
- rankId.value = rank.rank_id
- goodsSource.value = rank.goods_source
- getMescroll()?.resetUpScroll();
- }
- //获取榜单商品
- const getRankGoodsListFn = (mescroll: any) => {
- if (rankList.value.length == 0) return;
- loading.value = false;
- let data: object = {
- page: mescroll.num,
- limit: mescroll.size,
- rank_id: rankId.value
- };
- getRankGoodsList(data).then((res: any) => {
- let newArr = (res.data.data as Array<Object>).map((el: any) => {
- return el
- })
- let ifPage = true
- //设置列表数据
- if (mescroll.num == 1) {
- rankGoodsList.value = []; //如果是第一页需手动制空列表
- }
- rankGoodsList.value = rankGoodsList.value.concat(newArr);
- if (goodsSource.value == 'goods') {
- ifPage = false
- } else {
- ifPage = true
- }
- mescroll.endSuccess(newArr.length, ifPage);
- loading.value = true;
- }).catch(() => {
- loading.value = true;
- mescroll.endErr(); // 请求失败, 结束加载
- })
- }
- // 跳转商品详情
- const toLink = (goods_id: any) => {
- redirect({ url: '/addon/mall/pages/goods/detail', param: { goods_id } })
- }
- // 获取排行榜名次图片的函数
- function getRankBadge(sort: any) {
- switch (sort) {
- case 1:
- return img('addon/mall/rank/rank_first.png');
- case 2:
- return img('addon/mall/rank/rank_second.png');
- case 3:
- return img('addon/mall/rank/rank_third.png');
- default:
- return img('addon/mall/rank/rank.png');
- }
- }
- onLoad(async(option: any) => {
- rankId.value = option.rank_id || 0;
- getRankConfigFn()
- getRankListFn(true)
- })
- </script>
- <style lang="scss" scoped>
- @import '@/addon/mall/styles/common.scss';
- .rank-head {
- position: relative;
- .content-box {
- width: 100%;
- position: absolute;
- top: 328rpx;
- .category-slider {
- width: 95%;
- margin: 0 auto;
- line-height: 100rpx;
- white-space: nowrap;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- .category-con {
- width: 100%;
- display: flex;
- align-items: center;
- .category-btn {
- display: inline-block;
- padding: 0 20rpx;
- height: 55rpx;
- text-align: center;
- line-height: 55rpx;
- justify-content: center;
- align-items: center;
- border-radius: 40rpx;
- font-size: 24rpx;
- margin-right: 20rpx;
- }
- }
- }
- // .content {
- // display: flex;
- // justify-content: center;
- // align-items: center;
- // border-radius: 30rpx;
- // padding: 0 20rpx;
- // height: 44rpx;
- // font-size: 26rpx;
- // color: var(--primary-color);
- // background: linear-gradient(to right, #FFEBD7, #FFFFFF, #FFEBD7);
- // }
- }
- }
- .rank-list {
- background: #F8F8F8;
- border-radius: 34rpx 34rpx 0 0;
- .bank-buying {
- width: 100rpx;
- height: 44rpx;
- border-radius: 10rpx;
- font-family: PingFang SC, PingFang SC;
- font-weight: 500;
- font-size: 24rpx;
- color: #FFFFFF;
- line-height: 44rpx;
- text-align: center;
- }
- }
- </style>
|