知识库补图标 —— 顺带堵掉「图标名写错静默留白」这个洞

左轨那一格是空的:ZONES 给知识库写的 `library` 在 Ico 字典里根本没有,
而 Ico 查不到名字时返回一个**空 svg** —— 图标掉了不报错、不留痕,
八格有图标一格没有,看上去像布局塌了。是用户指出来才发现的。

三件事:
  · 补 library 字形(书脊排在架上,与运营侧知识库的 layers 明显不同形)
  · 查无此名时画一枚虚线空框(字体界的「豆腐块」),不画空气 —— 缺图标得看得见
  · 导出 hasIco,加一条测试逐格对 ZONES / ZM / TABS 的图标名,
    并自证查名管用(不在册的要答不在,否则前三条是空转)
    全库 92 个手写图标名扫过一遍,现在零缺

顺手改掉知识库占位卡的语气:原来用 Clear(绿对勾),读着像给「还没建」打了勾;
换成中性 Note + 三行未建清单。

四闸绿(api:check 未动契约)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-29 09:02:10 +08:00
co-authored by Claude Opus 5
parent 8cb00d1ff6
commit 331fd25922
4 changed files with 77 additions and 11 deletions
@@ -4,18 +4,25 @@
* 占位页只做一件事:**如实说它还没有**。不摆假数据、不摆点不动的按钮 ——
* 空着比装着强(与死信卡、沟通记录同一条规矩)。
*/
import { Card, Clear } from '@web-shared/gov/primitives.tsx';
import { Card, Note, Rows } from '@web-shared/gov/primitives.tsx';
import type { Nav } from '@web-shared/gov/gov-route.ts';
export function ArchiveHome({ nav }: { tab: string; nav: Nav }) {
void nav;
return (
<Card ico="library" title="知识库" extra="待建">
<Clear>
这里将放企业的资料与文档 —— 规范、图纸、样例、外部材料,供活动按需引用。
<br />
目前还没有建:没有存储、没有检索、没有引用机制。等要用到的第一条流出现时再动手。
</Clear>
{/* 中性口吻,不是绿对勾 —— 「还没建」不是一件办妥了的事 */}
<Note>
这里将放企业的资料与文档:规范、图纸、样例、外部材料,供活动按需引用。
等第一条真要用到资料的流出现时再动手 —— 那时才知道它该按什么检索、被谁引用。
</Note>
<Rows
items={[
{ k: '存储', v: '未建' },
{ k: '检索', v: '未建' },
{ k: '活动引用机制', v: '未建' },
]}
/>
</Card>
);
}
@@ -0,0 +1,40 @@
/**
* 导航表里的图标名**必须在册** —— 这条实测抓出来的洞(2026-07-29)。
*
* `Ico` 从前查不到名字就返回一个**空 svg**:左轨那一格什么都不画,
* 而周围八格都有图标,看上去像布局塌了。知识库区就这么空着上了一版,
* 是用户指出来的(「知识库至少做个图标啊」)。
*
* 图标名是**手写的字符串**,没有类型能拦住笔误;那就用一条测试逐格对表。
*/
import { describe, expect, it } from 'vitest';
import { hasIco } from '@ui';
import { TABS, ZM, ZONES } from '../gov-route.ts';
describe('导航图标名在册', () => {
it('左轨每一区的图标都画得出来', () => {
const missing = ZONES.filter((z) => !hasIco(z.icon)).map((z) => `${z.label}(${z.icon})`);
expect(missing, '左轨缺图标的区').toEqual([]);
});
it('区头每一区的图标都画得出来', () => {
const missing = Object.entries(ZM)
.filter(([, m]) => !hasIco(m.icon))
.map(([k, m]) => `${k}(${m.icon})`);
expect(missing, '区头缺图标的区').toEqual([]);
});
it('区内页签的图标都画得出来', () => {
const missing = Object.entries(TABS).flatMap(([zone, tabs]) =>
tabs.filter((t) => !hasIco(t.ico)).map((t) => `${zone}/${t.key}(${t.ico})`),
);
expect(missing, '页签缺图标的格').toEqual([]);
});
});
describe('查名本身管用', () => {
it('在册的答在、不在册的答不在 —— 否则上面三条测试是空转的', () => {
expect(hasIco('library'), '知识库那格的图标').toBe(true);
expect(hasIco('这个名字不存在')).toBe(false);
});
});
+23 -4
View File
@@ -1,6 +1,5 @@
// 共享内联 lucide 图标(会话面板 / 工作区驾驶舱 / 设置等共用)。
export function Ico({ name, s = 18 }: { name: string; s?: number }) {
const p: Record<string, React.ReactNode> = {
const GLYPHS: Record<string, React.ReactNode> = {
"chevron-right": (<path d="m9 18 6-6-6-6" />),
"chevron-down": (<path d="m6 9 6 6 6-6" />),
check: (<path d="M20 6 9 17l-5-5" />),
@@ -125,11 +124,31 @@ export function Ico({ name, s = 18 }: { name: string; s?: number }) {
"redo-2": (<><path d="m15 14 5-5-5-5" /><path d="M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13" /></>),
"maximize-2": (<><polyline points="15 3 21 3 21 9" /><polyline points="9 21 3 21 3 15" /><line x1="21" x2="14" y1="3" y2="10" /><line x1="3" x2="10" y1="21" y2="14" /></>),
x: (<><path d="M18 6 6 18" /><path d="m6 6 12 12" /></>),
library: (<><path d="m16 6 4 14" /><path d="M12 6v14" /><path d="M8 8v12" /><path d="M4 4v16" /></>),
"log-out": (<><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><polyline points="16 17 21 12 16 7" /><line x1="21" x2="9" y1="12" y2="12" /></>),
};
};
/**
* 名字在不在册 —— 给守闸的测试用。
* 导航表里写错一个名字,从前只会在界面上留一格**看不见的空白**(知识库那格就这么空了一版);
* 现在有测试逐个对表,写错当场红。
*/
export function hasIco(name: string): boolean {
return Object.hasOwn(GLYPHS, name);
}
/**
* 查无此名时画一枚**虚线空框**(字体界的「豆腐块」),不画空气 ——
* 缺图标得看得见才修得掉。
*/
const MISSING = (
<rect x="3" y="3" width="18" height="18" rx="2" strokeDasharray="3 3" opacity="0.55" />
);
export function Ico({ name, s = 18 }: { name: string; s?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
{p[name]}
{GLYPHS[name] ?? MISSING}
</svg>
);
}
+1 -1
View File
@@ -4,6 +4,6 @@
export type { ViewContainerProps } from './ViewContainer.tsx';
export { ViewContainer } from './ViewContainer.tsx';
export { ViewBlocks } from './ViewBlocks.tsx';
export { Ico } from './Ico.tsx';
export { Ico, hasIco } from './Ico.tsx';
export const UI_KIT_VERSION = '0.1.0-m6';