diff --git a/bklog/web/src/global/bk-space-choice/index.tsx b/bklog/web/src/global/bk-space-choice/index.tsx index f3542bd1071..149d521a8f0 100644 --- a/bklog/web/src/global/bk-space-choice/index.tsx +++ b/bklog/web/src/global/bk-space-choice/index.tsx @@ -25,8 +25,10 @@ */ import { defineComponent, ref, computed, watch, nextTick, onUnmounted } from 'vue'; +import $http from '@/api'; import useLocale from '@/hooks/use-locale'; import { useNavMenu } from '@/hooks/use-nav-menu'; +import { getAllSpaceList } from '@/preload'; import { SPACE_TYPE_MAP } from '@/store/constant'; import { BK_LOG_STORAGE } from '@/store/store.type'; import { debounce } from 'throttle-debounce'; @@ -76,6 +78,8 @@ export default defineComponent({ const refRootElement = ref(null); const isExternal = computed(() => store.state.isExternal); + // 首屏只加载当前空间,全量空间列表由 getAllSpaceList 异步补齐 + const spaceListLoading = computed(() => !store.state.spaceListLoaded); const demoUid = computed(() => store.getters.demoUid); const demoSpace = computed(() => mySpaceList.value.find(item => item.space_uid === demoUid.value)); @@ -361,6 +365,9 @@ export default defineComponent({ // 点击业务名称时触发,切换下拉框显示并聚焦搜索框 const handleClickBizSelect = () => { showBizList.value = !showBizList.value; + if (showBizList.value && spaceListLoading.value) { + getAllSpaceList($http, store); + } setTimeout(() => { menuSearchInput.value?.focus(); }, 100); @@ -529,6 +536,7 @@ export default defineComponent({ checked={spaceUid.value} commonList={commonList.value} list={groupList.value} + loading={spaceListLoading.value} theme={props.theme as ThemeType} selectedIndex={selectedIndex.value} selectableItems={selectableItems.value} diff --git a/bklog/web/src/global/bk-space-choice/list.scss b/bklog/web/src/global/bk-space-choice/list.scss index ac3fdc6a956..e49eb260012 100644 --- a/bklog/web/src/global/bk-space-choice/list.scss +++ b/bklog/web/src/global/bk-space-choice/list.scss @@ -32,6 +32,40 @@ padding-bottom: 24px; } + .list-skeleton { + height: 240px; + overflow: hidden; + } + + .list-skeleton-item { + display: flex; + align-items: center; + height: 32px; + padding: 0 12px; + + &:nth-child(3n) .list-skeleton-bar { + width: 55%; + } + + &:nth-child(3n + 2) .list-skeleton-bar { + width: 70%; + } + } + + .list-skeleton-bar { + width: 85%; + height: 12px; + background: linear-gradient(90deg, #eef0f4 25%, #f7f8fa 37%, #eef0f4 63%); + background-size: 400% 100%; + border-radius: 2px; + animation: bklog-space-skeleton-loading 1.4s ease infinite; + } + + &.dark .list-skeleton-bar { + background: linear-gradient(90deg, #242c40 25%, #2c354d 37%, #242c40 63%); + background-size: 400% 100%; + } + .list-group { position: relative; @@ -218,6 +252,16 @@ } } +@keyframes bklog-space-skeleton-loading { + 0% { + background-position: 100% 50%; + } + + 100% { + background-position: 0 50%; + } +} + // 设置默认弹窗 .confirm-dialog__set-default { .bk-dialog-body { diff --git a/bklog/web/src/global/bk-space-choice/list.tsx b/bklog/web/src/global/bk-space-choice/list.tsx index 4e34f91a1ff..d9d9b116bfa 100644 --- a/bklog/web/src/global/bk-space-choice/list.tsx +++ b/bklog/web/src/global/bk-space-choice/list.tsx @@ -67,6 +67,9 @@ export interface IListItem { export type ThemeType = 'dark' | 'light'; +// 骨架屏行数,按列表可视高度 240px / 行高 32px 铺满 +const SKELETON_ROW_COUNT = 7; + // const DEFAULT_BIZ_ID = 'DEFAULT_BIZ_ID'; export default defineComponent({ @@ -102,6 +105,10 @@ export default defineComponent({ type: Array as () => IListItem[], default: () => [], }, + loading: { + type: Boolean, + default: false, + }, }, emits: ['handleClickOutSide', 'handleClickMenuItem', 'openDialog'], setup(props, { emit }) { @@ -139,10 +146,26 @@ export default defineComponent({ return selectableIndex === props.selectedIndex; }; + // 空间列表加载中的骨架占位,避免只渲染首屏预加载的单个空间造成误解 + const renderSkeleton = () => ( +
+ {Array.from({ length: SKELETON_ROW_COUNT }).map((_, index) => ( +
+ +
+ ))} +
+ ); + // 渲染函数 return () => (
- {props.list.length > 0 ? ( + {props.loading ? ( + renderSkeleton() + ) : props.list.length > 0 ? ( // 滚动加载 { return list; }; +let allSpaceListPromise: Promise | null = null; + /** * 获取所有空间列表 + * 多处调用共享同一次请求,避免首屏与下拉打开时重复拉取 * @param http * @param store * @returns */ -export const getAllSpaceList = (http, store) => { - window.scheduler.postTask(() => { - http - .request('space/getMySpaceList') - .then((resp) => { - const spaceList = resp.data; - spaceList.forEach((item) => { - item.bk_biz_id = `${item.bk_biz_id}`; - item.space_uid = `${item.space_uid}`; - item.space_full_code_name = `${item.space_name}(#${item.space_id})`; - }); +export const getAllSpaceList = (http, store): Promise => { + if (store.state.spaceListLoaded) { + return Promise.resolve(); + } - store.commit('updateMySpaceList', spaceList); - store.commit(SET_APP_STATE, { spaceListLoaded: true }); - }) - .catch((e) => { - store.commit('updateMySpaceList', []); - store.commit(SET_APP_STATE, { spaceListLoaded: true }); - console.error('获取空间列表失败', e); - }); + if (allSpaceListPromise) { + return allSpaceListPromise; + } + + allSpaceListPromise = new Promise((resolve) => { + window.scheduler.postTask(() => { + http + .request('space/getMySpaceList') + .then((resp) => { + const spaceList = Array.isArray(resp?.data) ? resp.data : []; + spaceList.forEach((item) => { + item.bk_biz_id = `${item.bk_biz_id}`; + item.space_uid = `${item.space_uid}`; + item.space_full_code_name = `${item.space_name}(#${item.space_id})`; + }); + + store.commit('updateMySpaceList', spaceList); + }) + .catch((e) => { + // 保留首屏已解析出的当前空间,避免请求失败时业务名与下拉列表一起被清空 + console.error('获取空间列表失败', e); + }) + .finally(() => { + store.commit(SET_APP_STATE, { spaceListLoaded: true }); + resolve(); + }); + }); }); + + return allSpaceListPromise; }; /**