feat: 角色管理

This commit is contained in:
zhangyuanpeng
2024-11-25 17:08:39 +08:00
parent 32f5537af8
commit e240f4acc8
5 changed files with 521 additions and 0 deletions

76
src/api/system/menu.ts Normal file
View File

@@ -0,0 +1,76 @@
import request from "@/utils/request";
// 菜单基础URL
const MENU_BASE_URL = "/api/v1/menus";
const MenuAPI = {
/**
* 获取当前用户的路由列表
* <p/>
* 无需传入角色后端解析token获取角色自行判断是否拥有路由的权限
*
* @returns 路由列表
*/
getRoutes() {
return request<RouteVO[]>({
url: `${MENU_BASE_URL}/routes`,
method: "GET",
});
},
/**
* 获取菜单下拉数据源
*
* @returns 菜单下拉数据源
*/
getOptions(onlyParent?: boolean) {
return request<OptionType[]>({
url: `${MENU_BASE_URL}/options`,
method: "GET",
data: { onlyParent: onlyParent },
});
},
};
export default MenuAPI;
/** RouteVO路由对象 */
export interface RouteVO {
/** 子路由列表 */
children: RouteVO[];
/** 组件路径 */
component?: string;
/** 路由属性 */
meta?: Meta;
/** 路由名称 */
name?: string;
/** 路由路径 */
path?: string;
/** 跳转链接 */
redirect?: string;
}
/** Meta路由属性 */
export interface Meta {
/** 【目录】只有一个子路由是否始终显示 */
alwaysShow?: boolean;
/** 是否隐藏(true-是 false-否) */
hidden?: boolean;
/** ICON */
icon?: string;
/** 【菜单】是否开启页面缓存 */
keepAlive?: boolean;
/** 路由title */
title?: string;
}
/**
* 组件数据源
*/
interface OptionType {
/** 值 */
value: string | number;
/** 文本 */
label: string;
/** 子列表 */
children?: OptionType[];
}

View File

@@ -67,6 +67,18 @@
"style": {
"navigationBarTitleText": "通知详情"
}
},
{
"path": "pages/work/role/index",
"style": {
"navigationBarTitleText": "角色管理"
}
},
{
"path": "pages/work/role/assign-perm",
"style": {
"navigationBarTitleText": "角色分配权限"
}
}
],
"globalStyle": {

View File

@@ -36,6 +36,7 @@ const gridList = reactive([
{
icon: "/static/icons/role.png",
title: "角色管理",
url: "/pages/work/role/index",
prem: "sys:role:query",
},
{

View File

@@ -0,0 +1,113 @@
<template>
<view>
<view class="p-l-8 p-b-20">
<DaTree
ref="DaTreeRef"
:data="menuPermOptions"
:defaultExpandAll="true"
:showCheckbox="true"
/>
</view>
<wd-fab v-model:active="showFab" type="primary" position="right-top" direction="bottom">
<wd-button custom-class="custom-button" type="primary" @click="doCheckedTree(rootKeys, true)">
</wd-button>
<wd-button custom-class="custom-button" type="error" @click="doCheckedTree(rootKeys, false)">
全不选
</wd-button>
<wd-button custom-class="custom-button" type="success" @click="doExpandTree('all', true)">
全展开
</wd-button>
<wd-button custom-class="custom-button" type="warning" @click="doExpandTree('all', false)">
全收起
</wd-button>
</wd-fab>
<!-- 底部按钮 -->
<view class="footer-buttons" style="">
<wd-button size="medium" type="primary" block @click="handleAssignPermSubmit">
</wd-button>
</view>
</view>
</template>
<script lang="ts" setup>
import RoleAPI from "@/api/system/role";
import MenuAPI from "@/api/system/menu";
import DaTree from "@/components/da-tree/index.vue";
import { useMessage } from "wot-design-uni";
const message = useMessage();
let roleId = ref<number>(null); // 操作的角色ID
const menuPermOptions = ref<OptionType[]>([]); // 所有菜单
let rootKeys = ref<number[]>([]); // 根节点数组
const DaTreeRef = ref(); // 树引用
const showFab = ref(); // 悬浮按钮是否展开
/**
* 分配菜单权限页面数据初始化
*/
async function initAssignPerm() {
// 获取所有的菜单
menuPermOptions.value = await MenuAPI.getOptions(false);
console.log("menuPermOptions.value", menuPermOptions.value);
rootKeys.value = menuPermOptions.value.map((item) => item.value);
// 回显角色已拥有的菜单
RoleAPI.getRoleMenuIds(roleId.value).then((data) => {
const checkedMenuIds = data;
console.log("checkedMenuIds=", checkedMenuIds);
if (checkedMenuIds && checkedMenuIds.length > 0) {
DaTreeRef.value?.setCheckedKeys(checkedMenuIds, true);
}
});
}
// 展开/收起树节点
function doExpandTree(keys, expand) {
DaTreeRef.value?.setExpandedKeys(keys, expand);
showFab.value = false;
}
// 选中/取消树节点
function doCheckedTree(keys, checked) {
DaTreeRef.value?.setCheckedKeys(keys, checked);
showFab.value = false;
}
// 分配菜单权限提交
function handleAssignPermSubmit() {
if (roleId.value) {
const checkedMenuIds: number[] = DaTreeRef.value?.getCheckedKeys();
RoleAPI.updateRoleMenus(roleId.value, JSON.stringify(checkedMenuIds)).then(() => {
uni.showToast({ title: "分配权限成功", icon: "success" });
uni.navigateBack({ delta: 1 });
});
}
}
onLoad((options: any) => {
if (options && options.id) {
roleId.value = options.id;
}
initAssignPerm();
});
</script>
<style lang="scss" scoped>
.footer-buttons {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
padding: 20rpx;
background-color: #fff;
}
:deep(.custom-button) {
box-sizing: border-box;
width: 64px !important;
min-width: auto !important;
height: 32px !important;
margin: 8rpx;
border-radius: 16px !important;
}
</style>

View File

@@ -0,0 +1,319 @@
<template>
<view>
<!-- 筛选 -->
<wd-drop-menu>
<wd-drop-menu-item ref="filterDropMenu" icon="filter" icon-size="18px" title="筛选">
<view>
<wd-input
v-model="queryParams.keywords"
label="关键字"
type="text"
placeholder="请输入关键字"
/>
<view class="flex-between py-2">
<wd-button class="w-20%" type="info" @click="hendleResetQuery">重置</wd-button>
<wd-button class="w-70%" @click="handleQuery">确定</wd-button>
</view>
</view>
</wd-drop-menu-item>
</wd-drop-menu>
<!-- 卡片列表 -->
<view class="list-container">
<wd-card v-for="item in dataList" class="role-card">
<template #title>
<view class="flex-between">
<view class="flex-center">
<view class="ml-2">
<view class="font-bold">
{{ item.name }}
</view>
</view>
</view>
<view>
<wd-tag v-if="item.status === 1" type="success" plain>正常</wd-tag>
<wd-tag v-else-if="item.status === 0" plain>停用</wd-tag>
</view>
</view>
</template>
<wd-cell-group>
<wd-cell title="编码" title-width="150rpx" :value="item.code" />
<wd-cell title="排序号" title-width="150rpx" :value="item.sort" />
</wd-cell-group>
<template #footer>
<view class="flex-between">
<view class="text-left">
<wd-text text="创建时间:" size="small" class="font-bold" />
<wd-text :text="item.createTime" size="small" />
</view>
<view class="flex-right">
<wd-button type="icon" icon="more" @click="handleAction(item)" />
</view>
</view>
</template>
</wd-card>
<!-- 加载更多 -->
<wd-loadmore :state="loadMoreState" @reload="queryPageData" />
</view>
<!-- 底部按钮 -->
<wd-fab
position="left-bottom"
:expandable="false"
customStyle="width: 1rem; height: 1rem; line-height: 1rem;z-index:9"
@click="handleOpenDialog(null)"
/>
<wd-popup v-model="dialog.visible" position="bottom" class="yl-popup">
<wd-form ref="roleFormRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-input v-model="formData.name" label="角色名称" prop="name" />
<wd-input v-model="formData.code" label="角色编码" prop="code" />
<wd-select-picker
v-model="formData.dataScope"
type="radio"
label="数据权限"
:columns="dataScopeOptions"
:align-right="true"
prop="dataScope"
/>
<wd-cell title="状态" prop="status">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-cell>
<wd-cell title="排序" prop="sort">
<wd-input-number v-model="formData.sort" />
</wd-cell>
</wd-cell-group>
</wd-form>
<view class="footer">
<wd-button type="primary" block @click="handleSubmit">提交</wd-button>
</view>
</wd-popup>
<wd-message-box />
</view>
</template>
<script lang="ts" setup>
import RoleAPI, { RolePageVO, RolePageQuery, RoleForm } from "@/api/system/role";
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
import { DropMenuItemExpose } from "wot-design-uni/components/wd-drop-menu-item/types";
import { FormRules } from "wot-design-uni/components/wd-form/types";
import { useMessage } from "wot-design-uni";
const message = useMessage();
const loadMoreState = ref<LoadMoreState>("loading");
const total = ref(0);
const queryParams = reactive<RolePageQuery>({
pageNum: 1,
pageSize: 10,
keywords: "",
});
/**
* 搜索栏
*/
const filterDropMenu = ref<DropMenuItemExpose>();
function handleQuery() {
filterDropMenu.value?.close();
queryParams.pageNum = 1;
dataList.value = [];
queryPageData();
}
/**
* 重置查询
*/
const hendleResetQuery = () => {
filterDropMenu.value?.close();
queryParams.keywords = "";
queryParams.pageNum = 1;
dataList.value = [];
queryPageData();
};
// 角色列表数据
const dataList = ref<RolePageVO[]>([]);
/**
* 查询分页数据
*/
function queryPageData() {
loadMoreState.value = "loading";
RoleAPI.getPage(queryParams)
.then((data) => {
dataList.value?.push(...data.list);
total.value = data.total;
})
.catch((e) => {
console.log("系统异常", e);
})
.finally(() => {
loadMoreState.value = "finished";
});
}
/**
* 触底事件
*/
onReachBottom(() => {
if (queryParams.pageNum * queryParams.pageSize < total.value) {
queryParams.pageNum++;
queryPageData();
} else {
loadMoreState.value = "finished";
}
});
// 操作按钮
const handleAction = (item: RolePageVO) => {
const actions = ["删除", "分配权限", "编辑"];
uni.showActionSheet({
itemList: actions,
success: ({ tapIndex }) => {
switch (actions[tapIndex]) {
case "删除":
handleDelete(item.id);
break;
case "分配权限":
handleAssignPerm(item.id);
break;
case "编辑":
handleOpenDialog(item.id);
break;
}
},
});
};
const dialog = reactive({
visible: false,
});
const dataScopeOptions = ref<Record<string, any>[]>([
{ label: "全部数据", value: 0 },
{ label: "部门及子部门数据", value: 1 },
{ label: "本部门数据", value: 2 },
{ label: "本人数据", value: 3 },
]);
const formData = reactive<RoleForm>({});
const rules: FormRules = {
name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
code: [{ required: true, message: "请输入角色编码", trigger: "blur" }],
dataScope: [{ required: true, message: "请选择数据权限", trigger: "blur" }],
status: [{ required: true, message: "请选择状态", trigger: "change" }],
sort: [{ required: true, message: "请输入排序号", trigger: "change" }],
};
const roleFormRef = ref();
/**
* 打开弹窗
*/
async function handleOpenDialog(id?: number) {
dialog.visible = true;
formData.id = null;
formData.name = "";
formData.code = "";
formData.dataScope = 0;
formData.status = 1;
formData.sort = 1;
if (id) {
RoleAPI.getFormData(id).then((data) => {
Object.assign(formData, { ...data });
});
}
}
/**
* 提交保存
*/
function handleSubmit() {
roleFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
if (valid) {
const roleId = formData.id;
if (roleId) {
RoleAPI.update(roleId, formData).then(() => {
uni.showToast({ title: "修改成功", icon: "success" });
dialog.visible = false;
queryParams.pageNum = 1;
handleQuery();
});
} else {
RoleAPI.add(formData).then(() => {
uni.showToast({ title: "添加成功", icon: "success" });
dialog.visible = false;
queryParams.pageNum = 1;
handleQuery();
});
}
}
});
}
/**
* 删除
*
* @param id 用户id
*/
function handleDelete(id: number) {
message
.confirm({
msg: "确认删除角色吗?",
title: "提示",
})
.then(() => {
RoleAPI.deleteByIds(id + "").then(() => {
uni.showToast({ title: "删除成功", icon: "success" });
queryParams.pageNum = 1;
handleQuery();
});
})
.catch(() => {
console.log("点击了取消按钮");
});
}
function handleAssignPerm(id: number) {
uni.navigateTo({
url: "/pages/work/role/assign-perm?id=" + id,
});
}
onLoad(() => {
handleQuery();
});
</script>
<style lang="scss" scoped>
:deep(.wd-drop-menu .wd-drop-menu__item) {
display: flex;
justify-content: flex-end;
padding: 0 50rpx;
}
.list-container {
.role-card {
margin-top: 20rpx;
:deep(.wd-cell__wrapper) {
padding: 4rpx 0;
}
:deep(.wd-cell) {
padding-right: 10rpx;
background: #f8f8f8;
}
}
}
:deep(.wd-fab__trigger) {
width: 80rpx !important;
height: 80rpx !important;
}
.yl-popup {
.footer {
margin: 30rpx 0;
}
}
</style>