微信小程序项目开发笔记(完结)

微信小程序项目开发

  • 一、准备工作
    • 1.新建小程序项目
    • 2.整理项目
      • 2.1 删除文件或代码
      • 2.2 整理文件或代码
      • 2.3 添加单文件标题
    • 3.搭建目录结构
    • 4.搭建项目的页面
    • 5.搭建项目的tabbar
  • 二、正式开发
    • 1.初始化页面样式
    • 2.搜索框
    • 3.轮播图
    • 4.导航图
    • 5.楼层图
    • 6.分类界面
      • 6.1 搜索框
      • 6.2 左右两侧滚动功能
      • 6.3 渲染左右两侧的数据
      • 6.4 点击左侧栏切换右侧数据
      • 6.5 右侧界面置顶功能
    • 7.缓存数据技术
    • 8.商品列表页面
      • 8.1 传参跳转
      • 8.2 搜索框
      • 8.3 自定义Tab栏
      • 8.4 使用Tab栏
      • 8.5 Tabs子组件接收父组件参数
      • 8.6 Tabs子组件定义父组件函数
      • 8.7 父组件定义子组件函数
    • 9.商品列表页面渲染
      • 9.1 渲染思路
      • 9.2 布局CSS样式
    • 10.上拉获取剩余数据
      • 10.1 提示框wx.showToast
      • 10.2 基础代码
    • 11.下拉刷新enablePullDownRefresh
    • 12.加载等待框wx.showLoading&隐藏wx.hideLoading
    • 13.跳转商品详情
    • 14.商品详情页面布局
      • 14.1 基本布局
      • 14.2 隐藏按钮
    • 15.商品放大预览wx.previewImage
    • 16.工具栏跳转购物车
    • 17.加入购物车
    • 18.获取收获地址
      • 18.1 添加收获地址按钮
      • 18.2 业务逻辑
      • 18.3 函数封装
      • 18.4 代码简化
    • 19.地址与按钮切换
    • 20.地址数据渲染
    • 21.物品数据渲染
      • 21.1 布局思路
      • 21.2 添加购物车选中属性
      • 21.3 渲染数据
    • 22.结算工具
      • 22.1 结算工具布局
      • 22.2 价格、件数总计
      • 22.3 商品选中
      • 22.4 商品全选/反选
      • 22.5 商品数量+、- 按钮
      • 22.6 函数封装
      • 22.7 商品删除
      • 22.8 商品结算&wx.navigateTo
    • 23.支付界面
    • 24.微信支付
      • 24.1 支付逻辑&token
      • 24.2 授权界面
      • 24.3 创建订单
      • 24.4 判断token
      • 24.5 准备请求头、请求体
      • 24.6 获取订单
      • 24.7 移除购物车已支付物品
      • 24.8 跳转到订单页面
    • 25.个人中心
      • 25.1 用户登录
      • 25.2 登录信息获取
      • 25.3 页面渲染
    • 26.订单页面
      • 26.1 参数携带
      • 26.2 引用Tabs栏组件
    • 27.收藏功能
      • 27.1 页面栈getCurrentPages
      • 27.2 收藏状态
      • 27.3 改变收藏状态
      • 27.4 个人中心收藏数据
    • 28.收藏界面
      • 28.1 Tabs组件引用
      • 28.2 页面布局
      • 28.3 页面渲染
      • 28.4 商品跳转
    • 29.搜索中心界面布局
    • 30.渲染搜索界面
      • 30.1 防抖思维
      • 30.2 渲染搜索数据
      • 30.3 清除数据
    • 31.图片上传wx.uploadFile
  • 三、项目发布

一、准备工作

1.新建小程序项目

PS.我在新建的时候使用了APPID!
微信小程序项目开发笔记(完结)_第1张图片

2.整理项目

2.1 删除文件或代码

微信小程序项目开发笔记(完结)_第2张图片
删除图示的代码之后来到pages文件夹下,删除logs文件夹
微信小程序项目开发笔记(完结)_第3张图片

2.2 整理文件或代码

来到全局文件下,找到app.wxss将默认样式全部删除
微信小程序项目开发笔记(完结)_第4张图片
来到全局文件下,找到app.js将默认代码全部删除
微信小程序项目开发笔记(完结)_第5张图片
之后输入wx-app创建新代码,这里我更换了主题,黑屏看着有点难受
微信小程序项目开发笔记(完结)_第6张图片

之后找到首页index.wxml,删除里面的所有代码
找到index.wxss,删除里面的所有代码

这里就不演示图片了

2.3 添加单文件标题

来到index.json文件下,添加这一行代码

// An highlighted block
"navigationBarTitleText": "优购首页"

3.搭建目录结构

在文件主目录下新建以下五个文件夹,效果如下图

目录名 作用
styles 存放公共样式
components 存放组件
lib 存放第三⽅库
utils 帮助库
request 接口帮助库

微信小程序项目开发笔记(完结)_第7张图片

4.搭建项目的页面

页面名称 Value
⾸⻚ index
分类⻚⾯ category
商品列表⻚⾯ goods_list
商品详情⻚⾯ goods_detail
购物⻋⻚⾯ cart
收藏⻚⾯ collect
订单⻚⾯ order
搜索⻚⾯ search
个⼈中⼼⻚⾯ user
意⻅反馈⻚⾯ feedback
登录⻚⾯ login
授权⻚⾯ auth
结算⻚⾯ pay

找到app.json文件,在pages数组中新增需要的代码项

// An highlighted block
"pages": [
        "pages/index/index",
        "pages/category/index",
        "pages/goods_list/index",
        "pages/goods_detail/index",
        "pages/cart/index",
        "pages/collect/index",
        "pages/order/index",
        "pages/user/index",
        "pages/feedback/index",
        "pages/search/index",
        "pages/login/index",
        "pages/auth/index",
        "pages/pay/index"
    ],

5.搭建项目的tabbar

新建一个icons文件夹,用于存放tabbar图标
在app.json中创建一个tabbar数组在list中写属性

"tabBar": {
        "color": "",
        "selectedColor": "",
        "backgroundColor": "",
        "position": "bottom",
        "borderStyle": "black",
        "list": [
            {
                "pagePath": "",
                "text": "",
                "iconPath": "",
                "selectedIconPath": ""
            }
        ]
    },

二、正式开发

1.初始化页面样式

来到全局配置文件app.wxss,书写以下代码:
这里要特别注意,wxss不支持通配符*

page,
view,
text,
swiper,
swiper-item,
image,
navigator {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}


/* 主题颜色 */

page {
    /* 定义主题颜色 */
    --themeColor: #d32677;
    font-size: 28rpx;
}

如果要在组件中使用全局定义的主题颜色,就需要使用var(–themeColor)

2.搜索框

在此,项目需求是点击搜索框可以实现跳转到搜索页面功能
找到之前新建的components文件夹,新建一个组件文件夹Search
使用微信开发小助手的新建component功能建立好四个基本文件
在Search.wxml中建立一个搜索框,实现跳转到搜索页面:

<view class="search_input">
    <navigator open-type="navigate" url="/pages/search/index"><text class="iconfont icon-shoucang1"></text>搜索</navigator>
</view>

来到index.json文件下,新增使用组件代码

{
    "usingComponents": {"Search":"/components/Search/Search"},
}

之后我们就在index.wxml中使用该Search组件

<view class="xcx_index">
  <!-- 搜索框开始 -->
  <Search></Search>
  <!-- 搜索框结束 -->
</view>

3.轮播图

这里需要对外网的资源进行请求,我找的是itheima的
使用wx-request快速的建立一个请求,在此我已经在我的小程序中把它加入了白名单,当然也可以直接不校验https
为了避免回调地狱的问题,使用Promise技术
来到我之前创建的request文件夹,新建一个index.js文件,书写下列代码:

export const request = (params) => {
    return new Promise((resolve, reject) => {
        wx.request({
            ...params,
            success: (result) => {
                resolve(result)
            },
            fail: (err) => {
                reject(err)
            }
        });

    })
}

回到首页的那个index.js,在onload生命周期函数中使用它

// An highlighted block
import { request } from "../../request/index.js"
Page({
	onLoad: function(options) {
        request({ url: "https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata" })
        .then(result => {
            this.setData({
                swiperList: result.data.message
            })
        })
    },
})

之后就是引用轮播图了,使用wx:for渲染图片

<swiper autoplay="{{true}}" indicator-dots="{{true}}" circular="{{true}}" interval="2000" indicator-active-color="#497">
      <swiper-item wx:for="{{swiperList}}" wx:key="goods_id">
        <navigator>
          <image mode="widthFix" src="{{item.image_src}}"></image>
        </navigator>
      </swiper-item>
    </swiper>

4.导航图

与上述差不多的思路,从接口拿到数据之后渲染到页面上
把发送请求的函数单独写在与onload平级中,然后只需要在onload中使用this调用发送请求的函数

onLoad: function(options) {
	this.getCateList();
}
getCateList() {
        request({ url: "https://api-hmugo-web.itheima.net/api/public/v1/home/catitems" }).then(result => {
            this.setData({
                CateList: result.data.message
            })
        })
    },

页面渲染部分是使用wx:for,这里不过多演示代码了

5.楼层图

分为标题页和图片页,我需要分别的渲染,大致思路还是拿到数据之后渲染,非常简单

6.分类界面

6.1 搜索框

分类界面嘛,少不了搜索功能,引用组件就行,参考2步骤

6.2 左右两侧滚动功能

要满足左右两侧都能滚动而互不干扰的效果,就需要用到一个组件scroll-view,参考完文档之后,最适合我的是scroll-y属性
这里要在wxss中设置一下flex布局!

.fenlei_index .cates_container {
    height: calc( 100vh - 90rpx);
    display: flex;
}
.fenlei_index .cates_container .left_menu {
    flex: 2;
}
.fenlei_index .cates_container .right_content {
    flex: 5;
}

index文件代码:

<scroll-view scroll-y class="left_menu">
    <view class="menu_item" wx:for="{{leftMenu}}" wx:key="*this">
       {{item}}
    </view>
</scroll-view>
<scroll-view scroll-y class="right_content">
</scroll-view>

6.3 渲染左右两侧的数据

思路还是和之前一样,调接口拿数据
默认拿到的是第一项的数据。

Page({
	data: {
        leftMenu: [],
        rightContent: [],
        currentindex: 0
    },
    onLoad: function(options) {
        this.getCates();
    },
    getCates() {
        request({
            url: "https://api-hmugo-web.itheima.net/api/public/v1/categories"
        }).then(res => {
            this.Cates = res.data.message
            let leftMenu = this.Cates.map(v => v.cat_name)
            let rightContent = this.Cates[0].children
            this.setData({
                leftMenu,
                rightContent
            })
        })
    },
})

回到index,使用wx:for渲染数据

6.4 点击左侧栏切换右侧数据

这里涉及到了Tab栏切换的相关知识,给左侧菜单绑定一个事件,把当前的索引号传进去,让右侧数据根据传过来的索引号更新数据

//index代码
<view bind:tap="handleItemTap" data-index="{{index}}">
//js代码
handleItemTap(e) {
        const newindex = e.currentTarget.dataset.index
        let rightContent = this.Cates[newindex].children
        this.setData({
            currentindex: newindex,
            rightContent
        })
    },

6.5 右侧界面置顶功能

在一个tab栏内对右侧界面进行上下滑动时,切换至下一个tab栏,会出现右侧界面不置顶的bug,使用scroll-top解决

<scroll-view scroll-top="{{scrollTop}}" scroll-y class="right_content">
</scroll-view>

在data中定义这个scrollTop,在每一次渲染右侧页面时,给scrollTop赋值为0即可

7.缓存数据技术

使用缓存技术(本地存储)能够让用户第二次打开小程序的速度提升
web中的本地存储和 小程序中的本地存储的区别

1 写代码的方式不一样
web: localStorage.setItem("key","value");localStorage.getItem("key")
小程序中: wx.setStorageSync("key", "value"); wx.getStorageSync("key")
2:类型转换
web: 不管存入的是什么类型的数据,最终都会先调用一下 toString(),把数据变成了字符串再存入进去
小程序: 不存在类型转换的这个操作 存什么类似的数据进去,获取的时候就是什么类型

在onload生命周期函数书写代码:在上述4步骤中已经定义了getCates()方法

onLoad: function(options) {
		//获取本地存储数据
        const Cates = wx.getStorageSync("cates");
        //如果没有
        if (!Cates) {
            this.getCates();
        } else {
            //有数据
            if (Date.now() - Cates.time > 10000) {
                this.getCates();
            } else {
                this.Cates = Cates.data
                let leftMenu = this.Cates.map(v => v.cat_name)
                let rightContent = this.Cates[0].children
                this.setData({
                    leftMenu,
                    rightContent
                })
            }
        }
    },

改进getCates()方法,在里面加上获取数据后存入本地存储代码

wx.setStorageSync("cates", { time: Date.now(), data: this.Cates });

8.商品列表页面

8.1 传参跳转

这里我的业务功能是:点击步骤6的分类商品,跳转到具体的商品界面
回到第6步写好的navigator,在url属性加上参数

//跳转到商品列表页面时带上后台的id参数
<navigator url="/pages/goods_list/index?cid={{item2.cat_id}}">

8.2 搜索框

商品列表页面当然也少不了搜索功能,引用组件就行,参考2步骤

8.3 自定义Tab栏

这里再一次使用到了自定义组件
找到之前新建的components文件夹,新建一个组件文件夹Tabs
使用微信开发小助手的新建component功能建立好四个基本文件
分析结构,需要一个title区域和content区域
1.titie区域使用wx:for渲染,并且绑定点击事件,传递索引参数
2.使用三元表达式判断选中状态
3.content区域留下一个作用域插槽slot即可

<view class="tabs">
    <view class="tabs_title">
        <view bind:tap="handleTap" data-index="{{index}}" wx:for="{{tabs}}" wx:key="id" class="title_item {{item.isActive?'active':''}}">{{item.value}}</view>
    </view>
    <view class="tabs_content">
        <slot ></slot>
    </view>
</view>

8.4 使用Tab栏

来到需要使用tab栏的文件,修改json设置

"usingComponents": {
        "Search": "/components/Search/Search",
        "Tabs": "/components/Tabs/Tabs"
    },
    "navigationBarTitleText": "商品列表"

在wxml引入Tabs组件,定义待传递参数

<Tabs tabs="{{tabs}}">
</Tabs>

来到js文件,书写待传递参数

Page({
    /**
     * 页面的初始数据
     */
    data: {
        tabs: [{
                id: 0,
                value: "综合",
                isActive: true
            },{
                id: 1,
                value: "销量",
                isActive: false
            }, {
                id: 2,
                value: "价格",
                isActive: false
            }]
     },
 })

8.5 Tabs子组件接收父组件参数

来到子组件的js文件,使用properties接收父组件参数

properties: {
        tabs: {
            type: Array,
            value: []
        }
},

8.6 Tabs子组件定义父组件函数

来到子组件的js文件,使用methods定义父组件函数

methods: {
        handleTap(e) {
            const { index } = e.currentTarget.dataset
            this.triggerEvent("tabsitemchange", { index })
        }
    }

8.7 父组件定义子组件函数

wxml代码:

<Tabs bind:tabsitemchange="handleTabsChange" tabs="{{tabs}}">
</Tabs>

来到父组件的js文件,与onload函数平级书写函数
拿到子组件传递过来的索引号,拿到本函数定义的tabs数组数据,遍历,如果索引号与遍历索引相等,则更改选中状态,重新赋值tabs

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

9.商品列表页面渲染

9.1 渲染思路

由左侧图片和右侧文字说明组成
这里因为使用了Tab栏切换,所以我需要使用block和wx:if对tabs的选中状态进行判断,来决定显示哪一部分的内容

<block wx:if="{{tabs[0].isActive}}">0</block>
<block wx:elif="{{tabs[1].isActive}}">1</block>
<block wx:else="{{tabs[2].isActive}}">2</block>

定义数据

data: {
	goodslist:[]
}

然后从后台拿到数据,这里使用ES7的async await

async getGoodsList() {
   const res = await request({ url: "/goods/search", data: this.queryParams })
   this.setData({
       goodslist: res.goods
   });       
},

在数组索引0渲染页面:

<view class="first_tab">
    <navigator wx:for="{{goodslist}}" wx:key="goods_id" class="goods_item">
    <!-- 左侧 -->
    <view class="img">
        <image mode="widthFix" src="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" />
    </view>
    <!-- 右侧 -->
    <view class="goods_info">
       <view class="goods_name">{{item.goods_name}}</view>
       <view class="goods_price">{{item.goods_price}}</view>
     </view>
    </navigator>
</view>

9.2 布局CSS样式

使用了很多新知识,在这里记录一下:

//这四行代码均是为了一个功能而生:文字最大显示两行
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;

10.上拉获取剩余数据

10.1 提示框wx.showToast

wx.showToast({
    title: '',
    icon: 'none',
    image: '',
    duration: 1500,
    mask: false,
});

10.2 基础代码

这个功能是指:用户上滑页面 滚动条触底 开始加载下一页数据
定义滚动条触底事件:

 onReachBottom() {},

定义有关参数:

queryParams: {
   query: "",
   cid: "",
   pagenum: 1,
   pagesize: 10,
},
totalpage: 1,

判断还有没有下一页数据

/*
1 获取到总页数
 总页数 = Math.ceil(总条数 /  页容量  pagesize)
2 获取到当前的页码  pagenum
3 判断一下 当前的页码是否大于等于 总页数     
*/ 
async getGoodsList() {
	//省略一行拿数据代码
	const total = res.total
    this.totalpage = Math.ceil(total / this.queryParams.pagesize)
    this.total = total
    //数据请求回来  要对data中的数组进行拼接 而不是全部替换
    this.setData({
       goodslist: [...this.data.goodslist, ...res.goods]
    });
}
onReachBottom() {
	if (this.queryParams.pagenum >= this.totalpage) {
	//假如没有下一页数据 弹出一个提示
	//展示提示框
        wx.showToast({
            title: '没有更多啦-.-',
        });
    } else {
    //还有下一页数据 来加载下一页数据
    //当前的页码+1
        this.queryParams.pagenum++;
        this.getGoodsList()
    }
},

11.下拉刷新enablePullDownRefresh

来到需要下拉刷新的json文件,添加以下配置

{
	"enablePullDownRefresh": true,
    "backgroundTextStyle": "dark"
}

来到js文件,添加下拉事件

onPullDownRefresh: function() {
	//清空数据
    this.setData({
        goodslist: []
    })
    //归位当前页码
    this.queryParams.pagenum = 1;
    //重新获取数据
    this.getGoodsList()
},

12.加载等待框wx.showLoading&隐藏wx.hideLoading

为了增加用户体验,在获取数据是,加上等待框
这里我选择在request文件夹的index.js中设置,保证每一次使用请求时都能有加载框,在请求完成后隐藏加载框,使用wx.hideLoading

//同时发送异步代码的次数
let ajaxtime = 0
export const request={params}=>{
	//调用一次请求就+1
	ajaxtime++
    //显示加载中效果
    wx.showLoading({
        title: "Loading-.-",
        mask: true
    });
    return new Promise((resolve,reject)=>{
		wx.request({
			//新增请求完成函数
			complete: () => {
                ajaxtime--
                if (ajaxtime === 0) {
                    wx.hideLoading();
                }
            },
		})
	})
}

13.跳转商品详情

这里需要在商品列表页面中的navigator标签传递一个url参数,并且跳转到商品详情界面

<navigator url="/pages/goods_detail/index?goods_id={{item.goods_id}}">

修改商品详情的json文件,改变title值

{
    "usingComponents": {},
    "navigationBarTitleText": "商品详情"
}

事先定义商品细节对象,在js文件中的onload函数,使用options接收navigator标签传递的url参数

data: {
    goodsobj:{}
},
onLoad: function (options) {
    const goods_id=options.goods_id
    this.getGoodsDetail(goods_id)
 },

定义上述的getGoodsDetail方法,发请求拿数据,赋值商品细节对象

const res = await request({url:"/goods/detail",data:{goods_id}})
    this.setData({
      goodsobj:res
})

14.商品详情页面布局

14.1 基本布局

基本布局是由一个swiper轮播图,商品价格&商品信息,图文详情,以及下方的购物工具栏
依然是使用拿到的服务器数据来渲染页面
这里对布局的代码省略

14.2 隐藏按钮

联系客服、分享、购物车三个功能处,需要使用button进行功能呈现,但是我的布局采用了view,如果要修改非常麻烦,因此我想到加上一个隐藏的按钮
⭐在此仅举一个例子:

<view class="tool_item">
        <view class="iconfont icon-kefu"></view>
        <view>联系客服</view>
        //隐藏按钮
        <button open-type="contact"></button>
</view>

CSS代码:

.tool_item {
//给父容器开相对定位
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 90rpx;
}
.tool_item button {
//给按钮开绝对定位
  position: absolute;
  //完全透明覆盖
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
}

15.商品放大预览wx.previewImage

给轮播图片swiper-item绑定一个bind:tap函数,并且使用自定义属性传递图片

<swiper-item data-url="{{item.pics_mid}}" bind:tap="handlePreviewImage">

来到js文件,与onload平级,定义这样一个函数,并且需要定义一个全局商品数据,因为data中拿不到服务器发送请求后获得的数据

//全局商品数据
GoodsInfo: {},
handlePreviewImage(){
	const urls = this.goodsInfo.pics.map(v=>v.pics_mid)
	const current = e.currentTarget.dataset.url
	wx.previewImage({
		current,
		urls
	})
};

16.工具栏跳转购物车

将14.2步骤购物车按钮写的view标签改为navigator,指定跳转界面
但是这里要特别注意,navigator默认跳转至非tabbar界面,我们的购物车很明显是tabbar界面,因此需要指定open-type="switchTab"

<navigator open-type="switchTab" url="/pages/cart/index" class="tool_item">
        <view class="iconfont icon-gouwuche"></view>
        <view >购物车</view>
</navigator>

17.加入购物车

点击加入购物车按钮,实现在购物车界面看到添加的物品功能
首先给view标签绑定一个bind:tap事件:

<view bind:tap="handleCartAdd" class="tool_item btn-cart">

来到js文件,与onload平级,定义这样一个函数
1.首先从本地存储中查询是否有物品添加到购物车中,获取到一个cart数组
2.对该cart数组进行索引查询
3.如果结果=-1,证明该商品未被添加,给商品设置数量为1,push到cart数组
4.如果结果为其他值,证明该商品被查询出来,让该商品数量+1
5.把结果返回给本地存储,并且加上一个showToast

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
            cart.push(this.GoodsInfo)
        } else {
            cart[index].num++;
        }
        wx.setStorageSync("cart", cart);
        wx.showToast({
            title: '加入成功-.-',
            icon: 'success',
            mask: true,
        });
    },

18.获取收获地址

18.1 添加收获地址按钮

这个就比较简单,给按钮绑定一个函数即可

<button bind:tap="handlePut" type="primary" plain="{{true}}">+添加收货地址</button>

18.2 业务逻辑

I.首先需要拿到用户的授权状态。
II.如果授权结果是空,那么就要诱导用户打开授权界面
III.如果授权结果不为空,那么我们就可以获取用户的收货地址了
IV.将获取的信息放到本地存储中

18.3 函数封装

在utils文件夹下新建一个js文件,用于存放函数
暴露三个wx-函数:

export const getSetting = () => {
    return new Promise((resolve, reject) => {
        wx.getSetting({
        });
    })
}
export const chooseAddress = () => {
    return new Promise((resolve, reject) => {
        wx.chooseAddress({
        });
    })
}
export const openSetting = () => {
    return new Promise((resolve, reject) => {
        wx.openSetting({
        });
    })
}

在购物车的js文件中引入

import { getSetting, chooseAddress, openSetting } from "../../utils/asyncWX"
//防止ES7语法报错
import regeneratorRuntime from "../../lib/runtime/runtime"

18.4 代码简化

async handlePut() {
    const res = await getSetting()
    const scopeAddress = res.authSetting["scope.address"]
    if (scopeAddress === false) {
         await openSetting()
    }
    const address = await chooseAddress()
    wx.setStorageSync("address", address);
}

19.地址与按钮切换

功能:如果本地存储有地址信息,就渲染地址信息,本地存储内没有地址信息,就显示获取地址的按钮

//页面渲染完成获取本地存储的地址
data:{
	address:{}
}
onShow() {
	const address = wx.getStorgeSync("address")
	this.setData({
        address,
  	})
}

回到wxml文件,利用wx:if进行判断

<view wx:if="{{!address.userName}}" class="address_btn"></view>
<view wx:else class="user_info_row"></view>

20.地址数据渲染

根据后台返回的数据address进行渲染,较为简单

21.物品数据渲染

21.1 布局思路

左侧是否选中框;中间物品图片;右侧物品描述和价格;右下方购买件数与加减按钮
微信小程序项目开发笔记(完结)_第8张图片

21.2 添加购物车选中属性

回到之前的商品详情(13步骤),在点击添加购物车按钮后,再传递一个选中属性

handleCartAdd() {
	//省略部分代码
	...
	this.GoodsInfo.checked = true
	...
}

21.3 渲染数据

这里就需要获得(步骤17)本地存储的购物车数据以动态渲染

data: {
        address: {},
        cart: {}
    },
onShow() {
        const cart = wx.getStorageSync("cart");
        const address = wx.getStorageSync("address");
        this.setData({
            address,
            cart
        })
    }

wx:for循环返回的cart对象即可

22.结算工具

22.1 结算工具布局

左侧全选,中间为总价格,最右侧是结算按钮
在这里插入图片描述

22.2 价格、件数总计

这里需要对选中的商品进行价格计算,因此需要if进行判断
来到js文件,创建一个函数,用于计算选中的商品总价格
1.获取本地存储的cart数组,遍历返回商品是否全选
2.对数组进行遍历,先筛选掉未被选中的商品,再对剩下的商品进行计算价格,计算件数
3.设置回上述计算的值

setCart(cart) {
        wx.setStorageSync("cart", cart);
        //every数组方法会遍历会接收一个回调函数,那么每一个回调函数都返回true,那么every方法就会返回true
        const allChecked = cart.length ? cart.every(v => v.checked) : false
        let totalprice = 0
        let totalnum = 0
        cart.forEach(element => {
            if (element.checked) {
                totalprice += element.num * element.goods_price
                totalnum += element.num
            }
        });
        this.setData({
            cart,
            totalprice,
            totalnum,
            allChecked
        })
    }

在页面加载完成的时候需要调用这个函数

22.3 商品选中

给每个复选框绑定一个单击事件,并且传递商品的id值,便于js文件拿到来修改对应商品的选中状态

<checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">

来到js文件,新建这个函数

 handleItemChange(e) {
        const goods_id = e.currentTarget.dataset.id
        let { cart } = this.data
        let index = cart.findIndex(v => v.goods_id === goods_id)
        //对选中状态进行取反
        cart[index].checked = !cart[index].checked
        //调用上述定义的价格计算函数
        this.setCart(cart)
    },

22.4 商品全选/反选

给全选复选框绑定一个函数单击事件,从data数据中拿到购物车数据与全选状态,让全选状态取反,并且购物车所有选中项均与全选状态相同

<checkbox-group bindchange="handleItemAllChange">
     <checkbox checked="{{allChecked}}"/>全选
</checkbox-group>

来到js文件,编写这个函数

 handleItemAllChange() {
        let { cart, allChecked } = this.data;
        allChecked = !allChecked;
        cart.forEach(v => v.checked = allChecked);
        //调用计算函数
        this.setCart(cart)
},

22.5 商品数量+、- 按钮

给加减按钮绑定一个单击事件函数,为了区分是加功能还是减功能,添加一个data-operation属性,并且传递商品的id号以修改

<view bind:tap="handleNum" data-operation="{{-1}}" data-id="{{item.goods_id}}"">-</view>
<view bind:tap="handleNum" data-operation="{{1}}" data-id="{{item.goods_id}}">+</view>

来到js文件,定义函数

handleNum(e){
	const {operation,id} = e.currentTarget.dataset;
	let {cart} = this.data
	let index = cart.findIndex(v=>v.goods_id = id)
	cart[index].num+=operation
	this.setCart(cart)
}

22.6 函数封装

参考18.3:在utils文件夹下js文件中,书写弹出提示框,确认框函数

export const showModal = (content) => {
    return new Promise((resolve, reject) => {
        wx.showModal({
            title: 'Tip-.-',
            content: content,
            success: (result) => {
                resolve(result)
            },
            fail: (err) => {
                reject(err)
            },
        });
    })
}
export const showToast = (title) => {
    return new Promise((resolve, reject) => {
        wx.showToast({
            title: title,
            icon: 'none',
            duration: 1500,
            success: (result) => {
                resolve(result)
            },
            fail: (err) => {
                reject(err)
            },
        });
    })
}

22.7 商品删除

如果此时数量值小于1,我们就要弹出一个删除商品的提示,并且将数量锁为1
引用22.6定义的两个函数和ES7语法校验
来到js文件,在之前定义的handleNum函数增加下列代码

handleNum(e){
	const {operation,id} = e.currentTarget.dataset;
	let {cart} = this.data
	let index = cart.findIndex(v=>v.goods_id = id)
	cart[index].num+=operation
	if (cart[index].num < 1) {
            cart[index].num = 1
            const res = await showModal("Do you want to remove it?")
            if (res.confirm) {
                cart.splice(index, 1)
                this.setCart(cart)
            }
        }
	this.setCart(cart)
}

22.8 商品结算&wx.navigateTo

这里我们需要获取到两个数据,一个是地址,一个是购物车里面的物品,如果两者的值有一个为空,那就需要返回这一次请求

<view bind:tap="handlePay" class="order_pay_wrap">
     结算 ({{totalnum}})
</view>

js函数功能较简单,这里介绍一下wx.navigateTo,跳转界面,但是保留当前界面

 wx.navigateTo({
    url: '/pages/pay/index',
});

23.支付界面

与购物车界面布局相似,删除一些不必要的wxml结构,wxss样式,js函数即可
这里需要注意,在页面加载完毕之时,只需要对选中商品进行结算

onShow({
	const address = wx.getStorageSync("address") || [];
    let cart = wx.getStorageSync("cart");
    let checkedCart = cart.filter(v=>v.checked)
    let totalprice = 0
    let totalnum = 0
    checkedCart.forEach(v=>{
		totalprice += v.num*v.goods_price
		totalnum += v.num
	})
	this.setData({
		address,
        checkedCart,
        totalprice,
        totalnum,
	})
})

24.微信支付

24.1 支付逻辑&token

哪些人 哪些帐号 可以实现微信支付?
1 企业帐号
2 企业帐号的小程序后台中必须给开发者添加上白名单
首先给支付按钮绑定一个函数
此时,如果我们用户没有授权,就需要跳转到授权界面拿到用户的授权信息,存放与token中

24.2 授权界面

授权界面本身元素较少,这里我选择使用一个按钮来进行这个功能,绑定一个函数,来处理用户的授权操作

<button open-type="getUserInfo" type="warn" plain="{{true}}" bindgetuserinfo="handlegetuserinfo">

来到js界面,对定义的函数进行处理,需要拿到用户的各项信息
这里需要引入之前定义的发请求函数、代码简化文件

import { login } from "../../utils/asyncWX"
import { request } from "../../request/index"
import regeneratorRuntime, { isGeneratorFunction } from "../../lib/runtime/runtime"
Page({
    async handlegetuserinfo(e) {
        const { signature, iv, encryptedData, rawData } = e.detail
        const { code } = await login();
        const loginParams = { signature, iv, encryptedData, rawData, code }
        //发送请求获取token
        const { token } = await request({ url: "/users/wxlogin", data: (loginParams), method: "post" })
        wx.setStorageSync("token", token);
        //回退一个界面
        wx.navigateBack({
            delta: 1
        });
    },
})

24.3 创建订单

有了token之后,就可以发请求来创建一个微信支付订单了
给支付按钮绑定一个单击响应函数

<view class="order_pay_wrap" bind:tap="handleOrderPay">

来到js文件,编辑函数:

async handleOrderPay() {}

这里需要在代码简化区提前准备一个发起微信支付请求的函数
来到utils文件夹,导出一个函数:

//pay参数接收获取到的支付信息,使用扩展运算符插入请求中
export const requestPayment = (pay) => {
    return new Promise((resolve, reject) => {
        wx.requestPayment({
            ...pay,
            success: (result) => {
                resolve(result)
            },
            fail: (err) => {
                reject(err)
            }
        });
    })
}

24.4 判断token

从本地存储中拿到token,如果没有跳转到授权页面

async handleOrderPay() {
	const token = wx.getStorageSync("token");
    if (!token) {
       wx.navigateTo({
            url: '/pages/order/index',
       });
    }
}

24.5 准备请求头、请求体

授权过后,会有一系列的用户信息存储,这一步就是拿到它们用于发送请求

async handleOrderPay() {
	//接24.4函数
	...
	 const header = { Authorization: token }
     const order_price = this.data.totalprice
     const consignee_addr = this.data.address
     const checkedCart = this.data.checkedCart
     //定义商品空数组
     let goods = []
     checkedCart.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 }
}

24.6 获取订单

async handleOrderPay() {
	//接24.5函数
	...
	//创建订单 获取订单编号
	const { order_number } = await request({ url: "/my/orders/create", method: "POST", data: orderParams, header })
	//发起预支付接口
	const { pay } = await request({ url: "/my/orders/req_unifiedorder", method: "POST", data: { order_number }, header })
	//调出微信支付
	await requestPayment(pay);
	//查询后台订单状态
    const res = await request({ url: "/my/orders/chkOrder", method: "POST", data: { order_number } });
    //弹出支付成功对话框
    await showToast({ title: "支付成功" });
}

24.7 移除购物车已支付物品

从本地存储中拿到购物车数组,留下未选中商品,返回给本地存储的cart即可

async handleOrderPay() {
	//接24.6函数
	...
	let newCart = wx.getStorageSync("cart");
    newCart = newCart.filter(v => !v.checked);
    wx.setStorageSync("cart", newCart);
}

24.8 跳转到订单页面

async handleOrderPay() {
	//接24.7函数
	...
	wx.navigateTo({
    	url: '/pages/order/index',
	});
}

25.个人中心

25.1 用户登录

首先,用户未登录时,在个人中展示一个登录按钮,点击这个按钮会跳转到登录授权界面

<navigator class="user_btn" url="/pages/login/index">登录</navigator>

登录授权界面较为简单,一个按钮,绑定一个open-type和一个函数
来到js文件,操作这个函数,目的是为了把用户信息存放在本地存储中

handleuserinfo(e) {
        const { userInfo } = e.detail
        wx.setStorageSync("userinfo", userInfo);
        wx.navigateBack({
            delta: 1
        });
    }

25.2 登录信息获取

在页面一渲染完成后,从本地存储中拿到用户信息

Page({
    data: {
        userinfo: {}
    },
    onShow() {
        const userinfo = wx.getStorageSync("userinfo");
        this.setData({
            userinfo
        })
    }
})

25.3 页面渲染

老套路,从data拿数据,插值表达式赋值,很简单

26.订单页面

26.1 参数携带

让订单页面在跳转的时候携带参数,便于Tab栏切换页面信息

<navigator url="/pages/auth/index?type=1">
	<view class="order_name">全部订单</view>
</navigator>
<navigator url="/pages/auth/index?type=2">                
   	<view class="order_name">待付款</view>
</navigator>
<navigator url="/pages/auth/index?type=3">
    <view class="order_name">待收货</view>
</navigator>

26.2 引用Tabs栏组件

参考8.3~8.8步骤,这里省略传参等一系列行为

27.收藏功能

这里需要回到商品详情界面

27.1 页面栈getCurrentPages

getCurrentPages() 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。
首先,在onShow函数中,无法使用options拿到页面加载时的参数
这里就需要使用getCurrentPages获取当前页面栈的实例

onShow: function() {
		//获取页面栈
        let pages = getCurrentPages();
        //操作本级页面数据
        let currentPage = pages[pages.length - 1]
        //拿到原本在onLoad函数中的options
        let options = currentPage.options
        const goods_id = options.goods_id
        this.getGoodsDetail(goods_id)
},

27.2 收藏状态

在data中新建一个变量,用于保存商品的收藏状态

data: {
    isCollect: false
},

书写onShow函数中定义的getGoodsDetail函数

//原函数中补充以下代码:
let collect = wx.getStorageSync("collect") || [];
let isCollect = collect.some(v => v.goods_id === this.GoodsInfo.goods_id)
this.setData({
	isCollect
})

27.3 改变收藏状态

给原收藏icon绑定一个单击响应事件,通过isCollect渲染不同状态的样式

<text bind:tap="handleAddshoucang" class="iconfont {{isCollect?'icon-shoucang1':'icon-shoucang'}}"></text>

来到js文件,书写定义的函数

handleAddshoucang(){
		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
        } else {
        //未查询到索引,说明未收藏,添加商品
            collect.push(this.GoodsInfo)
            isCollect = true
        }
        wx.setStorageSync("collect", collect);
        this.setData({
            isCollect
        })
}

27.4 个人中心收藏数据

来到个人中心界面,找到收藏的商品栏,绑定一个navigator,跳转到收藏界面

<navigator url="/pages/collect/index">
       <view class="his_num">{{collectnums}}</view>
       <view class="his_name">收藏的商品</view>
</navigator>

接下来回到js文件,定义一个数据,用于显示收藏的商品数量

data: {
  collectnums: 0
},
onShow() {
		//获取收藏本地存储数据
        const collect = wx.getStorageSync("collect") || [];
        this.setData({
            collectnums: collect.length
        })
}

28.收藏界面

28.1 Tabs组件引用

参考8.3~8.8步骤,这里省略传参等一系列行为

28.2 页面布局

参考商品列表界面的商品布局即可
微信小程序项目开发笔记(完结)_第9张图片

28.3 页面渲染

在onShow函数中定义获取本地存储中的collect,赋值给在data中定义的collect数组

28.4 商品跳转

点击商品,跳转到对应的商品详情界面。带上商品id参数即可

<navigator url="/pages/goods_detail/index?goods_id={{item.goods_id}}" wx:for="{{collect}}" wx:key="goods_id" class="goods_item">

29.搜索中心界面布局

由一个搜索框和内容框组成,当搜索框中有内容时,显示一个取消按钮,点击清空所有数据
I 无数据
微信小程序项目开发笔记(完结)_第10张图片
II 有数据
微信小程序项目开发笔记(完结)_第11张图片

30.渲染搜索界面

30.1 防抖思维

防止多次改变输入框的搜索内容而多次发送请求,需要使用定时器防抖思维。给输入框绑定一个bindinput,在里面实现这个功能

<input value="{{inputValue}}" bindinput="handleinput" placeholder="write goods in here!" />

js文件,在data中定义一个商品数组,同层级下定义一个定时器,并且书写一个发请求函数,需要带上关键字参数

data: {
        goods: [],
        //搜索框内容
        inputValue:''
    },
Timeid: -1,
handleinput(e) {
        const { value } = e.detail
        if (!value.trim()) {
            this.setData({
                goods: [],
            })
            return
        }
        //清楚防抖定时器
        clearTimeout(this.Timeid)
        //开启防抖定时器
        this.Timeid = setTimeout(() => {
        	//定时调用搜索请求
            this.qsearch(value)
        }, 1000)
    },
async qsearch(query) {
        const res = await request({ url: "/goods/qsearch", data: { query } })
        this.setData({
            goods: res
        })
    },

30.2 渲染搜索数据

非常简单,拿到data的goods数组,循环拿到里面的商品名称即可,并且带上导航功能,直接跳转到对应的商品:
url="/pages/goods_detail/index?goods_id={{item.goods_id}}"

30.3 清除数据

给按钮绑定一个单击事件

<button bind:tap="handlecancal">取消</button>

js文件书写函数功能,重置数组和搜索框的内容

handlecancal() {
        this.setData({
            inputValue: "",
            goods: []
        })
    }

31.图片上传wx.uploadFile

调用wx.uploadFile可以上传文件,url表示文件上传至的路径,filePath表示本机的文件路径,name表示上传文件的命名,success表示成功之后的回调函数
这里仅仅只记录这个功能,图片上传功能我用于了意见反馈,但是微信小程序中内置了这个功能

三、项目发布

至此所有功能开发完毕,点击右上角的上传按钮
在这之前,一定要去除不校验合法域名的勾选
在这里插入图片描述
点击上传按钮,输入版本号和备注,上传完毕后
来到公众平台,点击版本管理,再点击提交审核即可微信小程序项目开发笔记(完结)_第12张图片
微信小程序项目开发笔记(完结)_第13张图片

你可能感兴趣的:(笔记,小程序,javascript,前端)