<template>
|
<div >
|
<div class='app' ref='departOrg' v-if='data.id'>
|
<!-- <div style="display: flex; padding: 10px 0;">
|
<div style="margin-right: 10px"><i-switch v-model="horizontal"></i-switch> 横向</div>
|
<div style="margin-right: 10px"><i-switch v-model="collapsable"></i-switch> 可收起</div>
|
<div style="margin-right: 10px"><i-switch v-model="disaled"></i-switch> 禁止编辑</div>
|
<div style="margin-right: 10px"><i-switch v-model="onlyOneNode"></i-switch> 仅拖动当前节点</div>
|
<div style="margin-right: 10px"><i-switch v-model="cloneNodeDrag"></i-switch> 拖动节点副本</div>
|
</div>
|
<div style="padding-bottom:10px">
|
背景色:
|
<color-picker v-model="style.background" size="small"></color-picker>
|
文字颜色:
|
<color-picker v-model="style.color" size="small"></color-picker>
|
搜索:
|
<input type="text" v-model="keyword" placeholder="请输入搜索内容" @keydown.enter="filter" />
|
</div>-->
|
<div style='height: 100%; border:1px solid #eee'>
|
<zm-tree-org
|
ref='tree'
|
:data='data'
|
center
|
:scalable='scalable'
|
:wheel='zoomWheel'
|
:disabled='disabled'
|
:horizontal='horizontal'
|
:collapsable='collapsable'
|
:label-style='style'
|
:node-draggable='draggable'
|
:default-expand-level='10'
|
:only-one-node='onlyOneNode'
|
:clone-node-drag='cloneNodeDrag'
|
:before-drag-end='beforeDragEnd'
|
:toolBar='toolBar'
|
:filterNodeMethod='filterNodeMethod'
|
@on-node-drag='nodeDragMove'
|
@on-node-drag-end='nodeDragEnd'
|
@on-contextmenu='onMenus'
|
@on-expand='onExpand'
|
@on-node-click='onNodeClick'
|
@on-node-dblclick='onNodeDblclick'
|
@on-node-copy='onNodeCopy'
|
>
|
<!-- <template v-slot="{node}">
|
<div class="tree-org-node__text node-label">{{node.label}}</div>
|
</template>
|
<template v-slot:expand="{node}">
|
<div>{{node.children.length}}</div>
|
</template> -->
|
|
|
<template v-slot='{node}'>
|
<!-- <div class="tree-org-node__text node-label">{{node.label}}</div>-->
|
<div v-if='node.leaf' style='width:120px'>
|
<div class='tree-org-node__text text-blod shadow'>{{ node.label }}
|
<div v-if='node.userList.length'>共{{ node.userList.length }}人</div>
|
</div>
|
|
<div class='tree-org-node__text shadow post-item' v-for='(item,index) in node.postList'>
|
<div class='text-blod'>
|
<span>{{ item.postName }}</span>
|
</div>
|
<div>
|
<span class='user-item' :class="isCustom(user) ? 'gray' : null" v-for='user in item.userList'
|
@click.stop='userClick($event,user)'>
|
<!--应届生-->
|
<a-popover title='提示'>
|
<template slot='content'>
|
<p> <a-icon type='star' style='color: #1890FF;' theme='filled' /> 应届生</p>
|
</template>
|
<a-icon v-if='user.grad == 1' type='star' style='color: #1890FF;' theme='filled' />
|
</a-popover>
|
|
<!--借调-->
|
<a-popover title='提示'>
|
<template slot='content'>
|
<p> <a-icon type='golden' style='color: #1890FF;' theme='filled' /> 借调人员</p>
|
</template>
|
<a-icon v-if='user.employStatu == 2' type='golden' style='color: #1890FF;' theme='filled' />
|
</a-popover>
|
<!--试用期-->
|
|
<a-popover title='提示'>
|
<template slot='content'>
|
<p> <a-icon type='thunderbolt' style='color: #1890FF;' theme='filled' /> 试用期</p>
|
</template>
|
<a-icon v-if='user.employ == 2' type='thunderbolt' style='color: #1890FF;' theme='filled' />
|
</a-popover>
|
<!--实习生-->
|
<a-popover title='提示'>
|
<template slot='content'>
|
<p> <a-icon type='flag' style='color: #1890FF;' theme='filled' /> 实习生</p>
|
</template>
|
<a-icon v-if='user.employ == 3' type='flag' style='color: #1890FF;' theme='filled' />
|
</a-popover>
|
<span :class="checkPostList.includes(item.postName) ? 'text-blue' : null">{{ user.realname }}</span>
|
|
</span>
|
</div>
|
|
</div>
|
|
</div>
|
<div v-else>
|
<div class='tree-org-node__text text-blod shadow'>{{ node.label }}</div>
|
</div>
|
</template>
|
<!-- <template v-slot:expand='{node}'>-->
|
<!-- <div>{{ node.children.length }}</div>-->
|
<!-- </template>-->
|
</zm-tree-org>
|
</div>
|
|
|
<template>
|
<div class='menu-handle'>
|
<a-popover placement='topRight'>
|
<template slot='content'>
|
<depart-info-list :data='data'></depart-info-list>
|
</template>
|
<div @click="showCmd('tips')" title='提示' class='menu-handle-item'>
|
<span class='menu-svg'>
|
<a-icon type='bulb' />
|
</span>
|
</div>
|
</a-popover>
|
|
<a-popover placement='topRight'>
|
|
<template slot='title'>
|
<div style='display: flex;justify-content: space-between;align-items: center'>
|
<span>选择职务</span>
|
<a-button size='small' type='link' @click='cleanCheckPostList'>
|
清空
|
</a-button>
|
</div>
|
</template>
|
|
<template slot='content'>
|
<depart-post-check-list ref='checkPostModal' :data='postList'
|
@ok='checkPostListOk'></depart-post-check-list>
|
</template>
|
<div @click="showCmd('filter')" title='筛选' class='menu-handle-item'>
|
<span class='menu-svg'>
|
<a-icon type='filter' />
|
</span>
|
</div>
|
</a-popover>
|
|
<!-- <div @click="showCmd('camera')" title='截屏' class='menu-handle-item'>
|
<span class='menu-svg'>
|
<a-icon type='camera' />
|
</span>
|
</div>-->
|
|
|
<div @click="showCmd('root')" title='设置根目录' class='menu-handle-item'>
|
<span class='menu-svg'>
|
<a-icon type='cluster' />
|
</span>
|
</div>
|
<div @click="showCmd('setting')" title='设置' class='menu-handle-item'>
|
<span class='menu-svg'>
|
<a-icon type='setting' />
|
</span>
|
</div>
|
|
|
</div>
|
</template>
|
|
|
<company-left-drawer ref='leftDrawer' @ok='onSelectDepartSave'
|
@selectDepart='onSelectDepart'></company-left-drawer>
|
<company-right-drawer ref='rightDrawer' @settingCmd='settingCmd'></company-right-drawer>
|
<!--用户信息-->
|
<user-modal ref='userModal' @ok='userModalOk'></user-modal>
|
</div>
|
<div v-else style='background-color: white;height: calc(100vh - 140px);display: flex;justify-content: center;align-items: center'>
|
<a-spin size="large" />
|
</div>
|
</div>
|
</template>
|
|
|
<script>
|
|
import { queryDepartTreeList } from '@/api/api'
|
import CompanyLeftDrawer from '@views/hrm/modules/CompanyLeftDrawer'
|
import CompanyRightDrawer from '@views/hrm/modules/CompanyRightDrawer'
|
import { getAction } from '@api/manage'
|
import UserModal from '@views/system/modules/UserModal'
|
import Vue from 'vue'
|
import DepartInfoList from '@views/hrm/modules/DepartInfoList'
|
import DepartPostCheckList from '@views/hrm/modules/DepartPostCheckList'
|
import html2canvas from 'html2canvas'
|
|
export default {
|
name: 'DepartOrgTree',
|
components: { DepartPostCheckList, DepartInfoList, UserModal, CompanyRightDrawer, CompanyLeftDrawer },
|
data() {
|
return {
|
toolBar: {
|
scale: true
|
},
|
keyword: '',
|
dataSource: [],
|
postList: [],
|
checkPostList: [],
|
rootNodeName: '研发中心',
|
data: {
|
|
/* id: 1,
|
label: 'xxx科技有限公司',
|
children: [
|
{
|
id: 2,
|
pid: 1,
|
label: '产品研发部',
|
style: { color: '#fff', background: '#108ffe' },
|
expand: false,
|
|
children: [
|
{
|
id: 6,
|
pid: 2,
|
label: '禁止编辑节点',
|
disabled: true
|
},
|
{
|
id: 7,
|
pid: 2,
|
label: '研发-后端'
|
},
|
{
|
id: 8,
|
pid: 2,
|
label: '禁止拖拽节点',
|
noDragging: true
|
},
|
{
|
id: 9,
|
pid: 2,
|
label: '产品经理'
|
},
|
{
|
id: 10,
|
pid: 2,
|
label: '测试'
|
}
|
]
|
},
|
{
|
id: 3,
|
pid: 1,
|
label: '客服部',
|
children: [
|
{
|
id: 11,
|
pid: 3,
|
label: '客服一部'
|
},
|
{
|
id: 12,
|
pid: 3,
|
label: '客服二部'
|
}
|
]
|
},
|
{
|
id: 4,
|
pid: 1,
|
label: '业务部'
|
},
|
{
|
id: 5,
|
pid: 1,
|
label: '人力资源中心'
|
}
|
]*/
|
},
|
horizontal: false,
|
collapsable: true,
|
draggable: false,
|
disabled: true,
|
scalable: true,
|
zoomWheel: false,
|
onlyOneNode: false,
|
cloneNodeDrag: false,
|
expandAll: true,
|
|
style: {
|
background: '#fff',
|
color: '#5e6d82'
|
}
|
}
|
},
|
created() {
|
this.loadSetting()
|
this.loadDepartTree()
|
this.loadPostList()
|
this.toggleExpandAll()
|
|
|
},
|
methods: {
|
loadSetting() {
|
//上次设置的根节点
|
const save = Vue.ls.get('select_depart_save')
|
if (save) {
|
this.rootNodeName = save
|
|
}
|
|
const collapsable = Vue.ls.get('org_setting_collapsable')
|
const disabled = Vue.ls.get('org_setting_disabled')
|
const scalable = Vue.ls.get('org_setting_scalable')
|
const zoomWheel = Vue.ls.get('org_setting_zoomWheel')
|
if (collapsable != null) {
|
this.collapsable = collapsable
|
}
|
if (disabled != null) {
|
this.disabled = disabled
|
}
|
if (scalable != null) {
|
this.scalable = scalable
|
}
|
if (zoomWheel != null) {
|
this.zoomWheel = zoomWheel
|
}
|
|
|
},
|
checkPostListOk(postList) {
|
this.checkPostList = postList
|
},
|
cleanCheckPostList() {
|
this.$refs.checkPostModal.clean()
|
this.checkPostList = []
|
|
},
|
isCustom(user) {
|
return user.grad || user.borrow || user.period || user.intern
|
},
|
onSelectDepart(depart) {
|
if (!depart) return
|
let res = this.findDataById(this.dataSource, depart)
|
if (this.data.id != res.id) {
|
this.data = res
|
console.info(this.data)
|
}
|
},
|
onSelectDepartSave(depart) {
|
if (!depart) {
|
this.$message.info('请选择一个部门后保存~')
|
return
|
}
|
let select = this.findDataById(this.dataSource, depart)
|
Vue.ls.set('select_depart_save', select.label)
|
this.$message.success('设置根节点成功~')
|
},
|
|
//通过点击的部门查找部门数据
|
findDataById(data, id) {
|
// 遍历每个节点
|
for (var i = 0; i < data.length; i++) {
|
var node = data[i]
|
|
// 如果当前节点的id等于目标id,返回该节点
|
if (node.id === id) {
|
return node
|
}
|
|
// 如果当前节点有子节点,则递归查找子节点
|
if (node.children && node.children.length > 0) {
|
var result = this.findDataById(node.children, id)
|
if (result) {
|
return result
|
}
|
}
|
}
|
|
// 如果未找到匹配的节点,返回null
|
return null
|
},
|
settingCmd(cmd) {
|
const name = cmd.name
|
const value = cmd.value
|
if (name === 'collapsable') {
|
this.collapsable = value
|
Vue.ls.set('org_setting_collapsable', value)
|
} else if (name === 'disabled') {
|
this.disabled = value
|
Vue.ls.set('org_setting_disabled', value)
|
} else if (name === 'scalable') {
|
this.scalable = value
|
Vue.ls.set('org_setting_scalable', value)
|
} else if (name === 'zoomWheel') {
|
this.zoomWheel = value
|
Vue.ls.set('org_setting_zoomWheel', value)
|
} else if (name === 'bgColor') {
|
this.style.background = value
|
} else if (name === 'textColor') {
|
this.style.color = value
|
}
|
},
|
|
|
showCmd(cmd) {
|
if (cmd === 'root') {
|
this.$refs.leftDrawer.onOpen()
|
} else if (cmd === 'setting') {
|
this.$refs.rightDrawer.onOpen()
|
} else if (cmd === 'camera') {
|
this.toImage()
|
}
|
},
|
|
|
userModalOk() {
|
this.loadDepartTree()
|
},
|
userClick(e, user) {
|
this.$refs.userModal.edit(user)
|
this.$refs.userModal.departDisabled = false
|
this.$message.info(user.realname)
|
},
|
loadDepartTree() {
|
|
getAction('/sys/org/depart', {}).then(res => {
|
if (res.success) {
|
// let array = this.filterData(res.result)
|
// this.dataSource = array
|
// console.info(this.dataSource)
|
// this.data = this.dataSource[0]
|
|
|
let array = res.result
|
this.dataSource = array
|
this.findDataByName(this.dataSource)
|
}
|
|
})
|
},
|
loadPostList() {
|
getAction('/sys/position/list', { pageNo: 1, pageSize: 1000,column:'sort',order:'asc' }).then((res) => {
|
if (res.success) {
|
this.postList = res.result.records
|
}
|
})
|
},
|
|
findDataByName(array) {
|
for (let i = 0; i < array.length; i++) {
|
let item = array[i]
|
if (item.label === this.rootNodeName) {
|
this.data = item
|
return
|
}
|
if (item.children) {
|
this.findDataByName(item.children)
|
}
|
}
|
|
},
|
|
filterData(result, pid) {
|
for (let i = 0; i < result.length; i++) {
|
let item = result[i]
|
item.label = item.departName
|
item.pid = pid
|
if (item.children) {
|
this.filterData(item.children, item.id)
|
}
|
}
|
return result
|
|
},
|
|
|
onMenus({ node, command }) {
|
console.log(node, command)
|
},
|
filter() {
|
this.$refs.tree.filter(this.keyword)
|
},
|
filterNodeMethod(value, data) {
|
if (!value) return true
|
return data.label.indexOf(value) !== -1
|
},
|
onExpand(e, data) {
|
console.log(e, data)
|
},
|
nodeDragMove(data) {
|
console.log(data)
|
},
|
beforeDragEnd(node, targetNode) {
|
return new Promise((resolve, reject) => {
|
if (!targetNode) reject()
|
if (node.id === targetNode.id) {
|
reject()
|
} else {
|
resolve()
|
}
|
})
|
},
|
nodeDragEnd(node, parentNode) {
|
node.id === parentNode.id && this.$Message.info('移动到自身')
|
},
|
onNodeClick(e, data) {
|
this.$message.info(data.label)
|
},
|
onNodeDblclick() {
|
this.$message.info('双击节点')
|
},
|
onNodeCopy() {
|
this.$message.success('复制成功')
|
},
|
handleNodeAdd(node) {
|
console.log(node)
|
this.$message.info('新增节点')
|
},
|
toggleExpandAll() {
|
this.toggleExpand(this.data, this.expandAll)
|
},
|
toggleExpand(data, val) {
|
if (Array.isArray(data)) {
|
data.forEach(item => {
|
this.$set(item, 'expand', val)
|
if (item.children) {
|
this.toggleExpand(item.children, val)
|
}
|
})
|
} else {
|
this.$set(data, 'expand', val)
|
if (data.children) {
|
this.toggleExpand(data.children, val)
|
}
|
}
|
},
|
// 页面元素转图片
|
toImage() {
|
// 手动创建一个 canvas 标签
|
const canvas = document.createElement('canvas')
|
// 获取父标签,意思是这个标签内的 DOM 元素生成图片
|
// imageTofile是给截图范围内的父级元素自定义的ref名称
|
let canvasBox = this.$refs.departOrg
|
// 获取父级的宽高
|
const width = parseInt(window.getComputedStyle(canvasBox).width)
|
const height = parseInt(window.getComputedStyle(canvasBox).height)
|
// 宽高 * 2 并放大 2 倍 是为了防止图片模糊
|
canvas.width = width * 2
|
canvas.height = height * 2
|
canvas.style.width = width + 'px'
|
canvas.style.height = height + 'px'
|
const context = canvas.getContext('2d')
|
context.scale(2, 2)
|
const options = {
|
backgroundColor: null,
|
canvas: canvas,
|
useCORS: true
|
}
|
html2canvas(canvasBox, options).then((canvas) => {
|
// toDataURL 图片格式转成 base64
|
let dataURL = canvas.toDataURL('image/png')
|
console.log(dataURL)
|
this.downloadImage(dataURL)
|
})
|
},
|
//下载图片
|
downloadImage(url) {
|
// 如果是在网页中可以直接创建一个 a 标签直接下载
|
let a = document.createElement('a')
|
a.href = url
|
a.download = '部门结构图'
|
a.click()
|
}
|
}
|
}
|
</script>
|
|
<style lang='less' scoped>
|
|
.app {
|
width: 100%;
|
//height: calc(100vh - 140px);
|
height: 2000px;
|
position: relative;
|
overflow: hidden;
|
}
|
|
|
.text-blod {
|
font-weight: bold;
|
}
|
|
.shadow {
|
box-shadow: 0 1px 5px rgba(0, 0, 0, .15);
|
}
|
|
.box-title {
|
display: inline-block;
|
white-space: nowrap;
|
}
|
|
.post-item {
|
margin-top: 10px;
|
|
.user-item {
|
padding: 2px 4px;
|
cursor: pointer;
|
word-break: break-word;
|
}
|
|
.gray {
|
color: darkgray;
|
}
|
}
|
|
|
.menu-handle {
|
position: fixed;
|
display: flex;
|
bottom: 30px;
|
right: 80px;
|
|
.menu-percent,
|
.menu-handle-item {
|
width: 32px;
|
height: 32px;
|
user-select: none;
|
line-height: 32px;
|
font-size: 12px;
|
text-align: center;
|
}
|
|
.menu-handle-item {
|
color: #999;
|
border: 1px solid #ddd;
|
cursor: pointer;
|
position: relative;
|
background: #fff;
|
|
&:not(:last-child) {
|
margin-right: 6px;
|
}
|
|
&.zoom-out {
|
margin-right: -1px;
|
z-index: 2;
|
}
|
|
&.zoom-in:hover {
|
z-index: 3;
|
}
|
|
&:hover {
|
color: #2d8cf0;
|
background: #f0faff;
|
border-color: #2d8cf0;
|
|
.menu-restore {
|
border-color: #2d8cf0;
|
|
&:after {
|
border-color: #2d8cf0;
|
}
|
}
|
}
|
|
.menu-icon {
|
font-size: 18px;
|
}
|
|
.menu-restore {
|
display: inline-block;
|
width: 10px;
|
height: 10px;
|
border: 1px solid #aaa;
|
margin-left: -2px;
|
margin-right: -2px;
|
|
&:after {
|
content: "";
|
display: block;
|
height: 100%;
|
border-top: 1px solid #aaa;
|
border-right: 1px solid #aaa;
|
transform: translate(3px, -3px);
|
}
|
}
|
|
.menu-svg {
|
display: flex;
|
height: 100%;
|
justify-content: center;
|
align-items: center;
|
|
img {
|
width: 50%;
|
height: 50%;
|
vertical-align: middle;
|
opacity: 0.5;
|
}
|
}
|
}
|
}
|
|
.text-blue {
|
color: #1890FF;
|
}
|
</style>
|