新增:菜单管理

This commit is contained in:
jmal 2021-01-11 00:03:45 +08:00
parent b7af6eada9
commit 204ec2ef0e
9 changed files with 626 additions and 106 deletions

53
src/api/menu.js Normal file
View File

@ -0,0 +1,53 @@
import request from '@/utils/request'
const qs = require('qs')
export default {
menuTree(params) {
return request({
url: '/menu/tree',
method: 'get',
params
})
},
// 权限标识列表
authorityList(params) {
return request({
url: '/menu/authorities',
method: 'get',
params
})
},
// 菜单信息
menuInfo(params) {
return request({
url: '/menu/info',
method: 'get',
params
})
},
add(data) {
return request({
url: '/menu/add',
method: 'post',
data
})
},
update(data) {
return request({
url: 'menu/update',
method: 'put',
data
})
},
delete(params) {
return request({
url: '/menu/delete',
method: 'delete',
params,
paramsSerializer: function(params) {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
})
}
}

View File

@ -5,7 +5,7 @@
import { Icon } from 'ant-design-vue'
const IconFont = Icon.createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_2024484_tbuvjy9a4bi.js'
scriptUrl: '//at.alicdn.com/t/font_2024484_85344xmpzhm.js'
})
export default {

View File

@ -0,0 +1,169 @@
<template>
<el-select
ref="treeSelect"
:value="valueTitle"
:clearable="clearable"
filterable
:placeholder="placeholder"
@clear="clearHandle"
:filter-method="filterMethod"
>
<el-option :value="valueTitle" :label="valueTitle" class="options">
<el-tree id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="props"
empty-text="未找到结果"
:node-key="props.value"
highlight-current
default-expand-all
:expand-on-click-node="false"
:default-expanded-keys="defaultExpandedKey"
:filter-node-method="filterNode"
@node-click="handleNodeClick">
</el-tree>
</el-option>
</el-select>
</template>
<script>
export default {
name: "TreeSelect",
props:{
/* 配置项 */
props:{
type: Object,
default:()=>{
return {
value:'id', // ID字段名
label: 'name', // 显示名称
children: 'children' // 子级字段名
}
}
},
/* 选项列表数据(树形结构的对象数组) */
options:{
type: Array,
default: ()=>{ return [] }
},
/* 初始值 */
value:{
type: String,
default: ''
},
/* 可清空选项 */
clearable:{
type:Boolean,
default:()=>{ return true }
},
/* 自动收起 */
accordion:{
type:Boolean,
default:()=>{ return false }
},
placeholder:{
type:String,
default:()=>{return "检索关键字"}
}
},
data() {
return {
valueId:this.value, // 初始值
valueTitle:'',
defaultExpandedKey:[]
}
},
mounted(){
this.initHandle()
},
methods: {
// 初始化值
initHandle(){
if(this.valueId){
this.valueTitle = this.$refs.selectTree.getNode(this.valueId).data[this.props.label] // 初始化显示
this.$refs.selectTree.setCurrentKey(this.valueId) // 设置默认选中
this.defaultExpandedKey = [this.valueId] // 设置默认展开
}
this.initScroll()
},
// 初始化滚动条
initScroll(){
this.$nextTick(()=>{
let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;'
scrollBar.forEach(ele => ele.style.width = 0)
})
},
// 切换选项
handleNodeClick(node){
this.valueTitle = node[this.props.label]
this.valueId = node[this.props.value]
this.$emit('getValue',this.valueId)
this.defaultExpandedKey = []
this.$refs.treeSelect.blur()
this.$emit('input', this.valueId)
},
// 清除选中
clearHandle(){
this.valueTitle = ''
this.valueId = null
this.defaultExpandedKey = []
this.clearSelected()
this.$emit('getValue',null)
},
/* 清空选中样式 */
clearSelected(){
let allNode = document.querySelectorAll('#tree-option .el-tree-node')
allNode.forEach((element)=>element.classList.remove('is-current'))
},
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
filterMethod(query){
this.$refs.selectTree.filter(query);
}
},
watch: {
value(){
this.valueId = this.value
this.initHandle()
}
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item{
height: auto;
max-height: 274px;
padding: 0;
overflow: hidden;
overflow-y: auto;
}
.el-select-dropdown__item.selected{
font-weight: normal;
}
ul li >>>.el-tree .el-tree-node__content{
height:auto;
padding: 0 20px;
}
.el-tree-node__label{
font-weight: normal;
}
.el-tree >>>.is-current .el-tree-node__label{
color: #409EFF;
font-weight: 700;
}
.el-tree >>>.is-current .el-tree-node__children .el-tree-node__label{
color:#606266;
font-weight: normal;
}
.selectInput{
padding: 0 5px;
box-sizing: border-box;
}
</style>

View File

@ -3,15 +3,18 @@
<div class="table-temp">
<el-table
:data="tableData"
row-key="id"
border
size="medium"
fit
highlight-current-row
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
v-loading="loading"
@selection-change="handleSelectionChange"
@sort-change="sortChange"
>
<el-table-column type="selection" width="55" align="center"></el-table-column>
<el-table-column v-if="hasSelection" type="selection" width="55" align="center"></el-table-column>
<el-table-column type="index" label="" align="center" fixed></el-table-column>
<!-- prop: 字段名name, label: 展示的名称, fixed: 是否需要固定(left, right), minWidth: 设置列的最小宽度(不传默认值), active: 是否有操作列
active.name: 操作列字段名称, active.clickFun: 操作列点击事件, formatData: 格式化内容-->
@ -22,26 +25,27 @@
:prop="item.prop"
:label="item.label"
:fixed="item.fixed"
:width="item.width"
:min-widitem="item.minWidth"
align="center"
:align="item.align || 'center'"
:sort-orders="['ascending', 'descending']"
:sortable="item.sortable"
:show-overflow-tooltip="true"
tooltip-effect="dark"
>
<template slot-scope="scope">
<div v-if="item.active">
<el-button
v-for="(o, key) in item.active"
:style="{color: o.color}"
:key="key"
@click="o.handle(scope.row)"
:type="o.type || 'text'"
:icon="o.icon"
size="medium"
>{{o.name}}</el-button>
</div>
<div v-else>
<template slot-scope="scope">
{{ !item.formatData && !item.active ? scope.row[item.prop] : ''}}
<div v-if="item.active">
<el-button
v-for="(o, key) in item.active"
:style="{color: o.color}"
:key="key"
@click="o.handle(scope.row)"
:type="o.type || 'text'"
:icon="o.icon"
size="medium"
>{{o.name}}</el-button>
</div>
<div v-if="item.formatData">
<div v-if="item.tag">
<el-tag size="mini" v-for="tagname in item.formatData(scope.row[item.prop])" :key="tagname">{{tagname}}</el-tag>
@ -50,11 +54,7 @@
<span else>{{ item.formatData(scope.row[item.prop]) || scope.row[item.prop]}}</span>
</div>
</div>
<div v-else>
<span>{{ scope.row[item.prop] }}</span>
</div>
</div>
</template>
</template>
</el-table-column>
</el-table>
<div class="pagination">
@ -91,6 +91,10 @@ export default {
type: Boolean,
default: false
},
hasSelection: {
type: Boolean,
default: true
},
pagination: {
type: Object,
default: {
@ -110,11 +114,6 @@ export default {
}
};
},
computed: {
formatters(formatData) {
return formatData()
}
},
methods: {
// 多选操作
handleSelectionChange(val) {

View File

@ -200,6 +200,18 @@ export const constantRoutes = [
}
]
},
{
path: '/setting/manager-menus',
component: Layout,
children: [
{
path: '',
name: 'manager-menus',
component: () => import('@/views/setting/sys/menuManager'),
meta: { title: '菜单管理' , icon: 'menu'}
}
]
},
{
path: '/setting/website',
component: Layout,

View File

@ -68,6 +68,7 @@
</div>
<table-list
:tableData="userList"
:loading="loading"
:tableHeader="tableHeader"
:pagination="pagination"
@pageChange="pageChange"
@ -106,6 +107,7 @@ export default {
return {
title: "用户管理",
userList: [],
loading: false,
roleList: [],
dialogVisible: false,
dialogTitle: '',
@ -123,7 +125,7 @@ export default {
pagination: {
pageIndex: 1,
pageSize: 15,
pageTotal: 60
pageTotal: 0
},
// 查询条件
queryCondition: {
@ -148,8 +150,8 @@ export default {
}
}
},
{prop: 'createTime',label: '创建时间',sortable: 'custom'},
{prop: 'quota',label: '配额(G)',sortable: 'custom'},
{prop: 'createTime',label: '创建时间',sortable: 'custom'},
{label: '操作',active: [
{name: '修改', icon: 'el-icon-edit', handle: (row) => this.handleEdit(row.id,row.username)},
{name: '删除', icon: 'el-icon-delete', color: '#ff4d4f', handle: (row) => this.handleDelete([row.id])},
@ -207,6 +209,7 @@ export default {
this.getUserList()
},
getUserList(){
this.loading = true
userList({
page: this.pagination.pageIndex,
pageSize: this.pagination.pageSize,
@ -214,6 +217,7 @@ export default {
}).then(res => {
this.userList = res.data
this.pagination.pageTotal = res.count
this.loading = false
})
},
getRoleList(){

View File

@ -0,0 +1,355 @@
<template>
<div class="container" v-wechat-title="title">
<el-dialog class="dialog-form" width="600px" :title="dialogTitle" :visible.sync="dialogVisible">
<el-form size="medium" ref="managerForm" label-width="90px" :model="form" :rules="rules">
<el-row :gutter="10">
<el-col :sm="12">
<el-form-item label="上级菜单:" prop="parentId">
<tree-select
ref="selectTree"
placeholder="请选择上级菜单"
:props="{value: 'id', label: 'name'}"
:options="dataList"
v-model="form.parentId"
clearable
/>
</el-form-item>
<el-form-item label="菜单名称:" prop="name">
<el-input placeholder="请输入菜单名称" v-model="form.name" />
</el-form-item>
<el-form-item label="菜单图片:" prop="icon">
<el-input placeholder="请输入菜单图片" v-model="form.icon" />
</el-form-item>
<el-form-item label="路由地址:" prop="path">
<el-input placeholder="请输入路由地址" v-model="form.path" />
</el-form-item>
<el-form-item label="组件路径:" prop="component">
<el-input placeholder="请输入组件路径" v-model="form.component" />
</el-form-item>
</el-col>
<el-col :sm="12">
<el-form-item label="权限标识:" prop="authority">
<el-input placeholder="请输入权限标识" v-model="form.authority" />
</el-form-item>
<el-form-item label="排序号:" prop="sortNumber">
<el-input-number v-model="form.sortNumber" controls-position="right" :min="0"></el-input-number>
</el-form-item>
<el-form-item label="菜单类型:" prop="menuType">
<el-radio v-model="form.menuType" :label="0">菜单</el-radio>
<el-radio v-model="form.menuType" :label="1">按钮</el-radio>
</el-form-item>
<el-form-item label="是否隐藏:" prop="hide">
<el-radio v-model="form.hide" :label="false">显示</el-radio>
<el-radio v-model="form.hide" :label="true">影藏</el-radio>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogVisible = false">取 消</el-button>
<el-button size="small" type="primary" :loading="updateLoading" @click.native.prevent="onSave()">保 存</el-button>
</div>
</el-dialog>
<el-card class="box-card table-search-header">
<div slot="header">
<div class="box-card-header">
<el-form size="medium" class="search-form" ref="queryForm" label-width="77px" :model="queryCondition" :rules="rules">
<el-row :gutter="10">
<el-col :sm="12" :md="8">
<el-form-item label="菜单名称:">
<el-input clearable placeholder="请输入" v-model="queryCondition.name" />
</el-form-item>
</el-col>
<el-col :sm="12" :md="8">
<el-form-item label="菜单地址:">
<el-input clearable placeholder="请输入" v-model="queryCondition.path" />
</el-form-item>
</el-col>
<el-col :sm="12" :md="8">
<div class="el-form-actions">
<el-button class="card-btn-icon" size="medium" icon="el-icon-search" type="primary" @click="getMenuTree()">查询</el-button>
<el-button class="card-btn-icon" size="medium" icon="el-icon-plus" type="primary" @click="add()">添加</el-button>
</div>
</el-col>
</el-row>
</el-form>
</div>
</div>
<table-list
:has-selection="false"
:tableData="dataList"
:loading="loading"
:tableHeader="tableHeader"
:pagination="pagination"
@pageChange="pageChange"
@selectFun="selectFun"
@sortChange="sortChange"
></table-list>
</el-card>
</div>
</template>
<script>
import menuApi from '@/api/menu'
import TableList from "@/components/table/TableList";
import TreeSelect from "@/components/select/tree";
export default {
components: {
TreeSelect,
TableList
},
props: {
},
data() {
return {
title: "角色管理",
dataList: [],
loading: false,
dialogVisible: false,
dialogTitle: '',
updateLoading: false,
valid: true,
form: {
name: '',
parentId: '',
authority: '',
path: '',
component: '',
icon: '',
sortNumber: '',
menuType: 0,
hide: false,
},
// 分页信息
pagination: {
pageIndex: 1,
pageSize: 15,
pageTotal: 0
},
// 查询条件
queryCondition: {
name: '',
code: '',
remarks: '',
sortProp: '',
sortOrder: ''
},
tableHeader: [
{prop: 'name', label: '菜单名称', noScope: true, align: 'left'},
{prop: 'path', label: '路由地址'},
{prop: 'component', label: '组件路径'},
{prop: 'authority', label: '权限标识'},
{prop: 'sortNumber', label: '排序', width: 50},
{prop: 'hide', label: '隐藏', width: 50,
formatData: (hide)=> {
if(hide){
return '是'
}else{
return '否'
}
}
},
{prop: 'menuType', label: '类型', width: 75, tag: true,
formatData: (menuType)=> {
if(menuType === 0){
return ['菜单']
}else{
return ['按钮']
}
}
},
{prop: 'createTime', label: '创建时间'},
{label: '操作',active: [
{name: '修改', icon: 'el-icon-edit', handle: (row) => this.handleEdit(row.id)},
{name: '删除', icon: 'el-icon-delete', color: '#ff4d4f', handle: (row) => this.handleDelete([row.id])},
],
},
],
multipleSelection: [],
rules: {
name: [
{ required: true, message: '请输入菜单名称', trigger: 'blur' },
],
sortNumber: [
{ required: true, message: '请输入排序号', trigger: 'blur' },
],
},
editMove: 1,// 1添加,2修改
}
},
computed: {
},
mounted() {
this.getMenuTree()
this.resize()
},
methods: {
resize(){
let clientWidth = document.querySelector(".container").clientWidth
const monbile = clientWidth <= 768;
},
// 组件选择完后把数据传过来
selectFun(data) {
this.multipleSelection = data.backData;
},
//表格组件返回排序对象
sortChange(data) {
let column = data.backData;
this.queryCondition.sortProp = column.prop
this.queryCondition.sortOrder = column.order
this.getRoleList()
},
//分页导航
pageChange(data) {
this.pagination = data.backData;
this.getMenuTree()
},
getMenuTree(){
this.loading = true
menuApi.menuTree({
page: this.pagination.pageIndex,
pageSize: this.pagination.pageSize,
...this.queryCondition
}).then(res => {
this.dataList = res.data
this.pagination.pageTotal = res.count
this.loading = false
})
},
async add() {
this.dialogVisible = true
this.dialogTitle = '添加角色'
this.editMove = 1
await this.$nextTick()
this.$refs.managerForm.resetFields()
this.$refs.selectTree.clearHandle()
},
// 设置formData
setFormData() {
let data = new FormData();
for (let formKey in this.form) {
if(this.form.hasOwnProperty(formKey)){
data.append(formKey,this.form[formKey])
}
}
data.delete("createTime")
return data
},
// 保存
onSave() {
this.$refs.managerForm.validate((valid) => {
this.valid = valid
if (valid) {
this.updateLoading = true
if(this.editMove === 1){
this.addMenu(this.setFormData())
}
if(this.editMove === 2){
this.updateMenu(this.setFormData())
}
} else {
return false;
}
});
},
handleEdit(id) {
this.editMove = 2
this.dialogVisible = true
this.dialogTitle = '修改菜单'
menuApi.menuInfo({menuId: id}).then(res => {
this.form = res.data
})
},
resetPassword() {
this.$confirm('确定重置该用户密码吗, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
resetPass(this.form.id).then(() => {
}).catch(() => {
this.onError()
})
})
},
// 删除选中
handleSelectDelete () {
this.$confirm('确定要删除选中的角色吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let ids = []
this.multipleSelection.forEach(row => {
ids.push(row.id)
})
this.deleteMenu(ids)
})
},
handleDelete(ids) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteMenu(ids)
})
},
deleteMenu(ids){
menuApi.delete({menuIds: ids}).then(() => {
this.onSuccess('删除成功!')
}).catch(() => {
this.onError()
})
},
// 修改用户信息操作
updateMenu(data){
menuApi.update(data).then(() => {
this.onSuccess()
}).catch(() => {
this.onError()
})
},
// 添加角色
addMenu(data){
menuApi.add(data).then(() => {
this.onSuccess()
}).catch(() => {
this.onError()
})
},
onSuccess(message){
this.getMenuTree()
let msg = '保存成功!'
if(message){
msg = message
}
this.updateLoading = false
this.dialogVisible = false
this.$message({
message: msg,
type: 'success',
duration: 1000
})
},
onError() {
this.updateLoading = false
}
}
}
</script>
<style lang="scss" scoped>
@import "src/styles/setting";
/deep/ .el-input {
width: 100%;
}
/deep/ .el-input-number {
width: unset;
}
/deep/ .el-select {
width: 100%;
}
</style>

View File

@ -50,6 +50,7 @@
</div>
<table-list
:tableData="dataList"
:loading="loading"
:tableHeader="tableHeader"
:pagination="pagination"
@pageChange="pageChange"
@ -63,14 +64,11 @@
<script>
import roleApi from '@/api/role'
import CropperDialog from '@/components/Cropper/dialog'
import TableList from "@/components/table/TableList";
export default {
name: 'cusomerManager',
components: {
TableList,
CropperDialog
TableList
},
props: {
},
@ -78,6 +76,7 @@ export default {
return {
title: "角色管理",
dataList: [],
loading: false,
dialogVisible: false,
dialogTitle: '',
updateLoading: false,
@ -91,7 +90,7 @@ export default {
pagination: {
pageIndex: 1,
pageSize: 15,
pageTotal: 60
pageTotal: 0
},
// 查询条件
queryCondition: {
@ -154,6 +153,7 @@ export default {
this.getRoleList()
},
getRoleList(){
this.loading = true
roleApi.roleList({
page: this.pagination.pageIndex,
pageSize: this.pagination.pageSize,
@ -161,6 +161,7 @@ export default {
}).then(res => {
this.dataList = res.data
this.pagination.pageTotal = res.count
this.loading = false
})
},
async add() {

View File

@ -1,73 +0,0 @@
<template>
<div class="app-container" v-wechat-title="title">
<el-tabs type="border-card" :value="tabsDefault" @tab-click="tabClick">
<el-tab-pane
:key="item.name"
v-for="item in tabs"
:label="item.label"
:name="item.name"
>
<span v-if="item.name === 'cusomerInfo'" slot="label"><i class="el-icon-user"></i>{{item.label}}</span>
<div v-if="item.name === 'cusomerInfo'">
<cusomer-info v-if="userInfo" :userInfo="userInfo" @isAdmin="isAdmin"></cusomer-info>
</div>
<span v-if="item.name === 'cusomerManager'" slot="label"><i class="el-icon-user-solid"></i>{{item.label}}</span>
<div v-if="item.name === 'cusomerManager'">
<cusomer-manager></cusomer-manager>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import cusomerInfo from '@/views/setting/sys/cusomerInfo'
import cusomerManager from '@/views/setting/sys/cusomerManager'
import categoryManager from '@/views/setting/articles/categoryManager'
export default {
components: {
cusomerInfo,cusomerManager,categoryManager
},
data() {
return {
title: "设置",
tabs: [
{name: 'cusomerInfo', label:'个人信息'},
],
adminTabs: [
{name: 'cusomerManager', label:'用户管理'}
],
tabsDefault: 'cusomerInfo',
userInfo:{}
}
},
computed: {
},
mounted() {
if(this.$route.query.tab){
this.tabsDefault = this.$route.query.tab
}
},
methods: {
tabClick(tab) {
if(tab.name === 'cusomerManager'){
// 用户管理
}
this.$router.push({path: this.$route.path, query: {tab: tab.name}})
},
isAdmin(isAdmin) {
if(isAdmin){
this.adminTabs.forEach(tab => {
if(!this.tabs.includes(tab)){
this.tabs.push(tab)
}
})
}
}
}
}
</script>
<style lang="scss" scoped>
</style>