|
@@ -1,12 +1,34 @@
|
|
|
<template>
|
|
|
- <div class="fullBox white pa-3">
|
|
|
+ <div class="fullBox white pa-3 relative" ref="boxRef">
|
|
|
<div ref="graphRef" v-loading="loading" class="fullBox"></div>
|
|
|
+ <div class="btnBox">
|
|
|
+ <el-upload class="el-button pa-0" action="#" :show-file-list="false" :http-request="onImport">
|
|
|
+ <m-button type="orange" icon="el-icon-upload2" size="small" :loading="importLoading">导入</m-button>
|
|
|
+ </el-upload>
|
|
|
+ <m-button type="orange" icon="el-icon-download" size="small" :loading="exportLoading" @click="onExport">导出</m-button>
|
|
|
+ <m-button type="orange" icon="el-icon-download" size="small" :loading="downloadLoading" @click="onDownload">模板下载</m-button>
|
|
|
+ <m-button type="orange" icon="el-icon-plus" size="small" @click="onAdd">新增机构</m-button>
|
|
|
+ </div>
|
|
|
+ <div ref="contextMenuRefs" class="contextMenu">
|
|
|
+ <el-card shadow="always" :body-style="{padding: 0}">
|
|
|
+ <div
|
|
|
+ v-for="menu in menus"
|
|
|
+ :key="menu.prop"
|
|
|
+ class="contextMenuItem pa-3"
|
|
|
+ @click="onMenuClick(menu.prop)"
|
|
|
+ >
|
|
|
+ {{ menu.label }}
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
<OrganizationEdit ref="organizationEditRefs" @success="onRefresh"></OrganizationEdit>
|
|
|
+ <OrganizationAdd ref="organizationAddRefs" @success="onRefresh"></OrganizationAdd>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import OrganizationEdit from './organizationEdit.vue'
|
|
|
+import OrganizationAdd from './organizationAdd.vue'
|
|
|
import {
|
|
|
CollapseExpandTree,
|
|
|
MindMapNode
|
|
@@ -14,13 +36,24 @@ import {
|
|
|
import {
|
|
|
Graph,
|
|
|
register,
|
|
|
+ NodeEvent,
|
|
|
+ CommonEvent,
|
|
|
+ CanvasEvent,
|
|
|
ExtensionCategory
|
|
|
} from '@antv/g6'
|
|
|
import {
|
|
|
getOrganizationAtlas,
|
|
|
- getOrganizationAtlasEmployee
|
|
|
+ getOrganizationAtlasEmployee,
|
|
|
+ importOrganization,
|
|
|
+ exportOrganization,
|
|
|
+ downloadOrganization,
|
|
|
+ deleteOrganization
|
|
|
} from '@/api/system'
|
|
|
import { mapGetters } from 'vuex'
|
|
|
+import {
|
|
|
+ upload,
|
|
|
+ download
|
|
|
+} from '@/utils/elementUploadAndDownload'
|
|
|
|
|
|
const NODE_TYPE = {
|
|
|
type: 'MindMapNode',
|
|
@@ -30,9 +63,9 @@ const NODE_TYPE = {
|
|
|
size: 15,
|
|
|
label: true,
|
|
|
labelFontSize: 14,
|
|
|
- labelLineHeight: d.depth === 3 ? 20 : 48,
|
|
|
+ labelLineHeight: 20,
|
|
|
labelPlacement: 'right',
|
|
|
- labelPadding: d.depth === 3 ? [10] : [0, 15, 20, 15],
|
|
|
+ labelPadding: [10],
|
|
|
labelText: d.name,
|
|
|
labelOffsetX: d.depth === 3 ? 20 : 50,
|
|
|
labelBackground: true,
|
|
@@ -47,11 +80,20 @@ const NODE_TYPE = {
|
|
|
export default {
|
|
|
name: 'organization-structure',
|
|
|
components: {
|
|
|
- OrganizationEdit
|
|
|
+ OrganizationEdit,
|
|
|
+ OrganizationAdd
|
|
|
},
|
|
|
data () {
|
|
|
return {
|
|
|
+ menus: [
|
|
|
+ { label: '编辑', prop: 'edit' },
|
|
|
+ { label: '删除', prop: 'delete' }
|
|
|
+ ],
|
|
|
+ nodes: null,
|
|
|
loading: false,
|
|
|
+ importLoading: false,
|
|
|
+ exportLoading: false,
|
|
|
+ downloadLoading: false,
|
|
|
graph: null
|
|
|
}
|
|
|
},
|
|
@@ -61,10 +103,27 @@ export default {
|
|
|
mounted () {
|
|
|
register(ExtensionCategory.BEHAVIOR, 'collapse-expand-tree', CollapseExpandTree)
|
|
|
register(ExtensionCategory.NODE, 'MindMapNode', MindMapNode)
|
|
|
- // this.renderGraph(treeToGraphData(this.assignTree(this.organizationTree)[0]))
|
|
|
this.onInit()
|
|
|
+ this.$nextTick(() => {
|
|
|
+ document.addEventListener('click', this.onClick)
|
|
|
+ this.$refs.boxRef.addEventListener('contextmenu', (e) => {
|
|
|
+ e.preventDefault()
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ beforeDestroy () {
|
|
|
+ if (this.graph) {
|
|
|
+ this.graph.off()
|
|
|
+ }
|
|
|
+ document.removeEventListener('click', this.onClick)
|
|
|
+ this.$refs.boxRef.removeEventListener('contextmenu', (e) => {
|
|
|
+ e.preventDefault()
|
|
|
+ })
|
|
|
},
|
|
|
methods: {
|
|
|
+ onClick () {
|
|
|
+ this.$refs.contextMenuRefs.style.display = 'none'
|
|
|
+ },
|
|
|
async getChildren (organizationNo) {
|
|
|
try {
|
|
|
const { data } = await getOrganizationAtlasEmployee({ organizationNo })
|
|
@@ -105,6 +164,10 @@ export default {
|
|
|
})
|
|
|
},
|
|
|
async renderGraph (data) {
|
|
|
+ if (this.graph) {
|
|
|
+ this.graph.updateData(data)
|
|
|
+ return
|
|
|
+ }
|
|
|
const graph = new Graph({
|
|
|
container: this.$refs.graphRef,
|
|
|
width: this.$refs.graphRef.clientWidth,
|
|
@@ -114,7 +177,7 @@ export default {
|
|
|
autoResize: false,
|
|
|
enable: false,
|
|
|
plugins: [
|
|
|
- 'minimap',
|
|
|
+ // 'minimap',
|
|
|
// 'contextmenu',
|
|
|
{
|
|
|
className: 'toolbar',
|
|
@@ -124,9 +187,6 @@ export default {
|
|
|
{ id: 'zoom-in', value: 'zoom-in' },
|
|
|
{ id: 'zoom-out', value: 'zoom-out' },
|
|
|
{ id: 'auto-fit', value: 'auto-fit' }
|
|
|
- // { id: 'export', value: 'export' },
|
|
|
- // { id: 'request-fullscreen', value: 'request-fullscreen' },
|
|
|
- // { id: 'exit-fullscreen', value: 'exit-fullscreen' }
|
|
|
],
|
|
|
onClick: (value) => {
|
|
|
const zoom = graph.getZoom()
|
|
@@ -149,12 +209,10 @@ export default {
|
|
|
],
|
|
|
behaviors: [
|
|
|
'drag-canvas',
|
|
|
- // 'zoom-canvas',
|
|
|
'scroll-canvas',
|
|
|
'drag-element',
|
|
|
'collapse-expand-tree'
|
|
|
],
|
|
|
- // transforms: ['assign-color-by-branch'],
|
|
|
animation: false,
|
|
|
layout: {
|
|
|
type: 'compact-box',
|
|
@@ -165,22 +223,12 @@ export default {
|
|
|
return 32
|
|
|
},
|
|
|
getVGap: function getVGap (e) {
|
|
|
- const { depth } = graph.getNodeData(e.id)
|
|
|
- return depth === 3 ? 8 : 20
|
|
|
+ return 8
|
|
|
},
|
|
|
getHGap: function getHGap (e) {
|
|
|
const { depth } = graph.getNodeData(e.id)
|
|
|
return depth < 2 ? 100 : 140
|
|
|
},
|
|
|
- // preventOverlap: true, // 防止节点重叠
|
|
|
- // iterations: 200, // 迭代次数
|
|
|
- // animation: true, // 启用布局动画
|
|
|
- // direction: 'LR',
|
|
|
- // nodeSep: 50,
|
|
|
- // rankSep: 500, // 层间距(px)
|
|
|
- // ranker: 'tight-tree', // 布局的模式 'network-simplex' | 'tight-tree' | 'longest-path'
|
|
|
- // rankdir: 'LR', // 布局的方向
|
|
|
- // nodeSize: 50, // 节点大小(直径)
|
|
|
radial: false
|
|
|
},
|
|
|
node: NODE_TYPE,
|
|
@@ -197,15 +245,39 @@ export default {
|
|
|
})
|
|
|
// graph.zoomTo(0.7)
|
|
|
graph.render()
|
|
|
- // await graph.collapseElement('74bf386cdba4b1ff60c8ac09b01c2e91', {
|
|
|
- // align: true
|
|
|
- // })
|
|
|
this.graph = graph
|
|
|
+ const contextMenu = this.$refs.contextMenuRefs
|
|
|
+ const { width, height } = this.$refs.boxRef.getBoundingClientRect()
|
|
|
+ const { left, top } = this.$refs.boxRef.getBoundingClientRect()
|
|
|
+ const content = {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ }
|
|
|
+ graph.on(NodeEvent.CONTEXT_MENU, (e) => {
|
|
|
+ const { target } = e // 获取被点击节点的 ID
|
|
|
+ this.nodes = graph.getNodeData(target.id)
|
|
|
+ e.preventDefault()
|
|
|
+ const { x, y } = e.client
|
|
|
+ content.x = x
|
|
|
+ content.y = y
|
|
|
+ contextMenu.style.left = x - left + 'px'
|
|
|
+ contextMenu.style.top = y - top + 'px'
|
|
|
+ contextMenu.style.display = 'block'
|
|
|
+ }).on(CommonEvent.WHEEL, () => {
|
|
|
+ contextMenu.style.display = 'none'
|
|
|
+ }).on(CanvasEvent.CONTEXT_MENU, (e) => {
|
|
|
+ contextMenu.style.display = 'none'
|
|
|
+ })
|
|
|
},
|
|
|
- editTag (nodes) {
|
|
|
- this.$refs.organizationEditRefs.open(nodes)
|
|
|
+ async editTag (nodes) {
|
|
|
+ this.loading = true
|
|
|
+ await this.$refs.organizationEditRefs.open(nodes)
|
|
|
+ this.loading = false
|
|
|
},
|
|
|
async onInit () {
|
|
|
+ this.loading = true
|
|
|
try {
|
|
|
const { data } = await getOrganizationAtlas()
|
|
|
const { nodes, edges } = data
|
|
@@ -213,28 +285,83 @@ export default {
|
|
|
nodes: nodes.map(e => {
|
|
|
return {
|
|
|
...e,
|
|
|
- getChildren: this.getChildren,
|
|
|
- editTag: this.editTag
|
|
|
+ getChildren: this.getChildren
|
|
|
}
|
|
|
}),
|
|
|
edges
|
|
|
})
|
|
|
} catch (error) {
|
|
|
this.$message.error(error)
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
}
|
|
|
},
|
|
|
onRefresh () {
|
|
|
this.graph.render()
|
|
|
+ },
|
|
|
+ async onImport (response) {
|
|
|
+ this.importLoading = true
|
|
|
+ await upload(importOrganization, response.file, this.onInit)
|
|
|
+ this.importLoading = false
|
|
|
+ },
|
|
|
+ async onExport () {
|
|
|
+ this.exportLoading = true
|
|
|
+ await download(exportOrganization)
|
|
|
+ this.exportLoading = false
|
|
|
+ },
|
|
|
+ async onDownload () {
|
|
|
+ this.downloadLoading = true
|
|
|
+ await download(downloadOrganization)
|
|
|
+ this.downloadLoading = false
|
|
|
+ },
|
|
|
+ onAdd () {
|
|
|
+ this.$refs.organizationAddRefs.open()
|
|
|
+ },
|
|
|
+ async onDelete (nodes) {
|
|
|
+ this.$confirm('是否删除该项', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ confirmButtonClass: 'el-button--orange'
|
|
|
+ }).then(async () => {
|
|
|
+ try {
|
|
|
+ await deleteOrganization({
|
|
|
+ organizationNo: nodes.id
|
|
|
+ })
|
|
|
+ this.$message.success('删除成功')
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(error)
|
|
|
+ }
|
|
|
+ }).catch(_ => {})
|
|
|
+ },
|
|
|
+ onMenuClick (prop) {
|
|
|
+ if (prop === 'edit') {
|
|
|
+ this.editTag(this.nodes)
|
|
|
+ }
|
|
|
+ if (prop === 'delete') {
|
|
|
+ this.onDelete(this.nodes)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
+.contextMenu {
|
|
|
+ display: none;
|
|
|
+ position: absolute;
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
.fullBox {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
box-sizing: border-box;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+.btnBox {
|
|
|
+ position: absolute;
|
|
|
+ left: 10px;
|
|
|
+ top: 10px;
|
|
|
}
|
|
|
|
|
|
::v-deep .toolbar {
|
|
@@ -248,4 +375,18 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+.relative {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.contextMenuItem {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ cursor: pointer;
|
|
|
+ &:hover {
|
|
|
+ color: #FFF;
|
|
|
+ background: $theme-color;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
</style>
|