目录
对promise网络请求应该用await修饰,方法用async修饰
改造swiper-item url指向分包路径和带上商品ID参数
如果链接要跳转tabbar页面只能用uni.switchTab()
对于数组修改应该新建一个数组,不要修改原数组的顺序,它是个引用对象
因为监听器watch不会在页面加载的时候第一时间执行,所以不会加载购物车的数量
抽离成mixins供每个tabbar页面使用,因为每个tabbar页面都需要加载购物车数量
当购物车商品数量发生改变,mixins数据没有更新的解决办法
项目结构
app.json文件
-
页面样式会覆盖全局样式
project.config.json文件
- setting里添加"checkSiteMap": false; 不会提示警告
sitemap.json文件
- 默认可以被微信索引所有页面allow
- 修改成disallow不被索引
新建页面
- app.json添加配置,会自动创建页面
- 修改首页
修改pages里的顺序就可以了,第一位是首页
wxml模板
- 可以理解成html,但有区别
wxss样式
- 可以理解成css,但有区别
app.js文件
宿主环境
微信就是小程序的宿主环境
通信主体
通信模型
- 都是由微信客户端来接收请求和响应的
运行机制
- 启动过程
- 渲染过程
组件
- 常用视图窗口类组件
- 使用html的语法
<rich-text nodes="<h1 style='color:red;'>标题</h1>"></rich-text>
- 其它常用组件
数据绑定
- 页面的js文件
data: {
msg:'你好'
},
- 页面wxml文件
<view>{{msg}}</view>
- 三元运算
跟java一样 type == 1 ? '男' : '女'
事件绑定
- 渲染层触发事件通过微信客户端交给js逻辑层处理
- 常用事件
- 事件回调收到event对象,它的属性有
- target和currentTarget区别,用得最多是target
- 数据赋值
- 事件传参数
- 事件接收参数
- input事件
条件渲染
- wx:if
- <block>使用wx:if
- hidden
- wx:if与hidden
- wx:for
不推荐自定义名字,太麻烦
- wx:key 没有ID可以用索引
wxss模板样式
- rpx尺寸单位
把屏幕分成750份,可以自动适合屏幕大小,和html的vw vh单位一样
用iphone6作为视觉标准
- @import样式导入
全局样式
- app.wxss文件里面的都属于全忆样式
局部样式
- 页面的wxss文件里面的都属于局部样式
- 当和全局样式冲突,局部会覆盖全局样式
全局配置
- window
常用配置属性
enablePullDownRefresh一般不会在全局设置,只会在需要的页面上配置即可
onReachBottomDistance没有特别需求保持默认值即可
- tabBar
tabBar节点配置
list属性
页面配置
- 如果和全局配置冲突,会覆盖全局配置
常用配置荐
网络请求
- wx.request
- 小程序不存在跨域问题
- 小程序没有Ajax,Ajax是依赖浏览器XMLHttpRequest对象的
页面导航
导航到tabBar页面
导航到非tabBar页面
后退导航
编程式导航
导航到tabBar页面
导航到非tabBar页面
后退导航
导航传参
声明式导航传参
编程式导航传参
在onLoad中接收参数
可以在data里定义一个属性接收options供页面使用
页面事件
下拉刷新
下拉刷新窗口样式
监听页面下拉刷新事件
停止下拉刷新
上拉触底
一般是用来处理数据列表分页
注意:要做节流处理,当前上拉触底请求没处理完不能再重复处理
配置上拉触底距离
随机颜色
- js解构
----------------------------------------
const User = {
name: '搞前端的半夏',
age: 18
}
const { name, age } = User;
------------------------------------------
const User = {
name: '搞前端的半夏',
age: '18',
contact:{
phone:'110',
}
}
const phone = User.contact.phone; //需要通过两层获取.
const {contact:{phone}}=User
consosle.log(phone) // 输出10.
----------------------------------------------
const { name, age=18 } = employee; //可以添加属性,也可以为属性赋默认值
-----------------------------------------------------------------------
const { age: userAge } = User;//为属性定义别名
console.log(userAge);
- js 三个点扩展运算符
// 数组
var number = [1,2,3,4,5,6]
console.log(...number) //1 2 3 4 5 6
//对象
var man = {name:'蔡',height:180}
console.log({...man}) / {name:'蔡',height:180}
---------------------------------------------------
//数组的复制
var arr1 = ['hello']
var arr2 =[...arr1]
arr2 // ['hello']
//对象的复制
var obj1 = {name:'Steven'}
var obj2 ={...obj2}
obj2 // {name:'Steven'}
---------------------------------------------
//数组的合并
var arr1 = ['hello']
var arr2 =['world']
var mergeArr = [...arr1,...arr2]
mergeArr // ['hello','world']
// 对象分合并
var obj1 = {name:'Steven'}
var obj2 = {height:181}
var mergeObj = {...obj1,...obj2}
mergeObj // {name: "Steven", height: 181}
渲染UI美化
加载层loading
wx.showLoading(Object object)
title属性必填
网络请求完成回调complete方法需要调用wx.hideLoading
节流处理
生命周期
应用生命周期函数
页面生命周期函数
添加编译模式方便开发
wxs脚本
wxml文件中不能调用页面.js文件中的函数,但是可以调用wxs定义的函数
典型应该就是“过滤器”
wxs和javaScript的关系
内嵌在wxml文件里的wxs脚本
自定义wxs脚本文件
特点
- 不能作为组件事件回调
- wxs和js代码是隔离的,互相不能调用
- 在ios设备上wxs比js快2~20倍,安卓设备上没有差异
修改跳转页面的title
data: {
//先定义一个接收跳转参数的变量
query:{}
},
onLoad(options) {
//页面加载时把参数赋值给query变量
this.setData({
query: options
})
},
onReady() {
//页面渲染完成后修改title
wx.setNavigationBarTitle({
title: this.data.query.name,
})
},
js字符串转数字
- "80" - 0
js回调函数参数
this.getShopList(() =>{
wx.stopPullDownRefresh()
})
getShopList(cb) {
...;
cb && cb();
}
})
},
项目商铺列表页
// pages/shopList/shopList.js
Page({
/**
* 页面的初始数据
*/
data: {
hideBottom: true,
isLoading: false,
query: {},
shopList: [],
page: 1,
pageSize: 10,
total: 0
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.setData({
query: options
})
this.getShopList();
},
// cb是回调方法
getShopList(cb) {
wx.showLoading({
title: '加载数据...',
})
this.setData({
isLoading: true
})
wx.request({
url: `https://www.escook.cn/categories/${this.data.query.id}/shops`,
method: 'GET',
data: {
_page: this.data.page,
_limit: this.data.pageSize
},
success: (res) => {
console.log(res.data)
this.setData({
shopList: [...this.data.shopList, ...res.data],
total: res.header['X-Total-Count'] - 0
})
},
complete: () => {
wx.hideLoading({
success: (res) => {
},
})
this.setData({
isLoading: false
})
cb && cb();
}
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
wx.setNavigationBarTitle({
title: this.data.query.title,
})
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
this.setData({
shopList:[],
page:1,
total:0,
hideBottom:true
})
// 只有下拉事件的时候才会传关闭下拉更新的回调函数
this.getShopList(() =>{
wx.stopPullDownRefresh()
})
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
if ((this.data.page * this.data.pageSize) >= this.data.total) {
this.setData({
hideBottom: false
})
return;
}
if (this.data.isLoading) {
return;
}
this.setData({
page: this.data.page + 1
})
this.getShopList();
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
<view class="shop-item" wx:for="{{shopList}}" wx:key="id">
<image src="{{item.images[0]}}" ></image>
<view class="shop-info">
<view>{{item.name}}</view>
<view>{{item.phone}}</view>
<view>{{item.address}}</view>
<view>{{item.businessHours}}</view>
</view>
</view>
<view class="bottom" hidden="{{hideBottom}}">---------------- 我是有底线的 -------------------</view>
自定义组件
创建与局部引入组件
- 项目结构新建如下目录
- 在需要引入组件的页面.json里引入
- 页面wxml文件
<my-test></my-test>
全局引入组件
只要在app.json里引入组件,全部页面就可以使用
/*app.json文件*/
"usingComponents": {
"my-test":"/components/test/test"
}
组件和页面区别
组件样式
组件样式隔离
注意点!!
修改组件隔离选项
data数据
methods方法
properties属性
data和properties区别
两个都指向同一个地方,里面的数据是共享的,也可以用setData修改properties的数据
数据监听器
rgb:{r:0,g:0,b:0}
fullColor:"0,0,0"
简化
纯数据字段
即纯用于业务逻辑处理,不用于页面渲染
使用规则
组件生命周期
- lifetimes节点
组件所在页面的生命周期
- pageLifetimes节点
插槽
- 默认单插槽
- 启用多插槽
父子组件之间通信
- 属性绑定,父组件传值给子组件
- 事件绑定,子组件传值给父组件
- 获取组件实例,访问子组件数据和方法
behaviors 组件中共享的东西
然后这个组件就可以使用behavior里面的所有东西了
- behavior可用节点
- 同名处理
使用npm包
Vant Weapp
- 先在项目根目录初始化 命令:npm init -y
- 安装步骤
使用Vant组件
定制全局主题样式
- 以--开头为变量名
- 以下是在html范围内有效,你也可以自定义在其它范围(类名)里生效,作用范围也会改变
- 在小程序里可以这样用
记得最好是定义在page范围里,这样页面所有组件都生效
根据你想改变样式的组件类型,找到样式,把变量名改成--开头,加上你要的样式
https://github.com/vant-ui/vant-weapp/blob/dev/packages/common/style/var.less
API Promise化
创建Promise化
- 第一步
- 第二步
为了不出错,先删除项目根目录的miniprogram_npm文件夹 (开发者工具新版本好像不需要删除了)
- 第三步
工具---构建npm,成功后重启开发者工具
实现Promise化
wxp和wx.p都是指向同一个内存地址
promisifyAll(wx,wxp)的意思是把微信小程序的wx定级对象进行了promise化,然后赋给wxp
因为wxp和wx.p都是指向同一个内存地址,所以今后可以使用wx.p就是promise化的api
- 使用
因为wx.p已经promise化,它调用的api是没有回调函数的,最张会得到promise对象。
所以方法需要加 async修饰,api调用前加await修饰
全局数据共享
跟vue里的vuex一样
安装MobX
- 项目根目录创建store/store.js
get代表是计算属性,是只读的
action函数是修改数据值的
将Store中的成员绑定到页面中
store是数据源、fields是字段(属性)、actions是方法。
storeBindings是得到的对象,前面加this是挂载到当前页面上作为自定义属性来用,可以用来清理绑定的东西
使用Store中的成员
将Store中的成员绑定到组件中
在组件中使用
分包
- 主包的公共资源可以被其它分包访问,但是分包里的私有资源不能给其它分包以及主包访问
分包加载规则
限制
配置方法
保存后项目自动生成目录结构
为每个分包添加别名属性"name": "p1"
开发者工具的基本信息可以查看项目代码体积大小
打包规则
引用规则
独立分包
- 不用下载主包就能打开小程序
应该场景
独立分包配置
在普通分包里加上"independent": true
引用独立分包规则
配置分包预下载
预下载规则
自定义tabBar
记住原来的list不能删除,给低版本的兼容用
使用Vant的tabBar
把原来app.json里的tabBar里的list整个复制到custom-tab-bar的.js文件的data里,然后循环item项
<van-tabbar-item wx:for="{{list}}" wx:key="index" info="{{item.info ? item.info : ''}}">
<image
slot="icon"
src="{{item.iconPath}}"
mode="aspectFit"
style="width: 30px; height: 30px;"
/>
<image
slot="icon-active"
src="{{item.selectedIconPath}}"
mode="aspectFit"
style="width: 30px; height: 30px;"
/>
{{item.text}}
</van-tabbar-item>
在自定义组件里要覆盖Vant的默认样式需开启styleIsolation: 'shared'
数字徽标可以在list里其中一个tab里添加info:this.data.info,页面循环用三元运算判断
组件引入store后需要用监听里面的数据值,如果发生改变就对List里的info赋值,简单
跳转就用wx.switchTab()方法,记得把pagePath要路径前加上/
解决选中项索引问题,把active抽离到store里,监测到改变tab时修改store里的active
更改选中tab的文本颜色:直接在van-tabbar标签上加上active-color="#123123"写死就好
uni-app项目
每做一个模块都新建一个分支
比如正在做tabbar页面:git checkout -b tabbar
导航栏不显示title
导航栏的navigationBarTitleText会被页面的navigationBarTitleText覆盖
把分支推送到仓库
git push -u origin tabbar
合并分支全并分支删除分支
先切换到主分支才可以合并 git checkout master
然后合并 git merge tabbar
最后把最新的master分支推送到仓库 git push
因为仓库已经有tabbar分支,所以最后删除本地的tabbar分支 git branch -d tabbar
下载第三方包前先初始化项目结构
npm init -y
安装第三方网络请求包
npm install @escook/request-miniprogram
如果出现错误 npm ERR! code ECONNREFUSED 解决方法是
npm config set proxy null
npm config set https-proxy null
npm config set registry http://registry.npmjs.org/
安装成功后导入,在main.js里
// 按需导入 $http 对象
import { $http } from '@escook/request-miniprogram'
// 在 uni-app 项目中,可以把 $http 挂载到 uni 顶级对象之上,方便全局调用
uni.$http = $http
// 请求拦截器
$http.beforeRequest = function(option){
uni.showLoading({
title:'加载中...'
})
}
// 响应拦截器
$http.afterRequest = function(option){
uni.hideLoading()
}
main.js添加请求的根路径
$http.baseUrl = 'https://www.uinav.com'
对promise网络请求应该用await修饰,方法用async修饰
async getSwiperList() {
const {data:res} = await uni.$http.get('/api/public/v1/home/swiperdata')
if(res.meta.status != 200){
return uni.showToast({
title:'请求失败!',
icon:"none",
duration:1500
})
}
this.swiperList = res.message
}
配置小程序分包
- 第一步:在项目根目录建subpkg文件夹
- 第二步:在pages.json文件添加配置后保存,注意一定要先保存,不然后面新建子包页面有问题
"subPackages": [
{
"root": "subpkg",
"pages": []
}
],
- 第三步:在subpkg文件夹添加页面文件
同时页面配置会记录到pages.json的subPackages里面
改造swiper-item url指向分包路径和带上商品ID参数
<navigator class="swiper-item" :url="'/subpkg/goods_detail/goods_detail?goods_id='+ item.goods_id">
<image :src="item.image_src" ></image>
</navigator>
这样点轮播图就会跳转到指定的分包页面
提示信息组件封装
参数可以有默认值
// 封装提示信息组件
uni.$showMsg = function(title = '数据请求失败!',duration = 1500){
uni.showToast({
title,
duration,
icon:'none'
})
}
如果链接要跳转tabbar页面只能用uni.switchTab()
uni.switchTab({
url:'/pages/cate/cate',
})
动态绑定样式
:style="{width: item.product_list[0].image_width + 'rpx'}"
遇到接口返回的路径和分包的路径不同可以修改后挂载给数据集合
async getFloorList() {
const {
data: res
} = await uni.$http.get('/api/public/v1/home/floordata')
res.message.forEach(floor =>{
floor.product_list.forEach(product =>{
product.url = '/subpkg/goods_list/goods_list?' + product.navigator_url.split('?')[1]
})
})
this.floorList = res.message
}
再把view改成navigator url跳转
如果大小是固定那就不需要用rpx,直接使用px
获取屏幕可高度
可用高度windowHeight = 屏幕高度 - 导航条高度 - tabbar高度
uni.getSystemInfoSync() - windowHeight
还有顶部如果有搜索的记得减去搜索栏的高度
根据条件动态修改样式
data里放一个被激活的属性active:0,因为默认第一个选中
:class="['left-item', i === active ? 'active':'']" 根据循环索引i的值决定是否有active样式
@click="activeChanged(i)" 添加点击事件,动态修改active为当前索引值
解决滚动条不会自动回到顶部BUG
为scroll-view添加 :scroll-top="scrollTop"属性,scrollTop在data设置为0,激活后赋值Math.random(),或者赋值不为0的数字,这个数字是滚动条么顶部的距离
组件要提供可灵活修改的属性
使用组件时如果是字符串类型记得用单引号包起来<my-search :bgcolor="'#000'" :radius="50"></my-search>
使用组件的自定义事件
<my-search @myclick="search"></my-search>因为自定义组件没有myclick事件
所以要在组件的view标签添加@click事件再通过this.$emit('myclick')来调用父页面的myclick事件
组件吸顶效果
.search-box {
position: sticky;
top: 0;
z-index: 999;
}
如果真机的跳转焦点效果没出来可以去修改源代码
修改第三方组件样式
进入uni_modules里面找到需要修改样式的组件
比如:uni-search-bar,找到vue文件,查看组件其中的类名
在引用组件的页面使用相同类名样式覆盖原组件样式
防抖处理输入框每次键盘输入都进行查询
input(e) {
//500毫秒内再次输入会清除延时器
clearTimeout(this.timer)
//500毫秒延时器
this.timer = setTimeout(() =>{
console.log(e)
},500)
}
对于数组修改应该新建一个数组,不要修改原数组的顺序,它是个引用对象
错误:this.histroyList.reverse()
正确:[...this.histroyList].reverse()
增加数组的内容时去重
//把原数组转成set数组
let list = new Set(this.historyList)
//添加元素时先删除数组里相同的元素
list.delete(e)
//添加元素
list.add(e)
//把set数组转变成数组
this.historyList = Array.from(list)
善用计算属性computed
如果使用 methods 执行函数,vue 每次都要重新执行一次函数,不管message 的值是否有变化;
如果使用computed 执行函数,只有当message 这个最初的数据发生变化时,函数才会被执行。(依赖-监测数据变化)
调用:{{history}}
historyList如果没有发生变化,走的缓存,只有它发生变化才执行该方法重新计算。
添加缓存和加载页面时取缓存
uni.setStorageSync('kw', JSON.stringify(this.historyList))
onLoad() {
this.historyList = JSON.parse(uni.getStorageSync('kw') || '[]')
}
添加默认图片属性,当商品没有图片时默认显示
defaultPic:''
<image :src="goods.goods_small_logo || defaultPic"></image>
商品列表里的每一项商品可以做成组件
props: {
goods: {
type: Object,
default: {}
},
}
调用:<my-goods :goods="goods"></my-goods>
如果没有显示清一下缓存刷新
善用过滤器
filters: {
toFixed(num) {
return Number(num).toFixed(2)
}
}
调用:{{goods.goods_price | toFixed}} 这是过滤器的使用方式
上拉刷新下一页判断条件
pagenum * pagesize >= total
下拉刷新
重点是把关闭下拉刷新的方法传给getGoodsList方法和执行
async getGoodsList(cb) {
this.isLoading = true
const {
data: res
} = await uni.$http.get('/api/public/v1/goods/search', this.queryObj)
this.goodsList = [...this.goodsList, ...res.message.goods]
this.total = res.message.total
this.queryObj.pagenum = res.message.pagenum
this.isLoading = false
cb && cb()
}
onPullDownRefresh() {
this.queryObj.pagenum = 1
this.goodsList = []
this.getGoodsList(() => {
uni.stopPullDownRefresh()
})
}
轮播图放大展示
<image class="item-pic" :src="pic.pics_big" @click="preview(i)"></image>
preview(i) {
uni.previewImage({
urls: this.goodsInfo.pics.map(x => x.pics_big),
current: i
})
}
展示富文本内容
<rich-text :nodes="goodsInfo.goods_introduce"></rich-text>
正则替换富文本内容,把图片底下的空白区去掉
res.message.goods_introduce.replace(/<img /g,'style:"display:block;"')
/<img 开头 /g 全局
webp图片格式对IOS系统不兼容,同样正则替换
解决页面加载瞬间文字闪烁undefind
v-if="goodsInfo.goods_name" 在标签上加判断
组件名:uni-goods-nav
注意不要遮挡最下面的介绍图
使用VUEX全局共享实现购物车功
挂载 VUEX
// 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: {
'm_cart': moduleCart
}
})
export default store
// main.js
import store from "@/store/store.js"
const app = new Vue({
...App,
store
})
store目录里创建cart.js文件作为购物车vuex模块
store/store.js挂载cart.js
调用
补充mutitions
判断对象obj是否为undefind
if(obj)
调用store里的方法
注意mapMutations只能放在methods里使用
import { mapState, mapMutations } from "vuex"
methods: {
...mapMutations('m_cart', ['addToCart']),
}
buttonClick(e) {
if (e.content.text === '加入购物车') {
const goods = {
goods_id: this.goodsInfo.goods_id,
goods_name: this.goodsInfo.goods_name,
goods_price: this.goodsInfo.goods_price,
goods_count: 1,
goods_small_logo: this.goodsInfo.goods_small_logo,
goods_state: true
}
this.addToCart(goods)
}
this.options[1].info++
}
补充getters
getter: {
total(state) {
let c = 0
state.cart.forEeach(x => c += x.goods_count)
return c
}
}
调用getters
//引入mapGetters
import {
mapState,
mapMutations,
mapGetters
} from "vuex"
//计算属性引入store里的total
computed: {
...mapState('m_cart', ['cart']),
...mapGetters('m_cart', ['total'])
},
//监听total变动
watch: {
total(newValue) {
const findResult = this.options.find(x => x.text === '购物车')
if (findResult) {
findResult.info = newValue
}
}
},
因为监听器watch不会在页面加载的时候第一时间执行,所以不会加载购物车的数量
解决:
跳转购物车页面添加tab徽标
//购物车页面
import {
mapGetters
} from "vuex"
export default {
computed: {
...mapGetters('m_cart', ['total'])
},
data() {
return {
};
},
onShow() {
this.setBage()
},
methods: {
setBage() {
uni.setTabBarBadge({
index: 2,
text: this.total + ''
})
}
},
}
抽离成mixins供每个tabbar页面使用,因为每个tabbar页面都需要加载购物车数量
把上面的代码放到/mixins/tabbar-badge.js里,然后在每个tabbar页面引用
import badgeMixin from "@/mixins/tabbar-badge.js"
export default {
mixins: [badgeMixin],
data() {
return {
};
}
}
修改商品列表组件
showRaido属性是供外面定义的,默认关闭不显示radio
<label class="radio" v-if="showRadio">
<radio checked="true" color="#C00000" /><text></text>
</label>
props: {
showRadio: {
type: Boolean,
default: false
},
goods: {
type: Object,
default: {}
},
},
获取微信地址需要在manifest.json添加权限
结构赋值
const [err, succ] = await uni.chooseAddress().catch(err => err)
收货地址数据要放到store里
store/user.js
让用户重新授权
苹果手机无法重新授权解决
利用reduce计算勾选商品的总数
当购物车商品数量发生改变,mixins数据没有更新的解决办法
购物车结算事件
settleHandler() {
if (!this.checkCount) {
return uni.showToast({
icon: "none",
title: '请选择要结算的商品'
})
}
console.log(this.checkCount)
if (!this.addstr) {
return uni.showToast({
icon: "none",
title: '请填写收货地址'
})
}
if (!this.token) {
return uni.showToast({
icon: "none",
title: '请先登陆'
})
}
}
微信登陆接口
<button type="primary" class="btn-login" open-type="getUserInfo" @click="getUserInfo">
一键登录
</button>
async getUserInfo(e) {
const [err, succ] = await uni.getUserProfile({
desc: '用于登陆小程序'
})
if (err && err.errMsg === 'getUserProfile:fail auth deny') {
uni.showToast({
title: '你取消了登陆授权',
icon: "none"
})
return
}
if (succ.errMsg === 'getUserProfile:ok') {
uni.showToast({
title: succ.userInfo.nickName + '登陆成功',
icon: "none"
})
console.log(succ)
}
}