| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
- /**
- * 页面顶栏 —— Demo 的「左右圆形浮动按钮 + 居中标题」(`.navbar` / `.fab`)
- *
- * 为什么不用 `ui-m-navbar`:底座版本是扁平常规导航条(白底 + 下边框),
- * 与 Demo 的浮钮式顶栏是两套视觉语言。此处按 Demo 自建,样式仍全部走令牌。
- *
- * 三种左侧形态(互斥,按 Demo `navbar()` 的参数决定):
- * · `back` → 返回箭头(二级页)
- * · `menu` → 抽屉入口(Tab 页)
- * · 都不传 → 占位,标题保持居中
- */
- @Component({
- selector: 'app-m-app-bar',
- changeDetection: ChangeDetectionStrategy.OnPush,
- template: `
- <header class="m-app-bar">
- @if (back()) {
- <button type="button" class="m-fab" aria-label="返回" (click)="backTap.emit()">
- <i class="fa-solid fa-chevron-left" aria-hidden="true"></i>
- </button>
- } @else if (menu()) {
- <button type="button" class="m-fab" aria-label="打开导航" (click)="menuTap.emit()">
- <i class="fa-solid fa-bars" aria-hidden="true"></i>
- </button>
- } @else {
- <span class="m-fab-spacer" aria-hidden="true"></span>
- }
- <div class="m-title">
- <span class="m-title-main">{{ title() }}</span>
- @if (subtitle()) {
- <small>{{ subtitle() }}</small>
- }
- </div>
- <div class="m-bar-right">
- <ng-content select="[barRight]" />
- @if (!hasRightContent()) {
- <span class="m-fab-spacer" aria-hidden="true"></span>
- }
- </div>
- </header>
- `,
- styles: [
- `
- :host {
- display: block;
- }
- /*
- * 🔴 标题必须相对**整个顶栏**居中(2026-11-04)。
- *
- * 原来三者等分 flex:左侧有时是返回键、有时是占位的 44px 圆圈,
- * 右侧按钮数量也不固定(0/1 个),两边宽度不等 → 标题偏左/偏右。
- *
- * 改为绝对定位铺满:标题左右各让出 44px,在剩余区域水平居中,
- * 左右按钮用相对定位压在两侧 —— 标题永远在整个顶栏的正中。
- */
- .m-app-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: var(--sp-3);
- height: calc(var(--mob-navbar-h) + var(--mob-safe-top));
- padding: var(--mob-safe-top) var(--sp-4) 0;
- position: relative;
- z-index: var(--z-affix);
- box-sizing: border-box;
- }
- .m-fab {
- width: var(--touch-min);
- height: var(--touch-min);
- border: 0;
- border-radius: var(--r-tag);
- display: grid;
- place-items: center;
- flex: none;
- background: var(--surface);
- box-shadow: var(--shadow-soft);
- color: var(--ink);
- font-size: var(--fs-17);
- cursor: pointer;
- transition: background var(--motion-fast) var(--ease-standard);
- }
- .m-fab:active {
- background: var(--surface-hover);
- }
- .m-fab-spacer {
- display: block;
- width: var(--touch-min);
- height: var(--touch-min);
- flex: none;
- }
- /*
- * 标题层铺满整条顶栏并居中;左右按钮盖在其上。
- *
- * 🔴 2026-11-07 修复重叠:原来 top 为安全区、bottom 为 0,直接铺满,
- * 但**没有约束子项高度** —— 标题 + 副标题两行同时存在时,
- * 文字会溢出这条绝对定位层、压到下方的消息区上(用户截图实测)。
- * 现在给标题层显式高度(与按钮同等的 44px 视觉行),并裁掉溢出。
- */
- .m-title {
- position: absolute;
- left: var(--touch-min);
- right: var(--touch-min);
- top: var(--mob-safe-top);
- height: calc(var(--mob-navbar-h) - var(--sp-2));
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 1px;
- min-width: 0;
- overflow: hidden;
- pointer-events: none;
- text-align: center;
- font-size: var(--fs-17);
- font-weight: 600;
- line-height: 1.2;
- color: var(--ink);
- }
- .m-title-main {
- display: block;
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .m-title small {
- display: block;
- font-size: var(--fs-11);
- font-weight: 400;
- color: var(--muted);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .m-bar-right {
- display: flex;
- align-items: center;
- gap: var(--sp-2);
- flex: none;
- }
- `,
- ],
- })
- export class MAppBarComponent {
- readonly title = input('');
- readonly subtitle = input('');
- /** 左侧显示返回箭头 */
- readonly back = input(false);
- /** 左侧显示抽屉入口(与 back 互斥,back 优先) */
- readonly menu = input(false);
- /** 右侧是否已有内容(父组件通过 `[barRight]` 投影);false 时渲染占位保持标题居中 */
- readonly hasRightContent = input(true);
- readonly backTap = output<void>();
- readonly menuTap = output<void>();
- }
|