Skip to content
本页目录

3. 首页

3.1 配置网络请求

由于平台的限制,小程序项目中不支持 axios,而且原生的 wx.request() API 功能较为简单,不支持拦截器等全局定制的功能。因此,建议在 uni-app 项目中自己封装网络数据请求

utils/request.js

js
class Request {
	constructor(options = {}) {
		// 请求的根路径
		this.baseUrl = options.baseUrl || ''
		// 请求的 url 地址
		this.url = options.url || ''
		// 请求方式
		this.method = 'GET'
		// 请求的参数对象
		this.data = null
		// header 请求头
		this.header = options.header || {}
		this.beforeRequest = null
		this.afterRequest = null
	}

	get(url, data = {}) {
		this.method = 'GET'
		this.url = this.baseUrl + url
		this.data = data
		return this._()
	}

	post(url, data = {}) {
		this.method = 'POST'
		this.url = this.baseUrl + url
		this.data = data
		return this._()
	}

	put(url, data = {}) {
		this.method = 'PUT'
		this.url = this.baseUrl + url
		this.data = data
		return this._()
	}

	delete(url, data = {}) {
		this.method = 'DELETE'
		this.url = this.baseUrl + url
		this.data = data
		return this._()
	}

	_() {
		// 清空 header 对象
		this.header = {}
		// 请求之前做一些事
		this.beforeRequest && typeof this.beforeRequest === 'function' && this.beforeRequest(this)
		// 发起请求
		return new Promise((resolve, reject) => {
			let weixin = wx
			// 适配 uniapp
			if ('undefined' !== typeof uni) {
				weixin = uni
			}
			weixin.request({
				url: this.url,
				method: this.method,
				data: this.data,
				header: this.header,
				success: (res) => {
					if (typeof res.data !== 'object') {
					  uni.showToast({ title: '服务端异常' , icon: 'none' })
					  return reject(res)
					}
					const { resultCode, data, message } = res.data
					// 判断请求结果
					if (resultCode !== 200) {
						if (message) uni.showToast({ title: message , icon: 'none' })
						return reject(data)
					}
					resolve(res.data)
				},
				fail: (err) => {
					reject(err)
				},
				complete: (res) => {
					// 请求完成以后做一些事情
					this.afterRequest && typeof this.afterRequest === 'function' && this
						.afterRequest(res)
				}
			})
		})
	}
}

export const $http = new Request()

最终,在项目新建 api目录进行封装使用 ,通过如下的方式进行配置:

api/index.js

js
import { $http } from '@/utils/request'

// 配置请求根地址
$http.baseUrl = 'http://zhi.zeng.pub/new-bee/api/v1/'

// 配置请求拦截器
$http.afterRequest = function(options) {
	
}

export default $http

api/modules/home.js

js
import http from '../index.js'

// 获取首页数据
export function getHome() {
  return http.get('/index-infos');
}

3.2 轮播图区域

3.2.1 轮播图UI布局及样式

vue
<!-- 轮播图 -->
<view class="banner">
    <swiper class="my-swiper" autoplay indicator-active-color="#1baeae" indicator-dots interval="3000">
        <swiper-item v-for="(item, index) in swiperList" :key="index"><image :src="item.carouselUrl"></image></swiper-item>
    </swiper>
</view>
scss
// 轮播
.banner {
	.my-swiper {
		height: 340rpx;
		image {
			width: 100%;
			height: 100%;
		}
	}
}

3.2.2 轮播图数据

vue
<script setup>
import { onMounted, reactive, toRefs } from 'vue'
import { getHome } from '@/api/modules/home.js'
import { categoryData } from '@/utils/file_data.js'
	
const state = reactive({
	categoryList: categoryData, // 分类数据
	swiperList: [], // 轮播图数据
	hots: [], // 热门
	newGoods: [], // 新品
	recommends: [], // 推荐
	loading: true // 是否加载中
})

onMounted(async () => {
	// 1. 显示加载
	uni.showLoading({ title: '加载中...', mask: true })
	// 2. 加载数据
	const { data } = await getHome()
	console.log(data)
	state.swiperList = data.carousels
	state.newGoods = data.newGoodses
	state.hots = data.hotGoodses
	state.recommends = data.recommendGoodses
	state.loading = false
	// 3. 隐藏加载
	uni.hideLoading()
})

// 跳转商品详情页
const goToDetail = item => {
	uni.navigateTo({ url: '/subpkg/goods_detail/goods_detail' })
}

const tips = () => {
	uni.$Toast('敬请期待')
}

const { categoryList, swiperList, hots, newGoods, recommends, loading } = toRefs(state)
</script>

3.2.3 配置小程序分包

分包可以减少小程序首次启动时的加载时间

为此,我们在项目中,把 tabBar 相关的 4 个页面放到主包中,其它页面(例如:商品详情页、商品列表页)放到分包中。在 uni-app 项目中,配置分包的步骤如下:

  1. 在项目根目录中,创建分包的根目录,命名为 subpkg

  2. pages.json 中,和 pages 节点平级的位置声明 subPackages 节点,用来定义分包相关的结构:

    json
    {
      "pages": [
        {
          "path": "pages/home/home",
          "style": {}
        },
        {
          "path": "pages/cate/cate",
          "style": {}
        },
        {
          "path": "pages/cart/cart",
          "style": {}
        },
        {
          "path": "pages/my/my",
          "style": {}
        }
      ],
      "subPackages": [
        {
          "root": "subpkg",
          "pages": []
        }
      ]
    }
    
  3. subpkg 目录上鼠标右键,点击 新建页面 选项,并填写页面的相关信息

    img

3.2.4 点击轮播图跳转到商品详情页面

代码略

3.2.5 封装 uni.$Toast() 方法

当数据请求失败之后,经常需要调用 uni.showToast({ /* 配置对象 */ }) 方法来提示用户。此时,可以在全局封装一个 uni.$Toast() 方法,来简化 uni.showToast() 方法的调用。具体的改造步骤如下:

  1. main.js 中,为 uni 对象挂载自定义的 $showMsg() 方法:

    js
    // 封装的展示消息提示的方法
    uni.$Toast = function (title = '数据加载失败!', duration = 1500) {
      uni.showToast({
        title,
        duration,
        icon: 'none',
      })
    }
    
  2. 今后,在需要提示消息的时候,直接调用 uni.$showMsg() 方法即可:

    js
    async getSwiperList() {
       const { data: res } = await uni.$http.get('/api/public/v1/home/swiperdata')
       if (res.meta.status !== 200) return uni.$Toast()
       this.swiperList = res.message
    }
    

3.3 分类区域

3.3.1 分类UI布局及样式

vue
<!-- 分类 -->
<view class="category-list">
    <view v-for="(item, index) in categoryList" :key="index" @click="tips">
        <image :src="item.imgUrl" mode=""></image>
        <text>{{ item.name }}</text>
    </view>
</view>

分类数据

js
// 分类数据
export const categoryData = [{
		name: 'NB超市',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E8%B6%85%E5%B8%82%402x.png',
		categoryId: 100001
	},
	{
		name: 'NB服饰',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E6%9C%8D%E9%A5%B0%402x.png',
		categoryId: 100003
	},
	{
		name: '全球购',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E7%90%83%E8%B4%AD%402x.png',
		categoryId: 100002
	},
	{
		name: 'NB生鲜',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%94%9F%E9%B2%9C%402x.png',
		categoryId: 100004
	},
	{
		name: 'NB到家',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%88%B0%E5%AE%B6%402x.png',
		categoryId: 100005
	},
	{
		name: '充值缴费',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%85%E5%80%BC%402x.png',
		categoryId: 100006
	},
	{
		name: '9.9元拼',
		imgUrl: 'https://s.yezgea02.com/1604041127880/9.9%402x.png',
		categoryId: 100007
	},
	{
		name: '领劵',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E9%A2%86%E5%88%B8%402x.png',
		categoryId: 100008
	},
	{
		name: '省钱',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%9C%81%E9%92%B1%402x.png',
		categoryId: 100009
	},
	{
		name: '全部',
		imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E9%83%A8%402x.png',
		categoryId: 100010
	}
]

样式

scss
// 分类列表
.category-list {
	display: flex;
	flex-shrink: 0;
	flex-wrap: wrap;
	padding-bottom: 26rpx;
	view {
		display: flex;
		flex-direction: column;
		width: 20%;
		text-align: center;
		image {
			width: 64rpx;
			height: 64rpx;
			margin: 18rpx auto;
		}
	}
}

3.3.2 逻辑实现

vue
<script setup>
const tips = () => {
	uni.$Toast('敬请期待')
}
</script>

3.4 新品上线【楼层】区域

3.4.1 新品上线UI布局及样式

UI布局

vue
<!-- 新品上线 -->
<view class="good">
    <view class="good-header">新品上线</view>
    <!-- 骨架屏 -->
    <vant-skeleton :row="3" :loading="loading">
        <view class="good-box">
            <view class="good-item" v-for="item in newGoods" :key="item.goodsId" @click="goToDetail">
                <image :src="item.goodsCoverImg"></image>
                <view class="good-desc">
                    <view class="title">{{ item.goodsName }}</view>
                    <view class="price">¥ {{ item.sellingPrice }}</view>
                </view>
            </view>
        </view>
    </vant-skeleton>
</view>

样式

scss
// 商品
.good {
	.good-header {
		background: #f9f9f9;
		height: 100rpx;
		line-height: 100rpx;
		text-align: center;
		color: $uni-primary;
		font-size: 32rpx;
		font-weight: 500;
	}
	.good-box {
		display: flex;
		justify-content: flex-start;
		flex-wrap: wrap;
		.good-item {
			box-sizing: border-box;
			width: 50%;
			border-bottom: 2rpx solid #e9e9e9;
			padding: 20rpx;
			image {
				display: block;
				width: 240rpx;
				height: 240rpx;
				margin: 0 auto;
			}
			.good-desc {
				text-align: center;
				font-size: 28rpx;
				padding: 20rpx 0;
				.title {
					color: #222333;
				}
				.price {
					color: $uni-primary;
				}
			}
			&:nth-child(2n + 1) {
				border-right: 2rpx solid #e9e9e9;
			}
		}
	}
}

3.4.2 获取数据

通过loading状态控制骨架屏

js
onMounted(async () => {
	// 1. 显示加载
	uni.showLoading({ title: '加载中...', mask: true })
	// 2. 加载数据
	const { data } = await getHome()
	console.log(data)
	state.swiperList = data.carousels
	state.newGoods = data.newGoodses
	state.hots = data.hotGoodses
	state.recommends = data.recommendGoodses
	state.loading = false
	// 3. 隐藏加载
	uni.hideLoading()
})