项目预览

小程序商城主要分为首页、分类、购物车和我的四个模块。

项目需求

接口文档

1
https://www.showdoc.com.cn/128719739414963?page_id=2513282464078443

项目搭建

新建小程序项目

进入官网,自行获取appId并下载微信开发者工具,在程序中点“+”创建新的小程序

搭建目录结构
目录名 作用
components 存放组件
images tabbar所用图片
request 请求的接口封装
utils 自己的帮助库
styles 存放公共样式
搭建项目页面
页面名称 文件名称
首页 index
分类页面 category
商品列表页面 goods_list
商品详情页面 goods_detail
购物车页面 cart
收藏页面 collect
个人中心页面 user
意见反馈页面 feedback
登录页面 login
授权页面 auth
结算页面 pay
订单页面 order
搜索页面 search
引入字体图标
  1. 打开阿里巴巴字体图标网站:https://www.iconfont.cn/

  2. 搜索需要的图标,添加购物车,并添加至项目

  3. 生成新的链接,并通过链接在网页中打开,复制全部

  4. 在本地的styles文件夹下创建iconfont.wxss文件,并将复制文件粘贴即可

  5. 在app.wxss中引入

    1
    @import "./styles/iconfont.wxss";
搭建项目tabbar结构

在images文件夹下存放上面几个类似的图片,并在app.json中如下配置即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/主页.png",
"selectedIconPath": "images/主页1.png"
},
{
"pagePath": "pages/category/index",
"text": "分类",
"iconPath": "images/分类.png",
"selectedIconPath": "images/分类1.png"
},
{
"pagePath": "pages/cart/index",
"text": "购物车",
"iconPath": "images/购物车.png",
"selectedIconPath": "images/购物车1.png"
},{
"pagePath": "pages/user/index",
"text": "我的",
"iconPath": "images/用户.png",
"selectedIconPath": "images/用户1.png"
}
]
}

项目开发

首页

首页共分为:搜索框、轮播图、导航和楼层四个部分。又因为搜索框属于常见的可复用的,所以可以单独将搜索框抽离成一个组件来使用。

搜索框组件

1.组件json文件中的”component”:true

2.wxml文件代码:

1
2
3
4
5
<view class="serach_input">
<navigator class="" target="" url="/pages/search/index" hover-class="navigator-hover" open-type="navigate">
搜索
</navigator>
</view>

搜索框组件只是一个模仿input框的伪搜索框,实际上的作用是导航到search页面下

3.wxss代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.serach_input {
height: 90rpx;
padding: 10rpx;
background-color: var(--themeColor);
}
.serach_input navigator {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
border-radius: 15rpx;
color: #666666;
}

4.在index文件夹下的json中引入组件。

1
2
3
"usingComponents": {
"SearchInput":"../../components/SearchInput/SearchInput"
},
轮播图、导航和楼层布局

1.wxml文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!-- 轮播图 -->
<swiper autoplay circular indicator-dots>
<swiper-item class="" wx:for="{{swiperList}}" wx:key="goods_id">
<navigator class="" target="" url="/pages/goods_detail/index?goods_id={{item.goods_id}}">
<image class="" mode="widthFix" src="{{item.image_src}}"></image>
</navigator>

</swiper-item>

</swiper>

<!-- 导航 -->
<view class="index_cate">
<navigator class="" wx:for="{{CateList}}" wx:key="name" url="/pages/category/index" open-type="switchTab">
<image class="" 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 mode="widthFix" src="{{item1.floor_title.image_src}}"></image>
</view>

<!-- 内容 -->
<view class="floor_list">
<navigator
wx:for="{{item1.product_list}}"
wx:for-item="item2"
wx:for-index="index2"
wx:key="name"
url="/pages/goods_list/index?query=精选">
<image src="{{item2.image_src}}" mode="{{index2===0?'widthFix':'scaleToFill'}}"></image>
</navigator>
</view>
</view>
</view>

2.wxss样式布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
.index_swiper swiper {
width: 750rpx;
height: 340rpx;
}
.index_swiper swiper image {
width: 100%;
}
.index_cate {
display: flex;
}
.index_cate navigator {
flex: 1;
padding: 20rpx;
}
.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: 33.33%;
}
.index_floor .floor_group .floor_list navigator:nth-last-child(-n+4) {
height: 27.72711207vw;
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%;
}

3.request函数封装

整个页面的数据都是通过wx.request函数来实现调用,为了代码的复用性,可将这块函数单独作为一个模块封装起来,其他页面使用时也只需要调用即可!

具体操作为:在request的文件夹下的js文件中写上如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
let ajaxTimes=0;		//全局变量,为的是让整个页面完全加载完成后,才让loading消失
export const request = (params) => {
// 判断url中是否带有/my/请求的私有路径 带上header token
let header={...params.header}
if(params.url.includes("/my/")){ //对个别页面,需要添加请求头
header["Authorization"]=wx.getStorageSync("token");
}

ajaxTimes++
// 显示加载中 效果
wx.showLoading({
title: '加载中',
mask: true
})

const baseUrl = "https://api-hmugo-web.itheima.net/api/public/v1"
return new Promise((resolve, reject) => {
wx.request({
...params,
header,
url: baseUrl + params.url,
success: (result) => {
resolve(result.data.message)
},
fail: (err) => {
reject(err)
},
complete: () => {
ajaxTimes--;
if(ajaxTimes===0){
wx.hideLoading()
}
}
});
})
}

4.js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

import { request } from "../../request/index";

Page({
data: {
// 轮播图数组
swiperList: [],
// 导航数组
CateList:[],
// 楼层数组
floorList:[]
},
// 页面开始加载 就会触发
onLoad: function () {
this.getSwiperList();
this.getCateList();
this.getFloorList();
},
getSwiperList() {
// 发送异步请求获取轮播图数据
request({ url: '/home/swiperdata'})
.then(result => {
this.setData({
swiperList: result
})
})
},
getCateList() {
// 发送异步请求获取导航数据
request({ url: '/home/catitems'})
.then(result => {
this.setData({
CateList: result
})
})
},
getFloorList() {
// 发送异步请求获取楼层数据
request({ url: '/home/floordata'})
.then(result => {
this.setData({
floorList: result
})
})
}
})

搜索框

搜索框的页面十分简陋,这里特别注意的是一点,针对移动端的用户体验问题。

一般来说,移动端很少会搜索信息的时候还需要点击”搜索”按钮才开始搜索,而对监听input框的事件设置延迟搜索效果更好,一定程度上减少了http发送次数,又优化了用户体验。

wxml和wxss的页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<view class="search_row">   
<input placeholder="请输入您要搜索的商品" value="{{inpValue}}" bindinput="handleInput"></input>
<button hidden="{{!isFocus}}" bindtap="handleCancel">取消</button>
</view>
<view class="search_content">
<navigator
url="/pages/goods_detail/index?goods_id={{item.goods_id}}"
wx:for="{{goods}}"
wx:key="goods_id"
class="search_item">
{{item.goods_name}}
</navigator>

</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
page {
background-color: #dedede;
padding: 20rpx;
}
view.search_row {
height: 60rpx;
display: flex;
}
view.search_row input {
background-color: #fff;
flex: 1;
height: 100%;
padding-left: 30rpx;
}
view.search_row button {
width: 110rpx;
height: 100%;
font-size: 28rpx;
padding: 0;
margin: 0 10rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 26rpx;
font-weight: normal;
}
.search_content {
margin-top: 40rpx;
}
.search_content .search_item {
background-color: #fff;
font-size: 26rpx;
padding: 15rpx 10rpx;
border-bottom: 1rpx solid #969595;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
实机效果图

页面布局中,在搜索之前或者input框为空时,”取消”按钮将会消失这一小小的特点。而每一个搜索出来的列表项,都是导向商品详情页面下。

js功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import {request} from "../../request/index"		//引入request请求
Page({
data: {
goods:[], //搜索到的数据存放 数组
isFocus:false, //判断取消按钮的显示隐藏
inpValue:"" //用于删除输入框中的值
},
TimeId:-1, //定义定时器
handleInput(e){
const {value}=e.detail
if(!value.trim()){ //value.trim()用于删除字符串的头尾空格
clearTimeout(this.TimeId) //先清除定时器
this.TimeId=setTimeout(()=>{ //设置定时器
this.setData({
goods:[],
isFocus:false
})
},1000)

return //跳出循环
}

//此时input的值为空
this.setData({
isFocus:true
})
//输入框为空时,清除商品列表项
clearTimeout(this.TimeId);
this.TimeId=setTimeout(()=>{
this.qsearch(value)
},1000)
},
//请求数据的封装函数
qsearch(query){
request({url:"/goods/qsearch",data:{query}}).then(res=>{
this.setData({
goods:res
})
})
},
//取消按钮的点击事件
handleCancel(){
this.setData({
inpValue:"",
goods:[]
})
}
})

分类

页面布局

页面其实可以看做两个部分,其一是搜索框组件,其二是scroll-view内容。具体页面内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<view class="cates">
<SearchInput></SearchInput> //搜索框组件

<view class="cates_container">
<!-- 左侧菜单 -->
<scroll-view class="left_menu" scroll-y scroll-top="{{scrollTop}}">
<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="{{rightContent}}"
wx:for-index="index1"
wx:for-item="item1">
<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-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">{{item.cat_name}}</view>
</navigator>
</view>
</view>
</scroll-view>
</view>

</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
page {
height: 100%;
}
.cates {
height: 100%;
}
.cates .cates_container {
height: calc( 100vh - 90rpx );
display: flex;
}
.cates .cates_container .left_menu {
flex: 2;
}
.cates .cates_container .left_menu .menu_item {
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
font-style: 30rpx;
}
.cates .cates_container .left_menu .active {
color: var(--themeColor);
}
.cates .cates_container .right_content {
flex: 5;
}
.cates .cates_container .right_content .goods_group .goods_title {
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
}
.cates .cates_container .right_content .goods_group .goods_title .delimiter {
color: #ccc;
padding: 0 10rpx;
}
.cates .cates_container .right_content .goods_group .goods_list {
display: flex;
flex-wrap: wrap;
}
.cates .cates_container .right_content .goods_group .goods_list navigator {
width: 33.33%;
text-align: center;
}
.cates .cates_container .right_content .goods_group .goods_list navigator image {
width: 50%;
}
js功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import { request } from "../../request/index";

Page({

data: {
leftMenuList:[], //左边菜单栏
rightContent:[], //右边商品栏
currentIndex:0,
scrollTop:0
},
// 接口的返回数据
Cates:[],
onLoad: function (options) {

// 1.获取本地存储中的数据
const Cates=wx.getStorageSync("cates");
// 2.判断
if(!Cates){
this.getCates();
}else{
// 有旧的数据 定义过期时间 10s 改成 5分钟
if(Date.now()-Cates.time>1000*300){
// 重新发送请求
this.getCates()
}else{
// 可以使用旧的数据
this.Cates=Cates.data;
this.dealListCont();
}
}

},
// 获取分类数据
getCates(){
request({
url:"/categories"
}).then(res=>{
this.Cates=res;

// 把接口的数据存入到本地存储中
wx.setStorageSync("cates", {time:Date.now(),data:this.Cates});

this.dealListCont();
})

},
// 初始页面数据分装
dealListCont(){
// 构造左侧的大菜单数据
let leftMenuList=this.Cates.map(v=>v.cat_name)
// 构造右侧的商品内容
let rightContent=this.Cates[0].children;
this.setData({
leftMenuList,
rightContent
})
},
// 左侧菜单点击事件
handleItemTap(e){
const {index}=e.currentTarget.dataset;
let rightContent=this.Cates[index].children;
this.setData({
currentIndex:index,
rightContent,
scrollTop:0
})

}
})

商品列表

在分类右侧商品栏中,点击每一个商品都会来到相应的商品1列表下。商品列表算是分类和商品详情页面的中转站,也是商城中必不可少的一栏。

列表页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<SearchInput></SearchInput>
<!-- 监听自定义事件 -->
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange">//bindtabsItemChange绑定的是tabs组件传回的tabsItemChange这个事件名
<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 class="" src="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode="widthFix"></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:else="{{tabs[2].isActive}}">
//没有数据,否则可以显示其他有关价格数据的商品
</block>
</Tabs>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.first_tab .goods_item {
display: flex;
border-bottom: 2rpx 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);
}

实机效果:

tab栏组件

tab切换的组件,原理同搜索框组件一样,不过它多了个父传子和子传父的数据传输过程。

tab组件布局
1
2
3
4
5
6
7
8
9
10
11
12
<view class="tabs">
<view class="tabs_title">
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.tabs .tabs_title {
display: flex;
}
.tabs .tabs_title .title_item {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 15rpx 0;
}
.active {
color: var(--themeColor);
border-bottom: 5rpx solid currentColor;
}

js功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Component({

properties: {
//对父组件传输的tabs对象名设置属性
tabs:{
type:Array,
value:[]
}
},
data: {

},

methods: {
//子组件的点击事件,并将索引值传回到父组件中
handleItemTap(e){
const {index}=e.currentTarget.dataset;
this.triggerEvent("tabsItemChange",{index}) //tabsItemChange子组件传回的事件名
}
}
})

js功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { request } from "../../request/index";

Page({

data: {
tabs: [
{
id: 0,
value: "综合",
isActive: true
},
{
id: 1,
value: "销量",
isActive: false
},
{
id: 2,
value: "价格",
isActive: false
}
],
goodsList: []
},
// 接口要的参数
QueryParams: {
query: "",
cid: "",
pagenum: 1,
pagesize: 10
},
totalPages: 1,

onLoad: function (options) {
this.QueryParams.cid = options.cid||""; //根据cid请求数据
this.QueryParams.query = options.query||"";//根据商品名请求数据
this.getGoodsList();
},

onReachBottom() {
if (this.QueryParams.pagenum >= this.totalPages) {
wx.showToast({
title: '没有下一页数据了',
"icon": "none"
});
} else {
this.QueryParams.pagenum++;
this.getGoodsList()
}
},
onPullDownRefresh() {
this.setData({
goodsList: []
})
this.QueryParams.pagenum = 1;
this.getGoodsList()
},
//获取商品列表
getGoodsList() {
const res = request({ url: '/goods/search', data: this.QueryParams })
.then(res => {
const total = res.total;
this.totalPages = Math.ceil(total / this.QueryParams.pagesize) //获取页数
this.setData({
goodsList: [...this.data.goodsList, ...res.goods] //拼接数组获得下拉刷新效果
})

// 配合request封装函数,可以完美实现关闭下拉刷新的窗口之前,先获取完所有数据
wx.stopPullDownRefresh()
})
},
//实现tabs样式切换效果
handleTabsItemChange(e) {
const { index } = e.detail;
let { tabs } = this.data;
tabs.forEach((v, i) => i === index ? v.isActive = true : v.isActive = false)
this.setData({
tabs
})
}

})

商品详情

详情页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<view class="detail_swiper">
<swiper indicator-dots autoplay circular>
<swiper-item wx:for="{{goodsObj.pics}}" wx:key="pics_id" data-url="{{item.pics_mid}}" bindtap="handlePreviewImage">
<image src="{{item.pics_mid}}" mode="widthFix"></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" bindtap="handleCollect">
<text class="iconfont {{isCollect?'icon-Collection':'icon-shoucang1'}} "></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 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-share-line"></view>
<view>分享</view>
<button open-type="share"></button>
</view>
<navigator url="/pages/cart/index" open-type="switchTab" class="tool_item">
<view class="iconfont icon-gouwuchezhengpin"></view>
<view>购物车</view>
</navigator>
<view class="tool_item btn_cart" bindtap="handleCartAdd">
加入购物车
</view>
<view class="tool_item btn_buy">
立即购买
</view>


</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
page {
padding-bottom: 90rpx;
}
.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: var(--themeColor);
}
.goods_name_row {
border-top: 5rpx solid #dedede;
border-bottom: 5rpx solid #ddeedd;
padding: 10rpx 0;
display: flex;
}
.goods_name_row .goods_name {
flex: 5;
color: #999;
font-size: 26rpx;
padding: 0 10rpx;
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.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 .icon-Collection {
color: orangered;
}
.goods_name_row .goods_collect .collect_text {
font-size: 26rpx;
color: #999;
}
.goods_info .goods_info_title {
font-size: 32rpx;
color: var(--themeColor);
font-weight: 600;
padding: 20rpx;
}
.btm_tool {
border-top: 1rpx 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: 24rpx;
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;
background-color: #ffa500;
color: #fff;
font-weight: 600;
font-size: 30rpx;
}
.btm_tool .btn_buy {
flex: 2;
background-color: #eb4450;
color: #fff;
font-weight: 600;
font-size: 30rpx;
}

js功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { request } from "../../request/index";

Page({

data: {
goodsObj:{},
isCollect:false
},
GoodsInfo:{},

onShow: function () {
let pages= getCurrentPages();
let currentPage=pages[pages.length-1]
let options=currentPage.options
const {goods_id}=options;
this.getGoodsDetail(goods_id)

},
//获取数据,并存入缓存,以及添加是否收藏标志
getGoodsDetail(goods_id){
request({url:'/goods/detail',data:{goods_id}})
.then(res=>{
let goodsObj=res;
this.GoodsInfo=res;
// 获取缓存中商品收藏数组
let collect=wx.getStorageSync("collect")||[];
let isCollect=collect.some(v=>v.goods_id===this.GoodsInfo.goods_id)
this.setData({
goodsObj:{
goods_name:goodsObj.goods_name,
goods_price:goodsObj.goods_price,
goods_introduce:goodsObj.goods_introduce.replace(/\.webp/g,'.jpg'),
pics:goodsObj.pics
},
isCollect
})
})
},
// 预览轮播图的图片
handlePreviewImage(e){
// 先构造要预览的图片数组
const urls=this.GoodsInfo.pics.map(v=>v.pics_mid)
const current=e.currentTarget.dataset.url
wx.previewImage({
current,
urls
});
},
//添加购物车功能
handleCartAdd(){
// 先获取缓存中的购物车 数组
let cart=wx.getStorageSync("cart")||[];
// 判断 商品对象是否存在于购物车数组中
let index=cart.findIndex(v=>v.goods_id===this.GoodsInfo.goods_id)
if(index===-1){
this.GoodsInfo.num=1
this.GoodsInfo.checked=true
cart.push(this.GoodsInfo)
}else{
cart[index].num++
}
wx.setStorageSync("cart", cart);
wx.showToast({
title: '加入成功',
icon: 'success',
duration: 1500,
mask: true
});

},
// 点击收藏商品
handleCollect(){
let isCollect=false;
let collect=wx.getStorageSync("collect")||[];
let index=collect.findIndex(v=>v.goods_id===this.GoodsInfo.goods_id)
if(index!=-1){
collect.splice(index,1)
isCollect=false
wx.showToast({
title: '取消成功',
icon: 'success',
image: '',
duration: 1500,
mask: true
});
}else{
collect.push(this.GoodsInfo)
isCollect=true
wx.showToast({
title: '收藏成功',
icon: 'success',
image: '',
duration: 1500,
mask: true
});
}
wx.setStorageSync("collect", collect);
this.setData({
isCollect
})
}
})
页面完成图

其中收藏、联系客服、分享、购物车、加入购物车功能均已实现,而立即购买仍没有去实现!

购物车

页面布局

购物车页面布局很简单,只有添加收货地址和商品列表这两大项,重难点在于js的代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!-- 收货地址 -->
<view class="recieve_address_row">
<!-- 当收货地址 不存在 按钮显示 -->
<view class="address_btn" wx:if="{{!address.userName}}">
<button type="primary" bindtap="handleChooseAddress" plain>获取收货地址</button>
</view>
<!-- 当收货地址 存在 详细信息就显示 -->
<view wx:elese class="user_info_row">
<view class="user_info">
<view>{{address.userName}}</view>
<view>{{address.all}}</view>
</view>
<view class="user_phone">{{address.telNumber}}</view>
</view>
</view>

<!-- 购物车内容 -->
<view class="cart_content">
<view class="cart_title">购物车</view>
<view class="cart_main">
<!-- 当cart数组 长度不为0 显示 商品信息 -->
<block wx:if="{{cart.length!=0}}">
<view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
<!-- 复选框 -->
<view class="cart_chk_wrap">
<checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
<checkbox checked="{{item.checked}}"></checkbox>

</checkbox-group>
</view>
<!-- 商品图片 -->
<navigator class="cart_img_wrap" url="">
<image class="" src="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode="widthFix"></image>
</navigator>
<!-- 商品信息 -->
<view class="cart_info_wrap">
<view class="goods_name">{{item.goods_name}}</view>
<view class="goods_prie_wrap">
<view class="goods_price">¥{{item.goods_price}}</view>
<view class="cart_num_tool">
<view bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{-1}}" class="num_edit">-</view>
<view class="goods_num">{{item.num}}</view>
<view bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{1}}" class="num_edit">+</view>
</view>
</view>
</view>
</view>
</block>
<block wx:else>
<image class="" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1597867170872&di=16a56791de9a4a330bb64ba090fad32c&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2Fe1b1467beea0a9c7d6a56b32bac6d7e5dcd914f7c3e6-YTwUd6_fw658" mode="widthFix">

</image>
</block>
</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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
page {
padding-bottom: 90rpx;
}
.recieve_address_row .address_btn {
padding: 20rpx;
}
.recieve_address_row .address_btn button {
width: 60%;
}
.recieve_address_row .user_info_row {
display: flex;
padding: 20rpx;
}
.recieve_address_row .user_info_row .user_info {
flex: 5;
}
.recieve_address_row .user_info_row .user_phone {
flex: 3;
text-align: right;
}
.cart_content .cart_title {
padding: 20rpx;
font-size: 36rpx;
color: var(--themeColor);
border-top: 1rpx solid currentColor;
border-bottom: 1rpx solid currentColor;
}
.cart_content .cart_main .cart_item {
display: flex;
padding: 10rpx;
border-bottom: 1rpx solid #ccc;
}
.cart_content .cart_main .cart_item .cart_chk_wrap {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.cart_content .cart_main .cart_item .cart_img_wrap {
flex: 2;
display: flex;
justify-content: center;
align-items: center;
}
.cart_content .cart_main .cart_item .cart_img_wrap image {
width: 80%;
}
.cart_content .cart_main .cart_item .cart_info_wrap {
flex: 4;
display: flex;
flex-direction: column;
justify-content: space-around;
padding: 0 20rpx;
}
.cart_content .cart_main .cart_item .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_item .cart_info_wrap .goods_prie_wrap {
display: flex;
justify-content: space-between;
}
.cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .goods_price {
color: var(--themeColor);
font-size: 34rpx;
}
.cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool {
display: flex;
}
.cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool .num_edit {
width: 55rpx;
height: 55rpx;
display: flex;
justify-content: center;
align-items: center;
border: 1rpx solid #ccc;
}
.cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool .goods_num {
width: 55rpx;
height: 55rpx;
display: flex;
justify-content: center;
align-items: center;
}
.footer_tool {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 90rpx;
background-color: #fff;
display: flex;
border-top: 1rpx solid #ccc;
}
.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 .total_price_text {
color: var(--themeColor);
font-size: 34rpx;
font-weight: 600;
}
.footer_tool .order_pay_wrap {
flex: 3;
background-color: var(--themeColor);
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
}
js功能实现
utils封装函数

由于涉及wx的封装函数过多,故而在utils中封装了一些函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
export const getSetting=()=>{
return new Promise((resolve,reject)=>{
wx.getSetting({
success: (result)=>{
resolve(result)
},
fail: (err)=>{
reject(err)
}
});
})
}

export const chooseAddress=()=>{
return new Promise((resolve,reject)=>{
wx.chooseAddress({
success: (result)=>{
resolve(result)
},
fail: (err)=>{
reject(err)
}
});
})
}
export const openSetting=()=>{
return new Promise((resolve,reject)=>{
wx.openSetting({
success: (result)=>{
resolve(result)
},
fail: (err)=>{
reject(err)
}
});
})
}
export const showModal=({content})=>{
return new Promise((resolve,reject)=>{
wx.showModal({
title: '提示',
content,
showCancel: true,
cancelText: '取消',
cancelColor: '#000000',
confirmText: '确定',
confirmColor: '#3CC51F',
success: (res) => {
resolve(res)
},
fail:(err)=>{
reject(err)
}
});
})
}
export const showToast=({title})=>{
return new Promise((resolve,reject)=>{
wx.showToast({
title,
icon: 'none',
success: (res)=>{
resolve(res)
},
fail: (err)=>{
reject(err)
}
});
})
}
export const login=()=>{
return new Promise((resolve,reject)=>{
wx.login({
timeout:10000,
success: (res)=>{
resolve(res)
},
fail: (err)=>{
reject(res)
}
});
})
}
// 支付!!
export const requestPayment=(pay)=>{
return new Promise((resolve,reject)=>{
wx.requestPayment({
...pay,
success: (result)=>{
resolve(result)
},
fail: (err)=>{
reject(err)
}
});
})
}

js代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { request } from "../../request/index";
import { getSetting, chooseAddress, openSetting,showModal,showToast } from "../../utils/asyncWx";
Page({

data: {
address:{},
cart:[],
allChecked:false,
totalPrice:0,
totalNum:0
},
onShow(){
const address=wx.getStorageSync("address"); //获取地址缓存
const cart=wx.getStorageSync("cart")||[]; //获取购物车缓存
// const allChecked=cart.length?cart.every(v=>v.checked):false
this.setData({address})
this.setCart(cart)
},

onLoad: function (options) {

},
// 点击 收货地址
handleChooseAddress() {
try {
getSetting().then(res => {
const scopAddress = res.authSetting["scope.address"]
if (scopAddress === false) {
openSetting()
}else{
chooseAddress().then(res=>{
res.all=res.provinceName+res.cityName+res.countyName+res.detailInfo
wx.setStorageSync("address", res);
})
}

})
} catch (error) {
console.log(error);
}
},

//商品的选中
handleItemChange(e){
const goods_id=e.currentTarget.dataset.id
// console.log(goods_id);
let {cart}=this.data;
let index=cart.findIndex(v=>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.goods_price
totalNum+=v.num;
}else{
allChecked=false //只要有一个列表复选框未选,则底部的复选框为false
}
})
allChecked=cart.length!=0?allChecked:false
this.setData({
cart,
allChecked,
totalPrice,
totalNum
})
wx.setStorageSync("cart", cart);
},
// 商品全选功能
handleItemAllCheck(){
let {cart,allChecked}=this.data
allChecked=!allChecked
cart.forEach(v=>v.checked=allChecked)
this.setCart(cart)
},
// 商品数量的编辑功能
handleItemNumEdit(e){
let {operation,id}=e.currentTarget.dataset
let {cart} = this.data
const index=cart.findIndex(v=>v.goods_id===id)
if(cart[index].num===1&&operation===-1){
showModal({content:"您是否要删除?"}).then(res=>{
if(res.confirm){
cart.splice(index,1)
this.setCart(cart)
}
})
}else{
cart[index].num+=operation
this.setCart(cart)
}

},
// 商品结算
handlePay(){
const {address,totalNum}=this.data;
if(!address.userName){
showToast({title:"您还没有选择收货地址"})
return
}
if(totalNum===0){
showToast({title:"您还没有选购商品"})
return
}
wx.navigateTo({
url: '/pages/pay/index',
});
}

})
页面图

支付

此项功能是调取wx自带的函数,需要appId为商用id才可以,具体流程是,获取登录的用户token,根据token才能够实现支付效果。我不能通过此项步骤,实机操作会出现报错,所以只能伪装获取到token以及跳过支付界面!

页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!-- 收货地址 -->
<view class="recieve_address_row">

<!-- 当收货地址 存在 详细信息就显示 -->
<view class="user_info_row">
<view class="user_info">
<view>{{address.userName}}</view>
<view>{{address.all}}</view>
</view>
<view class="user_phone">{{address.telNumber}}</view>
</view>
</view>

<!-- 购物车内容 -->
<view class="cart_content">
<view class="cart_title">购物车</view>
<view class="cart_main">
<!-- 当cart数组 长度不为0 显示 商品信息 -->

<view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">

<!-- 商品图片 -->
<navigator class="cart_img_wrap" url="">
<image class="" src="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode="widthFix"></image>
</navigator>
<!-- 商品信息 -->
<view class="cart_info_wrap">
<view class="goods_name">{{item.goods_name}}</view>
<view class="goods_prie_wrap">
<view class="goods_price">¥{{item.goods_price}}</view>
<view class="cart_num_tool">
<view class="goods_num">X {{item.num}}</view>
</view>
</view>
</view>
</view>

</view>
</view>

<!-- 底部工具栏 -->
<view class="footer_tool">
<!-- 总价格 -->
<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="handleOderPay">
支付({{totalNum}})
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
page {
padding-bottom: 90rpx;
}
.recieve_address_row .user_info_row {
display: flex;
padding: 20rpx;
}
.recieve_address_row .user_info_row .user_info {
flex: 5;
}
.recieve_address_row .user_info_row .user_phone {
flex: 3;
text-align: right;
}
.cart_content .cart_title {
padding: 20rpx;
font-size: 36rpx;
color: var(--themeColor);
border-top: 1rpx solid currentColor;
border-bottom: 1rpx solid currentColor;
}
.cart_content .cart_main .cart_item {
display: flex;
padding: 10rpx;
border-bottom: 1rpx solid #ccc;
}
.cart_content .cart_main .cart_item .cart_img_wrap {
flex: 2;
display: flex;
justify-content: center;
align-items: center;
}
.cart_content .cart_main .cart_item .cart_img_wrap image {
width: 80%;
}
.cart_content .cart_main .cart_item .cart_info_wrap {
flex: 4;
display: flex;
flex-direction: column;
justify-content: space-around;
padding: 0 20rpx;
}
.cart_content .cart_main .cart_item .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_item .cart_info_wrap .goods_prie_wrap {
display: flex;
justify-content: space-between;
}
.cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .goods_price {
color: var(--themeColor);
font-size: 34rpx;
}
.cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool {
display: flex;
}
.cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool .goods_num {
width: 55rpx;
height: 55rpx;
display: flex;
justify-content: center;
align-items: center;
}
.footer_tool {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 90rpx;
background-color: #fff;
display: flex;
border-top: 1rpx solid #ccc;
}
.footer_tool .total_price_wrap {
flex: 5;
padding-right: 15rpx;
text-align: right;
}
.footer_tool .total_price_wrap .total_price .total_price_text {
color: var(--themeColor);
font-size: 34rpx;
font-weight: 600;
}
.footer_tool .order_pay_wrap {
flex: 3;
background-color: var(--themeColor);
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
}
auth页面
登录获取token
1
<button type="primary" plain open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo">获取授权</button>
1
2
3
4
button{
width: 70%;
margin-top: 40rpx;
}
js实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { request,request2 } from "../../request/index";
import { login } from "../../utils/asyncWx";
Page({

data: {

},

onLoad: function (options) {

},
handleGetUserInfo(e) {
try {
const { encryptedData, rawData, iv, signature } = e.detail
// request2({url:"/api/login",data:{
// "username":"root",
// "password":"123456"
// },method:"post"}).then(res=>{
// wx.setStorageSync("token", res.data.token);
// request2({url:'/api/profile',header:{'Authorization':res.data.token}})
// .then(res=>{
// wx.navigateBack({
// delta: 1
// });
// })
// })
// login().then(res=>{
// const {code}=res;
// console.log(code);
// wx.request({
// url: 'https://api.it120.cc/dhqm/user/wxapp/login',
// data: {code},
// header: {'content-type':'application/x-www-form-urlencoded'},
// method: 'POST',
// dataType: 'json',
// responseType: 'text',
// success: (result)=>{
// console.log("登陆成功",result);
// }
// });
login().then(res=>{
const {code}=res;
const loginParams={ encryptedData, rawData, iv, signature ,code}
request({url:'/users/wxlogin',data:loginParams,method:"POST"})
.then(res=>{
// 伪装token
wx.setStorageSync("token", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIzLCJpYXQiOjE1NjQ3MzAwNzksImV4cCI6MTAwMTU2NDczMDA3OH0.YPt-XeLnjV-_1ITaXGY2FhxmCe4NvXuRnRB8OMCfnPo");
wx.navigateBack({
delta: 1
});
})
})

// })
} catch (error) {
console.log(error);
}
}

})
支付功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { request } from "../../request/index";
import { getSetting, chooseAddress, openSetting, showModal, showToast, requestPayment } from "../../utils/asyncWx";
Page({

data: {
address: {},
cart: [],
totalPrice: 0,
totalNum: 0
},
onShow() {
const address = wx.getStorageSync("address");
let cart = wx.getStorageSync("cart") || [];
cart = cart.filter(v => v.checked)

let totalPrice = 0;
let totalNum = 0;
cart.forEach(v => {
totalPrice += v.num * v.goods_price
totalNum += v.num;
})
this.setData({
cart,
totalPrice,
totalNum,
address
})
},

onLoad: function (options) {

},
handleOderPay() {
try {
const token = wx.getStorageSync("token");
if (!token) {
wx.navigateTo({
url: '/pages/auth/index'
});
return
}
// const header = { Authorization: token };
const order_price = this.data.totalPrice;
const consignee_addr = this.data.address.all;
const cart = this.data.cart;
let goods = []
cart.forEach(v => goods.push({
goods_id: v.goods_id,
goods_number: v.num,
goods_price: v.goods_price
}))
const orderParams = { order_price, consignee_addr, goods }
request({ url: "/my/orders/create", method: "POST", data: orderParams})
.then(res => {
const order_number = res.order_number
request({ url: "/my/orders/req_unifiedorder", method: "POST", data: { order_number } })
.then(res => {
showToast({ title: "支付成功!" }).then(res => {
console.log({ errMsg: "requestPayment:ok" });
})
// 支付流程
// const pay={...res.pay}
// requestPayment(pay).then(res=>{
// console.log(res);
// })
let newCart=wx.getStorageSync("cart");
newCart=newCart.filter(v=>!v.checked)
wx.setStorageSync("cart", newCart);
wx.redirectTo({
url: '/pages/order/index',

});
})
})
} catch (error) {
showToast({ title: "支付失败" }).then(res => {
console.log({ errMsg: "requestPayment:cancel" });
})
}

}

})
页面图

我的

页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<view class="user_info_wrap">
<view wx:if="{{userinfo.avatarUrl}}" class="user_img_wrap">
<image class="user_bg" src="{{userinfo.avatarUrl}}"></image>
<view class="user_info">
<image class="user_icon" src="{{userinfo.avatarUrl}}"></image>
<view class="user_name">{{userinfo.nickName}}</view>
</view>

</view>
<view wx:else class="user_btn">
<navigator class="" target="" url="/pages/login/index" hover-class="navigator-hover" open-type="navigate">
登录
</navigator>
</view>
</view>

<view class="user_content">
<view class="user_main">
<!-- 历史足迹 -->
<view class="history_wrap">
<navigator url="">
<view class="his_num">0</view>
<view class="his_name">收藏的店铺</view>
</navigator>
<navigator url="/pages/collect/index">
<view class="his_num">{{collectNums}}</view>
<view class="his_name">收藏的商品</view>
</navigator>
<navigator url="">
<view class="his_num">0</view>
<view class="his_name">关注的商品</view>
</navigator>
<navigator url="">
<view class="his_num">0</view>
<view class="his_name">我的足迹</view>
</navigator>
</view>
<!-- 我的订单 -->
<view class="orders_wrap">
<view class="orders_title">我的订单</view>
<view class="order_content">
<navigator class="" target="" url="/pages/order/index?type=1" hover-class="navigator-hover" open-type="navigate">
<view class="iconfont icon-dingdan-"></view>
<view class="order_name">全部订单</view>
</navigator>
<navigator class="" target="" url="/pages/order/index?type=2" hover-class="navigator-hover" open-type="navigate">
<view class="iconfont icon-fukuantongzhi"></view>
<view class="order_name">待付款</view>
</navigator>
<navigator class="" target="" url="/pages/order/index?type=3" hover-class="navigator-hover" open-type="navigate">
<view class="iconfont icon-huoche"></view>
<view class="order_name">待收货</view>
</navigator>
<navigator>
<view class="iconfont icon-tuihuotuikuan-"></view>
<view class="order_name">退款/退货</view>
</navigator>
</view>


</view>

<!-- 收货地址管理 -->
<view class="address_wrap">
收货地址管理
</view>

<!-- 应用信息相关 -->
<view class="app_info_wrap">
<view class="app_info_item app_info_contact">
<text>联系客服</text>
<text>400-618-4000</text>
</view>
<navigator url="/pages/feedback/index" class="app_info_item">意见反馈</navigator>
<view class="app_info_item">关于我们</view>
</view>

<!-- 推荐 -->
<view class="recommend_wrap">
把应用推荐给他人
</view>
</view>

</view>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
page {
background-color: #ccc;
}
.user_info_wrap {
height: 40vh;
background-color: var(--themeColor);
position: relative;
}
.user_info_wrap .user_img_wrap {
position: relative;
}
.user_info_wrap .user_img_wrap .user_bg {
width: 100%;
height: 45vh;
filter: blur(6rpx);
}
.user_info_wrap .user_img_wrap .user_info {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 20%;
text-align: center;
}
.user_info_wrap .user_img_wrap .user_info .user_icon {
width: 150rpx;
height: 150rpx;
border-radius: 50%;
z-index: 100;
margin: 0 auto;
}
.user_info_wrap .user_img_wrap .user_info .user_name {
color: #fff;
margin-top: 40rpx;
font-size: 40rpx;
}
.user_info_wrap .user_btn {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 40%;
border: 1rpx solid greenyellow;
color: greenyellow;
font-size: 38rpx;
padding: 30rpx;
border-radius: 10rpx;
}
.user_content {
position: relative;
}
.user_content .user_main {
position: absolute;
width: 90%;
left: 50%;
transform: translateX(-50%);
top: 40rpx;
color: #666;
padding-bottom: 100rpx;
}
.user_content .user_main .history_wrap {
background-color: #fff;
display: flex;
flex-direction: row;
}
.user_content .user_main .history_wrap navigator {
flex: 1;
text-align: center;
padding: 10rpx 0;
}
.user_content .user_main .history_wrap navigator .his_num {
color: var(--themeColor);
}
.user_content .user_main .orders_wrap {
background-color: #fff;
margin-top: 30rpx;
}
.user_content .user_main .orders_wrap .orders_title {
padding: 20rpx;
border-bottom: 1rpx solid #ccc;
}
.user_content .user_main .orders_wrap .order_content {
display: flex;
}
.user_content .user_main .orders_wrap .order_content navigator {
flex: 1;
text-align: center;
padding: 15rpx 0;
}
.user_content .user_main .orders_wrap .order_content navigator .iconfont {
color: var(--themeColor);
font-size: 40rpx;
}
.user_content .user_main .address_wrap {
margin-top: 30rpx;
background-color: #fff;
padding: 20rpx;
}
.user_content .user_main .app_info_wrap {
margin-top: 30rpx;
background-color: #fff;
}
.user_content .user_main .app_info_wrap .app_info_item {
padding: 20rpx;
border-bottom: 1rpx solid #ccc;
}
.user_content .user_main .app_info_contact {
display: flex;
justify-content: space-between;
}
.user_content .user_main .recommend_wrap {
margin-top: 30rpx;
background-color: #fff;
padding: 20rpx;
}

收藏浏览
页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange">
<view class="collect_main">
<view class="collect_title">
<view class="collect_tips active">全部</view>
<view class="collect_tips">正在热卖</view>
<view class="collect_tips">即将上线</view>
</view>
<view class="collect_content">
<navigator
class="goods_item"
wx:for="{{collect}}"
wx:key="goods_id"
url="/pages/goods_detail/index?goods_id={{item.goods_id}}">
<!-- 左侧 图片容器 -->
<view class="goods_img_wrap">
<image class="" src="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode="widthFix"></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>
</view>

</Tabs>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
.collect_main {
background-color: #f3f4f6;
}
.collect_main .collect_title {
padding: 30rpx 0;
display: flex;
}
.collect_main .collect_title .collect_tips {
padding: 15rpx;
border: 1rpx solid #ccc;
margin-left: 25rpx;
background-color: #fff;
}
.collect_main .collect_title .active {
color: var(--themeColor);
border-color: currentColor;
}
.collect_main .collect_content .goods_item {
display: flex;
border-bottom: 2rpx solid #ccc;
background-color: #fff;
}
.collect_main .collect_content .goods_item .goods_img_wrap {
flex: 2;
display: flex;
justify-content: center;
align-items: center;
}
.collect_main .collect_content .goods_item .goods_img_wrap image {
width: 70%;
}
.collect_main .collect_content .goods_item .goods_info_wrap {
flex: 3;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.collect_main .collect_content .goods_item .goods_info_wrap .goods_name {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.collect_main .collect_content .goods_item .goods_info_wrap .goods_price {
color: var(--themeColor);
}
收藏界面js功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// pages/collect/index.js
Page({

/**
* 页面的初始数据
*/
data: {
collect:[],
tabs: [
{
id: 0,
value: "商品收藏",
isActive: true
},
{
id: 1,
value: "品牌收藏",
isActive: false
},
{
id: 2,
value: "店铺收藏",
isActive: false
},
{
id: 3,
value: "浏览足迹",
isActive: false
}
],
},

onShow: function () {
const collect=wx.getStorageSync("collect")||[];
this.setData({
collect
})

},

handleTabsItemChange(e) {
const { index } = e.detail;
let { tabs } = this.data;
tabs.forEach((v, i) => i === index ? v.isActive = true : v.isActive = false)
this.setData({
tabs
})
}


})
页面图

登录
页面布局
1
<button type="primary" plain open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo">登录</button>
1
2
3
4
button{
width: 70%;
margin-top: 40rpx;
}
js功能实现
1
2
3
4
5
6
7
8
9
10
11
Page({
handleGetUserInfo(e){
// console.log(e);
const {userInfo}=e.detail
wx.setStorageSync("userinfo", userInfo);
wx.navigateBack({
delta: 1
});
}

})
订单
页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange">
<view class="order_main">
<view
wx:for="{{orders}}"
wx:key="order_id"
class="order_item">
<view class="order_no_row">
<view class="order_no_text">订单编号</view>
<view class="order_no_value">{{item.order_number}}</view>
</view>
<view class="order_price_row">
<view class="order_price_text">订单价格</view>
<view class="order_price_value">¥{{item.order_price}}</view>
</view>
<view class="order_time_row">
<view class="order_time_text">订单日期</view>
<view class="order_time_value">{{item.create_time_cn}}</view>

</view>
</view>
</view>
</Tabs>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.order_main .order_item {
padding: 20rpx;
border-bottom: 1rpx solid #ccc;
color: #666;
}
.order_main .order_item .order_no_row {
display: flex;
justify-content: space-between;
padding: 10rpx 0;
}
.order_main .order_item .order_price_row {
display: flex;
justify-content: space-between;
padding: 10rpx 0;
}
.order_main .order_item .order_price_row .order_price_value {
color: var(--themeColor);
font-size: 32rpx;
}
.order_main .order_item .order_time_row {
display: flex;
justify-content: space-between;
padding: 10rpx 0;
}
js功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
const { request } = require("../../request/index");
Page({

/**
* 页面的初始数据
*/
data: {
orders:[],
tabs: [
{
id: 0,
value: "全部",
isActive: true
},
{
id: 1,
value: "待付款",
isActive: false
},
{
id: 2,
value: "待收货",
isActive: false
},
{
id: 3,
value: "退款/退货",
isActive: false
}
],
},


onShow: function (options) {
const token=wx.getStorageSync("token");
if(!token){
wx.navigateTo({
url: '/pages/auth/index',
});
return
}

let pages= getCurrentPages();
let currentPage=pages[pages.length-1]
const {type}=currentPage.options
this.changeTitleByIndex(type-1)
this.getOrders(type)
},
// 获取列表的方法
getOrders(type){
request({url:"/my/orders/all",data:{type}})
.then(res=>{
this.setData({
orders:res.orders.map(v=>({...v,create_time_cn:(new Date(v.create_time*1000).toLocaleString())})),
})
})
},
// 根据标题索引来激活选中 标题数组
changeTitleByIndex(index){
let { tabs } = this.data;
tabs.forEach((v, i) => i === index ? v.isActive = true : v.isActive = false)
this.setData({
tabs
})
},
handleTabsItemChange(e) {
const { index } = e.detail;
this.changeTitleByIndex(index);
this.getOrders(index+1)
}

})
订单页面图

反馈
页面布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange">
<view class="fb_main">
<view class="fb_title">问题的种类</view>
<view class="fb_tips">
<text>功能建议</text>
<text>购买遇到问题</text>
<text>性能问题</text>
<text>其他</text>
</view>
<view class="fb_content">
<textarea placeholder="请描述您的问题" value="{{textVal}}" bindinput="handleTextInput"></textarea>
<view class="fb_tool">
<button bindtap="handleChooseImg">+</button>
<view
class="up_img_item"
wx:for="{{chooseImgs}}"
wx:key="*this"
bindtap="handleRemoveImg"
data-index="{{index}}">
<UpImg src="{{item}}"></UpImg>
</view>
</view>
</view>
<view class="form_btn_wrap">
<button type="warn" bindtap="handleFormSubmit">
<icon type="success_no_circle" size="23" color="white"></icon>
提交
</button>

</view>

</view>
</Tabs>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
page {
background-color: #eeeeee;
}
.fb_main {
padding: 20rpx;
color: #666;
}
.fb_main .fb_tips {
display: flex;
flex-wrap: wrap;
}
.fb_main .fb_tips text {
width: 30%;
padding: 10rpx;
text-align: center;
background-color: #fff;
margin: 20rpx 10rpx;
}
.fb_main .fb_content {
background-color: #fff;
margin-top: 20rpx;
}
.fb_main .fb_content textarea {
padding: 10rpx;
}
.fb_main .fb_content .fb_tool {
display: flex;
flex-wrap: wrap;
padding-bottom: 30rpx;
}
.fb_main .fb_content .fb_tool button {
width: 90rpx;
height: 90rpx;
font-size: 60rpx;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
color: #ccc;
margin: 0;
margin-left: 20rpx;
margin-top: 20rpx;
}
.fb_main .fb_content .fb_tool .up_img_item {
margin-left: 20rpx;
margin-top: 20rpx;
}
.fb_main .form_btn_wrap {
margin-top: 20rpx;
display: flex;
justify-content: flex-end;
}
.fb_main .form_btn_wrap button {
margin: 0;
width: 30%;
display: flex;
align-items: center;
justify-content: center;
}
js功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// pages/feedback/index.js
Page({

/**
* 页面的初始数据
*/
data: {
tabs: [
{
id: 0,
value: "体验问题",
isActive: true
},
{
id: 1,
value: "商品、商家投诉",
isActive: false
}
],
chooseImgs:[],
textVal:""
},
UploadImgs:[],
handleTabsItemChange(e) {
const { index } = e.detail;
let { tabs } = this.data;
tabs.forEach((v, i) => i === index ? v.isActive = true : v.isActive = false)
this.setData({
tabs
})
},
handleChooseImg(){
wx.chooseImage({
count: 9,
sizeType: ['original','compressed'],
sourceType: ['album','camera'],
success: (result)=>{
this.setData({
chooseImgs:[...this.data.chooseImgs,...result.tempFilePaths]
})
}
});
},
handleRemoveImg(e){
const {index}=e.currentTarget.dataset
let {chooseImgs}=this.data
chooseImgs.splice(index,1)
this.setData({
chooseImgs
})
},
handleTextInput(e){
this.setData({
textVal:e.detail.value
})
},
handleFormSubmit(e){
const {textVal,chooseImgs}=this.data
if(!textVal.trim()){
wx.showToast({
title: '输入不合法',
icon: 'none',
mask: true
});
return
}
wx.showLoading({
title: "正在上传中",
mask: true
});
if(chooseImgs.length!=0){
chooseImgs.forEach((v,i)=>{
wx.uploadFile({
url: 'https://media.mogu.com/image/scale?appKey=15m&w=500&h=500&quality=100',
filePath: v,
name: "image",
formData: {},
success: (result)=>{
let url=JSON.parse(result.data).result.url
this.UploadImgs.push(url)
if(i===chooseImgs.length-1){
wx.hideLoading();
console.log("把文本的内容和外网的图片数组 提交到后台中");
this.setData({
textVal:"",
chooseImgs:[]
})
wx.showToast({
title: '全部上传成功',
icon: 'none',
duration: 1500,
mask: true,
});
wx.navigateBack({
delta: 1
});
}
}
});
})
}else{
wx.hideLoading();
console.log("只是提交了文本");
wx.navigateBack({
delta: 1
});
}

}
})
反馈页面图

js功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// pages/user/index.js
Page({

data: {
userinfo:{},
collectNums:0
},

onShow: function () {
const userinfo=wx.getStorageSync("userinfo");
const collect=wx.getStorageSync("collect")||[];
this.setData({
userinfo,
collectNums:collect.length
})
},
})

总结

此次微信小程序,大多是和线上视频的老师跟打的。大多数的原理实现以及页面布局还算清楚,可从6.购物车开始,特别是支付那一段,的确有些晦涩别扭,是后续复习的注重点。

笔记于2020/8/24 20:50完成

纪念!