ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenObserve 前端 UI 布局与结构约定:O2 组件体系、页面骨架与工程规范实战指南

OpenObserve 前端 UI 布局与结构约定:O2 组件体系、页面骨架与工程规范实战指南 OpenObserve 前端 UI 布局与结构约定O2 组件体系、页面骨架与工程规范实战指南【免费下载链接】openobserveOpen source observability platform for logs, metrics, traces, RUM, Session replay, pipelines, SLO and LLM observability. A sophisticated, simple and highly performant alternative to Datadog, Splunk, and Elasticsearch with 140x lower storage costs and single binary deployment.项目地址: https://gitcode.com/GitHub_Trending/op/openobserve本文以 OpenObserve 仓库内.claude/skills/ui-architect技能包中的 conventions.md 为骨架系统讲解 OpenObserve Web 前端web/在开发新页面、面板、对话框、抽屉与表单时必须遵守的布局、间距与组件驱动约定。你将掌握基于 rem 的间距节奏与全出血列表页骨架、通过variant/size意图驱动而非外观覆盖的方式使用 O2 组件库、取消/保存按钮标准、dialog vs drawer vs 全页的选型规则、view → service → Vuex/local ref 的分层数据流以及这些规则在仓库源码中的具体落点与 CI 强制机制。1. 约定从何而来ui-architect 技能包与六大规则OpenObserve 的 Web 前端规模庞大web/src下包含 1358 个组件文件、343 个 composables、553 个 utils为了让每个新屏幕看起来都像同一团队在同一天构建的仓库在.claude/skills/ui-architect/SKILL.md中定义了六条硬性规则house rules而 references/conventions.md 则是其中**结构性决策Structural decisions部分的展开契约**——它覆盖四块内容间距、对齐与布局模式Spacing, Alignment Layout Patterns如何驱动 O2 组件Working with O2 components选择组件与代码结构Choosing components structure配套的 references/page-recipes.md、references/keyboard-shortcuts.md、references/creating-components.md、references/navigation-menus.md 等参考文档承载完整的配方与细节。六条规则的简明版本详见 references/house-rules.md#规则一句话要点1每个页面/模块头部用OPageHeader禁止手写div classheader…h1头部 create CTA 只能纯文字、不得带icon-leftadd2优先使用web/src/lib的 O2 组件存在O*等价物时禁止裸 HTML 控件与第三方 UI 原语用variant/size/state 表达意图3不写硬编码px一律 rem / % / vh / vw 或 Tailwind 的 rem 刻度只有 hairline、阴影、查询条件等特许位置允许 px且必须就地携带eslint-disable-next-line local/no-hardcoded-px -- reason4无style scoped、无内联style用裸 Tailwind 工具类tw:前缀已被移除5无字面颜色/尺寸一律走已注册 token 的工具类颜色来自--color-*token 工具类bg-surface-base等旧--o2-*词汇与.body--dark块被禁用6无硬编码面向用户文本所有文案来自useI18nTyped()的t()key 加入 web/src/locales/languages/en-US.json不可翻译字符串用raw()规则 3–5 全部由 CI 强制local/no-hardcoded-pxESLint 规则、lint:design:strict、lint:tokens等出现一次即构建失败——这正是 conventions 中所有禁止条款背后的工程动机。2. 间距、对齐与布局模式2.1 全部间距使用裸 Tailwind 工具类基于 rem 刻度所有间距使用裸 Tailwind 工具类flex flex-col gap-*、p-*、mb-*绝不使用内联style绝不使用tw:/q-*。rem 刻度让每个屏幕保持同一节奏gap-2 0.5remgap-4 1remgap-5 1.25remgap-6 1.5remp-6 1.5rem。换算关系来自 SKILL.md 规则 31rem 16px应用未设置html { font-size }根字号为浏览器默认值因此px ÷ 16→ rempx ÷ 4→ Tailwind 刻度步进如300px → 18.75rem → w-75。字号也有固定刻度text-sm14px是默认正文起点text-base16px用于表单输入text-lg18px用于卡片标题text-xl20px用于章节标题text-2xl24px用于页面/弹窗标题禁止text-[13px]之类的任意值。2.2 列表页全出血full-bleed骨架而非带 padding 的卡片列表/表格页是全出血的根节点flex flex-col h-full p-0OPageHeader拥有内边距——它自带px-page-edge因此消费方只需加shrink-0 border-b绝不加px-*表格容器紧贴内容边缘card-container flex-1 min-h-0 overflow-hidden包裹OTable :framefalse。p-6/gap-6只留给表单/详情视图——给页面加 inset 会破坏表格的 flush 对齐。这套骨架的完整代码示例与逐类解释见 references/page-recipes.md其中点明了min-h-0的关键作用它允许 flex 子元素收缩到内容之下从而让表格内部滚动、页面本身不滚动省略它会导致整个页面滚动而不是表格滚动。card-container是应用既有类为表格区域提供表面背景复用即可不要自造bg-*/border 盒子那会给无边框表格双重描边。2.3 卡片与堆叠面板页一致性优先于发明绝不发明间距。内边距/外边距/gap 不是每个页面的自由决策新面板必须逐字复制它所属兄弟家族的确切间距类相同的 header inset、相同的gap-*、相同的卡片类让页面读起来像同一块表面。如果面板需要一个兄弟没用过的 padding 值那本身就是警报信号——去读兄弟页面而不是凭肉眼微调。card-container不提供任何样式。它是一个没有 CSS 支撑的裸标记类——只靠它的卡片会渲染成漂浮文字无背景、无边框。每张卡片必须携带显式表面类例如 AI Observability 面板家族用bg-card-glass-bg rounded-default border border-border-defaultsynthetics 用bg-surface-base border border-border-default rounded-default。类字符串要从兄弟家族逐字复制。固定的堆叠面板组不能使页面滚动。当页面高度为 N 张卡片dashboards、signal panels时用全高拆分bodyflex flex-col无overflow-auto每张卡片flex flex-col flex-1 min-h-0 overflow-hidden每个OTable用填充高度模式classflex-1 min-h-0。行在表格内部滚动分页栏钉在卡片底部页面本身永不滚动。空态填满卡片而不强制溢出OTableEmpty的min-h-75下限只作用于非填充高度表格。2.4 ODialog 间距标题来自:titleprop——不要手写h*标题。ODialog已经为 body 加了内边距——不要再包一层带额外 padding 的 wrapper。字段间距放在OForm上classflex flex-col gap-5。它的class会透传到根form使OFormInput/OFormSelect子元素均匀分布。遗漏这行正是对话框字段之间没有间距、显得拥挤的原因——这是最常见的 bug在 SKILL.md 规则 4 中也被点名。紧凑子组一个 label 它的自定义控件、头部 key/value 行用classflex flex-col gap-2或classflex items-end gap-2包裹。底部栏使用 ODialog 的:primary-button-label/:secondary-button-label:form-id——绝不手搓或重样式底部按钮。2.5 ODrawer 间距顶部详情只读区块把事实分组放进 token 卡片——每张卡片classflex flex-col gap-1.5 p-3 rounded-surface bg-surface-subtlelabel value。只有真正不同的分组才给背景不要给每一行都加阴影。用分隔线将详情与编辑表单分开详情 wrapper 上class… mb-8 pb-8 border-b border-border-default。表单的章节标题classtext-base font-semibold mb-4。编辑表单与对话框一致——OForm上classflex flex-col gap-5。底部栏与 ODialog 相同的:primary-button-label/:secondary-button-label:form-id模式不要重样式按钮。2.6 键盘快捷键模式页面在快捷键注册表shortcutRegistry.ts位于 web/src/lib/vue-shortcut-manager/shortcutRegistry.ts中注册快捷键绝不使用内联keydown。完整的 registry → i18n → handler 配方含isInputFocused()守卫与focusSearchInput()见 references/keyboard-shortcuts.md。3. 驱动 O2 组件传递意图而非外观O2 组件把设计内化在自己内部这改变了驱动它们的方式传递意图variant、size、state绝不传递外观颜色、圆角、内边距。这些规则保证组件库的整体一致性。3.1 只传文档化的 props查看O{Name}.types.ts例如 web/src/lib/core/Button/OButton.types.ts、web/src/lib/core/Table/OTable.types.ts确认组件接受什么——通常是variant、size与状态 propsdisabled、loading、modelValue。组件库刻意没有color、rounded、flat、sharpprop。如果你在找一个重塑组件的 prop 却找不到那说明你需要的是给组件新增一个 variant而不是传一个不存在的 prop。3.2 调用点禁止外观覆盖O2 组件的外观由其自身的variant/size产生绝不从外部打补丁。以下是禁止清单禁止原因替代stylecolor: #fff; padding: 10px硬编码颜色/px绕过 token使用variant/size/ 组件设计classtw:px-2 tw:text-smtw:前缀前缀已被移除——tw:*无法解析使用组件variant/sizepropsstyle scoped针对其类设计系统的隐形分叉把意图移入variant任何与默认外观不同的视觉按此决策流处理能用variant/size表达→ 用 prop。没有 variant 覆盖→ 在组件源码中新增一个命名 variant然后使用它。只是布局/定位margin、flex-shrink、父级 hover 显露→ 用普通span/div包裹把规则放在wrapper上绝不放在 O2 组件上。3.3 取消 / 保存按钮标准强制表单、对话框、抽屉中的每一对取消/保存都必须完全遵循以下形状——正是它让所有操作行看起来一致div classflex gap-2 OButton variantoutline sizesm-action clickcancel {{ t(common.cancel) }} /OButton OButton variantprimary sizesm-action typesubmit clicksave {{ t(common.save) }} /OButton /div取消永远是variantoutline——绝不使用secondary或ghost-primary。保存/提交/确定永远是variantprimary。两者都用sizesm-action。父容器上用gap-2分隔——用父容器的gap-*而不是子元素 margin 或 spacer 元素。同一行其他操作Delete、Reject保留自己的variant如destructive但为对齐同样用sizesm-action。当按钮行位于ODialog/ODrawer底部栏时优先使用 overlay 内建的primary/secondary/neutralButtonLabelprops它们已自动应用此标准。3.4 暗黑模式是自动的每个 O2 组件和每个 token 在两种主题下都能正确解析。不要在 O2 组件周围写store.state.appTheme dark ? … : …条件、dark-only 类或暗色重复样式。如果暗黑模式下看起来不对修复方式是改dark.css中的 token 值而不是给组件加条件。3.5 注释保持简短一两行即可。注释承载的是非显然选择背后的why——不是对代码的复述也不是添加它的 PR 历史!-- good -- !-- OSelect fills its container, so the width lives on the wrapper. -- !-- bad — a paragraph, half of it review archaeology -- !-- Bounded elastic column: absorbs leftover width but stays inside the horizontal-scroll container instead of stretching to fit its longest value. Matches the recipe used by Nodes.vue, PipelinesList.vue, etc. (tag 02 review finding) --去掉 ticket id、review finding、as discussed、previously we… 以及任何只是复述下一条语句的行。.spec.ts文件同样一行说明 setup 为何不寻常而不是一段辩护。3.6 没有现成组件构建可复用组件而不是拼装裸类当你需要某个 UI 元素而没有现成组件匹配时答案是创建一个新的可复用组件而不是在调用点手拼div class…块。一堆 div 工具类对设计系统不可见无法复用被复制时立即漂移也是一次性px/颜色 hack 卷土重来的地方。值得构建一次的模式就值得做成组件。决定它放哪通用、与应用无关的原语新型按钮、统计块、带标签的值、badge variant、图标 chip——内部不含 store/router/i18n/API→ 构建在web/src/lib/group/Name/遵循相邻组件约定一个O{Name}.types.ts、一个.spec.ts、所有颜色走 token、variant/sizeprops 表达视觉差异绝无外观 props、暗黑模式走 token、data-test。若属于某个家族家族一起交付然后加入组件目录references/component-catalog.md让下一个人能找到它。可复用但应用特定的组合组合 O2 组件 应用数据/逻辑如拉取 streams 的 stream picker→ 构建为web/src/components/…下的具名组件本身由 O2 组件组成。仍然是带名称和 props 的真组件不是内联标记。触发条件很简单如果你正要写一个自包含的 UI 元素——尤其是会重复的那种——停下来把它做成组件。内联工具类只用于布局/组合组件flex、grid、gap、spacing绝不用于重建组件库应当拥有的组件。这也正是缺失 variant → 新增 variant、缺失 token → 注册 token的原因修复总是落在共享的东西上而不是调用点的一堆私有类。如何实际构建它——文件夹契约O{Name}.vue.types.ts.spec.ts、一起交付的家族、基于reka-ui的 headless-first、无 UI prop 泄漏规则仅variant/size、严格 TypeScript、token 注册、OXOFormX表单包装模式、以及 Analysis → Design → Implement → Test → Validate 工作流——都在 references/creating-components.md。写新O*组件前务必先读它。4. 选择组件与结构页面解剖、表格、分层与选型以上规则解决how怎么样式化本节解决what拿什么、代码放哪——那些否则会在每个屏幕上得到不同答案的重复决策。4.1 页面解剖与列表页配方从配方布局整个页面而不是从零开始——完整配方在 references/page-recipes.md。要点每个页面 顶部OPageHeader主操作 New/Add 放在#actions中。列表/表格页是全出血布局为全高 flex 列头部承载内边距OPageHeader内置px-page-edge且表格紧贴内容边缘——根节点flex flex-col h-full p-0头部shrink-0 border-b border-border-default无px-*——头部自内缩表格 wrappercard-container flex-1 min-h-0 overflow-hidden包裹OTable :framefalse。不是gap-6 p-6带内边距容器那是表单/详情视图用的。列表/表格页必须带三个工具栏功能缺一不可搜索 过滤器——搜索框与任何过滤控件OToggleGroup、下拉一起放在#toolbar槽并设:show-global-filterfalse。只有无其他过滤器的纯搜索列表才用内置show-global-filterv-model:global-filter。刷新——#toolbar-trailing槽中的OButton variantoutline sizeicon-sm icon-leftrefresh :loading接到 fetch 函数用 tooltip 携带r快捷键。列显示/隐藏切换——当:persist-columnstrue 稳定table-id 至少一个非操作列标记hideable: true三者同时成立时OTable自动注入该按钮位于#toolbar与#toolbar-trailing之间。三者缺一按钮就静默不出现。默认隐藏非必需列:column-visibility{ id: false }保留 name/status 可见默认隐藏时间戳/id/计数——用户可重新显示选择按table-id持久化。空态 一个OEmptyState放在#empty带preset:filteredprop搜索/过滤激活时自动切换为 No results found 清除过滤器。fetch 可能失败时也提供#error。行操作放在isAction列注册n///r快捷键。在导航中注册页面route rail 项 / Settings 子页 / flyout 子项并按环境/角色 gating——能渲染但没接进导航的页面是不可达的。完整方法见 references/navigation-menus.md。4.2 表格 →OTable用什么。所有表格数据都用OTableweb/src/lib/core/Table/OTable.vue——绝不手写table。怎么用。列是OTableColumnDef[]:columns:data复用共享宽度常量COL.*、TABLE_INDEX_COL_SIZE与预置单元格渲染器OTimeCell、OUserCell、ONumberCell、OCodeCell、ODataBarCell位于 web/src/lib/core/Table/cells而不是手格式化值。OTable默认客户端分页/排序/过滤——只有当后端对大到无法整体拉取的集合分页时才切换paginationserver/sortingserver不要给整表拉取完的列表接服务器模式。面向用户的列表设置persist-columns 稳定tableId。列表表格渲染为flush:framefalse本就是默认值绝不把它包在有边框/bg-*的盒子里——那会双重描边所以修多余的边框靠移除父级的而不是加 frame。完整 prop/slot/emit 列表与单元格渲染器见 references/core-controls-table.md。为什么。一个表格组件意味着排序、缩放、固定、空态、加载、暗黑模式行为统一——手写table会不一致地重答全部这些问题并重新实现你需要重新测试的分页。4.3 代码放哪项目分层列表/表格屏的数据流view (src/views) → [可选] composable (src/composables/use*) OR helper (utils/commons.ts) → service (src/services/domain.ts, via the http.ts wrapper) → Vuex store (shared/cacheable data) local refs (ephemeral render data)怎么落地。绝不在组件中直接调用http/axios。通过src/services中的领域 service 获取如dashboardService.list(...)。org_identifier取自store.state.selectedOrganization——不要通过 props 往下传。**Composablesuse***是可选薄包装注入 org 并包装 service 调用。当 2 组件共享同一 fetch/transform 时再加否则从 view 直接调 service 是这里公认的常态。状态放置真正共享/缓存的 org 数据streams、dashboards、folders…放进带命名空间的 Vuex 模块。每屏的表格行与列定义留在本地ref/computed——不要把一次性视图状态塞进 Vuex。保持 view 薄只做 fetch 接线。重型转换属于 service、composable 或utilshelper而不是模板 script 的内联代码。为什么。层边界让第二个屏幕能复用 fetch、让测试能 mock service、让 store 不被临时状态淤积。直接调 axios 的组件不接网络就无法复用或测试。4.4 相同的数字必须在所有地方渲染一致是什么。当你要加的数字/标签已出现在另一个屏幕上——同一个 API 字段、同一含义——复用那个屏幕的formatter 与 i18n key两者都不要重新推导。如果现有 formatter 是组件内的私有函数把它提升到 web/src/utils/formatters.ts 让两处都 import 它。为什么。两个调用点格式化同一字段总会漂移Home → Usage 打印2.9B而新的 Streams 磁贴从相同的/summarypayload 打印2,900,000,000罪魁祸首就是重复的formatEventCount。标签同理——page.summaryStreams/home.streams两个值相同的平行 key 会在某人编辑其中一个时立刻分叉。共享 key 才是让两个表面保持同步的东西。怎么落地。添加 formatter 前先 greputils/formatters.ts以及兄弟屏幕看是否已存在——formatEventCount、formatSizeFromMB、addCommasToNumber、formatLargeNumber、formatTimeWithSuffix。标签含义相同时直接复用对方屏幕的 i18n keyStreams 页的t(home.totalDataIngested)是正确的不是重复。新 key 只给真正的新标签。磁贴位于可比表面时也匹配 icon 与色调家族——但绝不为了匹配而把语义颜色绿/琥珀/红复制到非健康指标上这是唯一值得分歧的地方。4.5 在导航中注册新页面是什么。新页面要可达必须(1) 注册为route(2) 出现在恰好一个导航表面(3) 按环境与角色gating。三者都是数据驱动的——绝不手写router-link侧边栏项或定制菜单。完整方法文件路径、数据结构、gating 表达式、检查清单见 references/navigation-menus.md。怎么落地——按页面性质选表面页面是…表面编辑位置顶级产品区域普通左 rail 项web/src/layouts/MainLayout.vue 中的linksList若干相关数据目标之一rail 组下的hover-flyout 子项web/src/lib/core/Navbar/navGroups.ts 中的NAV_GROUPS管理/配置屏Settings 子页SectionRailweb/src/components/settings/index.vue 中的settingsItems访问控制屏IAM 子页SectionRailweb/src/views/IdentityAccessManagement.vue另外在拥有该领域的 composable 里加route懒加载component、meta.title、beforeEnter: routeGuard并用config.isEnterprise/config.isCloudweb/src/aws-exports.ts与store.state.zoConfig.*运行时标志rbac_enabled、actions_enabled、meta_org…构建gate。route 条件、导航项 gate与页面 SectionRail 的visible必须表达同一条规则——否则导航给出页面隐藏的链接OSS/RBAC 反向泄漏。确切的路由 composable、规范 gating 表达式与custom_hide_menus都在 references/navigation-menus.md。为什么。每个导航表面都从同一数据文件读取整个 IA 可在同一处判定并保持一致gating 规则防止 cloud/enterprise-only 页面泄漏进 OSS 构建或 RBAC 页面在无 RBAC 时出现。手写侧边栏链接绕过了 flyout 重塑、router.hasRoute死链过滤与 gate 镜像——然后漂移。4.6 表单dialog vs drawer vs 全页按交互重量匹配容器。这是代码库中已确立的拆分不是偏好交互容器典型尺寸确认 / 破坏性操作提示ConfirmDialogweb/src/components/ConfirmDialog.vueuseConfirmDialogweb/src/composables/useConfirmDialog.ts—短小原子表单——一到几个字段、单个决策重命名、克隆、添加流、创建链接ODialogsm/md小详情或预览弹窗ODialogsm/md中到大型表单或配置面板需要纵向空间或用户受益于看到页面上下文pipeline 节点配置、schema/regex/query 编辑器ODrawer侧rightmd/lg或:widthvw 覆盖详情 / 检视面板trace 详情、job 详情ODrawerlg/xl大型主多章节创建/编辑流程如 Add/Edit Alert用v-if切换的完整页内视图——不是 overlay—经验法则任何短小且天生模态的先拿ODialog。表单高/多字段或用户受益于侧边上下文时用ODrawer。当它是屏幕的主任务且有多章节时升级为全页。不要把又高又多章节的表单塞进 dialog会对着视口笨拙地滚动也不要用 drawer 做 yes/no 确认对这个决策太重。用内建能力不要手搓表单用persistent防止误关、formId支持 Enter 提交、用内建primary/secondary/neutralButtonLabel底部栏 API 而不是手写按钮行——同时仍遵守 canceloutline/ saveprimary标准。用ODialog/ODrawer承担这些职责。为什么。用户习得一种空间语法小请求是居中模态工作表面是侧边抽屉大任务有自己的页面。混用会让同一个应用像三个不同的应用。4.7 表单验证与绑定任何带验证的表单都用OForm Zod schema——绝不手写错误 refs、逐字段:rules或手动validate()。完整规则集与示例见 references/forms-validation.md构建表单前先读它。不可妥协项单一事实来源——OForm内每个控件都是OForm*只通过name寻址TanStack form 拥有值。没有v-model-to-ref没有平行formData用watch同步的镜像ref是这里排名第一的表单 bug——漂移、保存后required闪现。在submit中读验证后的value用form.setFieldValue(...)写字段。schema 就近放置——兄弟文件Form.schema.ts导出make…Schema(t)Zod 工厂、z.infer类型与类型化默认值绝不内联:default-values字面量。字段name匹配 schema key所以没有逐字段 rules。把每个控件转成OForm*变体可选的也转 →.optional()必填用requiredprop标记绝不用字面*。只有真正的非表单控件Monaco、query-editor保持裸件通过setFieldValue桥接一次。提交与 loading 是自动的——内联 Save 用typesubmitoverlay 底部栏用OForm idx overlay 的form-idx。删除任何useLoading/:loading绝不在无效时禁用 Save提交无效表单正是暴露错误的方式。用显式 key 构建 payload绝不{ ...value }它会泄漏.optional()辅助字段与字符串类型数字——OFormInput发出字符串所以用Number(...)/z.coerce.number()强转。基于表单状态的条件渲染——拥有OForm的组件用useOForm({ defaultValues, schema, onSubmit })创建它并传OForm :form内部的子组件用inject(FORM_CONTEXT_KEY)form.useStore。两者读同一个 form——绝不镜像它。字段数组用z.array 索引名rows[${i}].key和:keyindex——绝不用uuid稳定 id key 会在删除列表中间行时清空输入。交付一个删除非末行测试断言渲染出的输入。4.8 键盘快捷键快捷键是registry-driven——一个系统而不是临时keydown监听。完整指南见 references/keyboard-shortcuts.md。要点先注册再绑定再展示。在 web/src/lib/vue-shortcut-manager/shortcutRegistry.ts/lib/vue-shortcut-manager中声明一次快捷键id、key(s)/平台变体、i18ndescriptionKey。在setup()中用useShortcuts([{ id, handler }])绑定行为。用OShortcut id或ODropdownItem/OTooltip上的shortcut-idprop 展示键帽。绝不手写window.addEventListener(keydown, …)做应用快捷键也绝不硬编码⌘N文本到模板——registry 拥有 key、平台映射、作用域、cheatsheet 条目与展示。组件挂载期间作用域自动激活用焦点检查守卫裸字母 handler避免用户输入时触发。5. 验证与收尾CI 门禁与自检清单conventions 中的每条禁令都有配套的机器检查背书详见 SKILL.md 规则 3–5local/no-hardcoded-pxESLint定义于 web/eslint.config.js扫描src/**/*.{vue,ts,js,css}报告行:列并给出 rem 值与 Tailwind 步进无注解的 px 直接构建失败。lint:design:strict负责其余部分硬编码 hex、任意圆角/阴影、退役别名、原始调色板/ramp、模板或style块中的原始var()、无理由的style、字面字体栈加上lint:tokens、lint:token-purity、lint:stylesstylelint与format:checkprettier每个 PR 都跑。容忍度为零——design-debt-baseline.json已删除任一类别出现一次即失败。计数器扫描原始文本、注释也算——style块注释里的16px或#fff或逐字引用的禁用类都算技术债。守卫看不到的只在.vue/.ts行走、任意属性形式[background:…]绕过、var()fallback 隐藏读取需要人工 review 兜底。提交前对照 SKILL.md 的 Pre-flight checklist 自检头部是OPageHeader且 create CTA 纯文字、交互控件均有 O2 等价物、自包含元素已做成组件、表格用OTable且服务端模式只用于后端分页、图形走PanelSchemaRenderer、列表页用全高 flush 骨架并带齐三件套工具栏、空态是单个OEmptyState、页面已注册导航并 gating、数据经src/services获取、表单容器按重量匹配、OForm Zod 且字段只按name绑定、零未注解 px、间距复制兄弟、圆角只用rounded-default/rounded-surface/rounded-full、无style scoped/style、无字面颜色、文案全部走t()且 key 已加入 web/src/locales/languages/en-US.json、cd web npm run lint npm run type-check:app通过注意是type-check:app而非type-check——后者只覆盖 spec 文件不检查.vue。6. 结语约定的本质把本文的规则放在一起看它们共享同一哲学修复永远落在共享的东西上而不是调用点——缺失的 variant 去组件源码加缺失的 token 去注册表加缺失的组件去web/src/lib建缺失的间距去兄弟页面抄缺失的文本去 en-US.json 加。遵循 conventions.md 与它的兄弟参考文档page-recipes.md、creating-components.md、core-controls-table.md、navigation-menus.md、forms-validation.md、keyboard-shortcuts.md任何新屏幕——列表页、表单、对话框、抽屉、面板——都能无缝融入 OpenObserve 既有的产品表面。【免费下载链接】openobserveOpen source observability platform for logs, metrics, traces, RUM, Session replay, pipelines, SLO and LLM observability. A sophisticated, simple and highly performant alternative to Datadog, Splunk, and Elasticsearch with 140x lower storage costs and single binary deployment.项目地址: https://gitcode.com/GitHub_Trending/op/openobserve创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进