review: 分配菜单视图.
This commit is contained in:
@@ -0,0 +1,194 @@
|
|||||||
|
<template>
|
||||||
|
<table class="grant-table" border="1">
|
||||||
|
<thead>
|
||||||
|
<tr class="header-row">
|
||||||
|
<th class="parent-view-header">父页面</th>
|
||||||
|
<th class="child-view-header">子页面</th>
|
||||||
|
<th>功能</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<a-checkbox-group v-model="checkedKeys" style="display: contents">
|
||||||
|
<template v-for="parentMenu in menuData" :key="parentMenu.id">
|
||||||
|
<template v-for="(childrenMenu, index) in parentMenu.children" :key="childrenMenu.id">
|
||||||
|
<tr>
|
||||||
|
<!-- 父菜单 -->
|
||||||
|
<td v-if="index === 0" :rowspan="parentMenu.children.length">
|
||||||
|
<a-checkbox :value="parentMenu.id">
|
||||||
|
{{ parentMenu.name }}
|
||||||
|
</a-checkbox>
|
||||||
|
</td>
|
||||||
|
<!-- 子菜单 -->
|
||||||
|
<td>
|
||||||
|
<a-checkbox :value="childrenMenu.id">
|
||||||
|
{{ childrenMenu.name }}
|
||||||
|
</a-checkbox>
|
||||||
|
</td>
|
||||||
|
<!-- 功能 -->
|
||||||
|
<td>
|
||||||
|
<a-row v-if="childrenMenu.children && childrenMenu.children.length">
|
||||||
|
<a-col v-for="item in childrenMenu.children"
|
||||||
|
:key="item.id"
|
||||||
|
:span="8">
|
||||||
|
<a-checkbox :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'menu-grant-table'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MenuQueryResponse } from '@/api/system/menu';
|
||||||
|
import { useCacheStore } from '@/store';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const cacheStore = useCacheStore();
|
||||||
|
|
||||||
|
const isFirst = ref(false);
|
||||||
|
const menuData = ref<Array<MenuQueryResponse>>([]);
|
||||||
|
const checkedKeys = ref<Array<number>>([]);
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
const init = (keys: Array<number>) => {
|
||||||
|
isFirst.value = true;
|
||||||
|
checkedKeys.value = keys;
|
||||||
|
if (!menuData.value.length) {
|
||||||
|
menuData.value = [...cacheStore.menus];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取值
|
||||||
|
const getValue = () => {
|
||||||
|
return checkedKeys.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ init, getValue });
|
||||||
|
|
||||||
|
// 监听级联变化
|
||||||
|
watch(checkedKeys, (after: Array<number>, before: Array<number>) => {
|
||||||
|
if (isFirst.value) {
|
||||||
|
isFirst.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const afterLength = after.length;
|
||||||
|
const beforeLength = before.length;
|
||||||
|
if (afterLength > beforeLength) {
|
||||||
|
// 选择 一定是最后一个
|
||||||
|
checkMenu(after[afterLength - 1]);
|
||||||
|
} else if (afterLength < beforeLength) {
|
||||||
|
// 取消
|
||||||
|
let uncheckedId = null;
|
||||||
|
for (let i = 0; i < afterLength; i++) {
|
||||||
|
if (after[i] !== before[i]) {
|
||||||
|
uncheckedId = before[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uncheckedId == null) {
|
||||||
|
uncheckedId = before[beforeLength - 1];
|
||||||
|
}
|
||||||
|
uncheckMenu(uncheckedId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 寻找当前节点
|
||||||
|
const findNode = (id: number, arr: Array<MenuQueryResponse>): MenuQueryResponse | undefined => {
|
||||||
|
for (let node of arr) {
|
||||||
|
if (node.id === id) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 寻找子级
|
||||||
|
for (let node of arr) {
|
||||||
|
if (node?.children?.length) {
|
||||||
|
const inChildNode = findNode(id, node.children);
|
||||||
|
if (inChildNode) {
|
||||||
|
return inChildNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取所有子节点id
|
||||||
|
const flatChildrenId = (nodes: MenuQueryResponse[] | undefined, result: number[]) => {
|
||||||
|
if (!nodes || !nodes.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let node of nodes) {
|
||||||
|
result.push(node.id);
|
||||||
|
if (node.children) {
|
||||||
|
flatChildrenId(node.children, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 级联选择菜单
|
||||||
|
const checkMenu = (id: number) => {
|
||||||
|
// 查询当前节点
|
||||||
|
const node = findNode(id, menuData.value);
|
||||||
|
const childrenId: number[] = [];
|
||||||
|
// 获取所在子节点id
|
||||||
|
flatChildrenId(node?.children, childrenId);
|
||||||
|
checkedKeys.value.push(...childrenId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 级联取消选择菜单
|
||||||
|
const uncheckMenu = (id: number) => {
|
||||||
|
// 查询当前节点
|
||||||
|
const node = findNode(id, menuData.value);
|
||||||
|
const childrenId: number[] = [];
|
||||||
|
// 获取所在子节点id
|
||||||
|
flatChildrenId(node?.children, childrenId);
|
||||||
|
checkedKeys.value = checkedKeys.value.filter(s => !childrenId.includes(s));
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
.grant-table {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--color-fill-3);
|
||||||
|
text-indent: initial;
|
||||||
|
border-spacing: 2px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
td {
|
||||||
|
padding: 6px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-size: 17px;
|
||||||
|
padding: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
background-color: var(--color-fill-1);
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent-view-header, .child-view-header {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
body-class="modal-form"
|
body-class="modal-form"
|
||||||
title-align="start"
|
title-align="start"
|
||||||
title="分配菜单"
|
title="分配菜单"
|
||||||
|
width="80%"
|
||||||
:top="80"
|
:top="80"
|
||||||
:width="480"
|
:body-style="{padding: '16px 16px 0 16px', 'margin-bottom': '16px'}"
|
||||||
:body-style="{padding: '16px 16px 0 16px'}"
|
|
||||||
:align-center="false"
|
:align-center="false"
|
||||||
:draggable="true"
|
:draggable="true"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
@@ -14,25 +14,15 @@
|
|||||||
:cancel-button-props="{ disabled: loading }"
|
:cancel-button-props="{ disabled: loading }"
|
||||||
:on-before-ok="handlerOk"
|
:on-before-ok="handlerOk"
|
||||||
@close="handleClose">
|
@close="handleClose">
|
||||||
<a-spin :loading="loading">
|
<div class="role-menu-wrapper">
|
||||||
<div class="role-menu-wrapper">
|
<a-alert class="mb8 usn">
|
||||||
<!-- 角色名称 -->
|
<span>{{ roleRecord.name }} {{ roleRecord.code }}</span>
|
||||||
<div class="item-wrapper">
|
<span class="mx8">-</span>
|
||||||
<span class="item-label">角色名称</span>
|
<span>菜单分配后需要用户手动刷新页面才会生效</span>
|
||||||
<span class="item-value ml4">{{ roleRecord.name }}</span>
|
</a-alert>
|
||||||
</div>
|
<!-- 菜单 -->
|
||||||
<!-- 角色编码 -->
|
<menu-grant-table ref="table" />
|
||||||
<div class="item-wrapper">
|
</div>
|
||||||
<span class="item-label">角色编码</span>
|
|
||||||
<a-tag class="item-value">{{ roleRecord.code }}</a-tag>
|
|
||||||
</div>
|
|
||||||
<!-- 菜单 -->
|
|
||||||
<div class="menu-tree-wrapper item-wrapper">
|
|
||||||
<span class="item-label">菜单选择</span>
|
|
||||||
<menu-selector-tree ref="tree" class="item-value" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a-spin>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -51,13 +41,12 @@
|
|||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { useCacheStore } from '@/store';
|
import { useCacheStore } from '@/store';
|
||||||
import { getMenuList } from '@/api/system/menu';
|
import { getMenuList } from '@/api/system/menu';
|
||||||
|
import MenuGrantTable from '@/components/system/menu/grant/menu-grant-table.vue';
|
||||||
import MenuSelectorTree from '@/components/system/menu/selector/menu-selector-tree.vue';
|
|
||||||
|
|
||||||
const { visible, setVisible } = useVisible();
|
const { visible, setVisible } = useVisible();
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
const tree = ref();
|
const table = ref();
|
||||||
const roleRecord = ref<RoleQueryResponse>({} as RoleQueryResponse);
|
const roleRecord = ref<RoleQueryResponse>({} as RoleQueryResponse);
|
||||||
|
|
||||||
// 打开新增
|
// 打开新增
|
||||||
@@ -75,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
// 获取角色菜单
|
// 获取角色菜单
|
||||||
const { data: roleMenuIdList } = await getRoleMenuId(record.id);
|
const { data: roleMenuIdList } = await getRoleMenuId(record.id);
|
||||||
tree.value.init(roleMenuIdList);
|
table.value.init(roleMenuIdList);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -96,7 +85,7 @@
|
|||||||
// 修改
|
// 修改
|
||||||
await grantRoleMenu({
|
await grantRoleMenu({
|
||||||
roleId: roleRecord.value.id,
|
roleId: roleRecord.value.id,
|
||||||
menuIdList: [...tree.value.getValue()]
|
menuIdList: [...table.value.getValue()]
|
||||||
} as RoleGrantMenuRequest);
|
} as RoleGrantMenuRequest);
|
||||||
Message.success('分配成功');
|
Message.success('分配成功');
|
||||||
// 清空
|
// 清空
|
||||||
@@ -123,25 +112,8 @@
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.role-menu-wrapper {
|
.role-menu-wrapper {
|
||||||
min-width: 400px;
|
width: 100%;
|
||||||
max-height: calc(100vh - 285px);
|
max-height: calc(100vh - 285px);
|
||||||
|
|
||||||
.item-wrapper {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
.item-label {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: right;
|
|
||||||
width: 80px;
|
|
||||||
height: 32px;
|
|
||||||
margin-right: 12px;
|
|
||||||
line-height: 32px;
|
|
||||||
color: var(--color-text-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user