小程序笔记总结(三)

129 阅读10分钟

11.构建电商类型的小程序

11.1 准备对应的小程序UI库

所谓的UI库就是第三方创建的一些自定义的组件,我们可以拿来直接使用

youzan.github.io/vant-weapp/…

11.1.1 配置UI库

安装UI库

image.png

$ npm i @vant/weapp -S --production
  • 一定要进入miniprogram 目录内 先执行 npm init -y

修改App.json

  • 将 app.json 中的 "style": "v2" 去除

修改 project.config.json

开发者工具创建的项目,miniprogramRoot 默认为 miniprogrampackage.json 在其外部,npm 构建无法正常工作。

需要手动在 project.config.json 内添加如下配置,使开发者工具可以正确索引到 npm 依赖的位置。

{
  ...
  "setting": {
    ...
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",
        "miniprogramNpmDistDir": "./miniprogram/"
      }
    ]
  }
}

注意: 由于目前新版开发者工具创建的小程序目录文件结构问题,npm构建的文件目录为miniprogram_npm,并且开发工具会默认在当前目录下创建miniprogram_npm的文件名,所以新版本的miniprogramNpmDistDir配置为'./'即可

构建 npm 包

image.png

  • 此时就可以删除 当前项目文件夹下的 node_modules 文件夹了

  • miniprogram_npm 文件夹就是vantweapp 为我们所提供的自定义的组件库

  • 如果大家没有安装过node,也没有yarn,说明没有安装,如果不想安装,那么大家可以直接拷贝 miniprogram_npm 文件夹至你的项目即可

typescript 支持

如果你使用 typescript 开发小程序,还需要做如下操作,以获得顺畅的开发体验。

安装 miniprogram-api-typings

# 通过 npm 安装
npm i -D miniprogram-api-typings

# 通过 yarn 安装
yarn add -D miniprogram-api-typings

在 tsconfig.json 中增加如下配置,以防止 tsc 编译报错。

请将path/to/node_modules/@vant/weapp修改为项目的 node_modules 中 @vant/weapp 所在的目录。

{
  ...
  "compilerOptions": {
    ...
    "baseUrl": ".",
    "types": ["miniprogram-api-typings"],
    "paths": {
      "@vant/weapp/*": ["./node_modules/@vant/weapp/dist/*"]
    },
    "lib": ["ES6"]
  }
}

11.2 构建小程序的首页

准备接口文档:http://121.89.205.189:3000/apidoc/

11.2.1 构建轮播图

// utils/request.js
// 数据请求
// axios.get() axios.post() axios({})
// https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html
// 加载数据显示loading动画
// https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showLoading.html
const baseUrl = "http://121.89.205.189:3000/api"

const request = (config) => {
  let { url, method = 'GET', header = {}, data = {} } = config
  // method = method || 'GET'
  wx.showLoading({
    title: '加载中',
  })
  return new Promise((resolve, reject) => {
    wx.request({
      url: baseUrl + url,
      method: method,
      header: header,
      data: data,
      success: res => {
        resolve(res)
      },
      fail: () => {
        reject()
      },
      complete: () => {
        wx.hideLoading()
      }
    })
  })
}
export default request
// utils/request.ts
// https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html
// const request = {
// 	get () {},
// 	post () {}
// }
// request.get()
// request.post()
// 继承中间类型,data声明为any
interface AnyResult extends WechatMiniprogram.RequestSuccessCallbackResult {
  data: any
}
// 从中间类型继承一个泛型接口,data声明为泛型
export interface SpecResult<T> extends AnyResult {
  data: T
}
// 声明业务数据类型
export interface IMyData {
  code: string
  msg: string
  data?: any
}

export default function request (config: WechatMiniprogram.RequestOption) {
	// 显示loading动画
	wx.showLoading({
		title: '加载中'
	})
	const { url = '', data = {}, method = 'GET', header = {}} = config
	// Promise<SpecResult<IMyData>> 声明resolve参数的数据类型
	return new Promise<SpecResult<IMyData>>((resolve, reject) => {
		wx.request({
			url: 'http://121.89.205.189:3000/api' + url,
			method,
			data,
			header,
			success: (res: SpecResult<IMyData>) => {
				resolve(res)
			},
			fail: () => {
				reject()
			},
			complete: () => {
				// 取消loading动画
				wx.hideLoading()
			}
		})
	})
}
// api/home.js
import request from '../utils/request'
// 获取轮播图的数据
export function getBannerListData () {
  return request({ url: '/banner/list' })
}
// 获取产品列表分页的数据
export function getProListData (params) {
  return request({
    url: '/pro/list',
    data: params || {}
  })
}
// pages/home/home.js
import { getBannerListData } from '../../api/home'
Page({
  data: {
    bannerList: []
  },

  onLoad () {
    getBannerListData().then(res => {
      console.log(res)
      this.setData({
        bannerList: res.data.data
      })
    })
  }
  
})
// pages/home/home.ts
import { getBannerList } from '../../api/home'
interface IBanner {
  bannerid: string
  img: string
  alt: string
  link: string
}
interface IData {
  data: {
    bannerList: IBanner[]
  }
}
Page<IData, any>({

  /**
   * 页面的初始数据
   */
  data: {
    bannerList: []
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad() {
    getBannerList().then(res => {
      console.log(res)
      this.setData({
        bannerList: res.data.data
      })
    })
  },
})

// home 页面下创建组件 components/banner/banner

{
  "navigationBarTitleText": "喜购-首页",
  "navigationBarBackgroundColor": "#f00",
  "enablePullDownRefresh": true,
  "usingComponents": {
    "my-banner": "./components/banner/banner"
  }
}
<!--pages/home/home.wxml-->
<my-banner list = "{{ bannerList }}"></my-banner>

// pages/home/components/banner/banner.js
Component({
  properties: {
    list: Array
  }
})

<!--pages/home/components/banner/banner.wxml-->
<swiper
  indicator-dots  
  indicator-color="#ffffff"
  indicator-active-color="#ff0000"
  autoplay
  circular
  >
  <swiper-item wx:for="{{ list }}" wx:key="bannerid">
    <image class="bannerImg" src="{{ item.img }}" />
  </swiper-item>
</swiper>
/* pages/home/components/banner/banner.wxss */
.bannerImg {
  width: 100%;
}

11.2.2 构建nav导航

// home 页面下创建组件 components/nav/Nav

// utils/nav.js
const navList = [
  { navid: 1, title: '嗨购超市', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/125678/35/5947/4868/5efbf28cEbf04a25a/e2bcc411170524f0.png' },
  { navid: 2, title: '数码电器', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/178015/31/13828/6862/60ec0c04Ee2fd63ac/ccf74d805a059a44.png' },
  { navid: 3, title: '嗨购服饰', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/41867/2/15966/7116/60ec0e0dE9f50d596/758babcb4f911bf4.png' },
  { navid: 4, title: '嗨购生鲜', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/177902/16/13776/5658/60ec0e71E801087f2/a0d5a68bf1461e6d.png' },
  { navid: 5, title: '嗨购到家', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/196472/7/12807/7127/60ec0ea3Efe11835b/37c65625d94cae75.png' },
  { navid: 6, title: '充值缴费', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/185733/21/13527/6648/60ec0f31E0fea3e0a/d86d463521140bb6.png' },
  { navid: 7, title: '9.9元拼', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/36069/14/16068/6465/60ec0f67E155f9488/595ff3e606a53f02.png' },
  { navid: 8, title: '领券', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/186080/16/13681/8175/60ec0fcdE032af6cf/c5acd2f8454c40e1.png' },
  { navid: 9, title: '领金贴', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/196711/35/12751/6996/60ec1000E21b5bab4/38077313cb9eac4b.png' },
  { navid: 10, title: 'plus会员', imgurl: 'https://m.360buyimg.com/mobilecms/s120x120_jfs/t1/37709/6/15279/6118/60ec1046E4b5592c6/a7d6b66354efb141.png' }
]

export default navList
// pages/home/components/nav.json
{
  "component": true,
  "usingComponents": {
    "van-grid": "@vant/weapp/grid/index",
    "van-grid-item": "@vant/weapp/grid-item/index"
  }
}
// pages/home/home.js
import navList from '../../utils/nav'
import { getBannerListData } from '../../api/home'
Page({
  data: {
    bannerList: [],
    navList
  },

  onLoad () {
    getBannerListData().then(res => {
      console.log(res)
      this.setData({
        bannerList: res.data.data
      })
    })
  }
  
})
// pages/home/home.json
{
  "navigationBarTitleText": "喜购-首页",
  "navigationBarBackgroundColor": "#f00",
  "enablePullDownRefresh": true,
  "usingComponents": {
    "my-banner": "./components/banner/banner",
    "my-nav": "./components/nav/nav"
  }
}
<!--pages/home/home.wxml-->
<my-banner list = "{{ bannerList }}"></my-banner>

<my-nav list = "{{ navList }}"></my-nav>

// pages/home/components/nav/nav.js
Component({
  properties: {
    list: Array
  }
})

<!--pages/home/components/nav/nav.wxml-->
<van-grid column-num="{{5}}">
  <van-grid-item wx:for="{{ list }}" wx:key="navid" icon="{{ item.imgurl }}" text="{{ item.title }}" />
</van-grid>

如果项目不出效果,建议清楚缓存之后查看效果

11.2.3 秒杀列表

11.2.4 产品列表展示

home 页面下创建组件 components/proList/ProList

// pages/home/home.js
import navList from '../../utils/nav'
import { getBannerListData, getProListData } from '../../api/home'
Page({
  data: {
    bannerList: [],
    navList,
    proList: []
  },

  onLoad () {
    getBannerListData().then(res => {
      console.log(res)
      this.setData({
        bannerList: res.data.data
      })
    })

    getProListData().then(res => {
      console.log(res.data.data)
      this.setData({
        proList: res.data.data
      })
    })
  }
  
})
{
  "navigationBarTitleText": "喜购-首页",
  "navigationBarBackgroundColor": "#f00",
  "enablePullDownRefresh": true,
  "usingComponents": {
    "my-banner": "./components/banner/banner",
    "my-nav": "./components/nav/nav",
    "my-pro-list": "./components/proList/proList"
  }
}
<!--pages/home/home.wxml-->
<my-banner list = "{{ bannerList }}"></my-banner>

<my-nav list = "{{ navList }}"></my-nav>

<my-pro-list list = "{{ proList }}"></my-pro-list>

// pages/home/components/proList/proList.js
Component({
  properties: {
    list: Array
  }
})

<!--pages/home/components/proList/proList.wxml-->
<view class="proList">
  <view class="proItem" wx:for="{{ list }}" wx:key="proid">
    <view class="itemImage">
      <image src="{{ item.img1 }}" mode=""/>
    </view>
    <view class="itemInfo">
      <view class="title ">{{ item.proname }}</view>
      <view class="price"> ¥{{ item.originprice }}</view>
    </view>
  </view>
</view>
/**app.wxss**/
page {
  background-color: #efefef;
}
/* pages/home/components/proList/proList.wxss */
.proList {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
}
.proList .proItem {
  width: 48%;
  height: 260px;
  background-color: #fff;
  border-radius: 10px;
  overflow: hidden;
  margin: 5px 1%;
}
.proList .proItem .itemImage {
  width: 100%;
}
.proList .proItem .itemImage image {
  width: 100%;
  height: 180px;
}
.proList .proItem .itemInfo {
  padding: 8px 5px;
}
.proList .proItem .itemInfo .title {
  font-weight: bold;
  display: -webkit-box;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.proList .proItem .itemInfo .price {
  color: #ff6666;
}

11.2.5 首页实现上拉加载效果

小程序自带一个上拉加载的实现的事件

分析接口文档,发现列表的数据,可以分页 http://121.89.205.189:3000/apidoc/#api-Pro-GetProList

只需要不停的变换 页码即可

// pages/home/home.js
import navList from '../../utils/nav'
import { getBannerListData, getProListData } from '../../api/home'
Page({
  data: {
    bannerList: [],
    navList,
    proList: [],
    count: 2
  },

  onLoad () {
    getBannerListData().then(res => {
      console.log(res)
      this.setData({
        bannerList: res.data.data
      })
    })

    getProListData().then(res => {
      console.log(res.data.data)
      this.setData({
        proList: res.data.data
      })
    })
  },

  onReachBottom () {
    getProListData({ count: this.data.count }).then(res => {
      if (res.data.data.length === 0) {
        // 没有数据了,提示用户
        // https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showToast.html
        wx.showToast({
          title: '没有更多数据了',
          icon: 'none'
        })
      } else {
        // 有下一页数据,实现数据的拼接、页码加1
        this.setData({
          proList: [...this.data.proList, ...res.data.data],
          count: this.data.count + 1
        })
      }
    })
  }
  
})

11.2.6下拉刷新

// pages/home/home.json

{
  "navigationBarTitleText": "喜购-首页",
  "navigationBarBackgroundColor": "#f00",
  "enablePullDownRefresh": true,
  "usingComponents": {
    "my-banner": "./components/banner/banner",
    "my-nav": "./components/nav/nav",
    "my-pro-list": "./components/proList/ProList"
  }
}
// pages/home/home.js
import navList from '../../utils/nav'
import { getBannerListData, getProListData } from '../../api/home'
Page({
  data: {
    bannerList: [],
    navList,
    proList: [],
    count: 2
  },

  onLoad () {
    getBannerListData().then(res => {
      console.log(res)
      this.setData({
        bannerList: res.data.data
      })
    })

    getProListData().then(res => {
      console.log(res.data.data)
      this.setData({
        proList: res.data.data
      })
    })
  },
  // 下拉刷新
  // 请求第一页数据 ===》  重置数据
  onPullDownRefresh () {
    getProListData().then(res => {
      console.log(res.data.data)
      this.setData({
        proList: res.data.data,
        count: 2
      })
      // 停止下拉的动画效果
      wx.stopPullDownRefresh()
    })
  },
  // 上拉加载
  onReachBottom () {
    getProListData({ count: this.data.count }).then(res => {
      if (res.data.data.length === 0) {
        // 没有数据了,提示用户
        // https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showToast.html
        wx.showToast({
          title: '没有更多数据了',
          icon: 'none'
        })
      } else {
        // 有下一页数据,实现数据的拼接、页码加1
        this.setData({
          proList: [...this.data.proList, ...res.data.data],
          count: this.data.count + 1
        })
      }
    })
  }
  
})

11.2.7 返回顶部

// pages/home/home.json

{
  "navigationBarTitleText": "喜购-首页",
  "navigationBarBackgroundColor": "#f00",
  "enablePullDownRefresh": true,
  "usingComponents": {
    "my-banner": "./components/banner/banner",
    "my-nav": "./components/nav/nav",
    "my-pro-list": "./components/proList/proList",
    "van-icon": "@vant/weapp/icon/index"
  }
}
<!--pages/home/home.wxml-->
<my-banner list = "{{ bannerList }}"></my-banner>

<my-nav list = "{{ navList }}"></my-nav>

<my-pro-list list = "{{ proList }}"></my-pro-list>

<view class="backTop" bind:tap="backTop" wx:if="{{ scrollTop > 300 }}">
  <van-icon name="arrow-up" />
</view>

// pages/home/home.js
import navList from '../../utils/nav'
import { getBannerListData, getProListData } from '../../api/home'
Page({
  data: {
    bannerList: [],
    navList,
    proList: [],
    count: 2,
    scrollTop: 0
  },

  onLoad () {
    getBannerListData().then(res => {
      console.log(res)
      this.setData({
        bannerList: res.data.data
      })
    })

    getProListData().then(res => {
      console.log(res.data.data)
      this.setData({
        proList: res.data.data
      })
    })
  },
  // 下拉刷新
  // 请求第一页数据 ===》  重置数据
  onPullDownRefresh () {
    getProListData().then(res => {
      console.log(res.data.data)
      this.setData({
        proList: res.data.data,
        count: 2
      })
      // 停止下拉的动画效果
      wx.stopPullDownRefresh()
    })
  },
  // 上拉加载
  onReachBottom () {
    getProListData({ count: this.data.count }).then(res => {
      if (res.data.data.length === 0) {
        // 没有数据了,提示用户
        // https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showToast.html
        wx.showToast({
          title: '没有更多数据了',
          icon: 'none'
        })
      } else {
        // 有下一页数据,实现数据的拼接、页码加1
        this.setData({
          proList: [...this.data.proList, ...res.data.data],
          count: this.data.count + 1
        })
      }
    })
  },

  onPageScroll ({ scrollTop }) {
    // console.log(scrollTop)
    this.setData({
      scrollTop
    })
  },

  backTop () {
    // https://developers.weixin.qq.com/miniprogram/dev/api/ui/scroll/wx.pageScrollTo.html
    wx.pageScrollTo({
      duration: 1000,
      scrollTop: 0
    })
  }
  
})
/* pages/home/home.wxss */
.backTop {
  position: fixed;
  right: 10px;
  bottom: 10px;
  width: 32px;
  height: 32px;
  background-color: #ccc;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}

11.2.8 自定义首页头部 - 作业

/// <reference path="./types/index.d.ts" />

interface IAppOption {
  globalData: {
    statusBarHeight: number,
    pixelRatio: number,
    userInfo?: WechatMiniprogram.UserInfo
  }
  userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback,
}
// app.ts
App<IAppOption>({
  globalData: {
    statusBarHeight: 20,
    pixelRatio: 1
  },
  onShow() {
    console.log('小程序显示')
  },
  onHide() {
    console.log('小程序隐藏')
  },
  onLaunch() {
    // 获取设备信息
    const res = wx.getSystemInfoSync()
    console.log(res)
    this.globalData.statusBarHeight = res.statusBarHeight
    this.globalData.pixelRatio = res.pixelRatio
    // 展示本地存储能力
    const logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)

    // 登录
    wx.login({
      success: res => {
        console.log(res.code)
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
      },
    })
  },
})
<!--pages/home/home.wxml-->
<!-- 自定义头部 -->
<view class="slider-bg" >

</view>
<view class="box" style="height: {{statusBarHeight }}px"></view>
<view class="top" style="top: {{statusBarHeight }}px">首页</view>
<view class="bottom" style="top: {{ 44 + statusBarHeight }}px">搜索框</view>
<view style="height: {{ statusBarHeight + 88}}px;"></view>
<!-- 轮播图 -->
<my-banner list="{{ bannerList }}"></my-banner>
<!-- nav导航 -->
<my-nav list="{{ navList }}"></my-nav>
<!-- 产品列表 -->
<pro-list list="{{ proList }}"></pro-list>

<view 
	class="backTop"
	bindtap="backTop"
	wx:if="{{ scrollTop > 300 }}"
>
	<van-icon name="arrow-up" size="24"/>
</view>

/* pages/home/home.wxss */
.backTop {
	position: fixed;
	bottom: 10px;
	right: 10px;
	width: 32px;
	height: 32px;
	border: 1rpx solid #ccc;
	background-color: #fff;
	border-radius: 50%;
	display: flex;
	justify-content: center;
	align-items: center;
}
.slider-bg {
	background-image: -webkit-gradient(linear,left bottom,left top,from(#f1503b),color-stop(100%,#c82519));
	background-image: -webkit-linear-gradient(bottom,#f1503b,#c82519 100%);
	background-image: linear-gradient(0deg,#f1503b,#c82519 100%);
	position: absolute;
	top: 0;
	left: -25%;
	height: 400rpx;
	width: 150%;
	border-bottom-left-radius: 100%;
	border-bottom-right-radius: 100%;
	z-index:-1;
	
}
.box {
	position: fixed;
	z-index: 99;
	top: 0;
	left: 0;
	width: 100%;
	background-color: #c82519;
}
.top {
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 88rpx;
	background-color: #c82519;
	z-index: 99;
}
.bottom {
	position: fixed;
	top: 88rpx;
	left: 0;
	width: 100%;
	height: 88rpx;
	background-color: #f1503b;
	z-index: 99;
}

11.3 点击列表进入产品的详情页面并且渲染

构建详情页面 "pages/detail/detail"

11.3.1 跳转页面

声明式跳转

编程式跳转

<!--pages/home/components/proList/proList.wxml-->
<view class="proList" wx:if="{{ type === 'grid'}}">
  <view class="proItem" wx:for="{{ list }}" wx:key="proid">
  <!-- 
    open-type 
      navigate wx.navigateTo    不能跳转tabbar页面 router.push() 
      redirect wx.redirectTo    不能跳转tabbar页面 router.replace()
      switchTab wx.switchTab    小程序跳转tabbar的专属方法
      reLaunch wx.reLaunch      关闭所有的打开的页面,打开下一个页面
      navigateBack wx.navigateBack   router.back()  router.go(-1)
      wx.navigateBack({delta: 3})    router.go(-3)
   -->
    <navigator url="/pages/detail/detail?proid={{item.proid}}">
      <view class="itemImage">
        <image src="{{ item.img1 }}" mode=""/>
      </view>
      <view class="itemInfo">
        <view class="title ">{{ item.proname }}</view>
        <view class="price"> ¥{{ item.originprice }}</view>
      </view>
    </navigator>
  </view>
</view>

<van-card
  wx:if="{{ type === 'column'}}"
  wx:for="{{ list }}"
  wx:key="proid"
  price="{{ item.originprice }}"
  title="{{ item.proname }}"
  bind:tap="toDetail"
  data-proid="{{item.proid}}"
  thumb="{{ item.img1 }}"
/>

<!-- 
  声明式跳转 a                router-link
  编程时跳转 window.location  router.push
 -->
// pages/home/components/proList/proList.js
Component({
  properties: {
    list: Array,
    type: String
  },
  methods: {
    toDetail (event) {
      console.log(event)
      wx.navigateTo({
        url: '/pages/detail/detail?proid=' + event.target.dataset.proid
      })
    }
  }
})

12.3.2 详情页面获取参数并且请求相关数据

当页面跳转到详情时,添加一个新的针对详情的编译模式

请求数据

// api/detail.js
import request from '../utils/request'

export function getProDetail (proid) {
	return request({
		url: '/pro/detail/' + proid
	})
}
// pages/detail/detail.js
import { getProDetail } from '../../api/detail'
Page({
  data: {
    proid: '',
    banners: [],
    proname: [],
    originprice: 0,
    brand: '',
    category: ''
  },
  onLoad(options) {
    console.log(options)
    getProDetail(options.proid).then(res => {
      console.log(res.data.data)
      const { proid, banners, proname, originprice, brand, category } = res.data.data
      this.setData({
        proid,
        banners: banners[0].split(','),
        proname,
        originprice,
        brand,
        category
      })
    })
  },

})

渲染数据

// pages/detail/detail.json

{
  "usingComponents": {
    "van-goods-action": "/miniprogram_npm/@vant/weapp/goods-action/index",
    "van-goods-action-icon": "/miniprogram_npm/@vant/weapp/goods-action-icon/index",
    "van-goods-action-button": "/miniprogram_npm/@vant/weapp/goods-action-button/index"
  }
}
<!--pages/detail/detail.wxml-->
<view style="position: relative;">
  <swiper 
    style="height: 300px;"
    indicator-dots
    current="{{ current }}"
    bind:change="changeSwiper"
    >
    <swiper-item style="display: flex; justify-content: center;align-items: center;" wx:for="{{banners}}" wx:key="*this">
      <image  src="{{ item }}" mode=""  bind:tap="previewImage"/>
    </swiper-item>
  </swiper>
  <view style="position: absolute; bottom: 20px;right: 0px;width: 60px;height: 30px;background-color: #ccc; border-radius: 15px 0 0 15px;text-align: center;line-height: 30px;">
   {{ current + 1 }}/ {{ banners.length}}
  </view>
</view>

<van-goods-action>
  <van-goods-action-icon icon="chat-o" text="客服" />
  <van-goods-action-icon icon="cart-o" text="购物车" />
  <van-goods-action-button
    text="加入购物车"
    type="warning"
  />
  <van-goods-action-button text="立即购买"  />
</van-goods-action>

12.3.3 详情图片预览

// pages/detail/detail.js
import { getProDetail } from '../../api/detail'
Page({
  data: {
    proid: '',
    banners: [],
    proname: [],
    originprice: 0,
    brand: '',
    category: '',
    current: 0
  },
  changeSwiper ({ detail: { current }}) { // event.detail.current
    this.setData({current})
  },
  previewImage () {
    wx.previewImage({
      urls: this.data.banners,
      current: this.data.banners[this.data.current]
    })
  },
  onLoad(options) {
    console.log(options)
    getProDetail(options.proid).then(res => {
      console.log(res.data.data)
      const { proid, banners, proname, originprice, brand, category } = res.data.data
      this.setData({
        proid,
        banners: banners[0].split(','),
        proname,
        originprice,
        brand,
        category
      })
    })
  },

})

12.3.4 自定义头部

// pages/detail/detail.json

{
  "usingComponents": {
    "van-goods-action": "@vant/weapp/goods-action/index",
    "van-goods-action-icon": "@vant/weapp/goods-action-icon/index",
    "van-goods-action-button": "@vant/weapp/goods-action-button/index",
    "van-icon": "@vant/weapp/icon/index"
  },
  "navigationStyle": "custom"
}

11.4 登录

{
  "usingComponents": {
    "van-field": "@vant/weapp/field/index",
    "van-button": "@vant/weapp/button/index"
  }
}
<!--packageUser/pages/userLogin/login.wxml-->
<van-field
  value="{{ loginname }}"
  placeholder="请输入用户名"
  bind:input="changeLoginName"
/>
<van-field
  value="{{ password }}"
  placeholder="请输入密码"
  bind:input="changePassword"
/>

<van-button disabled="{{ flag }}" block color="linear-gradient(to right, #4bb0ff, #6149f6)">
  登录
</van-button>


// packageUser/pages/userLogin/login.js
Page({
  data: {
    loginname: '18813006814',
    password: 'Ty2206',
    flag: true
  },
  changeLoginName (event) {
    this.setData({
      loginname: event.detail
    })
    this.checkFlag()
  },
  changePassword (event) {
    this.setData({
      password: event.detail
    })
    this.checkFlag()
  },
  checkFlag () {
    // 正则校验
    if (this.data.loginname !== '' && this.data.password !== '') {
      this.setData({
        flag: false
      })
    } else {
      this.setData({
        flag: true
      })
    }
  },
  onLoad () {
    this.checkFlag()
  }
})
// api/user.js
import request from '../utils/request'

export function loginFn (parmas) {
	return request({
    url: '/user/login',
    method: 'POST',
    data: parmas
	})
}
// packageUser/pages/userLogin/login.js
import { loginFn } from '../../../api/user'
Page({
  data: {
    loginname: '18813006814',
    password: 'Ty2206',
    flag: true
  },
  changeLoginName (event) {
    this.setData({
      loginname: event.detail
    })
    this.checkFlag()
  },
  changePassword (event) {
    this.setData({
      password: event.detail
    })
    this.checkFlag()
  },
  checkFlag () {
    // 正则校验
    if (this.data.loginname !== '' && this.data.password !== '') {
      this.setData({
        flag: false
      })
    } else {
      this.setData({
        flag: true
      })
    }
  },
  onLoad () {
    this.checkFlag()
  },
  login () {
    loginFn({
      loginname: this.data.loginname,
      password: this.data.password
    }).then(res => {
      console.log(res.data)
      if (res.data.code === '10010') {
        wx.showToast({
          title: '用户名不存在',
          icon: 'none'
        })
      } else if (res.data.code === '10011') {
        wx.showToast({
          title: '密码错误',
          icon: 'none'
        })
      } else {
        wx.showToast({
          title: '登录成功',
          icon: 'none'
        })
        // 保存登录凭证到本地
        wx.setStorageSync('token', res.data.data.token)
        wx.setStorageSync('userid',res.data.data.userid)
        // 返回上一页
        wx.navigateBack()
      }
    })
  }
})

11.5 加入购物车

首先前端自我校验登录状态,如果已登录,调用加入购物车的接口,如果未登录,直接跳转至登录页面

<!--pages/detail/detail.wxml-->
<view style="position: relative;">
  <swiper 
    style="height: 300px;"
    indicator-dots
    current="{{ current }}"
    bind:change="changeSwiper"
    >
    <swiper-item style="display: flex; justify-content: center;align-items: center;" wx:for="{{banners}}" wx:key="*this">
      <image  src="{{ item }}" mode=""  bind:tap="previewImage"/>
    </swiper-item>
  </swiper>
  <view style="position: absolute; bottom: 20px;right: 0px;width: 60px;height: 30px;background-color: #ccc; border-radius: 15px 0 0 15px;text-align: center;line-height: 30px;">
   {{ current + 1 }}/ {{ banners.length}}
  </view>
</view>
<view>  
  <van-tag type="success">{{ category }}</van-tag>
  <van-tag type="danger">{{ brand }}</van-tag>
</view>
<view>{{ proname }}</view>
<view>¥{{ originprice }}</view>

<!-- 
  关于此商品的评论展示
  关于此商品带来的推荐商品展示
 -->
<van-goods-action>
  <van-goods-action-icon icon="chat-o" text="客服" />
  <van-goods-action-icon icon="cart-o" text="购物车" />
  <van-goods-action-button
    text="加入购物车"
    type="warning"
    bind:tap="addCart"
  />
  <van-goods-action-button text="立即购买"  />
</van-goods-action>

// api/cart.js
import request from '../utils/request'

export function addCartFn (params) {
  return request({
    url: '/cart/add',
    method: 'POST',
    data: params,
    header: {
      token: wx.getStorageSync('token') || ''
    }
  })
}
// pages/detail/detail.js
import { getProDetail } from '../../api/detail'
import { addCartFn } from '../../api/cart'
Page({
  data: {
    proid: '',
    banners: [],
    proname: [],
    originprice: 0,
    brand: '',
    category: '',
    current: 0
  },
  changeSwiper ({ detail: { current }}) { // event.detail.current
    this.setData({current})
  },
  previewImage () {
    wx.previewImage({
      urls: this.data.banners,
      current: this.data.banners[this.data.current]
    })
  },
  onLoad(options) {
    console.log(options)
    getProDetail(options.proid).then(res => {
      console.log(res.data.data)
      const { proid, banners, proname, originprice, brand, category } = res.data.data
      this.setData({
        proid,
        banners: banners[0].split(','),
        proname,
        originprice,
        brand,
        category
      })
    })
  },
  addCart () {
    addCartFn({
      userid: wx.getStorageSync('userid'),
      proid: this.data.proid,
      num: 1
    }).then(() => {
      wx.showToast({
        title: '加入购物车成功',
      })
    })
  }
})

渲染购物车页面,需要注意渲染的是哪一个用户的购物车数据 --- 登录状态

tabBar页面它会默认带有缓存效果,只有第一次打开时会销毁和创建页面,其余时刻不会销毁,所以当要保证数据的实时更新,那么就不要使用 onLoad 函数请求数据,可以使用 小程序页面的 onShow 函数请求数据,这个是实时的

11.6 渲染购物车数据