diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java index 2ff337d4..1f788f84 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java @@ -30,6 +30,7 @@ public class AlipayController { @ApiOperation(value="获取故事信息", notes = "说明接口的详细信息,介绍,用途,注意事项等。") @ApiMapping(value = "alipay.story.find") + // 参数必须封装在类中 public StoryVO getStory2(StoryParam story) { StoryVO storyVO = new StoryVO(); storyVO.id = 1L; @@ -43,6 +44,24 @@ public class AlipayController { return story; } + /** + * 演示文档表格树 + * @param story + * @return + */ + @ApiOperation(value="获取分类信息", notes = "演示表格树") + @ApiMapping(value = "alipay.category.get") + public Category getCategory(Category story) { + StoryVO storyVO = new StoryVO(); + storyVO.id = 1L; + storyVO.name = "白雪公主"; + storyVO.gmt_create = new Date(); + Category category = new Category(); + category.setCategoryName("娱乐"); + category.setStory(storyVO); + return category; + } + @Data public static class StoryVO { @ApiModelProperty(value = "故事ID", example = "1") @@ -52,4 +71,13 @@ public class AlipayController { @ApiModelProperty(value = "创建时间", example = "2019-04-14 19:02:12") private Date gmt_create; } + + @Data + public static class Category { + @ApiModelProperty(value = "分类名称", example = "娱乐") + private String categoryName; + + @ApiModelProperty(value = "分类故事") + private StoryVO story; + } } diff --git a/sop-website/website-front/assets/lib/layuiext/Form.js b/sop-website/website-front/assets/lib/layuiext/Form.js new file mode 100644 index 00000000..6868bb13 --- /dev/null +++ b/sop-website/website-front/assets/lib/layuiext/Form.js @@ -0,0 +1,159 @@ +/** + * 表单插件,使用方法: + * var myform = layui.Form('formId'); + * myform.setData({...}) + * + * var data = myform.getData(); + * + * var name = myform.getData('name') + */ +layui.define(function (exports) { + + /** + * form = new Form('formId'); + * @param formId + * @constructor + */ + var Form = function (formId) { + this.$form = $('#' + formId); + this.parseForm(this.$form); + } + + Form.prototype = { + fire: function (eventName, data) { + var handler = this['on' + eventName]; + handler && handler(data); + } + , opt: function (attr) { + return this[attr]; + } + /** + * @private + */ + , parseForm: function ($form) { + var that = this; + this.form = $form[0]; + } + + , getEls: function () { + return this.$form.find('input,select,textarea'); + } + + /** + * 同load(data) + */ + , setData: function (data) { + this.loadData(data); + } + + /** + * @private + */ + , loadData: function (data) { + this.reset(); + for (var name in data) { + var val = data[name]; + var $el = this.$form.find('[name="' + name + '"]'); + + $el.each(function () { + var _$el = $(this); + if (_$el.is(':radio') || _$el.is(':checkbox')) { + _$el.prop('checked', false); + var elVal = _$el.val(); + + if ($.isArray(val)) { + for (var i = 0, len = val.length; i < len; i++) { + if (elVal == val[i]) { + _$el.prop('checked', true); + } + } + } else { + _$el.prop('checked', elVal == val); + } + + } else { + _$el.val(val); + } + + }); + } + } + /** + * 清除表单中的值,清除错误信息 + */ + , clear: function () { + this.getEls().each(function () { + var _$el = $(this); + if (_$el.is(':radio') || _$el.is(':checkbox')) { + this.checked = false; + } else { + this.value = ''; + } + + var msg = _$el.data('msg'); + if (msg) { + msg.text(''); + } + + }); + } + /** + * 重置表单 + */ + , reset: function () { + var form = this.form; + if (form && form.reset) { + form.reset(); + } else { + this.clear(); + } + } + /** + * 获取表单数据,如果有fieldName参数则返回表单对应的值
+ * var id = form.getData('id') 等同于 var id = form.getData().id; + * @param {String} fieldName + * @return {Object} 返回JSON对象,如果有fieldName参数,则返回对应的值 + */ + , getData: function (fieldName) { + var that = this; + var data = {}; + + this.getEls().each(function () { + var value = that._getInputVal($(this)); + if (value) { + var name = this.name; + var dataValue = data[name]; + if (dataValue) { + if ($.isArray(dataValue)) { + dataValue.push(value); + } else { + data[name] = [dataValue, value]; + } + } else { + data[name] = value; + } + } + }); + + if (typeof fieldName === 'string') { + return data[fieldName]; + } + + return data; + } + , _getInputVal: function ($input) { + if ($input.is(":radio") || $input.is(":checkbox")) { + if ($input.is(':checked')) { + return $input.val(); + } + } else { + return $input.val(); + } + } + + } + + exports('Form', function (formId) { + return new Form(formId); + }); +}); \ No newline at end of file diff --git a/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.css b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.css new file mode 100644 index 00000000..584b2f8c --- /dev/null +++ b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.css @@ -0,0 +1,18 @@ +.treeTable-empty { + width: 20px; + display: inline-block; +} + +.treeTable-icon { + cursor: pointer; +} + +.treeTable-icon .layui-icon-triangle-d:before { + content: "\e623"; +} + +.treeTable-icon.open .layui-icon-triangle-d:before { + content: "\e625"; + background-color: transparent; +} + diff --git a/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js new file mode 100644 index 00000000..c95dec07 --- /dev/null +++ b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js @@ -0,0 +1,216 @@ +layui.define(['layer', 'table'], function (exports) { + var $ = layui.jquery; + var layer = layui.layer; + var table = layui.table; + + var treetable = { + // 渲染树形表格 + render: function (param) { + // 检查参数 + if (!treetable.checkParam(param)) { + return; + } + // 获取数据 + if (param.data) { + treetable.init(param, param.data); + } else { + // $.getJSON(param.url, param.where, function (res) { + // treetable.init(param, res.data); + // }); + $.ajax({ + url: param.url, + type: param.method || 'get', + data: param.where, + headers: param.headers || {}, + dataType: "json", + success: function (res) { + treetable.init(param, res.data); + } + }) + } + }, + // 渲染表格 + init: function (param, data) { + var mData = []; + var doneCallback = param.done; + var tNodes = data; + // 补上id和pid字段 + for (var i = 0; i < tNodes.length; i++) { + var tt = tNodes[i]; + if (!tt.id) { + if (!param.treeIdName) { + layer.msg('参数treeIdName不能为空', {icon: 5}); + return; + } + tt.id = tt[param.treeIdName]; + } + if (!tt.pid) { + if (!param.treePidName) { + layer.msg('参数treePidName不能为空', {icon: 5}); + return; + } + tt.pid = tt[param.treePidName]; + } + } + + // 对数据进行排序 + var sort = function (s_pid, data) { + for (var i = 0; i < data.length; i++) { + if (data[i].pid == s_pid) { + var len = mData.length; + if (len > 0 && mData[len - 1].id == s_pid) { + mData[len - 1].isParent = true; + } + mData.push(data[i]); + sort(data[i].id, data); + } + } + }; + sort(param.treeSpid, tNodes); + + // 重写参数 + param.url = undefined; + param.data = mData; + param.page = { + count: param.data.length, + limit: param.data.length + }; + param.cols[0][param.treeColIndex].templet = function (d) { + var mId = d.id; + var mPid = d.pid; + var isDir = d.isParent; + var emptyNum = treetable.getEmptyNum(mPid, mData); + var iconHtml = ''; + for (var i = 0; i < emptyNum; i++) { + iconHtml += ''; + } + if (isDir) { + iconHtml += ' '; + } else { + iconHtml += ''; + } + iconHtml += '  '; + var ttype = isDir ? 'dir' : 'file'; + var vg = ''; + return vg + iconHtml + d[param.cols[0][param.treeColIndex].field] + '' + }; + + param.done = function (res, curr, count) { + $(param.elem).next().addClass('treeTable'); + $('.treeTable .layui-table-page').css('display', 'none'); + $(param.elem).next().attr('treeLinkage', param.treeLinkage); + // 绑定事件换成对body绑定 + /*$('.treeTable .treeTable-icon').click(function () { + treetable.toggleRows($(this), param.treeLinkage); + });*/ + if (param.treeDefaultClose) { + treetable.foldAll(param.elem); + } + if (doneCallback) { + doneCallback(res, curr, count); + } + }; + + // 渲染表格 + table.render(param); + }, + // 计算缩进的数量 + getEmptyNum: function (pid, data) { + var num = 0; + if (!pid) { + return num; + } + var tPid; + for (var i = 0; i < data.length; i++) { + if (pid == data[i].id) { + num += 1; + tPid = data[i].pid; + break; + } + } + return num + treetable.getEmptyNum(tPid, data); + }, + // 展开/折叠行 + toggleRows: function ($dom, linkage) { + var type = $dom.attr('lay-ttype'); + if ('file' == type) { + return; + } + var mId = $dom.attr('lay-tid'); + var isOpen = $dom.hasClass('open'); + if (isOpen) { + $dom.removeClass('open'); + } else { + $dom.addClass('open'); + } + $dom.closest('tbody').find('tr').each(function () { + var $ti = $(this).find('.treeTable-icon'); + var pid = $ti.attr('lay-tpid'); + var ttype = $ti.attr('lay-ttype'); + var tOpen = $ti.hasClass('open'); + if (mId == pid) { + if (isOpen) { + $(this).hide(); + if ('dir' == ttype && tOpen == isOpen) { + $ti.trigger('click'); + } + } else { + $(this).show(); + if (linkage && 'dir' == ttype && tOpen == isOpen) { + $ti.trigger('click'); + } + } + } + }); + }, + // 检查参数 + checkParam: function (param) { + if (!param.treeSpid && param.treeSpid != 0) { + layer.msg('参数treeSpid不能为空', {icon: 5}); + return false; + } + + if (!param.treeColIndex && param.treeColIndex != 0) { + layer.msg('参数treeColIndex不能为空', {icon: 5}); + return false; + } + return true; + }, + // 展开所有 + expandAll: function (dom) { + $(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () { + var $ti = $(this).find('.treeTable-icon'); + var ttype = $ti.attr('lay-ttype'); + var tOpen = $ti.hasClass('open'); + if ('dir' == ttype && !tOpen) { + $ti.trigger('click'); + } + }); + }, + // 折叠所有 + foldAll: function (dom) { + $(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () { + var $ti = $(this).find('.treeTable-icon'); + var ttype = $ti.attr('lay-ttype'); + var tOpen = $ti.hasClass('open'); + if ('dir' == ttype && tOpen) { + $ti.trigger('click'); + } + }); + } + }; + + layui.link(layui.cache.base + 'treetable-lay/treetable.css'); + + // 给图标列绑定事件 + $('body').on('click', '.treeTable .treeTable-icon', function () { + var treeLinkage = $(this).parents('.treeTable').attr('treeLinkage'); + if ('true' == treeLinkage) { + treetable.toggleRows($(this), true); + } else { + treetable.toggleRows($(this), false); + } + }); + + exports('treetable', treetable); +}); diff --git a/sop-website/website-front/pages/doc/doc.html b/sop-website/website-front/pages/doc/doc.html index 8d6b0dc0..9e491b3d 100644 --- a/sop-website/website-front/pages/doc/doc.html +++ b/sop-website/website-front/pages/doc/doc.html @@ -32,6 +32,22 @@ .prop-type { width: 50px; } + + .treeTable-empty{margin-left: 20px;} + + /** 修改文件夹图标:未展开 */ + .treeTable-icon .layui-icon-layer:before { + content: ""; + } + + /** 修改文件夹图标:展开 */ + .treeTable-icon.open .layui-icon-layer:before { + content: ""; + } + /* 修改文件图标:*/ + .treeTable-icon .layui-icon-file:before { + content: ""; + } @@ -228,20 +244,7 @@
- - - - - - - - - - - - - -
参数类型是否必填最大长度描述示例值
+
@@ -313,20 +316,7 @@
- - - - - - - - - - - - - -
参数类型是否必填最大长度描述示例值
+
diff --git a/sop-website/website-front/pages/doc/doc.js b/sop-website/website-front/pages/doc/doc.js index 17c85fd3..c504a982 100644 --- a/sop-website/website-front/pages/doc/doc.js +++ b/sop-website/website-front/pages/doc/doc.js @@ -1,5 +1,11 @@ -layui.use(['element', 'form'], function(){ //加载code模块 +layui.config({ + base: '../../assets/lib/layuiext/module/' +}).extend({ + treetable: 'treetable-lay/treetable' +}).use(['element', 'form', 'treetable'], function(){ //加载code模块 var form = layui.form; + var treetable = layui.treetable; + var $ = layui.jquery; // key:module var docItemStore = {}; @@ -85,39 +91,56 @@ layui.use(['element', 'form'], function(){ //加载code模块 } function createRequestParameter(docItem) { - var html = createParameterBody(docItem.requestParameters); - $('#requestTbody').html(html); + var data = buildTreeData(docItem.requestParameters); + createTreeTable('treeTableReq', data); } function createResponseParameter(docItem) { - var html = createParameterBody(docItem.responseParameters); - $('#responseTbody').html(html); + var data = buildTreeData(docItem.responseParameters); + createTreeTable('treeTableResp', data); } - function createParameterBody(parameters) { - /* - - 参数 - 类型 - 是否必填 - 最大长度 - 描述 - 示例值 - - */ - var html = []; + function buildTreeData(parameters, parentId) { + var data = []; + parentId = parentId || 0; for (var i = 0; i < parameters.length; i++) { var parameter = parameters[i]; - html.push('\n' + - ' '+parameter.name+'\n' + - ' '+parameter.type+'\n' + - ' '+(parameter.required ? '' : '否')+'\n' + - ' -\n' + - ' '+parameter.description+'\n' + - ' ' + (parameter.example || parameter['x-example']) +'\n' + - '') + parameter.id = parentId * 100 + (i + 1); + parameter.parentId = parentId; + data.push(parameter); + var refs = parameter.refs; + if (refs && refs.length > 0) { + var childData = buildTreeData(refs, parameter.id); + data = data.concat(childData); + } } - return html.join(''); + return data; + } + + function createTreeTable(id, data) { + var el = '#' + id; + $(el).text(''); + treetable.render({ + elem: el, + treeColIndex: 0, + treeSpid: 0, + treeIdName: 'id', + treePidName: 'parentId', + treeDefaultClose: false, + treeLinkage: false, + data: data, + page: false, + cols: [[ + {field: 'name', title: '参数'} + ,{field: 'type', title: '类型', width: 80} + ,{field: 'required', title: '是否必填', width: 100, templet:function (row) { + return row.required ? '' : '否'; + }} + ,{field: 'maxLength', title: '最大长度', width: 100} + ,{field: 'description', title: '描述', width: 200} + ,{field: 'paramExample', title: '示例值', width: 200} + ]] + }); } function createResponseCode(docItem) { diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java index 17a6c10b..658c36c8 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java @@ -2,6 +2,9 @@ package com.gitee.sop.websiteserver.bean; import com.alibaba.fastjson.annotation.JSONField; import lombok.Data; +import org.apache.commons.lang.StringUtils; + +import java.util.List; /** * 参数 类型 是否必填 最大长度 描述 示例值 @@ -9,8 +12,10 @@ import lombok.Data; */ @Data public class DocParameter { + private String module; private String name; private String type; + private String maxLength = "-"; private boolean required; private String description; private String example = ""; @@ -18,4 +23,9 @@ public class DocParameter { @JSONField(name = "x-example") private String x_example = ""; + private List refs; + + public String getParamExample() { + return StringUtils.isBlank(example) ? x_example : example; + } } diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java index 85000a79..85f1550c 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java @@ -7,9 +7,12 @@ import com.gitee.sop.websiteserver.bean.DocItem; import com.gitee.sop.websiteserver.bean.DocModule; import com.gitee.sop.websiteserver.bean.DocParameter; import org.apache.commons.lang.StringUtils; +import org.springframework.beans.BeanUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -66,9 +69,7 @@ public class SwaggerDocParser implements DocParser { docItem.setDescription(docInfo.getString("description")); String moduleName = this.buildModuleName(docInfo, docRoot); docItem.setModule(moduleName); - Optional parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters")); - JSONArray parameters = parametersOptional.orElse(new JSONArray()); - List docParameterList = parameters.toJavaList(DocParameter.class); + List docParameterList = this.buildRequestParameterList(docInfo, docRoot); docItem.setRequestParameters(docParameterList); List responseParameterList = this.buildResponseParameterList(docInfo, docRoot); @@ -86,23 +87,88 @@ public class SwaggerDocParser implements DocParser { return title; } + protected List buildRequestParameterList(JSONObject docInfo, JSONObject docRoot) { + Optional parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters")); + JSONArray parameters = parametersOptional.orElse(new JSONArray()); + List docParameterList = new ArrayList<>(); + for (int i = 0; i < parameters.size(); i++) { + JSONObject fieldJson = parameters.getJSONObject(i); + DocParameter docParameter = fieldJson.toJavaObject(DocParameter.class); + docParameterList.add(docParameter); + } + + Map> collect = docParameterList.stream() + .filter(docParameter -> docParameter.getName().contains(".")) + .map(docParameter -> { + String name = docParameter.getName(); + int index = name.indexOf('.'); + String module = name.substring(0, index); + String newName = name.substring(index + 1); + DocParameter ret = new DocParameter(); + BeanUtils.copyProperties(docParameter, ret); + ret.setName(newName); + ret.setModule(module); + return ret; + }) + .collect(Collectors.groupingBy(DocParameter::getModule)); + + collect.entrySet().stream() + .forEach(entry -> { + DocParameter moduleDoc = new DocParameter(); + moduleDoc.setName(entry.getKey()); + moduleDoc.setType("object"); + moduleDoc.setRefs(entry.getValue()); + docParameterList.add(moduleDoc); + }); + + List ret = docParameterList.stream() + .filter(docParameter -> !docParameter.getName().contains(".")) + .collect(Collectors.toList()); + + return ret; + } + protected List buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) { String responseRef = getResponseRef(docInfo); - List respParameterList = new ArrayList<>(); + List respParameterList = Collections.emptyList(); if (StringUtils.isNotBlank(responseRef)) { - JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(responseRef); - JSONObject properties = responseObject.getJSONObject("properties"); - Set fieldNames = properties.keySet(); - for (String fieldName : fieldNames) { - JSONObject fieldInfo = properties.getJSONObject(fieldName); - DocParameter respParam = fieldInfo.toJavaObject(DocParameter.class); - respParam.setName(fieldName); - respParameterList.add(respParam); - } + respParameterList = this.buildDocParameters(responseRef, docRoot); } return respParameterList; } + protected List buildDocParameters(String ref, JSONObject docRoot) { + JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(ref); + JSONObject properties = responseObject.getJSONObject("properties"); + Set fieldNames = properties.keySet(); + List docParameterList = new ArrayList<>(); + for (String fieldName : fieldNames) { + /* + { + "description": "分类故事", + "$ref": "#/definitions/StoryVO", + "originalRef": "StoryVO" + } + */ + JSONObject fieldInfo = properties.getJSONObject(fieldName); + DocParameter respParam = fieldInfo.toJavaObject(DocParameter.class); + respParam.setName(fieldName); + docParameterList.add(respParam); + String originalRef = getRef(fieldInfo); + if (StringUtils.isNotBlank(originalRef)) { + List refs = buildDocParameters(originalRef, docRoot); + respParam.setRefs(refs); + } + } + return docParameterList; + } + + private String getRef(JSONObject fieldInfo) { + return Optional.ofNullable(fieldInfo) + .map(jsonObject -> jsonObject.getString("originalRef")) + .orElse(null); + } + protected String getResponseRef(JSONObject docInfo) { String ref = Optional.ofNullable(docInfo.getJSONObject("responses")) .flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("200")))