SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——Vue3.0前后端分离架构——后台系统前端登录实现

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新):https://blog.csdn.net/grd_java/article/details/144986730

1. 跨越解决

前后端分离项目必须解决跨越问题,我们在后端已经解决过了,不多赘述


https://blog.csdn.net/grd_java/article/details/145165507

2. 前端请求后端,保存token

1. auth.js工具类的作用,就是方便我们对token进行增删改查,存储时,是存储到cookie中的


在这里插入图片描述


文件中,统一使用功能js-cookie第三方工具包来实现对cookie的操作

2. request.js工具类,就是封装了3大件


1. service对象,发送请求时我们要求其拼接baseUrl和组合url,baseurl就是后端服务器的ip地址,组合url就是要请求的具体url。这样我们写请求时,就不用每次都手写baseurl了


2. 请求拦截器,每次发送请求时,先拦截请求,带上token(放在headers中的authorization中),如果没有就不带


3. 响应拦截器,后端响应后,进行拦截,根据后端响应的内容做一些通用处理,例如响应码不是20000的,就提示异常信息等等常用操作

3. api,就是封装了请求用户操作相关后端接口的一个工具类,这里初步定义了3个,登录,获取登录用户信息,退出登录3个接口


在这里插入图片描述

import {service} from '@/utils/request'
// import qs from 'qs'
// const str = "user/loging?"+qs.stringify({data})
export function login(data) {
  return service({
    url: 'security/login',
    method: 'get',
    params: data
  })
}
export function getInfo() {
  return service({
    url: 'security/LoginUserInfo',
    method: 'get'
  })
}

export function logout() {
  return service({
    url: 'security/logout',
    method: 'get'
  })
}

在这里插入图片描述

import {service} from '@/utils/request'

export function menuList(){
  return service({
    url:'/security/dd-menu/menus',
    method:'get'
  })
}
export function addMenu(data){
  return service({
    url:'/security/dd-menu/',
    method:'post',
    data
  })
}
export function updateMenu(data){
  return service({
    url:'/security/dd-menu/',
    method:'put',
    data
  })
}
export function deleteMenuById(id){
  return service({
    url:`/security/dd-menu/${id}`,
    method:"delete",
  })
}
export function deleteMenuList(parameter){
  return service({
    url:'/security/dd-menu/ids',
    method:"delete",
    params: parameter
  })
}
export function updateMenuAndRole(parameter){
  return service({
    url:'/security/dd-menu/roleAndMenu',
    method:"put",
    params: parameter
  })
}

export function menuIdByRoleId(rid){
  return service({
    url:`/security/dd-menu/menuIdByRoleId/${rid}`,
    method:"get"
  })
}


export function getMenusByUserId(id){
  return service({
    url:`/security/sysMenu/getMenusByUserId/${id}`,
    method:"get"
  })
}

4. pinia


在这里插入图片描述


首先,既然是处理user相关的全局变量,肯定要引入处理相关逻辑

1. 和登录有关的接口,就是上面的api文件中的3个接口


2. 有了用户信息,就要拿到这个用户的权限,以及他能看到的路由和按钮,这个是后面的内容,这里先放着


3. 树形结构工具,就是处理菜单用的工具


4. 获取路由,路由是直接定义好的(全有的状态),但是我们要根据用户权限,去掉这些路由中当前用户不能访问的路由


其它的代码就是业务代码,具体讲到的时候再说


关于登录的就是login


在这里插入图片描述

5. 修改store逻辑


登录依旧为请求后,设置token


在这里插入图片描述


获取用户信息


在这里插入图片描述

        // user login
        //处理登录业务
        async login(userInfo) {

            //解构出用户名和密码
            const { username,password} = userInfo;
            //异步调用/api/user文件下的login
            let result = await login({username: username.trim(),password:password});
            // console.log(result)
            if (result.code===20000){
                //我们规定token为tokenHead + " " + token字符串
                // console.log(result)
                const token = result.tokenInfo.tokenValue;
                //设置到state
                // commit("SET_TOKEN",token);
                this.token = token
                //通过auth.js文件的函数,设置到cookie
                setToken(token)
            }else{
                //登录失败
                return Promise.reject(new Error(result.msg));
            }
        },

        // get user info
        async getInfo() {
            //异步调用/api/user文件下的login
            let result = await getInfo();
            if(result.code === 20000){
                const loginInfo = result.userInfo
                if (!loginInfo) {
                    return reject('验证失败,请重新登录.');
                }
                //处理路由
                let menusResult = await getMenusByUserId(loginInfo.id)
                if(menusResult.code === 20000){
                    const { menus } = menusResult
                    if(menus.length > 0){
                        const _menus = menus
                        let routes = []
                        let buttons = []
                        _menus.forEach(e=>{
                            if(e.type === 'menu'){
                                routes.push(e.path)
                            }else if(e.type === 'button') {
                                buttons.push(e.permission)
                            }
                        })
                        // commit('SET_BUTTONS',buttons)
                        this.buttons = buttons;

                        // commit('SET_ROUTES',routes)
                        this.routes = routes;
                        const { roles } = loginInfo
                        // commit('SET_ROLES',roles)
                        this.roles = roles;

                        //对比后设置需要展示的异步路由
                        const resultAsyncRoutes = compareasyncRoutes(asyncRoutes,routes)
                        // commit('SET_RESULTASYNCROUTES',compareasyncRoutes(asyncRoutes,routes))
                        this.resultAsyncRoutes = resultAsyncRoutes
                        //合并常量、异步、路由最终展示的路由
                        this.routesResult = constantRoutes.concat(this.resultAsyncRoutes,anyRoutes);
                        // console.log("asd;kfjlk;asdjdkl;fds",JSON.parse(JSON.stringify(this.routesResult)))
                        // router.addRoute(JSON.parse(JSON.stringify(this.routesResult)))
                        // router.matcher.addRoutes(JSON.parse(JSON.stringify(this.routesResult)))
                        //将最终合并路由添加到路由中
                        for (let i = 0; i < resultAsyncRoutes.length;  i++) {
                            router.addRoute(resultAsyncRoutes[i])
                        }

                    }
                }else{
                    // commit('SET_RESULTASYNCROUTES',compareasyncRoutes(asyncRoutes,[]))
                    this.resultAsyncRoutes = []
                    this.routesResult = constantRoutes.concat(this.resultAsyncRoutes,anyRoutes);
                    console.log(asyncRoutes);
                    router.addRoute(this.routesResult)
                }
                const {username} = loginInfo;
                // commit('SET_NAME',username);
                // commit('SET_AVATAR','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif');
                this.name = username;
                this.avatar = 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif';
            }else{
                //登录失败
                return Promise.reject(new Error(result.msg));
            }
        },

3. 为什么没有手动传参token呢?因为我们request.js中的请求拦截器,会在每次请求时,都将token放在headers中的authorization中(如果有的话)


在这里插入图片描述

3. 前端登录页面逻辑实现

1. 先在常量路由中添加login路由,同时创建login对应的vue文件


在这里插入图片描述

{
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

2. 在App.vue中将针对about和home两个路由的link去掉,只留下router-view即可


在这里插入图片描述

3. 登录页面


在这里插入图片描述


在这里插入图片描述

<template>
  <div class="login-container">
    <!--    绑定loginForm表单 -->
    <el-form ref="loginRef"
             :model="loginForm"
             :rules="loginRules"
             class="login-form"
             auto-complete="on"
             label-position="left">

      <div class="title-container">
        <h3 class="title">Login Form</h3>
      </div>
<!--prop="username",是表单校验需要的内容-->
      <el-form-item prop="username">
        <span class="svg-container"><el-icon ><UserFilled /></el-icon></span>
<!--        绑定loginFrom中的username-->
        <el-input
            v-model="loginForm.username"
            placeholder="Username"
            name="username"
            type="text"
            auto-complete="on"
        />
      </el-form-item>

      <el-form-item prop="password">
        <el-icon class="svg-container"><Lock /></el-icon>
        <el-input
            v-model="loginForm.password"
            type="password"
            size="large"
            placeholder="Password"
            name="password"
            auto-complete="on"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
        </span>
      </el-form-item>
<!--      <el-checkbox style="margin: 0px 0px 25px 0px">记住密码</el-checkbox>-->
<!--      <el-form-item prop="code" class="code">-->
<!--        <span class="svg-container">-->
<!--&lt;!&ndash;          <svg-icon icon-class="el-icon-s-flag" />&ndash;&gt;-->
<!--          <i class="el-icon-edit"></i>-->
<!--        </span>-->

<!--        <el-input-->
<!--            class="code-input"-->
<!--            ref="code"-->
<!--            type="text"-->
<!--            v-model="loginForm.code"-->
<!--            placeholder="验证码"-->
<!--            name="code"-->
<!--            tabindex="2"-->
<!--            auto-complete="on"-->
<!--            @keyup.enter.native="handleLogin"-->
<!--        />-->

<!--      </el-form-item>-->
<!--      <img :src="codesrc" class="code-img" @click="getImgCode">-->
      <el-button ref="loginButton" :disabled="disabledFlag" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>

      <div class="tips">
        <span style="margin-right:20px;">username: admin</span>
        <span> password: any</span>
      </div>

    </el-form>
  </div>
</template>
<script setup>
import {watch, ref, onMounted} from 'vue'
  import {ElMessage} from 'element-plus'
  import {usePiniaUserStore} from '@/store/pinia_user'
  import {useRouter} from 'vue-router'

  const router = useRouter()//router对象,VUE3写法
  const piniaUserStore = usePiniaUserStore()
  //引用
  const loginForm = ref({
    username: '',
    password: ''
  })
  //获取ref="loginRef"的DOM元素
  const loginRef = ref(null)
  //登录按钮是否禁用
  const disabledFlag = ref(false)//先不禁用


  //表单校验规则
  const loginRules = {
    username: [
        //提交表单时触发,blur模糊匹配
        {required: true, trigger:'blur',message: '请输入用户名'},
        {min:3,max:25,message: '用户名长度应该为3-25个字符',trigger:'blur'},
    ],
    password: [{required: true, trigger:'blur',message: '请输入密码'},]
  }
/**
 * 监听路由的变化,如果是从某一个页面A跳转过来进行登录,就记录A的路由,完成登录后跳转回A,而不是无论如何都跳转回主页
 * watch: {
 *     $route: {
 *       handler: function(route) {
 *         this.redirect = route.query && route.query.redirect
 *       },
 *       immediate: true
 *     }
 *   },
 */
  const redirect = ref(null)//重定向地址
  //路由变动时需要的监听触发函数
  const handleRouteChange =  (newRoute)=>{
    console.log(router.currentRoute.value.query && router.currentRoute.value.query.redirect)
    redirect.value =  router.currentRoute.value.query && router.currentRoute.value.query.redirect
  }
  //模拟immediate=true的情况
  onMounted(()=>{
    handleRouteChange()
  })
  //监听路由的变化
  watch(router,handleRouteChange)
  //登录逻辑
  const handleLogin = () => {
    //禁用登录按钮
    disabledFlag.value=true
    //先表单验证
    loginRef.value.validate(async (valid) => {
      //如果验证通过
      if (valid) {
        //禁用登录按钮
        // disabledFlag.value=true
        //action监听,user.js下的login
        // await store.dispatch('user/login', loginForm.value).then(() => {
        await piniaUserStore.login(loginForm.value).then(() => {
          console.log('登录成功')
          console.log('跳转:',redirect.value || '/')
          //进行路由跳转,如果redirect存在,就跳转回redirect的地址,而不是'/'

          router.push({path:redirect.value || '/'})
          disabledFlag.value=false//异步过程中,阻止登录按钮的使用
        }).catch(()=>{
          disabledFlag.value=false//异步过程中,阻止登录按钮的使用
        })
      }else{//如果验证不通过
        console.log('登录失败')
        ElMessage.error('输入信息有问题个damn的!!!!')
        return false
      }
    })
  }
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */

$bg:#283443;
$light_gray:#fff;
$cursor: #fff;

@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  .login-container .el-input input {
    color: $cursor;
  }
}

/* reset element-ui css */
.login-container {
  .el-input {
    display: inline-block;
    height: 47px;
    width: 85%;
    .el-input__wrapper{
      display: inline-block;
      height: 100%;
      width: 100%;
      background: transparent !important;//背景透明
      box-shadow: 0 0 0px 0px $bg inset !important;
      -webkit-text-fill-color: $cursor !important;
      .el-input__inner {
        //background-color: transparent;
        background: transparent !important;//背景透明
        transition: background-color 5000s ease-in-out 0s !important;
        border: 0px;
        -webkit-appearance: none;
        border-radius: 0px;
        padding: 12px 5px 12px 15px;
        color: $light_gray;
        height: 47px;
        caret-color: $cursor;
      }
    }

  }

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    color: #454545;
  }
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;
  background-image: url("~@/assets/back.jpg");//想要使用@,必须加~前缀
  background-size:100% 100%;
  .code{
    width: 70%;
    float: left;
  }
  .code-img{
    float: right;
    width: 29.6%;
    //height: 100%;
  }
  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin-top: 20%;
    margin: 7% auto auto auto;
    overflow: hidden;
  }

  .tips {
    font-size: 14px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    width: 30px;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
  }

  .show-pwd {
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }
}
</style>

4. 测试

在这里插入图片描述


输入登录页面的路由,输入用户名和密码点击登录后,成功拿到token并保存在cookies中

5. 主页

5.1 架子

后台系统页面,先将基本的架子搭起来


使用element plus的下图所示布局,复制代码


在这里插入图片描述


将其放入layout文件夹下的index.vue中,对应的也要定义4个子组件,将Aside、Header、Main、Footer分成4个组件


在这里插入图片描述

<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px"> <AsideMenu/> </el-aside>
      <el-container>
        <el-header> <Header/> </el-header>
        <el-main> <Main/> </el-main>
        <el-footer> <Footer/> </el-footer>
      </el-container>
    </el-container>
  </div>
</template>

<script setup>
  import AsideMenu from '@/layout/AsideMenu'
  import Footer from "@/layout/Footer"
  import Header from "@/layout/Header"
  import Main from  "@/layout/Main"

</script>
<style scoped>

</style>

5.2 左侧导航栏

然后编写左侧导航栏样式


在这里插入图片描述


在这里插入图片描述

<template>
  <el-row class="tac">
    <el-col :span="12" class="menuBackGroud">
      <h5 class="mb-2 margin_mine">
        <el-avatar
            src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
            class="menu-avatar"
        />
        <span class="menuTitle">您好!{{piniaUserStore.name }}</span>

      </h5>
      <el-menu
          :router="true"
          active-text-color="#ffd04b"
          background-color="#545c64"
          class="el-menu-vertical-demo margin_mine"
          default-active="3"
          text-color="#fff"
          @open="handleOpen"
          @close="handleClose"
      >
        <el-sub-menu index="1">
          <template #title class="">
            <el-icon><location /></el-icon>
            <span>Navigator One</span>
          </template>

            <el-menu-item index="1-1">item one</el-menu-item>
            <el-menu-item index="1-2">item two</el-menu-item>


            <el-menu-item index="1-3">item three</el-menu-item>

          <el-sub-menu index="1-4">
            <template #title>item four</template>
            <el-menu-item index="1-4-1">item one</el-menu-item>
          </el-sub-menu>
        </el-sub-menu>
        <el-menu-item index="2">
          <el-icon><icon-menu /></el-icon>
          <span>Navigator Two</span>
        </el-menu-item>
        <el-menu-item index="3" disabled>
          <el-icon><document /></el-icon>
          <span>Navigator Three</span>
        </el-menu-item>
        <el-menu-item index="4">
          <el-icon><setting /></el-icon>
          <span>Navigator Four</span>
        </el-menu-item>
      </el-menu>
    </el-col>
  </el-row>
</template>

<script setup>
import {
  Document,
  Menu as IconMenu,
  Location,
  Setting,
} from '@element-plus/icons-vue'
//store
// import {useStore} from  "vuex"
// const store = useStore()
import {usePiniaUserStore} from '@/store/pinia_user'
const piniaUserStore = usePiniaUserStore()

import {useRouter} from "vue-router"
const router = useRouter();

import {ref, onMounted, computed} from 'vue'


const routes = computed(()=> {
  return piniaUserStore.routesResult
})
onMounted(() => {

  console.log(routes.value)

})
const handleOpen = (key, keyPath) => {
  console.log(key, keyPath)
}
const handleClose = (key, keyPath) => {
  console.log(key, keyPath)
}
</script>

<style lang="scss" scoped>
@import "@/assets/styles/ani.scss";
//$bg:#2c3e50;
$bg:rgba(#545c64,0.4);
.tac{
  height: 100%;
  width: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  display: block;
  position: absolute;
  .el-col {
    display: block;
    position: absolute;
    height: 100%;
    width: 100% !important;
    max-width: 100%;
    .mb-2{
      height: 60px;
      background-color: $bg;
      position: relative;
      /* flex 布局 */
      display: flex;
      /* 实现垂直居中 */
      align-items: center;
      margin-bottom: 6px !important;
      transition-duration: 1s;
      &:hover{
        background-color: rgba(#545c64,1) !important;
      }
      .menu-avatar{
        position: relative;
        margin-left: 8%;
        margin-right: 8%;
        left: 0;
      }
      .menuTitle{
        position: relative;
        font-weight: bold;
        font-size: 15px;
        color: white !important;

      }
    }
    .el-menu{
      background-color: $bg !important;
      border: 0;
      transition-duration: 1s;

      &:hover{
        background-color: rgba(#545c64,1) !important;
      }
      //.el-sub-menu{
      //  transition-duration: 1s;
      //  //background-color: rgba(#545c64,1) !important;
      //  &:hover{
      //    background-color: rgba(#545c64,1) !important;
      //  }
      //  .el-sub-menu__title{
      //    background-color: rgba(#545c64,1) !important;
      //  }
      //  .el-sub-menu{
      //    background-color: rgba(#545c64,0) !important;
      //    transition-duration: 1s;
      //    &:hover{
      //      background-color: rgba(#545c64,1) !important;
      //    }
      //  }
      //}
    }
    //.el-menu-item-group__title{
    //  --el-menu-bg-color:$bg !important;
    //  background-color: $bg !important;
    //}
    //.el-menu-item-group{
    //  --el-menu-bg-color:$bg !important;
    //  background-color: $bg !important;
    //
    //}
    .el-menu-vertical-demo{
      height: 100%;
      //width: 100%;
    }
  }

}
</style>

5.3 默认首页

进入主页后,默认重定向到的菜单地址,就是后台首页,我们将其定义为dashboard


在这里插入图片描述

<template>
  <div class="dashboard-container">
    <div class="dashboard-text">name: {{ name }}</div>
  </div>
</template>

<script setup>
// import { mapGetters } from 'vuex'

// export default {
//   name: 'Dashboard',
//   // computed: {
//   //   ...mapGetters([
//   //     'name'
//   //   ])
//   // }
// }
const name = "Dashboard";
</script>

<style lang="scss" scoped>
.dashboard {
  &-container {
    margin: 30px;
  }
  &-text {
    font-size: 30px;
    line-height: 46px;
  }
}
</style>

5.4 配置路由

我们要实现根据路由的不同显示不同页面到Main组件的位置上,初始自动显示首页,那么可以先将首页设置为默认地址的重定向


在这里插入图片描述


此时我们的默认首页就和’/dashboard’这个路径对应起来了,而且访问’/‘时会自动重定向到’/dashboard’

{
    path: '/',
    name: 'home',
    redirect: '/dashboard',
    component: () => import( '@/layout/index.vue'),
    children: [{//这里的内容会在<router-view :key="key" />标签渲染
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: '首页', icon: 'dashboard' }
    }]
  },

5.5 Main组件根据路由path渲染对应页面

<router-view :key="key" />可以渲染指定路由的组件


在这里插入图片描述

<template>
  <section class="app-main">
    <router-view v-slot="{ Component }">
      <transition name="fade-transform" mode="out-in">
        <div>
          <component :is="Component" :key="key" />
        </div>

      </transition>
    </router-view>

  </section>
</template>

<script setup>
  //获取路由
  import {useRouter} from "vue-router";
  import {computed} from "vue";
  const route = useRouter();
  const key = computed(() => {
    return route.path
  })
  //获取当前路由path
  // function key(){
  //   return route.path
  // }
</script>

<style lang="scss">
@import "@/assets/styles/ani";
.app-main {
  /*50 = navbar  */
  //min-height: calc(100vh - 50px);
  //margin-bottom: 200px;
  height: 100%;
  width: 100%;
  position: absolute;
  left: 0;
  top: 0;
  //overflow: hidden !important;
}
.fixed-header+.app-main {
  padding-top: 50px;
}
</style>

<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
  .fixed-header {
    padding-right: 15px;
  }
}
</style>

6. 登录页面跳转逻辑解析

要加上登录成功跳转到目标页面,有两种情况


1. 第一次登录,直接跳转’/'路径到首页即可


2. 已经在系统中操作,假设当前处于’\text\urlContr’路由,但是因为某些原因(token过期等)导致需要重新登录,则登录完成后,要跳转回’\text\urlContr’路由,而不是也跳转到首页


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

<script setup>
import {watch, ref, onMounted} from 'vue'
  import {ElMessage} from 'element-plus'
  import {usePiniaUserStore} from '@/store/pinia_user'
  import {useRouter} from 'vue-router'

  const router = useRouter()//router对象,VUE3写法
  const piniaUserStore = usePiniaUserStore()
  //引用
  const loginForm = ref({
    username: '',
    password: ''
  })
  //获取ref="loginRef"的DOM元素
  const loginRef = ref(null)
  //登录按钮是否禁用
  const disabledFlag = ref(false)//先不禁用


  //表单校验规则
  const loginRules = {
    username: [
        //提交表单时触发,blur模糊匹配
        {required: true, trigger:'blur',message: '请输入用户名'},
        {min:3,max:25,message: '用户名长度应该为3-25个字符',trigger:'blur'},
    ],
    password: [{required: true, trigger:'blur',message: '请输入密码'},]
  }
/**
 * 监听路由的变化,如果是从某一个页面A跳转过来进行登录,就记录A的路由,完成登录后跳转回A,而不是无论如何都跳转回主页
 * watch: {
 *     $route: {
 *       handler: function(route) {
 *         this.redirect = route.query && route.query.redirect
 *       },
 *       immediate: true
 *     }
 *   },
 */
  const redirect = ref(null)//重定向地址
  //路由变动时需要的监听触发函数
  const handleRouteChange =  (newRoute)=>{
    console.log(router.currentRoute.value.query && router.currentRoute.value.query.redirect)
    redirect.value =  router.currentRoute.value.query && router.currentRoute.value.query.redirect
  }
  //模拟immediate=true的情况
  onMounted(()=>{
    handleRouteChange()
  })
  //监听路由的变化
  watch(router,handleRouteChange)
  //登录逻辑
  const handleLogin = () => {
    //禁用登录按钮
    disabledFlag.value=true
    //先表单验证
    loginRef.value.validate(async (valid) => {
      //如果验证通过
      if (valid) {
        //禁用登录按钮
        // disabledFlag.value=true
        //action监听,user.js下的login
        // await store.dispatch('user/login', loginForm.value).then(() => {
        await piniaUserStore.login(loginForm.value).then(() => {
          console.log('登录成功')
          console.log('跳转:',redirect.value || '/')
          //进行路由跳转,如果redirect存在,就跳转回redirect的地址,而不是'/'

          router.push({path:redirect.value || '/'})
          disabledFlag.value=false//异步过程中,阻止登录按钮的使用
        }).catch(()=>{
          disabledFlag.value=false//异步过程中,阻止登录按钮的使用
        })
      }else{//如果验证不通过
        console.log('登录失败')
        ElMessage.error('输入信息有问题个damn的!!!!')
        return false
      }
    })
  }
</script>

7. header 和 Footer

先简单实现一下


在这里插入图片描述


在这里插入图片描述

<template>
  <div ref="footer_div" class="footer bg_mine">
    Copyright © 2025-2030 Taylor Sinclair 版权所有&nbsp;&nbsp; <a href="" target="_blank">殷志鹏</a>
    <div class="line-run"></div>
  </div>
</template>

<script setup>
  // import {ref,onMounted} from "vue";
  // const footer_div = ref(null)
  // onMounted(() => {
  //   let footer_div_dom = footer_div.value
  //   // console.log(footer_div_dom)
  //   footer_div.value.addEventListener('mouseenter', (e) => {
  //     // footer_div_dom.classList.add("mouseenter");
  //   })
  // })
</script>

<style lang="scss" scoped>
@import "@/assets/styles/ani";
.footer{
  //background-color: $bg_2;
  //color: $text-color;
  width: 100%;
  height: 100%;
  padding: 20px;
  display: flex;
  align-items: center;
  position: relative;
}


.mouseenter{
  positon: relative;

}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydenergy_殷志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值