js文件用于:控制逻辑代码,相当于vue的script
wxml 控制页面结构,就相当于vue里面的tempplate
wxss 控制样式,相当于vue的style
app.json 用于控制全局配置
{
"pages":[
"pages/index/index",
"pages/logs/logs",
"pages/heima/index"
],
"window":{
"backgroundTextStyle":"dark",
"backgroundColor": "#f0f",
"navigationBarBackgroundColor": "#000",
"navigationBarTitleText": "黑马优购",
"navigationBarTextStyle":"white",
"enablePullDownRefresh": true
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
pages: 控制页面路由,接收一个数组,数组是页面路径,如果路径不存在,会自动创建
window:控制页面窗口信息。
background前缀的用于控制背景配置
navigationBar前缀控制导航栏
特点:当page配置的属性和全局app.json相同的时候,会使用局部配置覆盖全局配置
app.wxss 全局样式
每个文件下page.wxss 局部样式
全局样式可以给所有页面使用,局部样式优先级高于全局样式,如果出现相同属性,局部样式覆盖全局样式
<view>{
{变量值}}view>
<view>{
{ gender === '男' ? '精神小伙': '表妹'}}view>
wx:for="{ {数组}}"
默认数据内容 item
,默认索引index
改变内容变量 wx:for-item='变量名'
改变索引变量名 wx:for-index=“变量名”
wx:key="字符串"
wx:key="*this"
*this
代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字。block
是一个空标签,可以用来写循环标签,或者判断标签都可以,他不会出现在页面上
<view>
<block wx:for="{
{hobby}}" wx:key="*this">
{
{item}}
block>
view>
<view wx:for="{
{对象}}" wx:key="index">
表示对象的属性值:{
{item}}
表示对象的key值:{
{index}}
view>
wx:if="{ {条件}}"
<view wx:if="{
{条件一}}">view>
<view wx:elif="{
{条件二}}">view>
<view wx:else>其他条件view>
hidden
<view hidden="{
{ gender === '女' }}">隐藏的模块view>
关键字:bind事件名="函数名"
如果需要给函数传递参数,需要通过data-参数名
进行传递
如果需要接受,需要在e.currentTarget.dataset
<view data-name="heima" data-id="100" bindtap="heimaTap">点击事件测试view>
定义函数:
Page({
heimaTap: function(e) {
const id = e.currentTarget.dataset.id;
const name = e.currentTarget.dataset.name;
console.log(id, name)
},
})
第一种方法:通过输入事件获取输入内容
<input bindinput="userInput" type="text" style="border: 2px solid #ccc; margin: 10px 10px"/>
userInput(e) {
// 用户输入的内容
console.log(e.detail.value)
},
第二种方法:简易的双向绑定
<view>
用户名:<input type="text" style="border: 2px solid #ccc; margin: 10px 10px" model:value="{
{userName}}"/>
<button bindtap="getUserName">获取用户名button>
view>
getUserName() {
// 读取data下的数据
console.log(this.data.userName)
},
兼容css大多数特征,而且在css的基础上进行扩展
1、响应式单位 rpx
响应式布局单位
屏幕的宽度就是750rpx
2、样式导入
使用@import
语句可以导入外联样式表,@import
后跟需要导入的外联样式表的相对路径,用;
表示语句结束。
示例代码:
/** common.wxss **/
.small-p {
padding:5px;
}
/** app.wxss **/
@import "common.wxss";
.middle-p {
padding:15px;
}
<image src="路径">image>
mode:表示图片显示的方式
只要记住一个:widthFix 这个模式和html图片显示方式是一样的。
用于图片或者元素的轮播。
注意:swiper默认高度为150px
使用方法:
<swiper>
<swiper-item>swiper-item>
<swiper-item>swiper-item>
<swiper-item>swiper-item>
swiper>
作用:用于页面的跳转
open-type 用于控制跳转的方式,redirect 表示替换前一个页面,相当于vue-router的replace
<navigator url="/pages/index/index">跳转到别的页面navigator>
<navigator url="../index/index">使用相对路径跳转到首页navigator>
<navigator url="/pages/index/index" open-type="redirect">通过redirect方式跳转navigator>
另外,还可以通过编程式跳转
wx.navigateTo({
url: '/pages/index/index',
})
<rich-text nodes="测试一个大标题
">rich-text>
一般按钮可以设置open-type获取微信开放能力
<button open-type="contact">打开客服功能button>
<button open-type="share">分享赚红包button>
<button bindgetphonenumber="getPhone" open-type="getPhoneNumber">获取手机号button>
<button bindgetuserinfo="getUserInfo" open-type="getUserInfo">获取用户信息button>
<radio-group bindchange="getGender">
<radio checked="{
{true}}" value="man">男radio>
<radio value="women">女radio>
radio-group>
<view>
<checkbox-group bindchange="getHobby">
<checkbox value="game">王者checkbox>
<checkbox value="movie">抖音checkbox>
<checkbox value="eat">吃饭checkbox>
checkbox-group>
view>
在全局配置文件app.json
注意:
pagePath
接收的路径前面不要斜杠
list
最少两个,最多五个
selectedColor
是放在tabBar对象下的
"tabBar": {
"selectedColor": "#E03440",
"color": "#8C8D8D",
"list": [{
"pagePath": "pages/demo3/index",
"text": "首页",
"iconPath": "/icons/index.png",
"selectedIconPath": "/icons/index_selected.png"
},{
"pagePath": "pages/demo2/index",
"text": "分类",
"iconPath": "/icons/category.png",
"selectedIconPath": "/icons/category_selected.png"
}]
},
1、通过ide右击新建组件,组件和页面也是一样,由四个文件组成,wxml wxss json js
2、json文件中必须配置"component": true,
在页面的配置文件声明
key值表示组件别名,value表示组件的路径
{
"usingComponents": {
"heima": "/components/heima"
}
}
<heima student="67" name="{
{name}}">heima>
properties: {
student: {
type: Number, // 参数的类型
value: 0, // 默认值
},
// 简写形式
name: Number
},
this.triggerEvent('事件名', 参数)
应用生命周期,定义app.js
页面生命周期,定义在各自页面js
//app.js
App({
// 小程序初始化的时候执行一次
// 使用场景:获取用户地理位置,用户授权
onLaunch() {
console.log('小程序初始化成功')
},
onShow() {
// 当应用隐藏到后台然后重新打开,会触发onShow
// 使用场景:例如抢购页面,当用户重新打开应用,重新刷新数据
console.log('小程序onShow被触发')
},
onHide() {
console.log('小程序onHide被触发')
},
onError() {
// 使用场景:网络异常,收集异常日志
console.log('应用出现异常')
}
})
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// options 表示获取页面参数,option是一个对象
// 使用场景:在这里发送网络请求
console.log('页面onLoad', options)
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
// 场景:获取页面元素
console.log('页面 onReady触发')
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
console.log('页面onShow')
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
console.log('页面onHide')
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
// 场景:定时器清除
console.log('页面onUnload')
},
lifetimes: {
created() {
console.log('组件创建完毕created')
},
attached() {
console.log('在组件实例进入页面节点树时执行attached')
},
detached() {
// 当页面被销毁的时候,组件也会随着销毁
console.log('在组件实例被从页面节点树移除时执行')
}
},
wx.canIUse('console.log')
wx.canIUse('CameraContext.onCameraFrame')
wx.canIUse('CameraFrameListener.start')
wx.canIUse('Image.src')
如果提示下面的错误,是因为请求域名没有加入到信任域名
方法一:「小程序后台-开发-开发设置-服务器域名」 中进行配置
方法二:如果你是用测试号,可以在开发工具设置忽略检查
网络请求使用方法:
wx.request({
url: '请求地址',
method: 'post', // 默认是get请求
success: (res) => { // 成功回调
}
})
回调的写法
wx.request({
url: 'https://api-hmugo-web.itheima.net/api/public/v1/home/catitems',
success: (res) => {
if(res.data.meta.status === 200) {
this.setData({
navs: res.data.message });
}
}
})
Promise写法
const myPromise = new Promise((reslove, reject) => {
wx.request({
url: 'https://api-hmugo-web.itheima.net/api/public/v1/home/catitems',
success: (res) => {
reslove(res);
}
})
})
myPromise.then(res => {
if(res.data.meta.status === 200) {
this.setData({
navs: res.data.message });
}
})
Promise.all([ promise对象,promise对象,promise对象]).then( () => {
console.log('三个promise都执行完毕了') } )
1、在页面的配置文件开启下拉刷新
"enablePullDownRefresh": true,
2、监听用户下拉刷新
onPullDownRefresh() {
}
3、停止下拉刷新动画
wx.stopPullDownRefresh();
支持:commonjs
模块导入标准
导出关键字: module.exports
导入关键字: require(‘路径’)
ESModule
导入关键字: import 变量 from ‘路径’
导出关键字: export default { }
const BASH_URL = 'https://api-hmugo-web.itheima.net/api/public/v1';
function get(url, data) {
const myPromise = new Promise((resolve, reject) => {
wx.request({
url: BASH_URL + url,
data,
success: (res) => {
resolve(res);
}
})
})
return myPromise;
}
function post() {
const myPromise = new Promise((resolve, reject) => {
wx.request({
method: 'POST',
url: BASH_URL + url,
data,
success: (res) => {
resolve(res);
}
})
})
return myPromise;
}
module.exports = {
get, post
}
1、使用DCloud公司提供的HBuilderX来快速开发
可视化的方式比较简单,HBuilderX内置相关环境,开箱即用,无需配置nodejs。官方IDE下载地址首次安装需要配置微信开发者工具的路径
2、使用脚手架来快速搭建和开发
全局的环境安装安装脚手架:
npm i -g @vue/cli (之前安装过可跳过此步->2)
创建项目:
vue create -p dcloudio/uni-preset-vue my-project
//**my-project** (你自己创建项目的名字)
启动(微信小程序):
npm run dev:mp-weixin
1、在微信开发者工具启动服务端口
点击 设置–》 安全–服务端口
2、运行到微信小程序
body的元素选择器请改为page,同样,div和ul和li等改为view、span和font改为text、a改为navigator、img改为image…
图片资源只能放在static
文件夹,否则会没编译资源到微信项目
需要重启小程序,注意去微信开发者检查是否已删除成功
scroll-view
组件@scrolltolower
监听滚动条是否滚动到底部
lower-threshold
距底部/右边多远时(单位px),触发 scrolltolower 事件
注意!! scroll-view 必须设置高度,另外需要设置 scroll-y
实现纵向滚动
1、监听滚动触底事件
2、在data中定义一个变量记录当前页码,如果翻页的时候,对页码进行加一的操作
3、重新的获取数据,并且把页码参数提交给服务端
4、当数据返回是,页面上原有的数据要和服务端数据进行合并,而非替换
this.products = this.products.concat(res.data.message.goods)
目标:为了节省用户访问页面时的流量和提升访问速度
为什么需要懒加载:因为页面可能是很长的,远远超出用户可视界面,如果用户还没访问到的地方,其实可以通过懒加载的形式,节省加载资源。
实现方法:lazy-load
设置为 true即可
图片预览功能的作用:用于给用户放大缩小查看图片的细节。
uni.previewImage
接收一个urls参数,该参数不能为空
urls必须是一个数组,数组里放图片的链接。
uni.previewImage({
urls: urls // urls必须是一个数组,数组里面放图片路径
})
// 通过该方法设置分享的标题,跳转地址,或者图片
onShareAppMessage() {
return {
// 分享标题
title: '黑马56期黑马优购项目',
// 用户打开点击分享内容是跳转的页面
path: '/pages/index/index'
}
},
实现思路:
1、给按钮添加事件
2、事件中修改商品的amount
属性
3、如果商品数量为0时,提示用户是否删除商品
4、如果用户选择是,直接把商品删除
// 减少数量
reduce(i) {
// 获取当前修改的商品对象
const product = this.cart[i];
if (product.amount === 1) {
// 提示是否删除商品
uni.showModal({
title: '提醒',
content: '是否真的删除该商品',
confirmText: '删除', // 修改确认按钮的文字
success: (e) => {
// e 会得到用户点击按钮的状态,如果cancel为true表示点击取消,confirm为true表示点击删除
// 表示点击删除按钮
if (e.confirm) {
// 把商品从购物车移除
this.cart.splice(i, 1);
}
}
})
} else {
product.amount--;
}
},
注意:购物车隐藏时保存购物车到本地存储
因为购物车修改数量的是this.cart
对象,但是当页面进来的时候,会获取本地存储的购物车对象,会导致购物车被覆盖,所以当页面隐藏的时候,需要先把购物车存到本地存储。
onHide() {
// 当页面隐藏的时候,把购物车写入到本地缓存
uni.setStorage({
key: 'cart',
data: this.cart
})
},
实现思路:
1、通过购物车商品对象的checked
属性判断商品是否选中,显示不同的勾选状态
2、实现点击切换选中状态:给按钮绑定点击事件,点击时,切换checked属性值
// 切换商品选中状态
toggleChecked(i) {
console.log('toggleChecked', i)
this.cart[i].checked = !this.cart[i].checked;
console.log(this.cart[i].checked)
}
注意:因为给商品对象增加了checked属性,新增的属性是不具备响应性(数据的变更会触发页面的重新渲染)特性,所以我们可以通过在商品详情页加入商品的时候,默认给他checked属性。
第二种解决方案
// 切换商品选中状态
toggleChecked(i) {
this.cart[i].checked = !this.cart[i].checked;
// 第二种解决方案
this.cart.splice(i, 1, this.cart[i])
}
实现思路:
1、使用计算属性监听购物车商品选择状态
computed: {
// 全选状态
allChecked() {
let isAllCheck = true
// 遍历购物车,判断商品是否全选或者非全选
this.cart.forEach(item => {
if (!item.checked) {
isAllCheck = false;
}
})
return isAllCheck;
}
}
2、点击购物车全选按钮切换单个商品的选择状态
实现思路:1、给全选按钮绑定点击事件
2、当点击的时候,把购物车商品checked属性切换状态
// 切换全选状态
toggleAllChecked() {
const cart = this.cart.map((item, index) => {
// item.checked = !this.allChecked;
return {
...item , checked: !this.allChecked}
})
this.cart = cart;
}
totalPrice() {
let totalPrice = 0;
this.cart.length && this.cart.forEach(item => {
if (item.checked) {
totalPrice += item.amount * item.goods_price
}
})
return totalPrice;
}
totalCount() {
let total = 0;
this.cart.length && this.cart.forEach(item => {
if (item.checked) {
total += item.amount;
}
})
return total;
}
实现思路:
1、在onLoad生命周期获取本地存储的购物车数据
2、把数据循环并传入给Product组件
响应性和非响应性数据的区别
响应性数据:当数据发生变更,页面会自动渲染
非响应性数据:当数据发生变更,页面不会自动渲染
为什么会出现非响应性数据?
因为Vue实现响应性使用Object.defineProperty()
,但是该接口不支持对象和数组的监听。
解决访问
1、初始化的时候给初始值
2、调用Vue.set方法
3、替换掉原来对象
数组解决方案:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
async chooseAddress() {
// 调用微信地址本
const [error, res] = await uni.chooseAddress()
if (!error) {
this.isShowAddress = true;
// 省+市+区+详细收货地址
this.address = res.provinceName + res.cityName + res.countyName + res.detailInfo;
this.userName = res.userName;
this.telNumber = res.telNumber;
}
}
思路:使用过滤器实现
filters: {
// 隐藏联系方式敏感信息,18661234567 =》 1866****567
// value 过滤器前面的值
hidePhone(value) {
// 数据处理
// 第一步,截取字符串的前四位
const start = value.substr(0, 4)
// 第二步,从字符串末尾截取三位
const end = value.substr(-3)
return start + '****' + end;
}
}
1、当用户没有选择地址的时候,提示用户选择地址
uni.showToast
2、当选择地址,没有登录,跳转到登录页
3、当选择地址并且登录,发起支付
用户登录完整代码
async userInfoCallBack(e){
console.log(e)
// 记录用户信息到本地缓存
uni.setStorage({
key: 'userInfo',
data: e.detail.userInfo
})
// 获取加密数据
const {
encryptedData, iv, rawData, signature } = e.detail;
// 获取code属性,通过登录接口获取
const [error, res] = await uni.login()
const {
code } = res;
// 发送网络请求登录
const [LoginError, loginRes] = await uni.request({
url: 'https://api-hmugo-web.itheima.net/api/public/v1/users/wxlogin',
method: 'POST',
data: {
encryptedData, iv, rawData, signature, code
}
})
if (loginRes.data.meta.status === 200) {
// 登录成功,把token存储到本地
uni.setStorageSync('token', loginRes.data.message.token)
// 提示用户登录成功,并且返回上一页
uni.showToast({
icon: 'none',
title: '登录成功'
})
uni.navigateBack()
} else {
uni.showToast({
icon: 'none',
title: '服务器异常,请重新点击登录'
})
}
}
思路:
1、判断用户是否已经选择地址,或者是否已经登录,处理三种不同状态
2、创建订单
3、创建预支付订单
4、调用支付api发起支付请求
5、支付完毕,跳转到订单页
// 支付按钮事件
async pay() {
const token = uni.getStorageSync('token');
// 第一种情况,没有选择地址
if (!this.isShowAddress) {
uni.showToast({
title: '请选择收货地址',
icon: 'none'
})
// 当选择地址,没有登录,跳转到登录
// 判断token是否存在
} else if (!token) {
uni.navigateTo({
url: '/pages/login/index'
})
} else {
// 生成订单
// 准备提交给服务端的商品数据
const requestProduct = this.cart.map(item => {
return {
goods_id: item.goods_id, goods_number: item.amount, goods_price: item.goods_price }
})
const [orderError, orderRes] = await uni.request({
url: 'https://api-hmugo-web.itheima.net/api/public/v1/my/orders/create',
method: 'POST',
header: {
Authorization: token
},
data: {
order_price: this.totalPrice,
consignee_addr: this.address,
goods: requestProduct
}
})
if (orderRes.data.meta.status === 200) {
// 发起支付
const [payError, payRes] = await uni.request({
url: 'https://api-hmugo-web.itheima.net/api/public/v1/my/orders/req_unifiedorder',
method: 'POST',
header: {
Authorization: token
},
data: {
order_number: orderRes.data.message.order_number
}
})
const [requestPaymentError, requestPaymentRes] = await uni.requestPayment(payRes.data.message.pay);
// 支付成功
if (!requestPaymentError) {
uni.showToast({
icon: 'none',
title: '支付成功'
})
uni.redirectTo({
url: '/pages/order/index'
})
}
}
}
}
// 清除已经结算的商品
async clearPaymentProduct() {
// 删除已经结算的商品
// 结算的商品列表 this.cart
// 本地缓存的购物车 uni.getStorage('cart')
// 把本地缓存的购物车删除已经结算的商品
const [err, originCart] = await uni.getStorage({
key: 'cart'
})
const notPayCart = originCart.data.filter(item => {
return !item.checked;
})
uni.setStorage({
key: 'cart',
data: notPayCart
})
}
1、显示会员头像和昵称
1.1 通过token判断用户是否已经登录
1.2 如果token存在,就认为已经登录,读取本地缓存的userInfo,显示相应的数据
2、当会员未登录,显示登录按钮
checkLogin() {
// 通过Token判断是否已经登录
const token = uni.getStorageSync('token');
if (token) {
// 已经登录
const userInfo = uni.getStorageSync('userInfo');
console.log(userInfo)
this.nickName = userInfo.nickName;
this.avatarUrl = userInfo.avatarUrl;
this.isLogin = true;
} else {
// 未登录
this.isLogin = false;
}
}
3、订单导航按钮
// 跳转到订单列表
goToOrderList(orderType) {
uni.navigateTo({
url: '/pages/order/index?orderType=' + orderType
})
}
4、拨打电话
callPhone() {
uni.makePhoneCall({
phoneNumber: '400-666-888'
})
}
5、平台差异处理
条件编译是用特殊的注释作为标记,在编译时根据这些特殊的注释,将注释里面的代码编译到不同平台。
**写法:**以 #ifdef 或 #ifndef 加 %PLATFORM% 开头,以 #endif 结尾。
例子:如果只希望在微信小程序平台显示
<view class="info">
<view class="iconfont icon-banben">view>
<view class="name">当前版本view>
<view class="value">v4.1.1view>
view>
通过运行时判断
判断是否为iphoneX
// 获取平台的信息
const sysInfo = uni.getSystemInfoSync()
this.isIphoneX = sysInfo.model === 'iPhone X';
订单日期处理
// 使用filter实现日期的转换
filters: {
timestampToDisplay(timestamp) {
// 把timestamp转换为以毫秒为单位的格式
const date = new Date(timestamp * 1000)
// 获取年
const year = date.getFullYear();
// 获取月
const month = date.getMonth() + 1;
// 获取日
const day = date.getDate();
// 时
const hour = date.getHours();
const min = date.getMinutes();
const sec = date.getSeconds();
return `${
year}/${
month}/${
day} ${
hour}:${
min}:${
sec}`
}
}
1、初始化项目
npm init -y
2、安装第三方模块
npm install dayjs
3、使用第三方模块
import dayjs from 'dayjs'
1、问题列表
思路:定义一个数组,然后通过循环显示到页面上
2、选中状态的切换
思路:
2.1 在data定义一个选中的数组
2.2 定义一个方法,当点击选项,如果存在,就移除数据,否则就加入数组
2.3 页面上,通过三元运算,显示active状态
select(e) {
// 如果已经存在selectedQuestion,要去掉,否则加入
const index = this.selectedQuestion.findIndex(item => {
return item === e;
})
if (index > -1) {
// 找到
this.selectedQuestion.splice(index, 1);
} else {
// 没找到
this.selectedQuestion.push(e);
}
}
3、选择图片
async chooseImage() {
// 判断是否超出选择的限制
if (this.uploadImags.length >= 4) {
// 已经不能再选
uni.showToast({
icon: 'none',
title: '已经超出选择限制'
})
} else {
// 判断用户应选择了多少张
// 剩余还能选多少张
const canChoose = 4 - this.uploadImags.length;
const [error, res] = await uni.chooseImage({
count: canChoose})
this.uploadImags = this.uploadImags.concat(res.tempFilePaths);
}
}
线上版本:需要微信审核,审核完毕后,所有普通用户都可以使用
开发版本:开发者通过微信开发者工具,点击上传之后的版本,这个版本只有开发者权限的人员查看
体验版本:给只有体验权限的人员使用,一般给产品经理或者测试人员使用
为什么微信需要限制包的大小?
当第一次访问的时候,如果包太大,会导致加载速度变慢
主包放资源策略?
主包限制是2M
非必要的(用户第一次加载的时候没用到的代码、图片资源)资源不要放在代码里
必要的资源就放在主包
分包策略
微信允许我们把代码分到不同包,分为主包和分包
主包的加载时机:当用户扫描小程序二维码就会加载
分包加载时机:当用户使用到分包的内容时才会加载
把什么页面放到分包?
把使用率较低的页面放到分包
怎么实现分包?
1、创建分包目录,然后把分包页面放到分包目录下
"subPackages": [{
"root": "pageA",
"pages": [{
"path": "feedback/index"
}]
}],
root 表示分包根目录
pages:页面数组
path:相对路径
分包注意事项:
原来跳转页面的路径需要更改为分包的路径
微信总包的限制?
目前小程序分包大小有以下限制: