/** * family-model.mjs - 组件族的唯一真源(数据层) * * 设计目标:把「同一交互/结构的重复组件」表达为「1 个基座组件 + N 个参数取值」。 * * 硬约束(改这个文件前必须读): * - frameworks/ 必须恰好 395 文件(79 × 5),任何族成员都不得增删文件; * - site/data.json 必须保持完整 79 个 slug(对外承诺,见 AGENTS.md 铁律 5); * - 族的合并发生在「实现来源」层(一份模板 + 参数)与「契约/API」层, * 不改变组件身份(slug)、class 前缀与视觉契约。 * * 每个族的 `mergeBasis` 必须写明实测依据,禁止凭名字臆断。 * 依据来源:.design_library/kole-ui/components/.json 的 * semanticTypeCandidates / variantDimensions / anatomy / structurePatterns / doNotInvent。 */ /** 参数取值类型:enum=枚举、bool=开关、list=数组、string=字符串、int=整数 */ export const FAMILIES = [ { id: 'nav-menu', label: '导航菜单', base: 'sidemenu', mergeBasis: 'topmenu 与 sidemenu 的 semanticTypeCandidates 分别为 menu|topnav 与 menu|sidebar,同一 menu 语义的两个 direction 取值;' + 'mixednavigation 的 anatomy 即 [top/side/sync/memory],本身就是前两者的参数化组合。' + 'sidemenu 实现内已存在通用 menu 词汇(kole-menu-item/icon/label/badge/arrow/children/pop)。', paramSurface: [ { name: 'direction', type: 'enum', values: ['top', 'side', 'mixed'], default: 'side', desc: '导航方向,决定一级菜单的排布' }, { name: 'collapsed', type: 'bool', default: false, desc: '侧边模式折叠为仅图标,悬停弹出子级' }, { name: 'level', type: 'enum', values: ['1', '2'], default: '2', desc: '最大层级;mixed 固定 2 级(顶一级 + 侧二级)' }, { name: 'badge', type: 'bool', default: false, desc: '菜单项徽章' }, { name: 'indicator', type: 'enum', values: ['none', 'top', 'left'], default: 'left', desc: '选中指示条;top 模式默认 top,side 模式默认 left' }, ], members: [ { slug: 'sidemenu', role: 'base', params: { direction: 'side', indicator: 'left', collapsed: false } }, { slug: 'topmenu', role: 'variant', params: { direction: 'top', indicator: 'top', collapsed: false } }, { slug: 'mixednavigation', role: 'variant', params: { direction: 'mixed', level: '2', collapsed: false } }, ], }, { id: 'table', label: '数据表格', base: 'table', mergeBasis: '六个成员的 semanticTypeCandidates 首项均为 table;每个变体的 anatomy 都是 Table 骨架(header/row/cell/selectedRow)加一个能力维度。' + 'treetable 额外带 indent/arrow/parentRow/childRow,是 table 的超集而非并列组件。', paramSurface: [ { name: 'expandable', type: 'bool', default: false, desc: '行展开;anatomy 增 icon/content/state' }, { name: 'fixedTarget', type: 'enum', values: ['none', 'left', 'right', 'header'], default: 'none', desc: '固定列 / 固定表头' }, { name: 'mergeAxis', type: 'enum', values: ['none', 'vertical', 'horizontal'], default: 'none', desc: '单元格合并轴向' }, { name: 'summary', type: 'enum', values: ['none', 'top', 'bottom'], default: 'none', desc: '统计行位置' }, { name: 'treeMode', type: 'bool', default: false, desc: '树形层级(缩进 16px/级 + 展开箭头)' }, ], members: [ { slug: 'table', role: 'base', params: {} }, { slug: 'expandabletable', role: 'variant', params: { expandable: true } }, { slug: 'fixedcolumntable', role: 'variant', params: { fixedTarget: 'left' } }, { slug: 'mergedcelltable', role: 'variant', params: { mergeAxis: 'vertical' } }, { slug: 'summaryrowtable', role: 'variant', params: { summary: 'bottom' } }, { slug: 'treetable', role: 'variant', params: { treeMode: true } }, ], }, { id: 'masked-input', label: '掩码输入', base: 'input', mergeBasis: '七个成员的 semanticTypeCandidates 第二项均为 input;anatomy 均为 input 加 format / validate / segment 的子集。' + '差异只是掩码规则与校验规则不同,结构完全同源。', paramSurface: [ { name: 'mask', type: 'string', default: '', desc: '掩码模式(每 4 位空格、6/8/4 分段、7/8 位车牌等)' }, { name: 'length', type: 'int', default: 0, desc: '期望长度;0 表示不限' }, { name: 'segment', type: 'list', default: [], desc: '分段长度数组,如 [6,8,4]' }, { name: 'validate', type: 'enum', values: ['none', 'luhn', 'checksum', 'regex'], default: 'none', desc: '校验规则' }, { name: 'scan', type: 'bool', default: false, desc: '扫码 / 识别入口' }, { name: 'autoFocusNext', type: 'bool', default: false, desc: '输入满格自动跳下一格' }, { name: 'masked', type: 'bool', default: false, desc: '回显脱敏(密码)' }, ], members: [ { slug: 'input', role: 'base', params: {} }, { slug: 'codeinput', role: 'variant', params: { length: 6, autoFocusNext: true, scan: false } }, { slug: 'passwordinput', role: 'variant', params: { masked: true } }, { slug: 'phoneinput', role: 'variant', params: { mask: '3-4-4', length: 11 } }, { slug: 'idcardinput', role: 'variant', params: { segment: [6, 8, 4], length: 18, scan: true } }, { slug: 'bankcardinput', role: 'variant', params: { mask: '4-4-4-4', validate: 'luhn', scan: true } }, { slug: 'plateinput', role: 'variant', params: { length: 7, validate: 'regex' } }, ], }, { id: 'modal-shell', label: '对话弹窗', base: 'modal', mergeBasis: 'alertmodal 与 confirmmodal 的 doNotInvent 原文写着「弹窗尺寸档位(见 Modal 契约)」——规范自身声明从属。' + '四个变体的 anatomy 均为 mask/container/title/content/footer 的子集或替换。' + 'popconfirm(240px 无遮罩面板)与 bottomsheet(底部滑出)不属本族:archetype 不同。', paramSurface: [ { name: 'size', type: 'enum', values: ['sm', 'md', 'lg'], default: 'md', desc: '宽度档位' }, { name: 'tone', type: 'enum', values: ['none', 'success', 'error', 'warning', 'info'], default: 'none', desc: '语义色 + 图标' }, { name: 'footer', type: 'enum', values: ['none', 'confirm', 'confirm-cancel'], default: 'confirm-cancel', desc: '底部操作' }, { name: 'columns', type: 'enum', values: ['1', '2'], default: '1', desc: '表单类弹窗的列数' }, { name: 'fullscreen', type: 'bool', default: false, desc: '全屏铺满' }, ], members: [ { slug: 'modal', role: 'base', params: {} }, { slug: 'alertmodal', role: 'variant', params: { tone: 'info', footer: 'confirm' } }, { slug: 'confirmmodal', role: 'variant', params: { footer: 'confirm-cancel' } }, { slug: 'formmodal', role: 'variant', params: { columns: '2', footer: 'confirm-cancel' } }, { slug: 'fullscreenmodal', role: 'variant', params: { fullscreen: true } }, ], }, { id: 'preference-switcher', label: '偏好切换器', base: 'themeswitcher', mergeBasis: '三个成员的 anatomy 全部包含 persist,structurePatterns 全部写着持久化到 localStorage,' + 'semanticTypeCandidates 末项均为 preference,变体维度均为同一形态的 3 档选项。三者完全同构,仅选项内容不同。', paramSurface: [ { name: 'options', type: 'list', default: [], desc: '选项数组 [{value,label,preview?}]' }, { name: 'selected', type: 'string', default: '', desc: '当前选中值' }, { name: 'persistKey', type: 'string', default: '', desc: 'localStorage 键名;空则持久化到内存' }, { name: 'applyScope', type: 'enum', values: ['global', 'local'], default: 'global', desc: '生效范围:全站令牌 / 局部预览' }, { name: 'extra', type: 'enum', values: ['none', 'brand-palette', 'demo-list'], default: 'none', desc: '附加区块(品牌色板 / 示例列表)' }, ], members: [ { slug: 'themeswitcher', role: 'base', params: { extra: 'brand-palette', applyScope: 'global' } }, { slug: 'layoutswitcher', role: 'variant', params: { extra: 'none', applyScope: 'global' } }, { slug: 'densityswitcher', role: 'variant', params: { extra: 'demo-list', applyScope: 'global' } }, ], }, { id: 'card-shell', label: '卡片容器', base: 'card', mergeBasis: '四个成员的 semanticTypeCandidates 均含 card 或 panel,anatomy 均为 card 骨架(style/padding/section)加各自填充区。' + '本轮不合并实现:employeecard 有独立的字段面(status/contact),合并会污染 card 参数面,仅登记族关系。', paramSurface: [ { name: 'style', type: 'enum', values: ['plain', 'outlined'], default: 'plain', desc: '描边 / 阴影' }, { name: 'padding', type: 'enum', values: ['md', 'lg'], default: 'md', desc: '内边距档位' }, { name: 'sections', type: 'list', default: [], desc: '区块序列:header/body/footer/media' }, { name: 'actions', type: 'list', default: [], desc: '操作区按钮' }, { name: 'layout', type: 'enum', values: ['vertical', 'horizontal'], default: 'vertical', desc: '内容排布方向' }, ], members: [ { slug: 'card', role: 'base', params: {} }, { slug: 'dashboardcard', role: 'variant', params: { layout: 'horizontal' } }, { slug: 'employeecard', role: 'variant', params: { sections: ['media', 'body'] } }, { slug: 'chartpanel', role: 'variant', params: { sections: ['header', 'body'] } }, ], mergeStatus: 'registered-only', }, { id: 'feedback-page', label: '结果与异常页', base: 'resultvariants', mergeBasis: '三个成员的 semanticTypeCandidates 分别为 result|feedback-page、exception|error-page、empty|placeholder,' + '均为「占满内容区的状态说明 + 插画 + 操作」。resultvariants.status[4] 与 exceptionvariants.code[4] 同构。', paramSurface: [ { name: 'status', type: 'enum', values: ['success', 'error', 'warning', 'info', 'empty'], default: 'info', desc: '状态语义' }, { name: 'illustration', type: 'enum', values: ['none', 'icon', 'image'], default: 'icon', desc: '插画形态' }, { name: 'extra', type: 'list', default: [], desc: '底部操作区' }, ], members: [ { slug: 'resultvariants', role: 'base', params: {} }, { slug: 'exceptionvariants', role: 'variant', params: { status: 'error' } }, { slug: 'emptypro', role: 'variant', params: { status: 'empty' } }, ], }, { id: 'loading-state', label: '加载态', base: 'loadingoverlay', mergeBasis: '三个成员的 semanticTypeCandidates 分别为 overlay|loading、skeleton|placeholder、progress|indicator,' + '是同一语义(加载中)的三种承载形态,共享 coverage / status / showText 三要素。', paramSurface: [ { name: 'shape', type: 'enum', values: ['overlay', 'block', 'bar'], default: 'overlay', desc: '承载形态:遮罩 / 占位块 / 进度条' }, { name: 'status', type: 'enum', values: ['active', 'success', 'exception'], default: 'active', desc: '状态' }, { name: 'coverage', type: 'enum', values: ['full', 'local'], default: 'local', desc: '覆盖范围' }, { name: 'showText', type: 'bool', default: false, desc: '显示文案 / 百分比' }, ], members: [ { slug: 'loadingoverlay', role: 'base', params: { shape: 'overlay' } }, { slug: 'skeletonpro', role: 'variant', params: { shape: 'block' } }, { slug: 'progressvariants', role: 'variant', params: { shape: 'bar' } }, ], }, { id: 'steps', label: '步骤', base: 'steps', mergeBasis: 'steps 与 steplist 的 semanticTypeCandidates 完全相同(均为 steps|wizard),变体维度同为 orientation[2] + state[3]。' + '唯一差异是节点形态(横向步骤条 / 纵向列表)。', paramSurface: [ { name: 'nodeStyle', type: 'enum', values: ['bar', 'list'], default: 'bar', desc: '步骤条 / 步骤列表' }, { name: 'orientation', type: 'enum', values: ['horizontal', 'vertical'], default: 'horizontal', desc: '排布方向' }, { name: 'editable', type: 'bool', default: false, desc: '可点击回退' }, ], members: [ { slug: 'steps', role: 'base', params: { nodeStyle: 'bar' } }, { slug: 'steplist', role: 'variant', params: { nodeStyle: 'list', orientation: 'vertical' } }, ], }, { id: 'tabs', label: '标签页', base: 'tabs', mergeBasis: 'tabs 的 semanticTypeCandidates 为 tabs|tabnav,enhancedtabnav 为 tabs|browser-tabs;' + 'enhancedtabnav 的变体维度 state[4]+feature[2] 是 tabs 的 state[4] 超集 —— 增强项即参数。', paramSurface: [ { name: 'type', type: 'enum', values: ['line', 'card'], default: 'line', desc: '标签形态' }, { name: 'draggable', type: 'bool', default: false, desc: '拖拽排序' }, { name: 'contextMenu', type: 'bool', default: false, desc: '右键菜单' }, { name: 'unsavedMark', type: 'bool', default: false, desc: '未保存标记' }, ], members: [ { slug: 'tabs', role: 'base', params: {} }, { slug: 'enhancedtabnav', role: 'variant', params: { draggable: true, contextMenu: true, unsavedMark: true } }, ], }, { id: 'notice', label: '全局轻提示', base: 'messagepro', mergeBasis: 'messagepro 为 message|toast、notificationpro 为 notification|notice,同属全局轻提示;' + '差异只在承载形态(顶部条 / 卡片)与位置,type/action/duration 三要素共享。', paramSurface: [ { name: 'placement', type: 'enum', values: ['top', 'top-right', 'bottom'], default: 'top', desc: '出现位置' }, { name: 'type', type: 'enum', values: ['success', 'error', 'warning', 'info', 'loading'], default: 'info', desc: '语义类型' }, { name: 'action', type: 'bool', default: false, desc: '操作按钮' }, { name: 'duration', type: 'int', default: 3000, desc: '自动关闭毫秒数;0 为不自动关闭' }, ], members: [ { slug: 'messagepro', role: 'base', params: { placement: 'top' } }, { slug: 'notificationpro', role: 'variant', params: { placement: 'top-right', action: true } }, ], }, { id: 'selection-card', label: '选择卡片', base: 'radiocard', mergeBasis: 'radiocard(radio|card)与 checkboxcard(checkbox|card)的 anatomy 同为 card + 选中态 + 内嵌操作,' + '变体维度均含 selection,差异仅为单选 / 多选语义。', paramSurface: [ { name: 'mode', type: 'enum', values: ['single', 'multiple'], default: 'single', desc: '单选 / 多选' }, { name: 'content', type: 'enum', values: ['text', 'rich'], default: 'text', desc: '内容形态' }, { name: 'innerActions', type: 'bool', default: false, desc: '卡片内嵌操作区' }, ], members: [ { slug: 'radiocard', role: 'base', params: { mode: 'single' } }, { slug: 'checkboxcard', role: 'variant', params: { mode: 'multiple', innerActions: true } }, ], }, { id: 'combobox', label: '候选输入', base: 'autocomplete', mergeBasis: 'autocomplete(combobox|input)与 mention(mention|input)的 anatomy 均为 input + 候选面板 + 键盘选择,' + '类名集合 Jaccard 0.448(全库最高组之一);差异仅为触发方式(连续输入 / 触发符)与插入行为。', paramSurface: [ { name: 'trigger', type: 'enum', values: ['input', 'symbol'], default: 'input', desc: '触发方式:连续输入 / 触发符' }, { name: 'suggestionSource', type: 'enum', values: ['static', 'async'], default: 'static', desc: '候选来源' }, { name: 'itemRender', type: 'enum', values: ['plain', 'custom'], default: 'plain', desc: '候选项渲染' }, { name: 'insert', type: 'enum', values: ['replace', 'token'], default: 'replace', desc: '选中后插入行为' }, ], members: [ { slug: 'autocomplete', role: 'base', params: { trigger: 'input' } }, { slug: 'mention', role: 'variant', params: { trigger: 'symbol', insert: 'token' } }, ], }, ]; /** 本批次实际用生成器重写实现的族(其余仅登记契约层族关系) */ export const IMPLEMENTED_FAMILIES = ['nav-menu']; /** 全部族成员 slug(去重) */ export function allMemberSlugs() { const set = new Set(); for (const f of FAMILIES) for (const m of f.members) set.add(m.slug); return [...set]; } /** slug -> {family, role, params} */ export function memberIndex() { const map = {}; for (const f of FAMILIES) { for (const m of f.members) { if (map[m.slug]) throw new Error(`组件 ${m.slug} 同时属于多个族:${map[m.slug].family} 与 ${f.id}`); map[m.slug] = { family: f.id, role: m.role, params: m.params, base: f.base }; } } return map; } export function familyById(id) { return FAMILIES.find((f) => f.id === id) || null; }