| | |
| | | size="min(100vw, 960px)" |
| | | direction="rtl" |
| | | append-to-body |
| | | class="role-scope-drawer" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div class="flex h-full flex-col gap-4"> |
| | | <div class="flex flex-wrap items-center justify-between gap-3"> |
| | | <div class="role-scope-drawer__content"> |
| | | <div class="role-scope-drawer__toolbar"> |
| | | <div class="flex flex-wrap gap-2"> |
| | | <ElButton @click="toggleSelectAll" v-ripple> |
| | | {{ |
| | |
| | | </ElButton> |
| | | <ElButton @click="toggleExpandAll" v-ripple> |
| | | {{ |
| | | expandedKeys.length === 0 |
| | | ? $t('roleScope.actions.expandAll') |
| | | : $t('roleScope.actions.collapseAll') |
| | | isExpanded ? $t('roleScope.actions.collapseAll') : $t('roleScope.actions.expandAll') |
| | | }} |
| | | </ElButton> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <ElCard |
| | | class="flex min-h-0 flex-1 flex-col overflow-hidden [&_.el-card__body]:flex-1 [&_.el-card__body]:min-h-0 [&_.el-card__body]:overflow-auto" |
| | | shadow="never" |
| | | > |
| | | <ElCard class="role-scope-drawer__tree-card" shadow="never"> |
| | | <ElSkeleton v-if="loading" :rows="10" animated /> |
| | | <ElEmpty |
| | | v-else-if="treeData.length === 0" |
| | | :description="$t('roleScope.messages.emptyData')" |
| | | /> |
| | | <ElTree |
| | | v-else |
| | | ref="treeRef" |
| | | node-key="id" |
| | | show-checkbox |
| | | check-strictly |
| | | check-on-click-node |
| | | :data="treeData" |
| | | :props="treeProps" |
| | | :default-expanded-keys="expandedKeys" |
| | | @check-change="handleCheckChange" |
| | | @check="handleCheck" |
| | | /> |
| | | <div v-else class="scope-tree-scroll"> |
| | | <ElTree |
| | | ref="treeRef" |
| | | node-key="id" |
| | | show-checkbox |
| | | check-strictly |
| | | check-on-click-node |
| | | :data="treeData" |
| | | :props="treeProps" |
| | | :default-expanded-keys="expandedKeys" |
| | | @check-change="handleCheckChange" |
| | | @check="handleCheck" |
| | | @node-expand="syncExpandedState" |
| | | @node-collapse="syncExpandedState" |
| | | /> |
| | | </div> |
| | | </ElCard> |
| | | </div> |
| | | </ElDrawer> |
| | |
| | | const selectedKeys = ref<Array<string>>([]) |
| | | const halfCheckedKeys = ref<Array<string>>([]) |
| | | const expandedKeys = ref<Array<string>>([]) |
| | | const isExpanded = ref(false) |
| | | |
| | | const treeProps = { |
| | | label: 'label', |
| | |
| | | return keys |
| | | } |
| | | |
| | | const getNodeFromMap = (nodesMap: unknown, key: string) => { |
| | | if (nodesMap instanceof Map) return nodesMap.get(key) |
| | | return (nodesMap as Record<string, { childNodes?: unknown[]; expanded?: boolean }> | undefined)?.[ |
| | | key |
| | | ] |
| | | } |
| | | |
| | | const applyNodeExpansion = (expand: boolean, keys = collectNodeKeys(treeData.value).parentKeys) => { |
| | | const nodesMap = treeRef.value?.store?.nodesMap |
| | | if (!nodesMap) return |
| | | |
| | | keys.forEach((key) => { |
| | | const node = getNodeFromMap(nodesMap, key) |
| | | if (node) node.expanded = expand |
| | | }) |
| | | } |
| | | |
| | | const syncExpandedState = () => { |
| | | const { parentKeys } = collectNodeKeys(treeData.value) |
| | | const nodesMap = treeRef.value?.store?.nodesMap |
| | | if (!nodesMap || parentKeys.length === 0) { |
| | | expandedKeys.value = [] |
| | | isExpanded.value = false |
| | | return |
| | | } |
| | | |
| | | expandedKeys.value = parentKeys.filter((key) => Boolean(getNodeFromMap(nodesMap, key)?.expanded)) |
| | | isExpanded.value = expandedKeys.value.length === parentKeys.length |
| | | } |
| | | |
| | | const setTreeExpansion = async ( |
| | | expand: boolean, |
| | | keys = collectNodeKeys(treeData.value).parentKeys |
| | | ) => { |
| | | expandedKeys.value = expand ? keys : [] |
| | | isExpanded.value = expand && keys.length > 0 |
| | | |
| | | await nextTick() |
| | | applyNodeExpansion(expand, keys) |
| | | } |
| | | |
| | | const loadTree = async () => { |
| | | loading.value = true |
| | | isExpanded.value = false |
| | | try { |
| | | const [scopeIds, scopeTree] = await Promise.all([ |
| | | props.role?.id ? fetchGetRoleScopeList(props.kind, props.role.id) : Promise.resolve([]), |
| | |
| | | |
| | | const { parentKeys } = collectNodeKeys(treeData.value) |
| | | expandedKeys.value = parentKeys |
| | | isExpanded.value = parentKeys.length > 0 |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | |
| | | await nextTick() |
| | | applyNodeExpansion(isExpanded.value, expandedKeys.value) |
| | | syncExpandedState() |
| | | programmaticChecking.value = true |
| | | treeRef.value?.setCheckedKeys(selectedKeys.value) |
| | | halfCheckedKeys.value = treeRef.value?.getHalfCheckedKeys?.() || [] |
| | |
| | | programmaticChecking.value = false |
| | | } |
| | | |
| | | const toggleExpandAll = () => { |
| | | if (expandedKeys.value.length === 0) { |
| | | expandedKeys.value = collectNodeKeys(treeData.value).parentKeys |
| | | } else { |
| | | expandedKeys.value = [] |
| | | } |
| | | const toggleExpandAll = async () => { |
| | | await setTreeExpansion(!isExpanded.value) |
| | | } |
| | | |
| | | const handleSave = async () => { |
| | |
| | | { immediate: true } |
| | | ) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | :global(.role-scope-drawer .el-drawer__body) { |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .role-scope-drawer__content { |
| | | display: grid; |
| | | grid-template-rows: auto minmax(0, 1fr); |
| | | gap: 16px; |
| | | width: 100%; |
| | | height: 100%; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .role-scope-drawer__toolbar { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .role-scope-drawer__tree-card { |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .role-scope-drawer__tree-card :deep(.el-card__body) { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .scope-tree-scroll { |
| | | flex: 1; |
| | | min-height: 0; |
| | | overflow: auto; |
| | | } |
| | | </style> |