[uni-app]小兔鲜-02项目首页

轮播图

轮播图组件需要在首页和分类页使用, 封装成通用组件

准备轮播图组件

<script setup lang="ts">
import type { BannerItem } from '@/types/home'
import { ref } from 'vue'
// 父组件的数据
defineProps<{
  list: BannerItem[]
}>()

// 高亮下标
const activeIndex = ref(0)
// 索引变化
const onChange: UniHelper.SwiperOnChange = (ev) => {
  // ! 非空断言,主观上排除空值的情况
  activeIndex.value = ev.detail!.current
}
</script>

<template>
  <view class="carousel">
    <swiper :circular="true" :autoplay="false" :interval="3000" @change="onChange">
      <swiper-item v-for="item in list" :key="item.id">
        <navigator url="/pages/index/index" 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">
/* 轮播图 */
@import '@/components/styles/XtxSwiper.scss';
</style>
  1. UniHelper 提供事件类型
  2. ? (可选链)允许前面表达式为空
  3. ! (非空断言)主观上排除掉空值情况
/** 首页-广告区域数据类型 */
export type BannerItem = {
    /** 跳转链接 */
    hrefUrl: string
    /** id */
    id: string
    /** 图片链接 */
    imgUrl: string
    /** 跳转类型 */
    type: number
}
  1. 统一管理项目需要使用的类型
import type { BannerItem } from '@/types/home'
import { http } from '@/utils/http'

// 首页广告区域
export const getHomeBannerAPI = (distributionSite = 1) => {
    return http<BannerItem[]>({
        method: 'GET',
        url: '/home/banner',
        data: {
            distributionSite,
        },
    })
}
  1. 为接口数据进行类型限制
<script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import type { BannerItem } from '@/types/home'
import { getHomeBannerAPI } from '@/services/home'

// 获取轮播图数据
const bannerList = ref<BannerItem[]>([])
const getHomeBannerData = async () => {
  const res = await getHomeBannerAPI()
  bannerList.value = res.result
}

// 页面加载
onLoad(async () => {
 getHomeBannerData()
})
</script>

<template>
   <!-- 自定义轮播图组件 -->
   <XtxSwiper :list="bannerList" />
</template>
  1. 定义变量时要进行类型限制, 接口返回数据和定义的变量保持一致, 增强项目的健壮性
  2. 引入类型文件可以使用 ctrl + i 快捷键

配置全局组件的自动导入

{
	// 组件自动引入规则
	"easycom": {
		// 开启自动扫描
		"autoscan": true,
		// 以正则方式自定义组件匹配规则
		"custom": {
			// uni-ui 规则如下配置
			"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
      // 自定义 规则如下配置
			"Xtx(.*)": "@/components/Xtx$1.vue"
		}
	},
 ... ...
}
  • 更改 easycom 配置需要重启服务

声明自定义组件的类型

import XtxSwiper from './components/XtxSwiper.vue'
import 'vue'
declare module 'vue' {
  export interface GlobalComponents {
    XtxSwiper: typeof XtxSwiper
  }
}
  • 通过 declare 指定 GlobalComponents 就可以定义全局组件的类型
  • 更多配置查看 Volar官网

分类导航

准备分类组件, 只有首页使用

<script setup lang="ts">
import type { CategoryItem } from '@/types/home'

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">
/* 前台类目 */
@import '../styles/category.scss';
</style>

使用分类组件

/** 首页-前台类目数据类型 */
export type CategoryItem = {
    /** 图标路径 */
    icon: string
    /** id */
    id: string
    /** 分类名称 */
    name: string
}
import type { CategoryItem } from '@/types/home'
import { http } from '@/utils/http'

// 首页分类数据
export const getHomeCategoryAPI = () => {
    return http<CategoryItem[]>({
        method: 'GET',
        url: '/home/category/mutli',
    })
}
<script setup lang="ts">
import { ref } from 'vue'
import type { CategoryItem } from '@/types/home'
import { getHomeCategoryAPI } from '@/services/home'
import CategoryPanel from './components/CategoryPanel.vue'

// 获取分类数据
const categoryList = ref<CategoryItem[]>([])
const getCategoryData = async () => {
  const res = await getHomeCategoryAPI()
  categoryList.value = res.result
}

</script>

<template>
  <!-- 分类组件 -->
  <CategoryPanel :list="categoryList" />
</template>

<style lang="scss">
page {
  background-color: #f3f3f3;
}
</style>
  1. 小程序页面根标签是page, 类似于网页的body标签

效果展示

热门推荐

准备热门推荐组件, 只有首页使用

<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?type=${item.type}`" class="cards">
        <image
          v-for="src in item.pictures"
          :key="src"
          class="image"
          mode="aspectFit"
          :src="src"
        ></image>
      </navigator>
    </view>
  </view>
</template>

<style lang="scss">
/* 热门推荐 */
@import '../styles/hot.scss';
</style>

使用推荐组件

/**
 * 首页-热门推荐数据类型
 */
export type HotItem = {
    /** 说明 */
    alt: string
    /** id */
    id: string
    /** 图片集合[ 图片路径 ] */
    pictures: string[]
    /** 跳转地址 */
    target: string
    /** 标题 */
    title: string
    /** 推荐类型 */
    type: string
}
import type { HotItem } from '@/types/home'
import { http } from '@/utils/http'

// 热门推荐数据
export const getHomeHotAPI = () => {
    return http<HotItem[]>({
        method: 'GET',
        url: '/home/hot/mutli',
    })
}
<script setup lang="ts">
import { ref } from 'vue'
import type { HotItem } from '@/types/home'
import { getHomeHotAPI } from '@/services/home'
import HotPanel from './components/HotPanel.vue'

// 获取热门推荐数据
const HotPanelList = ref<HotItem[]>([])
const getHomeHotData = async () => {
  const res = await getHomeHotAPI()
  HotPanelList.value = res.result
}
</script>

<template>
    <!-- 热门推荐 -->
    <HotPanel :list="HotPanelList" />
</template>

<style lang="scss">
page {
  background-color: #f3f3f3;
}
</style>

效果展示

猜你喜欢

需求分析

准备猜你喜欢组件

  • 猜你喜欢多个页面会用到
  • 定义组件的类型

  • 准备 scroll-view 滚动容器

  • 设置page 和 sroll-view样式

获取猜你喜欢数据

  • 封装请求API

  • 组件挂载完毕调用API

数据类型定义和列表渲染

分页准备工作

数据分页加载

分页条件判断

代码实现

猜你喜欢组件

<script setup lang="ts">
import { getHomeGoodsGuessAPI } from '@/services/home'
import type { PageParams } from '@/types/global'
import type { GuessItem } from '@/types/home'
import { onMounted, ref, defineExpose } from 'vue'

// 分页参数
// Required工具函数: 把可选参数转为必选
const pageParams: Required<PageParams> = {
  page: 1,
  pageSize: 10,
}
// 枯竭标记
const finish = ref(false)
// 获取猜你喜欢数据
const guessList = ref<GuessItem[]>([])
const getHomeGoodsGuessLikeData = async () => {
  if (finish.value) {
    return uni.showToast({
      icon: 'none',
      title: '没有更多了...',
    })
  }

  const res = await getHomeGoodsGuessAPI(pageParams)
  // 数组累加
  guessList.value.push(...res.result.items)
  // 分页条件 (当前页面小于总页数)
  if (pageParams.page < res.result.pages) {
    // 页码累加
    pageParams.page++
  } else {
    finish.value = true
  }
}

// 重置数据
const resetData = () => {
  guessList.value = []
  pageParams.page = 1
  finish.value = false
}

// 组件挂载
onMounted(() => {
  getHomeGoodsGuessLikeData()
})

// 对外暴漏
defineExpose({
  getMore: getHomeGoodsGuessLikeData,
  resetData,
})
</script>

<template>
  <!-- 猜你喜欢 -->
  <view class="caption">
    <text class="text">猜你喜欢</text>
  </view>
  <view class="guess">
    <navigator
      class="guess-item"
      v-for="item in guessList"
      :key="item.id"
      :url="`/pages/goods/goods?id=4007498`"
    >
      <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 ? '没有更多了...' : '正在加载...' }}
  </view>
</template>

<style lang="scss">
</style>
  1. 使用TS的 Required工具函数, 把可选参数转为必选

组件类型的声明

/**
 * declare module '@vue/runtime-core'
 *   现调整为
 * declare module 'vue'
 */
import XtxGuess from './component/XtxGuess.vue'
import 'vue'
declare module 'vue' {
  export interface GlobalComponents {
    XtxGuess: typeof XtxGuess
  }
}

// 组件实例类型
/**
 * InstanceType -> 获取组件实例的类型
 * typeof XtxGuess -> 获取组件的类型
 */
export type XtxGuessInstance = InstanceType<typeof XtxGuess>
  1. 使用TS提供的InstanceType工具方法, 可以获取组件的实例类型

请求接口封装

import type { PageParams, PageResult } from '@/types/global'
import type { GuessItem,  } from '@/types/home'
import { http } from '@/utils/http'

// 猜你喜欢数据
export const getHomeGoodsGuessAPI = (data?: PageParams) => {
    return http<PageResult<GuessItem>>({
        method: 'GET',
        url: '/home/goods/guessLike',
        data,
    })
}

数据类型的定义

/** 通用分页结果类型 */
export type PageResult<T> = {
    /** 列表数据 */
    items: T[]
    /** 总条数 */
    counts: number
    /** 当前页数 */
    page: number
    /** 总页数 */
    pages: number
    /** 每页条数 */
    pageSize: number
}

/** 通用分页参数类型 */
export type PageParams = {
    /** 页码:默认值为 1 */
    page?: number
    /** 页大小:默认值为 10 */
    pageSize?: number
}

/** 通用商品类型 */
export type GoodsItem = {
    /** 商品描述 */
    desc: string
    /** 商品折扣 */
    discount: number
    /** id */
    id: string
    /** 商品名称 */
    name: string
    /** 商品已下单数量 */
    orderNum: number
    /** 商品图片 */
    picture: string
    /** 商品价格 */
    price: number
}
import type { GoodsItem } from './global'

/** 猜你喜欢-商品类型 */
export type GuessItem = GoodsItem

使用组件

<script setup lang="ts">
import { ref } from 'vue'
import CustomNavbar from './components/CustomNavbar.vue'
import type { XtxGuessInstance } from '@/types/component'

// 猜你喜欢的组件实例
const guessRef = ref<XtxGuessInstance>()

// 滚动触底事件
const onScorlltolower = () => {
     guessRef.value.getMore()
}

</script>

<template>
  <!-- 自定义导航组件 -->
  <CustomNavbar />

  <scroll-view
    @scrolltolower="onScorlltolower"
    class="scroll_view"
    scroll-y
  >  
    <!-- 猜你喜欢 -->
    <XtxGuess ref="guessRef" />
  </scroll-view>
</template>

<style lang="scss">
page {
  background-color: #f3f3f3;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.scroll_view {
  flex: 1;
}
</style>
  1. 让页面page的高度为100%, 通过弹性布局, 让滚动容器自动占满页面剩余高度

下拉刷新

需求分析

开启下拉刷新

完成刷新逻辑

代码实现

使用scroll-view滚动容器

<script setup lang="ts">
// 获取轮播图数据
const getHomeBannerData = async () => {}

// 获取分类数据
const getCategoryData = async () => {}

// 获取热门推荐数据
const getHomeHotData = async () => {}

// 下拉刷新
const isTriggered = ref(false)
const onRefresherrefresh = async () => {
  isTriggered.value = true
  guessRef.value?.resetData()
  // 等待三个方法执行完毕,且三个方法同时执行
  await Promise.all([
    getHomeBannerData(),
    getCategoryData(),
    getHomeHotData(),
    guessRef.value?.getMore(),
  ])
  isTriggered.value = false
}
</script>

<template>
  <!-- 自定义导航组件 -->
  <CustomNavbar />
  
  <scroll-view
    :refresher-enabled
    @refresherrefresh="onRefresherrefresh"
    :refresher-triggered="isTriggered"
    scroll-y
    class="scroll_view"
    @scrolltolower="onScorlltolower"
  >
    <!-- 自定义轮播图组件 -->
    <XtxSwiper :list="bannerList" />
    <!-- 分类组件 -->
    <CategoryPanel :list="categoryList" />
    <!-- 热门推荐 -->
    <HotPanel :list="HotPanelList" />
    <!-- 猜你喜欢 -->
    <XtxGuess ref="guessRef" />
  </scroll-view>
</template>
  1. :refresher-enabled="true" // 开启下拉刷新
  2. @refresherrefresh="onRefresherrefresh" //绑定下拉事件
  3. :refresher-triggered="isTriggered" // 控制动画效果
  4. onScorlltolower // 触底加载更多
  5. Promise.all([]) // 把多个异步任务作为一组进行管理, 同时执行所有任务, 返回整组的执行情况

骨架屏

需求分析

准备骨架屏组件

  1. 小程序自动生成的骨架屏是整个页面的结构
  2. 骨架屏只需要填充动态加载的结构, 比如顶部的导航栏是固定结构, 我们按需一下

代码实现

<script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import PageSkeleton from './components/PageSkeleton.vue'


// 控制骨架屏
const isLoading = ref(false)
onLoad(async () => {
  isLoading.value = true
  await Promise.all([getHomeBannerData(), getCategoryData(), getHomeHotData()])
  isLoading.value = false
})
</script>

<template>
  <scroll-view>
    <!-- 骨架屏 -->
    <PageSkeleton v-if="isLoading" />
      
    <template v-else>
      <!-- 自定义轮播图组件 -->
      <XtxSwiper :list="bannerList" />
      <!-- 分类组件 -->
      <CategoryPanel :list="categoryList" />
      <!-- 热门推荐 -->
      <HotPanel :list="HotPanelList" />
      <!-- 猜你喜欢 -->
      <XtxGuess ref="guessRef" />
    </template>
  </scroll-view>
</template>

效果展示

上一篇:Java基础扫盲(二)


下一篇:贝锐蒲公英网盘首发,秒建私有云,高速远程访问