前端实现人员关系图谱

前端实现人员关系图谱入职前端工作到现在差不多有一年半的时间了,和朋友偶然聊天的时候被问到,能不能用所学的前端知识做一个家族关系的族谱,可以使家族关系更加简单明了。当时听完这个需求,觉得可能还是蛮简单的,后来动手做的时候,发现族谱的连线,是需要根据返回的数据动态生成的,这就是我这个小前端,有点头秃了????。解决技术困难当时阻碍我前进的就是如何实现族谱的连线以及根据数据渲染它们的对应关系,后来在逛博客的过程中,发现了antdesign的charts图表组件。利用这个组件,如果可以进行一些改造,可能就可以实现族谱的关系图。

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

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

解决技术困难

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

开始动手

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

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

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>
);
}

编辑页面的视觉效果:
在这里插入图片描述
代码:

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>
);
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 图片加载出错后显示默认图片

    图片加载出错后显示默认图片图片加载出错后展示默认设置的图片img因网络问题以及其他问题加载出错时,展示成下图的样式网络出错时,没有设置默认图片显示成这样使用one()绑定事件是防止默认图片也加载不出,防止死循环$(“.listList2”).one(“error”,function(e){$(this).attr(“src”,”image/head_portrait.png”);});或者img元素自带onerror属性,加载失败时,触发error事件<imgsrc=”img/yuan.

  • 配置设置文件怎么生成的_centos6.8网络配置

    配置设置文件怎么生成的_centos6.8网络配置前言每个测试用例都应该有config部分,可以配置用例级别。比如name、base_url、variables、verify、export等等案例演示fromhttprunnerimport

  • 图像去色算法_matlab去雾算法

    图像去色算法_matlab去雾算法先上图看一些算法效果                                           上图中从左到右依次是原图、photoshop去色结果、Matlab的rgb2gray函数处理效果、取rgb均值的效果、使用香港中文大学论文(见下)的结果、Glundland论文(见下)的结果。还有

  • linux .gz文件 解压缩命令的简单使用

    linux .gz文件 解压缩命令的简单使用压缩压缩文件语法gzip源文件1如压缩b.txt使用命令gzipb.txt即可注意压缩为.gz文件源文件会消失如果想保留源文件使用命令gzip-c源文件&gt;压缩文件1如压缩b.txt且保留b.txt使用命令gzip-cb.txt&gt;b.txt.gz压缩目录语法gzip-r目录1…

  • Linux最著名的文本编辑器,最优秀的5个Linux文本编辑器

    Linux最著名的文本编辑器,最优秀的5个Linux文本编辑器Vi/VimEditorVim以绝对优势获胜在大家的意料之中。如果你不熟悉最好的5个Linux文本编辑器中的任何一个,阅读本文剩下的部分对那些编辑器多点了解。1.VimEditor最新稳定版本:Vim7.2用C和Vimscript编写操作系统:跨平台(Unix,LinuxandWindows)阅读我们正在连载的Vi/Vim技巧和诀窍系列文章,掌握一些很棒的Vim绝…

  • 数据库设计工具介绍

    数据库设计工具介绍本文将从如下四个方面和您一起比较四种优秀数据库设计工具的各自优缺点。用户界面可支持的数据库数据工具售价1.DbSchema官网:https://dbschema.com/DbSchema是一种可用于复杂数据库设计和管理的可视化工具。该工具已经集成在大多数主流操作系统之中。用户界面DbSchema具有友好的用户界面,可简化数据库的设计。它能够为管理大型数据库提供友好的界面布局,以便用户更专注于数据库的特定功能。如下图所示,DbSchema界面能够让用户自由地浏览各种视图,在布局中拖放表格,

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号