@manohub/app-kit 0.1.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.
- package/CONTRACT.md +592 -0
- package/README.md +27 -0
- package/dist/atoms/app-button.d.ts +90 -0
- package/dist/atoms/app-button.js +74 -0
- package/dist/atoms/app-checkbox.d.ts +86 -0
- package/dist/atoms/app-checkbox.js +78 -0
- package/dist/atoms/app-icon-button.d.ts +79 -0
- package/dist/atoms/app-icon-button.js +54 -0
- package/dist/atoms/app-input.d.ts +118 -0
- package/dist/atoms/app-input.js +83 -0
- package/dist/atoms/app-layout.d.ts +348 -0
- package/dist/atoms/app-layout.js +102 -0
- package/dist/atoms/app-notice.d.ts +90 -0
- package/dist/atoms/app-notice.js +59 -0
- package/dist/atoms/app-query-state.d.ts +191 -0
- package/dist/atoms/app-query-state.js +161 -0
- package/dist/atoms/app-radio-card.d.ts +80 -0
- package/dist/atoms/app-radio-card.js +72 -0
- package/dist/atoms/app-search-box.d.ts +80 -0
- package/dist/atoms/app-search-box.js +64 -0
- package/dist/atoms/app-select.d.ts +106 -0
- package/dist/atoms/app-select.js +74 -0
- package/dist/atoms/app-switch.d.ts +72 -0
- package/dist/atoms/app-switch.js +66 -0
- package/dist/atoms/app-textarea.d.ts +89 -0
- package/dist/atoms/app-textarea.js +61 -0
- package/dist/atoms/app-tooltip.d.ts +70 -0
- package/dist/atoms/app-tooltip.js +48 -0
- package/dist/atoms/atoms.css +679 -0
- package/dist/atoms/state-illustration.d.ts +21 -0
- package/dist/atoms/state-illustration.js +101 -0
- package/dist/components/app-badge.d.ts +72 -0
- package/dist/components/app-badge.js +40 -0
- package/dist/components/app-dialog.d.ts +164 -0
- package/dist/components/app-dialog.js +121 -0
- package/dist/components/app-filter.d.ts +173 -0
- package/dist/components/app-filter.js +200 -0
- package/dist/components/app-form.d.ts +446 -0
- package/dist/components/app-form.js +159 -0
- package/dist/components/app-pagination.d.ts +78 -0
- package/dist/components/app-pagination.js +57 -0
- package/dist/components/app-panel.d.ts +399 -0
- package/dist/components/app-panel.js +213 -0
- package/dist/components/app-section.d.ts +68 -0
- package/dist/components/app-section.js +28 -0
- package/dist/components/app-steps.d.ts +115 -0
- package/dist/components/app-steps.js +108 -0
- package/dist/components/app-table.d.ts +415 -0
- package/dist/components/app-table.js +479 -0
- package/dist/components/app-tabs.d.ts +58 -0
- package/dist/components/app-tabs.js +54 -0
- package/dist/components/app-tree.d.ts +273 -0
- package/dist/components/app-tree.js +222 -0
- package/dist/components/components.css +597 -0
- package/dist/composables/use-client-pagination.d.ts +39 -0
- package/dist/composables/use-client-pagination.js +29 -0
- package/dist/entry/create-query-client.d.ts +8 -0
- package/dist/entry/create-query-client.js +14 -0
- package/dist/entry/create-sub-app.d.ts +45 -0
- package/dist/entry/create-sub-app.js +110 -0
- package/dist/entry/index.d.ts +4 -0
- package/dist/entry/index.js +12 -0
- package/dist/entry/initial-guard.d.ts +12 -0
- package/dist/entry/initial-guard.js +23 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +67 -0
- package/dist/providers/setup-i18n.d.ts +19 -0
- package/dist/providers/setup-i18n.js +24 -0
- package/dist/services/app-container.d.ts +15 -0
- package/dist/services/app-container.js +7 -0
- package/dist/services/app-context.d.ts +4 -0
- package/dist/services/app-context.js +17 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/loading.d.ts +16 -0
- package/dist/services/loading.js +27 -0
- package/dist/services/message-box.d.ts +84 -0
- package/dist/services/message-box.js +65 -0
- package/dist/services/modal.d.ts +51 -0
- package/dist/services/modal.js +51 -0
- package/dist/services/notify.d.ts +24 -0
- package/dist/services/notify.js +22 -0
- package/dist/shell/AppShell.d.ts +139 -0
- package/dist/shell/AppShell.js +171 -0
- package/dist/shell/shell.css +153 -0
- package/dist/styles/farris-bridge.css +129 -0
- package/dist/styles/index.css +16 -0
- package/dist/styles/markdown.css +85 -0
- package/dist/styles/reset.css +74 -0
- package/dist/styles/tokens.css +111 -0
- package/lint/__tests__/fixtures/clean-src/styles.css +14 -0
- package/lint/__tests__/fixtures/clean-src/views/good-page.tsx +15 -0
- package/lint/__tests__/fixtures/violations-src/styles.css +18 -0
- package/lint/__tests__/fixtures/violations-src/views/bad-page.tsx +38 -0
- package/lint/__tests__/fixtures/violations-src/views/no-shell-page.tsx +4 -0
- package/lint/__tests__/guardrails.spec.mjs +144 -0
- package/lint/component-audit.mjs +131 -0
- package/lint/guardrails.config.schema.json +74 -0
- package/lint/pre-commit.sample +26 -0
- package/lint/run-all.mjs +59 -0
- package/lint/shared.mjs +347 -0
- package/lint/structure-audit.mjs +254 -0
- package/lint/style-audit.mjs +200 -0
- package/package.json +78 -0
package/CONTRACT.md
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
# @manohub/app-kit CONTRACT —— 接入方必读
|
|
2
|
+
|
|
3
|
+
本文件是**使用**本包的权威规范;**编辑本包**请看包内 `AGENTS.md`(不随包分发)。
|
|
4
|
+
|
|
5
|
+
**优先级(冲突时以此为准)**:本文件 > 组件源码 `@example` > 消费仓 `AGENTS.md` > 其它文档。
|
|
6
|
+
发现 `@example` 与本文件冲突时,按本文件写,并把该 `@example` 当**缺陷**修掉(不要照抄)。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. 消费方式
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// main.ts —— 入口编排统一走 createSubApp(不要再手写 createApp/mount/window 协议)
|
|
14
|
+
import { createSubApp } from '@manohub/app-kit/entry'
|
|
15
|
+
|
|
16
|
+
export const { mount, unmount } = createSubApp({
|
|
17
|
+
rootComponent: App,
|
|
18
|
+
routes,
|
|
19
|
+
// 用 vue-i18n 的应用走插件注入,不要用本包的 i18n 选项(那是 i18next 语义)
|
|
20
|
+
extraPlugins: [i18n],
|
|
21
|
+
rootPathAliases: ['/index.html'], // 见 §9「初始守卫与选择模式」
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```css
|
|
26
|
+
/* apps/<app>/src/style.css —— 固定三行,顺序即契约 */
|
|
27
|
+
@import "@manohub/app-kit/reset.css";
|
|
28
|
+
@import "@manohub/app-kit/styles.css"; /* 内部首行引入 farris CSS,随后 tokens + farris-bridge + .ak-* */
|
|
29
|
+
@import "./app.css";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { AppShell, AppPanel, AppTable, AppButton, notify } from '@manohub/app-kit'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`createSubApp` 已统一负责:`.app-container` 包裹(令牌/桥接/reset 的作用域锚点)、pinia、Farris 插件、VueQuery、
|
|
37
|
+
路由、`mount(__MICRO_APP_CONTAINER__ || '#app')`、宿主语言监听、初始守卫、`window.mount/unmount` +
|
|
38
|
+
`window.microApp.mount/unmount`、非微前端环境自动挂载。**应用侧不得重复书写这些**。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 2. 四条铁律
|
|
43
|
+
|
|
44
|
+
1. **API 面 = 翻译后的标准面,不是底层组件库面的子集**:不继承其 props 类型、不 `...rest` 透传、不暴露其组件实例。
|
|
45
|
+
2. **数据入参只收朴素业务值**:不收底层配置对象(`valueField` / `textField` / `enumValueType` 之类一律内部翻译)。
|
|
46
|
+
3. **事件只回传业务值**:不回传包装对象(`row.raw` 的解包在包内做)。
|
|
47
|
+
4. **命名与形态遵守通行规范**:禁 `enableXxx` / `showXxx` 布尔前缀,禁嵌套配置对象 props,禁底层专有 prop 名。
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 3. 统一 prop 词表
|
|
52
|
+
|
|
53
|
+
| 语义 | 命名 |
|
|
54
|
+
|---|---|
|
|
55
|
+
| 受控值 / 非受控初值 | `modelValue` / `defaultValue` |
|
|
56
|
+
| 值变更 | `onChange(value)`(同时 emit `update:modelValue`) |
|
|
57
|
+
| 禁用 / 只读 / 加载中 | `disabled` / `readonly` / `loading` |
|
|
58
|
+
| 尺寸 | `size`: `'sm' \| 'md' \| 'lg'` |
|
|
59
|
+
| 色彩角色 | `tone`: `default \| primary \| secondary \| info \| success \| warning \| error` |
|
|
60
|
+
| 呈现形态 | `shape`: `solid \| soft \| outline \| ghost \| link` |
|
|
61
|
+
| 占位 / 可清除 | `placeholder` / `clearable` |
|
|
62
|
+
| 表单字段名 / 必填 / 校验文案 / 说明 | `label` / `required` / `error` / `hint` |
|
|
63
|
+
| 列表型入参(步骤、页签) | `items`(元素用 `key` 作标识) |
|
|
64
|
+
| 外观载荷 | `class` / `style`(仅此两项透传) |
|
|
65
|
+
|
|
66
|
+
### 禁止项(按「键名」判定,不按字面量前缀)
|
|
67
|
+
|
|
68
|
+
- **嵌套的底层配置对象**:`rowOption` / `columnOption` / `enableSelectRow` / `enabelSelectRow` /
|
|
69
|
+
`treeNodeIconsData` / `keepSelectingOnPaging` / `searchPlaceHolder` / `multiSelectMode` …
|
|
70
|
+
- **底层专有名**:`valueField` / `textField` / `idField` / `enumValueType` / `columnTemplate` /
|
|
71
|
+
`customClass` / `iconClass` / `fitColumns` / `updateDataSource` / `showCheckbox` / `showSelectAll` …
|
|
72
|
+
- **`enableXxx` / `showXxx` / `isShowXxx` 形态的布尔前缀**。
|
|
73
|
+
|
|
74
|
+
> **`selection={{…}}` 是合法的**:它是本包自己的结构化契约(`mode` / `selected` / `onSelectedChange` /
|
|
75
|
+
> `selectable` / `onSelectAll`),不是底层配置对象。判定口径是「这个键属于谁」,不是「长得像不像对象」。
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 4. 页面结构契约
|
|
80
|
+
|
|
81
|
+
### 4.1 三种页面模板
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// 模板 A:列表页(页头 + 表格 + 页脚分页)
|
|
85
|
+
<AppShell>
|
|
86
|
+
<AppShell.Header title="技能分类" toolbar={<AppSearchBox … />} />
|
|
87
|
+
<AppShell.Body mode="table">
|
|
88
|
+
<AppTable framed rows={rows} columns={cols} rowKey="id" />
|
|
89
|
+
</AppShell.Body>
|
|
90
|
+
<AppShell.Footer>
|
|
91
|
+
<AppPagination page={page} pageSize={size} total={total} … />
|
|
92
|
+
</AppShell.Footer>
|
|
93
|
+
</AppShell>
|
|
94
|
+
|
|
95
|
+
// 模板 B:双栏页(左树/列表 + 右详情/列表;栏内滚动由 AppPanel 承担)
|
|
96
|
+
<AppShell>
|
|
97
|
+
<AppShell.Header title="值映射管理" />
|
|
98
|
+
<AppShell.Body mode="plain">
|
|
99
|
+
<AppShell.Split sidebar={{ width: 208, content: () => (
|
|
100
|
+
<AppPanel title="业务域"><AppTree … /></AppPanel>
|
|
101
|
+
) }}>
|
|
102
|
+
<AppPanel
|
|
103
|
+
title="值映射列表"
|
|
104
|
+
toolbar={<><AppSearchBox … /><AppSelect … /></>}
|
|
105
|
+
actions={<><AppButton tone="primary">新建</AppButton><AppButton>刷新</AppButton></>}>
|
|
106
|
+
<AppTable framed rows={rows} columns={cols} rowKey="id" />
|
|
107
|
+
|
|
108
|
+
{/* 分页跟**承载表格的容器**走:表格在本面板内 → AppPanel.Footer(不要提到 AppShell.Footer) */}
|
|
109
|
+
<AppPanel.Footer><AppPagination … /></AppPanel.Footer>
|
|
110
|
+
</AppPanel>
|
|
111
|
+
</AppShell.Split>
|
|
112
|
+
</AppShell.Body>
|
|
113
|
+
</AppShell>
|
|
114
|
+
|
|
115
|
+
// 模板 C:详情/向导页(长内容自身滚)
|
|
116
|
+
<AppShell>
|
|
117
|
+
<AppShell.Header title="值映射详情" />
|
|
118
|
+
<AppShell.Body mode="scroll">…</AppShell.Body>
|
|
119
|
+
</AppShell>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- 筛选字段 > 3 或跨区域时,在 `AppShell.Header` **之前**加 `<AppShell.Filter>`(页面级筛选的唯一位置)。
|
|
123
|
+
- **分页归属跟「承载表格的容器」走**:表格直接挂在 `AppShell.Body` 下 → 分页放 `AppShell.Footer`(模板 A);
|
|
124
|
+
表格在 `AppPanel` 里 → 分页放 `AppPanel.Footer`(模板 B)。
|
|
125
|
+
二者错位(把区域列表的分页提到页面级、或把页面级分页塞进某个面板)属于布局错位:
|
|
126
|
+
表格与它的分页脱离同一个容器后,面板内的滚动/常驻关系就断了(表体滚、分页跟着页面走)。
|
|
127
|
+
- `AppShell.Toolbar` 仅保留兼容,新页面不要用。
|
|
128
|
+
- **禁** `h-screen` / `100vh`(子应用被注入宿主容器,`100vh` 取窗口高度会溢出);高度一律 `height: 100%`。
|
|
129
|
+
|
|
130
|
+
### 4.2 `AppShell.Split`(双栏)
|
|
131
|
+
|
|
132
|
+
- 中间那根 1px 竖直分隔线由 **Split 统一提供**(`--ui-base-300`,与页头下边线同源);
|
|
133
|
+
线右侧(主栏)留 16px 内距把内容与线拉开,**侧栏不加内距**(侧栏内容自带)—— 这是有意取舍,不要补齐。
|
|
134
|
+
**业务侧不得再给栏的子元素加竖直边框**(`border-right` / `border-left`)——叠第二条线属违规。
|
|
135
|
+
- `sidebar.width` 是**内容宽**(px 数字,`content-box` 语义):边线与内距加在框外,**不要手工加补偿值**。
|
|
136
|
+
需要随主题缩放的场景请先读 §9「缩放口径」——本包统一 px,不跟随根字号。
|
|
137
|
+
- **Split 自身不滚,栏内滚动由内容承担**:双栏内用 `AppPanel`(其 Body `overflow:auto`)。
|
|
138
|
+
两栏是 `overflow:hidden`,把内容直接塞进栏而不给滚动容器会导致内容被裁。
|
|
139
|
+
|
|
140
|
+
### 4.3 `AppPanel`(区域容器,无框)
|
|
141
|
+
|
|
142
|
+
- **三件结构**(与 `AppShell` 同款复合写法):`AppPanel.Header`(标题行)/ `AppPanel.Body`(内容区)/
|
|
143
|
+
`AppPanel.Footer`(区域内常驻控件,如本区域表格的分页条)。三件顺序固定,Header 与 Footer 不滚。
|
|
144
|
+
**自定义面板头(`header` 插槽)同样常驻**:Body 带 `flex: 1; min-height: 0`,是唯一会让的一件;
|
|
145
|
+
头(内建或自定义)保持内容高 —— 应用侧不必给它补 `flex-shrink` 之类的类(那是冗余保险)。
|
|
146
|
+
- **简写**等价于「内建 Header + 包一层的 Body」:`<AppPanel title=… toolbar=… actions=…>内容</AppPanel>`。
|
|
147
|
+
两种写法可混用 —— 显式的三个件会被提到对应位置,其余子节点自动进 Body
|
|
148
|
+
(所以 `<AppPanel title=…><AppTable … /><AppPanel.Footer>…</AppPanel.Footer></AppPanel>` 是合法的)。
|
|
149
|
+
- **无框契约**:不加描边、不加圆角、不加阴影、不设独立底色、不加单侧边框。分区感只由 Header 下方
|
|
150
|
+
1px 分隔线提供(Footer 也不加边框)。**卡片外观一律由内容件承载**(表格 → `AppTable framed`)。
|
|
151
|
+
- 两位分工**不可互换**:
|
|
152
|
+
- `toolbar` = 本区域的筛选/搜索位(≤3 字段)
|
|
153
|
+
- `actions` = 本区域的操作位(该区域的新建/刷新等)
|
|
154
|
+
- 头部控件只能进 `toolbar` / `actions`;完全自定义的面板头走 `AppPanel.Header`(不传 `title`)
|
|
155
|
+
或 `header` 插槽(需在接入方登记为白名单条目)。
|
|
156
|
+
- **无标题的区域头是合法形态**:不给 `title`、只给 `toolbar` / `actions` 时,那一行照渲染
|
|
157
|
+
(右组在左,因为没有标题与它两端对齐)—— 用于「标题在容器外层」的区域,如弹窗内的列表
|
|
158
|
+
(弹窗标题栏已写了区域名)。三样都不给才不渲染头。
|
|
159
|
+
- 不提供 `framed` / `variant` / `scroll` 维度:无框是硬契约,滚动由 Body 固定承担。
|
|
160
|
+
- **内距由 `padding` 给**(px 数字,或 `{ y, x }` 分别给上下 / 左右),**根件与三件同名同口径**:
|
|
161
|
+
- 根件 = 整块面板的内距:面板**默认贴容器边**,因为页面里内外距由 `AppShell` 的各部件承担;
|
|
162
|
+
弹窗内的区域没有别的东西可给内距,用 `padding={{ y: 12, x: 20 }}` 自带 ——
|
|
163
|
+
不要再为「只写 padding」给面板挂一个应用侧类。
|
|
164
|
+
- `AppPanel.Body.padding` = **内容区的内距**:弹窗内容区最常用的一条,
|
|
165
|
+
`padding={{ y: 24, x: 100 }}` —— `y` 是上下留白,`x` 开大即充当**限宽**(宽弹窗里不让表单行拉到右边缘)。
|
|
166
|
+
同样的道理:不要给 Body 挂「只写 padding」的应用侧类。
|
|
167
|
+
- `AppPanel.Footer.padding` = 常驻控件(分页条等)与内容的距离(`padding={{ y: 10 }}`)。
|
|
168
|
+
- `AppPanel.Header.padding` = 内建头那一行的内距;走 `header` 插槽(整行替换)时会把自定义头
|
|
169
|
+
**包一层**只带内距的 div —— 头内部的几何仍归消费方,组件不碰。
|
|
170
|
+
- 四件缺省都**不落任何行内 style**(DOM 与没有这个维度时一致)。
|
|
171
|
+
- **只给「区域」用**:区块标题(没有区域级筛选/操作、内容也不自己滚)用 `AppSection`(§4.11)。
|
|
172
|
+
拿本件当「带标题的卡片」用,会把区域头的高度与 Body 的滚动契约带进不需要它的地方。
|
|
173
|
+
|
|
174
|
+
### 4.4 两级滚动归属(不要混)
|
|
175
|
+
|
|
176
|
+
- **页面级**:`AppShell.Body.mode` ∈ `table` / `scroll` / `plain`,全页唯一。
|
|
177
|
+
- **区域内**:`AppPanel.Body`(**Header 与 Footer 固定 / 只有 Body 滚**)。
|
|
178
|
+
- `AppShell.Header` 与 `AppShell.Footer` 同样 `flex-shrink: 0` 常驻;`AppPanel.Footer` 与之同款,
|
|
179
|
+
所以「表格 + 分页」在面板内是一个整体:表体由网格内部滚、分页常驻面板底部。
|
|
180
|
+
|
|
181
|
+
组合示例(模板 B):`Body mode="plain"` → Split 不滚 → 左右 `AppPanel` 各自 Body 滚,分页在右面板 Footer。
|
|
182
|
+
|
|
183
|
+
### 4.5 四处操作位唯一化(**先问「过滤谁的数据」,再问「几个字段」**)
|
|
184
|
+
|
|
185
|
+
| 位置 | 放什么 |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `AppPanel.Header.toolbar` | **区域级**筛选/搜索:过滤**本区域**数据,字段数 ≤3 |
|
|
188
|
+
| `AppShell.Filter` | **页面级**筛选:字段数 >3,或跨区域,或已是组合查询方案(全页唯一) |
|
|
189
|
+
| `AppPanel.Header.actions` | **区域级**操作:该区域的新建/刷新等 |
|
|
190
|
+
| `AppShell.Header.toolbar` | **页面级**操作:返回/全局刷新(页面无面板时该位兼作页面级搜索位) |
|
|
191
|
+
|
|
192
|
+
判断顺序固定:**① 它过滤哪块数据 → 决定放哪个区域的 `toolbar`;② 一行放不下(>3 字段)→ 上提 `AppShell.Filter`**。
|
|
193
|
+
字段数是唯一可机械判定的项(护栏只拦它);归属判据不可机械判定,按本表评审并对照 §5 反例。
|
|
194
|
+
|
|
195
|
+
按钮组另有一条视觉约束:**同一按钮组内最多一个 `tone="primary"`,且必须是该组第一个按钮**(主操作唯一、且在阅读
|
|
196
|
+
顺序最前)。分组按 `toolbar` / `actions` / `AppShell.Header.toolbar` 各自独立计数 —— 即「筛选位里的按钮」与
|
|
197
|
+
「操作位里的按钮」互不影响。
|
|
198
|
+
|
|
199
|
+
⚠️ **其余按钮必须显式给非主色的 `tone`**(普通操作 `secondary`,图标按钮 `ghost`)。原因是 `tone` 缺省时
|
|
200
|
+
`solid` 形态**就渲染成主色**(farris 没有中性默认按钮,见 `atoms/app-button.tsx` 的 `default + solid → primary`),
|
|
201
|
+
于是「省略 tone」等于又加了一颗主色按钮,而只数 `tone="primary"` 是数不出这个错的(真实踩过:工具条三颗按钮全蓝)。
|
|
202
|
+
反例:一个组里出现两个 `tone="primary"`、primary 不在第一位、或非首个按钮省略 `tone`。
|
|
203
|
+
|
|
204
|
+
### 4.6 三态(`AppQueryState`:loading / empty / error)
|
|
205
|
+
|
|
206
|
+
`AppTable`、`AppTree` 的加载 / 空 / 错误三态由组件内建,**页面不写状态分支**;页面只负责给数据、文案与动作:
|
|
207
|
+
|
|
208
|
+
| 维度 | 归属 | 说明 |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| 结构与外观 | 组件 | 三态互斥,优先级 `loading > error > empty`;构成固定为 **插画 → 标题 → 副文案 → 操作区** |
|
|
211
|
+
| 文案 | 调用方 | `empty` / `emptyDescription` / `errorTitle`;**字符串错误原文自动作副文案**(保住可诊断信息) |
|
|
212
|
+
| 动作 | 调用方 | `emptyActionText` + `onEmptyAction`(如「新建」)、`errorActionText` + `onErrorAction`(如「重试」);**两者都传才渲染**,只传文案不渲染(不留点了没反应的死按钮) |
|
|
213
|
+
|
|
214
|
+
**空态与错误态各有入口,不要合并**:加载失败传 `error`,不要塞进 `empty`(见 §5 反例)。两者的插画、语义与可用
|
|
215
|
+
动作本来就不同。状态块内要放多颗按钮、或换整幅插画时,用 `AppQueryState` 的 `actions` / `illustration` 插槽
|
|
216
|
+
(`illustration` 属性可换成图片资源;不传时用内建插画,配色全部走 `--ui-*` 令牌)。
|
|
217
|
+
|
|
218
|
+
**状态块里的操作按需给,不默认给**:判据是「**就地**恢复或推进」——错误态给「重试」几乎总是对的(用户在失败
|
|
219
|
+
现场就能重来);空态若同区域工具条里已有入口(如「新建」),就不要再放一颗(重复入口稀释主操作)。
|
|
220
|
+
|
|
221
|
+
文案的 i18n 由调用方负责传 `t()`:组件内建的中文只是兜底(app-kit 统一 i18n 内置文案属后续批次)。
|
|
222
|
+
|
|
223
|
+
### 4.7 表单(`AppForm` / `AppForm.Item`)
|
|
224
|
+
|
|
225
|
+
**表单行只有一种写法**:label 与控件都由 `AppForm.Item` 承载,控件从默认插槽进。
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
<AppForm labelWidth={120}>
|
|
229
|
+
<AppForm.Item label="缓存策略" required error={errors.cache} hint="勾选后按需加载数据">
|
|
230
|
+
<AppSelect options={cacheOptions} modelValue={form.cache} onChange={(v) => (form.cache = v)} />
|
|
231
|
+
</AppForm.Item>
|
|
232
|
+
</AppForm>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- `label` 是字段名;`required` 让星号自动落在 label 文本**左侧**;`error` 非空即进入错误态;
|
|
236
|
+
`hint` 是常驻说明。错误与说明排在控件**下方**(文档流),不会遮挡下一行。
|
|
237
|
+
- `label` 与 `required` 都不给时**不渲染 label 列**,控件列占满整行 —— 整块内容(如列编辑区)
|
|
238
|
+
与纯说明行用这个形态,不要另起一套 div 排版。
|
|
239
|
+
- **label 对齐由 `labelAlign` 控制**:`right`(默认,文本贴着控件列,与 farris 一致)/ `left`(所有 label 起点一致)。
|
|
240
|
+
写在 `AppForm` 上是表单级默认,写在 `AppForm.Item` 上只改那一行。
|
|
241
|
+
label 宽度固定为 `labelWidth`:对齐与宽度都属于组件,应用侧不要写 CSS 去改。
|
|
242
|
+
- `labelWidth`(px,缺省 120)与 `controlWidth`(px,缺省撑满)都可写在 `AppForm` 上(下发给每个 Item),
|
|
243
|
+
Item 上再写则覆盖表单级。
|
|
244
|
+
- **表单太宽时的两条杠杆(可叠加)**:
|
|
245
|
+
① **限宽** —— 表单级或逐行给 `controlWidth`(如 360)封住控件列,输入框不会被拉成长条;
|
|
246
|
+
② **分列** —— `columns={2}` 让行并排:**上限 2 列**,窄容器自动回落成一列(与 §4.12 的栅格同一公式,
|
|
247
|
+
不依赖媒体查询)。列最小宽度取令牌 `--ui-form-column-min`(320,比栅格的 260 宽 —— 字段要放下 label + 控件)。
|
|
248
|
+
两列下「整块内容」(文本域 / 卡片组 / 表格 / 纯说明行)记得在该 Item 上给 `fullWidth`,否则会被压成半宽。
|
|
249
|
+
- **label 与控件首行是等高对齐的**(label 盒高 = 底层控件真实高度,随根字号走)。
|
|
250
|
+
因此**不要**在应用侧改控件高度(`height: 36px !important` 之类):高度一变,同一行里的 label 就错位了。
|
|
251
|
+
确有尺寸诉求走 §7 建件(在包内把尺寸做成维度),不要在页面里压底层类。
|
|
252
|
+
- 控件一律用本包的件(`AppInput` / `AppSelect` / `AppTextarea` / `AppCheckbox` / `AppSwitch`);
|
|
253
|
+
自绘分组(单选卡片组等)直接放进默认插槽,**不要**另起一套 label / 错误行。
|
|
254
|
+
- label 宽度、对齐、错误文案样式都属于组件,应用侧不写这些 CSS。
|
|
255
|
+
- **静态表单(摘要 / 详情)**:`AppForm.Item` 传 `text` 即切成**只读文本行** —— 传了 `text`(含空串)
|
|
256
|
+
就不走控件插槽,控件位渲染纯文本;空值(空串 / 全空白)统一显示 `—`,不必再写 `value || '—'`;
|
|
257
|
+
长文案按词折行、换行符原样保留。只读文本行与控件行可以在**同一个表单里混排**(label 列宽 / 行距 /
|
|
258
|
+
错误 / 说明完全共用),所以「确认摘要」「详情弹窗」**不要另建描述列表件**,也不要自绘 label/value 表格。
|
|
259
|
+
- **只读行的字段名自动弱化**:传了 `text` 的那一行(或显式 `readonly` 的那一行),label 走次要色
|
|
260
|
+
(`--ui-base-content-muted`)、值仍是正文色,字段名与取值一眼分得开;编辑行的 label 不受影响。
|
|
261
|
+
**这层区分属于组件**——应用侧禁写 `color`,不要(也做不到)在页面里给 label 补色或加自绘的分隔符。
|
|
262
|
+
- **只读内容的控件位不一定是纯文本**(徽标 / 链接这类,如「定义方式」的语义标签):这时用
|
|
263
|
+
`readonly` 而不是 `text` —— 内容照常走默认插槽,label 的弱化与文本行一致,不必为此退回自绘
|
|
264
|
+
「标签 + 值」两个 `span`。
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<AppForm labelAlign="left" labelWidth={150}>
|
|
268
|
+
<AppForm.Item label="编码" text={definition.code} />
|
|
269
|
+
<AppForm.Item label="缓存策略" text={isDatabaseTable ? '按需缓存' : '全量缓存'} />
|
|
270
|
+
</AppForm>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- **表单内分组用 `AppForm.Section`**(= `AppSection` 本身,见 §4.11;不要自绘 `<div class="xx-sub-title">`):
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
<AppForm labelAlign="left">
|
|
277
|
+
<AppForm.Section title="映射语义">
|
|
278
|
+
<AppForm.Item label="匹配类型" text="精确值映射" />
|
|
279
|
+
</AppForm.Section>
|
|
280
|
+
<AppForm.Section title="来源列" v-slots={{ extra: () => <span>{n} / 10</span> }}>
|
|
281
|
+
<AppTable … />
|
|
282
|
+
</AppForm.Section>
|
|
283
|
+
</AppForm>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- 它只管三件事:**组标题排版**(14px/600)、**组内行距**(8px,等于表单行距)、**组间距离**(16px 叠表单 gap)。
|
|
287
|
+
为什么必须由组件给:`AppForm` 的 `gap` 只作用于它的**直接子元素** —— 行一旦被包进分组容器,组内行距就没人管了
|
|
288
|
+
(自绘 sub-title 的老坑正是「标题有了、间距乱了」)。
|
|
289
|
+
- `title` 不传:不渲染标题行,只借它的组间距(纯分隔);`extra` 槽放标题行尾部内容(计数、行内按钮)。
|
|
290
|
+
- 分组标题与组内内容**不要各写自己的 margin**;本件也能脱离表单单独用(如「校验结果」这类不成表单的分组块)。
|
|
291
|
+
- 命名先说清:**不要叫 `form-group-*`** —— `FormGroup` 在本仓/farris 里已经指**一行字段**(`AppForm.Item` 的底层件即
|
|
292
|
+
`FDynamicFormGroup`),叫 group 会让人以为它管一行;`-title` 则是零件名,会把「分组」的容器职责漏掉。
|
|
293
|
+
- **`AppForm.Section` 与 `AppSection` 是同一个件**(同一对象、同一套样式),不要各写一份实现 ——
|
|
294
|
+
页面级区块的完整口径见 §4.11。
|
|
295
|
+
|
|
296
|
+
### 4.8 步骤条(`AppSteps`)
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
<AppSteps items={stepItems} modelValue={currentStep}
|
|
300
|
+
onBeforeChange={(index) => index <= maxVisitedStep}
|
|
301
|
+
onChange={(index) => (currentStep = index)} />
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- `items` 是唯一的步骤定义入口:`{ key, title, disabled? }` —— 步骤条**只展示步骤名这一行**
|
|
305
|
+
(说明文案写到页面正文里,不要指望它承载副标题);`disabled` 只关掉该步(其余步骤照常可点)。
|
|
306
|
+
- `modelValue` 是**0 基**当前步骤索引(与 `AppPagination` 的 0 基口径一致);`clickable=false` 时整条只读。
|
|
307
|
+
- `onBeforeChange` 返回 `false`(或 resolve 为 `false`)阻止跳转 —— 「只能回退、不能前跳」就写在这里。
|
|
308
|
+
- **点击只上报、不改状态**:消费方必须回写 `modelValue`,否则界面不动(受控语义)。
|
|
309
|
+
重复点击当前步骤不上报(值没变)。
|
|
310
|
+
- 步骤圆点、序号、连接线、完成勾都属于组件,**不要自绘**步骤条。
|
|
311
|
+
|
|
312
|
+
### 4.9 卡片式单选(`AppRadioCard`)
|
|
313
|
+
|
|
314
|
+
选项内容长(标题 + 说明)时用卡片组,别用下拉:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
<AppForm.Item label="定义方式" required={true} hint="切换定义方式会清空已配置的语义与列">
|
|
318
|
+
<AppRadioCard modelValue={form.type}
|
|
319
|
+
items={[{ value: 'A', label: '值映射设置', description: '手工维护来源/目标向量' },
|
|
320
|
+
{ value: 'B', label: '数据库表', description: '绑定 msu 与数据对象' }]}
|
|
321
|
+
onChange={(v) => (form.type = v)} />
|
|
322
|
+
</AppForm.Item>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
- `items` 是唯一入口:`{ value, label, description?, disabled? }`;`modelValue` + `onChange(value)` 走统一词表。
|
|
326
|
+
- 宽度:卡片基准 240、上限 360 —— 窄容器自动一列、宽容器并排,**不要**在应用侧给卡片写宽高。
|
|
327
|
+
- 禁用:整组 `disabled`,或单项 `items[].disabled`。
|
|
328
|
+
- 选中态(描边 + 浅底 + 圆点)、hover、键盘成组切换都是组件的事,**不要自绘卡片选项**。
|
|
329
|
+
|
|
330
|
+
### 4.10 弹窗(`AppDialog`)
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
<AppDialog open={visible} title="新增分类" width={640}
|
|
334
|
+
footer={() => (
|
|
335
|
+
<>
|
|
336
|
+
<AppButton tone="secondary" onClick={close}>取消</AppButton>
|
|
337
|
+
<AppButton tone="primary" loading={saving} onClick={submit}>确定</AppButton>
|
|
338
|
+
</>
|
|
339
|
+
)}
|
|
340
|
+
onUpdate:open={(v) => (visible = v)}>
|
|
341
|
+
…内容…
|
|
342
|
+
</AppDialog>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
- `open` 受控 + `onUpdate:open`;标题栏走内建件(`title` + 右上角关闭),**不要自绘 header**。
|
|
346
|
+
- 页脚用 `footer`(函数或节点)或 `footer` 插槽:容器(右对齐 / 内距 / `flex-shrink:0` 常驻不滚)
|
|
347
|
+
由本包补 —— **应用侧不要自绘 footer 容器**(消费方只需给按钮)。
|
|
348
|
+
- 关闭拦截用 `beforeClose`(返回 `false` 阻止关闭,可用来弹「确认退出」)。
|
|
349
|
+
- 内容固定渲染进 `.app-container`(令牌与应用侧样式的作用域锚点),渲染到 body 会「样式全丢」。
|
|
350
|
+
- 需要「固定高度 + 区域内滚动」时传 `fitContent={false}` + `height`。
|
|
351
|
+
|
|
352
|
+
### 4.11 `AppSection`(区块:标题 + 内容)
|
|
353
|
+
|
|
354
|
+
页面内容里的**小节**(详情页的「基本信息 / 映射语义 / 映射策略」、长表单里的分组)用它 ——
|
|
355
|
+
**不要自绘 `<div class="xx-sub-title">`,也不要拿 `AppPanel` 顶**。
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
<AppSection title="映射策略">
|
|
359
|
+
<AppTable framed rows={rows} columns={cols} rowKey="id" />
|
|
360
|
+
</AppSection>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
- 它只管三件事:**标题排版**(14px/600)、**内容间距**(8px)、**区块间距**(16px 叠父级 gap)。
|
|
364
|
+
内容间距必须由它给:父容器的 `gap` 只作用于**直接子元素**,内容被包进本件后里面的间距就没人管了。
|
|
365
|
+
- `title` 不传 → 不渲染标题行,只借区块间距;`extra` 槽放标题行尾部内容(计数、行内链接/按钮)。
|
|
366
|
+
- **嵌套(区块里再分组)自动降级**:直接子区块**不再叠区块间距**(父级 gap 8px 就是组内行距),
|
|
367
|
+
其标题降为**常规字重** —— 层级差由「一级 14/600 ↔ 二级 14/400」表达,而不是再叠 16px。
|
|
368
|
+
需要二级分组时直接嵌套即可,**应用侧不要自写 margin / font-weight 去修**(视觉属性在应用侧护栏内,
|
|
369
|
+
且真正的规则只有一套:`.ak-section > .ak-section`)。
|
|
370
|
+
- **`AppForm.Section` 就是本件**(同一对象、同一套样式;表单里写这个名字只为读起来贴语境):
|
|
371
|
+
区块这件事只有一套实现,改样式改 `app-section.tsx` / `.ak-section*`,不要另写一份。
|
|
372
|
+
- **与 `AppPanel` 的判据**:**内容会不会自己滚?有没有区域级筛选 / 操作 / 分页?**
|
|
373
|
+
有 → `AppPanel`(区域,§4.3);只是「给一段内容起个名」→ 本件(区块)。
|
|
374
|
+
拿 `AppPanel` 当带标题的卡片用,会把**区域头的高度与滚动契约**带进不需要它的地方
|
|
375
|
+
(真实发生:查看弹窗里 5 个区块被渲染成 48px 的 `.ak-panel__header`,而确认页同一批区块是 22px 的区块标题 ——
|
|
376
|
+
同一份数据两个页面两套规格,改一处另一处不会跟)。
|
|
377
|
+
|
|
378
|
+
### 4.12 `AppLayout`(布局原子件:`Row` / `Column`)
|
|
379
|
+
|
|
380
|
+
应用侧 CSS 只允许写布局,但**布局也不该各页面各写一套** —— `display:flex; align-items:center; gap:8px`
|
|
381
|
+
抄十遍之后,间距档与居中口径必然各页不同。两件只做:**轴向**、**间距档**(令牌档位)、**对齐**。
|
|
382
|
+
|
|
383
|
+
方向按**表格语义**定 —— 名字说的是"它**排**什么",不是"它自己长什么样":
|
|
384
|
+
**行与行上下相邻 ⇒ `Row` 竖排**;**列与列左右相邻 ⇒ `Column` 横排**。于是 CSS 里 `.ak-row` 是
|
|
385
|
+
`flex-direction: column`、`.ak-column` 是 `row` —— **与 CSS 属性名正好相反是刻意的**(`<tr>` 一行行往下堆、
|
|
386
|
+
一行里的格子左右并排,才是读表格的直觉)。写代码时按"我要排行还是排列"想,别按 CSS 想。
|
|
387
|
+
|
|
388
|
+
| 成员 | 干什么 | 什么时候用 |
|
|
389
|
+
|---|---|---|
|
|
390
|
+
| `AppLayout.Row` | **排「行」**:不传 `columns` = 竖排(一条一行往下堆);传了 = **一行分 n 列**(等分栅格,列数上限 + 窄了自动减列) | 逐条清单 / 摘要字段 / 并排字段与卡片 |
|
|
391
|
+
| `AppLayout.Column` | **排「列」**:水平容器(内容左右排)+ 栅格单元 | 徽标+文本 / 标签+值 / 控件+按钮 / `Row columns` 里的一格 |
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
{/* 竖排(不传 columns):一条一行、行内一列(列里横排) */}
|
|
395
|
+
<AppLayout.Row gap="lg">
|
|
396
|
+
<AppLayout.Column gap="sm"><AppBadge tone="success" shape="soft">通过</AppBadge><span>编码唯一性</span></AppLayout.Column>
|
|
397
|
+
<AppLayout.Column gap="sm"><AppBadge tone="success" shape="soft">通过</AppBadge><span>列结构一致性</span></AppLayout.Column>
|
|
398
|
+
</AppLayout.Row>
|
|
399
|
+
|
|
400
|
+
{/* 一行分 n 列(传了 columns) */}
|
|
401
|
+
<AppLayout.Row columns={2} gap="lg">
|
|
402
|
+
<AppLayout.Column gap="sm"><span>缓存方式</span><span>按需缓存</span></AppLayout.Column>
|
|
403
|
+
<AppLayout.Column gap="sm"><span>年度策略</span><span>无</span></AppLayout.Column>
|
|
404
|
+
<AppLayout.Column span={2}>整行内容</AppLayout.Column>
|
|
405
|
+
</AppLayout.Row>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
- **横向的"一组"一律用 `Column`**(徽标 + 文本、标签 + 值、输入框 + 按钮):它是水平容器,默认 `align-items: center`
|
|
409
|
+
(齐中线);内容宽了要折行就给它 `wrap`(表格单元格里的「按钮 + 摘要」靠它不撑破外层)。
|
|
410
|
+
- **往下的"一条条"归 `Row`**(或父容器的间距:`AppForm.Section` / `AppForm` 自带 8px —— 有可借的容器就别再叠一层)。
|
|
411
|
+
- **一格里要上下堆**(一个格子两行文字)→ 在 `Column` 里放一条 `Row`;**不要**拿 `Column` 当竖排容器用。
|
|
412
|
+
- **一件两形态**:`columns` 不传 = 竖排(`flex`,方向 column);传了 = 一行分 n 列(`grid`,轨道横向铺)。
|
|
413
|
+
一行只有一种心智,区别只在"这一行是往下堆还是分几格",所以**不另出 `Horizontal` / `Vertical`** ——
|
|
414
|
+
多一个件就多一个要选的名字、还要解释它与 `Row` 的边界。
|
|
415
|
+
- `gap`:`none` 0 / `sm` 4 / `md` 8(**默认**)/ `lg` 12 / `xl` 16(px,只映射 `--ui-space-1/2/3/4`,**不开放任意 px**)。
|
|
416
|
+
- `align` / `justify`:`start | center | end | baseline | stretch` / `start | center | end | between`。
|
|
417
|
+
`Row` 的交叉轴是**水平**(不传即 CSS 默认 `stretch`,子项齐宽);`Column` 的交叉轴是**纵向**,
|
|
418
|
+
默认 `center`(徽标/控件与文本齐中线)。`justify` 在水平容器上与直觉一致(`between` = 左文右操作);
|
|
419
|
+
栅格形态的横向铺满由 auto-fit 承担。
|
|
420
|
+
- `columns` 是**列数上限**(1/2/3/4),`minColumnWidth` 是单元最小宽度(默认取令牌 `--ui-grid-column-min` 260);
|
|
421
|
+
两者一起决定实际列数:**宽容器最多 N 列,窄了自动减列**(headless Chrome 实测 N=2/260:1400→2、900→2、600→2、500→1 列)。
|
|
422
|
+
实现是 `auto-fit + minmax(max(最小列宽, 按上限等分))`,**不是固定份数**(固定份数在宽容器里会挤成很窄的多列),
|
|
423
|
+
**也不用媒体查询断点**(本仓页面都在弹窗/分栏里,视口宽度代表不了容器宽度)。
|
|
424
|
+
- `Column` 的 `span` 跨格(默认 1,≤ 所在行的 `columns`),可做 2:1 这类不对称分栏。
|
|
425
|
+
- 两件都放开 `min-width: 0`:塞得进窄格子(表格单元格、限宽控件列),不会把外层撑破。
|
|
426
|
+
- **只写 `AppLayout.<成员>`**:不平级导出 `AppRow` / `AppColumn` / `AppHorizontal` ——
|
|
427
|
+
通用词会与 `AppTableColumn` 在一份 import 清单里混读,也破坏「成族子件用点号」的约定
|
|
428
|
+
(`AppShell.Header` / `AppPanel.Body` / `AppForm.Item` / `AppForm.Section`)。
|
|
429
|
+
`AppLayout` 是**命名空间**(不是组件);护栏自测锁住了"没有平级导出"。
|
|
430
|
+
- **没有 `Vertical`**:一条条往下堆就是 `Row` 的默认形态,不再出第二个同义件。
|
|
431
|
+
- **方向口径有自测锁住**(`app-layout.spec.ts`:`Row` 必须 `flex-direction: column`、`Column` 必须 `row`)——
|
|
432
|
+
改这两条等于改口径,先回头读本节。
|
|
433
|
+
- **边界**:表单里并排字段用 `AppForm columns={2}`(它管 label 列与行距);页面级两栏用 `AppShell.Split`(§4.2);
|
|
434
|
+
滚动 / 工具位 / 卡片外观都不归本族。手写 flex 只留给**一次性特例**(固定高度/自身滚动/复杂定位的容器)。
|
|
435
|
+
|
|
436
|
+
### 4.13 消息提示(`messageBox` / `notify` / `loading`)
|
|
437
|
+
|
|
438
|
+
- **确认、提示、报错一律走 `messageBox`,不要自绘确认弹窗**(`AppDialog` + 感叹号方块 + 双按钮那套已退役):
|
|
439
|
+
形态、按钮、层级、Esc 行为都由消息框提供,应用侧只出**文案**。
|
|
440
|
+
- **文案用文本传,尽量不用 HTML**:分行用 `\n`(`.toast-msg-title/detail` 已放开 `white-space: pre-line`),
|
|
441
|
+
嵌业务值直接拼字符串(服务内部**先转义**再交底层渲染,所以"编码里的尖括号"不会再被当标签)。
|
|
442
|
+
拼 `<div>` / `<b>` + 自己 `escapeHtml` 的老写法不许再用。
|
|
443
|
+
- 唯一例外是**静态长文说明**(如「值映射说明」这种多段须知)需要分段/强调:显式 `richText: true`,
|
|
444
|
+
内容必须来自静态语言包,**禁止拼接用户输入**。
|
|
445
|
+
- `confirm({ title, description, confirmText?, cancelText? })`:`title` = 主文案(第一行,问句也可放这里)、
|
|
446
|
+
`description` = 主文案下方的补充小字;点确定 `resolve(true)`、点取消 `resolve(false)`。
|
|
447
|
+
右上角 ✕ / Esc 只关窗**不触发回调**(farris 行为),Promise 不落定 —— 调用方按"未确认"处理即可。
|
|
448
|
+
- 轻量反馈(保存成功/失败)用 `notify`,长任务用 `loading`;两者都不带确认语义。
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 5. 反例库(这些写法一律违规)
|
|
453
|
+
|
|
454
|
+
| 反例 | 为什么违规 | 正确写法 |
|
|
455
|
+
|---|---|---|
|
|
456
|
+
| 把只过滤**右表**的搜索框放进左树面板的 `toolbar` | 筛选越区(§4.5 判据第 ① 步) | 放进右面板 `toolbar` |
|
|
457
|
+
| 把只过滤**左树**的搜索框放进右面板 `toolbar` | 同上 | 放进左面板 `toolbar` |
|
|
458
|
+
| 在右面板 `toolbar` 塞 4 个以上字段 | 超出区域级字段上限 | 上提为 `AppShell.Filter` |
|
|
459
|
+
| 把「新建」放进 `toolbar`、把搜索放进 `actions` | 两位分工互换 | 搜索进 `toolbar`、操作进 `actions` |
|
|
460
|
+
| 一个按钮组里写了两个 `tone="primary"`,或 `primary` 不在第一个 | 主操作不唯一、视觉焦点分散(§4.5) | 只给该组**第一个**按钮 `tone="primary"` |
|
|
461
|
+
| 按钮组里非首个按钮省略 `tone`(`<AppButton onClick=…>`) | `tone` 缺省 + `solid` 会渲染成**主色**,等于又多一颗主色按钮(§4.5) | 显式给 `tone="secondary"`(图标按钮用 `shape="ghost"`) |
|
|
462
|
+
| 把加载错误塞进空态槽(`empty={loadError || t('x.empty')}`) | 错误被渲染成空态:没有错误语义、也没有恢复入口,用户分不清「没数据」还是「加载挂了」(§4.6) | 错误传 `error`,恢复动作传 `errorActionText` + `onErrorAction` |
|
|
463
|
+
| 空数据 / 加载失败的状态块底下继续渲染分页(「共 0 条 显示 10 条」) | 状态块显得没画完,且「共 0 条」是假数(不是 0 条,是没读到) | 无数据或失败时不渲染分页 |
|
|
464
|
+
| 在 `AppPanel.Body` 里给 `AppTable` 外套一层 `height:auto` 的 div | 表格不再填满 Body → Body 整块滚、**表头跟着滚走** | 直接放;要测量容器就传 `AppTable.onContainerResize` |
|
|
465
|
+
| 给 `AppPanel` 或面板容器补描边/圆角/阴影/底色 | 卡片外观唯一来源被破坏(双框) | 组件不带外观;卡片外观用 `AppTable framed` 等内容件 |
|
|
466
|
+
| 在 Split 栏的第一层子元素上写 `border-right` / `border-left` | 与 Split 自带分隔线叠成第二条线 | 删掉,分隔线归 Split |
|
|
467
|
+
| 应用 CSS 写 `.ak-*` 选择器、写 `!important`、写 `rem` 尺寸、写视觉属性 | 违反 §6 样式纪律 | 布局属性只写布局;视觉走组件或 `--ui-*` 令牌 |
|
|
468
|
+
| 直接 `import { FDataGrid } from '@farris/ui-vue'` | 绕过本包边界(底层库必须被收在包内) | 用 `AppTable`;缺件走 §7 建件流程 |
|
|
469
|
+
| 自绘表单行(`.xxx-form-row` + 自写 label 宽 / 必填星号 / 错误 div) | 同一套几何在两处口径漂移(label 宽度、星号位置、错误间距各写各的) | 用 `AppForm.Item`(§4.7) |
|
|
470
|
+
| 自绘步骤条(序号圆点 + 连接线 + 当前/完成态) | 状态机与视觉重复实现,与其它页面不一致 | 用 `AppSteps`(§4.8) |
|
|
471
|
+
| 自绘卡片式选项(`.xxx-card-option` + 手写 `selected` 类 + 自己 `onClick`) | 视觉被护栏收走后只剩布局 ⇒ 选中态不可见(踩过);键盘/无障碍也没有 | 用 `AppRadioCard`(§4.9) |
|
|
472
|
+
| 自绘区块标题(`<div class="xx-sub-title">` + 自己调 margin) | 标题视觉与区块间距各页面各写一套,改了不会同步(`.vm-summary`/`.vm-sub-title` 就是这么变残的) | 用 `AppSection` / `AppForm.Section`(§4.11) |
|
|
473
|
+
| 拿 `AppPanel` 当「带标题的卡片」用(无 toolbar/actions、内容也不滚) | 把区域头的高度(48px)与 Body 的滚动契约带进不需要它的地方;同一批区块在别处是 22px 标题 ⇒ 两套规格 | 区块用 `AppSection`(§4.11),`AppPanel` 只留给真区域(§4.3) |
|
|
474
|
+
| 把校验错误或说明做成绝对定位的浮层提示 | 在弹窗/窄行里会压住下一行控件(farris 动态表单即此形态) | `AppForm.Item` 的 `error` / `hint`(§4.7) |
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## 6. 样式纪律
|
|
479
|
+
|
|
480
|
+
**应用侧 CSS 只允许布局属性**:`display` / `flex*` / `grid*` / `gap` / `width` / `height` / `min-*` / `max-*` /
|
|
481
|
+
`padding` / `margin` / `overflow` / `position` / `inset` / `align-*` / `justify-*` / `z-index` / `transform` /
|
|
482
|
+
`transition` / `cursor` / `white-space` / `text-align`。
|
|
483
|
+
|
|
484
|
+
**禁止**:`color` / `background*` / `border*(含色值)` / `border-radius` / `box-shadow` / `font-*` /
|
|
485
|
+
`fill` / `stroke` / `!important` / 裸元素选择器 / 自定义令牌 / **自写 `rem` 尺寸** / 书写 `.ak-*` 选择器。
|
|
486
|
+
|
|
487
|
+
- 字号 / 圆角 / 行高 / 间距 / 行高一律取 `--ui-font-*`(13/14/16/18/20)、`--ui-radius-*`(4/8/full)、
|
|
488
|
+
`--ui-space-*`、`--ui-line-*`、`--ui-weight-*`、`--ui-shadow-*`,不自由取值。
|
|
489
|
+
- **唯一例外**:文件头带 `@figma-fidelity` 标记且登记在豁免注册表的设计还原文件(跳过视觉属性 / 裸元素 /
|
|
490
|
+
选择器前缀三类检查,但 `!important` 与自写 rem 仍然禁止,且字面量只减不增)。
|
|
491
|
+
- 底层组件库内部 px 若确需覆写,**只能进包内 `farris-bridge.css`**(那里是全仓唯一允许出现 `!important` 的地方)。
|
|
492
|
+
- **布局优先用 `AppLayout` 两件**(§4.12):排「行」用 `Row`(竖排 / 传了 `columns` 则一行分 n 列)、
|
|
493
|
+
排「列」用 `Column`(水平容器;横排一组都归它)。
|
|
494
|
+
手写 `display:flex` + `gap` 只留给一次性特例 —— 理由与视觉属性一样:同一套间距/居中口径在十处各写一遍必然漂开,
|
|
495
|
+
组件里只有令牌档位。
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## 7. 缺件处置流程
|
|
500
|
+
|
|
501
|
+
需要的能力本包没有时,**不要**在页面里自绘外观,也不要直接引底层组件库:
|
|
502
|
+
|
|
503
|
+
1. 先查本文件已列能力与包导出面;
|
|
504
|
+
2. 确实缺件 → 在本包内**新建封装件**(命名锚定通用规范名,props 查 §3 词表),补导出面与单测;
|
|
505
|
+
3. 建件前先确认底层库是否已有对应件:有则封装(吃掉其坑),无则自建(原生元素 + `--ui-*` 令牌);
|
|
506
|
+
4. 页面侧只用建好的件;自建件的实现踩坑与取舍写进包内 `AGENTS.md`。
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## 8. 必跑命令与护栏
|
|
511
|
+
|
|
512
|
+
接入方在**应用包根**放 `appkit-guardrails.config.json`(应用目录、类名前缀、存量挂起、豁免注册表、`contractDoc`),
|
|
513
|
+
`package.json` 里**只挂两条**(三条护栏由统一入口并发跑,不要各挂一条):
|
|
514
|
+
|
|
515
|
+
```json
|
|
516
|
+
"lint": "node node_modules/@manohub/app-kit/lint/run-all.mjs",
|
|
517
|
+
"lint:changed": "node node_modules/@manohub/app-kit/lint/run-all.mjs --changed"
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
- 统一入口并发跑三条:`style-audit.mjs`(样式:只允许布局属性 / 禁 `!important` / 禁自写 rem)、
|
|
521
|
+
`component-audit.mjs`(组件使用:禁底层直连 / 禁底层风格写法)、
|
|
522
|
+
`structure-audit.mjs`(页面结构:骨架必用 / 禁自绘页头面板头 / 字段数上限)。
|
|
523
|
+
**排查单条**时才直接跑它(`node node_modules/@manohub/app-kit/lint/style-audit.mjs`),不必挂成脚本。
|
|
524
|
+
- `--changed` 只跑改动文件(pre-commit 用;非 git 环境不裁剪,宁可多查)。
|
|
525
|
+
- 每条违规都会给出 `file:line` + 片段 + **唯一改法**(`correction.summary` / `.example`)与 **`doc` 锚点**
|
|
526
|
+
(指回本文件对应章节)。**照 `correction` 改,不要自己另想一套。**
|
|
527
|
+
- 存量文件的口径:先产基线 → 挂起(`pending` + 豁免登记)→ **新增与改动过的文件必须归零**。
|
|
528
|
+
|
|
529
|
+
### 8.1 提交前检查(可选,目标 < 3 秒)
|
|
530
|
+
|
|
531
|
+
包内提供钩子样本,放进默认 hooks 目录即可启用(**不需要改 git 配置**):
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
cp node_modules/@manohub/app-kit/lint/pre-commit.sample .git/hooks/pre-commit
|
|
535
|
+
chmod +x .git/hooks/pre-commit
|
|
536
|
+
# 应用包不是仓库根时(monorepo),在钩子里或提交前 export 出包目录:
|
|
537
|
+
export APPKIT_APP_DIR=apps/sub-app
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
钩子只跑 `run-all.mjs --changed`(改动文件),所以秒级返回;`--changed` 在非 git 环境不裁剪(宁可多查)。
|
|
541
|
+
|
|
542
|
+
### 8.2 包自身(`@manohub/app-kit`)的门禁
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
pnpm --filter @manohub/app-kit type-check # 包内 vue-tsc:0 错
|
|
546
|
+
pnpm --filter @manohub/app-kit test:unit # vitest(契约单测)+ 护栏自测
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## 9. 已知偏差(有意为之,不要当 bug 修)
|
|
552
|
+
|
|
553
|
+
1. **缩放:全应用尺寸与字号统一取令牌(px),不跟随根字号缩放。**
|
|
554
|
+
平台调到 150% 档时,本包骨架与其它已接入应用观感一致(都不跟随)。
|
|
555
|
+
`AppShell.Split.sidebar.width` 因此是 px 数字,不要传 `rem` 字符串。
|
|
556
|
+
2. **初始守卫与选择模式**:`createSubApp` 默认首帧把非根路径重置到 `/` **并清空 query**。
|
|
557
|
+
以 `index.html?xxx=1` 形式直开(iframe / 选择模式)的入口必须用 `rootPathAliases` 登记该路径,否则 query 会被吞掉。
|
|
558
|
+
3. **`subAppContext` 归属**:`createSubApp` 在 `.app-container` 层 `provide('subAppContext')` 并写入宿主 globalData 快照。
|
|
559
|
+
应用侧若另有同名 provide,以**更内层**的为准(应用自己的出口优先)。
|
|
560
|
+
4. **分发隔离是人工约定**:包内 `AGENTS.md` 属开发文档,接入方只读 `CONTRACT.md`。
|
|
561
|
+
5. **`AppTable` 的 `columns[].width`**:数字按权重参与百分比分配,也接受百分比字符串;同一列集合内可混排
|
|
562
|
+
(分配规则与迁移前一致)。`tableKey` 只在缩放档位/尺寸基准变化时递增,日常容器宽度变化用 `onContainerResize`。
|
|
563
|
+
**行对象身份**:`columns[].render` / `rowClass` / `onRowClick` / `onRowDoubleClick` 收到的都是**传入 `rows` 的那一个业务对象**
|
|
564
|
+
(内部只把副本交给底层网格挂元数据),因此可以直接回写 —— `render: (row) => <AppInput onChange={(v) => (row.字段 = v)} />`
|
|
565
|
+
是受支持写法,不需要再用 index 兜底。
|
|
566
|
+
6. **`AppTree` 为自建件**(不封装底层树组件):底层树组件是列模板式、只有 `expandAll`/`parentId` 扁平模型
|
|
567
|
+
(无逐节点受控展开态)、且只在挂载时填充一次,与「数据刷新不丢展开态」冲突。
|
|
568
|
+
数据刷新不会改变展开态;`treeKey` 仅作极端场景的强制重挂出口。
|
|
569
|
+
视觉与几何对齐 `@aihub/theme` 的 Tree 规范(行高 30 / 紧凑档 28、圆角 6、字号 13、
|
|
570
|
+
滑过 Ne11、点击瞬态 `#F4F5F9`、选中 Au02 底 + Th03 字、箭头 8px)。
|
|
571
|
+
**缩进步进在 CSS 里算**:行上下发 `data-level`(数据)与 `--ak-tree-level`,
|
|
572
|
+
`padding-left = var(--ui-tree-indent-base) + level × var(--ui-tree-indent-step)`,
|
|
573
|
+
所以**不要**在应用侧改行内 padding 或写死缩进像素。
|
|
574
|
+
7. **`AppNotice`(说明条 / 警告提示)为自建件**:底层组件库**没有 Alert 组件**
|
|
575
|
+
(无 `FAlert` 导出;`.f-message-strip` 只是 notify 的 toast 容器),故自建并**对齐 `@aihub/theme` 的 Alert 规范**
|
|
576
|
+
(通栏 100% 宽、高 42、圆角 6、投影 `0 2 10 rgba(0,0,0,.08)`、文案 15px、语义底/边/字三件套)。
|
|
577
|
+
口径:`tone` 默认 `info`;**警示 / 错误按规范常驻**(`closable` 默认 `false`,只有通知类信息才显式开启);
|
|
578
|
+
行内可点文字用 `<AppButton shape="link">`,不要再自绘链接样式。
|
|
579
|
+
位置:页面顶部通栏(`AppShell.Body` 顶部),静态展示、不随滚动消失。
|
|
580
|
+
8. **`AppForm`(表单行)为自建件**:底层对应件 `FDynamicFormGroup` 是**动态表单(元数据驱动)体系**的分组件 ——
|
|
581
|
+
控件要经 `editor` 描述对象交给它渲染、标签宽度与字段限宽写死在 farris 表单类里、校验信息是**绝对定位浮层**。
|
|
582
|
+
与本包三条口径冲突(控件由消费方直接写 / 只收朴素业务值 / 错误走文档流),故按 §7 自建;
|
|
583
|
+
只沿用 farris 的两条既有约定:label 右对齐、必填星号在 label 文本左侧。
|
|
584
|
+
9. **`AppSteps` 复刻了底层步骤条的节点结构**:底层 `FStep` 的默认模板**只渲染 `title`**
|
|
585
|
+
(`description` / `icon` / `class` / `status` 四个字段压根不被读取),第二行副标题只能自己给 `stepTemplate`;
|
|
586
|
+
本包按底层默认结构复刻 DOM 并沿用其类名(底层配色与连接线样式照旧生效)。
|
|
587
|
+
另有两条同源事实:底层 `clickable` prop **完全无效**(真正拦住跳转的只有单步 `disabled`),
|
|
588
|
+
整条只读由本包门控承担;底层放行时会自己改内部态,本包恒不改内部态、只上抛新值(受控语义)。
|
|
589
|
+
10. **弹窗内容固定渲染进 `.app-container`**:底层 `FModal` 默认把弹窗 Teleport 到 `body`,
|
|
590
|
+
而令牌(`--ui-*`)与主题桥接(`--f-theme-*`)都锚在 `.app-container` 上、微前端下应用侧 CSS 还被
|
|
591
|
+
scopecss 限定在容器内 —— 落到 body 会让弹窗**内容里所有组件与应用样式一起失效**。
|
|
592
|
+
`AppDialog` / `modalService` 内部已改投 `.app-container`,消费方无需也不应干预渲染宿主。
|
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @manohub/app-kit
|
|
2
|
+
|
|
3
|
+
子应用统一骨架层(**预编译产物分发**,消费方按版本号安装):入口编排、布局契约(`AppShell`/`AppPanel`)、页面组件、原子件、服务、样式底座。
|
|
4
|
+
底层组件库(farris)被收敛在本包内部 —— 消费方不得安装或直连它。
|
|
5
|
+
|
|
6
|
+
- **接入方必读**:[CONTRACT.md](./CONTRACT.md) —— 页面结构契约、组件用法与词表、样式纪律、反例库、护栏用法、已知偏差。
|
|
7
|
+
**写页面代码前先读它**;它随包分发,冲突时以它为准。
|
|
8
|
+
- **改本包时才看**:`AGENTS.md`(包内实现踩坑与硬性要求,不随包分发)
|
|
9
|
+
- **入口**:`@manohub/app-kit/entry` → `createSubApp`
|
|
10
|
+
- **组件/服务**:`@manohub/app-kit` → `AppShell` / `AppPanel` / `AppTree` / `AppTable` / `AppButton` / `notify` …
|
|
11
|
+
- **样式**:`@manohub/app-kit/reset.css` + `@manohub/app-kit/styles.css`(应用侧 `style.css` 固定三行,见 CONTRACT.md §1)
|
|
12
|
+
- **护栏脚本**:`lint/style-audit.mjs` / `lint/component-audit.mjs` / `lint/structure-audit.mjs` / `lint/run-all.mjs`
|
|
13
|
+
(用法见 CONTRACT.md §8)
|
|
14
|
+
|
|
15
|
+
## 分发形态
|
|
16
|
+
|
|
17
|
+
由**源码分发**改为**产物分发**:`exports` 指向 `dist`(含类型声明与 `styles.css` / `reset.css`)。
|
|
18
|
+
|
|
19
|
+
- 公开入口名与源码分发期**逐字一致**,消费方的引用语句与样式导入三行不需要改;
|
|
20
|
+
- 消费方不再需要为本包准备 TSX 编译配置或单例包的类型钉死;
|
|
21
|
+
- `lint/`、`CONTRACT.md` 仍原样随包分发(消费方的护栏与契约按包内路径引用它们);
|
|
22
|
+
- 包内不存在 `dist/` 时先执行构建再联调。
|
|
23
|
+
|
|
24
|
+
## 来源
|
|
25
|
+
|
|
26
|
+
本包由 `gsp-cloud-ds/dip/ibp/aihub/aihub-frontend` 的 `app-kit` 分支(导入 commit `315b60f`)迁入,
|
|
27
|
+
只调整分发形态,未改包内实现。设计与实现依据(原仓 `docs/plans/*`)未随包迁入,留档在来源仓。
|