国外得组织架构图

不常见得组织架构图,用了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": "[email protected]",
            "updateBy": "[email protected]",
            "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 放大也只是把浏览器放大(要禁止)

相关推荐
森叶几秒前
利用 Chrome devTools Source Override 实现JS逆向破解案例
前端·javascript·chrome devtools
welkin3 分钟前
KMP 个人理解
前端·算法
用户90561493152225 分钟前
Flutter开发入门总结
前端
市民中心的蟋蟀6 分钟前
第四章: 使用订阅来共享模块状态
前端·javascript·react.js
从零开始学安卓7 分钟前
Kotlin(二) 单例的加载
前端·kotlin
橘猫云计算机设计7 分钟前
基于JavaWeb的二手图书交易系统(源码+lw+部署文档+讲解),源码可白嫖!
java·开发语言·前端·毕业设计·php
Soldier338 分钟前
加深前端各方面理解(一)
前端
我的div丢了肿么办8 分钟前
vue3第二次传递数据方法无法获取到最新的值
前端·面试·github
wangpq9 分钟前
微信小程序中封装天爱验证码
前端·vue.js
饮茶三千9 分钟前
基于 Web Components 封装下拉树组件 select-tree
前端·web components