# mini-program_230313
**Repository Path**: newsegmentfault/mini-program_230313
## Basic Information
- **Project Name**: mini-program_230313
- **Description**: mini-program_230313
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2023-08-30
- **Last Updated**: 2023-09-10
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 小程序基础
### 小程序介绍
1. Mini Program,是一种不需要下载安装即可使用的应用 ( 张小龙对其的定义是无需安装,用完即走,实际上是需要安装的,只不过小程序的体积特别小, 下载速度很快,用户感觉不到下载的过程 )
2. 小程序提供了一个简单、高效的应用开发框架和丰富的组件及API,帮助开发者在微信中开发具有原生 APP 体验的服务。
3. 小程序刚发布的时候要求压缩包的体积不能大于1M,,否则无法通过,在2017年4月做了改进,由原来的【1M】提升到【2M】;
4. 2017年1月9日0点,万众瞩目的微信第一批小程序正式低调上线。
**优点**
1. 同App进行互补,提供同app类型的功能,比app使用方便简洁
2. 通过扫一扫或者在微信搜索即可下载
3. **用户使用频率不高,但又不得不用的功能软件,目前看来小程序是首选**
4. 开发门槛低, 成本低
**准备工作**
1. 官网注册:https://mp.weixin.qq.com/
2. 微信开发工具
开发工具IDE文件夹中获取
3. 微信开发下载地址
https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=2018315
### 小程序注册
建议在PC完成注册,需要邮箱验证,需要实名认证
注册目的:获取到小程序的 AppID(小程序唯一标识) ,注册成功后在 【开发管理】-> 【开发设置】页面获取
注意:如果要选择小程序类目不要选择小游戏
### 编辑器创建项目 - minidemo
注意:创建项目的时候,项目路径是空路径可以选择模板,不是空路径(路径下有文件夹)不能选择模板
介绍编辑器如何使用
### 项目文件介绍
```js
|- pages 存放页面的文件夹
|- index index页面
|- index.js index页面js文件
|- index.json index页面json配置文件
|- index.wxml index页面wxml文件(可理解为index页面的html)
|- index.wxss index页面wxss文件(可理解为index页面的css)
|- logs logs页面
|- utils 所有工具类方法放在该文件夹
|- util.js 存放工具类方法文件
|- .eslintrc.js eslint规则配置
|- app.js 整个小程序应用的js文件
|- app.json 整个小程序应用的json配置文件(修改导航条、配置页面)
|- app.wxss 整个小程序应用的wxss文件(可理存放全局的css样式)
|- project.config.json 整个小程序应用的配置文件
|- project.private.config.json 整个小程序应用的私有配置文件,会覆盖project.config.json中的内容
|- sitemap.json 配置当前小程序哪些页面可以被微信检索到
```
**小程序特点**
1. **没有完整的浏览器对象,没有DOM,BOM操作**
2. 组件化开发
3. 逻辑层和渲染层分开(逻辑层不会阻塞渲染层的渲染)
正常的浏览器环境中js加载执行会影响页面渲染
4. 体积小,单个压缩包体积不能大于2M,否则无法上线
5. 小程序的四个重要的文件
| ***.wxml** | ***.wxss** | ***.js** | ***.json** |
| ---------- | ---------- | -------- | ------------ |
| View结构 | View样式 | View逻辑 | View配置文件 |
### 单向数据绑定和双向数据绑定
```js
Page({
data: {
id: 1001,
firstName: '尼古拉斯',
lastName: '赵四',
age: 18
},
})
姓: {{ firstName }}
名: {{ lastName }}
年龄: {{ age }}
-----------------
```
### 事件绑定
```js
Page({
data: {
id: 1001,
firstName: '尼古拉斯',
lastName: '赵四',
age: 18
},
changeName() {
console.log('触发点击事件', this.data)
// this.data.lastName = '王五' // 错误的
this.setData({
lastName: '王五'
})
}
})
姓: {{ firstName }}
名: {{ lastName }}
年龄: {{ age }}
-----------------
bindtap 绑定事件 属性值直接跟回调
回调直接写在和data同级的配置项即可
读取数据
在js中读取数据的时候 使用 this.data.xxx
修改数据
在js中修改响应式数据使用 this.setData
this.setData({
xxx: '数据'
})
```
### 关于事件参数
```js
Page({
data: {
id: 1001,
firstName: '尼古拉斯',
lastName: '赵四',
age: 18
},
changeName(e) {
console.log('触发点击事件', e.currentTarget.dataset.abc)
console.log('触发点击事件', e.currentTarget.id)
}
})
姓: {{ firstName }}
名: {{ lastName }}
年龄: {{ age }}
-----------------
关于事件回调的参数
在wxml中,回调后面不能跟小括号,没有小括号这一说,直接放的就是回调的名字
此时在回调中是有默认参数的,默认参数是e,是事件对象(和浏览器的事件对象长得不一样)
传参只能通过给元素绑定属性传参
绑定以data-开头的属性,在回调的事件对象e中可以获取到
e.currentTarget.dataset.abc
如果绑定的是id的话,比较特殊,获取的时候
e.currentTarget.id
```
### 列表循环
```js
Page({
data: {
list: [
{ id: 1001, content: '唱' },
{ id: 1002, content: '跳' },
{ id: 1003, content: 'rap' },
{ id: 1004, content: '篮球' },
]
},
})
{{ index }} -- {{ item.content }}
------------------
{{ idx }} -- {{qwer.content}}
```
### 条件渲染
```js
Page({
data: {
isShow: false,
sex: 2 // sex: 0, 1, 2
},
})
表白成功
表白失败
-----------------
男
女
未知
```
### 模板使用
`/pages/tempA/tempA.wxml`
```js
{{ username }}
抽烟
喝酒
烫头
```
`/pages/tempA/tempA.wxss`
```css
.content {
color: red
}
```
使用模板
```html
使用模板
```
```css
@import "/pages/tempA/tempA"
```
### 生命周期
官方图示:https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/page-life-cycle.html

| **生命周期函数** | **执行时机** | **执行次数** | **特点说明** |
| ---------------- | ------------------------ | ------------ | ------------------------------------------------------------ |
| **onLoad** | 页面加载时触发 | 1 | 可以通过参数获取打开当前页面路径中的参数 |
| **onShow** | 页面显示/切入前台时触发 | 多次 | 如页面没有被销毁,会重复多次显示隐藏,可以在此发送请求获取最新数据 |
| **onReady** | 页面初始化渲染完成时触发 | 1 | 可以同UI界面进行交互 |
| **onHide** | 页面隐藏/切入后台时触发 | 多次 | [wx.navigateTo](https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.navigateTo.html) 或底部 tab 切换到其他页面,小程序切入后台等 |
| **onUnload** | 页面卸载时触发 | 1 | [wx.redirectTo](https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.redirectTo.html)或[wx.navigateBack](https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.navigateBack.html)到其他页面时 |
**个人见解**
官网生命周期图示是错误的;错误部分:标注onLoad及onShow执行的位置不对
参考:**小程序启动执行的所有流程**
参考地址:
[**https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips/start_process.html**](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips/start_process.html)
### 小程序适配方案: rpx (responsive pixel响应式像素单位)
小程序适配单位: rpx
规定任何屏幕下宽度为750rpx
小程序会根据屏幕的宽度不同自动计算rpx值的大小
Iphone6下: 1rpx = 1物理像素 = 0.5px

# 慕尚花坊
## 项目介绍
导入已完成项目,看一下大概要做拿一些东西
介绍了接口文档:
接口文档地址: http://39.98.123.211:8300/doc.html
https请求的base路径:https://gmall-prod.atguigu.cn
## 项目准备
创建项目 - mini_flower
把项目中没用的页面删除掉,只留下 首页
把 static 静态的图片粘贴过来
使用 vscode 开发小程序,把项目用 vscode 打开,同时下载插件

## 导航设置
app.json
```json
{
"pages":[
"pages/index/index"
],
"window":{
"navigationBarBackgroundColor": "#9c0211",
"navigationBarTitleText": "慕尚花坊",
"navigationBarTextStyle":"white"
},
}
```
## 首页轮播
1. 静态搭建
```html
```
2. api准备 - 初始化数据展示
## 封装 request.js 文件
api准备要发请求,发请求得有 request.js 文件(这个文件在之前是对 axios 的二次封装),我们这里不用 axios,微信有自己发请求的api,但是需要封装
utils/request.js
```js
const baseURL = `https://gmall-prod.atguigu.cn`
export default function request({ // 使用对象接收参数不需要考虑参数顺序问题
url,
data,
method = 'get',
header = {'content-type':'application/json'},
timeout = 60000,
}) {
// 在创建promise实例的时候,传入一个构造器函数,这个函数是立即执行的
// 这个函数有两个参数 resolve, reject
// 当resolve调用的时候,此时这个promise状态才由 pendding -> 成功
// 当reject调用的时候,此时这个promise状态才由 pendding -> 失败
return new Promise((resolve, reject) => {
wx.request({
url: baseURL + url, // 地址
data, // 参数
method, // 请求方式
header, // 请求头
timeout, // 超时时间
dataType: 'json', // 返回数据格式
responseType: 'text', // 响应文本类型
success: (result)=>{ // 请求成功回调
// result 是响应的所有内容
// result.data 是响应体
let res = result.data // 是响应体
if (res.code == 200) {
resolve(res.data)
} else {
// 错误的统一处理
console.error(res) // 给程序员看的
// 谈个弹框,给个提示
reject(res.message)
}
},
fail: (err)=>{ // 请求失败回调
reject(err)
},
complete: ()=>{ // 不管请求成功或失败都会执行
}
});
})
}
// let result = await request({
// url: '/xxxxx',
// data: {xxx},
// })
```
page/index/index
```js
// 测试代码
import request from "../../utils/request"
async function getData() {
let result = await request({
url: '/mall-api/index/findBanner'
})
console.log('请求回来的数据', result)
}
getData()
```
## 优化 request.js 文件
1. 添加错误处理的提示
```js
wx.showToast({
title: '请求失败',
icon: 'error',
});
```
2. 添加 loading 状态
在请求之前调用
```js
wx.showLoading({
title: "正在加载"
});
```
不管请求成功还是失败都调用隐藏loading
```js
complete: ()=>{ // 不管请求成功或失败都会执行
wx.hideLoading(); // 隐藏弹框
}
```
## 首页轮播 - 获取数据展示
2. api 准备
```js
import request from '../utils/request'
// 获取轮播数据
export const reqBannerList = () => {
return request({
url: `/mall-api/index/findBanner`
})
}
```
初始化数据获取 - pages/index/index.js
```js
Page({
data: {
bannerList: []
},
async getBannerList() {
try {
let result = await reqBannerList()
this.setData({
bannerList: result
})
} catch (error) {
console.error(error)
}
},
onLoad: function (options) {
this.getBannerList()
}
})
```
初始化数据展示 - pages/index/index.wxml
```html
```
**关于路径映射@**
在 app.json 中配置
```json
{
"resolveAlias": {
"@/*": "/*"
},
}
```
## 首页导航
1. 静态搭建
```html
{{ item.name }}
注意: image 组件的 mode="widthFix" 是设置宽度,保持图片的宽高比不变,自己去算高度
/* 导航 */
.nav-container {
margin: 10rpx 0;
width: 100%;
display: flex;
flex-wrap: wrap; /*允许flex中的item换行*/
}
.nav-view {
width: 20%;
display: flex;
flex-direction: column; /* flex主轴变垂直方向 */
align-items: center;
}
.nav-img {
margin: 20rpx 0;
width: 60%;
}
.nav-img.small {
width: 30%;
}
.nav-title {
font-size: 28rpx;
}
```
2. api准备 - 初始化数据展示
```js
// 获取导航列表数据 - /mall-api/index/findCategory1
export const reqNavList = () => {
return request({
url: `/mall-api/index/findCategory1`
})
}
```
初始化请求数据 pages/index/index.js
```js
async getNavList() {
try {
let result = await reqNavList()
console.log(result)
this.setData({
navList: result
})
} catch (error) {
console.error(error)
}
},
onLoad: function (options) {
...
this.getNavList()
}
```
页面循环展示数据即可
## 活动图片
没有接口,使用mock数据,讲mock粘贴到根目录下,然后在页面中引入
```js
import { backgroundImg } from '../../mock/swiper'
Page({
data: {
...
backgroundImg: backgroundImg, // 活动图片
}
}
```
页面中直接使用即可
```html
```
## goods-list 静态
在 /components/goods-list文件夹下右击新建一个组件(微信开发者工具中新建)
在页面的 index.json 配置文件中去使用组件
```json
{
"usingComponents": {
"goods-list": "/components/goods-list/goods-list"
}
}
```
此时在页面中就可以使用 `` 组件了
搭建组件静态,中间列表区域再封装一个组件
```html
猜你喜欢
列表
查看更多
```
```css
.goods-container {
margin-top: 40rpx;
width: 100%;
}
.title {
font-size: 32rpx;
text-align: center;
margin-bottom: 20rpx;
}
.more {
margin: 0 40rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
background-color: #fff;
font-size: 32rpx;
text-align: center;
}
```
## goods-card静态
```html
Luv甜蜜Luv甜蜜Luv甜蜜Luv甜蜜
再一次触动妳的心.轻启一段甜蜜的恋爱物语. 用途: 情人、生日、追求她
¥ 1399
¥ 1599
Luv甜蜜Luv甜蜜Luv甜蜜Luv甜蜜
再一次触动妳的心.轻启一段甜蜜的恋爱物语. 用途: 情人、生日、追求她
¥ 1399
¥ 1599
```
```css
.list {
width: 100%;
display: flex;
flex-wrap: wrap; /*允许折行*/
justify-content: space-around;
}
.goods {
width: 45%;
background-color: #fff;
margin-bottom: 20rpx;
}
.goods-img {
width: 100%;
}
/* 内容 */
.content {
padding: 10rpx;
}
/* 标题 */
.title {
font-size: 28rpx;
font-weight: bold;
/* 单行省略号css样式 */
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
/* 描述 */
.desc {
height: 64rpx;
margin: 20rpx 0;
font-size: 24rpx;
color: #999;
/* 多行省略号 */
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
/* 价格 */
.info {
display: flex;
font-size: 24rpx;
align-items: center;
}
.price {
margin-right: 20rpx;
}
.oldprice {
color: #666;
margin-right: 40rpx;
text-decoration: line-through; /*中间画横线*/
}
.cart {
width: 40rpx;
height: 40rpx;
}
```
## goods-list 获取数据动态显示
1. 组件传参 - 把标题"猜你喜欢", "人气推荐"传入到组件中
怎么传? --- 标签直接帮属性即可
``
怎么接?
组件内使用 properties 接(不支持数组,支持对象和配置对象)
```js
// 不支持数组写法
// properties: ["title"],
// 支持对象写法
// properties: {
// title: String,
// },
// 支持配置对象写法
properties: {
title: {
type: String,
required: true
},
},
```
2. 准备api、首页初始化获取数据,获取到数据传给 goods-list 组件,goods-list 组件拿到数据之后再传给 goods-card 组件
准备api
```js
// 猜你喜欢接口 - /mall-api/index/findListGoods
export const reqLikeList = () => {
return request({
url: `/mall-api/index/findListGoods`
})
}
// 人气推荐接口 - /mall-api/index/findRecommendGoods
export const reqRecommendList = () => {
return request({
url: `/mall-api/index/findRecommendGoods`
})
}
```
首页初始化调用
```js
async getLikeList() {
try {
let result = await reqLikeList()
console.log(result)
this.setData({
likeList: result
})
} catch (error) {
console.error(error)
}
},
async getRecommendList() {
try {
let result = await reqRecommendList()
console.log(result)
this.setData({
recommendList: result
})
} catch (error) {
console.error(error)
}
},
onLoad: function (options) {
...
this.getLikeList() // 获取猜你喜欢数据
this.getRecommendList() // 获取人气推荐数据
}
```
传给 goods-list
```html
```
goods-list 接数据
```js
properties: {
title: {
type: String,
required: true
},
list: {
type: Array,
required: true
}
},
```
goods-list 接完数据再原封不动给 goods-card
```html
```
goods-card 接数据
```js
properties: {
list: Array
},
```
循环展示数据
```html
{{ item.name }}
{{ item.floralLanguage }}
¥ {{item.price}}
¥ {{item.marketPrice}}
```
## 底部tabbar 配置
地址: https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar
app.json 文件中配置
```json
"tabBar": {
"color": "#333",
"selectedColor": "#ff582f",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "/static/tabbar/home-icon1.png",
"selectedIconPath": "/static/tabbar/home-icon1-1.png"
},
{
"pagePath": "pages/category/category",
"text": "分类",
"iconPath": "/static/tabbar/home-icon2.png",
"selectedIconPath": "/static/tabbar/home-icon2-2.png"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "/static/tabbar/home-icon3.png",
"selectedIconPath": "/static/tabbar/home-icon3-3.png"
},
{
"pagePath": "pages/personal/personal",
"text": "我的",
"iconPath": "/static/tabbar/home-icon4.png",
"selectedIconPath": "/static/tabbar/home-icon4-4.png"
}
]
},
```
## 分类页面
1. 静态搭建
页面宽高设置100%是相对于父级的,而父级是 page 这个元素,这个元素需要设置 宽高100%才可以,这个元素样式在 app.wxss 中设置,这是个全局的,每个页面的根元素都是 page 这个元素
```html
爱礼精选
真情告白
真情告白
真情告白
真情告白
```
```css
.category-container {
width: 100%;
height: 100%;
display: flex;
}
/* 左 */
.menu {
width: 25%;
height: 100%;
background-color: #eee;
}
.menu-list {
width: 100%;
height: 100%;
}
.menu-item {
position: relative;
width: 100%;
height: 70rpx;
line-height: 70rpx;
font-size: 24rpx;
text-align: center;
}
.menu-item.active {
background-color: #fff;
}
.menu-item.active::before {
position: absolute;
left: 10px;
top: 15%;
content: "";
display: block;
width: 4rpx;
height: 70%;
background-color: red;
}
/* 右 */
.content {
width: 75%;
height: 100%;
}
.content-title {
padding: 20rpx;
}
.content-list {
display: flex;
flex-wrap: wrap;
}
.content-item {
width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
}
.content-img {
width: 40%;
margin: 20rpx 0;
}
.content-name {
font-size: 24rpx;
}
```
注意:scroll-view 如果要使用flex布局,需要设置enable-flex属性
2. 初始化数据展示
api准备
```js
export const reqCategoryTree = () => {
return request({
url: `/mall-api/index/findCategoryTree`
})
}
```
初始化页面(onLoad)发请求,获取数据,展示数据
```js
data: {
categoryList: [], // 左侧列表数据
activeIndex: 0, // 选中左侧的下标,默认是0
contentList: [] // 右侧展示的数组
},
async getCategoryTree() {
try {
let result = await reqCategoryTree()
this.setData({
categoryList: result,
contentList: result[this.data.activeIndex].children
})
} catch (error) {
console.error(error)
}
},
onLoad(options) {
this.getCategoryTree()
}
```
拿到数据循环展示即可
3. 交互
```js
左侧循环渲染项绑定index
changeContent(e) {
let activeIndex = e.currentTarget.dataset.index
let contentList = this.data.categoryList[activeIndex].children
this.setData({ activeIndex, contentList })
},
```
分类中右侧的每个内容(item),点击可以进入商品列表
## 商品列表
创建商品列表页面,/pages/goods/list/list
问: 商品列表页目前有几个入口?(点击哪里可以进入商品列表页?)
目前已知的有两个入口,首页的"查看更多"和分类页右侧的二级分类点击可以进入
问: 这两个进入页面之后有什么差异?

这里面差了一个query参数, category2Id,
> 注意: 从分类页右侧的二级分类进入,需要携带点击的参数(category2Id就是二级分类的Id)
>
> 从首页的"查看更多"进入,不需要携带参数
### 跳转页面
使用标签和js都可以跳转
1. 标签跳转
```html
```
同时携带了 query 参数(路由带参)
2. js跳转
```js
wx.navigateTo({
url: '/pages/goods/list/list',
});
```
没带参数
> 注意:
>
> 页面中的方法在配置项中设置的时候和data同级
>
> 组件中的方法需要放在 methods 配置项中
### 初始化数据展示
api准备
```js
export const reqGoodsList = (page, limit, data = {}) => {
return request({
// url: `/mall-api/goods/list/${page}/${limit}?category2Id=${category2Id}`
url: `/mall-api/goods/list/${page}/${limit}`,
// data: {
// category2Id: category2Id
// }
data //有data就拼接上,没有就不拼接
})
}
```
页面初始化的时候调用接口
```js
data: {
page: 1,
limit: 10,
category2Id: undefined, // 请求需要携带的参数
goodsList: [] // 商品列表数据
},
// 初始化数据
async getGoodsList() {
// 组转数据
const { page, limit, category2Id } = this.data
let data = {}
if (category2Id) {
data.category2Id = category2Id
}
// 发送请求
try {
let result = await reqGoodsList(page, limit, data)
this.setData({
goodsList: result.records
})
} catch (error) {
console.error(error)
}
},
onLoad(options) {
let category2Id = options.category2Id
if (category2Id) {
this.setData({ category2Id })
}
this.getGoodsList()
}
```
拿到数据去页面展示(注意这里要使用 goods-card 组件,需要在 .json 文件中引入 goods-card)
```json
{
"usingComponents": {
"goods-card": "/components/goods-card/goods-card"
}
}
```
展示页面
```html
```
### 交互
1. 当页面滚动到底部的时候,要加载第二页数据,这里是【分页处理】
2. 当页面没有更多数据的时候展示"没有更多了"
3. 当页面没有数据的时候,展示"该分类下无商品"内容
#### 分页处理
1. 页面触底的时候要发送请求,需要页面触底的回调(于data配置项同级)
```js
data: {
......
status: 'more' // 页面发请求的状态,总共有以下几个值: 'more'更多 'no-more'没有更多 'loading'加载中 'error'错误
},
// 页面触底回调
onReachBottom() {
if (this.data.status == 'no-more') {
return
}
// 翻页
this.setData({
page: this.data.page + 1
})
this.getGoodsList()
},
async getGoodsList() {
this.setData({ status: 'loading' })
// 组转数据
const { page, limit, category2Id } = this.data
let data = {}
if (category2Id) {
data.category2Id = category2Id
}
// 发送请求
try {
let result = await reqGoodsList(page, limit, data)
let goodsList = this.data.goodsList.concat(result.records) // 之前列表中的值不能清空
let status = 'more'
if (goodsList.length == result.total) { // 当获取到所有的数据之后,状态改为no-more
status = 'no-more'
}
......
```
> 这里使用 goosList.length 和 totoal 去判断也可以
#### 展示"没有更多了" - 第三方组件库的使用(vant)
打开vant官网,找到小程序版本点开,找到快速上手文档
地址:https://vant-ui.github.io/vant/#/zh-CN/

1. 安装第三方包
```js
npm i @vant/weapp -S --production
```
> 注意:初始化 package.json 指令是 `npm init`
2. 修改 app.json 文件中的 `"style": "v2"` , 把这行代码删掉
3. 配置 project.config.json 文件
```js
{
...
"setting": {
...
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./miniprogram/"
}
]
}
}
```
> 注意:新版的"微信开发者工具"需要把 ` "miniprogramNpmDistDir": "./miniprogram/"`
>
> 改为 ` "miniprogramNpmDistDir": "./"`
4. 点击"微信开发者"中的"工具"下的"构建npm",等待构建完成(等待构建完成之后,根目录会多一个 miniprogram_npm 文件夹),刷新页面
第三方包已经引入成功了,接下来就是使用组件了
1. 引入组件 - 在 "页面.json" 中引入
```js
"usingComponents": {
"goods-card": "/components/goods-card/goods-card",
"van-divider": "@vant/weapp/divider/index"
}
}
```
2. 页面中写组件的标签
```js
没有更多了
```
#### 展示"该分类下无商品"内容
页面的5中状态(思想)
```js
data: {
......
status: 'more'
// 页面发请求的状态,总共有以下几个值:
// 'more'更多(允许触底发请求)
// 'no-more'没有更多(展示刚刚引入 van-divider)
// 'empty'没有商品 (目前没有展示)
// 'loading'加载中(不处理,request封装处理过)
// 'error'错误(不处理)
}
```
状态默认是"more"
发请求,在返回的 total 和 goodsList 数组中商品的数量一致的时候,此时状态应该是 "no-more"
发请求,返回的 total 是0的情况下,状态应该是 "empty"
现在要做的事情是,在"empty"状态下,展示页面,要使用组件
```json
"usingComponents": {
...
"van-empty": "@vant/weapp/empty/index",
"van-button": "@vant/weapp/button/index"
}
```
页面结构
```html
正常展示即可
查看其他商品
```
## 个人中心
CV过来个人中心和登陆(去资料中的todo),这里需要注意的是,个人中心的自定义导航,如何设置,在"psersonal.json"文件中设置
```json
{
"usingComponents": {},
"navigationStyle": "custom" -----> 自定义导航
}
```
点击个人中心的头像跳转登陆页
### 登陆思路
```js
之前的做法 - 废弃了(了解即可)
wx.getUserProfile({
desc: '获取个人信息',
success(res) {
console.log(res)
}
})
```
废弃原因是因为有些小程序恶意获取用户的头像昵称,废弃了这个接口之后,微信提供了新的方式获取
「头像昵称填写能力」: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html
知道有这么回事就行了,目前研究新的登陆

### 登陆流程
1. 调用 `wx.login()` 获取code
> code是临时标识,只有5分钟内有效
```js
// 点击登陆按钮回调
getUserProfile() {
wx.login({
success: (result) => {
this.login(result.code)
},
});
}
```
2. 拿着code调用后端接口,获取token,存储token
api准备
```js
// 登陆获取token接口 - /mall-api/weixin/wxLogin/{code}
export const reqLogin = (code) => {
return request({
url: `/mall-api/weixin/wxLogin/${code}`
})
}
```
js方法
```js
async login(code) {
if (!code) {
wx.showToast({ title: '登陆失败', icon: 'error' });
return
}
try {
let result = await reqLogin(code)
// 存起token
// key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
wx.setStorageSync("TOKEN", result.token);
// 登陆完事了,需要获取个人用户信息
this.getUserInfo(result.token)
} catch (error) {
console.error(error)
}
},
```
> wx.setStorageSync(key, value)
>
> 每个项最大1M,所有的项最大10M(以用户为维度)
3. 获取个人信息
api准备
```js
// 获取用户信息 - /mall-api/weixin/getuserInfo
export const reqUserInfo = () => {
return request({
url: `/mall-api/weixin/getuserInfo`
})
}
```
js方法
```js
// 获取个人信息 - 注意,请求头中要携带token
async getUserInfo() {
try {
let result = await reqUserInfo()
wx.setStorageSync("USERINFO", result); // 这里可以直接存对象
wx.navigateBack(); // 回退上一页
} catch (error) {
console.error(error)
}
}
```
> 需要在请求头中携带token,封装request函数中
>
> ```js
> // 携带token
> let token = wx.getStorageSync("TOKEN");
> if (token) {
> header.token = token;
> }
> ```
4. 获取到个人信息之后,存储,跳转回"个人中心"页面
> 跳转到tabbar页面,关闭所有非tabbar页面
>
> ```js
> wx.switchTab({
> url: '/pages/personal/personal'
> });
> ```
## 头像编辑界面
目的:这个界面是为了解决用户头像不展示问题(不展示是由于清除文件缓存引起的)
界面从哪里进入? -- 获取完个人信息之后进入,登陆状态下点击头像可以编辑(两个入口)
界面怎么写?
参考连接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html
搭建静态:
/pages/user-edit/user-edit
```html
昵称
```
```css
.user-edit {
padding-top: 200rpx;
width: 100%;
height: 100%;
background-color: #eee;
}
.avatar-wrapper {
width: 200rpx;
height: 200rpx;
margin-bottom: 200rpx;
padding: 0;
}
.avatar {
width: 200rpx;
height: 200rpx;
}
.nickname-wrapper {
display: flex;
border-top: 0.5rpx solid #ccc;
border-bottom: 0.5rpx solid #ccc;
padding: 20rpx 0;
background-color: #fff;
}
.nickname-wrapper .label {
padding: 0 40rpx;
}
.operate {
margin-top: 80rpx;
display: flex;
justify-content: space-evenly;
}
.operate button {
margin: 0;
}
```
```js
import { reqEditUser } from '@/api/index'
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
Page({
data: {
headimgurl: defaultAvatarUrl,
nickname: ''
},
onChooseAvatar(e) {
console.log(e.detail)
const { avatarUrl } = e.detail
this.setData({
headimgurl: avatarUrl,
})
},
})
```
#### 回显数据
```js
// 初始化数据展示(回显个人信息数据)
onLoad() {
let userinfo = wx.getStorageSync("USERINFO");
if (userinfo && userinfo.nickname) {
this.setData({
headimgurl: userinfo.headimgurl,
nickname: userinfo.nickname
})
}
}
```
#### 编辑数据
点击保存调用接口保存更新数据,点击取消回到上一个页面
```js
onCancel() {
wx.navigateBack();
},
async onSave() {
const { headimgurl, nickname } = this.data
try {
await reqEditUser({ headimgurl, nickname })
wx.showToast({
title: '保存成功',
icon: 'success',
duration: 1500,
success: (result)=>{
wx.navigateBack(); // 回退上一页
},
});
wx.setStorageSync("USERINFO", { nickname, headimgurl });
} catch (error) {
}
},
```
```html
......
取消
保存
```
注意:
1. button 组件添加 `open-type="chooseAvatar"` 属性点击才能展示出选择头像的弹框
2. input 组件添加 `type="nickname"` 属性才能弹出选择昵称的弹框
这里有一个bug,选择完自己的昵称之后没有双向数据绑定(放一放)
问题:因为 使用的是 `van-field` 代替了原生了 `input`,此时点击这个 input 弹框还会弹出来,但是选择"微信昵称"的时候,得不到最新写入的值
如何解决?
把 `van-field` 组件替换回原生的 `input` 就 ok 了
#### 登陆后立马跳转编辑界面
为什么这么做?
因为小程序新版的获取头像和昵称,头像是灰色默认的,昵称是"微信用户",所以要直接跳转到编辑界面,让用户自己存一个头像和昵称
注意问题:
从个人信息点击编辑进入该页面只跳转了一次
从个人信息点击"登陆",跳转登录页,登陆页点击"登陆"按钮跳转该页面,跳转了两次
目前该页面后退只后退了一次,如何解决后退一次?
将跳转登录页不计入历史记录即可,从login跳转到 user-edit 页面使用 `redirectTo` 即可
```js
// redirectTo关闭当前页面,跳转到下一个页面(当前页面就不在历史记录中了)
wx.redirectTo({
url: '/pages/user-edit/user-edit'
});
```
总结:跳转页面的API
```js
wx.switchTab 跳转到tabbar页面
wx.redirectTo 关闭当前页面,跳转下一个页面,当前页面不在历史记录中
wx.navigateTo 跳转页面(有历史记录)
wx.navigateBack({ delta: 1 }) 回退页面,没有参数默认是1
wx.reLaunch 关闭所有的页面,打开到应用内的某个页面
```
## 商品详情页
应该去写购物车页面,因为购物车页面中没有商品,所以先写商品详情页,添加购物车。
1. 静态搭建
直接粘贴详情页过来,把结构整明白即可
注意:
* 详情页是展示商品的详细信息,需要发请求拿当前商品的详细信息,所以需要商品的 id,id点击的时候传过来,query传参(在goods-card组件的中,把view标签改成navigator标签加url)
```html
```
接参,在详情页中的 onLoad 中接参数
* 样式不对,因为我们这里没有 flex 这个类名
在 app.wxss 这个文件中加上 flex 这个类名即可
```css
.flex {
display: flex;
}
```
2. 初始化数据展示
api准备
页面初始化得到数据,渲染数据
3. 交互
"加入购物车" - 弹出弹框,有选择数量
"立即购买" - 弹出弹框,没有选择数量,数量是1
使用两个变量来控制,`isShowSheet: false` 控制弹框显示 `isShowCount: false` 控制弹框数量显示
给按钮绑定点击事件,修改这两个数据让弹框展示,同时加上关闭弹框的回调
```js
onClose() {
// 隐藏弹框,重置数据
this.setData({
isShowSheet: false,
isShowCount: false,
})
},
// 加入购物车弹框
addCart() {
this.setData({
isShowSheet: true,
isShowCount: true
})
},
// 立即购买弹框
addBuy() {
this.setData({
isShowSheet: true,
isShowCount: false
})
},
```
> 注意:弹框中显示的内容也是从 goodsDetail 中获取的
剩下就是收集数据,调用接口(加入购物车调用接口)或跳转页面(立即购买跳页面)
4. 收集数据,调用接口(调用添加购物车接口)
api准备 - 目的是为了让我们知道传给后端的参数
```js
export const reqAddCart = (goodsId, count, data) => {
return request({
url: `/mall-api/cart/addToCart/${goodsId}/${count}`,
data
})
}
```
然后收集后端需要的参数,调用接口
```js
// 商品数量修改回调
onChange(e) {
this.setData({
count: e.detail // 修改count数量
})
},
// 点击保存
async onSave() {
if (this.data.isShowCount) { // 展示count数量是去购物车的
// 收集的数据
const { goodsId, count, blessing } = this.data
console.log(goodsId, count, blessing)
try {
await reqAddCart(goodsId, count, { blessing })
wx.showToast({
title: '加入购物车成功',
icon: 'success'
});
// 关闭弹窗
this.onClose()
} catch (error) {
console.log(error)
}
} else { // 去立即购买页面(放一放)
}
},
```
> 注意:
>
> 收集 数量的时候使用的是 `van-stepper` 这不是表单元素,不能用双向绑定 `model:value="{{ count }}" ` 得自己写事件
点击去购物车查看,跳转购物车
> 注意:
>
> ``
>
> navigator 标签跳转tabbar 页面的时候需要加 `open-type="switchTab"`
### 商品详情 - 添加购物车遇到的问题
当没有登录的情况下,调用接口是 后端返回的 code 是 208,此时需要让用户登录,这种需要携带 token 的接口可能有很多,我们在写代码的时候可能会忘了,那么就会报 208 的错误,如何解决?
在 request.js 封装中,对 code 是208的情况做兜底处理
```js
wx.request({
...
success: (result)=>{ // 请求成功回调
let res = result.data // 是响应体
if (res.code == 200) {
resolve(res.data)
} else if (res.code == 208) { // 代表未登录 // 让用户跳转登录
// 对调用接口未登录的统一处理(被动处理-兜底方案)
// 对话框提示
wx.showModal({
title: '警告',
content: '未登录,请前往登录',
showCancel: true,
cancelText: '取消',
cancelColor: '#000000',
confirmText: '确定',
confirmColor: '#3CC51F',
success: (result) => {
if(result.confirm){
wx.navigateTo({
url: '/pages/login/login'
});
}
},
});
else {
...
}
```
## 购物车页面
1. 去购物车页面看购物车列表数据,静态搭建,把购物车粘贴过来,了解页面结构后,问:购物车页面有几种状态?
答:无商品、有商品、未登录(未登录的请况下,不能看到购物车中的内容,看看能不能用到兜底策略)
2. 初始化数据展示
api 准备 - 获取列表的api
```js
// 获取购物车列表 - /mall-api/cart/getCartList
export const reqCartList = () => {
return request({
url: `/mall-api/cart/getCartList`
})
}
```
页面初始化的时候调用api展示数据即可
> 注意:未登录是由兜底策略,登录之后还应该返回到购物车页面,此时应该重新发请求,拿数据,请求应该放在 onShow 中
```js
async getCartList() {
try {
let result = await reqCartList()
// 算是否全选
let isCheckAll = result.every(item => item.isChecked) && result.length > 0
// 总金额(【选中的】*【数量】*【金额】)
let totalAmount = result.reduce((prev, item) => prev + item.isChecked * item.price * item.count, 0)
// 保存数据
this.setData({
cartList: result,
isCheckAll,
totalAmount
})
} catch (error) {
console.error(error)
}
},
onShow() {
// 这里需要把请求放在onShow,因为有可能未登陆,等登录要去登录,登录完成回来之后要重新获取数据
this.getCartList()
}
```
拿到数据之后展示数据
* 直接展示 - 循环展示
* 计算展示 - 需要计算展示的内容(全选状态、总价)
3. 交互
* 单选交互
请求需要 goodsId 和 选中状态(0和1)
> 注意:
>
> 1. dataset 拿到的数据都是小写
> 2. 给后端的选中状态不是布尔值而是 0 和 1
* 数量修改交互
> 注意:
>
> 1. 传给后端的是差值,和添加购物车是一个接口
> 2. 输入的时候,频繁触发,把change事件拆成了 blur、plus、minus 单独处理,处理完成后再统一调用接口
* 删除交互
> 注意:
>
> 双重校验
* 全选交互
直接调用接口给后端传 0 和 1
* 结算跳转(后续再说)
```js
// 单选回调 - 单选(两个参数id和选中状态)
async checkChange(e) {
// 规则: 所有dataset中的数据,在标签上绑定的是大写,在js中获取的时候获取的是小写
let goodsId = e.currentTarget.dataset.goodsid // 这里一定要小写
let isChecked = e.detail ? 1 : 0 // 选中状态(接口需要的是0和1)
try {
await reqCheckCart(goodsId, isChecked)
// 刷新数据
this.getCartList()
} catch (error) {
console.error(error)
}
},
// 对于新增数量的修改一定要小心
plusHandler(e) {
// oldcount = e.currentTarget.dataset.oldcount // 之前的值
e.detail = Number(e.currentTarget.dataset.oldcount) + 1 // 之前的值 + 1(手动+1)
this.changeCount(e)
},
minusHandler(e) {
e.detail = Number(e.currentTarget.dataset.oldcount) - 1 // 之前的值 - 1(手动-1)
this.changeCount(e)
},
blurHandler(e) {
e.detail = e.detail.value
this.changeCount(e)
},
// 数量修改 - 需要两个参数 goodsId, count
async changeCount(e) {
console.log('修改数量', e)
if (e.detail < 1) { // 当前输入的值不能小于1
return
}
let goodsId = e.currentTarget.dataset.goodsid // 这里一定要小写
let oldcount = e.currentTarget.dataset.oldcount // 这里一定要小写
let count = e.detail - oldcount // 后端要的是差值
if (count == 0) {
return
}
try {
await reqAddCart(goodsId, count)
// 刷新数据
this.getCartList()
} catch (error) {
console.error(error)
}
},
// 删除商品 - 双重校验
async deleteCart(e) {
console.log('删除', e)
let res = await wx.showModal({
title: '警告',
content: '确认要删除该商品吗?'
});
if (res.confirm) {
let goodsId = e.currentTarget.dataset.goodsid
try {
await reqDeleteCart(goodsId)
// 刷新数据
this.getCartList()
} catch (error) {
console.error(error)
}
}
},
// 全选、全不选
async changeCheckAll(e) {
console.log('全选、全不选', e)
try {
await reqChangeCheckAll(e.detail ? 1 : 0)
// 刷新数据
this.getCartList()
} catch (error) {
console.error(error)
}
},
```
## 订单详情页
进入商品详情页有两个入口: 1. 购物车点击"去结算"进入 2. 在商品详情页点击"立即购买"进入
两个入口进入该页面调用的接口不一样,所以处理的逻辑也不一样

所以在初始化数据的时候,需要做判断,根据来源不同,调用不通接口
书写页面步骤:
1. 静态搭建
直接粘贴过来,需要考虑的问题就是从哪进入该页面
1. 从购物车过来
```js
toOrderDetail() {
wx.navigateTo({
url: '/pages/order-detail/detail'
});
},
```
2. 从商品详情过来
从商品详情过来的时候,需要携带 goodsId(商品ID) 和 blessing(祝福语)
```js
async onSave() {
// 收集的数据
const { goodsId, count, blessing } = this.data
if (this.data.isShowCount) { // 去购物车的
...
} else { // "立即购买" 跳转订单详情
if (!blessing) { // 没有祝福语,不让跳转
wx.showToast({
title: '请填写祝福语',
icon: 'error',
});
return
}
wx.navigateTo({
url: `/pages/order-detail/detail?goodsId=${goodsId}&blessing=${blessing}`
});
}
},
```
> 注意:
>
> 在 `detail.json` 文件中配置 `"navigationBarTitleText": "订单详情",` 当前页面的顶部导航标题文本发生改变
接下来该初始化数据展示,先处理购物车过来的逻辑,后续再处理立即购买过来的逻辑
2. 初始化展示数据
根据进入的入口不通,初始化数据展示调用的接口也不同,分为两套逻辑
1. 购物车进入初始化数据展示
调用两个接口,获取交易信息 和 获取订单地址
准备这两个接口的api
```js
// ------------------
// 获取订单地址 - /mall-api/userAddress/getOrderAddress
export const reqOrderAddress = () => {
return request({
url: `/mall-api/userAddress/getOrderAddress`
})
}
// ------------------
// 确认下单 - 获取交易信息 - /mall-api/order/trade
export const reqTradeInfo = () => {
return request({
url: `/mall-api/order/trade`
})
}
```
页面初始化数据调用接口,拿到数据进行展示
```js
async getTradeInfo() {
try {
let result = await reqTradeInfo()
console.log('购物车交易信息', result)
this.setData({
totalAmount: result.totalAmount,
cartList: result.cartVoList
})
} catch (error) {
console.error(error)
}
},
async getOrderAddress() {
try {
let result = await reqOrderAddress()
console.log('地址信息', result)
this.setData({ addressInfo: result })
} catch (error) {
console.error(error)
}
},
// 接参 - 判断是从哪个页面过来
onLoad(options) {
const { goodsId, blessing } = options
if (goodsId) { // 从商品详情页过来(放一放)
} else { // 从购物车过来
this.getTradeInfo()
this.getOrderAddress()
}
},
```
2. 商品详情"立即购买"进入初始化数据展示
调用两个接口,地址接口 和 立即购买接口,此时地址接口已经有了,只需要准备立即购买接口
api 准备
```js
// 立即购买 - /mall-api/order/buy/{goodsId}
export const reqOrderBuy = (goodsId, data = {}) => { // data -> { blessing: 'xxx' }
return request({
url: `/mall-api/order/buy/${goodsId}`,
data
})
}
```
初始化数据调用接口
```js
// 立即购买 - 初始化数据
async getOrderBuy(goodsId, blessing) {
try {
let result = await reqOrderBuy(goodsId, { blessing })
console.log('立即购买', result)
this.setData({
cartList: result.cartVoList,
totalAmount: result.totalAmount
})
} catch (error) {
console.error(error)
}
},
// 接参 - 判断是从哪个页面过来
onLoad(options) {
const { goodsId, blessing } = options
if (goodsId) { // 从商品详情页过来(放一放)
this.getOrderBuy(goodsId, blessing)
} else { // 从购物车过来
this.getTradeInfo()
}
// 不管从哪个页面进入都需要调用获取地址的接口
this.getOrderAddress()
},
```
3. 交互
* 收集数据交互
收集数据收集4个数据
```js
buyName: '', // 购买人名称
buyPhone: null, // 购买人手机号
deliveryDate: '请选择配送时间', // 期望送达日期
remarks: '', // 客户备注
```
这里只有 deliveryDate 是需要注意的,其他的使用 `model:value` 双向数据绑定收集
收集 `deliveryDate` 需要知道 `van-popup` 组件弹框,还需要知道 `van-datetime-picker` 组件
`van-popup` 组件,使用第三方包 moment 转时间戳
```html
show属性是布尔值用来控制弹框的显示
bind:close="onClose" 点击蒙层位置,在回调中让控制弹框显示的布尔值变成false
```
`van-datetime-picker` 组件
```html
```
moment 使用
```js
1. 安装 npm i moment
2. 去除 app.json 中的 style: v2
3. 添加project.config.json 中的配置项
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./"
}
]
4. 小程序"工具" -> "构建npm"
这里注意,步骤2和步骤3已经做过了,之一只需要步骤1和步骤4
注意: 如果构建失败,从小程序中删除该项目从新打开,再构建(之前构建成功,不是配置问题,就是小程序编译器的问题)
```
> 获取时间戳两种方式:
>
> new Date().getTime()
>
> Date.now()
* 地址选择的交互(一整套逻辑 - 单独说)
## 地址列表
入口:订单详情页点击选择地址的时候可以进入,个人中心界面点击"地址管理"按钮也可以进入地址列表
1. 静态搭建
直接粘贴过来,了解页面结构,分为三部分:1. 地址列表展示 2. 没有地址列表展示 3. 新增地址列表按钮
2. 初始化数据展示
api准备 - 准备进入页面之后,请求地址列表的接口
```js
// 地址列表 - /mall-api/userAddress/findUserAddress
export const reqAddressList = () => {
return request({
url: `/mall-api/userAddress/findUserAddress`
})
}
```
在进入页面之后,请求地址列表的接口,拿到数据进行循环展示
```js
async getAddressList() {
try {
let result = await reqAddressList()
console.log('地址列表 -> ', result)
this.setData({
addressList: result
})
} catch (error) {
console.error(error)
}
},
onLoad(options) {
this.getAddressList()
},
```
3. 交互
1. 新增 - 跳转编辑页面
#### 静态搭建
把页面粘贴过来,在地址列表页,点击新增地址,跳转到编辑界面
熟悉页面结构
#### 交互
收集数据 - api准备,看一下后端需要哪些数据,然后在点击"保存"的时候,调用api传给后端即可
api准备
```js
export const reqAddressAdd = (data) => {
return request({
url: `/mall-api/userAddress/save`,
method: "post",
data
})
}
```
收集数据
这里的所有的数据除了 "地址区域" 和 "是否默认" 需要咱们自己手动处理,其他的都直接双向绑定了
"地址区域" 用的 `picker`
```html
需要自己处理选择之后的回调
```
"是否默认" 用的 `switch`
```html
需要自己处理选择之后的回调
```
点击"保存"按钮的时候,可以校验数据(非空和正则 - 这里我们没做),调用接口,保存
保存成功之后跳转回上一个列表页,此时需要刷新数据,所以列表页获取数据的方法应该在 `onShow` 中调用
2. 编辑 - 回显数据
点击列表中的编辑按钮,跳转编辑界面,回显数据(注意:需要把id传给编辑界面),准备获取地址详情的api,进入编辑界面调用
api准备
```js
export const reqAddressInfo = (id) => {
return request({
url: `/mall-api/userAddress/${id}`,
})
}
```
进入页面调用
```js
// 回显数据
async getAddressInfo(addressId) {
try {
let result = await reqAddressInfo(addressId)
this.setData({
...result,
region: `${result.provinceName}/${result.cityName}/${result.districtName}`
})
} catch (error) {
console.error(error)
}
},
onLoad(options) {
let addressId = options.addressId
if (addressId) {
this.getAddressInfo(addressId)
}
},
```
准备更新api
```js
export const reqAddressUpdate = (data) => {
return request({
url: `/mall-api/userAddress/update`,
method: 'post',
data
})
}
```
收集数据走之前的逻辑,点击"保存"此时应该调用 更新的接口
```js
async onSave() {
// 可以在保存之前做校验(例如: 非空校验 和 正则校验)
try {
if (this.data.id) { // 编辑
await reqAddressUpdate(this.data)
} else { // 新增
await reqAddressAdd(this.data)
}
}
...
}
```
3. 删除 - 双重校验
api准备
```js
export const reqAddressDelete = (id) => {
return request({
url: `/mall-api/userAddress/delete/${id}`,
})
}
```
点击删除按钮双重校验之后,调用接口
```js
async toDelete(e) {
let addressId = e.currentTarget.dataset.id
// 双重校验
let res = await wx.showModal({
title: '警告',
content: '确认要删除该地址吗?',
});
if (res.confirm) { // 确认
try {
await reqAddressDelete(addressId)
// 提示
wx.showToast({
title: '删除成功',
icon: 'success',
});
// 刷新数据
this.getAddressList()
} catch (error) {
console.error(error)
}
}
},
```
4. 选择订单地址功能
当选择地址之后,跳转到上一个页面,如果上一个页面是"订单详情",要刷新地址信息的
api准备
```js
export const reqAddressSelect = (id) => {
return request({
url: `/mall-api/userAddress/selectAddress/${id}`,
})
}
```
点击列表中每一个的时候,调用接口,返回上一页,让上一页重新获取地址数据,让上一页的地址数据得到更新
```js
async toSelect(e) {
let addressId = e.currentTarget.dataset.id
try {
await reqAddressSelect(addressId)
// 回退上一页
wx.navigateBack();
} catch (error) {
console.error(error)
}
},
```
同时把订单详情页的调用获取地址信息的接口放到 onShow 当中
> 问题:
>
> 点击编辑、删除按钮的时候也是在点击当前这个地址的,会触发两个回调
>
> 这里应该将 编辑、删除 按钮绑定事件的 bind 换成 catch
>
> catch 阻止事件冒泡
这里地址列表已经完成,我们是从订单详情页做到了地址列表,现在还返回订单详情页,做结算,那么结算涉及到支付(支付也是面试的重点)
## 支付流程
地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3

流程:
1. 用户下单,发请求给商家服务器(这一步前端做),商家服务器生成订单,然后给腾讯服务发请求,生成预付单,生成腾讯生成预付单的目的是为了知道这个订单要收多少钱,腾讯服务知道收多钱之后反馈给商家服务器,生成支付签名信息返回给小程序
2. 小程序端接收到商家返回的支付签名信息,调用 `wx.requestPayment()` 发起微信支付(这一步前端做)
3. 发起之后要去腾讯服务鉴权,鉴定有没有实名、有没有绑定银行卡,鉴权成功之后告诉小程序可以调起微信支付,此时要去弹出密码输入框(这一步是调用完 `requestPayment`)小程序自己和腾讯服务的交互
4. 小程序弹出密码弹框,用户输入密码,授权付款,钱在腾讯服务上,所以授权要发送给腾讯服务,腾讯服务接到授权后,会把用户账号中的款扣了,把钱划给商家,返回支付成功告诉用户,提示"支付成功",这里的支付成功可以理解为"扣款成功"(这时会弹一个弹框,微信弹的)
在这个步骤的同时,会异步告诉商家服务,该用户已经付款,可以发货
5. 点击微信弹出弹框确认之后,要回到商家的小程序,小程序也要提示用户"订单支付成功",所需小程序发请求给商家服务,商家服务接到请求之后,找腾讯服务确认用户是否支付成功,确认之后,返回给用户支付成功的数据,小程序展示"订单支付成功"页面
步骤:
1. 调用创建订单接口,创建出订单,返回 `orderId`
2. 拿着 orderId 获取支付信息
3. 拿着支付信息调用 `wx.requestPayment()` 去支付
只有支付成功之后,才会走 `wx.requestPayment()` 的 success 回调
4. 在支付成功之后,调用接口查询订单状态(已支付),跳转支付成功页
## 订单列表
点击个人信息页面"商品订单"跳转订单列表
静态搭建,去粘贴过来,报错,因为接口没有,自己写一个接口页面就好了,其他的内容都写好了
## 分包
https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html
什么是分包?
小程序上线代码应该是一个包,可以把这个包拆分成多个,包含一个主包和多个子包,每个包的大小不能超过2M,tabbar 页面只能放在主包
为什么要分包?
1.包体超过2M 2. 优化首次使用小程序加载时间
分包类型:
普通分包、独立分包、分包预下载
### 普通分包
分包原则:
tabbar 页面只能放在主包
子包不能嵌套
子包只能使用当前自己子包和主包的内容(内容包含js、模板、资源)
步骤:
1. 将文件目录改成子包目录

2. 修改 "app.json" 文件
删除主包 "pages" 下的页面路径
配置 "subpackage"

3. 改完之后测试一下,因为页面的路径发生该改变,所以页面中引用内容的路径也需要改变
> 所有涉及到分包的内容都需要去测试以下,防止出错
如何证明我们包分成功了?

### 独立分包
场景:
QQ音乐播放音乐的时候,把播放界面分享出去,此时别人点击音乐播放的时候,直接进入播放页面,此时这个播放界面就是独立分包,不需要依赖主包就可以播放音乐
限制:
独立分包不能使用主包的任何资源(包含js、静态、模板)
如何做?
在分包中配置 `"independent": true` 即可

### 预下载分包
目的:用来提升用户体验的,预先加载其他分包
如何做?
通过在 app.json 配置 `preloadRule` 字段来实现
> 参考: https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/preload.html
# 谷粒微课
## 创建项目
```js
npx degit dcloudio/uni-preset-vue#vite-ts uni_course
```
使用的是vite + TS创建的项目,没有依赖
```js
cd uni_course
npm i
```
安装好依赖就可以启动项目了,在项目启动之前先了解项目目录结构
### 目录结构
需要注意的:
`manifest.json` 文件中要配置 微信小程序的 `appid`

`main.ts`
```ts
import { createSSRApp } from "vue"; // 引入的是 vue 服务端渲染
import App from "./App.vue"; // 根组件
export function createApp() {
const app = createSSRApp(App); // 创建一个app实例,把App根组件传进去
return {
app, // 把实例抛出去
};
}
```
> 客户端渲染:发请求的时候,拿回来的 html 页面只有一个 `` 元素,页面中所有的内容都是由 请求回来的css 和 js 生成的。
>
> 服务端渲染:发请求的时候,拿回来的 html 页面是一个完整的 html 页面
>
> 服务端渲染最大的优点在于 SEO优化 友好,SEO优化是搜索引擎优化,优化的越好,搜索引擎搜出来的时候排名越靠前(不掏钱的前提下)
`App.vue`
发现`App.vue` 根组件中只有 script 和 style,没有 template,为什么?
`App.vue` 的 style 相当于是 小程序中的 `app.wxss`
`App.vue` 的 script 相当于是 小程序中的 `app.js`
`pages.json` 相当于是小程序的 `app.json`,同时把每个页面的 json,也放在了 pages.json 中
### 启动项目
```js
npm run dev:mp-weixin
```
在 package.json 中发现启动微信小程序是该指令,执行之后,创建了一个 dist/dev 目录,在这个目录中由 `mp-weixin` 这个文件夹,这就是将 vue 编译成了小程序代码,使用 "微信开发者工具" 打开该项目,就可以看到了
当执行 `npm run dev:mp-weixin` 时候,`dist/dev/mp-weixin` 这个小程序代码每次都会清空掉重新构建
### 配置项目导航
去 pages.json 中配置
```js
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "谷粒课堂",
"navigationBarBackgroundColor": "#00cc99"
}
```
同时需要注意页面的配置项会覆盖全局的配置项,需要改页面的配置项
把首页的内容清空了
问题:
下载包下载不下来:

> 很多同学这里安装不上,使用课件中准备好的包即可
>
> 但是使用 `npm i` 也是安装不上依赖,可以使用 cnpm
>
> 如果没有 cnpm 在命令行窗口执行 `npm i cnpm -g`,执行完之后,在全局可以使用 cnpm 指令
>
> 然后使用 `cnpm i` 尝试安装依赖
>
> 每次安装依赖失败之后,要把 `node_modules` 删掉之后重新安装依赖
## 首页
在uni中即可以写 div 元素,也可以写原生的小程序标签
### 轮播
静态搭建、初始化数据展示
注意:
1. 使用 less 来写css,没有安装 less会报错,在项命令行执行 `npm i less` 安装 less,安装好后重新启动项目,书写的 less 就好使了
2. 没有轮播的接口,使用mock数据,将项目中的 `src/common/mock/home.ts` 复制到自己的项目相同路径下,使用 `home.ts` 中的数据进行渲染轮播
```vue
```
### nav
静态搭建、mock数据
```vue
{{ item.name }}
import { bannerCateList, hotCateList } from '@/common/mock/home'
const navList = ref(hotCateList)
```
### v-card、v-card-list 静态封装
#### v-card
```vue
{{ title }}
{{ more }} >
```
#### v-card-list
```vue
HTML5实战
20 已学习
¥ 21000
876 人购买
从事Java程序及移动安卓开发及IT培训事业十余年,曾就职于伊利集团作为开发团队主力开发了伊利集团人力资源管理系统、经销商管理系统等项目。后在上海京颐股份参与了公司移动医疗、医疗云、医疗物联网的开发,并在其子公司趣护网负责安卓端应用趣户APP的研发。转型为IT培训讲师后负责MySQL、JavaWeb、SSM框架、Android等课程的研发讲解。
孟老师
...... x3
```
接下来该发请求,调接口,拿数据,展示...
问:我们这里由发请求的工具吗?自己封装一个
之前我们使用的是函数的形式封装的(之前封装的拿过来把 `wx.request` 改成 `uni.request` ),没有用过实例的封装,所以这次写成实例封装的代码
实例封装的代码需要我们先看看 ES6 Class 的写法
### 简单复习ES6 Class
代码参考 `static` 目录下的 `class复习.html`
### 封装请求实例
baseUrl: https://gmall-prod.atguigu.cn/skb
接口文档地址: https://www.yuque.com/xiumubai/fe/gervhh
```ts
interface OptionsModel {
url: string,
data?: any,
method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'
}
class Request {
// 函数简写形式在原型上
// private 私有的,只能在当前类内容调用该方法,外部无法调用
private api(options: OptionsModel) {
return new Promise((resolve, reject) => {
uni.request({
url: options.url,
data: options.data,
method: options.method || 'GET',
header: {'content-type':'application/json'},
dataType: 'json',
responseType: 'text',
success: (result)=>{
resolve(result)
},
fail: (error) => {
reject(error)
},
complete: ()=>{}
});
})
}
get(options: OptionsModel) {
return this.api({ ...options, method: "GET" })
}
post(options: OptionsModel) {
return this.api({ ...options, method: "POST" })
}
put(options: OptionsModel) {
return this.api({ ...options, method: "PUT" })
}
delete(options: OptionsModel) {
return this.api({ ...options, method: "DELETE" })
}
}
export default Request
```
接下来要封装 api 接口,之前写法也是函数式的,现在用类来写
### api类封装 - 封装 CourseRequest 类
```ts
import Request from "@/utils/request";
class CourseRequest extends Request{
// 等号形式,放在当前实例上
// getHomeData = () => {}
// 简写形式,放在当前类的原型上
getHomeData() {
return this.get({
url: '/api/edu/index'
})
}
}
export default new CourseRequest()
```
在 main.ts 中测试自己封装的实例是不是能用(注意这是测试代码,最终要删除的)
```ts
import courseApi from "./api/course";
async function getData() {
let result = await courseApi.getHomeData()
console.log('返回首页的数据', result)
}
getData()
```
> 注意:api 是需要拼接 baseUrl 的
接下来应该去页面中调用接口拿数据
### 给api函数加TS类型(加返回值的类型)


```ts
export interface CourseModel {
buyCount: number
cover: string
gmtCreate: string
gmtModified: string
id: string
lessonNum: number
price: number
status: string
subjectId: string
subjectParentId: string
teacherId: string
title: string
version: number
viewCount: number
}
export interface TeacherModel {
avatar: string
career: string
deleted: boolean
gmtCreate: string
gmtModified: string
id: string
intro: string
level: number
name: string
sort: number
}
interface HomeModel {
courseList: CourseModel[]
teacherList: TeacherModel[] // 暂时写成空数组类型
}
class CourseRequest extends Request{
// 等号形式,放在当前实例上
// getHomeData = () => {}
// 简写形式,放在当前类的原型上
getHomeData() {
return this.get({
url: '/api/edu/index'
})
}
}
export default new CourseRequest()
```
### 初始化页面调用接口拿数据
```js
const courseList = ref([])
const teacherList = ref([])
const getHomeData = async () => {
try {
let result = await courseApi.getHomeData()
console.log('首页数据', result)
courseList.value = result.courseList
teacherList.value = result.teacherList
} catch (error) {
console.error(error)
}
}
onLoad(() => {
getHomeData()
})
```
将数据传递给组件进行展示
```html
```
### uni-ui 组件库使用
官网教程: https://uniapp.dcloud.net.cn/component/uniui/quickstart.html
在 `v-card-list` 组件中,用到了一个 icon ,这个 icon 使用 `uni-ui` 组件库
1. 安装 sass 和 sass-loader
```js
npm i sass sass-loader@10.1.1 -D
```
> 如果安装失败的话,最后加一个 -f 强制安装
2. 安装 `uni-ui` 包
```js
npm i @dcloudio/uni-ui
```
3. 配置 easycom
在 pages.json 中配置 easycom
```json
{
"easycom": {
"autoscan": true,
"custom": {
// uni-ui 规则如下配置
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
},
pages:[]
}
```
接下来就可以在模板中使用了
### back-top组件封装-uni事件绑定
需求:
1. 点击该组件要回退到页面的顶端
绑定点击事件即可
```js
const toTop = () => {
uni.pageScrollTo({ // wx的API
scrollTop: 0, // 页面到顶部的距离
duration: 300 // 页面到顶部的时间
})
}
```
2. 当页面滚动到下面的时候才显示该按钮
这里可以在首页直接做,但是要在这个组件中控制显示和隐藏,将 uni 的事件绑定和触发
* 找到uni,绑定事件,留下回调,接收参数
```js
uni.$on('xxxxHandler', (arg) => {})
```
* 找到uni,触发事件,传递参数
```js
uni.$emit('xxxxHandler', scrollTop)
```
## tabbar 配置
在 pages.json 中配置
```json
"tabBar": {
"color": "#333",
"selectedColor": "#409eff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "/static/images/nav/home-off.png",
"selectedIconPath": "/static/images/nav/home-on.png"
},
{
"pagePath": "pages/course/list/index",
"text": "课程",
"iconPath": "/static/images/nav/list-off.png",
"selectedIconPath": "/static/images/nav/list-on.png"
},
{
"pagePath": "pages/personal/index",
"text": "我的",
"iconPath": "/static/images/nav/my-off.png",
"selectedIconPath": "/static/images/nav/my-on.png"
}
]
},
```
## 课程列表页
#### 静态搭建(考察flex布局)
#### 初始化数据展示
准备api
```tsx
export interface CoursePageModel {
current: number
hasNext: boolean
hasPrevious: boolean
items: CourseModel[] // ------------- 该类型首页的时候写过
pages: number
size: number
total: number
}
class CourseRequest extends Request{
......
getCourseList(page: number, limit: number) {
return this.get({
url: `/api/edu/course/${page}/${limit}`
})
}
}
```
初始化数据页面调用 api
```ts
const courseList = ref([])
const page = ref(1)
const limit = ref(10)
const getCourseList = async () => {
try {
let result = await courseApi.getCourseList(page.value, limit.value)
courseList.value = courseList.value.concat(result.items)
} catch (error) {
console.error(error)
}
}
onLoad(() => {
getCourseList()
})
```
#### 分页
分页的时候使用 status 状态来控制 页面的请求,总共有三种状态 `more`、`no-more`、`loading`,同时需要在请求前将 status 至为 `loading` 状态、请求完成后对比 total 和 数据数组的长度,来决定 status 是 `more` 还是 `no-more`
```ts
// 页面状态
const status = ref<"more" | "no-more" | "loading">('more')
// 触底回调的钩子
onReachBottom(() => {
status.value == "more" && page.value++ && getCourseList()
})
......
let result = await courseApi.getCourseList(page.value, limit.value)
courseList.value = courseList.value.concat(result.items)
if (result.total == courseList.value.length) {
status.value = 'no-more'
} else {
status.value = 'more'
}
```
使用 uni 组件 `uni-load-more` 展示没有更多数据
```html
```
## 个人中心、登录静态
要使用pinia存储我们的数据,考虑数据怎么放?
token 存在 pinia 和 storage 中
个人信息存在 pinia 中
### 使用pinia
```js
npm i pinia@2.0.36
```
> 注意版本号
`src/store/index.ts`
```ts
import { createPinia } from 'pinia'
const store = createPinia()
export default store
```
`main.ts` 使用 pinia
```ts
import store from '@/store';
app.use(store);
```
### 登录
登录流程参照:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
调用 `wx.login()` 获取 code(5分钟时效),拿着 code 调用登录接口,获取token
准备api - `src/api/user.ts`
```js
class UserRequest extends Request{
// 登录
login(data: LoginParams) {
return this.get({
url: `/api/ucenter/webChat/callback`,
data
})
}
}
```
准备 store - `src/store/user.ts`
```ts
const useUserStore = defineStore('userStore', {
state(): StateModel {
return {
// 存token
token: '',
// 存个人信息
userInfo: initUserInfo()
}
},
actions: {
// 登录
async login (code: string) {
try {
let result = await userApi.login({ code })
console.log('登录获取token', result)
this.token = result.token // token 存到store中
uni.setStorageSync("TOKEN", result.token); // token 存到 Storage 中
// token存在代表登录成功
// 跳转回个人中心页,还要获取个人信息
uni.switchTab({
url: '/pages/personal/index',
});
} catch (error) {
console.error(error)
return Promise.reject(error) // 不确定外部需不需要错误的信息,直接抛
}
},
}
}
```
在登录页点击 `登录`按钮调用 wx.login() 获取 code ,拿着 code 调用 action 发请求
```js
const loginHnadler = () => {
// 获取code
uni.login({
timeout:10000,
success: async (result) => {
console.log('code -> ', result)
// 登录获取token
await userStore.login(result.code)
}
});
}
```
### 获取个人信息
api准备
```ts
// 个人信息 - 请求头携带token
getUserInfo() {
return this.get<{ item: UserInfoModel }>({
url: `/api/ucenter/member/auth/getLoginInfo`,
})
}
```
注意:获取个人信息需要请求头携带 token
`src/utils/request.ts`
```js
private api(options: OptionsModel): Promise {
let header: any = {'content-type':'application/json'};
// 请求头携带token
let token = uni.getStorageSync("TOKEN");
if (token) {
header.token = token;
}
return new Promise(() => {
uni.request({
...
header,
...
})
})
}
```
进入个人信息页面需要确认当前 store 中有没有个人信息,如果有则不获取,如果没有就获取
```js
个人中心页面
onShow(() => {
userStore.checkLogin()
})
store 中 actions 加一个 checkLogin 方法
actions: {
// 检测是否登录、是否包含个人信息
checkLogin() {
// 如果有个人信息,不做操作
if (this.userInfo.id) {
return
}
// 没有个人信息,看有没有token,有token直接获取个人信息
if (this.token) {
this.getUserInfo()
return
}
// 没有token跳转登录页,点击登录获取token
uni.navigateTo({
url: '/pages/login/index',
});
},
...
}
```
### 退出登录
这里没有退出登录的接口,所以只需要清除个人信息 和 token 即可
```js
退出登陆
const logout = () => {
userStore.reset()
}
store 中 reset 方法
action: {
reset() {
// 清除个人信息
this.userInfo = initUserInfo()
// 清除token
this.token = ""
uni.removeStorageSync("TOKEN");
}
}
```
## 商品详情页
### 静态
直接粘贴现成的即可(todo),页面粘贴过来之后,把 `v-back-top` 组件的引入和事件处理了。
注意:详情页有两个入口,首页 v-card-list 中商品的点击,课程列表页面的中 课程点击,两个入口进入该页面的时候,发请求带了哪些参数要看一看,因为决定了跳转到该页面的时候要不要携带参数
经过查看发现进详情页调用了两个接口,都需要用到 课程ID(courseId),那么跳转页面的时候传过来
### api准备 - 初始化数据展示
```ts
// 章节类型
export interface ChapterModel {
children: ChapterChildModel[]
id: string
title: string
}
// 详情页返回类型
export interface CourseDetailModel {
chapterList: ChapterModel[]
course: CourseModel
isBuy: boolean
isCollect: boolean
}
// 评论类型
export interface CommentModel {
avatar: string
content: string
courseId: string
deleted: null
gmtCreate: string
gmtModified: string
id: string
memberId: string
nickname: string
teacherId: string
}
// 获取评论分页数据
export interface CommentPageModel {
current: number
hasNext: boolean
hasPrevious: boolean
items: CommentModel[]
pages: number
size: number
total: number
}
// 获取课程详情
getCourseDetail(courseId: string) {
return this.get({
url: `/api/edu/course/${courseId}`
})
}
// 获取评论列表 - /api/edu/comment/{page}/{limit}?courseId={courseId} -- courseId不是必传
getCommentList(page: number, limit: number, data: { courseId: string }) {
return this.get({
url: `/api/edu/comment/${page}/${limit}`,
data
})
}
```
在页面onLoad的时候,调用两个接口,获取数据进行展示即可
### 交互
#### 收藏
点击收藏按钮,调用api收藏、再次点击取消收藏,调用api
准备api(收藏、取消收藏)
```js
// 收藏 - /api/edu/courseCollect/auth/save/{courseId} -- post
getSaveCollect(courseId: string) {
return this.post({
url: `/api/edu/courseCollect/auth/save/${courseId}`
})
}
// 取消收藏 - /api/edu/courseCollect/auth/remove/{id} -- delete
getCancelCollect(courseId: string) {
return this.delete({
url: `/api/edu/courseCollect/auth/remove/${courseId}`
})
}
// 点击的回调
const changeCollect = async () => {
try {
// 当前是收藏调用取消收藏
if (isCollect.value) {
await courseApi.getCancelCollect(courseId.value)
uni.showToast({ title: '取消收藏成功', icon: 'success' });
} else { // 取消收藏调用收藏
await courseApi.getSaveCollect(courseId.value)
uni.showToast({ title: '收藏成功', icon: 'success' });
}
isCollect.value = !isCollect.value
} catch (error) {
console.error(error)
}
}
```
> 注意:
>
> 鉴权,这里我们没有写鉴权的代码,在 `慕尚花坊` 小程序写过鉴权,在request请求封装的时候进行的统一处理,下去自己写
#### 评论列表
点击"查看更多"跳转评论列表页,看一看发请求携带的参数,我们发请求也要携带,所以跳转页面的时候,看看需不需要带参数跳转
经过查看完整版项目,发现在评论列表页发请求需要携带 `courseId` 和 `teacherId` ,所以跳页面要带这两个参数
步骤:
1. 静态
直接粘贴 todo 中的页面(注意在pages.json中写上页面),然后给"查看全部"添加跳转路径
2. 准备api(获取评论列表的api - 这个api已经有了,在商品详情页进入的时候调用过,只不过少一个 query 参数 teacherId,加上即可)
页面初始化的时候调用api(复制商品详情页代码,稍作修改,把page和limit和tracherId加上)
展示数据(注意:粘贴过来详情页的代码,样式也要粘贴)
交互
评论,输入评论内容,调用评论接口,刷新页面数据
准备评论的api接口,输入完内容,点击发送的时候,调用api,发送成功后,给个提示,重置页码,重置数据,刷新页面数据,把评论的内容清空
点击返回上一页,刚刚评论的内容应该展示在商品的详情页,把商品详情页获取评论的方法在 `onShow` 中调用即可
#### 去学习
```vue
课程目录
{{ child.title }}
```
#### 去购买
在课程详情页点击"点击购买"按钮,调用接口,拿到 `orderId` 传给 订单详情页(query),在订单详情页,发请求获取"订单信息",进行展示,勾选协议后,点击购买,拿着"订单信息"中的 orderNo 去获取交易信息,拿到交易信息调用 `wx.requestPayment()` 发起支付,支付的流程和之前的一样
(数白了,就是看已完成项目都先调用哪些接口了,我们也调用即可)
## 订单列表、收藏列表、全部名师
都是进入页面,发请求拿分页数据展示,比较简单(和课程列表页差不多),自己做
# 总结
登录
支付
分包、上线
如何创建一个小程序(包括 原生 和 uniapp )
小程序的基本语法(wx:for、生命周期)
页面通信***
1. navigateTo
A页面跳转B页面,携带参数过去,使用query携带参数,在另一个页面中的onLoad的参数中可以拿到传过来的参数
```js
uni.navigateTo({
url: `/pages/course/index?courseId=${courseId}`,
});
```
2. Storage
在 A 页面将数据存储到storage中,跳转到 B 页面再取出来
A页面
```js
wx.setStorageSync("courseId", courseId);
```
B 页面
```js
let courseId = wx.getStorageSync("courseId");
```
3. app 实例传参
在A页面将数据存储到 app 应用实例上,跳转到 B 页面再取出来
A 页面
```js
const app = getApp()
app.courseId = courseId
```
B 页面
```js
const app = getApp()
const courseId = app.courseId
```
4. pubsub
1. 安装
不在界面上显示的第三包使用步骤(工具类的,例如: pubsub、moment、lodash...)
```js
npm i pubsub-js
```
直接 【工具】 -> 【构建npm】 即可 使用第三方包
2. 在接收数据的组件中找到 pubsub ,绑定事件(订阅消息),留下回调,接收参数
使用 index 接收
```js
Pubsub.subscribe('receiveData', (messagetype, arg) => {
console.log(messagetype, arg)
})
```
3. 在发送数据的组件中找到 pubsub,触发事件(发布消息),传递参数
使用 logs 页面发送数据
```js
Pubsub.publish('receiveData', '我爱你')
```
> 注意:这个传参用的是 新打开的页面(logs) 给 被盖住的页面(index) 传参
>
> 如果是 被盖住的页面(index) 给 新打开的页面(logs) 去使用 pubsub 传参,注意执行时机,只要触发事件在绑定事件之后,就能接收到参数
5. eventChannel 事件通道
1. 被覆盖的页面(index) -> 被打开的页面传参(logs)
被覆盖的页面(index)
```js
wx.navigateTo({
url: '/pages/logs/logs',
success: (res) => {
// res.eventChannel 事件通道 - 触发事件事件传递参数
res.eventChannel.emit('xxx', '你是个好人')
}
})
```
被打开的页面(logs)
```js
onLoad: function (options) {
// 事件通道
const eventChannel = this.getOpenerEventChannel() // 拿到事件通道
// 绑定事件,留下回调,接收参数
eventChannel.on('xxx', (arg) => {
console.log('事件通道 logs 接参', arg)
})
},
```
2. 被打开的页面传参(logs) -> 被覆盖的页面(index)
被打开的页面传参(logs)
```js
onLoad: function (options) {
// 5. 事件通道
const eventChannel = this.getOpenerEventChannel() // 拿到事件通道
eventChannel.emit('yyy', '我们不合适')
},
```
被覆盖的页面(index)
```js
wx.navigateTo({
url: '/pages/logs/logs',
events: {
yyy: (arg) => {
console.log('index页面接收log参数', arg)
}
}
})
```
6. uniapp - 事件总线
uni.$on()
uni.$emit()
uni.$once()
uni.$off()