增加formily json schema生成
This commit is contained in:
parent
9c4f66be1d
commit
72178f3393
@ -0,0 +1,19 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FormilyComponentProps {
|
||||
String api() default ""; // 数据源API
|
||||
String labelField() default "label"; // 标签字段
|
||||
String valueField() default "value"; // 值字段
|
||||
boolean allowClear() default false; // 允许清除
|
||||
boolean showSearch() default false; // 显示搜索
|
||||
String placeholder() default ""; // 占位符
|
||||
String mode() default ""; // 模式(single/multiple等)
|
||||
String[] enum_() default {}; // 枚举值
|
||||
String[] enumNames() default {}; // 枚举显示值
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FormilyDecoratorProps {
|
||||
String tooltip() default ""; // 提示文本
|
||||
int labelCol() default 6; // 标签列宽
|
||||
int wrapperCol() default 12; // 组件列宽
|
||||
String labelAlign() default "right"; // 标签对齐方式
|
||||
String size() default "default"; // 尺寸
|
||||
boolean asterisk() default false; // 星号标记
|
||||
boolean bordered() default true; // 是否有边框
|
||||
boolean colon() default true; // 是否有冒号
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FormilyField {
|
||||
// 基础属性
|
||||
String title(); // 字段标题
|
||||
String description() default ""; // 字段描述
|
||||
String type() default "string"; // 字段类型(string/number/boolean/array/object)
|
||||
|
||||
// 组件属性
|
||||
String component() default "Input"; // 组件类型
|
||||
String decorator() default "FormItem"; // 装饰器类型
|
||||
FormilyComponentProps props() default @FormilyComponentProps; // 组件属性
|
||||
FormilyDecoratorProps decoratorProps() default @FormilyDecoratorProps; // 装饰器属性
|
||||
|
||||
// 校验规则
|
||||
FormilyValidator[] validators() default {}; // 校验规则
|
||||
|
||||
// 联动配置
|
||||
FormilyReaction[] reactions() default {}; // 联动规则
|
||||
|
||||
// 展示控制
|
||||
String pattern() default "editable"; // 展示模式(editable/disabled/readOnly)
|
||||
String display() default "visible"; // 显示模式(visible/hidden/none)
|
||||
boolean hidden() default false; // 是否隐藏
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FormilyForm {
|
||||
String name(); // 表单名称
|
||||
String description() default ""; // 表单描述
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FormilyReaction {
|
||||
String[] dependencies() default {}; // 依赖字段
|
||||
String state() default ""; // 状态表达式,如 dataSource
|
||||
String value() default ""; // 状态值,如 {{$fetch('/api/options/B?a=' + $deps.a).then(data => data.data)}}
|
||||
}
|
||||
@ -0,0 +1,205 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class FormilySchemaFactory {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 生成Formily JSON Schema
|
||||
* @param clazz 带有FormilyForm和FormilyField注解的类
|
||||
* @return Formily JSON Schema
|
||||
*/
|
||||
public static JsonNode generateSchema(Class<?> clazz) {
|
||||
FormilyForm formDef = clazz.getAnnotation(FormilyForm.class);
|
||||
if (formDef == null) {
|
||||
throw new IllegalArgumentException("Class must be annotated with @FormilyForm");
|
||||
}
|
||||
|
||||
ObjectNode schema = objectMapper.createObjectNode();
|
||||
schema.put("type", "object");
|
||||
schema.put("name", formDef.name());
|
||||
if (!formDef.description().isEmpty()) {
|
||||
schema.put("description", formDef.description());
|
||||
}
|
||||
|
||||
ObjectNode properties = schema.putObject("properties");
|
||||
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
FormilyField fieldDef = field.getAnnotation(FormilyField.class);
|
||||
if (fieldDef != null) {
|
||||
properties.set(field.getName(), generateFieldSchema(fieldDef));
|
||||
}
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
private static JsonNode generateFieldSchema(FormilyField field) {
|
||||
ObjectNode fieldSchema = objectMapper.createObjectNode();
|
||||
|
||||
// 基础属性
|
||||
fieldSchema.put("type", field.type());
|
||||
fieldSchema.put("title", field.title());
|
||||
if (!field.description().isEmpty()) {
|
||||
fieldSchema.put("description", field.description());
|
||||
}
|
||||
|
||||
// 组件属性
|
||||
fieldSchema.put("x-decorator", field.decorator());
|
||||
fieldSchema.put("x-component", field.component());
|
||||
|
||||
// 组件属性配置
|
||||
ObjectNode componentProps = generateComponentProps(field.props());
|
||||
if (componentProps.size() > 0) {
|
||||
fieldSchema.set("x-component-props", componentProps);
|
||||
}
|
||||
|
||||
// 装饰器属性配置
|
||||
ObjectNode decoratorProps = generateDecoratorProps(field.decoratorProps());
|
||||
if (decoratorProps.size() > 0) {
|
||||
fieldSchema.set("x-decorator-props", decoratorProps);
|
||||
}
|
||||
|
||||
// 校验规则
|
||||
if (field.validators().length > 0) {
|
||||
ArrayNode validators = fieldSchema.putArray("x-validator");
|
||||
for (FormilyValidator validator : field.validators()) {
|
||||
validators.add(generateValidator(validator));
|
||||
}
|
||||
}
|
||||
|
||||
// 联动配置
|
||||
if (field.reactions().length > 0) {
|
||||
ArrayNode reactions = fieldSchema.putArray("x-reactions");
|
||||
for (FormilyReaction reaction : field.reactions()) {
|
||||
reactions.add(generateReaction(reaction));
|
||||
}
|
||||
}
|
||||
|
||||
// 展示控制 - 这些属性始终输出,因为它们有默认值
|
||||
fieldSchema.put("x-pattern", field.pattern());
|
||||
fieldSchema.put("x-display", field.display());
|
||||
fieldSchema.put("x-hidden", field.hidden());
|
||||
|
||||
return fieldSchema;
|
||||
}
|
||||
|
||||
private static ObjectNode generateComponentProps(FormilyComponentProps props) {
|
||||
ObjectNode node = objectMapper.createObjectNode();
|
||||
|
||||
if (!props.api().isEmpty()) {
|
||||
node.put("api", props.api());
|
||||
}
|
||||
if (!props.labelField().equals("label")) {
|
||||
node.put("labelField", props.labelField());
|
||||
}
|
||||
if (!props.valueField().equals("value")) {
|
||||
node.put("valueField", props.valueField());
|
||||
}
|
||||
if (props.allowClear()) {
|
||||
node.put("allowClear", true);
|
||||
}
|
||||
if (props.showSearch()) {
|
||||
node.put("showSearch", true);
|
||||
}
|
||||
if (!props.placeholder().isEmpty()) {
|
||||
node.put("placeholder", props.placeholder());
|
||||
}
|
||||
if (!props.mode().isEmpty()) {
|
||||
node.put("mode", props.mode());
|
||||
}
|
||||
if (props.enum_().length > 0) {
|
||||
ArrayNode enumValues = node.putArray("enum");
|
||||
Arrays.stream(props.enum_()).forEach(enumValues::add);
|
||||
}
|
||||
if (props.enumNames().length > 0) {
|
||||
ArrayNode enumNames = node.putArray("enumNames");
|
||||
Arrays.stream(props.enumNames()).forEach(enumNames::add);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static ObjectNode generateDecoratorProps(FormilyDecoratorProps props) {
|
||||
ObjectNode node = objectMapper.createObjectNode();
|
||||
|
||||
if (!props.tooltip().isEmpty()) {
|
||||
node.put("tooltip", props.tooltip());
|
||||
}
|
||||
if (props.labelCol() != 6) {
|
||||
node.put("labelCol", props.labelCol());
|
||||
}
|
||||
if (props.wrapperCol() != 12) {
|
||||
node.put("wrapperCol", props.wrapperCol());
|
||||
}
|
||||
if (!props.labelAlign().equals("right")) {
|
||||
node.put("labelAlign", props.labelAlign());
|
||||
}
|
||||
if (!props.size().equals("default")) {
|
||||
node.put("size", props.size());
|
||||
}
|
||||
if (props.asterisk()) {
|
||||
node.put("asterisk", true);
|
||||
}
|
||||
if (!props.bordered()) {
|
||||
node.put("bordered", false);
|
||||
}
|
||||
if (!props.colon()) {
|
||||
node.put("colon", false);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static ObjectNode generateValidator(FormilyValidator validator) {
|
||||
ObjectNode node = objectMapper.createObjectNode();
|
||||
|
||||
if (validator.required()) {
|
||||
node.put("required", true);
|
||||
}
|
||||
if (!validator.message().isEmpty()) {
|
||||
node.put("message", validator.message());
|
||||
}
|
||||
if (!validator.format().isEmpty()) {
|
||||
node.put("format", validator.format());
|
||||
}
|
||||
if (!validator.pattern().isEmpty()) {
|
||||
node.put("pattern", validator.pattern());
|
||||
}
|
||||
if (validator.min() != -1) {
|
||||
node.put("min", validator.min());
|
||||
}
|
||||
if (validator.max() != -1) {
|
||||
node.put("max", validator.max());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static ObjectNode generateReaction(FormilyReaction reaction) {
|
||||
ObjectNode node = objectMapper.createObjectNode();
|
||||
|
||||
// 添加依赖字段
|
||||
if (reaction.dependencies().length > 0) {
|
||||
ArrayNode deps = node.putArray("dependencies");
|
||||
Arrays.stream(reaction.dependencies()).forEach(deps::add);
|
||||
}
|
||||
|
||||
// 创建fulfill结构
|
||||
ObjectNode fulfill = node.putObject("fulfill");
|
||||
ObjectNode state = fulfill.putObject("state");
|
||||
|
||||
if (!reaction.state().isEmpty() && !reaction.value().isEmpty()) {
|
||||
state.put(reaction.state(), reaction.value());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FormilyValidator {
|
||||
boolean required() default false; // 是否必填
|
||||
String message() default ""; // 错误信息
|
||||
String format() default ""; // 格式(email/url/phone等)
|
||||
String pattern() default ""; // 正则表达式
|
||||
int min() default -1; // 最小值/长度
|
||||
int max() default -1; // 最大值/长度
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation.formily;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class FormilySchemaFactoryTest {
|
||||
|
||||
@Test
|
||||
public void testGenerateSchema() {
|
||||
JsonNode schema = FormilySchemaFactory.generateSchema(TestForm.class);
|
||||
|
||||
// 验证基本结构
|
||||
assertEquals("object", schema.get("type").asText());
|
||||
assertEquals("测试表单", schema.get("name").asText());
|
||||
assertTrue(schema.has("properties"));
|
||||
|
||||
// 验证字段A
|
||||
JsonNode fieldA = schema.get("properties").get("a");
|
||||
assertEquals("string", fieldA.get("type").asText());
|
||||
assertEquals("选择A", fieldA.get("title").asText());
|
||||
assertEquals("Select", fieldA.get("x-component").asText());
|
||||
assertEquals("FormItem", fieldA.get("x-decorator").asText());
|
||||
assertEquals("editable", fieldA.get("x-pattern").asText());
|
||||
assertEquals("visible", fieldA.get("x-display").asText());
|
||||
assertFalse(fieldA.get("x-hidden").asBoolean());
|
||||
|
||||
// 验证组件属性
|
||||
JsonNode propsA = fieldA.get("x-component-props");
|
||||
assertEquals("/api/options/A", propsA.get("api").asText());
|
||||
assertEquals("name", propsA.get("labelField").asText());
|
||||
assertEquals("id", propsA.get("valueField").asText());
|
||||
assertTrue(propsA.get("allowClear").asBoolean());
|
||||
|
||||
// 验证字段B的联动
|
||||
JsonNode fieldB = schema.get("properties").get("b");
|
||||
JsonNode reactionsB = fieldB.get("x-reactions");
|
||||
assertTrue(reactionsB.isArray());
|
||||
JsonNode reactionB = reactionsB.get(0);
|
||||
assertTrue(reactionB.get("dependencies").isArray());
|
||||
assertEquals("a", reactionB.get("dependencies").get(0).asText());
|
||||
|
||||
JsonNode fulfillB = reactionB.get("fulfill");
|
||||
JsonNode stateB = fulfillB.get("state");
|
||||
assertEquals("{{$fetch('/api/options/B?a=' + $deps.a).then(data => data.data)}}",
|
||||
stateB.get("dataSource").asText());
|
||||
|
||||
// 验证字段C的联动
|
||||
JsonNode fieldC = schema.get("properties").get("c");
|
||||
JsonNode reactionsC = fieldC.get("x-reactions");
|
||||
assertTrue(reactionsC.isArray());
|
||||
JsonNode reactionC = reactionsC.get(0);
|
||||
assertTrue(reactionC.get("dependencies").isArray());
|
||||
assertEquals(2, reactionC.get("dependencies").size());
|
||||
assertEquals("a", reactionC.get("dependencies").get(0).asText());
|
||||
assertEquals("b", reactionC.get("dependencies").get(1).asText());
|
||||
|
||||
JsonNode fulfillC = reactionC.get("fulfill");
|
||||
JsonNode stateC = fulfillC.get("state");
|
||||
assertEquals("{{$fetch('/api/options/C?a=' + $deps.a + '&b=' + $deps.b).then(data => data.data)}}",
|
||||
stateC.get("dataSource").asText());
|
||||
|
||||
// 打印生成的Schema以便查看
|
||||
System.out.println(schema.toPrettyString());
|
||||
}
|
||||
}
|
||||
|
||||
@FormilyForm(name = "测试表单")
|
||||
class TestForm {
|
||||
@FormilyField(
|
||||
title = "选择A",
|
||||
component = "Select",
|
||||
props = @FormilyComponentProps(
|
||||
api = "/api/options/A",
|
||||
labelField = "name",
|
||||
valueField = "id",
|
||||
allowClear = true
|
||||
),
|
||||
validators = {
|
||||
@FormilyValidator(
|
||||
required = true,
|
||||
message = "请选择A"
|
||||
)
|
||||
}
|
||||
)
|
||||
private String a;
|
||||
|
||||
@FormilyField(
|
||||
title = "选择B",
|
||||
component = "Select",
|
||||
props = @FormilyComponentProps(
|
||||
labelField = "name",
|
||||
valueField = "id"
|
||||
),
|
||||
validators = {
|
||||
@FormilyValidator(
|
||||
required = true,
|
||||
message = "请选择B"
|
||||
)
|
||||
},
|
||||
reactions = {
|
||||
@FormilyReaction(
|
||||
dependencies = {"a"},
|
||||
state = "dataSource",
|
||||
value = "{{$fetch('/api/options/B?a=' + $deps.a).then(data => data.data)}}"
|
||||
)
|
||||
}
|
||||
)
|
||||
private String b;
|
||||
|
||||
@FormilyField(
|
||||
title = "选择C",
|
||||
component = "Select",
|
||||
props = @FormilyComponentProps(
|
||||
labelField = "name",
|
||||
valueField = "id"
|
||||
),
|
||||
validators = {
|
||||
@FormilyValidator(
|
||||
required = true,
|
||||
message = "请选择C"
|
||||
)
|
||||
},
|
||||
reactions = {
|
||||
@FormilyReaction(
|
||||
dependencies = {"a", "b"},
|
||||
state = "dataSource",
|
||||
value = "{{$fetch('/api/options/C?a=' + $deps.a + '&b=' + $deps.b).then(data => data.data)}}"
|
||||
)
|
||||
}
|
||||
)
|
||||
private String c;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user