|
|
import styles from './index.less';
|
|
|
import React, { useEffect, useState, useRef } from 'react';
|
|
|
import { OrgChartComponent } from '@/components/orgChart';
|
|
|
import * as d3 from 'd3';
|
|
|
import { TopBar } from '../components/topBar';
|
|
|
import ToolBar from '../components/toolBar';
|
|
|
import TimeLine from '../components/timeline';
|
|
|
import DrawerComponents from '../components/drawer';
|
|
|
import OperateDialog from '../components/dialog';
|
|
|
import jsPDF from 'jspdf';
|
|
|
import moment from 'moment';
|
|
|
import qs from 'qs';
|
|
|
import { message, Spin, notification } from 'antd';
|
|
|
import { SmileOutlined } from '@ant-design/icons';
|
|
|
|
|
|
let active = 'top';
|
|
|
let drawerCom = null;
|
|
|
let operateCom = null;
|
|
|
let timeLine = null;
|
|
|
let orgChart = null;
|
|
|
let topbar = null;
|
|
|
|
|
|
export default function companyPage() {
|
|
|
const [data, setData] = useState(null);
|
|
|
let compact = 0;
|
|
|
let expandAll = 0;
|
|
|
|
|
|
const [sliderProgress, setSliderProgress] = useState(50);
|
|
|
let addNodeChildFunc = null;
|
|
|
let topBarSearchRequest = null;
|
|
|
const [hasRight, setHasRight] = useState('');
|
|
|
const [timelineId, setTimelineId] = useState(0);
|
|
|
const infoRef = useRef();
|
|
|
|
|
|
useEffect(() => {
|
|
|
notification.open({
|
|
|
message: '提示',
|
|
|
description:
|
|
|
'组织架构图中编制数和在编数显示初始化需参考文档配置定时任务并执行!!!(编制数默认取本年度最新编制信息,人数统计展示仅限于行政维度)',
|
|
|
icon: <SmileOutlined style={{ color: '#108ee9' }} />,
|
|
|
});
|
|
|
}, []);
|
|
|
useEffect(() => {
|
|
|
infoRef.current = timelineId;
|
|
|
}, [timelineId]);
|
|
|
const [spinning, setSpinning] = useState(false);
|
|
|
|
|
|
// 点击节点
|
|
|
const onNodeClick = (node) => {
|
|
|
if (node.ftype == '2') {
|
|
|
const params = {
|
|
|
rootId: node.id,
|
|
|
fclass: topbar.state.requestData.fclass,
|
|
|
id: infoRef.current,
|
|
|
};
|
|
|
drawerCom.showDrawer(params);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 扩展按钮点击
|
|
|
const onButtonClick = (event, d) => {
|
|
|
if (d.children) {
|
|
|
let idsList = [];
|
|
|
d.children.forEach((item) => {
|
|
|
if (item.data.hasChildren && !item._children) {
|
|
|
idsList.push(item.data.id);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
if (idsList.length == 0) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
let idsStr = idsList.join(',');
|
|
|
let api = '';
|
|
|
if (topBarSearchRequest) {
|
|
|
let request = { ...topBarSearchRequest, ids: idsStr };
|
|
|
api =
|
|
|
'/api/bs/hrmorganization/orgchart/asyncCompanyData' +
|
|
|
qs.stringify(request, { addQueryPrefix: true });
|
|
|
} else {
|
|
|
api =
|
|
|
'/api/bs/hrmorganization/orgchart/asyncCompanyData?fclass=0&fisvitual=0&id=0&root=0&ids=' +
|
|
|
idsStr;
|
|
|
}
|
|
|
fetch(api)
|
|
|
.then((res) => res.json())
|
|
|
.then((data) => {
|
|
|
if (data.data) {
|
|
|
data.data.forEach((item) => {
|
|
|
// window.chart.addNode(item);
|
|
|
orgChart.addNode(item);
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 获取部门图片
|
|
|
function getDepartmentImage(fisvitual) {
|
|
|
return fisvitual == '0' ? `./img/back/level4.png` : `./img/back/level8.png`;
|
|
|
}
|
|
|
|
|
|
// 获取分部图片
|
|
|
function getSubcompanyImage(fisvitual) {
|
|
|
return fisvitual == '0' ? `./img/back/level1.png` : `./img/back/level5.png`;
|
|
|
}
|
|
|
|
|
|
// 获取数据
|
|
|
useEffect(() => {
|
|
|
d3.json(
|
|
|
'/api/bs/hrmorganization/orgchart/companyData?fclass=0&fisvitual=0&hidedept=0&root=0&level=2&id=0',
|
|
|
).then((data) => {
|
|
|
setData(data.data);
|
|
|
setHasRight(data?.hasRight);
|
|
|
});
|
|
|
}, [true]);
|
|
|
|
|
|
// ButtonContent渲染
|
|
|
const buttonContentRender = ({ node, state }) => {
|
|
|
if (node.children) {
|
|
|
return `<div style="border-radius:3px;padding:3px;font-size:10px;margin:auto auto;background-color:#66BAF5"> <div style="margin-top:0px;line-height:1.35;height:11px;font-size:25px; color: #fff;">ˆ</div> </div>`;
|
|
|
} else {
|
|
|
return `<div style="border-radius:3px;padding:3px;font-size:10px;margin:auto auto;background-color:#66BAF5"> <div style="margin-top:0px;line-height:1.35;height:11px;font-size:25px; color: #fff;transform:rotate(180deg)">ˆ</div> </div>`;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 节点宽度渲染
|
|
|
const nodeWidthRender = (d) => {
|
|
|
if (d.data.ftype == 0) {
|
|
|
return 220;
|
|
|
} else if (d.data.ftype == 1) {
|
|
|
return 144;
|
|
|
} else if (d.data.ftype == 2) {
|
|
|
return 144;
|
|
|
}
|
|
|
return 200;
|
|
|
};
|
|
|
|
|
|
const nodeHeightRender = (d) => {
|
|
|
if (d.data.ftype == 0) {
|
|
|
return 100;
|
|
|
} else if (d.data.ftype == 1) {
|
|
|
return 106;
|
|
|
} else if (d.data.ftype == 2) {
|
|
|
return 106;
|
|
|
}
|
|
|
return 120;
|
|
|
};
|
|
|
|
|
|
const nodeContentRender = (d, i, arr, state) => {
|
|
|
let fclass = topbar.state.requestData.fclass;
|
|
|
let statisticsStyle = fclass == 0 ? 'block' : 'none';
|
|
|
if (d.data.ftype == 0) {
|
|
|
return `<div>
|
|
|
<div style="display: inline-block; text-align: center; margin-left: 5px;">
|
|
|
<div style="
|
|
|
font-size: 24px;
|
|
|
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
|
|
|
font-weight: bold;
|
|
|
color: #000000;
|
|
|
line-height: 28px;
|
|
|
letter-spacing: 1px;
|
|
|
margin-top: 10px;
|
|
|
">${d.data.fname}</div>
|
|
|
</div>
|
|
|
</div>`;
|
|
|
} else if (d.data.ftype == 1) {
|
|
|
return `<div style='position:absolute;height:100%'>
|
|
|
<img style='width:144px;height:106px' src="${getSubcompanyImage(
|
|
|
d.data.fisvitual,
|
|
|
)}"/>
|
|
|
</div>
|
|
|
<div style="width: 144px;height: 80px;top: 35px;position: relative;font-weight: 400;font-size: 14px;
|
|
|
font-family: Microsoft YaHei-Regular, Microsoft YaHei;color: #333333;text-align: center;">
|
|
|
<div title=${
|
|
|
d.data.fname
|
|
|
} style="width: 110px;margin: 0 auto;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;-o-text-overflow:ellipsis;
|
|
|
line-height: 18px;word-break: break-all;">${d.data.fname}</div>
|
|
|
<div style="display: ${statisticsStyle}">
|
|
|
<span style="color:red">${d.data.staffNum}</span> /
|
|
|
<span style="color:green">${d.data.onJobNum}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
} else if (d.data.ftype == 2) {
|
|
|
return `
|
|
|
<div style="width: 100%; height: 100%; background-size: 100% 100%;">
|
|
|
<div style='position:absolute;height:100%'>
|
|
|
<img style='width:144px;height:106px' src="${getDepartmentImage(
|
|
|
d.data.fisvitual,
|
|
|
)}"/>
|
|
|
</div>
|
|
|
<div style="width: 144px;height: 80px;top: 35px;position: relative;font-weight: 400;font-size: 14px;
|
|
|
font-family: Microsoft YaHei-Regular, Microsoft YaHei;color: #333333;text-align: center;">
|
|
|
<div style="width: 110px;margin: 0 auto;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;
|
|
|
line-height: 18px;word-break: break-all;">${d.data.fname}</div>
|
|
|
<div style="display: ${statisticsStyle}">
|
|
|
<span style="color:red">${d.data.staffNum}</span> /
|
|
|
<span style="color:green">${d.data.onJobNum}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
return `<div>${d.data.fname}</div>`;
|
|
|
};
|
|
|
|
|
|
const handleTopLayoutClick = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
orgChart &&
|
|
|
orgChart
|
|
|
.layout('top')
|
|
|
.setCentered(orgChart.getChartState().root.id)
|
|
|
.render();
|
|
|
active = 'top';
|
|
|
};
|
|
|
|
|
|
const handleLeftLayoutClick = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
orgChart &&
|
|
|
orgChart
|
|
|
.layout('left')
|
|
|
.setCentered(orgChart.getChartState().root.id)
|
|
|
.render();
|
|
|
active = 'left';
|
|
|
};
|
|
|
|
|
|
const handleFullscreen = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
orgChart && orgChart.fullscreen('body');
|
|
|
};
|
|
|
|
|
|
const handleFit = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
orgChart && orgChart.fit();
|
|
|
};
|
|
|
|
|
|
const handleFolderAddNode = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
operateCom &&
|
|
|
operateCom.showOperate(topbar.state.requestData.root, '新增节点', 1);
|
|
|
};
|
|
|
|
|
|
const addFolderNode = (id) => {
|
|
|
orgChart &&
|
|
|
orgChart.addNode({
|
|
|
id: 'd_10091',
|
|
|
fname: '测试增加节点',
|
|
|
parentId: 's_10',
|
|
|
ftype: '2',
|
|
|
});
|
|
|
};
|
|
|
|
|
|
const handleDeleteNode = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
operateCom &&
|
|
|
operateCom.showOperate(topbar.state.requestData.root, '删除节点', 2);
|
|
|
};
|
|
|
|
|
|
const deleteNode = (id) => {
|
|
|
orgChart && orgChart.removeNode('d_10091');
|
|
|
};
|
|
|
|
|
|
const handleCompact = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
orgChart &&
|
|
|
orgChart
|
|
|
.compact(!!(compact++ % 2))
|
|
|
.render()
|
|
|
.fit();
|
|
|
};
|
|
|
|
|
|
const handleExpandAll = (progressBtn) => {
|
|
|
progressBtn.current.style.top = 50 + 'px';
|
|
|
orgChart && expandAll++ % 2 ? orgChart.collapseAll() : orgChart.expandAll();
|
|
|
};
|
|
|
|
|
|
const handleZoomIn = (progressBtn) => {
|
|
|
if (progressBtn) {
|
|
|
let top = parseInt(progressBtn.current.style.top) - 10;
|
|
|
if (top >= 0) {
|
|
|
progressBtn.current.style.top = top + 'px';
|
|
|
} else {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
orgChart && orgChart.zoomIn();
|
|
|
};
|
|
|
|
|
|
const handleZoomOut = (progressBtn) => {
|
|
|
if (progressBtn) {
|
|
|
let top = parseInt(progressBtn.current.style.top) + 10;
|
|
|
if (top <= 100) {
|
|
|
progressBtn.current.style.top = top + 'px';
|
|
|
} else {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
orgChart && orgChart.zoomOut();
|
|
|
};
|
|
|
|
|
|
const handleZoomBehavior = (value) => {
|
|
|
orgChart && orgChart.zoomBehavior(value - 50);
|
|
|
};
|
|
|
|
|
|
function downloadPdf(chart) {
|
|
|
chart.exportImg({
|
|
|
save: false,
|
|
|
full: true,
|
|
|
onLoad: (base64) => {
|
|
|
var pdf = new jsPDF();
|
|
|
var img = new Image();
|
|
|
img.src = base64;
|
|
|
img.onload = function () {
|
|
|
pdf.addImage(
|
|
|
img,
|
|
|
'JPEG',
|
|
|
5,
|
|
|
5,
|
|
|
595 / 3,
|
|
|
((img.height / img.width) * 595) / 3,
|
|
|
);
|
|
|
pdf.save('chart.pdf');
|
|
|
};
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 导出
|
|
|
* @param {*} type
|
|
|
*/
|
|
|
const handleExport = (type) => {
|
|
|
if (type == 'png') {
|
|
|
orgChart && orgChart.exportImg({ full: true });
|
|
|
} else {
|
|
|
orgChart && downloadPdf(orgChart);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 时间轴点击
|
|
|
* @param {*} timeline
|
|
|
*/
|
|
|
const timeLineSearch = (timeline) => {
|
|
|
setTimelineId(timeline.id);
|
|
|
const fclass = topbar.state.requestData.fclass;
|
|
|
const resetParams = {
|
|
|
root: undefined,
|
|
|
level: '2',
|
|
|
fisvitual: '0',
|
|
|
hidedept: '0',
|
|
|
};
|
|
|
topbar.handleFormChange({ ...resetParams });
|
|
|
topbar.getNodeTreeNode(
|
|
|
`/api/bs/hrmorganization/orgchart/getSubCompanyTree?fclass=${fclass}&id=${timeline.id}`,
|
|
|
false,
|
|
|
);
|
|
|
let requestData = { fclass: fclass, id: timeline.id, ...resetParams };
|
|
|
handleSearch(requestData, false);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 查询
|
|
|
* @param {*} requestData
|
|
|
*/
|
|
|
const handleSearch = (requestData, cache = true) => {
|
|
|
setSpinning(true);
|
|
|
if (cache) {
|
|
|
requestData = { ...requestData, id: infoRef.current };
|
|
|
}
|
|
|
topBarSearchRequest = requestData;
|
|
|
let api =
|
|
|
'/api/bs/hrmorganization/orgchart/companyData' +
|
|
|
qs.stringify(requestData, { addQueryPrefix: true });
|
|
|
fetch(api)
|
|
|
.then((res) => res.json())
|
|
|
.then((data) => {
|
|
|
if (data.data) {
|
|
|
if (!data.data.length) {
|
|
|
setData([{}]);
|
|
|
message.warning('暂无数据');
|
|
|
} else {
|
|
|
setData(data?.data);
|
|
|
}
|
|
|
}
|
|
|
setTimeout(function () {
|
|
|
setSpinning(false);
|
|
|
}, 200);
|
|
|
});
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 切换维度
|
|
|
* @param {*} requestData
|
|
|
*/
|
|
|
const handleChange = (requestData) => {
|
|
|
setTimelineId(0);
|
|
|
timeLine.searchTimeLines(
|
|
|
`/api/bs/hrmorganization/orgchart/timeLines?fclass=${requestData.fclass}`,
|
|
|
);
|
|
|
requestData = { ...requestData, id: 0 };
|
|
|
handleSearch(requestData, false);
|
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (active == 'left') {
|
|
|
orgChart &&
|
|
|
orgChart
|
|
|
.setCentered(orgChart.getChartState().root?.id)
|
|
|
.layout('left')
|
|
|
.render();
|
|
|
} else {
|
|
|
orgChart &&
|
|
|
orgChart
|
|
|
.setCentered(orgChart.getChartState().root?.id)
|
|
|
.layout('top')
|
|
|
.render();
|
|
|
}
|
|
|
}, [data]);
|
|
|
|
|
|
if (hasRight === false) {
|
|
|
return (
|
|
|
<div style={{ width: '100%', top: '40%', position: 'absolute' }}>
|
|
|
<img
|
|
|
style={{ display: 'block', margin: '0 auto' }}
|
|
|
src="./img/permission.png"
|
|
|
/>
|
|
|
<p style={{ textAlign: 'center' }}>对不起,您暂时没有权限!</p>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
hasRight && (
|
|
|
<div className={styles.contentWrapper}>
|
|
|
<TopBar
|
|
|
ref={(r) => (topbar = r)}
|
|
|
onExport={(type) => {
|
|
|
handleExport(type);
|
|
|
}}
|
|
|
onSearch={(requestData) => {
|
|
|
handleSearch(requestData);
|
|
|
}}
|
|
|
changeFclass={(requestData) => {
|
|
|
handleChange(requestData);
|
|
|
}}
|
|
|
type="company"
|
|
|
url="/api/bs/hrmorganization/orgchart/getCondition?fclass=0&type=company&id=0"
|
|
|
/>
|
|
|
<ToolBar
|
|
|
onTopLayoutClick={(progressBtn) => handleTopLayoutClick(progressBtn)}
|
|
|
onLeftLayoutClick={(progressBtn) =>
|
|
|
handleLeftLayoutClick(progressBtn)
|
|
|
}
|
|
|
onFullscreen={(progressBtn) => handleFullscreen(progressBtn)}
|
|
|
onFit={(progressBtn) => handleFit(progressBtn)}
|
|
|
onFolderAddNode={(progressBtn) => handleFolderAddNode(progressBtn)}
|
|
|
onDeleteNode={(progressBtn) => handleDeleteNode(progressBtn)}
|
|
|
onCompact={(progressBtn) => handleCompact(progressBtn)}
|
|
|
onExpandAll={(progressBtn) => handleExpandAll(progressBtn)}
|
|
|
onZoomOut={(progressBtn) => handleZoomOut(progressBtn)}
|
|
|
onZoomIn={(progressBtn) => handleZoomIn(progressBtn)}
|
|
|
onZoomBehavior={(value) => handleZoomBehavior(value)}
|
|
|
/>
|
|
|
<TimeLine
|
|
|
ref={(r) => (timeLine = r)}
|
|
|
onClick={(timeline) => {
|
|
|
timeLineSearch(timeline);
|
|
|
}}
|
|
|
url={'/api/bs/hrmorganization/orgchart/timeLines?fclass=0'}
|
|
|
/>
|
|
|
<Spin size="large" spinning={spinning}>
|
|
|
<OrgChartComponent
|
|
|
setChart={(chart) => (orgChart = chart)}
|
|
|
setClick={(click) => (addNodeChildFunc = click)}
|
|
|
onNodeClick={onNodeClick}
|
|
|
data={data}
|
|
|
onButtonClick={onButtonClick}
|
|
|
buttonContent={buttonContentRender}
|
|
|
nodeWidth={nodeWidthRender}
|
|
|
nodeHeight={nodeHeightRender}
|
|
|
nodeContent={nodeContentRender}
|
|
|
/>
|
|
|
</Spin>
|
|
|
<DrawerComponents ref={(r) => (drawerCom = r)} />
|
|
|
<OperateDialog
|
|
|
ref={(r) => (operateCom = r)}
|
|
|
addFolderNode={addFolderNode}
|
|
|
deleteNode={deleteNode}
|
|
|
/>
|
|
|
</div>
|
|
|
)
|
|
|
);
|
|
|
}
|