@skopon-cool/form-sdk 0.1.4 → 0.1.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.map +1 -1
- package/dist/adapter/formSchema.d.ts.map +1 -1
- package/dist/blocks/{case_singleselect → case_multiselect}/adapter.d.ts +2 -2
- package/dist/blocks/case_multiselect/adapter.d.ts.map +1 -0
- package/dist/blocks/case_multiselect/index.d.ts +3 -0
- package/dist/blocks/case_multiselect/index.d.ts.map +1 -0
- package/dist/catalog/resumeSearchContext.d.ts +9 -0
- package/dist/catalog/resumeSearchContext.d.ts.map +1 -1
- package/dist/catalog/skoponCaseSelect.d.ts.map +1 -1
- package/dist/catalog/skoponResumeSelect.d.ts.map +1 -1
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/components/SkoponA2uiStreamRenderer.d.ts.map +1 -1
- package/dist/form-sdk.css +1 -1
- package/dist/index.js +932 -892
- package/dist/submit/buildCurlStatement.d.ts +1 -1
- package/dist/submit/buildCurlStatement.d.ts.map +1 -1
- package/dist/submit/intersectPayloadBlocksWithForm.d.ts.map +1 -1
- package/dist/types/index.d.ts +11 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapter/a2uiAdapter.test.ts +37 -14
- package/src/adapter/a2uiAdapter.ts +1 -4
- package/src/adapter/formSchema.ts +49 -29
- package/src/blocks/case_multiselect/adapter.ts +90 -0
- package/src/blocks/case_multiselect/index.ts +14 -0
- package/src/blocks/registry.ts +2 -2
- package/src/catalog/resumeSearchContext.tsx +10 -0
- package/src/catalog/skoponCaseSelect.tsx +38 -13
- package/src/catalog/skoponResumeSelect.tsx +76 -10
- package/src/components/AskUserFormCard.tsx +3 -1
- package/src/components/SkoponA2uiStreamRenderer.tsx +11 -4
- package/src/styles/a2ui-preview.css +4 -4
- package/src/styles/index.css +148 -17
- package/src/submit/buildCurlStatement.ts +2 -23
- package/src/submit/intersectPayloadBlocksWithForm.ts +14 -2
- package/src/submit/submit.test.ts +34 -3
- package/src/types/index.ts +10 -3
- package/dist/blocks/case_singleselect/adapter.d.ts.map +0 -1
- package/dist/blocks/case_singleselect/index.d.ts +0 -3
- package/dist/blocks/case_singleselect/index.d.ts.map +0 -1
- package/src/blocks/case_singleselect/adapter.ts +0 -74
- package/src/blocks/case_singleselect/index.ts +0 -14
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import { Avatar, Button, Spin, Typography } from 'antd'
|
|
3
|
-
import { ReloadOutlined, UserOutlined } from '@ant-design/icons'
|
|
2
|
+
import { Avatar, Button, Spin, Tag, Typography } from 'antd'
|
|
3
|
+
import { FileImageOutlined, ReloadOutlined, UserOutlined } from '@ant-design/icons'
|
|
4
4
|
import type { ComponentContext } from '@a2ui/web_core/v0_9'
|
|
5
5
|
import { createBinderlessComponentImplementation } from '@a2ui/react/v0_9'
|
|
6
6
|
import { z } from 'zod'
|
|
@@ -9,6 +9,7 @@ import { useA2uiPreviewMode } from './a2uiPreviewContext'
|
|
|
9
9
|
import { asStringArray, useSkoponBoundField } from './useSkoponBoundField'
|
|
10
10
|
import {
|
|
11
11
|
type ResumeSearchItem,
|
|
12
|
+
type ResumeSearchWorkItem,
|
|
12
13
|
useResumeSearch,
|
|
13
14
|
} from './resumeSearchContext'
|
|
14
15
|
|
|
@@ -54,6 +55,60 @@ const SkoponResumeSelectApi = {
|
|
|
54
55
|
.passthrough(),
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
const MAX_TAG_COUNT = 3
|
|
59
|
+
const MAX_WORK_PREVIEW = 2
|
|
60
|
+
|
|
61
|
+
function renderTagRow(label: string, tags: string[] | undefined) {
|
|
62
|
+
const items = (tags ?? []).filter(Boolean)
|
|
63
|
+
const visible = items.slice(0, MAX_TAG_COUNT)
|
|
64
|
+
const overflow = items.length - visible.length
|
|
65
|
+
return (
|
|
66
|
+
<div className="skopon-resume-select-card-tags">
|
|
67
|
+
<span className="skopon-resume-select-card-tags-label">{label}</span>
|
|
68
|
+
{items.length > 0 ? (
|
|
69
|
+
<div className="skopon-resume-select-card-tags-list">
|
|
70
|
+
{visible.map((tag) => (
|
|
71
|
+
<Tag key={tag} className="skopon-resume-select-card-tag">
|
|
72
|
+
{tag}
|
|
73
|
+
</Tag>
|
|
74
|
+
))}
|
|
75
|
+
{overflow > 0 ? (
|
|
76
|
+
<Tag className="skopon-resume-select-card-tag">+{overflow}</Tag>
|
|
77
|
+
) : null}
|
|
78
|
+
</div>
|
|
79
|
+
) : (
|
|
80
|
+
<span className="skopon-resume-select-card-tags-empty">暂无</span>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function renderSatisfactionRow(satisfaction: number | undefined) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="skopon-resume-select-card-field">
|
|
89
|
+
<span className="skopon-resume-select-card-tags-label">满意度</span>
|
|
90
|
+
<span className="skopon-resume-select-card-field-value">
|
|
91
|
+
{typeof satisfaction === 'number' ? `${satisfaction}%` : '暂无'}
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function ResumeWorkPreview({ work }: { work: ResumeSearchWorkItem }) {
|
|
98
|
+
return (
|
|
99
|
+
<div className="skopon-resume-select-card-work">
|
|
100
|
+
<div className="skopon-resume-select-card-work-cover">
|
|
101
|
+
{work.coverUrl ? (
|
|
102
|
+
<img src={work.coverUrl} alt="" className="skopon-resume-select-card-work-img" />
|
|
103
|
+
) : (
|
|
104
|
+
<FileImageOutlined className="skopon-resume-select-card-work-placeholder" />
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
<div className="skopon-resume-select-card-work-title">{work.title || '未命名作品'}</div>
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
57
112
|
function ResumeCard({
|
|
58
113
|
item,
|
|
59
114
|
selected,
|
|
@@ -65,10 +120,12 @@ function ResumeCard({
|
|
|
65
120
|
disabled: boolean
|
|
66
121
|
onToggle: () => void
|
|
67
122
|
}) {
|
|
123
|
+
const works = (item.works ?? []).slice(0, MAX_WORK_PREVIEW)
|
|
124
|
+
|
|
68
125
|
return (
|
|
69
126
|
<button
|
|
70
127
|
type="button"
|
|
71
|
-
className={`skopon-resume-select-card${selected ? ' skopon-resume-select-card--selected' : ''}`}
|
|
128
|
+
className={`skopon-resume-select-card skopon-resume-select-card--profile${selected ? ' skopon-resume-select-card--selected' : ''}`}
|
|
72
129
|
disabled={disabled}
|
|
73
130
|
onClick={onToggle}
|
|
74
131
|
>
|
|
@@ -78,14 +135,23 @@ function ResumeCard({
|
|
|
78
135
|
icon={!item.avatarUrl ? <UserOutlined /> : undefined}
|
|
79
136
|
className="skopon-resume-select-card-avatar"
|
|
80
137
|
/>
|
|
138
|
+
<div className="skopon-resume-select-card-name">{item.name || '未命名简历'}</div>
|
|
81
139
|
<div className="skopon-resume-select-card-body">
|
|
82
|
-
|
|
83
|
-
{item.
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
<div className="skopon-resume-select-card-
|
|
87
|
-
|
|
88
|
-
{
|
|
140
|
+
{renderSatisfactionRow(item.satisfaction)}
|
|
141
|
+
{renderTagRow('性格', item.personality)}
|
|
142
|
+
{renderTagRow('专长', item.specialties)}
|
|
143
|
+
{renderTagRow('擅长领域', item.traits)}
|
|
144
|
+
<div className="skopon-resume-select-card-works">
|
|
145
|
+
<span className="skopon-resume-select-card-works-label">过往作品</span>
|
|
146
|
+
{works.length > 0 ? (
|
|
147
|
+
<div className="skopon-resume-select-card-works-list">
|
|
148
|
+
{works.map((work, index) => (
|
|
149
|
+
<ResumeWorkPreview key={`${work.title}-${index}`} work={work} />
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
) : (
|
|
153
|
+
<div className="skopon-resume-select-card-works-empty">暂无作品</div>
|
|
154
|
+
)}
|
|
89
155
|
</div>
|
|
90
156
|
</div>
|
|
91
157
|
</button>
|
|
@@ -120,10 +120,12 @@ export default function AskUserFormCard({
|
|
|
120
120
|
Boolean(payloadDef && payloadHasInputBlocks(payloadDef) && matchedBlocks.length === 0)
|
|
121
121
|
|
|
122
122
|
const renderBlocks = useMemo(() => {
|
|
123
|
+
if (usePayloadFallback && payloadDef) {
|
|
124
|
+
return getPayloadRenderableBlocks(payloadDef)
|
|
125
|
+
}
|
|
123
126
|
if (intersection && intersection.renderBlocks.length > 0) {
|
|
124
127
|
return intersection.renderBlocks
|
|
125
128
|
}
|
|
126
|
-
if (usePayloadFallback && payloadDef) return getPayloadRenderableBlocks(payloadDef)
|
|
127
129
|
return []
|
|
128
130
|
}, [intersection, usePayloadFallback, payloadDef])
|
|
129
131
|
|
|
@@ -37,7 +37,10 @@ export default function SkoponA2uiStreamRenderer({
|
|
|
37
37
|
injectBasicCatalogStyles()
|
|
38
38
|
}, [])
|
|
39
39
|
|
|
40
|
-
const processorRef = useRef<MessageProcessor<ReactComponentImplementation
|
|
40
|
+
const processorRef = useRef<MessageProcessor<ReactComponentImplementation> | null>(null)
|
|
41
|
+
if (processorRef.current === null) {
|
|
42
|
+
processorRef.current = createProcessor()
|
|
43
|
+
}
|
|
41
44
|
const processedCountRef = useRef(0)
|
|
42
45
|
const lastProcessedMessagesRef = useRef('')
|
|
43
46
|
const mountedRef = useRef(true)
|
|
@@ -68,9 +71,13 @@ export default function SkoponA2uiStreamRenderer({
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
const [processor, setProcessor] = useState(
|
|
74
|
+
const [processor, setProcessor] = useState(
|
|
75
|
+
() => processorRef.current as MessageProcessor<ReactComponentImplementation>,
|
|
76
|
+
)
|
|
72
77
|
const [surfaces, setSurfaces] = useState(() =>
|
|
73
|
-
Array.from(
|
|
78
|
+
Array.from(
|
|
79
|
+
(processorRef.current as MessageProcessor<ReactComponentImplementation>).model.surfacesMap.values(),
|
|
80
|
+
),
|
|
74
81
|
)
|
|
75
82
|
|
|
76
83
|
useEffect(() => {
|
|
@@ -137,7 +144,7 @@ export default function SkoponA2uiStreamRenderer({
|
|
|
137
144
|
}
|
|
138
145
|
}
|
|
139
146
|
|
|
140
|
-
const activeProcessor = processorRef.current
|
|
147
|
+
const activeProcessor = processorRef.current as MessageProcessor<ReactComponentImplementation>
|
|
141
148
|
const processedCount = processedCountRef.current
|
|
142
149
|
|
|
143
150
|
if (
|
|
@@ -254,8 +254,8 @@
|
|
|
254
254
|
box-shadow: var(--shadow-focus);
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
/* 原生 A2UI 按钮/chip;勿作用于 Ant Design(Switch、Select、Picker
|
|
258
|
-
.a2ui-surface.a2ui-container button:not([class*='ant-']) {
|
|
257
|
+
/* 原生 A2UI 按钮/chip;勿作用于 Ant Design(Switch、Select、Picker 等)及 Skopon 简历/案例卡片 */
|
|
258
|
+
.a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card) {
|
|
259
259
|
min-height: var(--button-height-md);
|
|
260
260
|
padding: 0 var(--button-padding-x-md);
|
|
261
261
|
font-family: var(--font-sans);
|
|
@@ -273,12 +273,12 @@
|
|
|
273
273
|
color var(--duration-fast) var(--ease-default);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
.a2ui-surface.a2ui-container button:not([class*='ant-']):hover:not(:disabled) {
|
|
276
|
+
.a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card):hover:not(:disabled) {
|
|
277
277
|
border-color: var(--color-border-strong);
|
|
278
278
|
background: var(--color-bg-subtle);
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
.a2ui-surface.a2ui-container button:not([class*='ant-']):disabled {
|
|
281
|
+
.a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card):disabled {
|
|
282
282
|
cursor: not-allowed;
|
|
283
283
|
opacity: 0.45;
|
|
284
284
|
}
|
package/src/styles/index.css
CHANGED
|
@@ -196,21 +196,58 @@
|
|
|
196
196
|
|
|
197
197
|
.skopon-resume-select-list {
|
|
198
198
|
display: flex;
|
|
199
|
-
flex-direction:
|
|
200
|
-
|
|
199
|
+
flex-direction: row;
|
|
200
|
+
flex-wrap: nowrap;
|
|
201
|
+
gap: var(--space-3, 12px);
|
|
201
202
|
margin-top: var(--space-2, 8px);
|
|
203
|
+
overflow-x: auto;
|
|
204
|
+
padding-bottom: var(--space-1, 4px);
|
|
205
|
+
-webkit-overflow-scrolling: touch;
|
|
202
206
|
}
|
|
203
207
|
.skopon-resume-select-card {
|
|
204
208
|
display: flex;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
width: 100%;
|
|
209
|
+
flex: 0 0 auto;
|
|
210
|
+
box-sizing: border-box;
|
|
208
211
|
padding: 12px;
|
|
209
212
|
border: 1px solid var(--color-border, #d9d9d9);
|
|
210
|
-
border-radius:
|
|
213
|
+
border-radius: 4px;
|
|
211
214
|
background: var(--color-surface, #fff);
|
|
212
215
|
text-align: left;
|
|
213
216
|
cursor: pointer;
|
|
217
|
+
min-height: unset;
|
|
218
|
+
font-weight: inherit;
|
|
219
|
+
line-height: inherit;
|
|
220
|
+
appearance: none;
|
|
221
|
+
-webkit-appearance: none;
|
|
222
|
+
}
|
|
223
|
+
.a2ui-surface.a2ui-container button.skopon-resume-select-card {
|
|
224
|
+
border-radius: 4px;
|
|
225
|
+
min-height: unset;
|
|
226
|
+
padding: 12px;
|
|
227
|
+
font-weight: inherit;
|
|
228
|
+
}
|
|
229
|
+
.skopon-resume-select-card--profile {
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
align-items: center;
|
|
232
|
+
width: 200px;
|
|
233
|
+
min-width: 200px;
|
|
234
|
+
max-width: 200px;
|
|
235
|
+
height: 320px;
|
|
236
|
+
min-height: 320px;
|
|
237
|
+
max-height: 320px;
|
|
238
|
+
padding: 10px;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
}
|
|
241
|
+
.a2ui-surface.a2ui-container button.skopon-resume-select-card--profile {
|
|
242
|
+
padding: 10px;
|
|
243
|
+
min-height: 320px;
|
|
244
|
+
}
|
|
245
|
+
.skopon-resume-select-card:not(.skopon-resume-select-card--profile) {
|
|
246
|
+
flex-direction: row;
|
|
247
|
+
align-items: flex-start;
|
|
248
|
+
gap: 12px;
|
|
249
|
+
width: 220px;
|
|
250
|
+
min-width: 220px;
|
|
214
251
|
}
|
|
215
252
|
.skopon-resume-select-card:hover:not(:disabled) {
|
|
216
253
|
border-color: var(--color-primary, #1677ff);
|
|
@@ -223,27 +260,121 @@
|
|
|
223
260
|
cursor: not-allowed;
|
|
224
261
|
opacity: 0.72;
|
|
225
262
|
}
|
|
226
|
-
.skopon-resume-select-card-
|
|
227
|
-
flex:
|
|
228
|
-
|
|
263
|
+
.skopon-resume-select-card-avatar {
|
|
264
|
+
flex-shrink: 0;
|
|
265
|
+
}
|
|
266
|
+
.skopon-resume-select-card--profile .skopon-resume-select-card-avatar {
|
|
267
|
+
margin-bottom: 6px;
|
|
229
268
|
}
|
|
230
269
|
.skopon-resume-select-card-name {
|
|
231
270
|
font-weight: 600;
|
|
271
|
+
font-size: 14px;
|
|
272
|
+
line-height: 1.3;
|
|
273
|
+
margin-bottom: 6px;
|
|
274
|
+
overflow: hidden;
|
|
275
|
+
text-overflow: ellipsis;
|
|
276
|
+
white-space: nowrap;
|
|
277
|
+
color: var(--color-text, rgba(0, 0, 0, 0.88));
|
|
278
|
+
}
|
|
279
|
+
.skopon-resume-select-card--profile > .skopon-resume-select-card-name {
|
|
280
|
+
flex-shrink: 0;
|
|
281
|
+
width: 100%;
|
|
282
|
+
text-align: center;
|
|
283
|
+
}
|
|
284
|
+
.skopon-resume-select-card-body {
|
|
285
|
+
width: 100%;
|
|
286
|
+
min-width: 0;
|
|
287
|
+
text-align: left;
|
|
288
|
+
}
|
|
289
|
+
.skopon-resume-select-card--profile .skopon-resume-select-card-body {
|
|
290
|
+
flex: 1;
|
|
291
|
+
min-height: 0;
|
|
292
|
+
display: flex;
|
|
293
|
+
flex-direction: column;
|
|
294
|
+
overflow-x: hidden;
|
|
295
|
+
overflow-y: auto;
|
|
296
|
+
}
|
|
297
|
+
.skopon-resume-select-card-field {
|
|
232
298
|
margin-bottom: 4px;
|
|
299
|
+
flex-shrink: 0;
|
|
300
|
+
}
|
|
301
|
+
.skopon-resume-select-card-field-value {
|
|
302
|
+
font-size: 12px;
|
|
303
|
+
color: var(--color-text, rgba(0, 0, 0, 0.88));
|
|
233
304
|
}
|
|
234
|
-
.skopon-resume-select-card-
|
|
235
|
-
|
|
236
|
-
|
|
305
|
+
.skopon-resume-select-card-tags-empty {
|
|
306
|
+
font-size: 12px;
|
|
307
|
+
color: var(--color-text-secondary, rgba(0, 0, 0, 0.45));
|
|
308
|
+
}
|
|
309
|
+
.skopon-resume-select-card-tags {
|
|
237
310
|
margin-bottom: 4px;
|
|
311
|
+
flex-shrink: 0;
|
|
312
|
+
}
|
|
313
|
+
.skopon-resume-select-card-tags-label,
|
|
314
|
+
.skopon-resume-select-card-works-label {
|
|
315
|
+
display: block;
|
|
316
|
+
margin-bottom: 2px;
|
|
317
|
+
font-size: 11px;
|
|
318
|
+
color: var(--color-text-secondary, rgba(0, 0, 0, 0.45));
|
|
319
|
+
}
|
|
320
|
+
.skopon-resume-select-card-tags-list {
|
|
321
|
+
display: flex;
|
|
322
|
+
flex-wrap: wrap;
|
|
323
|
+
gap: 4px;
|
|
324
|
+
}
|
|
325
|
+
.skopon-resume-select-card-tag {
|
|
326
|
+
margin: 0;
|
|
327
|
+
font-size: 11px;
|
|
328
|
+
line-height: 18px;
|
|
329
|
+
}
|
|
330
|
+
.skopon-resume-select-card-works {
|
|
331
|
+
margin-top: 4px;
|
|
332
|
+
flex-shrink: 0;
|
|
333
|
+
}
|
|
334
|
+
.skopon-resume-select-card-works-list {
|
|
335
|
+
display: flex;
|
|
336
|
+
flex-direction: column;
|
|
337
|
+
gap: 4px;
|
|
338
|
+
}
|
|
339
|
+
.skopon-resume-select-card-works-empty {
|
|
340
|
+
font-size: 12px;
|
|
341
|
+
color: var(--color-text-secondary, rgba(0, 0, 0, 0.45));
|
|
342
|
+
}
|
|
343
|
+
.skopon-resume-select-card-work {
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
gap: 6px;
|
|
347
|
+
min-width: 0;
|
|
348
|
+
}
|
|
349
|
+
.skopon-resume-select-card-work-cover {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
flex-shrink: 0;
|
|
354
|
+
width: 32px;
|
|
355
|
+
height: 32px;
|
|
356
|
+
border-radius: 2px;
|
|
357
|
+
border: 1px solid var(--color-border, #d9d9d9);
|
|
358
|
+
background: var(--color-bg-subtle, rgba(0, 0, 0, 0.02));
|
|
238
359
|
overflow: hidden;
|
|
239
|
-
text-overflow: ellipsis;
|
|
240
|
-
display: -webkit-box;
|
|
241
|
-
-webkit-line-clamp: 2;
|
|
242
|
-
-webkit-box-orient: vertical;
|
|
243
360
|
}
|
|
244
|
-
.skopon-resume-select-card-
|
|
361
|
+
.skopon-resume-select-card-work-img {
|
|
362
|
+
width: 100%;
|
|
363
|
+
height: 100%;
|
|
364
|
+
object-fit: cover;
|
|
365
|
+
}
|
|
366
|
+
.skopon-resume-select-card-work-placeholder {
|
|
367
|
+
font-size: 16px;
|
|
245
368
|
color: var(--color-text-secondary, rgba(0, 0, 0, 0.45));
|
|
369
|
+
}
|
|
370
|
+
.skopon-resume-select-card-work-title {
|
|
371
|
+
flex: 1;
|
|
372
|
+
min-width: 0;
|
|
246
373
|
font-size: 12px;
|
|
374
|
+
color: var(--color-text, rgba(0, 0, 0, 0.88));
|
|
375
|
+
overflow: hidden;
|
|
376
|
+
text-overflow: ellipsis;
|
|
377
|
+
white-space: nowrap;
|
|
247
378
|
}
|
|
248
379
|
.skopon-resume-select-actions {
|
|
249
380
|
margin-top: var(--space-2, 8px);
|
|
@@ -12,33 +12,12 @@ export function buildCurlStatement(payload: unknown, callbackUrl?: string | null
|
|
|
12
12
|
].join('\n')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
return ` ${JSON.stringify(key)}: ${JSON.stringify(value)}${trailingComma ? ',' : ''}`
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** 构建含行注释的 JSON body:extra 段标注「额外字段(未在卡片展示)」 */
|
|
15
|
+
/** 构建合法 JSON body(card + extra 合并,便于 curl 直接执行) */
|
|
20
16
|
export function buildAskUserCurlBodyJson(
|
|
21
17
|
cardValues: Record<string, unknown>,
|
|
22
18
|
extraValues: Record<string, unknown>,
|
|
23
19
|
): string {
|
|
24
|
-
|
|
25
|
-
const cardEntries = Object.entries(cardValues)
|
|
26
|
-
const extraEntries = Object.entries(extraValues)
|
|
27
|
-
|
|
28
|
-
cardEntries.forEach(([key, value], index) => {
|
|
29
|
-
const hasMore = index < cardEntries.length - 1 || extraEntries.length > 0
|
|
30
|
-
lines.push(formatJsonBodyLine(key, value, hasMore))
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
if (extraEntries.length > 0) {
|
|
34
|
-
lines.push(' // 额外字段(未在卡片展示)')
|
|
35
|
-
extraEntries.forEach(([key, value], index) => {
|
|
36
|
-
lines.push(formatJsonBodyLine(key, value, index < extraEntries.length - 1))
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
lines.push('}')
|
|
41
|
-
return lines.join('\n')
|
|
20
|
+
return JSON.stringify({ ...cardValues, ...extraValues }, null, 2)
|
|
42
21
|
}
|
|
43
22
|
|
|
44
23
|
export interface BuildAskUserCurlStatementOptions {
|
|
@@ -127,7 +127,12 @@ function blockValueFromDefault(block: FormBlock): unknown {
|
|
|
127
127
|
const { type, defaultValue } = block
|
|
128
128
|
if (defaultValue !== undefined) {
|
|
129
129
|
if (type === 'toggle') return defaultValue === true || defaultValue === 'true'
|
|
130
|
-
if (
|
|
130
|
+
if (
|
|
131
|
+
type === 'multiselect' ||
|
|
132
|
+
type === 'checkbox' ||
|
|
133
|
+
type === 'resume_multiselect' ||
|
|
134
|
+
type === 'case_multiselect'
|
|
135
|
+
) {
|
|
131
136
|
if (Array.isArray(defaultValue)) return defaultValue.map(String)
|
|
132
137
|
if (typeof defaultValue === 'string' && defaultValue) return [defaultValue]
|
|
133
138
|
return []
|
|
@@ -135,7 +140,14 @@ function blockValueFromDefault(block: FormBlock): unknown {
|
|
|
135
140
|
return defaultValue
|
|
136
141
|
}
|
|
137
142
|
if (type === 'toggle') return false
|
|
138
|
-
if (
|
|
143
|
+
if (
|
|
144
|
+
type === 'multiselect' ||
|
|
145
|
+
type === 'checkbox' ||
|
|
146
|
+
type === 'resume_multiselect' ||
|
|
147
|
+
type === 'case_multiselect'
|
|
148
|
+
) {
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
139
151
|
if (type === 'number') return null
|
|
140
152
|
return ''
|
|
141
153
|
}
|
|
@@ -31,21 +31,26 @@ describe('buildCurlStatement', () => {
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
describe('buildAskUserCurlStatement', () => {
|
|
34
|
-
it('
|
|
34
|
+
it('merges card and extra values into valid JSON', () => {
|
|
35
35
|
const body = buildAskUserCurlBodyJson(
|
|
36
36
|
{ video_title: 'hello' },
|
|
37
37
|
{ agent_hint: '请尽快确认' },
|
|
38
38
|
)
|
|
39
39
|
expect(body).toContain('"video_title": "hello"')
|
|
40
|
-
expect(body).toContain('// 额外字段(未在卡片展示)')
|
|
41
40
|
expect(body).toContain('"agent_hint": "请尽快确认"')
|
|
41
|
+
expect(body).not.toContain('// 额外字段')
|
|
42
|
+
expect(JSON.parse(body)).toEqual({
|
|
43
|
+
video_title: 'hello',
|
|
44
|
+
agent_hint: '请尽快确认',
|
|
45
|
+
})
|
|
42
46
|
|
|
43
47
|
const curl = buildAskUserCurlStatement({
|
|
44
48
|
cardValues: { video_title: 'hello' },
|
|
45
49
|
extraValues: { agent_hint: '请尽快确认' },
|
|
46
50
|
callbackUrl: 'https://example.com/hook',
|
|
47
51
|
})
|
|
48
|
-
expect(curl).toContain('
|
|
52
|
+
expect(curl).toContain('"agent_hint": "请尽快确认"')
|
|
53
|
+
expect(curl).not.toContain('// 额外字段')
|
|
49
54
|
})
|
|
50
55
|
})
|
|
51
56
|
|
|
@@ -96,6 +101,18 @@ describe('intersectPayloadBlocksWithForm', () => {
|
|
|
96
101
|
expect(extractExtraBlockValues(result.extraBlocks)).toEqual({ agent_hint: 'hint' })
|
|
97
102
|
})
|
|
98
103
|
|
|
104
|
+
it('defaults resume_multiselect extra blocks to empty array', () => {
|
|
105
|
+
const extraBlocks = [
|
|
106
|
+
{
|
|
107
|
+
id: '1',
|
|
108
|
+
type: 'resume_multiselect' as const,
|
|
109
|
+
name: 'resumes',
|
|
110
|
+
label: 'Resumes',
|
|
111
|
+
},
|
|
112
|
+
]
|
|
113
|
+
expect(extractExtraBlockValues(extraBlocks)).toEqual({ resumes: [] })
|
|
114
|
+
})
|
|
115
|
+
|
|
99
116
|
it('keeps form block type and schema when payload type differs', () => {
|
|
100
117
|
const selectForm: FormSchema = {
|
|
101
118
|
title: 'Form',
|
|
@@ -195,6 +212,20 @@ describe('payload fallback helpers', () => {
|
|
|
195
212
|
expect(payloadHasInputBlocks(surveyPayload)).toBe(true)
|
|
196
213
|
expect(getPayloadRenderableBlocks(surveyPayload).length).toBeGreaterThan(0)
|
|
197
214
|
})
|
|
215
|
+
|
|
216
|
+
it('prefers payload fallback blocks when intersection only has layout blocks', () => {
|
|
217
|
+
const result = intersectPayloadBlocksWithForm(surveyPayload, undefined)
|
|
218
|
+
const usePayloadFallback =
|
|
219
|
+
payloadHasInputBlocks(surveyPayload) && result.matchedBlocks.length === 0
|
|
220
|
+
expect(usePayloadFallback).toBe(true)
|
|
221
|
+
expect(result.renderBlocks.map((b) => b.type)).toEqual(['heading'])
|
|
222
|
+
|
|
223
|
+
const renderBlocks = usePayloadFallback
|
|
224
|
+
? getPayloadRenderableBlocks(surveyPayload)
|
|
225
|
+
: result.renderBlocks
|
|
226
|
+
expect(renderBlocks.map((b) => b.id)).toEqual(['title', 'q1'])
|
|
227
|
+
expect(getPayloadInputFieldNames(surveyPayload)).toEqual(['occupation'])
|
|
228
|
+
})
|
|
198
229
|
})
|
|
199
230
|
|
|
200
231
|
describe('submitFormJson', () => {
|
package/src/types/index.ts
CHANGED
|
@@ -56,6 +56,8 @@ export type FormBlockType =
|
|
|
56
56
|
| 'time'
|
|
57
57
|
| 'file'
|
|
58
58
|
| 'resume_multiselect'
|
|
59
|
+
| 'case_multiselect'
|
|
60
|
+
/** @deprecated 加载时自动迁移为 case_multiselect */
|
|
59
61
|
| 'case_singleselect'
|
|
60
62
|
| 'image'
|
|
61
63
|
| 'video'
|
|
@@ -92,13 +94,18 @@ export interface FormResumeFilter {
|
|
|
92
94
|
pageSize?: number
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
/**
|
|
97
|
+
/** 案例多选组件:渲染时用于 /univ/case/list 的筛选条件 */
|
|
96
98
|
export interface FormCaseFilter {
|
|
97
99
|
/** Agent 类型(编辑器辅助,运行时不用) */
|
|
98
100
|
agentKind?: string
|
|
99
|
-
/** Agent unique_id
|
|
101
|
+
/** Agent unique_id(运行时用于限定所属 Agent) */
|
|
100
102
|
agentUniqueId?: string
|
|
101
|
-
|
|
103
|
+
/** 流类型 unique_id */
|
|
104
|
+
flowType?: string
|
|
105
|
+
/** 流类别 unique_id */
|
|
106
|
+
category?: string
|
|
107
|
+
/** 流程编号(flow_unique_id)多选 */
|
|
108
|
+
flowUniqueIds?: string[]
|
|
102
109
|
pageSize?: number
|
|
103
110
|
}
|
|
104
111
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/blocks/case_singleselect/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAE3E,iBAAS,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAErD;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,mBAAmB,GACvB,sBAAsB,CAiBxB;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,SAAS,EAC9C,OAAO,EAAE;IACP,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAA;IACjD,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;CAC1C,GACA,SAAS,CAmCX;AAED,OAAO,EAAE,SAAS,EAAE,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/case_singleselect/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAOlD,eAAO,MAAM,uBAAuB,EAAE,kBAMrC,CAAA"}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { A2uiComponentNode, FormBlock } from '../../types/index'
|
|
2
|
-
import type { BlockAdapterContext, BlockToComponentResult } from '../types'
|
|
3
|
-
|
|
4
|
-
function asLiteral(value: unknown): string | undefined {
|
|
5
|
-
return typeof value === 'string' ? value : undefined
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function caseSingleselectToComponent(
|
|
9
|
-
block: FormBlock,
|
|
10
|
-
ctx: BlockAdapterContext,
|
|
11
|
-
): BlockToComponentResult {
|
|
12
|
-
const { id, label, path, withData } = ctx
|
|
13
|
-
return withData({
|
|
14
|
-
id,
|
|
15
|
-
component: 'SkoponCaseSelect',
|
|
16
|
-
label,
|
|
17
|
-
placeholder: block.placeholder ?? '',
|
|
18
|
-
help: block.help ?? '',
|
|
19
|
-
enableRefresh: block.caseEnableRefresh !== false,
|
|
20
|
-
caseFilter: {
|
|
21
|
-
agentKind: block.caseFilter?.agentKind,
|
|
22
|
-
agentUniqueId: block.caseFilter?.agentUniqueId,
|
|
23
|
-
flowId: block.caseFilter?.flowId,
|
|
24
|
-
pageSize: block.caseFilter?.pageSize ?? 20,
|
|
25
|
-
},
|
|
26
|
-
value: { path },
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function caseSingleselectFromComponent(
|
|
31
|
-
node: A2uiComponentNode,
|
|
32
|
-
base: (type: 'case_singleselect') => FormBlock,
|
|
33
|
-
helpers: {
|
|
34
|
-
asLiteral: (value: unknown) => string | undefined
|
|
35
|
-
nameFromValue: (value: unknown) => string
|
|
36
|
-
},
|
|
37
|
-
): FormBlock {
|
|
38
|
-
const name = helpers.nameFromValue(node.value)
|
|
39
|
-
const block: FormBlock = {
|
|
40
|
-
...base('case_singleselect'),
|
|
41
|
-
name,
|
|
42
|
-
caseEnableRefresh: node.enableRefresh !== false,
|
|
43
|
-
}
|
|
44
|
-
const placeholder = helpers.asLiteral(node.placeholder)
|
|
45
|
-
if (placeholder) block.placeholder = placeholder
|
|
46
|
-
const help = helpers.asLiteral(node.help)
|
|
47
|
-
if (help) block.help = help
|
|
48
|
-
if (node.caseFilter && typeof node.caseFilter === 'object') {
|
|
49
|
-
const cf = node.caseFilter as Record<string, unknown>
|
|
50
|
-
const flowIdRaw = typeof cf.flowId === 'number' ? cf.flowId : Number(cf.flowId)
|
|
51
|
-
const pageSizeRaw = typeof cf.pageSize === 'number' ? cf.pageSize : Number(cf.pageSize)
|
|
52
|
-
const agentKind =
|
|
53
|
-
typeof cf.agentKind === 'string' && cf.agentKind.trim()
|
|
54
|
-
? cf.agentKind.trim()
|
|
55
|
-
: undefined
|
|
56
|
-
const agentUniqueId =
|
|
57
|
-
typeof cf.agentUniqueId === 'string' && cf.agentUniqueId.trim()
|
|
58
|
-
? cf.agentUniqueId.trim()
|
|
59
|
-
: undefined
|
|
60
|
-
block.caseFilter = {
|
|
61
|
-
agentKind,
|
|
62
|
-
agentUniqueId,
|
|
63
|
-
flowId:
|
|
64
|
-
Number.isFinite(flowIdRaw) && flowIdRaw > 0 ? Math.floor(flowIdRaw) : undefined,
|
|
65
|
-
pageSize:
|
|
66
|
-
Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
|
|
67
|
-
? Math.floor(pageSizeRaw)
|
|
68
|
-
: 20,
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return block
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export { asLiteral }
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { BlockAdapterPlugin } from '../types'
|
|
2
|
-
import {
|
|
3
|
-
caseSingleselectFromComponent,
|
|
4
|
-
caseSingleselectToComponent,
|
|
5
|
-
} from './adapter'
|
|
6
|
-
import { SkoponCaseSelectImpl } from './catalog'
|
|
7
|
-
|
|
8
|
-
export const caseSingleselectAdapter: BlockAdapterPlugin = {
|
|
9
|
-
type: 'case_singleselect',
|
|
10
|
-
componentName: 'SkoponCaseSelect',
|
|
11
|
-
toComponent: caseSingleselectToComponent,
|
|
12
|
-
fromComponent: caseSingleselectFromComponent,
|
|
13
|
-
catalogComponents: [SkoponCaseSelectImpl],
|
|
14
|
-
}
|