import { MAvatarComponent } from '../../components/m-avatar/m-avatar'; import { ChangeDetectionStrategy, Component, computed, inject, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { UiMCell, UiMSearch, UiMTabs } from '../../../ui'; import { StatePanelComponent } from '../../../components/feedback/state-panel/state-panel'; import { StatusBadgeComponent } from '../../../components/domain/status-badge/status-badge'; import { Agent } from '../../../core/api/models'; import { ContactsService } from '../../../core/contacts.service'; import { LiveService } from '../../../core/live.service'; import { DeviceService } from '../../../core/device.service'; import { isMatrixReady } from '../../../core/format'; /** * 移动·通讯录(SPEC FR-03) * 三 Tab + 搜索 + 同事展开;行操作触达 ≥44px。 */ @Component({ selector: 'app-mb-contacts-page', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatePanelComponent, StatusBadgeComponent, UiMSearch, MAvatarComponent, UiMCell, UiMTabs], template: `

通讯录

@switch (contacts.state()) { @case ('loading') { } @case ('error') { } @case ('empty') { } @default { @if (contacts.isEmpty()) { } @else { @if (contacts.tab() !== 'people') {

AI 数字生命

@for (a of contacts.visibleAgents(); track a.id) { }
} @if (contacts.tab() !== 'ai') {

同事

@for (p of contacts.visiblePeople(); track p.id) { @if (contacts.expandedColleague() === p.id) { @for (a of contacts.agentsOfPerson(p); track a.id) { } } }
} } } }
`, styles: [ ` .m-page { padding: var(--sp-5) var(--mob-gutter) var(--sp-6); } .m-head h1 { margin: 0 0 var(--sp-4); font-size: var(--fs-26); color: var(--ink); } .m-search { display: flex; align-items: center; gap: var(--sp-2); min-height: 46px; padding: 0 var(--sp-3); margin-bottom: var(--sp-3); border: 1px solid var(--edge); border-radius: var(--r-md); background: var(--surface); color: var(--muted); } .m-search input { flex: 1; min-width: 0; border: 0; background: transparent; color: var(--ink); font-size: var(--fs-17); } .m-tabs { display: flex; gap: var(--sp-1); padding: var(--sp-1); margin-bottom: var(--sp-4); border-radius: var(--r-md); background: var(--surface-2); } .m-tab { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1); min-height: var(--touch-min); border: 0; border-radius: var(--r-sm); background: transparent; color: var(--ink-2); font-size: var(--fs-13); cursor: pointer; } .m-tab.active { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--shadow-soft); } .tab-count { font-size: var(--fs-11); opacity: 0.7; } .section-title { margin: 0 0 var(--sp-3); font-size: var(--fs-13); color: var(--ink-2); } .section-title:not(:first-of-type) { margin-top: var(--sp-5); } .rows { display: grid; gap: var(--sp-2); } .row { display: flex; align-items: center; gap: var(--sp-3); width: 100%; min-height: 64px; padding: var(--sp-2) var(--sp-3); border: 1px solid var(--edge); border-radius: var(--r-md); background: var(--surface); text-align: left; cursor: pointer; } .row:active { background: var(--surface-hover); } .row.child { margin-left: var(--sp-4); width: calc(100% - var(--sp-4)); background: var(--surface-2); } .row-body { display: grid; gap: 2px; flex: 1; min-width: 0; } .row-body strong { font-size: var(--fs-15); color: var(--ink); } .row-body small { font-size: var(--fs-12); color: var(--muted); } .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } `, ], }) export class MbContactsPage implements OnInit { readonly contacts = inject(ContactsService); private readonly live = inject(LiveService); private readonly router = inject(Router); private readonly device = inject(DeviceService); readonly tabs = [ { key: 'all' as const, label: '全部' }, { key: 'ai' as const, label: 'AI' }, { key: 'people' as const, label: '同事' }, ]; readonly tabItems = [{ label: '全部' }, { label: 'AI 数字生命' }, { label: '同事' }]; readonly tabIndex = computed(() => ({ all: 0, ai: 1, people: 2 })[this.contacts.tab()]); onTabIndex(i: number): void { this.contacts.setTab((['all', 'ai', 'people'] as const)[i] ?? 'all'); } readonly counts = computed(() => { const c = this.contacts.counts(); return { all: c.all, ai: c.ai, people: c.people }; }); async ngOnInit(): Promise { await this.contacts.ensureLoaded(); } async reload(): Promise { await this.live.refresh(); } onSearch(e: Event): void { this.contacts.keyword.set((e.target as HTMLInputElement).value); } open(a: Agent): void { void this.router.navigate( isMatrixReady(a) ? ['/', this.device.mode(), 'chat', a.id] : ['/', this.device.mode(), 'agent', a.id], ); } }