68 lines
1.8 KiB
Vue
68 lines
1.8 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref, watch } from 'vue'
|
|
import type { MenuOptionNode } from './user-menu-auth'
|
|
import { fetchGrantableMenus } from './user-menu-auth-api'
|
|
|
|
const props = defineProps<{ checked?: number[] }>()
|
|
const emit = defineEmits<{
|
|
(e: 'update:checked', ids: number[]): void
|
|
(e: 'load', ok: boolean): void
|
|
}>()
|
|
|
|
/** 仅暴露 el-tree 我们需要的两个方法,避免整组件类型耦合。 */
|
|
type TreeRef = { setCheckedKeys(keys: unknown[]): void; getCheckedKeys(): unknown }
|
|
const tree = ref<{ setCheckedKeys(keys: unknown[]): void; getCheckedKeys(): unknown } | null>(null)
|
|
const data = ref<MenuOptionNode[]>([])
|
|
const loaded = ref(false)
|
|
|
|
function applyChecked(): void {
|
|
const el: TreeRef | null = tree.value
|
|
if (!el) return
|
|
const ids = Array.isArray(props.checked) ? props.checked.filter((id) => typeof id === 'number' && id > 0) : []
|
|
el.setCheckedKeys(ids)
|
|
}
|
|
|
|
function onCheck(): void {
|
|
const el: TreeRef | null = tree.value
|
|
if (!el) return
|
|
const keys = el.getCheckedKeys()
|
|
const ids = (Array.isArray(keys) ? keys : []).map((key) => Number(key)).filter((id) => id > 0)
|
|
emit('update:checked', ids)
|
|
}
|
|
|
|
async function load(): Promise<void> {
|
|
try {
|
|
data.value = await fetchGrantableMenus()
|
|
loaded.value = true
|
|
applyChecked()
|
|
emit('load', true)
|
|
} catch {
|
|
loaded.value = false
|
|
emit('load', false)
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
watch(
|
|
() => props.checked,
|
|
() => {
|
|
if (loaded.value) applyChecked()
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<el-scrollbar max-height="300px" class="user-menu-tree-scroll">
|
|
<el-tree
|
|
ref="tree"
|
|
:data="data"
|
|
node-key="id"
|
|
show-checkbox
|
|
check-strictly
|
|
default-expand-all
|
|
:props="{ label: 'name', children: 'children' }"
|
|
@check="onCheck"
|
|
/>
|
|
</el-scrollbar>
|
|
</template>
|