perf(renderer): 整体优化响应速度 — rAF 合并编辑器刷新/自动保存缓存/粒子引擎空闲自停

同时: 不透明窗口 + 精简主题(8到2)、TabBar 简化、图谱宽松布局、MoreActions 层级修复
This commit is contained in:
hunter-dev
2026-09-10 00:41:24 +08:00
parent d0dbdfd00a
commit c0ba8df9b6
27 changed files with 417 additions and 178 deletions
+4 -11
View File
@@ -299,22 +299,15 @@ test('Graph: 控件+模式切换可用', async () => {
await a.app.close();
});
// === TabBar pin/close ===
test('TabBar: 固定+关闭可用', async () => {
// === TabBar close ===
test('TabBar: 关闭标签可用(pin 点已移除)', async () => {
const a = await launch(nodePath.join(os.tmpdir(), 'hn-tab-' + Date.now()));
const { v } = await setupVault(a, [{ name: 'Tab1', body: 't1' }, { name: 'Tab2', body: 't2' }]);
await a.page.getByTestId('filetree').getByText('Tab1').click();
await a.page.waitForTimeout(300);
await a.page.getByTestId('filetree').getByText('Tab2').click();
await a.page.waitForTimeout(300);
// 固定 Tab1(pin 按钮是 span[title="固定"]●)
const tab1 = a.page.getByTestId('tabbar').locator('div').filter({ hasText: 'Tab1' }).first();
await tab1.locator('span[title="固定"]').click();
await a.page.waitForTimeout(300);
// 取消固定
await tab1.locator('span[title="取消固定"]').click();
await a.page.waitForTimeout(300);
// 关闭 Tab2(close 按钮是 span 文本 x)
// 关闭 Tab2(close 按钮是 span 文本 x;pin 点按钮已按 UI 需求移除)
const tab2 = a.page.getByTestId('tabbar').locator('div').filter({ hasText: 'Tab2' }).first();
await tab2.locator('span').filter({ hasText: 'x' }).click();
await a.page.waitForTimeout(300);
@@ -354,4 +347,4 @@ test('OutlinePanel: 切换文档后大纲自动更新', async () => {
await expect(a.page.locator('.outline-item', { hasText: 'Title B' })).toBeVisible({ timeout: 5000 });
await expect(a.page.locator('.outline-item', { hasText: 'Deep B' })).toBeVisible({ timeout: 5000 });
await a.app.close();
});
});
+4 -3
View File
@@ -60,18 +60,19 @@ test('思维导图+知识图谱渲染验证(含可见性)', async () => {
const rects = svg.querySelectorAll('rect').length;
const texts = svg.querySelectorAll('text').length;
const lines = svg.querySelectorAll('line').length;
const linkPaths = svg.querySelectorAll('path').length;
// Check a node card is visible within the svg
const visibleCard = Array.from(svg.querySelectorAll('rect')).some((r) => {
const b = r.getBoundingClientRect();
return b.width > 0 && b.height > 0;
});
return { found: true, w: rect.width, h: rect.height, rects, texts, lines, visibleCard };
return { found: true, w: rect.width, h: rect.height, rects, texts, lines, linkPaths, visibleCard };
});
console.log('Knowledge graph:', JSON.stringify(graph));
expect(graph.found).toBe(true);
expect(graph.h).toBeGreaterThan(100);
expect(graph.rects).toBeGreaterThanOrEqual(5);
expect(graph.lines).toBeGreaterThanOrEqual(1);
expect(graph.rects).toBeGreaterThanOrEqual(3); // 2 卡 rect + 1 背景 rect(demo 卡片无左侧色条)
expect(graph.linkPaths).toBeGreaterThanOrEqual(1); // 曲线连线为 path
expect(graph.visibleCard).toBe(true);
await app.close();
+2 -1
View File
@@ -38,7 +38,8 @@ test('知识图谱:卡片+连线(消费 IPC.graph)', async () => {
await a.page.getByTitle('切换 编辑器/图谱').click();
await a.page.getByRole('button', { name: '知识图谱' }).click();
// 限定在知识图谱画布内统计卡片 rect(标题栏等 UI 图标也是 svg,不能全页统计)
await expect(a.page.locator('[data-testid="graph-canvas"] rect')).toHaveCount(5, { timeout: 10000 });
// 2 张卡片(2 卡 rect)+ 1 背景 rect = 3(demo 卡片无左侧色条)
await expect(a.page.locator('[data-testid="graph-canvas"] rect')).toHaveCount(3, { timeout: 10000 });
await a.app.close();
});
@@ -6,8 +6,8 @@ import os from 'node:os';
const MAIN_JS = nodePath.join(process.cwd(), 'dist-electron/main/index.js');
// 透明主题真透明:bgPrimary=transparent 时根背景必须是 alpha=0(配合主进程 transparent:true + backgroundColor:#00000000 + backgroundMaterial:'none' 即可透出下层软件)
test('透明主题:根背景 alpha=0(真透明,可见下层软件)', async () => {
// UI 重新设计:窗口不透明(transparent:false + backgroundColor 空间底色),根背景应为不透明的 L0 空间色
test('窗口不透明:根背景为 L0 空间底色(alpha=1,非透明)', async () => {
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-tr-'));
const ud = nodePath.join(tmp, 'ud'); const v = nodePath.join(tmp, 'v');
await fs.mkdir(ud, { recursive: true }); await fs.mkdir(v, { recursive: true });
@@ -15,15 +15,7 @@ test('透明主题:根背景 alpha=0(真透明,可见下层软件)', asy
const page = await app.firstWindow();
await page.waitForLoadState('domcontentloaded');
await page.evaluate(({ root }) => (globalThis as any).dsh.invoke('vault:switch', { root }), { root: v });
// 应用透明主题的 CSS 变量(bgPrimary='transparent'),并移除背景图遮罩
await page.evaluate(() => {
const el = document.documentElement;
el.style.setProperty('--bg-primary', 'transparent');
el.style.setProperty('--bg-secondary', 'rgba(20,20,28,0.5)');
el.style.setProperty('--text-primary', '#ddd');
document.querySelectorAll('div[style*="background-image"]').forEach((n: any) => n.remove());
});
await page.waitForTimeout(400);
await page.waitForTimeout(500);
const rootBg = await page.evaluate(() => {
const root = document.querySelector('#root > div') as HTMLElement;
if (!root) return null;
@@ -31,7 +23,15 @@ test('透明主题:根背景 alpha=0(真透明,可见下层软件)', asy
});
console.log('ROOT_BG', rootBg);
expect(rootBg).not.toBeNull();
// 透明背景应为 alpha=0(rgba(0,0,0,0) 或 transparent)
expect(rootBg).toMatch(/rgba\(0, 0, 0, 0\)|transparent/i);
// 窗口不透明:根背景为不透明空间底色(alpha=1,非 rgba(...,0) / transparent)
const m = rootBg!.match(/rgba\(([\d.]+),\s*([\d.]+),\s*([\d.]+),\s*([\d.]+)\)|^rgb\(([\d.]+),\s*([\d.]+),\s*([\d.]+)\)$/);
expect(m).not.toBeNull();
// 不透明:要么是 rgb(...)(隐式 alpha=1),要么 rgba alpha=1
if (rootBg!.startsWith('rgba')) {
const alpha = parseFloat(m![4]);
expect(alpha).toBe(1);
} else {
expect(rootBg!.startsWith('rgb(')).toBe(true);
}
await app.close();
});