@manohub/kit 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRACT.md +683 -659
- package/README.md +7 -4
- package/package.json +1 -1
- package/skills/README.md +82 -78
- package/skills/install.mjs +1 -1
- package/skills/{kit → manohub-kit}/SKILL.md +85 -85
- package/skills/{kit → manohub-kit}/references/adoption.md +170 -170
- package/skills/{kit → manohub-kit}/references/contract-index.md +76 -76
- package/skills/{kit-dev → manohub-kit-dev}/SKILL.md +117 -117
- package/skills/{kit-migrate → manohub-kit-migrate}/SKILL.md +125 -125
- /package/skills/{kit-dev → manohub-kit-dev}/references/page-recipes.md +0 -0
- /package/skills/{kit-dev → manohub-kit-dev}/references/style-rules.md +0 -0
- /package/skills/{kit-migrate → manohub-kit-migrate}/references/migration-map.md +0 -0
- /package/skills/{kit-migrate → manohub-kit-migrate}/references/migration-playbook.md +0 -0
package/CONTRACT.md
CHANGED
|
@@ -1,659 +1,683 @@
|
|
|
1
|
-
# @manohub/kit CONTRACT —— 接入方必读
|
|
2
|
-
|
|
3
|
-
**本文件是接入方的权威规范**。与它冲突的其它说法(组件里的示例、包 README、旧技能、口头约定)一律以本文为准。
|
|
4
|
-
|
|
5
|
-
> **0.6.0 起本包不再提供组件。** 组件与命令式服务的 API 归 `@manohub/ui`;
|
|
6
|
-
> 全局令牌(值)归 `@manohub/theme`;图形归 `@manohub/icon`。
|
|
7
|
-
> 本文管的是**页面怎么搭**、**样式怎么写**,以及**去哪读**。
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 怎么用这份契约
|
|
12
|
-
|
|
13
|
-
### 五层,一层一个职责方
|
|
14
|
-
|
|
15
|
-
**遇到问题先定位层,再读那一节。** 层的划分依据不是「检查手段」,是**谁负责**:
|
|
16
|
-
|
|
17
|
-
| 层 | 职责方 | 管什么 | 能不能有例外 |
|
|
18
|
-
| --- | --- | --- | --- |
|
|
19
|
-
| **L0 入口与作用域** | `kit` | 样式链、容器锚、单入口 | **不能** —— 漏了下面四层一起静默失效 |
|
|
20
|
-
| **L1 值** | `theme` | 值从哪来、怎么用、怎么加 | 能(须登记,见 §11) |
|
|
21
|
-
| **L1.5 图标** | `icon` | 图形语汇 | 能(须登记) |
|
|
22
|
-
| **L2 结构** | `ui` | 页面骨架怎么搭 | 能(须登记) |
|
|
23
|
-
| **L3 件与词表** | `ui` | 用哪些件、传哪些 prop | 能(须登记) |
|
|
24
|
-
|
|
25
|
-
**收口顺序就是层序**:L0 → L1 → L1.5 → L2 → L3。但**存量应用的最优迁移路线是反的**——
|
|
26
|
-
先换骨架(L2)、再换件(L3)、最后收样式(L1),每步都能单独跑通。
|
|
27
|
-
|
|
28
|
-
### 每条条款都写成闭集
|
|
29
|
-
|
|
30
|
-
**判据是闭集的意思是**:白名单里找不到就是违规。**不要自我说服**「这个应该算布局」「这个应该算例外」——
|
|
31
|
-
找不到就是找不到。
|
|
32
|
-
|
|
33
|
-
本文刻意不写「等」「之类」「比如」这类开集措辞。你读到一条判据时,应该能直接回答「符合 / 不符合」。
|
|
34
|
-
|
|
35
|
-
### 本文不抄值、不抄件名、不抄成员、不抄词表
|
|
36
|
-
|
|
37
|
-
它们**会漂移**。曾经有过 `Page.Toolbar`、`Input.Group`、`Input.Chip` 三个不存在的成员被写进文档。
|
|
38
|
-
所以本文只写「去哪读」——见 §0。
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## §0 权威源表:写任何东西之前先读这里
|
|
43
|
-
|
|
44
|
-
| 你要查 | 去哪儿读(消费方可达路径) | 读完怎么用 |
|
|
45
|
-
| --- | --- | --- |
|
|
46
|
-
| 全局令牌的**值** | `node_modules/@manohub/theme/dist/<主题>/{index,colors,typography,spacing,radius,shadows}.css` | 把它们当你的色板与尺寸表。**要什么值去这里找**,别猜、别抄进本文 |
|
|
47
|
-
| 组件令牌的**值** | `node_modules/@manohub/ui/dist/styles/components/<件>.tokens.css` | 同上;组件专属尺寸与档位距在这 |
|
|
48
|
-
| **有哪些件** | `@manohub/ui` 的 `dist/index.d.ts` | 以**导出表**为准。README 会漂移 |
|
|
49
|
-
| 复合**成员** | 各件 `dist/components/<件>/index.d.ts` 的 `Object.assign` 处 | 成员只走点号 |
|
|
50
|
-
| prop 的**存在性与取值** | 该件 `dist/components/<件>/index.d.ts` | 传 prop 前打开确认 |
|
|
51
|
-
| **语义词表**(改外观的唯一正规通道) | 同上,`.d.ts` 里的联合类型 | 26 个维度,`tone` / `variant` / `shape` / `size` / `status` / `gap` / `align` / `justify` / `padding` … |
|
|
52
|
-
| **骨架怎么用**(归位 / 滚动 / 页头 / 操作位) | `@manohub/ui/dist/components/page/index.d.ts`、`dist/components/panel/index.d.ts` 的头注释 | §5 已把判据写全;这两份是更细的展开 |
|
|
53
|
-
| 图标**名清单** | `@manohub/icon` 的 `dist/glyphs.d.ts`(`IconNameList`) | `name` 必须在清单里搜得到 |
|
|
54
|
-
| 图标**字形与出处** | `node_modules/@manohub/icon/dist/glyphs.js`(源码形态:`src/glyphs.ts`) | 缺图标改这里 + 重 build |
|
|
55
|
-
| **类名与档位类** | `@manohub/ui/dist/styles/index.css` 及其 `components/` | 应用侧**不写** `.mh-*` |
|
|
56
|
-
| 本仓**类名命名空间** | 本应用自己的 `docs/kit-namespaces.md`(若还没有,建一个) | 类名形状必须匹配表内某一格 |
|
|
57
|
-
|
|
58
|
-
> **为什么给的是 `dist/` 而不是 `src/`**:`theme` / `ui` / `icon` 三个包的 `files` 都只发布
|
|
59
|
-
> `dist` 与 `README.md`。`src/` 与 `docs/` 在消费方的 `node_modules` 里**不存在**——写 `src/` 的读取指令等于没写。
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## §1 接入
|
|
64
|
-
|
|
65
|
-
### 1.1 装什么
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
pnpm add @manohub/kit @manohub/ui @manohub/theme @manohub/icon
|
|
69
|
-
pnpm add vue vue-router pinia vue-i18n @tanstack/vue-query # peer,由应用提供
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
四个包**并列安装**,模块级互不依赖:kit 提供入口编排与本文,ui 提供组件与命令式服务,
|
|
73
|
-
theme 提供令牌(值),icon 提供图形。**本包不发布任何样式**(见 §1.2),
|
|
74
|
-
所以 `kit` 的 `peerDependencies` 里没有另外三个。
|
|
75
|
-
|
|
76
|
-
四者之间**唯一的跨包约定**是容器上的属性锚 `data-manohub-ui`(见 §1.3)。
|
|
77
|
-
|
|
78
|
-
### 1.2 样式链:两行 + 应用自己一行(顺序即契约)
|
|
79
|
-
|
|
80
|
-
```css
|
|
81
|
-
@import "@manohub/theme/default.css"; /* ① 令牌(值)—— 换主题只换这一行 */
|
|
82
|
-
@import "@manohub/ui/styles.css"; /* ② 组件面(类 + 组件令牌基础值) */
|
|
83
|
-
@import "./app.css"; /* ③ 应用自身(只写布局,见 §3) */
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
- **顺序不可换**:先值(令牌)后面(组件面)。反过来的话组件面里的 `var(--ui-*)` 全是空值。
|
|
87
|
-
- 用非兜底主题时把 ① 换成对应入口(如 `@manohub/theme/farris.css`),
|
|
88
|
-
并在入口给 `createSubApp({ theme: 'farris' })`(写到容器的 `data-theme` 上)。
|
|
89
|
-
- **`reset`(元素级基线)自 0.7.0 起已内置在 ② 里**,不需要第三行:`@manohub/ui/styles.css` 的**首条** import
|
|
90
|
-
就是它(盒模型、按钮外观、列表序号、链接下划线、标题字阶一并归零)。作用域锚 `[data-manohub-ui]`
|
|
91
|
-
且特异性为 0,**应用侧写任何元素选择器都能盖过它**。(0.6.x 没有它,那两版的应用侧自补过一份 —— 见 §12.2。)
|
|
92
|
-
- **入口基线(`html` / `body` / `#app` 的高度链)归消费方,且是合法的**:本包的 reset **有意不写**这三个
|
|
93
|
-
选择器 —— micro-app 的 `scopecss` 不作用域化它们,包内写进去会泄漏到宿主全局。但「页面撑满」这条链
|
|
94
|
-
**必须有人给**(`Page` 的 `height: 100%` 依赖它),所以它由**应用的入口基线**承担:写在 ③ 里,
|
|
95
|
-
或写在入口 HTML 的 `<style>` 里。典型形态是「`html` / `body` / `#app` 三个 `height: 100%` + `margin: 0`」。
|
|
96
|
-
这是 L1-6「不得用裸元素选择器」的**唯一例外**(`html` / `body` / `#app` 三个名字),
|
|
97
|
-
用到的属性全在 L1-4 白名单内。**别把这条链写进组件 / 页面样式** —— 它只属于入口。
|
|
98
|
-
- **富文本(markdown)要自行复权**:reset 归零了 `ul` / `ol` 的序号与 `a` 的下划线,
|
|
99
|
-
而富文本排版没有合规归属(见 §10 的已知缺口)—— 消费方须为自己的富文本容器类补回
|
|
100
|
-
`list-style` / 链接样式 / 段落边距。
|
|
101
|
-
|
|
102
|
-
### 1.3 入口
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
// src/main.ts
|
|
106
|
-
import { createSubApp } from '@manohub/kit/entry'
|
|
107
|
-
|
|
108
|
-
export const { mount, unmount } = createSubApp({
|
|
109
|
-
rootComponent: Root,
|
|
110
|
-
routes,
|
|
111
|
-
i18n: { messages: { zh, en } },
|
|
112
|
-
})
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
工厂替你做完:`div.app-container` 包裹 + `data-manohub-ui` / `data-theme` 属性、pinia / vue-router /
|
|
116
|
-
vue-query 装配、宿主挂载协议、宿主语言同步、首帧路由重置。**不要自己手写 main.ts 样板**——
|
|
117
|
-
漏掉 `exposeToHost` 会让宿主无法重挂子应用。
|
|
118
|
-
|
|
119
|
-
容器上的名字分三类,**只有前两个是跨包约定**(改名会断掉对方):
|
|
120
|
-
|
|
121
|
-
| 名字 | 谁写 | 用途 | 跨包契约? |
|
|
122
|
-
| --- | --- | --- | --- |
|
|
123
|
-
| **`data-manohub-ui`** | 本工厂**无条件**写 | **唯一作用域锚**:主题令牌 + 组件令牌 + 服务层宿主解析 | ✅ **是** |
|
|
124
|
-
| `data-theme` | 只在传 `theme` 时写 | 皮肤开关(`'farris'` 等),作用于上面的锚 | ✅ 是 |
|
|
125
|
-
| `class="app-container"` | 本工厂写 | 本包**内部**命名(`scopecss` 作用域故事 + 调试自查) | ❌ 不是 |
|
|
126
|
-
|
|
127
|
-
**服务层浮层落回本应用容器靠 `data-manohub-ui`**(`toast` / `confirm` / `showLoading` 据此解析宿主),
|
|
128
|
-
**令牌的作用域也锚在它上面**。宿主自建容器(不走 `createSubApp`)时**必须**自己带上 `data-manohub-ui`,
|
|
129
|
-
否则本库组件连几何令牌都拿不到。
|
|
130
|
-
|
|
131
|
-
### 1.4 目录约定
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
src/
|
|
135
|
-
├── main.ts 入口(createSubApp)
|
|
136
|
-
├── router/ 路由表
|
|
137
|
-
├── views/<页>/<页>.tsx 路由级页面(契约 L2-1 认这个目录段)
|
|
138
|
-
│ └── components/ 本页的可复用件(弹窗 / 抽屉 / 表单块 / 子表格)
|
|
139
|
-
└── app.css 应用自身样式(只写布局)
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**路由级页面**与**页面内可复用件**的分界是**目录段**:`views/<页>/components/` 下的文件不受
|
|
143
|
-
「必须用 `Page` 骨架」约束(它们由 `Panel` / `Form` / `Dialog` 承载)。
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## §2 L0 入口与作用域(`kit` 的职责,**不可豁免**)
|
|
148
|
-
|
|
149
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
150
|
-
| --- | --- | --- |
|
|
151
|
-
| **L0-1 样式链** | CSS 入口前两条 `@import` **逐字**为 `@manohub/theme/<主题>.css`、`@manohub/ui/styles.css`,且都在自有样式之前;全仓各出现一次 | 值在前、面在后;反了就全是空值 |
|
|
152
|
-
| **L0-2 容器与锚** | 应用根恰好一次 `createSubApp(...)`;**不得自己写 `data-manohub-ui`**;**不得依赖 `.app-container`** | 锚是令牌与宿主解析的共同前提;`.app-container` 是本包内部名,不是跨包约定 |
|
|
153
|
-
| **L0-3 单入口与 i18n** | 全仓 `createSubApp(` 恰好 1 次、`createI18n(` **0 次**(i18n 实例由本包创建) | 双实例会让文案全丢且不报错(见 §9) |
|
|
154
|
-
|
|
155
|
-
**为什么 L0 不可豁免**:锚是主题令牌、组件令牌、服务层宿主解析三者的共同前提。
|
|
156
|
-
漏了它,下面四层会**一起静默失效**——组件有框有距,但颜色是浏览器默认灰蓝。
|
|
157
|
-
|
|
158
|
-
**正误对照**
|
|
159
|
-
|
|
160
|
-
```tsx
|
|
161
|
-
// ✗ 自己写锚 / 依赖内部类名
|
|
162
|
-
document.querySelector('.app-container')!.setAttribute('data-manohub-ui', '')
|
|
163
|
-
// ✓ 交给入口层;需要自己挂浮层宿主时用服务层公开 API
|
|
164
|
-
configureHost(hostEl)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
自检见 §7 第 1–3 问。
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## §3 L1 值(`theme` 的职责)
|
|
172
|
-
|
|
173
|
-
### 条款
|
|
174
|
-
|
|
175
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
176
|
-
| --- | --- | --- |
|
|
177
|
-
| **L1-1 应用侧不持有值** | 应用侧的 `--*` 自定义属性**一处都不该有**。只有两种合法写法:① **重设已存在的令牌**(局部换肤)② 不写 | 值的唯一来源是 theme。局部换肤是合法且推荐的(见下) |
|
|
178
|
-
| **L1-2 引用必须存在** | 每个 `var(--xxx)` 的 `--xxx` 必须能在 theme 六片或 `<件>.tokens.css` 里**搜到**。**新增令牌名一律违规** | 写错名字不会报错,只会静默回退成默认值 |
|
|
179
|
-
| **L1-3 尺寸取令牌** | 尺寸取 `--ui-space-*` / `--ui-radius-*` / `--ui-control-height-*` / `--ui-row-height` / `--ui-grid-column-min`;**几何是设计基准、不跟根字号**,故禁 `rem`;内距给 **px 数字**(与 `Padding` 同口径) | 设计基准不随宿主根字号分叉 |
|
|
180
|
-
| **L1-4 只写布局属性** | `<style>` 与 `style={{ }}` 里的属性名必须在**下面的白名单**内 | theme 是视觉的唯一来源 |
|
|
181
|
-
| **L1-5 类名形状** | 类名一律 `<本仓命名空间>-<kebab-case>`。落点 A:CSS 选择器**首段**;落点 B:`class=` 的**每个 token** | 两个应用装进同一页面时不能撞名 |
|
|
182
|
-
| **L1-6 不碰库的类** | 选择器里**不得出现 `.mh-`**;不得用 `!important`;不得用裸元素选择器与 `*`。**唯一例外:入口基线**(`html` / `body` / `#app` 三个选择器及其高度链,见 §1.2)| `.mh-*` 与档位类归 ui;覆写升版即静默失效 |
|
|
183
|
-
| **L1-7 不引 Tailwind** | 全仓不得出现 `tailwindcss` / `@tailwindcss/` / `@theme` | theme 只认自己的 `@import './…css'` |
|
|
184
|
-
|
|
185
|
-
### L1-4 的属性白名单(闭集,逐字照用)
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
盒模型与定位:display / position / inset / top / right / bottom / left / z-index / box-sizing /
|
|
189
|
-
width / height / min-width / min-height / max-width / max-height /
|
|
190
|
-
margin(-top|right|bottom|left) / padding(-top|right|bottom|left) /
|
|
191
|
-
overflow / overflow-x / overflow-y / gap / row-gap / column-gap / aspect-ratio / resize
|
|
192
|
-
弹性与栅格:flex / flex-direction / flex-wrap / flex-grow / flex-shrink / flex-basis /
|
|
193
|
-
align-items / align-self / align-content / justify-content / justify-items / justify-self /
|
|
194
|
-
order / place-items / place-content / grid-template-columns / grid-template-rows /
|
|
195
|
-
grid-auto-flow / grid-auto-columns / grid-auto-rows / grid-column / grid-row
|
|
196
|
-
文字流(不含颜色与字号):white-space / text-overflow / overflow-wrap / word-break / text-align /
|
|
197
|
-
vertical-align / line-clamp / -webkit-line-clamp / hyphens / direction / writing-mode
|
|
198
|
-
交互与动效:cursor / user-select / pointer-events / visibility /
|
|
199
|
-
transition(-property|-duration|-delay|-timing-function) /
|
|
200
|
-
animation(-name|-duration|-delay|-iteration-count|-direction|-fill-mode) /
|
|
201
|
-
transform / transform-origin / will-change / scroll-behavior / overscroll-behavior /
|
|
202
|
-
touch-action / scrollbar-width
|
|
203
|
-
其他:content / list-style(-type|-position) / isolation / contain / object-fit / object-position / clip-path
|
|
204
|
-
|
|
205
|
-
唯一重置例外:border: 0 / border: none(去框)。写宽度或颜色不允许。
|
|
206
|
-
判定:属性名去掉 -webkit- / -moz- 前缀 → 上面找不到就是违规,不要自我说服。
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
**注意白名单里没有的东西**:`color` / `background*` / `border*`(除 `border: 0`)/ `border-radius` /
|
|
210
|
-
`box-shadow` / `font-*` / `letter-spacing` / `text-transform` / `text-decoration*` / `outline*` /
|
|
211
|
-
`caret-color` / `accent-color` / `filter` / `opacity` / `mix-blend-mode` / `fill` / `stroke`。
|
|
212
|
-
**这些一律违规**,改外观走 §6 的语义词表或 §3 的局部换肤。
|
|
213
|
-
|
|
214
|
-
### 局部换肤:唯一允许写 `--*` 的情形
|
|
215
|
-
|
|
216
|
-
在**更深的容器**上重设**已存在**的令牌,是合法且推荐的改外观手段:
|
|
217
|
-
|
|
218
|
-
```css
|
|
219
|
-
.role-tree-pane { --ui-primary: #7C3AED; }
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
区别在**「新名字」还是「已有名字」**:`--ui-primary` 在 theme 里有定义 → 合法(重设):
|
|
223
|
-
`--ui-my-brand` 在 theme 里搜不到 → 违规(发明)。想加新名字去 `@manohub/theme` 提。
|
|
224
|
-
|
|
225
|
-
### 正误对照
|
|
226
|
-
|
|
227
|
-
```css
|
|
228
|
-
/* ✗ 视觉写进应用 CSS */
|
|
229
|
-
.vm-tag { color: var(--ui-error); border-radius: 4px; }
|
|
230
|
-
/* ✗ 令牌名拼错(不报错,静默回退默认值) */
|
|
231
|
-
.vm-box { background: var(--ui-bg-page); } /* 真名是 --ui-base-bg */
|
|
232
|
-
/* ✓ 外观走语义 prop:<Tag tone="error">…</Tag> */
|
|
233
|
-
/* ✓ 局部换肤(重设已有令牌) */
|
|
234
|
-
.vm-tree-pane { --ui-primary: #7C3AED; }
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
自检见 §7 第 4–8 问。
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
## §4 L1.5 图标(`icon` 的职责)
|
|
242
|
-
|
|
243
|
-
这一层**不是值**(不自持任何 `--*`),**也不是面**(零 CSS)。它是**值到面之间的图形语汇**:
|
|
244
|
-
颜色靠继承(`currentColor`),尺寸由 L2 / L3 的面层给。
|
|
245
|
-
|
|
246
|
-
### 条款
|
|
247
|
-
|
|
248
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
249
|
-
| --- | --- | --- |
|
|
250
|
-
| **L1.5-1 唯一来源** | 需要图形处一律 `<Icon name="…" />`(非 Vue 场景用 `renderIconSvg()`)。**不得**自绘(`<svg` / `<path` / `<circle`);**不得**用文字符号(`✓` `⌄` `×` `▶` `←`)冒充 | 一套线重、一个视觉族;文字符号在不同字体下高矮不一 |
|
|
251
|
-
| **L1.5-2 尺寸在面层给** | 图标槽**只写 `width` / `height`** 两个属性,值取 `var(--ui-font-icon)`(要档距用 `calc()`);件内令牌写成它的**别名**。**不传 `size`** | `size` 默认 16,不写就静默落到 16;写死了也不跟主题缩放。**消费侧没有例外** |
|
|
252
|
-
| **L1.5-3 只继承色、不承载色** | 不给图标写任何颜色(`color` / `stroke` / `fill` / `stroke-width`) | 图标随文案色;theme 里**有意没有 icons 片**,不要想着去补一片 |
|
|
253
|
-
| **L1.5-4 方位在字形里** | 方位一律 `chevron-*`(四向齐备);**不得**为「翻向」写 `transform: rotate()`。面层出现 `rotate(` 的唯一合法情形是 `loading` 的转圈动效;`back` 只作「返回」语义 | 旋转过的字形在视觉上不是同一个族 |
|
|
254
|
-
| **L1.5-5 语义裸符号,容器归组件** | 状态 / 语义图标取**不带容器**的裸字形(`info` / `check` / `alert` / `close`);色底那一圈由**组件**给 | 字形再自带一圈会叠成双圈 |
|
|
255
|
-
| **L1.5-6 名字取自清单,缺图标补包** | `name` 必须能在 `IconNameList` 里搜到;缺图标 → 在 `@manohub/icon` 加一条并重 build,**不在应用仓画路,也不在 ui 画路** | 就地画一个 = 第二次分叉 |
|
|
256
|
-
| **L1.5-7 入口分栈** | Vue 应用只认主入口 `@manohub/icon`;`@manohub/icon/glyphs` **只在没有 Vue 组件可用处**(静态页 / 模板串 / 非 Vue 栈) | 两个入口的产物类型不同 |
|
|
257
|
-
|
|
258
|
-
### 正误对照
|
|
259
|
-
|
|
260
|
-
```tsx
|
|
261
|
-
// ✗ 手绘 / 文字符号冒充 / 传 size / 写颜色 / 靠旋转转方位
|
|
262
|
-
<svg viewBox="0 0 24 24"><path d="…" /></svg>
|
|
263
|
-
<span class="vm-arrow">⌄</span>
|
|
264
|
-
<Icon name="chevron-down" size={14} class="text-primary" />
|
|
265
|
-
<span class="vm-rot90"><Icon name="back" /></span>
|
|
266
|
-
// ✓ 图形来自包、尺寸在面层、颜色继承、方位是字形
|
|
267
|
-
<Icon name="chevron-down" />
|
|
268
|
-
.vm-select-caret { width: var(--ui-font-icon); height: var(--ui-font-icon); }
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
自检见 §7 第 9–12 问。
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## §5 L2 结构(`ui` 的面 → **骨架怎么用**)
|
|
276
|
-
|
|
277
|
-
这一层管**骨架怎么用**,不管件内部(那是 L3)。**20 条,按五组。**
|
|
278
|
-
|
|
279
|
-
### 组 1 · 承载:谁搭骨架
|
|
280
|
-
|
|
281
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
282
|
-
| --- | --- | --- |
|
|
283
|
-
| **L2-1 页面骨架唯一** | 路由页(`views` \| `pages` 下、**`components/` 子树除外**)**恰好一个** `<Page`;`Page` 成员只用 `Filter` / `Header` / `Body` / `Footer` / `Split` 这 5 个 | 每页只有一个页头规格与一个页面级滚动归属 |
|
|
284
|
-
| **L2-2 页面级 vs 区域级** | 页面级只有 `Page` 一个;**区域级一律 `Panel`**(可多个,三件 `Header` / `Body` / `Footer`) | 两级职责不同:`Page` 管页面级滚动与页脚固定,`Panel` 只管区域内 |
|
|
285
|
-
| **L2-3 骨架不得自绘** | 页头只有 `Page.Header`、区域头只有 `Panel.Header`、表格框只有 `Table` 的 `framed` 档——**三者都没有第二个承载者**。模板里不写冒充的 `<div>`,CSS 里不出现页头 / 面板头 / 表格框类名 | 页头条高、内距、图标、右侧位、分隔线都是骨架的契约 |
|
|
286
|
-
|
|
287
|
-
### 组 2 · 归位:成员怎么被认出来
|
|
288
|
-
|
|
289
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
290
|
-
| --- | --- | --- |
|
|
291
|
-
| **L2-4 成员归位** | 成员**不按书写顺序渲染**,一律归到固定位置:`Filter → Header → Body → Footer`(`Panel` 同款);自由内容一律进主体区,**不会被丢** | 「读代码的顺序」不必等于「视觉顺序」 |
|
|
292
|
-
| **L2-5 成员必须是直接子节点** | 归位按**组件类型**认领:成员只要**不是 `Page` 的直接子节点**(被任何元素或 `<template>` 包住)就会编译成 Fragment 而**认不出**——它会被当自由内容挪进主体区。`v-if` **直接写在成员标签上**没问题 | 这是归位的机制前提,也是最隐蔽的坑 |
|
|
293
|
-
| **L2-6 缺省 Body** | 没写显式 `Page.Body` 时**自动包一层**(缺省 `mode="plain"`)→ 双栏可以只写 `Page.Split`;`Panel` 同法:其余子节点自动进 `Body` | 不写不等于没结构 |
|
|
294
|
-
|
|
295
|
-
### 组 3 · 页头:`Page.Header` 怎么用
|
|
296
|
-
|
|
297
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
298
|
-
| --- | --- | --- |
|
|
299
|
-
| **L2-7 页头两模式不可混** | 传 `title` → 内置布局(`subTitle` 在下一行、`icon` 在左、`extra` 在右);**不传 `title` → 纯 slot 直出(整行替换)**,此时 `subTitle` 不生效、**`extra` 不渲染**。**想用右侧位就必须传 `title`** | 不传 title 却传 extra,右侧位会**静默消失**,最容易被当成「样式坏了」 |
|
|
300
|
-
| **L2-8 缺省图标** | `icon` 不传 → 渲染设计稿默认图标(26×26 圆角方块,`currentColor`)。**不要为了「去掉图标」而传空值**;换图标传 `IconNameList` 里的 `<Icon>` | 「不传」是「用默认」,不是「不要」 |
|
|
301
|
-
| **L2-9 `extra` 是唯一自由位** | 页头右侧**没有独立成员件**;**区域级筛选 / 操作不得因「页头能放」就提到页面级**;页面级页签用主体里的 `TabBar` / `Tabs` | 提到页面级会让「这块区域」失去自己的筛选位 |
|
|
302
|
-
|
|
303
|
-
### 组 4 · 区域:筛选 / 操作 / 滚动 / 分页
|
|
304
|
-
|
|
305
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
306
|
-
| --- | --- | --- |
|
|
307
|
-
| **L2-10 两位分工不可互换** | `toolbar` = **本区域筛选**(≤3 个单值输入控件:`Input` / `Search` / `Select` / `Textarea`);`actions` = **本区域操作**(新建 / 刷新)。判据**有顺序**:① 先问「**过滤谁的数据**」决定放哪个区域的 `toolbar` ② 再问「**几个字段**」,>3 上提页面级 `Page.Filter` ③ 同一字段**不得摆两处** | 放错位不是样式问题,是「这块区域的筛选没了」 |
|
|
308
|
-
| **L2-11 区域头四种形态** | ① 有 `title` → 内建头;② **只给 `toolbar` / `actions` 不给 `title` 合法**(标题在容器外层,如弹窗内区域),那一行照渲染;③ 三样都不给 → **不渲染**(不凭空多一条空行);④ 完全自定义头走 `Panel.Header` 不传 `title` 或 `header` 插槽,**须登记**(§11) | 四种都是合法形态,但要**知道自己在哪一种** |
|
|
309
|
-
| **L2-12 两种面板写法可混用** | 简写 `<Panel title toolbar actions>` = 内建 `Header` + 包一层 `Body`;显式 `Panel.Header` **优先**于简写 | 两种写法等价,混用不会双头 |
|
|
310
|
-
| **L2-13 两级滚动各归各的** | 页面级由 `Page.Body.mode` 管(`scroll` 自身滚 / `plain` 不滚,**缺省 `plain`**,**全页唯一**);区域级由 `Panel.Body` 管(头尾固定,**只有 Body 滚**);`Split` **自身不滚**,栏内滚动由栏内 `Panel` 承担 | 混了的表现是「标题被滚走、双滚动条」 |
|
|
311
|
-
| **L2-14 表格不双滚** | 表格的高度与滚动由**承载它的内容件**承担,**不靠 `Page.Body.mode` 表达**。**反例**:别在 `Body` 里给表格族外套一层 `height:auto` 的 `<div>`,否则 Body 整块滚、**表头跟着走** | 表格自己填满容器、网格内部滚,才不双滚 |
|
|
312
|
-
| **L2-15 分页跟承载表格的容器** | 表格在 `Panel` 内 → 分页 `Panel.Footer`;表格直接挂 `Page.Body` → 分页 `Page.Footer`。判据看**承载表格的容器在哪** | 分页跑远了就与它控制的列表脱节 |
|
|
313
|
-
|
|
314
|
-
### 组 5 · 版式:两栏、摆块、高度、三态
|
|
315
|
-
|
|
316
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
317
|
-
| --- | --- | --- |
|
|
318
|
-
| **L2-16 两栏用 Split** | 页面级两栏(各自滚动)用 `Page.Split`(`sidebar` / `rightSidebar` 互为镜像,可同时存在成三栏);**两栏之间竖直 1px 线由 `Split` 提供 → 业务侧不得给栏的子元素加 `border-right` / `border-left`**;`sidebar.width` 是 `content-box` **内容宽** | 自己画线会和 Split 的线叠成两条;收起态那条线要归零 |
|
|
319
|
-
| **L2-17 摆块用 Layout** | 块与块怎么摆 → `Layout.Row` / `Layout.Column`;**表单并排字段用 `Form columns={2}`**,不用 `Layout` 拼 | `Layout` 管块间距,`Form` 管 label 列与行距 |
|
|
320
|
-
| **L2-18 高度由容器给** | `Page` 需要所在容器给出**确定高度**;高度取 `100%` 而非 `100vh`——**禁 `h-screen` / `100vh`** | 子应用被注入宿主容器,`100vh` 取的是 window 视口,宿主有顶栏就会溢出 |
|
|
321
|
-
| **L2-19 三态分工** | 区域 / 页面三态 → `QueryState`(它是容器:插图 + 操作 + 占位);内容占位 → `Skeleton`;忙等 → `Loading`(遮罩,**拦住交互**是它的职责);`Table` **不内建三态**(空态走 `emptyText` / `#empty`,错误态用 `QueryState` 包住) | 三个件解决三个不同的问题 |
|
|
322
|
-
| **L2-20 骨架不加框** | `Panel` 只是「区域 + 三件」,**描边 / 圆角 / 底色一概不加**;需要框用 `Card` | 无框是 `Panel` 的契约;加框会让「区域」与「卡片」两种语义混掉 |
|
|
323
|
-
|
|
324
|
-
### 正误对照
|
|
325
|
-
|
|
326
|
-
```tsx
|
|
327
|
-
// ✗ 自绘页头 / 用 Layout 拼表单
|
|
328
|
-
<div class="vm-page-header">…</div>
|
|
329
|
-
<Layout.Row><Input a /><Input b /></Layout.Row>
|
|
330
|
-
// ✓
|
|
331
|
-
<Page>
|
|
332
|
-
<Page.Header title={t('page.title')} :extra="新建按钮" />
|
|
333
|
-
<Page.Body mode="plain">
|
|
334
|
-
<Panel title="技能列表">…</Panel>
|
|
335
|
-
</Page.Body>
|
|
336
|
-
</Page>
|
|
337
|
-
<Form columns={2}>…</Form>
|
|
338
|
-
|
|
339
|
-
// ✗ 用 <template v-if> 包成员 → 归位认不出,该成员被当自由内容挪进主体区
|
|
340
|
-
<template v-if="canEdit"><Page.Footer>…</Page.Footer></template>
|
|
341
|
-
// ✓ v-if 直接写在成员标签上
|
|
342
|
-
<Page.Footer v-if="canEdit">…</Page.Footer>
|
|
343
|
-
|
|
344
|
-
// ✗ 不传 title 却想用右侧位 → extra 静默不渲染
|
|
345
|
-
<Page.Header :extra="搜索框" />
|
|
346
|
-
// ✓ 要用右侧位就传 title
|
|
347
|
-
<Page.Header title="技能分类" :extra="搜索框" />
|
|
348
|
-
// ✓ 要整行自定义就不传 title,右侧位自己摆
|
|
349
|
-
<Page.Header><div class="my-head">…</div></Page.Header>
|
|
350
|
-
|
|
351
|
-
// ✗ 把区域级控件提到页头 / 把筛选塞进 actions
|
|
352
|
-
<Page.Header title="列表" :extra="新建按钮" /> /* 这是区域级的「新建」 */
|
|
353
|
-
<Panel title="列表" :actions="筛选输入框" />
|
|
354
|
-
// ✓ 各归各的位
|
|
355
|
-
<Page.Header title="列表" :extra="页面级控件" />
|
|
356
|
-
<Panel title="列表" :toolbar="搜索框" :actions="新建按钮" />
|
|
357
|
-
|
|
358
|
-
// ✗ 在 Body 里给表格族外套一层 height:auto 的 div → Body 整块滚、表头跟着走
|
|
359
|
-
<Panel.Body><div style={{ height: 'auto' }}><Table framed … /></div></Panel.Body>
|
|
360
|
-
// ✓ 表格直接进 Body
|
|
361
|
-
<Panel.Body><Table framed … /></Panel.Body>
|
|
362
|
-
|
|
363
|
-
// ✗ 两栏自己画分隔线 / 高度写 100vh
|
|
364
|
-
<aside style={{ borderRight: '1px solid var(--ui-line)' }}>…</aside>
|
|
365
|
-
<div style={{ height: '100vh' }}>…</div>
|
|
366
|
-
// ✓ 分隔线与高度都归骨架
|
|
367
|
-
<Page.Split :sidebar="{ width: 208, content: () => <Panel title="业务域" /> }">…</Page.Split>
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### 三种页面模板(起手式)
|
|
371
|
-
|
|
372
|
-
| 模板 | 骨架 | 典型 |
|
|
373
|
-
| --- | --- | --- |
|
|
374
|
-
| A 列表页 | `Page.Header` + `Page.Body mode="plain"` + `Page.Footer`,区域内用 `Panel` 包 `Table` | 技能列表、值映射列表 |
|
|
375
|
-
| B 双栏页 | `Page.Body` 内放 `Page.Split`,侧栏用 `Panel` 包 `Nav` | 组织架构 + 明细 |
|
|
376
|
-
| C 详情 / 向导页 | `Page.Header` + `Page.Body mode="scroll"`,内容用 `Form` / `Panel` 分段 | 详情、向导步骤 |
|
|
377
|
-
|
|
378
|
-
自检见 §7 第 13–20 问。
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
|
-
## §6 L3 件与词表(`ui` 的面 → 细节)
|
|
383
|
-
|
|
384
|
-
### 条款
|
|
385
|
-
|
|
386
|
-
| 条款 | 要什么(闭集) | 为什么 |
|
|
387
|
-
| --- | --- | --- |
|
|
388
|
-
| **L3-1 件白名单** | 只用导出表里的件;复合成员**只走点号**;**先查 `.d.ts` 再写** | README 与文档会漂移(`Page.Toolbar` / `Input.Group` / `Input.Chip` 都曾出现在文档里,实际不存在) |
|
|
389
|
-
| **L3-2 外观只走语义 prop** | 改外观**只能**用件自己的 prop(语义词表共 **26 个维度**,逐个列在 §0 指的那份 `.d.ts` 里)或**换件**;**不得写 CSS 改外观** | 「不写 CSS 也能改外观」是这套库的全部答案 |
|
|
390
|
-
| **L3-3 外观来源白名单** | **外观**只能来自这几处:`@manohub/ui`(子路径只认 `styles.css`)· `@manohub/icon`(或 `/glyphs`)· `@manohub/theme`(只认 `default.css` / `farris.css`)· `@manohub/kit/entry`(编排,零外观)。**不得从别处取外观**:不引第三方组件库 / CSS 框架 / 图标库,不直连底层组件库,不自绘(含手写 `<svg>` 与文字符号)。**不在本条款管辖内的**(它们不产生外观):① 框架与运行时(`vue` / `vue-router` / `pinia` / `@tanstack/vue-query` / `vue-i18n` / 微前端运行时);② 本仓**业务域自有包**(`@manohub/*` 里不在上列者,如 `@manohub/api-client`);③ **不产生外观的行为库**(拖拽 / 虚拟滚动 / 图表计算一类,只给行为与数据结构)。判据是「**它是不是外观来源**」,不是「它是不是依赖」 | 白名单管的是外观的**来源**。写成「import 清单」会把业务包与行为库一起圈进来 ⇒ 每个已迁移应用都命中一条「不可登记」的违规,而那条违规与「不引第三方组件库」的立法本意毫无关系 |
|
|
391
|
-
| **L3-4 交互不走原生控件** | 按钮 / 输入 / 下拉 / 文本域一律用组件;`<button>` `<input>` `<select>` `<textarea>` **带 `class` 承载外观**违规;**无 `href` 的 `<a class>`** 违规(「按钮的伪装」) | 原生控件拿不到令牌、键盘与语义也不对 |
|
|
392
|
-
| **L3-5 浮层归组件与服务层** | 模态 → `Dialog`;贴边 → `Drawer`;轻提示 → `toast()`;确认 / 告知 → `confirm()` / `alert()`;忙等 → `showLoading()`。**不自己写 `position: fixed` + `z-index` 的遮罩** | 自建遮罩拿不到令牌,也逃不过微前端的 `scopecss` |
|
|
393
|
-
|
|
394
|
-
**L3-6 服务层宿主落回本应用容器**:解析顺序是 `configureHost(el)` → `[data-manohub-ui]` → `document.body`。
|
|
395
|
-
微前端下**必须落回自己容器**,否则「样式全丢」(浮层飘到宿主 `body` 上,拿不到令牌)。
|
|
396
|
-
`showLoading()` 是**引用计数**的,`finally` 里关自己那一次。
|
|
397
|
-
|
|
398
|
-
### 件怎么用:高频口径
|
|
399
|
-
|
|
400
|
-
- **表单**:用 `Form` + `Form.Item`。**表头只有一个**(`Form.Header`,归位到最前)。要分组就用
|
|
401
|
-
`Panel` 分段或拆成多个区域——没有「表单内多分组小节」这一件。并排字段用 `Form columns={2}`。
|
|
402
|
-
只读详情同样走 `Form` + `Form.Item` 的 `text` 行,不要另建一套描述列表。
|
|
403
|
-
- **筛选**:`Filter` + `Filter.Item`,条件字段的控件自己放。值进出已经归一(空串 = 不过滤),
|
|
404
|
-
**不要**再自己拼「全部」选项。分页器 `Pagination.modelValue` 是 **1 基**页码。
|
|
405
|
-
- **区域容器**:`Panel`(无框、三段、管区域内滚动)。**不要**用 `Card` 当区域容器——
|
|
406
|
-
`Card` 是**内容卡片**(可选、可点、可禁用),给网格项与信息块用。
|
|
407
|
-
- **弹窗与抽屉**:`Dialog`(原生 `<dialog>`:遮罩 / Esc / 焦点陷阱全归浏览器)/ `Drawer`(贴边,
|
|
408
|
-
正文是唯一可滚的一段)。两者的关闭位、Esc、点遮罩**都只回调**,可见性归调用方——
|
|
409
|
-
不要指望组件自己消失。
|
|
410
|
-
- **导航与树**:侧栏导航用 `Panel title=…` 包 `Nav`(**不要**把 `Nav` 直接塞进 `Page.Split` 的侧栏,
|
|
411
|
-
区域头就没地方给了);`Nav` 的行渲染与受控展开走 `Tree`。展开态由 `expandedKeys` 受控持有,
|
|
412
|
-
**不要靠「重挂组件」保展开态**。
|
|
413
|
-
- **分隔线**:要一条线用 `Divider`。`Panel` / `Card` / `Table` 的框线是它们自己的边界,不要再叠一条。
|
|
414
|
-
- **`Layout`**:`Layout.Row` 排**行**(行与行上下相邻)、`Layout.Column` 排**列**(列与列左右相邻)。
|
|
415
|
-
**方向与 CSS 属性名相反是刻意的**(判据是「读表格」)。间距只开放 `none/sm/md/lg/xl` 档,
|
|
416
|
-
不开放任意 px——间距一旦能随手写,同一页面就会出现 6/7/9/10/14 这些「看着差不多」的值。
|
|
417
|
-
|
|
418
|
-
### 正误对照
|
|
419
|
-
|
|
420
|
-
```tsx
|
|
421
|
-
// ✗ 从别处取外观 / 用不存在的成员 / 传底层风格的 prop
|
|
422
|
-
import { Table } from '@farris/ui-vue'
|
|
423
|
-
import { Button } from '@manohub/ui/dist/components/button'
|
|
424
|
-
<Page.Toolbar /> <Input.Group />
|
|
425
|
-
<Table valueField="id" :rowOption="{ … }" />
|
|
426
|
-
// ✓
|
|
427
|
-
import { Page, Panel, Table, Button } from '@manohub/ui'
|
|
428
|
-
import { repositoriesApi } from '@manohub/api-client' // 业务包:不产生外观,L3-3 不管
|
|
429
|
-
import Sortable from 'sortablejs' // 行为库:不产生外观,L3-3 不管
|
|
430
|
-
<Page.Header :extra="<Search … />" />
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
自检见 §7 第 21–24 问。
|
|
434
|
-
|
|
435
|
-
---
|
|
436
|
-
|
|
437
|
-
## §7 自检清单
|
|
438
|
-
|
|
439
|
-
**写完后逐条回答「符合 / 不符合 + 位置」。** 不要跳——跳过的那些正是会出问题的地方。
|
|
440
|
-
|
|
441
|
-
**L0**
|
|
442
|
-
|
|
443
|
-
1. 我的样式链前两条 `@import` 逐字对吗?顺序对吗(theme 在前)?**入口基线**(`html` / `body` / `#app` 的高度链)给了吗(在 ③ 里或入口 HTML 里)?
|
|
444
|
-
2. 有没有自己写 `data-manohub-ui`?有没有依赖 `.app-container`?
|
|
445
|
-
3. `createSubApp` 还是只有一处?`createI18n` 是 0 次吗?
|
|
446
|
-
|
|
447
|
-
**L1**
|
|
448
|
-
|
|
449
|
-
4. 我写的每个 CSS 属性,都在 §3 的白名单里吗?把不在的列出来。
|
|
450
|
-
5. 我写的每个 `var(--xxx)`,在 theme 六片或 `<件>.tokens.css` 里搜得到吗?
|
|
451
|
-
6. 我有没有**新增** `--` 开头的名字(而不是重设已有令牌的值)?
|
|
452
|
-
7. 类名都以本仓命名空间开头吗?`class=` 里每个 token 都过了吗?有没有 `.mh-` / `!important` / `rem` / 裸元素选择器 / `tailwind`?(**入口基线的 `html` / `body` / `#app` 除外** —— 那是唯一例外)
|
|
453
|
-
8. 尺寸值都取令牌了吗?
|
|
454
|
-
|
|
455
|
-
**L1.5**
|
|
456
|
-
|
|
457
|
-
9. 我这次用到的每个图形都是 `<Icon name="…">` 吗?有没有手绘 `<svg` / 用 `✓ ⌄ ×` 一类文字符号冒充?
|
|
458
|
-
10. 我有没有给 `<Icon>` 传 `size`?尺寸是在 CSS 里写 `var(--ui-font-icon)` 吗?
|
|
459
|
-
11. 我有没有给图标写颜色(`color` / `stroke` / `fill` / `stroke-width`)?面层出现 `rotate(` 了吗(只允许 `loading` 的转圈)?
|
|
460
|
-
12. 我用的 `name` 在 `IconNameList` 里搜得到吗?方位是 `chevron-*` 吗?有没有把 `back` 当方位?
|
|
461
|
-
|
|
462
|
-
**L2**
|
|
463
|
-
|
|
464
|
-
13. 这页有几个 `<Page`?成员只用那 5 个吗(`Filter` / `Header` / `Body` / `Footer` / `Split`)?有没有自绘页头 / 面板头 / 表格框?
|
|
465
|
-
14. 每个成员都是 `Page` 的**直接子节点**吗?有没有被任何元素或 `<template>` 包住?
|
|
466
|
-
15. 页头走的是内置布局还是自定义出口?**两者混了吗**(不传 `title` 却还传了 `subTitle` / `extra`)?
|
|
467
|
-
16. `extra` 里放的是**页面级**控件吗?有没有区域级的筛选 / 操作被提到页头?
|
|
468
|
-
17. 每个区域的筛选都落在 `toolbar`、操作都落在 `actions` 吗?有没有互换?字段 >3 个上提了吗?同一个字段摆了两处吗?
|
|
469
|
-
18. 区域头是四种形态里的哪一种(有 `title` / 只有两位 / 三样都没给 / 完全自定义)?完全自定义的登记了吗?简写与显式三段混用对吗?
|
|
470
|
-
19. 这页的滚动归谁?`Page.Body.mode` 全页唯一吗?表格有没有被外套一层 `height:auto` 的 div?
|
|
471
|
-
20. 分页和承载表格的容器同层吗?两栏是不是 `Page.Split`(自己画了竖直边框吗)?高度写的是 `100%` 还是 `100vh`?表单并排用的是 `Form columns` 吗?三态用对件了吗?`Panel` 有没有被加上框?
|
|
472
|
-
|
|
473
|
-
**L3**
|
|
474
|
-
|
|
475
|
-
21. 我用的每个件都在导出表里吗?每个复合成员都在成员表里吗?
|
|
476
|
-
22. 我传的每个 prop 都能在 `.d.ts` 里找到吗?改外观是走词表维度而不是写 CSS 吗?
|
|
477
|
-
23. 我用到的每个 import,**有没有哪一处是从 L3-3 白名单之外取外观的**?(业务域自有包与不产生外观的行为库不在 L3-3 管辖内。)图标的入口是主入口(不是 `/glyphs`)吗?有没有用原生控件承载外观(含无 `href` 的 `<a class>`)?
|
|
478
|
-
24. 浮层走组件 / 服务层了吗?微前端下宿主落回本应用容器了吗?
|
|
479
|
-
|
|
480
|
-
---
|
|
481
|
-
|
|
482
|
-
## §8 缺件与新增怎么走
|
|
483
|
-
|
|
484
|
-
组件库没有你要的件时,**按顺序**走:
|
|
485
|
-
|
|
486
|
-
1. **先找替代组合**:多数「缺件」是两件组合(过滤器 = `Input` + `Button`;摘要 = `Form.Item text` 行)。
|
|
487
|
-
2. **再查 `.d.ts`**:件可能已经有了,只是名字不同(如「图标按钮」= `Button variant="icon"`,
|
|
488
|
-
依据是 §0 的导出表与类型声明,**不是 README**)。
|
|
489
|
-
3. **确实是缺件** → 记进本工程的 `docs/kit-gaps.md`(一行一件:用途 / 缺什么 / 暂时的替代写法),
|
|
490
|
-
并在 §11 登记。
|
|
491
|
-
4. **提给 `@manohub/ui` 建件**:新件按「组件 + 面 CSS + 登记 + 导出 + 文档页」**五处齐备**,
|
|
492
|
-
少一处会静默失效。建成后把 `docs/kit-gaps.md` 里那条划掉。
|
|
493
|
-
|
|
494
|
-
已知缺件(截至 0.6.0):`DatePicker`(日期选择)、`Number`(数字输入)。
|
|
495
|
-
**不要**因此回去直连底层组件库(§6 L3-3 会拦)。
|
|
496
|
-
|
|
497
|
-
---
|
|
498
|
-
|
|
499
|
-
## §9 国际化
|
|
500
|
-
|
|
501
|
-
- 实例由 `@manohub/kit` 创建(`legacy: false`),应用侧**不得** `createI18n`。
|
|
502
|
-
`createSubApp({ i18n: { messages } })` 传的是**纯 messages**(`{ zh: {...}, en: {...} }`),
|
|
503
|
-
没有 i18next 的 `translation` 包装层。
|
|
504
|
-
- 组件内用 `useI18n()`(vue-i18n 的),**不是** i18next 的 `useTranslation`。
|
|
505
|
-
- 语言来源优先级:宿主下发 > `localStorage['manohub:locale']` > `navigator` > `en`。
|
|
506
|
-
切换入口只有 `applyLocale`(`@manohub/kit/entry`),它同时写实例与持久化。
|
|
507
|
-
- 缺词条时渲染 key 本身(不抛错、不刷警告),也没有第三方语言包兜底——词条要传全。
|
|
508
|
-
- **消费侧 vite 必须 `dedupe: ['vue-i18n']`**:两份副本会各自注入不同的 inject symbol,
|
|
509
|
-
组件 `useI18n()` 解析不到本包 `app.use` 的实例——**文案全丢且不报错**。
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
## §10 已知偏差与有意取舍
|
|
514
|
-
|
|
515
|
-
| 取舍 | 原因 |
|
|
516
|
-
| --- | --- |
|
|
517
|
-
| 应用侧样式只允许布局(§3) | 视觉的唯一来源是 theme;散落的视觉属性必然改一处漏一处 |
|
|
518
|
-
| 禁 `rem`、统一 px 令牌 | 不跟随根字号缩放,避免不同宿主下尺寸分叉 |
|
|
519
|
-
| `Table` 不内建三态与序号列 | 错误态归 `QueryState`(它是容器);序号列自加一列 |
|
|
520
|
-
| `Steps` 不可点(无跳转门控) | 步骤条是只读展现;「上一步 / 下一步」的按钮与门控放页面里 |
|
|
521
|
-
| `Filter` 无关键字输入与就绪轮询 | 「关键字」就是普通 `Filter.Item` + `Input`;就绪时序归页面 |
|
|
522
|
-
| `Form` 只有一个头 | 分组用 `Panel` 或拆区域——多头会让「哪个头管哪些字段」说不清 |
|
|
523
|
-
| 命令式服务不提供异步 `confirm` | 需要「确定按钮进加载态」时直接用 `Dialog` 的 `onOk`(返回 Promise 自动进加载态) |
|
|
524
|
-
| 命令式 `confirm()` / `alert()` **显式关掉**「点遮罩关闭」 | 组件形态默认**开**(与主流一致,见 §12.2);命令式一条误点就丢一次决策,故显式关掉 —— 与 AntD 的 `<Modal maskClosable>`(开)/ `Modal.confirm`(关)同一分工 |
|
|
525
|
-
| `Text` 只做六个维度(字号 / 行高 / 字重 / 语义色 / 等宽 / 截断) | 它补的是「容器里的文字层级」这条一直缺的路径;富文本解析、省略的展开交互都不归它 |
|
|
526
|
-
| 本包零样式(无 reset、无富文本预设) | 样式只有两个来源:theme(值)与 ui(面)。本包夹在中间转发样式,只会让「值的来源」说不清 |
|
|
527
|
-
| `theme` **有意不建 icons / motion 两片** | 图标不承载颜色(随文案色,见 L1.5-3);动效目前没有跨组件统一的语义。这两片是**有意未建**,不是遗漏 |
|
|
528
|
-
| **已知缺口:富文本排版暂无归属** | 此前由本包的 `.app-markdown` 预设承担,已随样式删除;`@manohub/ui` 的 `reset` 又归零了 `ul` / `ol` 的序号与 `a` 的下划线,**富文本容器必须自己补回** `list-style` / 链接样式 / 段落边距。需要富文本排版的页面暂时只能整段进 §11 登记,或在 ui 提一个 `Markdown` 件 |
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## §11 定版文件清单(人工确认,不是通行证)
|
|
533
|
-
|
|
534
|
-
某些页面有设计稿逐像素还原的要求,实现上必然带**色值字面量**与**非白名单属性**。
|
|
535
|
-
这类文件**必须登记**——但登记**不等于放行**:
|
|
536
|
-
|
|
537
|
-
- 登记的作用是**让下一个人知道**:这里为什么和 §3 不一样、改它要人工确认。
|
|
538
|
-
- **色值字面量只减不增**:登记之后新加的视觉值仍然违规。
|
|
539
|
-
- **登记不放行的是这些**(无论怎么登记都仍然违规):从白名单之外取外观(L3-3)、自绘页头 / 面板头(L2-3)、
|
|
540
|
-
原生控件承载外观(L3-4)、`!important`、`.mh-*` 覆写、令牌名拼错(L1-2)。
|
|
541
|
-
|
|
542
|
-
登记表放本工程的 `docs/figma-fidelity.md`,格式:
|
|
543
|
-
|
|
544
|
-
| 文件 | 设计稿节点 | 为什么必须还原 | 登记日期 |
|
|
545
|
-
| --- | --- | --- | --- |
|
|
546
|
-
| `views/skill-market/SkillMarket.tsx` | MH后台-0911 node 0:3904 | 甲方定版稿,色值逐像素对齐 | 2026-09-23 |
|
|
547
|
-
|
|
548
|
-
**新登记必须给出「为什么这个值改不了」**——写不出来的,就是可以走令牌的。
|
|
549
|
-
|
|
550
|
-
---
|
|
551
|
-
|
|
552
|
-
## §12 升级(破坏性)
|
|
553
|
-
|
|
554
|
-
> **版本口径**:**0.6.0** 是一次四包重构(上一个已发布版本是 `@manohub/app-kit@0.4.3`);
|
|
555
|
-
> 下面 12.1 的三组变化是**同一次重构**的三个面 —— 组件换库、主题与样式链独立、契约与护栏重做,
|
|
556
|
-
> **不存在「先升到某个中间版本」的路径**,一次性做完。`AppShell` → `Page` 与 `AppTable` → `Table`
|
|
557
|
-
> 常常落在同一处代码,拆成几轮只会把同一段改两遍。
|
|
558
|
-
>
|
|
559
|
-
> **0.7.0** 是它之后的第一批修复与补齐(12.2),带去**四处行为变更** ——
|
|
560
|
-
> 从 0.6.x 升上来的应用**只需读 12.2**。
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
|
572
|
-
|
|
|
573
|
-
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
|
578
|
-
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
|
583
|
-
|
|
|
584
|
-
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
|
589
|
-
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
|
594
|
-
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
|
610
|
-
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
|
615
|
-
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
|
620
|
-
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1
|
+
# @manohub/kit CONTRACT —— 接入方必读
|
|
2
|
+
|
|
3
|
+
**本文件是接入方的权威规范**。与它冲突的其它说法(组件里的示例、包 README、旧技能、口头约定)一律以本文为准。
|
|
4
|
+
|
|
5
|
+
> **0.6.0 起本包不再提供组件。** 组件与命令式服务的 API 归 `@manohub/ui`;
|
|
6
|
+
> 全局令牌(值)归 `@manohub/theme`;图形归 `@manohub/icon`。
|
|
7
|
+
> 本文管的是**页面怎么搭**、**样式怎么写**,以及**去哪读**。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 怎么用这份契约
|
|
12
|
+
|
|
13
|
+
### 五层,一层一个职责方
|
|
14
|
+
|
|
15
|
+
**遇到问题先定位层,再读那一节。** 层的划分依据不是「检查手段」,是**谁负责**:
|
|
16
|
+
|
|
17
|
+
| 层 | 职责方 | 管什么 | 能不能有例外 |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| **L0 入口与作用域** | `kit` | 样式链、容器锚、单入口 | **不能** —— 漏了下面四层一起静默失效 |
|
|
20
|
+
| **L1 值** | `theme` | 值从哪来、怎么用、怎么加 | 能(须登记,见 §11) |
|
|
21
|
+
| **L1.5 图标** | `icon` | 图形语汇 | 能(须登记) |
|
|
22
|
+
| **L2 结构** | `ui` | 页面骨架怎么搭 | 能(须登记) |
|
|
23
|
+
| **L3 件与词表** | `ui` | 用哪些件、传哪些 prop | 能(须登记) |
|
|
24
|
+
|
|
25
|
+
**收口顺序就是层序**:L0 → L1 → L1.5 → L2 → L3。但**存量应用的最优迁移路线是反的**——
|
|
26
|
+
先换骨架(L2)、再换件(L3)、最后收样式(L1),每步都能单独跑通。
|
|
27
|
+
|
|
28
|
+
### 每条条款都写成闭集
|
|
29
|
+
|
|
30
|
+
**判据是闭集的意思是**:白名单里找不到就是违规。**不要自我说服**「这个应该算布局」「这个应该算例外」——
|
|
31
|
+
找不到就是找不到。
|
|
32
|
+
|
|
33
|
+
本文刻意不写「等」「之类」「比如」这类开集措辞。你读到一条判据时,应该能直接回答「符合 / 不符合」。
|
|
34
|
+
|
|
35
|
+
### 本文不抄值、不抄件名、不抄成员、不抄词表
|
|
36
|
+
|
|
37
|
+
它们**会漂移**。曾经有过 `Page.Toolbar`、`Input.Group`、`Input.Chip` 三个不存在的成员被写进文档。
|
|
38
|
+
所以本文只写「去哪读」——见 §0。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## §0 权威源表:写任何东西之前先读这里
|
|
43
|
+
|
|
44
|
+
| 你要查 | 去哪儿读(消费方可达路径) | 读完怎么用 |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| 全局令牌的**值** | `node_modules/@manohub/theme/dist/<主题>/{index,colors,typography,spacing,radius,shadows}.css` | 把它们当你的色板与尺寸表。**要什么值去这里找**,别猜、别抄进本文 |
|
|
47
|
+
| 组件令牌的**值** | `node_modules/@manohub/ui/dist/styles/components/<件>.tokens.css` | 同上;组件专属尺寸与档位距在这 |
|
|
48
|
+
| **有哪些件** | `@manohub/ui` 的 `dist/index.d.ts` | 以**导出表**为准。README 会漂移 |
|
|
49
|
+
| 复合**成员** | 各件 `dist/components/<件>/index.d.ts` 的 `Object.assign` 处 | 成员只走点号 |
|
|
50
|
+
| prop 的**存在性与取值** | 该件 `dist/components/<件>/index.d.ts` | 传 prop 前打开确认 |
|
|
51
|
+
| **语义词表**(改外观的唯一正规通道) | 同上,`.d.ts` 里的联合类型 | 26 个维度,`tone` / `variant` / `shape` / `size` / `status` / `gap` / `align` / `justify` / `padding` … |
|
|
52
|
+
| **骨架怎么用**(归位 / 滚动 / 页头 / 操作位) | `@manohub/ui/dist/components/page/index.d.ts`、`dist/components/panel/index.d.ts` 的头注释 | §5 已把判据写全;这两份是更细的展开 |
|
|
53
|
+
| 图标**名清单** | `@manohub/icon` 的 `dist/glyphs.d.ts`(`IconNameList`) | `name` 必须在清单里搜得到 |
|
|
54
|
+
| 图标**字形与出处** | `node_modules/@manohub/icon/dist/glyphs.js`(源码形态:`src/glyphs.ts`) | 缺图标改这里 + 重 build |
|
|
55
|
+
| **类名与档位类** | `@manohub/ui/dist/styles/index.css` 及其 `components/` | 应用侧**不写** `.mh-*` |
|
|
56
|
+
| 本仓**类名命名空间** | 本应用自己的 `docs/kit-namespaces.md`(若还没有,建一个) | 类名形状必须匹配表内某一格 |
|
|
57
|
+
|
|
58
|
+
> **为什么给的是 `dist/` 而不是 `src/`**:`theme` / `ui` / `icon` 三个包的 `files` 都只发布
|
|
59
|
+
> `dist` 与 `README.md`。`src/` 与 `docs/` 在消费方的 `node_modules` 里**不存在**——写 `src/` 的读取指令等于没写。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## §1 接入
|
|
64
|
+
|
|
65
|
+
### 1.1 装什么
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pnpm add @manohub/kit @manohub/ui @manohub/theme @manohub/icon
|
|
69
|
+
pnpm add vue vue-router pinia vue-i18n @tanstack/vue-query # peer,由应用提供
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
四个包**并列安装**,模块级互不依赖:kit 提供入口编排与本文,ui 提供组件与命令式服务,
|
|
73
|
+
theme 提供令牌(值),icon 提供图形。**本包不发布任何样式**(见 §1.2),
|
|
74
|
+
所以 `kit` 的 `peerDependencies` 里没有另外三个。
|
|
75
|
+
|
|
76
|
+
四者之间**唯一的跨包约定**是容器上的属性锚 `data-manohub-ui`(见 §1.3)。
|
|
77
|
+
|
|
78
|
+
### 1.2 样式链:两行 + 应用自己一行(顺序即契约)
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
@import "@manohub/theme/default.css"; /* ① 令牌(值)—— 换主题只换这一行 */
|
|
82
|
+
@import "@manohub/ui/styles.css"; /* ② 组件面(类 + 组件令牌基础值) */
|
|
83
|
+
@import "./app.css"; /* ③ 应用自身(只写布局,见 §3) */
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **顺序不可换**:先值(令牌)后面(组件面)。反过来的话组件面里的 `var(--ui-*)` 全是空值。
|
|
87
|
+
- 用非兜底主题时把 ① 换成对应入口(如 `@manohub/theme/farris.css`),
|
|
88
|
+
并在入口给 `createSubApp({ theme: 'farris' })`(写到容器的 `data-theme` 上)。
|
|
89
|
+
- **`reset`(元素级基线)自 0.7.0 起已内置在 ② 里**,不需要第三行:`@manohub/ui/styles.css` 的**首条** import
|
|
90
|
+
就是它(盒模型、按钮外观、列表序号、链接下划线、标题字阶一并归零)。作用域锚 `[data-manohub-ui]`
|
|
91
|
+
且特异性为 0,**应用侧写任何元素选择器都能盖过它**。(0.6.x 没有它,那两版的应用侧自补过一份 —— 见 §12.2。)
|
|
92
|
+
- **入口基线(`html` / `body` / `#app` 的高度链)归消费方,且是合法的**:本包的 reset **有意不写**这三个
|
|
93
|
+
选择器 —— micro-app 的 `scopecss` 不作用域化它们,包内写进去会泄漏到宿主全局。但「页面撑满」这条链
|
|
94
|
+
**必须有人给**(`Page` 的 `height: 100%` 依赖它),所以它由**应用的入口基线**承担:写在 ③ 里,
|
|
95
|
+
或写在入口 HTML 的 `<style>` 里。典型形态是「`html` / `body` / `#app` 三个 `height: 100%` + `margin: 0`」。
|
|
96
|
+
这是 L1-6「不得用裸元素选择器」的**唯一例外**(`html` / `body` / `#app` 三个名字),
|
|
97
|
+
用到的属性全在 L1-4 白名单内。**别把这条链写进组件 / 页面样式** —— 它只属于入口。
|
|
98
|
+
- **富文本(markdown)要自行复权**:reset 归零了 `ul` / `ol` 的序号与 `a` 的下划线,
|
|
99
|
+
而富文本排版没有合规归属(见 §10 的已知缺口)—— 消费方须为自己的富文本容器类补回
|
|
100
|
+
`list-style` / 链接样式 / 段落边距。
|
|
101
|
+
|
|
102
|
+
### 1.3 入口
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// src/main.ts
|
|
106
|
+
import { createSubApp } from '@manohub/kit/entry'
|
|
107
|
+
|
|
108
|
+
export const { mount, unmount } = createSubApp({
|
|
109
|
+
rootComponent: Root,
|
|
110
|
+
routes,
|
|
111
|
+
i18n: { messages: { zh, en } },
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
工厂替你做完:`div.app-container` 包裹 + `data-manohub-ui` / `data-theme` 属性、pinia / vue-router /
|
|
116
|
+
vue-query 装配、宿主挂载协议、宿主语言同步、首帧路由重置。**不要自己手写 main.ts 样板**——
|
|
117
|
+
漏掉 `exposeToHost` 会让宿主无法重挂子应用。
|
|
118
|
+
|
|
119
|
+
容器上的名字分三类,**只有前两个是跨包约定**(改名会断掉对方):
|
|
120
|
+
|
|
121
|
+
| 名字 | 谁写 | 用途 | 跨包契约? |
|
|
122
|
+
| --- | --- | --- | --- |
|
|
123
|
+
| **`data-manohub-ui`** | 本工厂**无条件**写 | **唯一作用域锚**:主题令牌 + 组件令牌 + 服务层宿主解析 | ✅ **是** |
|
|
124
|
+
| `data-theme` | 只在传 `theme` 时写 | 皮肤开关(`'farris'` 等),作用于上面的锚 | ✅ 是 |
|
|
125
|
+
| `class="app-container"` | 本工厂写 | 本包**内部**命名(`scopecss` 作用域故事 + 调试自查) | ❌ 不是 |
|
|
126
|
+
|
|
127
|
+
**服务层浮层落回本应用容器靠 `data-manohub-ui`**(`toast` / `confirm` / `showLoading` 据此解析宿主),
|
|
128
|
+
**令牌的作用域也锚在它上面**。宿主自建容器(不走 `createSubApp`)时**必须**自己带上 `data-manohub-ui`,
|
|
129
|
+
否则本库组件连几何令牌都拿不到。
|
|
130
|
+
|
|
131
|
+
### 1.4 目录约定
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
src/
|
|
135
|
+
├── main.ts 入口(createSubApp)
|
|
136
|
+
├── router/ 路由表
|
|
137
|
+
├── views/<页>/<页>.tsx 路由级页面(契约 L2-1 认这个目录段)
|
|
138
|
+
│ └── components/ 本页的可复用件(弹窗 / 抽屉 / 表单块 / 子表格)
|
|
139
|
+
└── app.css 应用自身样式(只写布局)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**路由级页面**与**页面内可复用件**的分界是**目录段**:`views/<页>/components/` 下的文件不受
|
|
143
|
+
「必须用 `Page` 骨架」约束(它们由 `Panel` / `Form` / `Dialog` 承载)。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## §2 L0 入口与作用域(`kit` 的职责,**不可豁免**)
|
|
148
|
+
|
|
149
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| **L0-1 样式链** | CSS 入口前两条 `@import` **逐字**为 `@manohub/theme/<主题>.css`、`@manohub/ui/styles.css`,且都在自有样式之前;全仓各出现一次 | 值在前、面在后;反了就全是空值 |
|
|
152
|
+
| **L0-2 容器与锚** | 应用根恰好一次 `createSubApp(...)`;**不得自己写 `data-manohub-ui`**;**不得依赖 `.app-container`** | 锚是令牌与宿主解析的共同前提;`.app-container` 是本包内部名,不是跨包约定 |
|
|
153
|
+
| **L0-3 单入口与 i18n** | 全仓 `createSubApp(` 恰好 1 次、`createI18n(` **0 次**(i18n 实例由本包创建) | 双实例会让文案全丢且不报错(见 §9) |
|
|
154
|
+
|
|
155
|
+
**为什么 L0 不可豁免**:锚是主题令牌、组件令牌、服务层宿主解析三者的共同前提。
|
|
156
|
+
漏了它,下面四层会**一起静默失效**——组件有框有距,但颜色是浏览器默认灰蓝。
|
|
157
|
+
|
|
158
|
+
**正误对照**
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
// ✗ 自己写锚 / 依赖内部类名
|
|
162
|
+
document.querySelector('.app-container')!.setAttribute('data-manohub-ui', '')
|
|
163
|
+
// ✓ 交给入口层;需要自己挂浮层宿主时用服务层公开 API
|
|
164
|
+
configureHost(hostEl)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
自检见 §7 第 1–3 问。
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## §3 L1 值(`theme` 的职责)
|
|
172
|
+
|
|
173
|
+
### 条款
|
|
174
|
+
|
|
175
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| **L1-1 应用侧不持有值** | 应用侧的 `--*` 自定义属性**一处都不该有**。只有两种合法写法:① **重设已存在的令牌**(局部换肤)② 不写 | 值的唯一来源是 theme。局部换肤是合法且推荐的(见下) |
|
|
178
|
+
| **L1-2 引用必须存在** | 每个 `var(--xxx)` 的 `--xxx` 必须能在 theme 六片或 `<件>.tokens.css` 里**搜到**。**新增令牌名一律违规** | 写错名字不会报错,只会静默回退成默认值 |
|
|
179
|
+
| **L1-3 尺寸取令牌** | 尺寸取 `--ui-space-*` / `--ui-radius-*` / `--ui-control-height-*` / `--ui-row-height` / `--ui-grid-column-min`;**几何是设计基准、不跟根字号**,故禁 `rem`;内距给 **px 数字**(与 `Padding` 同口径) | 设计基准不随宿主根字号分叉 |
|
|
180
|
+
| **L1-4 只写布局属性** | `<style>` 与 `style={{ }}` 里的属性名必须在**下面的白名单**内 | theme 是视觉的唯一来源 |
|
|
181
|
+
| **L1-5 类名形状** | 类名一律 `<本仓命名空间>-<kebab-case>`。落点 A:CSS 选择器**首段**;落点 B:`class=` 的**每个 token** | 两个应用装进同一页面时不能撞名 |
|
|
182
|
+
| **L1-6 不碰库的类** | 选择器里**不得出现 `.mh-`**;不得用 `!important`;不得用裸元素选择器与 `*`。**唯一例外:入口基线**(`html` / `body` / `#app` 三个选择器及其高度链,见 §1.2)| `.mh-*` 与档位类归 ui;覆写升版即静默失效 |
|
|
183
|
+
| **L1-7 不引 Tailwind** | 全仓不得出现 `tailwindcss` / `@tailwindcss/` / `@theme` | theme 只认自己的 `@import './…css'` |
|
|
184
|
+
|
|
185
|
+
### L1-4 的属性白名单(闭集,逐字照用)
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
盒模型与定位:display / position / inset / top / right / bottom / left / z-index / box-sizing /
|
|
189
|
+
width / height / min-width / min-height / max-width / max-height /
|
|
190
|
+
margin(-top|right|bottom|left) / padding(-top|right|bottom|left) /
|
|
191
|
+
overflow / overflow-x / overflow-y / gap / row-gap / column-gap / aspect-ratio / resize
|
|
192
|
+
弹性与栅格:flex / flex-direction / flex-wrap / flex-grow / flex-shrink / flex-basis /
|
|
193
|
+
align-items / align-self / align-content / justify-content / justify-items / justify-self /
|
|
194
|
+
order / place-items / place-content / grid-template-columns / grid-template-rows /
|
|
195
|
+
grid-auto-flow / grid-auto-columns / grid-auto-rows / grid-column / grid-row
|
|
196
|
+
文字流(不含颜色与字号):white-space / text-overflow / overflow-wrap / word-break / text-align /
|
|
197
|
+
vertical-align / line-clamp / -webkit-line-clamp / hyphens / direction / writing-mode
|
|
198
|
+
交互与动效:cursor / user-select / pointer-events / visibility /
|
|
199
|
+
transition(-property|-duration|-delay|-timing-function) /
|
|
200
|
+
animation(-name|-duration|-delay|-iteration-count|-direction|-fill-mode) /
|
|
201
|
+
transform / transform-origin / will-change / scroll-behavior / overscroll-behavior /
|
|
202
|
+
touch-action / scrollbar-width
|
|
203
|
+
其他:content / list-style(-type|-position) / isolation / contain / object-fit / object-position / clip-path
|
|
204
|
+
|
|
205
|
+
唯一重置例外:border: 0 / border: none(去框)。写宽度或颜色不允许。
|
|
206
|
+
判定:属性名去掉 -webkit- / -moz- 前缀 → 上面找不到就是违规,不要自我说服。
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**注意白名单里没有的东西**:`color` / `background*` / `border*`(除 `border: 0`)/ `border-radius` /
|
|
210
|
+
`box-shadow` / `font-*` / `letter-spacing` / `text-transform` / `text-decoration*` / `outline*` /
|
|
211
|
+
`caret-color` / `accent-color` / `filter` / `opacity` / `mix-blend-mode` / `fill` / `stroke`。
|
|
212
|
+
**这些一律违规**,改外观走 §6 的语义词表或 §3 的局部换肤。
|
|
213
|
+
|
|
214
|
+
### 局部换肤:唯一允许写 `--*` 的情形
|
|
215
|
+
|
|
216
|
+
在**更深的容器**上重设**已存在**的令牌,是合法且推荐的改外观手段:
|
|
217
|
+
|
|
218
|
+
```css
|
|
219
|
+
.role-tree-pane { --ui-primary: #7C3AED; }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
区别在**「新名字」还是「已有名字」**:`--ui-primary` 在 theme 里有定义 → 合法(重设):
|
|
223
|
+
`--ui-my-brand` 在 theme 里搜不到 → 违规(发明)。想加新名字去 `@manohub/theme` 提。
|
|
224
|
+
|
|
225
|
+
### 正误对照
|
|
226
|
+
|
|
227
|
+
```css
|
|
228
|
+
/* ✗ 视觉写进应用 CSS */
|
|
229
|
+
.vm-tag { color: var(--ui-error); border-radius: 4px; }
|
|
230
|
+
/* ✗ 令牌名拼错(不报错,静默回退默认值) */
|
|
231
|
+
.vm-box { background: var(--ui-bg-page); } /* 真名是 --ui-base-bg */
|
|
232
|
+
/* ✓ 外观走语义 prop:<Tag tone="error">…</Tag> */
|
|
233
|
+
/* ✓ 局部换肤(重设已有令牌) */
|
|
234
|
+
.vm-tree-pane { --ui-primary: #7C3AED; }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
自检见 §7 第 4–8 问。
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## §4 L1.5 图标(`icon` 的职责)
|
|
242
|
+
|
|
243
|
+
这一层**不是值**(不自持任何 `--*`),**也不是面**(零 CSS)。它是**值到面之间的图形语汇**:
|
|
244
|
+
颜色靠继承(`currentColor`),尺寸由 L2 / L3 的面层给。
|
|
245
|
+
|
|
246
|
+
### 条款
|
|
247
|
+
|
|
248
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
249
|
+
| --- | --- | --- |
|
|
250
|
+
| **L1.5-1 唯一来源** | 需要图形处一律 `<Icon name="…" />`(非 Vue 场景用 `renderIconSvg()`)。**不得**自绘(`<svg` / `<path` / `<circle`);**不得**用文字符号(`✓` `⌄` `×` `▶` `←`)冒充 | 一套线重、一个视觉族;文字符号在不同字体下高矮不一 |
|
|
251
|
+
| **L1.5-2 尺寸在面层给** | 图标槽**只写 `width` / `height`** 两个属性,值取 `var(--ui-font-icon)`(要档距用 `calc()`);件内令牌写成它的**别名**。**不传 `size`** | `size` 默认 16,不写就静默落到 16;写死了也不跟主题缩放。**消费侧没有例外** |
|
|
252
|
+
| **L1.5-3 只继承色、不承载色** | 不给图标写任何颜色(`color` / `stroke` / `fill` / `stroke-width`) | 图标随文案色;theme 里**有意没有 icons 片**,不要想着去补一片 |
|
|
253
|
+
| **L1.5-4 方位在字形里** | 方位一律 `chevron-*`(四向齐备);**不得**为「翻向」写 `transform: rotate()`。面层出现 `rotate(` 的唯一合法情形是 `loading` 的转圈动效;`back` 只作「返回」语义 | 旋转过的字形在视觉上不是同一个族 |
|
|
254
|
+
| **L1.5-5 语义裸符号,容器归组件** | 状态 / 语义图标取**不带容器**的裸字形(`info` / `check` / `alert` / `close`);色底那一圈由**组件**给 | 字形再自带一圈会叠成双圈 |
|
|
255
|
+
| **L1.5-6 名字取自清单,缺图标补包** | `name` 必须能在 `IconNameList` 里搜到;缺图标 → 在 `@manohub/icon` 加一条并重 build,**不在应用仓画路,也不在 ui 画路** | 就地画一个 = 第二次分叉 |
|
|
256
|
+
| **L1.5-7 入口分栈** | Vue 应用只认主入口 `@manohub/icon`;`@manohub/icon/glyphs` **只在没有 Vue 组件可用处**(静态页 / 模板串 / 非 Vue 栈) | 两个入口的产物类型不同 |
|
|
257
|
+
|
|
258
|
+
### 正误对照
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
// ✗ 手绘 / 文字符号冒充 / 传 size / 写颜色 / 靠旋转转方位
|
|
262
|
+
<svg viewBox="0 0 24 24"><path d="…" /></svg>
|
|
263
|
+
<span class="vm-arrow">⌄</span>
|
|
264
|
+
<Icon name="chevron-down" size={14} class="text-primary" />
|
|
265
|
+
<span class="vm-rot90"><Icon name="back" /></span>
|
|
266
|
+
// ✓ 图形来自包、尺寸在面层、颜色继承、方位是字形
|
|
267
|
+
<Icon name="chevron-down" />
|
|
268
|
+
.vm-select-caret { width: var(--ui-font-icon); height: var(--ui-font-icon); }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
自检见 §7 第 9–12 问。
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## §5 L2 结构(`ui` 的面 → **骨架怎么用**)
|
|
276
|
+
|
|
277
|
+
这一层管**骨架怎么用**,不管件内部(那是 L3)。**20 条,按五组。**
|
|
278
|
+
|
|
279
|
+
### 组 1 · 承载:谁搭骨架
|
|
280
|
+
|
|
281
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
282
|
+
| --- | --- | --- |
|
|
283
|
+
| **L2-1 页面骨架唯一** | 路由页(`views` \| `pages` 下、**`components/` 子树除外**)**恰好一个** `<Page`;`Page` 成员只用 `Filter` / `Header` / `Body` / `Footer` / `Split` 这 5 个 | 每页只有一个页头规格与一个页面级滚动归属 |
|
|
284
|
+
| **L2-2 页面级 vs 区域级** | 页面级只有 `Page` 一个;**区域级一律 `Panel`**(可多个,三件 `Header` / `Body` / `Footer`) | 两级职责不同:`Page` 管页面级滚动与页脚固定,`Panel` 只管区域内 |
|
|
285
|
+
| **L2-3 骨架不得自绘** | 页头只有 `Page.Header`、区域头只有 `Panel.Header`、表格框只有 `Table` 的 `framed` 档——**三者都没有第二个承载者**。模板里不写冒充的 `<div>`,CSS 里不出现页头 / 面板头 / 表格框类名 | 页头条高、内距、图标、右侧位、分隔线都是骨架的契约 |
|
|
286
|
+
|
|
287
|
+
### 组 2 · 归位:成员怎么被认出来
|
|
288
|
+
|
|
289
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| **L2-4 成员归位** | 成员**不按书写顺序渲染**,一律归到固定位置:`Filter → Header → Body → Footer`(`Panel` 同款);自由内容一律进主体区,**不会被丢** | 「读代码的顺序」不必等于「视觉顺序」 |
|
|
292
|
+
| **L2-5 成员必须是直接子节点** | 归位按**组件类型**认领:成员只要**不是 `Page` 的直接子节点**(被任何元素或 `<template>` 包住)就会编译成 Fragment 而**认不出**——它会被当自由内容挪进主体区。`v-if` **直接写在成员标签上**没问题 | 这是归位的机制前提,也是最隐蔽的坑 |
|
|
293
|
+
| **L2-6 缺省 Body** | 没写显式 `Page.Body` 时**自动包一层**(缺省 `mode="plain"`)→ 双栏可以只写 `Page.Split`;`Panel` 同法:其余子节点自动进 `Body` | 不写不等于没结构 |
|
|
294
|
+
|
|
295
|
+
### 组 3 · 页头:`Page.Header` 怎么用
|
|
296
|
+
|
|
297
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
298
|
+
| --- | --- | --- |
|
|
299
|
+
| **L2-7 页头两模式不可混** | 传 `title` → 内置布局(`subTitle` 在下一行、`icon` 在左、`extra` 在右);**不传 `title` → 纯 slot 直出(整行替换)**,此时 `subTitle` 不生效、**`extra` 不渲染**。**想用右侧位就必须传 `title`** | 不传 title 却传 extra,右侧位会**静默消失**,最容易被当成「样式坏了」 |
|
|
300
|
+
| **L2-8 缺省图标** | `icon` 不传 → 渲染设计稿默认图标(26×26 圆角方块,`currentColor`)。**不要为了「去掉图标」而传空值**;换图标传 `IconNameList` 里的 `<Icon>` | 「不传」是「用默认」,不是「不要」 |
|
|
301
|
+
| **L2-9 `extra` 是唯一自由位** | 页头右侧**没有独立成员件**;**区域级筛选 / 操作不得因「页头能放」就提到页面级**;页面级页签用主体里的 `TabBar` / `Tabs` | 提到页面级会让「这块区域」失去自己的筛选位 |
|
|
302
|
+
|
|
303
|
+
### 组 4 · 区域:筛选 / 操作 / 滚动 / 分页
|
|
304
|
+
|
|
305
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
306
|
+
| --- | --- | --- |
|
|
307
|
+
| **L2-10 两位分工不可互换** | `toolbar` = **本区域筛选**(≤3 个单值输入控件:`Input` / `Search` / `Select` / `Textarea`);`actions` = **本区域操作**(新建 / 刷新)。判据**有顺序**:① 先问「**过滤谁的数据**」决定放哪个区域的 `toolbar` ② 再问「**几个字段**」,>3 上提页面级 `Page.Filter` ③ 同一字段**不得摆两处** | 放错位不是样式问题,是「这块区域的筛选没了」 |
|
|
308
|
+
| **L2-11 区域头四种形态** | ① 有 `title` → 内建头;② **只给 `toolbar` / `actions` 不给 `title` 合法**(标题在容器外层,如弹窗内区域),那一行照渲染;③ 三样都不给 → **不渲染**(不凭空多一条空行);④ 完全自定义头走 `Panel.Header` 不传 `title` 或 `header` 插槽,**须登记**(§11) | 四种都是合法形态,但要**知道自己在哪一种** |
|
|
309
|
+
| **L2-12 两种面板写法可混用** | 简写 `<Panel title toolbar actions>` = 内建 `Header` + 包一层 `Body`;显式 `Panel.Header` **优先**于简写 | 两种写法等价,混用不会双头 |
|
|
310
|
+
| **L2-13 两级滚动各归各的** | 页面级由 `Page.Body.mode` 管(`scroll` 自身滚 / `plain` 不滚,**缺省 `plain`**,**全页唯一**);区域级由 `Panel.Body` 管(头尾固定,**只有 Body 滚**);`Split` **自身不滚**,栏内滚动由栏内 `Panel` 承担 | 混了的表现是「标题被滚走、双滚动条」 |
|
|
311
|
+
| **L2-14 表格不双滚** | 表格的高度与滚动由**承载它的内容件**承担,**不靠 `Page.Body.mode` 表达**。**反例**:别在 `Body` 里给表格族外套一层 `height:auto` 的 `<div>`,否则 Body 整块滚、**表头跟着走** | 表格自己填满容器、网格内部滚,才不双滚 |
|
|
312
|
+
| **L2-15 分页跟承载表格的容器** | 表格在 `Panel` 内 → 分页 `Panel.Footer`;表格直接挂 `Page.Body` → 分页 `Page.Footer`。判据看**承载表格的容器在哪** | 分页跑远了就与它控制的列表脱节 |
|
|
313
|
+
|
|
314
|
+
### 组 5 · 版式:两栏、摆块、高度、三态
|
|
315
|
+
|
|
316
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
317
|
+
| --- | --- | --- |
|
|
318
|
+
| **L2-16 两栏用 Split** | 页面级两栏(各自滚动)用 `Page.Split`(`sidebar` / `rightSidebar` 互为镜像,可同时存在成三栏);**两栏之间竖直 1px 线由 `Split` 提供 → 业务侧不得给栏的子元素加 `border-right` / `border-left`**;`sidebar.width` 是 `content-box` **内容宽** | 自己画线会和 Split 的线叠成两条;收起态那条线要归零 |
|
|
319
|
+
| **L2-17 摆块用 Layout** | 块与块怎么摆 → `Layout.Row` / `Layout.Column`;**表单并排字段用 `Form columns={2}`**,不用 `Layout` 拼 | `Layout` 管块间距,`Form` 管 label 列与行距 |
|
|
320
|
+
| **L2-18 高度由容器给** | `Page` 需要所在容器给出**确定高度**;高度取 `100%` 而非 `100vh`——**禁 `h-screen` / `100vh`** | 子应用被注入宿主容器,`100vh` 取的是 window 视口,宿主有顶栏就会溢出 |
|
|
321
|
+
| **L2-19 三态分工** | 区域 / 页面三态 → `QueryState`(它是容器:插图 + 操作 + 占位);内容占位 → `Skeleton`;忙等 → `Loading`(遮罩,**拦住交互**是它的职责);`Table` **不内建三态**(空态走 `emptyText` / `#empty`,错误态用 `QueryState` 包住) | 三个件解决三个不同的问题 |
|
|
322
|
+
| **L2-20 骨架不加框** | `Panel` 只是「区域 + 三件」,**描边 / 圆角 / 底色一概不加**;需要框用 `Card` | 无框是 `Panel` 的契约;加框会让「区域」与「卡片」两种语义混掉 |
|
|
323
|
+
|
|
324
|
+
### 正误对照
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
// ✗ 自绘页头 / 用 Layout 拼表单
|
|
328
|
+
<div class="vm-page-header">…</div>
|
|
329
|
+
<Layout.Row><Input a /><Input b /></Layout.Row>
|
|
330
|
+
// ✓
|
|
331
|
+
<Page>
|
|
332
|
+
<Page.Header title={t('page.title')} :extra="新建按钮" />
|
|
333
|
+
<Page.Body mode="plain">
|
|
334
|
+
<Panel title="技能列表">…</Panel>
|
|
335
|
+
</Page.Body>
|
|
336
|
+
</Page>
|
|
337
|
+
<Form columns={2}>…</Form>
|
|
338
|
+
|
|
339
|
+
// ✗ 用 <template v-if> 包成员 → 归位认不出,该成员被当自由内容挪进主体区
|
|
340
|
+
<template v-if="canEdit"><Page.Footer>…</Page.Footer></template>
|
|
341
|
+
// ✓ v-if 直接写在成员标签上
|
|
342
|
+
<Page.Footer v-if="canEdit">…</Page.Footer>
|
|
343
|
+
|
|
344
|
+
// ✗ 不传 title 却想用右侧位 → extra 静默不渲染
|
|
345
|
+
<Page.Header :extra="搜索框" />
|
|
346
|
+
// ✓ 要用右侧位就传 title
|
|
347
|
+
<Page.Header title="技能分类" :extra="搜索框" />
|
|
348
|
+
// ✓ 要整行自定义就不传 title,右侧位自己摆
|
|
349
|
+
<Page.Header><div class="my-head">…</div></Page.Header>
|
|
350
|
+
|
|
351
|
+
// ✗ 把区域级控件提到页头 / 把筛选塞进 actions
|
|
352
|
+
<Page.Header title="列表" :extra="新建按钮" /> /* 这是区域级的「新建」 */
|
|
353
|
+
<Panel title="列表" :actions="筛选输入框" />
|
|
354
|
+
// ✓ 各归各的位
|
|
355
|
+
<Page.Header title="列表" :extra="页面级控件" />
|
|
356
|
+
<Panel title="列表" :toolbar="搜索框" :actions="新建按钮" />
|
|
357
|
+
|
|
358
|
+
// ✗ 在 Body 里给表格族外套一层 height:auto 的 div → Body 整块滚、表头跟着走
|
|
359
|
+
<Panel.Body><div style={{ height: 'auto' }}><Table framed … /></div></Panel.Body>
|
|
360
|
+
// ✓ 表格直接进 Body
|
|
361
|
+
<Panel.Body><Table framed … /></Panel.Body>
|
|
362
|
+
|
|
363
|
+
// ✗ 两栏自己画分隔线 / 高度写 100vh
|
|
364
|
+
<aside style={{ borderRight: '1px solid var(--ui-line)' }}>…</aside>
|
|
365
|
+
<div style={{ height: '100vh' }}>…</div>
|
|
366
|
+
// ✓ 分隔线与高度都归骨架
|
|
367
|
+
<Page.Split :sidebar="{ width: 208, content: () => <Panel title="业务域" /> }">…</Page.Split>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### 三种页面模板(起手式)
|
|
371
|
+
|
|
372
|
+
| 模板 | 骨架 | 典型 |
|
|
373
|
+
| --- | --- | --- |
|
|
374
|
+
| A 列表页 | `Page.Header` + `Page.Body mode="plain"` + `Page.Footer`,区域内用 `Panel` 包 `Table` | 技能列表、值映射列表 |
|
|
375
|
+
| B 双栏页 | `Page.Body` 内放 `Page.Split`,侧栏用 `Panel` 包 `Nav` | 组织架构 + 明细 |
|
|
376
|
+
| C 详情 / 向导页 | `Page.Header` + `Page.Body mode="scroll"`,内容用 `Form` / `Panel` 分段 | 详情、向导步骤 |
|
|
377
|
+
|
|
378
|
+
自检见 §7 第 13–20 问。
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## §6 L3 件与词表(`ui` 的面 → 细节)
|
|
383
|
+
|
|
384
|
+
### 条款
|
|
385
|
+
|
|
386
|
+
| 条款 | 要什么(闭集) | 为什么 |
|
|
387
|
+
| --- | --- | --- |
|
|
388
|
+
| **L3-1 件白名单** | 只用导出表里的件;复合成员**只走点号**;**先查 `.d.ts` 再写** | README 与文档会漂移(`Page.Toolbar` / `Input.Group` / `Input.Chip` 都曾出现在文档里,实际不存在) |
|
|
389
|
+
| **L3-2 外观只走语义 prop** | 改外观**只能**用件自己的 prop(语义词表共 **26 个维度**,逐个列在 §0 指的那份 `.d.ts` 里)或**换件**;**不得写 CSS 改外观** | 「不写 CSS 也能改外观」是这套库的全部答案 |
|
|
390
|
+
| **L3-3 外观来源白名单** | **外观**只能来自这几处:`@manohub/ui`(子路径只认 `styles.css`)· `@manohub/icon`(或 `/glyphs`)· `@manohub/theme`(只认 `default.css` / `farris.css`)· `@manohub/kit/entry`(编排,零外观)。**不得从别处取外观**:不引第三方组件库 / CSS 框架 / 图标库,不直连底层组件库,不自绘(含手写 `<svg>` 与文字符号)。**不在本条款管辖内的**(它们不产生外观):① 框架与运行时(`vue` / `vue-router` / `pinia` / `@tanstack/vue-query` / `vue-i18n` / 微前端运行时);② 本仓**业务域自有包**(`@manohub/*` 里不在上列者,如 `@manohub/api-client`);③ **不产生外观的行为库**(拖拽 / 虚拟滚动 / 图表计算一类,只给行为与数据结构)。判据是「**它是不是外观来源**」,不是「它是不是依赖」 | 白名单管的是外观的**来源**。写成「import 清单」会把业务包与行为库一起圈进来 ⇒ 每个已迁移应用都命中一条「不可登记」的违规,而那条违规与「不引第三方组件库」的立法本意毫无关系 |
|
|
391
|
+
| **L3-4 交互不走原生控件** | 按钮 / 输入 / 下拉 / 文本域一律用组件;`<button>` `<input>` `<select>` `<textarea>` **带 `class` 承载外观**违规;**无 `href` 的 `<a class>`** 违规(「按钮的伪装」) | 原生控件拿不到令牌、键盘与语义也不对 |
|
|
392
|
+
| **L3-5 浮层归组件与服务层** | 模态 → `Dialog`;贴边 → `Drawer`;轻提示 → `toast()`;确认 / 告知 → `confirm()` / `alert()`;忙等 → `showLoading()`。**不自己写 `position: fixed` + `z-index` 的遮罩** | 自建遮罩拿不到令牌,也逃不过微前端的 `scopecss` |
|
|
393
|
+
|
|
394
|
+
**L3-6 服务层宿主落回本应用容器**:解析顺序是 `configureHost(el)` → `[data-manohub-ui]` → `document.body`。
|
|
395
|
+
微前端下**必须落回自己容器**,否则「样式全丢」(浮层飘到宿主 `body` 上,拿不到令牌)。
|
|
396
|
+
`showLoading()` 是**引用计数**的,`finally` 里关自己那一次。
|
|
397
|
+
|
|
398
|
+
### 件怎么用:高频口径
|
|
399
|
+
|
|
400
|
+
- **表单**:用 `Form` + `Form.Item`。**表头只有一个**(`Form.Header`,归位到最前)。要分组就用
|
|
401
|
+
`Panel` 分段或拆成多个区域——没有「表单内多分组小节」这一件。并排字段用 `Form columns={2}`。
|
|
402
|
+
只读详情同样走 `Form` + `Form.Item` 的 `text` 行,不要另建一套描述列表。
|
|
403
|
+
- **筛选**:`Filter` + `Filter.Item`,条件字段的控件自己放。值进出已经归一(空串 = 不过滤),
|
|
404
|
+
**不要**再自己拼「全部」选项。分页器 `Pagination.modelValue` 是 **1 基**页码。
|
|
405
|
+
- **区域容器**:`Panel`(无框、三段、管区域内滚动)。**不要**用 `Card` 当区域容器——
|
|
406
|
+
`Card` 是**内容卡片**(可选、可点、可禁用),给网格项与信息块用。
|
|
407
|
+
- **弹窗与抽屉**:`Dialog`(原生 `<dialog>`:遮罩 / Esc / 焦点陷阱全归浏览器)/ `Drawer`(贴边,
|
|
408
|
+
正文是唯一可滚的一段)。两者的关闭位、Esc、点遮罩**都只回调**,可见性归调用方——
|
|
409
|
+
不要指望组件自己消失。
|
|
410
|
+
- **导航与树**:侧栏导航用 `Panel title=…` 包 `Nav`(**不要**把 `Nav` 直接塞进 `Page.Split` 的侧栏,
|
|
411
|
+
区域头就没地方给了);`Nav` 的行渲染与受控展开走 `Tree`。展开态由 `expandedKeys` 受控持有,
|
|
412
|
+
**不要靠「重挂组件」保展开态**。
|
|
413
|
+
- **分隔线**:要一条线用 `Divider`。`Panel` / `Card` / `Table` 的框线是它们自己的边界,不要再叠一条。
|
|
414
|
+
- **`Layout`**:`Layout.Row` 排**行**(行与行上下相邻)、`Layout.Column` 排**列**(列与列左右相邻)。
|
|
415
|
+
**方向与 CSS 属性名相反是刻意的**(判据是「读表格」)。间距只开放 `none/sm/md/lg/xl` 档,
|
|
416
|
+
不开放任意 px——间距一旦能随手写,同一页面就会出现 6/7/9/10/14 这些「看着差不多」的值。
|
|
417
|
+
|
|
418
|
+
### 正误对照
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
// ✗ 从别处取外观 / 用不存在的成员 / 传底层风格的 prop
|
|
422
|
+
import { Table } from '@farris/ui-vue'
|
|
423
|
+
import { Button } from '@manohub/ui/dist/components/button'
|
|
424
|
+
<Page.Toolbar /> <Input.Group />
|
|
425
|
+
<Table valueField="id" :rowOption="{ … }" />
|
|
426
|
+
// ✓
|
|
427
|
+
import { Page, Panel, Table, Button } from '@manohub/ui'
|
|
428
|
+
import { repositoriesApi } from '@manohub/api-client' // 业务包:不产生外观,L3-3 不管
|
|
429
|
+
import Sortable from 'sortablejs' // 行为库:不产生外观,L3-3 不管
|
|
430
|
+
<Page.Header :extra="<Search … />" />
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
自检见 §7 第 21–24 问。
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## §7 自检清单
|
|
438
|
+
|
|
439
|
+
**写完后逐条回答「符合 / 不符合 + 位置」。** 不要跳——跳过的那些正是会出问题的地方。
|
|
440
|
+
|
|
441
|
+
**L0**
|
|
442
|
+
|
|
443
|
+
1. 我的样式链前两条 `@import` 逐字对吗?顺序对吗(theme 在前)?**入口基线**(`html` / `body` / `#app` 的高度链)给了吗(在 ③ 里或入口 HTML 里)?
|
|
444
|
+
2. 有没有自己写 `data-manohub-ui`?有没有依赖 `.app-container`?
|
|
445
|
+
3. `createSubApp` 还是只有一处?`createI18n` 是 0 次吗?
|
|
446
|
+
|
|
447
|
+
**L1**
|
|
448
|
+
|
|
449
|
+
4. 我写的每个 CSS 属性,都在 §3 的白名单里吗?把不在的列出来。
|
|
450
|
+
5. 我写的每个 `var(--xxx)`,在 theme 六片或 `<件>.tokens.css` 里搜得到吗?
|
|
451
|
+
6. 我有没有**新增** `--` 开头的名字(而不是重设已有令牌的值)?
|
|
452
|
+
7. 类名都以本仓命名空间开头吗?`class=` 里每个 token 都过了吗?有没有 `.mh-` / `!important` / `rem` / 裸元素选择器 / `tailwind`?(**入口基线的 `html` / `body` / `#app` 除外** —— 那是唯一例外)
|
|
453
|
+
8. 尺寸值都取令牌了吗?
|
|
454
|
+
|
|
455
|
+
**L1.5**
|
|
456
|
+
|
|
457
|
+
9. 我这次用到的每个图形都是 `<Icon name="…">` 吗?有没有手绘 `<svg` / 用 `✓ ⌄ ×` 一类文字符号冒充?
|
|
458
|
+
10. 我有没有给 `<Icon>` 传 `size`?尺寸是在 CSS 里写 `var(--ui-font-icon)` 吗?
|
|
459
|
+
11. 我有没有给图标写颜色(`color` / `stroke` / `fill` / `stroke-width`)?面层出现 `rotate(` 了吗(只允许 `loading` 的转圈)?
|
|
460
|
+
12. 我用的 `name` 在 `IconNameList` 里搜得到吗?方位是 `chevron-*` 吗?有没有把 `back` 当方位?
|
|
461
|
+
|
|
462
|
+
**L2**
|
|
463
|
+
|
|
464
|
+
13. 这页有几个 `<Page`?成员只用那 5 个吗(`Filter` / `Header` / `Body` / `Footer` / `Split`)?有没有自绘页头 / 面板头 / 表格框?
|
|
465
|
+
14. 每个成员都是 `Page` 的**直接子节点**吗?有没有被任何元素或 `<template>` 包住?
|
|
466
|
+
15. 页头走的是内置布局还是自定义出口?**两者混了吗**(不传 `title` 却还传了 `subTitle` / `extra`)?
|
|
467
|
+
16. `extra` 里放的是**页面级**控件吗?有没有区域级的筛选 / 操作被提到页头?
|
|
468
|
+
17. 每个区域的筛选都落在 `toolbar`、操作都落在 `actions` 吗?有没有互换?字段 >3 个上提了吗?同一个字段摆了两处吗?
|
|
469
|
+
18. 区域头是四种形态里的哪一种(有 `title` / 只有两位 / 三样都没给 / 完全自定义)?完全自定义的登记了吗?简写与显式三段混用对吗?
|
|
470
|
+
19. 这页的滚动归谁?`Page.Body.mode` 全页唯一吗?表格有没有被外套一层 `height:auto` 的 div?
|
|
471
|
+
20. 分页和承载表格的容器同层吗?两栏是不是 `Page.Split`(自己画了竖直边框吗)?高度写的是 `100%` 还是 `100vh`?表单并排用的是 `Form columns` 吗?三态用对件了吗?`Panel` 有没有被加上框?
|
|
472
|
+
|
|
473
|
+
**L3**
|
|
474
|
+
|
|
475
|
+
21. 我用的每个件都在导出表里吗?每个复合成员都在成员表里吗?
|
|
476
|
+
22. 我传的每个 prop 都能在 `.d.ts` 里找到吗?改外观是走词表维度而不是写 CSS 吗?
|
|
477
|
+
23. 我用到的每个 import,**有没有哪一处是从 L3-3 白名单之外取外观的**?(业务域自有包与不产生外观的行为库不在 L3-3 管辖内。)图标的入口是主入口(不是 `/glyphs`)吗?有没有用原生控件承载外观(含无 `href` 的 `<a class>`)?
|
|
478
|
+
24. 浮层走组件 / 服务层了吗?微前端下宿主落回本应用容器了吗?
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## §8 缺件与新增怎么走
|
|
483
|
+
|
|
484
|
+
组件库没有你要的件时,**按顺序**走:
|
|
485
|
+
|
|
486
|
+
1. **先找替代组合**:多数「缺件」是两件组合(过滤器 = `Input` + `Button`;摘要 = `Form.Item text` 行)。
|
|
487
|
+
2. **再查 `.d.ts`**:件可能已经有了,只是名字不同(如「图标按钮」= `Button variant="icon"`,
|
|
488
|
+
依据是 §0 的导出表与类型声明,**不是 README**)。
|
|
489
|
+
3. **确实是缺件** → 记进本工程的 `docs/kit-gaps.md`(一行一件:用途 / 缺什么 / 暂时的替代写法),
|
|
490
|
+
并在 §11 登记。
|
|
491
|
+
4. **提给 `@manohub/ui` 建件**:新件按「组件 + 面 CSS + 登记 + 导出 + 文档页」**五处齐备**,
|
|
492
|
+
少一处会静默失效。建成后把 `docs/kit-gaps.md` 里那条划掉。
|
|
493
|
+
|
|
494
|
+
已知缺件(截至 0.6.0):`DatePicker`(日期选择)、`Number`(数字输入)。
|
|
495
|
+
**不要**因此回去直连底层组件库(§6 L3-3 会拦)。
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## §9 国际化
|
|
500
|
+
|
|
501
|
+
- 实例由 `@manohub/kit` 创建(`legacy: false`),应用侧**不得** `createI18n`。
|
|
502
|
+
`createSubApp({ i18n: { messages } })` 传的是**纯 messages**(`{ zh: {...}, en: {...} }`),
|
|
503
|
+
没有 i18next 的 `translation` 包装层。
|
|
504
|
+
- 组件内用 `useI18n()`(vue-i18n 的),**不是** i18next 的 `useTranslation`。
|
|
505
|
+
- 语言来源优先级:宿主下发 > `localStorage['manohub:locale']` > `navigator` > `en`。
|
|
506
|
+
切换入口只有 `applyLocale`(`@manohub/kit/entry`),它同时写实例与持久化。
|
|
507
|
+
- 缺词条时渲染 key 本身(不抛错、不刷警告),也没有第三方语言包兜底——词条要传全。
|
|
508
|
+
- **消费侧 vite 必须 `dedupe: ['vue-i18n']`**:两份副本会各自注入不同的 inject symbol,
|
|
509
|
+
组件 `useI18n()` 解析不到本包 `app.use` 的实例——**文案全丢且不报错**。
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## §10 已知偏差与有意取舍
|
|
514
|
+
|
|
515
|
+
| 取舍 | 原因 |
|
|
516
|
+
| --- | --- |
|
|
517
|
+
| 应用侧样式只允许布局(§3) | 视觉的唯一来源是 theme;散落的视觉属性必然改一处漏一处 |
|
|
518
|
+
| 禁 `rem`、统一 px 令牌 | 不跟随根字号缩放,避免不同宿主下尺寸分叉 |
|
|
519
|
+
| `Table` 不内建三态与序号列 | 错误态归 `QueryState`(它是容器);序号列自加一列 |
|
|
520
|
+
| `Steps` 不可点(无跳转门控) | 步骤条是只读展现;「上一步 / 下一步」的按钮与门控放页面里 |
|
|
521
|
+
| `Filter` 无关键字输入与就绪轮询 | 「关键字」就是普通 `Filter.Item` + `Input`;就绪时序归页面 |
|
|
522
|
+
| `Form` 只有一个头 | 分组用 `Panel` 或拆区域——多头会让「哪个头管哪些字段」说不清 |
|
|
523
|
+
| 命令式服务不提供异步 `confirm` | 需要「确定按钮进加载态」时直接用 `Dialog` 的 `onOk`(返回 Promise 自动进加载态) |
|
|
524
|
+
| 命令式 `confirm()` / `alert()` **显式关掉**「点遮罩关闭」 | 组件形态默认**开**(与主流一致,见 §12.2);命令式一条误点就丢一次决策,故显式关掉 —— 与 AntD 的 `<Modal maskClosable>`(开)/ `Modal.confirm`(关)同一分工 |
|
|
525
|
+
| `Text` 只做六个维度(字号 / 行高 / 字重 / 语义色 / 等宽 / 截断) | 它补的是「容器里的文字层级」这条一直缺的路径;富文本解析、省略的展开交互都不归它 |
|
|
526
|
+
| 本包零样式(无 reset、无富文本预设) | 样式只有两个来源:theme(值)与 ui(面)。本包夹在中间转发样式,只会让「值的来源」说不清 |
|
|
527
|
+
| `theme` **有意不建 icons / motion 两片** | 图标不承载颜色(随文案色,见 L1.5-3);动效目前没有跨组件统一的语义。这两片是**有意未建**,不是遗漏 |
|
|
528
|
+
| **已知缺口:富文本排版暂无归属** | 此前由本包的 `.app-markdown` 预设承担,已随样式删除;`@manohub/ui` 的 `reset` 又归零了 `ul` / `ol` 的序号与 `a` 的下划线,**富文本容器必须自己补回** `list-style` / 链接样式 / 段落边距。需要富文本排版的页面暂时只能整段进 §11 登记,或在 ui 提一个 `Markdown` 件 |
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## §11 定版文件清单(人工确认,不是通行证)
|
|
533
|
+
|
|
534
|
+
某些页面有设计稿逐像素还原的要求,实现上必然带**色值字面量**与**非白名单属性**。
|
|
535
|
+
这类文件**必须登记**——但登记**不等于放行**:
|
|
536
|
+
|
|
537
|
+
- 登记的作用是**让下一个人知道**:这里为什么和 §3 不一样、改它要人工确认。
|
|
538
|
+
- **色值字面量只减不增**:登记之后新加的视觉值仍然违规。
|
|
539
|
+
- **登记不放行的是这些**(无论怎么登记都仍然违规):从白名单之外取外观(L3-3)、自绘页头 / 面板头(L2-3)、
|
|
540
|
+
原生控件承载外观(L3-4)、`!important`、`.mh-*` 覆写、令牌名拼错(L1-2)。
|
|
541
|
+
|
|
542
|
+
登记表放本工程的 `docs/figma-fidelity.md`,格式:
|
|
543
|
+
|
|
544
|
+
| 文件 | 设计稿节点 | 为什么必须还原 | 登记日期 |
|
|
545
|
+
| --- | --- | --- | --- |
|
|
546
|
+
| `views/skill-market/SkillMarket.tsx` | MH后台-0911 node 0:3904 | 甲方定版稿,色值逐像素对齐 | 2026-09-23 |
|
|
547
|
+
|
|
548
|
+
**新登记必须给出「为什么这个值改不了」**——写不出来的,就是可以走令牌的。
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## §12 升级(破坏性)
|
|
553
|
+
|
|
554
|
+
> **版本口径**:**0.6.0** 是一次四包重构(上一个已发布版本是 `@manohub/app-kit@0.4.3`);
|
|
555
|
+
> 下面 12.1 的三组变化是**同一次重构**的三个面 —— 组件换库、主题与样式链独立、契约与护栏重做,
|
|
556
|
+
> **不存在「先升到某个中间版本」的路径**,一次性做完。`AppShell` → `Page` 与 `AppTable` → `Table`
|
|
557
|
+
> 常常落在同一处代码,拆成几轮只会把同一段改两遍。
|
|
558
|
+
>
|
|
559
|
+
> **0.7.0** 是它之后的第一批修复与补齐(12.2),带去**四处行为变更** ——
|
|
560
|
+
> 从 0.6.x 升上来的应用**只需读 12.2**。
|
|
561
|
+
>
|
|
562
|
+
> **0.8.0** 的技能目录改名:随包技能目录由 `kit` / `kit-migrate` / `kit-dev` 改为
|
|
563
|
+
> `manohub-kit` / `manohub-kit-migrate` / `manohub-kit-dev`(`frontmatter.name` 同步)。
|
|
564
|
+
> 技能内容与流程一字未变,但**升级后必须手工删掉技能目录下的那三个旧目录**再重跑 `kit install` ——
|
|
565
|
+
> 安装器只管理自己白名单内的目录,不会替你清旧名,留着会让新旧两份技能同时在场。
|
|
566
|
+
|
|
567
|
+
### 12.1 四包重构(0.4.3 → 0.6.0)变了什么
|
|
568
|
+
|
|
569
|
+
**一、组件换库(`App*` 与 `.ak-*` 退场)**
|
|
570
|
+
|
|
571
|
+
| 变化 | 说明 |
|
|
572
|
+
| --- | --- |
|
|
573
|
+
| **本包不再提供组件** | `App*` 前缀名与 `App*` 组件全部退场;组件从 `@manohub/ui` 直接引 |
|
|
574
|
+
| **类名换命名空间** | `.ak-*` → 组件库的 `.mh-*`;本包只剩 `.app-container`(内部调试用) |
|
|
575
|
+
| **页面骨架换件** | `AppShell` → `Page`(`AppShell.Filter` → `Page.Filter`、`AppShell.Header.toolbar` → `Page.Header.extra`) |
|
|
576
|
+
| **命令式服务换实现** | `notify` / `messageBox` / `loading` / `modalService` → `toast()` / `confirm()` / `alert()` / `showLoading()`(**API 重新设计过,不是改名**) |
|
|
577
|
+
| **farris 退场** | `@farris/ui-vue` 不再是依赖;入口不再 `app.use(Farris)`;样式链首行的 farris CSS 移除 |
|
|
578
|
+
| **其他件换名** | `AppLayout` → `Layout`、`AppDrawer` → `Drawer`、`AppSearchBox` → `Search`;`AppIconButton` → `Button variant="icon"`、`AppSection` → `Form.Header` / `Panel` |
|
|
579
|
+
|
|
580
|
+
**二、主题与样式链独立(本包零样式)**
|
|
581
|
+
|
|
582
|
+
| 变化 | 说明 |
|
|
583
|
+
| --- | --- |
|
|
584
|
+
| **主题层独立成包** | 全局令牌(`--ui-primary` / `--ui-base-*` / `--ui-space-*` …)从 `@manohub/ui/theme/*` 搬到 **`@manohub/theme`**;`@manohub/ui/theme/*` 出口**已删除**(不留兼容指针) |
|
|
585
|
+
| **本包零样式** | `src/styles/` 整个删除:`reset.css` 与 `.app-markdown` 富文本预设不再提供,公开出口 `./styles.css` / `./reset.css` / `./markdown.css` 一并移除 |
|
|
586
|
+
| **属性锚改名** | 容器上的 `data-app-container` → **`data-manohub-ui`**;旧名不再被任何一方识别(不留兼容别名) |
|
|
587
|
+
| **作用域统一** | 主题令牌、组件令牌、服务层宿主解析**全锚 `data-manohub-ui` 一个属性** |
|
|
588
|
+
| **组件令牌变可覆盖** | 组件令牌基础值锚从「元件根类」上提到「容器锚」——表行高、label 宽这类终于能在容器上被覆盖(也可被误覆盖) |
|
|
589
|
+
| **`reset` 改由 `@manohub/ui` 提供(0.7.0)** | 元素级基线并入 `@manohub/ui/styles.css` 的**首条** import(锚 `[data-manohub-ui]`、特异性 0):应用侧不必再自己写 reset,消费方自建的全局 reset 可以移除 —— 但**富文本容器的复权要保留**(reset 归零了 `ul` / `ol` 的序号与 `a` 的下划线) |
|
|
590
|
+
|
|
591
|
+
**三、契约与护栏(本文件与 §7 自检清单)**
|
|
592
|
+
|
|
593
|
+
| 变化 | 说明 |
|
|
594
|
+
| --- | --- |
|
|
595
|
+
| **契约改成五层** | 本文件重写为 L0 / L1 / L1.5 / L2 / L3 五层,每条判据是**闭集**;原先按「检查手段」分的样式 / 接口 / 结构三域并入层内 |
|
|
596
|
+
| **`kit lint` 系列命令移除** | 三条脚本护栏(样式 / 接口 / 结构)整体下线,改为**本文 + §7 自检清单**。原 `appkit-guardrails.config.json` 不再被读取,请删除。(**0.7.0 起 `lint` 以新的立足点恢复**,见 §12.2 第三项) |
|
|
597
|
+
| **`api/*` 判据改白名单** | import 源从「黑名单」改为 5 处白名单(L3-3),更严 |
|
|
598
|
+
| **图标层进契约** | 新增 L1.5 七条(来源 / 尺寸 / 颜色 / 方位 / 裸符号 / 名清单 / 入口),原先只在包内自测 |
|
|
599
|
+
| **骨架层补齐** | L2 从「几种页面模板」扩到 20 条,含归位、直接子节点、页头两模式、区域头形态等原先没写下的规则 |
|
|
600
|
+
|
|
601
|
+
**三组一起怎么迁**:装包 → 样式链改由自己引(§1.2 两行;reset 自 0.7.0 起内含在 ② 里)→ 换主题入口 →
|
|
602
|
+
自建容器换锚 → 补富文本复权(或提件)→ 换骨架与件 → 收样式与图形。类名 `class="app-container"` 不必动
|
|
603
|
+
(已降级为本包内部命名)。
|
|
604
|
+
|
|
605
|
+
### 12.2 修复与补齐(0.6.x → 0.7.1)变了什么
|
|
606
|
+
|
|
607
|
+
#### 一、行为变更(五处,逐条确认)
|
|
608
|
+
|
|
609
|
+
| 变更 | 0.6.x | 0.7.0 | 为什么要改 / 怎么迁 |
|
|
610
|
+
| --- | --- | --- | --- |
|
|
611
|
+
| `Dialog` / `Drawer` 的 `closeOnBackdrop` / `maskClosable` | `false` | **`true`** | 点遮罩**从此会关**。组件形态跟主流(Element Plus 的 `close-on-click-modal`、AntD 的 `<Modal maskClosable>` 都是开);「防误关」是**命令式**的口径,`confirm()` / `alert()` 已显式关掉,**不受影响**。表单类弹窗若怕手滑丢内容,自己传 `:close-on-backdrop="false"` |
|
|
612
|
+
| `Upload.multiple` | `true` | **`false`** | 此前默认多选 + `maxCount` 不限量 ⇒ 单文件场景会**静默多收**。要多选显式给 `multiple` |
|
|
613
|
+
| `Notice` 的图标位 | `showIcon`(`boolean`) | **`icon`**(`boolean \| IconName`) | 与 `Toast` / `Notification` **同名同型**(三件原本三个名字)。模板里 `show-icon` → `icon` |
|
|
614
|
+
| `Search` 的触发位 | 只认 `searchText` | 新增 `action="icon"` | **取值不冲突**:不传 `action` 的行为一字未变。要「只有图标按钮可点」这一档时用它(框内的装饰放大镜会自动让位,不会同屏两个放大镜) |
|
|
615
|
+
| **`@manohub/icon` 的 7 个字形名** | `bell` / `wrench` / `heart` / `heart-filled` / `eye` / `grid-dots` / `sparkles` | **`notification` / `tool` / `favorite` / `favorite-filled` / `preview` / `apps` + `drag-handle` / `ai`** | 旧名说的是「画的是什么」,不是「该在什么场景用它」。**不给别名**(`IconName` 是 `keyof`,没有 alias 层)—— 升级后按类型报错逐个替换,对照表见 icon 包 README。`grid-dots` 原是一位两用(宫格入口 + 拖拽手柄),拆成 `apps` 与 `drag-handle` 两个字形 |
|
|
616
|
+
|
|
617
|
+
#### 二、修复(不改行为,只修外观与可用性)
|
|
618
|
+
|
|
619
|
+
| 修复 | 0.6.x 的表现 |
|
|
620
|
+
| --- | --- |
|
|
621
|
+
| `Select` 的选项行补按钮重置(`border` / `background` / `font-family` / `text-align` 四条) | 选项行呈**原生按钮外观**:灰底 + 2px 立体边框 + 居中 Arial —— 而同一个面板里的「创建 xxx」行是正常的(那个类写齐了重置) |
|
|
622
|
+
| `Dialog` 补三段 flex 布局 + 新增 `panelHeight` | 设固定高度时**底栏不贴底**(body 只有内容高,剩余空间全留在 footer 之后);且此前「固定高度」只能经 `attrs.style` 传给未定义路径 |
|
|
623
|
+
| `TabBar` / `Tabs` 补 `emits.update:value` | `v-model:value="current"` **静默失效** —— 受控值不变、内部也不更新,点 tab **毫无反应且不报错**(两件的文档注释都教 `v-model:value`) |
|
|
624
|
+
| `reset` 的两条明文禁例对齐实现 | 契约曾写「选择器里不得出现 `:root` / `html` / `body`」,而 reset 自身又要求「整链高度由消费方自理」 ⇒ 那条链既必须给、又不得写。**该条款已删除**,入口基线改为显式合法(见 §1.2) |
|
|
625
|
+
| L3-3 的措辞 | 旧文写成「import 源白名单」,把**业务域自有包**(`@manohub/api-client`)与**不产生外观的行为库**也判成越界;而契约 §11 又把「导入越界」列为**登记也不放行** ⇒ 每个已迁移应用都带着一条不可豁免的违规。**已改为「外观来源白名单」** |
|
|
626
|
+
|
|
627
|
+
#### 三、补齐(新增的能力与路径)
|
|
628
|
+
|
|
629
|
+
- **`drag-handle`**(`@manohub/icon`):6 点拖拽手柄(2 列 × 3 行)。原先列表排序借的是 `grid-dots`,
|
|
630
|
+
而 `grid-dots` 同时还是「我的技能」的宫格入口 —— 一个字形担两个语义,改一处必然误伤另一处。
|
|
631
|
+
- **`Text`**(`@manohub/ui`):最小排版件(`size` / `tone` / `weight` / `mono` / `truncate` / `as`),
|
|
632
|
+
**消费 theme 一直有、却没有件消费的那套 `--ui-font-*` 档位令牌**。此前「容器里的文字层级」
|
|
633
|
+
(卡片描述、只读值、次级信息)在应用侧无合规落点,每个应用都要为它留一条定版例外登记 ——
|
|
634
|
+
这条路径至此补上。
|
|
635
|
+
- **`Dialog.tone` 与服务层 `tone`**:`alert()` / `confirm()` 现在能表达 `info` / `success` / `warning` / `error`
|
|
636
|
+
四档(标题左侧一个裸符号 + 配色),`confirm({ tone: 'error' })` 还会把确定按钮自动染成危险色 ——
|
|
637
|
+
「删除确认」与「保存成功」不再长得一样。
|
|
638
|
+
- **`reset` 进驻 `@manohub/ui/styles.css` 首条**(0.6.x 里由应用自补):
|
|
639
|
+
**已经在 `app.css` 自补过 reset 的应用,可以删掉其中「盒模型 / 按钮外观 / 列表序号 / 链接 / 标题字阶」这一族
|
|
640
|
+
与「元素 margin 归零」**(② 已经给了);但**高度链(`html` / `body` / `#app`)必须留着** ——
|
|
641
|
+
那是入口基线,包的 reset **有意不写**(见 §1.2)。
|
|
642
|
+
- **`kit lint` 恢复**(`pnpm exec kit lint --root apps/<app> --namespace <前缀>`):
|
|
643
|
+
0.6.0 整批下线后,三个应用迁完就累计出「3 份重复 reset / 1 处登记描述错误 / 1 处注释引错文件名」——
|
|
644
|
+
都是「有脚本就当场红」的类型。恢复的版本**换了立足点**:不查目录结构,只查本契约里能机械判定的四组
|
|
645
|
+
(`style` 红线条 / `source` 外观来源 / `namespace` 类名前缀 / `property` 属性闭集)。
|
|
646
|
+
⚠️ 它是**应用侧工具**:L1-4 / L1-6 约束的是「应用自绘的东西」,对四包源码跑必然满屏假红
|
|
647
|
+
(脚本按包名直接拦住并提示该给哪个根)。**它只覆盖能机械判定的部分** ——
|
|
648
|
+
组件默认值是否与文档一致、骨架归位、三态分工这些仍走 §7 自检清单。
|
|
649
|
+
|
|
650
|
+
#### 四、0.7.1 的破坏性改名(**版本口径破例,逐条确认**)
|
|
651
|
+
|
|
652
|
+
⚠️ **破例声明**:本仓 `AGENTS.md` 的硬约束写「**0.x 单线,破坏性变更升次版本并通知消费方**」,
|
|
653
|
+
下面四项都属破坏性变更,按规矩该落 **0.8.0**。`0.7.1` 这个号是本地 tgz 直装联调期就定下的基线号,
|
|
654
|
+
发包时经裁定**维持 0.7.1、不再补发 0.8.0**(四包 2026-09-28 已发到 npm 官方仓)。
|
|
655
|
+
登记于此供后续升级与本仓的版本裁定参照 —— **下次同类批次请直接按硬约束升次版本**。
|
|
656
|
+
|
|
657
|
+
| 变更 | 0.7.0 | 0.7.1 | 为什么要改 / 怎么迁 |
|
|
658
|
+
| --- | --- | --- | --- |
|
|
659
|
+
| `Tabset` 更名 **`Tabs`** | `Tabset`(容器 / 骨架) | **`Tabs`** | `Tabs` 这个名字本就该给容器,`Tabset` 是历史叫法。类名 `.mh-tabset*` → `.mh-tabs*` |
|
|
660
|
+
| `Tabs` 更名 **`TabBar`** | `Tabs`(页签条) | **`TabBar`** | 旧 `Tabs` 只画那条页签条,和容器同名同族;改名后「条」与「容器」各归其位。类名 `.mh-tabs*` → `.mh-tabbar*`,令牌 `--ui-tabs-*` → `--ui-tabbar-*` |
|
|
661
|
+
| **零 JS 档位表退役** | `operations/tabs.css` 里有一张 1..8 档的 `:has(...)` 显形表 | **删除** | 它要求「上限 8 档 + 开关与面板同序 + 面板是 `.mh-tabs-panels` 直接子级」三条同时成立,实用面太窄,且 CSS 写不出通式。面板显隐**从此只剩组件路径**一条(按受控值给非选中面板下发 `hidden`)—— 纯 CSS 手写页签不再显形 |
|
|
662
|
+
| `.mh-tabs-panel` 不再钉 `display` | `display: block` | `flex: 1 1 auto` + `min-height: 0` | 钉 `display` 会顶掉消费方给的布局(实测把抽屉里的页签按钮挤到顶部)。面板高度从此由消费方与自身内容决定 |
|
|
663
|
+
|
|
664
|
+
**怎么迁**:`import { Tabset } from '@manohub/ui'` → 改名 `Tabs`;原先只当「页签条」用的 `Tabs` → 改名 `TabBar`。
|
|
665
|
+
新增 **`TabPanel`** 作为 `Tabs` 的信息入口(`name` 既是页签文字、也是缺省项值):写了 `TabPanel` 就以它为准,
|
|
666
|
+
否则回落「按顺序配对」旧写法,两条路不叠加;`selector` 插槽拿到的 `options` 就是提取出来的项。
|
|
667
|
+
**本仓内唯一消费点**是 `apps/mcp` 的配置抽屉(`mcp-config-drawer.tsx`),已随迁移同步。
|
|
668
|
+
|
|
669
|
+
### 12.3 存量应用怎么迁
|
|
670
|
+
|
|
671
|
+
按**层**推进,每步单独可跑通(这也是最优顺序):
|
|
672
|
+
|
|
673
|
+
1. **L0 自查**(不可豁免,最先看):样式链两行 + 一个入口 + 锚。
|
|
674
|
+
2. **L2 换骨架**:先按 §5 的三种模板把页面骨架摆对——这一步收益最大、风险最低。
|
|
675
|
+
3. **L3 逐件换**:按 §6 换件与 prop。旧 `App*` 名与底层风格 prop 的对照见
|
|
676
|
+
`skills/manohub-kit-migrate/references/migration-map.md`。**重点坑**:分页 `page`(0 基) →
|
|
677
|
+
`Pagination.modelValue`(1 基)、`Table.rows` → `Table.data`、`Textarea.maxLength` → `maxlength`、
|
|
678
|
+
`Tooltip.placement` → `side` + `align`、`Steps.items/modelValue` → `steps/current`。
|
|
679
|
+
4. **L1 收样式**:最后清应用 CSS——这一步做起来最快,但**前提是前三步已成**(否则你会把
|
|
680
|
+
「本该换件解决的问题」当成样式问题去改)。
|
|
681
|
+
5. **L1.5 收图形**:全量搜 `<svg` / 文字符号 / `size=` / `rotate(`。
|
|
682
|
+
|
|
683
|
+
逐文件的操作口径与验收清单见 `skills/manohub-kit-migrate/references/migration-playbook.md`。
|