You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/views/system/role/RoleAssignMenuForm.vue

256 lines
7.7 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<Dialog v-model="dialogVisible" :title="t('SystemManagement.Role.menuPermission')">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
label-width="auto"
class="role-assign-menu-form"
>
<el-form-item :label="t('SystemManagement.Role.name')">
<el-tag>{{ formData.name }}</el-tag>
</el-form-item>
<el-form-item :label="t('SystemManagement.Role.code')">
<el-tag>{{ formData.code }}</el-tag>
</el-form-item>
<el-form-item :label="t('SystemManagement.Role.menuPermission')">
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
<template #header>
<div class="role-assign-menu-form__header">
<el-tabs v-model="activeClientType" @tab-change="handleClientTypeChange">
<el-tab-pane :label="t('SystemManagement.Menu.clientTypeWeb')" :name="1" />
<el-tab-pane :label="t('SystemManagement.Menu.clientTypeApp')" :name="2" />
</el-tabs>
<div class="role-assign-menu-form__actions">
{{ t('SystemManagement.Role.selectAll') }}:
<el-switch
v-model="treeNodeAll"
:active-text="t('SystemManagement.Role.yes')"
:inactive-text="t('SystemManagement.Role.no')"
inline-prompt
@change="handleCheckedTreeNodeAll"
/>
{{ t('SystemManagement.Role.expandCollapse') }}:
<el-switch
v-model="menuExpand"
:active-text="t('SystemManagement.Role.yes')"
:inactive-text="t('SystemManagement.Role.no')"
inline-prompt
@change="handleCheckedTreeExpand"
/>
</div>
</div>
</template>
<el-tree
ref="treeRef"
:data="menuOptions"
:props="defaultProps"
:empty-text="t('SystemManagement.Role.loadingText')"
node-key="id"
show-checkbox
/>
</el-card>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">{{ t('common.ok') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { defaultProps, handleTree } from '@/utils/tree'
import * as RoleApi from '@/api/system/role'
import * as MenuApi from '@/api/system/menu'
import * as PermissionApi from '@/api/system/permission'
defineOptions({ name: 'SystemRoleAssignMenuForm' })
interface RoleAssignMenuFormData {
id: number | undefined
name: string
code: string
menuIds: number[]
}
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formData = reactive<RoleAssignMenuFormData>({
id: undefined,
name: '',
code: '',
menuIds: []
})
const formRef = ref() // 表单 Ref
const menuOptions = ref<any[]>([]) // 菜单树形结构
const menuExpand = ref(false) // 展开/折叠
const treeRef = ref() // 菜单树组件 Ref
const treeNodeAll = ref(false) // 全选/全不选
const activeClientType = ref(1) // 客户端类型1=Web2=APP
const currentTreeClientType = ref(1) // 当前树实际对应的客户端类型
const roleMenuIdsByClientType = reactive<Record<number, number[]>>({
1: [],
2: []
})
const loadedClientTypes = reactive<Record<number, boolean>>({
1: false,
2: false
})
const getCurrentTreeMenuIds = () => {
if (!treeRef.value) return []
return Array.from(new Set([
...(treeRef.value.getCheckedKeys(false) as Array<number>),
...(treeRef.value.getHalfCheckedKeys() as Array<number>)
]))
}
const saveCurrentClientMenuIds = () => {
if (!dialogVisible.value) return
roleMenuIdsByClientType[currentTreeClientType.value] = getCurrentTreeMenuIds()
}
const ensureRoleMenuIdsLoaded = async (roleId: number, clientType: number) => {
if (loadedClientTypes[clientType]) return
roleMenuIdsByClientType[clientType] = await PermissionApi.getRoleMenuList(roleId, clientType)
loadedClientTypes[clientType] = true
}
const loadRoleMenus = async (roleId: number) => {
currentTreeClientType.value = activeClientType.value
treeNodeAll.value = false
menuExpand.value = false
treeRef.value?.setCheckedNodes([])
menuOptions.value = handleTree(await MenuApi.getSimpleMenusList(currentTreeClientType.value))
await ensureRoleMenuIdsLoaded(roleId, currentTreeClientType.value)
await nextTick()
formData.menuIds = roleMenuIdsByClientType[currentTreeClientType.value] ?? []
formData.menuIds.forEach((menuId: number) => {
treeRef.value.setChecked(menuId, true, false)
})
}
/** 打开弹窗 */
const open = async (row: RoleApi.RoleVO) => {
dialogVisible.value = true
resetForm()
// 设置数据
formData.id = row.id
formData.name = row.name
formData.code = row.code
formLoading.value = true
try {
await loadRoleMenus(row.id)
} finally {
formLoading.value = false
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 切换客户端类型 */
const handleClientTypeChange = async (val: number | string) => {
saveCurrentClientMenuIds()
activeClientType.value = Number(val)
if (!dialogVisible.value || !formData.id) return
formLoading.value = true
try {
await loadRoleMenus(formData.id)
} finally {
formLoading.value = false
}
}
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
if (formData.id === undefined) return
saveCurrentClientMenuIds()
// 提交请求
formLoading.value = true
try {
await Promise.all([1, 2].map((clientType) => ensureRoleMenuIdsLoaded(formData.id as number, clientType)))
const data = {
roleId: formData.id,
menuIds: Array.from(new Set([
...roleMenuIdsByClientType[1],
...roleMenuIdsByClientType[2]
]))
}
await PermissionApi.assignRoleMenu(data)
message.success(t('common.updateSuccess'))
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
// 重置选项
activeClientType.value = 1
currentTreeClientType.value = 1
treeNodeAll.value = false
menuExpand.value = false
// 重置表单
formData.id = undefined
formData.name = ''
formData.code = ''
formData.menuIds = []
roleMenuIdsByClientType[1] = []
roleMenuIdsByClientType[2] = []
loadedClientTypes[1] = false
loadedClientTypes[2] = false
treeRef.value?.setCheckedNodes([])
formRef.value?.resetFields()
}
/** 全选/全不选 */
const handleCheckedTreeNodeAll = () => {
treeRef.value.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])
}
/** 展开/折叠全部 */
const handleCheckedTreeExpand = () => {
const nodes = treeRef.value?.store.nodesMap
for (let node in nodes) {
if (nodes[node].expanded === menuExpand.value) {
continue
}
nodes[node].expanded = menuExpand.value
}
}
</script>
<style scoped>
.role-assign-menu-form :deep(.el-form-item__label) {
min-width: 80px;
}
.role-assign-menu-form__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.role-assign-menu-form__header :deep(.el-tabs__header) {
margin: 0;
}
.role-assign-menu-form__actions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
</style>