小娴商城学习总结
基于uniapp开发的微信小程序
内化总结
0、总体把控
首先搭建好导航栏 tabbar,把握整体框架逻辑。再一个一个tabbar页面搭建UI结构。
在数据处理时,明确数据从哪来,在哪接收,在哪使用,是否需要存储到本地,通常使用vuex、props。
除此以外,插入图标icon、调用接口API时参考最新文档即可。
步骤:1、百度或csdn出需要使用的名称。2、去文档中看使用方法。
MDN中文文档
微信开放文档
uni-App
1、配置HbuilderX
2、uniApp框架
3、把项目运行到小程序
3.1 填写小程序的AppID
3.2 在HBuilder中配置微信开发者工具安装路径
3.3 在微信开发者工具中开启服务端口
4、使用Git管理项目
本地管理
在项目根目录中新建 .gitignore 忽略文件,并配置如下:
//忽略 node_modules 目录
/node_modules
/unpackage/dist
注意:由于我们忽略了 unpackage 目录中仅有的 dist 目录,因此默认情况下, unpackage 目录不会被 Git 追踪。
此时,为了让 Git 能够正常追踪 unpackage 目录,按照惯例,我们可以在 unpackage 目录下创建一个叫做 .gitkeep 的文件进行占位
打开终端,切换到项目根目录中,运行如下的命令,初始化本地 Git 仓库:
git init
将所有文件都加入到暂存区:
git add
.
本地提交更新:
git commit -m "init project"
把项目托管到码云
1、生成并配置 SSH 公钥
2、创建空白的码云仓库
3、把本地项目上传到码云对应的空白仓库中
5、tabBar
5.1 在pages.json
中配置新增tabBar节点
5.2 导航条样式效果在pages.json
中的globalStyle
节点中设置
6、配置网络请求
小程序平台不支持axios,原生的
wx.request()
不支持拦截器等全局定制的功能,因此使用@escook/request-miniprogram
第三方包发起网络数据请求。
参考文档:request-miniprogram的npm文档
7、发送网络请求并接收返回数据
配置根路径:
$http.baseUrl = 'https://www.域名.com'
解构将data从res返回对象中拿出来(可以先clog出res对象看看里面有什么)
const {data : res} = await uni.$http.get('/api/xxxxxxxxx')
//请求失败
if (res.meta.status !== 200) {
return uni.showToast({
title: '数据请求失败!',
duration: 1500,
icon: 'none',
})
}
//请求成功,为 data 中的数据赋值
this.swiperList = res.message
8、动态绑定属性
动态绑定属性时记得加冒号
9、封装uni.$showMsg方法
在全局封装一个 uni.$showMsg() 方法,来简化 uni.showToast() 方法的调用。
在main.js
中为uni对象挂载自定义的$showMsg()
方法
// 封装的展示消息提示的方法
uni.$showMsg = function (title = '数据加载失败!', duration = 1500) {
uni.showToast({
title,
duration,
icon: 'none',
})
}
今后,在需要提示消息的时候,直接调用 uni.$showMsg()
方法即可:
async getSwiperList() {
const { data: res } = await uni.$http.get('/api/public/v1/home/swiperdata')
if (res.meta.status !== 200) return uni.$showMsg()
// this.swiperList = res.message
}
10、分类页面两边滚动
用scroll-view
标签包裹,里面v-for渲染UI
选中项:控制data中active的数值,选中项的数值下标,判断active值来渲染样式。
返回的数据二级列表是一级列表的children,直接调用赋值即可。
scroll-top
属性:滚动条到顶部的距离。
11、动态计算窗口的剩余高度
用uni.getSystemInfoSync()
获取当前系统的信息,里面的windowHeight
就是窗口可用高度,计算后将结果wh存入data,在需要视图容器中渲染行内样式即可:style="{height: wh + 'px'}
12、v-if、v-else控制元素显示与否或切换
13、uni.navigateTo
跳转页面
14、自定义组件的复用
在子组件中定义props结点,定义接受的参数(类型和默认值)。
// 背景颜色
bgcolor: {
type: String,
default: '#C00000'
},
然后在使用子组件时,动态绑定style
属性,传入本次样式参数。
<view class="my-search-container" :style="{'background-color': bgcolor}"/>
15、搜索框
15.1 吸顶
// 设置定位效果为“吸顶”
position: sticky;
// 吸顶的“位置”
top: 0;
// 提高层级,防止被轮播图覆盖
z-index: 999;
15.2 搜索框自动获取焦点
改源码:uni_modules -> uni-search-bar -> uni-search-bar.vue
show: true,
showSync: true,
15.3 搜索框防抖
input事件:value发生改变时触发事件并返回最新value
设置定时器使如果 500 毫秒内,没有触发新的输入事件,则为搜索关键词赋值。
防止频繁搜索。
15.4 查询搜索建议列表
判断输入的关键词是否为空,是的话直接return,非空的话发送请求。
15.5 存储搜索历史
push进存储有搜索历史的数组,末尾追加。
数组顺序反转显示
定义一个计算属性 historys,将 historyList 数组 reverse 反转之后,就是此计算属性的值:
computed: {
historys() {
// 注意:由于数组是引用类型,所以不要直接基于原数组调用 reverse 方法,以免修改原数组中元素的顺序
// 而是应该新建一个内存无关的数组,再进行 reverse 反转
return [...this.historyList].reverse()
}
}
数组去重(set对象)
// 1. 将 Array 数组转化为 Set 对象
const set = new Set(this.historyList)
// 2. 调用 Set 对象的 delete 方法,移除对应的元素
set.delete(this.kw)
// 3. 调用 Set 对象的 add 方法,向 Set 中添加元素
set.add(this.kw)
// 4. 将 Set 对象转化为 Array 数组
this.historyList = Array.from(set)
16、组件的自定义事件
子组件中用this.$emit('xxx',OBJECT)
触发父组件绑定的自定义事件名,从而调用父组件自定义事件的处理函数。
实现子传父。
子:this.$emit(‘myclick’)
父:<mysearch @myclick=“gotoSearch”/>
17、定义请求参数对象
// 请求参数对象
queryObj: {
// 查询关键词
query: '',
// 商品分类Id
cid: '',
// 页码值
pagenum: 1,
// 每页显示多少条数据
pagesize: 10
}
发送请求传参时直接传queryObj。
// 将页面参数转存到 this.queryObj 对象中
this.queryObj.query = options.query || ''
上面这种写法等号右边值得学习:前者为空则赋后者的值,有前者就用前者。
18、 使用过滤器处理价格
和 data 节点平级,声明 filters 过滤器节点如下:
filters: {
// 把数字处理为带两位小数点的数字
tofixed(num) {
return Number(num).toFixed(2)
}
}
在渲染商品价格的时候,通过管道符 | 调用过滤器:
<!-- 商品价格 -->
<view class="goods-price">¥{{goods.goods_price | tofixed}}</view>
19、设置节流阀
触底加载数据时,为了防止发起额外的请求,设置一个flag用于控制是否加载下一个页面,保证这个页面加载完成后才可以加载下一个页面。若加载下个页面前发现已有正在进行的请求,则直接return。
在 data 中定义 isloading 节流阀如下:
data() {
return {
//是否正在请求数据
isloading: false
}
}
修改 getGoodsList 方法,在请求数据前后,分别打开和关闭节流阀:
// 获取商品列表数据的方法
async getGoodsList() {
// ** 打开节流阀
this.isloading = true
// 发起请求
const { data: res } = await uni.$http.get('/api/public/v1/goods/search', this.queryObj)
// ** 关闭节流阀
this.isloading = false
// 省略其它代码...
}
在 onReachBottom 触底事件处理函数中,根据节流阀的状态,来决定是否发起请求:
// 触底的事件
onReachBottom() {
// 判断是否正在请求其它数据,如果是,则不发起额外的请求
if (this.isloading) return
this.queryObj.pagenum += 1
this.getGoodsList()
}
20、富文本组件解析html结构
参考博客
注:ios不支持webp格式的图片,要用正则将其改为jpg格式。
.replace(/webp/g, 'jpg')
21、用组件实现商品导航
uni-goods-nav组件,使用时传入对应参数即可。
<uni-goods-nav :fill="true" :options="options" :buttonGroup="buttonGroup" @click="onClick" @buttonClick="buttonClick" />
组件库
22、Vuex的使用
1、初始化 Store 的实例对象:
// 1. 导入 Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'
// 2. 将 Vuex 安装为 Vue 的插件
Vue.use(Vuex)
// 3. 创建 Store 的实例对象
export default new Vuex.Store({
// TODO:挂载 store 模块
modules: {
})
在 main.js 中导入 store 实例对象并挂载到 Vue 的实例上:
// 1. 导入 store 的实例对象
import store from './store/store.js'
const app = new Vue({
...App,
// 2. 将 store 挂载到 Vue 实例上
store,
})
app.$mount()
2、在 cart.js 中,初始化如下的 vuex 模块:
export default {
// 为当前模块开启命名空间
namespaced: true,
// 模块的 state 数据
state: () => ({
cart: [],
}),
// 模块的 mutations 方法
mutations: {},
// 模块的 getters 属性
getters: {},
}
在 store/store.js 模块中,导入并挂载购物车的 vuex 模块:
// 1. 导入购物车的 vuex 模块
import moduleCart from './cart.js'
export default new Vuex.Store({
// TODO:挂载 store 模块
modules: {
// 2. 挂载购物车的 vuex 模块,模块内成员的访问路径被调整为 m_cart,例如:
// 购物车模块中 cart 数组的访问路径是 m_cart/cart
m_cart: moduleCart,
},
})
3、在商品详情页中使用 Store 中的数据
// 从 vuex 中按需导出 mapState 辅助方法
import { mapState } from 'vuex'
export default {
computed: {
// 调用 mapState 方法,把 m_cart 模块中的 cart 数组映射到当前页面中,作为计算属性来使用
// ...mapState('模块的名称', ['要映射的数据名称1', '要映射的数据名称2'])
...mapState('m_cart', ['cart']),// m_cart模块下的cart数组
},
}
23、持久化存储到本地
// 将购物车中的数据持久化存储到本地
saveToStorage(state) {
uni.setStorageSync('cart', JSON.stringify(state.cart))
}
addToCart(state, goods) {
// 通过 commit 方法,调用 m_cart 命名空间下的 saveToStorage 方法
this.commit('m_cart/saveToStorage')
}
修改 cart.js 模块中的 state 函数,读取本地存储的购物车数据,对 cart 数组进行初始化:
state: () => ({
cart: JSON.parse(uni.getStorageSync('cart') || '[]')
}),
24、watch设置初次加载就侦听
在watch事件中加入:
// immediate 属性用来声明此侦听器,是否在页面初次加载完毕后立即调用
immediate: true
25、一显示页面就加载徽标
onShow() {
// 在页面刚展示的时候,设置数字徽标
this.setBadge()
}
methods: {
setBadge() {
// 调用 uni.setTabBarBadge() 方法,为购物车设置右上角的徽标
uni.setTabBarBadge({
index: 2, // 索引
text: this.total + '' // 注意:text 的值必须是字符串,不能是数字
})
}
}
26、混入mixin
mixin混入
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步 在mixin.js文件中定义混入,例如:
export const 混合名 = {
data(){…},
methods:{…},
…
}
第二步 使用混入,例如:
(1)全局混入:Vue.mixin(xxx)
(2)局部混入: mixins:[‘xxx’]
//单文件混入
export default {
computed: {},
methods: {},
onShow() {},
watch: {}
}
27、单选按钮radio
动态渲染check属性
uni-number-box
组件选择数量
28、滑动删除
<!-- uni-swipe-action 是最外层包裹性质的容器 -->
<uni-swipe-action>
<block v-for="(goods, i) in cart" :key="i">
<!-- uni-swipe-action-item 可以为其子节点提供滑动操作的效果。需要通过 options 属性来指定操作按钮的配置信息 -->
<uni-swipe-action-item :options="options" @click="swipeActionClickHandler(goods)">
<my-goods :goods="goods" :show-radio="true"></my-goods>
</uni-swipe-action-item>
</block>
</uni-swipe-action>
在 data 节点中声明 options 数组,用来定义操作按钮的配置信息:
data() {
return {
options: [{
text: '删除', // 显示的文本内容
style: {
backgroundColor: '#C00000' // 按钮的背景颜色
}
}]
}
}
注:删除用filter
29、小程序收货地址
// 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能
// 返回值是一个数组:第 1 项为错误对象;第 2 项为成功之后的收货地址对象
const [err, succ] = await uni.chooseAddress().catch(err => err)
// 2. 用户成功的选择了收货地址
if (err === null && succ.errMsg === 'chooseAddress:ok') {
// 为 data 里面的收货地址对象赋值
this.address = succ
}
}
computed: {
// 收货详细地址的计算属性
addstr() {
if (!this.address.provinceName) return ''
// 拼接 省,市,区,详细地址 的字符串并返回给用户
return this.address.provinceName + this.address.cityName + this.address.countyName + this.address.detailInfo
}
}
30、勾选/反选
// !this.isFullCheck 表示:当前全选按钮的状态取反之后,就是最新的勾选状态
this.updateAllGoodsState(!this.isFullCheck)
31、微信登录
调用getUserProfile()接口接收返回对象,头像和昵称在rawData里取。
<!-- 可以从 getuserinfo 事件处理函数的形参中,获取到用户的基本信息 -->
<button type="primary" class="btn-login" @click="getUserProfile"> 一键登录 </button>
// 调用 uni.getUserProfile接口,返回微信用户信息userInfo
getUserProfile() {
uni.getUserProfile({
desc:'用于用户登录账号',
success: (res) => {
this.updateUserInfo(res.userInfo)
this.getToken(res)
},
fail: (res) => {
return uni.$showMsg('您取消了登录授权')
}
})
},
当获取到了微信用户的基本信息之后,还需要进一步调用login()接口,从而换取登录成功之后的 Token 字符串。方便后续操作。
// 调用登录接口,换取永久的 token
async getToken(info) {
// 调用微信登录接口
const [err,res] = await uni.login().catch(err => err)
// 判断是否 uni.login() 调用失败
if (err || res.errMsg !== 'login:ok') return uni.$showError('登录失败!')
// 准备参数对象
const query = {
code: res.code,
encryptedData: info.encryptedData,
iv: info.iv,
rawData: info.rawData,
signature: info.signature
}
// 换取 token
const { data: loginResult } = await uni.$http.post('https://www.fastmock.site/mock/9552649a5d76835b4565361bac1efe1f/api/token', query)
if (loginResult.meta.status !== 200) return uni.$showMsg('登录失败!')
uni.$showMsg('登录成功')
this.updateToken(loginResult.message.token)
//this.navigateBack()
},
32、微信支付 (一)
32.1 未登录三秒后自动跳转
// 延迟导航到 my 页面
delayNavigate() {
// 把 data 中的秒数重置成 3 秒
this.seconds = 3
this.showTips(this.seconds)
this.timer = setInterval(() => {
this.seconds--
if (this.seconds <= 0) {
clearInterval(this.timer)
uni.switchTab({
url: '/pages/my/my'
})
return
}
this.showTips(this.seconds)
}, 1000)
}
32.2 登录后跳转回支付页面
核心实现思路:在自动跳转到登录页面成功之后,把返回页面的信息存储到 vuex 中,从而方便登录成功之后,根据返回页面的信息重新跳转回去。返回页面的信息对象,主要包含 { openType, from } 两个属性,其中 openType 表示以哪种方式导航回之前的页面;from 表示之前页面的 url 地址。
state: () => ({
// 重定向的 object 对象 { openType, from }
redirectInfo: null
}),
mutations: {
// 更新重定向的信息对象
updateRedirectInfo(state, info) {
state.redirectInfo = info
}
}
uni.switchTab({
url: '/pages/my/my',
// 页面跳转成功之后的回调函数
success: () => {
// 调用 vuex 的 updateRedirectInfo 方法,把跳转信息存储到 Store 中
this.updateRedirectInfo({
// 跳转的方式
openType: 'switchTab',
// 从哪个页面跳转过去的
from: '/pages/cart/cart'
})
}
})
拿到token之后跳回去
// 调用登录接口,换取永久的 token
async getToken(info) {
// 判断 vuex 中的 redirectInfo 是否为 null
// 如果不为 null,则登录成功之后,需要重新导航到对应的页面
this.navigateBack()
}
// 返回登录之前的页面
navigateBack() {
// redirectInfo 不为 null,并且导航方式为 switchTab
if (this.redirectInfo && this.redirectInfo.openType === 'switchTab') {
// 调用小程序提供的 uni.switchTab() API 进行页面的导航
uni.switchTab({
// 要导航到的页面地址
url: this.redirectInfo.from,
// 导航成功之后,把 vuex 中的 redirectInfo 对象重置为 null
complete: () => {
this.updateRedirectInfo(null)
}
})
}
}
32.3 给提示信息加透明遮罩
// 为页面添加透明遮罩,防止点击穿透(scss)
mask: true,
33、微信支付(二)
33.1 判断是否需要权限,在请求头中添加 Token 身份认证的字段
原因说明:只有在登录之后才允许调用支付相关的接口,所以必须为有权限的接口添加身份认证的请求头字段
打开项目根目录下的 main.js,改造 $http.beforeRequest 请求拦截器中的代码。
// 请求开始之前做一些事情
$http.beforeRequest = function(options) {
// 判断请求的是否为有权限的 API 接口
if (options.url.indexOf('/my/') !== -1) {
// 为请求头添加身份认证字段
options.header = {
// 字段的值可以直接从 vuex 中进行获取
Authorization: store.state.m_user.token,
}
}
}
33.2 微信支付的流程
1、创建订单
请求创建订单的 API 接口:把(订单金额、收货地址、订单中包含的商品信息)发送到服务器
服务器响应的结果:订单编号res.message.order_number
2、订单预支付
请求订单预支付的 API 接口:把(订单编号)发送到服务器
服务器响应的结果:订单预支付的参数对象,里面包含了订单支付相关的必要参数
3、发起微信支付
调用 uni.requestPayment() 这个 API,发起微信支付;把步骤 2 得到的 “订单预支付对象” 作为参数传递给 uni.requestPayment() 方法
监听 uni.requestPayment() 这个 API 的 success,fail,complete 回调函数
// 1. 创建订单
// 1.1 组织订单的信息对象
const orderInfo = {
// 开发期间,注释掉真实的订单价格,
// order_price: this.checkedGoodsAmount,
// 写死订单总价为 1 分钱
order_price: 0.01,
consignee_addr: this.addstr,
goods: this.cart.filter(x => x.goods_state).map(x => ({ goods_id: x.goods_id, goods_number: x.goods_count, goods_price: x.goods_price }))
}
// 1.2 发起请求创建订单
const { data: res } = await uni.$http.post('/api/public/v1/my/orders/create', orderInfo)
if (res.meta.status !== 200) return uni.$showMsg('创建订单失败!')
// 1.3 得到服务器响应的“订单编号”
const orderNumber = res.message.order_number
// 2. 订单预支付
// 2.1 发起请求获取订单的支付信息
const { data: res2 } = await uni.$http.post('/api/public/v1/my/orders/req_unifiedorder', { order_number: orderNumber })
// 2.2 预付订单生成失败
if (res2.meta.status !== 200) return uni.$showError('预付订单生成失败!')
// 2.3 得到订单支付相关的必要参数
const payInfo = res2.message.pay
// 3. 发起微信支付
// 3.1 调用 uni.requestPayment() 发起微信支付
const [err, succ] = await uni.requestPayment(payInfo)
// 3.2 未完成支付
if (err) return uni.$showMsg('订单未支付!')
// 3.3 完成了支付,进一步查询支付的结果
const { data: res3 } = await uni.$http.post('/api/public/v1/my/orders/chkOrder', { order_number: orderNumber })
// 3.4 检测到订单未支付
if (res3.meta.status !== 200) return uni.$showMsg('订单未支付!')
// 3.5 检测到订单支付完成
uni.showToast({
title: '支付完成!',
icon: 'success'
})
总结:
熟悉了uniApp的开发流程和框架,熟悉了向服务器发送请求,熟悉了vuex,熟悉了小程序的登录支付接口。