fix(webui): 修「列表与正文之间那条大空隙」—— 包装层撑满而列表面板固定 320
用户发截图指出:「外框列表和右边正文那么大一个空隙」。
## 根因(在代码里很难看出来)
我为了让「通信」有内部页签与悬浮加号,给中栏加了一层包装 div,它写着 `flex-1`;
而里面的列表面板是 `lg:w-[320px]` **固定宽度** ⇒ 包装层把剩余宽度全占了,
**空隙在包装层里、不在任何面板里** —— 于是既看不出是哪一层的错,也不像"布局 bug",
看起来就像设计上多留了一条白。
## 改法
`.app-shell .comm-pane { flex: 0 0 auto; width: 320px }` —— 宽屏下中栏贴住列表面板的宽度。
窄屏不动:那边外壳是**列**方向,`flex-1` 管的是高度,宽度由 `w-full` 决定。
## 实测(自有浏览器)
宽屏 1600: 包装层 320 / 列表 320 / 详情 1180 → 包装层内空隙 0px,到详情 10px(设计间距)
窄屏 390: 包装层 370 / 列表 370 → 包装层内空隙 0px
顺带说一句:这条与前面「日历没有圆角」「列表项变成一张大框」是**同一个包装层**引出的
三个症状(撑满宽度 / 内层直角戳出圆角 / 每项没有独立卡)。包装层是我为合并导航加的,
当时的验证只看了结构(页签在不在、点得到点不到),**没有量过宽度**,
所以它一连串地出问题。现在宽度已有判据(上面的数字就是判据)。
This commit is contained in:
@ -11,6 +11,9 @@ import { MailApi, InboxResponse } from '../api/MailApi';
|
||||
import { AccountManager, AccountInfo } from '../api/AccountManager';
|
||||
import { SseService, SseEvent } from '../api/SseService';
|
||||
import { AppearanceStore } from '../common/AppearanceStore';
|
||||
import { image } from '@kit.ImageKit';
|
||||
import { AppearanceSnapshot, scrimOpacity } from '../model/Appearance';
|
||||
import { BackgroundPlan, PresetLayer, TRANSPARENT, resolveBackground } from '../model/Wallpaper';
|
||||
import { MailSummary, Contact, PermissionRequest, DecideResponse, SentResponse, PendingResponse } from '../model/Models';
|
||||
import {
|
||||
MailLike,
|
||||
@ -1447,6 +1450,125 @@ struct ContactsTab {
|
||||
@Component
|
||||
struct MainPage {
|
||||
@State currentIndex: number = 0;
|
||||
/*
|
||||
* 壁纸层(背景的**画法**在 `model/Wallpaper.ts` 里算好,这里只负责画)。
|
||||
*
|
||||
* P4 的第一版只做到"取回壁纸本体",**没有任何东西去画它** —— 也就是说
|
||||
* 那一版里"壁纸"其实只有数据没有画面(我当时的提交信息说"取回 PixelMap",
|
||||
* 那是真的;但文档里把它列成"未验渲染",听着像已经画出来了,这是我说得比证据强,已改)。
|
||||
* 现在补上:预设档画渐变、图片档画图 + 压暗。
|
||||
*/
|
||||
@State bgPlan: BackgroundPlan = new BackgroundPlan();
|
||||
@State wallpaperImage: image.PixelMap | null = null;
|
||||
private gridSettings: RenderingContextSettings = new RenderingContextSettings(true);
|
||||
private gridCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.gridSettings);
|
||||
|
||||
aboutToAppear(): void {
|
||||
// 主界面也要应用外观:只在设置页生效的话"一进主界面就变回去"(WebUI 侧踩过)
|
||||
this.applyAppearance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读外观(按**账号**)→ 算出画什么 → 落进 @State。
|
||||
*
|
||||
* `applyAppearance` 的名字与 AppearanceStore 里的同名方法一致:那边负责
|
||||
* "谁覆盖谁"和系统色彩模式,这里负责把结果**变成画面**。
|
||||
*/
|
||||
async applyAppearance(): Promise<void> {
|
||||
const ctx = this.getUIContext().getHostContext();
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
const acctMgr: AccountManager = AccountManager.getInstance(ctx);
|
||||
await acctMgr.load();
|
||||
const store: AppearanceStore = AppearanceStore.getInstance();
|
||||
store.loadLocal(ctx, acctMgr.getActiveId());
|
||||
const client: ApiClient = new ApiClient(ctx);
|
||||
await store.syncFromServer(ctx, client);
|
||||
const snap: AppearanceSnapshot = store.current();
|
||||
this.wallpaperImage = store.wallpaper;
|
||||
this.bgPlan = resolveBackground(snap.bgKind, snap.bgPresetId, scrimOpacity(snap.bgDim), store.wallpaper !== null);
|
||||
}
|
||||
|
||||
/** 一层渐变的色标:`['色', 位置]` 成对(页面才拼,纯逻辑里只存两个数组) */
|
||||
private gradientColors(layer: PresetLayer): [ResourceColor, number][] {
|
||||
const pairs: [ResourceColor, number][] = [];
|
||||
for (let i = 0; i < layer.colors.length; i++) {
|
||||
const c: string = layer.colors[i];
|
||||
pairs.push([c === TRANSPARENT ? Color.Transparent : c, layer.stops[i]]);
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 壁纸层:**整幅**铺在内容之下。
|
||||
*
|
||||
* 迷雾(模糊)**不在这里**:pi 的口径修正过 —— 正确的是"同一张底只许被模糊一次",
|
||||
* 而模糊该出现在"背后是可变内容"的层。壁纸层背后没有别的东西,
|
||||
* 在这里再糊一次只是把同一张图糊两遍(WebUI 侧的原话是"更脏、更掉帧")。
|
||||
* 导航条那一层的系统材质才是唯一一次模糊(判据按互斥形式钉住)。
|
||||
*/
|
||||
@Builder
|
||||
WallpaperLayer() {
|
||||
if (this.bgPlan.kind === 'preset') {
|
||||
Stack() {
|
||||
ForEach(this.bgPlan.layers, (layer: PresetLayer) => {
|
||||
if (layer.kind === 'radial') {
|
||||
Column()
|
||||
.width('100%').height('100%')
|
||||
.radialGradient({
|
||||
center: [layer.cx + '%', layer.cy + '%'],
|
||||
radius: layer.radius,
|
||||
colors: this.gradientColors(layer)
|
||||
})
|
||||
} else if (layer.kind === 'linear') {
|
||||
Column()
|
||||
.width('100%').height('100%')
|
||||
.linearGradient({ angle: layer.angle, colors: this.gradientColors(layer) })
|
||||
} else {
|
||||
/*
|
||||
* 网格档:CSS 的 `repeating-linear-gradient` 系统没有对应原语,
|
||||
* 用系统 `Canvas` 画线(线色/间隔照抄 CSS:gray-200 / 0.55 / 28)。
|
||||
*/
|
||||
Canvas(this.gridCtx)
|
||||
.width('100%').height('100%')
|
||||
.onReady(() => {
|
||||
const w: number = this.gridCtx.width;
|
||||
const h: number = this.gridCtx.height;
|
||||
this.gridCtx.strokeStyle = layer.lineColor;
|
||||
this.gridCtx.lineWidth = 1;
|
||||
this.gridCtx.globalAlpha = layer.lineAlpha;
|
||||
for (let x = 0; x < w; x += layer.step) {
|
||||
this.gridCtx.beginPath();
|
||||
this.gridCtx.moveTo(x, 0);
|
||||
this.gridCtx.lineTo(x, h);
|
||||
this.gridCtx.stroke();
|
||||
}
|
||||
for (let y = 0; y < h; y += layer.step) {
|
||||
this.gridCtx.beginPath();
|
||||
this.gridCtx.moveTo(0, y);
|
||||
this.gridCtx.lineTo(w, y);
|
||||
this.gridCtx.stroke();
|
||||
}
|
||||
})
|
||||
}
|
||||
}, (layer: PresetLayer, idx: number) => layer.kind + idx)
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
} else if (this.bgPlan.kind === 'image' && this.wallpaperImage !== null) {
|
||||
Stack() {
|
||||
Image(this.wallpaperImage)
|
||||
.width('100%').height('100%')
|
||||
.objectFit(ImageFit.Cover)
|
||||
// 压暗用**系统遮罩色** + 服务端给的浓度:换向(浅色洗白/深色压黑)由系统负责
|
||||
Column()
|
||||
.width('100%').height('100%')
|
||||
.backgroundColor(Theme.overlay)
|
||||
.opacity(this.bgPlan.scrim)
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
TabBarBuilder(title: string, icon: string, index: number) {
|
||||
@ -1492,7 +1614,10 @@ struct MainPage {
|
||||
* (status 与 from_agent 参考实现也不显示,见 `WorkCard.tsx` 的字段集,
|
||||
* 判据里有一条"卡片字段两边一致"钉住这件事,避免以后以为漏了。)
|
||||
*/
|
||||
Tabs({ barPosition: BarPosition.End }) {
|
||||
Stack() {
|
||||
// 背景在最底下:内容面(系统色)盖在它上面
|
||||
this.WallpaperLayer()
|
||||
Tabs({ barPosition: BarPosition.End }) {
|
||||
TabContent() {
|
||||
CommPage()
|
||||
}
|
||||
@ -1503,9 +1628,12 @@ struct MainPage {
|
||||
}
|
||||
.tabBar(this.TabBarBuilder('联系人', '👤', 1))
|
||||
}
|
||||
.onChange((index: number) => {
|
||||
this.currentIndex = index;
|
||||
})
|
||||
.onChange((index: number) => {
|
||||
this.currentIndex = index;
|
||||
})
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user