import { ChangeDetectionStrategy, Component, ElementRef, computed, input, output, viewChild, } from '@angular/core'; /** * 底部输入坞(Demo `.dock`) * * Demo 形态:圆角卡片 + 自适应高度 textarea + 工具行(附件 / 目标 / 发送)。 * 与桌面 `composer` 的区别:桌面版工具行在上、输入在下;移动端输入在上、 * 工具行在下(拇指可达区)。事件语义与桌面一致(draftChange / send / attach)。 * * 安全区:坞底由页面负责加 `--mob-safe-bottom`。 */ @Component({ selector: 'app-m-dock', changeDetection: ChangeDetectionStrategy.OnPush, template: `
@if (target()) { {{ target() }} }
@if (uploading()) {
}
`, styles: [ ` :host { display: block; } .m-dock { margin: 0 var(--sp-4); padding: var(--sp-3) var(--sp-4); border: 1px solid var(--edge); border-radius: var(--r-lg); background: var(--surface); box-shadow: var(--shadow-pop); box-sizing: border-box; } textarea { display: block; width: 100%; min-height: 44px; max-height: 120px; padding: var(--sp-2) 0; border: 0; outline: 0; resize: none; background: transparent; color: var(--ink); font: inherit; font-size: var(--fs-17); line-height: 1.5; } .m-dock-tools { display: flex; align-items: center; gap: var(--sp-3); color: var(--muted); } .m-tool { display: grid; place-items: center; width: 36px; height: 36px; border: 0; border-radius: var(--r-tag); background: none; color: var(--muted); font-size: var(--fs-17); cursor: pointer; } .m-target { display: inline-flex; align-items: center; gap: var(--sp-1); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: var(--fs-13); } .m-send { margin-left: auto; display: grid; place-items: center; flex: none; width: 36px; height: 36px; border: 0; border-radius: var(--r-tag); background: var(--primary-subtle); color: var(--muted); font-size: var(--fs-15); cursor: pointer; transition: background var(--motion-fast) var(--ease-standard), color var(--motion-fast) var(--ease-standard); } .m-send.ready { background: var(--primary); color: var(--primary-content); } .m-send:disabled { cursor: not-allowed; } .m-dock-progress { height: 3px; margin-top: var(--sp-2); border-radius: var(--r-tag); background: var(--surface-active); overflow: hidden; } .m-dock-progress i { display: block; height: 100%; background: var(--primary); transition: width var(--motion-detail) var(--ease-standard); } `, ], }) export class MDockComponent { readonly draft = input(''); readonly placeholder = input('发消息…'); /** 右侧显示的目标(数字生命名称);空则不显示 */ readonly target = input(''); readonly disabled = input(false); readonly sending = input(false); readonly uploading = input(false); readonly pct = input(0); readonly draftChange = output(); readonly send = output(); readonly attach = output(); /** 是否已有待发附件(由页面传入;纯附件也可发送) */ readonly hasAttachments = input(false); protected readonly fileRef = viewChild.required>('fileRef'); /** 文本非空 **或** 有待发附件即可发送 */ protected readonly hasContent = computed( () => (this.draft() ?? '').trim().length > 0 || this.hasAttachments(), ); protected onInput(e: Event): void { const el = e.target as HTMLTextAreaElement; this.draftChange.emit(el.value); el.style.height = 'auto'; el.style.height = `${Math.min(el.scrollHeight, 120)}px`; } /** 回车发送,Shift+Enter 换行 */ protected onEnter(e: Event): void { const ke = e as KeyboardEvent; if (ke.shiftKey) return; ke.preventDefault(); this.submit(); } protected submit(): void { if (this.disabled() || this.sending() || !this.hasContent()) return; this.send.emit(this.draft().trim()); } /** 打开系统文件选择器 */ protected pickFile(): void { this.fileRef().nativeElement.click(); } protected onFile(e: Event): void { const input = e.target as HTMLInputElement; if (input.files && input.files.length > 0) this.attach.emit(input.files); input.value = ''; } }