@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.
Files changed (103) hide show
  1. package/CONTRACT.md +592 -0
  2. package/README.md +27 -0
  3. package/dist/atoms/app-button.d.ts +90 -0
  4. package/dist/atoms/app-button.js +74 -0
  5. package/dist/atoms/app-checkbox.d.ts +86 -0
  6. package/dist/atoms/app-checkbox.js +78 -0
  7. package/dist/atoms/app-icon-button.d.ts +79 -0
  8. package/dist/atoms/app-icon-button.js +54 -0
  9. package/dist/atoms/app-input.d.ts +118 -0
  10. package/dist/atoms/app-input.js +83 -0
  11. package/dist/atoms/app-layout.d.ts +348 -0
  12. package/dist/atoms/app-layout.js +102 -0
  13. package/dist/atoms/app-notice.d.ts +90 -0
  14. package/dist/atoms/app-notice.js +59 -0
  15. package/dist/atoms/app-query-state.d.ts +191 -0
  16. package/dist/atoms/app-query-state.js +161 -0
  17. package/dist/atoms/app-radio-card.d.ts +80 -0
  18. package/dist/atoms/app-radio-card.js +72 -0
  19. package/dist/atoms/app-search-box.d.ts +80 -0
  20. package/dist/atoms/app-search-box.js +64 -0
  21. package/dist/atoms/app-select.d.ts +106 -0
  22. package/dist/atoms/app-select.js +74 -0
  23. package/dist/atoms/app-switch.d.ts +72 -0
  24. package/dist/atoms/app-switch.js +66 -0
  25. package/dist/atoms/app-textarea.d.ts +89 -0
  26. package/dist/atoms/app-textarea.js +61 -0
  27. package/dist/atoms/app-tooltip.d.ts +70 -0
  28. package/dist/atoms/app-tooltip.js +48 -0
  29. package/dist/atoms/atoms.css +679 -0
  30. package/dist/atoms/state-illustration.d.ts +21 -0
  31. package/dist/atoms/state-illustration.js +101 -0
  32. package/dist/components/app-badge.d.ts +72 -0
  33. package/dist/components/app-badge.js +40 -0
  34. package/dist/components/app-dialog.d.ts +164 -0
  35. package/dist/components/app-dialog.js +121 -0
  36. package/dist/components/app-filter.d.ts +173 -0
  37. package/dist/components/app-filter.js +200 -0
  38. package/dist/components/app-form.d.ts +446 -0
  39. package/dist/components/app-form.js +159 -0
  40. package/dist/components/app-pagination.d.ts +78 -0
  41. package/dist/components/app-pagination.js +57 -0
  42. package/dist/components/app-panel.d.ts +399 -0
  43. package/dist/components/app-panel.js +213 -0
  44. package/dist/components/app-section.d.ts +68 -0
  45. package/dist/components/app-section.js +28 -0
  46. package/dist/components/app-steps.d.ts +115 -0
  47. package/dist/components/app-steps.js +108 -0
  48. package/dist/components/app-table.d.ts +415 -0
  49. package/dist/components/app-table.js +479 -0
  50. package/dist/components/app-tabs.d.ts +58 -0
  51. package/dist/components/app-tabs.js +54 -0
  52. package/dist/components/app-tree.d.ts +273 -0
  53. package/dist/components/app-tree.js +222 -0
  54. package/dist/components/components.css +597 -0
  55. package/dist/composables/use-client-pagination.d.ts +39 -0
  56. package/dist/composables/use-client-pagination.js +29 -0
  57. package/dist/entry/create-query-client.d.ts +8 -0
  58. package/dist/entry/create-query-client.js +14 -0
  59. package/dist/entry/create-sub-app.d.ts +45 -0
  60. package/dist/entry/create-sub-app.js +110 -0
  61. package/dist/entry/index.d.ts +4 -0
  62. package/dist/entry/index.js +12 -0
  63. package/dist/entry/initial-guard.d.ts +12 -0
  64. package/dist/entry/initial-guard.js +23 -0
  65. package/dist/index.d.ts +37 -0
  66. package/dist/index.js +67 -0
  67. package/dist/providers/setup-i18n.d.ts +19 -0
  68. package/dist/providers/setup-i18n.js +24 -0
  69. package/dist/services/app-container.d.ts +15 -0
  70. package/dist/services/app-container.js +7 -0
  71. package/dist/services/app-context.d.ts +4 -0
  72. package/dist/services/app-context.js +17 -0
  73. package/dist/services/index.d.ts +5 -0
  74. package/dist/services/loading.d.ts +16 -0
  75. package/dist/services/loading.js +27 -0
  76. package/dist/services/message-box.d.ts +84 -0
  77. package/dist/services/message-box.js +65 -0
  78. package/dist/services/modal.d.ts +51 -0
  79. package/dist/services/modal.js +51 -0
  80. package/dist/services/notify.d.ts +24 -0
  81. package/dist/services/notify.js +22 -0
  82. package/dist/shell/AppShell.d.ts +139 -0
  83. package/dist/shell/AppShell.js +171 -0
  84. package/dist/shell/shell.css +153 -0
  85. package/dist/styles/farris-bridge.css +129 -0
  86. package/dist/styles/index.css +16 -0
  87. package/dist/styles/markdown.css +85 -0
  88. package/dist/styles/reset.css +74 -0
  89. package/dist/styles/tokens.css +111 -0
  90. package/lint/__tests__/fixtures/clean-src/styles.css +14 -0
  91. package/lint/__tests__/fixtures/clean-src/views/good-page.tsx +15 -0
  92. package/lint/__tests__/fixtures/violations-src/styles.css +18 -0
  93. package/lint/__tests__/fixtures/violations-src/views/bad-page.tsx +38 -0
  94. package/lint/__tests__/fixtures/violations-src/views/no-shell-page.tsx +4 -0
  95. package/lint/__tests__/guardrails.spec.mjs +144 -0
  96. package/lint/component-audit.mjs +131 -0
  97. package/lint/guardrails.config.schema.json +74 -0
  98. package/lint/pre-commit.sample +26 -0
  99. package/lint/run-all.mjs +59 -0
  100. package/lint/shared.mjs +347 -0
  101. package/lint/structure-audit.mjs +254 -0
  102. package/lint/style-audit.mjs +200 -0
  103. package/package.json +78 -0
@@ -0,0 +1,597 @@
1
+ /**
2
+ * 页面组件样式(AppTable / AppPagination)。
3
+ */
4
+
5
+ /* AppTable:撑满 Body(mode="table"),内部滚动(FDataGrid fit 模式) */
6
+ .ak-table {
7
+ height: 100%;
8
+ overflow: hidden;
9
+ }
10
+
11
+ /* ===== AppBadge(徽标 / 标签)=====
12
+ 批次 5 前 4 个应用各自手搓徽标,这里按设计稿收敛为 tone × shape × size × dot 的正交组合。
13
+ 每个 tone 先铺一组局部变量(前景 / 底色 / 描边 / 圆点),形态类只决定「用哪几个变量」。 */
14
+ .ak-badge {
15
+ --ak-badge-fg: var(--ui-base-content);
16
+ --ak-badge-bg: transparent;
17
+ --ak-badge-border: transparent;
18
+ --ak-badge-dot: var(--ui-base-content-subtle);
19
+ display: inline-flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ gap: 6px;
23
+ box-sizing: border-box;
24
+ border: 1px solid transparent;
25
+ font-size: var(--ui-font-caption);
26
+ line-height: 1;
27
+ white-space: nowrap;
28
+ color: var(--ak-badge-fg);
29
+ background: var(--ak-badge-bg);
30
+ border-color: var(--ak-badge-border);
31
+ }
32
+
33
+ .ak-badge--sm {
34
+ height: 20px;
35
+ padding: 0 6px;
36
+ min-width: 36px;
37
+ border-radius: var(--ui-radius-sm);
38
+ }
39
+
40
+ .ak-badge--md {
41
+ height: 24px;
42
+ padding: 0 8px;
43
+ border-radius: var(--ui-radius-md);
44
+ }
45
+
46
+ .ak-badge--default {
47
+ --ak-badge-fg: var(--ui-base-content);
48
+ --ak-badge-dot: var(--ui-base-content-subtle);
49
+ }
50
+
51
+ .ak-badge--primary {
52
+ --ak-badge-fg: var(--ui-primary);
53
+ --ak-badge-bg: var(--ui-primary-weak);
54
+ --ak-badge-border: var(--ui-primary);
55
+ --ak-badge-dot: var(--ui-primary);
56
+ }
57
+
58
+ .ak-badge--info {
59
+ --ak-badge-fg: var(--ui-info);
60
+ --ak-badge-bg: var(--ui-info-weak);
61
+ --ak-badge-border: var(--ui-info);
62
+ --ak-badge-dot: var(--ui-info);
63
+ }
64
+
65
+ .ak-badge--success {
66
+ --ak-badge-fg: var(--ui-success);
67
+ --ak-badge-bg: var(--ui-success-weak);
68
+ --ak-badge-border: var(--ui-success);
69
+ --ak-badge-dot: var(--ui-success);
70
+ }
71
+
72
+ .ak-badge--warning {
73
+ --ak-badge-fg: var(--ui-warning);
74
+ --ak-badge-bg: var(--ui-warning-weak);
75
+ --ak-badge-border: var(--ui-warning);
76
+ --ak-badge-dot: var(--ui-warning);
77
+ }
78
+
79
+ .ak-badge--error {
80
+ --ak-badge-fg: var(--ui-error);
81
+ --ak-badge-bg: var(--ui-error-weak);
82
+ --ak-badge-border: var(--ui-error);
83
+ --ak-badge-dot: var(--ui-error);
84
+ }
85
+
86
+ /* 浅底标签:只取浅底 + 同色文字(无描边) */
87
+ .ak-badge--soft {
88
+ background: var(--ak-badge-bg);
89
+ border-color: transparent;
90
+ }
91
+
92
+ .ak-badge--soft.ak-badge--default {
93
+ --ak-badge-bg: var(--ui-base-50);
94
+ --ak-badge-fg: var(--ui-base-content-muted);
95
+ }
96
+
97
+ .ak-badge--soft.ak-badge--primary {
98
+ --ak-badge-bg: var(--ui-primary-subtle);
99
+ }
100
+
101
+ /* 描边:浅底 + 同色描边;default 退化为白底 + 中性描边 + 正文色(状态胶囊) */
102
+ .ak-badge--outline {
103
+ background: var(--ak-badge-bg);
104
+ border-color: var(--ak-badge-border);
105
+ }
106
+
107
+ .ak-badge--outline.ak-badge--default {
108
+ --ak-badge-bg: var(--ui-base-100);
109
+ --ak-badge-border: var(--ui-base-300);
110
+ --ak-badge-fg: var(--ui-base-content);
111
+ }
112
+
113
+ /* 无底无框:靠圆点表达状态;成功 / 中性用正文色,错误用危险色 */
114
+ .ak-badge--plain {
115
+ background: transparent;
116
+ border-color: transparent;
117
+ }
118
+
119
+ .ak-badge--plain.ak-badge--default {
120
+ --ak-badge-fg: var(--ui-base-content-muted);
121
+ }
122
+
123
+ .ak-badge--plain.ak-badge--success {
124
+ --ak-badge-fg: var(--ui-base-content);
125
+ }
126
+
127
+ .ak-badge--plain.ak-badge--error {
128
+ --ak-badge-fg: var(--ui-error);
129
+ }
130
+
131
+ .ak-badge__dot {
132
+ width: 8px;
133
+ height: 8px;
134
+ flex: none;
135
+ border-radius: var(--ui-radius-full);
136
+ }
137
+
138
+ .ak-badge--dot-filled .ak-badge__dot {
139
+ background: var(--ak-badge-dot);
140
+ }
141
+
142
+ .ak-badge--dot-ring .ak-badge__dot {
143
+ box-sizing: border-box;
144
+ border: 2px solid var(--ak-badge-dot);
145
+ background: var(--ui-base-100);
146
+ }
147
+
148
+ /* AppTable 清单卡外框(framed):1px 描边 + 8px 圆角。
149
+ 原先是 skill-topic 在业务 CSS 里自绘的 `.topic-grid` 外框,此处上收为标准形态。 */
150
+ .ak-table--framed {
151
+ border: 1px solid var(--ui-base-300);
152
+ border-radius: var(--ui-radius-md);
153
+ }
154
+
155
+ /* AppTable 自绘选择列(farris 内置选择列不启用,避免选中态双通道) */
156
+ .ak-table-check {
157
+ display: inline-flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ cursor: pointer;
161
+ }
162
+
163
+ .ak-table-check input {
164
+ width: 14px;
165
+ height: 14px;
166
+ margin: 0;
167
+ cursor: pointer;
168
+ accent-color: var(--ui-primary);
169
+ }
170
+
171
+ .ak-table-check.is-disabled,
172
+ .ak-table-check.is-disabled input {
173
+ cursor: not-allowed;
174
+ opacity: 0.4;
175
+ }
176
+
177
+ .ak-pagination {
178
+ display: flex;
179
+ justify-content: flex-end;
180
+ }
181
+
182
+ /* AppFilter:条件区容器(farris 自带表单类由 FQuerySolution 输出,这里只补间距契约) */
183
+ .ak-filter {
184
+ width: 100%;
185
+ }
186
+
187
+ /* AppTabs:撑满容器时的高度契约(farrs fill 模式另有补丁,见 farris-bridge.css) */
188
+ .ak-tabs-fill {
189
+ height: 100%;
190
+ display: flex;
191
+ flex-direction: column;
192
+ min-height: 0;
193
+ }
194
+
195
+ /* AppDialog:模态内容默认不撑满(fitContent 由 farris 控制) */
196
+ .ak-dialog-body {
197
+ max-height: 60vh;
198
+ overflow: auto;
199
+ }
200
+
201
+ /* 弹窗按钮 loading spinner(AppModalButton.loading 内部映射到该类) */
202
+ @keyframes ak-modal-spin {
203
+ to { transform: rotate(360deg); }
204
+ }
205
+
206
+ .ak-modal-btn-spinner {
207
+ display: inline-block;
208
+ width: 14px;
209
+ height: 14px;
210
+ border: 2px solid currentColor;
211
+ border-top-color: transparent;
212
+ border-radius: var(--ui-radius-full);
213
+ animation: ak-modal-spin 0.6s linear infinite;
214
+ }
215
+
216
+ /* AppDialog 页脚按钮的品牌化外观。
217
+ 原先是 skill-topic 与 repository 各自 CSS 里逐字重复的 `.farris-modal-footer .btn-*` 覆盖块
218
+ (app 侧写 farris 选择器,违反 C1),此处上收为全仓唯一定义;两段式选择器压过 farris 单类规则。 */
219
+ .app-container .farris-modal-footer .btn {
220
+ height: 34px;
221
+ padding: 0 20px;
222
+ border-radius: var(--ui-radius-md);
223
+ font-size: var(--ui-font-body);
224
+ line-height: 32px;
225
+ }
226
+
227
+ .app-container .farris-modal-footer .btn-primary {
228
+ background: var(--ui-primary);
229
+ border-color: var(--ui-primary);
230
+ }
231
+
232
+ .app-container .farris-modal-footer .btn-primary:hover {
233
+ background: var(--ui-primary-hover);
234
+ border-color: var(--ui-primary-hover);
235
+ }
236
+
237
+ .app-container .farris-modal-footer .btn-light {
238
+ background: var(--ui-base-100);
239
+ border: 1px solid var(--ui-base-400);
240
+ color: var(--ui-base-content);
241
+ }
242
+
243
+ .app-container .farris-modal-footer .btn-light:hover {
244
+ border-color: var(--ui-primary);
245
+ color: var(--ui-primary);
246
+ }
247
+
248
+ /* 表格容器宽度测量宿主:仅在消费方传 onContainerResize / resizePollInterval 时渲染
249
+ (不传时 AppTable 的 DOM 结构与以前完全一致)。 */
250
+ .ak-table-host {
251
+ height: 100%;
252
+ min-height: 0;
253
+ }
254
+
255
+ /* ===== AppPanel(区域容器,无框)=====
256
+ 无框契约:面板自身不写描边 / 圆角 / 阴影 / 独立底色 / 单侧边框,
257
+ 分区感只由 Header 下方 1px 分隔线提供(与 .ak-shell-header 同一手法);
258
+ 两栏之间的竖直分隔线由 AppShell.Split 提供;卡片外观由内容件承载(表格用 AppTable.framed)。
259
+
260
+ 滚动归属:Header 固定(flex-shrink:0),Body 承担**区域内**滚动(overflow:auto)。
261
+ Split 两栏因此让出滚动(overflow:hidden),避免同栏两层滚动容器。 */
262
+ .ak-panel {
263
+ height: 100%;
264
+ display: flex;
265
+ flex-direction: column;
266
+ min-height: 0;
267
+ }
268
+
269
+ .ak-panel__header {
270
+ flex-shrink: 0;
271
+ height: var(--ui-header-height);
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: space-between;
275
+ gap: var(--ui-space-3);
276
+ padding: 0 var(--ui-space-2);
277
+ }
278
+
279
+ .ak-panel__header-title {
280
+ font-size: var(--ui-font-body);
281
+ font-weight: var(--ui-weight-semibold);
282
+ color: var(--ui-base-content);
283
+ white-space: nowrap;
284
+ overflow: hidden;
285
+ text-overflow: ellipsis;
286
+ }
287
+
288
+ /* 右侧组:toolbar(本区域筛选)在左、actions(本区域操作)在右,内部间距锁死 */
289
+ .ak-panel__header-right {
290
+ display: flex;
291
+ align-items: center;
292
+ gap: var(--ui-space-2);
293
+ min-width: 0;
294
+ }
295
+
296
+ .ak-panel__header-toolbar,
297
+ .ak-panel__header-actions {
298
+ display: flex;
299
+ align-items: center;
300
+ gap: var(--ui-space-2);
301
+ }
302
+
303
+ .ak-panel__body {
304
+ flex: 1;
305
+ min-height: 0;
306
+ min-width: 0;
307
+ overflow: auto;
308
+ }
309
+
310
+ /* 面板页脚:区域内**常驻**控件(本区域表格的分页条等),不随内容滚动。
311
+ 与 Header 同款 flex-shrink:0;不加边框(面板无框契约只管分区感由 Header 下边线提供),
312
+ 左右内距与 Body 对齐,让分页条与表格左右边缘齐平。 */
313
+ .ak-panel__footer {
314
+ flex-shrink: 0;
315
+ }
316
+
317
+ /* ===== AppTree(自建树:节点行 + 缩进 + 展开箭头 + 计数徽标位)=====
318
+ 几何与各态对齐 `@aihub/theme` 的 `components/tree.css` 与 Tree 专页实测:
319
+ - 行高 30(紧凑档 28)、圆角 6、字号 13、行内 padding 承载缩进(**行底满宽、只有内容缩进**)
320
+ - 缩进 = 基础内距 8 + level × 步进 10,**无上限**;level 由组件在行上下发
321
+ - 三态:滑过 Ne11、点击瞬态 #F4F5F9、选中 Au02 底 + Th03 字
322
+ - 箭头 8px 折线,收起态转 −90°;叶子节点用占位保持层级对齐
323
+
324
+ 缩进为什么走 CSS 变量而不是行内 paddingLeft:主题包用 typed `attr(data-level type(<number>))`
325
+ 一步算出(`padding-left: calc(8px + attr(...) * 10px)`);为兼容尚无该能力的引擎,这里由组件
326
+ 同时下发 `--ak-tree-level`,公式仍在 CSS 内(数值只取令牌,不散落字面量)。 */
327
+ .ak-tree {
328
+ display: flex;
329
+ flex-direction: column;
330
+ row-gap: 2px;
331
+ width: 100%;
332
+ }
333
+
334
+ .ak-tree__row {
335
+ display: flex;
336
+ align-items: center;
337
+ gap: var(--ui-space-1);
338
+ height: var(--ui-tree-row-height);
339
+ padding: 0 var(--ui-space-2);
340
+ /* 缩进:基础内距 + level × 步进(level 由组件下发) */
341
+ padding-left: calc(var(--ui-tree-indent-base) + var(--ak-tree-level, 0) * var(--ui-tree-indent-step));
342
+ border-radius: var(--ui-radius-control);
343
+ font-size: var(--ui-font-caption);
344
+ color: var(--ui-base-content);
345
+ cursor: pointer;
346
+ white-space: nowrap;
347
+ }
348
+
349
+ /* 紧凑档(S):节点行 28px */
350
+ .ak-tree--compact .ak-tree__row {
351
+ height: var(--ui-tree-row-height-compact);
352
+ }
353
+
354
+ .ak-tree__row:hover {
355
+ background: var(--ui-base-200);
356
+ }
357
+
358
+ .ak-tree__row:active {
359
+ background: var(--ui-tree-active-bg);
360
+ }
361
+
362
+ .ak-tree__row[data-selected='true'] {
363
+ background: var(--ui-primary-subtle);
364
+ color: var(--ui-primary);
365
+ }
366
+
367
+ /* 展开箭头:纯 CSS 三角(不引图标字体);默认指向右(收起),展开时转 90° 向下 */
368
+ .ak-tree__caret {
369
+ display: inline-flex;
370
+ align-items: center;
371
+ justify-content: center;
372
+ flex-shrink: 0;
373
+ width: 14px;
374
+ height: 14px;
375
+ color: var(--ui-base-content-subtle);
376
+ cursor: pointer;
377
+ }
378
+
379
+ .ak-tree__caret::before {
380
+ content: '';
381
+ display: block;
382
+ width: 0;
383
+ height: 0;
384
+ border-left: calc(var(--ui-tree-caret-size) / 2) solid currentColor;
385
+ border-top: calc(var(--ui-tree-caret-size) / 2 - 0.5px) solid transparent;
386
+ border-bottom: calc(var(--ui-tree-caret-size) / 2 - 0.5px) solid transparent;
387
+ transition: transform 0.15s ease;
388
+ }
389
+
390
+ .ak-tree__caret[data-expanded='true']::before {
391
+ transform: rotate(90deg);
392
+ }
393
+
394
+ /* 叶子节点:占位不显示图形,但保留槽位让同层文字对齐 */
395
+ .ak-tree__caret--empty {
396
+ cursor: default;
397
+ }
398
+
399
+ .ak-tree__caret--empty::before {
400
+ visibility: hidden;
401
+ }
402
+
403
+ .ak-tree__label {
404
+ flex: 1;
405
+ min-width: 0;
406
+ overflow: hidden;
407
+ text-overflow: ellipsis;
408
+ white-space: nowrap;
409
+ }
410
+
411
+ .ak-tree__extra {
412
+ display: inline-flex;
413
+ align-items: center;
414
+ flex-shrink: 0;
415
+ }
416
+
417
+ /* ===== AppForm(自建表单:label + 控件 + 错误 + 说明)=====
418
+ 沿 farris 表单的既有约定:label **右对齐**、必填星号在 label 文本**左侧**。
419
+ 与 farris 动态表单的两处有意差异:① 控件撑满剩余宽度(按需用 controlWidth 收敛),
420
+ 不受 farris「字段 max-width 26.625rem」约束;② 错误与说明走**文档流**排在控件下方
421
+ (farris 用绝对定位的浮层提示条,在弹窗里会压住下一行控件)。
422
+
423
+ label 列宽只由变量驱动:AppForm / AppForm.Item 落 `--ak-form-label-width`,默认取令牌,
424
+ 故换宽窄不需要在消费方写 CSS。 */
425
+ /* 行距取 8px 档:AppForm.Item 自己已经是一个完整行(label 与控件等高 + 错误/说明按文档流往下排),
426
+ 再用 16px 行距会把表单拉得很散 —— 尤其是步骤⑤那种「只读文本行」的摘要,行与行之间会显得发虚。
427
+ 要更松的排版属于消费方场景,改这里之前先确认不是想把「行距」当成区块间距在用。 */
428
+ .ak-form {
429
+ display: flex;
430
+ flex-direction: column;
431
+ gap: var(--ui-space-2);
432
+ }
433
+
434
+ /* 两列(AppForm columns=2):`auto-fit` + 列最小宽度 ⇒ 容器够宽才并排、窄了**自动回落一列**
435
+ (不需要媒体查询,也不需要容器查询)。`align-items: start` 让同一行的两个字段各自顶部对齐,
436
+ 不被隔壁那个「错误 + 多行说明」撑高的格子拉扯。 */
437
+ .ak-form--two-column {
438
+ display: grid;
439
+ /* 上限 2 列 + 最小列宽:track 下限取「最小列宽」与「按 2 列等分」的较大者
440
+ —— 否则宽表单里 auto-fit 会一路排到 4 列(实测 1400px 得 4 列),
441
+ 而 `columns={2}` 承诺的是"最多 2 列、窄了回落"(与 AppLayout.Row 同一公式)。 */
442
+ grid-template-columns: repeat(
443
+ auto-fit,
444
+ minmax(
445
+ max(var(--ui-form-column-min), calc((100% - var(--ui-space-2)) / 2)),
446
+ 1fr
447
+ )
448
+ );
449
+ align-items: start;
450
+ }
451
+
452
+ /* 跨满整行:说明行 / 卡片组 / 表格 / 文本域这类整块内容(单列时 grid-column 无效,无副作用) */
453
+ .ak-form-item--full {
454
+ grid-column: 1 / -1;
455
+ }
456
+
457
+ /* ===== AppSection(区块:区块标题 + 一组内容;表单内写 AppForm.Section,是同一个件)=====
458
+ 内容行距必须由本件自己给:父容器(如 `AppForm`)的 gap 只作用于它的**直接子元素**,
459
+ 内容一旦被包进本件,里面的行距就没人管了(自绘 `<div class="xx-sub-title">` 最常踩的正是这个坑:
460
+ 标题有了、间距乱了)。故固定两档:内容 8px、区块 16px 叠父级 gap(合 24px)——
461
+ 3:1 的对比才读得出「这是两块」。 */
462
+ .ak-section {
463
+ display: flex;
464
+ flex-direction: column;
465
+ gap: var(--ui-space-2);
466
+ }
467
+
468
+ /* 区块间距走 :not(:first-child):不依赖兄弟结构(两块之间可能夹着别的东西),
469
+ 也让本件能脱离表单单独使用(如「校验结果」这类不成表单的区块)。 */
470
+ .ak-section:not(:first-child) {
471
+ margin-top: var(--ui-space-4);
472
+ }
473
+
474
+ /*
475
+ * 嵌套区块(区块里再分组,如「来源和目标列」下的「来源列 / 目标列」):
476
+ * 直接子区块**不再叠区块间距** —— 父级 gap(8px)就是组内行距;再叠 16px 会让二级分组
477
+ * 离上一块比离自己的标题还远(`:not(:first-child)` 本意是「两块平级区块之间」,不是层级内部)。
478
+ * 选择器与上面的间距规则同特异性、写在后面,故嵌套时以后者为准(不靠源码外的顺序假设)。
479
+ */
480
+ .ak-section > .ak-section {
481
+ margin-top: 0;
482
+ }
483
+
484
+ /* 区块标题:14px/600。有意不打到 16px(`--ui-font-heading` 是弹窗标题那一档),
485
+ 区块标题只需与字段 label(14px/400)拉出字重差;`extra` 槽(计数、行内按钮)跟在文本之后。 */
486
+ .ak-section__title {
487
+ display: flex;
488
+ align-items: center;
489
+ gap: var(--ui-space-2);
490
+ font-size: var(--ui-font-body);
491
+ font-weight: var(--ui-weight-semibold);
492
+ line-height: var(--ui-line-body);
493
+ color: var(--ui-base-content);
494
+ }
495
+
496
+ /* 嵌套区块的标题**降到常规字重**:层级差改由「一级 14/600 ↔ 二级 14/400」表达。
497
+ * 二级若同样吃 600,与一级抢同一档 ⇒ 读不出从属关系(嵌套里两个 600 并排即为此坑)。 */
498
+ .ak-section > .ak-section > .ak-section__title {
499
+ font-weight: var(--ui-weight-regular);
500
+ }
501
+
502
+ .ak-form-item {
503
+ /* label 列宽:Item 行内下发 --ak-form-label-width;未传时取令牌 */
504
+ --ak-form-label-width: var(--ui-form-label-width);
505
+ display: flex;
506
+ align-items: flex-start;
507
+ }
508
+
509
+ /* label 列:固定宽 + **右对齐**(与 farris 的动态表单一致,也是水平表单的通行默认)。
510
+ min-height 取**控件真实高度**(--ui-form-control-height 由桥接层按 farris 控件规则给出):
511
+ label 盒与控件盒等高 ⇒ 两侧文字各自居中后落在同一条中线上。
512
+ 曾经写的是 32px 令牌,而 farris 控件实际 ≈24px(rem,随根字号)⇒ label 明显偏低,已修。 */
513
+ .ak-form-item__label {
514
+ flex: 0 0 auto;
515
+ width: var(--ak-form-label-width);
516
+ box-sizing: border-box;
517
+ min-height: var(--ui-form-control-height, auto);
518
+ padding-right: var(--ui-space-3);
519
+ display: flex;
520
+ align-items: center;
521
+ /* 右对齐:label 文本贴着控件列,短标签也不会在列里留下大段空隙(与 farris 动态表单一致) */
522
+ justify-content: flex-end;
523
+ gap: 2px;
524
+ font-size: var(--ui-font-body);
525
+ line-height: var(--ui-line-body);
526
+ color: var(--ui-base-content);
527
+ }
528
+
529
+ .ak-form-item__required {
530
+ flex: 0 0 auto;
531
+ color: var(--ui-error);
532
+ }
533
+
534
+ /* label 对齐:基类默认右对齐(贴控件列,`labelAlign` 缺省档);
535
+ `labelAlign="left"` 时挂这个修饰类改成左对齐 —— 只给「非默认值」挂类,避免两段声明争胜出顺序。
536
+ 位置必须在基类规则之后:`.ak-form-item__label {` 是它的子串,测试取规则体时按首次出现命中。 */
537
+ .ak-form-item--label-left .ak-form-item__label {
538
+ justify-content: flex-start;
539
+ }
540
+
541
+ /* 只读展示行(`AppForm.Item` 的 `text` 形态,或显式 `readonly`)的 label 降一档为**次要色**:
542
+ 值与字段名同取正文色时,摘要 / 详情读起来是一串并列文本,分不清哪个是字段名
543
+ (原型 .sum-group td.k / .field-item .k 用的正是次要色)。值侧不变,仍是正文色。
544
+ `muted` 而非 `subtle`:空值占位「—」已经是 subtle,label 再用同色会让空行整行同调。
545
+ 位置同样必须在基类 `.ak-form-item__label {` 之后(spec 的 ruleBody 按首次出现取规则体)。 */
546
+ .ak-form-item--readonly .ak-form-item__label {
547
+ color: var(--ui-base-content-muted);
548
+ }
549
+
550
+ /* label 文本过长时省略而不是换行(换行会把控件列挤错位) */
551
+ .ak-form-item__label-text {
552
+ min-width: 0;
553
+ overflow: hidden;
554
+ text-overflow: ellipsis;
555
+ white-space: nowrap;
556
+ }
557
+
558
+ .ak-form-item__control {
559
+ flex: 1;
560
+ min-width: 0;
561
+ }
562
+
563
+ /* 只读文本行(AppForm.Item 的 text 形态:摘要 / 详情):正文色与行高,
564
+ 长文案(SQL where、列说明)按词折行;`pre-wrap` 保留换行符(多行过滤条件回显)。 */
565
+ .ak-form-item__text {
566
+ min-height: var(--ui-form-control-height);
567
+ display: flex;
568
+ align-items: center;
569
+ font-size: var(--ui-font-body);
570
+ line-height: var(--ui-line-body);
571
+ color: var(--ui-base-content);
572
+ white-space: pre-wrap;
573
+ word-break: break-word;
574
+ }
575
+
576
+ /* 空值占位(组件统一给 `—`):压成次要色,避免它被当成真实取值 */
577
+ .ak-form-item__text[data-empty='true'] {
578
+ color: var(--ui-base-content-subtle);
579
+ }
580
+
581
+ .ak-form-item__error {
582
+ margin-top: var(--ui-space-1);
583
+ font-size: var(--ui-font-caption);
584
+ line-height: var(--ui-line-caption);
585
+ color: var(--ui-error);
586
+ }
587
+
588
+ .ak-form-item__hint {
589
+ margin-top: var(--ui-space-1);
590
+ font-size: var(--ui-font-caption);
591
+ line-height: var(--ui-line-caption);
592
+ color: var(--ui-base-content-subtle);
593
+ }
594
+
595
+ /* ===== AppSteps(封装 farris FStep)=====
596
+ 节点圆点 / 连接线 / 三角全部沿用上游样式(本件复刻其 DOM 结构、只替换完成勾图标),
597
+ 故这里没有自有规则;只读档(clickable=false)的光标在 farris-bridge.css。 */
@@ -0,0 +1,39 @@
1
+ import { type MaybeRefOrGetter, type Ref } from 'vue';
2
+ export interface UseClientPaginationOptions {
3
+ /** 初始页大小;缺省取 `pageSizeOptions[0]` */
4
+ pageSize?: number;
5
+ /** 页大小候选;缺省 `[10, 20, 50]`(与 AppPagination 默认一致) */
6
+ pageSizeOptions?: number[];
7
+ }
8
+ export interface UseClientPagination<T = unknown> {
9
+ /** 当前页码(**0 基**,与 AppPagination 契约一致) */
10
+ page: Ref<number>;
11
+ pageSize: Ref<number>;
12
+ /** 总条数(切片前) */
13
+ total: Ref<number>;
14
+ /** 总页数(至少 1) */
15
+ pageCount: Ref<number>;
16
+ /** 当前页数据(已切片,直接喂给 AppTable.rows) */
17
+ pageRows: Ref<T[]>;
18
+ pageSizeOptions: number[];
19
+ setPage: (page: number) => void;
20
+ setPageSize: (size: number) => void;
21
+ }
22
+ /**
23
+ * 客户端分页状态(切片 + 越界回退),与 `AppPagination` 同契约(**0 基**)。
24
+ *
25
+ * 为什么不在 `AppTable` 内建分页:已接入应用的既有契约是「页面持有 page/pageSize、
26
+ * 分页器放 `AppShell.Footer`」,`AppTable` 保持纯表格职责。本 composable 把
27
+ * 「切片 + 越界回退 + 页大小切换回首页」这三件样板事收起来。
28
+ *
29
+ * 越界回退:数据变少(过滤 / 删除)导致当前页超出范围时,自动回退到最后一页,
30
+ * 避免出现「空白页但分页器显示有数据」。
31
+ *
32
+ * @example
33
+ * const list = useClientPagination(() => filteredDefinitions.value)
34
+ * <AppTable :rows="list.pageRows" ... />
35
+ * <AppPagination :page="list.page" :pageSize="list.pageSize" :total="list.total"
36
+ * :pageSizeOptions="list.pageSizeOptions"
37
+ * :onPageChange="list.setPage" :onPageSizeChange="list.setPageSize" />
38
+ */
39
+ export declare function useClientPagination<T>(rows: MaybeRefOrGetter<T[]>, options?: UseClientPaginationOptions): UseClientPagination<T>;
@@ -0,0 +1,29 @@
1
+ import { ref, computed, toValue, watch } from "vue";
2
+ function useClientPagination(rows, options = {}) {
3
+ const pageSizeOptions = options.pageSizeOptions ?? [10, 20, 50];
4
+ const pageSize = ref(options.pageSize ?? pageSizeOptions[0]);
5
+ const page = ref(0);
6
+ const total = computed(() => toValue(rows).length);
7
+ const pageCount = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)));
8
+ watch([total, pageSize], () => {
9
+ const last = pageCount.value - 1;
10
+ if (page.value > last) page.value = last;
11
+ if (page.value < 0) page.value = 0;
12
+ });
13
+ const pageRows = computed(() => {
14
+ const start = page.value * pageSize.value;
15
+ return toValue(rows).slice(start, start + pageSize.value);
16
+ });
17
+ const setPage = (next) => {
18
+ const last = pageCount.value - 1;
19
+ page.value = Math.min(Math.max(0, next), last);
20
+ };
21
+ const setPageSize = (size) => {
22
+ pageSize.value = size;
23
+ page.value = 0;
24
+ };
25
+ return { page, pageSize, total, pageCount, pageRows, pageSizeOptions, setPage, setPageSize };
26
+ }
27
+ export {
28
+ useClientPagination
29
+ };
@@ -0,0 +1,8 @@
1
+ import { QueryClient } from '@tanstack/vue-query';
2
+ /**
3
+ * 缺省 QueryClient:createSubApp 未显式传入 queryClient 时使用。
4
+ *
5
+ * 与各子应用现状 providers.tsx 的公共默认一致(staleTime 30s、不因窗口聚焦重取);
6
+ * 带全局错误缓存/自定义 retry 的应用应自建 QueryClient 后经 createSubApp 注入。
7
+ */
8
+ export declare function createQueryClient(): QueryClient;
@@ -0,0 +1,14 @@
1
+ import { QueryClient } from "@tanstack/vue-query";
2
+ function createQueryClient() {
3
+ return new QueryClient({
4
+ defaultOptions: {
5
+ queries: {
6
+ staleTime: 30 * 1e3,
7
+ refetchOnWindowFocus: false
8
+ }
9
+ }
10
+ });
11
+ }
12
+ export {
13
+ createQueryClient
14
+ };