vue create vue-mart
cube-ui 是滴滴2017年底开源的一款基于 Vue.js 2.0 的移动端组件库,主要核心目标是做到体验极致、灵活性强、易扩展以及提供良好的周边生态—后编译。
Cubi-ui快速入门:https://didi.github.io/cube-ui/#/zh-CN/docs/quick-start
如果使用的是vue3.0以上版本,可以在项目目录下执行一下命令安装cube-ui插件。
vud add cube-ui
安装成功后,在项目中可以看到cube-ui.js文件。该文件把cube-ui的所有组件一次性导入。
vue add axios
vue add vuex
修改router/index.js文件,添加登录页的路由配置。
{
path: '/login',
name: 'login',
component: Login,
},
然后在views目录中新建Login.vue。然后使用cube-form组件定义表单。
cube-form定义格式:
<cube-form
:model="model"
:schema="schema"
:immediate-validate="false"
:options="options"
@validate="validateHandler"
@submit="submitHandler"
@reset="resetHandler">cube-form>
其中,model就是整个表单需要的数据源,schema 就是生成表单所定义的模式,submit 校验成功后提交事件,validate 每次有数据校验更新的事件。
登录表单完整代码:
<template>
<div>
<cube-form :model="model"
:schema="schema"
@submit="handleLogin"
@validate="handleValidate"></cube-form>
</div>
</template>
<script>
export default {
data() {
return {
model: { // 定义数据模型
username: '',
password: '',
},
schema: { // 定义表单结构
fields: [
{
type: 'input',
modelKey: 'username',
label: '用户名',
props: { // INPUT标签属性
placeholder: '请输入用户名'
},
rules: { // 校验规则
required: true
},
trigger: 'blur', // 触发器
messages: {
required: '用户名不能为空'
}
},
{
type: 'input',
modelKey: 'password',
label: '密码',
props: { // INPUT标签属性
placeholder: '请输入密码',
type: 'password',
eye: { // 小眼睛
open: false, // 关闭眼睛
}
},
rules: { // 校验规则
required: true
},
trigger: 'blur', // 触发器
messages: {
required: '密码不能为空'
},
},
{
type: 'submit',
label: '登录',
},
]
}
}
},
methods: {
async handleLogin(e) {
console.log('登录');
},
handleValidate(ret) {
console.log('校验结果:', ret);
},
}
}
</script>
<style lang="scss" scoped>
</style>
cube-form的详细用法:https://didi.github.io/cube-ui/#/zh-CN/docs/form
在main.js文件中定义全局的axios。
Vue.prototype.$http = axios
在Login.vue中实现handleLogin方法。
async handleLogin(e) {
e.preventDefault();
// 发送登录请求
const res = await this.$http.get('/api/login', {
params: {
username: this.model.username,
password: this.model.password,
}
});
// 把响应结果结构出来
const {code, token, message} = res.data;
if (code == 0) {
// 登录成功
localStorage.setItem('token', token); // 把token存储在本地
this.$store.commit('setToken', token); // 更新state
// 登录成功后回调到指定页面
const redirect = this.$route.query.redirect || '/';
this.$router.push(redirect);
} else {
// 登录失败
const toast = this.$createToast({
time: 2000,
txt: message || '登录失败',
type: 'error'
});
toast.show();
}
},
后台登录返回结果的数据格式:
{
code: ''
token: ''
message: ''
}
code:登录状态码,0代表登录成功,-1代表登录失败,可自行定义;
token:登录成功后生成的令牌;
message:登录失败的信息;
在vue.config.js中mock登录数据:
// mock数据
devServer: {
before(app) {
// 登录
app.get('/api/login', function(req, res) {
const {username, password} = req.query; // post请求在body中取出参数
if (username === 'admin' && password === '123') {
res.json({
code: 0,
token: 'jilie',
});
} else {
res.json({
code: -1,
message: '用户名或密码错误',
});
}
});
}
}
在store中存储用户token。
export default new Vuex.Store({
state: {
token: localStorage.getItem('token') || '', // 令牌
},
mutations: {
setToken(state, token) {
state.token = token;
}
},
getters: {
isLogin(state) {
return !!state.token;
}
},
actions: {
},
modules: {
}
})
上面在getters中定义了isLogin方法,用户判断用户是否登录。!!state.token中的双感叹号表示把state.token转换为布尔值。如果token为空,则转换为false,否则返回true。
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/login" v-if="!isLogin">Login</router-link>
<a v-if="isLogin" @click="logout">logout</a>
</div>
<router-view/>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
computed: {
...mapGetters(['isLogin'])
},
methods: {
async logout() {
await this.$http.get('/api/logout');
}
}
}
</script>
<style>
</style>
上面增加了登录和注销按钮的逻辑判断。如果state的isLogin方法返回true,则显示Logout;否则显示Login。
第一步:在src目录下新建http-interceptor.js文件。
第二步:在main.js文件引入http-interceptor。
import interceptor from './http-interceptor' // 引入拦截器,只会执行一次
请求拦截器用于在发送请求前加入token参数到请求头中。
import axios from 'axios';
import store from './store';
import router from './router';
axios.interceptors.request.use(
config => {
if (store.state.token) {
// 如果token存在,则放入请求头中
config.headers.token = store.state.token;
}
return config;
}
);
响应拦截器用于提前预处理响应。
axios.interceptors.response.use(
response => {
// 如果code等于-1,代表用户已注销或token已过期
// 此时需要重新登录,并且清除本地的缓存信息。
if (response.status == 200) {
if (response.data.code == -1) {
clearHandler();
}
return response;
}
}
);
// 清除用户登录状态,并跳转登录页
function clearHandler() {
store.commit('setToken', ''); // 清除store中的token
localStorage.removeItem('token'); // 清除localStorage中的token
// 跳转到登录页面
router.push({
path: '/login',
redirect: router.currentRoute.path, // 登陆后重定向回当前path
});
}
在vue.config.js文件中模拟/api/logout。
// 注销
app.get('/api/logout', function(req, res) {
res.json({code: -1});
});
修改index.js文件,调用router.beforeEach函数,该函数在访问路由之前进行过滤操作。
import store from '../store'
// 守护
router.beforeEach((to, from, next) => {
if (to.meta.auth) {
// 需要认证,则检查令牌
if (store.state.token) { // 如果存在令牌则放行
next();
} else { // 如果不存在令牌,则重定向到登录页面
next({
path: '/login',
query: {
redirect: to.path
}
});
}
} else {
next();
}
});
修改router/index.js文件,在菜单路由配置项中指定meta属性。如果meta.auth为false,代表该路由不需要认证;如果meta.auth为true,代表该路径需要认证。
const routes = [
{
path: '/',
name: 'home',
component: Home,
meta: {
auth: false,
},
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
auth: false,
},
},
{
path: '/about',
name: 'about',
meta: {
auth: true,
},
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
在vue.config.js中定义服务端中间件,该中间件在响应数据前先被调用。
app.use(function(req, res, next) {
// 检查token
if (/^\/api/.test(req.path)) {
if (req.path === '/api/login' || req.headers.token) {
next();
} else {
// 提示用户登录
res.sendStatus(401);
}
} else {
next();
}
});
修改http-interceptor.js文件,配置响应拦截器的失败处理事件。
axios.interceptors.response.use(
response => {
...
}, err => { // 服务器响应失败
if (err.response.status === 401) {
console.log('用户未认证');
clearHandler();
}
}
);