一、项目初始化
1.创建一个小程序
因为之前我已经注册过小程序,所以这里使用的邮箱是小欣欣的邮箱。创建具体步骤就不多说
2.删除不必要的内容
如图所示:
3.创建项目文件夹
4.创建项目文件
5.引入字体图标
6.搭建项目tabbar结构
7.项目样式初始化
二、实现小程序各页面
1.首页
1.1.搜索框
(1)首先创建了一个SearchInput组件
(2) 然后在要使用的那个页面的json文件中引入,在html文件中使用即可
(3)对搜索组件进行开发即可 css文件
.search_input {
height: 90rpx;
padding: 10rpx;
background-color: var(--themeColor);
}
.search_input navigator {
display: flex;
color: #666;
height: 100%;
justify-content: center;
align-items: center;
background-color: #fff;
border-radius: 15rpx;
}
效果图:
1.2 轮播图
(1)获取轮播图数据
onLoad: function (options) {
// 1发送异步请求获取轮播图数据
wx.request({
url: 'https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata',
success: (result) => {
this.setData({
swiperList:result.data.message
})
// console.log(this.data.swiperList);
},
(2)渲染轮播图
<swiper autoplay interval="3000" circular indicator-dots indicator-color='skyblue'>
<swiper-item wx:for="{{swiperList}}" wx:key="item.goods_id">
<navigator>
<image mode="widthFix" src="{{item.image_src}}"></image>
</navigator>
</swiper-item>
</swiper>
轮播图效果图:
1.3.封装请求数据函数
使用:
1.4 分类列表
请求数据:
// 获取导航分类数组
getCatesList(){
request({url: 'https://api-hmugo-web.itheima.net/api/public/v1/home/catitems'}).then(result=>{
// console.log(result);
this.setData({
catesList:result.data.message
})
})
},
<view class="index_cate">
<navigator
wx:for="{{catesList}}"
wx:key="name"
>
<image mode="widthFix" src="{{item.image_src}}"></image></navigator>
</view>
样式:
/**index.wxss**/
.index_swiper swiper {
width: 750rpx;
height: 340rpx;
}
.index_swiper swiper image {
width: 100%;
}
.index_cate {
display: flex;
}
.index_cate navigator {
padding: 20rpx;
flex: 1;
}
.index_cate navigator image {
width: 100%;
}
效果图:
1.5楼层
请求获取楼层数据:
getFloorList() {
request({ url: 'https://api-hmugo-web.itheima.net/api/public/v1/home/floordata' }).then(result => {
console.log(result);
this.setData({
floorList: result.data.message
})
})
},
html:
<!-- 楼层开始 -->
<view class="index_floor">
<view class="floor_group"
wx:for="{{floorList}}"
wx:for-item="item1"
wx:for-index="index1"
wx:key="floor_title"
>
<!-- 标题 -->
<view class="floor_title">
<image mode="widthFix" src="{{item1.floor_title.image_src}}"></image>
</view>
<!-- 内容 -->
<view class="floor_content">
<navigator
wx:for="{{item1.product_list}}"
wx:for-item="item2"
wx:for-index="index2"
wx:key="name"
>
<image mode="widthFix" src="{{item2.image_src}}"></image>
</navigator>
</view>
</view>
</view>
<!-- 楼层结束 -->
样式:
// 楼层样式
.index_floor{
.floor_group{
.floor_title{
padding: 10rpx 0;
image{
width: 100%;
}
}
.floor_content{
// 清除浮动
overflow: hidden;
navigator {
float: left;
width: 33.33%;
// 后四个超链接
&:nth-last-child(-n+4){
// 原图的宽高 232*386
// 232/386 =33.33vw/height
// 第一张图片的高为 33.33vw*386/232
height:27.72711vw ;
border-left: 10rpx solid #fff;
}
&:nth-child(2),
&:nth-child(3){
border-bottom: 10rpx solid #fff;
}
image{
height: 100%;
width: 100%;
}
}
}
}
}
效果图:
2.分类
设计图:
2.1.获取分类数据:
// pages/category/index.js
// 引入用来发送请求的封装好的方法
import { request } from "../../request/index.js"
Page({
/**
* 页面的初始数据
*/
data: {
// 左侧的菜单数据
leftMenuList:[],
// 右侧的商品数据
rightGoodsContent:[]
},
// 接口返回的数据
CatesList:[],
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.getCates();
},
// 获取分类数据
getCates(){
request({
url:'https://api-hmugo-web.itheima.net/api/public/v1/categories'
}).then(result=>{
this.CatesList=result.data.message
// 构造左侧的大菜单数据
let leftMenuList=this.CatesList.map(v=>v.cat_name);
let rightGoodsContent =this.CatesList[0].children
this.setData({
leftMenuList,
rightGoodsContent
})
console.log(result);
})
}
})
2.2布局+点击左侧菜单栏跳转
wxml:
<!--pages/category/index.wxml-->
<view class="cate_index">
<!-- 搜索框开始 -->
<SearchInput></SearchInput>
<!-- 搜索框结束 -->
<view class="cates_container">
<!-- 左侧菜单开始 -->
<scroll-view class="left_menu" scroll-y>
<view
class="menu_item {{index===currentIndex?'active':''}}"
wx:for="{{leftMenuList}}"
wx:key="*this"
bindtap="handleItemTap"
data-index="{{index}}"
>{{item}}</view>
</scroll-view>
<!-- 左侧菜单结束-->
<!-- 右侧商品内容开始 -->
<scroll-view class="right_content" scroll-y>
<view class="goods_group"
wx:for="{{rightGoodsContent}}"
wx:for-index="index1"
wx:for-item="item1"
wx:key='cat_id'>
<view class="goods_title"
>
<text class="delimiter">/</text>
<text class="title">{{item1.cat_name}}</text>
<text class="delimiter">/</text>
</view>
<view class="goods_list">
<navigator
wx:for="{{item1.children}}"
wx:for-item="item2"
wx:for-index="index2"
wx:key="cat_id"
>
<image mode="widthFix" src="{{item2.cat_icon}}"></image>
<view class="goods_name">{{item2.cat_name}}</view>
</navigator>
</view>
</view>
</scroll-view>
<!-- 右侧商品内容结束 -->
</view>
</view>
wxss:
/* pages/category/index.wxss */
page {
height: 100%;
}
.cate_index .cates_container {
height: calc(100vh - 90rpx);
display: flex;
}
.cate_index .cates_container .left_menu {
flex: 2;
}
.cate_index .cates_container .left_menu .menu_item {
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
}
.cate_index .cates_container .left_menu .active {
color: var(--themeColor);
border-left: 5rpx solid currentColor;
}
.cate_index .cates_container .right_content {
flex: 5;
}
.cate_index .cates_container .right_content .goods_group .goods_title {
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
}
.cate_index .cates_container .right_content .goods_group .goods_title .delimiter {
color: #ccc;
padding: 0 10rpx;
}
.cate_index .cates_container .right_content .goods_group .goods_list {
display: flex;
flex-wrap: wrap;
}
.cate_index .cates_container .right_content .goods_group .goods_list navigator {
width: 33.33%;
text-align: center;
}
js:
// pages/category/index.js
// 引入用来发送请求的封装好的方法
import { request } from "../../request/index.js"
Page({
/**
* 页面的初始数据
*/
data: {
// 左侧的菜单数据
leftMenuList:[],
// 右侧的商品数据
rightGoodsContent:[],
// 被点击的左侧菜单
currentIndex:0
},
// 接口返回的数据
CatesList:[],
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.getCates();
},
// 获取分类数据
getCates(){
request({
url:'https://api-hmugo-web.itheima.net/api/public/v1/categories'
}).then(result=>{
this.CatesList=result.data.message
// 构造左侧的大菜单数据
let leftMenuList=this.CatesList.map(v=>v.cat_name);
let rightGoodsContent =this.CatesList[0].children
this.setData({
leftMenuList,
rightGoodsContent
})
// console.log(result);
})
},
// 左侧菜单的点击事件
handleItemTap(e){
// console.log(e);
// 1 获取被点击的标题上的索引
// 2 给data中的currentIndexfuzhi
// 3根据不同的索引渲染右侧的页面
const {index} =e.currentTarget.dataset;
let rightGoodsContent = this.CatesList[index].children;
this.setData({
currentIndex:index,
rightGoodsContent
})
}
})
效果图:
2.3.使用缓存技术
onLoad: function (options) {
/*
0 web中的本地存储和小程序中的本地存储的区别
1 写代码方式不一样
web:localStorage.setItem("key","value") localstorage.getiItem("key")
小程序中:wxwx.setStorageSync('key', 'value);wx.getStorageSync('key')
2 存的时候,有没有做类型转换
web中会把数据变为字符串
小程序中不存在类型转换
1 先判断一下本地存储中有没有旧的数据
{time:Date.now(),data:[...]}
2 没有旧数据就直接发送新请求
3 有旧数据,并且旧数据没有过期,就使用本地存储中的旧数据即可
*/
// 1 获取本地存储中的数据,(小程序也是存在本地存储技术)
const Cates=wx.getStorageSync("cates");
// 2 判断
if(!Cates){
this.getCates();
}else{
// 有旧的数据,定义过期时间
if(Date.now()-Cates.time>1000*600){
// 发送请求
this.getCates();
}else{
// 可以使用旧数据
this.Cates =Cates.data
// 构造左侧的大菜单数据
let leftMenuList=this.Cates.map(v=>v.cat_name);
let rightGoodsContent =this.Cates[0].children
this.setData({
leftMenuList,
rightGoodsContent
})
}
}
在获取分类数据时,把数据存入本地缓存中
2.4 优化分类页面
使得点击每一个左侧分类时,右侧内容页面都是置顶显示
2.5 定义公共接口
export const request=(params)=>{
// 定义公共的url
// url:https://api-hmugo-web.itheima.net/api/public/v1
const baseUrl ='https://api-hmugo-web.itheima.net/api/public/v1'
return new Promise((resolve,reject)=>{
wx.request({
...params,
url:baseUrl+params.url,
success:(result)=>{
resolve(result);
},
fail:(err)=>{
reject(err);
}
})
})
}
2.6 让小程序中支持es7的async语法
3 商品列表
3.1 实现点击页面跳转和传递参数(分类id)
传递的参数在商品列表的onload生命周期函数的options里面
3.2 实现搜索框和tabs组件
自定义组件,方法与上面自定义组件类似,这里就以动图演示
3.3.商品列表的静态结构
先用静态图片写死,调样式,然后动态处理
html:
<SearchInput></SearchInput>
<!-- 监听自定义事件 -->
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange" >
<block wx:if="{{tabs[0].isActive}}">
<view class="first_tab">
<navigator class="goods_item"
wx:for="{{goodsList}}"
wx:key="goods_id"
url="/pages/goods_detail/index?goods_id={{item.goods_id}}"
>
<!-- 左侧 图片容器 -->
<view class="goods_img_wrap">
<image mode="widthFix" src="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}"></image>
</view>
<!-- 右侧 商品容器 -->
<view class="goods_info_wrap">
<view class="goods_name">{{item.goods_name}}</view>
<view class="goods_price">¥{{item.goods_price}}</view>
</view>
</navigator>
</view>
</block>
<block wx:elif="{{tabs[1].isActive}}">销量</block>
<block wx:elif="{{tabs[2].isActive}}">价格</block>
</Tabs>
css:
/* pages/goods_list/index.wxss */
.first_tab .goods_item {
display: flex;
border-bottom: 1px solid #ccc;
}
.first_tab .goods_item .goods_img_wrap {
flex: 2;
display: flex;
justify-content: center;
align-items: center;
}
.first_tab .goods_item .goods_img_wrap image {
width: 70%;
}
.first_tab .goods_item .goods_info_wrap {
flex: 3;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.first_tab .goods_item .goods_info_wrap .goods_name {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.first_tab .goods_item .goods_info_wrap .goods_price {
color: var(--themeColor);
font-size: 32rpx;
}
3.4 加载下一页数据
效果图:
3.5 下拉刷新
效果图:
3.6 添加全局正在加载图标效果
效果图:
4 商品详情页面
4.1 获取商品详情数据
4.2 渲染商品详情页(包括轮播图等)
<!--pages/goods_detail/index.wxml-->
<view class="goods_detail">
<view class="detail_swiper">
<!-- 轮播图开始 -->
<swiper autoplay interval="3000" circular indicator-dots indicator-color='skyblue'>
<swiper-item wx:for="{{goodsObj.pics}}" wx:key="pics_id">
<image mode="widthFix" src="{{item.pics_mid}}"></image>
</swiper-item>
</swiper>
</view>
<!-- 轮播图结束 -->
<!-- 商品价格开始 -->
<view class="goods_price">
¥{{goodsObj.goods_price}}
</view>
<!-- 商品价格结束 -->
<!-- 商品名称+收藏开始 -->
<view class="goods_name_row">
<!-- 商品名称 -->
<view class="goods_name">
{{goodsObj.goods_name}}
</view>
<view class="goods_collect">
<text class="iconfont icon-shoucang"></text>
<view class="collect_text">
收藏
</view>
</view>
</view>
<!-- 商品名称+收藏结束 -->
<!-- 图文详情开始 -->
<view class="goods_info">
<!-- 标题 -->
<view class="goods_info_title">图文详情</view>
<!-- 内容 -->
<view class="goods_info_content">
<!-- 富文本 -->
<rich-text class="" nodes="{{goodsObj.goods_introduce}}">
</rich-text>
</view>
</view>
<!-- 图文详情结束 -->
</view>
样式:
/* pages/goods_detail/index.wxss */
.goods_detail .detail_swiper swiper {
height: 65vw;
text-align: center;
}
.goods_detail .detail_swiper swiper image {
width: 60%;
}
.goods_detail .goods_price {
padding: 15rpx;
font-size: 32rpx ;
font-weight: 600;
color: var(--themeColor);
}
.goods_detail .goods_name_row {
border-top: 5rpx solid #dedede;
border-bottom: 5rpx solid #dedede;
padding: 10rpx 0;
display: flex;
}
.goods_detail .goods_name_row .goods_name {
flex: 5;
color: #000;
font-size: 30rpx;
padding: 0 10rpx;
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.goods_detail .goods_name_row .goods_collect {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-left: 1rpx solid #000;
}
.goods_detail .goods_info .goods_info_title {
font-size: 32rpx;
color: var(--themeColor);
font-weight: 700;
padding: 20rpx;
}
js:
// 获取商品详情数据
async getGoodsDetail(goods_id) {
const goodsObj = await request({ url: "/goods/detail", data: { goods_id } })
this.setData({
// 因为只用到这些数据,所以为了提高性能,只需要把这些数据保存
goodsObj: {
pics: goodsObj.pics,
goods_name: goodsObj.goods_name,
goods_price: goodsObj.goods_price,
// 转换图片格式,兼容iPhone手机
goods_introduce: goodsObj.goods_introduce.replace(/\.webp/g, '.jpg')
}
})
// console.log(this.data.goodsObj);
}
效果图:
4.4.放大预览图片
这里api里面的urls里面包含多少个链接放大就会显示多少个链接
效果图:
4.5.底部工具栏
<!-- 底部工具栏开始 -->
<view class="btm_tool">
<view class="tool_item">
<view class="iconfont icon-kefu"></view>
<view>客服</view>
<button open-type="contact"></button>
</view>
<view class="tool_item">
<view class="iconfont icon-yixianshi-"></view>
<view>分享</view>
<button open-type="share"></button>
</view>
<navigator open-type="switchTab" url="/pages/cart/index" class="tool_item">
<view class="iconfont icon-gouwuche"></view>
<view>购物车</view>
</navigator>
<view class="tool_item btn_cart " bindtap="handleCartAdd">
加入购物车
</view>
<view class="tool_item btn_buy">
立即购买
</view>
</view>
<!-- 底部工具栏结束 -->
css:
// 底部工具栏样式
.btm_tool{
border-top: 1rpx solid #ccc;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 90rpx;
background-color: #fff;
display: flex;
.tool_item{
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 24rpx;
position: relative;
button{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
}
.btn_cart{
flex: 2;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #ffa500;
color: #fff;
font-size: 30rpx;
font-weight: 600;
}
.btn_buy{
flex: 2;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #eb4450;
color: #fff;
font-size: 30rpx;
font-weight: 600;
}
}
这里的button用的非常秒,为了避免button带来的样式影响,所以这样设置
效果图:
4.6.加入购物车
这里暂时没有接口,只有存在本地存储中
// 点击加入购物车
handleCartAdd() {
// 1.获取缓存中的购物车数组
let cart = wx.getStorageSync('cart') || [];
// 2.判断商品对象是否存在于购物车中
let index = cart.findIndex(v => v.goods_id === this.goodsInfo.goods_id);
if (index === -1) {
// 3.不存在,商品为第一次添加
this.goodsInfo.num = 1;
cart.push(this.goodsInfo)
} else {
// 4.已经存在购物车数据了 执行num++
cart[index].num++
}
// 5.把购物车重新添加回缓存中
wx.setStorageSync("cart", cart);
// 6.弹窗
wx.showToast({
title: '加入成功',
// 防抖
icon: 'sucess',
mask: true
});
}
5.购物车
效果图: