feat(role): 角色模块的增删改查

This commit is contained in:
有来技术
2021-12-20 00:14:03 +08:00
parent 07536a9b59
commit cb70c6845b
11 changed files with 790 additions and 93 deletions

View File

@@ -38,7 +38,7 @@ export function listSelectMenus() {
*/
export function listTreeSelectMenus() {
return request({
url: '/youlai-admin/api/v1/menus/tree-select',
url: '/youlai-admin/api/v1/menus/tree_select',
method: 'get'
})
}

View File

@@ -31,7 +31,7 @@ export function listRoles(queryParams: object) {
*
* @param id
*/
export function getPermDetail(id: number) {
export function getRoleDetail(id: number) {
return request({
url: '/youlai-admin/api/v1/roles/' + id,
method: 'get'
@@ -43,7 +43,7 @@ export function getPermDetail(id: number) {
*
* @param data
*/
export function addPerm(data: object) {
export function addRole(data: object) {
return request({
url: '/youlai-admin/api/v1/roles',
method: 'post',
@@ -57,7 +57,7 @@ export function addPerm(data: object) {
* @param id
* @param data
*/
export function updatePerm(id: number, data: object) {
export function updateRole(id: number, data: object) {
return request({
url: '/youlai-admin/api/v1/roles/' + id,
method: 'put',
@@ -77,3 +77,57 @@ export function deleteRoles(ids: string) {
})
}
/**
* 获取角色的菜单列表
*
* @param roleId
*/
export function listRoleMenuIds(roleId: number) {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/menu_ids',
method: 'get',
})
}
/**
* 修改角色的菜单
*
* @param roleId
* @param menuIds
*/
export function updateRoleMenu(roleId: number, menuIds: Array<Number>) {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/menus',
method: 'put',
data: {menuIds: menuIds}
})
}
/**
* 获取角色的权限列表
*
* @param roleId
*/
export function listRolePerms(roleId: number) {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
method: 'get',
})
}
/**
* 保存角色权限
*
* @param menuId 菜单ID归类权限
* @param roleId
* @param permIds
*/
export function saveRolePerms(menuId: number, roleId: number, permIds: Array<number>) {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
method: 'put',
data: {menuId: menuId, permIds: permIds}
})
}

View File

@@ -7,92 +7,86 @@
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script lang="ts">
import {computed, defineComponent} from "vue";
<script setup>
import {computed,defineProps,defineEmits} from "vue";
import {scrollTo} from '@/utils/scroll-to'
export default defineComponent({
name: 'Pagination',
props: {
total: {
required: true,
type: Number,
default: 0
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
const props=defineProps({
total: {
required: true,
type: Number,
default: 0
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
emits: ['pagination', 'update:page', 'update:limit'],
setup(props: any, ctx) {
const currentPage = computed({
get() {
return props.page
},
set(val) {
ctx.emit('update:page', val)
}
})
const pageSize = computed({
get() {
return props.limit
},
set(val) {
ctx.emit('update:limit', val)
}
})
const handleSizeChange = (val: number) => {
ctx.emit('pagination', {page: currentPage, limit: val})
if (props.autoScroll) {
scrollTo(0, 800)
}
}
const handleCurrentChange = (val: number) => {
ctx.emit('pagination', {page: val, limit: props.pageSizes})
if (props.autoScroll) {
scrollTo(0, 800)
}
}
return {currentPage, pageSize, handleSizeChange, handleCurrentChange}
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
})
const emit = defineEmits();
const currentPage = computed({
get() {
return props.page
},
set(val) {
emit('update:page', val)
}
})
const pageSize = computed({
get() {
return props.limit
},
set(val){
emit('update:limit', val)
}
})
function handleSizeChange(val){
emit('pagination', {page: currentPage, limit: val})
if (props.autoScroll) {
scrollTo(0, 800)
}
}
function handleCurrentChange(val){
emit('pagination', {page: val, limit: props.pageSizes})
if (props.autoScroll) {
scrollTo(0, 800)
}
}
</script>
<style scoped>

View File

@@ -8,8 +8,7 @@
size="mini"
>
<el-form-item>
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="success" :icon="Edit" :disabled="state.single" @click="handleUpdate">修改</el-button>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon='Delete' :disabled="state.multiple" @click="handleDelete">删除</el-button>
</el-form-item>
@@ -71,8 +70,8 @@
<pagination
v-show="state.total>0"
:total="state.total"
:page.sync="state.queryParams.pageNum"
:limit.sync="state.queryParams.pageSize"
v-model:page="state.queryParams.pageNum"
v-model:limit="state.queryParams.pageSize"
@pagination="handleQuery"
/>
@@ -114,8 +113,9 @@
import {listClientsWithPage, detail, update, add, del} from '@/api/system/client'
import {Search, Plus, Edit, Refresh, Delete} from '@element-plus/icons'
import {onMounted, reactive, getCurrentInstance, ref, unref} from 'vue'
import {ElForm, ElMessage, ElMessageBox} from "element-plus";
import {ElForm, ElMessage, ElMessageBox} from "element-plus"
const dataForm = ref(ElForm)
const state = reactive({
loading: true,
// 选中ID数组
@@ -200,7 +200,7 @@ function handleUpdate(row: any) {
})
}
const dataForm = ref(ElForm)
function submitForm() {
const form = unref(dataForm)
form.validate((valid: any) => {
@@ -237,6 +237,7 @@ function resetForm() {
}
function cancel() {
resetForm()
state.dialog.visible = false
}

View File

@@ -65,8 +65,8 @@
<pagination
v-show="state.total>0"
:total="state.total"
:page.sync="state.queryParams.pageNum"
:limit.sync="state.queryParams.pageSize"
v-model:page="state.queryParams.pageNum"
v-model:limit="state.queryParams.pageSize"
@pagination="handleQuery"
/>

View File

@@ -10,6 +10,7 @@
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
<el-form-item>
<el-input
v-model="state.queryParams.name"
@@ -169,7 +170,7 @@
import {listTableMenus, getMenuDetail, listTreeSelectMenus, addMenu, deleteMenus, updateMenu} from "@/api/system/menu";
import {Search, Plus, Edit, Refresh, Delete} from '@element-plus/icons'
import {ElForm, ElMessage, ElMessageBox} from "element-plus";
import {defineEmits, reactive, ref, unref, onMounted, watch, getCurrentInstance, computed} from "vue";
import {defineEmits, reactive, ref, unref, onMounted} from "vue";
import SvgIcon from '@/components/SvgIcon/index.vue';
import TreeSelect from '@/components/TreeSelect/index.vue';
import IconSelect from '@/components/IconSelect/index.vue';
@@ -230,8 +231,6 @@ function handleQuery() {
state.menuList = data
state.total = total
state.loading = false
}).catch(() => {
state.loading = false
})
}
@@ -331,7 +330,6 @@ function submitForm() {
})
}
function resetForm() {
state.formData = {
id: undefined,
@@ -346,6 +344,7 @@ function resetForm() {
}
function cancel() {
resetForm()
state.dialog.visible = false
}
@@ -375,7 +374,6 @@ function selected(name: string) {
showChooseIcon.value = false;
}
onMounted(() => {
handleQuery()
})

View File

@@ -15,7 +15,7 @@
<template #header>
<svg-icon color="#333" icon-class="perm"/>
<span style="margin:0 5px;">权限列表</span>
<el-tag type="success" v-if=" state.menuId">{{ state.menuName }}</el-tag>
<el-tag type="success" v-if="state.menuId">{{ state.menuName }}</el-tag>
<el-tag type="warning" v-else size="small">请点击左侧菜单列表选择</el-tag>
</template>
<perm-table :menuId="state.menuId" :menuName="state.menuName"/>
@@ -37,7 +37,7 @@ const state = reactive({
menuName: ''
})
const handleMenuClick = (menuRow: any) => {
function handleMenuClick (menuRow: any){
if (menuRow) {
state.menuId = menuRow.id
state.menuName = menuRow.name

View File

@@ -0,0 +1,92 @@
<template>
<div style="width: 100%;">
<el-form size="mini" >
<el-form-item>
<el-row>
<el-col :span="12">
<el-button type="success" plain :icon="Switch" >展开/折叠</el-button>
</el-col>
<el-col :span="12" style="text-align: right">
<el-button type="primary" :icon="Check" @click="handleQuery">提交</el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
<el-tree
ref="menuRef"
:default-expanded-keys="state.expandedKeys"
:default-expand-all="true"
:data="state.menuOptions"
show-checkbox
node-key="id"
empty-text="加载菜单中..."
:check-strictly="state.checkStrictly"
highlight-current
@node-click="handleNodeClick"
/>
</div>
</template>
<script setup lang="ts">
import {listTreeSelectMenus} from "@/api/system/menu";
import {listRoleMenuIds, updateRoleMenu} from "@/api/system/role"
import {defineEmits, defineProps,onMounted, reactive, ref, watch} from "vue"
import {ElTree, ElMessage, ElMessageBox} from "element-plus"
import {Switch,Check} from '@element-plus/icons'
const emit = defineEmits(['menuClick'])
const props = defineProps({
role: {
type: Object,
default: {}
}
})
const menuRef = ref(ElTree) // 属性名必须和元素的ref属性值一致
watch(() => props.role.id as any, (newVal, oldVal) => {
const roleId = props.role.id
if (roleId) {
state.checkStrictly = true
listRoleMenuIds(roleId).then(response => {
const checkedMenuIds = response.data
console.log('选中的菜单',checkedMenuIds)
menuRef.value.setCheckedKeys(checkedMenuIds)
state.checkStrictly = false
})
}
})
const state = reactive({
expandedKeys: [], // 展开的节点
menuOptions: [],
checkStrictly: false
})
/**
* 加载菜单树
*/
async function loadTreeSelectMenuOptions() {
await listTreeSelectMenus().then(response => {
state.menuOptions = response.data
})
}
function handleNodeClick(node: any) {
emit('menuClick', node)
}
onMounted(() => {
loadTreeSelectMenuOptions()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,187 @@
<template>
<div class="perm-container">
<el-card class="box-card" shadow="always">
<div class="clearfix" slot="header">
<b>
<svg-icon icon-class="menu"/>
{{ menu && menu.label ? "【" + menu.label + "】" : "" }}权限分配
</b>
</div>
<el-row style="margin-bottom: 10px">
<el-col :span="18">
<el-tag v-if="role" type="primary">{{ role.name }}</el-tag>
<el-tag v-if="menu" type="success" style="margin-left: 5px">{{
menu.label
}}
</el-tag>
<el-tag v-if="!role" type="info" style="margin-left: 5px"
><i class="el-icon-info"> </i> 请选择角色
</el-tag>
<el-tag v-if="!menu" type="info" style="margin-left: 5px"
><i class="el-icon-info"> </i> 请选择菜单
</el-tag>
</el-col>
<el-col :span="6" style="text-align: right">
<el-button
type="primary"
:disabled="isRoot"
icon="el-icon-check"
size="mini"
@click="handleSubmit"
>提交
</el-button>
</el-col>
</el-row>
<div v-if="permissionList.length > 0">
<el-checkbox
:indeterminate="isIndeterminate"
v-model="checkAll"
@change="handleCheckAllChange"
style="margin-top: 20px"
>全选
</el-checkbox>
<el-row>
<el-col
:span="8"
v-for="permission in permissionList"
style="margin-top: 20px"
>
<el-checkbox
border
v-model="permission.checked"
:label="permission.id"
:key="permission.id"
@change="handleCheckChange"
size="mini"
>
{{ permission.name }}
</el-checkbox>
</el-col>
</el-row>
</div>
<div style="text-align: center" v-else>
<el-empty :description=" !role? '请选择角色': !menu? '请选择菜单': '暂无数据,您可在【菜单管理】配置权限数据'"></el-empty>
</div>
</el-card>
</div>
</template>
<script>
import {getPermissionList} from "@/api/system/permission";
import {listRolePermission, updateRolePermission} from "@/api/system/role";
export default {
name: "Permission",
props: ["type"],
data() {
return {
loading: false,
ids: [],
initialCheckedPermissionIds: [],
menu: undefined,
role: undefined,
isIndeterminate: true,
checkAll: false,
permissionList: [],
isRoot: false,
};
},
methods: {
handleQuery() {
this.loading = true;
const menuId = this.menu.value;
getPermissionList({
menuId: menuId
}).then((response) => {
const {data} = response;
if (this.role.code == this.ROOT_ROLE_CODE) {
// 如果是超级管理员默认勾选全部且不可编辑
this.isRoot = true
this.checkAll = true
this.isIndeterminate = false
data.map((item) => this.$set(item, "checked", true))
this.permissionList = data
this.loading = false
} else {
this.isRoot = false;
listRolePermission(this.role.id, {menuId: menuId}).then((res) => {
this.initialCheckedPermissionIds = res.data;
let checkAll = true
data.map((item) => {
if (this.initialCheckedPermissionIds.includes(item.id)) {
item.checked = true;
} else {
checkAll = false
}
});
this.checkAll = checkAll
if (checkAll) {
this.isIndeterminate = false
}
this.permissionList = data;
this.loading = false;
});
}
});
},
menuClick(menu, role) {
this.role = role;
this.menu = menu;
if (role && menu) {
this.handleQuery();
} else {
this.permissionList = [];
this.initialCheckedPermissionIds = [];
}
},
handleSubmit: function () {
const checkedPermissionIds = this.permissionList
.filter((item) => item.checked)
.map((item) => item.id);
// 判断选中权限是否变动
if (
this.initialCheckedPermissionIds.length ==
checkedPermissionIds.length &&
this.initialCheckedPermissionIds.sort().every(function (v, i) {
return v == checkedPermissionIds[i];
})
) {
this.$message.warning("数据未变动");
return;
}
updateRolePermission(
this.menu.value,
this.role.id,
checkedPermissionIds
).then((response) => {
this.$message.success("提交成功");
});
},
handleCheckAllChange(checked) {
if (checked) {
this.permissionList.map((item) => (item.checked = true));
} else {
// 全不选
this.permissionList.map((item) => (item.checked = false));
}
this.isIndeterminate = false;
},
handleCheckChange(item, val) {
const checkedCount = this.permissionList.filter(
(item) => item.checked
).length;
this.checkAll = checkedCount === this.permissionList.length;
this.isIndeterminate =
checkedCount > 0 && checkedCount < this.permissionList.length;
},
},
};
</script>
<style scoped>
.perm-container {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,270 @@
<template>
<div>
<!-- 搜索表单 -->
<el-form
ref="queryForm"
:model="state.queryParams"
size="mini"
:inline="true"
>
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon='Delete' :disabled="state.multiple" @click="handleDelete">删除</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input
v-model="state.queryParams.name"
placeholder="角色名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table
ref="roleTable"
v-loading="state.loading"
:data="state.pageList"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
border
highlight-current-row
size="mini"
>
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="角色名称" prop="name"/>
<el-table-column label="角色编码" prop="code"/>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button
type="primary"
:icon="Edit"
size="mini"
circle
plain
@click.stop="handleUpdate(scope.row)"
/>
<el-button
type="danger"
:icon="Delete"
size="mini"
circle
plain
@click.stop="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-show="state.total>0"
:total="state.total"
v-model:page="state.queryParams.pageNum"
v-model:limit="state.queryParams.pageSize"
@pagination="handleQuery"
/>
<!-- 表单弹窗 -->
<el-dialog
:title="state.dialog.title"
v-model="state.dialog.visible"
width="450px"
>
<el-form
ref="dataForm"
:model="state.formData"
:rules="state.rules"
label-width="100px"
>
<el-form-item label="角色名称" prop="name">
<el-input v-model="state.formData.name" placeholder="请输入角色名称"/>
</el-form-item>
<el-form-item label="角色编码" prop="code">
<el-input v-model="state.formData.code" placeholder="请输入角色编码"/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="state.formData.sort" controls-position="right" :min="0" style="width: 100px"/>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="state.formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {listRolesWithPage, updateRole, getRoleDetail, addRole, deleteRoles} from '@/api/system/role'
import {defineEmits, defineProps, onMounted, reactive, ref, unref} from "vue"
import {add, del, detail, update} from "@api/system/client";
import {ElForm, ElMessage, ElMessageBox} from "element-plus";
import {Search, Plus, Edit, Refresh, Delete} from '@element-plus/icons'
const emit = defineEmits(['roleClick'])
const dataForm = ref() // 属性名必须和元素的ref属性值一致
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
name: undefined
},
pageList: [],
total: 0,
dialog: {
title: '',
visible: false
},
formData: {
id: undefined,
parentId: 0,
name: undefined,
sort: 1,
status: 1
},
rules: {
name: [
{required: true, message: '请输入角色名称', trigger: 'blur'}
],
code: [
{required: true, message: '请输入角色编码', trigger: 'blur'}
]
}
})
function handleQuery() {
state.loading = true
listRolesWithPage(state.queryParams).then(response => {
const {data, total} = response as any
state.pageList = data
state.total = total
state.loading = false
})
}
function resetQuery() {
state.queryParams = {
pageNum: 1,
pageSize: 10,
name: undefined
}
handleQuery()
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.id)
state.single = selection.length !== 1
state.multiple = !selection.length
}
function handleRowClick(row: any) {
emit('roleClick', row)
}
function handleAdd() {
resetForm()
state.dialog = {
title: '添加角色',
visible: true,
}
}
function handleUpdate(row: any) {
resetForm()
state.dialog = {
title: '修改角色',
visible: true,
}
const roleId = row.id || state.ids
getRoleDetail(roleId).then(response => {
state.formData = response.data
})
}
function submitForm() {
const form = unref(dataForm)
form.validate((valid: any) => {
if (valid) {
if (state.formData.id) {
updateRole(state.formData.id as any, state.formData).then(response => {
ElMessage.success('修改成功')
state.dialog.visible = false
handleQuery()
})
} else {
addRole(state.formData).then(response => {
ElMessage.success('新增成功')
state.dialog.visible = false
handleQuery()
})
}
}
})
}
function resetForm() {
state.formData = {
id: undefined,
parentId: 0,
name: undefined,
sort: 1,
status: 1
}
}
function cancel() {
resetForm()
state.dialog.visible = false
}
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',')
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteRoles(ids).then(() => {
ElMessage.success('删除成功')
handleQuery()
})
}).catch(() =>
ElMessage.info('已取消删除')
)
}
onMounted(() => {
handleQuery()
})
</script>

View File

@@ -0,0 +1,101 @@
<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="10" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon color="#333" icon-class="menu"/>
角色列表
</template>
<role ref="role" @roleClick="handleRoleClick"/>
</el-card>
</el-col>
<el-col :span="6" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon color="#333" icon-class="menu"/>
<span style="margin:0 5px;">菜单分配</span>
<el-tag type="success" v-if="state.role.id" size="small">{{ state.role.name }}</el-tag>
<el-tag type="warning" v-else size="small">请选择角色</el-tag>
</template>
<menus ref="menu" @menuClick="handleMenuClick" :role="state.role"/>
</el-card>
</el-col>
<el-col :span="8" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon color="#333" icon-class="menu"/>
<span style="margin:0 5px;">权限分配</span>
<el-tag type="success" v-if="state.role.id" size="small">{{ state.role.name }}</el-tag>
<el-tag type="warning" v-else size="small"> 请选择角色</el-tag>
<el-tag type="success" v-if="state.role.id" size="small">{{ state.role.name }}</el-tag>
<el-tag type="warning" v-else size="small"> 请选择菜单</el-tag>
</template>
<!--<perm ref="perm" :menu="state.menu" :role="state.role"/>-->
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import Role from './components/Role.vue'
import Menus from './components/Menu.vue'
import {reactive} from "vue";
import {ElMessage} from "element-plus";
import SvgIcon from '@/components/SvgIcon/index.vue';
const state = reactive({
role: {
id: undefined,
name: '',
},
menu: {
id: undefined,
name: ''
}
})
function handleRoleClick(roleRow: any) {
if (roleRow) {
state.role = {
id: roleRow.id,
name: roleRow.name
}
} else {
state.role = {
id: undefined,
name: ''
}
}
}
function handleMenuClick(menuRow: any) {
if (!state.role.id) {
ElMessage.warning('请选择角色')
return false
}
if (menuRow) {
state.menu = {
id: menuRow.id,
name: menuRow.name
}
} else {
state.menu = {
id: undefined,
name: ''
}
}
}
</script>
<style scoped>
</style>