你看下面这个react文件,它展现了一棵树状结构,并且可以展开合并节点,如果我想在节点展开和合并时有渐变效果,你可以帮我实现吗?

你看下面这个react文件,它展现了一棵树状结构,并且可以展开合并节点,如果我想在节点展开和合并时有渐变效果,你可以帮我实现吗?

/**
 * @file
 * @author
 * @version
 * @since
 * @description This is a generic component used to show the cube's outline (dimensions roles tree).
 */
import * as React from 'react';
import Box from '@mui/material/Box';
import { useEffect, useState } from 'react';
import MetaApi from '../utils/meta-api';

const CubeOutline = ({ cubeGid, callback_selected_node }) => {

  const [tree, setTree] = useState([]);
  const [selectedNode, setSelectedNode] = useState(null);  // 新增选中节点的状态

  useEffect(() => {

    const data_initialization = async () => {
      // 根据cube_gid查询meta-api,获得包括度量在内的全部维度角色。
      if (cubeGid) {
        const dim_roles = await MetaApi.load_cube_dim_roles(cubeGid);
        const mapping = dim_roles.map((dr) => ({
          key: `${dr.gid}`,
          state: '[ + ]',
          display: `[维度角色] ${dr.name}`,
          type: 'dimension_role',
          olapEntity: dr,
          children: [],
        }));
        setTree(mapping);
      }
    };

    data_initialization();

  }, [cubeGid]); // 依赖数组中包含cubeGid,确保每次cubeGid变化时都重新发起请求

  // 切换节点展开/闭合状态的函数
  const toggleNodeState = async (node) => {
    node.state = node.state === '[ + ]' ? '[ - ]' : '[ + ]'; // 切换状态
    if (node.state === '[ - ]') {
      if (node.type === 'dimension_role') {
        const dim_role = node.olapEntity;
        const hierarchies = await MetaApi.load_dim_hierarchies(node.olapEntity.dimensionGid);
        node.children = hierarchies.map((hierarchy) => ({
          key: `${dim_role.gid}_${hierarchy.gid}`,
          state: '[ + ]',
          display: `[结构角色] ${hierarchy.name}`,
          type: 'hierarchy_role',
          olapEntity: {
            dimensionRole: dim_role,
            hierarchy,
          },
          children: [],
        }));
      } else if (node.type === 'hierarchy_role') {
        const dimensionRole = node.olapEntity.dimensionRole;
        const hierarchy = node.olapEntity.hierarchy;
        const members = await MetaApi.load_hierarchy_members(hierarchy.gid);
        const root = members.filter((m) => m.parentGid === 0)[0];

        node.children = [{
          key: `${dimensionRole.gid}_${root.gid}`,
          state: '[ + ]',
          display: `[成员角色] ${root.name}`,
          type: 'member_role',
          olapEntity: {
            dimensionRole,
            member: root,
          },
          children: [],
        }];
      } else if (node.type === 'member_role') {
        const parent_member = node.olapEntity.member;
        const hierarchy_gid = node.olapEntity.member.hierarchyGid;
        let members = await MetaApi.load_hierarchy_members(hierarchy_gid);
        members = members.filter((m) => m.parentGid === parent_member.gid);

        node.children = members.map((m) => ({
          key: `${node.olapEntity.dimensionRole.gid}_${m.gid}`,
          state: '[ + ]',
          display: `[成员角色] ${m.name}`,
          type: 'member_role',
          olapEntity: {
            dimensionRole: node.olapEntity.dimensionRole,
            member: m,
          },
          children: [],
        }));

      }
    } else { // node.state === '[ + ]'
      node.children = [];
    }

    setTree([...tree]); // 更新state以触发重新渲染
  };

  // 递归渲染树形结构的函数
  const handleNodeClick = (node) => {
    setSelectedNode(node.key);  // 更新选中节点的key
    callback_selected_node(node);
  };

  const renderTree = (node) => {
    const isSelected = node.key === selectedNode;  // 判断当前节点是否被选中
    return (
      <Box key={node.key} sx={{ paddingLeft: 2 }}>
        <Box sx={{ textAlign: 'left' }}>
          <Box>
            {/* 点击切换节点展开/闭合状态 */}
            <span
              style={{ cursor: 'pointer' }}
              onClick={() => toggleNodeState(node)}  // 切换节点状态
            >
              {node.state}
            </span>
            {/* 点击时调用callback_selected_node,并传递当前的olapEntity */}
            <span
              style={{
                cursor: 'pointer',
                marginLeft: 8,
                backgroundColor: isSelected ? '#d3d3d3' : 'transparent',  // 设置选中节点的背景色
                padding: '2px 4px', // 增加一些内边距,使背景色更明显
              }}
              onClick={() => handleNodeClick(node)}  // 点击节点时更新选中状态
            >
              {node.display}
            </span>
          </Box>
        </Box>
        {/* 如果有子节点,递归渲染 */}
        {node.children && node.children.length > 0 && (
          <Box sx={{ paddingLeft: 2 }}>
            {node.children.map(renderTree)}
          </Box>
        )}
      </Box>
    );
  };

  return (
    <Box sx={{ minHeight: 352, minWidth: 250 }}>
      {tree.map(renderTree)}
    </Box>
  );
};

export default CubeOutline;

eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

为了在节点展开和合并时添加渐变效果,你可以使用 CSS 动画或者 react-spring 等动画库来实现平滑的展开和收起效果。这里我将给出一种使用 CSS 动画的实现方式。

修改步骤:

  1. 添加渐变效果:使用 transition 属性来实现节点展开和合并时的渐变效果。
  2. 动态控制高度:为节点的子节点容器设置一个可变的高度,并通过过渡效果来平滑地展开和收起。
  3. 动画过渡:通过设置 max-height 属性,并配合 overflow 来控制节点展开和合并时的动画。

实现代码

我们会修改 toggleNodeState 方法,在切换节点状态时添加动画效果,并为树的每个节点容器设置过渡动画。

import * as React from 'react';
import Box from '@mui/material/Box';
import { useEffect, useState } from 'react';
import MetaApi from '../utils/meta-api';

const CubeOutline = ({ cubeGid, callback_selected_node }) => {
  const [tree, setTree] = useState([]);
  const [selectedNode, setSelectedNode] = useState(null);  // 新增选中节点的状态

  useEffect(() => {
    const data_initialization = async () => {
      if (cubeGid) {
        const dim_roles = await MetaApi.load_cube_dim_roles(cubeGid);
        const mapping = dim_roles.map((dr) => ({
          key: `${dr.gid}`,
          state: '[ + ]',
          display: `[维度角色] ${dr.name}`,
          type: 'dimension_role',
          olapEntity: dr,
          children: [],
        }));
        setTree(mapping);
      }
    };

    data_initialization();
  }, [cubeGid]);

  // 切换节点展开/闭合状态的函数
  const toggleNodeState = async (node) => {
    node.state = node.state === '[ + ]' ? '[ - ]' : '[ + ]'; // 切换状态
    if (node.state === '[ - ]') {
      if (node.type === 'dimension_role') {
        const dim_role = node.olapEntity;
        const hierarchies = await MetaApi.load_dim_hierarchies(node.olapEntity.dimensionGid);
        node.children = hierarchies.map((hierarchy) => ({
          key: `${dim_role.gid}_${hierarchy.gid}`,
          state: '[ + ]',
          display: `[结构角色] ${hierarchy.name}`,
          type: 'hierarchy_role',
          olapEntity: {
            dimensionRole: dim_role,
            hierarchy,
          },
          children: [],
        }));
      } else if (node.type === 'hierarchy_role') {
        const dimensionRole = node.olapEntity.dimensionRole;
        const hierarchy = node.olapEntity.hierarchy;
        const members = await MetaApi.load_hierarchy_members(hierarchy.gid);
        const root = members.filter((m) => m.parentGid === 0)[0];

        node.children = [{
          key: `${dimensionRole.gid}_${root.gid}`,
          state: '[ + ]',
          display: `[成员角色] ${root.name}`,
          type: 'member_role',
          olapEntity: {
            dimensionRole,
            member: root,
          },
          children: [],
        }];
      } else if (node.type === 'member_role') {
        const parent_member = node.olapEntity.member;
        const hierarchy_gid = node.olapEntity.member.hierarchyGid;
        let members = await MetaApi.load_hierarchy_members(hierarchy_gid);
        members = members.filter((m) => m.parentGid === parent_member.gid);

        node.children = members.map((m) => ({
          key: `${node.olapEntity.dimensionRole.gid}_${m.gid}`,
          state: '[ + ]',
          display: `[成员角色] ${m.name}`,
          type: 'member_role',
          olapEntity: {
            dimensionRole: node.olapEntity.dimensionRole,
            member: m,
          },
          children: [],
        }));
      }
    } else { // node.state === '[ + ]'
      node.children = [];
    }

    setTree([...tree]); // 更新state以触发重新渲染
  };

  const handleNodeClick = (node) => {
    setSelectedNode(node.key);  // 更新选中节点的key
    callback_selected_node(node);
  };

  // 渲染每个节点的树状结构
  const renderTree = (node) => {
    const isSelected = node.key === selectedNode;  // 判断当前节点是否被选中
    return (
      <Box key={node.key} sx={{ paddingLeft: 2 }}>
        <Box sx={{ textAlign: 'left' }}>
          <Box>
            {/* 点击切换节点展开/闭合状态 */}
            <span
              style={{ cursor: 'pointer' }}
              onClick={() => toggleNodeState(node)}  // 切换节点状态
            >
              {node.state}
            </span>
            {/* 点击时调用callback_selected_node,并传递当前的olapEntity */}
            <span
              style={{
                cursor: 'pointer',
                marginLeft: 8,
                backgroundColor: isSelected ? '#d3d3d3' : 'transparent',
                padding: '2px 4px',  // 增加一些内边距,使背景色更明显
              }}
              onClick={() => handleNodeClick(node)}  // 点击节点时更新选中状态
            >
              {node.display}
            </span>
          </Box>
        </Box>

        {/* 子节点的展开与折叠效果 */}
        {node.children && node.children.length > 0 && (
          <Box
            sx={{
              paddingLeft: 2,
              overflow: 'hidden',
              maxHeight: node.state === '[ - ]' ? '1000px' : '0px',  // 控制最大高度
              transition: 'max-height 0.3s ease-in-out',  // 添加渐变效果
            }}
          >
            {node.children.map(renderTree)}
          </Box>
        )}
      </Box>
    );
  };

  return (
    <Box sx={{ minHeight: 352, minWidth: 250 }}>
      {tree.map(renderTree)}
    </Box>
  );
};

export default CubeOutline;

关键修改:

  1. 添加渐变效果

    • 我在子节点容器 (Box) 上使用了 max-height 属性,并将其设置为 0px1000px 来控制节点展开和折叠的高度。
    • 使用 transition: 'max-height 0.3s ease-in-out' 来实现渐变效果,这样节点展开或折叠时会有平滑过渡。
  2. 节点切换:每次点击展开或合并节点时,都会触发 toggleNodeState 方法,该方法会根据当前节点的状态来切换是否展开或折叠节点,并且重新渲染树状结构。

  3. 设置 max-height:设置 max-height1000px 使得展开时的子节点可见,并通过 overflow: 'hidden' 隐藏折叠时的内容。你可以根据需要调整 1000px 为合适的值。

结果:

  • 点击每个节点时,展开和折叠都会有渐变效果,表现为平滑的展开和收缩。
  • 点击节点时,选中的节点会变更背景颜色,体现出选中的状态。

posted on   及途又八  阅读(8)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
历史上的今天:
2021-02-25 K8S部署后测试nginx
2021-02-25 kube-flannel.yml
2021-02-25 centos7部署K8S

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示