文档结果支持树状结构

1.x
tanghc 6 years ago
parent 0316004830
commit 0339025123
  1. 28
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java
  2. 159
      sop-website/website-front/assets/lib/layuiext/Form.js
  3. 18
      sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.css
  4. 216
      sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js
  5. 46
      sop-website/website-front/pages/doc/doc.html
  6. 75
      sop-website/website-front/pages/doc/doc.js
  7. 10
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java
  8. 92
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.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;
}
}

@ -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参数则返回表单对应的值<br>
* 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);
});
});

@ -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;
}

@ -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 += '<span class="treeTable-empty"></span>';
}
if (isDir) {
iconHtml += '<i class="layui-icon layui-icon-triangle-d"></i> <i class="layui-icon layui-icon-layer"></i>';
} else {
iconHtml += '<i class="layui-icon layui-icon-file"></i>';
}
iconHtml += '&nbsp;&nbsp;';
var ttype = isDir ? 'dir' : 'file';
var vg = '<span class="treeTable-icon open" lay-tid="' + mId + '" lay-tpid="' + mPid + '" lay-ttype="' + ttype + '">';
return vg + iconHtml + d[param.cols[0][param.treeColIndex].field] + '</span>'
};
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);
});

@ -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: "";
}
</style>
</head>
<body data-find="_5">
@ -228,20 +244,7 @@
</fieldset>
</div>
<div class="site-text">
<table class="layui-table">
<thead>
<tr>
<th class="prop-name">参数</th>
<th class="prop-type">类型</th>
<th>是否必填</th>
<th>最大长度</th>
<th class="prop-desc">描述</th>
<th class="prop-example">示例值</th>
</tr>
</thead>
<tbody id="requestTbody">
</tbody>
</table>
<table id="treeTableReq"></table>
</div>
<div class="site-title">
@ -313,20 +316,7 @@
</fieldset>
</div>
<div class="site-text">
<table class="layui-table">
<thead>
<tr>
<th class="prop-name">参数</th>
<th class="prop-type">类型</th>
<th>是否必填</th>
<th>最大长度</th>
<th class="prop-desc">描述</th>
<th class="prop-example">示例值</th>
</tr>
</thead>
<tbody id="responseTbody">
</tbody>
</table>
<table id="treeTableResp"></table>
</div>
<div class="site-title">

@ -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) {
/*
<tr>
<th class="prop-name">参数</th>
<th class="prop-type">类型</th>
<th>是否必填</th>
<th>最大长度</th>
<th class="prop-desc">描述</th>
<th class="prop-example">示例值</th>
</tr>
*/
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('<tr>\n' +
' <th class="prop-name">'+parameter.name+'</th>\n' +
' <th class="prop-type">'+parameter.type+'</th>\n' +
' <th>'+(parameter.required ? '<span style="color:red;">是</span>' : '否')+'</th>\n' +
' <th>-</th>\n' +
' <th class="prop-desc">'+parameter.description+'</th>\n' +
' <th class="prop-example">' + (parameter.example || parameter['x-example']) +'</th>\n' +
'</tr>')
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 ? '<span style="color: red;">是</span>' : '否';
}}
,{field: 'maxLength', title: '最大长度', width: 100}
,{field: 'description', title: '描述', width: 200}
,{field: 'paramExample', title: '示例值', width: 200}
]]
});
}
function createResponseCode(docItem) {

@ -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<DocParameter> refs;
public String getParamExample() {
return StringUtils.isBlank(example) ? x_example : example;
}
}

@ -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<JSONArray> parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters"));
JSONArray parameters = parametersOptional.orElse(new JSONArray());
List<DocParameter> docParameterList = parameters.toJavaList(DocParameter.class);
List<DocParameter> docParameterList = this.buildRequestParameterList(docInfo, docRoot);
docItem.setRequestParameters(docParameterList);
List<DocParameter> responseParameterList = this.buildResponseParameterList(docInfo, docRoot);
@ -86,23 +87,88 @@ public class SwaggerDocParser implements DocParser {
return title;
}
protected List<DocParameter> buildRequestParameterList(JSONObject docInfo, JSONObject docRoot) {
Optional<JSONArray> parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters"));
JSONArray parameters = parametersOptional.orElse(new JSONArray());
List<DocParameter> 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<String, List<DocParameter>> 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<DocParameter> ret = docParameterList.stream()
.filter(docParameter -> !docParameter.getName().contains("."))
.collect(Collectors.toList());
return ret;
}
protected List<DocParameter> buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) {
String responseRef = getResponseRef(docInfo);
List<DocParameter> respParameterList = new ArrayList<>();
List<DocParameter> respParameterList = Collections.emptyList();
if (StringUtils.isNotBlank(responseRef)) {
JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(responseRef);
JSONObject properties = responseObject.getJSONObject("properties");
Set<String> 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<DocParameter> buildDocParameters(String ref, JSONObject docRoot) {
JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(ref);
JSONObject properties = responseObject.getJSONObject("properties");
Set<String> fieldNames = properties.keySet();
List<DocParameter> 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<DocParameter> 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")))

Loading…
Cancel
Save