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

Vue和FastAPI实现前后端分离 一个是它的异步处理性能比较好,二是它可以类似java swagger的API交互文档,这个对应前后端分离多人开发非常友好。之前总结过Vue和Flask实现前后端分离,这次同样借个小项目实践、并总结下Vue和FastAPI实现前后端分离。 阅读详情
SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新):https://blog.youkuaiyun.com/grd_java/article/details/144986730

1. 跨越解决

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


https://blog.youkuaiyun.com/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>
python后台vue3前端构建网站最简单示范 构建一个简单的网站样本程序,使用 Vue 3 作为前端框架,Python 的 FastAPI 作为后端 API。 阅读详情

相关推荐

使用 FastAPI 和 Vue.js 实现前后端分离

前后端分离是现代 Web 开发的趋势。使用 FastAPI 和 Vue.js 可以构建一个高效、灵活且易于维护的 Web 应用。FastAPI 提供了高性能的后端服务,而 Vue.js 作为一种渐进式 JavaScript 框架,可以构建动态的前端界面。本文将详细介绍如何使用 FastAPI 和 Vue.js 实现前后端分离的项目,包括环境搭建、API 开发、前端页面构建以及数据交互。

Rocky006的博客 7771

FastAPI 教程、结合vue实现前后端分离

FastAPI 内部提供了一个 HTTPException,但是我们也可以自定义,但是注意:我们自定义完异常之后,还要定义一个 handler,将异常和 handler 绑定在一起,然后引发该异常的时候就会触发相应的 handler。""""""pass# 通过装饰器的方式,将 ASCIIException 和 ascii_exception_handler 绑定在一起"""当引发 ASCIIException 的时候,会触发 ascii_exception_handler 的执行。

freeking101的博客 1万+

AI辅助开发:FastAPI和VUE联合工作

AI辅助开发:FastAPI和VUE联合工作

天涯雨的博客 1215

基于python FastAPI + Vue的一套CMS开源源码模板

FastAPI-Vue是个人开发并使用的CURD模板之一,代码功能不难,主要是减少浪费时间在用户系统的开发。fastAPI的性能在Python中还算挺不错的,使用起来也很方便。github上也有其他fastAPI和vue组合的代码,个人觉得不是很符合我自己,然后就自己开发了一套,现开源共享出来。后端项目的目录结构借鉴了Django的目录结构。前端Vue代码使用了Ruoyi的Vue代码。大部分功能和若依相似,但是在原基础上删除了我个人认为用得很少的 部门 模块。

sinat_34149445的博客 1万+

从零构建全栈用户管理系统:FastAPI + Vue 3 + MySQL 实战指南

用户列表展示与关键词搜索(支持用户名和邮箱搜索)新增用户(包含用户名、年龄、邮箱信息)编辑现有用户信息删除用户(含确认机制,防止误操作)表单验证与错误处理加载状态与操作反馈本文详细介绍了基于 FastAPI + Vue 3 + MySQL 的用户管理系统实现过程,涵盖了后端 API 设计、数据库交互、前端状态管理、组件开发等核心知识点。如何使用 FastAPI 快速构建 RESTful API,结合 Pydantic 进行数据验证。

2303_77081844的博客 2495

FastAPI + vue3 前后端分离后台管理系统,包含PC端,微信小程序端.zip

FastAPI + vue3 前后端分离后台管理系统,包含PC端,微信小程序端。接口使用:FastAPI+Pydantic+SQLAlchemy 2.0+Mysql 【项目资源】:包含前端、后端、移动开发、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源,毕业设计等各种技术项目的源码。包括C++、Java、python、web、C#、EDA等项目的源码。 【适用人群】:适用于希望学习不同技术领域的初学者或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。 【项目质量】:所有源码都经过测试,可以直接运行。功能在确认正常工作后才上传。

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新)

随着公司业务的增加,公司需要一个java+python人工智能相互配合架构,正常网站业务用java来做,而ai,例如电价预测等回归任务,以及大模型预测全网负荷,新能源出力等任务,使用python通过fastapi暴露接口来做,那么就需要springcloud架构注册发现。前端统一使用Vue进行效果的展示

殷志鹏的博客 2484

DSPACE仿真平台的使用

DSPACE仿真平台的使用 文章目录DSPACE仿真平台的使用1.ModelDesk新建项目2.MotionDesk引入项目3.ControlDesk引入项目4.项目整合运行 我们使用DSPACE仿真平台软件来模拟小汽车的自动驾驶。 总共需要用到DSPACE大类下的三个软件:ModelDesk、ControlDesk、MotionDesk。 1.ModelDesk新建项目 在桌面上点击ModelDesk图标,打开DSPACE—ModelDesk 选择 File 标签,侧边栏选择 New,点击 New A

qq_42582489的博客 7527

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

权限管理模块的前端实现

殷志鹏的博客 1163

基于STM32F103的二维码识别项目——OV7670篇

此系列用于记录做过的项目,这篇文章介绍基于STM32F103的二维码识别项目之OV7670FIFO模块。

beyourself_he的博客 4288

启动盘安装Win10完整指南:U盘制作、BIOS设置与分区排错

系统重装是解决电脑卡顿、系统崩溃、病毒残留等问题的基础操作,而U盘启动盘则是目前最主流的安装介质。其核心原理在于通过Rufus等工具,将原版ISO镜像写入U盘,并依据主板固件类型(UEFI或Legacy)选择对应的GPT或MBR分区表,确保引导程序能正确加载。掌握这些底层概念,不仅能提升安装成功率,还能避免因引导模式不匹配导致的黑屏、无法启动等问题。在实际应用中,无论是老旧电脑升级、硬盘更换,还是需要纯净的系统环境,制作一个可靠的启动盘都是必备技能。本文从备份数据、下载官方镜像讲起,逐步演示U盘制作、启动快

weixin_32667955的博客 339

FastAPI+Vue敏捷开发框架

一款 Python 语言基于FastAPI、Vue、ElementUI、MySQL等框架精心打造的一款模块化、高性能、企业级的敏捷开发框架,本着简化开发、提升开发效率的初衷触发,框架自研了一套个性化的组件,实现了可插拔的组件式开发方式:单图上传、多图上传等一系列个性化、轻量级的组件,是一款真正意义上实现组件化开发的敏捷开发框架。

一米阳光的博客 1063

LAMMPS+Phonopy计算声子色散曲线

机器学习训练完势函数之后,需要对加以验证。对于晶体而言,色散曲线是其衡量计算是否准确的最重要标准,当然,还有能量,比热,RDF等性质。

qq_68842798的博客 3462

【免费下载】 推荐开源项目:Vue3-FastAPI —— 前后端一体化开发的新星

推荐开源项目:Vue3-FastAPI —— 前后端一体化开发的新星 是一个创新的开源项目,它结合了前端的 Vue.js 3 和后端的 FastAPI,为你提供了一站式的现代Web应用开发框架。该项目旨在简化开发流程,提高效率,并且让开发者能够更好地利用最新技术和工具。 项目简介 Vue3-FastAPI 提供了一个完整的前后端解决方案,包括基础模板、身份验证系统和数据库模型。前端部分基于 Vue...

gitblog_00087的博客 2648

在 SAP 中,寄售业务(Consignment)与管道业务(Pipeline)都属于“物料先到、发票后到”的特殊采购模式,但两者的库存形态、物权转移时点、结算触发条件、系统配置完全不同

SAP中寄售业务与管道业务均属"先货后票"采购模式,但存在本质差异。寄售业务(如手机芯片)采用实物库存管理(标记"K"),物权在领用时转移,通过411K/201K移动类型触发负债;管道业务(如蒸汽)无实物库存,采用261P等移动类型直接消耗并生成应付。系统配置上,寄售需K类信息记录及GR收货,管道需P类信息记录且免GR。两者均通过MRKO汇总结算,但寄售按领用量计费,管道按提取量实时计费。核心区别在于库存形态与物权转移时点。

金牌架构师 557

36、T5L 迪文屏C51开发之绘制动态曲线

T5L 迪文屏C51开发之绘制动态曲线1、介绍2、例程功能简介3、GUI界面设计3、C51程序设计 1、介绍   本文将利用DGUS Tool软件中的【动态曲线控件】来绘制曲线效果,在T5L迪文屏中总共支持8个通道的曲线,而曲线数据是需要缓冲区的,如果 8 通道曲线同时使用,0x1000-0x4FFF 将作为曲线缓冲区地址,此时该部分变量地址不能被其他控键使用,其他控键地址使用范围为0x5000-0xFFFF,当某曲线通道不被使用时,那么其所对应的缓冲区地址可以做其他用途,曲线通道具体的地址分布如下图所示:

嵌入式羊圈 3131

部署前后端分离项目Vue+FastAPI

将vue.config.js文件中的publicPath: '/',改为publicPath: './' (多了一个点)生成的dist文件夹,点开,然后浏览器打开index.html文件,如果静态页面正常,说明打。2. 前端部署,用npm run build:prod(以package.json中的为准)7:前后端连接,是将后端写的跨域部分中加入前端地址。将生产环境的地址改为 -> 服务器地址+后端的端口号。6:地址栏输入:服务器地址+前端端口号。3:将打包到的前端dist上传到这里。

qq_44658163的博客 1674

摩尔线程嵌入式面试题及参考答案(2万字长文)

说一下你对 drm 框架的理解。 DRM(Direct Rendering Manager)是 Linux 系统中用于管理图形显示设备的一个重要框架。 从架构层面来讲,它处于内核空间,主要目的是为用户空间的图形应用程序提供一个统一的接口来访问图形硬件。DRM 包括内核态的驱动模块和用户态的库。内核态的驱动负责和硬件进行交互,比如对显存的管理、硬件寄存器的配置等。用户态的库则方便应用程序利用内核态提供的功能进行图形渲染等操作。 在图形显示的具体流程中,DRM 可以控制显示设备的分辨率、刷

大模型大数据攻城狮的专栏 1229

FastAPI + Vue3 + NaiveUI 框架的开发技巧

在定义路由时,可以为每个路由设置对应的权限标识,然后在路由守卫中,根据用户当前的角色或者权限信息来判断是否允许访问该路由,如果用户没有权限访问,则可以重定向到无权访问页面或者登录页面等,从而保证系统的安全性,防止用户直接通过路由地址访问没有权限的页面。NaiveUI 是一个基于 Vue3 的 UI 组件库,提供了丰富的组件,如表格(用于展示用户列表、角色列表等)、表单(用于创建和编辑用户、角色等信息)、弹窗(用于显示编辑表单、确认删除对话框等)、按钮(触发各种操作,如查询、保存、删除等)等。

xuukai的博客 2081

IoT安全软件:Forescout二次开发_Forescout基础架构与原理

Forescout平台是一个集成了多种安全功能的综合解决方案,旨在帮助组织管理和保护其网络中的IoT设备。该平台通过自动发现、分类和监控网络中的设备,提供实时的安全可见性和控制。Forescout的核心优势在于其能够处理混合环境中的各种设备,包括传统IT设备、IoT设备和OT设备。Forescout平台由多个核心组件组成,每个组件负责特定的功能。了解这些组件的运作机制是进行二次开发的基础。假设我们需要定义一个策略,禁止所有厂商为XYZ的设备访问互联网。选择策略类型。

chenlz2007的博客 1965

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Django+Vue+ElementUI管理后台源码

一款 Python 语言基于Django、Vue2.x、ElementUI、MySQL等框架精心打造的一款模块化、高性能、企业级的敏捷开发框架,本着简化开发、提升开发效率的初衷触发,框架自研了一套个性化的组件,实现了可插拔的组件式开发方式:单图上传、多图上传、下拉选择、开关按钮、单选按钮、多选按钮等等一系列个性化、轻量级的组件,是一款真正意义上实现低代码开发的敏捷开发框架。

一米阳光的博客 1298

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

后端“fastapi”+前端“vue3+ts+ElementPlus”上传文件到uploads目录

确保已安装好python3和fastapimail.py运行fastapi服务器使用浏览器访问。

airdot的专栏 1838

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

fastapi+vue实现前后端分离

这段代码是在 FastAPI 应用中添加了一个跨域资源共享(CORS)中间件。CORS 是一种机制,它允许网页从不同的源(域名、协议、端口)请求资源,通过配置合适的 CORS 设置,可以确保前端应用(运行在浏览器中的 JavaScript 代码)能够安全、正确地与后端 FastAPI 应用进行交互,即使它们位于不同的源。为了使前端 Vue 应用能够访问 FastAPI 服务,需要设置 CORS。使用 Axios 在 Vue 组件中连接到 FastAPI 提供的 API。(但我们的小程序中并没有用到)

2301_80089510的博客 2073

vue3 + fastapi 实现选择目录所有文件自定义上传到服务器

大家好,我是yma16,本文分享关于vue3 + fastapi 实现选择目录文件上传到服务器指定位置。 vue3系列相关文章: 前端vue2、vue3去掉url路由“ # ”号——nginx配置 csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板 认识vite_vue3 初始化项目到打包 python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示 python系列文章: python爬虫_基本数据类型 python爬虫_函数

编程知识分享,主打前端 2万+

FastAPI+Vue前后端分离框架

一款 Python 语言基于FastAPI、Vue、ElementUI、MySQL等框架精心打造的一款模块化、高性能、企业级的敏捷开发框架,本着简化开发、提升开发效率的初衷触发,框架自研了一套个性化的组件,实现了可插拔的组件式开发方式:单图上传、多图上传等一系列个性化、轻量级的组件,是一款真正意义上实现组件化开发的敏捷开发框架。

一米阳光的博客 973

FastAPI + Vue 实现 OAuth2 的 jwt token 登录验证

后端使用 FastAPI,前端使用 Vue 来完成登录过程的用 jwt token 实现登录验证功能。 一、后端 FastAPI 1.1 设计用户表 本文采用的是 MySQL 数据库。 首先连接 MySQL 数据库,关于 MySQL 数据库的连接可参见另外一篇文章:FastAPI 连接 MySQL 用户的数据库表如下: class User(Base): __tablename__ = 'users' # 数据库表名 username = Column(String(255), prim

VLyb 7945
上一篇: SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——Vue3.0前后端分离架构——后台系统前端环境搭建
下一篇: SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——Vue3.0前后端分离架构——后台系统前端权限管理模块实现
ydenergy_殷志鹏
博客等级 码龄7年 5885粉丝 543原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

ydenergy_殷志鹏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值