|
@@ -15,9 +15,9 @@ import { MessageBubbleComponent } from '../../../components/domain/message-bubbl
|
|
|
import { UiMButton, UiMNoticeBar, UiMPopup } from '../../../ui';
|
|
import { UiMButton, UiMNoticeBar, UiMPopup } from '../../../ui';
|
|
|
import { Deliverable } from '../../../core/api/models';
|
|
import { Deliverable } from '../../../core/api/models';
|
|
|
import { ChatService } from '../../../core/chat.service';
|
|
import { ChatService } from '../../../core/chat.service';
|
|
|
|
|
+import { ProjectsService } from '../../../core/projects.service';
|
|
|
import { DeliverablesService } from '../../../core/deliverables.service';
|
|
import { DeliverablesService } from '../../../core/deliverables.service';
|
|
|
import { LiveService } from '../../../core/live.service';
|
|
import { LiveService } from '../../../core/live.service';
|
|
|
-import { DeviceService } from '../../../core/device.service';
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 移动·会话(全屏消息 + 成果抽屉)
|
|
* 移动·会话(全屏消息 + 成果抽屉)
|
|
@@ -31,7 +31,7 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
imports: [ComposerComponent, MessageBubbleComponent, MStatePanelComponent, MStatusBadgeComponent, MAvatarComponent, UiMButton, UiMNoticeBar, UiMPopup],
|
|
imports: [ComposerComponent, MessageBubbleComponent, MStatePanelComponent, MStatusBadgeComponent, MAvatarComponent, UiMButton, UiMNoticeBar, UiMPopup],
|
|
|
template: `
|
|
template: `
|
|
|
- <div class="full-chat">
|
|
|
|
|
|
|
+ <div class="full-chat m-page--fixed">
|
|
|
<header class="chat-head">
|
|
<header class="chat-head">
|
|
|
<button type="button" class="icon-btn" aria-label="返回列表" (click)="back()">
|
|
<button type="button" class="icon-btn" aria-label="返回列表" (click)="back()">
|
|
|
<i class="fa-solid fa-chevron-left" aria-hidden="true"></i>
|
|
<i class="fa-solid fa-chevron-left" aria-hidden="true"></i>
|
|
@@ -97,43 +97,83 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 成果抽屉(底部 sheet,含安全区) -->
|
|
|
|
|
- @if (drawerOpen()) {
|
|
|
|
|
- <div class="sheet-backdrop" (click)="drawerOpen.set(false)" role="presentation"></div>
|
|
|
|
|
- <aside class="sheet" role="dialog" aria-label="相关成果">
|
|
|
|
|
|
|
+ <!-- 成果抽屉:ui-m-popup 提供焦点管理、Esc/遮罩关闭与 body 滚动锁 -->
|
|
|
|
|
+ <ui-m-popup [open]="drawerOpen()" position="bottom" (openChange)="drawerOpen.set($event)">
|
|
|
|
|
+ <aside class="sheet" aria-labelledby="deliverables-sheet-title">
|
|
|
<span class="sheet-handle" aria-hidden="true"></span>
|
|
<span class="sheet-handle" aria-hidden="true"></span>
|
|
|
<header class="sheet-head">
|
|
<header class="sheet-head">
|
|
|
- <h2>相关成果 <span class="count">{{ agentDeliverables().length }}</span></h2>
|
|
|
|
|
- <button type="button" class="icon-btn" aria-label="关闭" (click)="drawerOpen.set(false)">
|
|
|
|
|
|
|
+ <div class="sheet-head-copy">
|
|
|
|
|
+ <span class="sheet-kicker">{{ chat.agent()?.name || '数字生命' }} · 成果与项目</span>
|
|
|
|
|
+ <h2 id="deliverables-sheet-title">交付成果</h2>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="sheet-total">{{ agentDeliverables().length }} 份</span>
|
|
|
|
|
+ <button type="button" class="sheet-close" aria-label="关闭" (click)="drawerOpen.set(false)">
|
|
|
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
|
|
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
|
|
|
</button>
|
|
</button>
|
|
|
</header>
|
|
</header>
|
|
|
<div class="sheet-body">
|
|
<div class="sheet-body">
|
|
|
- @if (agentDeliverables().length === 0) {
|
|
|
|
|
- <p class="sheet-empty">该数字生命暂无交付成果。</p>
|
|
|
|
|
- } @else {
|
|
|
|
|
- @for (d of agentDeliverables(); track d.id) {
|
|
|
|
|
- <div class="sheet-row">
|
|
|
|
|
- <button type="button" class="sheet-main" (click)="preview(d)">
|
|
|
|
|
- <span class="row-icon" aria-hidden="true"><i class="fa-solid fa-file-lines"></i></span>
|
|
|
|
|
- <span class="row-body">
|
|
|
|
|
- <strong class="truncate">{{ d.title }}</strong>
|
|
|
|
|
- <small class="truncate">{{ d.filename }} · {{ d.size }}</small>
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <!-- ① 当前项目(原型 .rail-section > .rail-project) -->
|
|
|
|
|
+ @if (currentProject(); as p) {
|
|
|
|
|
+ <section class="rail-section">
|
|
|
|
|
+ <span class="eyebrow">当前项目</span>
|
|
|
|
|
+ <div class="rail-project">
|
|
|
|
|
+ <strong>{{ p.name }}</strong>
|
|
|
|
|
+ <p>{{ p.description || p.brief }}</p>
|
|
|
|
|
+ <button type="button" class="text-link" (click)="openProject(p.id)">
|
|
|
|
|
+ 打开项目 <i class="fa-solid fa-arrow-right" aria-hidden="true"></i>
|
|
|
</button>
|
|
</button>
|
|
|
- @if (d.artifacts[0]; as art) {
|
|
|
|
|
- <a
|
|
|
|
|
- class="sheet-dl"
|
|
|
|
|
- [href]="art.url"
|
|
|
|
|
- [attr.download]="art.name"
|
|
|
|
|
- [attr.aria-label]="'下载 ' + art.name"
|
|
|
|
|
- >
|
|
|
|
|
- <i class="fa-solid fa-download" aria-hidden="true"></i>
|
|
|
|
|
- </a>
|
|
|
|
|
- }
|
|
|
|
|
</div>
|
|
</div>
|
|
|
- }
|
|
|
|
|
|
|
+ </section>
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ② 来自该数字生命的成果(原型 .rail-file 卡片式) -->
|
|
|
|
|
+ <section class="rail-section">
|
|
|
|
|
+ <div class="rail-head-row">
|
|
|
|
|
+ <h3>成果列表</h3>
|
|
|
|
|
+ <span class="rail-count">{{ agentDeliverables().length }} 份成果</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ @if (agentDeliverables().length === 0) {
|
|
|
|
|
+ <p class="rail-empty">该数字生命暂无交付成果。</p>
|
|
|
|
|
+ } @else {
|
|
|
|
|
+ @for (d of agentDeliverables(); track d.id) {
|
|
|
|
|
+ <div class="rail-file">
|
|
|
|
|
+ <button type="button" class="rail-file-main" [attr.aria-label]="'预览 ' + d.title" (click)="preview(d)">
|
|
|
|
|
+ <span class="rail-file-top">
|
|
|
|
|
+ <span class="file-icon" aria-hidden="true">
|
|
|
|
|
+ <i class="fa-solid" [class]="fileIcon(d.fileType)"></i>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="ver-badge">{{ d.version }}</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <strong>{{ d.title }}</strong>
|
|
|
|
|
+ <p>{{ d.subtitle }}</p>
|
|
|
|
|
+ <span class="rail-file-foot">
|
|
|
|
|
+ <span>{{ d.size }}</span>
|
|
|
|
|
+ <i class="fa-solid fa-arrow-right" aria-hidden="true"></i>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <!-- 直接下载(用户要求) -->
|
|
|
|
|
+ @if (d.artifacts[0]; as art) {
|
|
|
|
|
+ <a
|
|
|
|
|
+ class="rail-file-dl"
|
|
|
|
|
+ [href]="art.url"
|
|
|
|
|
+ [attr.download]="art.name"
|
|
|
|
|
+ [attr.aria-label]="'下载 ' + art.name"
|
|
|
|
|
+ (click)="$event.stopPropagation()"
|
|
|
|
|
+ >
|
|
|
|
|
+ <i class="fa-solid fa-download" aria-hidden="true"></i>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ }
|
|
|
|
|
+ </div>
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ③ 底部说明(原型 .rail-foot) -->
|
|
|
|
|
+ <p class="rail-foot">
|
|
|
|
|
+ <i class="fa-solid fa-circle-info" aria-hidden="true"></i>
|
|
|
|
|
+ 成果与当前数字生命关联。打开文件即可预览或下载。
|
|
|
|
|
+ </p>
|
|
|
</div>
|
|
</div>
|
|
|
<footer class="sheet-foot">
|
|
<footer class="sheet-foot">
|
|
|
<ui-m-button variant="ghost" block icon="fa-solid fa-box" (click)="goAllResults()">
|
|
<ui-m-button variant="ghost" block icon="fa-solid fa-box" (click)="goAllResults()">
|
|
@@ -141,7 +181,7 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
</ui-m-button>
|
|
</ui-m-button>
|
|
|
</footer>
|
|
</footer>
|
|
|
</aside>
|
|
</aside>
|
|
|
- }
|
|
|
|
|
|
|
+ </ui-m-popup>
|
|
|
|
|
|
|
|
<!-- 成果内联预览:底部 sheet(用户要求"直接预览而不是跳转") -->
|
|
<!-- 成果内联预览:底部 sheet(用户要求"直接预览而不是跳转") -->
|
|
|
<ui-m-popup [open]="!!previewing()" position="bottom" (openChange)="previewing.set(null)">
|
|
<ui-m-popup [open]="!!previewing()" position="bottom" (openChange)="previewing.set(null)">
|
|
@@ -205,7 +245,7 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
gap: var(--sp-2);
|
|
gap: var(--sp-2);
|
|
|
min-height: var(--mob-navbar-h);
|
|
min-height: var(--mob-navbar-h);
|
|
|
- padding: var(--sp-2) var(--sp-3);
|
|
|
|
|
|
|
+ padding: var(--sp-2) var(--mob-gutter);
|
|
|
padding-top: calc(var(--sp-2) + var(--mob-safe-top));
|
|
padding-top: calc(var(--sp-2) + var(--mob-safe-top));
|
|
|
border-bottom: 1px solid var(--edge);
|
|
border-bottom: 1px solid var(--edge);
|
|
|
background: var(--surface);
|
|
background: var(--surface);
|
|
@@ -271,25 +311,19 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
/* 输入栏在文档流内(非 fixed),故此处仅留呼吸空间 */
|
|
/* 输入栏在文档流内(非 fixed),故此处仅留呼吸空间 */
|
|
|
background: var(--canvas);
|
|
background: var(--canvas);
|
|
|
}
|
|
}
|
|
|
- .sheet-backdrop {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- inset: 0;
|
|
|
|
|
- z-index: var(--z-mask);
|
|
|
|
|
- background: var(--overlay);
|
|
|
|
|
- }
|
|
|
|
|
.sheet {
|
|
.sheet {
|
|
|
- position: fixed;
|
|
|
|
|
- left: 0;
|
|
|
|
|
- right: 0;
|
|
|
|
|
- bottom: 0;
|
|
|
|
|
- z-index: var(--z-modal);
|
|
|
|
|
- max-height: 88dvh;
|
|
|
|
|
- display: flex;
|
|
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-height: min(88dvh, 760px);
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- border-radius: var(--r-lg) var(--r-lg) 0 0;
|
|
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ border-radius: var(--r-sheet, 24px) var(--r-sheet, 24px) 0 0;
|
|
|
background: var(--surface);
|
|
background: var(--surface);
|
|
|
box-shadow: var(--shadow-overlay);
|
|
box-shadow: var(--shadow-overlay);
|
|
|
- padding-bottom: var(--mob-safe-bottom);
|
|
|
|
|
|
|
+ animation: deliverables-sheet-in var(--motion-normal) var(--ease-standard) both;
|
|
|
|
|
+ }
|
|
|
|
|
+ @keyframes deliverables-sheet-in {
|
|
|
|
|
+ from { opacity: 0; transform: translateY(var(--sp-4)); }
|
|
|
|
|
+ to { opacity: 1; transform: translateY(0); }
|
|
|
}
|
|
}
|
|
|
.sheet-handle {
|
|
.sheet-handle {
|
|
|
width: 36px;
|
|
width: 36px;
|
|
@@ -303,75 +337,66 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
gap: var(--sp-2);
|
|
gap: var(--sp-2);
|
|
|
- padding: var(--sp-3) var(--sp-4);
|
|
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding: var(--sp-2) var(--mob-gutter) var(--sp-3);
|
|
|
border-bottom: 1px solid var(--edge);
|
|
border-bottom: 1px solid var(--edge);
|
|
|
|
|
+ background: var(--surface);
|
|
|
|
|
+ }
|
|
|
|
|
+ .sheet-head-copy {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 2px;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .sheet-kicker {
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ font-size: var(--fs-11);
|
|
|
}
|
|
}
|
|
|
.sheet-head h2 {
|
|
.sheet-head h2 {
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: var(--sp-2);
|
|
|
|
|
margin: 0;
|
|
margin: 0;
|
|
|
- font-size: var(--fs-15);
|
|
|
|
|
color: var(--ink);
|
|
color: var(--ink);
|
|
|
|
|
+ font-size: var(--fs-17);
|
|
|
}
|
|
}
|
|
|
- .count {
|
|
|
|
|
|
|
+ .sheet-total {
|
|
|
|
|
+ padding: 0 var(--sp-2);
|
|
|
|
|
+ border-radius: var(--r-sm);
|
|
|
|
|
+ background: var(--primary-subtle);
|
|
|
|
|
+ color: var(--primary);
|
|
|
font-size: var(--fs-11);
|
|
font-size: var(--fs-11);
|
|
|
- color: var(--muted);
|
|
|
|
|
- font-weight: 400;
|
|
|
|
|
|
|
+ line-height: 24px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
}
|
|
|
- .sheet-body {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- min-height: 0;
|
|
|
|
|
- overflow-y: auto;
|
|
|
|
|
- padding: var(--sp-3) var(--sp-4) var(--sp-5);
|
|
|
|
|
|
|
+ .sheet-close {
|
|
|
|
|
+ width: var(--touch-min);
|
|
|
|
|
+ height: var(--touch-min);
|
|
|
display: grid;
|
|
display: grid;
|
|
|
- gap: var(--sp-2);
|
|
|
|
|
- align-content: start;
|
|
|
|
|
- }
|
|
|
|
|
- /* 抽屉行:主体(预览) + 右侧直接下载 */
|
|
|
|
|
- .sheet-row {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: stretch;
|
|
|
|
|
- gap: var(--sp-1);
|
|
|
|
|
- border: 1px solid var(--edge);
|
|
|
|
|
- border-radius: var(--r-md);
|
|
|
|
|
- background: var(--surface);
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- }
|
|
|
|
|
- .sheet-main {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: var(--sp-3);
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- min-width: 0;
|
|
|
|
|
- min-height: 64px;
|
|
|
|
|
- padding: var(--sp-2) var(--sp-3);
|
|
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
border: 0;
|
|
border: 0;
|
|
|
|
|
+ border-radius: var(--r-md);
|
|
|
background: transparent;
|
|
background: transparent;
|
|
|
- text-align: left;
|
|
|
|
|
|
|
+ color: var(--ink-2);
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
}
|
|
}
|
|
|
- .sheet-main:active {
|
|
|
|
|
- background: var(--surface-hover);
|
|
|
|
|
|
|
+ .sheet-close:focus-visible {
|
|
|
|
|
+ outline: 2px solid var(--primary-ring);
|
|
|
|
|
+ outline-offset: -2px;
|
|
|
}
|
|
}
|
|
|
- .sheet-dl {
|
|
|
|
|
|
|
+ .sheet-body {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ overscroll-behavior: contain;
|
|
|
|
|
+ padding: var(--sp-3) var(--mob-gutter) var(--sp-4);
|
|
|
display: grid;
|
|
display: grid;
|
|
|
- place-items: center;
|
|
|
|
|
- width: var(--touch-min);
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
- margin: var(--sp-2) var(--sp-2) var(--sp-2) 0;
|
|
|
|
|
- border-radius: var(--r-sm);
|
|
|
|
|
- background: var(--primary-subtle);
|
|
|
|
|
- color: var(--primary);
|
|
|
|
|
- font-size: var(--fs-17);
|
|
|
|
|
- text-decoration: none;
|
|
|
|
|
- }
|
|
|
|
|
- .sheet-dl:active {
|
|
|
|
|
- background: var(--primary-ring);
|
|
|
|
|
|
|
+ gap: var(--sp-1);
|
|
|
|
|
+ align-content: start;
|
|
|
}
|
|
}
|
|
|
.sheet-foot {
|
|
.sheet-foot {
|
|
|
- padding: var(--sp-2) var(--sp-4) var(--sp-4);
|
|
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding: var(--sp-2) var(--mob-gutter) calc(var(--sp-2) + var(--mob-safe-bottom));
|
|
|
border-top: 1px solid var(--edge);
|
|
border-top: 1px solid var(--edge);
|
|
|
|
|
+ background: var(--surface);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* 成果预览(底部 sheet) */
|
|
/* 成果预览(底部 sheet) */
|
|
@@ -444,29 +469,174 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
font-size: var(--fs-13);
|
|
font-size: var(--fs-13);
|
|
|
text-decoration: none;
|
|
text-decoration: none;
|
|
|
}
|
|
}
|
|
|
- .row-icon {
|
|
|
|
|
|
|
+ /* ===== 抽屉内容(原型 .rail-section / .rail-project / .rail-file / .rail-foot)===== */
|
|
|
|
|
+ .rail-section {
|
|
|
|
|
+ margin-bottom: var(--sp-5);
|
|
|
|
|
+ }
|
|
|
|
|
+ .eyebrow {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-family: var(--mono, monospace);
|
|
|
|
|
+ font-size: var(--fs-11);
|
|
|
|
|
+ letter-spacing: 1px;
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-project {
|
|
|
|
|
+ margin-top: var(--sp-2);
|
|
|
|
|
+ padding: var(--sp-3);
|
|
|
|
|
+ border: 1px solid var(--edge);
|
|
|
|
|
+ border-radius: var(--r-md);
|
|
|
|
|
+ background: var(--surface-2);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-project strong {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: var(--fs-15);
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--ink);
|
|
|
|
|
+ margin: var(--sp-2) 0 var(--sp-1);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-project p {
|
|
|
|
|
+ display: -webkit-box;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
|
|
+ margin: 0 0 var(--sp-2);
|
|
|
|
|
+ font-size: var(--fs-13);
|
|
|
|
|
+ line-height: 1.55;
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ }
|
|
|
|
|
+ .text-link {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: var(--sp-1);
|
|
|
|
|
+ min-height: var(--touch-min);
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ font-size: var(--fs-13);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .rail-head-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: var(--sp-2);
|
|
|
|
|
+ margin-bottom: var(--sp-2);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-head-row h3 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: var(--fs-13);
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: var(--ink);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-count {
|
|
|
|
|
+ font-size: var(--fs-11);
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 成果卡:纵向布局(原型 .rail-file —— 图标+版本 → 标题 → 副标题 → 底部) */
|
|
|
|
|
+ .rail-file {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: stretch;
|
|
|
|
|
+ margin-top: var(--sp-2);
|
|
|
|
|
+ border: 1px solid var(--edge);
|
|
|
|
|
+ border-radius: var(--r-md);
|
|
|
|
|
+ background: var(--surface);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-file-main {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: var(--sp-1);
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ padding: var(--sp-3);
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-file-main:active {
|
|
|
|
|
+ background: var(--surface-hover);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-file-top {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-file .file-icon {
|
|
|
display: grid;
|
|
display: grid;
|
|
|
place-items: center;
|
|
place-items: center;
|
|
|
width: 32px;
|
|
width: 32px;
|
|
|
- height: 32px;
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
|
|
+ height: 38px;
|
|
|
border-radius: var(--r-sm);
|
|
border-radius: var(--r-sm);
|
|
|
background: var(--info-subtle);
|
|
background: var(--info-subtle);
|
|
|
color: var(--info);
|
|
color: var(--info);
|
|
|
}
|
|
}
|
|
|
- .row-body {
|
|
|
|
|
- display: grid;
|
|
|
|
|
- gap: 2px;
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- min-width: 0;
|
|
|
|
|
|
|
+ .ver-badge {
|
|
|
|
|
+ padding: 0 var(--sp-2);
|
|
|
|
|
+ border-radius: var(--r-sm);
|
|
|
|
|
+ background: var(--surface-hover);
|
|
|
|
|
+ color: var(--ink-2);
|
|
|
|
|
+ font-size: var(--fs-11);
|
|
|
|
|
+ line-height: 20px;
|
|
|
}
|
|
}
|
|
|
- .row-body strong {
|
|
|
|
|
|
|
+ .rail-file-main strong {
|
|
|
font-size: var(--fs-13);
|
|
font-size: var(--fs-13);
|
|
|
|
|
+ font-weight: 600;
|
|
|
color: var(--ink);
|
|
color: var(--ink);
|
|
|
|
|
+ margin-top: var(--sp-2);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-file-main p {
|
|
|
|
|
+ margin: 0 0 var(--sp-2);
|
|
|
|
|
+ font-size: var(--fs-11);
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-file-foot {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ font-size: var(--fs-11);
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ }
|
|
|
|
|
+ /* 下载按钮:卡片右下角浮动 */
|
|
|
|
|
+ .rail-file-dl {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ width: var(--touch-min);
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ align-self: center;
|
|
|
|
|
+ margin-right: var(--sp-2);
|
|
|
|
|
+ border-radius: var(--r-sm);
|
|
|
|
|
+ background: var(--primary-subtle);
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ font-size: var(--fs-17);
|
|
|
|
|
+ text-decoration: none;
|
|
|
|
|
+ align-self: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-file-dl:active {
|
|
|
|
|
+ background: var(--primary-ring);
|
|
|
}
|
|
}
|
|
|
- .row-body small {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ .rail-foot {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: var(--sp-2);
|
|
|
|
|
+ margin: var(--sp-5) 0 0;
|
|
|
font-size: var(--fs-11);
|
|
font-size: var(--fs-11);
|
|
|
|
|
+ line-height: 1.8;
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ }
|
|
|
|
|
+ .rail-empty {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: var(--sp-4);
|
|
|
|
|
+ border-radius: var(--r-md);
|
|
|
|
|
+ background: var(--surface-2);
|
|
|
|
|
+ font-size: var(--fs-13);
|
|
|
color: var(--muted);
|
|
color: var(--muted);
|
|
|
|
|
+ text-align: center;
|
|
|
}
|
|
}
|
|
|
.sheet-empty {
|
|
.sheet-empty {
|
|
|
margin: 0;
|
|
margin: 0;
|
|
@@ -480,6 +650,9 @@ import { DeviceService } from '../../../core/device.service';
|
|
|
text-overflow: ellipsis;
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
white-space: nowrap;
|
|
|
}
|
|
}
|
|
|
|
|
+ @media (prefers-reduced-motion: reduce) {
|
|
|
|
|
+ .sheet { animation: none; }
|
|
|
|
|
+ }
|
|
|
`,
|
|
`,
|
|
|
],
|
|
],
|
|
|
})
|
|
})
|
|
@@ -487,9 +660,9 @@ export class MbChatPage implements OnInit {
|
|
|
readonly chat = inject(ChatService);
|
|
readonly chat = inject(ChatService);
|
|
|
private readonly live = inject(LiveService);
|
|
private readonly live = inject(LiveService);
|
|
|
private readonly deliverables = inject(DeliverablesService);
|
|
private readonly deliverables = inject(DeliverablesService);
|
|
|
|
|
+ private readonly projects = inject(ProjectsService);
|
|
|
private readonly route = inject(ActivatedRoute);
|
|
private readonly route = inject(ActivatedRoute);
|
|
|
private readonly router = inject(Router);
|
|
private readonly router = inject(Router);
|
|
|
- private readonly device = inject(DeviceService);
|
|
|
|
|
|
|
|
|
|
readonly drawerOpen = signal(false);
|
|
readonly drawerOpen = signal(false);
|
|
|
/** 抽屉内联预览的成果(用户要求:直接预览,不跳转) */
|
|
/** 抽屉内联预览的成果(用户要求:直接预览,不跳转) */
|
|
@@ -500,6 +673,18 @@ export class MbChatPage implements OnInit {
|
|
|
return id ? this.deliverables.byAgent(id) : [];
|
|
return id ? this.deliverables.byAgent(id) : [];
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ /** 当前数字生命关联的项目(原型抽屉顶部「当前项目」段) */
|
|
|
|
|
+ readonly currentProject = computed(() => {
|
|
|
|
|
+ const id = this.chat.agentId();
|
|
|
|
|
+ if (!id) return undefined;
|
|
|
|
|
+ return this.projects.projects().find((p) => p.agentIds.includes(id));
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ /** 文件类型图标 */
|
|
|
|
|
+ fileIcon(type?: string): string {
|
|
|
|
|
+ return type === 'CSV' ? 'fa-file-csv' : 'fa-file-lines';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
async ngOnInit(): Promise<void> {
|
|
async ngOnInit(): Promise<void> {
|
|
|
await Promise.all([this.live.ensureLoaded(), this.deliverables.ensureLoaded()]);
|
|
await Promise.all([this.live.ensureLoaded(), this.deliverables.ensureLoaded()]);
|
|
|
const id = this.route.snapshot.paramMap.get('id');
|
|
const id = this.route.snapshot.paramMap.get('id');
|
|
@@ -507,7 +692,7 @@ export class MbChatPage implements OnInit {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
back(): void {
|
|
back(): void {
|
|
|
- void this.router.navigate(['/', this.device.mode(), 'messages']);
|
|
|
|
|
|
|
+ void this.router.navigate(['/mobile', 'messages']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
reconnect(): void {
|
|
reconnect(): void {
|
|
@@ -529,15 +714,20 @@ export class MbChatPage implements OnInit {
|
|
|
.map((l) => l.split(','));
|
|
.map((l) => l.split(','));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ openProject(id: string): void {
|
|
|
|
|
+ this.drawerOpen.set(false);
|
|
|
|
|
+ void this.router.navigate(['/mobile', 'projects', id]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** 抽屉底部「查看全部成果」 */
|
|
/** 抽屉底部「查看全部成果」 */
|
|
|
goAllResults(): void {
|
|
goAllResults(): void {
|
|
|
this.drawerOpen.set(false);
|
|
this.drawerOpen.set(false);
|
|
|
- void this.router.navigate(['/', this.device.mode(), 'deliverables']);
|
|
|
|
|
|
|
+ void this.router.navigate(['/mobile', 'deliverables']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
openResult(id: string): void {
|
|
openResult(id: string): void {
|
|
|
this.drawerOpen.set(false);
|
|
this.drawerOpen.set(false);
|
|
|
- void this.router.navigate(['/', this.device.mode(), 'deliverables'], {
|
|
|
|
|
|
|
+ void this.router.navigate(['/mobile', 'deliverables'], {
|
|
|
queryParams: { focus: id },
|
|
queryParams: { focus: id },
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|