国外得组织架构图

不常见得组织架构图,用了G6和自己一些写法再里面。此篇为自己得记录篇-----------------------------

js 复制代码
let data = [
  {
    "id": 10009070,
    "shortNameEn": "MAINT",
    "shortNameZh": null,
    "shortNameAr": null,
    "nameEn": "Maintenance",
    "nameZh": "Maintenance",
    "nameAr": "Maintenance",
    "sectionCount": 0,
    "localCount": 38,
    "expatCount": 68,
    "mixedCount": 0,
    "headcount": 106,
    "employeeCount": 0,
    "leaderList": null,
    "positionList": [
      {
        "id": 10074621,
        "unicode": "AOSIMTNSPOS111",
        "enterpriseId": 10000504,
        "buId": 10009070,
        "name": "Metering Engineer",
        "shortName": "Metering Engineer",
        "reportTo": 10074531,
        "reportBuId": 10009153,
        "personalType": 0,
        "positionType": 1,
        "workPatternId": 10000042,
        "workPatternName": "14/14",
        "headCount": 2,
        "sort": 1,
        "employeeCount": 1,
        "employeeList": [{
            "id": 10087744,
            "unicode": null,
            "fullName": "HASAN LAZIM MOHSIN",
            "workPatternId": 10000042,
            "workPatternName": "14/14",
          }
        ],
        "children": null
      }
    ],
    "children": [
      {
        "id": 10009109,
        "tenantId": 10000000,
        "appId": 10000000,
        "unicode": "AOSIMAINTSEC003",
        "enterpriseId": 10000504,
        "parentId": 10009070,
        "unitTypeId": 10001520,
        "shortNameEn": "Diesel.",
        "shortNameZh": null,
        "shortNameAr": null,
        "nameEn": "Diesel Workshop",
        "nameZh": "Diesel Workshop",
        "nameAr": "Diesel Workshop",
        "sectionCount": 0,
        "localCount": 0,
        "expatCount": 4,
        "mixedCount": 0,
        "headcount": 4,
        "employeeCount": 0,
        "unitTypeIcon": "section",
        "leaderList": null,
        "positionList": [{
            "id": 10076160,
            "uuid": "a858b0a8b57e47f7adbaeda46e67bbd9",
            "deleted": 0,
            "createTime": "2024-12-30 11:07:25",
            "updateTime": "2024-12-30 11:07:25",
            "createBy": "levi.liang@itforce-tech.com",
            "updateBy": "levi.liang@itforce-tech.com",
            "tenantId": 10000000,
            "appId": 10000000,
            "unicode": "AOSIDIESEL.POS002",
            "enterpriseId": 10000504,
            "buId": 10009109,
            "name": "WPB",
            "shortName": "WPB",
            "reportTo": 10074592,
            "reportBuId": 10009161,
            "personalType": 1,
            "positionType": 0,
            "headCount": 1,
            "sort": 1,
            "employeeCount": 2,
            "employeeList": [{
                "id": 10087898,
                "unicode": null,
                "fullName": "WALEED KAMIL JASIM",
                "workPatternId": 10000042,
                "workPatternName": "14/14",
              },
            ],
            "children": null
          },
        ],
        "children": null
      }
    ]
  }
]

这是组织树需要得数据结构 view-org-chart.vue 父级

js 复制代码
<script setup>
import { h } from 'vue';
import { CloseOutlined } from '@ant-design/icons-vue';
import { useLanguage } from '@/hooks/index';
import { GetBusinessUnitTree } from '@/api/organization/index';
const { isCn, isArabic } = useLanguage();
const TreeChart = defineAsyncComponent(() =>
	import('./components/TreeChart.vue'),
);
const route = useRoute();
const router = useRouter();
const TreeChartRef = ref(null);
// 使用watch来监听对象
const stringTree = ref([]);
const convertValuesToString = (tree) => {
	return tree.map((node) => {
		const convertedNode = { ...node };
		if (convertedNode.children) {
			convertedNode.children = convertValuesToString(convertedNode.children);
		}
		convertedNode.id = String(convertedNode.id);
		convertedNode.nameZh =
			isCn && convertedNode.nameZh
				? convertedNode.nameZh
				: convertedNode.nameEn;
		convertedNode.nameAr =
			isArabic && convertedNode.nameAr
				? convertedNode.nameAr
				: convertedNode.nameEn;
		return convertedNode;
	});
};

// 我给了每个层级加了标识好区分
const addLevels = (nodes, level2 = 0) => {
	return nodes.map((node) => {
		node.level2 = level2; // 添加层级标记
		if (node.children && node.children.length > 0) {
			node.children = addLevels(node.children, level2 + 1);
		}
		return node;
	});
};
// 这个是树形访问接口,可以替换
const getSelectTree = async () => {
	await GetBusinessUnitTree({
		id: route.query.buId,
	}).then((res) => {
		stringTree.value = addLevels(convertValuesToString(res.data));
	});
};
onMounted(() => {
	getSelectTree();
});

const loading = ref(false);
const loading2 = ref(false);
// 下载svg 图片
const handleDownIamges = debounce(() => {
	loading.value = true;
	TreeChartRef.value.downImage();
	loading.value = false;
}, 1000);
// 下载PDF
const handleDownPdf = debounce(() => {
	loading2.value = true;
	TreeChartRef.value.downPdf();
	loading2.value = false;
}, 1000);
const onClose = () => {
	router.back();
};
</script>
<template>
	<div style="background-color: #fff; border-radius: 6px">
		<a-row style="padding: 16px 16px 0 16px">
			<a-col :span="24" style="display: flex; justify-content: flex-end">
				<a-button
					style="margin-right: 8px"
					:loading="loading"
					@click="handleDownIamges"
				>
					<i class="fa fa-download" />
					<span style="margin-left: 4px; margin-right: 4px">SVG</span>
				</a-button>
				<a-button
					style="margin-right: 8px"
					:loading="loading2"
					@click="handleDownPdf"
				>
					<i class="fa fa-download" />
					<span style="margin-left: 4px; margin-right: 4px">PDF</span>
				</a-button>
				<a-button :icon="h(CloseOutlined)" @click="onClose"></a-button>
			</a-col>
		</a-row>

		<TreeChart
			v-if="stringTree.length > 0"
			ref="TreeChartRef"
			:tree-data="stringTree"
		/>
	</div>
</template>

TreeChart.vue 子级 渲染图

js 复制代码
<script setup>
import G6 from '@antv/g6';
import { useLanguage } from '@/hooks/index';
import jsPDF from 'jspdf';
import { Session } from '@/utils/storage';
const router = useRouter();
const { isCn, isArabic } = useLanguage();

const props = defineProps({
	treeData: { type: Array },
});

const graphContainer = ref(null);
let graph = null;

const isCategory = (cfg) => {
	if (cfg.personalType === 0) {
		return `<div class="square-style-right-top positionGreen">
      ${cfg.name}
    </div>`;
	} else if (cfg.personalType === 1) {
		return `<div class="square-style-right-top positionOrange">
      ${cfg.name}
    </div>`;
	} else {
		return `<div class="square-style-right-top gradualChange">
      ${cfg.name}
    </div>`;
	}
};
const generateSonHtml = (children, hasOuterLine = false) => {
	if (!children) return '';

	let level = 0;

	let html = '';

	function generaHtml(kids, level) {
		if (!kids) return '';
		level++;

		let kidHtml = '';
		kids.forEach((cfg, idx) => {
			kidHtml += `
              <div class="outermost">
                ${
									level === 1 && hasOuterLine
										? `
                    <div class="outermost-line">
                      <div class="top-line"></div>
                      ${
												idx === kids.length - 1
													? ''
													: `<div class="bottom-line"></div>`
											}
                    </div>
                  `
										: ''
								}
                <li class="hierarchy" data-level='${level}' last-index='${
				kids.length - 1
			}' index='${idx}'>
                  <div class="square-wrap">
                    ${
											level > 1
												? `
                        <div class="line">
                          <div class="top-line"></div>
                          ${
														idx === kids.length - 1
															? ''
															: `<div class="bottom-line"></div>`
													}
                        </div>
                      `
												: ''
										}
                    <div class="node square-style">
                      <div class="square-style-left">
                        <div class="square-style-left-top"> ${
													cfg.employeeCount || 0
												} / ${
				(cfg.employeeList && cfg.employeeList.length) || 0
			}</div>
                        <div class="square-style-left-bottom"> ${
													cfg.workPatternName || '--'
												}</div>
                      </div>
                      <div class="square-style-right">
                        ${isCategory(cfg)}
                        ${
													(cfg.employeeList &&
														cfg.employeeList
															.map((item) => {
																if (item.fullName == 'TBC') {
																	return `<div class="square-style-right-bottom" style="color: #409eff">${item.fullName}</div>`;
																} else {
																	return `<div class="square-style-right-bottom" data-id='${
																		item.id
																	}'>${item.fullName}(${
																		item.workPatternName || '--'
																	})</div>`;
																}
															})
															.join('')) ||
													`<div class="square-style-right-bottom">--</div>`
												}
                      </div>
                    </div>
                  </div>
                  ${
										cfg.children && cfg.children.length
											? `<ul class="nodes">${generaHtml(
													cfg.children,
													level,
											  )}</ul>`
											: ''
									}
                </li>
              </div>
      `;
		});

		return kidHtml;
	}

	html += `
      <ul class="nodes">
        ${generaHtml(children, level)}
      </ul>
    `;

	return html;
};

const getTopHtml = (cfg) => {
	return `
    <div class="top-box-html">
      <div class="diamond-style">
        <div class="diamond-top">${cfg.employeeCount || 0} / ${
		cfg.headcount || 0
	}</div>
        <div class="diamond-bottom">${cfg.workPatternName || '--'}</div>
      </div>
      <div class="tox-box-diamond">
        <div class="diamond-title">
          ${
						isCn.value && cfg.nameZh
							? cfg.nameZh
							: isArabic.value && cfg.nameAr
							? cfg.nameAr
							: cfg.nameEn
					}
        </div>
        ${
					(cfg.leaderList &&
						cfg.leaderList
							.map((item) => {
								return `<div class="diamond-name">${item.fullName.replace(
									/(.{19})/g,
									'$1\n',
								)}(${item.workPatternName || '--'})</div>`;
							})
							.join('')) ||
					`<div class="diamond-name">--</div>`
				}
      </div>
    </div>`;
};
const generateSonTopHtml = (children, hasOuterLine = false) => {
	if (!children) return '';

	let level = 0;

	let html = '';

	function generaHtml(kids, level) {
		if (!kids) return '';
		level++;

		let kidHtml = '';
		kids.forEach((cfg, idx) => {
			kidHtml += `
              <div class="outermost">
                ${
									level === 1 && hasOuterLine
										? `
                    <div class="outermost-line">
                      ${
												idx === 0
													? '<div class="top-line0"></div>'
													: `<div class="top-line"></div>`
											}
                      <div class="bottom-line"></div>
                    </div>
                  `
										: ''
								}
                <li class="hierarchy" data-level='${level}' last-index='${
				kids.length - 1
			}' index='${idx}'>
                  <div class="square-wrap">
                    ${
											level > 1
												? `
                        <div class="line">
                          <div class="top-line"></div>
                          ${
														idx === kids.length - 1
															? ''
															: `<div class="bottom-line"></div>`
													}
                        </div>
                      `
												: ''
										}
                    <div class="node square-style">
                      <div class="square-style-left">
                        <div class="square-style-left-top">${
													cfg.employeeCount || 0
												} / ${
				(cfg.employeeList && cfg.employeeList.length) || 0
			}</div>
                        <div class="square-style-left-bottom"> ${
													cfg.workPatternName || '--'
												}</div>
                      </div>
                      <div class="square-style-right">
                        ${isCategory(cfg)}
                        ${
													(cfg.employeeList &&
														cfg.employeeList
															.map((item) => {
																if (item.fullName == 'TBC') {
																	return `<div class="square-style-right-bottom" style="color: #409eff">${item.fullName}</div>`;
																} else {
																	return `<div class="square-style-right-bottom" data-id="${
																		item.id
																	}">${item.fullName}(${
																		item.workPatternName || '--'
																	})</div>`;
																}
															})
															.join('')) ||
													`<div class="square-style-right-bottom">--</div>`
												}
                      </div>
                    </div>
                  </div>
                  ${
										cfg.children && cfg.children.length
											? `<ul class="nodes">${generaHtml(
													cfg.children,
													level,
											  )}</ul>`
											: ''
									}
                </li>
              </div>
      `;
		});

		return kidHtml;
	}

	html += `
      <ul class="nodes">
        ${generaHtml(children, level)}
      </ul>
    `;

	return html;
};

function calculateMaxChildren(data) {
	let maxChildrenCount = 0;

	for (let node of data) {
		let childrenCount = 0;

		// 递归计算子节点的数量
		if (node.children) {
			childrenCount += calculateMaxChildren(node.children);
		}

		// 计算当前节点及其子节点的子节点数量
		childrenCount += node.children ? node.children.length : 0;

		// 更新最大子节点数量
		maxChildrenCount = Math.max(maxChildrenCount, childrenCount);
	}

	return maxChildrenCount;
}
const methodChart = () => {
	G6.registerNode(
		'dom-node',
		{
			draw(cfg, group) {
				const container = document.createElement('div');
				if (cfg.level2 === 0) {
					container.innerHTML = `
              <div class="boxChartTop" id="${cfg.id}">
                ${getTopHtml(cfg)}
								${
									cfg.positionList && cfg.positionList.length > 0
										? `<div class="center-line"></div>`
										: ''
								}
                ${generateSonTopHtml(cfg.positionList, true)}
              </div>`;
				} else {
					container.innerHTML = `
              <div class="boxChart" id="${cfg.id}">
                <div class="circle-wrap">
                  <div class="outermost-line">
                    <div class="bottom-line"></div>
                  </div>
                  <div class="circle-style">${
										isCn.value && cfg.nameZh
											? cfg.nameZh
											: isArabic.value && cfg.nameAr
											? cfg.nameAr
											: cfg.nameEn
									}</div>
                </div>
                ${generateSonHtml(cfg.positionList, true)}
              </div>
            `;
				}
				// 将container插入隐藏容器以计算宽高
				const hiddenContainer = document.getElementById('hiddenContainer');
				hiddenContainer.appendChild(container);
				const width = container.clientWidth;
				const height = container.clientHeight;
				hiddenContainer.removeChild(container);

				const shape = group.addShape('dom', {
					attrs: {
						width,
						height: height + 10,
						x: 0,
						y: 0,
						html: container.innerHTML,
						size: [width, height],
					},
					draggable: true,
				});

				cfg.width = width;
				cfg.height = height;

				return shape;
			},
		},
		'dom',
	);

	G6.registerEdge('flow-line', {
		draw(cfg, group) {
			let startPoint = cfg.startPoint;
			let endPoint = cfg.endPoint;

			// 计算源节点底部中间位置
			const sourceModel = graph.findById(cfg.source).getModel();
			const sourceHeight = sourceModel.height;

			// 目标节点 这块可以优化,看看那位大佬帮忙优化一下
			const targetModel = graph.findById(cfg.target).getModel();
			const targetHeight = targetModel.height;
			const list =
				sourceModel.positionList && sourceModel.positionList.length > 0
					? calculateMaxChildren(sourceModel.positionList)
					: 0;
			console.log('计算', list);
			// 计算父节点底部中间位置
			let sourceX = startPoint.x;
			if (
				sourceModel.level2 === 0 &&
				sourceModel.positionList?.length > 0 &&
				list < 1
			) {
				console.log('1');
				sourceX = startPoint.x - 115;
			} else if (
				sourceModel.level2 === 0 &&
				sourceModel.positionList?.length <= 3 &&
				list <= 3
			) {
				console.log('2');
				sourceX = startPoint.x - 150;
			} else if (
				sourceModel.level2 === 0 &&
				sourceModel.positionList?.length > 3 &&
				list < 3
			) {
				console.log('3');
				sourceX = startPoint.x - 180;
			} else if (
				sourceModel.level2 === 0 &&
				sourceModel.positionList?.length < 4 &&
				list > 3
			) {
				console.log('4');
				sourceX = startPoint.x - list * 35;
			} else if (
				sourceModel.level2 === 0 &&
				sourceModel.positionList?.length === 4 &&
				list === 3
			) {
				console.log('5');
				sourceX = startPoint.x - list * 50;
			} else if (
				sourceModel.level2 === 0 &&
				sourceModel.positionList?.length <= 4 &&
				list >= 4
			) {
				console.log('6');
				sourceX = startPoint.x - list * 50;
			}

			const sourceY = startPoint.y + sourceHeight / 2;

			// 计算目标节点顶部中间位置
			const targetX = endPoint.x;
			const targetY = endPoint.y - targetHeight / 2;

			const isSafari = /^((?!chrome|android).)*safari/i.test(
				navigator.userAgent,
			);

			// 控制连接线的路径,使其从父节点底部中间连接到子节点顶部中间
			const path = [
				['M', sourceX, sourceY - 17],
				['L', sourceX, (sourceY + targetY) / 2],
				['L', targetX, (sourceY + targetY) / 2],
				['L', targetX, targetY],
			];

			// 绘制连接线
			const shape = group.addShape('path', {
				attrs: {
					path: path,
					stroke: '#e07572',
					lineWidth: 1.5,
				},
			});

			return shape;
		},
	});
};

const treeChart = () => {
	methodChart();
	const width = graphContainer.value.scrollWidth || 1740;
	const height = graphContainer.value.scrollHeight || 700;
	graph = new G6.TreeGraph({
		container: graphContainer.value,
		width,
		height,
		renderer: 'svg',
		fitView: true,
		linkCenter: true,
		modes: {
			default: ['drag-canvas', 'zoom-canvas'],
		},
		defaultNode: {
			type: 'dom-node',
		},
		defaultEdge: {
			type: 'flow-line',
			style: {
				stroke: '#e07572',
				lineWidth: 1,
			},
		},
		layout: {
			type: 'compactBox',
			direction: 'TB',
			getId: function getId(d) {
				return d.id;
			},
			getHeight: function getHeight(d) {
				return d.height || 200;
			},
			getWidth: function getWidth(d) {
				return d.width || 16;
			},
			// 每个节点的垂直间隙
			getVGap: function getVGap() {
				return 50;
			},
			// 每个节点的水平间隙
			getHGap: function getHGap() {
				return 70;
			},
		},
	});

	graph.read(props.treeData[0]);
	graph.clear();

	// 在首次渲染后获取节点实际宽高并重新布局
	setTimeout(() => {
		graph.read(props.treeData[0]);
		graph.fitView();
	}, 650);

	if (typeof window !== 'undefined')
		window.onresize = () => {
			if (!graph || graph.get('destroyed')) return;
			if (
				!graphContainer.value ||
				!graphContainer.value.scrollWidth ||
				!graphContainer.value.scrollHeight
			)
				return;
			graph.changeSize(
				graphContainer.value.scrollWidth,
				graphContainer.value.scrollHeight,
			);
		};
};
const context = ref();
const downImage = async () => {
	graph.fitView(); // 确保视图包含所有节点和边
	setTimeout(() => {
		domtoimage
			.toSvg(context.value)
			.then(function (dataUrl) {
				const link = document.createElement('a');
				link.href = dataUrl;
				link.download = `Org Chart-${props.treeData[0].nameEn}`;
				document.body.appendChild(link);
				link.click();
				document.body.removeChild(link);
			})
			.catch(function (error) {
				console.error('oops, something went wrong!', error);
			});
	}, 200);
};

const downPdf = () => {
	graph.fitView(); // 确保视图包含所有节点和边
	const target = context.value;
	console.log('target', target);

	let contentWidth = target.clientWidth; // 获得该容器的宽
	let contentHeight = target.clientHeight; // 获得该容器的高

	target.ownerDocument.defaultView.devicePixelRatio = 1.25;
	target.ownerDocument.defaultView.innerWidth = contentWidth;
	target.ownerDocument.defaultView.innerHeight = contentHeight;

	let opts = {
		scale: 4,
		width: contentWidth,
		height: contentHeight,
		useCORS: true,
		bgcolor: '#fff',
	};
	domtoimage
		.toPng(target, opts)
		.then(function (dataUrl) {
			var img = new Image();
			img.src = dataUrl;
			//一页pdf显示html页面生成的canvas高度;
			var pageHeight = (contentWidth / 592.28) * 841.89;
			//未生成pdf的html页面高度
			var leftHeight = contentHeight;
			//页面偏移
			var position = 0;
			//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
			var imgWidth = 595.28;
			var imgHeight = (592.28 / contentWidth) * contentHeight;

			var pdf = new jsPDF('', 'pt', 'a4');

			//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
			//当内容未超过pdf一页显示的范围,无需分页
			if (leftHeight < pageHeight) {
				pdf.addImage(dataUrl, 'PNG', 0, 0, imgWidth, imgHeight);
			} else {
				while (leftHeight > 0) {
					pdf.addImage(dataUrl, 'PNG', 0, position, imgWidth, imgHeight);
					leftHeight -= pageHeight;
					position -= 841.89;
					//避免添加空白页
					if (leftHeight > 0) {
						pdf.addPage();
					}
				}
			}

			pdf.save(`Org Chart-${props.treeData[0].nameEn}.pdf`);
		})
		.catch(function (error) {
			console.error('oops, something went wrong!', error);
		});
};
onUnmounted(() => {
	// 销毁图实例
	if (graph) {
		graph.clear();
		graph.destroy();
	}
});

const closeChart = () => {
	if (graph) {
		graph.clear();
		graph.destroy();
	}
};

onMounted(() => {
	Session.remove('employeeID');
	Session.remove('employeeParams');
	if (props.treeData && props.treeData.length) {
		treeChart();
	}
});
const clickHandler = (event) => {
	if (event.target.classList.contains('square-style-right-bottom')) {
		var dataId = event.target.getAttribute('data-id');
		if (dataId) {
			Session.set('employeeID', dataId);
			let params = {
				form: 'organization',
				employeeId: dataId,
				to: 'workInfo',
			};
			Session.set('employeeParams', params);
			router.push({ name: 'employDetails' });
		}
	}
};

document.body.addEventListener('click', clickHandler);
onBeforeUnmount(() => {
	document.body.removeEventListener('click', clickHandler);
});

defineExpose({
	treeChart,
	downImage,
	downPdf,
	closeChart,
});
</script>
<template>
	<div id="context" ref="context">
		<div class="use-cases">
			<div class="total-table">
				<div class="lattice">
					<div class="box">{{ $t('employee.organization.expat') }}</div>
					<div class="box">{{ props.treeData[0].expatCount }}</div>
				</div>
				<div class="lattice">
					<div class="box">{{ $t('employee.organization.local') }}</div>
					<div class="box">{{ props.treeData[0].localCount }}</div>
				</div>
				<div class="lattice">
					<div class="box">{{ $t('employee.organization.mixed') }}</div>
					<div class="box">{{ props.treeData[0].mixedCount }}</div>
				</div>
				<div class="lattice total">
					<div class="box">{{ $t('employee.organization.total') }}</div>
					<div class="box">{{ props.treeData[0].headcount }}</div>
				</div>
			</div>
			<div class="org-text">
				<div class="box positionGreen"></div>
				{{ $t('employee.organization.local') }}
				<div class="box positionOrange"></div>
				{{ $t('employee.organization.expat') }}
				<div class="box gradualChange"></div>
				{{ $t('employee.organization.mixed') }}
			</div>
		</div>
		<div id="graphContainer" ref="graphContainer"></div>
		<div
			id="hiddenContainer"
			style="visibility: hidden; position: absolute"
		></div>
	</div>
</template>
<style lang="scss">
.total-table {
	border: 1px solid #dae0e6;
	border-radius: 4px;
	.lattice {
		display: flex;
		color: #66809e;
		border-bottom: 1px solid #dae0e6;
		&:last-child {
			border-bottom: none;
		}
		.box {
			text-align: center;
			padding: 8px 16px;
			width: 80px;
			border-right: 1px solid #dae0e6;
			&:last-child {
				border-right: none;
			}
		}
	}
	.total {
		background-color: #f2f4f7;
	}
}
.use-cases {
	display: flex;
	justify-content: space-between;
	padding: 24px 24px 0px 24px;
	.org-text {
		display: flex;
		align-items: center;
		column-gap: 8px;
		margin-right: 8px;
		color: #66809e;
		.box {
			width: 20px;
			height: 20px;
			border-radius: 4px;
		}
		.positionGreen {
			background-color: #6fc9c2;
		}
		.positionOrange {
			background-color: #f2f09f;
		}
		.gradualChange {
			background: linear-gradient(to right, #f3ef9f, #6ecac1);
		}
	}
}

.boxChart {
	display: flex;
	flex-direction: column;
	align-items: center;
	user-select: none;
	height: 100%;

	.circle-wrap {
		display: flex;
		width: 100%;
		margin-bottom: -10px;
	}

	.circle-style {
		width: 100%;
		height: 80px;
		padding: 8px;
		border-radius: 48%;
		border: 1px solid #242424;
		background-color: #fff2cc;
		text-align: center;
		word-wrap: break-word;
		font-size: 20px;
		font-weight: bold;
		display: flex;
		justify-content: center;
		align-items: center;
	}

	.diamond-style {
		display: flex;
		flex-direction: column;

		.diamond-top {
			border: 1px solid #242424;
			border-right: none;
			width: 50px;
			height: 28px;
			line-height: 28px;
			text-align: center;
		}

		.diamond-bottom {
			border: 1px solid #242424;
			border-right: none;
			width: 50px;
			height: 26px;
			line-height: 28px;
			text-align: center;
		}
	}

	.nodes {
		list-style: none;

		.outermost {
			display: flex;
			overflow: hidden;

			.outermost-line {
				.top-line {
					width: 10px;
					height: 35px;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}

				.bottom-line {
					width: 0;
					height: 5000%;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}
			}
		}

		.hierarchy {
			padding-bottom: 20px;
			overflow: hidden;

			.square-wrap {
				display: flex;
				padding-top: 20px;

				&:last-child {
					margin-bottom: 0;
				}
			}
		}

		.square-wrap {
			.line {
				margin-top: -21px;

				.top-line {
					width: 10px;
					height: 35px;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}

				.bottom-line {
					width: 0;
					height: 5000%;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}
			}
		}

		.nodes {
			padding-left: 60px;
		}
	}

	.square-style {
		display: flex;
		background-color: #fff;

		&-left {
			display: flex;
			flex-direction: column;

			&-top {
				border: 1px solid #242424;
				border-right: none;
				width: 50px;
				padding: 8px;
				text-align: center;
			}

			&-bottom {
				border: 1px solid #242424;
				border-right: none;
				border-top: none;
				width: 50px;
				padding: 8px;
				text-align: center;
			}
		}

		&-right {
			width: 200px;
			height: auto;
			text-align: center;

			&-top {
				border: 1px solid #242424;
				padding: 8px;
			}

			&-bottom {
				border: 1px solid #242424;
				border-top: none;
				padding: 8px 0;
				cursor: pointer;
			}
		}
	}
}

.boxChartTop {
	display: flex;
	flex-direction: column;
	align-items: center;
	user-select: none;
	padding-right: 32px;

	.circle-wrap {
		display: flex;
		width: 100%;
		margin-left: -40px;
	}

	.center-top {
		width: 100%;
		display: flex;
		align-items: center;
	}

	.top-vertical {
		flex: 1;
		width: 0;
		height: 100%;
		border-color: rgba(217, 83, 79, 0.8);
		border-style: solid;
		border-width: 0 0 2px 2px;
		margin-left: 42.8%;
	}

	.diamond-style {
		display: flex;
		flex-direction: column;

		.diamond-top {
			border: 1px solid #242424;
			border-right: none;
			width: 50px;
			height: 28px;
			line-height: 28px;
			text-align: center;
		}

		.diamond-bottom {
			border: 1px solid #242424;
			border-right: none;
			border-top: none;
			width: 50px;
			height: 28px;
			line-height: 28px;
			text-align: center;
		}
	}

	.nodes {
		list-style: none;
		padding-left: 60px !important;

		.outermost {
			display: flex;
			overflow: hidden;

			.outermost-line {
				.top-line {
					width: 10px;
					height: 35px;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}

				.top-line0 {
					width: 10px;
					height: 35px;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 0;
				}

				.bottom-line {
					width: 0;
					height: 5000%;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}
			}
		}

		.hierarchy {
			padding-bottom: 21px;
			overflow: hidden;

			.square-wrap {
				display: flex;
				padding-top: 20px;

				&:last-child {
					margin-bottom: 0;
				}
			}
		}

		.square-wrap {
			.line {
				margin-top: -21px;

				.top-line {
					width: 10px;
					height: 35px;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}

				.bottom-line {
					width: 0;
					height: 5000%;
					border-color: rgba(217, 83, 79, 0.8);
					border-style: solid;
					border-width: 0 0 2px 2px;
				}
			}
		}

		.nodes {
			padding-left: 60px;
			// display: flex;
		}
	}

	.center-line {
		width: 0;
		height: 20px;
		border-color: rgba(217, 83, 79, 0.8);
		border-style: solid;
		border-width: 0 0 2px 2px;
		margin-bottom: -20px;
	}

	.square-style {
		display: flex;
		background-color: #fff;

		&-left {
			display: flex;
			flex-direction: column;

			&-top {
				border: 1px solid #242424;
				border-right: none;
				width: 50px;
				padding: 8px;
				text-align: center;
			}

			&-bottom {
				border: 1px solid #242424;
				border-right: none;
				border-top: none;
				width: 50px;
				padding: 8px;
				text-align: center;
			}
		}

		&-right {
			width: 200px;
			height: auto;
			text-align: center;

			&-top {
				border: 1px solid #242424;
				padding: 8px;
			}

			&-bottom {
				border: 1px solid #242424;
				border-top: none;
				padding: 8px 0;
				cursor: pointer;
			}
		}
	}

	.top-right {
		margin-left: -41px;
	}
}

.top-box-html {
	display: flex;
	width: 100%;
	margin-left: 135px;
	.tox-box-diamond {
		width: 200px;
		height: auto;
		text-align: center;

		.diamond-title {
			border: 1px solid #242424;
			background-color: #4f5b72;
			color: #fff;
			padding: 4px;
			min-height: 28px;
		}

		.diamond-name {
			border: 1px solid #242424;
			border-top: none;
			padding: 4px 0;
			min-height: 28px;
			background-color: #fff;
		}
	}
}

.positionGreen {
	background-color: #6fc9c2;
}

.positionOrange {
	background-color: #f2f09f;
}

.gradualChange {
	background: linear-gradient(to right, #f3ef9f, #6ecac1);
}
</style>

G6不能使用绝对定位,所以处理起来很麻烦。代码写的很繁琐,我已经尽力了。我项目使用了多语言,所以有$t()。如果大家有更好得处理方式可以优化。美少女写这个头发掉了一大把。后面产品这块需要改版,看了大概好像更难,欲哭无泪。。。

大概是这样子,不是最终版本,主要是线得问题。G6不能使用绝对定位就很头大。我想着用纯HTML写但是不能放大缩小然后随便拖。Html 放大也只是把浏览器放大(要禁止)

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794488 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存