今天优先开发菜单及角色,明天将开发岗位配置、级别配置等功能。具体看下图
而前端的路由不需要手动添加,是依据数据库里面存储的路径。
一、添加视图
在根目录下src文件夹下views文件夹下permission文件夹下menu文件夹下,新建index.vue,代码如下
<template>
<div class="app-container">
<div class="filter-container" style="float:right;">
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexAdd')" class="filter-item" style="margin-left: 10px;" @click="handleAdd" type="primary">添加</el-button>
<el-button class="filter-item" style="margin-left: 10px;" @click="getAll">刷新</el-button>
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexTostatus')" class="filter-item" style="margin-left: 10px;" @click="handleStatus(1)" type="success">启用</el-button>
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexTostatus')" class="filter-item" style="margin-left: 10px;" @click="handleStatus(0)" type="warning">禁用</el-button>
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexDelete')" class="filter-item" @click="handleDelete" type="danger">删除</el-button>
</div>
<el-table
ref="resTable"
:key="tableKey"
v-loading="listLoading"
:data="list"
row-key="id"
border
highlight-current-row
style="width:100%;margin-top:10px;height:100%;"
lazy
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:default-sort="{prop: 'id', order: 'descending'}"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="ID" sortable prop="id" align="center" width="100">
<template slot-scope="{row}">
<span>{{ row.id }}</span>
</template>
</el-table-column>
<el-table-column label="标识" prop="menuname" align="center">
<template slot-scope="{row}">
<span>{{ row.menuname }}</span>
</template>
</el-table-column>
<el-table-column label="名称" prop="title" align="center">
<template slot-scope="{row}">
<span>{{ row.title }}</span>
</template>
</el-table-column>
<el-table-column label="图标" prop="icon" align="center" width="80">
<template slot-scope="{row}">
<span><svg-icon :icon-class="row.icon" /></span>
</template>
</el-table-column>
<el-table-column label="路由" prop="path" align="center">
<template slot-scope="{row}">
<span>{{ row.path }}</span>
</template>
</el-table-column>
<el-table-column label="组件" prop="component" align="center">
<template slot-scope="{row}">
<span>{{ row.component }}</span>
</template>
</el-table-column>
<el-table-column label="重定向" prop="redirect" align="center">
<template slot-scope="{row}">
<span>{{ row.redirect }}</span>
</template>
</el-table-column>
<el-table-column label="缓存" prop="is_cache" align="center" width="70">
<template slot-scope="{row}">
<el-tag v-if="row.is_cache == 1" type="success">是</el-tag>
<el-tag v-if="row.is_cache == 0" type="danger">否</el-tag>
</template>
</el-table-column>
<el-table-column label="隐藏" prop="is_hidden" align="center" width="70">
<template slot-scope="{row}">
<el-tag v-if="row.is_hidden == 1" type="success">是</el-tag>
<el-tag v-if="row.is_hidden == 0" type="danger">否</el-tag>
</template>
</el-table-column>
<el-table-column label="显示" prop="always_show" align="center" width="70">
<template slot-scope="{row}">
<el-tag v-if="row.always_show == 1" type="success">是</el-tag>
<el-tag v-if="row.always_show == 0" type="danger">否</el-tag>
</template>
</el-table-column>
<el-table-column label="图标" prop="is_icon" align="center" width="70">
<template slot-scope="{row}">
<el-tag>{{ row.is_icon === 1 ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="菜单" prop="is_menu" align="center" width="70">
<template slot-scope="{row}">
<el-tag>{{ row.is_menu === 1 ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" prop="status" align="center" width="70">
<template slot-scope="{row}">
<el-tag>{{ row.status === 1 ? '启用' : '禁用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" prop="action" align="center" width="210">
<template slot-scope="{row}">
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexDetails')" size="mini" @click="handleDetails(row.id)" type="info">详情</el-button>
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexEdit')" size="mini" @click="handleEdit(row.id)" type="primary">编辑</el-button>
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexDelete')" size="mini" @click="handleDelete(row.id)" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改路由菜单 -->
<el-dialog :visible.sync="dialogVisible" :title="resTemp.id === 0 ? '添加' : '编辑'" :close-on-click-modal="false" :close-on-press-escape="false">
<el-form ref="resForm" :rules="formRules" :model="resTemp" label-position="right" label-width="100px">
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="父级菜单" prop="pid">
<el-cascader
v-model="resTemp.pid"
:options="options"
:show-all-levels="false"
change-on-select
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否为菜单" prop="is_menu">
<el-switch v-model="resTemp.is_menu" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="唯一标识" prop="menuname">
<el-input v-model="resTemp.menuname" style="width:300px" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item v-if="resTemp.is_menu === 1" label="菜单名称" prop="title">
<el-input v-model="resTemp.title" style="width:300px" />
</el-form-item>
<el-form-item v-if="resTemp.is_menu === 0" label="按钮名称" prop="title">
<el-input v-model="resTemp.title" style="width:300px" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="路由地址" prop="path">
<el-input v-model="resTemp.path" style="width:300px" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="组件路径" prop="component">
<el-input v-model="resTemp.component" style="width:300px" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item v-if="resTemp.pid === '' || resTemp.pid[0] === 0" label="重定向" prop="redirect">
<el-input v-model="resTemp.redirect" style="width:300px" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="菜单图标" prop="icon">
<el-popover
placement="bottom-start"
width="460"
trigger="click"
>
<IconPicker ref="iconPicker" @selected="selected" @show="$refs['iconPicker'].reset()" />
<el-input slot="reference" v-model="resTemp.icon" placeholder="点击选择图标" readonly style="width:300px">
<svg-icon v-if="resTemp.icon" slot="prefix" :icon-class="resTemp.icon" class="el-input__icon" style="height: 32px;width: 16px;" />
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
</el-popover>
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="排序" prop="sort">
<el-input-number v-model="resTemp.sort" size="small" :min="0" :max="99" label="描述文字" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="缓存" prop="is_cache">
<el-switch v-model="resTemp.is_cache" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="隐藏" prop="is_hidden">
<el-switch v-model="resTemp.is_hidden" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="总是显示" prop="always_show">
<el-switch v-model="resTemp.always_show" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="是否显示图标" prop="is_icon">
<el-switch v-model="resTemp.is_icon" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 0" :span="24">
<el-form-item label="按钮类型" prop="button_type">
<el-radio-group v-model="resTemp.button_type">
<el-radio label="0">无效</el-radio>
<el-radio label="1">列表</el-radio>
<el-radio label="2">所有</el-radio>
<el-radio label="3">添加</el-radio>
<el-radio label="4">编辑</el-radio>
<el-radio label="5">保存</el-radio>
<el-radio label="6">删除</el-radio>
<el-radio label="7">详情</el-radio>
<el-radio label="8">信息</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible=false">取消</el-button>
<el-button v-if="$store.getters.butts.includes('PermissionMenuIndexSave')" type="primary" @click="saveInfo()">提交</el-button>
</div>
</el-dialog>
<!-- 详情路由菜单 -->
<el-dialog :visible.sync="dialogDetails" title="详情" :close-on-click-modal="false" :close-on-press-escape="false">
<el-form ref="resForm" :rules="formRules" :model="resTemp" label-position="right" label-width="100px">
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="父级菜单" prop="pid">
<el-cascader
v-model="resTemp.pid"
:options="options"
:show-all-levels="false"
change-on-select
disabled
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否为菜单" prop="is_menu">
<el-switch v-model="resTemp.is_menu" :active-value="1" :inactive-value="0" disabled />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="唯一标识" prop="menuname">
<el-input v-model="resTemp.menuname" style="width:300px" disabled />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item v-if="resTemp.is_menu === 1" label="菜单名称" prop="title">
<el-input v-model="resTemp.title" style="width:300px" disabled />
</el-form-item>
<el-form-item v-if="resTemp.is_menu === 0" label="按钮名称" prop="title">
<el-input v-model="resTemp.title" style="width:300px" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="路由地址" prop="path">
<el-input v-model="resTemp.path" style="width:300px" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="组件路径" prop="component">
<el-input v-model="resTemp.component" style="width:300px" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item v-if="resTemp.pid === '' || resTemp.pid[0] === 0" label="重定向" prop="redirect">
<el-input v-model="resTemp.redirect" style="width:300px" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="菜单图标" prop="icon">
<el-popover
placement="bottom-start"
width="460"
trigger="click"
>
<IconPicker ref="iconPicker" @selected="selected" @show="$refs['iconPicker'].reset()" />
<el-input slot="reference" v-model="resTemp.icon" placeholder="点击选择图标" disabled style="width:300px" >
<svg-icon v-if="resTemp.icon" slot="prefix" :icon-class="resTemp.icon" class="el-input__icon" style="height: 32px;width: 16px;" />
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
</el-popover>
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="24">
<el-form-item label="排序" prop="sort">
<el-input-number v-model="resTemp.sort" size="small" :min="0" :max="99" label="描述文字" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="缓存" prop="is_cache">
<el-switch v-model="resTemp.is_cache" :active-value="1" :inactive-value="0" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="隐藏" prop="is_hidden">
<el-switch v-model="resTemp.is_hidden" :active-value="1" :inactive-value="0" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="总是显示" prop="always_show">
<el-switch v-model="resTemp.always_show" :active-value="1" :inactive-value="0" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 1" :span="12">
<el-form-item label="是否显示图标" prop="is_icon">
<el-switch v-model="resTemp.is_icon" :active-value="1" :inactive-value="0" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-switch v-model="resTemp.status" :active-value="1" :inactive-value="0" disabled />
</el-form-item>
</el-col>
<el-col v-if="resTemp.is_menu === 0" :span="24">
<el-form-item label="按钮类型" prop="button_type">
<el-radio-group v-model="resTemp.button_type" disabled>
<el-radio label="1">列表</el-radio>
<el-radio label="2">所有</el-radio>
<el-radio label="3">添加</el-radio>
<el-radio label="4">编辑</el-radio>
<el-radio label="5">保存</el-radio>
<el-radio label="6">删除</el-radio>
<el-radio label="7">详情</el-radio>
<el-radio label="8">信息</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogDetails=false">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { succ, warn, err } from '@/utils/message'
import { getInfo, getAll, saveInfo, deleteInfo, statusInfo } from '@/api/permission/menu'
import IconPicker from '@/components/iconPicker'
export default {
name: 'PermissionMenuIndex', // 名空间
components: { IconPicker },
data() {
const validateButtonType = (rule, value, callback) => {
const myArr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
if (myArr.findIndex(item => item === (value * 1 + 1)) === 0) {
callback(new Error('请选择正确的按钮类型'))
} else {
callback()
}
}
return {
list: [],
options: [],
tableKey: 0,
listLoading: true,
dialogVisible: false,
dialogDetails: false,
resTemp: {
id: 0,
pid: '',
menuname: '',
title: '',
path: '',
component: '',
redirect: '',
icon: '',
sort: 0,
is_cache: 1,
is_hidden: 0,
always_show: 0,
is_icon: 1,
is_menu: 1,
button_type: 0,
status: 1
},
formRules: {
menuname: [{ required: true, message: '请输入唯一标识', trigger: 'blur' }],
title: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
path: [{ required: true, message: '请输入路由地址', trigger: 'blur' }],
component: [{ required: true, message: '请输入组件路径', trigger: 'blur' }],
icon: [{ required: true, message: '请输入菜单图标', trigger: 'blur' }],
redirect: [
{ required: true, message: '请填写重定向', trigger: 'blur' },
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
],
button_type: [
{ required: true, message: '请选择按钮类型', trigger: 'blur' },
{ trigger: 'blur', validator: validateButtonType }
]
}
}
},
created() {
this.getAll()
},
methods: {
// 图标选择器
selected(name) {
this.resTemp.icon = name
},
// 所有
getAll() {
this.listLoading = true
getAll().then(res => {
this.list = res.data
this.options = this.getParent(res.data)
// 延时
setTimeout(() => {
this.listLoading = false
}, 0.5 * 1000)
})
},
// 递归
getParent(arr) {
const roles = []
roles.push({ label: '无(一级菜单)', value: 0 })
arr.forEach(function(val) {
const chil = []
if (val.children !== undefined) {
val.children.forEach(function(v) {
chil.push({ label: v.title, value: v.id })
})
}
if (val.children !== undefined) {
roles.push({ label: val.title, value: val.id, children: chil })
} else {
roles.push({ label: val.title, value: val.id })
}
})
return roles
},
// 重置表单数据 ---添加时候需要使用
resetTemp() {
this.resTemp = {
id: 0,
pid: '',
menuname: '',
title: '',
path: '',
component: '',
redirect: '',
icon: '',
sort: 0,
is_cache: 1,
is_hidden: 0,
always_show: 0,
is_icon: 1,
is_menu: 1,
button_type: 0,
status: 1
}
},
// 添加
handleAdd() {
this.resetTemp()
this.dialogVisible = true
this.$nextTick(() => {
this.$refs['resForm'].clearValidate()
})
},
// 编辑
handleEdit(id) {
getInfo({ id: id }).then(res => {
const row = res.data
this.resTemp = Object.assign({}, row)
if (row.pid === 0) {
this.resTemp.pid = [0]
}
this.resTemp.button_type = row.button_type + ''
this.dialogVisible = true
this.$nextTick(() => {
this.$refs['resForm'].clearValidate()
})
})
},
// 详情
handleDetails(id){
getInfo({ id: id }).then(res => {
const row = res.data
this.resTemp = Object.assign({}, row)
if (row.pid === 0) {
this.resTemp.pid = [0]
}
this.resTemp.button_type = row.button_type + ''
this.dialogDetails = true
this.$nextTick(() => {
this.$refs['resForm'].clearValidate()
})
})
},
// 保存
async saveInfo() {
this.$refs['resForm'].validate((valid) => {
if (valid) {
this.loading = true
saveInfo(this.resTemp).then(res => {
this.loading = false
succ(res.message)
this.getAll()
this.dialogVisible = false
})
} else {
return false
}
})
},
// 删除
handleDelete(id) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
center: true
}).then(async() => {
const ids = []
if (id > 0) { // 单个删除
ids.push(id)
} else { // 批量删除
const select = this.$refs.resTable.selection
if (select.length === 0) {
warn('批量删除必须选择指定产品')
return false
}
// 组合数据
select.forEach(item => {
ids.push(item.id)
})
}
// 删除
deleteInfo({ id: ids }).then(res => {
this.getAll()// 更新列表
succ(res.message)// 提示结果
})
}).catch(err => {
err(err.message)
return false
})
},
// 启禁用
handleStatus(status){
let statusText = status == 1 ? '启用' : '禁用';
this.$confirm('此操作将永久'+ statusText +'该产品, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
center: true
}).then(async() => {
const ids = []
const select = this.$refs.resTable.selection
if (select.length === 0) {
warn('批量'+ statusText +'必须选择指定产品')
return false
}
// 组合数据
select.forEach(item => {
ids.push(item.id)
})
// 删除
statusInfo({ id: ids, status:status }).then(res => {
this.getAll()// 更新列表
succ(res.message)// 提示结果
})
}).catch(err => {
err(err.message)
return false
})
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
padding:10px;
::v-deep {
.el-dialog {
.el-dialog__header {
border-bottom: 1px solid #ebebeb;
}
.el-dialog__footer {
border-top: 1px solid #ebebeb;
}
}
}
}
</style>
二、添加ajax请求
在根目录下src文件夹下api文件夹下permission文件夹下menu.js,代码如下
import request from '@/utils/request'
// 所有
export function getAll() {
return request({
url: '/permission/menu/get_all',
method: 'post'
})
}
// 获取
export function getInfo(data) {
return request({
url: '/permission/menu/get_info',
method: 'post',
data
})
}
// 保存
export function saveInfo(data) {
return request({
url: '/permission/menu/save_info',
method: 'post',
data
})
}
// 删除
export function deleteInfo(data) {
return request({
url: '/permission/menu/delete_info',
method: 'post',
data
})
}
// 启禁用
export function statusInfo(data) {
return request({
url: '/permission/menu/status_info',
method: 'post',
data
})
}
三、添加图标文件
在根目录下src文件夹下components文件夹下创建文件夹iconPicker,在iconPicker文件夹下新建index.vue,代码如下
<template>
<div class="icon-picker">
<el-input v-model="name" class="icon-search" clearable placeholder="输入图标名称" @clear="filterIcons"
@input.native="filterIcons">
<i slot="suffix" class="el-icon-search el-input__icon"/>
</el-input>
<div class="icon-list">
<el-row>
<el-col v-for="(item, index) in iconList" :key="index" class="svg-box">
<el-tooltip effect="dark" :content="item" placement="top-start">
<svg-icon :icon-class="item" @click="iconPicker(item)"/>
</el-tooltip>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import svgIcons from '../../icons/svg-icon'
export default {
name: 'IconPicker',
data() {
return {
name: '',
iconList: svgIcons
}
},
methods: {
filterIcons() {
if (this.name) {
this.iconList = this.iconList.filter(item => item.includes(this.name))
} else {
this.iconList = svgIcons
}
},
iconPicker(name) {
this.$emit('selected', name)
document.body.click()
},
reset() {
this.name = ''
this.iconList = svgIcons
}
}
}
</script>
<style lang="scss" scoped>
.icon-picker {
width: 100%;
padding: 5px 0 5px 0;
.icon-search {
position: relative;
}
.icon-list {
height: 230px;
overflow-y: auto;
.svg-box {
height: 40px;
width: 40px;
padding: 7px;
border: 1px solid #ddd;
border-radius: 5px;
margin: 6px;
.svg-icon {
width: 24px;
height: 24px;
}
}
.svg-box :hover {
color: #409EFF;
}
}
}
</style>
在根目录下src文件夹下icons文件夹下新建js文件并命名为svg-icon.js,代码如下
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const svgIcons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default svgIcons