perf: Lazy loading for third-party platform organization structure

This commit is contained in:
fit2cloud-chenyw
2026-05-29 15:23:43 +08:00
committed by fit2cloud-chenyw
parent 0b4a765115
commit faa5c9f2e0
6 changed files with 123 additions and 17 deletions
+2 -1
View File
@@ -27,6 +27,7 @@ export const modelApi = {
query: (id: number) => request.get(`/system/aimodel/${id}`),
setDefault: (id: number) => request.put(`/system/aimodel/default/${id}`),
check: (data: any) => request.fetchStream('/system/aimodel/status', data),
platform: (id: number) => request.get(`/system/platform/org/${id}`),
platform: (id: number, lazy?: number, pid?: string) =>
request.get(`/system/platform/org/${id}`, { params: { lazy, pid } }),
userSync: (data: any) => request.post(`/system/platform/user/sync`, data),
}
+1
View File
@@ -38,6 +38,7 @@
"field_details": "Field Details",
"integration": "Platform integration needs to be enabled.",
"the_existing_user": "If the user already exists, overwrite the existing user.",
"lazy_load": "Lazy Load",
"sync_users": "Sync Users",
"sync_wechat_users": "Sync WeChat Users",
"sync_dingtalk_users": "Sync DingTalk Users",
+1
View File
@@ -38,6 +38,7 @@
"field_details": "필드 세부 정보",
"integration": "플랫폼 통합을 활성화해야 합니다.",
"the_existing_user": "해당 사용자가 이미 존재하는 경우 기존 사용자를 덮어씁니다.",
"lazy_load": "지연 로딩",
"sync_users": "사용자 동기화",
"sync_wechat_users": "위챗 사용자 동기화",
"sync_dingtalk_users": "딩톡 사용자 동기화",
+1
View File
@@ -38,6 +38,7 @@
"field_details": "字段详情",
"integration": "需开启平台对接",
"the_existing_user": "若用户已存在,覆盖旧用户",
"lazy_load": "懒加载",
"sync_users": "同步用户",
"sync_wechat_users": "同步企业微信用户",
"sync_dingtalk_users": "同步钉钉用户",
+1
View File
@@ -38,6 +38,7 @@
"field_details": "欄位詳情",
"integration": "需開啟平台對接",
"the_existing_user": "若使用者已存在,覆蓋舊使用者",
"lazy_load": "懶加載",
"sync_users": "同步使用者",
"sync_wechat_users": "同步企業微信使用者",
"sync_dingtalk_users": "同步釘釘使用者",
+117 -16
View File
@@ -1,13 +1,17 @@
<template>
<el-dialog
v-model="centerDialogVisible"
:title="$t(dialogTitle)"
modal-class="sync-user_ding"
width="840"
>
<el-dialog v-model="centerDialogVisible" modal-class="sync-user_ding" width="840">
<template #header>
<div class="dialog-header">
<span style="margin-right: 12px">{{ $t(dialogTitle) }}</span>
<el-checkbox v-model="isLazy" class="lazy-checkbox">
{{ $t('sync.lazy_load') }}
</el-checkbox>
</div>
</template>
<div v-loading="loading" class="flex border" style="height: 428px; border-radius: 6px">
<div class="p-16 border-r">
<el-input
v-if="!isLazy"
v-model="search"
:validate-event="false"
:placeholder="$t('datasource.search')"
@@ -20,19 +24,26 @@
</el-icon>
</template>
</el-input>
<div class="mt-8 max-height_workspace">
<div
class="max-height_workspace"
:class="{ 'mt-8': !isLazy }"
:style="isLazy ? { maxHeight: '100%' } : {}"
>
<el-tree
:key="`${treeKey}-${isLazy}`"
ref="organizationUserRef"
style="max-width: 426px"
class="checkbox-group-block"
:data="organizationUserList"
:data="isLazy ? EMPTY_DATA : organizationUserList"
:filter-node-method="filterNode"
show-checkbox
:default-checked-keys="defaultCheckedKeys"
:props="defaultProps"
node-key="id"
default-expand-all
:default-expand-all="!isLazy"
:expand-on-click-node="false"
:lazy="isLazy"
:load="isLazy ? loadNode : undefined"
@check="handleCheck"
>
<template #default="{ node, data }">
@@ -112,16 +123,15 @@
</template>
<script lang="ts" setup>
import { ref, computed, watch, nextTick } from 'vue'
import { modelApi } from '@/api/system'
import { ElLoading } from 'element-plus-secondary'
import avatar_personal from '@/assets/svg/avatar_personal.svg'
import avatar_organize from '@/assets/svg/avatar_organize.svg'
import avatar_personal from '@/assets/svg/avatar_personal.svg'
import Close from '@/assets/svg/icon_close_outlined_w.svg'
import Search from '@/assets/svg/icon_search-outline_outlined.svg'
import type { CheckboxValueType } from 'element-plus-secondary'
import type { FilterNodeMethodFunction } from 'element-plus-secondary'
import type { CheckboxValueType, FilterNodeMethodFunction } from 'element-plus-secondary'
import { ElLoading } from 'element-plus-secondary'
import { cloneDeep } from 'lodash-es'
import { computed, nextTick, ref, watch } from 'vue'
const checkAll = ref(false)
const existingUser = ref(false)
const isIndeterminate = ref(false)
@@ -134,12 +144,17 @@ const defaultCheckedKeys = ref<any[]>([])
const defaultProps = {
children: 'children',
label: 'name',
isLeaf: (data: any) => data.options?.is_user,
}
let rawTree: any = []
const organizationUserList = ref<any[]>([])
const loading = ref(false)
const centerDialogVisible = ref(false)
const checkTableList = ref([] as any[])
const EMPTY_DATA: any[] = []
const isLazy = ref(true)
const treeKey = ref(0)
const wecomDeptCache = ref(new Map<string, any[]>())
const workspaceWithKeywords = computed(() => {
return workspace.value.filter((ele: any) =>
@@ -185,6 +200,21 @@ watch(search, () => {
})
})
watch(isLazy, async () => {
search.value = ''
checkTableList.value = []
checkedWorkspace.value = []
defaultCheckedKeys.value = []
wecomDeptCache.value.clear()
loading.value = true
const systemWorkspaceList = await modelApi.platform(oid, isLazy.value ? 1 : 0)
organizationUserList.value = isLazy.value
? stripDeptChildren(systemWorkspaceList.tree || [])
: systemWorkspaceList.tree || []
rawTree = cloneDeep(systemWorkspaceList.tree)
loading.value = false
})
const filterNode: FilterNodeMethodFunction = (value: string, data: any) => {
if (!value) return true
return data.name.includes(value)
@@ -230,7 +260,61 @@ const handleCheckedWorkspaceChange = (value: CheckboxValueType[]) => {
}
let oid: any = null
const buildWecomDeptCache = (tree: any[]) => {
const cache = new Map<string, any[]>()
const stack = [...tree]
while (stack.length) {
const node = stack.pop()!
if (!node.options?.is_user && node.children?.length) {
cache.set(
node.id,
node.children.map((c: any) => ({ ...c, children: [] }))
)
stack.push(...node.children)
}
}
wecomDeptCache.value = cache
}
const stripDeptChildren = (nodes: any[]) => {
return nodes.map((node: any) => {
if (!node.options?.is_user) {
const { children, ...rest } = node
void children
return rest
}
return { ...node }
})
}
const loadNode = (node: any, resolve: any) => {
if (node.level === 0) {
modelApi.platform(oid, 1).then((res: any) => {
resolve(stripDeptChildren(res.tree || []))
})
return
}
if (oid === 6) {
const deptChildren = wecomDeptCache.value.get(node.data.id) || []
modelApi.platform(oid, 1, node.data.id).then((res: any) => {
resolve(stripDeptChildren([...(res.tree || []), ...deptChildren]))
nextTick(() => {
organizationUserRef.value?.setChecked(node.data.id, false, true)
})
})
return
}
modelApi.platform(oid, 1, node.data.id).then((res: any) => {
resolve(stripDeptChildren(res.tree || []))
nextTick(() => {
organizationUserRef.value?.setChecked(node.data.id, false, true)
})
})
}
const open = async (id: any, title: any) => {
isLazy.value = true
treeKey.value++
dialogTitle.value = title
loading.value = true
search.value = ''
@@ -240,8 +324,15 @@ const open = async (id: any, title: any) => {
checkAll.value = false
isIndeterminate.value = false
const loadingInstance = ElLoading.service({ fullscreen: true })
const systemWorkspaceList = await modelApi.platform(id)
organizationUserList.value = systemWorkspaceList.tree || []
const systemWorkspaceList = await modelApi.platform(id, isLazy.value ? 1 : 0)
if (isLazy.value && id === 6) {
buildWecomDeptCache(systemWorkspaceList.tree)
organizationUserList.value = stripDeptChildren(systemWorkspaceList.tree || [])
} else {
organizationUserList.value = isLazy.value
? stripDeptChildren(systemWorkspaceList.tree || [])
: systemWorkspaceList.tree || []
}
rawTree = cloneDeep(systemWorkspaceList.tree)
loadingInstance?.close()
loading.value = false
@@ -283,6 +374,16 @@ defineExpose({
</script>
<style lang="less">
.sync-user_ding {
.dialog-header {
display: flex;
align-items: center;
.lazy-checkbox {
font-size: 12px;
color: #8f959e;
}
}
.mb-8 {
margin-bottom: 8px;
}