组织结构图v1.0

feature/fj
MustangDeng 3 years ago
commit 6e668c9d89

@ -0,0 +1,16 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

20
.gitignore vendored

@ -0,0 +1,20 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/npm-debug.log*
/yarn-error.log
/yarn.lock
/package-lock.json
# production
/dist
# misc
.DS_Store
# umi
/src/.umi
/src/.umi-production
/src/.umi-test
/.env.local

@ -0,0 +1,8 @@
**/*.md
**/*.svg
**/*.ejs
**/*.html
package.json
.umi
.umi-production
.umi-test

@ -0,0 +1,11 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

@ -0,0 +1,25 @@
import { defineConfig } from 'umi';
export default defineConfig({
hash: true,
history: { type: 'hash'},
base: "/spa/orgChart/",
// exportStatic: {},
publicPath: './',
nodeModulesTransform: {
type: 'none',
},
routes: [
{ path: '/user', component: '@/pages/user' },
{ path: '/company', component: '@/pages/company' }
],
fastRefresh: {},
antd: {},
proxy: {
"/api": { // 标识需要进行转换的请求的url
"target": "http://localhost:18089/api", // 服务端域名
"changeOrigin": true, // 允许域名进行转换
"pathRewrite": { "^/api": ''} // 将请求url里的ci去掉
}
}
});

@ -0,0 +1,15 @@
# umi project
## Getting Started
Install dependencies,
```bash
$ yarn
```
Start the dev server,
```bash
$ yarn start
```

@ -0,0 +1,158 @@
export default {
'GET /company/data': [
{
"id": 1,
"fname": "集团公司",
"ftype": 0,
"parentId": null
},
{
"id": 2,
"fname": "HRSSC共享服务中心",
"ftype": 1,
"parentId": 1
},
{
"id": 3,
"fname": "事业部A",
"ftype": 1,
"parentId": 1
},
{
"id": 4,
"fname": "苏州分公司",
"ftype": 1,
"parentId": 1
},
{
"id": 5,
"fname": "人力资源部经理",
"ftype": 2,
"parentId": 2
},
{
"id": 6,
"fname": "招聘组",
"ftype": 2,
"parentId": 3
},
{
"id": 7,
"fname": "薪酬核算组",
"ftype": 2,
"parentId": 4
},
{
"id": 8,
"fname": "人事服务组",
"ftype": 2,
"parentId": 2
},
{
"id": 9,
"fname": "培训组",
"ftype": 2,
"parentId": 2
},
{
"id": 10,
"fname": "员工关系组",
"ftype": 2,
"parentId": 2
},
{
"id": 11,
"fname": "服务管理组",
"ftype": 2,
"parentId": 2
},
{
"id": 12,
"fname": "信息与数据组",
"ftype": 2,
"parentId": 2
}
],
'GET /user/data': [
{
"id": 1,
"parentId": null,
"ftype": 0,
"fname": "维森集团",
"fleadername": "杨文元",
"fleaderimg": "./img/avator.png",
"fleaderjob": "董事长",
"fplan": 1000,
"fonjob": 987
},
{
"id": 2,
"parentId": 1,
"ftype": 1,
"fname": "南京分公司",
"fleadername": "杨文元",
"fleaderimg": "./img/avator.png",
"fleaderjob": "总经理",
"fplan": 300,
"fonjob": 287
},
{
"id": 3,
"parentId": 1,
"ftype": 1,
"fname": "南京分公司",
"fleadername": "杨文元",
"fleaderimg": "./img/avator.png",
"fleaderjob": "总经理",
"fplan": 300,
"fonjob": 287
},
{
"id": 4,
"parentId": 1,
"ftype": 1,
"fname": "南京分公司",
"fleadername": "杨文元",
"fleaderimg": "./img/avator.png",
"fleaderjob": "总经理",
"fplan": 300,
"fonjob": 287
},
{
"id": 5,
"parentId": 2,
"ftype": 2,
"fname": "销售部",
"fleadername": "杨文元",
"fleaderimg": "./img/avator.png",
"fleaderjob": "部长",
"fplan": 200,
"fonjob": 200
},
{
"id": 6,
"parentId": 5,
"ftype": 3,
"fname": "销售",
"fleadername": null,
"fleaderimg": null,
"fleaderjob": null,
"fplan": 200,
"fonjob": 200
},
{
"id": 7,
"parentId": 6,
"ftype": 4,
"fname": "杨文元",
"fleadername": "杨文元",
"department": "销售部",
"fleaderimg": "./img/avator.png",
"fleaderjob": "销售",
"mobile": "13989058743",
"address": "秦淮区新街口12-201",
"fplan": 200,
"fonjob": 200
}
]
}

@ -0,0 +1,44 @@
{
"private": true,
"scripts": {
"start": "umi dev",
"build": "umi build",
"postinstall": "umi generate tmp",
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
"test": "umi-test",
"test:coverage": "umi-test --coverage"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,less,md,json}": [
"prettier --write"
],
"*.ts?(x)": [
"prettier --parser=typescript --write"
]
},
"dependencies": {
"@ant-design/pro-layout": "^6.5.0",
"@types/d3": "^7.4.0",
"d3": "7.4.4",
"d3-org-chart": "2.6.0",
"jspdf": "^2.5.1",
"moment": "^2.29.3",
"qs": "^6.11.0",
"react": "17.x",
"react-dom": "17.x",
"umi": "^3.5.26"
},
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.5.26",
"lint-staged": "^10.0.7",
"prettier": "^2.2.0",
"typescript": "^4.1.2",
"yorkie": "^2.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@ -0,0 +1,58 @@
import React, { useLayoutEffect, useRef, useEffect } from 'react';
import { OrgChart } from 'd3-org-chart';
import * as d3 from 'd3';
export const OrgChartComponent = (props, ref) => {
const d3Container = useRef(null);
let chart = null;
function addNode(node) {
chart.addNode(node);
}
props.setClick(addNode);
// We need to manipulate DOM
useLayoutEffect(() => {
if (props.data && d3Container.current) {
if (!chart) {
chart = new OrgChart();
}
props.setChart(chart)
try {
chart
.container(d3Container.current)
.data(props.data)
.nodeWidth(props.nodeWidth)
.nodeHeight(props.nodeHeight)
.layout("left")
.linkUpdate(function(d, i, arr) {
d3.select(this)
.attr("stroke", "#66BAF5")
.attr("stroke-width", 1)
.style("stroke-dasharray", ("3, 3"))
})
.onNodeClick((d, i, arr) => {
console.log(d, 'Id of clicked node ');
props.onNodeClick(d);
})
.buttonContent(props.buttonContent)
.nodeContent(props.nodeContent)
.render();
chart.expandAll()
} catch(err) {
console.log(err);
}
}
}, [props.data, d3Container.current]);
return (
<div>
<div ref={d3Container} />
</div>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

@ -0,0 +1,78 @@
import React from 'react'
import add from './img/add.png'
import decrease from './img/decrease.png'
import styles from './index.less';
import top from './img/top.png';
import left from './img/left.png';
import topActive from './img/top_active.png'
import leftActive from './img/left_active.png'
export default class ToolBar extends React.Component {
progressBtn = React.createRef()
start = false;
clientY = 0
originalY = 0
top = 50
constructor(props) {
super(props);
this.state = {
toolActive: "left"
}
}
handleMouseDown(e) {
this.clientY = e.clientY
this.originalY = e.clientY
this.top = parseInt(this.progressBtn.current.style.top)
this.start = true;
}
handleMouseMove(e) {
if(this.start && e.clientY - this.clientY > 10) {
this.props.onZoomOut()
this.clientY = e.clientY
} else if(this.start && e.clientY - this.clientY < - 10) {
this.props.onZoomIn()
this.clientY = e.clientY
}
if(this.start) {
let offset = e.clientY - this.originalY
console.log("offset:", offset);
if((this.top + offset) < 0) {
offset = 0 - this.top
}
if((this.top + offset) > 100) {
offset = 100 - this.top
}
console.log("(this.top + offset)", (this.top + offset))
this.progressBtn.current.style.top = (this.top + offset) + "px";
}
}
handleMouseUp(e) {
this.start = false;
console.log("this.start:", this.start)
}
render() {
return (
<div className={styles.toolbarWrapper}>
<img src={add} style={{cursor: 'pointer'}} onClick={() => {this.props.onZoomIn(this.progressBtn)}}/>
<div className={styles.progressWrapper}>
<div className={styles.progressBtn} style={{top: 50}} ref={this.progressBtn}
onMouseDown={(e) => {this.handleMouseDown(e)}}
onMouseMove={(e) => {this.handleMouseMove(e)}}
onMouseUp={(e) => {this.handleMouseUp(e)}}
onMouseLeave={(e) => {this.handleMouseUp(e)}}
></div>
<div className={styles.progressLine}></div>
</div>
<img src={decrease} style={{cursor: 'pointer'}} onClick={() => {this.props.onZoomOut(this.progressBtn)}}/>
<img className={styles.toolBarItem} src={this.state.toolActive == 'top'? topActive : top} onClick={() => {this.setState({toolActive: "top"}); this.props.onTopLayoutClick(this.progressBtn)}}/>
<img className={styles.toolBarItem} src={this.state.toolActive == 'left'? leftActive : left} onClick={() => {this.setState({toolActive: "left"}); this.props.onLeftLayoutClick(this.progressBtn)}}/>
</div>
)
}
}

@ -0,0 +1,38 @@
.toolbarWrapper {
width: 68px;
position: fixed;
right: 10px;
z-index: 100;
background: #fff;
border-radius: 20px;
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
.progressWrapper {
position: relative;
padding-top: 5px;
padding-bottom: 5px;
margin-left: 20px;
.progressLine {
height: 100px;
width: 0px;
border-left: 2px solid #C9C9C9;
margin-left: 12px;
}
.progressBtn {
width: 16px;
height: 9px;
background-color: #C9C9C9;
position: absolute;
left: 5px;
top: 0px;
}
}
.toolBarItem {
display: block;
margin-top: 20px;
margin-bottom: 20px;
margin-left: 20px;
cursor: pointer;
}
}

@ -0,0 +1,123 @@
import React from 'react'
import style from './index.less'
import { DatePicker, Select, Button, Checkbox, Row, Col, Dropdown, Menu, } from 'antd'
const { Option } = Select;
import moment from 'moment';
export class TopBar extends React.Component {
constructor(props) {
super(props)
this.state = {
fclasslist: [],
companylist: [],
requestData: {
date: moment(new Date()).format("YYYY-MM-DD"),
fclass: "0",
root: "0",
level: "3",
fisvitual: "0"
}
}
}
handleFormChange(payload) {
let requestData = {...this.state.requestData, ...payload}
this.setState({requestData})
}
handleExportMenuClick(e) {
this.props.onExport(e.key == '1' ? "png" : "pdf")
}
handleExportButtonClick() {
this.props.onExport("png")
}
componentDidMount() {
fetch(this.props.url).then(res => res.json()).then(data => {
this.setState({
fclasslist: data.fclasslist,
companylist: data.companylist
})
})
}
menu = (
<Menu
onClick={this.handleExportMenuClick.bind(this)}
items={[
{
label: '导出图片',
key: '1',
},
{
label: '导出PDF',
key: '2',
},
]}
/>
);
render() {
return (
<div className={style.topbarWrapper}>
<Row>
<Col span={4}>
数据日期<DatePicker placeholder="请选择日期" style={{ width: 120 }} defaultValue={moment(new Date())} value={moment(this.state.requestData.date)} onChange={(value) => this.handleFormChange({date: value && value != "" ? value.format("YYYY-MM-DD") : ""})} />
</Col>
<Col span={4}>
维度<Select defaultValue="0" style={{ width: 120 }} value={this.state.requestData.fclass} onChange={(value) => this.handleFormChange({fclass: value})}>
{
this.state.fclasslist.map(item => (<Option value={item.id}>{item.companyname}</Option>))
}
</Select>
</Col>
<Col span={4}>
根节点<Select
showSearch
filterOption={(input, option) => (option?.children ).includes(input)}
defaultValue="0" style={{ width: 120 }} value={this.state.requestData.root} onChange={(value) => this.handleFormChange({root: value})}>
{
this.state.companylist.map(item => (<Option value={item.id}>{item.fname}</Option>))
}
</Select>
</Col>
<Col span={4}>
显示层级<Select defaultValue="3" style={{ width: 120 }} value={this.state.requestData.level} onChange={(value) => this.handleFormChange({level: value})}>
<Option value="1">一级</Option>
<Option value="2">二级</Option>
<Option value="3">三级</Option>
<Option value="4">四级</Option>
<Option value="5">五级</Option>
<Option value="6">六级</Option>
<Option value="7">七级</Option>
<Option value="8">八级</Option>
</Select>
</Col>
<Col span={4}>
<Checkbox style={{ marginTop: "5px" }} onChange={(e) => this.handleFormChange({fisvitual: e.target.checked ? "1": "0"})}>显示虚拟组织</Checkbox>
</Col>
<Col span={4}>
<Button type="primary" style={{ marginRight: "10px" }} onClick={() => {this.props.onSearch(this.state.requestData)}}>查询</Button>
<Dropdown overlay={this.menu}>
<Button onClick={this.handleExportButtonClick.bind(this)}>
导出
</Button>
</Dropdown>
</Col>
</Row>
</div>
)
}
}

@ -0,0 +1,9 @@
.topbarWrapper {
margin: 20px;
background: #FFFFFF;
// background: red;
padding: 20px 26px 20px 26px;
box-shadow: 0px 5px 5px #e2e2e2;
border-radius: 0px 0px 0px 0px;
vertical-align: middle;
}

@ -0,0 +1,5 @@
import React from 'react'
export default () => {
return <div>Hello</div>
}

@ -0,0 +1,252 @@
import styles from './index.less';
import React, { useEffect, useState } from "react";
import {OrgChartComponent} from '@/components/orgChart'
import * as d3 from 'd3';
import { TopBar } from '../components/topBar';
import ToolBar from '../components/toolBar';
import jsPDF from 'jspdf'
import moment from 'moment';
import qs from 'qs';
export default function companyPage() {
const [data, setData] = useState(null);
const [sliderProgress, setSliderProgress] = useState(50);
let addNodeChildFunc = null;
let orgChart = null;
//
function onNodeClick(nodeId) {
// alert('clicked ' + nodeId);
}
//
function getDepartmentImage() {
let index = Math.floor(Math.random() * 8) + 1
return `./img/department/${index}.png`
}
//
function getSubcompanyImage() {
let index = Math.floor(Math.random() * 3) + 1
return `./img/subcompany/${index}.png`
}
//
useEffect(() => {
d3.json(
// "/company/data"
"/api/bs/hrmorganization/orgchart/companyData?fclass=0&root=0&date=" + moment(new Date()).format("YYYY-MM-DD")
).then(data => {
setData(data.data);
});
}, [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.2;height:11px;font-size:25px; color: #fff;">ˆ</div> </div>`;
} else {
return `<div style="border-radius:3px;padding:1px;font-size:10px;margin:auto auto;background-color:#66BAF5"> <div style="margin-top:-22px;margin-bottom: 3px;font-size:25px; color: #fff;">ˬ</div> </div>`
}
}
//
const nodeWidthRender = d =>
{
if(d.data.ftype == 0) {
return 220;
} else if(d.data.ftype == 1) {
return 160;
} 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 160;
}else if(d.data.ftype == 2) {
return 56;
}
return 120;
}
const nodeContentRender = (d, i, arr, state) => {
if(d.data.ftype == 0) {
return `<div>
<div style="display: inline-block; vertical-align: top;">
<img src="./img/company.png" />
</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 style="
font-size: 16px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #D0D0D0;
line-height: 19px;
margin-top: 10px;
">COMPANY_GROUP</div>
</div>
</div>`
} else if(d.data.ftype == 1) {
return `<div>
<div style="width: 85px; height: 85px; border: 1px solid #66BAF5; border-radius: 50%;text-align: center; line-height: 85px; margin: 0 auto;">
<img src="${getSubcompanyImage()}" />
</div>
<div style="width: 136px; border: 1px solid #66BAF5; margin: 10px auto 0px; border-radius: 23px; text-align: center;
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
line-height: 18px;
padding: 15px 10px;
">
${d.data.fname}
</div>
</div>`
} else if(d.data.ftype == 2) {
return `
<div style="width: 100%; height: 100%; background: url('./img/company_job_label.png');background-size: 100% 100%;">
<div style="padding-left: 8px; padding-top: 23px;">
<img src="${getDepartmentImage()}"/>
<span style="
margin-left: 3px;
font-size: 12px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
">${d.data.fname}</span>
</div>
</div>
`
}
return `<div>${d.data.fname}</div>`
}
// tool bar start
const handleTopLayoutClick = (progressBtn) => {
progressBtn.current.style.top= 50 + "px";
orgChart && orgChart.layout('top').render().fit();
}
const handleLeftLayoutClick = (progressBtn) => {
progressBtn.current.style.top= 50 + "px";
orgChart && orgChart.layout('left').render().fit();
}
const handleZoomIn = (progressBtn) => {
if(progressBtn) {
let top = (parseInt(progressBtn.current.style.top) - 10)
if(top <= 0) {
top = 30;
}
progressBtn.current.style.top = top + "px";
}
orgChart && orgChart.zoomIn();
}
const handleZoomOut = (progressBtn) => {
if(progressBtn) {
let top = (parseInt(progressBtn.current.style.top) + 10)
if(top >= 100) {
top = 70;
}
progressBtn.current.style.top = top + "px";
}
orgChart && orgChart.zoomOut();
}
const handleZoomBehavior = (value) => {
orgChart && orgChart.zoomBehavior(value - 50);
}
// tool bar end
// top bar start
function downloadPdf(chart) {
chart.exportImg({
save: false,
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');
};
},
});
}
const handleExport = (type) => {
if(type == "png") {
orgChart && orgChart.exportImg({full:true});
} else {
orgChart && downloadPdf(orgChart)
}
}
const handleSearch = (requestData) => {
let api = "/api/bs/hrmorganization/orgchartt/companyData" + qs.stringify(requestData, {addQueryPrefix: true})
fetch(api).then(res => res.json()).then(data => {
setData(data.data);
})
}
// top bar end
return (
<div className={styles.contentWrapper}>
<TopBar
onExport={(type) => {handleExport(type)}}
onSearch={(requestData) => {handleSearch(requestData)}}
url="/bs/hrmorganization/orgchart/jcl/orgchart/getCondition?type=company"
/>
<ToolBar
onTopLayoutClick={(progressBtn) => handleTopLayoutClick(progressBtn)}
onLeftLayoutClick={(progressBtn) => handleLeftLayoutClick(progressBtn)}
onZoomOut={(progressBtn) => handleZoomOut(progressBtn)}
onZoomIn={(progressBtn) => handleZoomIn(progressBtn)}
onZoomBehavior={(value) => handleZoomBehavior(value)}
/>
<OrgChartComponent
setChart={(chart) => orgChart = chart}
setClick={click => (addNodeChildFunc = click)}
onNodeClick={onNodeClick}
data={data}
buttonContent={
buttonContentRender
}
nodeWidth={nodeWidthRender}
nodeHeight={nodeHeightRender}
nodeContent={nodeContentRender}
/>
</div>
);
}

@ -0,0 +1,11 @@
.title {
background: rgb(121, 242, 157);
}
.wrapper {
background: #F7F9FD;
}
.contentWrapper {
background-color: #F7F9FD;
}

@ -0,0 +1,9 @@
import styles from './index.less';
export default function IndexPage() {
return (
<div className={styles.wrapper}>
<h1 className={styles.title}>Page index</h1>
</div>
);
}

@ -0,0 +1,304 @@
import styles from './index.less';
import React, { useEffect, useState } from "react";
import {OrgChartComponent} from '@/components/orgChart'
import * as d3 from 'd3';
import { TopBar } from '../components/topBar';
import ToolBar from '../components/toolBar';
import moment from "moment";
import qs from 'qs';
export default function userPage() {
const [data, setData] = useState(null);
const [progressTop, setProgressTop] = useState(50);
let addNodeChildFunc = null;
let orgChart = null;
let progressBtnRef = null;
//
function onNodeClick(nodeId) {
// alert('clicked ' + nodeId);
}
//
function getDepartmentImage() {
let index = Math.floor(Math.random() * 8) + 1
return `./img/department/${index}.png`
}
//
function getSubcompanyImage() {
let index = Math.floor(Math.random() * 3) + 1
return `./img/subcompany/${index}.png`
}
//
useEffect(() => {
d3.json(
// "/user/data"
"/api/bs/hrmorganization/orgchart/userData?fclass=0&root=0&date=" + moment(new Date()).format("YYYY-MM-DD")
).then(data => {
setData(data.data);
});
}, [true]);
// ButtonContent
const buttonContentRender = ({ node, state }) => {
return `
<div style="margin-left: 16px; margin-top: 10px;">
<img src="./img/button_content.png" />
</div>
`
}
//
const nodeWidthRender = d =>
{
return 280;
}
const nodeHeightRender = d => {
return 160;
}
// tool bar start
const handleTopLayoutClick = (progressBtn) => {
progressBtn.current.style.top= 50 + "px";
orgChart && orgChart.layout('top').render().fit();
}
const handleLeftLayoutClick = (progressBtn) => {
progressBtn.current.style.top= 50 + "px";
orgChart && orgChart.layout('left').render().fit();
}
const handleZoomIn = (progressBtn) => {
if(progressBtn) {
let top = (parseInt(progressBtn.current.style.top) - 10)
if(top <= 0) {
top = 30;
}
progressBtn.current.style.top = top + "px";
}
orgChart && orgChart.zoomIn();
}
const handleZoomOut = (progressBtn) => {
if(progressBtn) {
let top = (parseInt(progressBtn.current.style.top) + 10)
if(top >= 100) {
top = 70;
}
progressBtn.current.style.top = top + "px";
}
orgChart && orgChart.zoomOut();
}
// tool bar end
// top bar start
function downloadPdf(chart) {
chart.exportImg({
save: false,
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');
};
},
});
}
const handleExport = (type) => {
if(type == "png") {
orgChart && orgChart.exportImg({full:true});
} else {
orgChart && downloadPdf(orgChart)
}
}
const handleSearch = (requestData) => {
let api = "/api/bs/hrmorganization/orgchart/jcl/orgchart/userData" + qs.stringify(requestData, {addQueryPrefix: true})
fetch(api).then(res => res.json()).then(data => {
setData(data.data)
})
}
// top bar end
const nodeContentRender = (d, i, arr, state) => {
if(d.data.ftype == 0 || d.data.ftype == 1 || d.data.ftype == 2) {
return `<div>
<div style="position: relative;">
<img src="./img/user-card/card-label-start.png" />
<span style="display: inline-block;
position: absolute;
left: 5px;
top: -8px;
background: #F7F9FD;
z-index: 100;
padding: 0px 10px;
font-size: 16px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
font-weight: bold;
color: #000000;
">${d.data.fname}</span>
<span style="margin-left: 70px;">
<img src="./img/user-card/line1.png" />
<img src="./img/user-card/line2.png" />
</span>
<div style="background: url('./img/user-card/user-card.png'); height: 152px;background-size: 100% 100%;box-sizing: border-box;padding-top: 30px;">
<div style="display: inline-block; background: url('./img/user-card/avatar-outer.png'); background-size: 100% 100%; width: 90px; height: 90px; text-align:center; vertical-align: top; margin-left: 11px;box-sizing: border;">
<img src="${d.data.fleaderimg ? d.data.fleaderimg : "./img/default_avator.png"}" style="width: 58px; height: 58px; border-radius: 50%; margin-top: 16px;"/>
</div>
<div style="display: inline-block; margin-left: 6px;">
<div style="
font-size: 13px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
font-weight: bold;
color: #333333;
margin-bottom: 9px;
">${d.data.fleadername}</div>
<div style="
font-size: 13px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
margin-bottom: 19px;
">${d.data.fname} / ${d.data.fleaderjob}</div>
<div style="display: flex;">
<div style="height: 28px;border: 1px solid #00C2FF; border-radius: 10px; line-height: 28px; padding: 0px 5px; min-width: 69px;">编制: ${d.data.fplan}</div>
<div style="height: 28px;border: 1px solid #00C2FF; border-radius: 10px; line-height: 28px; padding: 0px 5px; min-width: 69px; margin-left: 10px;">在岗: ${d.data.fonjob}</div>
</div>
</div>
</div>
</div>
</div>`
} else if(d.data.ftype == 3) {
return `<div>
<div style="position: relative;">
<img src="./img/user-card/card-label-start.png" />
<span style="display: inline-block;
position: absolute;
left: 5px;
top: -8px;
background: #F7F9FD;
z-index: 100;
padding: 0px 10px;
font-size: 16px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
font-weight: bold;
color: #000000;
">${d.data.fname}</span>
<span style="margin-left: 70px;">
<img src="./img/user-card/line1.png" />
<img src="./img/user-card/line2.png" />
</span>
<div style="background: url('./img/user-card/user-card.png'); height: 152px;background-size: 100% 100%;box-sizing: border-box;padding-top: 40px;">
<img src="./img/user-card/jobicon.png" style="margin-left: 20px; vertical-align: top;"/>
<div style="display: inline-block; margin-left: 15px;">
<div style="
font-size: 13px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
font-weight: bold;
color: #333333;
margin-bottom: 23px;
">${d.data.fname}</div>
<div style="
font-size: 13px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
">
<span>编制${d.data.fplan}</span>
<span style="margin-left: 10px;">在岗${d.data.fonjob}</span>
</div>
</div>
</div>
</div>
</div>`
} else if(d.data.ftype == 4) {
return `<div>
<div style="position: relative;">
<img src="./img/user-card/card-label-start.png" />
<span >
<img src="./img/user-card/line1.png" />
<img src="./img/user-card/line2.png" />
</span>
<div style="background: url('./img/user-card/user-card.png'); height: 152px;background-size: 100% 100%;box-sizing: border-box;padding-top: 30px;">
<div style="display: inline-block; background: url('./img/user-card/avatar-outer.png'); background-size: 100% 100%; width: 90px; height: 90px; text-align:center; vertical-align: top; margin-left: 11px;box-sizing: border;">
<img src="${d.data.fleaderimg ? d.data.fleaderimg : "./img/default_avator.png"}" style="width: 58px; height: 58px; border-radius: 50%; margin-top: 16px;"/>
</div>
<div style="display: inline-block; margin-left: 6px;">
<div style="
font-size: 13px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
color: #333333;
margin-bottom: 9px;
">${d.data.fname}</div>
<div style="
font-size: 13px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
">${d.data.department} / ${d.data.fleaderjob}</div>
<div style="
font-size: 10px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
margin-bottom: 10px;
">${d.data.mobile}</div>
<div style="
font-size: 10px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
">地址${d.data.address}</div>
</div>
</div>
</div>
</div>`
}
}
return (
<div className={styles.contentWrapper}>
<TopBar
onExport={() => {handleExport()}}
onSearch={(requestData) => {handleSearch(requestData)}}
url="/api/bs/hrmorganization/orgchart/getCondition?type=user"
/>
<ToolBar
onTopLayoutClick={(progressBtn) => handleTopLayoutClick(progressBtn)}
onLeftLayoutClick={(progressBtn) => handleLeftLayoutClick(progressBtn)}
onZoomOut={(progressBtn) => handleZoomOut(progressBtn)}
onZoomIn={(progressBtn) => handleZoomIn(progressBtn)}
/>
<OrgChartComponent
setChart={(chart) => orgChart = chart}
setClick={click => (addNodeChildFunc = click)}
onNodeClick={onNodeClick}
data={data}
buttonContent={
buttonContentRender
}
nodeWidth={nodeWidthRender}
nodeHeight={nodeHeightRender}
nodeContent={nodeContentRender}
/>
</div>
);
}

@ -0,0 +1,37 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"importHelpers": true,
"jsx": "react-jsx",
"esModuleInterop": true,
"sourceMap": true,
"baseUrl": "./",
"strict": true,
"paths": {
"@/*": ["src/*"],
"@@/*": ["src/.umi/*"]
},
"allowSyntheticDefaultImports": true
},
"include": [
"mock/**/*",
"src/**/*",
"config/**/*",
".umirc.ts",
"typings.d.ts"
],
"exclude": [
"node_modules",
"lib",
"es",
"dist",
"typings",
"**/__test__",
"test",
"docs",
"tests"
]
}

10
typings.d.ts vendored

@ -0,0 +1,10 @@
declare module '*.css';
declare module '*.less';
declare module '*.png';
declare module '*.svg' {
export function ReactComponent(
props: React.SVGProps<SVGSVGElement>,
): React.ReactElement;
const url: string;
export default url;
}
Loading…
Cancel
Save