在这里插入图片描述

前言

组织架构是企业管理的基础,OA系统中的组织架构树组件用于展示公司的部门层级关系和人员分布。一个优秀的组织架构树组件需要支持多级展开收起、节点搜索、人员选择等功能,同时要保证在大数据量下的渲染性能。本文将详细介绍如何使用Flutter和OpenHarmony开发一个功能完善的组织架构树组件,帮助企业用户快速了解公司结构和查找同事。

组件设计思路

组织架构树采用递归结构设计,每个节点可以是部门或人员。部门节点可以包含子部门和人员,支持展开收起操作。组件需要支持单选和多选两种模式,单选模式用于选择审批人,多选模式用于群发通知或会议邀请。在视觉设计上,不同层级使用缩进区分,部门和人员使用不同图标标识。

Flutter端实现

定义组织架构节点数据模型:

class OrgNode {
  final String id;
  final String name;
  final String type; // department or employee
  final String? avatar;
  final List<OrgNode> children;
  bool isExpanded;
  
  OrgNode({
    required this.id,
    required this.name,
    required this.type,
    this.avatar,
    this.children = const [],
    this.isExpanded = false,
  });
}

数据模型采用递归结构,children字段存储子节点列表。type字段区分部门和人员,avatar字段存储人员头像。isExpanded用于控制节点的展开收起状态,设为非final以便在运行时修改。

组织架构树组件的基础结构:

class OrgTreeWidget extends StatefulWidget {
  final OrgNode rootNode;
  final bool multiSelect;
  final Function(List<OrgNode>) onSelectionChanged;
  
  const OrgTreeWidget({
    Key? key,
    required this.rootNode,
    this.multiSelect = false,
    required this.onSelectionChanged,
  }) : super(key: key);
  
  
  State<OrgTreeWidget> createState() => _OrgTreeWidgetState();
}

组件接收根节点数据和选择模式配置,onSelectionChanged回调在选择变化时触发,返回当前选中的节点列表。multiSelect参数控制单选或多选模式,默认为单选。

状态类中的选中节点管理:

class _OrgTreeWidgetState extends State<OrgTreeWidget> {
  final Set<String> _selectedIds = {};
  
  void _toggleSelection(OrgNode node) {
    setState(() {
      if (widget.multiSelect) {
        if (_selectedIds.contains(node.id)) {
          _selectedIds.remove(node.id);
        } else {
          _selectedIds.add(node.id);
        }
      } else {
        _selectedIds.clear();
        _selectedIds.add(node.id);
      }
    });
    widget.onSelectionChanged(_getSelectedNodes());
  }
}

使用Set存储选中节点的ID,Set的特性保证了ID的唯一性。单选模式下先清空再添加,多选模式下切换选中状态。_getSelectedNodes方法根据ID列表获取对应的节点对象。

递归构建树节点的方法:

Widget _buildTreeNode(OrgNode node, int level) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      InkWell(
        onTap: () {
          if (node.type == 'department') {
            setState(() => node.isExpanded = !node.isExpanded);
          } else {
            _toggleSelection(node);
          }
        },
        child: Padding(
          padding: EdgeInsets.only(left: level * 24.0),
          child: _buildNodeContent(node),
        ),
      ),
      if (node.isExpanded)
        ...node.children.map((child) => _buildTreeNode(child, level + 1)),
    ],
  );
}

递归方法接收节点和层级参数,层级用于计算缩进距离。点击部门节点切换展开状态,点击人员节点切换选中状态。使用展开运算符将子节点列表展开到Column的children中。

节点内容的构建:

Widget _buildNodeContent(OrgNode node) {
  final isSelected = _selectedIds.contains(node.id);
  
  return Row(
    children: [
      if (node.type == 'department')
        Icon(
          node.isExpanded ? Icons.folder_open : Icons.folder,
          color: Colors.amber,
        )
      else
        CircleAvatar(
          radius: 16,
          backgroundImage: node.avatar != null 
            ? NetworkImage(node.avatar!) 
            : null,
          child: node.avatar == null ? Text(node.name[0]) : null,
        ),
      SizedBox(width: 8),
      Expanded(child: Text(node.name)),
      if (node.type == 'employee')
        Checkbox(
          value: isSelected,
          onChanged: (_) => _toggleSelection(node),
        ),
    ],
  );
}

节点内容根据类型显示不同的图标,部门显示文件夹图标,人员显示头像。人员节点右侧显示复选框用于选择操作。CircleAvatar在没有头像时显示姓名首字母作为占位。

OpenHarmony鸿蒙端实现

定义组织架构节点接口:

interface OrgNode {
  id: string
  name: string
  type: 'department' | 'employee'
  avatar?: string
  children: OrgNode[]
  isExpanded: boolean
}

TypeScript的interface支持递归定义,children字段类型为OrgNode数组。type使用联合类型限定只能是department或employee两个值,提供了更好的类型安全。

组织架构树组件的基础结构:

@Component
struct OrgTree {
  @Prop rootNode: OrgNode
  @Prop multiSelect: boolean = false
  @State selectedIds: Set<string> = new Set()
  private onSelectionChange: (nodes: OrgNode[]) => void = () => {}
}

使用@State管理选中ID集合,Set类型保证ID唯一性。@Prop接收根节点和选择模式配置,回调函数用于通知父组件选择变化。

递归构建树节点:

@Builder
TreeNode(node: OrgNode, level: number) {
  Column() {
    Row() {
      if (node.type === 'department') {
        Image(node.isExpanded ? $r('app.media.folder_open') : $r('app.media.folder'))
          .width(24)
          .height(24)
      } else {
        Image(node.avatar || $r('app.media.default_avatar'))
          .width(32)
          .height(32)
          .borderRadius(16)
      }
      
      Text(node.name)
        .fontSize(14)
        .margin({ left: 8 })
        .layoutWeight(1)
      
      if (node.type === 'employee') {
        Checkbox({ name: node.id })
          .select(this.selectedIds.has(node.id))
          .onChange((value: boolean) => {
            this.toggleSelection(node)
          })
      }
    }
    .width('100%')
    .padding({ left: level * 24, top: 12, bottom: 12, right: 16 })
    .onClick(() => this.handleNodeClick(node))
    
    if (node.isExpanded && node.children.length > 0) {
      ForEach(node.children, (child: OrgNode) => {
        this.TreeNode(child, level + 1)
      })
    }
  }
}

鸿蒙的@Builder方法支持递归调用,通过ForEach渲染子节点。padding的left值根据层级动态计算实现缩进效果。Checkbox组件的select属性绑定选中状态,onChange处理选择变化。

节点点击处理逻辑:

private handleNodeClick(node: OrgNode) {
  if (node.type === 'department') {
    node.isExpanded = !node.isExpanded
  } else {
    this.toggleSelection(node)
  }
}

private toggleSelection(node: OrgNode) {
  if (this.multiSelect) {
    if (this.selectedIds.has(node.id)) {
      this.selectedIds.delete(node.id)
    } else {
      this.selectedIds.add(node.id)
    }
  } else {
    this.selectedIds.clear()
    this.selectedIds.add(node.id)
  }
  this.onSelectionChange(this.getSelectedNodes())
}

点击处理逻辑与Flutter版本类似,部门节点切换展开状态,人员节点切换选中状态。Set的has、add、delete方法用于管理选中ID集合,clear方法在单选模式下清空之前的选择。

搜索过滤功能:

@State searchKeyword: string = ''

@Builder
SearchBar() {
  Search({ placeholder: '搜索部门或员工' })
    .width('100%')
    .height(40)
    .margin({ bottom: 12 })
    .onChange((value: string) => {
      this.searchKeyword = value
    })
}

private filterNodes(node: OrgNode): OrgNode | null {
  if (node.name.includes(this.searchKeyword)) {
    return node
  }
  const filteredChildren = node.children
    .map(child => this.filterNodes(child))
    .filter(child => child !== null) as OrgNode[]
  
  if (filteredChildren.length > 0) {
    return { ...node, children: filteredChildren, isExpanded: true }
  }
  return null
}

搜索功能通过递归过滤实现,如果节点名称匹配关键词则保留,如果子节点中有匹配的也保留父节点。过滤后的节点自动展开,方便用户查看搜索结果。这种递归过滤算法保持了树的层级结构。

总结

本文详细介绍了Flutter和OpenHarmony平台上组织架构树组件的开发方法。组织架构树是OA系统中的基础组件,广泛应用于人员选择、部门浏览等场景。递归结构的设计使组件能够处理任意深度的组织层级,搜索过滤功能帮助用户快速定位目标节点。两个平台的实现思路相似,都采用了递归渲染和状态管理的方式。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐