From adf2a979ec20df7792e43b7b5d33ad7654a838da Mon Sep 17 00:00:00 2001 From: xintaoLi Date: Mon, 17 Aug 2026 16:30:54 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E3=80=90=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E3=80=91=E5=8F=8C=E5=87=BB=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=80=BC=E7=BC=96=E8=BE=91=E6=97=B6=E5=BC=B9=E6=A1=86=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E9=94=99=E4=B9=B1=20#1010158081137132305?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bklog/web/.ai-agent/memory/decisions.md | 16 ++++ .../result-cell-element/operator-tools.vue | 6 +- .../views/retrieve-v2/result-comp/kv-list.vue | 6 +- .../search-bar/ui-mode/fuzzy-match-mode.vue | 86 ++++++++++++------- .../search-bar/ui-mode/ui-input-option.scss | 14 +++ .../log-result/log-rows.tsx | 2 +- 6 files changed, 96 insertions(+), 34 deletions(-) diff --git a/bklog/web/.ai-agent/memory/decisions.md b/bklog/web/.ai-agent/memory/decisions.md index 9df069d84ea..34834a3bf10 100644 --- a/bklog/web/.ai-agent/memory/decisions.md +++ b/bklog/web/.ai-agent/memory/decisions.md @@ -352,3 +352,19 @@ Knowledge update: updated. - 约束: - Worker 侧模块禁止 import `vue` / `page-highlight.ts` / `hooks-helper.ts` 等带 vue 或 DOM 模块级副作用的文件;需要复用的纯逻辑先下沉到无框架依赖的模块。 - Monitor 构建新增 externals 时无需再关心 Worker,包装 loader 会让其打进 Blob;但 Worker 内仍禁止动态 `import()`,避免产生需要 `importScripts` 的异步分片。 + +## 2026-08-17 Monitor-only inject 必须给 default + +- 场景:日志平台检索页 `OperatorTools` 报 `Injection "handleRelatedTraceClick" not found`。 +- 根因:该回调只在 `retrieve-v3/monitor/monitor.tsx` provide;独立日志检索没有祖先 provide。调用本身已用 `__IS_MONITOR_APM__` 守卫。 +- 约束:Monitor 专用 inject(如 `handleRelatedTraceClick`)必须带 default,写法对齐 `handleChartDataZoom`。Options API 用 `inject: { key: { default: null } }`;Composition API 用 `inject(key, () => {})`。 +- 文件:`operator-tools.vue`、`kv-list.vue`、`log-rows.tsx`。 + +## 2026-08-17 UI 过滤值双击编辑层必须限制在 tag 内 + +- 场景:`ui-input-option.vue` 双击 value tag 进入编辑时,浅蓝 textarea 铺满整个条件弹层。 +- 根因:`.tag-item-input` 使用 `position: absolute` + `width/height: 100%`,但 `.tag-item` 未设定位包含块,包含块落到 Tippy popper。 +- 约束:tag 上的绝对定位编辑层必须把 `.tag-item` 设为 `position: relative`,并用 `inset: 0` 钉在 tag 盒内;不要让 `height: 100%` 相对弹层祖先计算。 +- 文件:`src/views/retrieve-v2/search-bar/ui-mode/ui-input-option.scss`、`fuzzy-match-mode.vue`。 +- 编辑层必须 `min-width: 0`,避免 textarea/input 默认最小宽度撑破只读 tag。 +- `fuzzy-match-tag` 双击编辑与常规 `tag-item` 同一交互:只读文本保留占位,编辑 input overlay 贴合 tag,背景 `#e1ecff`;不要再用 `min-width: 120px` 的新增输入框样式。 diff --git a/bklog/web/src/views/retrieve-v2/components/result-cell-element/operator-tools.vue b/bklog/web/src/views/retrieve-v2/components/result-cell-element/operator-tools.vue index e8d0f1a87ae..030610643d6 100644 --- a/bklog/web/src/views/retrieve-v2/components/result-cell-element/operator-tools.vue +++ b/bklog/web/src/views/retrieve-v2/components/result-cell-element/operator-tools.vue @@ -174,7 +174,11 @@ handleClick: Function, }, emits: ['handleAi'], - inject: ['handleRelatedTraceClick'], + inject: { + handleRelatedTraceClick: { + default: null, + }, + }, data() { return { showAllHandle: false, diff --git a/bklog/web/src/views/retrieve-v2/result-comp/kv-list.vue b/bklog/web/src/views/retrieve-v2/result-comp/kv-list.vue index 409ff66b1ea..6898571f0a4 100644 --- a/bklog/web/src/views/retrieve-v2/result-comp/kv-list.vue +++ b/bklog/web/src/views/retrieve-v2/result-comp/kv-list.vue @@ -173,7 +173,11 @@ }, mixins: [tableRowDeepViewMixin], inheritAttrs: false, - inject: ['handleRelatedTraceClick'], + inject: { + handleRelatedTraceClick: { + default: null, + }, + }, props: { data: { type: Object, diff --git a/bklog/web/src/views/retrieve-v2/search-bar/ui-mode/fuzzy-match-mode.vue b/bklog/web/src/views/retrieve-v2/search-bar/ui-mode/fuzzy-match-mode.vue index 62a0541bf2e..928039f6e18 100644 --- a/bklog/web/src/views/retrieve-v2/search-bar/ui-mode/fuzzy-match-mode.vue +++ b/bklog/web/src/views/retrieve-v2/search-bar/ui-mode/fuzzy-match-mode.vue @@ -419,29 +419,26 @@ defineExpose({ :key="`${item}-${index}`" class="fuzzy-match-tag" > - - + + {{ item }} + ('handleRelatedTraceClick'); + const handleRelatedTraceClick = inject('handleRelatedTraceClick', () => {}); const pageIndex = ref(1); /** 前端本地分页每页条数 */ From 7cd3dc174cdacb83bf80c94ead27f7d80e552d81 Mon Sep 17 00:00:00 2001 From: xintaoLi Date: Tue, 18 Aug 2026 17:01:38 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20spaceList=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96=20--story=3D137267003?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/src/global/bk-space-choice/index.tsx | 8 +++ .../web/src/global/bk-space-choice/list.scss | 44 +++++++++++++++ bklog/web/src/global/bk-space-choice/list.tsx | 25 ++++++++- bklog/web/src/preload.ts | 55 ++++++++++++------- 4 files changed, 112 insertions(+), 20 deletions(-) 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; }; /**