【升级】彻底优化登录逻辑、大数据量下的机构以及in拆分

This commit is contained in:
俞宝山
2026-02-12 21:11:53 +08:00
parent 6e1a071a73
commit 8dcf6075ff
26 changed files with 1090 additions and 284 deletions

View File

@@ -58,6 +58,7 @@
<script setup name="bizOrgForm">
import { required } from '@/utils/formRules'
import bizOrgApi from '@/api/biz/bizOrgApi'
import userCenterApi from '@/api/sys/userCenterApi'
import tool from '@/utils/tool'
// 定义emit事件
@@ -72,6 +73,30 @@
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgInTree = (orgId) => {
if (!orgId || orgId === '0' || findNodeInTree(treeData.value, orgId)) return
userCenterApi.userCenterGetOrgListByIdList({ idList: [orgId] }).then((data) => {
if (data && data.length > 0 && treeData.value.length > 0) {
const topNode = treeData.value[0]
if (topNode.children) {
topNode.children.push({ ...data[0], isLeaf: true })
} else {
topNode.children = [{ ...data[0], isLeaf: true }]
}
treeData.value = [...treeData.value]
}
})
}
// 打开抽屉
const onOpen = (record, parentId) => {
visible.value = true
@@ -81,16 +106,8 @@
if (parentId) {
formData.value.parentId = parentId
}
if (record) {
const param = {
id: record.id
}
bizOrgApi.orgDetail(param).then((data) => {
formData.value = Object.assign({}, data)
})
}
// 获取机构树并加入顶级
bizOrgApi.orgTreeLazySelector().then((res) => {
const treePromise = bizOrgApi.orgTreeLazySelector().then((res) => {
treeData.value = [
{
id: '0',
@@ -106,6 +123,18 @@
}
]
})
if (record) {
const detailPromise = bizOrgApi.orgDetail({ id: record.id }).then((data) => {
formData.value = Object.assign({}, data)
return data
})
// 等待树和详情都加载完成后,确保父节点可回显
Promise.all([treePromise, detailPromise]).then(([_, detail]) => {
if (detail.parentId) {
ensureOrgInTree(detail.parentId)
}
})
}
}
// 懒加载子节点
const onLoadData = (treeNode) => {

View File

@@ -1,18 +1,28 @@
<template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left>
<div ref="treeContainerRef" style="height: 100%">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
:height="treeHeight"
@select="treeSelect"
<div ref="treeContainerRef" style="height: 100%; display: flex; flex-direction: column">
<a-input-search
v-model:value="treeSearchKey"
placeholder="搜索机构"
allow-clear
size="small"
style="margin-bottom: 8px; flex-shrink: 0"
@search="onTreeSearch"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</template>
<template #right>
@@ -179,7 +189,7 @@
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
treeHeight.value = treeContainerRef.value.clientHeight - 40
}
}
onMounted(() => {
@@ -208,6 +218,44 @@
tableRef.value.refresh(true)
}
const treeLoading = ref(true)
const treeSearchKey = ref('')
const searchMode = ref(false)
// 收集树所有节点key用于搜索时全部展开
const collectTreeKeys = (nodes) => {
const keys = []
const traverse = (list) => {
if (!list) return
list.forEach((node) => {
keys.push(node.id)
if (node.children) traverse(node.children)
})
}
traverse(nodes)
return keys
}
// 树搜索
const onTreeSearch = (value) => {
if (!value || !value.trim()) {
searchMode.value = false
loadTreeData()
return
}
treeLoading.value = true
searchMode.value = true
bizOrgApi
.orgTree({ searchKey: value.trim() })
.then((res) => {
if (res !== null) {
treeData.value = res
defaultExpandedKeys.value = collectTreeKeys(res)
} else {
treeData.value = []
}
})
.finally(() => {
treeLoading.value = false
})
}
// 加载左侧的树
const loadTreeData = () => {
treeLoading.value = true

View File

@@ -50,6 +50,7 @@
<script setup name="bizPositionForm">
import { required } from '@/utils/formRules'
import bizPositionApi from '@/api/biz/bizPositionApi'
import userCenterApi from '@/api/sys/userCenterApi'
import tool from '@/utils/tool'
// 定义emit事件
@@ -63,6 +64,25 @@
const treeData = ref([])
const submitLoading = ref(false)
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgInTree = (orgId) => {
if (!orgId || findNodeInTree(treeData.value, orgId)) return
userCenterApi.userCenterGetOrgListByIdList({ idList: [orgId] }).then((data) => {
if (data && data.length > 0) {
treeData.value.push({ ...data[0], isLeaf: true })
treeData.value = [...treeData.value]
}
})
}
// 打开抽屉
const onOpen = (record, orgId) => {
visible.value = true
@@ -83,6 +103,10 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
// 编辑时确保选中的机构可回显
if (record && formData.value.orgId) {
ensureOrgInTree(formData.value.orgId)
}
})
}
// 懒加载子节点

View File

@@ -1,18 +1,28 @@
<template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left>
<div ref="treeContainerRef" style="height: 100%">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
:height="treeHeight"
@select="treeSelect"
<div ref="treeContainerRef" style="height: 100%; display: flex; flex-direction: column">
<a-input-search
v-model:value="treeSearchKey"
placeholder="搜索机构"
allow-clear
size="small"
style="margin-bottom: 8px; flex-shrink: 0"
@search="onTreeSearch"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</template>
<template #right>
@@ -170,7 +180,7 @@
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
treeHeight.value = treeContainerRef.value.clientHeight - 40
}
}
onMounted(() => {
@@ -199,6 +209,42 @@
tableRef.value.refresh(true)
}
const treeLoading = ref(true)
const treeSearchKey = ref('')
const searchMode = ref(false)
const collectTreeKeys = (nodes) => {
const keys = []
const traverse = (list) => {
if (!list) return
list.forEach((node) => {
keys.push(node.id)
if (node.children) traverse(node.children)
})
}
traverse(nodes)
return keys
}
const onTreeSearch = (value) => {
if (!value || !value.trim()) {
searchMode.value = false
loadTreeData()
return
}
treeLoading.value = true
searchMode.value = true
bizOrgApi
.orgTree({ searchKey: value.trim() })
.then((res) => {
if (res !== null) {
treeData.value = res
defaultExpandedKeys.value = collectTreeKeys(res)
} else {
treeData.value = []
}
})
.finally(() => {
treeLoading.value = false
})
}
// 加载左侧的树
const loadTreeData = () => {
treeLoading.value = true

View File

@@ -322,6 +322,30 @@
const formData = ref({})
const treeFieldNames = { children: 'children', title: 'dictLabel', key: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgsInTree = (orgIds) => {
const missingIds = orgIds.filter((id) => id && !findNodeInTree(treeData.value, id))
if (missingIds.length === 0) return
userCenterApi.userCenterGetOrgListByIdList({ idList: missingIds }).then((data) => {
if (data && data.length > 0) {
data.forEach((org) => {
treeData.value.push({ ...org, isLeaf: true })
})
treeData.value = [...treeData.value]
}
})
}
// 树加载Promise用于协调回显时序
let treeLoadPromise = null
// 打开抽屉
const onOpen = (record, orgId) => {
visible.value = true
@@ -341,7 +365,7 @@
}
nextTick(() => {
// 机构选择器数据
bizUserApi.userOrgTreeLazySelector().then((res) => {
treeLoadPromise = bizUserApi.userOrgTreeLazySelector().then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
@@ -392,7 +416,7 @@
id: record.id
}
// 查询详情
bizUserApi.userDetail(param).then((data) => {
const detailPromise = bizUserApi.userDetail(param).then((data) => {
if (data.positionJson) {
// 替换表单中的格式与后端查到的
data.positionJson = JSON.parse(data.positionJson)
@@ -407,6 +431,24 @@
})
}
selePositionData(formData.value.orgId)
return data
})
// 等待树和详情都加载完成后,确保机构节点可回显
nextTick(() => {
if (treeLoadPromise) {
Promise.all([treeLoadPromise, detailPromise]).then(([_, detail]) => {
const orgIds = [detail.orgId]
if (detail.positionJson) {
const positions = typeof detail.positionJson === 'string'
? JSON.parse(detail.positionJson)
: detail.positionJson
positions.forEach((item) => {
if (item.orgId) orgIds.push(item.orgId)
})
}
ensureOrgsInTree(orgIds)
})
}
})
}
// 默认要校验的

View File

@@ -1,18 +1,28 @@
<template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left>
<div ref="treeContainerRef" style="height: 100%">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
:height="treeHeight"
@select="treeSelect"
<div ref="treeContainerRef" style="height: 100%; display: flex; flex-direction: column">
<a-input-search
v-model:value="treeSearchKey"
placeholder="搜索机构"
allow-clear
size="small"
style="margin-bottom: 8px; flex-shrink: 0"
@search="onTreeSearch"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</template>
<template #right>
@@ -246,7 +256,7 @@
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
treeHeight.value = treeContainerRef.value.clientHeight - 40
}
}
onMounted(() => {
@@ -274,6 +284,42 @@
tableRef.value.refresh(true)
}
const treeLoading = ref(true)
const treeSearchKey = ref('')
const searchMode = ref(false)
const collectTreeKeys = (nodes) => {
const keys = []
const traverse = (list) => {
if (!list) return
list.forEach((node) => {
keys.push(node.id)
if (node.children) traverse(node.children)
})
}
traverse(nodes)
return keys
}
const onTreeSearch = (value) => {
if (!value || !value.trim()) {
searchMode.value = false
loadTreeData()
return
}
treeLoading.value = true
searchMode.value = true
bizOrgApi
.orgTree({ searchKey: value.trim() })
.then((res) => {
if (res !== null) {
treeData.value = res
defaultExpandedKeys.value = collectTreeKeys(res)
} else {
treeData.value = []
}
})
.finally(() => {
treeLoading.value = false
})
}
// 加载左侧树
const loadTreeData = () => {
treeLoading.value = true

View File

@@ -58,6 +58,7 @@
<script setup name="orgForm">
import { required } from '@/utils/formRules'
import orgApi from '@/api/sys/orgApi'
import userCenterApi from '@/api/sys/userCenterApi'
import tool from '@/utils/tool'
// 定义emit事件
@@ -71,6 +72,30 @@
const treeData = ref([])
const submitLoading = ref(false)
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgInTree = (orgId) => {
if (!orgId || orgId === '0' || findNodeInTree(treeData.value, orgId)) return
userCenterApi.userCenterGetOrgListByIdList({ idList: [orgId] }).then((data) => {
if (data && data.length > 0 && treeData.value.length > 0) {
const topNode = treeData.value[0]
if (topNode.children) {
topNode.children.push({ ...data[0], isLeaf: true })
} else {
topNode.children = [{ ...data[0], isLeaf: true }]
}
treeData.value = [...treeData.value]
}
})
}
// 打开抽屉
const onOpen = (record, parentId) => {
visible.value = true
@@ -80,16 +105,8 @@
if (parentId) {
formData.value.parentId = parentId
}
if (record) {
const param = {
id: record.id
}
orgApi.orgDetail(param).then((data) => {
formData.value = Object.assign({}, data)
})
}
// 获取机构树并加入顶级
orgApi.orgOrgTreeLazySelector().then((res) => {
const treePromise = orgApi.orgOrgTreeLazySelector().then((res) => {
treeData.value = [
{
id: '0',
@@ -105,6 +122,18 @@
}
]
})
if (record) {
const detailPromise = orgApi.orgDetail({ id: record.id }).then((data) => {
formData.value = Object.assign({}, data)
return data
})
// 等待树和详情都加载完成后,确保父节点可回显
Promise.all([treePromise, detailPromise]).then(([_, detail]) => {
if (detail.parentId) {
ensureOrgInTree(detail.parentId)
}
})
}
}
// 懒加载子节点
const onLoadData = (treeNode) => {

View File

@@ -1,18 +1,28 @@
<template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left>
<div ref="treeContainerRef" style="height: 100%">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
:height="treeHeight"
@select="treeSelect"
<div ref="treeContainerRef" style="height: 100%; display: flex; flex-direction: column">
<a-input-search
v-model:value="treeSearchKey"
placeholder="搜索组织"
allow-clear
size="small"
style="margin-bottom: 8px; flex-shrink: 0"
@search="onTreeSearch"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</template>
<template #right>
@@ -176,7 +186,8 @@
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
// 减去搜索框高度和间距
treeHeight.value = treeContainerRef.value.clientHeight - 40
}
}
onMounted(() => {
@@ -205,6 +216,45 @@
tableRef.value.refresh(true)
}
const treeLoading = ref(true)
const treeSearchKey = ref('')
const searchMode = ref(false)
// 收集树所有节点key用于搜索时全部展开
const collectTreeKeys = (nodes) => {
const keys = []
const traverse = (list) => {
if (!list) return
list.forEach((node) => {
keys.push(node.id)
if (node.children) traverse(node.children)
})
}
traverse(nodes)
return keys
}
// 树搜索
const onTreeSearch = (value) => {
if (!value || !value.trim()) {
// 清空搜索,恢复懒加载模式
searchMode.value = false
loadTreeData()
return
}
treeLoading.value = true
searchMode.value = true
orgApi
.orgTree({ searchKey: value.trim() })
.then((res) => {
if (res !== null) {
treeData.value = res
defaultExpandedKeys.value = collectTreeKeys(res)
} else {
treeData.value = []
}
})
.finally(() => {
treeLoading.value = false
})
}
// 加载左侧的树
const loadTreeData = () => {
treeLoading.value = true

View File

@@ -49,6 +49,7 @@
<script setup name="positionForm">
import { required } from '@/utils/formRules'
import positionApi from '@/api/sys/positionApi'
import userCenterApi from '@/api/sys/userCenterApi'
import tool from '@/utils/tool'
// 定义emit事件
@@ -62,6 +63,25 @@
const treeData = ref([])
const submitLoading = ref(false)
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgInTree = (orgId) => {
if (!orgId || findNodeInTree(treeData.value, orgId)) return
userCenterApi.userCenterGetOrgListByIdList({ idList: [orgId] }).then((data) => {
if (data && data.length > 0) {
treeData.value.push({ ...data[0], isLeaf: true })
treeData.value = [...treeData.value]
}
})
}
// 打开抽屉
const onOpen = (record, orgId) => {
visible.value = true
@@ -82,6 +102,10 @@
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
// 编辑时确保选中的机构可回显
if (record && formData.value.orgId) {
ensureOrgInTree(formData.value.orgId)
}
})
}
// 懒加载子节点

View File

@@ -1,18 +1,28 @@
<template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left>
<div ref="treeContainerRef" style="height: 100%">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
:height="treeHeight"
@select="treeSelect"
<div ref="treeContainerRef" style="height: 100%; display: flex; flex-direction: column">
<a-input-search
v-model:value="treeSearchKey"
placeholder="搜索组织"
allow-clear
size="small"
style="margin-bottom: 8px; flex-shrink: 0"
@search="onTreeSearch"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</template>
<template #right>
@@ -167,7 +177,7 @@
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
treeHeight.value = treeContainerRef.value.clientHeight - 40
}
}
onMounted(() => {
@@ -196,28 +206,68 @@
tableRef.value.refresh(true)
}
const treeLoading = ref(true)
const treeSearchKey = ref('')
const searchMode = ref(false)
const collectTreeKeys = (nodes) => {
const keys = []
const traverse = (list) => {
if (!list) return
list.forEach((node) => {
keys.push(node.id)
if (node.children) traverse(node.children)
})
}
traverse(nodes)
return keys
}
const onTreeSearch = (value) => {
if (!value || !value.trim()) {
searchMode.value = false
loadTreeData()
return
}
treeLoading.value = true
searchMode.value = true
orgApi
.orgTree({ searchKey: value.trim() })
.then((res) => {
if (res !== null) {
treeData.value = res
defaultExpandedKeys.value = collectTreeKeys(res)
} else {
treeData.value = []
}
})
.finally(() => {
treeLoading.value = false
})
}
// 加载左侧的树
orgApi
.orgTreeLazy()
.then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
if (isEmpty(defaultExpandedKeys.value)) {
// 默认展开第一级
if (treeData.value.length > 0) {
defaultExpandedKeys.value.push(treeData.value[0].id)
const loadTreeData = () => {
treeLoading.value = true
orgApi
.orgTreeLazy()
.then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
if (isEmpty(defaultExpandedKeys.value)) {
// 默认展开第一级
if (treeData.value.length > 0) {
defaultExpandedKeys.value.push(treeData.value[0].id)
}
}
}
}
})
.finally(() => {
treeLoading.value = false
})
})
.finally(() => {
treeLoading.value = false
})
}
loadTreeData()
// 懒加载子节点
const onLoadData = (treeNode) => {
return new Promise((resolve) => {

View File

@@ -1,18 +1,28 @@
<template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left>
<div ref="treeContainerRef" style="height: 100%">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
:height="treeHeight"
@select="treeSelect"
<div ref="treeContainerRef" style="height: 100%; display: flex; flex-direction: column">
<a-input-search
v-model:value="treeSearchKey"
placeholder="搜索组织"
allow-clear
size="small"
style="margin-bottom: 8px; flex-shrink: 0"
@search="onTreeSearch"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</template>
<template #right>
@@ -213,7 +223,7 @@
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
treeHeight.value = treeContainerRef.value.clientHeight - 40
}
}
onMounted(() => {
@@ -246,6 +256,43 @@
tableRef.value.refresh(true)
}
const treeLoading = ref(true)
const treeSearchKey = ref('')
const searchMode = ref(false)
const collectTreeKeys = (nodes) => {
const keys = []
const traverse = (list) => {
if (!list) return
list.forEach((node) => {
keys.push(node.id)
if (node.children) traverse(node.children)
})
}
traverse(nodes)
return keys
}
const onTreeSearch = (value) => {
if (!value || !value.trim()) {
searchMode.value = false
loadTreeData()
return
}
treeLoading.value = true
searchMode.value = true
orgApi
.orgTree({ searchKey: value.trim() })
.then((res) => {
if (res !== null) {
// 搜索模式下也保留全局节点
treeData.value = [{ id: 'GLOBAL', name: '全局', isLeaf: true }, ...res]
defaultExpandedKeys.value = collectTreeKeys(res)
} else {
treeData.value = [{ id: 'GLOBAL', name: '全局', isLeaf: true }]
}
})
.finally(() => {
treeLoading.value = false
})
}
// 加载左侧的树
const loadTreeData = () => {
treeLoading.value = true

View File

@@ -322,6 +322,30 @@
const formData = ref({})
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgsInTree = (orgIds) => {
const missingIds = orgIds.filter((id) => id && !findNodeInTree(treeData.value, id))
if (missingIds.length === 0) return
userCenterApi.userCenterGetOrgListByIdList({ idList: missingIds }).then((data) => {
if (data && data.length > 0) {
data.forEach((org) => {
treeData.value.push({ ...org, isLeaf: true })
})
treeData.value = [...treeData.value]
}
})
}
// 树加载Promise用于协调回显时序
let treeLoadPromise = null
// 打开抽屉
const onOpen = (record, orgId) => {
visible.value = true
@@ -341,7 +365,7 @@
}
nextTick(() => {
// 机构选择器数据
userApi.userOrgTreeLazySelector().then((res) => {
treeLoadPromise = userApi.userOrgTreeLazySelector().then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
@@ -392,7 +416,7 @@
id: record.id
}
// 查询详情
userApi.userDetail(param).then((data) => {
const detailPromise = userApi.userDetail(param).then((data) => {
if (data.positionJson) {
// 替换表单中的格式与后端查到的
data.positionJson = JSON.parse(data.positionJson)
@@ -407,6 +431,24 @@
})
}
selePositionData(formData.value.orgId)
return data
})
// 等待树和详情都加载完成后,确保机构节点可回显
nextTick(() => {
if (treeLoadPromise) {
Promise.all([treeLoadPromise, detailPromise]).then(([_, detail]) => {
const orgIds = [detail.orgId]
if (detail.positionJson) {
const positions = typeof detail.positionJson === 'string'
? JSON.parse(detail.positionJson)
: detail.positionJson
positions.forEach((item) => {
if (item.orgId) orgIds.push(item.orgId)
})
}
ensureOrgsInTree(orgIds)
})
}
})
}

View File

@@ -1,18 +1,28 @@
<template>
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
<template #left>
<div ref="treeContainerRef" style="height: 100%">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
:height="treeHeight"
@select="treeSelect"
<div ref="treeContainerRef" style="height: 100%; display: flex; flex-direction: column">
<a-input-search
v-model:value="treeSearchKey"
placeholder="搜索组织"
allow-clear
size="small"
style="margin-bottom: 8px; flex-shrink: 0"
@search="onTreeSearch"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</template>
<template #right>
@@ -267,7 +277,7 @@
let resizeObserver = null
const calcTreeHeight = () => {
if (treeContainerRef.value) {
treeHeight.value = treeContainerRef.value.clientHeight
treeHeight.value = treeContainerRef.value.clientHeight - 40
}
}
onMounted(() => {
@@ -290,28 +300,68 @@
})
}
const treeLoading = ref(true)
const treeSearchKey = ref('')
const searchMode = ref(false)
const collectTreeKeys = (nodes) => {
const keys = []
const traverse = (list) => {
if (!list) return
list.forEach((node) => {
keys.push(node.id)
if (node.children) traverse(node.children)
})
}
traverse(nodes)
return keys
}
const onTreeSearch = (value) => {
if (!value || !value.trim()) {
searchMode.value = false
loadTreeData()
return
}
treeLoading.value = true
searchMode.value = true
orgApi
.orgTree({ searchKey: value.trim() })
.then((res) => {
if (res !== null) {
treeData.value = res
defaultExpandedKeys.value = collectTreeKeys(res)
} else {
treeData.value = []
}
})
.finally(() => {
treeLoading.value = false
})
}
// 左侧树查询
orgApi
.orgTreeLazy()
.then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
if (isEmpty(defaultExpandedKeys.value)) {
// 默认展开第一级
if (treeData.value.length > 0) {
defaultExpandedKeys.value.push(treeData.value[0].id)
const loadTreeData = () => {
treeLoading.value = true
orgApi
.orgTreeLazy()
.then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
if (isEmpty(defaultExpandedKeys.value)) {
// 默认展开第一级
if (treeData.value.length > 0) {
defaultExpandedKeys.value.push(treeData.value[0].id)
}
}
}
}
})
.finally(() => {
treeLoading.value = false
})
})
.finally(() => {
treeLoading.value = false
})
}
loadTreeData()
// 懒加载子节点
const onLoadData = (treeNode) => {
return new Promise((resolve) => {

View File

@@ -0,0 +1,65 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.common.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* SQL工具类处理数据库兼容性问题
*
* @author yubaoshan
* @date 2026/2/12 18:43
**/
public class CommonSqlUtil {
/** Oracle IN子句最大元素数量限制 */
private static final int IN_CLAUSE_LIMIT = 999;
/**
* 安全的IN查询自动处理Oracle IN子句1000限制
* 当集合大小超过999时自动拆分为多个IN子句用OR连接
* (column IN (...999个) OR column IN (...999个) OR ...)
*
* @param wrapper MyBatis-Plus LambdaQueryWrapper
* @param column 查询列
* @param values IN子句的值集合
* @param <T> 实体类型
*/
public static <T> void safeIn(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> column, Collection<?> values) {
if (ObjectUtil.isEmpty(values)) {
return;
}
if (values.size() <= IN_CLAUSE_LIMIT) {
wrapper.in(column, values);
return;
}
List<?> valueList = values instanceof List ? (List<?>) values : new ArrayList<>(values);
List<? extends List<?>> partitions = CollectionUtil.split(valueList, IN_CLAUSE_LIMIT);
wrapper.and(w -> {
for (int i = 0; i < partitions.size(); i++) {
if (i == 0) {
w.in(column, partitions.get(i));
} else {
w.or().in(column, partitions.get(i));
}
}
});
}
}

View File

@@ -267,5 +267,9 @@ public abstract class SaBaseLoginUser {
/** 数据范围 */
@Schema(description = "数据范围")
private List<String> dataScope;
/** 是否全部数据范围SCOPE_ALL为true时dataScope为空表示不需要过滤 */
@Schema(description = "是否全部数据范围", hidden = true)
private boolean scopeAll;
}
}

View File

@@ -39,17 +39,25 @@ public class StpLoginUserUtil {
/**
* 获取当前B端登录用户的当前请求接口的数据范围
* 返回null表示SCOPE_ALL全部数据权限无需过滤
* 返回空列表表示无数据权限(需限制)
* 返回非空列表表示按机构ID过滤
*
* @author xuyuxiang
* @date 2022/7/8 10:41
**/
public static List<String> getLoginUserDataScope() {
List<String> resultList = CollectionUtil.newArrayList();
getLoginUser().getDataScopeList().forEach(dataScope -> {
if(dataScope.getApiUrl().equals(CommonServletUtil.getRequest().getServletPath())) {
String servletPath = CommonServletUtil.getRequest().getServletPath();
for (SaBaseLoginUser.DataScope dataScope : getLoginUser().getDataScopeList()) {
if (dataScope.getApiUrl().equals(servletPath)) {
if (dataScope.isScopeAll()) {
// SCOPE_ALL返回null表示全部数据权限无需过滤
return null;
}
resultList.addAll(dataScope.getDataScope());
}
});
}
return resultList;
}
}

View File

@@ -39,6 +39,7 @@ import vip.xiaonuo.biz.modular.user.entity.BizUser;
import vip.xiaonuo.biz.modular.user.enums.BizUserStatusEnum;
import vip.xiaonuo.biz.modular.user.service.BizUserService;
import vip.xiaonuo.common.enums.CommonSortOrderEnum;
import vip.xiaonuo.common.util.CommonSqlUtil;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
import vip.xiaonuo.common.page.CommonPageRequest;
@@ -134,16 +135,22 @@ public class BizGroupServiceImpl extends ServiceImpl<BizGroupMapper, BizGroup> i
public List<Tree<String>> orgTreeSelector() {
// 获取所有机构
List<BizOrg> allOrgList = bizOrgService.list();
// 定义机构集合
Set<BizOrg> bizOrgSet = CollectionUtil.newHashSet();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(bizOrgService.getParentListById(allOrgList, orgId, true)));
} else {
// 确定用于构建树的机构集合
List<BizOrg> resultOrgList;
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return CollectionUtil.newArrayList();
}
List<TreeNode<String>> treeNodeList = bizOrgSet.stream().map(bizOrg ->
if(loginUserDataScope == null) {
// SCOPE_ALL使用全部机构
resultOrgList = allOrgList;
} else {
Set<BizOrg> bizOrgSet = CollectionUtil.newHashSet();
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(bizOrgService.getParentListById(allOrgList, orgId, true)));
resultOrgList = CollectionUtil.newArrayList(bizOrgSet);
}
List<TreeNode<String>> treeNodeList = resultOrgList.stream().map(bizOrg ->
new TreeNode<>(bizOrg.getId(), bizOrg.getParentId(),
bizOrg.getName(), bizOrg.getSortCode()).setExtra(JSONUtil.parseObj(bizOrg)))
.collect(Collectors.toList());
@@ -157,11 +164,12 @@ public class BizGroupServiceImpl extends ServiceImpl<BizGroupMapper, BizGroup> i
queryWrapper.lambda().eq(BizUser::getUserStatus, BizUserStatusEnum.ENABLE.getValue());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizUser::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizUser::getOrgId, loginUserDataScope);
}
// 只查询部分字段
queryWrapper.lambda().select(BizUser::getId, BizUser::getAvatar, BizUser::getOrgId, BizUser::getPositionId, BizUser::getAccount,
BizUser::getName, BizUser::getSortCode, BizUser::getGender, BizUser::getEntryDate);

View File

@@ -26,6 +26,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import vip.xiaonuo.biz.modular.org.entity.BizOrg;
import vip.xiaonuo.biz.modular.org.enums.BizOrgSourceFromTypeEnum;
@@ -77,8 +78,8 @@ public class BizOrgController {
@Operation(summary = "获取机构树")
@SaCheckPermission("/biz/org/tree")
@GetMapping("/biz/org/tree")
public CommonResult<List<Tree<String>>> tree() {
return CommonResult.data(bizOrgService.tree());
public CommonResult<List<Tree<String>>> tree(@RequestParam(required = false) String searchKey) {
return CommonResult.data(bizOrgService.tree(searchKey));
}
/**

View File

@@ -46,6 +46,14 @@ public interface BizOrgService extends IService<BizOrg> {
*/
List<Tree<String>> tree();
/**
* 获取机构树(带搜索关键字)
*
* @author xuyuxiang
* @date 2022/4/24 20:08
*/
List<Tree<String>> tree(String searchKey);
/**
* 获取机构树(懒加载)
*

View File

@@ -44,6 +44,7 @@ import vip.xiaonuo.biz.modular.position.service.BizPositionService;
import vip.xiaonuo.biz.modular.user.entity.BizUser;
import vip.xiaonuo.biz.modular.user.service.BizUserService;
import vip.xiaonuo.common.cache.CommonCacheOperator;
import vip.xiaonuo.common.util.CommonSqlUtil;
import vip.xiaonuo.common.enums.CommonSortOrderEnum;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
@@ -109,30 +110,51 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
}
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizOrg::getId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizOrg::getId, loginUserDataScope);
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@Override
public List<Tree<String>> tree() {
return this.tree(null);
}
@Override
public List<Tree<String>> tree(String searchKey) {
// 获取所有机构
List<BizOrg> allOrgList = this.getAllOrgList();
// 定义机构集合
Set<BizOrg> bizOrgSet = CollectionUtil.newHashSet();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(this.getParentListById(allOrgList, orgId, true)));
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return CollectionUtil.newArrayList();
}
if(loginUserDataScope == null) {
bizOrgSet.addAll(allOrgList);
} else {
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(this.getParentListById(allOrgList, orgId, true)));
}
List<BizOrg> bizOrgArrayList = new ArrayList<>(bizOrgSet);
// 如果有搜索关键字,过滤匹配的机构及其所有父级
if (ObjectUtil.isNotEmpty(searchKey)) {
Set<BizOrg> filteredSet = CollectionUtil.newLinkedHashSet();
bizOrgArrayList.stream()
.filter(org -> StrUtil.containsIgnoreCase(org.getName(), searchKey))
.forEach(org -> filteredSet.addAll(this.getParentListById(allOrgList, org.getId(), true)));
// 取交集:既在数据范围内,又匹配搜索条件
bizOrgArrayList = new ArrayList<>(filteredSet);
bizOrgArrayList.retainAll(bizOrgSet);
}
// 修复使用稳定的排序方式首先按排序码排序然后按机构ID排序作为次级条件
List<BizOrg> bizOrgArrayList = new ArrayList<>(bizOrgSet);
bizOrgArrayList.sort(Comparator.comparingInt(BizOrg::getSortCode)
.thenComparing(BizOrg::getId)); // 添加ID作为次级排序条件
@@ -150,10 +172,31 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
String parentId = ObjectUtil.isNotEmpty(bizOrgTreeLazyParam.getParentId()) ? bizOrgTreeLazyParam.getParentId() : "0";
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if (ObjectUtil.isEmpty(loginUserDataScope)) {
if (loginUserDataScope != null && ObjectUtil.isEmpty(loginUserDataScope)) {
return CollectionUtil.newArrayList();
}
// loginUserDataScope == null 时为 SCOPE_ALL不做数据范围过滤
if (loginUserDataScope == null) {
// SCOPE_ALL直接查询当前父级下的所有子机构
List<BizOrg> childList = this.list(new LambdaQueryWrapper<BizOrg>()
.eq(BizOrg::getParentId, parentId)
.orderByAsc(BizOrg::getSortCode));
if (ObjectUtil.isEmpty(childList)) {
return CollectionUtil.newArrayList();
}
List<String> childIds = childList.stream().map(BizOrg::getId).collect(Collectors.toList());
Set<String> hasChildrenParentIds = this.list(new LambdaQueryWrapper<BizOrg>()
.select(BizOrg::getParentId)
.in(BizOrg::getParentId, childIds))
.stream().map(BizOrg::getParentId).collect(Collectors.toSet());
return childList.stream().map(bizOrg -> {
JSONObject jsonObject = JSONUtil.parseObj(bizOrg);
jsonObject.set("isLeaf", !hasChildrenParentIds.contains(bizOrg.getId()));
return jsonObject;
}).collect(Collectors.toList());
}
// 从版本化缓存获取可见机构ID集合命中时无需加载全量数据
Set<String> visibleOrgIds = this.getVisibleOrgIds(loginUserDataScope);
if (ObjectUtil.isEmpty(visibleOrgIds)) {
@@ -161,10 +204,11 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
}
// SQL直查获取当前父级下的可见子机构替代内存过滤2万条记录
List<BizOrg> childList = this.list(new LambdaQueryWrapper<BizOrg>()
.eq(BizOrg::getParentId, parentId)
.in(BizOrg::getId, visibleOrgIds)
.orderByAsc(BizOrg::getSortCode));
LambdaQueryWrapper<BizOrg> childQueryWrapper = new LambdaQueryWrapper<BizOrg>()
.eq(BizOrg::getParentId, parentId);
CommonSqlUtil.safeIn(childQueryWrapper, BizOrg::getId, visibleOrgIds);
childQueryWrapper.orderByAsc(BizOrg::getSortCode);
List<BizOrg> childList = this.list(childQueryWrapper);
if (ObjectUtil.isEmpty(childList)) {
return CollectionUtil.newArrayList();
@@ -172,10 +216,11 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
// SQL批量查询判断哪些子机构还有可见的下级单次SQL替代N次遍历
List<String> childIds = childList.stream().map(BizOrg::getId).collect(Collectors.toList());
Set<String> hasChildrenParentIds = this.list(new LambdaQueryWrapper<BizOrg>()
LambdaQueryWrapper<BizOrg> hasChildrenWrapper = new LambdaQueryWrapper<BizOrg>()
.select(BizOrg::getParentId)
.in(BizOrg::getParentId, childIds)
.in(BizOrg::getId, visibleOrgIds))
.in(BizOrg::getParentId, childIds);
CommonSqlUtil.safeIn(hasChildrenWrapper, BizOrg::getId, visibleOrgIds);
Set<String> hasChildrenParentIds = this.list(hasChildrenWrapper)
.stream().map(BizOrg::getParentId).collect(Collectors.toSet());
return childList.stream().map(bizOrg -> {
@@ -203,12 +248,13 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
BizOrgCategoryEnum.validate(bizOrgAddParam.getCategory());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
throw new CommonException("您没有权限增加机构");
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizOrgAddParam.getParentId())) {
throw new CommonException("您没有权限在该机构下增加机构机构id{}", bizOrgAddParam.getParentId());
}
} else {
throw new CommonException("您没有权限增加机构");
}
BizOrg bizOrg = BeanUtil.toBean(bizOrgAddParam, BizOrg.class);
@@ -236,6 +282,9 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
BizOrg bizOrg = this.queryEntity(bizOrgEditParam.getId());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
throw new CommonException("您没有权限编辑该机构机构id{}", bizOrg.getId());
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizOrg.getId())) {
throw new CommonException("您没有权限编辑该机构机构id{}", bizOrg.getId());
@@ -243,8 +292,6 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
if(!loginUserDataScope.contains(bizOrg.getParentId())) {
throw new CommonException("您没有权限编辑该机构下的机构机构id{}", bizOrg.getParentId());
}
} else {
throw new CommonException("您没有权限编辑该机构机构id{}", bizOrg.getId());
}
BeanUtil.copyProperties(bizOrgEditParam, bizOrg);
boolean repeatName = this.count(new LambdaQueryWrapper<BizOrg>().eq(BizOrg::getParentId, bizOrg.getParentId())
@@ -273,12 +320,13 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
if(ObjectUtil.isNotEmpty(orgIdList)) {
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
throw new CommonException("您没有权限删除这些机构机构id{}", orgIdList);
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!new HashSet<>(loginUserDataScope).containsAll(orgIdList)) {
throw new CommonException("您没有权限删除这些机构机构id{}", orgIdList);
}
} else {
throw new CommonException("您没有权限删除这些机构机构id{}", orgIdList);
}
List<BizOrg> allOrgList = this.getAllOrgList();
// 获取所有子机构
@@ -466,14 +514,17 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
// 定义机构集合
Set<BizOrg> bizOrgSet = CollectionUtil.newHashSet();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return CollectionUtil.newArrayList();
}
if(loginUserDataScope == null) {
// SCOPE_ALL不做过滤查询全部机构
} else {
// 获取所有机构
List<BizOrg> allOrgList = this.getAllOrgList();
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(this.getParentListById(allOrgList, orgId, true)));
List<String> loginUserDataScopeFullList = bizOrgSet.stream().map(BizOrg::getId).collect(Collectors.toList());
lambdaQueryWrapper.in(BizOrg::getId, loginUserDataScopeFullList);
} else {
return CollectionUtil.newArrayList();
CommonSqlUtil.safeIn(lambdaQueryWrapper, BizOrg::getId, loginUserDataScopeFullList);
}
lambdaQueryWrapper.orderByAsc(BizOrg::getSortCode);
List<BizOrg> bizOrgList = this.list(lambdaQueryWrapper);
@@ -488,11 +539,12 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
QueryWrapper<BizOrg> queryWrapper = new QueryWrapper<BizOrg>().checkSqlInjection();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizOrg::getId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizOrg::getId, loginUserDataScope);
}
// 查询部分字段
queryWrapper.lambda().select(BizOrg::getId, BizOrg::getParentId, BizOrg::getName,
BizOrg::getCategory, BizOrg::getSortCode);
@@ -511,11 +563,12 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
QueryWrapper<BizUser> queryWrapper = new QueryWrapper<BizUser>().checkSqlInjection();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizUser::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizUser::getOrgId, loginUserDataScope);
}
// 只查询部分字段
queryWrapper.lambda().select(BizUser::getId, BizUser::getAvatar, BizUser::getOrgId, BizUser::getPositionId, BizUser::getAccount,
BizUser::getName, BizUser::getSortCode, BizUser::getGender, BizUser::getEntryDate);
@@ -546,6 +599,9 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
if(ObjectUtil.isNotEmpty(orgIdList)) {
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
throw new CommonException("您没有权限复制机构");
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
// 如果有数据范围限制则校验目标父id是否有权限
if(!loginUserDataScope.contains(targetParentId)) {
@@ -555,8 +611,6 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
if(!new HashSet<>(loginUserDataScope).containsAll(orgIdList)) {
throw new CommonException("您没有权限复制这些机构机构id{}", orgIdList);
}
} else {
throw new CommonException("您没有权限复制机构");
}
// 遍历复制

View File

@@ -47,6 +47,7 @@ import vip.xiaonuo.common.enums.CommonSortOrderEnum;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
import vip.xiaonuo.common.page.CommonPageRequest;
import vip.xiaonuo.common.util.CommonSqlUtil;
import java.util.HashSet;
import java.util.List;
@@ -92,11 +93,12 @@ public class BizPositionServiceImpl extends ServiceImpl<BizPositionMapper, BizPo
}
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizPosition::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizPosition::getOrgId, loginUserDataScope);
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@@ -106,12 +108,13 @@ public class BizPositionServiceImpl extends ServiceImpl<BizPositionMapper, BizPo
BizPositionCategoryEnum.validate(bizPositionAddParam.getCategory());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
throw new CommonException("您没有权限在该机构下增加岗位机构id{}", bizPositionAddParam.getOrgId());
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizPositionAddParam.getOrgId())) {
throw new CommonException("您没有权限在该机构下增加岗位机构id{}", bizPositionAddParam.getOrgId());
}
} else {
throw new CommonException("您没有权限在该机构下增加岗位机构id{}", bizPositionAddParam.getOrgId());
}
BizPosition bizPosition = BeanUtil.toBean(bizPositionAddParam, BizPosition.class);
boolean repeatName = this.count(new LambdaQueryWrapper<BizPosition>().eq(BizPosition::getOrgId, bizPosition.getOrgId())
@@ -133,12 +136,12 @@ public class BizPositionServiceImpl extends ServiceImpl<BizPositionMapper, BizPo
BizPosition bizPosition = this.queryEntity(bizPositionEditParam.getId());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizPositionEditParam.getOrgId())) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
if(!bizPositionEditParam.getId().equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限编辑该机构下的岗位机构id{}", bizPositionEditParam.getOrgId());
}
} else {
if(!bizPositionEditParam.getId().equals(StpUtil.getLoginIdAsString())) {
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizPositionEditParam.getOrgId())) {
throw new CommonException("您没有权限编辑该机构下的岗位机构id{}", bizPositionEditParam.getOrgId());
}
}
@@ -163,12 +166,13 @@ public class BizPositionServiceImpl extends ServiceImpl<BizPositionMapper, BizPo
Set<String> positionOrgIdList = this.listByIds(positionIdList).stream().map(BizPosition::getOrgId).collect(Collectors.toSet());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
throw new CommonException("您没有权限删除这些机构下的岗位机构id{}", positionOrgIdList);
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!new HashSet<>(loginUserDataScope).containsAll(positionOrgIdList)) {
throw new CommonException("您没有权限删除这些机构下的岗位机构id{}", positionOrgIdList);
}
} else {
throw new CommonException("您没有权限删除这些机构下的岗位机构id{}", positionOrgIdList);
}
// 岗位下有人不能删除(直属岗位)
boolean hasOrgUser = bizUserService.count(new LambdaQueryWrapper<BizUser>().in(BizUser::getPositionId, positionIdList)) > 0;
@@ -223,14 +227,15 @@ public class BizPositionServiceImpl extends ServiceImpl<BizPositionMapper, BizPo
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
// 定义机构集合
Set<BizOrg> bizOrgSet = CollectionUtil.newHashSet();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return CollectionUtil.newArrayList();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
// 获取所有机构
List<BizOrg> allOrgList = bizOrgService.list();
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(bizOrgService.getParentListById(allOrgList, orgId, true)));
List<String> loginUserDataScopeFullList = bizOrgSet.stream().map(BizOrg::getId).collect(Collectors.toList());
lambdaQueryWrapper.in(BizOrg::getId, loginUserDataScopeFullList);
} else {
return CollectionUtil.newArrayList();
CommonSqlUtil.safeIn(lambdaQueryWrapper, BizOrg::getId, loginUserDataScopeFullList);
}
lambdaQueryWrapper.orderByAsc(BizOrg::getSortCode);
List<BizOrg> bizOrgList = bizOrgService.list(lambdaQueryWrapper);
@@ -250,11 +255,12 @@ public class BizPositionServiceImpl extends ServiceImpl<BizPositionMapper, BizPo
QueryWrapper<BizPosition> queryWrapper = new QueryWrapper<BizPosition>();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizPosition::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizPosition::getOrgId, loginUserDataScope);
}
// 查询部分字段
queryWrapper.lambda().select(BizPosition::getId, BizPosition::getOrgId, BizPosition::getName,
BizPosition::getCategory, BizPosition::getSortCode);

View File

@@ -142,10 +142,10 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
queryWrapper.lambda().ne(BizUser::getAccount, BizBuildInEnum.BUILD_IN_USER_ACCOUNT.getValue());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizUser::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
queryWrapper.lambda().eq(BizUser::getId, StpUtil.getLoginIdAsString());
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizUser::getOrgId, loginUserDataScope);
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@@ -174,12 +174,13 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
private void checkParam(BizUserAddParam bizUserAddParam) {
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
throw new CommonException("您没有权限在该机构下增加人员机构id{}", bizUserAddParam.getOrgId());
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUserAddParam.getOrgId())) {
throw new CommonException("您没有权限在该机构下增加人员机构id{}", bizUserAddParam.getOrgId());
}
} else {
throw new CommonException("您没有权限在该机构下增加人员机构id{}", bizUserAddParam.getOrgId());
}
if (this.count(new LambdaQueryWrapper<BizUser>()
.eq(BizUser::getAccount, bizUserAddParam.getAccount())) > 0) {
@@ -225,12 +226,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
private void checkParam(BizUserEditParam bizUserEditParam) {
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUserEditParam.getOrgId())) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
if(!bizUserEditParam.getId().equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限编辑该机构下的人员机构id{}", bizUserEditParam.getOrgId());
}
} else {
if(!bizUserEditParam.getId().equals(StpUtil.getLoginIdAsString())) {
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUserEditParam.getOrgId())) {
throw new CommonException("您没有权限编辑该机构下的人员机构id{}", bizUserEditParam.getOrgId());
}
}
@@ -279,15 +280,15 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
Set<String> userOrgIdList = this.listByIds(bizUserIdList).stream().map(BizUser::getOrgId).collect(Collectors.toSet());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
if(bizUserIdList.size() != 1 || !bizUserIdList.get(0).equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限删除这些机构下的人员机构id{}", userOrgIdList);
}
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!new HashSet<>(loginUserDataScope).containsAll(userOrgIdList)) {
throw new CommonException("您没有权限删除这些机构下的人员机构id{}",
CollectionUtil.subtract(userOrgIdList, loginUserDataScope));
}
} else {
if(bizUserIdList.size() != 1 || !bizUserIdList.get(0).equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限删除这些机构下的人员机构id{}", userOrgIdList);
}
}
// 清除【将这些人员作为主管】的信息
this.update(new LambdaUpdateWrapper<BizUser>().in(BizUser::getDirectorId, bizUserIdList).set(BizUser::getDirectorId, null));
@@ -330,12 +331,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
BizUser bizUser = this.detail(bizUserIdParam);
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限禁用该机构下的人员:{}机构id{}", bizUser.getName(), bizUser.getOrgId());
}
} else {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
throw new CommonException("您没有权限禁用该机构下的人员:{}机构id{}", bizUser.getName(), bizUser.getOrgId());
}
}
@@ -349,12 +350,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
BizUser bizUser = this.detail(bizUserIdParam);
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限启用该机构下的人员:{}机构id{}", bizUser.getName(), bizUser.getOrgId());
}
} else {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
throw new CommonException("您没有权限启用该机构下的人员:{}机构id{}", bizUser.getName(), bizUser.getOrgId());
}
}
@@ -368,12 +369,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
BizUser bizUser = this.detail(bizUserIdParam);
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限为该机构下的人员:{}重置密码机构id{}", bizUser.getName(), bizUser.getOrgId());
}
} else {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
throw new CommonException("您没有权限为该机构下的人员:{}重置密码机构id{}", bizUser.getName(), bizUser.getOrgId());
}
}
@@ -393,12 +394,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
BizUser bizUser = this.queryEntity(bizUserGrantRoleParam.getId());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
throw new CommonException("您没有权限为该机构下的人员:{}授权角色机构id{}", bizUser.getName(), bizUser.getOrgId());
}
} else {
if(!bizUser.getId().equals(StpUtil.getLoginIdAsString())) {
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(!loginUserDataScope.contains(bizUser.getOrgId())) {
throw new CommonException("您没有权限为该机构下的人员:{}授权角色机构id{}", bizUser.getName(), bizUser.getOrgId());
}
}
@@ -414,10 +415,10 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
queryWrapper.lambda().ne(BizUser::getAccount, BizBuildInEnum.BUILD_IN_USER_ACCOUNT.getValue());
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizUser::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
queryWrapper.lambda().eq(BizUser::getId, StpUtil.getLoginIdAsString());
} else if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizUser::getOrgId, loginUserDataScope);
}
if(ObjectUtil.isNotEmpty(bizUserExportParam.getUserIds())) {
queryWrapper.lambda().in(BizUser::getId, StrUtil.split(bizUserExportParam.getUserIds(), StrUtil.COMMA));
@@ -624,14 +625,15 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
// 定义机构集合
Set<BizOrg> bizOrgSet = CollectionUtil.newHashSet();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return CollectionUtil.newArrayList();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
// 获取所有机构
List<BizOrg> allOrgList = bizOrgService.list();
loginUserDataScope.forEach(orgId -> bizOrgSet.addAll(bizOrgService.getParentListById(allOrgList, orgId, true)));
List<String> loginUserDataScopeFullList = bizOrgSet.stream().map(BizOrg::getId).collect(Collectors.toList());
lambdaQueryWrapper.in(BizOrg::getId, loginUserDataScopeFullList);
} else {
return CollectionUtil.newArrayList();
CommonSqlUtil.safeIn(lambdaQueryWrapper, BizOrg::getId, loginUserDataScopeFullList);
}
lambdaQueryWrapper.orderByAsc(BizOrg::getSortCode);
List<BizOrg> bizOrgList = bizOrgService.list(lambdaQueryWrapper);
@@ -651,11 +653,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
QueryWrapper<BizOrg> queryWrapper = new QueryWrapper<BizOrg>().checkSqlInjection();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizOrg::getId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizOrg::getId, loginUserDataScope);
}
// 查询部分字段
queryWrapper.lambda().select(BizOrg::getId, BizOrg::getParentId, BizOrg::getName,
BizOrg::getCategory, BizOrg::getSortCode);
@@ -674,11 +677,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
QueryWrapper<BizPosition> queryWrapper = new QueryWrapper<BizPosition>().checkSqlInjection();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizPosition::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizPosition::getOrgId, loginUserDataScope);
}
// 查询部分字段
queryWrapper.lambda().select(BizPosition::getId, BizPosition::getOrgId, BizPosition::getName,
BizPosition::getCategory, BizPosition::getSortCode);
@@ -697,6 +701,9 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
public Page<BizUserRoleResult> roleSelector(BizUserSelectorRoleParam bizUserSelectorRoleParam) {
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
if(ObjectUtil.isNotEmpty(bizUserSelectorRoleParam.getOrgId())) {
if(loginUserDataScope.contains(bizUserSelectorRoleParam.getOrgId())) {
@@ -715,8 +722,14 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
bizUserSelectorRoleParam.getSearchKey(), loginUserDataScope, true), Page.class);
}
}
}
// loginUserDataScope == null 时表示SCOPE_ALL查询全部角色不限制数据范围
if(ObjectUtil.isNotEmpty(bizUserSelectorRoleParam.getOrgId())) {
return BeanUtil.toBean(sysRoleApi.roleSelector(bizUserSelectorRoleParam.getOrgId(), bizUserSelectorRoleParam.getCategory(),
bizUserSelectorRoleParam.getSearchKey(), null, true), Page.class);
} else {
return new Page<>();
return BeanUtil.toBean(sysRoleApi.roleSelector(null, bizUserSelectorRoleParam.getCategory(),
bizUserSelectorRoleParam.getSearchKey(), null, true), Page.class);
}
}
@@ -725,11 +738,12 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
QueryWrapper<BizUser> queryWrapper = new QueryWrapper<BizUser>().checkSqlInjection();
// 校验数据范围
List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
queryWrapper.lambda().in(BizUser::getOrgId, loginUserDataScope);
} else {
if(loginUserDataScope != null && loginUserDataScope.isEmpty()) {
return new Page<>();
}
if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
CommonSqlUtil.safeIn(queryWrapper.lambda(), BizUser::getOrgId, loginUserDataScope);
}
// 只查询部分字段
queryWrapper.lambda().select(BizUser::getId, BizUser::getAvatar, BizUser::getOrgId, BizUser::getPositionId, BizUser::getAccount,
BizUser::getName, BizUser::getSortCode, BizUser::getGender, BizUser::getEntryDate);

View File

@@ -25,6 +25,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import vip.xiaonuo.common.annotation.CommonLog;
import vip.xiaonuo.common.pojo.CommonResult;
@@ -74,8 +75,8 @@ public class SysOrgController {
@ApiOperationSupport(order = 2)
@Operation(summary = "获取组织树")
@GetMapping("/sys/org/tree")
public CommonResult<List<Tree<String>>> tree() {
return CommonResult.data(sysOrgService.tree());
public CommonResult<List<Tree<String>>> tree(@RequestParam(required = false) String searchKey) {
return CommonResult.data(sysOrgService.tree(searchKey));
}
/**

View File

@@ -46,6 +46,14 @@ public interface SysOrgService extends IService<SysOrg> {
*/
List<Tree<String>> tree();
/**
* 获取组织树(带搜索关键字)
*
* @author xuyuxiang
* @date 2022/4/24 20:08
*/
List<Tree<String>> tree(String searchKey);
/**
* 获取机构树(懒加载)
*
@@ -110,6 +118,14 @@ public interface SysOrgService extends IService<SysOrg> {
**/
List<SysOrg> getAllOrgList();
/**
* 获取所有组织ID列表从缓存获取避免每次stream转换
*
* @author yubaoshan
* @date 2026/2/12
**/
List<String> getAllOrgIdList();
/**
* 根据组织全名称获取组织id有则返回无则创建
*

View File

@@ -52,10 +52,7 @@ import vip.xiaonuo.sys.modular.user.entity.SysUser;
import vip.xiaonuo.sys.modular.user.enums.SysUserStatusEnum;
import vip.xiaonuo.sys.modular.user.service.SysUserService;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -69,6 +66,15 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
private static final String ORG_ALL_LIST_CACHE_KEY = "sys-org:all-list";
/** 本地内存缓存避免每次从Redis取出后JSON反序列化2万+条记录 */
private volatile List<SysOrg> localOrgListCache;
/** 本地内存缓存parentId -> 直接子机构列表,用于快速查找子机构 */
private volatile Map<String, List<SysOrg>> localParentChildrenMap;
/** 本地内存缓存所有机构ID列表避免每次stream().map().toList() */
private volatile List<String> localAllOrgIdList;
@Resource
private CommonCacheOperator commonCacheOperator;
@@ -108,7 +114,23 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
@Override
public List<Tree<String>> tree() {
List<SysOrg> sysOrgList = this.getAllOrgList();
return this.tree(null);
}
@Override
public List<Tree<String>> tree(String searchKey) {
List<SysOrg> allOrgList = this.getAllOrgList();
List<SysOrg> sysOrgList;
// 如果有搜索关键字,过滤匹配的组织及其所有父级
if (ObjectUtil.isNotEmpty(searchKey)) {
Set<SysOrg> filteredSet = CollectionUtil.newLinkedHashSet();
allOrgList.stream()
.filter(org -> StrUtil.containsIgnoreCase(org.getName(), searchKey))
.forEach(org -> filteredSet.addAll(this.getParentListById(allOrgList, org.getId(), true)));
sysOrgList = new ArrayList<>(filteredSet);
} else {
sysOrgList = allOrgList;
}
// 使用稳定的排序方式首先按排序码排序然后按机构ID排序作为次级条件
sysOrgList.sort(Comparator.comparingInt(SysOrg::getSortCode)
.thenComparing(SysOrg::getId)); // 添加ID作为次级排序条件
@@ -190,7 +212,7 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
// 发布增加事件
CommonDataChangeEventCenter.doAddWithData(SysDataTypeEnum.ORG.getValue(), JSONUtil.createArray().put(sysOrg));
// 清除缓存
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
clearOrgCache();
}
@Transactional(rollbackFor = Exception.class)
@@ -215,7 +237,7 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
// 发布更新事件
CommonDataChangeEventCenter.doUpdateWithData(SysDataTypeEnum.ORG.getValue(), JSONUtil.createArray().put(sysOrg));
// 清除缓存
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
clearOrgCache();
}
@Transactional(rollbackFor = Exception.class)
@@ -262,7 +284,7 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
// 发布删除事件
CommonDataChangeEventCenter.doDeleteWithDataIdList(SysDataTypeEnum.ORG.getValue(), toDeleteOrgIdList);
// 清除缓存
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
clearOrgCache();
}
}
@@ -312,7 +334,7 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
}
});
// 清除缓存
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
clearOrgCache();
}
}
@@ -327,15 +349,65 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
@Override
public List<SysOrg> getAllOrgList() {
// 优先从本地内存缓存获取避免每次JSON反序列化
List<SysOrg> localCache = localOrgListCache;
if (localCache != null) {
return localCache;
}
// 其次从Redis缓存获取
Object cached = commonCacheOperator.get(ORG_ALL_LIST_CACHE_KEY);
if (cached != null) {
return JSONUtil.toList(JSONUtil.parseArray(cached), SysOrg.class);
List<SysOrg> list = JSONUtil.toList(JSONUtil.parseArray(cached), SysOrg.class);
localOrgListCache = list;
buildParentChildrenMap(list);
return list;
}
// 最后从数据库查询
List<SysOrg> list = this.list(new LambdaQueryWrapper<SysOrg>().orderByAsc(SysOrg::getSortCode));
commonCacheOperator.put(ORG_ALL_LIST_CACHE_KEY, list);
localOrgListCache = list;
buildParentChildrenMap(list);
return list;
}
/**
* 构建parentId -> 子机构列表的索引Map同时构建所有机构ID列表
*/
private void buildParentChildrenMap(List<SysOrg> orgList) {
Map<String, List<SysOrg>> map = new HashMap<>();
List<String> allIdList = new ArrayList<>(orgList.size());
for (SysOrg org : orgList) {
map.computeIfAbsent(org.getParentId(), k -> new ArrayList<>()).add(org);
allIdList.add(org.getId());
}
localParentChildrenMap = map;
localAllOrgIdList = allIdList;
}
/**
* 获取所有机构ID列表从缓存获取避免每次stream转换
*/
@Override
public List<String> getAllOrgIdList() {
List<String> cached = localAllOrgIdList;
if (cached != null) {
return cached;
}
// 触发缓存构建
getAllOrgList();
return localAllOrgIdList;
}
/**
* 清除本地内存缓存和Redis缓存
*/
private void clearOrgCache() {
localOrgListCache = null;
localParentChildrenMap = null;
localAllOrgIdList = null;
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
}
@Override
public String getOrgIdByOrgFullNameWithCreate(String orgFullName) {
List<SysOrg> allOrgList = this.getAllOrgList();
@@ -386,7 +458,7 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
// 发布增加事件
CommonDataChangeEventCenter.doAddWithData(SysDataTypeEnum.ORG.getValue(), JSONUtil.createArray().put(sysOrg));
// 清除缓存
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
clearOrgCache();
return sysOrg.getId();
}
@@ -479,7 +551,25 @@ public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> impleme
@Override
public List<SysOrg> getChildListById(List<SysOrg> originDataList, String id, boolean includeSelf) {
List<SysOrg> resultList = CollectionUtil.newArrayList();
execRecursionFindChild(originDataList, id, resultList);
// 优先使用预构建的parentId索引MapO(n)复杂度;否则降级为原始递归
Map<String, List<SysOrg>> parentMap = localParentChildrenMap;
if (parentMap != null) {
// BFS方式查找所有子机构
Deque<String> queue = new ArrayDeque<>();
queue.add(id);
while (!queue.isEmpty()) {
String parentId = queue.poll();
List<SysOrg> children = parentMap.get(parentId);
if (children != null) {
for (SysOrg child : children) {
resultList.add(child);
queue.add(child.getId());
}
}
}
} else {
execRecursionFindChild(originDataList, id, resultList);
}
if(includeSelf) {
SysOrg self = this.getById(originDataList, id);
if(ObjectUtil.isNotEmpty(self)) {

View File

@@ -1527,30 +1527,34 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
public List<JSONObject> getScopeListByMap(Map<String, List<SysRelation>> groupMap, String orgId) {
List<JSONObject> resultList = CollectionUtil.newArrayList();
List<SysOrg> sysOrgList = sysOrgService.getAllOrgList();
List<String> scopeAllList = sysOrgList.stream().map(SysOrg::getId).toList();
List<String> scopeOrgList = CollectionUtil.newArrayList(orgId);
List<String> scopeOrgChildList = sysOrgService.getChildListById(sysOrgList, orgId, true)
.stream().map(SysOrg::getId).toList();
// 懒加载子机构列表:局部缓存,避免重复计算,线程安全
final List<String>[] orgChildListHolder = new List[]{null};
groupMap.forEach((key, value) -> {
JSONObject jsonObject = JSONUtil.createObj().set("apiUrl", key);
boolean hasScopeAll = false;
Set<String> scopeSet = CollectionUtil.newHashSet();
value.forEach(sysRelation -> {
for (SysRelation sysRelation : value) {
JSONObject extJsonObject = JSONUtil.parseObj(sysRelation.getExtJson());
String scopeCategory = extJsonObject.getStr("scopeCategory");
if (!scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_SELF.getValue())) {
if (scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_ALL.getValue())) {
scopeSet.addAll(scopeAllList);
} else if (scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_ORG.getValue())) {
scopeSet.addAll(scopeOrgList);
} else if (scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_ORG_CHILD.getValue())) {
scopeSet.addAll(scopeOrgChildList);
} else {
scopeSet.addAll(extJsonObject.getBeanList("scopeDefineOrgIdList", String.class));
if (scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_ALL.getValue())) {
hasScopeAll = true;
break;
} else if (scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_ORG.getValue())) {
scopeSet.addAll(scopeOrgList);
} else if (scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_ORG_CHILD.getValue())) {
if (orgChildListHolder[0] == null) {
orgChildListHolder[0] = sysOrgService.getChildListById(sysOrgService.getAllOrgList(), orgId, true)
.stream().map(SysOrg::getId).toList();
}
scopeSet.addAll(orgChildListHolder[0]);
} else if (!scopeCategory.equals(SysRoleDataScopeCategoryEnum.SCOPE_SELF.getValue())) {
scopeSet.addAll(extJsonObject.getBeanList("scopeDefineOrgIdList", String.class));
}
});
resultList.add(jsonObject.set("dataScope", CollectionUtil.newArrayList(scopeSet)));
}
jsonObject.set("scopeAll", hasScopeAll);
jsonObject.set("dataScope", hasScopeAll ? CollectionUtil.newArrayList() : CollectionUtil.newArrayList(scopeSet));
resultList.add(jsonObject);
});
return resultList;
}