diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/public/avatar.jpg b/public/avatar.jpg new file mode 100644 index 0000000..13ad0f5 Binary files /dev/null and b/public/avatar.jpg differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..7a23e05 Binary files /dev/null and b/public/logo.png differ diff --git a/src/App.css b/src/App.css index 74b5e05..f56cfd3 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,38 @@ -.App { - text-align: center; -} +/*.App {*/ +/* !*text-align: center;*!*/ +/*}*/ -.App-logo { - height: 40vmin; - pointer-events: none; -} +/*.App-logo {*/ +/* height: 40vmin;*/ +/* pointer-events: none;*/ +/*}*/ -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} +/*@media (prefers-reduced-motion: no-preference) {*/ +/* .App-logo {*/ +/* animation: App-logo-spin infinite 20s linear;*/ +/* }*/ +/*}*/ -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} +/*.App-header {*/ +/* background-color: #282c34;*/ +/* min-height: 100vh;*/ +/* display: flex;*/ +/* flex-direction: column;*/ +/* align-items: center;*/ +/* justify-content: center;*/ +/* font-size: calc(10px + 2vmin);*/ +/* color: white;*/ +/*}*/ -.App-link { - color: #61dafb; -} +/*.App-link {*/ +/* color: #61dafb;*/ +/*}*/ -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} +/*@keyframes App-logo-spin {*/ +/* from {*/ +/* transform: rotate(0deg);*/ +/* }*/ +/* to {*/ +/* transform: rotate(360deg);*/ +/* }*/ +/*}*/ diff --git a/src/App.js b/src/App.js index 3784575..c18ff67 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,528 @@ -import logo from './logo.svg'; import './App.css'; +import {OrgChart} from "./components/OrgChart"; +import {Component, Fragment} from "react"; +import {OrgChartNode} from "./components/OrgChartNode"; +import "./iconfont/iconfont.css" -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- { + return ( + + + + ) + } + + componentDidMount() { + document.onmousedown = this.mouseDown + } + + mouseDown = (e) => { + let pageX = e.pageX - this.state.left + let pageY = e.pageY - this.state.top + this.setState({ + cursor: 'move' + }) + document.body.style.cursor = "move" + // 记录起始位置 + document.onmousemove = (event) => { + + // 计算移动位置,移动目标元素 + this.setState({ + left: event.pageX - pageX, + top: event.pageY - pageY + }) + } + document.onmouseup = (event) => { + // 松开鼠标,结束移动 + document.onmousemove = () => { + } + document.body.style.cursor = "auto" + this.setState({ + cursor: 'auto' + }) + } + } + + + // document.documentElement.scrollLeft = (document.documentElement.scrollWidth - document.documentElement.clientWidth) / 2 + + showParent = (node) => { + if(!node){ + return + } + this.state.orgTree.forEach(item=>{ + this.findChildrenShow(item,node) + }) + this.setState({ + orgTree:[...this.state.orgTree] + }) + } + + + + findChildrenShow = (node,children)=>{ + let childrenIndex = node.children.findIndex(item=>item.id === children.id) + if(childrenIndex === -1){ + // 没有找到 + node.children.forEach(item=>{ + item.show = 0 + this.findChildrenShow(item,node) + }) + }else { + node.children.forEach((item,index)=>{ + if(index !== childrenIndex){ + item.show = 0 + }else { + item.show = 1 + } + }) + } + } + hiddenParent = (node) => { + console.log("hidden", node) + } + amplification = () => { + let scale = parseFloat(this.state.scale) + 0.05 + this.setState({scale}) + } + + narrow = () => { + let scale = parseFloat(this.state.scale) - 0.05 + if (scale <= 0) { + scale = 0.05 + } + this.setState({scale}) + } + + render() { + return ( + +
+ + {parseInt(this.state.scale * 100)}% + + this.setState({scale: 1, left: 0, top: 0})}> +
+
-
- ); + { + this.state.orgTree.map(item => { + return + }) + } + + + ); + } } export default App; diff --git a/src/components/OrgChart.jsx b/src/components/OrgChart.jsx new file mode 100644 index 0000000..b04079a --- /dev/null +++ b/src/components/OrgChart.jsx @@ -0,0 +1,182 @@ +import {Component, Fragment} from 'react' +// import {OrgChartNode} from "./OrgChartNode"; +import "../css/index.css" + +export class OrgChart extends Component { + + state = { + marginTop: 0, + orgChildren: "" + } + + constructor(props) { + super(props); + this.state = {...this.state, ...props} + } + + showOrHidden = (node) => { + if (node.showChildren === 0) { + this.showChildren(node) + } else { + this.hiddenChildren((node)) + } + } + + showChildren = (node) => { + node.showChildren = 1 + this.setState({ + data: {...this.state.data} + }) + } + + + hiddenChildren = (node) => { + this.setState({ + orgChildren: "hidden-org-children" + }) + setTimeout(() => { + node.showChildren = 0 + this.hiddenChildrenStack(node) + this.setState({ + data: {...this.state.data}, + orgChildren: "", + }) + }, 300) + } + + hiddenChildrenStack = (node)=>{ + if(!node.children || node.children.length === 0){ + return + } + node.children.forEach(item=>{ + item.showChildren = 0 + this.hiddenChildrenStack(item) + }) + } + + getOrgChartNode = (node) => { + return +
+ { + node.isRoot ? "" : (this.props.parent && this.props.parent.show && this.props.parent.show === 1 ? + this.props.hiddenParent(this.props.parent)}> + : + this.props.showParent(this.props.parent)}>) + } + {this.state.getChartNode(node)} + {/*{*/} + {/* node.children && node.children.length > 0 ? (node.showChildren === 0 ?*/} + {/* this.showChildren(node)}>*/} + {/* */} + {/* :*/} + {/* this.hiddenChildren(node)}>*/} + {/* ) : ""*/} + {/*}*/} + { + node.children && node.children.length > 0 ? { + this.showOrHidden(node) + }} + > : "" + } +
+
+ // return this.props.getChartNode(node) + } + + getOrgItem = (parent) => { + let getChartNode = this.state.getChartNode + let node = this.state.data + if (node.children && node.children.length > 0) { + return ( +
+
this.setState({marginTop: 2})} + onMouseOut={() => this.setState({marginTop: 0})} + > + { + this.getOrgChartNode(node) + } +
+ { + node.showChildren === 1 ? +
+
+ + { + node.children.length === 1 ? + + + : node.children.map((item, index) => { + if (index === 0) { + return + } + if (index === node.children.length - 1) { + return + } + return + }) + } +
+
: "" + } + +
+ ) + } else { + return this.getOrgChartNode(node) + } + } + + render() { + return ( + + { + this.state.lineClass ? +
+
+
+ { + this.getOrgItem(this.props.parent) + } +
+ :
{this.getOrgItem(this.props.parent)}
+ } +
+ ) + } + +} \ No newline at end of file diff --git a/src/components/OrgChartItem.jsx b/src/components/OrgChartItem.jsx new file mode 100644 index 0000000..9011f4d --- /dev/null +++ b/src/components/OrgChartItem.jsx @@ -0,0 +1,45 @@ +import {Component, Fragment} from 'react' +import {OrgChart} from "./OrgChart"; + +export class OrgChartItem extends Component { + + getHasChildrenItem = (node) => { + return ( + +
+ { + node.children.map(item=>{ + return + }) + } +
+
+ ) + } + + getRenderItem = (node) => { + if (node.children || node.children.length > 0) { + // 存在下一等级 + return this.getHasChildrenItem(node) + }else { + return ( + + { + this.props.getOrgChartNode(node) + } + + ) + } + } + + render() { + console.log(this.props) + return ( + + { + this.getRenderItem(this.props.node) + } + + ) + } +} \ No newline at end of file diff --git a/src/components/OrgChartNode.jsx b/src/components/OrgChartNode.jsx new file mode 100644 index 0000000..e0a400e --- /dev/null +++ b/src/components/OrgChartNode.jsx @@ -0,0 +1,77 @@ +import {Component, Fragment} from 'react' +import '../css/node.css' + +export class OrgChartNode extends Component { + + + render() { + let node = this.props.node + return ( + +
+
+
+
+ logo +
+
+ { + node.type === 1 ? + + : + + + + } +
+
+ + + + + + {node.downLevel} +
+
+
+
+ {node.name} +
+
+
+ DEPARTMENT +
+
+ {node.department} +
+
+
+
+ JOB +
+
+ {node.department} +
+
+
+
+
+
+ ) + } +} \ No newline at end of file diff --git a/src/css/index.css b/src/css/index.css new file mode 100644 index 0000000..c33f584 --- /dev/null +++ b/src/css/index.css @@ -0,0 +1,218 @@ + +body{ + -moz-user-select:none; /*火狐*/ + -webkit-user-select:none; /*webkit浏览器*/ + -ms-user-select:none; /*IE10*/ + -khtml-user-select:none; /*早期浏览器*/ + user-select:none; + /*cursor: move;*/ +} + +#org-chart-container{ + text-align: center; + padding: 20px; + position: relative; + transform-origin: left center; +} +#org-chart-container .org-root{ + transition: height 0.3s; +} +.functional-container{ + position: fixed; + top: 20px; + right: 20px; + font-size: 14px; + display: flex; + align-items: center; + z-index: 99; + background-color:rgba(255,255,255,0.8); + padding: 10px; + border-radius: 5px; + box-shadow: 1px 1px 5px 1px #f1f1f1; +} +.functional-container .cus_iconfont{ + font-size: 25px; + margin: 0 5px; + cursor: pointer; + color: #333; + line-height: 0 !important; +} + +.functional-container>span{ + margin: 0 5px; + color: #666; +} +.functional-container .cus_iconfont:hover{ + color: #C32929; +} + + +#org-chart-container .org-children-chart{ + display: flex; + flex-wrap: nowrap; + align-items: flex-start; + animation: 0.3s children-show; + opacity: 1; + transition: 0.3s all; + /*margin-top: -20px;*/ +} + +#org-chart-container .org-children-chart.hidden-org-children{ + animation: 0.4s children-hidden; +} + + +@keyframes children-hidden { + 0%{ + overflow: hidden; + } + 70%{ + opacity: 0; + overflow: hidden; + } + + to{ + margin-top: -50px; + z-index: -1; + height: 0; + opacity: 0; + overflow: hidden; + } +} + +@keyframes children-show { + from{ + opacity: 0; + margin-top: -50px; + } + to{ + margin-top: 0px; + opacity: 1; + } +} + +#org-chart-container .line{ + line-height: 0 !important; +} + + +#org-chart-container .org-chart-node { + display: inline-block; + /*line-height: 0;*/ + /*position: relative;*/ + /*left: 50%;*/ + /*transform: translateX(-50%);*/ +} +#org-chart-container .org-chart-root{ + display: inline-block; +} + +#org-chart-container .bottom-down-line{ + width: 2px; + height: 20px; + background-color: #C32929; + position: relative; + left: 50%; + transform: translateX(-50%); + transition: all 0.3s; +} + +#org-chart-container .right-row-line{ + height: 2px; + width: 50%; + position: relative; + left: 50%; + background-color: #C32929; + transition: all 0.3s; +} +#org-chart-container .left-row-line{ + height: 2px; + width: 50%; + position: relative; + left: 0; + background-color: #C32929; + transition: all 0.3s; +} +#org-chart-container .row-line{ + height: 2px; + position: relative; + left: 0; + background-color: #C32929; + transition: all 0.3s; +} +#org-chart-container .top-down-line{ + height: 20px; + width: 2px; + background-color: #C32929; + position: relative; + top: -2px; + left: 50%; + transform: translateX(-50%); + transition: all 0.3s; +} + + +#org-chart-container .org-chart-item{ + position: relative; + margin-top: -3px; +} + +#org-chart-container .org-chart-item:hover{ + transition: all 0.3s; + /*transform: scale(0.975);*/ +} + + + + +#org-chart-container .org-chart-item .hidden-icon{ + transform: translateX(-50%) rotateZ(180deg) !important; + display: inline-block; +} +#org-chart-container .org-chart-item .show-parent-icon{ + transform: rotateZ(180deg); + display: inline-block; +} + +#org-chart-container .org-chart-item .expansion-icon{ + position: absolute; + bottom: -16px; + font-size: 25px; + left: 50%; + transform: translateX(-50%); + z-index: 999; + display: none; + color: #C32929; + cursor: pointer !important; +} +#org-chart-container .org-chart-item:hover .expansion-icon{ + display: inline-block; +} +#org-chart-container .org-chart-item .expansion-icon:hover{ + color: #C32929; + transform: translateX(-50%); + font-size: 30px; + transition: all 0.3s; +} + +#org-chart-container .org-chart-item .show-or-hidden{ + color: #C32929; + font-size: 31px; + position: absolute; + bottom: -15px; + transform: translateX(-50%); + transition: all 0.3s; + opacity: 0; +} +#org-chart-container .org-chart-item:hover .show-or-hidden{ + display: inline-block; + opacity: 1; + bottom: -17px; + cursor: pointer; +} + + +#org-chart-container .org-chart-item .show-or-hidden:hover{ + transform: translateX(-50%) scale(1.125); + transform-origin: center; +} \ No newline at end of file diff --git a/src/css/node.css b/src/css/node.css new file mode 100644 index 0000000..eaa1192 --- /dev/null +++ b/src/css/node.css @@ -0,0 +1,150 @@ + +.node-container{ + width: 160px; + border: 1px #C32929 solid; + border-radius: 10px; + position: relative; + margin: 50px 15px 0px 15px; + font-size: 10px; + padding: 10px 10px 20px 10px; + height: 100%; + animation: 0.6s show; +} + +.org-chart-node-item{ + overflow: hidden; + margin: 0 0 -3px 0; +} +.node-container .content-container{ + animation: 0.5s show-content; +} + +@keyframes show-content { + from{ + height: 0; + opacity: 0; + overflow: hidden; + } + 90%{ + overflow: hidden; + } + to{ + height: auto; + opacity: 1; + } +} + +.hidden-org-children .node-container{ + animation: 0.6s hidden; +} + +.hidden-org-children .node-container .content-container{ + animation: 0.6s hidden-content; +} + +@keyframes hidden-content { + 0%{ + overflow: hidden; + } + to{ + overflow: hidden; + width: 0; + opacity: 0; + height: 0; + } +} +@keyframes hidden { + to{ + width: 0; + opacity: 0; + height: 0; + } +} + +@keyframes show { + from{ + width: 0; + opacity: 0; + height: 0; + } + to{ + width: 160px; + opacity: 1; + } +} + + +.node-container .avatar-container{ + border-radius: 50%; + width: 100px; + height: 100px; + overflow: hidden; + position: absolute; + left: 50%; + top: -50px; + transform: translateX(-50%); + border: 1px #C32929 solid; + background-color: #f2f2f2; +} +.node-container .logo{ + position: absolute; + left: calc(50% + 2px); + transform: translateX(-50%); + z-index: 10; + top: 42px; +} + +.node-container .down-level-type{ + display: flex; + align-items: flex-start; + justify-content: space-between; + color: #666666; +} + +.node-container .down-level-type .down-level{ + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + font-size: 10px; +} + +.node-container .name{ + margin-top: 35px; + font-size: 14px; + font-weight: 600; +} + +.node-container .department-job-container{ + margin-top: 10px; + text-align: left; +} + +.node-container .department-job-container .title{ + font-weight: 600; + font-size: 12px; + text-align: left; + padding: 5px 0; + display: flex; + align-items: center; +} + +.node-container .department-job-container .title::before{ + content: ""; + display: inline-block; + width: 4px; + height: 13px; + background-color: #C32929; + margin-right: 5px; +} + +.node-container .department-job-container .content{ + font-size: 12px; + margin-left: 8px; + color: #666; + transform: scale(0.8); + width: 125%; + transform-origin:center left; +} + + diff --git a/src/iconfont/iconfont.css b/src/iconfont/iconfont.css new file mode 100644 index 0000000..7efdc7b --- /dev/null +++ b/src/iconfont/iconfont.css @@ -0,0 +1,63 @@ +@font-face { + font-family: "cus_iconfont"; /* Project id 3778032 */ + src: url('iconfont.woff2?t=1669465666296') format('woff2'), + url('iconfont.woff?t=1669465666296') format('woff'), + url('iconfont.ttf?t=1669465666296') format('truetype'); +} + +.cus_iconfont { + font-family: "cus_iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-fangda1:before { + content: "\e614"; +} + +.icon-zhongzhi:before { + content: "\e613"; +} + +.icon-fangda:before { + content: "\e60f"; +} + +.icon-24gf-shrinkVertical2:before { + content: "\eb78"; +} + +.icon-24gf-shrinkHorizontal2:before { + content: "\eb79"; +} + +.icon-suoxiao:before { + content: "\e612"; +} + +.icon-zhankai_mian:before { + content: "\e639"; +} + +.icon-zhankai-mian:before { + content: "\e60e"; +} + +.icon-yuandian:before { + content: "\e683"; +} + +.icon-zhankai:before { + content: "\e65d"; +} + +.icon-bjxxjr:before { + content: "\e606"; +} + +.icon-qunzu:before { + content: "\e6da"; +} + diff --git a/src/iconfont/iconfont.ttf b/src/iconfont/iconfont.ttf new file mode 100644 index 0000000..6d77cf5 Binary files /dev/null and b/src/iconfont/iconfont.ttf differ diff --git a/src/iconfont/iconfont.woff b/src/iconfont/iconfont.woff new file mode 100644 index 0000000..1db0f27 Binary files /dev/null and b/src/iconfont/iconfont.woff differ diff --git a/src/iconfont/iconfont.woff2 b/src/iconfont/iconfont.woff2 new file mode 100644 index 0000000..63a84fd Binary files /dev/null and b/src/iconfont/iconfont.woff2 differ