| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- <!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
- <route lang="json5" type="home">
- {
- layout: 'tabbar',
- style: {
- // 'custom' 表示开启自定义导航栏,默认 'default'
- navigationStyle: 'custom'
- }
- }
- </route>
- <script lang="ts" setup>
- // 必须导入需要用到的页面生命周期(即使在当前页面上没有直接使用到)
- // eslint-disable-next-line unused-imports/no-unused-imports
- import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
- import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
- import { advList, bannerList, noticeUnread } from '@/api/common'
- import { getList } from '@/api/product'
- import { toPage } from '@/utils/page'
- defineOptions({
- name: 'Index', // 首页
- })
- // 获取屏幕边界到安全区域距离
- const systemInfo = uni.getSystemInfoSync()
- const safeAreaInsets = systemInfo.safeAreaInsets
- // z-paging
- const paging = ref(null)
- // 类似mixins,如果是页面滚动务必要写这一行,并传入当前ref绑定的paging,注意此处是paging,而非paging.value
- useZPaging(paging)
- // 轮播图
- const current = ref<number>(0)
- const swiperList = ref([])
- function handleSwiperClick(e: any) {
- if (e.item.linkType === 0) {
- toPage({ url: e.item.link })
- }
- else {
- toPage({ url: '/pages/webLink/webLink', params: { title: e.item.title, link: e.item.link } })
- }
- }
- // 导航图标
- const navIcons = ref([
- {
- image: '/static/icons/mission-center.png',
- title: 'home.missionCenter',
- size: '100rpx',
- url: '/pages/missionCenter/missionCenter',
- },
- {
- image: '/static/icons/refer-earn.png',
- title: 'home.refer&earn',
- size: '100rpx',
- url: '/pages/referEarn/referEarn',
- },
- {
- image: '/static/icons/vip-membership.png',
- title: 'home.vip',
- size: '112rpx',
- url: '/pages/vipMembership/vipMembership',
- },
- {
- image: '/static/icons/best-sellers.png',
- title: 'home.bestSellers',
- size: '100rpx',
- url: '/pages/bestSellers/bestSellers',
- },
- {
- image: '/static/icons/top-champions.png',
- title: 'home.topChampions',
- size: '100rpx',
- url: '/pages/topChampions/topChampions',
- },
- ])
- // 新品列表
- const newProducts = ref<any>([])
- async function getNewList() {
- const res = await getList({ page: 1, size: 20, sort: 'CREATE_DESC' })
- console.log(res)
- newProducts.value = res.data.list
- }
- // 商品列表
- const priceTab = ref<string>('300')
- // 300Spot 500Spot 1000Spot 2000Spot
- const priceTabList = ref([
- {
- title: 'home.priceTab.300spot',
- value: '300',
- minPrice: 0,
- maxPrice: 300,
- },
- {
- title: 'home.priceTab.500spot',
- value: '500',
- minPrice: 300,
- maxPrice: 500,
- },
- {
- title: 'home.priceTab.1000spot',
- value: '1000',
- minPrice: 500,
- maxPrice: 1000,
- },
- {
- title: 'home.priceTab.2000spot',
- value: '2000',
- minPrice: 1000,
- maxPrice: 2000,
- },
- {
- title: 'home.priceTab.3000spot',
- value: '3000',
- minPrice: 2000,
- maxPrice: 3000,
- },
- ])
- const dataList = ref<any>([])
- const isProductListLoading = ref(false) // 商品列表加载状态
- function handlePriceTabChange() {
- // 获取 tabs 元素位置,确保切换后页面位置在 tabs 位置
- uni.createSelectorQuery()
- .select('.productList')
- .boundingClientRect((rect: any) => {
- if (rect) {
- uni.createSelectorQuery()
- .selectViewport()
- .scrollOffset((scrollRes: any) => {
- const currentScrollTop = scrollRes?.scrollTop || 0
- const tabsTop = currentScrollTop + rect.top
- // reload 数据
- paging.value?.reload()
- // 延迟滚动到 tabs 位置
- setTimeout(() => {
- uni.pageScrollTo({
- scrollTop: tabsTop - 40,
- duration: 0,
- })
- }, 100)
- })
- .exec()
- }
- })
- .exec()
- }
- async function queryList(pageNo: number, pageSize: number) {
- // 如果是第一页,显示骨架屏
- if (pageNo === 1) {
- isProductListLoading.value = true
- }
- try {
- const params = {
- page: pageNo,
- size: pageSize,
- sort: 'SALES_DESC',
- price: priceTab.value,
- }
- const res = await getList(params)
- paging.value.complete(res.data.list)
- }
- finally {
- if (pageNo === 1) {
- isProductListLoading.value = false
- }
- }
- }
- async function getBannerList() {
- const res = await bannerList({ page: 1, size: 20 })
- swiperList.value = res.data.list
- }
- const unread = ref(0)
- const isPageLoading = ref(true) // 页面加载状态
- // 创建一个通用方法来处理所有初始数据加载
- async function loadData() {
- try {
- isPageLoading.value = true
- await Promise.all([
- getUnread(),
- getNewList(),
- getBannerList(),
- ])
- }
- finally {
- isPageLoading.value = false
- }
- }
- async function getUnread() {
- try {
- const res = await noticeUnread()
- if (res.code === '200') {
- unread.value = Number(res.data) || 0
- }
- }
- catch {}
- }
- const curtain = reactive({
- show: false,
- img: '',
- link: '',
- linkType: '', // IN OUT
- title: '',
- })
- async function getCurtain() {
- try {
- const res = await advList({ advType: 'INDEX' })
- if (res.code === '200' && res.data.length) {
- curtain.show = true
- curtain.img = res.data[0].advImage
- curtain.link = res.data[0].link
- curtain.linkType = res.data[0].linkType
- curtain.title = res.data[0].title
- }
- }
- catch {}
- }
- function curtainClick() {
- if (curtain.linkType === 'IN') {
- toPage({ url: curtain.link })
- }
- else {
- toPage({ url: '/pages/webLink/webLink', params: { title: curtain.title, link: curtain.link } })
- }
- }
- onLoad(async () => {
- getCurtain()
- await loadData()
- })
- onShow(() => {
- getUnread()
- })
- </script>
- <template>
- <z-paging ref="paging" v-model="dataList" :auto-scroll-to-top-when-reload="false" :auto-clean-list-when-reload="false" use-page-scroll @query="queryList" @on-refresh="loadData">
- <template #top>
- <view
- class="flex items-center justify-between bg-white pb-40rpx pl-42rpx pr-34rpx pt-26rpx"
- :style="{ paddingTop: `${safeAreaInsets?.top + 13}px` }"
- >
- <image src="/static/header-logo.png" class="h-44rpx w-275rpx" />
- <view class="flex items-center">
- <wd-badge :model-value="0">
- <image
- src="/static/icons/search.png"
- class="mr-20rpx h-40rpx w-40rpx"
- @click="toPage({ url: '/pages/search/search' })"
- />
- </wd-badge>
- <wd-badge :model-value="unread" :max="99">
- <image
- src="/static/icons/notifications.png"
- class="h-40rpx w-40rpx"
- @click="toPage({ url: '/pages/notifications/notifications' })"
- />
- </wd-badge>
- </view>
- </view>
- </template>
- <!-- 页面加载时显示骨架屏 -->
- <template v-if="isPageLoading">
- <!-- 轮播图骨架屏 -->
- <wd-skeleton
- :row-col="[{ height: '400rpx' }]"
- animation="gradient"
- />
- <view class="px-24rpx pb-24rpx">
- <!-- 导航图标区域骨架屏 -->
- <view class="pb-22rpx pt-24rpx">
- <wd-skeleton
- :row-col="[
- [
- { width: '100rpx', height: '100rpx', type: 'circle' },
- { width: '100rpx', height: '100rpx', type: 'circle' },
- { width: '100rpx', height: '100rpx', type: 'circle' },
- { width: '100rpx', height: '100rpx', type: 'circle' },
- { width: '100rpx', height: '100rpx', type: 'circle' },
- ],
- [
- { width: '60rpx', height: '24rpx' },
- { width: '60rpx', height: '24rpx' },
- { width: '60rpx', height: '24rpx' },
- { width: '60rpx', height: '24rpx' },
- { width: '60rpx', height: '24rpx' },
- ],
- ]"
- animation="gradient"
- />
- </view>
- <!-- 新品区域骨架屏 -->
- <view class="mb-32rpx">
- <wd-skeleton
- :row-col="[
- { width: '120rpx', height: '32rpx', marginBottom: '16rpx' }, // 标题
- [
- { width: '260rpx', height: '260rpx' },
- { width: '260rpx', height: '260rpx', marginLeft: '16rpx' },
- { width: '260rpx', height: '260rpx', marginLeft: '16rpx' },
- ],
- ]"
- animation="gradient"
- />
- </view>
- <!-- 价格分类标签骨架屏 -->
- <view class="mb-20rpx">
- <wd-skeleton
- :row-col="[
- [
- { width: '80rpx', height: '36rpx' },
- { width: '80rpx', height: '36rpx', marginLeft: '20rpx' },
- { width: '80rpx', height: '36rpx', marginLeft: '20rpx' },
- { width: '80rpx', height: '36rpx', marginLeft: '20rpx' },
- { width: '80rpx', height: '36rpx', marginLeft: '20rpx' },
- ],
- ]"
- animation="gradient"
- />
- </view>
- <!-- 商品列表骨架屏 -->
- <view class="grid grid-cols-2 gap-20rpx">
- <wd-skeleton
- v-for="i in 6"
- :key="i"
- :row-col="[
- { height: '340rpx' }, // 商品图片
- { width: '180rpx', height: '40rpx', marginTop: '10rpx' }, // 商品名称
- [
- { width: '80rpx', height: '24rpx' }, // 价格
- { width: '60rpx', height: '20rpx' }, // 销量
- ],
- ]"
- animation="gradient"
- />
- </view>
- </view>
- </template>
- <!-- 实际内容 -->
- <template v-else>
- <wd-swiper
- v-model:current="current"
- :list="swiperList"
- value-key="image"
- autoplay
- indicator
- indicator-position="bottom-right"
- image-mode="aspectFill"
- height="388rpx"
- @click="handleSwiperClick"
- />
- <view class="px-24rpx pb-24rpx">
- <view class="flex items-end justify-between pb-22rpx pt-24rpx">
- <view
- v-for="(item, index) in navIcons"
- :key="index"
- class="flex flex-col items-center"
- @click="toPage({ url: item.url })"
- >
- <image :src="item.image" :style="`width: ${item.size}; height: ${item.size};`" />
- <view class="mt-14rpx whitespace-pre-line text-center text-22rpx text-#898989 font-bold">
- {{ $t(item.title) }}
- </view>
- </view>
- </view>
- <view v-if="newProducts.length">
- <view class="mb-16rpx text-32rpx">
- {{ $t('home.news') }}
- </view>
- <scroll-view scroll-x class="whitespace-nowrap">
- <view class="flex items-center gap-16rpx" style="min-width: max-content;">
- <Product
- v-for="(item, index) in newProducts"
- :key="index"
- :title-font-size="18"
- :item="item"
- class="shrink-0"
- @item-click="toPage({ url: '/pages/productDetail/productDetail', params: { productId: item.productId } })"
- />
- </view>
- </scroll-view>
- </view>
- <view class="productList">
- <wd-sticky :offset-top="0">
- <view class="tabs-container">
- <wd-tabs v-model="priceTab" slidable="always" :line-width="0" :line-height="0" @click="handlePriceTabChange">
- <template v-for="item in priceTabList" :key="item">
- <wd-tab :title="$t(item.title)" :name="item.value" />
- </template>
- </wd-tabs>
- </view>
- </wd-sticky>
- <!-- Tab切换时的商品列表骨架屏 -->
- <view v-if="isProductListLoading" class="grid grid-cols-2 gap-20rpx">
- <wd-skeleton
- v-for="i in 6"
- :key="i"
- :row-col="[
- { height: '340rpx', borderRadius: '12rpx' }, // 商品图片
- { width: '180rpx', height: '40rpx', marginTop: '10rpx' }, // 商品名称
- [
- { width: '80rpx', height: '24rpx' }, // 价格
- { width: '60rpx', height: '20rpx' }, // 销量
- ],
- ]"
- animation="gradient"
- />
- </view>
- <!-- 实际商品列表 -->
- <view v-else class="grid grid-cols-2 gap-20rpx">
- <Product
- v-for="(item, index) in dataList"
- :key="index"
- width="100%"
- :height="340"
- :item="item"
- @item-click="toPage({ url: '/pages/productDetail/productDetail', params: { productId: item.productId } })"
- />
- </view>
- </view>
- </view>
- </template>
- <wd-curtain v-model="curtain.show" :src="curtain.img" :to="curtain.link" close-position="bottom" :width="280" @click="curtainClick" />
- </z-paging>
- <!-- 在页面最下方添加占位视图,高度等于 tabBar 的高度 -->
- <!-- <view class="edgeInsetBottom" /> -->
- </template>
- <style lang="scss" scoped>
- :deep(.productList) {
- .wd-tabs {
- background: none;
- .wd-tabs__nav {
- background: none;
- height: 72rpx;
- }
- .wd-tabs__nav-item {
- padding-left: 0 !important;
- height: 72rpx;
- }
- }
- .wd-sticky__container[style*='position: fixed'] {
- .tabs-container {
- width: 100vw;
- margin-left: -24rpx;
- padding: 28rpx 24rpx 4rpx;
- background: rgba(255, 255, 255, 0.85);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
- }
- }
- }
- </style>
|