@jxyszb/jxy-form-designer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/chunks/RuntimePreviewPanel-3704c8f8.js +1991 -0
- package/dist/chunks/compositeField-98c1b192.js +588 -0
- package/dist/chunks/dataSourceUrl-4b17feb8.js +55 -0
- package/dist/designer.js +3023 -0
- package/dist/index.js +45 -0
- package/dist/runtime.js +67 -0
- package/dist/schema.js +266 -0
- package/dist/style.css +1 -0
- package/dist/testing.js +1026 -0
- package/docs/field-suffix-extension.md +289 -0
- package/package.json +100 -0
- package/types/designer.d.ts +12 -0
- package/types/index.d.ts +345 -0
- package/types/runtime.d.ts +22 -0
- package/types/schema.d.ts +31 -0
- package/types/testing.d.ts +3 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# JXY 表单字段右侧扩展位使用说明
|
|
2
|
+
|
|
3
|
+
## 功能定位
|
|
4
|
+
|
|
5
|
+
字段右侧扩展位用于在表单控件右边渲染由业务页面提供的附加内容,例如评价按钮、查看详情、辅助计算或状态提示。
|
|
6
|
+
|
|
7
|
+
JXY Form Designer 只负责:
|
|
8
|
+
|
|
9
|
+
- 在 Schema 中记录字段是否启用扩展位及扩展位宽度。
|
|
10
|
+
- 在 Runtime 中提供统一的 `field-suffix` 作用域插槽。
|
|
11
|
+
- 向插槽回传当前字段节点、字段标识、字段值和运行时状态。
|
|
12
|
+
- 隔离扩展组件与宿主 `el-form-item` 的校验上下文。
|
|
13
|
+
|
|
14
|
+
组件不会内置具体业务按钮,也不会将扩展组件的数据合并进表单 `formData`。
|
|
15
|
+
|
|
16
|
+
## 设计器配置
|
|
17
|
+
|
|
18
|
+
在右侧“表单配置”页签中可以设置整张表单的默认扩展位宽度:
|
|
19
|
+
|
|
20
|
+
| 配置项 | 默认值 | 说明 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| 右侧扩展位宽度(px) | `60` | 当前表单所有字段的默认扩展位宽度 |
|
|
23
|
+
|
|
24
|
+
在设计器画布中选中一个字段,打开右侧“属性面板”,可以配置:
|
|
25
|
+
|
|
26
|
+
| 配置项 | 默认值 | 说明 |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| 启用右侧扩展位 | 关闭 | 控制当前字段是否允许渲染 `field-suffix` 插槽内容 |
|
|
29
|
+
| 右侧扩展位宽度(px) | 空 | 留空时继承表单配置,填写后仅覆盖当前字段,单位为 px |
|
|
30
|
+
|
|
31
|
+
开启后,设计器画布会在字段右侧显示“扩展位”占位。占位仅用于表示布局,不会在画布内运行真实业务组件。
|
|
32
|
+
|
|
33
|
+
实际预览时还必须由调用方提供 `field-suffix` 插槽;只开启配置但没有传入插槽,不会产生空白区域。
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Schema 结构
|
|
38
|
+
|
|
39
|
+
扩展位配置保存在每个 `field` 节点的 `extensions.suffix` 中:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
{
|
|
43
|
+
config: {
|
|
44
|
+
suffixWidth: 60
|
|
45
|
+
},
|
|
46
|
+
nodes: [
|
|
47
|
+
{
|
|
48
|
+
id: 'field_name',
|
|
49
|
+
nodeType: 'field',
|
|
50
|
+
prop: 'name',
|
|
51
|
+
label: '姓名',
|
|
52
|
+
type: 'text',
|
|
53
|
+
extensions: {
|
|
54
|
+
suffix: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
width: ''
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
宽度解析优先级为:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
field.extensions.suffix.width
|
|
68
|
+
> schema.config.suffixWidth
|
|
69
|
+
> 内置兜底 60px
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
表单级 `suffixWidth` 必须是大于等于 `0` 的数字。字段级 `width` 可以为空,非空时必须是大于等于 `0` 的数字。旧 Schema 加载后会补充表单默认值 `60`,字段宽度补为空以继承表单配置。
|
|
73
|
+
|
|
74
|
+
## 插槽参数
|
|
75
|
+
|
|
76
|
+
`JXYFormRuntime` 提供唯一的字段右侧扩展插槽:
|
|
77
|
+
|
|
78
|
+
```vue
|
|
79
|
+
<template #field-suffix="scope">
|
|
80
|
+
<!-- 业务组件 -->
|
|
81
|
+
</template>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`scope` 包含以下参数:
|
|
85
|
+
|
|
86
|
+
| 参数 | 类型 | 说明 |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `field` | `JxyFieldNode` | 当前字段的完整 Schema 节点 |
|
|
89
|
+
| `fieldId` | `string` | 当前字段节点 ID,适合用作扩展数据主键 |
|
|
90
|
+
| `prop` | `string` | 当前字段标识,对应 `formData` 的 key |
|
|
91
|
+
| `label` | `string` | 当前字段标题 |
|
|
92
|
+
| `value` | `unknown` | 当前字段的实时值,只建议读取 |
|
|
93
|
+
| `disabled` | `boolean` | 字段合并静态配置、表单配置和联动规则后的禁用状态 |
|
|
94
|
+
| `required` | `boolean` | 字段合并静态配置和联动规则后的必填状态 |
|
|
95
|
+
|
|
96
|
+
插槽不提供修改字段值的方法。字段值仍应通过 Runtime 的 `v-model`、`setData()` 或正常表单交互修改。
|
|
97
|
+
|
|
98
|
+
## Runtime 接入示例
|
|
99
|
+
|
|
100
|
+
下面示例将扩展数据按 `fieldId` 独立保存:
|
|
101
|
+
|
|
102
|
+
```vue
|
|
103
|
+
<template>
|
|
104
|
+
<JXYFormRuntime
|
|
105
|
+
ref="runtimeRef"
|
|
106
|
+
v-model="formData"
|
|
107
|
+
:schema="publishedSchema"
|
|
108
|
+
>
|
|
109
|
+
<template #field-suffix="scope">
|
|
110
|
+
<FieldReview
|
|
111
|
+
:model-value="reviews[scope.fieldId]"
|
|
112
|
+
:field-id="scope.fieldId"
|
|
113
|
+
:field-prop="scope.prop"
|
|
114
|
+
:field-label="scope.label"
|
|
115
|
+
:field-value="scope.value"
|
|
116
|
+
:disabled="scope.disabled"
|
|
117
|
+
@update:model-value="value => updateReview(scope, value)"
|
|
118
|
+
/>
|
|
119
|
+
</template>
|
|
120
|
+
</JXYFormRuntime>
|
|
121
|
+
</template>
|
|
122
|
+
|
|
123
|
+
<script setup>
|
|
124
|
+
import { reactive, ref } from 'vue';
|
|
125
|
+
import { JXYFormRuntime } from '@jxyszb/jxy-form-designer';
|
|
126
|
+
import FieldReview from './FieldReview.vue';
|
|
127
|
+
|
|
128
|
+
const runtimeRef = ref();
|
|
129
|
+
const formData = ref({});
|
|
130
|
+
const publishedSchema = ref({});
|
|
131
|
+
const reviews = reactive({});
|
|
132
|
+
|
|
133
|
+
function updateReview(scope, value) {
|
|
134
|
+
reviews[scope.fieldId] = {
|
|
135
|
+
fieldId: scope.fieldId,
|
|
136
|
+
prop: scope.prop,
|
|
137
|
+
label: scope.label,
|
|
138
|
+
...value
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function submit() {
|
|
143
|
+
await runtimeRef.value.validate();
|
|
144
|
+
|
|
145
|
+
const payload = {
|
|
146
|
+
formData: runtimeRef.value.getData(),
|
|
147
|
+
reviews: JSON.parse(JSON.stringify(reviews))
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// 调用方根据后端接口要求提交 payload。
|
|
151
|
+
console.log(payload);
|
|
152
|
+
}
|
|
153
|
+
</script>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
推荐使用 `fieldId` 作为扩展数据主键。设计器修改字段 `prop` 时,节点 ID 通常保持不变,可以避免评价数据因字段标识调整而失去关联。
|
|
157
|
+
|
|
158
|
+
## 编写扩展组件
|
|
159
|
+
|
|
160
|
+
扩展组件应将字段信息视为只读上下文,将自己的业务值通过独立的 `v-model` 交给调用方管理。
|
|
161
|
+
|
|
162
|
+
下面是一个简化的 Checkbox 多选加输入框组件:
|
|
163
|
+
|
|
164
|
+
**截图**
|
|
165
|
+
|
|
166
|
+

|
|
167
|
+
|
|
168
|
+

|
|
169
|
+
|
|
170
|
+
**代码**
|
|
171
|
+
|
|
172
|
+
```vue
|
|
173
|
+
<template>
|
|
174
|
+
<el-popover placement="right" :width="320" trigger="click">
|
|
175
|
+
<template #reference>
|
|
176
|
+
<el-button size="small" :disabled="disabled">评价</el-button>
|
|
177
|
+
</template>
|
|
178
|
+
|
|
179
|
+
<div class="field-review">
|
|
180
|
+
<div>{{ fieldLabel }}({{ fieldProp }})</div>
|
|
181
|
+
<div>当前字段值:{{ displayValue }}</div>
|
|
182
|
+
|
|
183
|
+
<el-checkbox-group
|
|
184
|
+
:model-value="review.checkedValues"
|
|
185
|
+
:validate-event="false"
|
|
186
|
+
@update:model-value="updateCheckedValues"
|
|
187
|
+
>
|
|
188
|
+
<el-checkbox value="accurate">准确</el-checkbox>
|
|
189
|
+
<el-checkbox value="complete">完整</el-checkbox>
|
|
190
|
+
<el-checkbox value="standard">规范</el-checkbox>
|
|
191
|
+
</el-checkbox-group>
|
|
192
|
+
|
|
193
|
+
<el-input
|
|
194
|
+
:model-value="review.comment"
|
|
195
|
+
type="textarea"
|
|
196
|
+
:rows="3"
|
|
197
|
+
:validate-event="false"
|
|
198
|
+
placeholder="请输入评价说明"
|
|
199
|
+
@update:model-value="updateComment"
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
202
|
+
</el-popover>
|
|
203
|
+
</template>
|
|
204
|
+
|
|
205
|
+
<script setup>
|
|
206
|
+
import { computed } from 'vue';
|
|
207
|
+
|
|
208
|
+
const props = defineProps({
|
|
209
|
+
modelValue: {
|
|
210
|
+
type: Object,
|
|
211
|
+
default: () => ({ checkedValues: [], comment: '' })
|
|
212
|
+
},
|
|
213
|
+
fieldId: { type: String, required: true },
|
|
214
|
+
fieldProp: { type: String, default: '' },
|
|
215
|
+
fieldLabel: { type: String, default: '' },
|
|
216
|
+
fieldValue: { default: undefined },
|
|
217
|
+
disabled: { type: Boolean, default: false }
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const emit = defineEmits(['update:modelValue']);
|
|
221
|
+
|
|
222
|
+
const review = computed(() => ({
|
|
223
|
+
checkedValues: Array.isArray(props.modelValue?.checkedValues)
|
|
224
|
+
? props.modelValue.checkedValues
|
|
225
|
+
: [],
|
|
226
|
+
comment: typeof props.modelValue?.comment === 'string'
|
|
227
|
+
? props.modelValue.comment
|
|
228
|
+
: ''
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
const displayValue = computed(() => {
|
|
232
|
+
if (typeof props.fieldValue === 'string') return props.fieldValue || '(空)';
|
|
233
|
+
return JSON.stringify(props.fieldValue);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
function updateCheckedValues(checkedValues) {
|
|
237
|
+
emit('update:modelValue', {
|
|
238
|
+
checkedValues: [...checkedValues],
|
|
239
|
+
comment: review.value.comment
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function updateComment(comment) {
|
|
244
|
+
emit('update:modelValue', {
|
|
245
|
+
checkedValues: [...review.value.checkedValues],
|
|
246
|
+
comment
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
</script>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
不要监听 `fieldValue` 后重置扩展组件的业务数据。这样字段值变化、`setData()` 或 `resetFields()` 都只影响表单本身,不会意外覆盖评价内容。
|
|
253
|
+
|
|
254
|
+
Runtime 已在扩展位边界隔离外层 `el-form-item` 上下文。示例中的 `validate-event="false"` 仍建议保留,用于明确表达扩展控件不参与宿主字段校验。
|
|
255
|
+
|
|
256
|
+
## Designer 和预览接入
|
|
257
|
+
|
|
258
|
+
`JXYFormDesigner` 的全屏预览和 `JXYRuntimePreviewPanel` 使用同一个 `field-suffix` 插槽契约:
|
|
259
|
+
|
|
260
|
+
```vue
|
|
261
|
+
<JXYFormDesigner :data-source-provider="previewProvider">
|
|
262
|
+
<template #field-suffix="scope">
|
|
263
|
+
<FieldReview
|
|
264
|
+
:model-value="reviews[scope.fieldId]"
|
|
265
|
+
:field-id="scope.fieldId"
|
|
266
|
+
:field-prop="scope.prop"
|
|
267
|
+
:field-value="scope.value"
|
|
268
|
+
@update:model-value="value => updateReview(scope, value)"
|
|
269
|
+
/>
|
|
270
|
+
</template>
|
|
271
|
+
</JXYFormDesigner>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
设计器画布只显示扩展位占位;全屏预览和 Runtime 才会运行外部组件。调用方没有提供插槽时,即使字段启用了扩展位也不会渲染空容器。
|
|
275
|
+
|
|
276
|
+
## 数据与生命周期边界
|
|
277
|
+
|
|
278
|
+
- 字段隐藏时,字段控件和扩展位都不会渲染。
|
|
279
|
+
- 字段禁用状态通过 `disabled` 参数传递,是否禁用扩展组件由业务组件决定。
|
|
280
|
+
- 字段值变化会更新插槽参数 `value`,但不会自动修改扩展组件自己的值。
|
|
281
|
+
- `validate()`、`resetFields()` 和 `getData()` 只处理 Runtime 表单数据。
|
|
282
|
+
- 扩展数据的校验、保存、恢复和提交由业务页面负责。
|
|
283
|
+
- 表单宽度是默认值,字段宽度非空时优先;扩展组件应适配容器宽度,避免写死超过扩展位的最小宽度。
|
|
284
|
+
|
|
285
|
+
## Playground 测试说明
|
|
286
|
+
|
|
287
|
+
当前 playground 中的 `FieldEvaluation.vue` 只用于验证插槽参数、Checkbox 多选和独立数据管理,不属于正式组件能力,也不会打入 npm 包的 `dist`。
|
|
288
|
+
|
|
289
|
+
需要移除测试功能时,删除 playground 中的评价组件以及 `field-suffix`、`preview-actions` 测试插槽即可,不需要修改 Runtime。
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jxyszb/jxy-form-designer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A schema-driven form designer and runtime for Vue 3, Pinia, and Element Plus.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./types/index.d.ts",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "jxyszb",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"vue3",
|
|
12
|
+
"element-plus",
|
|
13
|
+
"form-designer",
|
|
14
|
+
"form-builder",
|
|
15
|
+
"schema-form"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public",
|
|
19
|
+
"registry": "https://registry.npmjs.org/"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"types",
|
|
24
|
+
"docs/field-suffix-extension.md",
|
|
25
|
+
"README.md",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./types/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./designer": {
|
|
35
|
+
"types": "./types/designer.d.ts",
|
|
36
|
+
"import": "./dist/designer.js"
|
|
37
|
+
},
|
|
38
|
+
"./runtime": {
|
|
39
|
+
"types": "./types/runtime.d.ts",
|
|
40
|
+
"import": "./dist/runtime.js"
|
|
41
|
+
},
|
|
42
|
+
"./schema": {
|
|
43
|
+
"types": "./types/schema.d.ts",
|
|
44
|
+
"import": "./dist/schema.js"
|
|
45
|
+
},
|
|
46
|
+
"./testing": {
|
|
47
|
+
"types": "./types/testing.d.ts",
|
|
48
|
+
"import": "./dist/testing.js"
|
|
49
|
+
},
|
|
50
|
+
"./style.css": "./dist/style.css"
|
|
51
|
+
},
|
|
52
|
+
"typesVersions": {
|
|
53
|
+
"*": {
|
|
54
|
+
"designer": [
|
|
55
|
+
"types/designer.d.ts"
|
|
56
|
+
],
|
|
57
|
+
"runtime": [
|
|
58
|
+
"types/runtime.d.ts"
|
|
59
|
+
],
|
|
60
|
+
"schema": [
|
|
61
|
+
"types/schema.d.ts"
|
|
62
|
+
],
|
|
63
|
+
"testing": [
|
|
64
|
+
"types/testing.d.ts"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"sideEffects": [
|
|
69
|
+
"**/*.css"
|
|
70
|
+
],
|
|
71
|
+
"scripts": {
|
|
72
|
+
"dev": "vite --config playground/vite.config.js",
|
|
73
|
+
"test": "vitest run --threads=false",
|
|
74
|
+
"typecheck": "tsc --noEmit && node ./scripts/check-sfc.mjs",
|
|
75
|
+
"build": "vite build",
|
|
76
|
+
"check:dist": "node ./scripts/check-dist.mjs",
|
|
77
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
78
|
+
"prepack": "npm run test && npm run typecheck && npm run build && npm run check:dist"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"element-plus": ">=2.7.0 <3",
|
|
82
|
+
"pinia": ">=2.0.22 <3",
|
|
83
|
+
"vue": ">=3.2.45 <4"
|
|
84
|
+
},
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"@element-plus/icons-vue": "~2.3.0",
|
|
87
|
+
"vuedraggable": "^4.1.0"
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@vitejs/plugin-vue": "4.0.0",
|
|
91
|
+
"@vue/compiler-sfc": "3.2.45",
|
|
92
|
+
"element-plus": "~2.7.0",
|
|
93
|
+
"pinia": "2.0.22",
|
|
94
|
+
"sass": "1.56.1",
|
|
95
|
+
"typescript": "4.9.5",
|
|
96
|
+
"vite": "4.3.1",
|
|
97
|
+
"vitest": "0.29.7",
|
|
98
|
+
"vue": "3.2.45"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { JXYFormDesigner, useFormDesignerStore } from './index';
|
|
2
|
+
export type {
|
|
3
|
+
JxyFormDesignerProps,
|
|
4
|
+
JxyFormDesignerExpose,
|
|
5
|
+
JxyFieldSuffixSlotScope,
|
|
6
|
+
JxyFieldSuffixSlots,
|
|
7
|
+
JxyPreviewActionSlots,
|
|
8
|
+
JxyFormDesignerSlots,
|
|
9
|
+
JxyFormDesignerStoreState,
|
|
10
|
+
JxyFormDesignerStoreActions
|
|
11
|
+
} from './index';
|
|
12
|
+
export { JXYFormDesigner as default } from './index';
|