commit 6e668c9d89ae211d4f8806aff2d39bc699de6a89 Author: MustangDeng <670124965@qq.com> Date: Tue Jul 12 17:10:03 2022 +0800 组织结构图v1.0 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e3649a --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee1cf6 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..0d4222f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +**/*.md +**/*.svg +**/*.ejs +**/*.html +package.json +.umi +.umi-production +.umi-test diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..94beb14 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "overrides": [ + { + "files": ".prettierrc", + "options": { "parser": "json" } + } + ] +} diff --git a/.umirc.ts b/.umirc.ts new file mode 100644 index 0000000..e54955f --- /dev/null +++ b/.umirc.ts @@ -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去掉 + } + } +}); diff --git a/README.md b/README.md new file mode 100644 index 0000000..07afeb7 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# umi project + +## Getting Started + +Install dependencies, + +```bash +$ yarn +``` + +Start the dev server, + +```bash +$ yarn start +``` diff --git a/mock/api.ts b/mock/api.ts new file mode 100644 index 0000000..f845c0d --- /dev/null +++ b/mock/api.ts @@ -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 + } + ] + } \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7f0d301 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/public/img/avator.png b/public/img/avator.png new file mode 100644 index 0000000..ea4f2d0 Binary files /dev/null and b/public/img/avator.png differ diff --git a/public/img/button_content.png b/public/img/button_content.png new file mode 100644 index 0000000..458a4f0 Binary files /dev/null and b/public/img/button_content.png differ diff --git a/public/img/company.png b/public/img/company.png new file mode 100644 index 0000000..3288b0e Binary files /dev/null and b/public/img/company.png differ diff --git a/public/img/company_job_label.png b/public/img/company_job_label.png new file mode 100644 index 0000000..109a4eb Binary files /dev/null and b/public/img/company_job_label.png differ diff --git a/public/img/default_avator.png b/public/img/default_avator.png new file mode 100644 index 0000000..84572e3 Binary files /dev/null and b/public/img/default_avator.png differ diff --git a/public/img/department/1.png b/public/img/department/1.png new file mode 100644 index 0000000..34cec80 Binary files /dev/null and b/public/img/department/1.png differ diff --git a/public/img/department/2.png b/public/img/department/2.png new file mode 100644 index 0000000..f21e553 Binary files /dev/null and b/public/img/department/2.png differ diff --git a/public/img/department/3.png b/public/img/department/3.png new file mode 100644 index 0000000..f9296ad Binary files /dev/null and b/public/img/department/3.png differ diff --git a/public/img/department/4.png b/public/img/department/4.png new file mode 100644 index 0000000..36c4979 Binary files /dev/null and b/public/img/department/4.png differ diff --git a/public/img/department/5.png b/public/img/department/5.png new file mode 100644 index 0000000..47981a3 Binary files /dev/null and b/public/img/department/5.png differ diff --git a/public/img/department/6.png b/public/img/department/6.png new file mode 100644 index 0000000..3441b82 Binary files /dev/null and b/public/img/department/6.png differ diff --git a/public/img/department/7.png b/public/img/department/7.png new file mode 100644 index 0000000..c685368 Binary files /dev/null and b/public/img/department/7.png differ diff --git a/public/img/department/8.png b/public/img/department/8.png new file mode 100644 index 0000000..1d3a147 Binary files /dev/null and b/public/img/department/8.png differ diff --git a/public/img/manager.png b/public/img/manager.png new file mode 100644 index 0000000..34cec80 Binary files /dev/null and b/public/img/manager.png differ diff --git a/public/img/subcompany/1.png b/public/img/subcompany/1.png new file mode 100644 index 0000000..bb91a88 Binary files /dev/null and b/public/img/subcompany/1.png differ diff --git a/public/img/subcompany/2.png b/public/img/subcompany/2.png new file mode 100644 index 0000000..3a93936 Binary files /dev/null and b/public/img/subcompany/2.png differ diff --git a/public/img/subcompany/3.png b/public/img/subcompany/3.png new file mode 100644 index 0000000..88ffaa1 Binary files /dev/null and b/public/img/subcompany/3.png differ diff --git a/public/img/subcompany1.png b/public/img/subcompany1.png new file mode 100644 index 0000000..bb91a88 Binary files /dev/null and b/public/img/subcompany1.png differ diff --git a/public/img/user-card/avatar-outer.png b/public/img/user-card/avatar-outer.png new file mode 100644 index 0000000..8570ea4 Binary files /dev/null and b/public/img/user-card/avatar-outer.png differ diff --git a/public/img/user-card/card-label-start.png b/public/img/user-card/card-label-start.png new file mode 100644 index 0000000..88b48b4 Binary files /dev/null and b/public/img/user-card/card-label-start.png differ diff --git a/public/img/user-card/jobicon.png b/public/img/user-card/jobicon.png new file mode 100644 index 0000000..5c25edd Binary files /dev/null and b/public/img/user-card/jobicon.png differ diff --git a/public/img/user-card/line1.png b/public/img/user-card/line1.png new file mode 100644 index 0000000..341964f Binary files /dev/null and b/public/img/user-card/line1.png differ diff --git a/public/img/user-card/line2.png b/public/img/user-card/line2.png new file mode 100644 index 0000000..aa3045e Binary files /dev/null and b/public/img/user-card/line2.png differ diff --git a/public/img/user-card/user-card.png b/public/img/user-card/user-card.png new file mode 100644 index 0000000..92e0995 Binary files /dev/null and b/public/img/user-card/user-card.png differ diff --git a/src/components/orgChart/index.jsx b/src/components/orgChart/index.jsx new file mode 100644 index 0000000..a010448 --- /dev/null +++ b/src/components/orgChart/index.jsx @@ -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 ( +
+
+
+ ); +}; diff --git a/src/components/toolBar/img/add.png b/src/components/toolBar/img/add.png new file mode 100644 index 0000000..f300dcc Binary files /dev/null and b/src/components/toolBar/img/add.png differ diff --git a/src/components/toolBar/img/decrease.png b/src/components/toolBar/img/decrease.png new file mode 100644 index 0000000..3f70bc2 Binary files /dev/null and b/src/components/toolBar/img/decrease.png differ diff --git a/src/components/toolBar/img/left.png b/src/components/toolBar/img/left.png new file mode 100644 index 0000000..ae542f9 Binary files /dev/null and b/src/components/toolBar/img/left.png differ diff --git a/src/components/toolBar/img/left_active.png b/src/components/toolBar/img/left_active.png new file mode 100644 index 0000000..9986912 Binary files /dev/null and b/src/components/toolBar/img/left_active.png differ diff --git a/src/components/toolBar/img/top.png b/src/components/toolBar/img/top.png new file mode 100644 index 0000000..935c0b0 Binary files /dev/null and b/src/components/toolBar/img/top.png differ diff --git a/src/components/toolBar/img/top_active.png b/src/components/toolBar/img/top_active.png new file mode 100644 index 0000000..379a699 Binary files /dev/null and b/src/components/toolBar/img/top_active.png differ diff --git a/src/components/toolBar/index.jsx b/src/components/toolBar/index.jsx new file mode 100644 index 0000000..5de765c --- /dev/null +++ b/src/components/toolBar/index.jsx @@ -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 ( +
+ {this.props.onZoomIn(this.progressBtn)}}/> +
+
{this.handleMouseDown(e)}} + onMouseMove={(e) => {this.handleMouseMove(e)}} + onMouseUp={(e) => {this.handleMouseUp(e)}} + onMouseLeave={(e) => {this.handleMouseUp(e)}} + >
+
+
+ {this.props.onZoomOut(this.progressBtn)}}/> + + {this.setState({toolActive: "top"}); this.props.onTopLayoutClick(this.progressBtn)}}/> + {this.setState({toolActive: "left"}); this.props.onLeftLayoutClick(this.progressBtn)}}/> +
+ ) + } +} \ No newline at end of file diff --git a/src/components/toolBar/index.less b/src/components/toolBar/index.less new file mode 100644 index 0000000..ffe10d8 --- /dev/null +++ b/src/components/toolBar/index.less @@ -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; + } +} \ No newline at end of file diff --git a/src/components/topBar/index.jsx b/src/components/topBar/index.jsx new file mode 100644 index 0000000..c0a888a --- /dev/null +++ b/src/components/topBar/index.jsx @@ -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 = ( + + ); + + render() { + return ( +
+ + + + 数据日期: this.handleFormChange({date: value && value != "" ? value.format("YYYY-MM-DD") : ""})} /> + + + + + 维度: + + + + 根节点: + + + + 显示层级: + + + + this.handleFormChange({fisvitual: e.target.checked ? "1": "0"})}>显示虚拟组织 + + + + + + + + + + + +
+ ) + } +} \ No newline at end of file diff --git a/src/components/topBar/index.less b/src/components/topBar/index.less new file mode 100644 index 0000000..e3e9c4d --- /dev/null +++ b/src/components/topBar/index.less @@ -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; +} \ No newline at end of file diff --git a/src/department/index.js b/src/department/index.js new file mode 100644 index 0000000..b04fce2 --- /dev/null +++ b/src/department/index.js @@ -0,0 +1,5 @@ +import React from 'react' + +export default () => { + return
Hello
+} \ No newline at end of file diff --git a/src/pages/company.jsx b/src/pages/company.jsx new file mode 100644 index 0000000..0a6fd8c --- /dev/null +++ b/src/pages/company.jsx @@ -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 `
ˆ
`; + } else { + return `
ˬ
` + } + } + + // 节点宽度渲染 + 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 `
+
+ +
+
+
${d.data.fname}
+
COMPANY_GROUP
+
+
` + } else if(d.data.ftype == 1) { + return `
+
+ +
+
+ ${d.data.fname} +
+
` + } else if(d.data.ftype == 2) { + return ` +
+
+ + ${d.data.fname} +
+
+ ` + } + return `
${d.data.fname}
` + } + + + // 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 ( +
+ {handleExport(type)}} + onSearch={(requestData) => {handleSearch(requestData)}} + url="/bs/hrmorganization/orgchart/jcl/orgchart/getCondition?type=company" + /> + handleTopLayoutClick(progressBtn)} + onLeftLayoutClick={(progressBtn) => handleLeftLayoutClick(progressBtn)} + onZoomOut={(progressBtn) => handleZoomOut(progressBtn)} + onZoomIn={(progressBtn) => handleZoomIn(progressBtn)} + onZoomBehavior={(value) => handleZoomBehavior(value)} + /> + orgChart = chart} + setClick={click => (addNodeChildFunc = click)} + onNodeClick={onNodeClick} + data={data} + buttonContent={ + buttonContentRender + } + nodeWidth={nodeWidthRender} + nodeHeight={nodeHeightRender} + nodeContent={nodeContentRender} + /> +
+ ); +} diff --git a/src/pages/index.less b/src/pages/index.less new file mode 100644 index 0000000..0186693 --- /dev/null +++ b/src/pages/index.less @@ -0,0 +1,11 @@ +.title { + background: rgb(121, 242, 157); +} + +.wrapper { + background: #F7F9FD; +} + +.contentWrapper { + background-color: #F7F9FD; +} \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx new file mode 100644 index 0000000..b725177 --- /dev/null +++ b/src/pages/index.tsx @@ -0,0 +1,9 @@ +import styles from './index.less'; + +export default function IndexPage() { + return ( +
+

Page index

+
+ ); +} diff --git a/src/pages/user.jsx b/src/pages/user.jsx new file mode 100644 index 0000000..0dea0da --- /dev/null +++ b/src/pages/user.jsx @@ -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 ` +
+ +
+ ` + } + + // 节点宽度渲染 + 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 `
+
+ + ${d.data.fname} + + + + +
+
+ +
+
+
${d.data.fleadername}
+
${d.data.fname} / ${d.data.fleaderjob}
+
+
编制: ${d.data.fplan}
+
在岗: ${d.data.fonjob}
+
+
+
+
+
` + } else if(d.data.ftype == 3) { + return `
+
+ + ${d.data.fname} + + + + +
+ +
+
${d.data.fname}
+
+ 编制:${d.data.fplan} + 在岗:${d.data.fonjob} +
+
+
+
+
` + } else if(d.data.ftype == 4) { + return `
+
+ + + + + +
+
+ +
+
+
${d.data.fname}
+
${d.data.department} / ${d.data.fleaderjob}
+
${d.data.mobile}
+
地址:${d.data.address}
+
+
+
+
` + } + } + + return ( +
+ {handleExport()}} + onSearch={(requestData) => {handleSearch(requestData)}} + url="/api/bs/hrmorganization/orgchart/getCondition?type=user" + /> + handleTopLayoutClick(progressBtn)} + onLeftLayoutClick={(progressBtn) => handleLeftLayoutClick(progressBtn)} + onZoomOut={(progressBtn) => handleZoomOut(progressBtn)} + onZoomIn={(progressBtn) => handleZoomIn(progressBtn)} + /> + orgChart = chart} + setClick={click => (addNodeChildFunc = click)} + onNodeClick={onNodeClick} + data={data} + buttonContent={ + buttonContentRender + } + nodeWidth={nodeWidthRender} + nodeHeight={nodeHeightRender} + nodeContent={nodeContentRender} + /> +
+ ); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6d42f8c --- /dev/null +++ b/tsconfig.json @@ -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" + ] +} diff --git a/typings.d.ts b/typings.d.ts new file mode 100644 index 0000000..06c8a5b --- /dev/null +++ b/typings.d.ts @@ -0,0 +1,10 @@ +declare module '*.css'; +declare module '*.less'; +declare module '*.png'; +declare module '*.svg' { + export function ReactComponent( + props: React.SVGProps, + ): React.ReactElement; + const url: string; + export default url; +}