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

python后台vue3前端构建网站最简单示范 构建一个简单的网站样本程序,使用 Vue 3 作为前端框架,Python 的 FastAPI 作为后端 API。 阅读详情
SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新):https://blog.youkuaiyun.com/grd_java/article/details/144986730

1. 路由和权限控制相关页面

我们rbac有三个菜单页面,用户管理页面,角色管理页面,菜单权限管理页面


路由进行修改,注意他们都在security这个父路由下,security这个父路由就是layout页面,我们都要在这个页面进行渲染


在这里插入图片描述

页面如下,由于代码简单,不额外贴出代码


在这里插入图片描述

2. path配置

由于我们自定义导航栏组件需要使用node.js的原生path,需要做额外配置


在这里插入图片描述

const { defineConfig } = require('@vue/cli-service')

const path = require('path')
const { DefinePlugin } = require('webpack')
function resolve(dir) {
  return path.join(__dirname, dir)
}
module.exports = defineConfig({
  transpileDependencies: true,
  //node.js,中无法直接在浏览器访问,用path-browserify模拟
  configureWebpack: {
    plugins: [
      new DefinePlugin({
        __VUE_OPTIONS_API__: 'true',
        __VUE_PROD_DEVTOOLS__: 'false',
        __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
      })
    ],
    resolve: {
      alias: {
        '@': resolve('src')
      },
      fallback: {
        path: require.resolve('path-browserify')
      }
    },
  }
})

3. 左边导航栏

上一节已经实现登录后跳转至系统首页,接下来先实现左边导航栏根据用户权限进行显示

3.1 异步路由逻辑

异步路由已经在处理用户登录时,就处理过了,在pinia中顺便就处理了,可以参考前面的章节


在这里插入图片描述

3.2 layout使用自定义组件

在这里插入图片描述

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

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

</script>
<style lang="scss">
@import "@/assets/styles/ani.scss";
.common-layout {
  height: 100%;
  .common-layout-container{
    height: 100%;
    .asideMenu{
      height: 100%;
      overflow-y: hidden;
      position: relative;
    }
  }
  .el-header{
    border: 0;
    padding: 0;
  }
  .el-main{
    border: 0;
    padding: 0;
    height: 100%;
    overflow-y: hidden;
    position: relative;
  }
  .el-footer{
    padding: 0;
  }
}
</style>

3.3 动态节点vnode

封装每一个菜单的名称和图标,动态生成


在这里插入图片描述

<script>
import {h, resolveComponent} from "vue";
  import { ElIcon } from 'element-plus';
  export default {
    name: 'MenuItem',
    functional: true,
    props: {
      icon: {
        type: String,
        default: ''
      },
      title: {
        type: String,
        default: ''
      }
    },
    setup(props) {
        const { icon, title } = props
        const vnodes = []
        if (icon) {
          const resolveComponent1 = resolveComponent(icon);
          vnodes.push(h(ElIcon,()=>h(resolveComponent1)))

          // if (icon.includes('el-icon')) {
          //   vnodes.push(<i class={[icon, 'sub-el-icon']} />)
          // } else {
          //   vnodes.push(<svg-icon icon-class={icon}/>)
          // }
        }
        if (title) {
          vnodes.push(h('span',{slot:'title'}, title))
          // vnodes.push(<span slot='title'>{(title)}</span>)
        }
        // console.log(vnodes)
        // return vnodes
        return ()=>h('div',{class:"hDiv"},vnodes)
    }


  }
</script>

<style scoped lang="scss">
.sub-el-icon {
  color: currentColor;
  width: 1em;
  height: 1em;
}
.hDiv{
  position: relative;
  //font-size: 20px;
  //width: 100%;
  //height: 100%;
}
.myIcon{
  position: relative;
  left: 10em;
  //color: currentColor;
  //width: 100% !important;
  height: 40px !important;
}
</style>

3.4 Link动态地址解析组件

每次点击菜单,根据后退配置的链接地址,进行相应的跳转


在这里插入图片描述

<template>
  <component :is="type" v-bind="linkProps(props.to)">
    <slot />
  </component>
</template>
<script setup>
//引入utils中的工具栏,处理url正则匹配
import { isExternal } from '@/utils/validate'
import {computed} from "vue";
const props = defineProps({
  to: { //要去的地址
    type: String,
    required: true
  }
});
//计算正则匹配,是路由跳转还是跳转到其它网站或页面
const isExternalComputed = computed(()=> {
  return isExternal(props.to)
})
//根据正则匹配结果,处理生成a标签还是router-link标签处理
const type = computed(()=> {
  if (isExternalComputed.value) {
    return 'a'
  }
  return 'router-link'
})
//如果是传统url,直接a连接跳转新页面(可以是站外)
function linkProps(to) {
  if (isExternalComputed.value) {
    return {
      href: to,
      target: '_blank',
      rel: 'noopener'
    }
  }
  //否则就是当前app应用的路由跳转
  return {
    to: to
  }
}

</script>



<style scoped lang="scss">

</style>

3.5 递归sidebar-item导航栏核心

我们通过element plus组件,根据后端传来的树形菜单结构,动态递归生成导航栏


在这里插入图片描述

<template>
  <div v-if="!item.hidden">
<!--    如果只有一个孩子(这个孩子不可以有孙子),如果有孙子也必须都是不展示的。并且当前路由对象不可以是永远展示alwaysShow的-->
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
        </el-menu-item>
      </app-link>
    </template>
<!--    否则递归进行显示-->
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)">
<!--      先显示当前父级-->
      <template #title>
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title = "item.meta.title"></item>
      </template>
<!--      递归显示子集-->
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :item="child"
        :is-nest="true"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      ></sidebar-item>
    </el-sub-menu>
  </div>
</template>
<script setup>
import Item from '@/layout/components/AsideMenu/Item.vue'
import AppLink from '@/layout/components/AsideMenu/Link.vue'
import {ref} from "vue"
//node.js,中无法直接在浏览器访问,用path-browserify模拟
import path from 'path-browserify';
import {isExternal} from "@/utils/validate";
  const props = defineProps({
    // route object
    item: { //当前路由对象
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {//当前路由对象需要跳转的页面
      type: String,
      default: ''
    }
  })
  //如果只有一个孩子
  const onlyOneChild = ref(null);
  //是否只有一个需要展示的孩子
  const hasOneShowingChild = (children = [],parent) => {
    const showingChildren = children.filter(item => {
      if (item.hidden) {
        return false
      } else {
        // Temp set(will be used if only has one showing child)
        onlyOneChild.value = item
        return true
      }
    })

    // When there is only one child router, the child router is displayed by default
    if (showingChildren.length === 1) {
      return true
    }

    // Show parent if there are no child router to display
    if (showingChildren.length === 0) {
      onlyOneChild.value = { ... parent, path: '', noShowingChildren: true }
      return true
    }
    return false
  }
  //地址解析
  const resolvePath = (routePath) => {
    if (isExternal(routePath)) {
      return routePath
    }
    if (isExternal(props.basePath)) {
      return props.basePath
    }
    return path.resolve(props.basePath, routePath)
  }
</script>



<style scoped lang="scss">
.myIcon{
  color: currentColor;
  width: 1em !important;
  height: 1em !important;
}
</style>

3.6 组件本身

上面都是子组件,这个文件是通过上面的子组件组装成为完整的导航栏


在这里插入图片描述

<template>
  <el-row class="tac cantSelectText">
    <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="activeMenu"
          text-color="#fff"
          @open="handleOpen"
          @close="handleClose"
      >
<!--  routes为用户拥有的路由,key是当前组件的标识,item是给子组件传递的当前路由对象,base-path是当前路由对象需要跳转的页面      -->
        <sidebar-item v-for="route in routes" :key = "route.path" :item = "route" :base-path="route.path"></sidebar-item>

      </el-menu>
    </el-col>
  </el-row>
</template>

<script setup>
//引入图表库
// import '@element-plus/icons-vue'
//引入组件
import SidebarItem from "@/layout/components/AsideMenu/SidebarItem.vue";

//store
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 JSON.parse(JSON.stringify(piniaUserStore.routesResult))
})
const activeMenu = computed(()=> {
  // if (value.meta.active)
  return router.currentRoute.value.path
})
onMounted(() => {
  // console.log(routes.value)
  // console.log(activeMenu.value)
  // console.log()
})
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";
//$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;
      background-color: rgba(#545c64,1) !important;
      position: relative;
      /* flex 布局 */
      display: flex;
      /* 实现垂直居中 */
      align-items: center;
      margin-bottom: 6px !important;
      transition-duration: 1s;
      //&:hover{
      //  background-color: rgba(#545c64,1) !important;
      //}
      .menu-avatar{
        min-width: 20%;
        position: relative;
        margin-left: 8%;
        margin-right: 8%;
        left: 0;
      }
      .menuTitle{
        position: relative;
        font-weight: bold;
        font-size: 15px;
        color: white !important;
        /* 溢出文字开启省略号显示 */
        white-space: nowrap;/*关闭自动换行*/
        text-overflow: ellipsis;/*文字溢出内容处理为省略号*/
        overflow: hidden;/* 通过设置overflow 开启BFC */

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

      &:hover{
        //background-color: rgba(#545c64,1) !important;
      }
    }
    .el-menu-vertical-demo{
      height: 100%;
    }
  }

}
</style>

3.7 效果演示

根据权限进行展示菜单,点击用户管理就进入用户管理页面
在这里插入图片描述

点击角色管理,就进入角色管理页面


在这里插入图片描述

4. 顶部header配置

4.1 面包屑图标


在这里插入图片描述

<template>
  <div style="padding: 0 15px;" @click="toggleClick">
    <svg
        :class="{'is-active':isActive}"
        class="hamburger"
        viewBox="0 0 1024 1024"
        xmlns="http://www.w3.org/2000/svg"
        width="64"
        height="64"
    >
      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
    </svg>
  </div>
</template>

<script>
export default {
  name: 'Hamburger',
  props: {
    isActive: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    toggleClick() {
      this.$emit('toggleClick')
    }
  }
}
</script>

<style scoped>
.hamburger {
  display: inline-block;
  vertical-align: middle;
  width: 20px;
  height: 20px;
}

.hamburger.is-active {
  transform: rotate(180deg);
}
</style>

4.2 面包屑路径


在这里插入图片描述

<template>
  <el-breadcrumb class="app-breadcrumb cantSelectText" separator="/">
    <transition-group name="breadcrumb">
      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
        <span v-if="item.redirect==='noRedirect'||index===levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
      </el-breadcrumb-item>
    </transition-group>
  </el-breadcrumb>
</template>

<script>
import pathToRegexp from 'path-to-regexp'

export default {
  data() {
    return {
      levelList: null
    }
  },
  watch: {
    $route() {
      this.getBreadcrumb()
    }
  },
  created() {
    this.getBreadcrumb()
  },
  methods: {
    getBreadcrumb() {
      // only show routes with meta.title
      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
      const first = matched[0]

      if (!this.isDashboard(first)) {
        matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
      }

      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
    },
    isDashboard(route) {
      const name = route && route.name
      if (!name) {
        return false
      }
      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
    },
    pathCompile(path) {
      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
      const { params } = this.$route
      var toPath = pathToRegexp.compile(path)
      return toPath(params)
    },
    handleLink(item) {
      const { redirect, path } = item
      if (redirect) {
        this.$router.push(redirect)
        return
      }
      this.$router.push(this.pathCompile(path))
    }
  }
}
</script>

<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 50px;
  margin-left: 8px;

  .no-redirect {
    color: #97a8be;
    cursor: text;
  }
}
</style>

4.3 组件本身

除了组装面包屑以外,还要在右上角添加一个退出登录菜单


在这里插入图片描述

<template>
  <div class="navbar">
    <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <breadcrumb class="breadcrumb-container" />

    <div class="right-menu">
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
<!--          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">-->
          <img src="@/assets/user-avatar.gif" class="user-avatar">
          <i class="el-icon-caret-bottom" />
        </div>
        <template #dropdown>
          <el-dropdown-menu slot="dropdown" class="user-dropdown">
            <router-link to="/">
              <el-dropdown-item>
                Home
              </el-dropdown-item>
            </router-link>
<!--            <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">-->
<!--              <el-dropdown-item>Github</el-dropdown-item>-->
<!--            </a>-->
<!--            <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">-->
<!--              <el-dropdown-item>Docs</el-dropdown-item>-->
<!--            </a>-->
            <el-dropdown-item divided @click.native="logout">
              <span style="display:block;">Log Out</span>
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>

    </div>

  </div>
</template>

<script setup>
  import Hamburger from './Hamburger.vue'
  import Breadcrumb from './Breadcrumb.vue'
  import {computed, onMounted} from "vue";

  import {useRouter} from 'vue-router'
  const router = useRouter()//router对象,VUE3写法

  import {usePiniaAppStore} from "@/store/pinia_app";
  import {usePiniaUserStore} from "@/store/pinia_user";
  const appStore = usePiniaAppStore();
  const userStore = usePiniaUserStore();

  const sidebar = computed(()=>{
    return appStore.sidebar
  })
  const avatar = computed(()=>{
    return userStore.avatar
  })
  const fullPath = computed(()=> {
    // if (value.meta.active)
    return router.currentRoute.value.fullPath
  })
  onMounted(() => {
    // console.log(routes.value)
    // console.log(activeMenu.value)
    // console.log(fullPath.value)
  })
  const toggleSideBar = ()=> {
    // this.$store.dispatch('app/toggleSideBar')
    appStore.toggleSideBar()
  }
  const logout = async()=> {
    await userStore.logout()
    // await this.$store.dispatch('user/logout')
    console.log(router)
    router.push(`/login?redirect=${fullPath.value}`)
  }
</script>

<style lang="scss" scoped>
.navbar {
  //height: 50px;
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0,21,41,.08);

  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background .3s;
    -webkit-tap-highlight-color:transparent;

    &:hover {
      background: rgba(0, 0, 0, .025)
  }
}

  .breadcrumb-container {
    float: left;
}

  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;

    &:focus {
      outline: none;
  }

    .right-menu-item {
      display: inline-block;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;

      &.hover-effect {
        cursor: pointer;
        transition: background .3s;

        &:hover {
          background: rgba(0, 0, 0, .025)
      }
    }
  }

    .avatar-container {
      margin-right: 30px;

      .avatar-wrapper {
        margin-top: 5px;
        position: relative;

        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
      }
        .el-icon-caret-bottom {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
      }
    }
  }
}
}
</style>

4.4 pinia全局管理


在这里插入图片描述

import {defineStore} from "pinia";
import Cookies from 'js-cookie'
export const usePiniaAppStore = defineStore("piniaAppStore", {
    state:()=>{
        return {
            sidebar: {
                opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
                withoutAnimation: false
            },
            device: 'desktop'
        }
    },
    getters:{
        getSidebar: state=>state.sidebar,
        getDevice: state=>state.device,
    },
    actions:{
        toggleSideBar() {
            this.sidebar.opened = !this.sidebar.opened
            this.sidebar.withoutAnimation = false
            if (this.sidebar.opened) {
                Cookies.set('sidebarStatus', 1)
            } else {
                Cookies.set('sidebarStatus', 0)
            }
        },
        closeSideBar({ withoutAnimation }) {
            Cookies.set('sidebarStatus', 0)
            this.sidebar.opened = false
            this.sidebar.withoutAnimation = withoutAnimation
        },
        toggleDevice(device) {
            this.device = device
        }
    }
})

4.5 效果展示

在这里插入图片描述


在这里插入图片描述

5. 后端api封装

user.js用户相关


在这里插入图片描述

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'
  })
}
//返回用户列表,包括其角色
export function userPage(data,current,size){
  return service({
    url:`security/sysUserRole/getAllPage/${current}/${size}`,
    method:'post',
    data
  })
}
//添加用户
export function addUser(data){
  return service({
    url:'/security/sysUser/add',
    method:'post',
    data
  })
}
//根据id删除用户
export function deleteUserById(id){
  return service({
    url:`/security/sysUser/delete`,
    method:'delete',
    params: {id:id}
  })
}
//修改用户
export function updateUser(data){
  return service({
    url:'/security/sysUser/update',
    method:'put',
    data
  })
}
//修改用户密码
export function updatePasswordById(id,password){
  return service({
    url:`/security/sysUser/updatePasswordById/${id}/${password}`,
    method:'put'
  })
}
//修改用户和角色
export function updateUserAndRole(data){
  return service({
    url:`/security/sysUserRole/updateUserRole`,
    method:'put',
    params:data
  })
}

role.js角色相关


在这里插入图片描述

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

//分页查询角色列表
export function getAllRolesPage(current,size) {
  return service({
    url:`/security/sysRole/page/${current}/${size}`,
    method:'get',
  })
}
//添加角色
export function addRole(data){
  return service({
    url:'/security/sysRole/addRole',
    method:'post',
    data
  })
}
//修改角色
export function updateRole(data){
  return service({
    url:'/security/sysRole/updateRole',
    method:'put',
    data
  })
}
//删除角色
export function deleteRoleById(id){
  return service({
    url:`/security/sysRole/${id}`,
    method:'delete'
  })
}

menu.js菜单管理相关api


在这里插入图片描述

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

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

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

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

6. user页面逻辑


在这里插入图片描述


在这里插入图片描述

<template>
  <div class="user-container">
    <!--    条件查询开始-->
    <el-form :inline="true" :model="formInline">
      <el-form-item label="用户名" class="margin_right_10">
        <el-input v-model="formInline.username" placeholder="用户名" ></el-input>
      </el-form-item>
      <el-form-item label="角色名" class="margin_right_10">
        <el-input v-model="formInline.roles[0].nameZh" placeholder="角色名"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit">查询</el-button>
      </el-form-item>
    </el-form>
    <!--    条件查询结束-->
    <!--    添加和批量删除开始-->
    <el-form :inline="true">
      <el-form-item class="margin_right_10">
        <el-popover placement="bottom" width="800" v-model="addVisible">
          <template #reference>
            <el-button type="primary" v-show="store.buttons.indexOf('btn:security')!==-1">添加</el-button>
          </template>

          <el-form :inline="true" :model="addUserData">
            <el-form-item label="用户名">
              <el-input v-model="addUserData.username" placeholder="用户名"></el-input>
            </el-form-item>
            <el-form-item label="密码">
              <el-input v-model="addUserData.password" placeholder="密码"></el-input>
            </el-form-item>
          </el-form>
          <div style="text-align: right; margin: 0">
            <el-button text @click="addVisible = false">取消</el-button>
            <el-button type="primary" @click="addUserFun">添加</el-button>
          </div>
<!--          <el-button type="primary" slot="reference" v-show="store.buttons.indexOf('btn:security')!==-1">添加</el-button>-->
<!--          <el-button type="primary" slot="reference" v-show="store.buttons">添加</el-button>-->
        </el-popover>
      </el-form-item>
      <el-form-item>
        <el-button type="danger" @click="" :disabled="deleteAllFlag">批量删除</el-button>
      </el-form-item>
    </el-form>
    <!--    添加和批量删除结束-->
    <!--    表格开始-->
    <el-table v-loading="loading" :data="tableData" border
              :default-sort = "{prop: 'username', order: 'ascending'}"
              style="width: 100%"
              @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="100"></el-table-column>
      <el-table-column label="序号" type="index" fixed="left" width="100"></el-table-column>
      <!--      <el-table-column prop="id" label="序号" fixed="left" width="0px"></el-table-column>-->
      <el-table-column prop="username" label="username" width="200"></el-table-column>
      <el-table-column prop="rolesStr" label="拥有角色列表" width="400"></el-table-column>
      <el-table-column prop="authoritiesStr" label="拥有权限列表" width="400"></el-table-column>
      <el-table-column align="center" fixed="right" label="操作(分配角色/编辑用户/修改密码/删除用户)" width="300">
        <template #default = "{row,$index}"><!--注意这个可以让我们使用row和$index两个值,方便我们操作-->
          <el-button type="success" icon="UserFilled" @click="updateUserAndRoleClick(row)"></el-button>
          <el-button type="primary" icon="Edit" @click="updateUserClick(row)"></el-button>
          <el-button type="primary" icon="Key" @click="updataPasswordClick(row)"></el-button>
<!--          <el-button type="danger" icon="Delete" @click="deleteUserByIdM(tableData[$index].id)"></el-button>-->
          <el-button type="danger" icon="Delete" @click="deleteUserByIdM(row.id)"></el-button>
        </template>
      </el-table-column>
    </el-table>
    <!--    表格结束-->
    <!--    分页开始-->
    <div class="block">
      <el-pagination
          :background="false"
          :current-page="pageNum"
          :page-size="pageSize"
          layout="total, prev, pager, next, jumper"
          :total="total"
          @current-change="handleCurrentChange"
          class="margin_top_10"
      />
<!--      <el-pagination-->
<!--          @current-change="handleCurrentChange"-->
<!--          :current-page="pageNum"-->
<!--          :page-size="pageSize"-->
<!--          layout="total, prev, pager, next, jumper"-->
<!--          :total="total">-->
<!--      </el-pagination>-->
    </div>
    <!--    分页结束-->
    <!--    对话框开始-->
    <el-dialog title="修改用户拥有角色" v-model="roleVisible" width="30%">
      <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
      <div style="margin: 15px 0;"></div>
      <el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange">
        <el-checkbox v-for="city in cities" :label="city" :value="city" :key="city.id">{{city.nameZh}}</el-checkbox>
      </el-checkbox-group>
      <span slot="footer" class="dialog-footer">
        <el-button @click="roleVisible = false">取 消</el-button>
        <el-button type="primary" @click="updateUserAndRoleFun">确 定</el-button>
      </span>
    </el-dialog>
    <el-dialog title="修改用户信息" v-model="editVisible" width="30%">
      <el-form :inline="true" class="demo-form-inline">
        <el-form-item label="用户名">
          <el-input v-model="updateUserData.username" placeholder="用户名"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editVisible = false">取 消</el-button>
        <el-button type="primary" @click="updateUserFun">确 定</el-button>
      </span>
    </el-dialog>
    <el-dialog title="修改用户密码" v-model="passwordVisible" width="30%">
      <el-form :inline="true" class="demo-form-inline">
        <el-form-item label="要修改密码">
          <el-input v-model="passwordData.password" placeholder="密码"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="passwordVisible = false">取 消</el-button>
        <el-button type="primary" @click="updatePasswordFun">确 定</el-button>
      </span>
    </el-dialog>
    <!--    对话框结束-->
  </div>
</template>
<script setup>
import {usePiniaUserStore} from "@/store/pinia_user";
const store = usePiniaUserStore();

import {userPage,addUser,deleteUserById,updateUser,updatePasswordById,updateUserAndRole} from '@/api/user'
import { getAllRolesPage } from '@/api/role'

import {ref} from 'vue'
import {ElMessage,ElMessageBox} from "element-plus";
//查询条件
const formInline = ref({
  username: '',
  roles:[
    {
      nameZh:''
    }
  ]
})
const addUserData=ref({
  password: "",
  username: ""
})//添加用户

const updateUserData =ref({})//修改用户
const passwordData = ref({})//修改用户密码
const updateUserAndRolendRoleData = ref({})//修改用户拥有角色
//用户展示数据
const tableData= ref([{
  username:"admin",
  roles:"超级管理员,普通用户",
  authorities:"ROLE_admin"
},
  {
    username:"admin",
    roles:"超级管理员,普通用户",
    authorities:"ROLE_admin"
  }
])
const loading = ref(false)
const pageNum = ref(1)//当前页
const pageSize = ref(8)//每页条目个数
const total = ref(1)//数据个数
const multipleSelection = ref([])//当前选中的用户数据
const deleteAllFlag = ref(true)//批量删除按钮是否可用
const addVisible = ref(false)//添加按钮的弹出框
const roleVisible = ref(false)//修改用户角色的对话框
const editVisible = ref(false)//修改用户信息对话框
const passwordVisible= ref(false)//修改用户密码
//分配用户角色,的复选框数据
const checkAll = ref(false )//是否全选
const isIndeterminate = ref(true)//表示 checkbox 的不确定状态,一般用于实现全选的效果
const checkedCities = ref([])//当前选中的
const cities = ref(['上海', '北京', '广州', '深圳'])//多少个复选项

/**=================================================api请求=============================================**/
//分页查询用户
const userPageFun = async(current,size)=>{
  loading.value = true;//查询表格开始,加载动画显示
  pageSize.value= size;//每页条目个数
  //深度克隆一份查询条件
  const _formInline = JSON.parse(JSON.stringify(formInline.value))
  if(!_formInline.roles[0].nameZh){//如果没有这个条件,需要移除
    delete _formInline.roles
  }
  //进行查询
  let result = await userPage(_formInline,current,pageSize.value);
  console.log(result);
  //响应正常
  if(result.code === 20000){
    //拿到响应数据
    let data = result.data
    //过来数据,方便我们展示数据
    //将对象roles和authorities封装成字符串,并将字符串作为一个字段添加到数据中
    data.forEach(function(item){
      // console.log(item)
      if(item.roles && item.roles.length>0){
        let strRole = ''
        let strAuthorities = ''
        item.roles.forEach(item2=> {
          strRole += item2.nameZh+",";
          strAuthorities += item2.name+",";
        });
        item.rolesStr = strRole.substring(0,strRole.length-1);
        item.authoritiesStr = strAuthorities.substring(0,strAuthorities.length-1);
      }else{
        item.rolesStr = "当前用户没有所属角色"
        item.authoritiesStr = "当前用户没有任何权限"

      }
    });
    tableData.value = data
    pageNum.value=result.current;//当前页
    total.value=result.Total;//数据个数
  }else{
    tableData.value = []
    // this.$message('用户信息查询失败');
    ElMessage({
      message: '用户信息查询失败',
      type: 'warning',
    })
  }
  loading.value = false;//表格加载结束
}
//查询角色
const getAllRolesPageFun =  async ()=>{
  var result = await getAllRolesPage(0,1000);
  if (result.code === 20000){
    // console.log(result)
    cities.value = result.data;
  }else{
    // this.$message.error("获取角色列表失败!!!")
    ElMessage.error('获取角色列表失败!!!')
  }
}
//添加用户
const addUserFun =  async ()=>{
  if(addUserData.value.username ==="" ||addUserData.value.password===""){
    ElMessage('请正确输入用户名和密码!!!');
    return;
  }
  let result = await addUser(addUserData.value);
  if(result.code===20000){
    ElMessage({
      message: '添加用户成功',
      type: 'success'
    });
    init()
  }
  //request.js统一处理
  // else{
  //   ElMessage.error(result.msg);
  // }
  addVisible.value = false
}
const updateUserFun = async ()=>{
  var result = await updateUser(updateUserData.value);
  if (result.code === 20000){
    ElMessage.success("修改成功")
    init();
  }else{
    ElMessage.error("修改失败")
  }
  editVisible.value = false;
}
const updatePasswordFun = async ()=>{
  const data = passwordData.value;
  if(data.password === '' || data.password=== undefined){
    ElMessage.info("请输入密码!!!");
    return ;
  }
  var result = await updatePasswordById(data.id,data.password)
  if(result.code === 20000){
    ElMessage.success("修改成功")
  }else{
    ElMessage.error(result.msg||"修改失败!!")
  }
  passwordVisible.value = false;
}
//更新用户角色
const updateUserAndRoleFun = async ()=>{
  let arr = [];

  checkedCities.value.filter(e=>{
    arr.push(e.id)
  })
  updateUserAndRolendRoleData.value.rids = arr.join(",")
  console.log(updateUserAndRolendRoleData.value)
  let result = await updateUserAndRole(updateUserAndRolendRoleData.value)
  if(result.code === 20000){
    ElMessage.success("修改成功!!!")
    init()
  }else{
    ElMessage.error("修改失败!!!")
  }
  roleVisible.value = false
}


//初始化方法
function init(){
  userPageFun(pageNum.value,pageSize.value)
  getAllRolesPageFun()
  addUserData.value={
    password: "",
    username: ""
  };
}
init();



/**=================================================组件回调=============================================**/
const onSubmit = () => {
  console.log(formInline.value);
  userPageFun(1,pageSize.value)
}
const handleSelectionChange = (val) => {
  multipleSelection.value = val;
  if(val.length > 0 ){
    deleteAllFlag.value = false;
  }else{
    deleteAllFlag.value = true;
  }
}
const updateUserClick = (data)=>{
  editVisible.value = true;
  const _data = JSON.parse(JSON.stringify(data))
  updateUserData.value = _data;
}
//修改用户拥有角色,复选框的回显
const updateUserAndRoleClick = (data)=>{
  updateUserAndRolendRoleData.value.uid = data.id //封装后端接口需要的数据
  roleVisible.value = true
  const _data = JSON.parse(JSON.stringify(data))
  checkedCities.value = []//先清空 当前选中数据
  //如果已有角色,需要进行数据回显
  if(_data.roles && _data.roles.length>0){
    cities.value.forEach(e=>{
      _data.roles.forEach(r=>{
        if(r.id==e.id){
          checkedCities.value.push(e)
        }
      })
    })
  }else{
    checkedCities.value=[]
  }
}
const updataPasswordClick = (data)=>{
  passwordVisible.value = true;
  const _data = JSON.parse(JSON.stringify(data))
  passwordData.value.id = _data.id;
}
const deleteUserByIdM = (id)=>{
  if(id === '1'){
    ElMessage.info("超级管理员用户不可删除!!!")
    return ;
  }
  ElMessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(async () => {
    var result = await deleteUserById(id);
    if(result.code === 20000){
      ElMessage({
        type: 'success',
        message: '删除成功!'
      });
      init()
    }else{
      ElMessage.error(result.msg||"删除失败!!!");
    }

  }).catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消删除'
    });
  });
}
const handleCurrentChange = (val) => {
  userPageFun(val,pageSize.value)
}
const handleCheckAllChange = (val) => {
  checkedCities.value = val ? cities.value : [];
  isIndeterminate.value = false;
}
const handleCheckedCitiesChange = (value) => {
  console.log("handleCheckedCitiesChange",value);
  let checkedCount = value.length;
  checkAll.value = checkedCount === cities.value.length;
  isIndeterminate.value = checkedCount > 0 && checkedCount < cities.value.length;
}

</script>

<style lang="scss" scoped>
.el-scrollbar__view{
  width: 100vw !important;
  .el-table__body{
    width: 100vw;
  }
}
.user {
  &-container {
    margin: 30px;
  }
  &-text {
    font-size: 30px;
    line-height: 46px;
  }
}
</style>

7. role页面逻辑


在这里插入图片描述


在这里插入图片描述

<template>
  <div class="role-container">
    <!--    添加和批量删除开始-->
    <el-form :inline="true">
      <el-form-item class="margin_right_10">
        <el-popover placement="bottom" width="800" v-model="addVisible">
          <template #reference>
            <el-button type="primary">添加</el-button>
          </template>
          <el-form :inline="true" :model="addUserData">
            <el-form-item label="角色名">
              <el-input v-model="addUserData.nameZh" placeholder="角色名"></el-input>
            </el-form-item>
            <el-form-item label="权限名">
              <el-input v-model="addUserData.name" placeholder="权限名"></el-input>
            </el-form-item>
          </el-form>
          <div style="text-align: right; margin: 0">
            <el-button text @click="addVisible = false">取消</el-button>
            <el-button type="primary" @click="addRole">添加</el-button>
          </div>
        </el-popover>
      </el-form-item>
      <el-form-item>
        <el-button type="danger" @click="" :disabled="deleteAllFlag">批量删除</el-button>
      </el-form-item>
    </el-form>
    <!--    添加和批量删除结束-->
    <!--    表格开始-->
    <el-table v-loading="loading" :data="tableData" border @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="100"></el-table-column>
      <el-table-column label="序号" type="index"  width="100"></el-table-column>
      <el-table-column prop="nameZh" label="角色名" width="400"></el-table-column>
      <el-table-column prop="name" label="权限字段" width="500"></el-table-column>
      <el-table-column fixed="right" label="操作(分配菜单权限/编辑角色/删除角色)" width="300vw" align="center">
        <template #default="{row,$index}"><!--注意这个可以让我们使用row和$index两个值,方便我们操作-->
          <el-button type="success" icon="Menu" @click="updateMenuAndRoleClick(row)"></el-button>
          <el-button type="primary" icon="Edit" @click="updateRoleClick(row)"></el-button>
          <el-button type="danger" icon="Delete" @click="deleteRoleById(row.id)"></el-button>
        </template>
      </el-table-column>
    </el-table>
    <!--    表格结束-->
    <!--    分页开始-->
    <div class="block">
      <el-pagination
          @current-change="handleCurrentChange"
          :current-page="pageNum"
          :page-size="pageSize"
          layout="total, prev, pager, next, jumper"
          :total="total"
          class="margin_top_10">
      </el-pagination>
    </div>
    <!--    分页结束-->
    <!--    对话框开始-->
    <el-dialog title="修改角色拥有菜单权限" v-model="menuVisible" width="30%">
      <el-tree
          :data="treeMenuData"
          show-checkbox
          node-key="id"
          :props="defaultProps"
          :default-expanded-keys="currentNodes"
          :default-checked-keys="currentNodes"
          ref="tree">
      </el-tree>
      <span slot="footer" class="dialog-footer">
        <el-button @click="menuVisible = false">取 消</el-button>
        <el-button type="primary" @click="updateMenuAndRole">确 定</el-button>
      </span>
    </el-dialog>
    <el-dialog title="修改角色信息" v-model="editVisible" width="30%">
      <el-form :inline="true" class="demo-form-inline">
        <el-form-item label="角色名">
          <el-input v-model="updateRoleData.nameZh" placeholder="角色名"></el-input>
        </el-form-item>
        <el-form-item label="权限名">
          <el-input v-model="updateRoleData.name" placeholder="权限字段"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editVisible = false">取 消</el-button>
        <el-button type="primary" @click="updateRole">确 定</el-button>
      </span>
    </el-dialog>
    <!--    对话框结束-->
  </div>
</template>

<script>
import {addRole, deleteRoleById, getAllRolesPage, updateRole} from '@/api/role'
import {menuIdByRoleId, menuList, updateMenuAndRole} from '@/api/menu'

export default {
  name: 'index.vue',
  data(){
    return{
      addUserData:{
        name: "",
        nameZh: ""
      },//添加角色
      updateRoleData:{},//修改角色
      //角色展示数据
      tableData: [{
        name:"ROLE_admin",
        nameZh:"超级管理员"
      },
        {
          name:"ROLE_user",
          nameZh: "普通用户"
        }
      ],
      loading:false,
      pageNum: 0,//当前页
      pageSize:8,//每页条目个数
      total:1,//数据个数
      addVisible:false,//添加按钮的弹出框
      deleteAllFlag:true,//批量删除按钮是否可用
      menuVisible:false,//修改拥有菜单权限
      editVisible:false,//修改角色信息对话框
      treeMenuData: [],//菜单树结构
      defaultProps: {
        children: 'children',
        label: 'name'
      },
      currentNodes:[],//当前选中的节点
      updateMenuAndRoleData:{rid:'',mids:''}//修改角色拥有菜单权限的数据
    }
  },
  created() {
    this.init()
  },
  methods:{
    init(){
      this.getAllRolesPage(this.pageNum,this.pageSize);
      this.menuList()
      this.addUserData={
        name: "",
        nameZh: ""
      };
    },
    /**=================================================api请求=============================================**/
    async getAllRolesPage(current,size){
      this.loading = true;//查询表格开始,加载动画显示
      var result = await getAllRolesPage(current,size);
      // console.log(result)
      let data = result.data
      if (result.code === 20000){
        this.tableData = data
        this.pageNum=result.current;//当前页
        this.total=result.Total;//数据个数
      }else {
        this.$message('角色信息查询失败');
        this.tableData = []
      }
      this.loading = false;
    },
    async menuList(){
      var result = await menuList()
      if(result.code === 20000){
        this.treeMenuData = result.menuAllList
      }else{
        this.$message.error(result.msg);
      }
    },
    //角色id获取菜单id
    async menuIdByRoleId(rid){
      var result = await menuIdByRoleId(rid)
      if(result.code === 20000){
        return result.menuId
      }
      // else{
      //   this.$message.info("查询当前角色菜单列表失败!!!")
      // }
    },
    //添加角色
    async addRole(){
      if(this.addUserData.name ==="" ||this.addUserData.nameZh===""){
        this.$message('请正确输入权限名和角色名!!!');
        return;
      }
      let result = await addRole(this.addUserData);
      if(result.code===20000){
        this.$message({
          message: '添加角色成功',
          type: 'success'
        });
        this.init()
      }else{
        this.$message.error(result.msg);
      }
      this.addVisible = false
    },
    async updateRole(){
      if(this.updateRoleData.name===''||this.updateRoleData.nameZh===''){
        this.$message('请正确输入权限名和角色名!!!');
        return;
      }
      let result = await updateRole(this.updateRoleData)
      if (result.code === 20000){
        this.$message.success("修改完成!!")
        this.init()
      }else{
        this.$message.error(result.msg||"修改失败!!!");
      }
      this.editVisible = false
    },
    async updateMenuAndRole(){
      this.updateMenuAndRoleData.mids = this.$refs.tree.getCheckedKeys().join(",")
      var result = await updateMenuAndRole(this.updateMenuAndRoleData);
      if(result.code === 20000){
        this.init()
        this.$message.success("修改成功!!!")
      }else{
        this.$message.error(result.msg||"修改失败")
      }
      this.menuVisible = false
    },
    /**=================================================组件回调=============================================**/
    updateMenuAndRoleClick(data){
      this.currentNodes = []
      this.updateMenuAndRoleData.rid = data.id
      // //根据角色id获取菜单id,做数据回显
      const _this = this
      this.menuIdByRoleId(data.id).then(arr=>{
        if(arr.length > 0){
          this.currentNodes = arr
          _this.$refs.tree.setCheckedKeys(arr);
        }else{
          this.currentNodes = []
          _this.$refs.tree.setCheckedKeys([]);
        }
      }).catch(err=>{
        this.currentNodes = []
        _this.$refs.tree.setCheckedKeys([]);
      })
      this.menuVisible = true
    },
    updateRoleClick(data){
      const _data = JSON.parse(JSON.stringify(data))
      this.updateRoleData = _data;
      this.editVisible = true;
    },
    handleSelectionChange(val) {
      this.multipleSelection = val;
      if(val.length > 0 ){
        this.deleteAllFlag = false;
      }else{
        this.deleteAllFlag = true;
      }
    },
    deleteRoleById(id){
      if(id === '1'){
        this.$message.info("超级管理员角色不可删除!!!")
        return ;
      }
      this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        var result = await deleteRoleById(id);
        if(result.code === 20000){
          this.$message({
            type: 'success',
            message: '删除成功!'
          });
          this.init()
        }else{
          this.$message.error(result.message||"删除失败!!!");
        }
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },
    handleCurrentChange(val) {
      this.getAllRolesPage(val,this.pageSize)
    }
  },
}
</script>

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

8. menu页面逻辑


在这里插入图片描述


在这里插入图片描述

<template>
  <div class="menu-container">
    <!--    添加开始-->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-button type="primary" @click="addMenuClick(0)">添加顶级菜单</el-button>
      </el-form-item>
    </el-form>
    <!--    添加结束-->
    <el-table :data="tableData" style="width: 100%;margin-bottom: 20px;" row-key="id" border default-expand-all
              :tree-props="{children: 'children', hasChildren: 'hasChildren'}">
      <el-table-column prop="name" label="菜单名" sortable width="500"></el-table-column>
      <el-table-column prop="type" label="菜单类型" sortable width="180"></el-table-column>
      <el-table-column prop="permission" label="菜单权限(一般给按钮使用)"></el-table-column>
      <el-table-column prop="url" label="菜单url(与后端对应,无此菜单权限的用户,无法访问)"></el-table-column>
      <el-table-column prop="path" label="菜单path(要和前端路由配置的path一致)"></el-table-column>
      <!--      <el-table-column prop="component" label="组件(对应路由组件)"></el-table-column>-->
      <!--      <el-table-column prop="iconCls" label="组件图标"></el-table-column>-->
      <el-table-column label="操作(添加子菜单/编辑菜单/删除菜单)" width="300" align="center" fixed="right">
        <template #default="{row,$index}"><!--注意这个可以让我们使用row和$index两个值,方便我们操作,row是当前列-->
          <el-button type="success" icon="Plus" @click="addMenuClick(row.id)"></el-button>
          <el-button type="primary" icon="Edit" @click="updateMenuClick(row)"></el-button>
          <el-button type="danger" icon="Delete" @click="deleteMenu(row)"></el-button>
        </template>
      </el-table-column>
    </el-table>
    <!--    对话框开始-->
    <el-dialog title="添加菜单" v-model="addVisible" width="80%">
      <el-form :inline="true" label-width="10vw" class="demo-form-inline">
        <el-form-item label="父id" v-show="false" >
          <el-input v-model="addMenuData.pid" placeholder="父id"></el-input>
        </el-form-item>
        <el-form-item label="菜单名">
          <el-input v-model="addMenuData.name" placeholder="菜单名"></el-input>
        </el-form-item>
        <el-form-item label="菜单类型">
          <el-input v-model="addMenuData.type" placeholder="menu(菜单)/button(按钮)"></el-input>
        </el-form-item>
        <el-form-item label="菜单权限">
          <el-input v-model="addMenuData.permission" placeholder="菜单权限"></el-input>
        </el-form-item>
        <el-form-item label="菜单url">
          <el-input v-model="addMenuData.url" placeholder="与后端url对应"></el-input>
        </el-form-item>
        <el-form-item label="菜单path">
          <el-input v-model="addMenuData.path" placeholder="与前端路由对应"></el-input>
        </el-form-item>
        <el-form-item label="组件">
          <el-input v-model="addMenuData.component" placeholder="可省略"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="addVisible = false">取 消</el-button>
        <el-button type="primary" @click="addOrUpdateMenu">确 定</el-button>
      </span>
    </el-dialog>
    <!--    对话框结束-->
  </div>
</template>

<script>
import {treeData,unTreeData} from '@/utils/treeutil'
import {menuList,addMenu,deleteMenuList,deleteMenuById,updateMenu} from '@/api/menu'
export default {
  name: 'index.vue',
  data(){
    return{
      tableData: [],//树形结构数据
      addMenuData:{},//添加菜单的数据
      updateMenuData:{},//修改菜单数据
      addVisible:false,//添加菜单对话框
      editVisible:false,//编辑菜单对话框
    }
  },
  created() {
    this.init()
  },
  methods:{
    init() {
      this.menuList();
      this.addMenuData = {};
    },
    /**=================================================api请求=============================================**/
    async menuList(){
      var result = await menuList()
      if(result.code === 20000){
        this.tableData = result.menuAllList
      }else{
        this.$message.error(result.message);
      }
    },
    async addOrUpdateMenu(){
      if(this.addMenuData.name === undefined || this.addMenuData.permission === undefined
      ||this.addMenuData.name === "" || this.addMenuData.permission === ""){
        this.$message.error("菜单名和菜单权限为必选项");
        return;
      }

      if(this.addMenuData.id){//如果有id说明是修改
        var result = await updateMenu(this.addMenuData);
        if(result.code === 20000){
          this.$message.success("修改菜单成功")
          this.init()
        }else{
          this.$message.error("修改失败")
        }
      }else{
        var result = await addMenu(this.addMenuData);
        if(result.code === 20000){
          this.$message.success("添加菜单成功")
          this.init()
        }else{
          this.$message.error("添加失败")
        }
      }
      this.addVisible = false;
    },
    /**=================================================组件回调=============================================**/
    deleteMenu(menu){
      this.$confirm('此操作将删除当前菜单和当前菜单所有子菜单,是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        let result = {};
        if(menu.children){
          let arr =[]
          unTreeData([menu]).filter(e=>{
            arr.push(e.id)
          });
          result = await deleteMenuList({ids:arr.join(',')})
        }else{
          // console.log(2)
          result = await deleteMenuById(menu.id);
        }

        if(result.code === 20000){
          this.$message({
            type: 'success',
            message: '删除成功!'
          });
          this.init()
        }else{
          this.$message.error(result.message||"删除失败!!!");
        }
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },
    //添加按钮回调
    addMenuClick(pid){
      this.addVisible = true;
      this.addMenuData = {}
      this.addMenuData.pid = pid
    },
    //修改按钮回调
    updateMenuClick(data){
      this.addVisible = true;
      const _data = JSON.parse(JSON.stringify(data))
      this.addMenuData = _data;
    },
  }
}
</script>

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

打通Vue3+Flask(python3)+Mysql-实现简单数据交互 一般是右键新建数据库,然后选中数据库,选择创建新的表,然后设计需要的字段,注意的是每一个表需要设计主键(选中行,右键创建新的索引-PRIMARY)。(建议下载长期支持版本,版本不宜过低,比如18,20),Vue.js(npm install -g @vue/cli,下载速度慢可以设置淘宝镜像)。5、接下来,输入cd vueproject,进入当前项目目录,输入npm i,下载项目所需要的依赖。我这里是中文版,如果有需要,可以在设置里面的插件,下载中文包,然后重启pycharm即可,插件如下。 阅读详情

相关推荐

使用python库uvicorn替代Nginx发布Vue3项目

在python项目中新建文件夹static,然后将打包好的vue包复制进去。③强制让JS文件的MIME类型为application/javascript。至于打包,就跟平时打包到nginx一样的去打包就行了。新建api_service.py文件作为入口类。②配置VueRouter路由的反向代理。(博主vue版本:3.2.44)不要添加base参数,画蛇添足!

Good Luck 4409

Python+Django+Vue3全栈开发商城项目

一个Django+Vue3前后端分离的全栈商城系统项目,旨在帮助广大初学者将所学知识快速融合,实现快速开发,专为大学生或者找工作为目的者而设,将零散的知识点融合到项目中,并且收获真实项目的设计思维和开发经验。 本项目前端使用课程《Vue3.0正式版核心技术》开发的商城前端项目,后端使用Python的快速开发框架Django框架。 希望本课程能给广大学者带来技能和项目上的提升。 项目截图:

【Python百日进阶-Web开发-Vue3】Day501 - Vue简介与快速入门

Vue是一个前端框架,主要负责帮助我们构建用户的界面Vue负责VM的工作(视图模型),通过Vue可以将视图和模型相关联当模型发生变化时,视图会自动更新也可以通过视图去操作模型Vue思想组件式开发声明式编程Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

专注于医院数据分析技术与系统开发的创作与分享。 765

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

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

殷志鹏的博客 2484

【Python百日进阶-Web开发-Vue3】Day503 - Vue3模板语法

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。

专注于医院数据分析技术与系统开发的创作与分享。 524

Python-pythonvue3前后端分离项目

python vue3前后端分离项目

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——权限管理rbac接口实现

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

殷志鹏的博客 979

一款Python基于VUE3.0的高颜值卡密发卡系统源码.zip

一款基于VUE3.0的高颜值卡密发卡系统,特别适合虚拟商品、知识付费等。

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Sa-Token实现Security权限管理

【代码】SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Sa-Token实现Security权限管理。

殷志鹏的博客 936

保姆级教程:从Salmon输出到DESeq2,搞定RNA-seq表达矩阵标准化与下游分析准备

本文提供从Salmon输出到DESeq2标准化的RNA-seq数据分析全流程指南,详细解析表达矩阵构建、标准化方法选择及下游分析准备。重点介绍如何将Salmon定量结果转换为DESeq2兼容格式,包括TPM与原始计数的处理技巧,以及常见问题排查方法,帮助研究人员高效完成RNA-seq数据分析。

weixin_42699679的博客 304

原神真端,原神服务端,虚拟机启动部署傻瓜式教学

没玩过抽卡 游戏想体验一下抽卡的感觉,听说原神有真端了,所以弄一个局域网的服务体验一下,这里要感谢一些服务端的分享者。

azyouyouyou的博客 1万+

python使用pywebview集成vue3和element-plus开发桌面系统框架

随着web技术越来越成熟,就连QQ的windows客户端都用web技术来开发,所以在未来,web技术来开发windows桌面软件也会越来越多,所以在此发展驱动之下,将最近流程的python与web技术相结合,使用vue3和element-plus、vue-router、saas(CSS扩展语言),来开发桌面管理系统,也是一个不错的选择。最主要的是两者技术门槛比较低,而且开发效率也很快,同时也可以做出漂亮的界面。 一、技术背景 python:版本号为3.7(你也可以换成高版本,同样支持) vue3:版本为

专注技术交流、咨询 5537

腾讯云TDSQL MySQL版 - 开发指南兼容性

语言结构 分布式实例支持所有 MySQL 使用的文字格式,包括: String Literals Numeric Literals Date and Time Literals Hexadecimal Literals Bit-Value Literals Boolean Literals NULL Values String Literals String Literals 是一个 bytes 或者 characters 的序列,两端被单引号&#39;或者双引号"包围,TDSQL MySQL版

weixin_43872058的博客 909

Python VUE3 + Django Ninja的后台管理系统

Python VUE3 + Django Ninja的后台管理系统

qq_36934713的博客 3940

告别混乱接线!用EPLAN 3D布局图,手把手教你搞定电气柜的3D建模与自动布线

本文详细介绍了如何使用EPLAN 3D布局图进行电气柜的3D建模与自动布线,帮助工程师告别混乱接线。从项目准备、基础结构建模到智能设备布局与逻辑定义,再到高级布线技术与问题排查,提供了一套完整的实战指南。通过EPLAN的3D功能,显著提升设计效率与系统可靠性。

weixin_30680385的博客 339

科研党必备:5分钟搞定Arxiv论文批量导入NoteExpress(附PDF自动下载技巧)

本文为科研人员提供了一套高效的自动化工作流,实现从Arxiv批量检索论文、自动下载PDF到无缝导入NoteExpress的全过程。通过Python脚本和NASA ADS等工具,可大幅提升文献管理效率,特别适合需要追踪前沿动态的研究生和科研团队。

orange的博客 398

初尝python flask + vue3 axios 接口配置

如果此处不使用JSON.stringify进行解析,则服务端flask就需要解析。前端和服务端如果都不解析,则会报错。Vue的axios包自带JSON解析功能,但仅限于获取数据,因此发送数据时,还是需要解析后发送的。这里注意代码中的axios.post方法,相比较之前文章的方法。此处,我们虽然设置了传参param,但是如果直接提交,服务器会返回ERROR-415,不支持的媒体类型。之后,开启flask,服务器在端口5000运行。之后返回App.vue,渲染页面。在项目的app.py文件下,

SeverusYang的博客 2314

车载LIN总线避坑指南:主从节点通信失败的5个常见原因及解决方案

本文深度解析车载LIN总线主从节点通信失败的五大常见原因,涵盖物理层信号异常、调度表冲突、LDF配置错误、节点状态机缺陷及环境干扰。重点剖析了LDF文件配置不一致、波特率不匹配等关键问题,并提供了从示波器波形分析到软件配置检查的完整实战排查路径,帮助工程师高效定位并解决LIN通信故障。

g9h0i1的博客 1249

Python Vue3 前后端分离项目

Python Vue3 前后端分离项目 【下载地址】PythonVue3前后端分离项目 这是一个基于 Python 和 Vue3 构建的前后端分离项目,采用现代化的开发模式,兼具高效与灵活性。前端使用 Vue3 框架,提供流畅的用户体验和响应式设计,兼容多种设备;后端基于 Python 开发,具备强大的数据处理能力。项目...

gitblog_06776的博客 557

Python中的串口通信库pyserial(基础)

pyserial是一个跨平台的库,可以在多个操作系统上使用,包括Windows、Linux和MacOS。:flushInput方法用于清除输入缓冲区中的所有数据,flushOutput方法用于清除输出缓冲区中的所有待发送数据。:PySerial允许在创建Serial对象后,通过修改对象的属性来设置串口参数,如波特率、数据位、校验位和停止位。同时,也提供了方法或属性来检查串口的状态,如是否已打开。:sendBreak方法用于向串口发送一个BREAK信号,这可以用于向连接的设备发送一个中断或重置信号。

weixin_44217158的博客 1万+

RK3568 Android14 移远GNSS卫星导航模组LC760Z GPS北斗定位模块调试

Rockchip RK3568 作为一款高性能、低功耗的国产SoC,被广泛应用于工业网关、车载中控和智能手持终端等领域。在这些应用场景中,位置服务(Location Service) 往往是不可或缺的功能。本文将详细记录如何集成GPS模组到 RK3568 开发板上,通过 UART(串口)接口集成通用的 GPS 模块(如 移远LC760Z 或 EC20 4G 模块自带的 GPS 功能也差不多),并实现数据的解析与上层应用对接。

qq_33427070的博客 1413

论坛网站全栈项目Vue3+Python+Flask+MySQL+Redis

校园论坛网站系统。使用技术:python+Flask+Vue3+MySQL+Redis 主要功能: ● 用户登录、注册 ● 首页(轮播图显示、文章显示、分页、文章分类) ● 发表文章,轮播图,对文章的评论 ● 查询(基于文章时间、评论个数、所属分类) ● 个人中心(编辑个人头像、个性签名) 后台管理,权限管理

子午的博客 1528

廖雪峰Python教程[完整版]

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

Vue进阶:得心应手的开发体验-基于Pycharm的Vue3项目构建与开发环境搭建

Vue.js是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue采用自底向上增量开发的设计。Vue的核心库只关注视图层,是一个基于MVVM结构的轻量级框架,非常容易与其它库或已有项目整合。

SteveRocket's-Blog 5368

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

关于python FastAPI与Vue3的通信

在设计网络程序时,前后端的分离设计已经是一门成熟的技术,在之前我所做的网络程序中,并未将前后端分离,比如之前的一系列Flask文章,更多是侧重于网络后端的开发,甚至会将原本需要在前端做的工作,比如校验数据等,都提交给服务器来实现,这加大了前后端的交互,也使得在实际部署后,增大了网络通信成本。本着不断学习的理念,现计划学习一下前后端分离技术,前端采用三大框架之一的Vue.js,后端则采用Python的FastAPI框架。

全粘工程师 841

AI全栈实战 | 3.9-01 爬虫入门:requests 加 BeautifulSoup 三分钟跑通,但 90% 的人栽在这 4 个“礼貌“问题上

⚠️:本文内容仅用于。请遵守目标站点的服务条款与相关法律法规,不得对目标服务造成负担,不得采集、传播个人隐私数据。技术是中性的,边界在你手里。爬虫的入门技术(requests 发请求 + BeautifulSoup 解析)三分钟就能学会。真正区分"会用爬虫"和"会做爬虫的人"的,是这篇我们跑通"请求→解析→存储"的最小闭环,然后重点把"合规与礼貌"这根弦给你绷上。

源码获取 | 运行部署 | 定制二开修改,如果回复不及时请耐心等待,请见谅。 405
上一篇: SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——Vue3.0前后端分离架构——后台系统前端登录实现
下一篇: 开源项目RuoYi-Cloud-Plus详解——主目录(持续更新)
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、付费专栏及课程。

余额充值