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