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 项目中,配置分包的步骤如下:
在项目根目录中,创建分包的根目录,命名为
subpkg
在
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": [] } ] }
在
subpkg
目录上鼠标右键,点击新建页面
选项,并填写页面的相关信息
3.2.4 点击轮播图跳转到商品详情页面
代码略
3.2.5 封装 uni.$Toast() 方法
当数据请求失败之后,经常需要调用 uni.showToast({ /* 配置对象 */ })
方法来提示用户。此时,可以在全局封装一个 uni.$Toast()
方法,来简化 uni.showToast()
方法的调用。具体的改造步骤如下:
在
main.js
中,为uni
对象挂载自定义的$showMsg()
方法:js// 封装的展示消息提示的方法 uni.$Toast = function (title = '数据加载失败!', duration = 1500) { uni.showToast({ title, duration, icon: 'none', }) }
今后,在需要提示消息的时候,直接调用
uni.$showMsg()
方法即可:jsasync 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()
})