Procházet zdrojové kódy

!17 requestBody增加注释属性面板,swagger文档兼容knife4j
Merge pull request !17 from Lianjy/dev

小东 před 4 roky
rodič
revize
2add478885
20 změnil soubory, kde provedl 977 přidání a 172 odebrání
  1. 56 0
      magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java
  2. 4 3
      magic-api/src/main/java/org/ssssssss/magicapi/model/ApiInfo.java
  3. 19 5
      magic-api/src/main/java/org/ssssssss/magicapi/model/BaseDefinition.java
  4. 12 1
      magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java
  5. 4 0
      magic-api/src/main/java/org/ssssssss/magicapi/model/DataType.java
  6. 2 0
      magic-api/src/main/java/org/ssssssss/magicapi/model/JsonCodeConstants.java
  7. 17 3
      magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerEntity.java
  8. 93 13
      magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerProvider.java
  9. binární
      magic-editor/src/console/src/assets/images/array.gif
  10. binární
      magic-editor/src/console/src/assets/images/elbow-end.gif
  11. binární
      magic-editor/src/console/src/assets/images/elbow-line.gif
  12. binární
      magic-editor/src/console/src/assets/images/elbow.gif
  13. binární
      magic-editor/src/console/src/assets/images/object.gif
  14. binární
      magic-editor/src/console/src/assets/images/s.gif
  15. 35 0
      magic-editor/src/console/src/components/common/magic-json-tree-format.vue
  16. 108 0
      magic-editor/src/console/src/components/common/magic-json-tree.vue
  17. 222 0
      magic-editor/src/console/src/components/common/magic-json.vue
  18. 37 2
      magic-editor/src/console/src/components/editor/magic-script-editor.vue
  19. 346 144
      magic-editor/src/console/src/components/layout/magic-request.vue
  20. 22 1
      magic-editor/src/console/src/scripts/utils.js

+ 56 - 0
magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java

@@ -2,6 +2,7 @@ package org.ssssssss.magicapi.controller;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -30,6 +31,7 @@ import org.ssssssss.magicapi.model.*;
 import org.ssssssss.magicapi.modules.ResponseModule;
 import org.ssssssss.magicapi.provider.ResultProvider;
 import org.ssssssss.magicapi.script.ScriptManager;
+import org.ssssssss.magicapi.utils.JsonUtils;
 import org.ssssssss.magicapi.utils.PatternUtils;
 import org.ssssssss.script.MagicScriptContext;
 import org.ssssssss.script.MagicScriptDebugContext;
@@ -108,6 +110,33 @@ public class RequestHandler extends MagicController {
 			return requestEntity.isRequestedFromTest() ? new JsonBean<>(PATH_VARIABLE_INVALID, value) : value;
 		}
 		MagicScriptContext context = createMagicScriptContext(requestEntity);
+
+		if (context.get(VAR_NAME_REQUEST_BODY) != null && StringUtils.isNotBlank(requestEntity.getApiInfo().getRequestBody()) && JsonUtils.readValue(requestEntity.getApiInfo().getRequestBody(), BaseDefinition.class).getChildren().size() > 0) {
+			// 验证 body
+			BaseDefinition body = JsonUtils.readValue(requestEntity.getApiInfo().getRequestBody(), BaseDefinition.class);
+			Object bodyValue = ObjectUtils.clone(context.get(VAR_NAME_REQUEST_BODY));
+
+			// 请求体首层是数组的时候单独处理
+            if (bodyValue instanceof List) {
+				if (!VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY.equalsIgnoreCase(body.getDataType().getJavascriptType())) {
+                    Object result = resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, String.format("body参数错误,应为[%s]", body.getDataType().getJavascriptType()));
+                    return requestEntity.isRequestedFromTest() ? new JsonBean<>(BODY_INVALID, result) : result;
+				}
+
+				for (Map valueMap : (List<Map>)bodyValue) {
+					value = doValidate(requestEntity, VAR_NAME_REQUEST_BODY, body.getChildren().get(0).getChildren(), ObjectUtils.clone(valueMap));
+					if (value != null) {
+						return requestEntity.isRequestedFromTest() ? new JsonBean<>(BODY_INVALID, value) : value;
+					}
+				}
+			} else {
+				value = doValidate(requestEntity, VAR_NAME_REQUEST_BODY, body.getChildren(), (Map)bodyValue);
+				if (value != null) {
+					return requestEntity.isRequestedFromTest() ? new JsonBean<>(BODY_INVALID, value) : value;
+				}
+			}
+		}
+
 		requestEntity.setMagicScriptContext(context);
 		RequestContext.setRequestEntity(requestEntity);
 		// 执行前置拦截器
@@ -130,6 +159,31 @@ public class RequestHandler extends MagicController {
 
 	private <T extends BaseDefinition> Object doValidate(RequestEntity requestEntity, String comment, List<T> validateParameters, Map<String, Object> parameters) {
 		for (BaseDefinition parameter : validateParameters) {
+
+			// 针对requestBody多层级的情况
+			if (VAR_NAME_REQUEST_BODY_VALUE_TYPE_OBJECT.equalsIgnoreCase(parameter.getDataType().getJavascriptType())) {
+				Map map = (Map)parameters.get(parameter.getName());
+				Object result = doValidate(requestEntity, VAR_NAME_REQUEST_BODY, parameter.getChildren(), map);
+				if (result != null) {
+					return result;
+				}
+			}
+
+            if (VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY.equalsIgnoreCase(parameter.getDataType().getJavascriptType())) {
+            	if (parameters == null || parameters.get(parameter.getName()) == null) {
+					return resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]为必填项", comment, parameter.getName())));
+				}
+				if (!(parameters.get(parameter.getName()) instanceof List)) {
+					return resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]数据类型错误", comment, parameter.getName())));
+				}
+                for (Map valueMap : (List<Map>)parameters.get(parameter.getName())) {
+					Object result = doValidate(requestEntity, VAR_NAME_REQUEST_BODY, parameter.getChildren().get(0).getChildren(), ObjectUtils.clone(valueMap));
+					if (result != null) {
+						return result;
+					}
+                }
+            }
+
 			if (StringUtils.isNotBlank(parameter.getName())) {
 				String requestValue = StringUtils.defaultIfBlank(Objects.toString(parameters.get(parameter.getName()), EMPTY), Objects.toString(parameter.getDefaultValue(), EMPTY));
 				if (StringUtils.isBlank(requestValue)) {
@@ -433,6 +487,8 @@ public class RequestHandler extends MagicController {
 					}
 				}
 			} catch (HttpMessageNotReadableException ignored) {
+				System.out.println(ignored.getHttpInputMessage().getBody().toString());
+				ignored.printStackTrace();
 				return null;
 			}
 		}

+ 4 - 3
magic-api/src/main/java/org/ssssssss/magicapi/model/ApiInfo.java

@@ -152,7 +152,7 @@ public class ApiInfo extends MagicEntity {
 				.flatMap(it -> it.getOptions().stream())
 				.forEach(option -> {
 					if (!map.containsKey(option.getName())) {
-						map.put(option.getName(), option.getValue());
+						map.put(option.getName(), String.valueOf(option.getValue()));
 					}
 				});
 		return map;
@@ -228,8 +228,9 @@ public class ApiInfo extends MagicEntity {
 				.flatMap(it -> it.getOptions().stream())
 				.filter(it -> key.equals(it.getName()))
 				.findFirst()
-				.map(BaseDefinition::getValue)
-				.orElse(null);
+				.map(it -> {
+					return Objects.toString(it.getValue(), null);
+				}).orElse(null);
 	}
 
 	@Override

+ 19 - 5
magic-api/src/main/java/org/ssssssss/magicapi/model/BaseDefinition.java

@@ -1,5 +1,6 @@
 package org.ssssssss.magicapi.model;
 
+import java.util.ArrayList;
 import java.util.Objects;
 
 public class BaseDefinition {
@@ -12,7 +13,7 @@ public class BaseDefinition {
 	/**
 	 * 值
 	 */
-	private String value;
+	private Object value;
 
 	/**
 	 * 描述
@@ -54,6 +55,11 @@ public class BaseDefinition {
 	 */
 	private String expression;
 
+	/**
+	 * @Description 子集,数据类型为对象或数组时有数据
+	 */
+	private ArrayList<BaseDefinition> children;
+
 	public BaseDefinition() {
 	}
 
@@ -70,11 +76,11 @@ public class BaseDefinition {
 		this.name = name;
 	}
 
-	public String getValue() {
+	public Object getValue() {
 		return value;
 	}
 
-	public void setValue(String value) {
+	public void setValue(Object value) {
 		this.value = value;
 	}
 
@@ -142,16 +148,24 @@ public class BaseDefinition {
 		this.type = type;
 	}
 
+	public ArrayList<BaseDefinition> getChildren() {
+		return children;
+	}
+
+	public void setChildren(ArrayList<BaseDefinition> children) {
+		this.children = children;
+	}
+
 	@Override
 	public boolean equals(Object o) {
 		if (this == o) return true;
 		if (!(o instanceof BaseDefinition)) return false;
 		BaseDefinition that = (BaseDefinition) o;
-		return required == that.required && Objects.equals(name, that.name) && Objects.equals(value, that.value) && Objects.equals(description, that.description) && dataType == that.dataType && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(validateType, that.validateType) && Objects.equals(error, that.error) && Objects.equals(expression, that.expression);
+		return required == that.required && Objects.equals(name, that.name) && Objects.equals(value, that.value) && Objects.equals(description, that.description) && dataType == that.dataType && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(validateType, that.validateType) && Objects.equals(error, that.error) && Objects.equals(expression, that.expression) && Objects.equals(children, that.children);
 	}
 
 	@Override
 	public int hashCode() {
-		return Objects.hash(name, value, description, required, dataType, defaultValue, validateType, error, expression);
+		return Objects.hash(name, value, description, required, dataType, defaultValue, validateType, error, expression, children);
 	}
 }

+ 12 - 1
magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java

@@ -82,12 +82,23 @@ public class Constants {
 	 * 脚本中header的变量名
 	 */
 	public static final String VAR_NAME_HEADER = "header";
+	/**
+	 * 脚本中query的变量名
+	 */
+	public static final String VAR_NAME_QUERY = "query";
 
 	/**
 	 * 脚本中RequestBody的变量名
 	 */
 	public static final String VAR_NAME_REQUEST_BODY = "body";
-
+	/**
+	 * 脚本中RequestBody的变量值字段类型
+	 */
+	public static final String VAR_NAME_REQUEST_BODY_VALUE_TYPE_OBJECT = "object";
+	/**
+	 * 脚本中RequestBody的变量名字段类型
+	 */
+	public static final String VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY = "array";
 	/**
 	 * 脚本中RequestBody的变量名
 	 */

+ 4 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/DataType.java

@@ -9,6 +9,9 @@ import java.math.BigDecimal;
 import static org.ssssssss.script.reflection.JavaReflection.findInvoker;
 
 public enum DataType {
+	Object("object"),
+	Array("array"),
+	Boolean("boolean"),
 	String("string"),
 	Integer(true, findInvoker(BigDecimal.class, "intValue"), "number"),
 	Double(true, findInvoker(BigDecimal.class, "doubleValue"), "number"),
@@ -19,6 +22,7 @@ public enum DataType {
 	MultipartFile(findInvoker(RequestModule.class, "getFile", new Class<?>[]{String.class}), true, false, "file"),
 	MultipartFiles(findInvoker(RequestModule.class, "getFiles", new Class<?>[]{String.class}), true, false, "file");
 
+
 	private boolean isNumber;
 
 	private JavaInvoker<Method> invoker;

+ 2 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/JsonCodeConstants.java

@@ -61,6 +61,8 @@ public interface JsonCodeConstants {
 
 	JsonCode PATH_VARIABLE_INVALID = new JsonCode(0, "路径变量验证失败");
 
+	JsonCode BODY_INVALID = new JsonCode(0, "body验证失败");
+
 	JsonCode FILE_IS_REQUIRED = new JsonCode(0, "请上传文件");
 
 	JsonCode SIGN_IS_INVALID = new JsonCode(0, "签名验证失败");

+ 17 - 3
magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerEntity.java

@@ -17,7 +17,7 @@ public class SwaggerEntity {
 
 	private Set<Tag> tags = new TreeSet<>(Comparator.comparing(Tag::getName));
 
-	private Map<String, String> definitions = Collections.emptyMap();
+	private Map<String, Object> definitions = new HashMap<>();
 
 	private Map<String, Map<String, Path>> paths = new HashMap<>();
 
@@ -40,6 +40,7 @@ public class SwaggerEntity {
 			result.put("properties", properties);
 		} else {
 			result.put("example", target == null ? "" : target);
+			result.put("description", target == null ? "" : target);
 		}
 		return result;
 	}
@@ -104,10 +105,12 @@ public class SwaggerEntity {
 		this.basePath = basePath;
 	}
 
-	public Map<String, String> getDefinitions() {
+	public Map<String, Object> getDefinitions() {
 		return definitions;
 	}
-
+	public void addDefinitions(String path, Object definition) {
+		definitions.put(path, definition);
+	}
 	public Set<Tag> getTags() {
 		return tags;
 	}
@@ -216,6 +219,8 @@ public class SwaggerEntity {
 
 		private String summary;
 
+		private String description;
+
 		private String operationId = UUID.randomUUID().toString().replace("-", "");
 
 		private List<String> produces = new ArrayList<>();
@@ -305,6 +310,14 @@ public class SwaggerEntity {
 		public void setResponses(Map<String, Object> responses) {
 			this.responses = responses;
 		}
+
+		public String getDescription() {
+			return description;
+		}
+
+		public void setDescription(String description) {
+			this.description = description;
+		}
 	}
 
 	public static class Parameter {
@@ -334,6 +347,7 @@ public class SwaggerEntity {
 				schema.put("type", type);
 				schema.put("example", example);
 				this.schema = doProcessSchema(example);
+				this.schema = "";
 			} else {
 				this.example = example;
 				/*

+ 93 - 13
magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerProvider.java

@@ -5,14 +5,16 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.ssssssss.magicapi.config.MappingHandlerMapping;
 import org.ssssssss.magicapi.model.ApiInfo;
+import org.ssssssss.magicapi.model.BaseDefinition;
 import org.ssssssss.magicapi.model.Path;
 import org.ssssssss.magicapi.provider.GroupServiceProvider;
+import org.ssssssss.magicapi.utils.JsonUtils;
 import org.ssssssss.script.parsing.ast.literal.BooleanLiteral;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.ssssssss.magicapi.model.Constants.*;
 
 /**
  * 生成swagger用的json
@@ -30,6 +32,17 @@ public class SwaggerProvider {
 
 	private SwaggerEntity.Info info;
 
+	/**
+	 * swagger Model定义路径前缀
+	 */
+    private static final String DEFINITION = "#/definitions/";
+    /**
+	 * body空对象
+     */
+    private static final String BODY_EMPTY = "{}";
+
+    private final Map<String, Object> DEFINITION_MAP = new ConcurrentHashMap<>();
+
 	public void setMappingHandlerMapping(MappingHandlerMapping mappingHandlerMapping) {
 		this.mappingHandlerMapping = mappingHandlerMapping;
 	}
@@ -48,6 +61,7 @@ public class SwaggerProvider {
 
 	@ResponseBody
 	public SwaggerEntity swaggerJson() {
+		this.DEFINITION_MAP.clear();
 		List<ApiInfo> infos = mappingHandlerMapping.getApiInfos();
 		SwaggerEntity swaggerEntity = new SwaggerEntity();
 		swaggerEntity.setInfo(info);
@@ -61,9 +75,13 @@ public class SwaggerProvider {
 			boolean hasBody = false;
 			try {
 				List<SwaggerEntity.Parameter> parameters = parseParameters(mapper, info);
-				hasBody = parameters.stream().anyMatch(it -> "body".equals(it.getIn()));
+				hasBody = parameters.stream().anyMatch(it -> VAR_NAME_REQUEST_BODY.equals(it.getIn()));
+                if (hasBody) {
+                    BaseDefinition baseDefinition = JsonUtils.readValue(info.getRequestBody(), BaseDefinition.class);
+                    doProcessDefinition(baseDefinition, info, StringUtils.defaultIfBlank(baseDefinition.getName(), VAR_NAME_REQUEST_BODY));
+                }
 				parameters.forEach(path::addParameter);
-				path.addResponse("200", mapper.readValue(Objects.toString(info.getResponseBody(), "{}"), Object.class));
+				path.addResponse("200", mapper.readValue(Objects.toString(info.getResponseBody(), BODY_EMPTY), Object.class));
 			} catch (Exception ignored) {
 			}
 			if (hasBody) {
@@ -72,17 +90,26 @@ public class SwaggerProvider {
 				path.addConsume("*/*");
 			}
 			path.addProduce("application/json");
-			path.setSummary(StringUtils.defaultIfBlank(info.getDescription(), info.getName()));
+			path.setSummary(info.getName());
+			path.setDescription(StringUtils.defaultIfBlank(info.getDescription(), info.getName()));
 
 			swaggerEntity.addPath(requestPath, info.getMethod(), path);
 		}
+
+		if (this.DEFINITION_MAP.size() > 0) {
+			Set<Map.Entry> entries = ((Map) this.DEFINITION_MAP).entrySet();
+			for (Map.Entry entry : entries) {
+				swaggerEntity.addDefinitions(Objects.toString(entry.getKey()), entry.getValue());
+			}
+		}
+
 		return swaggerEntity;
 	}
 
 	private List<SwaggerEntity.Parameter> parseParameters(ObjectMapper mapper, ApiInfo info) {
 		List<SwaggerEntity.Parameter> parameters = new ArrayList<>();
-		info.getParameters().forEach(it -> parameters.add(new SwaggerEntity.Parameter(it.isRequired(), it.getName(), "query", it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
-		info.getHeaders().forEach(it -> parameters.add(new SwaggerEntity.Parameter(it.isRequired(), it.getName(), "header", it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
+		info.getParameters().forEach(it -> parameters.add(new SwaggerEntity.Parameter(it.isRequired(), it.getName(), VAR_NAME_QUERY, it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
+		info.getHeaders().forEach(it -> parameters.add(new SwaggerEntity.Parameter(it.isRequired(), it.getName(), VAR_NAME_HEADER, it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
 		List<Path> paths = new ArrayList<>(info.getPaths());
 		MappingHandlerMapping.findGroups(info.getGroupId())
 				.stream()
@@ -92,14 +119,67 @@ public class SwaggerProvider {
 						paths.add(it);
 					}
 				});
-		paths.forEach(it -> parameters.add(new SwaggerEntity.Parameter(it.isRequired(), it.getName(), "path", it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
+		paths.forEach(it -> parameters.add(new SwaggerEntity.Parameter(it.isRequired(), it.getName(), VAR_NAME_PATH_VARIABLE, it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
 		try {
-			Object object = mapper.readValue(info.getRequestBody(), Object.class);
-			if ((object instanceof List || object instanceof Map) && BooleanLiteral.isTrue(object)) {
-				parameters.add(new SwaggerEntity.Parameter(false, "body", "body", object instanceof List ? "array" : "object", null, object));
+			if (StringUtils.isNotBlank(info.getRequestBody()) && !BODY_EMPTY.equals(info.getRequestBody().replaceAll("\\s", ""))) {
+				BaseDefinition baseDefinition = JsonUtils.readValue(info.getRequestBody(), BaseDefinition.class);
+				if (BooleanLiteral.isTrue(baseDefinition)) {
+					SwaggerEntity.Parameter parameter = new SwaggerEntity.Parameter(true, StringUtils.isNotBlank(baseDefinition.getName()) ? baseDefinition.getName() : VAR_NAME_REQUEST_BODY, VAR_NAME_REQUEST_BODY, baseDefinition.getDataType().getJavascriptType(), baseDefinition.getDescription(), baseDefinition);
+
+					Map<String, Object> schema = new HashMap<>(2);
+					String groupName = groupServiceProvider.getFullName(info.getGroupId()).replace("/", "-");
+					String voName =  groupName + "«" + info.getPath().replaceFirst("/", "").replaceAll("/", "_") + "«";
+					if (VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY.equalsIgnoreCase(baseDefinition.getDataType().getJavascriptType())) {
+						voName += StringUtils.defaultIfBlank(baseDefinition.getChildren().get(0).getName(), VAR_NAME_REQUEST_BODY + "_" + StringUtils.defaultIfBlank(baseDefinition.getName(), VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY)) + "»»";
+					} else {
+						voName += StringUtils.defaultIfBlank(baseDefinition.getName(), VAR_NAME_REQUEST_BODY) + "»»";
+					}
+
+					schema.put("originalRef", voName);
+					schema.put("$ref", DEFINITION + voName);
+					parameter.setSchema(schema);
+					parameters.add(parameter);
+				}
 			}
+
 		} catch (Exception ignored) {
 		}
 		return parameters;
 	}
+
+    private Map<String, Object> doProcessDefinition(BaseDefinition target, ApiInfo info, String parentName) {
+        Map<String, Object> result = new HashMap<>(4);
+        result.put("type", target.getDataType().getJavascriptType());
+        result.put("description", target.getDescription());
+        if (VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY.equalsIgnoreCase(target.getDataType().getJavascriptType())) {
+            if (target.getChildren().size() > 0) {
+                result.put("items", doProcessDefinition(target.getChildren().get(0), info, parentName + "_" + StringUtils.defaultIfBlank(target.getName(), VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY)));
+            } else {
+                result.put("items", Collections.emptyList());
+            }
+
+        } else if (VAR_NAME_REQUEST_BODY_VALUE_TYPE_OBJECT.equalsIgnoreCase(target.getDataType().getJavascriptType())) {
+            String groupName = groupServiceProvider.getFullName(info.getGroupId()).replace("/", "-");
+            String voName = groupName + "«" + info.getPath().replaceFirst("/", "").replaceAll("/", "_") + "«" + StringUtils.defaultIfBlank(target.getName(), parentName)  + "»»";
+            if (this.DEFINITION_MAP.containsKey(voName)) {
+				// TODO 应该不会出现名字都一样的
+				voName.replace("»»", "_" + parentName + "»»");
+			}
+            result.put("originalRef", voName);
+            result.put("$ref", DEFINITION + voName);
+
+            Map<String, Object> definition = new HashMap<>(4);
+            Map<String, Map<String, Object>> properties = new HashMap<>(target.getChildren().size());
+            for (BaseDefinition obj : target.getChildren()) {
+                properties.put(obj.getName(), doProcessDefinition(obj, info, parentName));
+            }
+            definition.put("properties", properties);
+            definition.put("description", target.getDescription());
+
+            this.DEFINITION_MAP.put(voName, definition);
+        } else {
+            result.put("example", target.getValue());
+        }
+        return result;
+    }
 }

binární
magic-editor/src/console/src/assets/images/array.gif


binární
magic-editor/src/console/src/assets/images/elbow-end.gif


binární
magic-editor/src/console/src/assets/images/elbow-line.gif


binární
magic-editor/src/console/src/assets/images/elbow.gif


binární
magic-editor/src/console/src/assets/images/object.gif


binární
magic-editor/src/console/src/assets/images/s.gif


+ 35 - 0
magic-editor/src/console/src/components/common/magic-json-tree-format.vue

@@ -0,0 +1,35 @@
+<template>
+  <div v-if="item.level > 0">
+    <span v-for="index in item.level - 1" :key="index">
+      <img :src="imgLine" v-if="indentLevel[index] != 1"/>
+      <img :src="imgIndent" style="width: 16px;height: 16px;" v-else/>
+    </span>
+    <img :src="index + 1 == dataLength ? imgEnd : img"/>
+  </div>
+</template>
+
+<script>
+
+  export default {
+    name: 'MagicJsonTreeFormat',
+    props: {
+      item: {
+        type: Object,
+        required: true
+      },
+      index: Number,
+      dataLength: Number,
+      indentLevel: Array
+    },
+    data() {
+      return {
+        imgEnd: require('../../assets/images/elbow-end.gif'),
+        imgLine: require('../../assets/images/elbow-line.gif'),
+        img: require('../../assets/images/elbow.gif'),
+        imgIndent: require('../../assets/images/s.gif')
+      }
+    },
+  }
+</script>
+<style>
+</style>

+ 108 - 0
magic-editor/src/console/src/components/common/magic-json-tree.vue

@@ -0,0 +1,108 @@
+<template>
+   <ul class="ma-tree">
+    <li v-for="(item, index) in jsonData" :key="index" @click="handleItemClick(item, index)">
+      <!-- 解决子组件不强制刷新 -->
+      <div v-show="forceUpdate"></div>
+      <div class="item-inline"  :class="item.selected ? 'tree-item item-selected' : 'tree-item'">
+        <magic-json-tree-format :item="item" :index="index" :dataLength="jsonData.length" :indentLevel="indentLevel"/>
+        <div class="item-inline">
+          <template  v-if="item.dataType == 'Object' || item.dataType == 'Array'" >
+            <img :src="imgObject" v-if="item.dataType == 'Object'"/>
+            <img :src="imgArray" v-else/>
+          </template>
+
+          {{item.level > 0 ? item.name : ''}}{{item.dataType != 'Object' && item.dataType != 'Array' ? ':' : ''}}
+          <span :style="item.dataType | color">
+              {{item.dataType == 'String' ? (item.value == 'null' || item.value == null ? 'null' : '"' + item.value + '"') : item.value}}
+            </span>
+        </div>
+      </div>
+      <template  v-if="item.dataType == 'Object' || item.dataType == 'Array'" >
+        <magic-json-tree :jsonData="item.children" :indentLevel="indentLevel | createLevel(jsonData, item, index)" :forceUpdate="forceUpdate" v-on="$listeners"/>
+      </template>
+    </li>
+  </ul>
+</template>
+
+<script>
+  import MagicJsonTreeFormat from './magic-json-tree-format'
+  import {deepClone} from "../../scripts/utils";
+
+  export default {
+    name: 'MagicJsonTree',
+    data() {
+      return {
+        imgArray: require('../../assets/images/array.gif'),
+        imgObject: require('../../assets/images/object.gif'),
+      }
+    },
+    props: {
+      jsonData: {
+        type: Array,
+        required: true
+      },
+      indentLevel: Array,
+      forceUpdate: Boolean
+    },
+    filters: {
+      createLevel(indentLevel, jsonData, item, index) {
+        if (!indentLevel) {
+          indentLevel = []
+        }
+        indentLevel[item.level] = jsonData.length == index + 1 ? 1 : 0
+        return deepClone(indentLevel)
+      },
+      color(dataType) {
+        let color = "color: #42b983; margin-left: 5px;";
+        switch (dataType) {
+          case 'Integer':
+          case 'Double':
+          case 'Long':
+          case 'Short':
+          case 'Float':
+          case 'Byte':
+            color = "color: #fc1e70; margin-left: 5px;";
+            break;
+          case 'Boolean':
+            color = "color: #e08331; margin-left: 5px;";
+            break;
+        }
+        return color;
+      }
+    },
+    components: {
+      MagicJsonTreeFormat
+    },
+    methods: {
+      handleItemClick(item, index) {
+        this.$emit('jsonClick', item)
+        this.jsonData.map(item => {
+          item.selected = false;
+        })
+        this.jsonData[index].selected = !this.jsonData[index].selected
+        event.stopPropagation()
+      }
+    }
+  }
+</script>
+<style>
+  .ma-tree {
+    font-size: 14px;
+    line-height: 14px;
+  }
+
+  .ma-tree .tree-item {
+    cursor: pointer;
+    height: 20px;
+  }
+
+  .ma-tree .item-selected {
+    background-color: var(--toolbox-list-hover-background);
+  }
+
+  .item-inline {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+  }
+</style>

+ 222 - 0
magic-editor/src/console/src/components/common/magic-json.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="ma-json-container">
+    <div class="json-view f_c">
+      <!-- 解决子组件不强制刷新 -->
+      <div v-show="forceUpdate"></div>
+      <div class="header">视图</div>
+      <magic-json-tree :jsonData="jsonData" :forceUpdate="forceUpdate" class="view-box" v-on:jsonClick="handleJsonClick"></magic-json-tree>
+    </div>
+    <div class="json-panel f_c">
+      <div class="header">属性</div>
+      <div class="panel-box f_c" v-if="fieldObj.dataType && fieldObj.dataType != 'Object' && fieldObj.dataType != 'Array'">
+        <div class="box-item">
+          <div class="item-title">Key</div>
+          <div class="item-content">{{fieldObj.name}}</div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">Value</div>
+          <div class="item-content">{{fieldObj.value}}</div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">参数类型</div>
+          <div class="item-content">
+            <magic-select :border="false" :options="bodyTypes"
+                          :value.sync="fieldObj.dataType" default-value="String" style="width: 100%"/>
+          </div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">是否必填</div>
+          <div class="item-content">
+            <div style="width: 25px; height: 25px;"><magic-checkbox :value.sync="fieldObj.required"/></div>
+          </div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">默认值</div>
+          <div class="item-content">
+            <magic-input :value.sync="fieldObj.defaultValue" style="width: 100%"/>
+          </div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">验证方式</div>
+          <div class="item-content">
+            <magic-select :border="false" :options="validates"
+                          :value.sync="fieldObj.validateType" default-value="pass" style="width: 100%"/>
+          </div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">表达式或正则表达式</div>
+          <div class="item-content">
+            <magic-input :value.sync="fieldObj.expression" style="width: 100%"/>
+          </div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">验证说明</div>
+          <div class="item-content">
+            <magic-input :value.sync="fieldObj.error" style="width: 100%"/>
+          </div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">字段注释</div>
+          <div class="item-content">
+            <magic-input :value.sync="fieldObj.description" style="width: 100%"/>
+          </div>
+        </div>
+
+      </div>
+      <div class="panel-box f_c" v-else>
+        <div class="box-item">
+          <div class="item-title">对象VO</div>
+          <div class="item-content"><magic-input :value.sync="fieldObj.name" style="width: 100%" :placeholder="'请输入对象VO名称'"/></div>
+        </div>
+        <div class="box-item">
+          <div class="item-title">对象注释</div>
+          <div class="item-content">
+            <magic-input :value.sync="fieldObj.description" style="width: 100%"/>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+
+  import MagicInput from '@/components/common/magic-input.vue'
+  import MagicSelect from '@/components/common/magic-select.vue'
+  import MagicCheckbox from '@/components/common/magic-checkbox.vue'
+  import MagicJsonTree from './magic-json-tree'
+
+  export default {
+    name: 'MagicJson',
+    props: {
+      jsonData: {
+        type: [Object, Array, String, Number, Boolean, Function],
+        required: true
+      },
+      // 解决子组件不强制刷新
+      forceUpdate: Boolean
+    },
+    data() {
+      return {
+        validates: [
+          {value: 'pass', text: '不验证'},
+          {value: 'expression', text: '表达式验证'},
+          {value: 'pattern', text: '正则验证'},
+        ],
+        bodyTypes: [
+          {value: 'String', text: 'String'},
+          {value: 'Integer', text: 'Integer'},
+          {value: 'Double', text: 'Double'},
+          {value: 'Long', text: 'Long'},
+          {value: 'Short', text: 'Short'},
+          {value: 'Float', text: 'Float'},
+          {value: 'Byte', text: 'Byte'},
+          {value: 'Boolean', text: 'Boolean'},
+        ],
+        fieldObj: {dataType: "Object"}
+      }
+    },
+    components: {
+      MagicInput,
+      MagicSelect,
+      MagicCheckbox,
+      MagicJsonTree
+    },
+    watch: {
+      jsonData: {
+        handler(newVal, oldVal) {
+          if (newVal && newVal.length > 0) {
+            this.getActiveNode(newVal)
+          } else {
+            this.fieldObj = {dataType: "Object"}
+          }
+        },
+        deep: true
+      }
+    },
+    methods: {
+      getActiveNode(node) {
+        node.forEach(item => {
+          if (item.selected) {
+            this.fieldObj = item;
+            return;
+          } else {
+            this.getActiveNode(item.children)
+          }
+        })
+      },
+      recurveChildren(children) {
+        children.map(item => {
+          item.selected = false;
+          item.children = this.recurveChildren(item.children)
+        })
+        return children
+      },
+      handleJsonClick(e) {
+        this.jsonData.map(item => {
+          item.selected = false;
+          item.children = this.recurveChildren(item.children)
+        })
+        this.fieldObj = e;
+      }
+    }
+  }
+</script>
+<style>
+  .ma-json-container {
+    display: flex;
+    flex-direction: row;
+  }
+
+  .f_c {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .json-view {
+    width: 55%;
+    margin: 0px 10px;
+    background-color: var(--input-background);
+    border: 1px solid var(--border-color);
+  }
+
+  .json-view .view-box {
+    padding: 15px 20px;
+    height: 335px;
+    overflow: scroll;
+  }
+
+  .json-panel {
+    flex: 1;
+    margin: 0px 10px;
+    background-color: var(--input-background);
+    border: 1px solid var(--border-color);
+  }
+
+  .json-panel .panel-box {
+    padding: 5px 20px;
+  }
+
+  .json-panel .panel-box .box-item {
+    height: 35px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    border-bottom: 1px solid #E3E3E3;
+  }
+  .json-panel .panel-box .box-item .item-title {
+    width: 150px;
+  }
+  .json-panel .panel-box .box-item .item-content {
+    flex: 1;
+  }
+  .header {
+    height: 30px;
+    line-height: 30px;
+    font-size: 14px;
+    text-align: left;
+    padding-left: 20px;
+    background-color: var(--selected-background);
+    width: 100%;
+  }
+</style>

+ 37 - 2
magic-editor/src/console/src/components/editor/magic-script-editor.vue

@@ -488,12 +488,13 @@ export default {
       }
       if (this.info.requestBody) {
         try {
-          JSON.parse(this.info.requestBody)
+         let requestBody = JSON.parse(this.info.requestBody)
           requestConfig.params = params
-          requestConfig.data = this.info.requestBody
+          requestConfig.data = JSON.stringify(this.buildRequestBodyData(requestBody))
           requestConfig.headers['Content-Type'] = 'application/json'
           requestConfig.transformRequest = []
         } catch (e) {
+          console.log('magic-script-editor', e);
           this.$magicAlert({
             content: 'RequestBody 参数有误,请检查!'
           })
@@ -529,6 +530,40 @@ export default {
         info.ext.eventSource.close()
       })
     },
+    buildRequestBodyData(o) {
+      let requestBody = o
+      let newBody = {}
+      if ('Object' == requestBody.dataType) {
+        let body = {}
+        newBody = this.createJsonData(body, requestBody.children)
+      } else if ('Array' == requestBody.dataType) {
+        let body = []
+        newBody = this.createJsonData(body, requestBody.children, true)
+      }
+      // console.log('buildRequestBodyData', newBody);
+      return newBody
+    },
+    createJsonData(newBody, data, arrayFlag = false) {
+      data.map(item => {
+        let key, value = item.value;
+        if (!arrayFlag) {
+          key = item.name
+        }
+        if ('Object' == item.dataType) {
+          value = {}
+          newBody[key] = this.createJsonData(value, item.children)
+        } else if ('Array' == item.dataType) {
+          value = []
+          newBody[key] = this.createJsonData(value, item.children, true)
+        } else {
+            newBody[key] = (value == 'null' || value == 'undefined') ? null : value
+        }
+        if (arrayFlag) {
+          newBody.push(value)
+        }
+      })
+      return newBody;
+    },
     viewHistory() {
       if (!this.selected) {
         return

+ 346 - 144
magic-editor/src/console/src/components/layout/magic-request.vue

@@ -151,7 +151,17 @@
         </div>
 
         <div v-show="showIndex === 3" class="ma-layout-container">
-          <div ref="bodyEditor" class="ma-body-editor"></div>
+
+          <div style="display: flex; flex-direction: row; height: 100%;">
+            <div style="width: 40%">
+              <div class="header">编辑器(停止编辑2s后同步更新视图属性)</div>
+              <div ref="bodyEditor" class="ma-body-editor"></div>
+            </div>
+            <div style="flex: 1;">
+              <magic-json :jsonData="requestBody" :forceUpdate="forceUpdate"></magic-json>
+            </div>
+          </div>
+
         </div>
         <div v-show="showIndex === 4" class="ma-layout-container" style="overflow: hidden; right: 0">
           <magic-textarea :value.sync="info.description" style="width: 100%; height: 100%; margin: 2px"/>
@@ -166,163 +176,356 @@
 </template>
 
 <script>
-import MagicInput from '@/components/common/magic-input.vue'
-import MagicSelect from '@/components/common/magic-select.vue'
-import MagicCheckbox from '@/components/common/magic-checkbox.vue'
-import MagicTextarea from '@/components/common/magic-textarea.vue'
-import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
-import {formatJson, isVisible} from '@/scripts/utils.js'
-import bus from '@/scripts/bus.js'
-import store from '@/scripts/store.js'
+  import MagicInput from '@/components/common/magic-input.vue'
+  import MagicSelect from '@/components/common/magic-select.vue'
+  import MagicCheckbox from '@/components/common/magic-checkbox.vue'
+  import MagicTextarea from '@/components/common/magic-textarea.vue'
+  import MagicJson from '@/components/common/magic-json.vue'
 
-export default {
-  name: 'MagicRequest',
-  props: {
-    info: Object
-  },
-  components: {
-    MagicInput,
-    MagicSelect,
-    MagicTextarea,
-    MagicCheckbox
-  },
-  data() {
-    return {
-      navs: ['请求参数', '请求Header', '路径变量',  '请求Body', '接口描述'],
-      options: [
-        {value: 'GET', text: 'GET'},
-        {value: 'POST', text: 'POST'},
-        {value: 'PUT', text: 'PUT'},
-        {value: 'DELETE', text: 'DELETE'}
-      ],
-      headerTypes: [
-        {value: 'String', text: 'String'},
-        {value: 'Integer', text: 'Integer'},
-        {value: 'Double', text: 'Double'},
-        {value: 'Long', text: 'Long'},
-        {value: 'Short', text: 'Short'},
-        {value: 'Float', text: 'Float'},
-        {value: 'Byte', text: 'Byte'},
-      ],
-      types: [
-        {value: 'String', text: 'String'},
-        {value: 'Integer', text: 'Integer'},
-        {value: 'Double', text: 'Double'},
-        {value: 'Long', text: 'Long'},
-        {value: 'Short', text: 'Short'},
-        {value: 'Float', text: 'Float'},
-        {value: 'Byte', text: 'Byte'},
-        {value: 'MultipartFile', text: 'MultipartFile'},
-        {value: 'MultipartFiles', text: 'MultipartFiles'}
-      ],
-      validates: [
-        {value: 'pass', text: '不验证'},
-        {value: 'expression', text: '表达式验证'},
-        {value: 'pattern', text: '正则验证'},
-      ],
-      showIndex: 0,
-      parameterIndex: 0,
-      headerIndex: 0,
-      pathIndex: 0,
-      bodyEditor: null,
-      updating: false
-    }
-  },
-  mounted() {
-    bus.$on('update-request-body', (newVal) => {
-      this.initRequestBodyDom()
-      this.bodyEditor && this.bodyEditor.setValue(formatJson(newVal) || newVal || '{\r\n\t\r\n}')
-    })
-  },
-  methods: {
-    layout() {
-      this.$nextTick(() => {
-        if (this.bodyEditor && isVisible(this.$refs.bodyEditor)) {
-          this.bodyEditor.layout()
-        }
-      })
+  import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
+  import {formatJson, isVisible, deepClone} from '@/scripts/utils.js'
+  import bus from '@/scripts/bus.js'
+  import store from '@/scripts/store.js'
+
+  let timeout = null;
+  export default {
+    name: 'MagicRequest',
+    props: {
+      info: Object
     },
-    addRow() {
-      if (!this.info.parameters) {
-        this.$magicAlert({
-          content: '请先添加或选择接口'
-        })
-        return
+    components: {
+      MagicInput,
+      MagicSelect,
+      MagicTextarea,
+      MagicCheckbox,
+      MagicJson
+    },
+    data() {
+      return {
+        navs: ['请求参数', '请求Header', '路径变量', '请求Body', '接口描述'],
+        options: [
+          {value: 'GET', text: 'GET'},
+          {value: 'POST', text: 'POST'},
+          {value: 'PUT', text: 'PUT'},
+          {value: 'DELETE', text: 'DELETE'}
+        ],
+        headerTypes: [
+          {value: 'String', text: 'String'},
+          {value: 'Integer', text: 'Integer'},
+          {value: 'Double', text: 'Double'},
+          {value: 'Long', text: 'Long'},
+          {value: 'Short', text: 'Short'},
+          {value: 'Float', text: 'Float'},
+          {value: 'Byte', text: 'Byte'},
+        ],
+        types: [
+          {value: 'String', text: 'String'},
+          {value: 'Integer', text: 'Integer'},
+          {value: 'Double', text: 'Double'},
+          {value: 'Long', text: 'Long'},
+          {value: 'Short', text: 'Short'},
+          {value: 'Float', text: 'Float'},
+          {value: 'Byte', text: 'Byte'},
+          {value: 'MultipartFile', text: 'MultipartFile'},
+          {value: 'MultipartFiles', text: 'MultipartFiles'}
+        ],
+        validates: [
+          {value: 'pass', text: '不验证'},
+          {value: 'expression', text: '表达式验证'},
+          {value: 'pattern', text: '正则验证'},
+        ],
+        showIndex: 0,
+        parameterIndex: 0,
+        headerIndex: 0,
+        pathIndex: 0,
+        bodyEditor: null,
+        updating: false,
+        bodyName: '',
+        bodyIndex: 0,
+        requestBody: [],
+        forceUpdate: false,
+        editorJson: '{\n\t\n}',
+        bodyEditorFlag: false
       }
-      if (this.showIndex === 0) {
-        this.info.parameters.push({name: '', value: '', description: ''})
-        this.parameterIndex = this.info.parameters.length - 1
-      } else if (this.showIndex === 1) {
-        this.info.headers.push({name: '', value: '', description: ''})
-        this.headerIndex = this.info.headers.length - 1
-      } else if (this.showIndex === 2) {
-        this.info.paths.push({name: '', value: '', description: ''})
-        this.pathIndex = this.info.paths.length - 1
+    },
+    created() {
+
+    },
+    watch: {
+      requestBody: {
+        handler(newVal, oldVal) {
+          // console.log('watch -handler', newVal);
+          if (this.bodyEditorFlag) {
+            this.info.requestBody = JSON.stringify(newVal[0])
+          }
+        },
+        deep: true
       }
-      this.$forceUpdate()
     },
-    removeRow() {
-      if (!this.info.parameters) {
-        this.$magicAlert({
-          content: '请先添加或选择接口'
+    mounted() {
+      let that = this;
+      bus.$on('update-request-body', (newVal) => {
+        // console.log('update-request-body', newVal);
+        this.initRequestBodyDom()
+        if (!newVal || newVal == null) {
+          that.bodyEditorFlag = false
+          that.requestBody = []
+          that.bodyEditor && that.bodyEditor.setValue('{\r\n\t\r\n}')
+          return
+        }
+        try {
+          let body = JSON.parse(newVal)
+          if (body.dataType && body.children) {
+            that.requestBody = [body]
+            that.buildBodyEditorData(body)
+          } else {
+            /**
+             * 旧的json结构,不能直接用,需通过editor转换
+             */
+            this.editorJson = formatJson(newVal)
+            this.bodyEditor && this.bodyEditor.setValue(this.editorJson)
+          }
+
+        } catch (e) {
+          // console.log(e);
+        }
+
+      })
+
+    },
+
+    methods: {
+      buildBodyEditorData(o) {
+        let requestBody = o
+        let newBody = {}
+        if ('Object' == requestBody.dataType) {
+          let body = {}
+          newBody = this.createJsonData(body, requestBody.children)
+        } else if ('Array' == requestBody.dataType) {
+          let body = []
+          newBody = this.createJsonData(body, requestBody.children, true)
+        }
+        // console.log('buildBodyEditorData', newBody);
+        this.editorJson = formatJson(JSON.stringify(newBody))
+        this.bodyEditor && this.bodyEditor.setValue(this.editorJson)
+      },
+      createJsonData(newBody, data, arrayFlag = false) {
+        data.map(item => {
+          let key, value = item.value;
+          if (!arrayFlag) {
+            key = item.name
+          }
+          if ('Object' == item.dataType) {
+            value = {}
+            newBody[key] = this.createJsonData(value, item.children)
+          } else if ('Array' == item.dataType) {
+            value = []
+            newBody[key] = this.createJsonData(value, item.children, true)
+          } else {
+            newBody[key] = (value == 'null' || value == 'undefined') ? null : value
+          }
+          if (arrayFlag) {
+            newBody.push(value)
+          }
         })
-        return
-      }
-      if (this.showIndex === 0) {
-        this.info.parameters.splice(this.parameterIndex, 1)
-        if (this.info.parameters.length === 0) {
-          this.parameterIndex = 0
-          this.addRow()
-        } else if (this.info.parameters.length <= this.parameterIndex) {
-          this.parameterIndex = this.info.parameters.length - 1
+        return newBody;
+      },
+      layout() {
+        this.$nextTick(() => {
+          if (this.bodyEditor && isVisible(this.$refs.bodyEditor)) {
+            this.bodyEditor.layout()
+          }
+        })
+      },
+      addRow() {
+        if (!this.info.parameters) {
+          this.$magicAlert({
+            content: '请先添加或选择接口'
+          })
+          return
         }
-      } else if (this.showIndex === 1) {
-        this.info.headers.splice(this.headerIndex, 1)
-        if (this.info.headers.length === 0) {
-          this.headerIndex = 0
-          this.addRow()
-        } else if (this.info.headers.length <= this.headerIndex) {
+        if (this.showIndex === 0) {
+          this.info.parameters.push({name: '', value: '', description: ''})
+          this.parameterIndex = this.info.parameters.length - 1
+        } else if (this.showIndex === 1) {
+          this.info.headers.push({name: '', value: '', description: ''})
           this.headerIndex = this.info.headers.length - 1
-        }
-      } else if (this.showIndex === 2) {
-        this.info.paths.splice(this.pathIndex, 1)
-        if (this.info.paths.length === 0) {
-          this.pathIndex = 0
-          this.addRow()
-        } else if (this.info.paths.length <= this.pathIndex) {
+        } else if (this.showIndex === 2) {
+          this.info.paths.push({name: '', value: '', description: ''})
           this.pathIndex = this.info.paths.length - 1
         }
-      }
-      this.$forceUpdate()
-    },
-    initRequestBodyDom() {
-      if (this.bodyEditor == null && this.showIndex === 3) {
-        this.bodyEditor = monaco.editor.create(this.$refs.bodyEditor, {
-          minimap: {
-            enabled: false
-          },
-          language: 'json',
-          fixedOverflowWidgets: true,
-          folding: true,
-          wordWrap: 'on',
-          lineDecorationsWidth: 35,
-          theme: store.get('skin') || 'default',
-          value: formatJson(this.info.requestBody) || '{\r\n\t\r\n}'
+        this.$forceUpdate()
+      },
+      removeRow() {
+        if (!this.info.parameters) {
+          this.$magicAlert({
+            content: '请先添加或选择接口'
+          })
+          return
+        }
+        if (this.showIndex === 0) {
+          this.info.parameters.splice(this.parameterIndex, 1)
+          if (this.info.parameters.length === 0) {
+            this.parameterIndex = 0
+            this.addRow()
+          } else if (this.info.parameters.length <= this.parameterIndex) {
+            this.parameterIndex = this.info.parameters.length - 1
+          }
+        } else if (this.showIndex === 1) {
+          this.info.headers.splice(this.headerIndex, 1)
+          if (this.info.headers.length === 0) {
+            this.headerIndex = 0
+            this.addRow()
+          } else if (this.info.headers.length <= this.headerIndex) {
+            this.headerIndex = this.info.headers.length - 1
+          }
+        } else if (this.showIndex === 2) {
+          this.info.paths.splice(this.pathIndex, 1)
+          if (this.info.paths.length === 0) {
+            this.pathIndex = 0
+            this.addRow()
+          } else if (this.info.paths.length <= this.pathIndex) {
+            this.pathIndex = this.info.paths.length - 1
+          }
+        }
+        this.$forceUpdate()
+      },
+      debounce(func, wait = 2000) {
+        // 清除定时器
+        if (timeout !== null) clearTimeout(timeout);
+        timeout = setTimeout(function () {
+          typeof func === 'function' && func();
+        }, wait);
+      },
+      initRequestBodyDom() {
+        if (this.bodyEditor == null && this.showIndex === 3) {
+          this.bodyEditor = monaco.editor.create(this.$refs.bodyEditor, {
+            minimap: {
+              enabled: false
+            },
+            language: 'json',
+            fixedOverflowWidgets: true,
+            folding: true,
+            wordWrap: 'on',
+            lineDecorationsWidth: 35,
+            theme: store.get('skin') || 'default',
+            value: this.editorJson || '{\r\n\t\r\n}'
+          })
+          this.layout()
+          this.bodyEditor.onDidChangeModelContent(() => {
+            // 延时更新,防止还没改完立即更新导致之前填写的属性信息丢失
+            this.debounce(() => {
+              this.bodyEditorFlag = true
+              this.updateRequestBody(this.bodyEditor.getValue())
+            })
+
+          })
+          bus.$on('update-window-size', () => this.layout())
+        }
+      },
+      updateRequestBody(bodyStr) {
+        if (this.bodyEditor.getValue().replace(/\s/g,"") == '{}') {
+          this.requestBody = []
+          return false
+        }
+        try {
+          let body = JSON.parse(bodyStr)
+          let reqBody = []
+          reqBody.push({
+            name: '',
+            value: '',
+            dataType: this.getType(body),
+            validateType: '',
+            expression: '',
+            error: '',
+            description: '',
+            children: this.processBody(body, 0),
+            level: 0,
+            selected: this.requestBody.length > 0 ? false : true
+          })
+
+          this.requestBody = this.valueCopy(reqBody, this.requestBody)
+          this.forceUpdate = !this.forceUpdate;
+        } catch (e) {
+          // console.error(e)
+        }
+      },
+      processBody(body, level) {
+        let arr = [], that = this
+        Object.keys(body).forEach((key) => {
+          let param = {
+            name: 'Array' != this.getType(body) ? key : '',
+            value: 'Object' != that.getType(body[key]) && 'Array' != that.getType(body[key]) ? body[key] : '',
+            dataType: this.getType(body[key]),
+            validateType: '',
+            expression: '',
+            error: '',
+            description: '',
+            children: [],
+            level: level + 1,
+            selected: false
+          }
+          if ('Object' == that.getType(body[key]) || 'Array' == that.getType(body[key])) {
+            let children = that.processBody(body[key], level + 1);
+            param.children = children;
+          }
+          arr.push(param)
+
         })
-        this.layout()
-        this.bodyEditor.onDidChangeModelContent(() => {
-          this.info.requestBody = this.bodyEditor.getValue()
+        return arr;
+      },
+      getType(object) {
+        if (Object.prototype.toString.call(object) == '[object Number]') {
+          return "Integer";
+        }
+        if (Object.prototype.toString.call(object) == '[object String]') {
+          return "String";
+        }
+        if (Object.prototype.toString.call(object) == '[object Boolean]') {
+          return "Boolean";
+        }
+        if (Object.prototype.toString.call(object) == '[object Array]') {
+          return "Array";
+        }
+        if (Object.prototype.toString.call(object) == '[object Object]') {
+          return "Object";
+        }
+        return "String";
+      },
+      valueCopy(newBody, oldBody, arrayFlag = false) {
+        let that = this;
+        newBody.map(item => {
+          let oldItemArr = oldBody.filter(old => {
+            if (old.level == 0 || arrayFlag) {
+              return old
+            }
+            return old.name == item.name
+          })
+          if (oldItemArr.length > 0) {
+            if (item.dataType == 'Object' || item.dataType == 'Array') {
+              item.children = that.valueCopy(item.children, oldItemArr[0].children, item.dataType == 'Array' ? true : false)
+            } else {
+              item.validateType = oldItemArr[0].validateType
+              item.expression = oldItemArr[0].expression
+              item.error = oldItemArr[0].error
+            }
+            item.name = oldItemArr[0].name
+            item.description = oldItemArr[0].description
+            item.selected = oldItemArr[0].selected
+            item.required = oldItemArr[0].required
+          }
+
         })
-        bus.$on('update-window-size', () => this.layout())
+        return deepClone(newBody);
+      }
+
+    },
+    destroyed() {
+      if (this.bodyEditor) {
+        this.bodyEditor.dispose()
       }
-    }
-  },
-  destroyed() {
-    if (this.bodyEditor) {
-      this.bodyEditor.dispose()
     }
   }
-}
 </script>
 
 <style scoped>
@@ -331,5 +534,4 @@ export default {
   height: 100%;
 }
 
-
 </style>

+ 22 - 1
magic-editor/src/console/src/scripts/utils.js

@@ -63,4 +63,25 @@ const requestGroup = (path, group) => {
         transformRequest: []
     })
 }
-export {replaceURL, isVisible, formatJson, formatDate, paddingZero, download, requestGroup}
+// 判断arr是否为一个数组,返回一个bool值
+const isArray = (arr) => {
+  return Object.prototype.toString.call(arr) === '[object Array]';
+}
+
+// 深度克隆
+const deepClone = (obj) => {
+  // 对常见的“非”值,直接返回原来值
+  if([null, undefined, NaN, false].includes(obj)) return obj;
+  if(typeof obj !== "object" && typeof obj !== 'function') {
+    //原始类型直接返回
+    return obj;
+  }
+  var o = isArray(obj) ? [] : {};
+  for(let i in obj) {
+    if(obj.hasOwnProperty(i)){
+      o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
+    }
+  }
+  return o;
+}
+export {replaceURL, isVisible, formatJson, formatDate, paddingZero, download, requestGroup, deepClone}