@manohub/kit 0.6.0

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.
Files changed (36) hide show
  1. package/CONTRACT.md +597 -0
  2. package/README.md +158 -0
  3. package/bin/kit.mjs +118 -0
  4. package/dist/composables/use-client-pagination.d.ts +43 -0
  5. package/dist/composables/use-client-pagination.js +28 -0
  6. package/dist/entry/create-query-client.d.ts +8 -0
  7. package/dist/entry/create-query-client.js +14 -0
  8. package/dist/entry/create-sub-app.d.ts +81 -0
  9. package/dist/entry/create-sub-app.js +111 -0
  10. package/dist/entry/index.d.ts +4 -0
  11. package/dist/entry/index.js +13 -0
  12. package/dist/entry/initial-guard.d.ts +12 -0
  13. package/dist/entry/initial-guard.js +23 -0
  14. package/dist/index.d.ts +19 -0
  15. package/dist/index.js +7 -0
  16. package/dist/providers/locale-detection.d.ts +35 -0
  17. package/dist/providers/locale-detection.js +44 -0
  18. package/dist/providers/setup-i18n.d.ts +50 -0
  19. package/dist/providers/setup-i18n.js +42 -0
  20. package/dist/services/app-container.d.ts +31 -0
  21. package/dist/services/app-container.js +22 -0
  22. package/dist/services/app-context.d.ts +4 -0
  23. package/dist/services/app-context.js +11 -0
  24. package/dist/services/index.d.ts +7 -0
  25. package/package.json +68 -0
  26. package/skills/README.md +78 -0
  27. package/skills/install.mjs +299 -0
  28. package/skills/kit/SKILL.md +85 -0
  29. package/skills/kit/references/adoption.md +170 -0
  30. package/skills/kit/references/contract-index.md +76 -0
  31. package/skills/kit-dev/SKILL.md +117 -0
  32. package/skills/kit-dev/references/page-recipes.md +350 -0
  33. package/skills/kit-dev/references/style-rules.md +47 -0
  34. package/skills/kit-migrate/SKILL.md +125 -0
  35. package/skills/kit-migrate/references/migration-map.md +294 -0
  36. package/skills/kit-migrate/references/migration-playbook.md +188 -0
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: kit-dev
3
+ version: 1.0.0
4
+ description: 按 @manohub/kit 骨架层规范做日常页面与组件开发:选页面模板、选组件与 prop、守样式纪律、处置缺件、收工前自检。触发条件:用户要求写或改子应用页面、要求按骨架层规范实现列表页/表单/弹窗/树/分页/加载空错三态、指定用 Page/Panel/Table/Form 等组件库件、或询问某个页面写法是否正确时使用。
5
+ ---
6
+
7
+ # kit-dev:日常开发辅助
8
+
9
+ 面向**已接入**骨架层的应用。任务是把需求落到「契约允许的写法」上,而不是边写边试。
10
+
11
+ 组件本身有哪些 prop —— 查 `node_modules/@manohub/ui/README.md` 与类型声明;
12
+ 本技能管的是**页面怎么搭**。**合规判据在契约里,本技能不复述条款。**
13
+
14
+ ## 一、开工前必做
15
+
16
+ 1. 读 `node_modules/@manohub/kit/CONTRACT.md` 的相关章节(用 `../kit/references/contract-index.md` 按主题定位)。
17
+ **优先级**:契约 > `@manohub/ui` 的 README 与 `@example`(`node_modules/@manohub/ui/dist/**/*.d.ts`)
18
+ > 消费仓 `AGENTS.md` > 其它文档;冲突时按此优先级执行。
19
+ 2. 先定四件事,再动手写:**页面模板**、**两级滚动归属**、**操作位**、**分页归属**(都在契约 §5)。
20
+ 这四件错了,组件用得再对也要返工。
21
+ 3. 组件不存在时先查契约 §8 缺件处置流程(那里给了替代口径与登记方式),
22
+ **不在应用侧自绘近似件,也不回去直连底层组件库**。
23
+
24
+ ## 二、按意图取配方
25
+
26
+ 配方文件给的是**可直接抄的形状**;判据仍在契约。
27
+
28
+ | 需求 | 取用 |
29
+ |---|---|
30
+ | 新页面(列表 / 表单 / 双栏) | `references/page-recipes.md`「页面模板」;起手式见契约 §5 末「三种页面模板」 |
31
+ | 表格、筛选、分页、多选 | `references/page-recipes.md`「表格」;操作位与滚动归属见契约 §5「组 4 · 区域」 |
32
+ | 表单、校验、只读摘要行 | `references/page-recipes.md`「表单」;契约 §5「组 5 · 版式」 |
33
+ | 弹窗 / 抽屉 / 命令式提示 | `references/page-recipes.md`「浮层与提示」;契约 §6 |
34
+ | 树、导航、卡片单选、步骤条 | `references/page-recipes.md`「树 / 卡片单选 / 步骤条」;契约 §6 |
35
+ | 加载 / 空 / 错三态 | `references/page-recipes.md`「三态」;契约 §5「组 5」 |
36
+ | 行 / 列怎么排、间距档 | `references/page-recipes.md`「布局」;契约 §5「组 5」 |
37
+ | 样式怎么写、哪些禁止 | 契约 §3 L1(含属性白名单与局部换肤);`references/style-rules.md` 给替代写法 |
38
+ | 图标怎么用 | 契约 §4 L1.5 |
39
+ | 某个写法是不是违规 | 读契约对应层的「正误对照」段,不要凭经验判断 |
40
+
41
+ ## 三、改代码前先读(判据都在契约里)
42
+
43
+ | 你要动什么 | 先读 |
44
+ |---|---|
45
+ | 页面结构(加 / 挪成员、改页头、调滚动归属、放筛选与分页、表单分栏) | 契约 §5 L2 |
46
+ | 任何 CSS 属性(含「只是想微调一下外观」的念头) | 契约 §3 L1(判据)+ `references/style-rules.md`(替代写法) |
47
+ | 挑件、传 prop、换外观档位 | 契约 §6 L3;件名与成员以 `.d.ts` 为准 |
48
+ | 加图标 | 契约 §4 L1.5 |
49
+ | 入口 / 容器锚 / 依赖来源 | 契约 §2 L0 —— **这一层不可豁免**:漏了它,上面三层写得再对也不生效 |
50
+
51
+ 三件最容易出错的**事实**(判据见契约,这里只提醒它们存在):
52
+
53
+ 1. **受控口径**:表单与选择族「传 `modelValue` 即受控,不传即由 `defaultValue` 起」——
54
+ 别把值藏在组件内部再通过 ref 读(细节见契约 §6)。
55
+ 2. **骨架职责不在应用侧**:入口编排、pinia、路由、微前端协议、宿主语言监听都由工厂装配;
56
+ 自己再写一份会和工厂的协议打架(细节见契约 §2)。
57
+ 3. **浮层有对应的件与服务**:模态 / 抽屉 / 命令式提示各有归属,自己写遮罩拿不到令牌,
58
+ 也逃不过微前端的 `scopecss`(细节见契约 §6)。
59
+
60
+ ## 四、本包不管的三件事(别在这里找写法)
61
+
62
+ 1. **数据获取**:走消费仓既有方式(`@tanstack/vue-query` / 自建 api 层)。本包只**收结果** ——
63
+ 把 `isLoading` / `error` / `data` 映射到 `QueryState` 的 `loading` / `error` / `empty`
64
+ 与 `Table` 的 `data`,不要为「加载中」自绘遮罩或骨架,也不要引入新的请求库。
65
+ 2. **文案与 i18n**:`createSubApp({ i18n: { messages } })` 是 **vue-i18n** 语义,文案用 `useI18n()` 的 `t()`;
66
+ 实例由骨架层唯一创建并注册,应用侧**不要自己 `createI18n`**(契约 §9)。
67
+ 组件内建的中文只是兜底,页面文案一律自己传。
68
+ 3. **路由与权限**:路由表由应用维护并交给 `createSubApp({ routes })`;权限判断、菜单、面包屑属消费仓。
69
+
70
+ ## 五、`.vue`(SFC)项目怎么用这些配方
71
+
72
+ 配方以 TSX 书写(消费方主流形态)。SFC 项目按等价转写:
73
+
74
+ | TSX 配方 | SFC 写法 |
75
+ |---|---|
76
+ | `modelValue={x} onChange={(v) => (x = v)}` | `:model-value="x" @change="x = $event"`(或 `v-model`) |
77
+ | `extra={() => <span/>}` | `<template #extra><span/></template>` |
78
+ | `footer={() => <>…</>}` | `<template #footer>…</template>` |
79
+ | `columns={cols}` + `#cell` 插槽 | 列定义放 `setup` 的常量;单元格内容走 `<template #cell="{ row, column, value }">` |
80
+ | `Layout.Row` / `Layout.Column` | `<Layout.Row>` / `<Layout.Column>`(点号成员在模板里直接可用) |
81
+
82
+ ⚠️ **结构合规靠自检,不靠工具** —— 0.6.0 起没有机器规则兜底,SFC 与 TSX 一视同仁:
83
+ 收工前必须**人工过一遍契约 §7 自检清单**,并按 §5 的骨架条款核对页面结构
84
+ (尤其「成员必须是 `Page` 直接子节点」这一条:`<template v-if>` 包一层就认不出成员)。
85
+
86
+ ## 六、收工前自检(必做)
87
+
88
+ ```bash
89
+ pnpm exec vue-tsc --noEmit # 类型
90
+ pnpm build # 构建
91
+ ```
92
+
93
+ 然后是**人工过契约 §7 自检清单**(全是能直接回答的问句,逐条答,不要跳过):
94
+
95
+ - L0 / L1 / L1.5 / L2 / L3 五组问句,每组对应一层;答不上来的就回去读该层条款。
96
+ - 「本条答不出」= 你自己也不知道这段代码在做什么,先搞清再提交,不要靠猜。
97
+ - 新增文件**必须整份合规**;改动文件至少保证**本次改动没有引入新的违规**。
98
+
99
+ ## 七、失败处理
100
+
101
+ | 现象 | 处理 |
102
+ |---|---|
103
+ | 想要的组件 / 能力不存在 | 先查契约 §8(替代口径与登记方式),仍不够就提缺件;应用侧不自绘 |
104
+ | 表单里要日期 / 数字控件 | 组件库暂无这两件;按契约 §8 用 `Input` 顶住并标注格式,或提缺件 |
105
+ | 组件渲染了但样式不对 | 检查 `src/style.css` 三行的导入与顺序(契约 §1.2,本包已不转发样式);不要在页面里补视觉样式 |
106
+ | 视觉想「微调一下」 | 走契约 §3 的**局部换肤**(在更深容器重设**已有**令牌的值)或用语义 prop;**不得**发明新令牌名 |
107
+ | 命令式提示样式全丢 | 提示没落回应用容器:确认入口走的是 `createSubApp`,别自己手写 `createApp` |
108
+ | `showLoading` 关不掉 | 引用计数没配平:用返回的句柄在 `finally` 里关,别用「开始显示 / 结束隐藏」的一对布尔 |
109
+ | 页面结构对不对拿不准 | 读契约 §5 的骨架条款 + 「三种页面模板」,别猜;SFC 没有工具兜底 |
110
+
111
+ ## 八、参考
112
+
113
+ - `../kit/references/contract-index.md`:按主题定位契约章节
114
+ - `references/page-recipes.md`:页面模板与高频场景配方
115
+ - `references/style-rules.md`:样式纪律的替代写法(判据以契约 §3 为准)
116
+ - `node_modules/@manohub/ui/README.md`:组件清单与 API 入口
117
+ - `node_modules/@manohub/kit/CONTRACT.md`:规范唯一事实源(§7 是收工自检清单)
@@ -0,0 +1,350 @@
1
+ # 页面与高频场景配方
2
+
3
+ 配方以 TSX 书写(消费方主流形态);SFC 的等价转写见 `../SKILL.md` 第五节。
4
+ 契约条款:`node_modules/@manohub/kit/CONTRACT.md`;组件 API:`node_modules/@manohub/ui/README.md`。
5
+
6
+ **先定四件事再动手**:页面模板、两级滚动归属、操作位、分页归属(都在契约 §5)。
7
+
8
+ > 本文只给**可直接抄的形状**。判据以契约为准;形状与契约冲突时按契约改,并把本文件当缺陷。
9
+
10
+ ---
11
+
12
+ ## 一、三种页面模板(契约 §5 末「三种页面模板」)
13
+
14
+ ### 模板 A:列表页
15
+
16
+ ```tsx
17
+ import { Button, Input, Page, Panel, Pagination, QueryState, Table } from '@manohub/ui'
18
+
19
+ export default function SkillList() {
20
+ const query = useSkillList() // 数据层归应用(vue-query / 自建 api)
21
+
22
+ return (
23
+ <Page>
24
+ <Page.Header title="技能列表" extra={<Input placeholder="搜索技能名称" clearable />} />
25
+ <Page.Body mode="plain">
26
+ <Panel
27
+ title="技能列表"
28
+ actions={<Button variant="primary">新建</Button>}
29
+ >
30
+ <QueryState
31
+ loading={query.isLoading.value}
32
+ error={query.error.value?.message}
33
+ empty={!query.isLoading.value && query.data.value.length === 0}
34
+ emptyActionText="新建"
35
+ onEmptyAction={openCreate}
36
+ >
37
+ <Table
38
+ data={query.data.value}
39
+ columns={columns}
40
+ rowKey="id"
41
+ sortKey={sortKey.value}
42
+ sortOrder={sortOrder.value}
43
+ onSort={onSort}
44
+ onRow={(row) => ({ onClick: () => open(row) })}
45
+ />
46
+ </QueryState>
47
+ <Panel.Footer>
48
+ <Pagination v-model="page" v-model:page-size="pageSize" :total="query.total.value" />
49
+ </Panel.Footer>
50
+ </Panel>
51
+ </Page.Body>
52
+ </Page>
53
+ )
54
+ }
55
+ ```
56
+
57
+ - `Page.Body mode="plain"`:主体不滚,滚动下沉到 `Panel.Body`(表格类页面的标准姿态)。
58
+ `plain` 是库缺省,不写也一样 —— 但显式写出来更清楚。
59
+ - 错误态归 `QueryState`(**不要**塞进 `Table`);空态也用 `QueryState` 的 `empty`,
60
+ 或者用 `Table` 的 `emptyText` / `#empty` 槽做轻量空态。
61
+ - 分页放 `Panel.Footer`(表格在 `Panel` 里 —— 契约 §5 组 4)。
62
+
63
+ ### 模板 B:双栏页
64
+
65
+ ```tsx
66
+ import { Nav, Page, Panel } from '@manohub/ui'
67
+
68
+ <Page>
69
+ <Page.Header title="组织架构" />
70
+ <Page.Body mode="plain">
71
+ <Page.Split
72
+ sidebar={{
73
+ width: 240,
74
+ content: () => (
75
+ <Panel title="业务域">
76
+ <Nav nodes={tree} rowKey="id" selected={selected.value} onSelect={onSelect} />
77
+ </Panel>
78
+ ),
79
+ }}
80
+ >
81
+ <Panel title="明细">…</Panel>
82
+ </Page.Split>
83
+ </Page.Body>
84
+ </Page>
85
+ ```
86
+
87
+ - 侧栏内容是**渲染函数**(不是插槽):`sidebar.content: () => VNode`。
88
+ - 侧栏的头归 `Panel`(不要把 `Nav` 直接塞进 `Split` 的侧栏 —— 区域头就没地方给了)。
89
+ - `Split` 的分隔线即拖拽手柄,收起 / 展开把手都内建,**不要再自绘竖直边框**。
90
+ - 栏内滚动由栏内 `Panel.Body` 承担:`Split` 自身不滚(契约 §5 组 4)。
91
+
92
+ ### 模板 C:详情 / 向导页
93
+
94
+ ```tsx
95
+ <Page>
96
+ <Page.Header title="值映射详情" extra={<Button variant="primary">保存</Button>} />
97
+ <Page.Body mode="scroll">
98
+ <Form labelWidth={96} columns={2}>
99
+ <Form.Header title="基本信息" description="保存后进入待准入队列" />
100
+ <Form.Item label="编码" required><Input modelValue={code.value} onChange={(v) => (code.value = v)} /></Form.Item>
101
+ <Form.Item label="名称" required><Input modelValue={name.value} onChange={(v) => (name.value = v)} /></Form.Item>
102
+ <Form.Item label="来源表" text={source.value} />
103
+ </Form>
104
+ </Page.Body>
105
+ </Page>
106
+ ```
107
+
108
+ `mode="scroll"`:内容长、主体自己滚。要分组(多个小节)就用多个 `Panel` 分段 ——
109
+ `Form.Header` 是**单头**(契约 §5 组 5)。
110
+
111
+ ---
112
+
113
+ ## 二、表格(`Table`)
114
+
115
+ ```tsx
116
+ const columns: TableColumn[] = [
117
+ { key: 'name', title: '名称', width: '240px' },
118
+ { key: 'type', title: '类型', width: '120px' },
119
+ { key: 'status', title: '状态', width: '100px' },
120
+ { key: 'ops', title: '操作', align: 'right', width: '120px' },
121
+ ]
122
+
123
+ <Table data={list.value} columns={columns} rowKey="id" selectable={true} v-model={selected.value} />
124
+ ```
125
+
126
+ - **`data` 始终是当前页数据** —— 组件不切片(服务端分页与客户端分页的口径差别留在页面里)。
127
+ - **排序算法与列宽拖拽归调用方**:`sortKey` / `sortOrder` 是受控显示态(不回写箭头不变),
128
+ 点排序发 `onSort({ key, order })`。
129
+ - **单元格渲染两条路,只走一条**:给了 `#cell` 就接管所有单元格;不给就用列的 `align="right"`
130
+ 渲染 `#actions` 槽(行尾操作区)。
131
+ - **错误态不要塞进表格**:外面套 `QueryState`。
132
+ - 序号列、外框、行高都要自己接:序号自加一列,外框由承载它的 `Panel` 给(契约 §10)。
133
+
134
+ ```tsx
135
+ // 批量操作:选中非空时批量栏出现在表格上方
136
+ <Table data={list.value} columns={columns} rowKey="id" selectable v-model={selected.value}>
137
+ {{ batch: ({ selected }) => <BatchBar rows={selected} /> }}
138
+ </Table>
139
+
140
+ // 行内操作
141
+ <Table data={list.value} columns={columns} rowKey="id"
142
+ onRow={(row) => ({ onClick: () => open(row) })}>
143
+ {{ actions: ({ row }) => <Button variant="link" onClick={() => edit(row)}>编辑</Button> }}
144
+ </Table>
145
+ ```
146
+
147
+ ⚠️ 单元格编辑器用 `Input` / `Select`:原生 `<input>` / `<button class>` 拿不到令牌,
148
+ 视觉与受控口径都要自己兜(契约 §6 L3-3)。
149
+
150
+ ---
151
+
152
+ ## 三、三态(契约 §5 组 5)
153
+
154
+ | 场景 | 件 |
155
+ |---|---|
156
+ | 区域 / 页面的加载·空·错误三态 | `QueryState` |
157
+ | 内容占位(「这里将出现什么」) | `Skeleton` |
158
+ | 正在忙、请等待(遮罩 + 拦交互) | `Loading`(或服务层 `showLoading()`) |
159
+
160
+ ```tsx
161
+ <QueryState
162
+ loading={loading.value}
163
+ error={error.value} // 字符串进副文案
164
+ empty={!loading.value && rows.value.length === 0}
165
+ skeletonRows={6}
166
+ emptyActionText="新建"
167
+ onEmptyAction={openCreate}
168
+ >
169
+ <Table … />
170
+ </QueryState>
171
+ ```
172
+
173
+ **错误态与空态分开传**:加载失败给 `error`,不要塞进 `empty`(那是「确实没有数据」的意思)。
174
+
175
+ ---
176
+
177
+ ## 四、表单(`Form` / `Form.Item`)
178
+
179
+ ```tsx
180
+ <Form labelWidth={88} columns={2} gap={16}>
181
+ <Form.Header title="技能定义">
182
+ {{ extra: () => <Button variant="primary" onClick={save}>保存</Button> }}
183
+ </Form.Header>
184
+
185
+ <Form.Item label="技能名称" required error={errors.name}>
186
+ <Input modelValue={name.value} onChange={(v) => (name.value = v)} />
187
+ </Form.Item>
188
+
189
+ {/* 有 name 就有值作用域:{ value, setValue, clear } */}
190
+ <Form.Item name="code" label="编码">
191
+ {{ default: ({ value, setValue }) => <Input modelValue={String(value ?? '')} onChange={setValue} /> }}
192
+ </Form.Item>
193
+
194
+ {/* 只读摘要行:text 给值,label 自动弱化 */}
195
+ <Form.Item label="创建时间" text={createdAt.value} />
196
+ </Form>
197
+ ```
198
+
199
+ - 校验信息走 `Form.Item` 的 `error`(文档流内的错误行),`hint` 是说明行。
200
+ - 表单**只有一个头**(`Form.Header`,会归位到最前);要分多段就用 `Panel` 分段(契约 §5 组 5)。
201
+ - 需要「表单级收集值」时给 `Form` 传 `modelValue`;命令式出口是 `getValues()` / `clear()`。
202
+
203
+ ---
204
+
205
+ ## 五、浮层与提示(契约 §6)
206
+
207
+ ```tsx
208
+ // 模态:内建底栏(okText + onOk)—— onOk 返回 Promise 自动进加载态
209
+ <Dialog v-model:open={visible.value} title="删除确认" okText="删除" okDanger
210
+ onOk={remove} onCancel={() => (visible.value = false)}>
211
+ 删除后不可恢复,确认继续?
212
+ </Dialog>
213
+
214
+ // 自绘底栏(要用别的按钮形态时)
215
+ <Dialog v-model:open={visible.value} title="详情">
216
+ 正文
217
+ {{ footer: () => <Button variant="text" onClick={() => (visible.value = false)}>关闭</Button> }}
218
+ </Dialog>
219
+
220
+ // 抽屉:贴边面板,正文是唯一可滚的一段
221
+ <Drawer v-model:open={visible.value} title="MCP 配置" :width="640" onOk={save}
222
+ onCancel={() => (visible.value = false)}>
223
+ 正文
224
+ </Drawer>
225
+ ```
226
+
227
+ ```ts
228
+ // 命令式(组件树之外:请求回调、路由守卫、工具函数)
229
+ import { toast, confirm, alert, showLoading } from '@manohub/ui'
230
+
231
+ toast('success', '保存成功') // 3 秒自动消失
232
+ toast('error', '导出失败', { action: { text: '重试', onClick: retry } })
233
+ const pending = toast('loading', '正在导入…', { duration: 0 }) // 常驻
234
+ pending.close()
235
+
236
+ if (await confirm({ title: '删除确认', detail: '删除后不可恢复', okDanger: true })) await remove()
237
+ await alert({ title: '导入完成', detail: '共 128 条' })
238
+
239
+ const done = showLoading('保存中…') // 引用计数:并发要各关各的
240
+ try { await save() } finally { done() }
241
+ ```
242
+
243
+ **可见性归调用方**:`Dialog` / `Drawer` 的关闭位、Esc、点遮罩都只回调(`update:open` + `onClose`/`onCancel`)。
244
+ 要「关闭前拦截」就在 `onOk` / `onCancel` 里自己判断(失败就不改 `open`)。
245
+
246
+ ---
247
+
248
+ ## 六、树 / 导航 / 卡片单选 / 步骤条
249
+
250
+ ```tsx
251
+ // 树:受控展开(数据刷新不丢展开态,不要靠重挂组件)
252
+ <Tree
253
+ nodes={tree.value}
254
+ rowKey="id"
255
+ expandedKeys={expanded.value}
256
+ onExpandChange={(keys) => (expanded.value = keys)}
257
+ selected={selected.value}
258
+ onSelect={(node) => (selected.value = node.id)}
259
+ renderActions={(node) => <Button variant="icon" size="sm" title="删除" icon="delete" />}
260
+ />
261
+
262
+ // 侧栏导航(行渲染与受控展开走 Tree,两者对行的几何一致)
263
+ <Panel title="业务域">
264
+ <Nav variant="tree" nodes={tree.value} rowKey="id" selected={selected.value} onSelect={onSelect} />
265
+ </Panel>
266
+
267
+ // 卡片式单选:一组互斥选项
268
+ <Radio.Card
269
+ items={[{ value: 'a', label: '按需缓存', description: '命中才拉取' }, { value: 'b', label: '全量缓存' }]}
270
+ modelValue={mode.value}
271
+ onChange={(v) => (mode.value = v)}
272
+ />
273
+
274
+ // 步骤条:**只读展现**(跳转与门控归页面)
275
+ <Steps steps={[{ title: '基本信息' }, { title: '字段映射' }, { title: '确认' }]} current={step.value} />
276
+ ```
277
+
278
+ ⚠️ `Radio.Card` 是单选组:**缺省会选中第一个可选项**(「可全不选」要显式处理)。
279
+ ⚠️ `Steps` 不可点:要「上一步 / 下一步」就用 `Button` + 自己的校验。
280
+
281
+ ---
282
+
283
+ ## 七、布局(`Layout`)
284
+
285
+ ```tsx
286
+ // 横着排一组(徽标 + 文本、标签 + 值、控件 + 按钮)→ Column
287
+ <Layout.Column gap="sm">
288
+ <Badge tone="success" shape="soft">通过</Badge>
289
+ <span>编码唯一性</span>
290
+ </Layout.Column>
291
+
292
+ // 一条一条往下堆 → Row
293
+ <Layout.Row gap="lg">…</Layout.Row>
294
+
295
+ // 一行分 n 列 + 跨格
296
+ <Layout.Row :columns="2" gap="lg">
297
+ <Layout.Column>…</Layout.Column>
298
+ <Layout.Column>…</Layout.Column>
299
+ <Layout.Column :span="2">整行内容</Layout.Column>
300
+ </Layout.Row>
301
+ ```
302
+
303
+ **方向与 CSS 属性名相反是刻意的**(判据是「读表格」):`Row` 是竖排容器、`Column` 是水平容器。
304
+ 间距只开放 `none/sm/md/lg/xl` 档 —— 别写任意 px(契约 §5 组 5)。
305
+
306
+ ---
307
+
308
+ ## 八、区块容器怎么选
309
+
310
+ | 你要什么 | 用谁 |
311
+ |---|---|
312
+ | 一块**区域**(有头、头上有工具条与操作、自己有滚动) | `Panel`(无框是硬契约) |
313
+ | 一个**表单内的区块头** | `Form.Header`(单头)或 `Panel` 分段 |
314
+ | 一个**内容卡片**(可点 / 可选 / 可禁用) | `Card`;网格项配 `ListView.CardItem` |
315
+ | 内容之间一条线 | `Divider` |
316
+ | 一段富文本 | 组件库暂无此件;元素级排版预设(原 `.app-markdown`)已随 kit 的样式一并删除,归属见契约 §10「已知缺口」 |
317
+
318
+ **不要**给 `Panel` 加描边 / 圆角 / 阴影 / 底色(那是 `Card` 的活)。
319
+
320
+ ---
321
+
322
+ ## 九、页签 / 页面级筛选 / 分页
323
+
324
+ ```tsx
325
+ // 只要页签条 → Tabs(带「更多」收纳)
326
+ <Tabs v-model={tab.value} options={[{ label: '基本信息', value: 'base' }, { label: '字段映射', value: 'fields' }]} />
327
+
328
+ // 条 + 面板切换 → Tabset(选择控件由 selector 插槽给)
329
+ <Tabset v-model={tab.value} lazy>
330
+ {{ default: () => <div>面板内容</div>, selector: () => <Capsule items={caps} modelValue={tab.value} onChange={(v) => (tab.value = v)} /> }}
331
+ </Tabset>
332
+
333
+ // 页面级筛选:条件字段写成 Filter.Item 子件,控件自放
334
+ <Page.Filter onQuery={query} onReset={reset}>
335
+ <Filter.Item code="name" label="技能名称">
336
+ {{ default: ({ value, setValue }) => <Input modelValue={value} onChange={setValue} /> }}
337
+ </Filter.Item>
338
+ <Filter.Item code="status" label="状态">
339
+ {{ default: ({ value, setValue }) => <Select modelValue={value} options={STATUS} onChange={setValue} /> }}
340
+ </Filter.Item>
341
+ </Page.Filter>
342
+
343
+ // 分页:modelValue 是 1 基页码
344
+ <Pagination v-model="page" v-model:page-size="pageSize" :total="total" />
345
+ ```
346
+
347
+ - `Filter` 的值进出已归一(空串 = 不过滤),不要再自己拼「全部」选项。
348
+ - `onQuery` / `onReset` 是两条回调(契约 §5 组 4);`onChange` 描述「值变了」。
349
+ - 分页归属跟承载表格的容器走(契约 §5 组 4);客户端分页可用 `useClientPagination`
350
+ (`@manohub/kit` 导出)。
@@ -0,0 +1,47 @@
1
+ # 样式:意图 → 合规做法
2
+
3
+ 应用侧 CSS 的边界是「**只写布局**」。**判据在契约里** —— 属性白名单是闭集(逐字照用)、
4
+ 禁止项与唯一的换肤通道都在契约 §3 L1(含「局部换肤」段与「正误对照」段)。
5
+ 本文**不复述判据**,只回答一件事:**「我想写某段样式」时,合规的做法是什么**。
6
+
7
+ 契约原文:`node_modules/@manohub/kit/CONTRACT.md` §3 L1。
8
+
9
+ ---
10
+
11
+ ## 一、常见意图的合规替代
12
+
13
+ | 我想… | 合规做法 |
14
+ |---|---|
15
+ | 拉开两个区块的间距 | 区块分组用 `Panel`;表单内分组用 `Panel` 分段或 `Form` 的行距。**不要**手写 `margin-bottom` |
16
+ | 把标题行右端的计数 / 按钮推到最右 | `Panel.Header` 的 `actions`(贴右由组件负责)。**不要**自己写 `margin-left:auto` / `justify-content:space-between` |
17
+ | 在页头放一个「刷新 / 导出」按钮 | 页面级动作放页头 `extra`;**区域级**动作归 `Panel.Header.actions`(契约 §5 组 3 / 组 4) |
18
+ | 做「标签 + 值」一行(label 弱化) | `Form.Item` 的 `text` 行。**不要**自绘两个不同颜色的 `span` |
19
+ | 做卡片的选中态 | `Radio.Card`。**不要**手写选中类 |
20
+ | 在表格单元格里放输入框 | `Table` + `Input` / `Select` + `column.align`。**不要**塞原生 `<input>` 再写居中类 |
21
+ | 做表格(列宽、固定表头、空态留白) | `Table`。**不要**用 CSS Grid 自绘 |
22
+ | 做提示条(带关闭) | `Notice`;行内可点文字用 `Button variant="link"` |
23
+ | 做行内操作(手型、hover、禁用态) | `Button variant="link"`(状态由组件给) |
24
+ | 给一块区域加描边 / 圆角 / 阴影 / 底色 | **`Panel` 无框是硬契约**(契约 §5 组 4);要卡片外观用 `Card` / `ListView.CardItem` |
25
+ | 给 `Page.Split` 两栏之间加分隔线 | 删掉 —— 分隔线归 `Split` 内建(它还兼拖拽手柄) |
26
+ | 给 `Panel` / `Panel.Body` 挂一个「只写 padding」的类 | 用组件的 `padding` 维度(数字或 `{ y, x }`) |
27
+ | 改控件高度(`height: 36px !important`) | 不要改;确有诉求走契约 §8 建件流程 |
28
+ | 自己排一行 / 一列 | `Layout.Row` / `Layout.Column`(间距只有档位,别写任意 px)。**不要**自绘 `display:flex; gap` |
29
+ | 做加载遮罩 + 转圈 | `Loading`(`overlay="area"` / `"screen"`)或服务层 `showLoading()` |
30
+ | 把某些文字调大 / 调小 / 变粗 | 用组件的 `size` 档;组件没有该档时提缺件。**不要**写 `font-size` |
31
+ | 加一个图标 | 见契约 §4 L1.5(图标源于 `@manohub/icon`,颜色继承文案色,尺寸在面层给) |
32
+ | 让这段样式压过组件自带样式 | 出现这个念头说明**这条样式本不该你写** —— 找对的令牌(契约 §3「局部换肤」)或提缺件 |
33
+ | 想给某个子应用单独一套主色 | 契约 §3「局部换肤」:**在更深容器重设已有令牌的值**(不发明新令牌名) |
34
+ | 想让某个区域高度撑满 | `height: 100%`。**不要** `100vh` / `h-screen`(子应用被注入宿主容器) |
35
+
36
+ ## 二、两条边界
37
+
38
+ 1. **类名归谁**:本仓的类名前缀表在 `docs/kit-namespaces.md`(per-app 文件);
39
+ 库锚 `[data-manohub-ui]` 恒允许。**不要**为了绕过规则去登记 `.mh-*` 或底层组件库的内部类前缀。
40
+ 出现「这个类该不该存在」的疑问时:属于自己的 → 登记;属于外部 / 遗留 → 删。
41
+ 2. **设计定版例外**:按设计稿逐像素还原的文件走契约 §11(定版文件清单,**人工确认,不是通行证**)——
42
+ §11 明确列了**登记也不放行**的几类,先读那一段再决定。
43
+
44
+ ## 三、收工前
45
+
46
+ 契约 §7 自检清单的 L1 组(属性白名单 / 令牌引用 / 自定义属性 / 工具类形态)逐条答一遍。
47
+ 0.6.0 起没有自动扫描,**答不上来就等于没检查**。
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: kit-migrate
3
+ version: 1.0.0
4
+ description: 把存量子应用改造到 @manohub/ui + @manohub/kit 骨架层(含清理 App* 旧组件名、.ak-* 样式与直连底层组件库的写法):按契约分层盘点违规、划批次、逐文件替换、逐层收口、验收;也支持只迁一部分(典型是「只迁页面骨架」,其余层分批收口)。触发条件:用户要求迁移/改造/接入子应用、要求盘点或降低违规存量、要求先只迁页面骨架或分阶段迁移、或代码里出现 App* 组件名 / .ak-* 样式 / 直连 farris 需要清理时使用。
5
+ ---
6
+
7
+ # kit-migrate:存量应用改造
8
+
9
+ 把存量页面从「旧组件名 + `.ak-*` 样式 + 直连底层组件库」迁到「`@manohub/ui` 组件 +
10
+ `@manohub/theme` 令牌」。
11
+
12
+ 分六阶段,每阶段都有可核对的产出。
13
+ **替换对照表见 `references/migration-map.md`**;阶段细则、盘点登记格式与验收清单见 `references/migration-playbook.md`。
14
+
15
+ > **0.6.0 起本包不再发布消费侧机器规则**(`kit lint` 三条护栏已下线)。
16
+ > 改造的判据变成「契约条款 + §7 自检清单」,违规的**盘点与归零由人/代理按契约逐条过**,
17
+ > 不再有自动拦断。这决定了两件事:① 阶段 1 的基线靠人工按层盘;② 阶段 3/4 的「归零」
18
+ > 以**人工过 §7 清单**为证据,而不是一张 lint 退出码。
19
+
20
+ ## 阶段 0 · 摸清现状
21
+
22
+ 记下改造前的四个事实(写进应用文档,后面验收要用):
23
+
24
+ | 记录项 | 怎么取 |
25
+ |---|---|
26
+ | 基线版本 | 现装 `@manohub/kit`、`@manohub/ui`、`@manohub/theme` 的版本号 |
27
+ | 页面清单 | `src/views/` 下的路由页,与页面内 `components/` 子目录的可复用件分开数 |
28
+ | 组件用量 | 搜索 `App` 前缀组件名 / `.ak-` 样式 / `@farris/ui-vue` 的出现次数(三个数分开记) |
29
+ | 构建与体积 | 一次生产构建的产物大小量级(验收时对比) |
30
+
31
+ ## 阶段 1 · 按契约分层盘点违规
32
+
33
+ 没有自动扫描了,所以这一步是**人工按层过**:打开 `CONTRACT.md`,逐层逐条对着代码盘。
34
+
35
+ 1. 装依赖:
36
+
37
+ ```bash
38
+ pnpm add @manohub/ui @manohub/theme
39
+ pnpm add @manohub/kit # 若尚未接入
40
+ ```
41
+
42
+ 2. 建本仓的**命名空间表** `docs/kit-namespaces.md`(哪个应用占哪个类名前缀),
43
+ 格式见 `references/migration-playbook.md`。这张表取代了旧配置里的 `prefixes`。
44
+ 3. 按契约的五层盘一遍,把命中记成条款级清单(`L0-3 / L1-5 / L2-12 …` 这个样子),
45
+ 登记格式见 `references/migration-playbook.md`。**盘点是「改造前的事实」,不是「要修掉的清单」**。
46
+
47
+ > 首次盘必然很多命中(`App*` 名字、`.ak-*` 样式、缺 `data-manohub-ui` 都会中),这是预期的。
48
+
49
+ ## 阶段 2 · 划定层级与批次
50
+
51
+ **层序就是收口顺序**:L0 入口(漏了什么都没有)→ L1 值 → L1.5 图标 → L2 结构 → L3 件。
52
+
53
+ 但**存量的最优施工顺序**与层序相反 —— 先动结构、后动件、最后收样式,因为:
54
+
55
+ 1. **页面骨架**(`AppShell.*` → `Page.*`):L2 立刻能整体收口,收益最大、风险最低。
56
+ 2. **组件逐件替换**(按 `references/migration-map.md`):表格 / 分页 / 表单 / 弹窗优先,
57
+ 它们的 prop 差异最大,改完能把大部分页面的阻塞解掉。
58
+ 3. **命令式服务替换**:`notify` / `messageBox` / `loading` / `modalService` → 服务层的
59
+ `toast()` / `confirm()` / `alert()` / `showLoading()`(**API 是重新设计的,逐处改写调用点**)。
60
+ 4. **样式收口**:删 `.ak-*` 覆写,残留的视觉意图翻成主题令牌。
61
+
62
+ **只迁一部分时用「承诺层」表达**(不是豁免):每批只承诺某几层全合规,其余层**不承诺**,
63
+ 并在应用文档里写明「本批承诺 L2;L1/L3 待第 N 批」。没有配置文件要改 —— 承诺写入应用文档即可。
64
+
65
+ ## 阶段 3 · 逐文件替换
66
+
67
+ 每个文件一轮:
68
+
69
+ 1. 按 `migration-map.md` 换组件名与 prop;
70
+ 2. **对照契约条款逐条改**:该文件涉及哪几层,就打开那几层的条款对着改;
71
+ 3. 跑类型检查,确保没有「两种同名类型不兼容」(那是装了两份依赖的信号)。
72
+
73
+ - 契约的「正误对照」段是改法的直接依据;样式违规的处理原则:**能删就删**(迁移后不需要的覆盖段),
74
+ 需要保留的改为布局属性或组件的 prop。
75
+
76
+ ## 阶段 4 · 逐层收口与验收
77
+
78
+ | 检查 | 通过标准 |
79
+ |---|---|
80
+ | 已承诺的层 | 该层条款**逐条**对照代码过一遍,无命中(证据:清单里该层打勾,附文件清单) |
81
+ | 未承诺的层 | 照常记录、数量不高于阶段 1 的盘点(写进应用文档) |
82
+ | 类型 | `pnpm exec vue-tsc --noEmit` 0 错 |
83
+ | 构建 | `pnpm build` 成功;产物大小与阶段 0 记录值量级一致(差异要能解释) |
84
+
85
+ - **归零没有退出码了**,所以证据必须落在文档上:**每条打勾的条款都要能指出「哪些文件里没有它」**。
86
+ 做不到就说明没真过一遍。
87
+ - 下一批的起点 = 本轮未承诺层的盘点结果;收口顺序 = 层序(L0 → L1 → L1.5 → L2 → L3)。
88
+
89
+ ## 阶段 5 · 验收(缺一不可)
90
+
91
+ 1. 浏览器逐个过页面与弹窗:渲染、交互、样式、**弹层落点**。
92
+ 2. **命令式提示必须单独验**:`toast('success', 'ok')` 之后检查 DOM 里 `[data-manohub-ui]` 内有 `.mh-toast`
93
+ (不在容器内 = 样式会全丢);`showLoading()` 之后必须能关掉(引用计数配平)。
94
+ 3. **特殊入口必须单独验**:以 `index.html?xxx=1` 直开的入口要在 `createSubApp` 的 `rootPathAliases`
95
+ 里登记,否则首屏守卫会清空 query。
96
+ 4. 已知缺口如实判断是否属本次范围,不要误判为「漏改」:
97
+ - **没有任何工具会替你检查结构** —— SFC 与 TSX 一律人工过契约 §7 自检清单。
98
+ - 组件库尚未提供的件(契约 §8)只能按替代口径顶住,不得自绘。
99
+ 5. 结论记录到应用文档:改了什么、验收证据、遗留项。
100
+
101
+ ## 阶段 6 · 收尾登记
102
+
103
+ - 基线文档更新为「迁移后:0」并写明日期;未承诺层的清单留作下一批起点。
104
+ - 在应用 `AGENTS.md` 里补充「本应用已接入骨架层」与本次改造的关键结论。
105
+ - 命名空间表落 `docs/kit-namespaces.md`(若本仓有多个子应用,逐个登记前缀)。
106
+
107
+ ## 失败处理
108
+
109
+ | 现象 | 处理 |
110
+ |---|---|
111
+ | 改动文件仍不合规 | 读契约该层的条款与「正误对照」段;仍不解就在应用文档里记为待定,不要自创等价写法 |
112
+ | 组件疑似缺失 | 走契约 §8 缺件处置流程;不要在应用侧自绘,也不要回去直连底层组件库 |
113
+ | 替换后视觉与改造前不一致 | 先确认是不是「旧写法本来就在覆写组件内部类」(那种覆写删掉即可);确属缺能力 → 记录待建件 |
114
+ | 类型出现「两种同名类型不兼容」 | 检查是否装了两份 vue / 依赖重复;按 `../kit/references/adoption.md` 的单例收敛处理 |
115
+ | 命令式提示样式全丢 | 提示没落回应用容器:确认入口走的是 `createSubApp`(写 `data-manohub-ui`),或显式 `configureHost(el)` |
116
+ | 分页页码整体差 1 | 旧 `page` 是 0 基、新 `Pagination.modelValue` 是 1 基(`migration-map.md` 的 Pagination 一节) |
117
+ | 想找「跑一条命令看还剩多少违规」 | 没有了 —— 按阶段 4 的分层盘点口径人工过,成果记在应用文档 |
118
+
119
+ ## 参考
120
+
121
+ - `references/migration-map.md`:替换映射表(旧写法 → 新写法 → 注意事项),按类目查
122
+ - `references/migration-playbook.md`:阶段细则、命名空间表与盘点登记格式、收口口径、验收清单、**「只迁骨架」变体路径**
123
+ - `../kit/references/adoption.md`:接入 SOP
124
+ - `node_modules/@manohub/ui/README.md`:组件清单与服务层 API
125
+ - `node_modules/@manohub/kit/CONTRACT.md`:规范唯一事实源(§7 是自检清单,§12 是升级说明)