今日学习内容
- 配置自定义导航栏
- 通用轮播组件
- 通用的轮播图组件完善以及主页调用
- 分类面板以及热门推荐面板
- 猜你喜欢模块(分页查询)
- 首页下拉刷新
- 首页骨架屏
配置自定义导航栏
1、创建自定义组件
/index/components/CustomNavbar.vue
<script setup lang="ts">
// 获取屏幕边界到安全区域距离
const { safeAreaInsets } = uni.getSystemInfoSync()
</script>
<template>
<view class="navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<!-- logo文字 -->
<view class="logo">
<image class="logo-image" src="@/static/images/logo.png"></image>
<text class="logo-text">新鲜 · 亲民 · 快捷</text>
</view>
<!-- 搜索条 -->
<view class="search">
<text class="icon-search">搜索商品</text>
<text class="icon-scan"></text>
</view>
</view>
</template>
<style lang="scss">
/* 自定义导航条 */
.navbar {
background-image: url(@/static/images/navigator_bg.png);
background-size: cover;
position: relative;
display: flex;
flex-direction: column;
padding-top: 20px;
.logo {
display: flex;
align-items: center;
height: 64rpx;
padding-left: 30rpx;
padding-top: 20rpx;
.logo-image {
width: 166rpx;
height: 39rpx;
}
.logo-text {
flex: 1;
line-height: 28rpx;
color: #fff;
margin: 2rpx 0 0 20rpx;
padding-left: 20rpx;
border-left: 1rpx solid #fff;
font-size: 26rpx;
}
}
.search {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10rpx 0 26rpx;
height: 64rpx;
margin: 16rpx 20rpx;
color: #fff;
font-size: 28rpx;
border-radius: 32rpx;
background-color: rgba(255, 255, 255, 0.5);
}
.icon-search {
&::before {
margin-right: 10rpx;
}
}
.icon-scan {
font-size: 30rpx;
padding: 15rpx;
}
}
</style>
2、配置隐藏栏样式为自定义
pages.json
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom", // 隐藏默认导航
"navigationBarTextStyle": "white",
"navigationBarTitleText": "首页"
}
},
3、在index中使用
<script setup lang="ts">
import CustomNavbar from './components/CustomNavbar.vue'
</script>
<template>
<CustomNavbar />
<uni-card title="基础卡片" sub-title="副标题" extra="额外信息"
thumbnail="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png">
<text>这是一个带头像和双标题的基础卡片,此示例展示了一个完整的卡片。</text>
</uni-card>
</template>
效果图:
通用轮播组件
配置通用组件自动导入
pages.json文件中配置
"easycom": {
"autoscan": true,
"custom": {
// uni-ui 规则如下配置
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
"^Xtx(.*)": "@/components/Xtx$1.vue"
}
},
配置类型声明
新建一个ts文件
// src/types/components.d.ts
import XtxSwiper from './XtxSwiper.vue'
declare module 'vue' {
export interface GlobalComponents {
XtxSwiper: typeof XtxSwiper
}
}
轮播组件代码
<script setup lang="ts">
import { ref } from 'vue'
//高亮的
const activeIndex = ref(0)
//当swiper下标发生变化时出发
const onChange: UniHelper.SwiperOnChange = (ev) => {
// console.log(ev.detail?.current)
activeIndex.value = ev.detail?.current
}
</script>
<template>
<view class="carousel">
<swiper @change="onChange" :circular="true" :autoplay="false" :interval="3000">
<swiper-item>
<navigator url="/pages/index/index" hover-class="none" class="navigator">
<image mode="aspectFill" class="image"
src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_1.jpg"></image>
</navigator>
</swiper-item>
<swiper-item>
<navigator url="/pages/index/index" hover-class="none" class="navigator">
<image mode="aspectFill" class="image"
src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_2.jpg"></image>
</navigator>
</swiper-item>
<swiper-item>
<navigator url="/pages/index/index" hover-class="none" class="navigator">
<image mode="aspectFill" class="image"
src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_3.jpg"></image>
</navigator>
</swiper-item>
</swiper>
<!-- 指示点 -->
<view class="indicator">
<text v-for="(item, index) in 3" :key="item" class="dot" :class="{ active: index === activeIndex }"></text>
</view>
</view>
</template>
<style lang="scss">
/* 轮播图 */
.carousel {
height: 280rpx;
position: relative;
overflow: hidden;
transform: translateY(0);
background-color: #efefef;
.indicator {
position: absolute;
left: 0;
right: 0;
bottom: 16rpx;
display: flex;
justify-content: center;
.dot {
width: 30rpx;
height: 6rpx;
margin: 0 8rpx;
border-radius: 6rpx;
background-color: rgba(255, 255, 255, 0.4);
}
.active {
background-color: #fff;
}
}
.navigator,
.image {
width: 100%;
height: 100%;
}
}
</style>
通用的轮播图组件完善以及主页调用
1、声明轮播图数据的类型
//轮播图数据类型
export type BannerItem = {
//跳转连接
hrefUrl: string
//id
id: string
//图片地址
imgUrl: string
//跳转类型
type: number
}
2、封装接口
import type { BannerItem } from "@/types/home"
import { http } from "@/utils/http"
//首页的广告区域
export const getHomeBannerApi = (distributionSite = 1) => {
return http<BannerItem[]>({
url: '/home/banner',
method: 'GET',
data: {
distributionSite
}
})
}
3、首页调用
<script setup lang="ts">
import CustomNavbar from './components/CustomNavbar.vue'
import { getHomeBannerApi } from '@/services/home'
import { onLoad } from '@dcloudio/uni-app';
import { ref } from 'vue'
import type { BannerItem } from "@/types/home"
const bannerList = ref<BannerItem[]>([])
//获取轮播图数据
const getHomeBannerData = async () => {
const res = await getHomeBannerApi()
bannerList.value = res.result
console.log(bannerList.value)
}
onLoad(() => {
getHomeBannerData()
})
</script>
<template>
<CustomNavbar />
<XtxSwiper :list="bannerList" />
<uni-card title="基础卡片" sub-title="副标题" extra="额外信息"
thumbnail="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png">
<text>这是一个带头像和双标题的基础卡片,此示例展示了一个完整的卡片。</text>
</uni-card>
</template>
<style lang="scss">
//
</style>
4、完善通用组件
<script setup lang="ts">
import type { BannerItem } from '@/types/home';
import { ref } from 'vue'
//高亮的指示点
const activeIndex = ref(0)
//当swiper下标发生变化时出发
const onChange: UniHelper.SwiperOnChange = (ev) => {
// console.log(ev.detail?.current)
activeIndex.value = ev.detail!.current
}
//定义props 接收
defineProps<{
list: BannerItem[]
}>()
</script>
<template>
<view class="carousel">
<swiper @change="onChange" :circular="true" :autoplay="false" :interval="3000">
<swiper-item v-for="item in list" :key="item.id">
<navigator :url="'/pages/index/index/' + item.hrefUrl" hover-class="none" class="navigator">
<image mode="aspectFill" class="image" :src="item.imgUrl"></image>
</navigator>
</swiper-item>
</swiper>
<!-- 指示点 -->
<view class="indicator">
<text v-for="(item, index) in list" :key="item.id" class="dot" :class="{ active: index === activeIndex }"></text>
</view>
</view>
</template>
<style lang="scss">
/* 轮播图 */
.carousel {
height: 280rpx;
position: relative;
overflow: hidden;
transform: translateY(0);
background-color: #efefef;
.indicator {
position: absolute;
left: 0;
right: 0;
bottom: 16rpx;
display: flex;
justify-content: center;
.dot {
width: 30rpx;
height: 6rpx;
margin: 0 8rpx;
border-radius: 6rpx;
background-color: rgba(255, 255, 255, 0.4);
}
.active {
background-color: #fff;
}
}
.navigator,
.image {
width: 100%;
height: 100%;
}
}
</style>
分类面板以及热门推荐面板
1、定义类型
//分类数据类型
export type CategoryItem = {
//id
id: string
//名字
name: string
//图标
icon: string
}
/**
* 热门推荐数据
*/
export type HotItem = {
/**
* 推荐说明
*/
alt: string,
/**
* id
*/
id: string,
/**
* 图片集合
*/
pictures: string[],
/**
* 跳转地址
*/
target: string,
/**
* 推荐标题
*/
title: string,
/**
* 推荐类型
*/
type: string
}
2、封装接口
//前台分类api
export const getHomeCategoryApi = () => {
return http<CategoryItem[]>({
url: '/home/category/mutli',
method: 'GET',
})
}
//热门推荐api
export const getHomeHotApi = () => {
return http<HotItem[]>({
url: '/home/hot/mutli',
method: 'GET',
})
}
3、首页调用
<script setup lang="ts">
import CustomNavbar from './components/CustomNavbar.vue'
import HotPanel from './components/HotPanel.vue'
import { getHomeBannerApi, getHomeCategoryApi, getHomeHotApi } from '@/services/home'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import type { BannerItem, CategoryItem, HotItem } from '@/types/home'
import CategoryPanel from './components/CategoryPanel.vue'
const bannerList = ref<BannerItem[]>([])
const categoryList = ref<CategoryItem[]>([])
const hotList = ref<HotItem[]>([])
//获取轮播图数据
const getHomeBannerData = async () => {
const res = await getHomeBannerApi()
bannerList.value = res.result
}
//获取前台分类数据
const getHomeCategoryData = async () => {
const res = await getHomeCategoryApi()
categoryList.value = res.result
}
//获取热门推荐数据
const getHomeHotData = async () => {
const res = await getHomeHotApi()
hotList.value = res.result
}
onLoad(() => {
getHomeBannerData()
getHomeCategoryData()
getHomeHotData()
})
</script>
<template>
<!-- 自定义导航 -->
<CustomNavbar />
<!-- 首页轮播图 -->
<XtxSwiper :list="bannerList" />
<!-- 前台分类 -->
<CategoryPanel :list="categoryList" />
<!-- 热门推荐 -->
<HotPanel :list="hotList" />
index
</template>
<style lang="scss">
page {
background-color: #f7f7f7;
}
</style>
4、组件完善
<script setup lang="ts">
import type { CategoryItem } from '@/types/home'
//定义props 接收
defineProps<{
list: CategoryItem[]
}>()
</script>
<template>
<view class="category">
<navigator
class="category-item"
hover-class="none"
url="/pages/index/index"
v-for="item in list"
:key="item.id"
>
<image class="icon" :src="item.icon"></image>
<text class="text">{{ item.name }}</text>
</navigator>
</view>
</template>
<style lang="scss">
/* 前台类目 */
.category {
margin: 20rpx 0 0;
padding: 10rpx 0;
display: flex;
flex-wrap: wrap;
min-height: 328rpx;
.category-item {
width: 150rpx;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
box-sizing: border-box;
.icon {
width: 100rpx;
height: 100rpx;
}
.text {
font-size: 26rpx;
color: #666;
}
}
}
</style>
<script setup lang="ts">
import type { HotItem } from '@/types/home'
//定义props 接收
defineProps<{
list: HotItem[]
}>()
</script>
<template>
<!-- 推荐专区 -->
<view class="panel hot">
<view class="item" v-for="item in list" :key="item.id">
<view class="title">
<text class="title-text">{{ item.title }}</text>
<text class="title-desc">{{ item.alt }}</text>
</view>
<navigator hover-class="none" url="/pages/hot/hot" class="cards">
<image v-for="i in item.pictures" :key="i" class="image" mode="aspectFit" :src="i"></image>
</navigator>
</view>
</view>
</template>
<style lang="scss">
/* 热门推荐 */
.hot {
display: flex;
flex-wrap: wrap;
min-height: 508rpx;
margin: 20rpx 20rpx 0;
border-radius: 10rpx;
background-color: #fff;
.title {
display: flex;
align-items: center;
padding: 24rpx 24rpx 0;
font-size: 32rpx;
color: #262626;
position: relative;
.title-desc {
font-size: 24rpx;
color: #7f7f7f;
margin-left: 18rpx;
}
}
.item {
display: flex;
flex-direction: column;
width: 50%;
height: 254rpx;
border-right: 1rpx solid #eee;
border-top: 1rpx solid #eee;
.title {
justify-content: start;
}
&:nth-child(2n) {
border-right: 0 none;
}
&:nth-child(-n + 2) {
border-top: 0 none;
}
.image {
width: 150rpx;
height: 150rpx;
}
}
.cards {
flex: 1;
padding: 15rpx 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>
猜你喜欢模块(分页查询)
1、定义类型
组件类型
/**
* declare module '@vue/runtime-core'
* 现调整为
* declare module 'vue'
*/
import XtxSwiper from '@/components/XtxSwiper.vue'
import XtxGuess from '@/components/XtxGuess.vue'
import 'vue'
declare module 'vue' {
export interface GlobalComponents {
XtxSwiper: typeof XtxSwiper,
XtxGuess: typeof XtxGuess
}
}
//组件实例类型
export type XtxGuessInstance = InstanceType<typeof XtxGuess>
通用分页相关类型
/**
* 返回数据
*/
export type PageResult<T> = {
/**
* 总条数
*/
counts: number,
/**
* 当前页数据
*/
items: T[],
/**
* 当前页数
*/
page: number,
/**
* 总页数
*/
pages: number,
/**
* 每页条数
*/
pageSize: number
}
//分页查询参数
export type PageParam = {
page?: number,
pageSize?: number
}
猜你喜欢item类型
//猜你喜欢item类型
export type GuessItem = {
/**
* 商品描述
*/
desc: string,
/**
* 商品折扣
*/
discount: number,
/**
* id
*/
id: string,
/**
* 商品名称
*/
name: string,
/**
* 商品已下单数量
*/
orderNum: number,
/**
* 商品图片
*/
picture: string,
/**
* 商品价格
*/
price: number
}
2、封装接口
//猜你喜欢
export const getHomeGuessApi = (param: PageParam) => {
return http<PageResult<GuessItem>>({
url: '/home/goods/guessLike',
method: 'GET',
data: param
})
}
3、组件代码
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { getHomeGuessApi } from '@/services/home'
import type { GuessItem } from '@/types/home';
import type { PageParam } from '@/types/global'
const finish = ref(false)
const list = ref<GuessItem[]>([]);
const pageParam: Required<PageParam> = {
page: 1,
pageSize: 10
}
const getGuessData = async () => {
if (finish.value === true) {
return
}
const res = await getHomeGuessApi(pageParam)
//追加数据
list.value.push(...res.result.items)
if (pageParam.page < res.result.pages) {
//增加pageNo
pageParam.page++
} else {
finish.value = true
}
}
onMounted(() => {
getGuessData()
})
defineExpose({
getMore: getGuessData,
})
</script>
<template>
<!-- 猜你喜欢 -->
<view class="caption">
<text class="text">猜你喜欢</text>
</view>
<view class="guess">
<navigator class="guess-item" v-for="item in list" :key="item.id" :url="`/pages/goods/goods?id=${item.orderNum}`">
<image class="image" mode="aspectFill" :src="item.picture"></image>
<view class="name"> {{ item.name }} </view>
<view class="price">
<text class="small">¥</text>
<text>{{ item.price }}</text>
</view>
</navigator>
</view>
<view class="loading-text"> {{ finish === false ? '正在加载...' : '已经到底了~~' }} </view>
</template>
<style lang="scss">
:host {
display: block;
}
/* 分类标题 */
.caption {
display: flex;
justify-content: center;
line-height: 1;
padding: 36rpx 0 40rpx;
font-size: 32rpx;
color: #262626;
.text {
display: flex;
justify-content: center;
align-items: center;
padding: 0 28rpx 0 30rpx;
&::before,
&::after {
content: '';
width: 20rpx;
height: 20rpx;
background-image: url(@/static/images/bubble.png);
background-size: contain;
margin: 0 10rpx;
}
}
}
/* 猜你喜欢 */
.guess {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 20rpx;
.guess-item {
width: 345rpx;
padding: 24rpx 20rpx 20rpx;
margin-bottom: 20rpx;
border-radius: 10rpx;
overflow: hidden;
background-color: #fff;
}
.image {
width: 304rpx;
height: 304rpx;
}
.name {
height: 75rpx;
margin: 10rpx 0;
font-size: 26rpx;
color: #262626;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.price {
line-height: 1;
padding-top: 4rpx;
color: #cf4444;
font-size: 26rpx;
}
.small {
font-size: 80%;
}
}
// 加载提示文字
.loading-text {
text-align: center;
font-size: 28rpx;
color: #666;
padding: 20rpx 0;
}
</style>
4、首页调用
<script setup lang="ts">
import CustomNavbar from './components/CustomNavbar.vue'
import HotPanel from './components/HotPanel.vue'
import { getHomeBannerApi, getHomeCategoryApi, getHomeHotApi } from '@/services/home'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import type { BannerItem, CategoryItem, HotItem } from '@/types/home'
import type { XtxGuessInstance } from '@/types/component'
import CategoryPanel from './components/CategoryPanel.vue'
const bannerList = ref<BannerItem[]>([])
const categoryList = ref<CategoryItem[]>([])
const hotList = ref<HotItem[]>([])
//获取轮播图数据
const getHomeBannerData = async () => {
const res = await getHomeBannerApi()
bannerList.value = res.result
}
//获取前台分类数据
const getHomeCategoryData = async () => {
const res = await getHomeCategoryApi()
categoryList.value = res.result
}
//获取热门推荐数据
const getHomeHotData = async () => {
const res = await getHomeHotApi()
hotList.value = res.result
}
onLoad(() => {
getHomeBannerData()
getHomeCategoryData()
getHomeHotData()
})
const guessRef = ref<XtxGuessInstance>()
const onScrolltolower = () => {
guessRef.value!.getMore()
}
</script>
<template>
<!-- 自定义导航 -->
<CustomNavbar />
<!-- 滚动容器 -->
<scroll-view class="scroll-view" scroll-y @scrolltolower="onScrolltolower">
<!-- 首页轮播图 -->
<XtxSwiper :list="bannerList" />
<!-- 前台分类 -->
<CategoryPanel :list="categoryList" />
<!-- 热门推荐 -->
<HotPanel :list="hotList" />
<!-- 猜你喜欢 -->
<XtxGuess ref="guessRef" />
</scroll-view>
</template>
<style lang="scss">
page {
background-color: #f7f7f7;
height: 100%;
display: flex;
flex-direction: column;
}
.scroll-view {
flex: 1;
}
</style>
首页下拉刷新
<script setup lang="ts">
import CustomNavbar from './components/CustomNavbar.vue'
import HotPanel from './components/HotPanel.vue'
import { getHomeBannerApi, getHomeCategoryApi, getHomeHotApi } from '@/services/home'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import type { BannerItem, CategoryItem, HotItem } from '@/types/home'
import type { XtxGuessInstance } from '@/types/component'
import CategoryPanel from './components/CategoryPanel.vue'
const bannerList = ref<BannerItem[]>([])
const categoryList = ref<CategoryItem[]>([])
const hotList = ref<HotItem[]>([])
//获取轮播图数据
const getHomeBannerData = async () => {
const res = await getHomeBannerApi()
bannerList.value = res.result
}
//获取前台分类数据
const getHomeCategoryData = async () => {
const res = await getHomeCategoryApi()
categoryList.value = res.result
}
//获取热门推荐数据
const getHomeHotData = async () => {
const res = await getHomeHotApi()
hotList.value = res.result
}
onLoad(() => {
getHomeBannerData()
getHomeCategoryData()
getHomeHotData()
})
const isRefresher = ref(false)
//组件
const guessRef = ref<XtxGuessInstance>()
const onScrolltolower = () => {
guessRef.value!.getMore()
}
//下拉刷新事件
const onRefresherrefresh = async () => {
//开启动画
isRefresher.value = true
//三个请求同时进行
await Promise.all([guessRef.value?.clear(), getHomeBannerData(), getHomeCategoryData(), getHomeHotData()])
//第一次亲求猜你喜欢
guessRef.value!.getMore()
//关闭动画
isRefresher.value = false
}
</script>
<template>
<!-- 自定义导航 -->
<CustomNavbar />
<!-- 滚动容器 -->
<scroll-view :refresher-triggered="isRefresher" @refresherrefresh="onRefresherrefresh" :refresher-enabled="true"
class="scroll-view" scroll-y @scrolltolower="onScrolltolower">
<!-- 首页轮播图 -->
<XtxSwiper :list="bannerList" />
<!-- 前台分类 -->
<CategoryPanel :list="categoryList" />
<!-- 热门推荐 -->
<HotPanel :list="hotList" />
<!-- 猜你喜欢 -->
<XtxGuess ref="guessRef" />
</scroll-view>
</template>
<style lang="scss">
page {
background-color: #f7f7f7;
height: 100%;
display: flex;
flex-direction: column;
}
.scroll-view {
flex: 1;
}
</style>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getHomeGuessApi } from '@/services/home'
import type { GuessItem } from '@/types/home'
import type { PageParam } from '@/types/global'
const finish = ref(false)
const list = ref<GuessItem[]>([])
const pageParam: Required<PageParam> = {
page: 1,
pageSize: 10,
}
const getGuessData = async () => {
if (finish.value === true) {
return
}
const res = await getHomeGuessApi(pageParam)
//追加数据
list.value.push(...res.result.items)
if (pageParam.page < res.result.pages) {
//增加pageNo
pageParam.page++
} else {
finish.value = true
}
}
//重置数据接口
const flush = () => {
finish.value = false
pageParam.page = 1
pageParam.pageSize = 10
list.value = []
}
onMounted(() => {
getGuessData()
})
defineExpose({
getMore: getGuessData,
clear: flush
})
</script>
<template>
<!-- 猜你喜欢 -->
<view class="caption">
<text class="text">猜你喜欢</text>
</view>
<view class="guess">
<navigator class="guess-item" v-for="item in list" :key="item.id" :url="`/pages/goods/goods?id=${item.orderNum}`">
<image class="image" mode="aspectFill" :src="item.picture"></image>
<view class="name"> {{ item.name }} </view>
<view class="price">
<text class="small">¥</text>
<text>{{ item.price }}</text>
</view>
</navigator>
</view>
<view class="loading-text"> {{ finish === false ? '正在加载...' : '已经到底了~~' }} </view>
</template>
<style lang="scss">
:host {
display: block;
}
/* 分类标题 */
.caption {
display: flex;
justify-content: center;
line-height: 1;
padding: 36rpx 0 40rpx;
font-size: 32rpx;
color: #262626;
.text {
display: flex;
justify-content: center;
align-items: center;
padding: 0 28rpx 0 30rpx;
&::before,
&::after {
content: '';
width: 20rpx;
height: 20rpx;
background-image: url(@/static/images/bubble.png);
background-size: contain;
margin: 0 10rpx;
}
}
}
/* 猜你喜欢 */
.guess {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 20rpx;
.guess-item {
width: 345rpx;
padding: 24rpx 20rpx 20rpx;
margin-bottom: 20rpx;
border-radius: 10rpx;
overflow: hidden;
background-color: #fff;
}
.image {
width: 304rpx;
height: 304rpx;
}
.name {
height: 75rpx;
margin: 10rpx 0;
font-size: 26rpx;
color: #262626;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.price {
line-height: 1;
padding-top: 4rpx;
color: #cf4444;
font-size: 26rpx;
}
.small {
font-size: 80%;
}
}
// 加载提示文字
.loading-text {
text-align: center;
font-size: 28rpx;
color: #666;
padding: 20rpx 0;
}
</style>
首页骨架屏
1、生成代码
<script setup lang="ts">
</script>
<template>
<view is="components/XtxSwiper">
<view class="carousel XtxSwiper--carousel">
<swiper :circular="true" :interval="3000" :current="0" :autoplay="false">
<swiper-item
style="position: absolute; width: 100%; height: 100%; transform: translate(0%, 0px) translateZ(0px);">
<navigator class="navigator XtxSwiper--navigator" hover-class="none">
<image class="image XtxSwiper--image sk-image" mode="aspectFill"></image>
</navigator>
</swiper-item>
</swiper>
<view class="indicator XtxSwiper--indicator">
<text class="dot XtxSwiper--dot active XtxSwiper--active"></text>
<text class="dot XtxSwiper--dot"></text>
<text class="dot XtxSwiper--dot"></text>
<text class="dot XtxSwiper--dot"></text>
<text class="dot XtxSwiper--dot"></text>
</view>
</view>
</view>
<view is="pages/index/components/CategoryPanel">
<view class="category CategoryPanel--category">
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-240 sk-text">居家</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-430 sk-text">锦鲤</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-543 sk-text">服饰</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-124 sk-text">母婴</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-822 sk-text">个护</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-514 sk-text">严选</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-525 sk-text">数码</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-717 sk-text">运动</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-122 sk-text">杂项</text>
</navigator>
<navigator class="category-item CategoryPanel--category-item" hover-class="none">
<image class="icon CategoryPanel--icon sk-image"></image>
<text class="text CategoryPanel--text sk-transparent sk-text-14-2857-140 sk-text">品牌</text>
</navigator>
</view>
</view>
<view is="pages/index/components/HotPanel">
<view class="panel HotPanel--panel hot HotPanel--hot">
<view class="item HotPanel--item">
<view class="title HotPanel--title">
<text class="title-text HotPanel--title-text sk-transparent sk-text-14-2857-123 sk-text">特惠推荐</text>
<text class="title-desc HotPanel--title-desc sk-transparent sk-text-14-2857-825 sk-text">精选全攻略</text>
</view>
<navigator class="cards HotPanel--cards" hover-class="none">
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
</navigator>
</view>
<view class="item HotPanel--item">
<view class="title HotPanel--title">
<text class="title-text HotPanel--title-text sk-transparent sk-text-14-2857-165 sk-text">爆款推荐</text>
<text class="title-desc HotPanel--title-desc sk-transparent sk-text-14-2857-196 sk-text">最受欢迎</text>
</view>
<navigator class="cards HotPanel--cards" hover-class="none">
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
</navigator>
</view>
<view class="item HotPanel--item">
<view class="title HotPanel--title">
<text class="title-text HotPanel--title-text sk-transparent sk-text-14-2857-940 sk-text">一站买全</text>
<text class="title-desc HotPanel--title-desc sk-transparent sk-text-14-2857-59 sk-text">精心优选</text>
</view>
<navigator class="cards HotPanel--cards" hover-class="none">
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
</navigator>
</view>
<view class="item HotPanel--item">
<view class="title HotPanel--title">
<text class="title-text HotPanel--title-text sk-transparent sk-text-14-2857-210 sk-text">新鲜好物</text>
<text class="title-desc HotPanel--title-desc sk-transparent sk-text-14-2857-841 sk-text">生活加分项</text>
</view>
<navigator class="cards HotPanel--cards" hover-class="none">
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
<image class="image HotPanel--image sk-image" mode="aspectFit"></image>
</navigator>
</view>
</view>
</view>
</template>
<style setup>
.sk-transparent {
color: transparent !important;
}
.sk-text-3-5714-320 {
background-image: linear-gradient(transparent 3.5714%, #EEEEEE 0%, #EEEEEE 96.4286%, transparent 0%) !important;
background-size: 100% 28.0000rpx;
position: relative !important;
}
.sk-text {
background-origin: content-box !important;
background-clip: content-box !important;
background-color: transparent !important;
color: transparent !important;
background-repeat: repeat-y !important;
}
.sk-text-14-2857-597 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 39.2000rpx;
position: relative !important;
}
.sk-text-14-2857-240 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-430 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-543 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-124 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-822 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-514 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-525 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-717 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-122 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-140 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 36.4000rpx;
position: relative !important;
}
.sk-text-14-2857-123 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 44.8000rpx;
position: relative !important;
}
.sk-text-14-2857-825 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 33.6000rpx;
position: relative !important;
}
.sk-text-14-2857-165 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 44.8000rpx;
position: relative !important;
}
.sk-text-14-2857-196 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 33.6000rpx;
position: relative !important;
}
.sk-text-14-2857-940 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 44.8000rpx;
position: relative !important;
}
.sk-text-14-2857-59 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 33.6000rpx;
position: relative !important;
}
.sk-text-14-2857-210 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 44.8000rpx;
position: relative !important;
}
.sk-text-14-2857-841 {
background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
background-size: 100% 33.6000rpx;
position: relative !important;
}
.sk-image {
background: #EFEFEF !important;
}
.sk-pseudo::before,
.sk-pseudo::after {
background: #EFEFEF !important;
background-image: none !important;
color: transparent !important;
border-color: transparent !important;
}
.sk-pseudo-rect::before,
.sk-pseudo-rect::after {
border-radius: 0 !important;
}
.sk-pseudo-circle::before,
.sk-pseudo-circle::after {
border-radius: 50% !important;
}
.sk-container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: transparent;
}
</style>
2、首页使用组件
<script setup lang="ts">
import CustomNavbar from './components/CustomNavbar.vue'
import HotPanel from './components/HotPanel.vue'
import { getHomeBannerApi, getHomeCategoryApi, getHomeHotApi } from '@/services/home'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import type { BannerItem, CategoryItem, HotItem } from '@/types/home'
import type { XtxGuessInstance } from '@/types/component'
import CategoryPanel from './components/CategoryPanel.vue'
import PageSkeleton from './components/PageSkeleton.vue'
const bannerList = ref<BannerItem[]>([])
const categoryList = ref<CategoryItem[]>([])
const hotList = ref<HotItem[]>([])
//获取轮播图数据
const getHomeBannerData = async () => {
const res = await getHomeBannerApi()
bannerList.value = res.result
}
//获取前台分类数据
const getHomeCategoryData = async () => {
const res = await getHomeCategoryApi()
categoryList.value = res.result
}
//获取热门推荐数据
const getHomeHotData = async () => {
const res = await getHomeHotApi()
hotList.value = res.result
}
const isLoading = ref(false)
onLoad(async () => {
isLoading.value = true
await Promise.all([
getHomeBannerData(),
getHomeCategoryData(),
getHomeHotData()
])
isLoading.value = false
})
const isRefresher = ref(false)
//组件
const guessRef = ref<XtxGuessInstance>()
const onScrolltolower = () => {
guessRef.value!.getMore()
}
//下拉刷新事件
const onRefresherrefresh = async () => {
//开启动画
isRefresher.value = true
//三个请求同时进行
await Promise.all([
guessRef.value?.clear(),
getHomeBannerData(),
getHomeCategoryData(),
getHomeHotData(),
])
//第一次亲求猜你喜欢
guessRef.value!.getMore()
//关闭动画
isRefresher.value = false
}
</script>
<template>
<!-- 自定义导航 -->
<CustomNavbar />
<!-- 滚动容器 -->
<scroll-view :refresher-triggered="isRefresher" @refresherrefresh="onRefresherrefresh" :refresher-enabled="true"
class="scroll-view" scroll-y @scrolltolower="onScrolltolower">
<PageSkeleton v-if="isLoading" />
<template v-else>
<!-- 首页轮播图 -->
<XtxSwiper :list="bannerList" />
<!-- 前台分类 -->
<CategoryPanel :list="categoryList" />
<!-- 热门推荐 -->
<HotPanel :list="hotList" />
<!-- 猜你喜欢 -->
<XtxGuess ref="guessRef" />
</template>
</scroll-view>
</template>
<style lang="scss">
page {
background-color: #f7f7f7;
height: 100%;
display: flex;
flex-direction: column;
}
.scroll-view {
flex: 1;
}
</style>
效果: