|
|
@@ -1,7 +1,7 @@
|
|
|
import { NgTemplateOutlet } from '@angular/common';
|
|
|
import { ChangeDetectionStrategy, Component, OnDestroy, computed, inject, input, output, signal } from '@angular/core';
|
|
|
import { NavigationEnd, Router, RouterLink } from '@angular/router';
|
|
|
-import { effect } from '@angular/core';
|
|
|
+import { effect, untracked } from '@angular/core';
|
|
|
import { UiAvatar, UiButton, UiEmpty, UiInput, UiModal, UiSkeleton, UiSwitch, UiTooltip } from '../../../ui';
|
|
|
import { navItemActive } from '../../../desktop/shell/dk-top-nav/dk-top-nav';
|
|
|
import { ChatSession, groupOf } from '../../../core/chat-session';
|
|
|
@@ -274,6 +274,14 @@ export interface NewSessionRequest {
|
|
|
|
|
|
@if (isWsOpen(w.id)) {
|
|
|
<div class="ws-body">
|
|
|
+ <!-- 2026-11-14:加载完成前不渲染条目,避免与骨架/其它分组混排 -->
|
|
|
+ @if (!sessionsReady()) {
|
|
|
+ <div class="skel" role="status" aria-label="正在加载对话">
|
|
|
+ @for (i of [1, 2]; track i) {
|
|
|
+ <ui-skeleton variant="rect" height="32px" />
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
@if (sessionsIn(w.id).length === 0) {
|
|
|
<p class="sect-hint inset">还没有对话</p>
|
|
|
}
|
|
|
@@ -305,6 +313,7 @@ export interface NewSessionRequest {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
</div>
|
|
|
}
|
|
|
</div>
|
|
|
@@ -470,23 +479,30 @@ export interface NewSessionRequest {
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
- @for (s of daily(); track s.id) {
|
|
|
- <ng-container *ngTemplateOutlet="row; context: { $implicit: s, owner: null }"></ng-container>
|
|
|
- }
|
|
|
-
|
|
|
<!--
|
|
|
- 孤儿归属兜底(2026-11-01):归属于某个已不存在的工作区。
|
|
|
- 不单独列出的话,这些会话在界面上彻底消失,用户会以为对话丢了。
|
|
|
- 点开仍可正常进入,移动工作区即可修正归属。
|
|
|
+ 2026-11-14:真实条目必须等加载完成才渲染。
|
|
|
+ 原来 daily 列表的 for 在这个 if 外面,无条件渲染 ——
|
|
|
+ 于是骨架屏和真实列表同时出现(用户截图实证)。
|
|
|
+ 现在整块包在 sessionsReady 里,加载期间只有骨架。
|
|
|
-->
|
|
|
- @if (orphanSessions().length > 0) {
|
|
|
- <p class="sect-hint orphan-hint">
|
|
|
- 以下 {{ orphanSessions().length }} 条对话所属的工作区已不存在,可移动到其它工作区:
|
|
|
- </p>
|
|
|
- @for (s of orphanSessions(); track s.id) {
|
|
|
+ @if (sessionsReady()) {
|
|
|
+ @for (s of daily(); track s.id) {
|
|
|
<ng-container *ngTemplateOutlet="row; context: { $implicit: s, owner: null }"></ng-container>
|
|
|
}
|
|
|
- }
|
|
|
+
|
|
|
+ <!--
|
|
|
+ 孤儿归属兜底(2026-11-01):归属于某个已不存在的工作区。
|
|
|
+ 不单独列出的话,这些会话在界面上彻底消失,用户会以为对话丢了。
|
|
|
+ 点开仍可正常进入,移动工作区即可修正归属。
|
|
|
+ -->
|
|
|
+ @if (orphanSessions().length > 0) {
|
|
|
+ <p class="sect-hint orphan-hint">
|
|
|
+ 以下 {{ orphanSessions().length }} 条对话所属的工作区已不存在,可移动到其它工作区:
|
|
|
+ </p>
|
|
|
+ @for (s of orphanSessions(); track s.id) {
|
|
|
+ <ng-container *ngTemplateOutlet="row; context: { $implicit: s, owner: null }"></ng-container>
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
@if (dailyArchived().length > 0) {
|
|
|
<button
|
|
|
@@ -509,6 +525,7 @@ export interface NewSessionRequest {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
</div>
|
|
|
}
|
|
|
</section>
|
|
|
@@ -2119,11 +2136,20 @@ export class SessionTreeComponent implements OnDestroy {
|
|
|
*/
|
|
|
this.win.addEventListener('focus', this.onWindowFocus);
|
|
|
// 数据/尺寸变化后重测溢出(工作区与会话列表都是信号,变化即重算)
|
|
|
+ /*
|
|
|
+ * 🔴 2026-11-14:**只追踪数据信号,写入放到 untracked**。
|
|
|
+ *
|
|
|
+ * `measureOverflow()` 内部有 3 处 `signal.set()`。虽然它用 queueMicrotask
|
|
|
+ * 延后执行,但微任务仍在同一变更检测周期内 —— 加骨架屏后渲染时序变了,
|
|
|
+ * 实测被放大成 10 秒 187 次 /api 请求(原代码 60 次)。
|
|
|
+ *
|
|
|
+ * 这里显式 `untracked` 包住写入口,切断「渲染 → 测溢出 → 写信号 → 再渲染」。
|
|
|
+ */
|
|
|
effect(() => {
|
|
|
this.workspaces();
|
|
|
this.sessionsSvc.all();
|
|
|
this.dailyOpen();
|
|
|
- this.measureOverflow();
|
|
|
+ untracked(() => this.measureOverflow());
|
|
|
});
|
|
|
if (typeof window !== 'undefined') {
|
|
|
window.addEventListener('resize', () => this.measureOverflow());
|