145 lines
3.7 KiB
Vue
145 lines
3.7 KiB
Vue
<template>
|
||
<div v-if="props.enabled" class="art-full-height super-admin-channel-shell">
|
||
<div class="box-border flex gap-3 h-full max-md:block max-md:gap-0 max-md:h-auto">
|
||
<div class="channel-list-panel flex-shrink-0 h-full max-md:w-full max-md:h-auto max-md:mb-5">
|
||
<ElCard
|
||
class="channel-tree-card tree-card art-card-xs flex flex-col h-full mt-0"
|
||
shadow="never"
|
||
v-loading="loadingChannels"
|
||
>
|
||
<template #header>
|
||
<b class="channel-list-title">{{ $t('common.channelScope.listTitle') }}</b>
|
||
</template>
|
||
<ElScrollbar>
|
||
<ElTree
|
||
:data="displayTreeData"
|
||
:props="{ children: 'children', label: 'label' }"
|
||
node-key="id"
|
||
:current-node-key="selectedDeptId"
|
||
default-expand-all
|
||
highlight-current
|
||
@node-click="onNodeClick"
|
||
/>
|
||
</ElScrollbar>
|
||
</ElCard>
|
||
</div>
|
||
|
||
<div class="flex flex-col flex-grow min-w-0 min-h-0">
|
||
<div v-if="selectedDisplayLabel" class="channel-banner mb-3 text-sm text-g-500">
|
||
{{ bannerLabel }}:<b>{{ selectedDisplayLabel }}</b>
|
||
</div>
|
||
<div class="flex flex-col flex-1 min-h-0 min-w-0">
|
||
<slot />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<slot v-else />
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useI18n } from 'vue-i18n'
|
||
import { useRoute } from 'vue-router'
|
||
import { isRoleChannelRoute } from '@/utils/channelLayout'
|
||
import {
|
||
DEFAULT_CHANNEL_ID,
|
||
useChannelDeptScope,
|
||
type ChannelTreeNode
|
||
} from '@/composables/useChannelDeptScope'
|
||
|
||
const route = useRoute()
|
||
|
||
defineOptions({ name: 'SuperAdminChannelShell' })
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
enabled?: boolean
|
||
}>(),
|
||
{
|
||
enabled: true
|
||
}
|
||
)
|
||
|
||
const { t } = useI18n()
|
||
|
||
const {
|
||
treeData,
|
||
selectedDeptId,
|
||
loadingChannels,
|
||
handleChannelClick,
|
||
provideScope,
|
||
isConfigScope,
|
||
isAllChannelScope,
|
||
showDefaultTemplate
|
||
} = useChannelDeptScope()
|
||
|
||
provideScope()
|
||
|
||
const displayTreeData = computed(() => {
|
||
const nodes = showDefaultTemplate.value
|
||
? treeData.value
|
||
: treeData.value.filter((n) => n.id !== DEFAULT_CHANNEL_ID)
|
||
const defaultLabel = isRoleChannelRoute(route)
|
||
? t('common.channelScope.defaultRoleTemplate')
|
||
: t('common.channelScope.defaultTemplate')
|
||
const emptyNodeLabel = isAllChannelScope.value ? t('common.channelScope.allChannels') : defaultLabel
|
||
return nodes.map((node) =>
|
||
node.id === DEFAULT_CHANNEL_ID && !node.label ? { ...node, label: emptyNodeLabel } : node
|
||
)
|
||
})
|
||
|
||
const selectedDisplayLabel = computed(() => {
|
||
const item = displayTreeData.value.find((node) => node.id === selectedDeptId.value)
|
||
return item?.label ?? ''
|
||
})
|
||
|
||
const bannerLabel = computed(() => {
|
||
if (isConfigScope.value) {
|
||
return t('common.channelScope.currentConfig')
|
||
}
|
||
if (isRoleChannelRoute(route)) {
|
||
return t('common.channelScope.currentRole')
|
||
}
|
||
return t('common.channelScope.currentChannel')
|
||
})
|
||
|
||
const onNodeClick = (data: ChannelTreeNode) => {
|
||
handleChannelClick(data)
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.super-admin-channel-shell {
|
||
min-height: 0;
|
||
}
|
||
|
||
/* 原 w-64(16rem) 的 0.6 倍 */
|
||
.channel-list-panel {
|
||
width: 9.6rem;
|
||
}
|
||
|
||
.channel-tree-card :deep(.el-card__header) {
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
.channel-list-title {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.channel-tree-card :deep(.el-tree-node__content) {
|
||
height: 30px;
|
||
padding-right: 4px;
|
||
}
|
||
|
||
.channel-tree-card :deep(.el-tree-node__label) {
|
||
font-size: 13px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.channel-banner b {
|
||
color: var(--el-color-primary);
|
||
}
|
||
</style>
|