导航布局
src/views/login/index.vue```vue<a name="2wGH0"></a> ## 表单布局与校验 - `src/views/login/index.vue` ```vue <template> <div class="login"> <!-- 顶部导航 --> <!-- 登陆表单 --> <van-form @submit="onSubmit"> <van-field v-model="form.phone" name='phone' label='手机号' placeholder='请输入手机号' :rules="[{ required: true, message: '请填写手机号' }, { validator: phoneCheck, message: '格式有误,请重新输入' }]"></van-field> <van-field v-model="form.password" type='password' name='密码' label='密码' palceholder='请输入密码' :rules="[{ required: true, message: '请填写密码' }, { validator: passwordCheck, message: '格式有误,请重新输入' }]"></van-field> <div style="margin:16px"> <van-button round block type='info' native-type='submit' :loading='isLoading'>登陆</van-button> </div> </van-form> </div> </template> <script> export default { name: 'Login', data () { return { // 登陆表单数据 form: { phone: '17201234567', password: 'qsryja' }, // 登陆按钮加载中状态 isLoading: false } }, methods: { // 手机号校验 phoneCheck (value) { return /^1\d{10}$/.test(value) }, // 密码校验 passwordCheck (value) { return /^[a-zA-Z0-9]{6,12}$/.test(value) }, // 提交 onSubmit () { this.$toast('登陆成功') }, } } </script> <style lang="scss" scoped> </style>接口封装
src/services/user.js```javascript import request from ‘@/utils/request’
// 用户登陆 export const login = data => { return request({ method: ‘POST’, url: ‘/front/user/login’, data: new URLSearchParams(data).toString() //转换参数格式 {key:val} => ‘key=val’ }) }
<a name="5Twu0"></a> ## 存储登陆状态 - `src/store/index.js` ```javascript import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { // 存储登陆用户数据,尝试读取本地存储数据 user: JSON.parse(window.localStorage.getItem('lagouedu-mobile-user') || null) }, mutations: { // 用于修改 user setUser (state, payload) { // payload 请求到的用户数据,JSON 格式 state.user = JSON.parse(payload) // 将数据设置到本地存储, window.localStorage.setItem('lagouedu-mobile-user', payload) } }, actions: { }, modules: { } })src/views/login/index.vue<script> import { login } from '@/services/user' export default { methods: { // 提交 async onSubmit () { this.isLoading = true const { data } = await login(this.form) // 登陆成功 if (data.state === 1) { // 将数据保存到本地存储中 this.$store.commit('setUser', data.content) // 提示,$toast 也可以通过内部方法触发不同提示效果,使用方式类似 element-ui this.$toast.success('登陆成功') } else { this.$toast.fail('登陆失败') } this.isLoading = false }, } } </script>登陆状态检测
src/router/index.js```javascript
const routes = [
// 学习页面 { path: ‘/learn’, name: ‘learn’, component: () => import(/ webpackChunkName: ‘learn’ / ‘@/views/learn’), meta: { requiresAuth: true } // 标志需要进行登陆验证 }, // 用户页 { path: ‘/user’, name: ‘user’, component: () => import(/ webpackChunkName: ‘login’ / ‘@/views/user’), meta: { requiresAuth: true } },
]
const router = new VueRouter({ routes })
// 导航守卫进行登陆检测与跳转 router.beforeEach((to, form, next) => { // 验证 to 路由是否需要进行身份认证 if (to.matched.some(record => record.meta.requiresAuth)) { // 验证 Vuex 的 store 中的用户登陆信息是否存储 if (!store.state.user) { // 未登陆, 跳转到登陆页 return next({ name: ‘login’, query: { // 将本次路由的 fullPath 传递给login 页面 redirect: to.fullPath } }) } // 已登陆 next() } else { next() } }) export default router
- `src/views/login/index.vue` ```vue <script> // 提交 async onSubmit () { // 登陆成功 if (data.state === 1) { // 跳转 this.$router.push(this.$route.query.redirect || '/') } else { } }, </script>通过请求拦截器设置 Token
src/utils/requrest.js```javascript
import store from ‘@/store’
// 设置请求拦截器进行接口鉴权 request.interceptors.request.use(config => { // 读取 store 中存储的 user 数据 const { user } = store.state // 检测 user 是否存在数据,如果有,则进行 token 设置 if (user && user.access_token) { config.headers.Authorization = user.access_token }
return config })
export default request
<a name="1qjmy"></a> ## 刷新 Token - `src/utils/request.js` ```javascript import axios from 'axios' import store from '@/store' import router from '@/router' // 设置请求拦截器进行接口鉴权 // 封装函数,用于跳转登陆页 function redirectLogin () { router.push({ name: 'login', query: { redirect: router.currentRoute.fullPath } }) } // 存储是否正在更新 Token 的状态 let isRefreshing = false // 存储因为等待 Token 刷新而挂起的请求 let requests = [] // 响应拦截器 request.interceptors.response.use(function (response) { // 成功 return response }, function (error) { if (error.response) { const { status } = error.response if (status === 400) { } else if (status === 401) { if (!store.state.user) { // 没有登陆信息 redirectLogin() return Promise.reject(error) } // 检测是否已经存在了正在刷新 Token 的请求 if (isRefreshing) { // 将当前失败的请求,存储到请求列表中 return requests.push(() => { // 当前函数调用后,会自动发送本次失败的请求 request(error.config) }) } isRefreshing = true // 发送请求,获取新的 access_token return request({ method: 'POST', url: '/front/user/refresh_token', data: new window.URLSearchParams({ refreshtoken: store.state.user.refresh_token }).toString() }).then(res => { // 刷新token 失败 if (res.data.state !== 1) { // 清除无效的用户信息 store.commit('setUser', null) redirectLogin() return Promise.reject(error) } // 刷新 token 成功 // 存储新的 token store.commit('setUser', res.data.content) // 重新发送失败的请求 (根据 requests 发送所有失败的请求) requests.forEach(callback => callback()) // 发送完毕,清除 requests 内容即可 requests = [] // 将本次请求发送 return requests(error.config) }).finally(() => { // 请求发送完毕,响应处理完毕,将刷新状态更改为 false isRefreshing = false }) } } // 将本次请求的错误对象继续向后抛出,让接收响应的处理函数进行操作 return Promise.reject(error) }) export default request用户信息处理
src/views/user/index.vue```vue<!-- 用户信息区域 --> <van-cell class="user-info" :border='false'> <van-image width='50' height='50' round :src="userInfo.portrait || 'https://s0.lgstatic.com/common/image/pc/default_boy_headpic1.png'"> </van-image> <div class="user-info-content"> <h3>{{ userInfo.userName || '无昵称' }}</h3> <span><van-icon name='edit'>编辑个人资料</van-icon></span> </div> </van-cell><a name="yBhkm"></a> ## 账号信息处理 - `src/views/user/index.vue` ```vue <template> <div class="user"> <van-cell-group> <!-- 用户信息区域 --> <!-- 账号信息 --> <van-cell class="account-info"> <van-grid :border='false'> <van-grid-item> <span class="grid-item-value">99.99</span> <span class="grid-item-title">学习时长</span> </van-grid-item> <van-grid-item> <span class="grid-item-value">99.99</span> <span class="grid-item-title">钱包/勾豆</span> </van-grid-item> <van-grid-item> <span class="grid-item-value">99</span> <span class="grid-item-title">优惠券</span> </van-grid-item> <van-grid-item> <span class="grid-item-value">999</span> <span class="grid-item-title">学分</span> </van-grid-item> </van-grid> </van-cell> </van-cell-group> <layout-footer></layout-footer> </div> </template> <style lang="scss" scoped> // 用户信息 // 账户信息 .account-info { background-color: rgb(248, 150, 3) } .account-info .van-cell__value{ border-radius: 15px; } .account-info .grid-item-value{ font-size: 22px; font-weight: 700; } </style>底部菜单列表
src/views/user/index.vue```vue
<a name="rwMxL"></a> ## 封装接口与数据绑定 - `src/services/user.js` ```javascript // 用户基本信息 export const getInfo = () => { return request({ method: 'GET', url: '/front/user/getInfo' }) }src/views/user/index.vue```vue ```
