@skopon-cool/form-sdk 0.6.3 → 0.6.5
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/dist/adapter/a2uiAdapter.d.ts +1 -1
- package/dist/adapter/a2uiAdapter.d.ts.map +1 -1
- package/dist/adapter/formAssetOptions.d.ts +3 -0
- package/dist/adapter/formAssetOptions.d.ts.map +1 -0
- package/dist/adapter/formSchema.d.ts.map +1 -1
- package/dist/adapter/resolveSurface.d.ts +1 -1
- package/dist/adapter/resolveSurface.d.ts.map +1 -1
- package/dist/catalog/a2uiCustomCatalog.d.ts.map +1 -1
- package/dist/catalog/materialUploadContext.d.ts +24 -0
- package/dist/catalog/materialUploadContext.d.ts.map +1 -0
- package/dist/components/AskUserFormCard.d.ts +5 -0
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/components/SkoponFormRenderer.d.ts +3 -0
- package/dist/components/SkoponFormRenderer.d.ts.map +1 -1
- package/dist/form-sdk.css +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2170 -1928
- package/dist/submit/buildAnswerAgentBody.d.ts.map +1 -1
- package/dist/submit/intersectPayloadBlocksWithForm.d.ts +11 -2
- package/dist/submit/intersectPayloadBlocksWithForm.d.ts.map +1 -1
- package/dist/types/index.d.ts +35 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapter/a2uiAdapter.test.ts +4 -0
- package/src/adapter/a2uiAdapter.ts +58 -2
- package/src/adapter/formAssetOptions.ts +19 -0
- package/src/adapter/formSchema.ts +54 -0
- package/src/adapter/resolveSurface.ts +4 -1
- package/src/catalog/a2uiCustomCatalog.tsx +296 -46
- package/src/catalog/fileUploadPreview.test.tsx +77 -11
- package/src/catalog/materialUploadContext.tsx +43 -0
- package/src/catalog/moduleTabs.test.tsx +59 -0
- package/src/components/AskUserFormCard.tsx +33 -9
- package/src/components/AskUserFormCard.upload.test.tsx +66 -0
- package/src/components/SkoponFormRenderer.tsx +6 -0
- package/src/icons/FilePlaceholderIcon.tsx +2 -2
- package/src/index.ts +2 -0
- package/src/styles/index.css +295 -0
- package/src/submit/buildAnswerAgentBody.ts +2 -0
- package/src/submit/intersectPayloadBlocksWithForm.ts +52 -3
- package/src/submit/submit.test.ts +54 -0
- package/src/types/index.ts +37 -0
package/src/styles/index.css
CHANGED
|
@@ -230,6 +230,210 @@
|
|
|
230
230
|
justify-content: flex-end;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
.skopon-module-panel {
|
|
234
|
+
min-width: 0;
|
|
235
|
+
padding: var(--space-3) var(--space-2) var(--space-2);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.skopon-module-panel__description {
|
|
239
|
+
margin: 0 0 var(--space-3) !important;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.skopon-module-panel__grid {
|
|
243
|
+
display: grid;
|
|
244
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
245
|
+
gap: var(--space-3);
|
|
246
|
+
align-items: start;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.skopon-module-panel__field {
|
|
250
|
+
min-width: 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.skopon-module-panel__field .form-block-preview {
|
|
254
|
+
padding: 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.skopon-compact-upload {
|
|
258
|
+
display: grid;
|
|
259
|
+
grid-template-columns: 86px minmax(0, 1fr);
|
|
260
|
+
gap: 13px;
|
|
261
|
+
align-items: center;
|
|
262
|
+
min-width: 0;
|
|
263
|
+
padding: 12px;
|
|
264
|
+
border: 1px solid var(--color-border-subtle);
|
|
265
|
+
border-radius: 12px;
|
|
266
|
+
background: var(--color-surface);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.skopon-compact-upload--failed {
|
|
270
|
+
border-color: color-mix(in srgb, var(--color-error, #cf3030) 45%, var(--color-border-subtle));
|
|
271
|
+
background: color-mix(in srgb, var(--color-error, #cf3030) 7%, var(--color-surface));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.skopon-compact-upload__thumb {
|
|
275
|
+
position: relative;
|
|
276
|
+
display: grid;
|
|
277
|
+
place-items: center;
|
|
278
|
+
width: 86px;
|
|
279
|
+
height: 92px;
|
|
280
|
+
overflow: hidden;
|
|
281
|
+
border-radius: 9px;
|
|
282
|
+
color: var(--color-text-secondary);
|
|
283
|
+
background: linear-gradient(145deg, var(--color-bg-subtle), var(--color-border-subtle));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.skopon-compact-upload__thumb img {
|
|
287
|
+
width: 100%;
|
|
288
|
+
height: 100%;
|
|
289
|
+
object-fit: cover;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.skopon-compact-upload__state-mark {
|
|
293
|
+
position: absolute;
|
|
294
|
+
right: 6px;
|
|
295
|
+
bottom: 6px;
|
|
296
|
+
display: grid;
|
|
297
|
+
place-items: center;
|
|
298
|
+
width: 22px;
|
|
299
|
+
height: 22px;
|
|
300
|
+
border-radius: 999px;
|
|
301
|
+
color: var(--color-text-secondary);
|
|
302
|
+
background: color-mix(in srgb, var(--color-surface) 92%, transparent);
|
|
303
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14);
|
|
304
|
+
font-size: 13px;
|
|
305
|
+
font-weight: 700;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.skopon-compact-upload--passed .skopon-compact-upload__state-mark,
|
|
309
|
+
.skopon-compact-upload--passed .skopon-compact-upload__status {
|
|
310
|
+
color: var(--color-success, #087a48);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.skopon-compact-upload--failed .skopon-compact-upload__state-mark,
|
|
314
|
+
.skopon-compact-upload--failed .skopon-compact-upload__status,
|
|
315
|
+
.skopon-compact-upload__error {
|
|
316
|
+
color: var(--color-error, #cf3030);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.skopon-compact-upload__meta {
|
|
320
|
+
display: flex;
|
|
321
|
+
flex-direction: column;
|
|
322
|
+
align-items: flex-start;
|
|
323
|
+
gap: 5px;
|
|
324
|
+
min-width: 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.skopon-compact-upload__meta > strong {
|
|
328
|
+
font-size: var(--font-size-sm);
|
|
329
|
+
font-weight: var(--font-weight-medium);
|
|
330
|
+
line-height: 1.4;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.skopon-compact-upload__status,
|
|
334
|
+
.skopon-compact-upload__error {
|
|
335
|
+
max-width: 100%;
|
|
336
|
+
overflow: hidden;
|
|
337
|
+
color: var(--color-text-muted);
|
|
338
|
+
font-size: var(--font-size-xs);
|
|
339
|
+
line-height: 1.4;
|
|
340
|
+
text-overflow: ellipsis;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.skopon-compact-upload__source {
|
|
344
|
+
display: inline-flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
min-height: 20px;
|
|
347
|
+
padding: 1px 7px;
|
|
348
|
+
border: 1px solid var(--color-border-subtle);
|
|
349
|
+
border-radius: 999px;
|
|
350
|
+
color: var(--color-text-secondary);
|
|
351
|
+
background: var(--color-bg-subtle);
|
|
352
|
+
font-size: 11px;
|
|
353
|
+
line-height: 1.4;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.skopon-compact-upload__source--asset_only {
|
|
357
|
+
color: var(--color-primary);
|
|
358
|
+
border-color: color-mix(in srgb, var(--color-primary) 24%, var(--color-border-subtle));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.skopon-compact-upload__source--upload_or_asset {
|
|
362
|
+
color: var(--color-text);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.skopon-compact-upload__meta .ant-btn {
|
|
366
|
+
min-height: 30px;
|
|
367
|
+
padding-inline: 11px;
|
|
368
|
+
border-radius: 8px;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.skopon-module-panel__guide {
|
|
372
|
+
display: flex;
|
|
373
|
+
align-items: center;
|
|
374
|
+
justify-content: space-between;
|
|
375
|
+
gap: var(--space-4);
|
|
376
|
+
margin-top: 14px;
|
|
377
|
+
padding: 13px 0 0;
|
|
378
|
+
border-top: 1px solid var(--color-border-subtle);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.skopon-module-panel__requirements .ant-typography {
|
|
382
|
+
margin-bottom: 0;
|
|
383
|
+
white-space: pre-line;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.skopon-module-panel__example-list {
|
|
387
|
+
display: flex;
|
|
388
|
+
flex-wrap: wrap;
|
|
389
|
+
gap: 6px;
|
|
390
|
+
margin-top: 4px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.skopon-module-panel__example-list .ant-image,
|
|
394
|
+
.skopon-module-panel__example-list .ant-image-img {
|
|
395
|
+
width: 42px !important;
|
|
396
|
+
height: 42px !important;
|
|
397
|
+
border-radius: 7px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.a2ui-surface.a2ui-container .a2ui-tabs-header.a2ui-tab-button {
|
|
401
|
+
min-height: 44px;
|
|
402
|
+
padding: 0 14px !important;
|
|
403
|
+
border-radius: 0 !important;
|
|
404
|
+
border-bottom: 2px solid transparent !important;
|
|
405
|
+
background: transparent !important;
|
|
406
|
+
color: var(--color-text-muted) !important;
|
|
407
|
+
font-size: var(--font-size-sm);
|
|
408
|
+
font-weight: 400;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.a2ui-surface.a2ui-container .a2ui-tabs-header.a2ui-tab-button.active {
|
|
412
|
+
border-bottom-color: var(--color-text) !important;
|
|
413
|
+
color: var(--color-text) !important;
|
|
414
|
+
font-weight: var(--font-weight-medium);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
@media (max-width: 640px) {
|
|
418
|
+
.skopon-module-panel__grid {
|
|
419
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.skopon-module-panel__guide {
|
|
423
|
+
align-items: flex-start;
|
|
424
|
+
flex-direction: column;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.skopon-compact-upload {
|
|
428
|
+
grid-template-columns: 72px minmax(0, 1fr);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.skopon-compact-upload__thumb {
|
|
432
|
+
width: 72px;
|
|
433
|
+
height: 82px;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
233
437
|
.ask-user-curl-card {
|
|
234
438
|
display: flex;
|
|
235
439
|
flex-direction: column;
|
|
@@ -617,3 +821,94 @@
|
|
|
617
821
|
padding: 12px 0;
|
|
618
822
|
color: var(--color-text-secondary, rgba(0, 0, 0, 0.45));
|
|
619
823
|
}
|
|
824
|
+
|
|
825
|
+
.skopon-file-source-actions {
|
|
826
|
+
display: flex;
|
|
827
|
+
flex-wrap: wrap;
|
|
828
|
+
gap: 8px;
|
|
829
|
+
margin: 8px 0;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.skopon-asset-picker {
|
|
833
|
+
display: grid;
|
|
834
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
835
|
+
gap: 12px;
|
|
836
|
+
max-height: min(68vh, 620px);
|
|
837
|
+
overflow: auto;
|
|
838
|
+
padding: 2px;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.skopon-asset-picker__card {
|
|
842
|
+
display: flex;
|
|
843
|
+
flex-direction: column;
|
|
844
|
+
gap: 9px;
|
|
845
|
+
min-width: 0;
|
|
846
|
+
padding: 10px;
|
|
847
|
+
border: 1px solid #e5e7eb;
|
|
848
|
+
border-radius: 10px;
|
|
849
|
+
background: #fff;
|
|
850
|
+
transition: border-color 160ms ease, box-shadow 160ms ease;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.skopon-asset-picker__card:hover {
|
|
854
|
+
border-color: #91caff;
|
|
855
|
+
box-shadow: 0 2px 10px rgba(22, 119, 255, 0.1);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.skopon-asset-picker__media {
|
|
859
|
+
display: flex;
|
|
860
|
+
align-items: center;
|
|
861
|
+
justify-content: center;
|
|
862
|
+
width: 100%;
|
|
863
|
+
height: 150px;
|
|
864
|
+
overflow: hidden;
|
|
865
|
+
border-radius: 6px;
|
|
866
|
+
background: #f5f7fa;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.skopon-asset-picker__media .ant-image,
|
|
870
|
+
.skopon-asset-picker__media .ant-image-img {
|
|
871
|
+
display: block;
|
|
872
|
+
width: 100%;
|
|
873
|
+
height: 100%;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.skopon-asset-picker__media .ant-image-img {
|
|
877
|
+
object-fit: contain;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.skopon-asset-picker__placeholder {
|
|
881
|
+
display: flex;
|
|
882
|
+
justify-content: center;
|
|
883
|
+
align-items: center;
|
|
884
|
+
width: 100%;
|
|
885
|
+
height: 100%;
|
|
886
|
+
color: #8c8c8c;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.skopon-asset-picker__name {
|
|
890
|
+
overflow-wrap: anywhere;
|
|
891
|
+
min-height: 22px;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
@media (max-width: 600px) {
|
|
895
|
+
.skopon-asset-picker {
|
|
896
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
897
|
+
gap: 8px;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.skopon-asset-picker__card {
|
|
901
|
+
padding: 8px;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.skopon-asset-picker__media {
|
|
905
|
+
height: 120px;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.skopon-selected-assets__row {
|
|
910
|
+
display: flex;
|
|
911
|
+
align-items: center;
|
|
912
|
+
justify-content: space-between;
|
|
913
|
+
gap: 8px;
|
|
914
|
+
}
|
|
@@ -24,8 +24,10 @@ function toBlocksJsonShape(
|
|
|
24
24
|
return source
|
|
25
25
|
}
|
|
26
26
|
return {
|
|
27
|
+
...(obj.schema === 'skopon.material-form/v2' ? { schema: obj.schema } : {}),
|
|
27
28
|
title: typeof obj.title === 'string' ? obj.title : '',
|
|
28
29
|
description: typeof obj.description === 'string' ? obj.description : '',
|
|
30
|
+
modules: Array.isArray(obj.modules) ? obj.modules : undefined,
|
|
29
31
|
blocks: obj.blocks,
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -3,7 +3,8 @@ import { hasRenderableFormOptions } from '../adapter/formOptions'
|
|
|
3
3
|
import type { FormBlock, FormSchema } from '../types/index'
|
|
4
4
|
import { isDisplayBlockType, isInputBlockType } from '../types/index'
|
|
5
5
|
|
|
6
|
-
export type PayloadBlocksDefinition = Pick<FormSchema, 'title' | 'description' | 'blocks'> & {
|
|
6
|
+
export type PayloadBlocksDefinition = Pick<FormSchema, 'title' | 'description' | 'modules' | 'blocks'> & {
|
|
7
|
+
schema?: 'skopon.material-form/v2'
|
|
7
8
|
/**
|
|
8
9
|
* ask 原始 blocks(normalize 前)。交集合并按此键集覆盖 form,
|
|
9
10
|
* 避免 normalize 填入的空默认值误盖表单样式。
|
|
@@ -18,9 +19,24 @@ export interface PayloadBlocksIntersection {
|
|
|
18
19
|
renderBlocks: FormBlock[]
|
|
19
20
|
title: string
|
|
20
21
|
description: string
|
|
22
|
+
modules?: FormSchema['modules']
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const FORM_ANCHORED_KEYS = new Set(['type', 'name', 'id'])
|
|
26
|
+
const RUNTIME_SNAPSHOT_KEYS = new Set([
|
|
27
|
+
'defaultValue',
|
|
28
|
+
'fileReviewStatus',
|
|
29
|
+
'fileReviewMessage',
|
|
30
|
+
'fileRejectedUrls',
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
export interface IntersectPayloadBlocksOptions {
|
|
34
|
+
/**
|
|
35
|
+
* snapshot:消息快照中的显式配置优先(兼容旧行为);
|
|
36
|
+
* latest:最新表单负责布局/文案/校验,只保留快照里的填写与审核运行态。
|
|
37
|
+
*/
|
|
38
|
+
definitionPolicy?: 'snapshot' | 'latest'
|
|
39
|
+
}
|
|
24
40
|
|
|
25
41
|
function isHiddenTextBlock(block: FormBlock): boolean {
|
|
26
42
|
return block.type === 'text' && block.hidden === true
|
|
@@ -94,7 +110,27 @@ function mergeBlock(formBlock: FormBlock, rawPayloadBlock: FormBlock): FormBlock
|
|
|
94
110
|
merged.type = formBlock.type
|
|
95
111
|
merged.name = formBlock.name?.trim() || rawPayloadBlock.name
|
|
96
112
|
merged.id = formBlock.id
|
|
97
|
-
|
|
113
|
+
const normalized = normalizePayloadBlock(merged)
|
|
114
|
+
// 单 block normalize 没有 modules 上下文,会把合法 moduleId 当成悬空引用清掉。
|
|
115
|
+
normalized.moduleId = merged.moduleId
|
|
116
|
+
return normalized
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function mergeBlockWithLatestDefinition(
|
|
120
|
+
formBlock: FormBlock,
|
|
121
|
+
rawPayloadBlock: FormBlock,
|
|
122
|
+
): FormBlock {
|
|
123
|
+
const merged: FormBlock = { ...formBlock }
|
|
124
|
+
for (const key of Object.keys(rawPayloadBlock) as (keyof FormBlock)[]) {
|
|
125
|
+
if (!RUNTIME_SNAPSHOT_KEYS.has(key)) continue
|
|
126
|
+
if (!Object.prototype.hasOwnProperty.call(rawPayloadBlock, key)) continue
|
|
127
|
+
const value = rawPayloadBlock[key]
|
|
128
|
+
if (value === undefined) continue
|
|
129
|
+
Object.assign(merged, { [key]: value })
|
|
130
|
+
}
|
|
131
|
+
const normalized = normalizePayloadBlock(merged)
|
|
132
|
+
normalized.moduleId = merged.moduleId
|
|
133
|
+
return normalized
|
|
98
134
|
}
|
|
99
135
|
|
|
100
136
|
/** 解析 ask_user payload 为 blocksJson 形(title / description / blocks) */
|
|
@@ -108,12 +144,15 @@ export function parsePayloadBlocksJson(payload: unknown): PayloadBlocksDefinitio
|
|
|
108
144
|
const normalized = normalizeFormDefinition({
|
|
109
145
|
title: typeof obj.title === 'string' ? obj.title : '',
|
|
110
146
|
description: typeof obj.description === 'string' ? obj.description : '',
|
|
147
|
+
modules: Array.isArray(obj.modules) ? obj.modules as FormSchema['modules'] : undefined,
|
|
111
148
|
blocks: rawBlocks,
|
|
112
149
|
jsonSchema: {},
|
|
113
150
|
})
|
|
114
151
|
return {
|
|
152
|
+
schema: obj.schema === 'skopon.material-form/v2' ? obj.schema : undefined,
|
|
115
153
|
title: normalized.title,
|
|
116
154
|
description: normalized.description,
|
|
155
|
+
modules: normalized.modules,
|
|
117
156
|
blocks: normalized.blocks,
|
|
118
157
|
rawBlocks,
|
|
119
158
|
}
|
|
@@ -126,7 +165,9 @@ export function parsePayloadBlocksJson(payload: unknown): PayloadBlocksDefinitio
|
|
|
126
165
|
export function intersectPayloadBlocksWithForm(
|
|
127
166
|
payloadDef: PayloadBlocksDefinition,
|
|
128
167
|
formDefinition: FormSchema | undefined,
|
|
168
|
+
options?: IntersectPayloadBlocksOptions,
|
|
129
169
|
): PayloadBlocksIntersection {
|
|
170
|
+
const definitionPolicy = options?.definitionPolicy ?? 'snapshot'
|
|
130
171
|
const formByName = new Map<string, FormBlock>()
|
|
131
172
|
for (const block of formDefinition?.blocks ?? []) {
|
|
132
173
|
if (isInputBlockType(block.type) && block.name?.trim()) {
|
|
@@ -144,7 +185,11 @@ export function intersectPayloadBlocksWithForm(
|
|
|
144
185
|
const formBlock = formByName.get(name)
|
|
145
186
|
if (formBlock) {
|
|
146
187
|
const rawPayloadBlock = resolveRawInputBlock(payloadDef, name, payloadBlock)
|
|
147
|
-
matchedBlocks.push(
|
|
188
|
+
matchedBlocks.push(
|
|
189
|
+
definitionPolicy === 'latest'
|
|
190
|
+
? mergeBlockWithLatestDefinition(formBlock, rawPayloadBlock)
|
|
191
|
+
: mergeBlock(formBlock, rawPayloadBlock),
|
|
192
|
+
)
|
|
148
193
|
} else {
|
|
149
194
|
extraBlocks.push(normalizePayloadBlock(payloadBlock))
|
|
150
195
|
}
|
|
@@ -156,6 +201,10 @@ export function intersectPayloadBlocksWithForm(
|
|
|
156
201
|
renderBlocks: buildRenderBlocks(payloadDef, matchedBlocks),
|
|
157
202
|
title: payloadDef.title?.trim() || formDefinition?.title?.trim() || '',
|
|
158
203
|
description: payloadDef.description?.trim() || formDefinition?.description?.trim() || '',
|
|
204
|
+
modules:
|
|
205
|
+
definitionPolicy === 'latest'
|
|
206
|
+
? (formDefinition?.modules?.length ? formDefinition.modules : payloadDef.modules)
|
|
207
|
+
: (payloadDef.modules?.length ? payloadDef.modules : formDefinition?.modules),
|
|
159
208
|
}
|
|
160
209
|
}
|
|
161
210
|
|
|
@@ -262,6 +262,60 @@ describe('intersectPayloadBlocksWithForm', () => {
|
|
|
262
262
|
expect(result.matchedBlocks[0]?.defaultValue).toBe('hello')
|
|
263
263
|
})
|
|
264
264
|
|
|
265
|
+
it('uses latest form presentation while preserving snapshot runtime state', () => {
|
|
266
|
+
const form: FormSchema = {
|
|
267
|
+
title: 'Latest Form',
|
|
268
|
+
description: '',
|
|
269
|
+
modules: [{ id: 'latest-person', label: '人物(新版)' }],
|
|
270
|
+
blocks: [{
|
|
271
|
+
id: 'latest-file',
|
|
272
|
+
type: 'file',
|
|
273
|
+
name: 'image01',
|
|
274
|
+
label: '新版人物图',
|
|
275
|
+
moduleId: 'latest-person',
|
|
276
|
+
help: '新版说明',
|
|
277
|
+
fileReviewCriteria: '新版审核标准',
|
|
278
|
+
fileExampleUrls: ['https://example.com/latest.png'],
|
|
279
|
+
}],
|
|
280
|
+
jsonSchema: {},
|
|
281
|
+
}
|
|
282
|
+
const snapshot = parsePayloadBlocksJson({
|
|
283
|
+
title: '当前任务素材',
|
|
284
|
+
modules: [{ id: 'old-person', label: '人物(旧版)' }],
|
|
285
|
+
blocks: [{
|
|
286
|
+
id: 'old-file',
|
|
287
|
+
type: 'file',
|
|
288
|
+
name: 'image01',
|
|
289
|
+
label: '旧版人物图',
|
|
290
|
+
moduleId: 'old-person',
|
|
291
|
+
help: '旧版说明',
|
|
292
|
+
fileReviewCriteria: '旧版审核标准',
|
|
293
|
+
defaultValue: ['https://example.com/uploaded.png'],
|
|
294
|
+
fileReviewStatus: 'failed',
|
|
295
|
+
fileReviewMessage: '人物模糊',
|
|
296
|
+
fileRejectedUrls: ['https://example.com/rejected.png'],
|
|
297
|
+
}],
|
|
298
|
+
})!
|
|
299
|
+
|
|
300
|
+
const result = intersectPayloadBlocksWithForm(snapshot, form, {
|
|
301
|
+
definitionPolicy: 'latest',
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
expect(result.modules).toEqual(form.modules)
|
|
305
|
+
expect(result.matchedBlocks[0]).toMatchObject({
|
|
306
|
+
id: 'latest-file',
|
|
307
|
+
label: '新版人物图',
|
|
308
|
+
moduleId: 'latest-person',
|
|
309
|
+
help: '新版说明',
|
|
310
|
+
fileReviewCriteria: '新版审核标准',
|
|
311
|
+
fileExampleUrls: ['https://example.com/latest.png'],
|
|
312
|
+
defaultValue: ['https://example.com/uploaded.png'],
|
|
313
|
+
fileReviewStatus: 'failed',
|
|
314
|
+
fileReviewMessage: '人物模糊',
|
|
315
|
+
fileRejectedUrls: ['https://example.com/rejected.png'],
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
265
319
|
it('keeps form caseFilter when ask omits caseFilter key', () => {
|
|
266
320
|
const form: FormSchema = {
|
|
267
321
|
title: 'Form',
|
package/src/types/index.ts
CHANGED
|
@@ -20,6 +20,16 @@ export type A2uiComponentName =
|
|
|
20
20
|
| 'SkoponSelect'
|
|
21
21
|
| 'SkoponResumeSelect'
|
|
22
22
|
| 'SkoponCaseSelect'
|
|
23
|
+
| 'SkoponModulePanel'
|
|
24
|
+
|
|
25
|
+
/** 表单分组模块。运行时以 Tab 展示,要求与示例统一放在模块底部。 */
|
|
26
|
+
export interface FormModule {
|
|
27
|
+
id: string
|
|
28
|
+
label: string
|
|
29
|
+
description?: string
|
|
30
|
+
reviewCriteria?: string
|
|
31
|
+
exampleUrls?: string[]
|
|
32
|
+
}
|
|
23
33
|
|
|
24
34
|
export interface A2uiComponentNode {
|
|
25
35
|
id: string
|
|
@@ -88,6 +98,14 @@ export interface FormBlockOption {
|
|
|
88
98
|
label: string
|
|
89
99
|
}
|
|
90
100
|
|
|
101
|
+
export interface FormAssetOption {
|
|
102
|
+
assetId: string
|
|
103
|
+
url: string
|
|
104
|
+
previewUrl?: string
|
|
105
|
+
label: string
|
|
106
|
+
assetType?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
export type FormConditionOperator = 'eq' | 'ne' | 'empty' | 'notEmpty'
|
|
92
110
|
|
|
93
111
|
export interface FormBlockCondition {
|
|
@@ -165,6 +183,21 @@ export interface FormBlock {
|
|
|
165
183
|
fileExampleUrls?: string[]
|
|
166
184
|
/** 制作模板的逐字段审核标准;由工作流读取,不能使用提交值覆盖。 */
|
|
167
185
|
fileReviewCriteria?: string
|
|
186
|
+
/** 是否需要由工作流审核上传内容;素材库已审核素材可以关闭。 */
|
|
187
|
+
fileReviewRequired?: boolean
|
|
188
|
+
/** 物料采集来源模式,由 skopon-admin 环节配置。 */
|
|
189
|
+
fileSourceMode?: 'upload_required' | 'upload_or_asset' | 'asset_only'
|
|
190
|
+
/** 通用收集流从 Footage 为当前素材位查询的候选;提交选中的素材 ID。 */
|
|
191
|
+
fileAssetOptions?: FormAssetOption[]
|
|
192
|
+
/** 对应 Footage 同步素材位的限定 key。 */
|
|
193
|
+
assetSlotKey?: string
|
|
194
|
+
assetType?: 'image' | 'video' | 'audio' | 'text'
|
|
195
|
+
/** 运行时素材审核状态;用于返工表单回显,不作为用户提交值。 */
|
|
196
|
+
fileReviewStatus?: 'passed' | 'failed'
|
|
197
|
+
/** 运行时素材审核说明;failed 时通常为不合格原因。 */
|
|
198
|
+
fileReviewMessage?: string
|
|
199
|
+
/** 审核未通过的历史文件,仅作错误态回显,不计入有效提交。 */
|
|
200
|
+
fileRejectedUrls?: string[]
|
|
168
201
|
defaultValue?: string | string[] | boolean
|
|
169
202
|
/** 派生 JSON Schema 时写入 property.$comment */
|
|
170
203
|
jsonSchemaDescription?: string
|
|
@@ -195,11 +228,14 @@ export interface FormBlock {
|
|
|
195
228
|
/** 案例单选:列表筛选条件(持久化进 block json) */
|
|
196
229
|
caseFilter?: FormCaseFilter
|
|
197
230
|
condition?: FormBlockCondition
|
|
231
|
+
/** 所属模块;命中 FormSchema.modules.id 时在对应 Tab 中展示。 */
|
|
232
|
+
moduleId?: string
|
|
198
233
|
}
|
|
199
234
|
|
|
200
235
|
export interface FormSchema {
|
|
201
236
|
title?: string
|
|
202
237
|
description?: string
|
|
238
|
+
modules?: FormModule[]
|
|
203
239
|
blocks: FormBlock[]
|
|
204
240
|
jsonSchema: FormJsonSchema
|
|
205
241
|
}
|
|
@@ -240,6 +276,7 @@ export function supportsDefaultValue(type: FormBlockType): boolean {
|
|
|
240
276
|
export interface FormDefinitionPayload {
|
|
241
277
|
title?: string
|
|
242
278
|
description?: string
|
|
279
|
+
modules?: FormModule[]
|
|
243
280
|
blocks?: FormBlock[]
|
|
244
281
|
a2ui?: A2uiSurfaceDoc
|
|
245
282
|
json_schema?: FormJsonSchema
|