组织结构图v1.0
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 238 B |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 737 B |
|
After Width: | Height: | Size: 978 B |
|
After Width: | Height: | Size: 981 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 997 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 945 B |
|
After Width: | Height: | Size: 978 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 140 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 232 B |
|
After Width: | Height: | Size: 233 B |
|
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>
|
||||
);
|
||||
};
|
||||
|
After Width: | Height: | Size: 798 B |
|
After Width: | Height: | Size: 744 B |
|
After Width: | Height: | Size: 643 B |
|
After Width: | Height: | Size: 438 B |
|
After Width: | Height: | Size: 372 B |
|
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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||