ke: 鸿蒙 UI 整改:emoji→AmIcon 图标、宽屏侧栏、Md 第三方库、通知可配置项

- common/Icons.ets(生成器产出):43 枚与 WebUI icons.tsx 同几何的 Path 图标
- NavItems.ts:icon(emoji) → iconKey,与 WebUI 导航同图标
- 全页面 emoji 清除(空态改单行灰字,按钮/头像/刷新等换 AmIcon)
- MailDetailPage 接入 @luvi/lv-markdown-in(第三方,替代手写解析器)
- WideSidebar + MainPage isWide(≥768vp):宽屏侧栏 60vp 图标轨、藏底部条、onAreaChange 断点
- 通知=可配置项(默认关):PushService.isEnabled/setEnabled + reportToken 最前 gate (关=不取token/不弹权限/不打网关)+ SettingsPage Toggle + 服务端通道状态提示
- EntryAbility.onCreate 解析 want:冷启点通知也能路由(原来只有 onNewWant)
- 判据:harmony-widescreen(4) + harmony-push-optin(4) 新增;harmony-nav 同步 iconKey/条件 padding
This commit is contained in:
2026-09-16 07:46:52 +08:00
parent 9c6e9c66ad
commit cfe7808af4
19 changed files with 766 additions and 89 deletions

View File

@ -5,6 +5,8 @@
import { ApiClient, ApiError } from '../api/ApiClient';
import { Theme } from '../common/Theme';
import { AuthApi } from '../api/AuthApi';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
import { AccountManager, AccountInfo } from '../api/AccountManager';
import { SseService } from '../api/SseService';
import { AppearanceStore } from '../common/AppearanceStore';
@ -15,6 +17,8 @@ import { AdminUser } from '../model/Models';
import { isAdminRole } from '../model/AdminUsers';
import { ApiBaseCheck, validateApiBase } from '../model/ApiBase';
import { BackgroundPicker } from '../common/BackgroundPicker';
import { PushService } from '../api/PushService';
import { PushTokenList } from '../model/PushContract';
@Entry
@Component
@ -35,6 +39,12 @@ struct SettingsPage {
@State appearanceStatus: string = 'local-only';
/** 当前账号是不是管理员 —— 只用来决定「管理」入口显不显示(口径见 model/AdminUsers.ts 的 isAdminRole) */
@State isAdmin: boolean = false;
/*
* ★ 2026-09-15:接入系统通知 = **可配置项**(用户强调多次:“用户可以自由部署自己的 agentmail”)。
* 默认 **关** —— 没开就不取 token / 不弹权限 / 不打网关。开关持久化在 PushService 的偏好里。
*/
@State pushEnabled: boolean = false;
@State pushServerStatus: string = 'n/a';
/*
* 背景四值(P4c):由选择器(`BackgroundPicker`,@Link 双向绑)改,
* 用户改一次就回调 `onUserChanged` ⇒ 这里推服务端 —— 与 `setTheme` 同一形状。
@ -63,6 +73,7 @@ struct SettingsPage {
this.refreshList();
this.loadAppearance();
this.loadRole();
this.loadPushSetting();
});
}
}
@ -152,6 +163,53 @@ struct SettingsPage {
}
}
/**
* ★ 2026-09-15:接入系统通知 = **可配置项**(默认关)。
* 读一次持久化开关回填 @State,顺带看服务端有没有配推送(enabled 字段)。
* 服务端没配/读不到 → 明确告诉用户“服务器未配置推送” —— 但开关本身照常可用。
*/
async loadPushSetting(): Promise<void> {
const ctx = this.getUIContext().getHostContext();
if (ctx === undefined) {
return;
}
this.pushEnabled = PushService.isEnabled(ctx);
// 顺带读一次服务端 enabled(有账号时);读不到也是正常态(自部署没配凭证)
const client: ApiClient | null = this.client;
if (client === null) {
this.pushServerStatus = 'n/a';
return;
}
try {
const resp: PushTokenList = await client.get<PushTokenList>('/me/devices/push-token');
this.pushServerStatus = resp.enabled ? 'server-on' : 'server-off';
} catch (e) {
this.pushServerStatus = 'n/a';
}
}
/** 用户拨动开关:写持久化;开了就立刻补一次上报(不开就不碰推送) */
async togglePush(enabled: boolean): Promise<void> {
const ctx = this.getUIContext().getHostContext();
if (ctx === undefined) {
return;
}
this.pushEnabled = enabled;
PushService.setEnabled(ctx, enabled);
if (!enabled) {
return;
}
// 开了:立刻取 token + 申请权限 + 上报(全部静默,失败下次启动再试)
const client: ApiClient | null = this.client;
if (client !== null) {
// 页面里的 `getHostContext()` 返回 `Context`;这里就是在 ability 里(UIAbilityContext 是其子类)
const push: PushService = PushService.getInstance(ctx as common.UIAbilityContext);
push.reportToken(client).catch((err: Object) => {
hilog.info(0x0001, 'SettingsPage', '开后立即上报失败(静默):%{public}s', JSON.stringify(err));
});
}
}
/**
* 拉一次外观:按合并规则落地(规则在 `model/Appearance.ts`,判据跑那一份)。
*
@ -290,7 +348,7 @@ struct SettingsPage {
const username: string = optionalUsername.length > 0 ? optionalUsername : user.username;
const account: AccountInfo = await manager.addAccount(server, username, token, displayName);
SseService.getInstance().connectForAccount(account.id, account.server, account.token);
this.getUIContext().getPromptAction().showToast({ message: '✅ 添加成功: ' + account.displayName });
this.getUIContext().getPromptAction().showToast({ message: '添加成功:' + account.displayName });
this.showAddDialog = false;
this.newDisplayName = '';
this.newServer = '';
@ -324,7 +382,7 @@ struct SettingsPage {
if (this.accounts.length === 0) {
Column() {
Text('📭 暂无账号').fontSize(16).fontColor(Theme.textMuted)
Text('暂无账号').fontSize(16).fontColor(Theme.textMuted)
Button('添加第一个账号')
.margin({ top: 16 }).backgroundColor(Theme.accent)
.onClick(() => { this.showAddDialog = true; })
@ -407,6 +465,41 @@ struct SettingsPage {
.backgroundColor(Theme.surface)
.borderRadius(Theme.radiusCard)
/*
* ── 系统通知(★ 2026-09-15:**可配置项**,默认关)──
*
* 用户可以自由部署自己的 agentmail:没有服务端推送凭证时,
* 关着 = 不取 token / 不弹权限 / 不打网关,零开销;
* 开了 = 取 token + 申请通知权限 + 上报服务端(全部静默,失败只写日志)。
*/
Column() {
Row() {
Column() {
Text('系统通知').fontSize(14).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
Text(this.pushServerStatus === 'server-off'
? '服务器未配置推送'
: (this.pushServerStatus === 'server-on' ? '服务器已开启推送通道' : '推送通道状态未知'))
.fontSize(11)
.fontColor(this.pushServerStatus === 'server-on' ? Theme.textMuted : Theme.warnFg)
.margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Start)
Toggle({ type: ToggleType.Switch, isOn: this.pushEnabled })
.onChange((isOn: boolean) => {
this.togglePush(isOn);
})
}
.width('100%')
Text('新邮件到达时由系统弹通知(点通知直达邮件)。未配置服务端推送时请保持关闭。')
.fontSize(11).fontColor(Theme.textSubtle).margin({ top: 6 })
}
.width('100%').alignItems(HorizontalAlign.Start)
.padding(16).margin({ top: 8 })
.backgroundColor(Theme.surface)
.borderRadius(Theme.radiusCard)
/*
* ── 管理入口(仅管理员可见)──
*