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

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新):https://blog.csdn.net/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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ydenergy_殷志鹏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值