eagleye

企业级组织架构面包屑导航组件(OrganizationBreadcrumb.vue)文档

企业级组织架构面包屑导航组件(OrganizationBreadcrumb.vue)文档

一、组件概述

OrganizationBreadcrumb是基于 Quasar 框架和 Vue 3 组合式 API 开发的企业级导航组件,专为大型组织架构系统设计。该组件通过可视化路径展示当前部门在企业层级中的位置,支持动态路径构建、智能折叠、响应式布局和路由导航,满足复杂组织架构的导航需求。

二、核心功能亮点

1. 智能响应式设计

  • 屏幕适配:自动根据设备尺寸调整显示策略

移动端(<600px):仅显示根部门和当前部门

桌面端(≥600px):显示更多层级,超出部分折叠

  • 动态折叠:当部门层级超过 3 级时,中间层级自动折叠为下拉菜单
  • 实时尺寸监听:窗口大小变化时自动重新计算布局
  • 部门类型图标:不同部门类型显示专属图标(如企业总部、安全管理部门等)
  • 路径完整性:自动构建从根部门到当前部门的完整路径
  • 折叠徽标:折叠菜单旁显示数字徽标,提示隐藏的部门数量
  • 悬停效果:面包屑项悬停时轻微上浮(transform: translateY(-2px))
  • 加载状态:数据加载中显示沙漏图标和"加载中..."文本
  • 平滑过渡:路由切换时路径更新有过渡动画
  • 路由集成:点击面包屑项自动导航至对应部门详情页
  • 类型安全:全程使用 TypeScript 类型定义(如Department接口、OrganizationNodeType枚举)
  • 模块化设计:通过组合式 API 分离数据获取、路径构建和 UI 逻辑

2. 层级可视化

3. 交互体验优化

4. 企业级功能

三、完整代码实现

<template>

<q-breadcrumbs class="organization-breadcrumb" active-color="white">

<!-- 自定义分隔符 -->

<template v-slot:separator>

<q-icon name="chevron_right" size="xs" />

</template>

<!-- 根部门 -->

<q-breadcrumbs-el

v-if="rootDepartment"

:label="rootDepartment.name"

icon="corporate_fare"

:to="getDepartmentRoute(rootDepartment)"

/>

<!-- 折叠的中间部门(响应式) -->

<q-breadcrumbs-el v-if="collapsedItems.length > 0">

<q-menu auto-close anchor="bottom middle" self="top middle">

<q-list dense class="breadcrumb-menu">

<q-item

v-for="dept in collapsedItems"

:key="dept.id"

clickable

:to="getDepartmentRoute(dept)"

>

<q-item-section avatar>

<q-icon :name="getDepartmentIcon(dept.node_type)" size="sm" />

</q-item-section>

<q-item-section>

<div class="text-weight-medium">{{ dept.name }}</div>

<div class="text-caption text-grey-6">{{ dept.node_type_display }}</div>

</q-item-section>

</q-item>

</q-list>

</q-menu>

<q-btn flat round dense icon="more_horiz" size="sm" class="collapsed-btn">

<q-badge v-if="collapsedItems.length" color="primary" floating transparent>

{{ collapsedItems.length }}

</q-badge>

</q-btn>

</q-breadcrumbs-el>

<!-- 当前部门(非点击态) -->

<q-breadcrumbs-el

v-if="currentDepartment"

:label="currentDepartment.name"

:icon="getDepartmentIcon(currentDepartment.node_type)"

/>

<!-- 加载状态占位 -->

<q-breadcrumbs-el v-if="loading" label="加载中..." icon="hourglass_empty" />

</q-breadcrumbs>

</template>

<script setup lang="ts">

import { ref, computed, watch, onMounted, onUnmounted } from 'vue'

import { useRoute, useRouter } from 'vue-router'

import { useQuasar } from 'quasar'

import type { Department } from 'src/types/auth/organization'

import { OrganizationNodeType } from 'src/types/auth/organization'

import { useOrganization } from 'src/composable/useOrganization'

// 外部依赖

const $q = useQuasar()

const route = useRoute()

const router = useRouter()

const { fetchDepartment, loading } = useOrganization()

// 状态管理

const rootDepartment = ref<Department | null>(null)

const currentDepartment = ref<Department | null>(null)

const departmentPath = ref<Department[]>([]) // 完整路径数组

const screenWidth = ref(window.innerWidth)

// 计算属性:折叠的中间部门

const collapsedItems = computed(() => {

if (departmentPath.value.length <= 3) return []

// 移动端折叠更多项

const collapseThreshold = $q.screen.lt.md ? 1 : 2

return departmentPath.value.slice(collapseThreshold, -1)

})

// 监听路由参数变化(部门ID变更)

watch(

() => route.params.id,

async (newId) => {

if (newId) {

await loadDepartment(newId as string)

}

},

{ immediate: true } // 初始加载时执行

)

// 监听窗口尺寸变化

onMounted(() => {

const handleResize = () => {

screenWidth.value = window.innerWidth

}

window.addEventListener('resize', handleResize)

// 清理监听

onUnmounted(() => {

window.removeEventListener('resize', handleResize)

})

})

// 加载部门数据并构建路径

const loadDepartment = async (departmentId: string) => {

const dept = await fetchDepartment(departmentId)

if (dept) {

currentDepartment.value = dept

await buildDepartmentPath(dept)

}

}

// 递归构建部门路径(从当前部门到根部门)

const buildDepartmentPath = async (dept: Department) => {

const path: Department[] = [dept]

let current = dept

// 向上追溯所有父部门

while (current.parent) {

try {

const parent = await fetchDepartment(current.parent)

if (parent) {

path.unshift(parent) // 父部门插入到路径头部

current = parent

} else {

break // 父部门不存在时终止

}

} catch (err) {

console.error('获取父部门失败:', err)

break

}

}

departmentPath.value = path

rootDepartment.value = path[0] || null // 根部门为路径第一项

}

// 生成部门详情页路由

const getDepartmentRoute = (dept: Department) => {

return {

name: 'DepartmentDetail',

params: { id: dept.id }

}

}

// 根据部门类型获取图标

const getDepartmentIcon = (nodeType: OrganizationNodeType): string => {

const iconMap: Record<OrganizationNodeType, string> = {

[OrganizationNodeType.ROOT_ORGANIZATION]: 'corporate_fare',

[OrganizationNodeType.SECURITY_SERVICE_PROVIDER]: 'security',

[OrganizationNodeType.HAZARD_MANAGEMENT_PROVIDER]: 'construction',

[OrganizationNodeType.PRODUCTION_UNIT]: 'factory',

[OrganizationNodeType.SAFETY_DEPARTMENT]: 'verified_user',

[OrganizationNodeType.REGULATORY_AGENCY]: 'gavel',

[OrganizationNodeType.THIRD_PARTY_AUDITOR]: 'assignment'

}

return iconMap[nodeType] || 'business'

}

</script>

<style lang="scss" scoped>

.organization-breadcrumb {

padding: 0 12px;

max-width: 600px;

overflow: hidden;

// 面包屑项样式

:deep(.q-breadcrumbs__el) {

max-width: 200px;

white-space: nowrap;

overflow: hidden;

text-overflow: ellipsis;

transition: all 0.3s ease;

&:hover {

transform: translateY(-2px);

}

}

// 折叠按钮样式

.collapsed-btn {

position: relative;

background: rgba(255, 255, 255, 0.15);

border-radius: 50%;

width: 28px;

height: 28px;

&:hover {

background: rgba(255, 255, 255, 0.25);

}

.q-badge {

top: -4px;

right: -4px;

font-size: 8px;

min-width: 16px;

min-height: 16px;

border: 1px solid $primary;

}

}

}

// 折叠菜单样式

.breadcrumb-menu {

min-width: 250px;

max-height: 300px;

overflow-y: auto;

.q-item {

border-bottom: 1px solid rgba(0, 0, 0, 0.05);

&:last-child {

border-bottom: none;

}

&:hover {

// 浅蓝背景突出

}

}

}

// 移动端适配

@media (max-width: 600px) {

.organization-breadcrumb {

max-width: 300px;

:deep(.q-breadcrumbs__el) {

max-width: 100px;

}

}

}

</style>

四、使用指南

1. 安装与引入

<!-- 在布局组件中引入 -->

<template>

<q-header>

<q-toolbar>

<!-- 其他工具栏内容 -->

<organization-breadcrumb />

</q-toolbar>

</q-header>

</template>

<script setup>

import OrganizationBreadcrumb from 'src/components/organization/OrganizationBreadcrumb.vue'

</script>

2. 依赖要求

  • 路由配置:需定义名为DepartmentDetail的路由,接受id参数
  • 类型定义:需提供Department接口和OrganizationNodeType枚举
  • 数据服务:需实现useOrganization组合式函数,提供fetchDepartment方法

3. 数据结构示例

// Department 接口示例

interface Department {

id: string

name: string

parent?: string // 父部门ID(根部门无此属性)

node_type: OrganizationNodeType

node_type_display: string // 部门类型显示名

// 其他属性...

}

// OrganizationNodeType 枚举示例

enum OrganizationNodeType {

ROOT_ORGANIZATION = 10,

SECURITY_SERVICE_PROVIDER = 21,

HAZARD_MANAGEMENT_PROVIDER = 22,

// 其他类型...

}

五、技术实现详解

1. 路径构建机制

  • 递归追溯:从当前部门出发,通过parent字段递归获取所有父部门,构建完整路径
  • 路径缓存:部门数据存储在departmentPath数组中,避免重复请求
  • 异常处理:父部门获取失败时终止递归,确保组件稳定性

2. 响应式折叠逻辑

// 核心逻辑:根据屏幕尺寸和路径长度决定折叠范围

const collapsedItems = computed(() => {

if (departmentPath.value.length <= 3) return []

const collapseThreshold = $q.screen.lt.md ? 1 : 2 // 移动端折叠阈值更低

return departmentPath.value.slice(collapseThreshold, -1)

})

3. 路由集成设计

  • 双向绑定:路由参数变化时自动加载对应部门路径,部门点击时自动更新路由
  • 类型安全:通过泛型约束确保路由参数和部门ID类型一致
  • 导航反馈:加载状态通过loading属性从useOrganization注入,自动显示/隐藏
  • 全程使用 TypeScript 类型定义,避免运行时类型错误
  • 部门类型通过枚举OrganizationNodeType严格约束,防止非法值
  • 路由参数和 API 响应均有接口定义,确保数据结构一致性
  • 事件监听清理onUnmounted中移除窗口 resize 监听,避免内存泄漏
  • 请求限制:递归获取父部门时设置异常捕获,防止无限请求
  • DOM 优化:通过v-if条件渲染减少不必要的 DOM 节点
  • 逻辑分离:数据获取(useOrganization)、路由处理、UI 逻辑分层管理
  • 命名规范:变量/方法命名遵循业务语义(如buildDepartmentPath、getDepartmentIcon)
  • 样式隔离:使用scoped样式和深度选择器:deep(),避免样式污染

六、企业级特性

1. 类型安全保障

2. 性能优化

3. 可维护性设计

七、注意事项

1. 路由配置:确保路由中存在name: 'DepartmentDetail'的路由定义,否则导航功能失效

2. 数据接口fetchDepartment方法需正确处理 404 错误(部门不存在场景)

3. 样式覆盖:如需自定义样式,可通过深度选择器:deep()修改内部元素样式

4. 性能监控:对于超大型组织(层级 >10),建议添加路径缓存机制减少 API 调用

完整代码示例可参考企业级前端组件库GitCode 仓库

 

posted on 2025-08-09 16:00  GoGrid  阅读(18)  评论(0)    收藏  举报

导航