【机构】左侧树加入虚拟滚动,优化业务授权机构逻辑

This commit is contained in:
俞宝山
2026-02-11 00:32:26 +08:00
parent 62eeabfdb4
commit 1c086d5b52
8 changed files with 299 additions and 103 deletions

View File

@@ -1,15 +1,18 @@
<template> <template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0"> <XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left> <template #left>
<a-tree <div ref="treeContainerRef" style="height: 100%">
v-if="treeData.length > 0" <a-tree
v-model:expandedKeys="defaultExpandedKeys" v-if="treeData.length > 0"
:tree-data="treeData" v-model:expandedKeys="defaultExpandedKeys"
:field-names="treeFieldNames" :tree-data="treeData"
:load-data="onLoadData" :field-names="treeFieldNames"
@select="treeSelect" :load-data="onLoadData"
/> :height="treeHeight"
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" /> @select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</template> </template>
<template #right> <template #right>
<a-form ref="searchFormRef" :model="searchFormState"> <a-form ref="searchFormRef" :model="searchFormState">
@@ -116,6 +119,7 @@
<script setup name="bizOrg"> <script setup name="bizOrg">
import { Empty } from 'ant-design-vue' import { Empty } from 'ant-design-vue'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { triggerRef, onMounted, onActivated, onUnmounted } from 'vue'
import bizOrgApi from '@/api/biz/bizOrgApi' import bizOrgApi from '@/api/biz/bizOrgApi'
import Form from './form.vue' import Form from './form.vue'
import CopyForm from './copyForm.vue' import CopyForm from './copyForm.vue'
@@ -168,10 +172,31 @@
const treeData = ref([]) const treeData = ref([])
// 替换treeNode 中 title,key,children // 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' } const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
}
}
onMounted(() => {
calcTreeHeight()
if (treeContainerRef.value) {
resizeObserver = new ResizeObserver(calcTreeHeight)
resizeObserver.observe(treeContainerRef.value)
}
})
onActivated(calcTreeHeight)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
// 表格查询 返回 Promise 对象 // 表格查询 返回 Promise 对象
const loadData = (parameter) => { const loadData = (parameter) => {
loadTreeData()
return bizOrgApi.orgPage(Object.assign(parameter, searchFormState.value)).then((res) => { return bizOrgApi.orgPage(Object.assign(parameter, searchFormState.value)).then((res) => {
return res return res
}) })
@@ -200,10 +225,11 @@
} }
}) })
} }
loadTreeData()
// 懒加载子节点 // 懒加载子节点
const onLoadData = (treeNode) => { const onLoadData = (treeNode) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (treeNode.dataRef.children) { if (treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
resolve() resolve()
return return
} }
@@ -218,7 +244,7 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf isLeaf: item.isLeaf === undefined ? false : item.isLeaf
} }
}) })
treeData.value = [...treeData.value] triggerRef(treeData)
resolve() resolve()
}) })
}) })

View File

@@ -1,15 +1,18 @@
<template> <template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0"> <XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left> <template #left>
<a-tree <div ref="treeContainerRef" style="height: 100%">
v-if="treeData.length > 0" <a-tree
v-model:expandedKeys="defaultExpandedKeys" v-if="treeData.length > 0"
:tree-data="treeData" v-model:expandedKeys="defaultExpandedKeys"
:field-names="treeFieldNames" :tree-data="treeData"
:load-data="onLoadData" :field-names="treeFieldNames"
@select="treeSelect" :load-data="onLoadData"
/> :height="treeHeight"
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" /> @select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</template> </template>
<template #right> <template #right>
<a-form ref="searchFormRef" :model="searchFormState"> <a-form ref="searchFormRef" :model="searchFormState">
@@ -107,6 +110,7 @@
<script setup name="bizPosition"> <script setup name="bizPosition">
import { Empty } from 'ant-design-vue' import { Empty } from 'ant-design-vue'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { triggerRef, onMounted, onActivated, onUnmounted } from 'vue'
import bizPositionApi from '@/api/biz/bizPositionApi' import bizPositionApi from '@/api/biz/bizPositionApi'
import bizOrgApi from '@/api/biz/bizOrgApi' import bizOrgApi from '@/api/biz/bizOrgApi'
import Form from './form.vue' import Form from './form.vue'
@@ -159,6 +163,28 @@
const treeData = ref([]) const treeData = ref([])
// 替换treeNode 中 title,key,children // 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' } const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
}
}
onMounted(() => {
calcTreeHeight()
if (treeContainerRef.value) {
resizeObserver = new ResizeObserver(calcTreeHeight)
resizeObserver.observe(treeContainerRef.value)
}
})
onActivated(calcTreeHeight)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
// 表格查询 返回 Promise 对象 // 表格查询 返回 Promise 对象
const loadData = (parameter) => { const loadData = (parameter) => {
@@ -210,7 +236,7 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf isLeaf: item.isLeaf === undefined ? false : item.isLeaf
} }
}) })
treeData.value = [...treeData.value] triggerRef(treeData)
resolve() resolve()
}) })
.catch(() => { .catch(() => {

View File

@@ -1,15 +1,18 @@
<template> <template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0"> <XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left> <template #left>
<a-tree <div ref="treeContainerRef" style="height: 100%">
v-if="treeData.length > 0" <a-tree
v-model:expandedKeys="defaultExpandedKeys" v-if="treeData.length > 0"
:tree-data="treeData" v-model:expandedKeys="defaultExpandedKeys"
:field-names="treeFieldNames" :tree-data="treeData"
:load-data="onLoadData" :field-names="treeFieldNames"
@select="treeSelect" :load-data="onLoadData"
/> :height="treeHeight"
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" /> @select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</template> </template>
<template #right> <template #right>
<a-form ref="searchFormRef" :model="searchFormState"> <a-form ref="searchFormRef" :model="searchFormState">
@@ -169,6 +172,7 @@
<script setup name="bizUser"> <script setup name="bizUser">
import { message, Empty } from 'ant-design-vue' import { message, Empty } from 'ant-design-vue'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { triggerRef, onMounted, onActivated, onUnmounted } from 'vue'
import tool from '@/utils/tool' import tool from '@/utils/tool'
import downloadUtil from '@/utils/downloadUtil' import downloadUtil from '@/utils/downloadUtil'
import bizUserApi from '@/api/biz/bizUserApi' import bizUserApi from '@/api/biz/bizUserApi'
@@ -235,6 +239,28 @@
const RoleSelectorPlusRef = ref() const RoleSelectorPlusRef = ref()
const selectedRecord = ref({}) const selectedRecord = ref({})
const loading = ref(false) const loading = ref(false)
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
}
}
onMounted(() => {
calcTreeHeight()
if (treeContainerRef.value) {
resizeObserver = new ResizeObserver(calcTreeHeight)
resizeObserver.observe(treeContainerRef.value)
}
})
onActivated(calcTreeHeight)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
// 表格查询 返回 Promise 对象 // 表格查询 返回 Promise 对象
const loadData = (parameter) => { const loadData = (parameter) => {
return bizUserApi.userPage(Object.assign(parameter, searchFormState.value)).then((res) => { return bizUserApi.userPage(Object.assign(parameter, searchFormState.value)).then((res) => {
@@ -285,7 +311,7 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf isLeaf: item.isLeaf === undefined ? false : item.isLeaf
} }
}) })
treeData.value = [...treeData.value] triggerRef(treeData)
resolve() resolve()
}) })
.catch(() => { .catch(() => {

View File

@@ -1,15 +1,18 @@
<template> <template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0"> <XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left> <template #left>
<a-tree <div ref="treeContainerRef" style="height: 100%">
v-if="treeData.length > 0" <a-tree
v-model:expandedKeys="defaultExpandedKeys" v-if="treeData.length > 0"
:tree-data="treeData" v-model:expandedKeys="defaultExpandedKeys"
:field-names="treeFieldNames" :tree-data="treeData"
:load-data="onLoadData" :field-names="treeFieldNames"
@select="treeSelect" :load-data="onLoadData"
/> :height="treeHeight"
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" /> @select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</template> </template>
<template #right> <template #right>
<a-form ref="searchFormRef" :model="searchFormState"> <a-form ref="searchFormRef" :model="searchFormState">
@@ -115,6 +118,7 @@
<script setup name="sysOrg"> <script setup name="sysOrg">
import { Empty } from 'ant-design-vue' import { Empty } from 'ant-design-vue'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { triggerRef, onMounted, onActivated, onUnmounted } from 'vue'
import orgApi from '@/api/sys/orgApi' import orgApi from '@/api/sys/orgApi'
import Form from './form.vue' import Form from './form.vue'
import CopyForm from './copyForm.vue' import CopyForm from './copyForm.vue'
@@ -165,10 +169,31 @@
const treeData = ref([]) const treeData = ref([])
// 替换treeNode 中 title,key,children // 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' } const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
}
}
onMounted(() => {
calcTreeHeight()
if (treeContainerRef.value) {
resizeObserver = new ResizeObserver(calcTreeHeight)
resizeObserver.observe(treeContainerRef.value)
}
})
onActivated(calcTreeHeight)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
// 表格查询 返回 Promise 对象 // 表格查询 返回 Promise 对象
const loadData = (parameter) => { const loadData = (parameter) => {
loadTreeData()
return orgApi.orgPage(Object.assign(parameter, searchFormState.value)).then((res) => { return orgApi.orgPage(Object.assign(parameter, searchFormState.value)).then((res) => {
return res return res
}) })
@@ -197,10 +222,11 @@
} }
}) })
} }
loadTreeData()
// 懒加载子节点 // 懒加载子节点
const onLoadData = (treeNode) => { const onLoadData = (treeNode) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (treeNode.dataRef.children) { if (treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
resolve() resolve()
return return
} }
@@ -215,7 +241,7 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf isLeaf: item.isLeaf === undefined ? false : item.isLeaf
} }
}) })
treeData.value = [...treeData.value] triggerRef(treeData)
resolve() resolve()
}) })
}) })

View File

@@ -1,15 +1,18 @@
<template> <template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0"> <XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left> <template #left>
<a-tree <div ref="treeContainerRef" style="height: 100%">
v-if="treeData.length > 0" <a-tree
v-model:expandedKeys="defaultExpandedKeys" v-if="treeData.length > 0"
:tree-data="treeData" v-model:expandedKeys="defaultExpandedKeys"
:field-names="treeFieldNames" :tree-data="treeData"
:load-data="onLoadData" :field-names="treeFieldNames"
@select="treeSelect" :load-data="onLoadData"
/> :height="treeHeight"
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" /> @select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</template> </template>
<template #right> <template #right>
<a-form ref="searchFormRef" :model="searchFormState"> <a-form ref="searchFormRef" :model="searchFormState">
@@ -107,6 +110,7 @@
<script setup name="sysPosition"> <script setup name="sysPosition">
import { Empty } from 'ant-design-vue' import { Empty } from 'ant-design-vue'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { triggerRef, onMounted, onActivated, onUnmounted } from 'vue'
import positionApi from '@/api/sys/positionApi' import positionApi from '@/api/sys/positionApi'
import orgApi from '@/api/sys/orgApi' import orgApi from '@/api/sys/orgApi'
import Form from './form.vue' import Form from './form.vue'
@@ -156,6 +160,28 @@
const treeData = ref([]) const treeData = ref([])
// 替换treeNode 中 title,key,children // 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' } const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
}
}
onMounted(() => {
calcTreeHeight()
if (treeContainerRef.value) {
resizeObserver = new ResizeObserver(calcTreeHeight)
resizeObserver.observe(treeContainerRef.value)
}
})
onActivated(calcTreeHeight)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
// 表格查询 返回 Promise 对象 // 表格查询 返回 Promise 对象
const loadData = (parameter) => { const loadData = (parameter) => {
@@ -188,7 +214,7 @@
// 懒加载子节点 // 懒加载子节点
const onLoadData = (treeNode) => { const onLoadData = (treeNode) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (treeNode.dataRef.children) { if (treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
resolve() resolve()
return return
} }
@@ -203,7 +229,7 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf isLeaf: item.isLeaf === undefined ? false : item.isLeaf
} }
}) })
treeData.value = [...treeData.value] triggerRef(treeData)
resolve() resolve()
}) })
}) })

View File

@@ -1,15 +1,18 @@
<template> <template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0"> <XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left> <template #left>
<a-tree <div ref="treeContainerRef" style="height: 100%">
v-if="treeData.length > 0" <a-tree
v-model:expandedKeys="defaultExpandedKeys" v-if="treeData.length > 0"
:tree-data="treeData" v-model:expandedKeys="defaultExpandedKeys"
:field-names="treeFieldNames" :tree-data="treeData"
:load-data="onLoadData" :field-names="treeFieldNames"
@select="treeSelect" :load-data="onLoadData"
/> :height="treeHeight"
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" /> @select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</template> </template>
<template #right> <template #right>
<a-form ref="searchFormRef" :model="searchFormState"> <a-form ref="searchFormRef" :model="searchFormState">
@@ -144,6 +147,7 @@
<script setup name="sysRole"> <script setup name="sysRole">
import { Empty } from 'ant-design-vue' import { Empty } from 'ant-design-vue'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { triggerRef, onMounted, onActivated, onUnmounted } from 'vue'
import roleApi from '@/api/sys/roleApi' import roleApi from '@/api/sys/roleApi'
import orgApi from '@/api/sys/orgApi' import orgApi from '@/api/sys/orgApi'
import GrantResourceForm from './grantResourceForm.vue' import GrantResourceForm from './grantResourceForm.vue'
@@ -202,6 +206,28 @@
const treeFieldNames = { children: 'children', title: 'name', key: 'id' } const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 记录数据 // 记录数据
const recordCacheData = ref({}) const recordCacheData = ref({})
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
}
}
onMounted(() => {
calcTreeHeight()
if (treeContainerRef.value) {
resizeObserver = new ResizeObserver(calcTreeHeight)
resizeObserver.observe(treeContainerRef.value)
}
})
onActivated(calcTreeHeight)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
// 表格查询 返回 Promise 对象 // 表格查询 返回 Promise 对象
const loadData = (parameter) => { const loadData = (parameter) => {
@@ -268,7 +294,7 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf isLeaf: item.isLeaf === undefined ? false : item.isLeaf
} }
}) })
treeData.value = [...treeData.value] triggerRef(treeData)
resolve() resolve()
}) })
.catch(() => { .catch(() => {

View File

@@ -1,15 +1,18 @@
<template> <template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0"> <XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left> <template #left>
<a-tree <div ref="treeContainerRef" style="height: 100%">
v-if="treeData.length > 0" <a-tree
v-model:expandedKeys="defaultExpandedKeys" v-if="treeData.length > 0"
:tree-data="treeData" v-model:expandedKeys="defaultExpandedKeys"
:field-names="treeFieldNames" :tree-data="treeData"
:load-data="onLoadData" :field-names="treeFieldNames"
@select="treeSelect" :load-data="onLoadData"
/> :height="treeHeight"
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" /> @select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</template> </template>
<template #right> <template #right>
<a-form ref="searchFormRef" :model="searchFormState"> <a-form ref="searchFormRef" :model="searchFormState">
@@ -187,6 +190,7 @@
<script setup name="sysUser"> <script setup name="sysUser">
import { message, Empty } from 'ant-design-vue' import { message, Empty } from 'ant-design-vue'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { triggerRef, onMounted, onActivated, onUnmounted } from 'vue'
import tool from '@/utils/tool' import tool from '@/utils/tool'
import downloadUtil from '@/utils/downloadUtil' import downloadUtil from '@/utils/downloadUtil'
import userApi from '@/api/sys/userApi' import userApi from '@/api/sys/userApi'
@@ -256,6 +260,28 @@
const ImpExpRef = ref() const ImpExpRef = ref()
const grantResourceFormRef = ref() const grantResourceFormRef = ref()
const grantPermissionFormRef = ref() const grantPermissionFormRef = ref()
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
}
}
onMounted(() => {
calcTreeHeight()
if (treeContainerRef.value) {
resizeObserver = new ResizeObserver(calcTreeHeight)
resizeObserver.observe(treeContainerRef.value)
}
})
onActivated(calcTreeHeight)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
// 表格查询 返回 Promise 对象 // 表格查询 返回 Promise 对象
const loadData = (parameter) => { const loadData = (parameter) => {
return userApi.userPage(Object.assign(parameter, searchFormState.value)).then((res) => { return userApi.userPage(Object.assign(parameter, searchFormState.value)).then((res) => {
@@ -282,7 +308,7 @@
// 懒加载子节点 // 懒加载子节点
const onLoadData = (treeNode) => { const onLoadData = (treeNode) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (treeNode.dataRef.children) { if (treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
resolve() resolve()
return return
} }
@@ -297,7 +323,7 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf isLeaf: item.isLeaf === undefined ? false : item.isLeaf
} }
}) })
treeData.value = [...treeData.value] triggerRef(treeData)
resolve() resolve()
}) })
}) })

View File

@@ -139,38 +139,52 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
@Override @Override
public List<JSONObject> treeLazy(BizOrgTreeLazyParam bizOrgTreeLazyParam) { public List<JSONObject> treeLazy(BizOrgTreeLazyParam bizOrgTreeLazyParam) {
String parentId = ObjectUtil.isNotEmpty(bizOrgTreeLazyParam.getParentId()) ? bizOrgTreeLazyParam.getParentId() : "0"; String parentId = ObjectUtil.isNotEmpty(bizOrgTreeLazyParam.getParentId()) ? bizOrgTreeLazyParam.getParentId() : "0";
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope(); List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if (ObjectUtil.isEmpty(loginUserDataScope)) {
// 获取所有机构
List<BizOrg> allOrgList = this.getAllOrgList();
Set<String> visibleOrgIds = null;
// 如果数据范围不为空则计算可见机构ID集合包含自身及其所有父级
if (ObjectUtil.isNotEmpty(loginUserDataScope)) {
Set<BizOrg> bizOrgSet = CollectionUtil.newHashSet();
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(this.getParentListById(allOrgList, orgId, true)));
visibleOrgIds = bizOrgSet.stream().map(BizOrg::getId).collect(Collectors.toSet());
}
// 过滤出当前父级下的可见子级
final Set<String> finalVisibleOrgIds = visibleOrgIds;
List<BizOrg> sysOrgList = allOrgList.stream()
.filter(bizOrg -> bizOrg.getParentId().equals(parentId))
.filter(bizOrg -> finalVisibleOrgIds == null || finalVisibleOrgIds.contains(bizOrg.getId()))
.sorted(Comparator.comparingInt(BizOrg::getSortCode).thenComparing(BizOrg::getId))
.collect(Collectors.toList());
if (ObjectUtil.isEmpty(sysOrgList)) {
return CollectionUtil.newArrayList(); return CollectionUtil.newArrayList();
} }
// 判断这些机构是否还有可见子机构 // 获取所有机构
return sysOrgList.stream().map(bizOrg -> { List<BizOrg> allOrgList = this.getAllOrgList();
// 构建索引避免重复线性遍历O(N) 一次性构建)
Map<String, BizOrg> orgById = allOrgList.stream()
.collect(Collectors.toMap(BizOrg::getId, org -> org, (a, b) -> a));
Map<String, List<BizOrg>> childrenByParentId = allOrgList.stream()
.collect(Collectors.groupingBy(BizOrg::getParentId));
// 计算可见机构ID集合包含自身及其所有父级使用索引将复杂度从 O(M*N*D) 降至 O(M*D)
Set<String> visibleOrgIds = new HashSet<>();
for (String orgId : loginUserDataScope) {
if (orgById.containsKey(orgId)) {
visibleOrgIds.add(orgId);
// 向上遍历添加所有父级
String currentId = orgById.get(orgId).getParentId();
while (ObjectUtil.isNotEmpty(currentId) && !"0".equals(currentId) && orgById.containsKey(currentId)) {
if (!visibleOrgIds.add(currentId)) {
break; // 已经添加过,停止遍历
}
currentId = orgById.get(currentId).getParentId();
}
}
}
// 过滤出当前父级下的可见子级(使用 parentId 索引O(K) 而非 O(N)
List<BizOrg> childList = childrenByParentId.getOrDefault(parentId, Collections.emptyList()).stream()
.filter(bizOrg -> visibleOrgIds.contains(bizOrg.getId()))
.sorted(Comparator.comparingInt(BizOrg::getSortCode).thenComparing(BizOrg::getId))
.collect(Collectors.toList());
if (ObjectUtil.isEmpty(childList)) {
return CollectionUtil.newArrayList();
}
// 判断这些机构是否还有可见子机构(使用 parentId 索引O(1) 查找子节点列表)
return childList.stream().map(bizOrg -> {
JSONObject jsonObject = JSONUtil.parseObj(bizOrg); JSONObject jsonObject = JSONUtil.parseObj(bizOrg);
// 计算是否有可见的子节点 List<BizOrg> subChildren = childrenByParentId.getOrDefault(bizOrg.getId(), Collections.emptyList());
boolean hasChildren = allOrgList.stream() boolean hasChildren = subChildren.stream().anyMatch(item -> visibleOrgIds.contains(item.getId()));
.anyMatch(item -> item.getParentId().equals(bizOrg.getId()) && (finalVisibleOrgIds == null || finalVisibleOrgIds.contains(item.getId())));
jsonObject.set("isLeaf", !hasChildren); jsonObject.set("isLeaf", !hasChildren);
return jsonObject; return jsonObject;
}).collect(Collectors.toList()); }).collect(Collectors.toList());