我的页面介绍
登录 - store里准备好token
- token要放到vuex里,所以来到
store/modules/user.js
,里面声明一个state里的数据用来存token
state() {
return {
.......
// 存token的地方
token: uni.getStorageSync('hmbuy-token') || ''
}
},
- 提供一个mutations用来修改
setToken(state, token) {
state.token = token
// 存储到本地
uni.setStorageSync('hmbuy-token', token)
}
登录 - 两大组件准备
我的页面有:登录、用户信息两大页,这部分可以封装组件
- 来到
components
新建my-login
和my-info
两个组件 - 然后来到
pages/my
页面,进行使用
<my-info v-if="token"></my-info>
<my-login v-else></my-login>
- 这里要依赖store里的token,所以做一个映射
import {
mapState
} from 'vuex'
.......
computed: {
...mapState('user', ['token'])
},
登录组件布局
- 如图所示:登录组件一共三部分:图标、按钮、文字
- 他们应该包到一个根标签里,根标签默认没有高度,靠内容撑开,应该给可用屏幕高度,然后让它的内容居中
- 图标就是给size大小即可,按钮给宽、高、背景颜色、文字颜色、圆角和上下间距、提示文字给字体大小变小一点,文字颜色灰色
- html结构如下
<template>
<view class="login-box" :style="{height: h + 'px'}">
<uni-icons type="contact-filled" size="100"></uni-icons>
<button class="btn-login">一键登录</button>
<view class="tip">登录后尽享更多权益</view>
</view>
</template>
- 因为要拿到可用高度,所以写
mounted
(因为当前是组件,组件的生命周期跟页面的不一样)
// 页面的生命周期跟组件的生命周期是不一样的
// 这里是组件要用组件的生命周期
mounted() {
this.h = uni.getSystemInfoSync().windowHeight
},
data() {
return {
h: 0
};
}
- 样式如下
.login-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.btn-login {
width: 480rpx;
background-color: #c00;
color: #fff;
height: 50px;
border-radius: 25px;
margin: 15px 0;
}
.tip {
font-size: 12px;
color: gray;
}
}
小程序登录讲解
- 总结:一般是省去了注册那一步,直接用微信的身份标识去自己服务器换取对应的token
点击登录获得用户信息并存到vuex
官方文档:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html
-
用法(已过时)
- wx.getUserProfile()
- 但是要在
2.27.1
的基础库版本以下才能使用 - 所以如果自己想调试,要把微信开发者工具的
- 这个接口会返回如上图的数据:有真实的昵称、真实的头像,以及这个用户在微信中的签名
- 拿到标识
扩展:最新的获取头像和昵称如何用
- 新版里如何获取头像?
- 用
button
给一个行内属性叫open-type
,它的值给一个叫chooseAvatar
就能具备获取头像的能力(还能选择相册里的头像和拍照),当选择完头像后会触发chooseavatar
的事件
- 用
<button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
<image class="avatar" src="{{avatarUrl}}"></image>
</button>
- 获取到的头像在这个事件绑定的`e.detail`里
- 新版里如何获取昵称?
- 用
input
标签,给一个type="nickname"
即可,选择完昵称会自动把昵称给这个输入框的value属性
- 用
- 新版只能拿到头像和昵称,没有签名。所以在2022年11月8号以后,全面用新版,如果要做登录,需要写两大步
- 通过上面的方法获取头像和昵称
- 再调用
wx.getUserProfile
来获取签名 - 结合到一起再发请求给服务器
- 但是因为本项目的接口只要你传签名,所以我们还是只用
wx.getUserProfile
,但是为了能显示头像和昵称,我们修改小程序开发者工具里的基础库版本即可
发送请求获取token
请求URL:
- api/public/v1/users/wxlogin
参数:
以下字段主要用作后台服务器生成用户token所有,无特殊用意
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
encryptedData | 是 | string | 执行小程序 获取用户信息后 得到 |
rawData | 是 | string | 执行小程序 获取用户信息后 得到 |
iv | 是 | string | 执行小程序 获取用户信息后 得到 |
signature | 是 | string | 执行小程序 获取用户信息后 得到 |
code | 是 | string | 用户微信登陆的唯一凭证 |
- 发现获取用户信息时,除了code以外的其他签名都有,唯独code没有。原因是因为:code还要通过调用
wx.login()
才能获取 - 步骤
- 给
一键登录
按钮加点击事件,点击事件里调用uni.getUserProfile()
获取头像、昵称和签名 - 此时发现发请求还需要携带code,而code要通过
uni.login()
获取,所以点击事件如下
- 给
methods: {
async doLogin() {
// 这个方法过时了,永远都只能拿到昵称为:“微信用户”,头像为灰色头像
const res = await uni.getUserProfile({
desc: '别问,老子就是要'
})
if (res[0]) {
return uni.showToast({
title: '请允许获取头像',
icon: 'none'
})
}
// 能来到这代表有值,就存到vuex
this.$store.commit('user/setUserInfo', res[1].userInfo)
uni.login({
success: async loginRes => {
// 然后发请求去服务器交换得到token
const returnData = await uni.$http.post('users/wxlogin', {
encryptedData: res[1].encryptedData,
rawData: res[1].rawData,
iv: res[1].iv,
signature: res[1].signature,
code: loginRes.code
})
this.$store.commit('user/setToken', returnData.data.message.token)
}
})
}
}
解释微信小程序开发团队
- 小程序分开发阶段和正式上线
- 开发阶段只有项目里的成员才能访问,正式上线所有人都能访问
- 因为你们没有添加到这个接口对应项目的开发者,所以无法调用登录
- 大概知道登录的流程,以后就能做
登录 - 用户信息布局
- 一个大盒子,里面放image和view显示头像和文字,大盒子给高度和背景色,弹性布局让内容居中
<!-- 头像资料区域 -->
<view class="profile-box">
<image :src="userInfo.avatarUrl"></image>
<view class="name">{{ userInfo.nickName }}</view>
</view>
- 样式如下
.profile-box {
height: 360rpx;
background-color: #c00;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
image {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
}
.name {
margin-top: 10px;
font-size: 24px;
font-weight: 700;
color: #fff;
}
}
登录 - 渲染用户的头像和昵称
- 之前存储用户信息时,要存到本地,以及从本地取出来(改vuex里的)
- 来到
my-info
组件,导入mapState
做映射
import {
mapState
} from 'vuex'
export default {
name: "my-info",
computed: {
...mapState('user', ['userInfo'])
},
}
- 然后去渲染即可
登录 - 渲染三个面板区域
- 如上图所示布局即可
<!-- 内容区域 -->
<view class="content-box">
<view class="data-box">
<view class="data-item">
<text>8</text>
<text>收藏的店铺</text>
</view>
<view class="data-item">
<text>14</text>
<text>收藏的商品</text>
</view>
<view class="data-item">
<text>18</text>
<text>关注的商品</text>
</view>
<view class="data-item">
<text>84</text>
<text>足迹</text>
</view>
</view>
<view class="order-box">
<view class="title">
我的订单
</view>
<view class="order-list">
<view class="order-item">
<image src="../../static/my-icons/icon1.png"></image>
<text>待付款</text>
</view>
<view class="order-item">
<image src="../../static/my-icons/icon2.png"></image>
<text>待收货</text>
</view>
<view class="order-item">
<image src="../../static/my-icons/icon3.png"></image>
<text>退款/退货</text>
</view>
<view class="order-item">
<image src="../../static/my-icons/icon4.png"></image>
<text>全部订单</text>
</view>
</view>
</view>
<uni-list :border="false">
<uni-list-item title="收货地址" link></uni-list-item>
<uni-list-item title="联系客服" link></uni-list-item>
<uni-list-item title="退出登录" link></uni-list-item>
</uni-list>
</view>
.content-box {
padding: 10px;
background-color: #fff;
height: 600px;
width: 90%;
position: absolute;
top: 340rpx;
left: 50%;
transform: translateX(-50%);
.data-box {
display: flex;
.data-item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
.order-box {
margin: 20px 0;
.title {
padding: 10px;
border-bottom: 1px solid #ddd;
}
.order-list {
display: flex;
margin-top: 10px;
.order-item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
image {
width: 40px;
height: 40px;
}
}
}
}
}
实现退出功能
清空用户信息、地址、token
- 模态框文档
- 退出需要清空vuex和本地存储,所以来到
store/modules/user
提供一个做退出的方法
logout(state) {
state.token = ''
state.userInfo = {}
// 本地存储也删了
uni.removeStorageSync('hmbuy-token')
uni.removeStorageSync('hmbuy-useinfo')
}
- 来到
my-info
组件,给退出加点击事件,点击事件先弹出确认框,当点击确定才调用vuex里的方法做退出
<uni-list-item @click="doLogout" title="退出登录" link></uni-list-item>
doLogout() {
// 弹出一个去确认框
uni.showModal({
title: '是否确定退出?',
success: (res) => {
if (res.confirm) {
this.$store.commit('user/logout')
}
}
})
}
结算点击事件 - 条件判断
点击结算需判断是否有填写地址、是否有选中商品、是否有登录
- 来到
cart
页面,给结算
加点击事件,点击事件里做判断
<view @click="order" class="order-btn">结算({{ total }})</view>
import {
......
mapState
} from 'vuex'
...........
computed: {
.......
...mapState('user', ['address', 'token'])
},
// 结算的点击事件
order() {
// 判断有没有选择地址
if (!this.address.provinceName) {
return uni.showToast({
title: '请先选择地址',
icon: 'none'
})
}
// 判断有没有选择至少一件商品
if (this.total === 0) {
return uni.showToast({
title: '请至少选一件商品',
icon: 'none'
})
}
// 判断有没有登录
if (!this.token) {
// 后续还要做3秒延迟跳转
}
}
结算点击 - 3秒跳转到登录
- 大概流程就是: 先弹出一个提示,然后开一个定时器每隔一秒-1,判断如果到0就跳转到登录,把这段代码写到上面最后的判断里
let sec = 3
uni.showToast({
title: `暂未登录,${sec}秒后跳转到登录页`,
icon: 'none'
})
// 定时器
let timerId = setInterval(() => {
sec--
uni.showToast({
title: `暂未登录,${sec}秒后跳转到登录页`,
icon: 'none'
})
if (sec == 0) {
clearInterval(timerId)
// 跳转到登录页(我的)
uni.switchTab({
url: '/pages/my/my'
})
}
}, 1000)
登录成功返回之前的页面
核心实现思路:在跳转到登录页之前,先把我要回的页面,和回来的方式给传递过去,但是switchTab是不能传值的,所以我可以把值存到全局对象uni身上。
返回页面的信息对象,主要包含 { openType, from } 两个属性,其中 openType 表示以哪种方式导航回之前的页面;from 表示之前页面的 url 地址
- 在订单判断里,打回登录页之前,先存信息
........
uni.backTo = {
// 记录回这个页面要用什么方式跳
openType: 'switchTab',
// 记录了从哪里跳来的
from: '/pages/cart/cart'
}
// 跳转到登录页(我的)
uni.switchTab({
url: '/pages/my/my'
})
..............
- 在
my-login
页面,登录成功后要先判断uni
里有没有backTo
这个属性,有就打回backTo
记录的页面
// 判断uni里是否有backTo
if (uni.backTo) {
if (uni.backTo.openType === 'switchTab') {
uni.switchTab({
url: uni.backTo.from,
success: () => {
// 清空标记,不然每次登录都会跳转到原来的那个页面
uni.backTo = null
}
})
}
}
微信支付 - 流程介绍
- 创建订单
- 请求创建订单的 API 接口:把(订单金额、收货地址、订单中包含的商品信息)发送到服务器
- 服务器响应的结果:订单编号
- 订单预支付(获取支付参数)
- 请求订单预支付的 API 接口:把(订单编号)发送到服务器
- 服务器响应的结果:订单预支付的参数对象,里面包含了订单支付相关的必要参数
- 参数都是为了保证交易安全
- 发起微信支付
- 调用
uni.requestPayment()
发起微信支付;把步骤 2 得到的 “订单预支付对象” 作为参数传递给这个方法 - 监听
uni.requestPayment()
这个 API 的success
,fail
,complete
回调函数
- 调用
微信支付 - 创建订单
- 发现创建订单、查询订单、获取支付参数这些接口都需要先登录、因为它需要传递token给服务器,为了方便,没必要一个一个加token,而是在请求拦截里统一加token
- 来到
utils/request.js
在请求拦截里判断是不是要访问需要token的接口,如果是就携带token
$http.beforeRequest = function(options) {
uni.showLoading({
title: '数据加载中...',
})
if (options.url.indexOf('my/orders') != -1) {
// 请求的是要携带token的接口
options.header.Authorization = store.state.user.token
}
}
- 来到
my_settle
组件,因为发请求需要拿到所有的购物车数据,因此先导入mapState
工具函数,把购物车做一个计算属性的映射
import {mapState} from 'vuex'
computed: {
...mapState('cart', ['cartList'])
}
- 然后回到订单的点击事件里,在后面加如下代码
// 结算的点击事件
async order() {
.........................
..........................
// 筛选出选中的商品
const chkList = this.cartList.filter(v => v.goods_checked)
// 对选中的商品用部分属性组成新数组
const newArr = chkList.map(v => {
return {
goods_id: v.goods_id,
goods_number: v.goods_count,
goods_price: v.goods_price
}
})
// 能来到这代表所需的都有,就可以发请求创建订单
const res = await uni.$http.post('my/orders/create', {
order_price: this.totalPrice,
consignee_addr: this.addressStr,
goods: newArr
})
console.log(res)
}
微信支付 - 订单预支付
- 本步骤为了支付的相关参数
- 发请求传递上一步拿到的订单号
- 代码如下(这个代码接在上面写的代码后)
// 发请求做预支付 --- 拿到付款的那些签名
const res2 = await uni.$http.post('my/orders/req_unifiedorder', {
order_number: res.data.message.order_number
})
console.log(res2)
微信支付 - 发起微信支付
https://developers.weixin.qq.com/miniprogram/dev/api/payment/wx.requestPayment.html
// 发起微信支付 - 传入拿到的付款签名
uni.requestPayment({
...res2.data.message.pay,
success: () => {
uni.showToast({
title: '支付成功'
})
},
fail: () => {
uni.showToast({
title: '支付失败',
icon: 'error'
})
}
})
发布小程序
-
然后在开发者后台的版本管理里即可看到刚刚上传的版本,点击提交审核就会进入到官方审核上架环节