关闭小程序开发工具-调试器里的警告信息
- 根目录-project.config.json 里面的 settings-checkSiteMap 改为 false
使当前小程序不被微信索引
- 根目录-sitemap.json 里面的 "action": "disallow",
新增 pages 页面
- 在 app.json - pages 中新增页面的存放路径,自动创建
更改首页
- 小程序会把排在最上面的页面当作首页进行渲染
标签
div -- view
span -- text
img -- image
a -- navigator <navigator url="/x/x"></navogator>
样式
- rpx 单位:所以大小的屏幕都是 750rpx;ipone6 上面:1rpx = 0.5px
组件
-
view
-
scroll-view:
<scroll-view scoll-y> <view><view> </scroll-view>默认为 x 轴滚动- 必须加高度或者宽度
- 滚动的视图区域
- 常用来实现滚动列表
-
swiper、swiper-item
- 轮播图的容器和轮播项
- 属性:
indicator-dots类型:boolean 默认值: false 作用:是否显示小圆点indicator-colorcolor rgba(0,0,0,.3) 小圆点的颜色indicator-active-color#000 当前选中的小圆点的颜色autoplay(可以没有值) boolean false 是否自动切换intervalnumber 5000ms 切换的时间间隔circular(可以没有值) boolean false 是否采用衔接滑动
duration一张图从开始到结束耗时
-
text
selectable属性(可以没有值) 支持用户长按选中其中的文字 -
rich-text 富文本组件,支持把 HTML 字符串渲染为 WXML 结构
nodes属性,nodes="<h1 style='color:red'>介绍</h1>"- 常应用于商品的详情介绍页(从服务器返回的是一串包含 html 的字符串时)
-
button
type属性:primary主色调warn警告size属性defaultminiplain属性(可以没有值) true 为镂空的按钮
-
image 默认宽度为 300px,高度 240px
mode属性- scaleToFill(默认),缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸填满
- aspectFit,缩放模式,保持纵横比缩放
- aspectFill,缩放模式,但是超出 img 空间的会被截取
- widthFix
- heightFix
数据绑定
- 定义: 入口文件(index.js) -- page 函数 -- data 中直接定义
- 渲染: 插值表达式。如果绑定属性,不同于 Vue,没有 v-bind,还是用插值语法
事件绑定
- 常用事件
- tap 绑定方式: bindtap 或者 bind:tap 描述:手指触摸后马上离开,类似 click
- input bindinput、bind:input 输入事件
- change bindchange、bind:change 状态改变
- sync bindSync、bind:sync 自定义事件(sync 是自定义的名字)
- 事件对象的属性
- type 事件类型
- timeStamp 页面打开到触发事件所用毫秒
- target 触发事件的组件的一些属性值集合
- currentTarget 当前组件的属性值集合
- detail 额外信息
- touches 当前停留在屏幕中的触摸点信息的数组
- changedTouches 当前变化的触摸点信息的数组
- 事件传参
<button bindtap="changeCount" data-number="{{count}}">传参</button>使用data-参数,插值内为参数的值- 获取传入的值:
e.target.dataset.number - input 获取输入的值 bandinput 值:
e.detail.value
条件渲染
- 使用
wx:if={{true / false}}来判断是否需要渲染该代码块,可以和wx:elif和wx:else配合使用 - 当有多个组件需要判断是否渲染,使用
block标签包裹,block不会被渲染。当然可以使用view包裹,但是外面会多一个view,block的作用仅此而已 - 使用
hidden也可以条件判断渲染,条件为 true 隐藏,值只能为 true 和 false - 两者区别:
wx:if用动态创建和移除元素的方式控制,hidden只是将 display 的 none / block 进行切换- 频繁切换并控制条件简单时,使用
hidden,反之用wx:if
列表渲染(循环)
- 使用
wx:for。 绑定的 key 值直接用字符串,当列表内为对象并使用对象的 id 作为 key 时,直接写wx:key="id",他会直接到 item 里去找 id(莫名其妙的设定,不应该自己写 item.id 么?)
<view wx:for="{{array}}" wx:key="index">
{{index}},{{item}}
</view>
- 可以自定义 index 和 item 的别名
<view wx:for="{{array}}" wx:for-index="myIndex" wx:for-item="myItem" wx:key="myIndex">
{{index}},{{item}}
</view>
全局配置文件
"window": {
// main 下拉页面 背景颜色 仅支持 dark、light
"backgroundTextStyle": "light",
// 顶部导航栏背景颜色
"navigationBarBackgroundColor": "#fff",
// 标题栏内容文字
"navigationBarTitleText": "Weixin",
// 导航栏标题颜色,仅支持 black、white
"navigationBarTextStyle": "black",
// 窗口的背景色
"backgroundColor":"#efefef",
// 是否全局开启下拉更新,当配置在页面中 json 时,只影响当前页面
"enablePullDownRefresh":"fasle",
// 页面上拉触底事件触发时距离底部距离,单位为 px
"onReachBottomDistance":50
},
tabBar 全局配置 (路由导航)
- 顶部 tabBar:当渲染顶部 tabBar 时,不显示 icon,不显示文本
- tabBar 中只能配置最少 2 个,最多 5 个 tab 页签
- tabBar 有 6 个组成部分
- backgroundColor 背景颜色
- selectedIconPath 底部 tabBar 图标
- selectedColor 选中的页签的文字颜色
- borderStyle 底部 tabBar 上边缘样式,或者 顶部 tabBar 下边缘样式
- iconPath 未选择的页签 icon
- color 未选择的页签文字h颜色
- tabBar 配置项
"tabBar": {
"position": "bottom", //tabBar 位置
"borderStyle": "black", // 仅支持 white / black
"color": "",
"selectedColor": "",
"backgroundColor": "",
// 页签列表
"list": [
{
"pagePath":"",// 页面路径,页面必须事先在 pages 中定义
"text":"", // tab 上显示的文字
"iconPath": "",
"selectedIconPath":""
},
]
}
- 注意点: tabBar 的页面在 pages 中配置时,必须全放在最上面
网络数据请求
- 小程序中网络请求的限制
- 只能请求 HTTPS 类型的接口
- 必须将接口的域名添加到信任列表中
登录后台 - 开发管理 - 开发设置 - 服务器域名开始配置 - request 合法域名 - 域名不能使用 IP 地址
- 域名必须经过 ICP 备案
- 服务器域名一个月内最多可申请 5 次修改
- get 请求
wx.request({
url: 'https://www.escook.cn/api/get',
method: 'GET',
data: { // 发送到服务器的数据
name: 'zs',
age: 18
},
success: request => console.log(request.data)
})
- 在页面刚加载时请求数据:将请求数据的函数在 onLoad: function(){} 中调用
- 临时跳过 request 合法域名校验:
开发者工具 - 详情 - 本地设置 - 不检验合法域名... - 打勾,此项仅在开发阶段使用
前端路由
- 声明式导航:
<navigator>导航组件- 通过
<navigator>跳转到 tabBar 页面<navigator url="/pages/news/news" open-type="switchTab">跳转</navgator>- url 必须以根目录 '/' 开头,open-type="switchTab" 必带
- 通过
<navigator>跳转到非 tabBar 页面<navigator url="/pages/news/news" open-type="navigate">跳转</navgator>- url 必须以根目录 '/' 开头,open-type="navigate" 可以省略不写
- 后退导航:
<navigator delta="1" open-type="navigateBack">后退</navgator>- open-type="navigateBack" 必带,delta 的值为后退的层级,默认为 1,如果为 1,可以省略
- 传参:
<navigator url="/pages/news/news?name=zs&age=20" open-type="navigate">跳转</navgator>
- 通过
- 编程式导航:
- 调用
wx.switchTab({obj})方法,跳转到 tabBar 页面- url 必选 需要跳转的路径,必须以 / 根路径开始
- success 非必选 成功的回调
- fail 非必选 失败的回调
- complete 非必选 跳转结束的回调,成功失败都会执行
- 调用
wx.navigateTo({obj}),跳转到 非 tabBar 页面- 属性同上
- 调用
wx.navigateBack({obj}),实现后退路由- delta 非必选 回退的层级,默认为一
- 同上
- 调用
- 在 onLoad 函数中可以直接接收路由参数(options),我们可以手动赋值到 data 上面
Page({
data: {
query: {}
},
onLoad function(options) {
this.setData({
query:options
})
}
})
生命周期
- 应用生命周期(定义在 app.js 里面)
- 前台:应用处于引动设备的屏幕里;后台:应用未处于当前引动设备的屏幕里,但是后台未关闭
- onLaunch: 当小程序初始化完成时调用,全局只会调用一次
- onShow: 当小程序启动,或从后台今日前台时调用
- onHide: 当小程序从前台今日后台时触发
- 页面生命周期
- onLoad 第一次进入页面(准备数据),只会调用一次
- onShow 页面显示 / 切入前台时触发
- onReady 初次渲染完成时,只会调用一次,如 wx.setNavigationBarTitle(修改页面标题)可在此阶段执行
- onHide 页面隐藏 / 切入后台时,如任意路由切换到其他页面,小程序切入后台
- onUnload 页面卸载时,如 wx.redirectTo 或 wx.navigateBack 到其他页面
- 组件的生命周期
- created 组件实例第一次创建
- attached 组件实例进入页面节点树时 相当于 mounted
- ready 组件在视图层完成布局
- moved 组件实例移动到节点树另一个位置
- detached 组件实例从节点树被移除 相当于 destroyed
- error 组件方法抛出错误时
- 组件的生命周期使用推荐写在 lifetimes 配置项中,优先级最高
Component({
lifetimes:{
attached(){},
detached(){}
}
})
- 在组件中可以访问到的页面生命周期
- show 组件所在页面被展示时
- hide 组件所在页面被隐藏时
- resize 组件所在页面尺寸大小变化时
- 在组件中使用所在页面的生命周期函数,必须定义在
pageLifetimes节点中
Component({
pageLifetimes:{
show:function(){},
hide:function(){},
resize:function(size){}
}
})
wxs 模块
- 类似 JS 的 ES5 以下写法
- 具体作用大概是说小程序的框架分为应用层和逻辑层,比如需要完成这样一个需求:在设备屏幕上有两个点 A 和 B,用户按住其中一个并移动,另一个也会跟着移动。代码逻辑大概就是给 A 点绑定 touchmove 事件,并将用户手指移动的坐标赋值给 A,然后再讲 A 的坐标赋值给 B。这样数据会多次经过应用层和逻辑层,这样频繁的交互势必会造成卡顿。这样分层的目的是管控,开发者的代码只能运行在逻辑层。而 wxs 的代码可以写在视图层,这样就减少了通信的次数,让事件在视图层相应。
- 基础用法:
// wxml 文件中
// 内嵌定义
<wxs moudle="x">
moudle.export y = function (options){...}
</wxs>
// 使用
<view>{{x.y(数据)}}</view>
// 使用外联的 wxs 模块
<wxs src="相对路径" module="x"></wxs>
<view>{{x.y(数据)}}</view>
全局 css 对组件的影响
- 全局使用类选择器定义的样式不影响组件,而标签、属性和 id 选择器影响
- 当需要在外界控制组件内的样式时:
// 在组件的 .js 文件中新增配置
Component({
options:{
styleIoslation:'isolated' //默认值,不配置默认为 true,表示互不影响
/* 其他可选值: apply-shared 默认为 false,表示页面会影响组件,但组件不会影响页面
shared 默认为false,表示互相都会影响 */
}
})
// 或在组件的 .json 文件中新增配置
{
"styleIsonlation":"isolated"
}
小程序的弹窗
wx.showToast({title:'弹窗显示的文本',icon:'弹窗显示的图标'})相当于 window.alert
组件的外部属性
- 接收:
properties:{key: value}
Component({
properties:{
n:{
type: Number,
value: 0 // 默认值
},
n: Number //不需要默认值时的简写方式
}
})
- 小程序中的外部属性可读可写
插槽
- 使用多个插槽时需要配置,定义和使用参照 vue 具名插槽
Component({
options: {
multipleSlots:true
}
})
小程序中的 watch
observers:{}
Component({
observers:{
'监听属性1,监听属性2':function(newValue1,newValue2){
...
}
}
})
- 监听的任何一个值发生变化都会调用 watch
- 监听对象,可以单独写,如果需要监听所有但是值太多,直接用通配符,得到一个包含新值的对象
Component({
observers:{
'obj.**':function(obj){
...
}
}
})
粗暴且通用的纯数据字段设置(适合没时间研究正则的同学)
- 纯数据字段是指只用于逻辑代码但不会被页面渲染的字段,可以提升性能
Component({
options:{
pureDataPattern:/^_/
},
data:{
a:true, //普通数据
_b:true // 只要前面带上下划线就会变成纯数据字段
}
})
小程序中的 $emit
this.triggerEvent('自定义事件名',{key: value})自定义事件需要父组件传递- 父组件通过
e.detail.key获取数据 - 父组件也可以通过获取子组件的实例来操作子组件的数据
// wxml
<my-component class="component" bindSync="syncCount"></my-component>
// 父组件 js
getComponent(){
const child = this.selectComponent('.component')
}
小程序中的 mixins
behaviors- 定义
module.exports = Behavior({
// 属性节点
properties:{},
// 私有数据节点
data:{user: 'zs'},
// 事件处理函数和自定义方法
methods:{},
// 引入其他的属性节点
behaviors:[],
// 可以直接使用组件的生命周期函数
// 其他节点
...
})
- 导入
const myBehavior = require('相对路径') - 使用
Component({
behaviors:[myBrhavior]
})
小程序中使用 CSS 自定义变量 类似于 scss
- 全局定义:
page { --color: red; } - 使用:
div { background-color: var(--color) }
使小程序的 API Promise化
yarn add miniprogram-api-promise@1.0.4 -s- 小程序中安装第三方包后,必须执行构建 npm(推荐先删除 miniprogram_npm 目录再构建)
// app.js
import { promisifyAll } from 'miniprogram-api-promise'
const wxp = wx.p = {}
promisifyAll(wx, wxp)
小程序全局数据共享 mobx
mobx-miniprogram用来创建 Store 实例对象mobx-miniprogram-bindings用来把 Store 中的共享数据或方法,绑定到组件或页面中yarn add mobx-miniprogram@4.13.2 mobx-miniprogram-bindings@1.2.1- 初始化 store
/store/store.js
import { observable } from 'mobx-miniprogram'
export const store = observable({
// 数据
n1: 1,
n2: 2,
// 方法
update1: action(fn1(step) { return this.n1 += step }),
update2: action(fn2() {}),
// computed
get sum() {
return this.n1 + this.n2
}
})
- 页面中数据绑定
/pages/index/index.js
import { creatStoreBindings } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Page({
onLoad:function() {
this.storeBindings = createStoreBinds(this,{
store, // 数据源
fields: ['n1','n2'], // 数据
actions:['update1'] // 方法
})
},
onUnload: function () { // 页面卸载同时卸载数据
this.stroeBindings.destroyStoreBindings()
}
})
- 组件中数据绑定
/components/myComponent/myComponent.js
import { storeBindingBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../store/store'
Component({
behaviors:[storeBindingBehavior], // 通过 storeBindingBehavior 自动绑定
storeBindings:{
store, // 数据源
fields: {// 数据
n1: 'n1',
n2: 'n2'
}
actions: { // 方法
update1: 'update1'
}
},
})
分包
// app.json
"pages":[ // 主包所有页面
"pages/index",
"pages/logs"
],
"subpackages":[ // 通过 subpackages 节点,声明分包的结构
{
"root":"packageA", // 第一个分包的根目录
"pages":[ // 当前分包下,所有页面的相对路径
"pages/cat",
"pages/dog"
]
},{
"root":"packageB", // 第二个分包的根目录
"name":"pack2", // 分包的别名
"pages":[ // 当前分包下,所有页面的相对路径
"pages/apple",
"pages/banana"
]
}
]
- 分包预下载(同一个分包中的页面享有共同的预下载大小限额 2M)
// app.json
...
"preloadRule":{
"pages/personal/personnal":{ // 进入哪个页面时要进行预下载
"packages":[
"packageB" // 要预下载哪个页面
],
"network":"all" // 使用什么网络 可选值: all / wifi
}
}
自定义 tabBar(我试了,会造成页面严重闪烁)
- 项目根目录/custom-tab-bar - 创建 component,并命名为 index
- app.json - "tabBar" 配置项 - 添加 "custom":true,
HbiuilderX 搭配 uni-app
- HbiuilderX 官网下载地址
https://dcloud.io/hbuilderx.html - 安装 sass/scss 编译插件。地址:
https://ext.dcloud.net.cn/plugin?id=2046 - 打开 HbiuilderX 创建
uni-ui项目,创建完成后打开 根目录/uni_moudules/manifest.json 文件中找到微信小程序配置,填入 appID - 点击 HbiuilderX 的 工具 - 设置 - 运行设置,配置微信开发者工具路径
- 打开 微信开发者工具,点击 设置 - 安全设置,将端口号打开
- HbiuilderX 中点击运行 - 运行到小程序模拟器 - 微信开发者工具,会自动用开发者工具打开当前项目进行调试
git 管理
- 根目录下创建
.gitignore文件,忽略/node_modules和/unpackage/dist - 在
unpackage文件夹下创建.getkeep文件用来占位,防止 git 忽略unpackage文件夹
项目笔记
创建 tabBar 分支,制作自定义 tabBar
- git checkout -b tabbar
- 查看当前所有分支
git branch - 创建 首页(home)、分类(cate)、购物车(cart)、我的(my)四个 tabBar 页面
- pages.json 中 配置 tabBar
- 第一次提交 tabbar 分支:
git push -u origin tabbar - 将 tabbar 合并到本地的 master 分支:
git checkout mastergit merge tabbar - 删除本地的 tabbar 分支:
git branch -d tabbar - 再次推送主分支:
git push
配置网络请求
- 小程序不支持 axios,原生的
wx.request()功能单一,不支持拦截器等功能 - uni-app 中使用
@escook/request-miniprogram发起网络数据请求npmjs.com/package/@escook/request-miniprogram - 根目录初始化:
yarn init -y - 安装
- 导入:
import {$http} from '@escook/request-miniprogram' - 挂载到 wx:
wx.$http = $http - 使用 uni-app 时,挂载到
uni.$http = $http - 拦截器
$http.beforeRequest = function(options) {
uni.showLoading({
title:'拼命加载中...'
})
}
$http.afterRequest = function() {
uni.hideLoading()
}
- 配置请求的根路径:
$http.baseUrl = '...',不嫌麻烦可以不配置,就是每次请求都要把路径写全 - 请求发出后的判断代码
if(result.meta.status !== 200) {
// uni.showToast 请求失败的弹窗
return uni.showToast({
title:"数据请求失败!", // 弹窗标题
duration:1500, // 弹窗存在事件
icon:'none' // 弹窗的图标
})
}
HbiuilderX 快速生成轮播图快捷键
usw
uni 弹窗
- 模态框提示:
uni.showToast({title: ' ',mask(遮罩层):true, duration: 1500, icon: 'none'})无返回值 - confirm 弹窗:
uni.showModal(titile: '提示',content: '确定要推出吗')返回成功和失败的回调
配置分包
- 本次项目中,将 tabBar 相关的四个页面放在主包,其他所有详情,列表页放在分包
- 在项目根目录中,创建分包根目录,命名为
subpkg - app.json 中,与 page 节点平级:
"subPackages":[
{
"root":"subpkg",
"pages":[]
}
]
- 在 subpkg 目录中创建 goods_detail 分包
改造轮播图点击跳转到指定商品详情页
- 将 swiper-item 下的 view 标签改为 navigator 标签
- 动态绑定 url
/subskg/goods_detail/goods_detail - id传参:
'/subskg/goods_detail/goods_detail?goods_id='+item.goods_id
滑动页面
- 调用
uni.getSystemInfoSync()可以获取当前设备信息- brand 手机品牌
- model 手机型号
- screenWidth 屏幕宽度
- screenHeight 屏幕高度 ....
- 我没用这个,用的 vh 和 vw 单位,暂时没出问题
此项目手机调试报错:message: "request:fail net::ERR_CERT_DATE_INVALID"
- message: “request:fail 对应的服务器证书无效。” 需要替换有效证书
- 接口问题,网上找的接口,这个没有办法解决
关于 scroll-view 标签的小技巧
- scroll 标签用于实现滑动效果,他的属性:
scroll-top="0",可以实现每次在切换视图的时候将初始滑动的位置调整为顶部 0 像素 - 当需求有多级标签切换,动态绑定 scroll-top 的值,每次切换都手动将其调整为 0
- 这里有一个坑,就是每次给 scroll-top 赋的值如果是一样的话,则不生效
- 所以这里只能使其值在 0.1 和 0 之间切换:
this.scrollTop === 0 ? 0.1 : 0
搜索功能
- component 目录右键点击创建组件 my-search
- 最新版本的 uni-app 在 uni-modules 里有基本库,包含了 uni-search-bar 组件,可以直接用
- 用完发现并没有自带联想功能
- 修改 uni 内置组件的样式:
/deep/ .uni-searchbar {
border: 1px solid red;
}
- 自定义 search
<template>
<view>
<view class="my-search-container" :style="{'background-color':backgroundColor}">
<view class="my-search-box" :style="{'border-radius': radius}">
<uni-icons type="search" :size="18"></uni-icons>
<text class="placeholder">搜索</text>
</view>
</view>
</view>
</template>
<script>
export default {
name:"my-search",
props: {
backgroundColor: {
type: String,
default: '#c00000'
},
radius: {
type: String,
default: '18px'
}
},
data() {
return {
};
}
}
</script>
<style lang="scss">
.my-search-container {
height: 50px;
display: flex;
align-items: center;
padding: 0 10px;
.my-search-box {
height: 36px;
background-color: #fff;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
.placeholder {
font-size: 15px;
margin-left: 5px;
}
}
}
</style>
- 设置页面的吸顶效果,
.search-box {
position: stucky;
top: 0;
// 提高层级,防止被轮播图覆盖
z-index: 999
}
- 搜索页面防抖处理
- 定义 timer 用来存放定时器
timer: null,keyWords 变量用来存放用户输入
searchInput(e) { clearTimeout(this.timer) this.timer = setTimeout(()=> { this.keyWords = e // this.getSearchList() },500) } - 定义 timer 用来存放定时器
- 匹配搜索关键字
...
data(){
return {
searchResult: []
}
}
...
async getSearchList() {
// 判断搜索关键词是否为空
// console.log(this.keyWords.trim().length)
if(this.keyWords.trim().length === 0) {
this.searchResult = []
return
}
const result = await uni.$http.get('https://api-ugo-web.itheima.net/api/publv1/goods/qsearch',{query : this.keyWords})
if(result.data.meta.status !== 200) return uni.$uniShowMessage()
this.searchResult = result.data.message
}
uni 本地存储
- uni.setStorageSync('存储的key名',JSON.stringify(数据))
- JSON.parse(uni.getStorageSync('key名') || '[]')
- uni.removeStorageSync('指定删除的key名')
- uni.clearStorageSync() // 全清空
uni confirm 提示窗
clear() {
uni.showModal({
title: '提示',
content: '确定要清空吗?',
cancleText: '取消',
confirmText:'确认',
showCancel:true, // 是否显示默认按钮,默认为 true
success: (result) => {
if(result.confirm){
this.history = []
uni.removeStorageSync('searchKeyWords')
}else {
return
}
}
})
}
uni 单管道符
- {{item.goods_price | toFixed}} 意思前面的数据通过后面的函数处理之后再展示
- toFixed 函数必须声明在 filters 配置项里,filters 配置项和 data 等配置项同级
实现上拉加载
上拉触底
- 监听页面的上拉触底事件:
onReachBottom() - 请求数据时的 loading 效果(uni-app 封装了这个功能)
- 在请求发起前调用:
wx.showLoading({title: '拼命加载中'}),加载完成后不会自动关闭 - 在请求结束后的回调中调用:
wx.hideLoading(),关闭加载动画
- 在请求发起前调用:
- 打开项目根目录中的 pages.json 配置文件,为 subPackages 分包中的 goods_list 页面 配置上拉触底的距离:
{
"path" : "goods_list/goods_list",
"style": {
"navigationBarTitleText": "",
"onReachBottomDistance": 150,
"enablePullDownRefresh": false
}
}
- 在 goods_list 页面中,和 methods 节点平级,声明 onReachBottom 事件处理函数,用 来监听页面的上拉触底行为:
// 上拉触底事件
onReachBottom() {
// 让页面自增
this.queryParam.pagenum += 1
// 重新获取列表数据
this.getGoodsList()
}
- 改造 methods 中的 getGoodsList 函数,当列表数据请求成功之后,进行新旧数据的拼接处 理:
async getGoodsList() {
const result = await uni.$http.get('https://api-ugo-web.itheima.net/api/public/v1/goods/search', this.queryParam)
if (result.data.meta.status !== 200) return uni.$uniShowMessage()
// 从直接赋值改为拼接数据
this.goodsList = [...this.goodsList,...result.data.message.goods]
this.total = result.data.message.total
}
- 添加节流阀
- 判断还有无数据
- 如果:当前的页码值 * 每页显示多少条数据 >= 总数条数 也就是:pagenum * pagesize >= total
- onReachBottom 函数里:
if(this.queryParam.pagesize * this.queryParam.pagenum >= this.total) return
下拉刷新
- 监听页面的下拉刷新事件:
onPullDownRefresh() - 真机测试中,不能自动关闭下拉的动画
- 在
onPullDownRefresh()函数执行的最后调用wx.stopPullDownRefresh()
- 在
- 配置页面开启下拉刷新 enableOnPullDownRefresh
- 监听下拉刷新事件 onpullDownRefresh
onPullDownRefresh() {
// 重置关键数据
this.queryParam.pagenum = 1
this.total = 0
this.isLoading = false
this.goodsList = []
// 重新发起请求,并传入关闭的回调,在合适的时候调用
this.getGoodsList(()=>uni.stopPullDownRefresh())
}
- 改造 getGoddsList 函数,调用关闭回调
uni 轮播图片预览
- 轮播图片绑定 click 事件,将图片 index 传递出去
<swiper circular="true" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item v-for="(item,index) in goods_detail.pics" :key="index">
<image :src="item.pics_big" @click="preview(index)"></image>
</swiper-item>
</swiper>
- 事件内调用
uni.previewImage({})
preview(index){
uni.previewImage({
// 预览时,默认显示图片的索引
current: index,
// 所有团 url 地址的数组
urls: this.goods_detail.pics.map(item => item.pics_big)
})
}
富文本渲染 商品详情页面
- rich-text 富文本组件,支持把 HTML 字符串渲染为 WXML 结构
nodes属性,nodes="<h1 style='color:red'>介绍</h1>"- 常应用于商品的详情介绍页(从服务器返回的是一串包含 html 的字符串时)
- 在页面结构中,使用 rich-text 组件,将带有 HTML 标签的内容,渲染为小程序的页面结构:
<rich-text :nodes="goods_info.goods_introduce"></rich-text>
- 解决图片底部 空白间隙 的问题,并将 webp 格式替换为 jpg,解决 .webp 格式图片在 ios 设备上无法正常显示的问题:
async getGoodsDetail(goods_id) {
const {data: result} = await uni.$http.get('https://api-ugo-web.itheima.net/api/public/v1/goods/detail',{goods_id:goods_id})
if(result.meta.status !== 200) return uni.$uniShowMessage()
// 添加行内样式,消除富文本渲染的图片底部的空白间隙问题
result.message.goods_introduce = result.message.goods_introduce.replace(/<img/g,'<img style="display:block;"').replace(/webp/g,'jpg')
this.goods_detail = result.message
},
- 价格、名称等闪烁问题:页面刷新时,价格先显示的是 undefined,因为在商品信息请求完成之前,goods_detail 里的 price 为 undefined,应该用 v-if 判断一下商品信息是否存在再显示,比如判断商品名称是否存在
uni 使用 vuex 实现状态管理
- 创建 store
// /store/store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
// 数据挂载处
}
})
export default store
- 注册 store
// /main.js
...
import store from '@/store/store.js'
...
const app = new Vue({
...App,
store
})
- 创建公共数据 cart
// /store/cart.js
export default {
namespaced: true // 开启命名空间
state: ()=>({
cart: JSON.parse(uni.getStorageSync('cart') || '[]')
}),
mutations: { // 操作数据的方法,必须定义在此
addToCart(state, goods) { // 定义一个加入购物车的方法
const result = state.cart.find(item => item.goods_id === goods.goods_id)
if(!result) {
state.cart.push(goods)
}else {
result.goods_count++
}
this.commit('moduleCart/saveCartInfo')
},
saveCartInfo(state) { // 本地存储购物车数量
uni.setStorageSync('cart',JSON.stringify(state.cart))
}
},
getters: {
total(state) { // 动态统计购物车中的数量
let n = 0
this.goods_detail.forEach(item => n += item.goods.count)
return n
}
}
}
- 数据挂载
// /store/store.js
import Vue from 'vue'
import Vuex from 'vuex'
import moduleCart from '@/store/cart.js'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
moduleCart:moduleCart
}
})
export default store
- 使用数据
// /subpackage/goodsDetail/goods_detail.vue
import { mapState,mapMutations,mapGetters } from 'vuex'
export default {
computed: {
...mapStates('moduleCart', ['cart']),
...mapGetters('moduleCart',['total'])
},
methods: {
...mapMutations('moduleCart',['addToCart'])
},
watch: {
total(newValue) {
const result = this.options.find(item => item.text === '购物车')
if(result) {
result.info = newValue
}
}
}
}
- 如果购物车中有商品,在首次加载详情页时,不显示购物车的 info
- 原因:使用了 watch 的简写形式
- 解决:改成完整形式的 watch
watch: { total: { handler(newValue) { const result = this.options.find(item => item.text === '购物车') if(result) { result.info = newValue } }, immediate: true } } - tabBar 页面的徽标数字展示:
uni.setTabBarBadge()
// /pages/cart.vue
// 1. 映射 store 里的 total
// 2. 在页面 onLoad 时调用函数
// 3. 定义函数
setBadge() {
uni.setTabBarBadge({
index: 2, // 购物车按钮在 tabBar 组件内的 索引,购物车在第三个
text: this.total + '' // text 只支持字符串
})
}
uni 单选框
<radio checked color="#c00000"/>- 更新单选框选中状态
- checked 属性改为动态绑定
<radio :checked="goods.goods_state" color="#c00000"/> - 外部传入自定义事件:
<my-goods @update:checked="checkedChangeHandler"></my-goods> - 内部绑定 click 事件用来触发外部传入的自定义事件:
<radio @click="checkedChangeHandler" :checked="goods.goods_state" color="#c00000" /> - 定义事件处理函数,将商品 id 和 state 传出去
// my-goods 组件 checkedChangeHandler() { this.$emit('update:checked',{ goods_id: this.goods.goods_id, goods_state: !this.goods.goods_state // 传递最新的状态,所以要取反 }) }- 定义 vuex / store 全局方法
// cart.js ... mutations: { ... updateChecked(state, goods) { const result = state.cart.find(item => item.goods_id === goods.goods_id) if(result) { result.goods_state = goods.goods_state this.commit('moduleCart/saveCart') // 保存到本地存储 } } }- cart.vue 引入 updateChecked 方法
... checkedChangeHandler(e) { this.updateChecked(e) } - checked 属性改为动态绑定
uni 数字加减按钮
<uni-number-box :min="1" :max="9"></uni-number-box>
uni 滑动列表出现删除按钮
- 滑动删除需要用到 uni-ui 的 uni-swipe-action 组件和 uni-swipe-action-item。详细的官方文档请参考 SwipeAction 滑动操作。
- 要求基本库 2.14.0 以上
- iOS 端由于存在bounce效果,滑动体验略差,建议禁止bounce效果
uni 收件地址
uni.chooseAddress()返回值是一个数组:第 1 项为错误对象;第 2 项为成功之后的收货地址对象
async chooseAdress() {
const [error, sucsess] = await uni.chooseAdress()
if(error === null && sucesee.errMsg === 'chooseAdress:ok') {
this.address = sucsess
}
}
js 基础:数组的 every、some 方法复习
- 好久没用,忘了这俩 API 了
array.every(i => i.name === '张三')array 中每一个元素的 name 都为张三返回 truearray.some(i => i.name === '张三')array 中只要有一个元素的 name 为张三返回 true- some 不会检测空数组
用户登录
- 为登录的 button 绑定
open-type="getUserInfo"属性,表示点击按钮时,希望获取用户的基本信息:<button type="primary" @click="getUserProfile"></button>
- getUserInfo 事件处理函数:
getUserProfile() { // 1. 判断用户信息是否获取成功 if (e.detail.errMsg === 'getUserInfo:fail auth deny') return uni.$showMsg('您取消了登录授权!') // 获取用户信息成功, e.detail.userInfo 就是用户的基本信息 uni.getUserProfile({ desc:'test', // 获取用户信息的用途介绍,最多三十个字符 success:(userInfo)=> { // 成功 console.log(userInfo) this.getToken(userInfo) // 登录函数 }, fail:(error)=> { // 失败 if(error.errMsg = 'getUserProfile:fail auth deny') { uni.showToast({ title: '用户取消了授权', duration: 1500, icon: 'none' }) } } }) }
- 登录需要的五个参数
- code 用户登录凭证 uni.login()
- encryptedData 完整用户信息密文 getUserInfo
- iv 加密算法的初始向量 getUserInfo
- rawDate 用户信息元素数据字符串 getUserInfo 获取 JSON.stringify() 转字符串
- signature 使用 shal 得到字符串 getUserInfo
- 登录
- 因为微信小程序的登录是调用 uni.login() 获取临时登录 id,然后在服务端调用 调用
auth.code2Session 接口,目前只有被服务器端管理员配置为小程序的开发者以后,才能调用成功登录,支付这些特殊的接口,目前做不了
- 因为微信小程序的登录是调用 uni.login() 获取临时登录 id,然后在服务端调用 调用
async getToken(userInfo) {
const [error, response] = await uni.login().catch(error => error)
console.log(response)
if(error || response.errMsg !== "login:ok") return uni.$uniShowMessage('登录失败!')
// 初始化参数
const query = {
code: response.code,
encryptedData: userInfo.encryptedData,
iv: userInfo.iv,
rawData: userInfo.rawData,
signature: userInfo.signature
}
// 换取 token
const { data: loginResult } = await uni.$http.post('https://api-ugo-web.itheima.net/api/public/v1/users/wxlogin',query)
if(loginResult.meta.status !== 200) return uni.$uniShowMessage('登录失败!')
uni.$uniShowMessage('登录成功 !')
}
微信支付
- 在请求头中添加 Token 身份认证的字段(只有在登录之后才允许调用支付相关的接口,所以必须为有权限的接口添加身份认证的请求头字段)
// main.js 前置路由守卫
$http.beforeRequest = function(options){
uni.showLoading({
title: '拼命加载中...'
})
// 只有登录过的用户路径里才会有 my(个人信息)
if(options.url.indexOf('/my/') !== -1) {
// 为请求头添加身份认证字段
options.header = {
// 字段值直接从 vuex 中获取
Authorization: store.state.moduleAddress.token,
}
}
}
-
支付流程
- 创建订单
- 请求创建订单的 API 接口:把(订单金额、收货地址、订单中包含的商品信息)发送到服务器
- 服务器响应的结果:订单编号
- 订单预支付
- 请求订单预支付的 API 接口:把(订单编号)发送到服务器
- 服务器响应的结果:订单预支付的参数对象 ,里面包含了订单支付相关的必要参数
- 发起微信支付
- 调用 uni.requestPayment() 这个 API,发起微信支付;把步骤 2 得到的 “订单预支付对象” 作为参数传递给 uni.requestPayment() 方法
- 监听 uni.requestPayment() 这个 API 的 success , fail , complete 回调函数
- 创建订单
-
支付
- 在点击了结算按钮后,调用微信支付的方法
- 定义方法
async payOrder() { // 创建订单,初始化订单对象 const orderInfo = { // 开发期间,注释掉真实的订单价格 // order_price:this.checkedGoodsAmount, // 写死价格 order_price:0.01, consignee_addr: this.addstr, goods: this.cart.filter(item => item.goods_state).map(item => ({ goods_id: item.goods_id, goods_number: item.goods_count, goods_price: item.goods_price })) } // 发起请求创建订单 const { data: result } = await uni.$http.post('服务器订单接口',orderInfo) if(result.meta.status !== 200) return uni.$uniShowMessage('订单创建失败!') // 得到服务器响应的 订单编号 const orderNumber = result.message.order_number // 订单预支付 发起请求获取订单的支付信息 const { data: result2 } = await uni.$http.post('服务器预支付接口',{ order_number: orderNumber }) if(result2.meta.status !== 200) return uni.$uniShowMessage('预支付订单生成失败!') // 得到订单支付相关参数 const payInfo = result2.message.pay // 发起微信支付 const [error, success] = await uni.requestPayment(payInfo) if(error) return uni.$uniShowMessage('订单未支付!') // 支付完成,查询支付结果 const { data: result3 } = await uni.$http.post('服务器订单查询接口',{ order_number: orderNumber }) if(result3.meta.status !== 200) return uni.$uniShowMessage('订单未支付!') uni.showToast({ title:'支付完成!', icon:'success' }) }