登录功能开发
实现POST提交
HTTP协议规定请求消息内容类型(Content-Type)有哪些?—— 只有四种
text/plain 没有编码的普通数据
application/x-www-form-urlencoded 编码后的普通数据
multipart/form-data 请求主体中包含文件上传域
application/json 请求主体是 JSON 格式字符串
HTTP协议规定响应消息内容类型(Content-Type)有哪些?—— 有很多种
text/html、
text/plain、
text/css、
application/javascript、
image/jpeg、
application/mpeg3、
application/json、
…
<script>export default {data(){return {statusBarHeight: 0, //系统状态栏高度hidePwd: true, //是否隐藏密码phone: "13501234567", //用户输入的登录手机号pwd:"123456", //用户输入的登录密码}},//生命周期方法 —— 页面挂载完成onLoad(){ //此处此方法类似于mounted()//console.log('login组件挂载完成')//获取系统信息,读取其中的“状态栏高度”let {statusBarHeight} = uni.getSystemInfoSync()// console.log('屏幕高度:', screenHeight);this.statusBarHeight = statusBarHeight},methods: {async doLogin(){//console.log('当前输入:',this.phone, this.pwd)//1.验证手机号是否合法,不合法就弹出提示框,退出执行if(!/^1[3-9]\d{9}$/.test(this.phone)){uni.showToast({title: '手机号非法', //提示标题icon: 'none', //图标duration: 3000 //持续时长 })return}//2.验证密码是否合法,不合法就弹出提示框,退出执行if(this.pwd.length < 6){uni.showToast({title:'密码格式非法',icon: 'none',duration: 3000})return }//3.把手机号/密码提交给服务器端数据API,进行登录验证/**********使用uni.request()发起POST请求************/let url = "https://www.codeboy.com/zhsqapi/user/login"let [err, res] = await uni.request({ url,method: 'POST',//header: { 'Content-Type': 'application/json'},header: { }, //请求内容类型默认就是JSON格式//data: `{"phone":"${this.phone}", "pwd":"${this.pwd}"}`//JSON系列化:把普通的JS对象转换为JSON格式的字符串//data: JSON.stringify( {phone:this.phone, pwd:this.pwd})//uni.request方法会自动根据请求内容类型,把数据转换为需要的格式data: {phone:this.phone, pwd:this.pwd}})if(err){console.log('执行失败:', err);}else {console.log('异步请求成功:', res);}//4.登录成功,提示“欢迎回来”,跳转到首页},}}
</script>
实现页面跳转
吐司对话框—— 用于简单的提醒,非重要事件的提示
uni.showToast( {title, icon, duration} )
模态对话框 —— 用于严重的提示,甚至必须作出选择的提示
uni.showModal({title, content })
加载中对话框—— 提示操作正在进行中
uni.showLoading( ) / uni.hideLoading()
动作清单对话框—— 让用户选择要执行的动作
uni.showActionSheet( )
导航跳转:、 uni.navigateTo( )
导航返回跳转:、 uni.navigateBack( )
重定向跳转:、 uni.redirectTo( )
切换页签跳转:、 uni.switchTab ( )
重启跳转:、 uni.reLaunch( )
<script>export default {data(){return {statusBarHeight: 0, //系统状态栏高度hidePwd: true, //是否隐藏密码phone: "13501234567", //用户输入的登录手机号pwd:"123456", //用户输入的登录密码}},//生命周期方法 —— 页面挂载完成onLoad(){ //此处此方法类似于mounted()//console.log('login组件挂载完成')//获取系统信息,读取其中的“状态栏高度”let {statusBarHeight} = uni.getSystemInfoSync()// console.log('屏幕高度:', screenHeight);this.statusBarHeight = statusBarHeight},methods: {async doLogin(){//console.log('当前输入:',this.phone, this.pwd)//1.验证手机号是否合法,不合法就弹出提示框,退出执行if(!/^1[3-9]\d{9}$/.test(this.phone)){uni.showToast({title: '手机号非法', //提示标题icon: 'none', //图标duration: 3000 //持续时长 })return}//2.验证密码是否合法,不合法就弹出提示框,退出执行if(this.pwd.length < 6){uni.showToast({title:'密码格式非法',icon: 'none',duration: 3000})return }//3.把手机号/密码提交给服务器端数据API,进行登录验证/**********使用uni.request()发起POST请求************/let url = "https://www.codeboy.com/zhsqapi/user/login"let [err, res] = await uni.request({ url,method: 'POST',//header: { 'Content-Type': 'application/json'},header: { }, //请求内容类型默认就是JSON格式//data: `{"phone":"${this.phone}", "pwd":"${this.pwd}"}`//JSON系列化:把普通的JS对象转换为JSON格式的字符串//data: JSON.stringify( {phone:this.phone, pwd:this.pwd})//uni.request方法会自动根据请求内容类型,把数据转换为需要的格式data: {phone:this.phone, pwd:this.pwd}})if(err){console.log('执行失败:', err);}else {console.log('异步请求成功:', res);}//4.登录成功,提示“欢迎回来”,跳转到首页//跳转到“首页” —— 切换页签uni.showToast({title: '欢迎回来',icon: 'none', //图标:不要duration: 3000, //持续时间:3scomplete(){ //对话框成功关闭//跳转到“首页” —— 切换页签uni.switchTab({url:'/pages/index/index'})}})},}}
</script>
Ajax集中管理
因为ajax访问在应用开发中频繁使用、代码复杂,且接口地址、数据结构等可能会发生变化
所以将ajax相关代码提取为方法,统一保存管理在外部文件中
创建service/index.js
/****对服务器端数据API进行访问二次封装****//*** 服务器端基础地址*/
export let base = 'https://www.codeboy.com/zhsqapi/'/*** API-1.2、用户登录* 接口地址:user/login* 请求方式:POST* 请求主体格式:application/json * 名称 必填 类型 说明* phone 是 string 手机号* pwd 是 string 密码*/
export let userLogin = async (phone, pwd)=>{//1.准备请求URLlet url = base + 'user/login'//console.log(url)//2.显示“加载中”提示框uni.showLoading({title: '用户登录中'})//3.发起异步请求消息let [err, res] = await uni.request({url,method: 'POST',data: {phone, pwd}})//4.隐藏“加载中”提示框uni.hideLoading()//5.返回响应消息主体return res.data
}
<script>import { userLogin } from '../../service/'export default {data(){return {statusBarHeight: 0, //系统状态栏高度hidePwd: true, //是否隐藏密码phone: "13501234567", //用户输入的登录手机号pwd:"123456", //用户输入的登录密码}},//生命周期方法 —— 页面挂载完成onLoad(){ //此处此方法类似于mounted()//console.log('login组件挂载完成')//获取系统信息,读取其中的“状态栏高度”let {statusBarHeight} = uni.getSystemInfoSync()// console.log('屏幕高度:', screenHeight);this.statusBarHeight = statusBarHeight},methods: {async doLogin(){//console.log('当前输入:',this.phone, this.pwd)//1.验证手机号是否合法,不合法就弹出提示框,退出执行if(!/^1[3-9]\d{9}$/.test(this.phone)){uni.showToast({title: '手机号非法', //提示标题icon: 'none', //图标duration: 3000 //持续时长 })return}//2.验证密码是否合法,不合法就弹出提示框,退出执行if(this.pwd.length < 6){uni.showToast({title:'密码格式非法',icon: 'none',duration: 3000})return }//3.把手机号/密码提交给服务器端数据API,进行登录验证let data = await userLogin(this.phone, this.pwd)console.log(data)//4.登录成功,提示“欢迎回来”,跳转到首页if(data.code===2000){ //登录成功//弹出一个“吐司”对话框uni.showToast({title: '欢迎回来',icon: 'none', //图标:不要duration: 3000, //持续时间:3scomplete(){ //对话框成功关闭//跳转到“首页” —— 切换页签uni.switchTab({url:'/pages/index/index'})}})}else { //登录失败//弹出一个“模态”对话框uni.showModal({title: '错误',content: '登录失败!服务器返回消息:'+data.msg})}},}}
</script>
处理吐司弹窗
无法实现吐司弹窗结束后页面跳转
所以需要将吐司提示放置到目标页-index.vue中实现
login.vue
<script>import { userLogin } from '../../service/'export default {data(){return {statusBarHeight: 0, //系统状态栏高度hidePwd: true, //是否隐藏密码phone: "13501234567", //用户输入的登录手机号pwd:"123456", //用户输入的登录密码}},//生命周期方法 —— 页面挂载完成onLoad(){ //此处此方法类似于mounted()//console.log('login组件挂载完成')//获取系统信息,读取其中的“状态栏高度”let {statusBarHeight} = uni.getSystemInfoSync()// console.log('屏幕高度:', screenHeight);this.statusBarHeight = statusBarHeight},methods: {async doLogin(){//console.log('当前输入:',this.phone, this.pwd)//1.验证手机号是否合法,不合法就弹出提示框,退出执行if(!/^1[3-9]\d{9}$/.test(this.phone)){uni.showToast({title: '手机号非法', //提示标题icon: 'none', //图标duration: 3000 //持续时长 })return}//2.验证密码是否合法,不合法就弹出提示框,退出执行if(this.pwd.length < 6){uni.showToast({title:'密码格式非法',icon: 'none',duration: 3000})return }//3.把手机号/密码提交给服务器端数据API,进行登录验证let data = await userLogin(this.phone, this.pwd)// console.log(data)//4.登录成功,提示“欢迎回来”,跳转到首页if(data.code===2000){ //登录成功//跳转到“首页” —— 切换页签uni.switchTab({url:'/pages/index/index'})}}else { //登录失败//弹出一个“模态”对话框uni.showModal({title: '错误',content: '登录失败!服务器返回消息:'+data.msg})}},}}
</script>
index.vue
//生命周期方法:组件加载完成
async onLoad() { //弹出“欢迎回来”提示框uni.showToast({title:'欢迎回来',icon: 'none',duration: 3000})
},
令牌机制
令牌概念
HTTP协议属于**“无状态协议”**——客户端发起一个HTTP请求,服务器返回一个HTTP响应,服务器不会记录客户端的任何信息。实际应用中,很多场景下需要服务器记录客户端访问信息:例如根据访问历史进行后续的推荐、主题选择、购物车…
实现这类效果可用的技术:Cookie、SessionStorage&LocalStorage、Session、Token
Token:令牌,用于证明客户端身份的机制。
**原理:服务器端把客户端的信息保存在一个对象中,**加密为一个定长字符串,发送给客户端;客户端保存起来;等到下次请求时,客户端可以再把加密字符串返回给服务器;服务器可以解密出其中的原始信息,从而进一步查询更多信息——类似于银行给客户端的“银行卡”,其中存储着客户端的信息(加密存储,客户端是读不懂的),后续有些请求需要客户端出示此“银行卡”有些请求则不需要。
客户端 | 服务器 |
---|---|
1、客户端发送简单请求,包含phone和pwd | |
2、服务器验证登录信息,成功后,把客户端信息保存在一个对象中,形如:{ 用户编号:123, 用户名:yaya, 登录时间:x年x月x日 xx:xx:xx, 登录过期时间:x年x月x日 xx:xx:xx …. } | |
3.服务器将上述对象加密为定长字符串(即token);随同响应消息一同返回给客户端: { code:2000, msg: ‘login succ’, token: ‘加密后的定长字符串’ } | |
4、接收到响应消息,把其中的token保存在客户端 uni.setStorageSync(‘userToken’, data.token) | |
5、从客户端读取之前保存的token let token=uni.getStorageSync(‘userToken’) | |
6、发送请求消息,把token放在请求消息头中(与后端协商好的请求头) uni.request({ url, header:{ token: token } }) 生成的请求消息形如: GET /index/data HTTP1.1 token: ‘‘加密后的字符串’’ | |
7、服务器接收到请求消息,从请求头中读取req.headers.token(即token),解密令牌,得到原始的令牌信息,即:{ 用户编号:123, 用户名:yaya 登录时间:x年x月x日 xx:xx:xx, 登录过期时间:x年x月x日 xx:xx:xx …. } | |
8、服务器根据用户信息查询数据库,将用户信息返回给客户端 |
本地存储
异步操作localStorage:
uni.setStorage(k, v, success(){})
uni.getStorage(k, success(){})
uni.removeStorage(k, success(){})
uni.clearStorage( success(){} )
同步操作localStorage:
uni.setStorageSync(k, v)
uni.getStorageSync(k)
uni.removeStorageSync(k)
uni.clearStorageSync( )
<script>import { userLogin } from '../../service/'export default {data(){return {statusBarHeight: 0, //系统状态栏高度hidePwd: true, //是否隐藏密码phone: "13501234567", //用户输入的登录手机号pwd:"123456", //用户输入的登录密码}},//生命周期方法 —— 页面挂载完成onLoad(){ //此处此方法类似于mounted()//console.log('login组件挂载完成')//获取系统信息,读取其中的“状态栏高度”let {statusBarHeight} = uni.getSystemInfoSync()// console.log('屏幕高度:', screenHeight);this.statusBarHeight = statusBarHeight},methods: {async doLogin(){//console.log('当前输入:',this.phone, this.pwd)//1.验证手机号是否合法,不合法就弹出提示框,退出执行if(!/^1[3-9]\d{9}$/.test(this.phone)){uni.showToast({title: '手机号非法', //提示标题icon: 'none', //图标duration: 3000 //持续时长 })return}//2.验证密码是否合法,不合法就弹出提示框,退出执行if(this.pwd.length < 6){uni.showToast({title:'密码格式非法',icon: 'none',duration: 3000})return }//3.把手机号/密码提交给服务器端数据API,进行登录验证let data = await userLogin(this.phone, this.pwd)// console.log(data)//4.登录成功,提示“欢迎回来”,跳转到首页if(data.code===2000){ //登录成功//在客户端存储服务器返回的token(身份令牌)uni.setStorageSync('userToken', data.token)//跳转到“首页” —— 切换页签uni.switchTab({url:'/pages/index/index'})}else { //登录失败//弹出一个“模态”对话框uni.showModal({title: '错误',content: '登录失败!服务器返回消息:'+data.msg})}},}}
</script>
获取主页数据
service/index.js中提供后去主页数据方法
传输令牌 获取数据
/*** API-2.1、首页数据* 接口地址:index/data* 请求方式:GET* 请求头部:token - 用户登录后保存在客户端的身份凭证*/
export let indexData = async ( )=>{//1.准备请求URLlet url = base + 'index/data'//2.显示“加载中”提示框 uni.showLoading({title: '首页数据读取中'})//3.发起异步请求消息let [err, res] = await uni.request({url, //请求地址header: { //请求头部-token(客户端身份令牌)token: uni.getStorageSync('userToken')}})//4.隐藏“加载中”提示框uni.hideLoading()//5.返回响应消息主体return res.data
}
index.vue在onLoad()中获取主页数据
import { indexData, base } from '@/service'
//生命周期方法:组件加载完成
async onLoad() { //弹出“欢迎回来”提示框uni.showToast({title:'欢迎回来',icon: 'none',duration: 3000})//向服务器请求首页数据let data = await indexData()console.log(data)
},
生命周期方法
应用程序生命周期方法
整个应用程序的生命周期方法 —— App.vue —— 高仿微信小程序**
onLaunch():整个应用程序启动了
onShow():应用程序显示出来,例如:第一次启动完成、从其它应用切换会当前应用
onHide():应用程序隐藏起来了,例如:来电话了、用户点击桌面按钮
页面生命周期方法
页面的生命周期方法 —— pages —— 高仿微信小程序**
onLoad():当前页面挂载完成,功能类似于mounted
onShow():页面显示出来了,例如:第一次挂载完成、导航返回之前的页面
onReady():页面准备就绪了,每个页面此方法调用且仅调用一次——第一次调用onShow之后
onHide():页面隐藏起来了,例如:导航跳转到下一个页面
onUnload():当前页面完成卸载,功能类似于destroyed
onPageScroll():页面滚动了
onReachBottom():页面滚动到底部了
onPullDownRefresh():页面在顶部下拉刷新了
组件生命周期方法
组件的生命周期方法 —— components —— 高仿Vue.js
创建时期:beforeCreate()、created()
挂载时期:beforeMount()、mounted()
更新时期:beforeUpdate()、updated()
销毁时期:beforeDestroy()、destroyed()
主页开发
保存主页数据
<script>
import { indexData, base } from '@/service'export default {data() {return {base, //把服务器基础地址变量设置为数据属性carousels:[], //轮播广告条目列表menuItems:[], //当前用户选中的功能菜单列表activities:[], //最新的社区活动列表}},//生命周期方法:组件加载完成async onLoad() { //弹出“欢迎回来”提示框uni.showToast({title:'欢迎回来',icon: 'none',duration: 3000})//向服务器请求首页数据let data = await indexData()this.carousels = data.carousels //轮播广告this.menuItems = data.menuItems //功能菜单this.activities = data.activities //社区活动},methods: {}}
</script>
轮播图实现
<!-- F1: 轮播广告 -->
<!-- indicator-dots:是否显示“小圆饼”指示器 -->
<!-- autoplay:是否自动播放轮播广告 -->
<!-- interval:时间间隔,两个广告间停留时间 -->
<!-- duration:持续时长,一个广告的过渡动画持续时长 -->
<swiper :indicator-dots="true" :autoplay="true" :interval="2000" :duration="500"><swiper-item v-for="(c, i) in carousels" :key="i"><view class="swiper-item"><image @click="jump(c.href)" :src="base + c.pic" mode="widthFix"/></view></swiper-item>
</swiper>
<script>
import { indexData, base } from '@/service'export default {data() {return {base, //把服务器基础地址变量设置为数据属性carousels:[], //轮播广告条目列表menuItems:[], //当前用户选中的功能菜单列表activities:[], //最新的社区活动列表}},//生命周期方法:组件加载完成async onLoad() { //弹出“欢迎回来”提示框uni.showToast({title:'欢迎回来',icon: 'none',duration: 3000})//向服务器请求首页数据let data = await indexData()this.carousels = data.carousels //轮播广告this.menuItems = data.menuItems //功能菜单this.activities = data.activities //社区活动},methods: {jump(url){console.log(url)//导航跳转到指定页uni.navigateTo({ url })}}}
</script>
<style scoped lang="scss">//提示:页面中可以使用标签选择器,但是组件中不能使用.swiper-item > image {width: 750rpx;}
</style>
宫格图实现
需要安装uni-grid组件
<!-- F2: 功能菜单 -->
<!-- column:一行中默认显示几列 -->
<!-- showBorder:是否显示边框 -->
<!-- square:每个宫格项是否显示为方形 -->
<uni-grid class="func-menu" :column="4" :showBorder="false" :square="true"><uni-grid-item v-for="(item,i) in menuItems" :key="i"><view class="menu-item" @click="jump(item.href)"><image :src="base+item.pic" mode="widthFix"/><text>{{item.title}}</text></view></uni-grid-item>
</uni-grid>
<style scoped lang="scss">.func-menu {margin-top: $uni-spacing-col-base;background-color: $uni-bg-color;.menu-item {height: 100%;//把弹性容器的主轴方向修改为:纵向flex-direction: column;//弹性容器中的子元素在主轴方向上居中对齐justify-content: center;//弹性容器中的子元素交叉轴方向上居中对齐align-items: center;> image { width:35%; margin-bottom: $uni-spacing-col-sm; }}}
</style>
商业服务功实现
需要安装uni-card扩展组件
<!-- F3: 商业服务 -->
<!-- isFull:是否显示为“通栏卡片”(左右撑满) -->
<uni-card class="card" title="| 商业服务" is-full><view class="service"><!-- 左侧:房屋租售 --><view class="service-item"><view class="txt"><text>房屋租售</text><view><navigator>租房</navigator><navigator>短租</navigator></view></view> <!-- 图片缩放模式1:widthFix --><!-- 图片缩放模式2:scaleToFill:不保持原始宽高比,缩放图片填满指定宽高 --><image class="img" mode="scaleToFill" src="../../static/img/chuzu.png"/></view><!-- 右侧:便民服务 --><view class="service-item"><view class="txt"><text>便民服务</text><view><navigator>便利店</navigator><navigator>超市</navigator></view></view> <image class="img" mode="scaleToFill" src="../../static/img/bianmin.png"/></view></view>
</uni-card>
.service {width: 100%;.service-item {//弹性子元素尺寸增长权重为:1flex: 1;padding-top: $uni-spacing-col-sm;padding-bottom: $uni-spacing-col-sm;justify-content: space-between; //弹性容器中的子元素在主轴方向上:空白在中央align-items: center; //弹性容器中的子元素在交叉轴方向上:居中对齐&:nth-child(1) {padding-right: $uni-spacing-row-sm;}&:nth-child(2) {padding-left: $uni-spacing-row-sm;}.txt { font-size: $uni-font-size-sm; flex-direction: column; flex: 1; //弹性子元素尺寸增长权重:1navigator {margin-right: $uni-spacing-row-sm;}}.img { width: 150rpx; height: 120rpx;}}
}