前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端实现人员关系图谱

前端实现人员关系图谱

作者头像
全栈程序员站长
发布2022-09-07 15:59:56
9370
发布2022-09-07 15:59:56
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

入职前端工作到现在差不多有一年半的时间了,和朋友偶然聊天的时候被问到,能不能用所学的前端知识做一个家族关系的族谱,可以使家族关系更加简单明了。当时听完这个需求,觉得可能还是蛮简单的,后来动手做的时候,发现族谱的连线,是需要根据返回的数据动态生成的,这就是我这个小前端,有点头秃了🤡。

解决技术困难

当时阻碍我前进的就是如何实现族谱的连线以及根据数据渲染它们的对应关系,后来在逛博客的过程中,发现了antdesign的charts图表组件。利用这个组件,如果可以进行一些改造,可能就可以实现族谱的关系图。

在这里插入图片描述
在这里插入图片描述

开始动手

首先需要安装ant-design/charts,具体安装过程请参考官方文档

安装完成以后,就要根据数据渲染出想要的视觉效果,由于svg图相关的知识比较薄弱,所以实现的视觉可能有点丑陋,大家将就着看看,我写这篇文章的目的就是总结自己的技术探索历程,可能这篇文章下周就修改了。 先看视觉效果。

在这里插入图片描述
在这里插入图片描述

代码:

代码语言:javascript
复制
import React, { 
    useEffect, useState } from "react";
import "./Treechart.css";
import { 
    Modal } from "antd";
import { 
    OrganizationGraph } from "@ant-design/charts";
import * as _ from "lodash"


// const data = { 
   
// id: "joel",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// style: { 
   
// fill: "#00CED1",
// width: "200",
// },
// img: "/api/img/%E8%80%81%E4%BA%BA.jpeg",
// },
// style: { 
   
// width: 110,
// height: 40,
// // stroke: "#87ceeb",
// fill: " #FFC0CB",
// radius: "8",
// // textAlign: "center",
// },
// children: [
// { 
   
// id: "c1",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// children: [
// { 
   
// id: "c1-1",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// { 
   
// id: "c1-2",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// children: [
// { 
   
// id: "c1-2-1",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// { 
   
// id: "c1-2-2",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://lwfcll.oss-cn-hangzhou.aliyuncs.com/img/1630238333160.png",
// },
// },
// ],
// },
// ],
// },
// { 
   
// id: "c2",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// { 
   
// id: "c3",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// children: [
// { 
   
// id: "c3-1",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// { 
   
// id: "c3-2",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://lwfcll.oss-cn-hangzhou.aliyuncs.com/img/%E4%B8%AD%E5%B9%B4%E5%A4%AB%E5%A6%872.jpeg",
// },
// children: [
// { 
   
// id: "c3-2-1",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// { 
   
// id: "c3-2-2",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// { 
   
// id: "c3-2-3",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// ],
// },
// { 
   
// id: "c3-3",
// value: { 
   
// text: "李伟峰",
// value: "陈兰兰",
// // 建议使用 bae64 数据
// icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
// },
// },
// ],
// },
// ],
// };

const colorArr = ["#00CED1", "#FFA07A", "#87CEFA", "#BA55D3", "#00FA9A"];

export default function TreeChart() { 
   
  /** * 遍历树的方法 */
  // const traverseTree = (data, fn) => { 
   
  // if (typeof fn !== "function") { 
   
  // return;
  // }

  // if (fn(data) === false) { 
   
  // return false;
  // }

  // if (data && data.children) { 
   
  // for (let i = data.children.length - 1; i >= 0; i--) { 
   
  // if (!traverseTree(data.children[i], fn)) return false;
  // }
  // }
  // return true;
  // };

  // traverseTree(data, (d) => { 
   
  // d.leftIcon = { 
   
  // style: { 
   
  // fill: "#e6fffb",
  // stroke: "#e6fffb",
  // },
  // img:
  // "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ",
  // };
  // d.rightIcon = { 
   
  // style: { 
   
  // fill: "#e6fffb",
  // stroke: "#e6fffb",
  // },
  // img:
  // "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ",
  // };
  // return true;
  // });
  const [data, setData] = useState({ 
   })

  const randomNum = (minNum: number, maxNum: number) => { 
   
    switch (arguments.length) { 
   
      case 1:
        return parseInt(String(Math.random() * minNum + 1), 10);
        break;
      case 2:
        return parseInt(String(Math.random() * (maxNum - minNum + 1) + minNum), 10);
        break;
      default:
        return 0;
        break;
    }
  };

  const fetchTableData = () => { 
   
    fetch("http://localhost:9091/genealogy/info/selectUserPage", { 
   
      method: "POST",
      body: JSON.stringify({ 
   
        size: 20,
        current: 1,
      }),
      headers: { 
   
        "content-type": "application/json",
      },
    }).then((res) => { 
   
      res.json().then((data) => { 
   
        setData(arrayToTree(data.data)[0])
        console.log(456, arrayToTree(data.data)[0])
      });
    });
  };

  const arrayToTree = (items: []) => { 
   
    const result: any = [];   // 存放结果集
    const itemMap: any = { 
   };
    let newItem: any = _.cloneDeep(items);
    newItem = _.map(newItem, (item: any) => { 
   
      let compain = newItem.findIndex((i: any) => item?.companionId === i.id)
      let value = ''
      if (compain !== -1) { 
   
        value = newItem[compain]?.name;
        newItem.splice(compain, 1)

      }
      return { 
   
        ...item,
        value
      }
    })
    items = newItem.filter((item: any) => item?.id)
    console.log(123, items)
    items.sort((x: any, y: any) => { 
   
      return x.id - y.id
    })
    console.log(items)
    // 
    for (const item: any of items) { 
   
      const id = item.id;
      const parentId = item.parentId;

      if (!itemMap[id]) { 
   
        itemMap[id] = { 
   
          children: [],
        }
      }

      itemMap[id] = { 
   
        ...item,
        id: String(item?.id),
        value: { 
   
          text: item?.name,
          value: item?.value,
          // 建议使用 bae64 数据
          icon: "https://avatars.githubusercontent.com/u/31396322?v=4",
          style: { 
   
            fill: "#00CED1",
            width: "200",
          },
        },
        children: itemMap[id]['children']
      }

      const treeItem = itemMap[id];

      if (parentId === 0) { 
   
        result.push(treeItem);
      } else { 
   
        if (!itemMap[parentId]) { 
   
          itemMap[parentId] = { 
   
            children: [],
          }
        }
        itemMap[parentId].children.push(treeItem)
      }

    }
    return result;
  }

  useEffect(() => { 
   
    fetchTableData()
  }, [])


  return (
    <div className="content">
      <OrganizationGraph
        width={ 
   1000}
        height={ 
   1000}
        data={ 
   data}
        nodeCfg={ 
   { 
   
          // type:'circle',
          padding: 0,
          size: [150, 40],
          style: (node) => { 
   
            const num = randomNum(0, 4);
            console.log('node', node)
            return { 
   
              fill: colorArr[num],
            };
          },
          label: { 
   
            style: (node: any, group, type: any) => { 
   
              const styles = { 
   
                icon: { 
   
                  width: 40,
                  height: 40,
                  x: 0,
                  y: 0,
                },
                value: { 
   
                  fill: "#fff",
                  x: 100,
                  // y: 4,
                },
                text: { 
   
                  fill: "#fff",
                  x: 100,
                  y: node?.value?.value ? 4 : 16,
                },
              };
              return styles[type];
            },
          },
        }}
      />
    </div>
  );
}

编辑页面的视觉效果:

在这里插入图片描述
在这里插入图片描述

代码:

代码语言:javascript
复制
import React, { 
    useEffect, useState } from "react";
import { 
   
  Table,
  Tag,
  Space,
  Modal,
  Button,
  Form,
  message,
  Input,
  Tooltip,
  DatePicker,
  Select
} from "antd";
import { 
    PlusCircleTwoTone, EditTwoTone } from "@ant-design/icons";
import moment from 'moment';
const { 
    Option } = Select;
export default function EditTree() { 
   
  const [tabelData, setTableData] = useState([]);
  const [newPersonVisible, setNewPersonVisible] = useState(false);
  const [modalOkLoading, setModalOkLoading] = useState(false);
  const [currentPerson, setCurrentPerson] = useState({ 
   });
  const [form] = Form.useForm();
  useEffect(() => { 
   
    fetchTableData();
  }, []);

  const fetchTableData = () => { 
   
    fetch("http://localhost:9091/genealogy/info/selectUserPage", { 
   
      method: "POST",
      body: JSON.stringify({ 
   
        size: 20,
        current: 1,
      }),
      headers: { 
   
        "content-type": "application/json",
      },
    }).then((res) => { 
   
      res.json().then((data) => { 
   
        setTableData(data.data);
      });
    });
  };

  const columns = [
    { 
   
      title: "姓名",
      dataIndex: "name",
      key: "name",
      render: (text) => <a>{ 
   text}</a>,
    },
    { 
   
      title: "年龄",
      dataIndex: "date",
      key: "date",
      render:(record:any)=>{ 
   
        let birthday = moment(record).year();
        let now = moment().year();
        return now - birthday
      }
    },
    { 
   
      title: "创建日期",
      dataIndex: "createTime",
      key: "createTime",
    },
    { 
   
      title: "子女",
      key: "childrenName",
      dataIndex: "childrenName",
      align: "center",
      render: (child) => (
        <>
          { 
   child && child.length > 0 ? (
            child.map((item, index) => { 
   
              let color = index > 0 ? "geekblue" : "green";

              return (
                <Tag color={ 
   color} key={ 
   item}>
                  { 
   item}
                </Tag>
              );
            })
          ) : (
            <a>暂无子女</a>
          )}
        </>
      ),
    },
    { 
   
      title: "操作",
      key: "action",
      render: (text, record) => (
        <Space size="middle">
          <Tooltip title={ 
   "新增子女"} placement="top">
            <PlusCircleTwoTone
              onClick={ 
   () => { 
   
                setNewPersonVisible(true);
                setCurrentPerson(record);
              }}
            />
          </Tooltip>
          <Tooltip title={ 
   "编辑个人信息"} placement="top">
            <EditTwoTone />
          </Tooltip>
        </Space>
      ),
    },
  ];

  const handlModalOk = () => { 
   
    const { 
    getFieldsValue,resetFields } = form;
    let result = getFieldsValue();
    
    if(result?.type === 'child') { 
   
      result = { 
   
        ...result,
        parentId: currentPerson?.id,
        date: moment(result?.date).format('X') + '000'
      };
      delete result?.type
    }else { 
   
      result = { 
   
        ...result,
        companionId: currentPerson?.id,
        date: moment(result?.date).format('X') + '000'
      };
      delete result?.type
    }
    setModalOkLoading(true);
    fetch("http://localhost:9091/genealogy/info/saveUser", { 
   
      method: "POST",
      body: JSON.stringify(result),
      headers: { 
   
        "content-type": "application/json",
      },
    }).then((res) =>
      res.json().then((data) => { 
   
        if (data.isSuccess) { 
   
          setModalOkLoading(false);
          setNewPersonVisible(false);
          message.success("新建成功");
          fetchTableData();
          resetFields()
        } else { 
   
          message.error(data.msg);
          setModalOkLoading(false);
        }
      })
    );
  };
  return (
    <div>
      <Table columns={ 
   columns} rowKey={ 
   (record)=>  record?.id} dataSource={ 
   tabelData} />
      <Modal
        title={ 
   `新建${ 
     currentPerson.name}信息`}
        visible={ 
   newPersonVisible}
        onCancel={ 
   () => { 
   
          setNewPersonVisible(false);
        }}
        onOk={ 
   handlModalOk}
        cancelText="取消"
        okText="确定"
        centered
        confirmLoading={ 
   modalOkLoading}
      >
        <Form form={ 
   form}>
          <Form.Item label="姓名" name={ 
   "name"} required>
            <Input placeholder="请输入姓名" />
          </Form.Item>

          <Form.Item label="生日" name={ 
   "date"} required>
            <DatePicker
              style={ 
   { 
    width: '100%' }}
              placeholder={ 
   '请选择出生日期'}
            />
          </Form.Item>
      
          <Form.Item label="类型" name={ 
   "type"} required>
            <Select
              style={ 
   { 
    width: '100%' }}
              placeholder="请选择新增的是伴侣还是子女"
            >
              <Option value="child">子女</Option>
              <Option value="companion">伴侣</Option>
            </Select>
          </Form.Item>

        </Form>
      </Modal>
    </div>
  );
}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/153282.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年6月2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 解决技术困难
  • 开始动手
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档