项目介绍
1、商品首页
2、分类页面
3、商品列表
4、商品详情
5、购物车
6、支付页面
7、授权页面
8、订单页面
9、搜索页面
10、个人中心
11、收藏页面
12、意见反馈
首先我们先在app.json配置文件中把以上的页面都添加好,整个项目的颜色是使用的蕃茄红,所以在app.wxss中也先进行一个颜色配置。
此时app.wxss的代码
page{
--themeColor:#e23b46;
}
image{
width:100%;
}
app.json的代码
{
"pages": [
"pages/index/index",
"pages/goods_list/index",
"pages/goods_detail/index",
"pages/cart/index",
"pages/collect/index",
"pages/order/index",
"pages/search/index",
"pages/user/index",
"pages/feedback/index",
"pages/login/index",
"pages/auth/index",
"pages/pay/index",
"pages/category/index"
],
"window": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#eb4450",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle": "white"
},
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "icons/home.jpg",
"selectedIconPath": "icons/home1.jpg"
},
{ "pagePath": "pages/category/index",
"text": "分类",
"iconPath": "icons/category.jpg",
"selectedIconPath": "icons/category1.jpg"
},
{ "pagePath": "pages/cart/index",
"text": "购物车",
"iconPath": "icons/cart.jpg",
"selectedIconPath": "icons/cart1.jpg"
},
{"pagePath": "pages/user/index",
"text": "我的",
"iconPath": "icons/my.jpg",
"selectedIconPath": "icons/my1.jpg"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
tips:
"style": "v2"表示启用新版的组件样式。
使用v2版本的组件样式默认情况和v1不同,可以先清除组件默认样式,使它与v1版本相同。
v2版本button默认display: block;要设置宽高可更改为display: inline-block;背景色可正常设置。
关键字sitemapLocation的作用主要是设置是否允许微信能否搜索小程序内的页面
官方说明:
微信现已开放小程序内搜索,开发者可以通过 sitemap.json 配置,或者管理后台页面收录开关来配置其小程序页面是否允许微信索引。当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。 爬虫访问小程序内页面时,会携带特定的 user-agent:mpcrawler 及场景值:1129。需要注意的是,若小程序爬虫发现的页面数据和真实用户的呈现不一致,那么该页面将不会进入索引中。
# sitemap 配置
小程序根目录下的 sitemap.json 文件用于配置小程序及其页面是否允许被微信索引,文件内容为一个 JSON 对象,如果没有 sitemap.json ,则默认为所有页面都允许被索引;
3、引入字体 图标
1、打开阿里巴巴字体图标库(网址:https://www.iconfont.cn/ )使用GitHub账号登陆
把想要的图标都添加到购物车里
点击图标新建项目
font class是指通过类的方式来使用这些图标,需要把他们引入到项目去
选择查看在线链接,链接如下图所示,然后把这个代码复制到网页里打开,出现了许多代码
然后把所有的代码都复制
然后回到项目打开styles文件夹目录,新增一个文件iconfont.wxss,把代码都放里面
然后在app.wxss(全局样式文件)中导入
@import "./styles/iconfont.wxss";
然后您index.wxml中测试
<view>首页
<text class="iconfont icon-shoucang1></text>
</view>
显示结果
1、实现首页功能
首页分为四大块内容
1、搜索框
您看搜索框有好几个页面都需要用到,为了方便复用,所以这里我们使用组件的形式来写搜索框
step1新建一个名为components的文件夹,然后右键选择新建一个component,取名为SearchInput
SearchInput.wxml
一点击搜索框,就会跳转到搜索页面去。
<view class="a">
<navigator url="/pages/search/index" open-type="navigate" >
搜索 </navigator>
</view>
tips:
navigator的open-type属性 可选值 'navigate'、'redirect'、'switchTab',对应于wx.navigateTo、wx.redirectTo、wx.switchTab的功能
open-type="navigate"等价于API的 wx.navigateTo 而wx.navigateTo的url是需要跳转的应用内非 tabBar 的页面的路径
open-type="redirect"等价于API的 wx.redirectTo 而wx.redirectTo的url是需要跳转的应用内非 tabBar 的页面的路径
open-type="switchTab"等价于API的 wx.switchTab而wx.switchTab的url是需要跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
最后一个switchTab事件触发以后 把前面的页面都关闭了
获取用户权限open-type="getUserInfo"
<button open-type="getUserInfo" @getuserinfo="bindGetUserInfo" @click="getUserInfo1">获取权限</button>
SearchInput.wxss代码:
.a {
height: 100rpx;
padding: 5rpx;
background-color: #eb4450;
}
.a navigator {
height: 40%;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
border-radius: 15rpx;
color: #666;
}
此时index/wxml代码:
<view class="pyg.index">
<!--搜索框 开始-->
<SearchInput> </SearchInput>
<!--搜索框 结束-->
</view>
在index.json中引入搜索框组件
{
"component": true,
"usingComponents": {
"SearchInput":"../../components/SearchInput/SearchInput"
},
"navigationBarTitleText":"优购首页"
}
2、轮播图、导航栏、楼层
step1 获取轮播图数据
首先我们先新建一个文件夹request,建立index.js文件
其中代码如下
export const request=(params)=>{
//定义公共的URL
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);
}
});
})
}
index.js代码如下:
//引入用来发送请求的方法,一定要把路径补全
import{request}from "../../request/index.js";
Page({
data:{
//轮播图数组
swiperList:[],
//导航数组
catesList:[],
//分类导航数组
floorList:[]
},
//这是页面开始加载时候就会触发的一个事件
onLoad:function(options){
this.getSwiperList();
this.getCateList();
this.getFloorList();
},
//获取轮播图数据方法
getSwiperList(){
request({url:"/home/swiperdata"})
.then(result=>{
this.setData({
swiperList:result.data.message
})
})
},
//获取分类导航数据方法
getCateList(){
request({url:"/home/catitems"})
.then(result=>{
this.setData({
catesList:result.data.message,
})
})
},
//获取分类导航楼层数据方法
getFloorList(){
request({url:"/home/floordata"})
.then(result=>{
this.setData({
floorList:result.data.message
})
})
},
})
此时index.wxml代码如下:
<view class="pyg.index">
<!--搜索框 开始-->
<SearchInput> </SearchInput>
<!--搜索框 结束-->
<!--轮播图 开始-->
<view class="index_swiper">
<!--
1、swiper标签存在默认的宽度和高度100%*150px
2、image标签也存在默认的宽度和高度320px*240px
(所以如果您不在wxss里写自适应的代码,切换到其他机型图片还是会像那个样子
3、设计图片和轮播图
1、原图的宽高 750*340
让图片的高度自适应 宽度等于100%
4、介绍一个图片标签
mode属性 渲染模式
widthFix 让图片的标签宽高和图片标签的内容的宽高都等比例的发生变化
-->
<swiper autoplay indicator-dots circular>
<swiper-item
wx:for="{{swiperList}}"
wx:key="goods_id">
<navigator>
<image mode="widthFix" src="{{item.image_src}}"></image>
</navigator>
</swiper-item>
</swiper>
</view>
<!--轮播图 结束-->
<!--导航 开始-->
<view class="index_cate">
<navigator
wx:for="{{catesList}}"
wx:key="name"
>
<image mode="widthFix" src="{{item.image_src}}">
</image>
</navigator>
</view>
<!--导航 结束-->
<!--页面楼层 开始-->
<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 src="{{item1.floor_title.image_src}}" mode="widthFix"></image>
</view>
<!-- 内容 -->
<view class="floor_list">
<navigator wx:for="{{item1.product_list}}" wx:for-item="item2" wx:for-index="index2" wx:key="name">
<image src="{{item2.image_src}}" mode="{{index2===0?'widthFix':'scaleToFill'}}"></image>
</navigator>
</view>
</view>
</view>
<!--页面楼层 结束-->
</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%;
}
.index_floor .floor_group .floor_title {
padding: 10rpx 0;
}
.index_floor .floor_group .floor_title image {
width: 100%;
}
.index_floor .floor_group .floor_list navigator {
float: left;
width: 30%;
}
.index_floor .floor_group .floor_list navigator:nth-last-child(-n+4) {
height: 24.52711207vw;
border-left: 10rpx solid #fff;
}
.index_floor .floor_group .floor_list navigator:nth-child(2),
.index_floor .floor_group .floor_list navigator:nth-child(3) {
border-bottom: 10rpx solid #fff;
}
.index_floor .floor_group .floor_list navigator image {
width: 100%;
height: 100%;
}
此时首页效果如图
2、分类页面(category)
首先我们先打开category的index.json文件,添加搜索框组件
{
"usingComponents": {
"SearchInput":"../../components/SearchInput/SearchInput"
},
"navigationBarTitleText":"商品分类"
}
首先我们先看后台封装的数据,分为左侧菜单和右侧菜单
1、获取接口数据
// pages/category/index.js
import{request}from "../../request/index.js";
Page({
/**
* 页面的初始数据
*/
data: {
//左侧的菜单数据
leftMenuList:[],
//右侧的商品数据
rightContent:[],
},
//接口的返回数据
Cates:[],
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.getCates();
},
//获取分类数据
/*
request成功以后,触发then方法,里面返回值res
*/
getCates(){
request({
url:"/categories"
})
.then(res=>{
this.Cates=res.data.message;
/*
map是遍历数组的意思
*/
//构造左侧的大菜单数据
let leftMenuList=this.Cates.map(v=>v.cat_name);
//构造右侧的商品数据
let rightContent=this.Cates[0].children;
this.setData({
leftMenuList,
rightContent
})
})
}
})
此时我们写category的 index.wxml代码
您能发现左右两侧菜单都能进行滚动,在小程序中提供了一个很好用的组件来实现滚动效果
<view class="cates">
<SearchInput></SearchInput>
<view class="cates_container">
<!--左侧菜单 scroll-y="true"是允许纵向滚动的意思 后面的true可省略不写-->
<scroll-view scroll-y="true" class="left_menu">
</scroll-view>
<!--右侧商品-->
<scroll-view scroll-top="{{scrollTop}}" scroll-y="true" class="right_content">
</scroll-view>
</view>
</view>
这里的思路是把左右两个菜单各看成一个容器盒子
category的index.wxml
<view class="cates">
<SearchInput></SearchInput>
<view class="cates_container">
<!--左侧菜单 scroll-y="true"是允许纵向滚动的意思 后面的true可省略不写-->
<scroll-view scroll-y="true" class="left_menu">
<view
class="menu_item"
wx:for="{{leftMenuList}}"
wx:key="*this"
>
{{item}}
</view>
</scroll-view>
<!--右侧商品 goods_group是个大容器,里面分为两个容器,一个标题,一个内容-->
<scroll-view scroll-top="{{scrollTop}}" scroll-y="true" class="right_content">
<!--因为右侧菜单数据需要做双层循环,所以这里需要用wx:for-index|item 因为在右边菜单标题数据中找不到唯一的属性值,所以这里加不加key值都可以 -->
<view class="goods_group"
wx:for="{{rightContent}}"
wx:for-item="item1"
wx:for-index="index1"
>
<!--右侧标题部分 因为这里的cat_name具有唯一性,所以使用这个属性名 且效果图是有两个斜杠-->
<view class="goods_title">
<text class="delimiter">/</text>
<text class="title">{{item1.cat_name}}</text>
<text class="delimiter">/</text>
</view>
<!--每一款商品都是一个超链接,所以这里要用到navigator标签-->
<view class="goods_list">
<navigator
wx:for="{{item1.children}}"
wx:for-index="index2"
wx:for-item="item2"
wx:key="cat_id"
>
<!--您看效果图此时应该要有图片了,图片的属性关键词就是cat_icon-->
<image mode="widthFix" src="{{item2.cat_icon}}"></image>
<!--图片下的文字,其关键词是cat_name-->
<view class="goods_name">{{item2.cat_name}}</view>
</navigator>
</view>
</view>
</scroll-view>
</view>
</view>
category的 index.wxss
page {
height: 100%;
}
.cates {
height: 100%;
}
.cates_container {
height: calc(100vh - 90rpx);
display: flex;
}
.cates_container .left_menu {
flex: 2;
}
.cates_container .left_menu .active{
color:#e23b46;
border-left:5rpx solid currentColor;
}
.cates .cates_container .left_menu .menu_item{
height:80rpx;
display: flex;
justify-content: center;
align-items: center;
font-size:30rpx;
}
.cates_container .right_content {
flex:5;
}
.cates_container .right_content .goods_group .goods_list{
display:flex;
flex-wrap:wrap;
}
.cates_container .right_content .goods_group .goods_title {
height:80rpx;
display:flex;
justify-content:center;
align-items:center;
}
.cates_container .right_content .goods_group .goods_title .delimiter {
color:#ccc;
padding:0 10rpx;
}
.cates_container .right_content .goods_group .goods_list navigator{
width:33.33%;
text-align:center;
}
.cates_container .right_content .goods_group .goods_list navigator image{
width:50%;
text-align:center;
}
p62 点击菜单切换商品内容
实现点击左侧菜单能有点击效果
首先在index.wxss中写
.cates_container .left_menu .active{
color:#e23b46;
border-left:5rpx solid currentColor;
}
然后看左边菜单栏,给谁添加这个类呢,得做个判断,我把左侧菜单的值变成一个索引
然后在category index.js中的data中添加被点击的左侧的菜单
// pages/category/index.js
import{request}from "../../request/index.js";
Page({
/**
* 页面的初始数据
*/
data: {
//左侧的菜单数据
leftMenuList:[],
//右侧的商品数据
rightContent:[],
//被点击的左侧的菜单索引
currentIndex:0
},
//接口的返回数据
Cates:[],
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
//获取分类数据
/*
request成功以后,触发then方法,里面返回值res
*/
getCates(){
request({
url:"/categories"
})
.then(res=>{
this.Cates=res.data.message;
/*
map是遍历数组的意思
*/
//构造左侧的大菜单数据
let leftMenuList=this.Cates.map(v=>v.cat_name);
//构造右侧的商品数据
let rightContent=this.Cates[0].children;
this.setData({
leftMenuList,
rightContent
})
})
}
})
并且在category index.wxml中的左侧菜单一栏中添加判断,并且给左侧菜单添加一下点击事件
<!--左侧菜单-->
<scroll-view scroll-y="true" class="left_menu">
<!--当index等于此时是否在激活的左侧菜单索引的状态中,是的话变成active的样式,否的话就是空,没有任何变化-->
<view
class="menu_item {{index===currentIndex?'active':''}}"
wx:for="{{leftMenuList}}"
wx:key="*this"
//给左侧菜单绑定一个点击事件
bindtap="handleItemTap"
//点击的时候还需要传递一些参数过去
data-index="{{index}}"
>
{{item}}
</view>
</scroll-view>
然后在category的index.js中添加左侧菜单的点击事件
//左侧菜单的点击事件
handleItemTap(e){
/*
1 获取被点击的标题身上的索引
2 给data中的currentIndex赋值
*/
const{index}=e.currentTarget.dataset;
/*
此时右侧原本的Cates[0],应该变成Cates[index]随机左侧点击的数据变化而变化
根据不同的索引来渲染右侧的商品内容
*/
let rightContent=this.Cates[index].children;
/*
这个this.setData是赋值操作,把左侧拿到的数据进行一个能渲染的代码
*/
this.setData({
currentIndex:index
/*
这时也要把右侧的数据扔到这里面来
*/
rightContent
})
}
此时左侧菜单已经可以出现激活选中的效果,这时右侧内容要随着左边点击的数据变化而变化
因为每次您在右侧商品栏下滑时候,然后左侧重新点一个分类,虽然会切换内容,可是商品也是从上次的位置显示,没有重新置顶显示商品,这个用户体验就不好。
在category index.wxml中,此处添加scroll-top里写一个变量,而不能写死一个数值0
<!--右侧商品-->
<scroll-view scroll-top="{{scrollTop}}" scroll-y="true" class="right_content">
然后看到index.js中的data里添加一条
data: {
//左侧的菜单数据
leftMenuList:[],
//右侧的商品数据
rightContent:[],
//被点击的左侧的菜单索引
currentIndex:0,
//右侧内容的滚动条距离顶部的距离,一开始先等于0
scrollTop:0
},
然后在index.js中的左侧菜单点击事件中添加
//左侧菜单的点击事件
handleItemTap(e){
/*
1 获取被点击的标题身上的索引
2 给data中的currentIndex赋值
*/
const{index}=e.currentTarget.dataset;
/*
此时右侧原本的Cates[0],应该变成Cates[index]随机左侧点击的数据变化而变化
根据不同的索引来渲染右侧的商品内容
*/
let rightContent=this.Cates[index].children;
/*
这个this.setData是赋值操作,把左侧拿到的数据进行一个能渲染的代码
*/
this.setData({
currentIndex:index,
/*
这时也要把右侧的数据扔到这里面来
*/
rightContent,
/*设置右侧内容的scroll-view标签的距离顶部的距离
*/
scrollTop:0
})
}
此时就已经完善了这个bug
因为这个页面的数据量太大,所以这里我们使用缓存技术
// pages/category/index.js
import{request}from "../../request/index.js";
Page({
/**
* 页面的初始数据
*/
data: {
//左侧的菜单数据
leftMenuList:[],
//右侧的商品数据
rightContent:[],
//被点击的左侧的菜单索引
currentIndex:0
},
//接口的返回数据
Cates:[],
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
/*
1、先判断以下本地存储中没有旧的数据
{time:Date.now(),data:[...]}
2、没有旧数据,直接发送新请求
3、有旧数据且该数据没有过期,就使用本地存储中的旧数据即可
小程序也有本地存储技术
*/
//1、获取本地存储中的数据
const Cates=wx.getStorageSync("cates");
//2、判断
if(!Cates){
//不存在 发送请求获取数据
this.getCates();
}else{
//有旧数据,定义过去事件为10s 1000是毫秒 *10 就是10秒的意思
if(Date.now()-Cates.time>1000*10){
//重新发送请求
this.getCates();
}else{
//可以使用旧数据
this.Cates=Cates.data;
//构造左侧的大菜单数据
let leftMenuList=this.Cates.map(v=>v.cat_name);
//构造右侧的商品数据
let rightContent=this.Cates[0].children;
this.setData({
leftMenuList,
rightContent
})
}
}
//没有旧数据,发送请求获取数据
this.getCates();
},
//获取分类数据
/*
request成功以后,触发then方法,里面返回值res
*/
getCates(){
request({
url:"/categories"
})
.then(res=>{
this.Cates=res.data.message;
//把接口的数据存入到本地存储中
wx.setStorageSync("cates",{time:Date.now(),data:this.Cates});
/*
map是遍历数组的意思
*/
//构造左侧的大菜单数据
let leftMenuList=this.Cates.map(v=>v.cat_name);
//构造右侧的商品数据
let rightContent=this.Cates[0].children;
this.setData({
leftMenuList,
rightContent
})
})
}
})
优化 接口路径
您可以看到上图中的接口链接,代码非常长,您想简化代码
您还可以去看pages/index.js的链接,我们都具有公共相同的部分
您在pages/request/index.js中优化代码如下
export const request=(params)=>{
//定义公共的URL
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);
}
});
})
}
此时,举例子我们还是在分类页面,其URL只需要写成
request({
url:"/categories"
})
如果不这样简化写,还会报错。
例如此时首页的js代码没有简化书写,页面出不来。去掉那些公共部分即可。
代码优化:简化返回值和使用es7的async语法
es7的async号称是解决回调的最终方案
商品列表页面(goods_list)
首先,先去category页面内的index.wxml右侧菜单的内容里添加一个URL进行一个页面跳转
然后这个跳转的key值是cat_id,根据该值的不同,所跳转的商品列表也不同。
这里URL中使用到的问号是传参的意思
<view class="goods_list">
<navigator
wx:for="{{item1.children}}"
wx:for-index="index2"
wx:for-item="item2"
wx:key="cat_id"
url="/pages/goods_list/index?cid={{item2.cat_id}}"
>
<image mode="widthFix" src="{{item2.cat_icon}}"></image>
<view class="goods_name">{{item2.cat_name}}</view>
</navigator>
您观察到搜索框下面有tab标签
我们首先我们在components文件夹中,添加文件夹Tabs,然后点击右键新建component
在index.js中
// pages/goods_list/index.js
Page({
/**
* 页面的初始数据
*/
data: {
tabs:[
{
id:0,
value:"综合",
isActive:true
},
{
id:1,
value:"销量",
isActive:false
},
{
id:2,
value:"价格",
isActive:false
}
]
},
/**
* 生命周期函数--监听页面加载
这里的console.log(options)是获取cat_id的意思
*/
onLoad: function (options) {
console.log(options);
},
//标题点击事件
handleTabsItemChange(e){
// 获取被点击的标题索引
const{index}=e.detail;
// 修改源数据
let{tabs}=this.data;
tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
// 赋值到data中
this.setData({
tabs
})
}
})
然后打开index.wxml中写 放一个组件名,属性值也是tabs
<SearchInput></SearchInput>
<Tabs tabs="{{tabs}}"><Tabs>
剩下的代码在tabs组件中去编写,回到组件Tabs文件夹中
Tabs.js
// components/Tabs/Tabs.js
Component({
/**
* 组件的属性列表
在properties中存放要接受副元素的数据
tabs属性名,这是一个对象,type数据类型
默认值是空数组value
*/
properties: {
tabs:{
type:Array,
value:[]
}
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
在methods中定义点击事件
*/
methods: {
//点击事件
handleItemTap(e){
//获取点击的索引
const {index}=e.currentTarget.dataset;
//触发父组件中的事件 事件名是自己取的 属性名属性值都是index
this.triggerEvent("tabsItemChange",{index});
}
}
})
Tabs.wxml
<view class="tabs">
//分为标题部分
<view class="tabs_title">
//添加一个view容器,开始循环 还要给这个添加一个激活选中的代码
<view
wx:for="{{tabs}}"
wx:key="id"
class="title_item {{item.isActive?'active':''}}"
//给每一个小标签都绑定一个点击事件
bindtap="handleItemTap"
//传递一个被点击的索引
data-index="{{index}}">
{{item.value}}
</view>
</view>
//还有一部分是内容部分
<view class="tabs_content">
<slot></slot>
</view>
</view>
然后在goods_list中的index.json
{
"usingComponents": {
"SearchInput":"../../components/SearchInput/SearchInput",
"Tabs":"../../components/Tabs/Tabs"
},
"navigationBarTitleText":"商品列表"
}
然后您再分析这个最终的效果图,左侧放的是图片,右侧放的是商品的名称,商品名称下面是商品价格
商品列表的URL
https://api-hmugo-web.itheima.net/api/public/v1/goods/search
这是获取到的数据
goods_list的index.wxml代码
<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"
>
<!--左侧 图片容器 wrap是容器的意思 -->
<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>
<!--右侧 商品容器 右侧分为商品信息和价格两个容器 ps:看不懂goods_name样式代码的去百度css默认不换行样式-->
<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:if="{{tabs[1].isActive}}">1</block>
<block wx:if="{{tabs[2].isActive}}">2</block>
</Tabs>
index.json
{
"usingComponents": {
"SearchInput":"../../components/SearchInput/SearchInput",
"Tabs":"../../components/Tabs/Tabs"
},
"navigationBarTitleText":"商品列表",
"enablePullDownRefresh":true,
"backgroundTextStyle":"dark"
}
inde
// pages/goods_list/index.js
// pages/category/index.js
import{request}from "../../request/index.js";
Page({
/**
* 页面的初始数据
*/
data: {
tabs:[
{
id:0,
value:"综合",
isActive:true
},
{
id:1,
value:"销量",
isActive:false
},
{
id:2,
value:"价格",
isActive:false
}
],
//再来定义一个数据
goodsList:[]
},
//URL传参 前两个参数名给个空值 pagesize一次请求10条数据
QueryParams:{
query:"",
cid:"",
pagenum:1,
pagesize:10
},
//把总页数变成一个全局参数
totalPages:1,
/**
* 生命周期函数--监听页面加载,在onload里帮助上面的URL代码进行传参
*/
onLoad: function (options) {
this.QueryParams.cid=options.cid;
this.getGoodsList();
/*
添加请求数据时候显示加载中的提示,这个在官方文档的交互中能看到相应的关键词
5000是五秒的意思,每一次有请求都手动写一次代码太繁琐了,把这个写到全局请求代码中去
*/
//wx.showLoading({
//title:"加载中",
// })
// setTimeout(function(){
//wx.hideLoading()
//},5000)
},
//发送异步请求 获取商品列表数据 发送请求的方式使用es7的,这里使用async是代表异步方法
async getGoodsList(){
const res=await request({url:"/goods/search",data:this.QueryParams});
//获取总条数 因为这个总条数需要在别的方法使用,所以要把它变成一个全剧参数
const total=res.data.message.total;
//计算总页数
this.totalPages=Math.ceil(total/this.QueryParams.pagesize);
//console.log(this.totalPages);
//goods_list属性名 res.goods属性值
//首先先把旧数据用...解构出来,然后把新数据...解构出来,然后把getGoodsList()放到onReachBottom方法中进行重新调用
this.setData({
//拼接的数组
goodsList:[...this.data.goodsList,...res.data.message.goods]
})
//关闭下拉刷新的窗口 如果没有调用下拉刷新窗口,也不会报错
wx.stopPullDownRefresh();
},
//标题点击事件 从子组件(tabs组件)传递过来的
handleTabsItemChange(e){
// 获取被点击的标题索引
const{index}=e.detail;
// 修改源数组 让它产生一个激活被选中的效果 v是循环项,i是索引等于大于
let{tabs}=this.data;
tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
// 赋值到data中
this.setData({
tabs
})
},
/*
现在我们来实现向下滑动实现加载下一页数据的动作
1、找到滚动条触底事件(去官方文档看)
2、判断是否还有下一页数据
step1 获取总页数(获取数据中的total就是总条数 根据总条数算一下总页数)
总页数的计算用一个数学方法天花板函数
总页数=Math.ceil(总条数/页容量pagesize)
=Math.ceil(23/10)
这样算起来,第一页有10条数据,第二页有10条,第三页有三条
step2 获取到当前的页数(pagenum)
step3 判断一下 当前的页码是否大于或者等于总页数,如果为true,则不需要再加载下一页数据
3、如果没有下一页数据,弹出来一个提示
4、假如还有下一页数据,来加载下一页数据
1、当前页码加1
2、重新发送获取下一页请求
3、数据请求回来后,不要直接写this.setdata,而是要追加到原数组goodsList中
进行一个数据的拼接,而不是全部替换
*/
//页面上滑 滚动条触底事件
onReachBottom(){
// 判断还有没有下一页数据
//this.QueryParams.pagenum当前页码
//this.totalPages是总页数
if(this.QueryParams.pagenum>=this.totalPages){
//没有下一页数据
//使用一个方法 这个方法是提示一会儿这个提示会自动隐藏
wx.showToast({
title: '没有下一页数据了'
})
}else{
//还有下一页数据 先把页码加一
this.QueryParams.pagenum++;
//调用获取数据方法
this.getGoodsList();
}
},
/*需求二 下拉刷新页面
1、触发下拉刷新事件
需要在index.json文件中开启一个配置项目
添加enablePullRefresh属性,这是全局下拉刷新的意思,开启后上拉没有等待的小圆圈
还得添加“backgroundTextStyle":"dark"
找到触发下拉刷新的事件,并在里面添加逻辑
2、在这个方法中找到重置数据 数组 在触发事件后应该都变成空数组
3、 重置页码 设置为1
4、重新发送请求
5、数据刷新请求回来以后,需要手动的关闭等待效果
*/
//下拉刷新事件 这个事件存放在页面的生命周期函数中,在这里面重置数组,重置页码
onPullDownRefresh(){
//1、重置数组
this.setData(
{
goodsList:[]
}
)
//2、重置页码
this.QueryParams.pagenum=1;
//3、发送请求
this.getGoodsList();
}
})
index.wxss
navigator{
display:flex;
border-bottom: 5rpx solid #ccc;
}
navigator .goods_img_wrap{
flex:2;
display:flex;
justify-content: center;
align-items: center;
}
navigator .goods_img_wrap image{
width:70%;
}
navigator .goods_info_wrap{
flex:3;
display: flex;
flex-direction:column;
justify-content:space-around;
}
navigator .goods_info_wrap .goods_name{
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
navigator .goods_info_wrap .goods_price{
color:rgb(238, 2, 34);
font-size: 32rpx;
}
这里用到es7新语法,详情里点击增强编译就好
request文件夹到index.js代码
//同时发送异步代码的次数 ajax
let ajaxTimes=0;
export const request=(params)=>{
ajaxTimes++;
//显示加载中的效果 mask表示要不要显示一层蒙板
wx.showLoading({
title: '加载中',
mask:true
});
//定义公共的URL
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);
},
//把第三个回调函数掉出来,这个函数不管成功或者失败都会触发都一个函数
//关闭正在等待的图标
complete:()=>{
ajaxTimes--;
if(ajaxTimes===0){
//关闭正在等待的图标
wx.hideLoading();
}
}
});
})
}
商品详情页面(goods_detail)
首先您得在goods_list页面写一个跳转代码,您看这个跳转需要有goods-id的key值
商品详情页面的效果图
首先我们先在onload函数中获取商品列表页面的点击参数
商品详情页面的URL是https://api-hmugo-web.itheima.net/api/public/v1/goods/detail
通过该地址直接通过文档访问是不行的,因为没有必填的参数,必须通过程序中的传入相关参数访问才可以的,必须要在后面加?goods_id=43986才有用
所以正确的URL是:https://api-hmugo-web.itheima.net/api/public/v1/goods/detail?goods_id=43986
这个goods_id的值是你点击不同的商品而成产生的id值,比如你点击三星电视,获取的id就是{goods_id: "25972"}
您再把数值换成26972,此时网页显示的详情列表就是三星电视的列表了。
它的返回值是一个对象模式
存储的时候不要写成数组
step开始发送请求获取数据
您看效果图上面首页得来一个轮播图,轮播图数据就在pics里
step2 页面分析
关于wxss的
padding:10rpx 0;
代表上下为10,左右为0
您想要一个容器离上下容器的距离和这中间的颜色
border-top:5rpx solid #dedede;
border-bottom:5rpx solid #dedede;
您想要离左边容器有距离,用颜色表示距离
border-left:1rpx solid #000;
想要容器的框固定不被撑开,加上以下四行代码display...
.goods_name_row .goods_name{
flex:5;
color:#000;
font-size:35rpx;
padding:0 10rpx;
display:-webkit-box;
overflow:hidden;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;
}
您拿到的这个数据非常巨大
可是您使用到的属性值也不过仅仅五六个,如果全部就用的话加载会很慢,代码运行会很臃肿,所以去js文件的获取商品详情数据的方法的this.setData中手动添加需要用到的属性值
实现点击图片会放大的功能
<!--购物车在首页存在tabbar上,navigator不能直接跳,需要open设置-->
<navigator open-type="switchTab“ url="/pages/cart/index"
goods_detail index.wxml代码
<!--首先先写轮播图数据 swiper-->
<view class="detail_swiper">
<!--给轮播图加上三个属性-->
<swiper
autoplay
circular
indicator-dots
>
<!--对swiper-item进行循环,对这个循环要进行一个对象.方法的形式-->
<swiper-item
wx:for="{{goodsObj.data.message.pics}}"
wx:key="pics_id"
bindtap="handlePreviewImage"
data-url="{{item.pics_mid}}"
>
<!--这里选一个中间大小的图片URLpics_mid-->
<image mode="widthFix" src="{{item.pics_mid}}">
</image>
</swiper-item>
</swiper>
</view>
<!--价格-->
<view class="goods_price">
¥{{goodsObj.data.message.goods_price}}
</view>
<!--商品名称及收藏功能-->
<view class="goods_name_row">
<!--左边是商品名称-->
<view class="goods_name">{{goodsObj.data.message.goods_name}}</view>
<!--右边是收藏按钮-->
<view class="goods_collect">
<text class="iconfont icon-shoucang1"></text>
<view class="collect_text">收藏</view>
</view>
</view>
<!--图文详情-->
<view class="goods_info">
<!--标题-->
<view class="goods_info_title">图文详情</view>
<!--内容 要使用富文本,里面存在一个nodes属性-->
<view class="goods_info_content">
<rich-text nodes="{{goodsObj.data.message.goods_introduce}}"></rich-text>
</view>
</view>
<!--底部工具栏-->
<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-fenxiang"></view>
<view>分享</view>
<button open-type="share"></button>
</view>
<navigator open-type="switchTab" url="/pages/cart/index" class="tool_item">
<view class="iconfont icon-qicheqianlian-"></view>
<view>购物车</view>
</navigator>
<!--使用小程序内置的本地存储来缓存购物车数据-->
<view class="tool_item btn_cart" bindtap="handleCartAdd">
加入购物车
</view>
<view class="tool_item btn_buy">
立即购买
</view>
</view>
index.js
// pages/goods_detail/index.js
import{request}from "../../request/index.js";
Page({
/**
* 页面的初始数据
*/
data: {
goodsObj:{}
},
//商品对象
GoodsInfo:{},
/**
* 生命周期函数--监听页面加载
* 首先我们先在onLoad里面添加商品列表页参数goods_id
* 然后在onload中使用this调用getGoodsDetail方法就能在控制台看到获取的数据信息,然后我们把数据填充到data当中
* 在data写一个goodObj对象,在数据拿到之后,给该对象赋值,写在getGoodsDetail中,this.setData
* step2 放大预览功能
* 思路;给轮播图绑定点击事件
* 调用小程序对API previewImage
给<swiper-item>标签添加bintap="handlePreviewImage"
step3 点击加入购物车
1、绑定点击事件
2、获取缓存中的购物车数据 数组格式的数据,后期可以存储多个商品
3、先判断 当前商品是否已经存在于购物车
* 若已经存在商品,则添加其商品的数量(执行购物车数量++
把购物车数组填充到缓存中
*若这个商品是第一次添加,不存在于购物车的数组中,直接给购物车数组添加一个新元素即可
带上一个购物数量属性num/nums都可以
*/
onLoad: function (options) {
const{goods_id}=options;
this.getGoodsDetail(goods_id);
},
//获取商品详情数据 (goods_id是传递的参数),使用es7的方式获取
async getGoodsDetail(goods_id){
//const res=await request({url:"/goods/detail",data:{goods_id}});
//干脆把这个变量名res页改成goodsObj
const goodsObj=await request({url:"/goods/detail",data:{goods_id}});
this.GoodsInfo=goodsObj;
//console.log(res);
this.setData({
goodsObj
})
},
//点击轮播图,放大预览,在data下建立商品对象GoodsInfo
handlePreviewImage(e){
//console.log("dayin");
//先构造要预览的图片数组,可是在这里你无法拿到轮播图数据,所以需要去做全局声明
//在data下声明一个全局对象GoodsInfo
//等拿到商品详情数据后去给GoodsInfo赋值this.GoodsInfo=goodsObj;
//然后在此方法内this.data中要使用goodsObj,而不是GoodsInfo,会报错
const urls=this.data.goodsObj.data.message.pics.map(v=>v.pics_mid);
//点击事件触发以后,需要接收传递过来的图片URL,回到wxml文件中的标签中自定义属性的方式来传参data-url
const current=e.currentTarget.dataset.url;
wx.previewImage({
urls,
current
})
},
//点击 加入购物车
handleCartAdd(){
//1、获取缓存中的购物车 数组
//getStorageSync同步的方式 key的名称也叫cart
//let cart=wx.getStorageSync("key")
//因为第一次获取的时候是空字符串,所以需要用||转换一下数组[]格式
//经过转换以后可以确定该数组肯定是个数组格式
let cart=wx.getStorageSync("cart")||[];
//通过以下方式来判断商品对象是否存在于购物车数组中
//findIndex的作用是为真时返回-1
let index=cart.findIndex(v=>v.goods_id===this.GoodsInfo.goods_id);
if(index===-1){
//不存在 第一次添加
this.GoodsInfo.num=1;
cart.push(this.GoodsInfo);
}else{
//已经存在于购物车,执行num++
cart[index].num++;
}
//5、把购物车重新添加回缓存中
wx.setStorageSync('cart', cart);
// 6、弹出提示
wx.showToast({
title: '加入购物车成功',
icon:'success',
//把mask改成true,可以防止用户手抖疯狂点击加入
//该属性值打开后,只有过了五秒以后,用户才能继续点击
mask:true
});
}
})
index.wxss
.detail_swiper swiper {
height: 65vw;
text-align: center;
}
.detail_swiper swiper image {
width: 60%;
}
.goods_price{
padding:15rpx;
font-size:32rpx;
font-weight:600;
color:rgb(235, 13, 13)
}
.goods_name_row{
display:flex;
border-top:5rpx solid #dedede;
border-bottom:5rpx solid #dedede;
padding:10rpx 0;
}
.goods_name_row .goods_name{
flex:5;
color:#000;
font-size:35rpx;
padding:0 10rpx;
display:-webkit-box;
overflow:hidden;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;
}
.goods_name_row .goods_collect{
flex:1;
display:flex;
flex-direction:column;
justify-content: center;
align-items:center;
border-left:1rpx solid #000;
}
.goods_name_row .goods_collect .iconfont{
}
.goods_name_row .goods_collect .collect_text{
}
.goods_info{
}
.goods_info .goods_info_title{
font-size:45rpx;
color:rgba(230, 9, 9, 0.89);
font-weight:600;
padding:20rpx;
}
.goods_info .goods_info_content{
}
.btm_tool{
border-top: 1px solid #ccc;
position:fixed;
left:0;
bottom:0;
width:100%;
height:90rpx;
background-color: #fff;
display:flex;
}
.btm_tool .tool_item{
flex:1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size:30rpx;
position:relative;
}
.btm_tool .tool_item button{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
opacity:0;
}
.btm_tool .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;
}
.btm_tool .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;
}
index.json
{
"usingComponents": {},
"navigationBarTitleText":"商品详情"
}
购物车页面
购物车页面index.js
/*
1、第一个功能:获取用户的收货地址
step1 绑定点击事件
step2 事件触发时候,调用小程序内置的API(获取用户的收货地址)
wx.chooseAddress
*/
Page({
//点击按钮 获取收获地址
handleChooseAddress(){
//2、获取收获地址 无论是谁,调用这行代码时候都会调出这个张三的地址
wx.chooseAddress({
success: (result) => {
console.log(result);
},
})
}
})
写到这里的时候编译运行,控制台有这个地址的信息
wx.getSetting(Object object)
查看获取用户当前的权限属性。
wx.getSetting({ success:(result)=>{ console.log(result);//打印台输出authSetting里的scope值为true } })
tips:undefined是未定义的意思
这个接口可以直接获取地址(系统默认地址张三),而不会有权限弹窗确定
//2、获取收获地址 调用这行代码时候都会调出这个张三的地址
wx.chooseAddress({
success: (result) => {
//console.log(result);
}
})
这个接口可以获取当前地址,会有权限弹窗提示,但仅有一次
wx.chooseLocation({
success: (result) => {
//console.log(result);
}
})
此时此刻index.js代码
/*
1、第一个功能:获取用户的收货地址
step1 绑定点击事件
step2 事件触发时候,调用小程序内置的API(获取用户的收货地址)
wx.chooseAddress
获取用户对小程序对获取地址的「权限状态」(scope)
小程序现在更新了,已经不需要获取这个权限状态了
但还是照着教程写一遍,即使依旧没有弹窗提示要授予权限了
首先获取小程序授予获取地址对权限状态scope(使用const获取)
如果scope为true或者为undefined(未定义【从来没有调用过获取用户地址的API),则直接调用wx.chooseAddress方法
如果弹窗出现后,点击了取消,scope值为false,那需要引导用户再打开授权设置的页面
wx.openSetting,当用户重新获取地址权限后,再调用wx.chooseAddress方法
step3 把获取到的地址存入到本地存储中
页面加载完毕,放在onLoad事件中,由于购物车页面被频繁使用
希望购物车页面每一次打开的时候都能初始化,使用onShow生命周期事件
1、首先获取本地存储中到地址数据
2、把数据设置给data中到一个变量
*/
Page({
data:{
address:{}
},
onShow:function(){
//1、获取缓存中的收货地址信息
const address=wx.getStorageInfoSync('address');
//2 给data赋值
this.setData({
address
})
},
//点击按钮 获取收获地址
handleChooseAddress(){
//首先获取权限状态
wx.getSetting({
success:(result)=>{
// console.log(result);//打印台输出authSetting里的scope值为true
//authSetting后面本应该写.scope.address,但这里这样写就太奇怪了
//如果发现一些属性名很怪异的时候,就要用[" "]的形式来获取属性值
const scopeAddress=result.authSetting["scope.address"];
if(scopeAddress===true||scopeAddress===undefined){
wx.chooseAddress({
//为了使这个api里的参数名和getSetting中的参数名不重复,所以这里加个1区分
// success: (result) => {},
success: (result1) => {
console.log(result1);
}
});
}else{
//用户以前拒绝过授予权限,应该先又到用户打开权限
wx.openSetting({
//获取到数据后,success表示用户重新授予权限
success:(result2)=>{
//直接可以调用收获地址代码
wx.chooseAddress({
//为了使这个api里的参数名和openSetting中的参数名不重复,所以这里加个3区分
// success: (result) => {},
success: (result3) => {
console.log(result3);
}
});
}
})
}
}
})
}
})
如果写openSetting这个方法
wx.getSetting({
success:(result)=>{
// console.log(result);//打印台输出authSetting里的scope值为true
//authSetting后面本应该写.scope.address,但这里这样写就太奇怪了
//如果发现一些属性名很怪异的时候,就要用[" "]的形式来获取属性值
const scopeAddress=result.authSetting["scope.address"];
if(scopeAddress===true||scopeAddress===undefined){
//用户以前拒绝过授予权限,应该先又到用户打开权限
wx.openSetting({
//获取到数据后,success表示用户重新授予权限
success:(result2)=>{
//直接可以调用收获地址代码
wx.chooseAddress({
//为了使这个api里的参数名和openSetting中的参数名不重复,所以这里加个3区分
// success: (result) => {},
success: (result3) => {
console.log(result3);
}
});
}
})
}
}
})
点击获取收货地址按钮后
如果您还想加一个弹窗提示
wx.showModal({
title: '提示',
content: '这是一个模态弹窗',
success (res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
我们在utils中的asyncWx.js中添加了一些优化封装代码
//对cart中的js代码进行一个优化,对getSetting进行封装
//export导出getSetting这个函数
export const getSetting=()=>{
//resolve成功的回调函数 reject失败的
return new Promise((resolve,reject)=>{
wx.getSetting({
withSubscriptions: true,
success: (result) => {
resolve(result);
},
fail: (err) => {
reject(err);
}
})
})
}
export const chooseAddress=()=>{
//resolve成功的回调函数 reject失败的
return new Promise((resolve,reject)=>{
wx.chooseAddress({
withSubscriptions: true,
success: (result) => {
resolve(result);
},
fail: (err) => {
reject(err);
}
})
})
}
export const openSetting=()=>{
//resolve成功的回调函数 reject失败的
return new Promise((resolve,reject)=>{
wx.openSetting({
withSubscriptions: true,
success: (result) => {
resolve(result);
},
fail: (err) => {
reject(err);
}
})
})
}
//对cart中的js代码进行一个优化,对showModel进行封装
//export导出showModel这个函数
export const showModel=(content)=>{
//resolve成功的回调函数 reject失败的
return new Promise((resolve,reject)=>{
wx.showModal({
title:'提示',
content:'您确定要删除吗?',
success:(res)=>{
resolve(res);
},
fail:(err)=>{
reject(err);
}
})
})
}
export const showToast=(ctitle)=>{
//resolve成功的回调函数 reject失败的
return new Promise((resolve,reject)=>{
wx.showToast({
title:'title',
icon:'none',
success:(res)=>{
resolve(res);
},
fail:(err)=>{
reject(err);
}
})
})
}
在detail的js文件中添加了
//点击 加入购物车
handleCartAdd(){
//1、获取缓存中的购物车 数组
//getStorageSync同步的方式 key的名称也叫cart
//let cart=wx.getStorageSync("key")
//因为第一次获取的时候是空字符串,所以需要用||转换一下数组[]格式
//经过转换以后可以确定该数组肯定是个数组格式
let cart=wx.getStorageSync("cart")||[];
//通过以下方式来判断商品对象是否存在于购物车数组中
//findIndex的作用是为真时返回-1
let index=cart.findIndex(v=>v.data.message.goods_id===this.GoodsInfo.data.message.goods_id);
if(index===-1){
//不存在 第一次添加
this.GoodsInfo.num=1;
//商品的选中状态
this.GoodsInfo.checked=true;
cart.push(this.GoodsInfo);
}else{
//已经存在于购物车,执行num++
cart[index].num++;
}
//5、把购物车重新添加回缓存中
wx.setStorageSync('cart', cart);
// 6、弹出提示
wx.showToast({
title: '加入购物车成功',
icon:'success',
//把mask改成true,可以防止用户手抖疯狂点击加入
//该属性值打开后,只有过了五秒以后,用户才能继续点击
mask:true
});
}
})
cart.wxml
<!--收获地址容器-->
<!--使用if判断是否存在收货地址,随便验证一个属性名,有值则存在,!感叹号是取反的意思-->
<view class="revice_address_row">
<view class="address_btn" wx:if="{{!address.userName}}">
<!--primary是绿色 背景为plain白色
给button添加一个点击事件,然后回js文件中写事件
-->
<button bindtap="handleChooseAddress" type="primary" plain>获取收货地址</button>
</view>
<!--当收获地址存在时候 显示详细信息 这里的else和上面的if相对于-->
<view wx:else class="user_info_row">
<!--左边-->
<view class="user_info">
<view>{{address.userName}}</view>
<view>{{address.all}}</view>
</view>
<!--右边-->
<view class="user_phone">
<view>{{address.telNumber}}</view>
</view>
</view>
</view>
<!--购物车内容-->
<view class="cart_content">
<!--购物车部分名称-->
<view class="cart_title">
购物车
</view>
<!--内容部分-->
<!--当cart数组长度不为0 显示商品信息-->
<view class="cart_main">
<block wx:if="{{cart.length!==0}}">
</block>
<block wx:else><image mode="widthFix" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2Fe1b1467beea0a9c7d6a56b32bac6d7e5dcd914f7c3e6-YTwUd6_fw658&refer=http%3A%2F%2Fhbimg.b0.upaiyun.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631121598&t=5ae46738ff4f8cf63de9f4b048d40905"></image></block>
<!--内容部分有以下元素-->
<view class="cart_item"
wx:for="{{cart}}"
wx:key="goods_id"
>
<!--复选框-->
<view class="cart_chk_wrap">
<checkbox-group data-id="{{item.data.message.goods_id}}" bindchange="handeItemChange">
<checkbox checked="{{item.checked}}"></checkbox>
</checkbox-group>
</view>
<!--商品图片-->
<navigator class="cart_img_wrap">
<image mode="widthFix" src="{{item.data.message.goods_small_logo}}"></image>
</navigator>
<!--商品信息-->
<view class="cart_info_wrap">
<view class="goods_name">{{item.data.message.goods_name}}</view>
<view class="goods_price_wrap">
<view class="goods_price">${{item.data.message.goods_price}}</view>
<view class="cart_num_tool">
<view bindtap="handleItemNumEdit" data-id="{{item.data.message.goods_id}}" data-operation="{{-1}}" class="num_edit">-</view>
<view class="goods_num">{{item.num}}</view>
<view bindtap="handleItemNumEdit" data-id="{{item.data.message.goods_id}}" data-operation="{{1}}"class="num_edit">+</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!--底部工具栏-->
<view class="footer_tool">
<!--全选-->
<view class="all_chk_wrap">
<checkbox-group bindchange="handleItemAllCheck">
<checkbox checked="{{allChecked}}">
全选
</checkbox>
</checkbox-group>
</view>
<!--总价格-->
<view class="total_price_wrap">
<view class="total_price">
合计:<text class="total_price_text">${{totalPrice}}</text>
</view>
<view>包含运费</view>
</view>
<!--结算-->
<view class="order_pay_wrap" bindtap="handlePay">
结算{{totalNum}}
</view>
<!---->
</view>
cart.js
/*
1、第一个功能:获取用户的收货地址
step1 绑定点击事件
step2 事件触发时候,调用小程序内置的API(获取用户的收货地址)
wx.chooseAddress
获取用户对小程序对获取地址的「权限状态」(scope)
小程序现在更新了,已经不需要获取这个权限状态了
但还是照着教程写一遍,即使依旧没有弹窗提示要授予权限了
首先获取小程序授予获取地址对权限状态scope(使用const获取)
如果scope为true或者为undefined(未定义【从来没有调用过获取用户地址的API),则直接调用wx.chooseAddress方法
如果弹窗出现后,点击了取消,scope值为false,那需要引导用户再打开授权设置的页面
wx.openSetting,当用户重新获取地址权限后,再调用wx.chooseAddress方法
把获取到的地址存入到本地存储中
step3 页面加载完毕,放在onLoad事件中,由于购物车页面被频繁使用
希望购物车页面每一次打开的时候都能初始化,使用onShow生命周期事件
1、首先获取本地存储中到地址数据
2、把数据设置给data中到一个变量
step4 onShow
0、回到了商品详情页面,第一次添加商品时候,手动添加属性
num=1
checked=true
1、您在商品详情页面点击「加入购物车」之后会获取到一个商品数据
2、把购物车数据填充到data中
5 总价格totalPrice和总数量totalNum
1、商品被选中
2、获取购物车数组
3、遍历
4、判断商品是否被选中
5、总价格+=商品单价*商品数量
6、总数量+=商品的数量
7、把计算后的价格和数量,设置回data即可
6 点击勾选功能,取消该商品,同时结算价格改变,打开后端URL该功能属性名为checked
1、给wxml中的checkbox-group绑定一个change事件
2、获取到被修改的商品对象(可以通过索引或者goods_id)
在wxml中的checkbox-group富文本中添加data-id
3、商品对象的选中状态 取反
4、重新填充回data以及缓存中
5、重新计算全选的价格(总价格 总数量—)
7 全选和反选allChecked
1 给全选复选框绑定一个事件
2 获取data中的全选变量 allChecked
3 遍历购物车数组,让里面的商品选中状态随着allChecked改变
4 把购物车数组和allChecked重新设置回data,把
购物车重新设置回缓存中
*/
//在js中添加在utils里写的优化代码,把三个函数放在花括号内
import{getSetting,chooseAddress,openSetting,showModel,showToast} from "../../utils/asyncWx.js";
//2、页面加载完毕(使用onload/onShow事件)
//1、获取本地存储中的地址数据
//2、把数据设置给data中的一个变量
Page({
data:{
address:{},
cart:[],
allChecked:false,
totalPrice:0,
totalNum:0
},
onShow:function(){
//1、获取缓存中的收货地址信息
const address=wx.getStorageSync('address');
//1、获取商品详情页面的加入购物车的数据 这还可能是空值 加一个数组[]
const cart=wx.getStorageSync("cart")||[];
/*
//计算全选 cart是数组 .every是个方法
//every是一个数组方法,会遍历每一个函数,最后返回true
//v的意思是每一个循环项目
//做个优化⬇️const allChecked=cart.length?cart.every(v=>v.checked):false;
let allChecked=true;
//计算 首先声明变量 然后遍历数组
let totalPrice=0;
let totalNum=0;
cart.forEach(v=>{
if(v.checked){
totalPrice+=v.num*v.data.message.goods_price;
totalNum+=v.num;
}else{
allChecked:false;
}
})
//判断一下数组是否为空
allChecked=cart.length!=0?allChecked:false;
//2 给data赋值
this.setData({
address,
cart,
allChecked,
totalPrice,
totalNum
})
},
*/
this.setData({address});
this.setCart(cart);
},
/*
//点击按钮 获取收获地址
async handleChooseAddress(){
//首先获取权限状态
wx.getSetting({
success:(result)=>{
// console.log(result);//打印台输出authSetting里的scope值为true
//authSetting后面本应该写.scope.address,但这里这样写就太奇怪了
//如果发现一些属性名很怪异的时候,就要用[" "]的形式来获取属性值
const scopeAddress=result.authSetting["scope.address"];
if(scopeAddress===true||scopeAddress===undefined){
wx.chooseAddress({
//为了使这个api里的参数名和getSetting中的参数名不重复,所以这里加个1区分
// success: (result) => {},
success: (result1) => {
console.log(result1);
}
});
}else{
//用户以前拒绝过授予权限,应该先又到用户打开权限
wx.openSetting({
//获取到数据后,success表示用户重新授予权限
success:(result2)=>{
//直接可以调用收获地址代码
wx.chooseAddress({
//为了使这个api里的参数名和openSetting中的参数名不重复,所以这里加个3区分
// success: (result) => {},
success: (result3) => {
console.log(result3);
}
});
}
})
}
}
});
//优化后用es7语法写
//1 获取 权限状态
const res1=await getSetting();
const scopeAddress=res1.authSetting["scope.address"];
//判断 权限状态
/*if(scopeAddress===true||scopeAddress===undefined){
//调用获取收货地址的API
const res2=await chooseAddress();
console.log(res2);
}else{
//3 先诱导用户打开授权页面
await openSetting();
//4 调用获取收货地址的api
const res2=await chooseAddress();
console.log(res2);
}
*/
//判断权限状态,只要scope为false执行openSetting,其他全部chooseAddress
/*
if(scopeAddress===false){
await openSetting();
}
// 调用获取收货地址的API
const res2=await chooseAddress();
console.log(res2);
}
*/
//点击收货
async handleChooseAddress(){
try{
//1 获取 权限状态
const res1=await getSetting();
const scopeAddress=res1.authSetting["scope.address"];
// 2 判断权限状态
if(scopeAddress===false){
await openSetting();
}
//3 调用获取收货地址的API
//const res2=await chooseAddress(); const是声明常量,所以这里改了let
let address=await chooseAddress();
console.log(address);
//4 把获取到的地址信息存入到缓存中
//wx.setStorageSync(key,data);这个key值可以随便取名儿,这个data本来是写res2,但是后期要做优化,改名了
//为了后期繁琐的写地址信息代码,在这里做优化,把address.all写到wxml页面去
address.all=address.provinceName+address.cityName+address.countyName+address.detailInfo;
wx.setStorageSync("address",address);
}catch(error){
console.log(error);
}
},
//商品的选中
handeItemChange(e){
//获取被修改的商品的ID
const goods_id=e.currentTarget.dataset.id;
//console.log(goods_id);
//获取购物车数组
let{cart}=this.data;
//找到被修改的商品对象 findIndex找到索引的意思
//v=>goods_id是指购物车数组中的goods_id,后面那个goods_id是指点击复选框被修改的商品的ID
let index=cart.findIndex(v=>goods_id===goods_id);
//选中状态取反
cart[index].checked=!cart[index].checked;
this.setCart(cart);
},
//封装 设置购物车状态同时,重新计算底部工具栏的数据全选总价格 购物的数量
setCart(cart){
let allChecked=true;
//计算 首先声明变量 然后遍历数组
let totalPrice=0;
let totalNum=0;
cart.forEach(v=>{
if(v.checked){
totalPrice+=v.num*v.data.message.goods_price;
totalNum+=v.num;
}else{
allChecked:false;
}
})
//判断一下数组是否为空
allChecked=cart.length!=0?allChecked:false;
this.setData({
cart,
totalPrice,
totalNum,
allChecked
});
//这就是es6中的解构
wx.setStorageSync('cart', cart)
},
//商品的全选功能
handleItemAllCheck(e){
//1 获取data中的数据
let {cart,allChecked}=this.data;
//2 修改值
allChecked=!allChecked;
//3 循环修改cart数组中的商品选中状态
cart.forEach(v=>v.checked=allChecked);
// 4 修改后的指填充回data或者缓存中
this.setCart({cart,allChecked});
},
//商品数量的编辑功能
async handleItemNumEdit(e){
//1 获取传递过来的参数
const{operation,id}=e.currentTarget.dataset;
//console.log(operation,id);
// 2 获取购物车数组
let{cart}=this.data;
// 3 找到需要修改的商品的索引
const index=cart.findIndex(v=>v.goods_id===id.id);
//判断是否要执行删除
if(cart[index].num===1&&operation===-1){
//弹窗提示
const res=await showModel('您是否要删除?')
if(res.confirm){
cart.splice(index,1);
this.setCart(cart);
return
}
}else{
//4 进行修改数量
cart[index].num+=operation;
//5 设置回缓存和data中
this.setCart(cart);
}
},
//结算
async handlePay(e){
// 1 判断收货地址是否存在
const{address,totalNum}=this.data;
if(!address.userName){
await showToast({title:"您还没有选择收货地址"});
return;
}
// 2 判断用户有没有选购商品
if(totalNum===0){
await showToast({title:"您还没有选购商品"});
return;
}
// 3 跳转到支付页面
wx.navigateTo({
url:'/pages/pay/index'
});
}
})
cart.wxss
page{
padding-bottom: 90rpx;
}
.revice_address_row{
}
.revice_address_row .address_btn{
padding: 10rpx;
}
.revice_address_row .address_btn button{
width:70%;
}
.revice_address_row .user_info_row {
display: flex;
padding:20rpx;
}
.revice_address_row .user_info_row .user_info{
flex:5;
}
.revice_address_row .user_info_row .user_phone{
flex:3;
text-align: right;
}
.cart_content .cart_title{
padding: 20rpx;
font-size:36rpx;
color:rgb(219, 22, 22);
border-top: 1px solid rgb(230, 44, 44);
border-bottom: 1px solid rgb(241, 9, 9);
}
.cart_content .cart_main{
padding: 20rpx;
font-size:36rpx;
color:rgb(219, 22, 22);
border-top: 1px solid rgb(230, 44, 44);
border-bottom: 1px solid rgb(241, 9, 9);
}
.cart_content .cart_main .cart_item{
display: flex;
padding: 10rpx;
border-bottom: 1px solid #ccc;
}
.cart_content .cart_main .cart_chk_wrap{
flex:1;
display: flex;
justify-content: conter;
align-items: center;
}
.cart_content .cart_main .cart_chk_wrap .checkbox-group{
}
.cart_content .cart_main .cart_chk_wrap .checkbox-group checkbox{
}
.cart_content .cart_main .cart_img_wrap{
flex:2;
display: flex;
justify-content: conter;
align-items: center;
}
.cart_content .cart_main .cart_img_wrap image{
width:80%;
}
.cart_content .cart_main .cart_info_wrap{
flex:4;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.cart_content .cart_main .cart_info_wrap .goods_name{
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp:2;
color:#666;
}
.cart_content .cart_main .cart_info_wrap .goods_price_wrap{
display: flex;
justify-content: space-between;
}
.cart_content .cart_main .cart_info_wrap .goods_price_wrap .goods_price{
color:rgb(235, 5, 5);
font-size: 34rpx;
}
.cart_content .cart_main .cart_info_wrap .goods_price_wrap .cart_num_tool{
display: flex;
}
.cart_content .cart_main .cart_info_wrap .goods_price_wrap .cart_num_tool .num_edit{
width: 55rpx;
height: 55rpx;
display: flex;
justify-content: center;
align-items: center;
border: 1rpx solid #ccc;
color:#000000;
}
.cart_content .cart_main .cart_info_wrap .goods_price_wrap .cart_num_tool .goods_num{
width: 55rpx;
height: 55rpx;
display: flex;
justify-content: center;
align-items: center;
color:#000;
}
.footer_tool{
position: fixed;
bottom:0;
left:0;
width:100%;
height: 90rpx;
background-color: #fff;
display: flex;
border-top: 1px solid #ccc;
border-bottom: 4px solid #fff;
}
.footer_tool .all_chk_wrap{
flex:2;
display: flex;
justify-content: center;
align-items: center;
}
.footer_tool .total_price_wrap{
flex:5;
padding-right: 15rpx;
text-align: right;
}
.footer_tool .total_price_wrap .total_price{
}
.footer_tool .total_price_wrap .total_price .total_price_text{
color: #ee0d0d;
font-style: 28rpx;
font-weight: 600;
}
.footer_tool .order_pay_wrap{
flex:3;
background-color: #dd0303;
color:#fff;
font-style: 32rpx;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
}
cart.json
{
"usingComponents": {},
"navigationBarTitleText":"购物车"
}
支付页面
s