@done-coding/admin-core 0.31.0 → 0.31.1-alpha.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 (80) hide show
  1. package/es/bridge/config-hook.mjs +3 -0
  2. package/es/bridge/index.mjs +6 -0
  3. package/es/bridge/theme/index.mjs +11 -3
  4. package/es/components/app-layout/AppBody.vue.mjs +1 -1
  5. package/es/components/app-layout/AppBody.vue2.mjs +2 -2
  6. package/es/components/app-layout/AppPage.vue.mjs +1 -1
  7. package/es/components/app-layout/AppPage.vue2.mjs +175 -33
  8. package/es/components/app-layout/AppPageNavBar.vue.mjs +7 -0
  9. package/es/components/app-layout/AppPageNavBar.vue2.mjs +148 -0
  10. package/es/components/app-layout/app-page-geometry.mjs +3 -2
  11. package/es/components/app-layout/app-page-slot-surface.mjs +31 -0
  12. package/es/components/app-layout/use-app-page-title-field.mjs +41 -0
  13. package/es/components/data-view/DataGridView.vue.mjs +1 -1
  14. package/es/components/data-view/DataGridView.vue2.mjs +2 -1
  15. package/es/components/data-view/DataListView.vue.mjs +1 -1
  16. package/es/components/data-view/DataListView.vue2.mjs +14 -9
  17. package/es/components/data-view/DataListViewItem.vue.mjs +1 -1
  18. package/es/components/data-view/DataListViewItem.vue2.mjs +3 -1
  19. package/es/components/data-view/InfiniteListView.vue.mjs +1 -1
  20. package/es/components/data-view/InfiniteListView.vue2.mjs +25 -18
  21. package/es/components/data-view/use-grid-layout.mjs +13 -15
  22. package/es/components/display/TabsMain.vue.mjs +3 -1
  23. package/es/components/display/use-badge.mjs +10 -3
  24. package/es/components/form/FormItem.vue.mjs +1 -1
  25. package/es/components/form/FormMain.vue.mjs +1 -1
  26. package/es/components/form/FormMain.vue2.mjs +11 -4
  27. package/es/components/form/FormSearch.vue.mjs +1 -1
  28. package/es/components/form/FormSearch.vue2.mjs +12 -4
  29. package/es/components/form/FormSubmitPanel.vue.mjs +1 -1
  30. package/es/components/form/FormSubmitPanel.vue2.mjs +2 -0
  31. package/es/components/form/use-ai-fill.mjs +7 -1
  32. package/es/components/list-layout/ListLayout.vue.mjs +1 -1
  33. package/es/components/list-layout/ListLayout.vue2.mjs +19 -10
  34. package/es/components/modal/ModalConfirm.vue.mjs +1 -1
  35. package/es/components/modal/ModalConfirm.vue2.mjs +28 -5
  36. package/es/components/table/TableToolbar.vue.mjs +1 -1
  37. package/es/components/table/TableToolbar.vue2.mjs +15 -1
  38. package/es/components/view-layout/ViewLayout.vue.mjs +1 -1
  39. package/es/components/view-layout/ViewLayout.vue2.mjs +22 -29
  40. package/es/components/view-layout/ViewLayoutToolbar.vue.mjs +1 -1
  41. package/es/components/view-layout/ViewLayoutToolbar.vue2.mjs +16 -129
  42. package/es/config/slot-region.mjs +1 -0
  43. package/es/hooks/use-surface.mjs +5 -0
  44. package/es/hooks/use-theme-apply.mjs +18 -0
  45. package/es/index.mjs +4 -1
  46. package/es/store/app.mjs +26 -11
  47. package/es/style.css +230 -172
  48. package/package.json +2 -2
  49. package/src/bridge/docs/README.md +19 -3
  50. package/src/components/app-layout/docs/README-AppBody.md +25 -1
  51. package/src/components/app-layout/docs/README-AppHeader.md +1 -1
  52. package/src/components/app-layout/docs/README-AppPage.md +172 -3
  53. package/src/components/data-view/docs/README-DataListView.md +40 -1
  54. package/src/components/data-view/docs/README-InfiniteListView.md +68 -4
  55. package/src/components/display/docs/README-BadgeMark.md +18 -10
  56. package/src/components/form/docs/README-FormMain.md +26 -0
  57. package/src/components/list-layout/docs/README-ListLayout.md +12 -1
  58. package/src/components/menu/README.md +1 -1
  59. package/src/components/modal/docs/README-ModalConfirm.md +57 -0
  60. package/src/components/view-layout/docs/README-ViewLayout.md +46 -129
  61. package/types/bridge/config-hook.d.ts +5 -2
  62. package/types/bridge/index.d.ts +22 -0
  63. package/types/components/app-layout/AppPage.vue.d.ts +43 -1
  64. package/types/components/app-layout/AppPageNavBar.vue.d.ts +48 -0
  65. package/types/components/app-layout/app-page-geometry.d.ts +27 -2
  66. package/types/components/app-layout/app-page-slot-surface.d.ts +18 -0
  67. package/types/components/app-layout/index.d.ts +1 -0
  68. package/types/components/app-layout/types.d.ts +37 -1
  69. package/types/components/app-layout/use-app-page-title-field.d.ts +49 -0
  70. package/types/components/data-view/DataListViewItem.vue.d.ts +7 -0
  71. package/types/components/data-view/types.d.ts +26 -3
  72. package/types/components/data-view/use-grid-layout.d.ts +31 -6
  73. package/types/components/form/types.d.ts +8 -2
  74. package/types/components/list-layout/ListLayout.vue.d.ts +23 -2
  75. package/types/components/list-layout/types.d.ts +7 -0
  76. package/types/components/view-layout/ViewLayout.vue.d.ts +4 -16
  77. package/types/components/view-layout/ViewLayoutToolbar.vue.d.ts +3 -9
  78. package/types/components/view-layout/types.d.ts +11 -50
  79. package/types/config/slot-region.d.ts +17 -1
  80. package/types/injectInfo.json.d.ts +1 -1
@@ -90,6 +90,63 @@
90
90
 
91
91
  无。`inheritAttrs: false` + 全量 `v-bind $attrs` 透传底层 ElDialog/ElDrawer(未声明 EP prop 如 `draggable`/`destroy-on-close` 直达);provide `BODY_CONTENT_VIEWPORT_HEIGHT`(老 key)+ `SCOPE_VIEWPORT_MAX_HEIGHT`(新 key)。
92
92
 
93
+ ## 抽屉的 chrome 面貌(v0.32.0)
94
+
95
+ 三段结构的观感由三样东西撑起来,**[MUST] 一起看**:
96
+
97
+ | 项 | 取值 | 为什么 |
98
+ | --- | --- | --- |
99
+ | **分割线** | `var(--dc-core-border-color, var(--el-border-color))` | 走规则 14 的两类来源 ⇒ **切主题 / 切亮暗时跟着变**。此前是**裸 `--el-border-color-lighter`**:主题系的 `borderColor` 映射到 `--el-border-color`,它读的是另一个变量 ⇒ 换主题时这条线纹丝不动 |
100
+ | **header / footer 面色** | `var(--dc-core-surface, var(--el-bg-color))` | 与正文分出层次 |
101
+ | **三处 padding** | 横向三处同取**外圈档**;纵向正文取**外圈档**、header/footer 取**呼吸档** | 接回间距阶梯(原先写死 `16/20/12 + 20`,改 gap 基数 / 密度档时一律不动) |
102
+
103
+ 🔴 **面色与分割线互为保底,[MUST] 两个都要**:单靠色差在暗色下不够 ——
104
+ 本仓实测 `ground ↔ surface` 色差**亮 18 / 暗仅 10**。
105
+ 🔴 **[MUST NOT] 把分割线改成阴影**:本仓去框化那次的原话「暗色下投影不可见、只剩灰框」。
106
+ 🔴 **横向三处同值不可拆**:标题 / 正文 / footer 的**左缘恒对齐** ——
107
+ 让 chrome 取呼吸档而正文取外圈档会差一档,本仓为这条对齐专门返工过。
108
+
109
+ ### 🔴 抽屉正文取**底色**,modal 正文仍是**面**(v0.32.0)
110
+
111
+ 三段层次:**chrome 面 / 正文底 / 正文里的卡再成面**。
112
+
113
+ | 形态 | 正文底 | 正文里的组件(`TableMain` / `DataGridView` / `InfiniteListView` / `FormSubmitPanel`) |
114
+ | --- | --- | --- |
115
+ | **drawer** | **底色**(`--dc-core-ground`) | **自持面**(v0.32.0 起恢复) |
116
+ | **modal** | 面(EP 的 overlay 档) | **不二次成面**(逐字不变) |
117
+
118
+ 这靠**两个不同的槽位标**实现:`SlotRegion.ModalConfirm_Body`(modal)与
119
+ `SlotRegion.ModalConfirm_DrawerBody`(drawer)。
120
+ 🔴 **[MUST NOT] 合成一个标再反转它的含义** —— 那个标的语义一直是「**你脚下是一块面**」,
121
+ 抽屉正文改成底之后它就不再是那个意思了;反转含义会把 modal 形态一起改掉,
122
+ 而 modal 那儿的自动关面是**对的**。
123
+
124
+ ⚠️ **两档都 provide 有界可用高** ⇒ `ViewLayout` / `TabsMain` 的 `refine` 默认
125
+ **两个档都列**,漏一个就是「抽屉里掉回自然流」。
126
+
127
+ ⚠️ **暗色下单靠色差不够**(`ground ↔ surface` 实测亮 18 / 暗仅 10)——
128
+ 三段层次 [MUST] 与 header/footer 的**分割线一起**看,[MUST NOT] 因为加了底色就撤线。
129
+
130
+ ### 下游:既有的显式 `surface: false` 要撤
131
+
132
+ 抽屉 → `ListLayout` → `TableMain` 这种**隔了容器**的场景,此前自动关面**够不着**
133
+ (`useContextDefaults` 只读直系父 DOM)⇒ 使用方得手动传 `surface: false` 兜着。
134
+ 正文改底色后**那份手动补偿反过来了**:现在传 `false` 会让内容**摊在底色上**。
135
+ ⇒ **撤掉它**(撤了就是对的)。
136
+
137
+ ### padding 为什么必须落在 `.el-drawer__body`
138
+
139
+ 被观测的正是 `__body`(内容可用高 = 它的 `contentRect.height`,已扣其 padding)
140
+ ⇒ 改它的 padding,provide 出去的可用高**自动跟上、零 JS 改动**。
141
+
142
+ 🔴 **[MUST NOT] 挪到 `.confirm-modal__content`(子元素)**:那份 padding 在被量的盒子
143
+ **里面**、不会从可用高里扣 ⇒ 内部 `ListLayout` / `ViewLayout` 拿到的可用高多出 2×padding
144
+ ⇒ **每个抽屉恒溢出**(v0.28 修过的「refine 四类恒定溢出」第三类)。
145
+
146
+ ℹ️ 值经 `:style` 以 CSS 变量挂在弹层元素上,**[MUST NOT] 改用 `v-bind()`** ——
147
+ 打这些 EP 结构类的样式块是**非 scoped** 的、目标元素被 teleport 到了 body,
148
+ 而 `v-bind()` 的变量挂在**组件根**上,够不着。
149
+
93
150
  ## 反模式 / 注意
94
151
 
95
152
  - **关闭回调命名三件套是 props**:`onClose`/`onCancel`/`onConfirm` 走 props 传(`:on-confirm="..."`),不是 `@confirm`;emit 侧只有 `update:show`
@@ -79,11 +79,16 @@
79
79
  标题本身只给字号 / 字重,[MUST NOT] 自带背景与描边 —— 它坐在 toolbar 这条**分区条**里,
80
80
  底色由条统一给。
81
81
 
82
- **toolbar 是「条」(bar) 不是「卡」(card)**(甲方 2026-09-16 定性):底色取「面与底的中点」
83
- (`color-mix(in srgb, --dc-core-surface 50%, --dc-core-ground)`,亮暗自动跟随),
84
- 但守两条纪律 —— **通栏**(零圆角)· **不加高**(零上下呼吸)。
82
+ **toolbar 是「条」(bar) 不是「卡」(card)**(甲方 2026-09-16 定性):底色直接取**面色那一档**
83
+ (`var(--dc-core-surface, var(--el-bg-color))`,亮暗自动跟随),
84
+ 但守两条纪律 —— **通栏**(零圆角)· **零上下呼吸**([MUST NOT] 加纵向 padding/margin)。
85
85
  此前「工具条成卡」被否的根因不是「有面」,而是面顺带塞进 12px 内呼吸把 32px 的条撑到 56px。
86
86
 
87
+ ⚠️ **v0.32.0(m5tvq8 ②)起不再用 `color-mix`**:原先取「面与底的中点」,而本主题
88
+ ground↔surface 色差实测**亮 18 / 暗仅 10** ⇒ 中点在暗色只剩 Δ5、条与内容几乎分不开
89
+ (这条风险当时就登记了,甲方批注「最后我验收时再调整都来得及」)。取满面色即那次调整,
90
+ [MUST NOT] 退回中点、[MUST NOT] 改用真 alpha 透明(两者不重叠,透明只是多一层合成开销)。
91
+
87
92
  横向呼吸**左右同值**:各 **一份**(甲方 2026-09-19:「对齐右侧 都是 8」)。
88
93
 
89
94
  ⚠️ 此前左缘是**两份**,理由是「左缘那条 3px 主色条占住了第一层留白,只给一份时
@@ -99,18 +104,32 @@
99
104
 
100
105
  ### 主色起点标记(`&::before`)
101
106
 
102
- 甲方 2026-09-16 要的是「给这条**结构分区**一个可辨识的**起点**」。原先做成左缘一条 3px 竖条,
103
- 它站在文字带里 ⇒ 要么文字贴着它、要么左缘得多让一份。现改为**左上角一小段横条**
104
- (高 3px、长三份、贴 `top:0 left:0`),意图原样保住、左侧文字带彻底空出来。
107
+ 甲方 2026-09-16 要的是「给这条**结构分区**一个可辨识的**起点**」。它经历过三种形态:
108
+
109
+ | 版本 | 形态 | 为什么改 |
110
+ | --- | --- | --- |
111
+ | ~v0.30 | 左缘一条 3px **竖条**(absolute) | 站在文字带里 ⇒ 要么文字贴着它、要么左缘得多让一份 |
112
+ | v0.31.0 | 左上角一小段**横条**(absolute,长三份) | 挪出文字带 ⇒ 左右留白得以各取一份 |
113
+ | **v0.32.0** | **参与文档流的行内块**(`inline-block`,自带一份右留白) | 甲方 2026-09-19 指定的形态 |
114
+
115
+ **现形态**(m5tvq8 ③):`display: inline-block` · `box-sizing: border-box` ·
116
+ `width: 四份` · `padding-right: 一份` · `background-clip: content-box`
117
+ ⇒ **着色段仍是三份**(v0.31.0 认可的长度),多出的第四份正是它**自带的那道右留白**。
118
+
119
+ 🔴 **它因此重新进入布局账**:作为 flex 项**占位**,标题左缘右移四份。
120
+ ⇒ `__left` [MUST] 拿 `flex: 1 1 auto` —— 根是 `space-between`,三个 flex 项等距分布会把
121
+ **标题推到正中**(不是审美问题,是布局坏掉)。
122
+
123
+ 🔴 **`background-clip: content-box` 不能省**:右留白是**留白**,背景铺过去就没有白可留。
124
+
125
+ ⚠️ **与「标题与正文左缘对齐」相抵触**:标记占位后标题比正文右移四份。那条对齐是
126
+ 9dabaj 的裁定,本条是**更后一次的裁定** —— 两者不可兼得,[MUST] 以甲方眼验为准。
105
127
 
106
128
  🔴 **[MUST NOT] 改用负偏移把它挂到盒外去「让出整一份」**:实测 `ViewLayout` /
107
129
  `.app-page-shim` / `.app-page` 四层左缘**全在同一条线上**(逐层 `padding-left: 0`)——
108
130
  盒外没有余量,负偏移会画到**宿主**身上,与「[MUST NOT] 反向伸手改宿主」的既定边界冲突。
109
131
 
110
- 🔴 仍走**伪元素 absolute** 而非 `border-top`:本条的高度账量的是 `clientHeight`
111
- (含 padding、不含 border),border 会直接进高度账、吃掉 refine 的可用高。
112
-
113
- `appBar` 形态下它**不出现**(四项 gate 之一,见下)。
132
+ ⚠️ `appBar` 形态曾让它不渲染;该形态已于 v0.32.0 摘除 ⇒ **现在它恒存在**。
114
133
 
115
134
  ## 底部区(`#bottom`)
116
135
 
@@ -186,120 +205,24 @@
186
205
  - `AppPageListDetailLayout` 的 `#detailBottom` **自动开**(用了那个槽就是要贴底),
187
206
  仍可经 `viewLayoutProps.fillHeight` 关掉。
188
207
 
189
- ## `appBar` —— toolbar 的第二形态:移动端 app bar(opt-in)
190
-
191
- 甲方 2026-09-19 口述:「为啥不使用真 header?因为**当前 toolbar 支持展示 title 很平滑**,
192
- 没必要强行对齐。」⇒ 本能力是给现有 toolbar **加一个形态开关**,
193
- [MUST NOT] 另造 `AppBar` 组件、[MUST NOT] 去动 `AppHeader`(那是布局四区的全局顶栏,不是页面级的)。
194
-
195
- ```vue
196
- <ViewLayout
197
- :api="api"
198
- title="订单详情"
199
- appBar
200
- :back="() => router.back()"
201
- :moreOptions="[{ label: '分享', value: 'share' }, { label: '删除', value: 'remove' }]"
202
- :onMoreSelect="(item) => handle(item.value)"
203
- />
204
- ```
205
-
206
- ### 开关打开后变了什么(逐条)
207
-
208
- | # | 变化 | 关闭时 |
209
- | --- | --- | --- |
210
- | ① | 左缘 3px 主色条**去掉** | 色条在 |
211
- | ② | 下边缘加 `box-shadow` | 无投影 |
212
- | ③ | `min-height: 44px` | 无高度下限(内容自撑) |
213
- | ④ | 给了 `back` ⇒ 渲染左侧返回箭头(热区 ≥44×44) | **传了也不渲染** |
214
- | ⑤ | 给了 `moreOptions` / `onMore` / `#more` ⇒ 渲染右侧「更多」位 | **传了也不渲染** |
215
-
216
- ### 🔴 四项全部由开关 gate,[MUST NOT] 漏 gate 任何一项
217
-
218
- 甲方明确:「左侧的 `back`、右侧的三个点、chrome 最小高度 44、无圆角,
219
- **都是通过 props 开启移动端模式才有的**」。
220
-
221
- ⇒ **不开 `appBar` 时**:`back` 传了也不渲染箭头、`moreOptions` 传了也不渲染 ⋮、
222
- 高度不加 44 下限、圆角处理一律不变。
223
- 漏 gate 的典型写法是「传了就生效」,而漏了之后在默认形态下**看起来只是多了个按钮、不会报错** ——
224
- 故守卫逐项带了阴性对照。
225
-
226
- ### 🔴 只由消费方显式开启,[MUST NOT] 自动检测
227
-
228
- 甲方原话:「是**开启**,不是检测是移动端」。
208
+ ## `appBar` 已摘除(v0.32.0 breaking)—— navbar 归位到 `AppPage`
229
209
 
230
- 本形态 [MUST NOT] 用 `matchMedia` / UA / 视口宽 / 容器断点去「自动判断是移动端就切」:
231
- 同一套 core 要服务多形态应用(见产品定位),**「窄就是移动端」不成立**
232
- (分栏详情 / 侧栏 / 弹窗里都窄),自动切会让消费方在完全没配置的情况下突然换一套 chrome。
210
+ `appBar` v0.31.0 发过一版,**v0.32.0 摘除**。navbar **page chrome**(一页恒一个、
211
+ 横跨整页),做在内容容器上有三条代价:N 个族各做一遍 · 一页多容器时出两个 navbar ·
212
+ 只盖住自己那半边。⇒ 整套原语(返回热区 / 光学对齐 / 四形态 / 下边缘阴影)
213
+ **整体搬到 `AppPage`**,[MUST NOT] 在本组件重建。
233
214
 
234
- ### 高度是 `min-height: 44px`,不是固定高
235
-
236
- 甲方两次修订后的终稿。**为什么不是 `height: 44px`**:固定高会把「44」这个**后果**写成
237
- **原因**,且本组件的 chrome 卡方案当初被毙的根因正是「面顺带塞进 12px 内呼吸、
238
- 32px 的条撑到 56px」—— 硬钉是同一动作换个数字;内容真超过 44 时还会被压。
239
- **为什么不是「纯内容自撑」**:会让有 `back` 的页(≈44)与没有的页(≈32)头部高度不一致,
240
- 而真移动端 header 是恒高的,来回切页看得见跳动。
241
-
242
- `min-height` 两头都吃到:**下限恒 44**(视觉恒高,也正好容得下 44×44 触控热区),
243
- **内容超出照常撑高**。
244
- 🔴 本形态下它是必须项,[MUST NOT] 以「条不加高」纪律为由删掉 —— 那条约束的是**默认形态**。
245
-
246
- ### 右侧「更多」位:插槽 + 配置驱动菜单
247
-
248
- ```
249
- <span class="…__more" @click="…"> ← 点击绑这里,热区靠它的 padding 撑到 ≥44×44
250
- <slot name="more"><MoreFilled /></slot> ← 默认 ⋮,插槽可换
251
- </span>
252
- ```
253
-
254
- - ⋮ 取 element-plus 的 `MoreFilled`。⚠️ v0.30 删掉的是 core **自绘**的 icon 族,
255
- EP 图标集照常可用,[MUST NOT] 因此又自绘一个。
256
- - 菜单复用 `ElDropdown`,[MUST NOT] 自研浮层。
257
- - **渲染条件**:`moreOptions`(非空)**或** `onMore` **或** `#more` 插槽,三者有其一即渲染;
258
- **三者皆无则整块不渲染** —— [MUST NOT] 渲一个点不动的 ⋮。
259
- - **位置**:在右区**最末**(导航 / `rightExtraButtons` / `AutoRefreshGroup` 之后)。
260
- ⚠️ appBar 形态**不会**自动隐藏右区既有内容 —— 那是没被要求的行为变更;
261
- 移动端 header 里按钮多显得挤,是消费方自己少配的事。
262
-
263
- ### 🔴 两个回调各自单义,[MUST NOT] 合成一个带可选参数的
264
-
265
- | 回调 | 何时可能触发 | 入参 |
266
- | --- | --- | --- |
267
- | `onMoreSelect(item)` | **配了** `moreOptions` ⇒ 点击开菜单,选中某项 | **恒有**,是整条 item 配置(不是 `value`) |
268
- | `onMore()` | **没配** `moreOptions` ⇒ 点击直接触发,不出菜单 | 无 |
269
-
270
- 两者**由配置互斥**。之所以不留单回调:消费方写代码时**静态就知道**自己配没配菜单,
271
- 可选参数会强迫他处理一个在他那份配置里**根本不可能发生**的分支;
272
- 且文档只能靠脚注补救(「没配菜单时 item 为 undefined」)——
273
- **需要脚注解释的签名通常是该拆了**。
274
-
275
- ### ⚠️ `#more` 插槽的双触发坑
276
-
277
- 点击绑在**外层 `span`** 上 ⇒ 插槽内容若自带点击(比如往里塞了 `ActionBtn`),
278
- 其事件会冒泡到 `span`、与回调**同时触发**。
279
-
280
- ⇒ 契约:**`#more` 的内容按「哑内容」设计(图标 / 文字),点击统一走外层**。
281
- 要自己接管点击就**别传 `onMore` / `moreOptions`**,或在插槽内 `@click.stop`。
282
-
283
- ### 关于「去圆角」:本组件内**没有圆角可去**(已实测)
284
-
285
- 从 toolbar 一路向上到 `<body>` 的祖先链**逐层 `border-radius` 都是 `0`**
286
- (toolbar 自己早就写死 `border-radius: 0`:「条贴边通栏」)。
287
- ⇒ `appBar` 形态在常态落点(`AppPage` 默认槽 / 独立使用)下**不需要任何去圆角动作**。
288
-
289
- 唯一在圆角的是 **`AppPage` 四向槽的 shim**(`#top`/`#bottom`/`#left`/`#right`,实测 `4px`)——
290
- 那属于**宿主**,`ViewLayout` [MUST NOT] 反向去改它,也 [MUST NOT] 用负 margin 挣脱宿主 padding
291
- (会让高度账与几何同时失真)。
292
-
293
- **要贴边时的出口**:那圈圆角是**随背景画的** ⇒ 把该槽的 `topBg` / `bottomBg` / `leftBg` /
294
- `rightBg` 传 `"transparent"`,圆角即不可见(用现成 prop、零新增 API);
295
- 要连背景一起保留又想直角,则消费方自行覆盖该槽 shim 的 `border-radius`。
215
+ | 旧(≤0.31.x) | 新(≥0.32.0) |
216
+ | --- | --- |
217
+ | `<ViewLayout app-bar :back="…" :more-options="…" />` | `<AppPage show-nav-bar :back="…" :more-options="…"><ViewLayout … /></AppPage>` |
218
+ | `#more` 槽在 `ViewLayout` 上 | `#more` 槽在 `AppPage` 上 |
219
+ | 标题走 `ViewLayout.title` | navbar 标题走 `AppPage` 的 `v-model:title`(子层回写见 `useAppPageTitleField`) |
296
220
 
297
- ### 高度账仍然成立
221
+ 详见 [README-AppPage](./../../app-layout/docs/README-AppPage.md) 与 `migrations/v0.32.0.md`。
298
222
 
299
- toolbar `HeightProvider` `#header` 槽、经 `WatchSize` 量 `clientHeight` 得 reserve。
300
- 本形态改高(箭头热区 + 44 下限)是**被量到的**,机制上安全。
301
- 🔴 阴影 [MUST] 用 `box-shadow`(不占布局),[MUST NOT] `border-bottom`(占宽高账)
302
- 或 margin(`clientHeight` 抓不到)。refine 零溢出在「开 / 关 × 有 back / 无 back」四种组合下都实测过。
223
+ ⚠️ **本组件的 toolbar 仍在**,只是回到单一形态:标题行 + 操作区 + 主色起点标记。
224
+ `min-height: 44px` toolbar 的**常驻纪律**(它当初就不是 appBar 专属),
225
+ [MUST NOT] 因为 appBar 没了就把它删掉。
303
226
 
304
227
  ## API
305
228
  > ⚠️ API 以 types.ts 与组件源码为真相源;与文档冲突时以源码为准。
@@ -322,20 +245,14 @@ toolbar 在 `HeightProvider` 的 `#header` 槽、经 `WatchSize` 量 `clientHeig
322
245
  | `showNavigation` | `boolean?` | `true` | 是否显示内置「上一条 / 下一条」导航。⚠️ **真正的显隐判据是可选 inject 探测**:只有置于 `AppPageListDetailLayout` 内才渲染,单独使用 ViewLayout 时无论本值为何都不渲染(老用法零变化);本 prop 用于在门面内显式关掉 |
323
246
  | `leftExtraButtons` | `ViewLayoutButtonConfig[]` | `[]` | 左侧额外按钮(`ActionBtnGroup` 配置,ctx = `{ loading, refresh }`,`default-size="small"` 对齐 table-toolbar) |
324
247
  | `rightExtraButtons` | `ViewLayoutButtonConfig[]` | `[]` | 右侧额外按钮(置于 AutoRefreshGroup 之前) |
325
- | `appBar` | `boolean?` | `false` | **移动端 app bar 形态**(见上):去色条 + 下边缘阴影 + `min-height:44px`,并**解锁** `back` / 更多位。🔴 只由消费方显式开,[MUST NOT] 自动检测 |
326
- | `back` | `(() => void)?` | — | 返回回调;**仅 `appBar` 下**渲染左侧箭头(热区 ≥44×44)。不开 appBar 时传了也不渲染 |
327
- | `moreOptions` | `ViewLayoutMoreOption[]?` | — | 更多菜单项(`{ label, value }`);**仅 `appBar` 下**生效。配了即点击开 `ElDropdown` |
328
- | `onMoreSelect` | `((item: ViewLayoutMoreOption) => void)?` | — | 选中菜单项,入参**恒是整条 item 配置**。仅在配了 `moreOptions` 时可能触发 |
329
- | `onMore` | `(() => void)?` | — | **没配** `moreOptions` 时点击更多位直接触发(不出菜单)。与 `onMoreSelect` 由配置互斥 |
330
248
 
331
249
  ### Slots
332
250
 
333
251
  | 槽 | scope | 语义 |
334
252
  | --- | --- | --- |
335
253
  | 默认 | `{ data, loading, refresh, viewportHeight, fetchSeq }`(`ViewLayoutScope<R>`) | 展示内容;PanelMain 或自由编排皆可;viewportHeight refine 时给可用高(**已扣内容区自身 padding**)、自然流 undefined;`fetchSeq` = **成功**拉取次数(失败不递增),供下游懒加载块判「该重拉了」 |
336
- | `#header` | `{ data, loading }` | 标题行(见上「标题行」段);不传且无 `title` 时不渲染 |
254
+ | `#header` | `{ data, loading, title }` | 标题行(见上「标题行」段);不传且无 `title` 时不渲染。**scope 带 `title`**(v0.32.0 补)⇒ 插槽接管后仍拿得到原标题,不必再从外面透一遍 |
337
255
  | `#bottom` | `{ data, loading, fetchSeq }` | 底部区:refine 固定在底部(计入 reserve)/ 自然流平铺在内容之后;不传不渲染。比 `#header` 多 `fetchSeq`(本槽放操作条,提交后要判「真的重拉成功了」) |
338
- | `#more` | 无 | **仅 `appBar` 下**:替换右侧「更多」位的内容(默认 `MoreFilled` ⋮)。⚠️ 点击绑在外层 `span` 上 ⇒ 内容按**哑内容**设计,[MUST NOT] 在里面再挂点击(会与回调双触发) |
339
256
 
340
257
  ### 内置上/下一条导航(仅列表详情范式内出现)
341
258
 
@@ -363,9 +280,9 @@ toolbar 在 `HeightProvider` 的 `#header` 槽、经 `WatchSize` 量 `clientHeig
363
280
  - **指望 `#bottom` 默认就贴容器底**:默认不贴(内容盒只有上限、不定高)——要贴底开 `fillHeight`
364
281
  - **为了贴底把 `max-height` 改写成 `height`**:[MUST NOT]——那波及所有 refine 页面,是本 prop 存在的理由掉
365
282
  - **在消费侧写死像素补偿 toolbar / 内容区的间距**:[MUST NOT]——间距由 gap 阶梯派生,写死会在改 `gap` / 换密度档时脱钩
366
- - **指望 `back` / `moreOptions` 不开 `appBar` 也生效**:[MUST NOT]——四项能力**全部由开关 gate**,不开时传了也不渲染(甲方明确要求)
283
+ - **在本组件上找 `appBar` / `back` / `moreOptions`**:[MUST NOT]——**已于 v0.32.0 摘除**,navbar 归位到 `AppPage`(`showNavBar`),见上
367
284
  - **往 `#more` 插槽里塞带点击的组件**:[MUST NOT]——点击绑在外层 `span`,会与 `onMore` / 菜单双触发;插槽内容按哑内容设计
368
- - **指望 `appBar` 自动在窄屏打开**:[MUST NOT]——只由消费方显式开(甲方:「是开启,不是检测是移动端」);分栏详情 / 侧栏 / 弹窗里都窄,「窄 = 移动端」不成立
285
+ - **指望 navbar 自动在窄屏打开**:[MUST NOT]——只由消费方显式开(甲方:「是开启,不是检测是移动端」);分栏详情 / 侧栏 / 弹窗里都窄,「窄 = 移动端」不成立。该口径随 navbar 一起搬到了 `AppPage`
369
286
  - **让 `ViewLayout` 去关宿主的圆角**:[MUST NOT] 跨组件伸手,也 [MUST NOT] 用负 margin 挣脱宿主 padding;`AppPage` 四向槽的圆角由消费方把该槽 `*Bg` 传 `"transparent"` 自行处理
370
287
  - **toolbar 不含表格能力全集**:对齐的是 table-toolbar 的**结构与视觉惯例**(左右分栏 + extra 按钮 + AutoRefreshGroup),[MUST NOT] 期待导出 / 批量下载 / 视图切换等表格特有能力出现在 ViewLayout——需要表格能力用 `ListLayout` / `TableMain`
371
288
 
@@ -1,5 +1,5 @@
1
1
  import { DeepReadonly } from 'vue';
2
- import { AppBaseInfo, AppLayoutHeaderConfig, AppLayoutFooterConfig, AppLayoutSidebarConfig, AppLayoutAsideConfig, AppLayoutBreadcrumbConfig, AppLayoutBodyConfig, AppLayoutViewportConfig, AppLayoutGapConfig, AppThemeConfig, AppLayoutConfig } from './index';
2
+ import { AppBaseInfo, AppLayoutHeaderConfig, AppLayoutFooterConfig, AppLayoutSidebarConfig, AppLayoutAsideConfig, AppLayoutBreadcrumbConfig, AppLayoutBodyConfig, AppLayoutViewportConfig, AppLayoutGapConfig, AppFormConfig, AppThemeConfig, AppLayoutConfig } from './index';
3
3
  /**
4
4
  * 可 update 的响应式配置字段 key 联合(插件系统一期 7 个 + viewport 边框期补 1 = 8 个)。
5
5
  *
@@ -8,7 +8,7 @@ import { AppBaseInfo, AppLayoutHeaderConfig, AppLayoutFooterConfig, AppLayoutSid
8
8
  * `APP_LAYOUT_VIEWPORT_CONFIG`(同属布局子,纳入本联合 + layoutAggregate)。
9
9
  * 其余 init 字段(env / cache / router 族 / userInfoAccess)非响应式范围,[MUST NOT] 纳入本联合。
10
10
  */
11
- export type UpdatableConfigKey = "APP_BASE_INFO" | "APP_LAYOUT_HEADER_CONFIG" | "APP_LAYOUT_FOOTER_CONFIG" | "APP_LAYOUT_SIDEBAR_CONFIG" | "APP_LAYOUT_ASIDE_CONFIG" | "APP_LAYOUT_BREADCRUMB_CONFIG" | "APP_LAYOUT_BODY_CONFIG" | "APP_LAYOUT_VIEWPORT_CONFIG" | "APP_LAYOUT_GAP_CONFIG" | "APP_THEME_CONFIG";
11
+ export type UpdatableConfigKey = "APP_BASE_INFO" | "APP_LAYOUT_HEADER_CONFIG" | "APP_LAYOUT_FOOTER_CONFIG" | "APP_LAYOUT_SIDEBAR_CONFIG" | "APP_LAYOUT_ASIDE_CONFIG" | "APP_LAYOUT_BREADCRUMB_CONFIG" | "APP_LAYOUT_BODY_CONFIG" | "APP_LAYOUT_VIEWPORT_CONFIG" | "APP_LAYOUT_GAP_CONFIG" | "APP_FORM_CONFIG" | "APP_THEME_CONFIG";
12
12
  /**
13
13
  * 7 个可 update 字段 key → 值类型映射。
14
14
  *
@@ -34,6 +34,7 @@ export interface UpdatableConfigMap {
34
34
  APP_LAYOUT_VIEWPORT_CONFIG: AppLayoutViewportConfig;
35
35
  /** 应用布局-模块间距配置(内缝 gap) */
36
36
  APP_LAYOUT_GAP_CONFIG: AppLayoutGapConfig;
37
+ APP_FORM_CONFIG: AppFormConfig;
37
38
  /** 应用主题配置 */
38
39
  APP_THEME_CONFIG: AppThemeConfig;
39
40
  }
@@ -77,6 +78,8 @@ export interface BridgeConfigHook {
77
78
  roViewport: DeepReadonly<AppLayoutViewportConfig>;
78
79
  /** 应用布局-模块间距配置对外只读视图(内缝 gap) */
79
80
  roGap: DeepReadonly<AppLayoutGapConfig>;
81
+ /** 表单配置只读视图(`bridge.update("APP_FORM_CONFIG", …)` 后响应式生效) */
82
+ roForm: DeepReadonly<AppFormConfig>;
80
83
  /** 应用主题配置对外只读视图 */
81
84
  roTheme: DeepReadonly<AppThemeConfig>;
82
85
  /**
@@ -265,6 +265,23 @@ export interface AppConfig {
265
265
  * 要模块贴合显式传 `{ size: 0 }`。`size>0` 时 store 几何 calc 在相邻模块间叠加该间距
266
266
  * (缺位侧不留悬空缝)。布局插件经 `bridge.update("APP_LAYOUT_GAP_CONFIG", { size })` 设值。
267
267
  */
268
+ /**
269
+ * 应用表单配置。
270
+ *
271
+ * 🔴 `layoutByContainer` 缺省 **`true`**:`ElCol` 的响应式 span 是按 **window 宽**解析的
272
+ * CSS 媒体查询,而表单**几乎从不满宽**(要扣侧栏 / 各层 padding / 卡片内呼吸)——
273
+ * 实测一个普通全宽列表页,视口 1280(`lg`) 而表单容器只有 924(`sm`),**差两档**;
274
+ * 弹窗内更极端(视口 1280 而弹窗 420)⇒ 按视口解析必然挤爆。
275
+ * ⇒ 容器宽才是真相源,视口宽只是它的**代理**,而这个代理在不满宽时就是错的。
276
+ *
277
+ * ⚠️ **出问题可随时关掉、不必等发版**:`createCoreBridge({ APP_FORM_CONFIG: { layoutByContainer: false } })`
278
+ * 即全局回到按视口解析(本字段存在的理由就是这个逃生口)。
279
+ * 组件上显式传的 `layoutByContainer` **恒优先**于本配置。
280
+ */
281
+ APP_FORM_CONFIG: {
282
+ /** 表单布局按**容器断点**校准;缺省 `true`(见上方说明),显式 prop 恒优先 */
283
+ layoutByContainer: boolean;
284
+ };
268
285
  APP_LAYOUT_GAP_CONFIG: {
269
286
  /** 模块间距(px);缺省 8 = 开缝,0 = 贴合 */
270
287
  size: number;
@@ -421,6 +438,8 @@ export type AppLayoutBodyConfig = AppConfig["APP_LAYOUT_BODY_CONFIG"];
421
438
  export type AppLayoutViewportConfig = AppConfig["APP_LAYOUT_VIEWPORT_CONFIG"];
422
439
  /** 应用布局-模块间距配置 type 别名(内缝 gap) */
423
440
  export type AppLayoutGapConfig = AppConfig["APP_LAYOUT_GAP_CONFIG"];
441
+ /** 表单配置(见 {@link AppConfig.APP_FORM_CONFIG}) */
442
+ export type AppFormConfig = AppConfig["APP_FORM_CONFIG"];
424
443
  /** 应用主题配置 type 别名 */
425
444
  export type AppThemeConfig = AppConfig["APP_THEME_CONFIG"];
426
445
  /** 应用路由元信息默认配置 type 别名 */
@@ -515,6 +534,8 @@ export interface CoreBridgeInitOptions<UserInfo = unknown> {
515
534
  * 布局插件经 preset.layout.gap 或 `bridge.update` 设值。
516
535
  */
517
536
  APP_LAYOUT_GAP_CONFIG?: Partial<AppLayoutGapConfig>;
537
+ /** 应用表单配置(可选;核内默认 `{ layoutByContainer: true }`,见 {@link AppConfig.APP_FORM_CONFIG}) */
538
+ APP_FORM_CONFIG?: Partial<AppFormConfig>;
518
539
  /** 应用路由元信息默认配置(bridge 内部 generateRouteMetaRawTree 派生) */
519
540
  APP_ROUTER_META_DEFAULT_CONFIG: AppRouterMetaDefaultConfig;
520
541
  /** 应用路由配置(#2.5 期新增字段) */
@@ -614,6 +635,7 @@ export interface CoreBridge<UserInfo = unknown, LoginParams = unknown> {
614
635
  readonly APP_LAYOUT_VIEWPORT_CONFIG: DeepReadonly<AppLayoutViewportConfig>;
615
636
  /** 应用布局-模块间距配置(内缝 gap;响应式只读视图,经 bridge.update 跟随) */
616
637
  readonly APP_LAYOUT_GAP_CONFIG: DeepReadonly<AppLayoutGapConfig>;
638
+ readonly APP_FORM_CONFIG: DeepReadonly<AppFormConfig>;
617
639
  /** 应用布局聚合配置(per #2.5 期 ADR-3 bridge 内部聚合派生) */
618
640
  readonly APP_LAYOUT_CONFIG: DeepReadonly<AppLayoutConfig>;
619
641
  /** 应用路由元信息默认配置(per #2.5 期 REQ-1) */
@@ -1,3 +1,4 @@
1
+ import { AppPageNavBarMoreOption, AppPageTitleField } from './types';
1
2
  type __VLS_Props = {
2
3
  /** 充满视口 */
3
4
  fullViewport?: boolean;
@@ -62,11 +63,46 @@ type __VLS_Props = {
62
63
  * [MUST] 为 number——几何链三处出口需其参与数值运算,CSS 串会让样式与公布几何脱钩。
63
64
  */
64
65
  contentMaxWidth?: number;
66
+ /**
67
+ * **页面级 navbar**(返回 / 标题 / ⋮ / tabbar),默认关。
68
+ *
69
+ * 🔴 navbar 是 **page chrome**:一页恒一个、横跨整页、在内容滚动之外
70
+ * ⇒ 它归**页面容器**,[MUST NOT] 做在 `ViewLayout` / `ListLayout` 这类内容容器上
71
+ * (split 档同页两个容器都开 ⇒ 一页两个 navbar,且各自只盖住半边)。
72
+ * 与 `AppHeader`(布局四区之一的**全局**顶栏)不同层级:那个管整个应用,本条管这一页。
73
+ *
74
+ * 🔴 **只由消费方显式开,[MUST NOT] 自动检测移动端**:同一套 core 服务多形态应用,
75
+ * 「窄就是移动端」不成立(分栏详情 / 侧栏 / 弹窗里都窄)。
76
+ */
77
+ showNavBar?: boolean;
78
+ /** 返回回调;给了才渲染 navbar 左侧箭头(热区 ≥44×44)。仅 `showNavBar` 开启时 */
79
+ back?: () => void;
80
+ /** navbar ⋮ 的菜单项;给了即点击开菜单 */
81
+ moreOptions?: AppPageNavBarMoreOption[];
82
+ /** 菜单项点选回调,入参**恒是整条 item 配置** */
83
+ onMoreSelect?: (item: AppPageNavBarMoreOption) => void;
84
+ /** **没配** `moreOptions` 时点击 ⋮ 直接触发(不出菜单);与 `onMoreSelect` 由配置互斥 */
85
+ onMore?: () => void;
86
+ /** navbar 的 WatchSize RO 观测(tabbar 有过渡折叠等非重渲染高度变化时开) */
87
+ navBarObserveResize?: boolean;
88
+ /**
89
+ * 标题回写的**能力令牌**(`useAppPageTitleField()` 的产物)。
90
+ * 同一个句柄再传给子件,子件才够得着 `changeTitle`;不传 = 没有任何子层能改标题。
91
+ */
92
+ titleField?: AppPageTitleField;
65
93
  };
94
+ type __VLS_PublicProps = {
95
+ "title"?: string | undefined;
96
+ } & __VLS_Props;
66
97
  declare function __VLS_template(): {
67
98
  attrs: Partial<{}>;
68
99
  slots: {
69
100
  default?(_: {}): any;
101
+ navTitle?(_: {
102
+ title: string | undefined;
103
+ }): any;
104
+ more?(_: {}): any;
105
+ tabbar?(_: {}): any;
70
106
  top?(_: {}): any;
71
107
  bottom?(_: {}): any;
72
108
  left?(_: {
@@ -82,7 +118,11 @@ declare function __VLS_template(): {
82
118
  rootEl: HTMLDivElement;
83
119
  };
84
120
  type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
85
- declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
121
+ declare const __VLS_component: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
122
+ "update:title": (value: string | undefined) => any;
123
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
124
+ "onUpdate:title"?: ((value: string | undefined) => any) | undefined;
125
+ }>, {
86
126
  gap: number;
87
127
  fullViewport: boolean;
88
128
  fullMode: "min-height" | "height";
@@ -97,6 +137,8 @@ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}
97
137
  rightObserveResize: boolean;
98
138
  contentCentered: boolean;
99
139
  contentMaxWidth: number;
140
+ showNavBar: boolean;
141
+ navBarObserveResize: boolean;
100
142
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
101
143
  appPageRef: HTMLDivElement;
102
144
  }, HTMLDivElement>;
@@ -0,0 +1,48 @@
1
+ import { AppPageNavBarMoreOption } from './types';
2
+ type __VLS_Props = {
3
+ /** 标题(`AppPage` 的 title model 值) */
4
+ title?: string;
5
+ /** 返回回调;给了才渲染返回箭头 */
6
+ back?: () => void;
7
+ /** ⋮ 菜单项;给了即点击开菜单 */
8
+ moreOptions?: AppPageNavBarMoreOption[];
9
+ /** 菜单项点选回调(入参恒是整条 item) */
10
+ onMoreSelect?: (item: AppPageNavBarMoreOption) => void;
11
+ /** 无菜单时 ⋮ 的直接点击回调 */
12
+ onMore?: () => void;
13
+ /**
14
+ * 横向 keyline(px 串,外圈档)——由 `AppPage` 按间距阶梯算好传入。
15
+ * 🔴 [MUST NOT] 在本组件重算:真相源是 `AppPage` 的 `outerGap`(规则同 `ViewLayoutToolbar.padXPx`)。
16
+ */
17
+ keylinePx: string;
18
+ };
19
+ declare function __VLS_template(): {
20
+ attrs: Partial<{}>;
21
+ slots: {
22
+ navTitle?(_: {
23
+ title: string | undefined;
24
+ }): any;
25
+ more?(_: {}): any;
26
+ more?(_: {}): any;
27
+ tabbar?(_: {}): any;
28
+ };
29
+ refs: {
30
+ titleBoxEl: HTMLDivElement;
31
+ };
32
+ rootEl: HTMLDivElement;
33
+ };
34
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
35
+ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
36
+ titleBox: (el: HTMLElement | undefined) => any;
37
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
38
+ onTitleBox?: ((el: HTMLElement | undefined) => any) | undefined;
39
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
40
+ titleBoxEl: HTMLDivElement;
41
+ }, HTMLDivElement>;
42
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
43
+ export default _default;
44
+ type __VLS_WithTemplateSlots<T, S> = T & {
45
+ new (): {
46
+ $slots: S;
47
+ };
48
+ };
@@ -27,6 +27,25 @@ export interface AppPageSlotSizes {
27
27
  leftW: number;
28
28
  rightW: number;
29
29
  }
30
+ /**
31
+ * 单向让位量(与 app-page-shim padding 同式,纯函数):槽存在才让,`v=0`(无槽)→ **0**。
32
+ *
33
+ * 🔴 **huhyx7 改口径:由「减」变「加」**(原 `max(0, v − shimPadding)`)。
34
+ * 原式的问题不只是数值:**间隔的归属方是错的** —— 它让默认内容紧贴槽边缘(让位量恰好
35
+ * = 槽宽 − shimPadding),间隔全由槽自己的内 padding 出。于是槽内若放自带底色的卡,
36
+ * 卡边缘离槽边一份、默认内容离槽边 0,两个面之间只剩一份,且**随槽内组件有没有 padding 而漂移**。
37
+ *
38
+ * 新式 `v + outer`:槽占多宽就让多宽,**再额外让一个外圈档**作为「槽 ↔ 内容」的间隔。
39
+ * 槽自身朝内容侧的 padding 相应归零(见 {@link computeAppPageSlotShimStyles})——
40
+ * [MUST NOT] 两边各让一份,那会变成双份。
41
+ *
42
+ * 无槽方向恒 0:[MUST NOT] 给不存在的方向留间隔(甲方明令)。
43
+ *
44
+ * 🔴 **导出是为了 navbar 复用同一式**(vq3mzn):navbar 是贴内容矩形上沿的第五条固定带,
45
+ * 让位量与四向槽**同式同源**。[MUST NOT] 在 `AppPage` 里另写一遍 `h ? h + gap : 0` ——
46
+ * huhyx7 就是「只改一处、漏了另一处」才让同一页出现两种让位口径。
47
+ */
48
+ export declare function appPageSlotInset(v: number, outerGap: number): number;
30
49
  /**
31
50
  * 扣掉 top/bottom 槽后的内容视口可用高(纯函数,钳非负)。
32
51
  *
@@ -112,8 +131,14 @@ options: {
112
131
  * 一个**有形的块**而非纯占位轨 —— 内容贴着块边缘(还会被圆角切角)不成样子,故给块内呼吸。
113
132
  *
114
133
  * ⚠️ 这**不构成上文的「双份」**:让位量取槽的**实测尺寸**(`clientWidth/Height` 含 padding),
115
- * 槽变胖多少默认内容就多让多少 ⇒「槽边缘 ↔ 默认内容」恒为一个外圈档不变,新增的 8px 落在
134
+ * 槽变胖多少默认内容就多让多少 ⇒「槽边缘 ↔ 默认内容」恒为一个外圈档不变,新增的这一份落在
116
135
  * **槽内**(槽边缘 ↔ 槽内内容)。上文「朝内容侧 [MUST] 归零」针对的是旧式做法 ——
117
136
  * 那时槽无背景、padding 直接充当间隔,才会与让位叠成双份。
137
+ *
138
+ * 🔴 **值由调用方按间距阶梯的「模块档」传入**(t4xhnr 连带 ⓑ):原先写死 `8px`,
139
+ * 不随 gap 基数 / 密度档变 —— 违反本仓「取值一律走 gap 阶梯」的口径。
140
+ * 默认基数(8)下仍解出 8 ⇒ **零视觉变化**。[MUST NOT] 退回写死常量。
141
+ *
142
+ * @param moduleGap 间距阶梯**模块档**(`gapScaleValue(基数, "module", 密度)`)
118
143
  */
119
- export declare function computeAppPageSlotShimStyles(): AppPageSlotStyles;
144
+ export declare function computeAppPageSlotShimStyles(moduleGap: number): AppPageSlotStyles;
@@ -0,0 +1,18 @@
1
+ import { VNode } from 'vue';
2
+ /**
3
+ * 在一棵插槽 VNode 树里数出「自带面的宿主」有几个。
4
+ *
5
+ * 🔴 **递归下探,[MUST NOT] 只看直系子**(t4xhnr §三之一,甲方当场更正过一次):
6
+ * 列表详情门面自己插了一层 wrapper(`__list`),`ListLayout` 不是槽的直系子
7
+ * ⇒ 直系子判定**在最常见的场景下恒不命中**。这与 `vq3mzn` 写死的
8
+ * 「[MUST NOT] 校验直系子元素」是同一条教训。
9
+ *
10
+ * **边界**:只走**本槽自己的 VNode 树**(`children` 为数组时继续下探)——
11
+ * 门面的 wrapper 是本槽内容的一部分、其子节点在同一棵树里,够得到;
12
+ * 而别的组件的插槽内容此时还没被渲染成 VNode,本来也走不进去。
13
+ *
14
+ * ⚠️ **[MUST NOT] 改写成 DOM 遍历**:槽的背景是**内联**的
15
+ * (`:style="slotShimStyle('left')"`),DOM 读到时样式已经落上去了,
16
+ * 要改只能 `!important` 硬顶;VNode 侧则是在**算样式之前**就知道答案。
17
+ */
18
+ export declare const countSlotSurfaceHosts: (nodes: VNode[] | undefined) => number;
@@ -12,3 +12,4 @@ export * from './types';
12
12
  export * from './viewport';
13
13
  export * from './app-page-geometry';
14
14
  export * from './app-page-recommended';
15
+ export { useAppPageTitleField } from './use-app-page-title-field';
@@ -1,4 +1,4 @@
1
- import { Component, VNode } from 'vue';
1
+ import { Component, VNode, ShallowRef } from 'vue';
2
2
  import { createAppStore, createUserStore } from '../../store';
3
3
  import { CoreBridge } from '../../bridge';
4
4
  import { RouteMetaResolveRaw } from '../../types';
@@ -181,3 +181,39 @@ export interface AppPageListLayoutRecommended {
181
181
  paginationBackground: false;
182
182
  };
183
183
  }
184
+ /**
185
+ * navbar 右侧「更多」位(⋮)的菜单项。
186
+ *
187
+ * 形状与 `ViewLayoutMoreOption` 逐字一致 —— 那份随 `appBar` 一起摘除,
188
+ * navbar 归位后由本类型接手,[MUST NOT] 两处各留一份。
189
+ */
190
+ export interface AppPageNavBarMoreOption {
191
+ /** 菜单项展示文案 */
192
+ label: string;
193
+ /** 菜单项值(回调入参恒是整条 item,而非只给这个值) */
194
+ value: string | number;
195
+ }
196
+ /**
197
+ * 页面标题回写的**能力令牌**(`useAppPageTitleField` 的产物)。
198
+ *
199
+ * 🔴 **令牌性 = 不给就够不着**:同一个句柄同时传给 `AppPage` 与子件,两边才接得上;
200
+ * 没拿到句柄的地方改不动标题。[MUST NOT] 退回 provide/inject 全局通道 ——
201
+ * 那等于「页面里任何后代都能改标题」,正是本设计要挡的。
202
+ */
203
+ export interface AppPageTitleField {
204
+ /**
205
+ * 写回页面标题。
206
+ *
207
+ * ⚠️ 句柄**没被任何 `AppPage` 认领**时:dev 期 `console.warn` 点名,
208
+ * [MUST NOT] 静默返回 `false` —— 「标题没变」这种失败形态,本仓已连修四个
209
+ * (`labelHide` 类挂上规则打不中 / 方位槽误用 / 徽标静默 / 引导卡死)。
210
+ */
211
+ changeTitle: (title: string | undefined) => void;
212
+ /**
213
+ * navbar 标题位的**元素**引用,供 `<Teleport :to="titleBoxRef">` 放自定义标题。
214
+ *
215
+ * 🔴 [MUST] 给元素,[MUST NOT] 返回 class 字符串 —— 字符串选择器要求消费方
216
+ * 知道内部 DOM,且选不中时静默失败。
217
+ */
218
+ titleBoxRef: Readonly<ShallowRef<HTMLElement | undefined>>;
219
+ }