Files
effekt-interface-frontend/src/components/System/MenuManage.vue

500 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-wrap">
<page-section title="菜单管理">
<template slot="extra">
<el-button size="small" :loading="initPermissionMenuLoading" @click="initPermissionMenu">初始化权限管理菜单</el-button>
<el-button size="small" :loading="initBugMenuLoading" @click="initBugManagementMenu">初始化 Bug 管理菜单</el-button>
<el-button type="primary" size="small" @click="openCreate(0)">新建菜单</el-button>
</template>
<el-form :inline="true" :model="queryForm" size="small" @submit.native.prevent>
<el-form-item label="状态">
<el-select v-model="queryForm.status" clearable placeholder="全部状态">
<el-option label="启用" :value="1"></el-option>
<el-option label="禁用" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchTree">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="tableData"
border
row-key="id"
default-expand-all
:tree-props="{ children: 'children' }"
style="width: 100%; margin-top: 16px;">
<el-table-column prop="name" label="菜单名称" min-width="180"></el-table-column>
<el-table-column prop="code" label="菜单编码" min-width="160"></el-table-column>
<el-table-column prop="type" label="类型" width="90">
<template slot-scope="scope">{{ formatType(scope.row.type) }}</template>
</el-table-column>
<el-table-column prop="path" label="路径" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="component" label="组件" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column label="权限标识" min-width="160" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.permission_code || scope.row.permissionCode }}</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="80"></el-table-column>
<el-table-column prop="visible" label="显示" width="80">
<template slot-scope="scope">{{ scope.row.visible === 0 ? '否' : '是' }}</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="80">
<template slot-scope="scope">{{ scope.row.status === 0 ? '禁用' : '启用' }}</template>
</el-table-column>
<el-table-column label="操作" width="220" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="openCreate(scope.row.id)">新增下级</el-button>
<el-button type="text" @click="openEdit(scope.row)">编辑</el-button>
<el-button type="text" style="color: #F56C6C;" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</page-section>
<el-dialog :title="dialogMode === 'create' ? '新建菜单' : '编辑菜单'" :visible.sync="dialogVisible" width="620px" @close="resetDialogForm">
<el-form ref="menuForm" :model="menuForm" :rules="menuRules" label-width="100px" size="small">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="上级菜单" prop="parentId">
<el-input :value="getParentName(menuForm.parentId)" disabled></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单类型" prop="type">
<el-select v-model="menuForm.type" placeholder="请选择类型" style="width: 100%;">
<el-option label="目录" :value="1"></el-option>
<el-option label="菜单" :value="2"></el-option>
<el-option label="按钮" :value="3"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="菜单名称" prop="name">
<el-input v-model.trim="menuForm.name" maxlength="64" placeholder="请输入菜单名称"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单编码" prop="code">
<el-input v-model.trim="menuForm.code" maxlength="64" placeholder="请输入菜单编码"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="访问路径" prop="path">
<el-input v-model.trim="menuForm.path" maxlength="128" placeholder="请输入访问路径"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="组件路径" prop="component">
<el-input v-model.trim="menuForm.component" maxlength="128" placeholder="请输入组件路径"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="图标" prop="icon">
<el-input v-model.trim="menuForm.icon" maxlength="64" placeholder="请输入图标"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="权限标识" prop="permissionCode">
<el-input v-model.trim="menuForm.permissionCode" maxlength="64" placeholder="请输入权限标识"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="排序" prop="sort">
<el-input-number v-model="menuForm.sort" :min="0" :max="9999" controls-position="right" style="width: 100%;"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="是否显示" prop="visible">
<el-select v-model="menuForm.visible" placeholder="请选择" style="width: 100%;">
<el-option label="是" :value="1"></el-option>
<el-option label="否" :value="0"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="状态" prop="status">
<el-select v-model="menuForm.status" placeholder="请选择" style="width: 100%;">
<el-option label="启用" :value="1"></el-option>
<el-option label="禁用" :value="0"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer">
<el-button size="small" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" size="small" :loading="submitting" @click="submitForm">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { createMenu, deleteMenu, getMenuTree, updateMenu } from '@/api/rbacApi'
const getDefaultForm = () => ({
menuId: undefined,
parentId: 0,
name: '',
code: '',
type: 2,
path: '',
component: '',
icon: '',
permissionCode: '',
sort: 0,
visible: 1,
status: 1
})
export default {
name: 'MenuManage',
components: { PageSection },
data() {
return {
loading: false,
submitting: false,
initPermissionMenuLoading: false,
initBugMenuLoading: false,
dialogVisible: false,
dialogMode: 'create',
queryForm: {
status: ''
},
menuForm: getDefaultForm(),
menuRules: {
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入菜单编码', trigger: 'blur' }],
type: [{ required: true, message: '请选择菜单类型', trigger: 'change' }],
sort: [{ required: true, message: '请输入排序', trigger: 'change' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
},
tableData: []
}
},
methods: {
fetchTree() {
this.loading = true
getMenuTree({ status: this.queryForm.status }).then(res => {
const data = res && res.data ? res.data : res || []
this.tableData = Array.isArray(data) ? data : (data.list || data.items || [])
}).catch(() => {
this.tableData = []
}).finally(() => {
this.loading = false
})
},
resetSearch() {
this.queryForm = {
status: ''
}
this.fetchTree()
},
formatType(type) {
const map = {
1: '目录',
2: '菜单',
3: '按钮'
}
return map[type] || type
},
flattenMenus(list, result) {
(list || []).forEach(item => {
result.push(item)
if (item.children && item.children.length) {
this.flattenMenus(item.children, result)
}
})
},
getParentName(parentId) {
if (!parentId) {
return '顶级菜单'
}
const result = []
this.flattenMenus(this.tableData, result)
const target = result.find(item => item.id === parentId)
return target ? target.name : '顶级菜单'
},
openCreate(parentId) {
this.dialogMode = 'create'
this.dialogVisible = true
this.$nextTick(() => {
this.menuForm = Object.assign(getDefaultForm(), { parentId: parentId || 0, sort: 0 })
if (this.$refs.menuForm) {
this.$refs.menuForm.clearValidate()
}
})
},
openEdit(row) {
this.dialogMode = 'edit'
this.dialogVisible = true
this.$nextTick(() => {
this.menuForm = Object.assign(getDefaultForm(), row, {
menuId: row.menuId || row.id,
parentId: row.parentId !== undefined ? row.parentId : (row.parent_id || 0),
permissionCode: row.permissionCode !== undefined ? row.permissionCode : (row.permission_code || ''),
sort: row.sort !== undefined && row.sort !== null ? Number(row.sort) : 0
})
if (this.$refs.menuForm) {
this.$refs.menuForm.clearValidate()
}
})
},
resetDialogForm() {
this.menuForm = getDefaultForm()
this.submitting = false
this.$nextTick(() => {
if (this.$refs.menuForm) {
this.$refs.menuForm.resetFields()
}
})
},
submitForm() {
this.$refs.menuForm.validate(valid => {
if (!valid) {
return
}
this.submitting = true
const basePayload = {
parentId: Number(this.menuForm.parentId || 0),
name: this.menuForm.name,
code: this.menuForm.code,
type: Number(this.menuForm.type),
path: this.menuForm.path,
component: this.menuForm.component,
icon: this.menuForm.icon,
permissionCode: this.menuForm.permissionCode,
sort: Number(this.menuForm.sort || 0),
visible: Number(this.menuForm.visible),
status: Number(this.menuForm.status)
}
const payload = this.dialogMode === 'create'
? basePayload
: Object.assign({ menuId: Number(this.menuForm.menuId) }, basePayload)
const request = this.dialogMode === 'create'
? createMenu(payload)
: updateMenu(payload)
request.then(res => {
if (res && res.code === 20000) {
this.$message.success((res && res.message) || (this.dialogMode === 'create' ? '菜单创建成功' : '菜单更新成功'))
this.dialogVisible = false
this.fetchTree()
return
}
this.$message.error((res && res.message) || (this.dialogMode === 'create' ? '菜单创建失败' : '菜单更新失败'))
}).finally(() => {
this.submitting = false
})
})
},
handleDelete(row) {
this.$confirm('确认删除该菜单吗?', '提示', {
type: 'warning'
}).then(() => {
deleteMenu({ menuId: row.menuId || row.id }).then(res => {
if (res && res.code !== 20000) {
this.$message.error((res && res.message) || '菜单删除失败')
return
}
this.$message.success((res && res.message) || '菜单删除成功')
this.fetchTree()
})
}).catch(() => {})
},
initPermissionMenu() {
this.initPermissionMenuLoading = true
getMenuTree({}).then(res => {
const data = res && res.data ? res.data : res || []
const tree = Array.isArray(data) ? data : (data.list || data.items || [])
const flattened = []
this.flattenMenus(tree, flattened)
let systemMenu = flattened.find(item => {
const path = item.path || ''
const name = item.name || ''
return path === '/system' || name === '系统管理'
})
const hasPermissionMenu = flattened.some(item => (item.path || '') === '/system/permission')
const createPermissionChild = parentId => {
if (hasPermissionMenu) {
this.$message.success('权限管理菜单已存在')
return Promise.resolve()
}
return createMenu({
parentId: Number(parentId || 0),
name: '权限管理',
code: 'system_permission_manage',
type: 2,
path: '/system/permission',
component: '@/components/System/PermissionManage',
icon: 'lock',
permissionCode: 'permission:list',
sort: 40,
visible: 1,
status: 1
})
}
if (systemMenu) {
return createPermissionChild(systemMenu.id || systemMenu.menuId)
}
return createMenu({
parentId: 0,
name: '系统管理',
code: 'system_manage',
type: 1,
path: '/system',
component: '',
icon: 'setting',
permissionCode: '',
sort: 90,
visible: 1,
status: 1
}).then(createRes => {
const created = (createRes && createRes.data) || {}
const parentId = created.id || created.menuId
if (parentId) {
return createPermissionChild(parentId)
}
// 如果接口不返回id则重新拉取菜单树定位系统管理菜单
return getMenuTree({}).then(latestRes => {
const latestData = latestRes && latestRes.data ? latestRes.data : latestRes || []
const latestTree = Array.isArray(latestData) ? latestData : (latestData.list || latestData.items || [])
const latestFlattened = []
this.flattenMenus(latestTree, latestFlattened)
const latestSystem = latestFlattened.find(item => (item.path || '') === '/system' || (item.name || '') === '系统管理')
return createPermissionChild(latestSystem ? (latestSystem.id || latestSystem.menuId) : 0)
})
})
}).then(() => {
this.$message.success('权限管理菜单初始化完成')
this.fetchTree()
}).catch(() => {
this.$message.error('初始化权限管理菜单失败')
}).finally(() => {
this.initPermissionMenuLoading = false
})
},
initBugManagementMenu() {
this.initBugMenuLoading = true
getMenuTree({}).then(res => {
const data = res && res.data ? res.data : res || []
const tree = Array.isArray(data) ? data : (data.list || data.items || [])
const flattened = []
this.flattenMenus(tree, flattened)
const hasBugMenu = flattened.some(item => String(item.path || '').indexOf('/bug') === 0)
if (hasBugMenu) {
this.$message.success('Bug 管理菜单已存在')
return Promise.resolve()
}
return createMenu({
parentId: 0,
name: 'Bug管理',
code: 'bug_manage_root',
type: 1,
path: '/bug',
component: '',
icon: 'el-icon-s-claim',
permissionCode: '',
sort: 88,
visible: 1,
status: 1
}).then(parentRes => {
const created = (parentRes && parentRes.data) || {}
let parentId = created.id || created.menuId
const resolveParentId = () => {
if (parentId) return Promise.resolve(parentId)
return getMenuTree({}).then(latestRes => {
const latestData = latestRes && latestRes.data ? latestRes.data : latestRes || []
const latestTree = Array.isArray(latestData) ? latestData : (latestData.list || latestData.items || [])
const flat = []
this.flattenMenus(latestTree, flat)
const p = flat.find(item => (item.path || '') === '/bug' || (item.code || '') === 'bug_manage_root')
return p ? (p.id || p.menuId) : 0
})
}
return resolveParentId().then(pid => {
if (!pid) {
return Promise.reject(new Error('未获取到 Bug 管理父菜单 ID'))
}
const children = [
{
name: '新建 Bug',
code: 'bug_create',
path: '/bug/create',
component: '@/components/Bug/BugEditor',
permissionCode: 'bug:create',
sort: 5,
icon: 'el-icon-document-add'
},
{
name: 'Bug 列表',
code: 'bug_list',
path: '/bug/list',
component: '@/components/Bug/BugList',
permissionCode: 'bug:list',
sort: 10,
icon: 'el-icon-document'
},
{
name: 'Bug 统计',
code: 'bug_stats',
path: '/bug/stats',
component: '@/components/Bug/BugStats',
permissionCode: 'bug:stats',
sort: 20,
icon: 'el-icon-data-line'
}
]
return children.reduce((chain, c) => {
return chain.then(() => createMenu({
parentId: Number(pid),
name: c.name,
code: c.code,
type: 2,
path: c.path,
component: c.component,
icon: c.icon,
permissionCode: c.permissionCode,
sort: c.sort,
visible: 1,
status: 1
}))
}, Promise.resolve())
})
})
}).then(() => {
this.$message.success('Bug 管理菜单初始化完成')
this.fetchTree()
}).catch(() => {
this.$message.error('初始化 Bug 管理菜单失败')
}).finally(() => {
this.initBugMenuLoading = false
})
}
},
created() {
this.fetchTree()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>