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>