• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
技术杨的博客园
希望每一次的努力都能得到自己想要的
博客园    首页    新随笔    联系   管理    订阅  订阅

vue3+element-plus+登录逻辑token+环境搭建

vue3+element-plus+登录逻辑token环境搭建

  • 安装脚手架工具

1 npm i @vue/cli@4.5.13 -g
  • 验证是否安装成功

1 vue -V # 输出 @vue/cli 4.5.13
  • 脚手架初始化目录结构

1 vue create jd-shop-manager

  • 手动配置需要的功能

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  •  进入到项目目录

1 cd jd-shop-manager
  • 附加其他依赖

1 npm i axios element-plus pinia -S
  • 运行项目

1 npm run serve

浏览器输入localhost:8080查看效果

 

 

 

配置第三方库,main.js根目录

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// 1. 引入 { createPinia }
import { createPinia } from 'pinia'


import router from './router'

createApp(App)
// 2. 安装插件
.use(createPinia())
.use(ElementPlus)
.use(router)
.mount('#app')

创建文件utils/request.js,关于token认证,

  1. 获取token 
  2. 保存token
  3. 后续携带toke
import Axios from 'axios';

const req = Axios.create({
// baseURL:'http://localhost:3000' // 跨域
baseURL:'/api' // 实例独特的表示,走代理解决跨域
});
// 拦截器 = 公共行为: 请求loading 响应关闭loading
// 响应的错误常规处理 401 => 无权访问, 路由跳转到401页面
// 请求时 如果有token, 自动添加到请求头,响应时自动存储token
req.interceptors.response.use((response)=>{
// response:{ data,config:{ headers } }
if (response.data.token) {
// 保存到本地存储: sessionStorage, localStorage
sessionStorage.setItem('token',response.data.token)
}
// 处理响应的业务异常
return response;
},(err)=>{
// 4xx 5xx异常
// 非业务范畴的通用异常提示
console.log('响应异常:',err)
})
// 请求使用token
req.interceptors.request.use((config)=>{
const token = sessionStorage.getItem('token');
if (token) {
config.headers.token = token;
}
return config;
});
export default req;

 

Axios,vue.config.js配置代理,去我微博里面找

 

创建文件api/user.js

1
// 引入3000端口的实例
import req from '@/utils/request';

export const test = ()=> {
return req.get('/')
}
export const login = (data)=>{
return req({
url:'/login',
method:'post',
data
})
}

export const loadMenu = ()=>{
return req.get('/menus/build')
}

pinia插件配置,创建文件store/user.js

// 3. 定义一个Store
// 3.1 引入定义Store的函数
import { defineStore } from 'pinia';
// 4.按约定俗成 返回useXxx
export const useUser = defineStore({
    id:'user',
    state(){
        return {
            userInfo:{},
            userMenu:[]
        }
    },
    // 修改方式
    actions:{
        setUserInfo(user){
            this.userInfo = user;
        },
        // { userMenu:值  }
        setUserMenu(patchData) {
            this.$patch(patchData);
        }
    }
})

 

配置路由文件

创建router/index.js

import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'
import { useUser } from '@/store/user'
import { loadMenu } from '@/api/user';
import Layout from '@/views/Layout.vue'


function checkLogin() {
// 前端验证token有效性
// 请求后端验证token有效性
return window.sessionStorage.getItem('token');
}


function asyncRouteHandler(routes) {
return routes.map(route=>{
// 判断Componet是不是一个布局组件名 Layout
if (route.component === 'Layout') {
route.component = Layout;
} else {
const compPath = route.component;
route.component = () => import(`../views/${compPath}.vue`)
}
// 处理children
if (route.children && route.children.length > 0) {
route.children = asyncRouteHandler(route.children);
}
// 返回route
return route;
});
}

const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// 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')
},
{
path: '/login',
name: 'Login',
// 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/Login.vue')
}
]

const router = createRouter({
history: createWebHashHistory(),
routes
})

async function loadAsyncMenu(to, next) {
const user = useUser();
// 动态根据用户的角色或者id拿到他能看到的页面,并生成路由
const res = await loadMenu();
console.log(res.data,'路由数据'); // []

const asyncRoutes = asyncRouteHandler(res.data);
// Todo: 保存菜单到pinia
user.setUserMenu({ userMenu:res.data });

// 添加到原来的路由对象中
asyncRoutes.forEach(r=>{
router.addRoute(r);
});
// 重新进入路由 , replace避免多一个上一步
next({...to,replace:true});
}
// 路由守卫, 在每次确认路由和跳转页面之前
router.beforeEach((to, from, next) => {
const user = useUser();
// next(); 放行
// next(route对象|| stringPath ); 重定向
// next(false); 取消用户导航行为
// 不调用 白屏卡住
// to 代表当前的访问路径 from 代表从哪里来 to|from.path 具体路径

// 访问白名单(可以是个数组)不需要验证 /login
if (to.path === '/login' || to.path === '/register') {
// 不验证/不重定向 => 放行
return next();
}
// 需要验证
if (!checkLogin()) {
// 去登录
// return next('/login?redirect='+ to.path);
return next({
name: 'Login', query: {
redirect: to.path
}
});
}
// 登录了, 判断是否已生成路由
if (user.userMenu && user.userMenu.length > 0) {
return next(); // 放行
}
// 没有菜单,加载菜单
loadAsyncMenu(to, next)
})
export default router

 

 

实现一个基于ElementPlus的基础登录框

 

创建Login.vue中写

<template> 
  <el-form label-width="80px" ref="formRef" :model="form" :rules="rules">
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username"></el-input>
    </el-form-item>
    <el-form-item label="密码" prop="password">
      <el-input v-model="form.password"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button @click="submit(formRef)">提交</el-button>
      <el-button @click="reset(formRef)">重置</el-button>
    </el-form-item>
  </el-form>
</template>
<script setup>
//基本 内部多属性
import { ref, reactive } from 'vue';
import { test, login } from '@/api/user'
import { useUser } from '@/store/user';
import { useRoute,useRouter } from 'vue-router'


// 一定要在方法内使用 useUser
const user = useUser(); // 相当于setup方法
const route = useRoute();
const router = useRouter();


// 单向数据输出,双向数据绑定,还需要API方法的调用
const formSize = ref('default'); // formSize.value
const ruleForm = reactive({ // 直接跟原始结构一样 ruleForm.username
username: 'Hello123',
password: '123456'
})
// 需要改变的复杂对象, reactive, 性能优化,直接使用固定对象
const rules = {
username: [
{ required: true, message: '必须输入用户名', trigger: 'blur' },
],
password: [
{ required: true, message: '必须输入密码', trigger: 'blur' },
]
}
// 获取页面组件对象
const ruleFormRef = ref();


// const doSubmit = () => {
// // 需要注意
// ruleFormRef.value.validate((valid, fields) => {
// if (valid) {
// console.log('submit!')
// } else {
// console.log('error submit!', fields)
// }
// })
// }

// 传递参数的方式
const doSubmit = (form) => {
// 需要注意
form.validate(async (valid, fields) => {
if (valid) {
console.log('submit!');
// login
let res = await login(ruleForm);
console.log(res, '测试数据');
// 登录成功
// 修改Pinia里面的数据状态
// user.setUserInfo({
// username: 'Green'
// });

// 跳转到首页 或者 [之前地址栏的页面] 路由参数redirect
router.push(route.query.redirect || '/');




} else {
console.log('error submit!', fields)
}
})
}

 样式篇

  • 初始化样式
html,body { height: 100%; width: 100%;}a { text-decoration: none; }ul {padding:0; }li {list-style: none; }html,body,h1,h2,h3,h4,h5,h6 { padding:0; margin:0; }span {display:inline-block; }
View Code

 

posted @ 2022-09-16 18:15  技术杨  阅读(2419)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3