a4c3c39556
?tab=wiki used to call listWikiPages({page_size: 500}) in a loop up to
50 times, loading ~25k pages into memory before rendering the sidebar
on knowledge bases with tens of thousands of pages. All rows were then
materialized as DOM nodes inside collapsible groups stacked vertically.
This commit reshapes the sidebar around per-type buckets that are
fetched on demand and rendered through a single virtual scroller:
* pagesByType holds one bucket per page_type (summary / entity /
concept / synthesis / comparison), each with its own items list,
pagination cursor, KB-wide total, and loading flag. Initial load
fires one 50-item request per type in parallel.
* Index and Log are fetched directly via getWikiIndex / getWikiLog
instead of scanning the full page list to find them.
* Groups are surfaced as a horizontal tab bar; switching tabs swaps
the RecycleScroller's items. Only one bucket is visible at a time,
so we never stack multiple virtual viewports.
* The scroller runs in page-mode with the outer .wiki-page-list as the
scrollable ancestor; a 1px sentinel after the list is watched by
IntersectionObserver to trigger the next page fetch. After each
batch lands we call scroller.updateVisibleItems(true) on the next
tick so appended rows render in order without requiring the user
to jiggle the scroll.
* Switching tabs resets scrollTop so the retained position of the
previous tab can't re-trigger the sentinel and cascade loads on
the new (shorter) list.
* Search enters a dedicated flat-result mode (searchResults ref);
clearing the query flips back to the bucketed view without
refetching the buckets the user already had open.
Adds vue-virtual-scroller dependency and imports its stylesheet.
37 lines
1.2 KiB
TypeScript
37 lines
1.2 KiB
TypeScript
import { createApp } from "vue";
|
|
import { createPinia } from "pinia";
|
|
import App from "./App.vue";
|
|
import router from "./router";
|
|
import "./assets/fonts.css";
|
|
import TDesign from "tdesign-vue-next";
|
|
// 引入组件库的少量全局样式变量
|
|
import "tdesign-vue-next/es/style/index.css";
|
|
import "@/assets/theme/theme.css";
|
|
import "@/assets/dropdown-menu.less";
|
|
// vue-virtual-scroller ships its own tiny stylesheet — required for
|
|
// RecycleScroller/DynamicScroller to size their viewport correctly.
|
|
// Without it the scroller computes 0 height and renders no items.
|
|
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
|
import i18n from "./i18n";
|
|
import { initTheme } from "@/composables/useTheme";
|
|
import { initFont } from "@/composables/useFont";
|
|
import { installTDesignIconOfflineGuard } from "@/utils/tdesign-icon-offline";
|
|
|
|
// 必须在 Vue 组件挂载之前执行,避免 tdesign-icons 运行时请求 tdesign.gtimg.com
|
|
installTDesignIconOfflineGuard();
|
|
|
|
initTheme();
|
|
initFont();
|
|
|
|
const app = createApp(App);
|
|
|
|
app.use(TDesign);
|
|
app.use(createPinia());
|
|
app.use(router);
|
|
app.use(i18n);
|
|
|
|
// 等首屏路由(含导航守卫、Lite 自动登录)完成后再挂载,避免先闪默认页再跳转
|
|
router.isReady().finally(() => {
|
|
app.mount("#app");
|
|
});
|