fix(island): 修好"非紧急叫人 / 紧急叫人收起 / 设置页预览"三处点不开,并让胶囊汇总通知类型
用户反馈的三个场景: 1) 非紧急叫人(HIGH + kind=call)—— 上课时段原先只进队列、屏幕上看不到东西, 自然"无法点开"。现在:上课时段仍以**胶囊**保持可见(不自动展开、不打断课堂), 点一下即可展开详情;下课自动展开。新增 isOpenable():紧急消息与"任何叫人"都允许点开。 2) 紧急叫人点击缩回后点不开 —— 归因于固定包围盒窗口的命中丢失(上一轮已改为光标兜底轮询), 本轮补上"展开 → 收起 → 再次展开"的叫人类专项回归。 3) 设置页"预览效果"示例岛无法正常展开 —— 预览通知现在走 preview 通道:直接展开、 失焦不收起(用户拖滑块时主窗口一直是焦点)、30 秒后自行消失、点"知道了"不留残影。 顺带修掉两个真问题: - pushNotification 在"胶囊态"收到新消息时会把上一条静默顶掉(只有展开态才回队); 现在只要换了当前展示的消息,上一条一律回队列 —— 否则类型汇总与"还有 N 条"都会漏。 - syncWindow 的上课时段守卫只认"紧急",导致普通叫人被 hideImmediately 藏掉;改用 isOpenable。 胶囊类型汇总(用户要求):多条未处理消息时收起态显示 「新消息:叫人/作业/通知(共 3 条)」+「点击查看」,并把三种类型做成小标签; 单条时仍显示类型名(新作业 / 老师叫你 / 新消息)。 性能:岛体矩形上报加限流(~10 次/秒,尾随补发最终形态),修掉"每帧 IPC + 每帧读光标" 导致的渲染变慢(实测会让开合采样从 57 帧掉到 4 帧)。 验证:verify:desktop 71/71 + 离线复跑 65/65;typecheck/lint 全绿。
This commit is contained in:
1 parent
d8b0eb05c2
commit
86430c5f4a
5 files changed
+353
-35
No files matched your search
@@ -58,6 +58,43 @@ const pillTitle = computed(() => {
|
||||
return isUrgent.value ? '紧急通知' : '新消息';
|
||||
});
|
||||
|
||||
/**
|
||||
* 未处理消息的**类型汇总**(当前展示的 + 队列里的):
|
||||
* 用户要求收起后的胶囊直接说明这批消息里都有什么,例如
|
||||
* 「新消息:叫人/作业/通知(共 3 条)」+「点击查看」。
|
||||
*/
|
||||
const TYPE_LABELS: Record<IslandNotificationKind, string> = {
|
||||
call: '叫人',
|
||||
homework: '作业',
|
||||
notification: '通知',
|
||||
};
|
||||
/** 汇总展示顺序:叫人 > 作业 > 通知(与用户示例一致) */
|
||||
const TYPE_ORDER: IslandNotificationKind[] = ['call', 'homework', 'notification'];
|
||||
|
||||
const pendingNotifications = computed<IslandNotification[]>(() => {
|
||||
const list: IslandNotification[] = [];
|
||||
if (state.value.active) list.push(state.value.active);
|
||||
for (const item of state.value.queued) {
|
||||
if (!list.some((existing) => existing.id === item.id)) list.push(item);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
/** 这批未处理消息覆盖了哪些类型(按"叫人/作业/通知"顺序) */
|
||||
const pendingTypes = computed<string[]>(() => {
|
||||
const kinds = new Set(pendingNotifications.value.map((item) => item.kind ?? 'notification'));
|
||||
return TYPE_ORDER.filter((item) => kinds.has(item)).map((item) => TYPE_LABELS[item]);
|
||||
});
|
||||
|
||||
const pendingCount = computed(() => pendingNotifications.value.length);
|
||||
|
||||
/** 多条消息时把类型摊开:`新消息:叫人/作业/通知(共 3 条)` */
|
||||
const pillSummaryTitle = computed(() => {
|
||||
if (pendingCount.value <= 1) return pillTitle.value;
|
||||
const types = pendingTypes.value.join('/');
|
||||
return types ? `新消息:${types}(共 ${pendingCount.value} 条)` : `新消息(共 ${pendingCount.value} 条)`;
|
||||
});
|
||||
|
||||
/** 展开态徽标:叫人 / 新作业 / 优先级文案 */
|
||||
const badgeText = computed(() => {
|
||||
if (isCall.value) return '叫人';
|
||||
@@ -301,8 +338,11 @@ const islandClass = computed(() => ({
|
||||
let lastInteractive: boolean | null = null;
|
||||
/** 最近一次已知的指针位置:岛收起/变大后用它重算命中,避免"鼠标没动就点不到" */
|
||||
let lastPointer: { x: number; y: number } | null = null;
|
||||
/** 最近一次上报给主进程的岛体矩形(去重,避免形变期间刷屏 IPC) */
|
||||
/** 最近一次上报给主进程的岛体矩形(去重 + 限流,避免形变期间刷屏 IPC 拖慢渲染) */
|
||||
let lastHitRectKey: string | null = null;
|
||||
let lastHitRectAt = 0;
|
||||
const HIT_RECT_MIN_INTERVAL_MS = 100;
|
||||
let hitRectTrailingTimer: number | null = null;
|
||||
|
||||
/** 把岛体矩形上报给主进程:主进程按光标位置兜底校正命中(不依赖 mousemove 转发) */
|
||||
function reportHitRect(rect: DOMRect | null): void {
|
||||
@@ -312,7 +352,30 @@ function reportHitRect(rect: DOMRect | null): void {
|
||||
: 'none';
|
||||
if (key === lastHitRectKey) return;
|
||||
lastHitRectKey = key;
|
||||
bridge?.setHitRect?.(payload);
|
||||
const now = performance.now();
|
||||
const elapsed = now - lastHitRectAt;
|
||||
if (elapsed >= HIT_RECT_MIN_INTERVAL_MS) {
|
||||
lastHitRectAt = now;
|
||||
bridge?.setHitRect?.(payload);
|
||||
return;
|
||||
}
|
||||
// 限流:形变过程中每帧都发会挤占主进程(实测拖慢到采样都变少),
|
||||
// 这里保证"最后一次形状"一定会送达(尾随发送)。
|
||||
if (hitRectTrailingTimer !== null) window.clearTimeout(hitRectTrailingTimer);
|
||||
hitRectTrailingTimer = window.setTimeout(() => {
|
||||
hitRectTrailingTimer = null;
|
||||
lastHitRectAt = performance.now();
|
||||
const node = document.querySelector('.island-card') as HTMLElement | null;
|
||||
const fullyHidden = state.value?.mode === 'hidden' && !appearance.value.idleSliver;
|
||||
const latest = node && !fullyHidden ? node.getBoundingClientRect() : null;
|
||||
const latestPayload = latest
|
||||
? { x: latest.left, y: latest.top, width: latest.width, height: latest.height }
|
||||
: null;
|
||||
lastHitRectKey = latestPayload
|
||||
? `${Math.round(latestPayload.x)},${Math.round(latestPayload.y)},${Math.round(latestPayload.width)},${Math.round(latestPayload.height)}`
|
||||
: 'none';
|
||||
bridge?.setHitRect?.(latestPayload);
|
||||
}, HIT_RECT_MIN_INTERVAL_MS);
|
||||
}
|
||||
|
||||
function updateInteractive(clientX: number, clientY: number): void {
|
||||
@@ -449,11 +512,15 @@ onMounted(async () => {
|
||||
</svg>
|
||||
</span>
|
||||
<span class="pill-text">
|
||||
<span class="pill-title">
|
||||
{{ pillTitle }}
|
||||
<template v-if="queueCount > 0">· 共 {{ queueCount + 1 }} 条</template>
|
||||
<span class="pill-title">{{ pillSummaryTitle }}</span>
|
||||
<span class="pill-sub">
|
||||
<template v-if="pendingCount > 1">点击查看</template>
|
||||
<template v-else>{{ notification?.teacherName || '老师' }} · 点击查看</template>
|
||||
</span>
|
||||
<span class="pill-sub">{{ notification?.teacherName || '老师' }} · 点击查看</span>
|
||||
</span>
|
||||
<!-- 多类型时把类型也做成小圆点,收起状态一眼看出有什么 -->
|
||||
<span v-if="pendingCount > 1 && pendingTypes.length > 1" class="pill-types" aria-hidden="true">
|
||||
<span v-for="item in pendingTypes" :key="item" class="pill-type">{{ item }}</span>
|
||||
</span>
|
||||
<span class="pill-chevron" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24">
|
||||
@@ -753,6 +820,25 @@ body {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 多条消息时把类型做成胶囊小标签(叫人/作业/通知),收起状态也能一眼看出内容构成 */
|
||||
.pill-types {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--island-font, 13px) * 0.2);
|
||||
flex: 0 0 auto;
|
||||
padding-right: calc(var(--island-font, 13px) * 0.2);
|
||||
}
|
||||
|
||||
.pill-type {
|
||||
font-size: calc(var(--island-font, 13px) * 0.6);
|
||||
line-height: 1.4;
|
||||
padding: 0 calc(var(--island-font, 13px) * 0.36);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: var(--wn-text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pill-chevron {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in new issue
Block a user