🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

yapi-plugin-api-doc

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

yapi-plugin-api-doc - npm Package Compare versions

Comparing version
0.0.6
to
0.1.0
+185
controllers/group.js
const baseController = require('controllers/base.js');
const projectModel = require('models/project.js');
const groupModel = require('models/group.js');
const settingModel = require('../models/project');
const groupDocModel = require('../models/group');
const yapi = require('yapi.js');
const homeTheme = require('../theme/home/home.js');
class homeController extends baseController {
constructor(ctx) {
super(ctx);
this.projectModel = yapi.getInst(projectModel);
this.groupModel = yapi.getInst(groupModel);
this.settingModel = yapi.getInst(settingModel);
this.groupDocModel = yapi.getInst(groupDocModel);
}
/**
* 获取html文档
* @param {*} ctx
*/
async index(ctx) {
// 来自 client/constants/variable.js
const PROJECT_COLOR = {
blue: '#2395f1',
green: '#00a854',
yellow: '#ffbf00',
red: '#f56a00',
pink: '#f5317f',
cyan: '#00a2ae',
gray: '#bfbfbf',
purple: '#7265e6'
};
try {
let datas = await this.getAllProjects();
let htmlBody = "";
for (let i = 0; i < datas.length; i++) {
const data = datas[i];
htmlBody += `
<div class="section card-panel card-panel-s">
<div class="group">
<h2>${data.group_name}</h2>
<div style="display: ${data.group_desc ? "" : "none"}">(${data.group_desc})</div>
</div>
<div class="ant-row">`;
for (let j = 0; j < data.projects.length; j++) {
const project = data.projects[j];
htmlBody += `<div class="ant-col-xs-6 ant-col-lg-5 ant-col-xxl-3">
<a class="card-container" href="/api/public/plugin/doc?pid=${project._id}">
<div class="ant-card m-card">
<div class="ant-card-body">
<i class="anticon anticon-${project.icon} ui-logo" style="background-color: ${PROJECT_COLOR[project.color] || PROJECT_COLOR.blue};"></i>
<h4 class="ui-title">${project.name}</h4>
</div>
</div>
</a>
</div>`;
}
htmlBody += `</div></div>`;
}
let html = `<!DOCTYPE html>
<html>
<head>
<title>在线接口文档</title>
<meta charset="utf-8" />
<script src="/prd/assets.js?v=${Math.random()}"></script>
<script>
document.write('<link rel="stylesheet" href="/prd/' + window.WEBPACK_ASSETS['index.js'].css + '" />');
</script>
${homeTheme}
</head>
<body class="fine-api-doc">
${htmlBody || "<h3 style='text-align:center; margin: 50px 0;'>你走错地方啦,这里什么都没有</h3>"}
</body>
</html>
`;
ctx.set('Content-Type', 'text/html');
ctx.body = html;
} catch (error) {
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错,请联系管理员');
}
}
/**
* 排序
* @param {*} ctx
*/
async upIndex(ctx) {
try {
let params = ctx.request.body;
if (this.getRole() != 'admin') {
return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限'));
}
if (!params || !Array.isArray(params)) {
ctx.body = yapi.commons.resReturn(null, 400, '请求参数必须是数组');
}
params.forEach(item => {
if (item.id) {
this.groupDocModel.findOneAndUpdate(item.id, {index: item.index}).then(
res => {},
err => {
yapi.commons.log(err.message, 'error');
}
);
}
});
return (ctx.body = yapi.commons.resReturn('成功!'));
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 400, e.message);
}
}
/**
* 获取目录树
* @param {*} ctx
*/
async get(ctx) {
try {
if (this.getRole() != 'admin') {
return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限'));
}
ctx.body = yapi.commons.resReturn(await this.getAllProjects());
} catch (error) {
ctx.body = yapi.commons.resReturn(null, 502, '获取项目出错');
}
}
async getAllProjects() {
try {
let groups = await this.groupModel.list();
let docGroups = await this.groupDocModel.listAll() || [];
let groupIndexs = {};
for (let i = 0; i < docGroups.length; i++) {
let docGroup = docGroups[i];
groupIndexs[docGroup.group_id] = docGroup;
}
let datas = [];
for (let j = 0; j < groups.length; j++) {
const group = groups[j];
let docGroup = groupIndexs[group._id] || {};
if (docGroup.is_public === false) {
continue;
}
let data = {
_id: group._id,
group_name: group.group_name,
group_desc: group.group_desc,
index: docGroup.index || 0,
projects: []
};
let projects = await this.projectModel.list(group._id);
for (let i = 0; i < projects.length; i++) {
const project = projects[i];
let docSetting = await this.settingModel.findByProject(project._id);
if(docSetting && docSetting.is_public) {
data.projects.push({
_id: project._id,
name: project.name,
desc: project.desc,
icon: project.icon,
color: project.color,
index: docSetting.index
});
}
}
if (data.projects.length > 0) {
data.projects.sort((a, b) => a.index - b.index);
datas.push(data);
}
}
datas.sort((a, b) => a.index - b.index);
return datas;
} catch (error) {
yapi.commons.log(error.message);
return [];
}
}
}
module.exports = homeController;
const baseController = require('controllers/base.js');
const interfaceModel = require('models/interface.js');
const projectModel = require('models/project.js');
const groupModel = require('models/group.js');
const interfaceCatModel = require('models/interfaceCat.js');
const settingModel = require('../models/project');
const yapi = require('yapi.js');
const markdownIt = require('markdown-it');
const markdownItAnchor = require('markdown-it-anchor');
const markdownItTableOfContents = require('markdown-it-table-of-contents');
const defaultTheme = require('../theme/default/defaultTheme.js');
const md = require('../utils/markdown');
class projectDocController extends baseController {
constructor(ctx) {
super(ctx);
this.catModel = yapi.getInst(interfaceCatModel);
this.interModel = yapi.getInst(interfaceModel);
this.projectModel = yapi.getInst(projectModel);
this.groupModel = yapi.getInst(groupModel);
this.settingModel = yapi.getInst(settingModel);
}
async handleListClass(pid, status) {
let result = await this.catModel.list(pid),
newResult = [];
for (let i = 0, item, list; i < result.length; i++) {
item = result[i].toObject();
list = await this.interModel.listByInterStatus(item._id, status);
list = list.sort((a, b) => {
return a.index - b.index;
});
if (list.length > 0) {
item.list = list;
newResult.push(item);
}
}
return newResult;
}
/**
* 获取开放接口的文档
* @param {*} ctx
*/
async getOpenDoc(ctx) {
try {
let pid = ctx.request.query.pid;
if (!pid) {
ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空');
}
let docSetting = await this.settingModel.findByProject(pid);
if (docSetting && docSetting.is_public) {
await this.getDoc(ctx, 'open');
} else {
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
} catch (error) {
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
}
/**
* 获取项目的文档
* @param {请求} ctx
* @param {接口类型} status
*/
async getDoc(ctx, status) {
let pid = ctx.request.query.pid;
status = status || ctx.request.query.status;
if (!pid) {
ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空');
}
let curProject, wikiData;
let tp = '';
try {
curProject = await this.projectModel.get(pid);
try {
const wikiModel = require('../../yapi-plugin-wiki/wikiModel.js');
wikiData = await yapi.getInst(wikiModel).get(pid);
} catch (error) {
}
ctx.set('Content-Type', 'text/html');
const list = await this.handleListClass(pid, status);
tp = await createHtml.bind(this)(list);
return (ctx.body = tp);
} catch (error) {
yapi.commons.log(error, 'error');
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
async function createHtml(list) {
let md = await createMarkdown.bind(this)(list, true);
let markdown = markdownIt({ html: true, breaks: true });
markdown.use(markdownItAnchor); // Optional, but makes sense as you really want to link to something
markdown.use(markdownItTableOfContents, {
markerPattern: /^\[toc\]/im,
includeLevel: [2, 3]
});
let tp = unescape(markdown.render(md));
let left;
let content = tp.replace(
/<div\s+?class="table-of-contents"\s*>[\s\S]*?<\/ul>\s*<\/div>/gi,
function(match) {
left = match;
return '';
}
);
return createHtml5(left || '', content);
}
function createHtml5(left, tp) {
//html5模板
let html = `<!DOCTYPE html>
<html>
<head>
<title>${curProject.name}</title>
<meta charset="utf-8" />
${defaultTheme}
</head>
<body>
<div class="header-box" style="display: ${status === 'open' ? '' : 'none'}">
<div class="breadcrumb">
<span><a href="/api/public/plugin/documents">首页</a>/</span><span>${curProject.name}</span>
</div>
</div>
<div class="g-doc">
${left}
<div id="right" class="content-right">
${tp}
</div>
</div>
</body>
</html>
`;
return html;
}
function createMarkdown(list, isToc) {
//拼接markdown
//模板
let mdTemplate = ``;
try {
// 项目名称信息
mdTemplate += md.createProjectMarkdown(curProject, wikiData);
// 分类信息
mdTemplate += md.createClassMarkdown(curProject, list, isToc);
return mdTemplate;
} catch (e) {
yapi.commons.log(e, 'error');
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
}
}
/**
* 获取设置
* @param {*} ctx
*/
async getSetting(ctx) {
try {
const projectId = ctx.params.project_id;
let setting = await this.settingModel.findByProject(projectId);
if (setting) {
ctx.body = yapi.commons.resReturn(setting);
} else {
ctx.body = yapi.commons.resReturn({
is_public: false,
project_id: projectId
});
}
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
/**
* 保存/更新设置
* @param {*} ctx
*/
async saveSetting(ctx) {
let params = ctx.request.body;
try {
if ((await this.checkAuth(params.project_id, 'project', 'edit')) !== true) {
return (ctx.body = yapi.commons.resReturn(null, 406, '没有权限'));
}
let data = {
project_id: params.project_id,
is_public: params.is_public,
uid: this.getUid()
};
let res = await this.settingModel.findOneAndUpdate(params.project_id, data);
ctx.body = yapi.commons.resReturn(res);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
async upIndex(ctx) {
try {
let params = ctx.request.body;
if (this.getRole() != 'admin') {
return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限'));
}
if (!params || !Array.isArray(params)) {
ctx.body = yapi.commons.resReturn(null, 400, '请求参数必须是数组');
}
params.forEach(item => {
if (item.id) {
this.settingModel.findOneAndUpdate(item.id, {index: item.index}).then(
res => {},
err => {
yapi.commons.log(err.message, 'error');
}
);
}
});
return (ctx.body = yapi.commons.resReturn('成功!'));
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 400, e.message);
}
}
}
module.exports = projectDocController;
const yapi = require('yapi.js');
const baseModel = require('models/base.js');
class groupDocModel extends baseModel {
getName() {
return 'fine_api_document_group';
}
getSchema() {
return {
uid: Number,
// 分组id
group_id: {
type: Number,
required: true
},
// 排序
index: {
type: Number,
required: false,
default: 0
},
//是否公开访问
is_public: {
type: Boolean,
default: true
},
up_time: Number
};
}
save(data) {
data.up_time = yapi.commons.time();
let doc = new this.model(data);
return doc.save();
}
listAll() {
return this.model
.find()
.sort({ index: -1 })
.exec();
}
find(id) {
return this.model.findOne({ _id: id });
}
findByGroup(id) {
return this.model
.findOne({
group_id: id
});
}
/**
* 找到就更新,没找到就新增
* @param {*} group_id
* @param {*} data
*/
findOneAndUpdate(group_id, data) {
data.up_time = yapi.commons.time();
return this.model.findOneAndUpdate(
{
group_id: group_id
},
data,
{
upsert: true
}
);
}
}
module.exports = groupDocModel;
const yapi = require('yapi.js');
const baseModel = require('models/base.js');
class projectDocModel extends baseModel {
getName() {
return 'fine_api_document_project';
}
getSchema() {
return {
uid: Number,
// 项目id
project_id: {
type: Number,
required: true
},
// 排序
index: {
type: Number,
required: false,
default: 0
},
//是否公开访问
is_public: {
type: Boolean,
default: false
},
up_time: Number
};
}
save(data) {
data.up_time = yapi.commons.time();
let doc = new this.model(data);
return doc.save();
}
listAll() {
return this.model
.find()
.sort({ _id: -1 })
.exec();
}
find(id) {
return this.model.findOne({ _id: id });
}
findByProject(id) {
return this.model
.findOne({
project_id: id
});
}
update(id, data) {
data.up_time = yapi.commons.time();
return this.model.update(
{
_id: id
},
data
);
}
/**
* 找到就更新,没找到就新增
* @param {*} project_id
* @param {*} data
*/
findOneAndUpdate(project_id, data) {
data.up_time = yapi.commons.time();
return this.model.findOneAndUpdate(
{
project_id: project_id
},
data,
{
upsert: true
}
);
}
del(id) {
return this.model.remove({
_id: id
});
}
}
module.exports = projectDocModel;
import './index.scss';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import { Form, Switch, message} from "antd";
const FormItem = Form.Item;
const formItemLayout = {
labelCol: {
lg: { span: 5 },
xs: { span: 24 },
sm: { span: 10 }
},
wrapperCol: {
lg: { span: 16 },
xs: { span: 24 },
sm: { span: 12 }
},
className: "form-item"
};
export default class FineDocPage extends Component {
constructor(props) {
super(props);
this.state = {
setting: {},
loading: true
};
}
static propTypes = {
match: PropTypes.object
};
async componentDidMount() {
await this.getSetting();
}
async getSetting() {
let id = this.props.match.params.id;
let result = await axios.get('/api/plugin/fine/document/setting?project_id=' + id);
if (result.data.errcode === 0) {
if (result.data.data) {
this.setState({
setting: result.data.data
});
}
}
this.setState({
loading: false
});
}
onChange = v => {
this.setState({
loading: true
});
this.onSave(v);
}
async onSave(v) {
let result = {};
let id = this.props.match.params.id;
let params = {
project_id: id,
is_public: v
};
result = await axios.post("/api/plugin/fine/document/setting/save", params);
if (
result.data &&
result.data.errcode &&
result.data.errcode !== 40011
) {
message.error(result.data.errmsg);
} else {
message.success("保存成功");
this.setState({
setting: params
});
}
this.setState({
loading: false
});
}
render() {
const { match } = this.props;
return (
<div className="g-row">
<FormItem
label="是否开放公开接口"
{...formItemLayout}
>
<Switch
checked={this.state.setting.is_public}
loading={this.state.loading}
onChange={this.onChange}
checkedChildren="开"
unCheckedChildren="关"
/>
<span
className="yapi-public-dock"
style={{
display: this.state.setting.is_public ? "" : "none"
}}>
<a target="_blank" href={"/api/public/plugin/doc?pid=" + match.params.id}>
{`点击访问无需登录地址 ${window.location.origin}/api/public/plugin/doc?pid=${match.params.id}`}
</a>
</span>
</FormItem>
<iframe className="m-panel yapi-doc" src={"/api/plugin/doc?pid=" + match.params.id} width="100%" style={{minHeight: "calc(100vh - 156px)"}}></iframe>
</div>
);
}
}
.yapi-doc.m-panel {
border: none;
padding-top: 0;
}
.yapi-public-dock {
margin-left: 15px;
}
import React, { Component } from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import PropTypes from 'prop-types';
import { Layout, Tooltip, Card, Icon, Row, Tree} from "antd";
const TreeNode = Tree.TreeNode;
const { Content, Sider } = Layout;
import { arrayChangeIndex } from 'client/common.js';
import { setBreadcrumb } from 'client/reducer/modules/user';
import constants from 'client/constants/variable.js';
import './index.scss';
@connect(
null,
{
setBreadcrumb
}
)
export default class FineDocSettingPage extends Component {
static propTypes = {
setBreadcrumb: PropTypes.func
};
constructor(props) {
super(props);
this.state = {
projectData: [],
expands: null
};
}
async componentWillMount() {
this.props.setBreadcrumb([{ name: '接口文档' }]);
}
async componentDidMount() {
await this.getSetting();
}
async getSetting() {
let result = await axios.get('/api/plugin/fine/document');
if (result.data.errcode === 0) {
if (result.data.data) {
this.setState({
projectData: result.data.data
});
}
}
}
onDrop = async e => {
console.log("drop", e)
const fromNode = e.dragNode.props.eventKey;
const toNode = e.node.props.eventKey;
const dropPos = e.node.props.pos.split('-');
const dropIndex = Number(dropPos[dropPos.length - 1]);
const dragPos = e.dragNode.props.pos.split('-');
const dragIndex = Number(dragPos[dragPos.length - 1]);
const { projectData } = this.state;
// 移动分组
if (fromNode.indexOf("group_") > -1 && toNode.indexOf("group_") > -1) {
let changes = arrayChangeIndex(projectData, dragIndex, dropIndex);
axios.post("/api/plugin/fine/document/group/up_index", changes);
let oldDatas = {};
for (let i = 0; i < projectData.length; i++) {
const oldData = projectData[i];
oldDatas[oldData._id] = oldData;
}
this.setState({
projectData: changes.map(v => {
return Object.assign({}, oldDatas[v.id], v);
})
})
}
// 移动项目
if (fromNode.indexOf("project_") > -1 && toNode.indexOf("project_") > -1) {
const dropGroupIndex = Number(dropPos[1]);
// 相同分组下的才能移动
if (dropGroupIndex === Number(dragPos[1])) {
let projectChanges = arrayChangeIndex(projectData[dropGroupIndex].projects, dragIndex, dropIndex);
axios.post("/api/plugin/fine/document/up_index", projectChanges);
let oldDatas = {};
for (let i = 0; i < projectData[dropGroupIndex].projects.length; i++) {
const oldData = projectData[dropGroupIndex].projects[i];
oldDatas[oldData._id] = oldData;
}
projectData[dropGroupIndex].projects = projectChanges.map(v => {
return Object.assign({}, oldDatas[v.id], v);
});
this.setState({
projectData: projectData
})
}
}
};
render() {
const { projectData } = this.state;
const itemProjectCreate = item => {
return (
<TreeNode
title={
<div
className="container-title"
>
<Icon
type={item.icon || 'star-o'}
className="project-logo"
style={{
marginRight: 5,
backgroundColor: constants.PROJECT_COLOR[item.color] || constants.PROJECT_COLOR.blue
}}
/>
{item.name}
</div>
}
key={"project_" + item._id}
/>
);
};
return (
<div className="g-row fine-doc-setting">
<Layout style={{ minHeight: 'calc(100vh - 156px)', marginLeft: '24px', marginTop: '24px' }}>
<Sider style={{ height: '100%' }} width={300}>
<div className="left-menu">
<Row className="tabs-large">
<div className="test-icon-style">
<h3>
文档分组列表&nbsp;<Tooltip placement="top" title="拖动调整文档顺序">
<Icon type="question-circle-o" />
</Tooltip>
</h3>
</div>
</Row>
<Tree
className="draggable-tree"
draggable
blockNode
onDrop={this.onDrop}
>
{projectData.map(item => {
return (
<TreeNode
title={
<div
className="container-title"
>
<Icon type="folder" style={{ marginRight: 5 }} />
{item.group_name}
</div>
}
key={"group_" + item._id}
className={"interface-item-nav"}
>
{item.projects.map(itemProjectCreate)}
</TreeNode>
);
})}
</Tree>
</div>
</Sider>
<Layout className="test-content">
<Content style={{
padding: 24,
textAlign: "center"
}}>
<Card title="接口文档地址">
<Card title="内部接口文档地址" type="inner">
<a target="_blank" href={"/api/plugin/documents"}>
{`${window.location.origin}/api/plugin/documents`}
</a>
</Card>
<Card title="无需登录文档地址" type="inner" style={{ marginTop: 16 }}>
<a target="_blank" href={"/api/public/plugin/documents"}>
{`${window.location.origin}/api/public/plugin/documents`}
</a>
</Card>
</Card>
</Content>
</Layout>
</Layout>
</div>
);
}
}
.fine-doc-setting {
.left-menu h3 {
line-height: 40px;
border-bottom: 1px solid #ddd;
padding-left: 10px;
}
.project-logo {
border-radius: 50%;
color: #fff;
background-color: #2395f1;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}
}
@charset "UTF-8";
html,
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote {
margin: 0;
padding: 0;
font-weight: normal;
-webkit-font-smoothing: antialiased;
}
/* 设置滚动条的样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
/* 外层轨道 */
::-webkit-scrollbar-track {
-webkit-box-shadow: inset006pxrgba(255, 0, 0, 0.3);
background: rgba(0, 0, 0, 0.1);
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
border-radius: 4px;
background: rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.5);
}
::-webkit-scrollbar-thumb:window-inactive {
background: rgba(0, 0, 0, 0.2);
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
font-size: 13px;
line-height: 25px;
color: #393838;
position: relative;
}
table {
margin: 10px 0 15px 0;
border-collapse: collapse;
}
td,
th {
border: 1px solid #ddd;
padding: 3px 10px;
}
th {
padding: 5px 10px;
}
a, a:link, a:visited {
color: #34495e;
text-decoration: none;
}
a:hover, a:focus {
color: #59d69d;
text-decoration: none;
}
a img {
border: none;
}
p {
padding-left: 10px;
margin-bottom: 9px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #404040;
line-height: 36px;
}
h1 {
color: #2c3e50;
font-weight: 600;
margin-bottom: 16px;
font-size: 32px;
padding-bottom: 16px;
border-bottom: 1px solid #ddd;
line-height: 50px;
}
h2 {
font-size: 28px;
padding-top: 10px;
padding-bottom: 10px;
}
h3 {
clear: both;
font-weight: 400;
margin-top: 20px;
margin-bottom: 20px;
border-bottom: 3px solid #59d69d;
padding-left: 8px;
font-size: 18px;
}
h4 {
font-size: 16px;
padding-left: 8px;
border-left: 3px solid #59d69d;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 13px;
}
hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family: georgia, serif;
font-style: italic;
}
blockquote:before {
font-size: 40px;
margin-left: -10px;
font-family: georgia, serif;
color: #eee;
}
blockquote p {
font-size: 14px;
font-weight: 300;
line-height: 18px;
margin-bottom: 0;
font-style: italic;
}
code,
pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
code {
background-color: #fee9cc;
color: rgba(0, 0, 0, 0.75);
padding: 1px 3px;
font-size: 12px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
pre {
display: block;
padding: 14px;
margin: 0 0 18px;
line-height: 16px;
font-size: 11px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
background: #f6f6f6;
}
pre code {
background-color: #f6f6f6;
color: #737373;
font-size: 11px;
padding: 0;
}
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
* {
-webkit-print-color-adjust: exact;
}
@media print {
body,
code,
pre code,
h1,
h2,
h3,
h4,
h5,
h6 {
color: black;
}
table,
pre {
page-break-inside: avoid;
}
}
html,
body {
height: 100%;
}
.table-of-contents, #left {
position: fixed;
top: 40px;
left: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
width: 260px;
}
.table-of-contents > ul > li > a {
font-size: 20px;
margin-bottom: 16px;
margin-top: 16px;
}
#left .table-of-contents>ul>li>ul>li>a {
font-size: 18px;
}
#left .table-of-contents>ul>li>ul>li>a:before {
content: "+";
}
#left .table-of-contents>ul>li>ul>li.open>a:before {
content: "-";
}
#left .table-of-contents>ul>li>ul>li>ul {
display: none;
}
#left .table-of-contents>ul>li>ul>li.open>ul {
display: block;
}
#left .table-of-contents>ul>li>ul>li>ul>li>a.active {
color: #59d69d;
}
.table-of-contents ul {
overflow: auto;
margin: 0px;
height: 100%;
padding: 0px 0px;
box-sizing: border-box;
list-style-type: none;
}
.table-of-contents ul li {
padding-left: 20px;
}
.table-of-contents a {
padding: 2px 0px;
display: block;
text-decoration: none;
}
.content-right {
max-width: 700px;
margin-left: 290px;
padding-left: 70px;
flex-grow: 1;
}
.content-right h2:target {
padding-top: 35px;
}
body > p {
margin-left: 30px;
}
body > table {
margin-left: 30px;
}
body > pre {
margin-left: 30px;
}
.curProject {
position: fixed;
top: 20px;
font-size: 25px;
color: black;
margin-left: -240px;
width: 240px;
padding: 5px;
line-height: 25px;
box-sizing: border-box;
}
.g-doc {
padding-top: 50px;
display: flex;
}
.curproject-name {
font-size: 42px;
}
.m-header {
background: #32363a;
height: 56px;
line-height: 56px;
padding-left: 60px;
display: flex;
align-items: center;
position: fixed;
z-index: 9;
top: 0;
left: 0;
right: 0;
}
.m-header .title {
font-size: 22px;
color: #fff;
font-weight: normal;
-webkit-font-smoothing: antialiased;
margin: 0;
margin-left: 16px;
padding: 0;
line-height: 56px;
border: none;
}
.m-header .nav {
color: #fff;
font-size: 16px;
position: absolute;
right: 32px;
top: 0;
}
.m-header .nav a {
color: #fff;
margin-left: 16px;
padding: 8px;
transition: color .2s;
}
.m-header .nav a:hover {
color: #59d69d;
}
.m-footer {
border-top: 1px solid #ddd;
padding-top: 16px;
padding-bottom: 16px;
}
/*# sourceMappingURL=defaultTheme.css.map */
.m-card .ui-logo {
width: 100px;
height: 100px;
border-radius: 50%;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, 0.24rem);
font-size: 50px;
color: #fff;
background-color: #2395f1;
line-height: 100px;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}
.header-box {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #32363a;
}
.header-box .breadcrumb {
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0 0 0 20px;
padding: 0;
color: rgba(0,0,0,0.65);
font-variant: tabular-nums;
line-height: 40px;
list-style: none;
-webkit-font-feature-settings: 'tnum';
font-feature-settings: 'tnum';
color: rgba(0,0,0,0.45);
font-size: 16px;
}
.header-box .breadcrumb span {
color: #fff;
padding-right: 10px;
}
.header-box .breadcrumb span a {
color: #fff;
padding-right: 10px;
}
.header-box .breadcrumb span a:hover {
color: #2395f1;
}
const fs = require('fs');
const sysPath = require('path');
const css = fs.readFileSync(sysPath.join(__dirname, './defaultTheme.css'));
module.exports = '<style>' + css + '</style>';
@charset "UTF-8";
.fine-api-doc {
background-color: #ffffff;
}
.fine-api-doc .group {
background: #eee;
height: 64px;
line-height: 64px;
border-radius: 4px;
text-align: right;
padding: 0 10px;
font-weight: bold;
margin-bottom: 15px;
display: flex;
align-items: center;
color: rgba(39, 56, 72, 0.85);
font-weight: 500;
}
.fine-api-doc .group h2 {
font-size: 26px;
padding-right: 10px;
}
const fs = require('fs');
const sysPath = require('path');
const css = fs.readFileSync(sysPath.join(__dirname, './home.css'));
module.exports = '<style>' + css + '</style>';
const schema = require('../../../common/shema-transformTo-table.js');
const _ = require('underscore');
const json_parse = function(json) {
try {
return JSON.parse(json);
} catch (err) {
return {};
}
};
// 处理字符串换行
const handleWrap = str => {
return _.isString(str) ? str.replace(/\n/gi, '<br/>') : str;
};
const messageMap = {
desc: '备注',
default: '实例',
maximum: '最大值',
minimum: '最小值',
maxItems: '最大数量',
minItems: '最小数量',
maxLength: '最大长度',
minLength: '最小长度',
uniqueItems: '元素是否都不同',
itemType: 'item 类型',
format: 'format',
enum: '枚举',
enumDesc: '枚举备注',
mock: 'mock'
};
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name'
},
{
title: '类型',
dataIndex: 'type',
key: 'type'
},
{
title: '是否必须',
dataIndex: 'required',
key: 'required'
},
{
title: '默认值',
dataIndex: 'default',
key: 'default'
},
{
title: '备注',
dataIndex: 'desc',
key: 'desc'
},
{
title: '其他信息',
dataIndex: 'sub',
key: 'sub'
}
];
function escapeStr(str, isToc) {
return isToc ? escape(str) : str;
}
function createBaseMessage(basepath, inter) {
// 基本信息
let baseMessage = `#### 基本信息\n\n**Path:** ${basepath + inter.path}\n\n**Method:** ${
inter.method
}\n\n**接口描述:**\n${_.isUndefined(inter.desc) ? '' : inter.desc}\n`;
return baseMessage;
}
function createReqHeaders(req_headers) {
// Request-headers
if (req_headers && req_headers.length) {
let headersTable = `**Headers**\n\n`;
headersTable += `| 参数名称 | 参数值 | 是否必须 | 示例 | 备注 |\n| ------------ | ------------ | ------------ | ------------ | ------------ |\n`;
for (let j = 0; j < req_headers.length; j++) {
headersTable += `| ${req_headers[j].name || ''} | ${req_headers[j].value || ''} | ${
req_headers[j].required == 1 ? '是' : '否'
} | ${handleWrap(req_headers[j].example) || ''} | ${handleWrap(req_headers[j].desc) ||
''} |\n`;
}
return headersTable;
}
return '';
}
function createPathParams(req_params) {
if (req_params && req_params.length) {
let paramsTable = `**路径参数**\n\n`;
paramsTable += `| 参数名称 | 示例 | 备注 |\n| ------------ | ------------ | ------------ |\n`;
for (let j = 0; j < req_params.length; j++) {
paramsTable += `| ${req_params[j].name || ''} | ${handleWrap(req_params[j].example) ||
''} | ${handleWrap(req_params[j].desc) || ''} |\n`;
}
return paramsTable;
}
return '';
}
function createReqQuery(req_query) {
if (req_query && req_query.length) {
let headersTable = `**Query**\n\n`;
headersTable += `| 参数名称 | 是否必须 | 示例 | 备注 |\n| ------------ | ------------ | ------------ | ------------ |\n`;
for (let j = 0; j < req_query.length; j++) {
headersTable += `| ${req_query[j].name || ''} | ${
req_query[j].required == 1 ? '是' : '否'
} | ${handleWrap(req_query[j].example) || ''} | ${handleWrap(req_query[j].desc) ||
''} |\n`;
}
return headersTable;
}
return '';
}
function createReqBody(req_body_type, req_body_form, req_body_other, req_body_is_json_schema) {
if (req_body_type === 'form' && req_body_form.length) {
let bodyTable = `**Body**\n\n`;
bodyTable += `| 参数名称 | 参数类型 | 是否必须 | 示例 | 备注 |\n| ------------ | ------------ | ------------ | ------------ | ------------ |\n`;
let req_body = req_body_form;
for (let j = 0; j < req_body.length; j++) {
bodyTable += `| ${req_body[j].name || ''} | ${req_body[j].type || ''} | ${
req_body[j].required == 1 ? '是' : '否'
} | ${req_body[j].example || ''} | ${req_body[j].desc || ''} |\n`;
}
return `${bodyTable}\n\n`;
} else if (req_body_other) {
if (req_body_is_json_schema) {
let reqBody = createSchemaTable(req_body_other);
return `**Body**\n\n` + reqBody;
} else {
//other
return `**Body**\n\n` + '```javascript' + `\n${req_body_other || ''}` + '\n```';
}
}
return '';
}
function tableHeader(columns) {
let header = ``;
columns.map(item => {
header += `<th key=${item.key}>${item.title}</th>`;
});
return header;
}
function handleObject(text) {
if (!_.isObject(text)) {
return text;
}
let tpl = ``;
Object.keys(text || {}).map((item, index) => {
let name = messageMap[item];
let value = text[item];
tpl += _.isUndefined(text[item])
? ''
: `<p key=${index}><span style="font-weight: '700'">${name}: </span><span>${value.toString()}</span></p>`;
});
return tpl;
}
function tableCol(col, columns, level) {
let tpl = ``;
columns.map((item, index) => {
let dataIndex = item.dataIndex;
let value = col[dataIndex];
value = _.isUndefined(value) ? '' : value;
let text = ``;
switch (dataIndex) {
case 'sub':
text = handleObject(value);
break;
case 'type':
text =
value === 'array'
? `<span>${col.sub ? col.sub.itemType || '' : 'array'} []</span>`
: `<span>${value}</span>`;
break;
case 'required':
text = value ? '必须' : '非必须';
break;
case 'desc':
text = _.isUndefined(col.childrenDesc)
? `<span style="white-space: pre-wrap">${value}</span>`
: `<span style="white-space: pre-wrap">${col.childrenDesc}</span>`;
break;
case 'name':
text = `<span style="padding-left: ${20 * level}px"><span style="color: #8c8a8a">${
level > 0 ? '├─' : ''
}</span> ${value}</span>`;
break;
default:
text = value;
}
tpl += `<td key=${index}>${text}</td>`;
});
return tpl;
}
function tableBody(dataSource, columns, level) {
// 按照columns的顺序排列数据
let tpl = ``;
dataSource.map(col => {
let child = null;
tpl += `<tr key=${col.key}>${tableCol(col, columns, level)}</tr>`;
if (!_.isUndefined(col.children) && _.isArray(col.children)) {
let index = level + 1;
child = tableBody(col.children, columns, index);
}
tpl += child ? `${child}` : ``;
});
return tpl;
}
function createSchemaTable(body) {
let template = ``;
let dataSource = schema.schemaTransformToTable(json_parse(body));
template += `<table>
<thead class="ant-table-thead">
<tr>
${tableHeader(columns)}
</tr>
</thead>`;
template += `<tbody className="ant-table-tbody">${tableBody(dataSource, columns, 0)}
</tbody>
</table>
`;
return template;
}
function createResponse(res_body, res_body_is_json_schema, res_body_type) {
let resTitle = `\n#### 返回数据\n\n`;
if (res_body) {
if (res_body_is_json_schema && res_body_type === 'json') {
let resBody = createSchemaTable(res_body);
return resTitle + resBody;
} else {
let resBody = '```javascript' + `\n${res_body || ''}\n` + '```';
return resTitle + resBody;
}
}
return '';
}
function createInterMarkdown(basepath, listItem, isToc) {
let mdTemplate = ``;
const toc = `[TOC]\n\n`;
// 接口名称
mdTemplate += `\n### ${escapeStr(`${listItem.title}\n<a class="inter-title" id=${listItem.title}> </a>`, isToc)}\n`;
isToc && (mdTemplate += toc);
// 基本信息
mdTemplate += createBaseMessage(basepath, listItem);
// Request
mdTemplate += `\n#### 请求参数\n`;
// Request-headers
mdTemplate += createReqHeaders(listItem.req_headers);
// Request-params
mdTemplate += createPathParams(listItem.req_params);
// Request-query
mdTemplate += createReqQuery(listItem.req_query);
// Request-body
mdTemplate += createReqBody(
listItem.req_body_type,
listItem.req_body_form,
listItem.req_body_other,
listItem.req_body_is_json_schema
);
// Response
// Response-body
mdTemplate += createResponse(
listItem.res_body,
listItem.res_body_is_json_schema,
listItem.res_body_type
);
return mdTemplate;
}
function createProjectMarkdown(curProject, wikiData) {
let mdTemplate = ``;
// 项目名、项目描述
mdTemplate += `\n # ${escapeStr(curProject.name, true)} \n ${curProject.desc || ''}\n`;
mdTemplate += `[TOC]\n\n`
// 增加公共wiki信息展示
mdTemplate += wikiData ? `\n#### 公共信息\n${wikiData.desc || ''}\n` : '';
return mdTemplate;
}
function createGroupMarkdown(curGroup) {
let mdTemplate = ``;
// 分组名、分组描述
let title = `<h1 class="cur-group-name"> ${curGroup.group_name} </h1>`;
mdTemplate += `\n ${title} \n ${curGroup.group_desc || ''}\n\n`;
return mdTemplate;
}
function createClassMarkdown(curProject, list, isToc) {
let mdTemplate = ``;
const toc = `[TOC]\n\n`;
list.map(item => {
// 分类名称
mdTemplate += `\n## ${escapeStr(item.name, isToc)}\n`;
isToc && (mdTemplate += toc);
for (let i = 0; i < item.list.length; i++) {
//循环拼接 接口
// 接口内容
mdTemplate += createInterMarkdown(curProject.basepath, item.list[i], isToc);
}
});
return mdTemplate;
}
let r = {
createInterMarkdown,
createGroupMarkdown,
createProjectMarkdown,
createClassMarkdown
};
module.exports = r;
+19
-3

@@ -1,11 +0,27 @@

import DocPage from './page';
import FineDocPage from './page/Doc';
import FineDocSettingPage from './page/Setting';
module.exports = function() {
this.bindHook('sub_nav', function(app) {
app.doc = {
app.fineDocPage = {
name: '接口文档',
path: '/project/:id/doc',
component: DocPage
component: FineDocPage
};
});
this.bindHook('header_menu', function (menu) {
menu.fineDocSettingPage = {
path: '/document/api',
name: '接口文档',
icon: 'file-text',
adminFlag: true
}
})
this.bindHook('app_route', function (app) {
app.fineDocSettingPage = {
path: '/document/api',
component: FineDocSettingPage
}
})
};
+1
-1
{
"name": "yapi-plugin-api-doc",
"version": "0.0.6",
"version": "0.1.0",
"description": "在标签页中加入查看接口文档",

@@ -5,0 +5,0 @@ "main": "index.js",

# yapi-plugin-api-doc
根据内置导出数据插件,在分组的项目导航栏中加入*接口文档*标签,点击可以直接查看接口文档。
根据内置导出数据插件,在分组的某个项目导航栏中加入*接口文档*标签,点击可以直接查看接口文档。

@@ -9,3 +9,19 @@ 第一步:在config.json 这层目录下运行 ```yapi plugin --name yapi-plugin-api-doc``` 安装插件

## 更新
1. 提供开放接口,不需要登录可以直接查看公开接口的文档(需要支持public接口版本的YAPI)。
## 特性
### 项目接口文档
点击项目导航栏的*接口文档*,可以设置查看当前分组所有接口的文档。页面顶部有一个开关,用来设置当前分组的开放接口文档是否公开可见,打开开关之后可以看到公开访问地址。
每个接口编辑页面都可以设置当前接口是否为开放接口,如果是开放接口可以通过这边的设置来觉得是否开放为无需登录即可访问的接口。
### 全局接口文档
对于一个工程中有很多分组,一个分组又会有很多项目,这些分组和项目之间对外提供文档是需要一定顺序的。
点击右上角的头像会出现下拉选项,点击*接口文档*即可进入全局文档的设置界面。
当前支持在目录树上进行拖拽排序操作,轻松搞定文档的顺序。通过右侧给出的文档地址可以轻松访问。
### 公开访问
目前的YAPI不支持用户不登录进行访问操作,需要支持public接口的版本才能免登录公开访问

@@ -1,15 +0,38 @@

const controller = require('./controller');
const projectDocController = require('./controllers/project');
const groupDocController = require('./controllers/group');
module.exports = function() {
this.bindHook('add_router', function(addRouter) {
// 项目接口文档设置
addRouter({
// 获取doc信息
controller: controller,
controller: projectDocController,
method: 'get',
path: 'fine/document/setting',
action: 'getSetting'
});
addRouter({
controller: projectDocController,
method: 'post',
path: 'fine/document/setting/save',
action: 'saveSetting'
});
// 更新排序
addRouter({
controller: projectDocController,
method: 'post',
path: 'fine/document/up_index',
action: 'upIndex'
});
// 获取项目接口文档
addRouter({
controller: projectDocController,
method: 'get',
path: 'doc',
action: 'getDoc'
});
// 获取开放项目接口文档
addRouter({
// 获取doc信息
controller: controller,
controller: projectDocController,
prefix: "/public",

@@ -20,11 +43,47 @@ method: 'get',

});
// 获取开放项目接口文档,同documents
addRouter({
// 获取doc信息
controller: controller,
controller: groupDocController,
prefix: "/public",
method: 'get',
path: 'document',
action: 'getDocument'
action: 'index'
});
// 获取开放项目接口文档
addRouter({
controller: groupDocController,
prefix: "/public",
method: 'get',
path: 'documents',
action: 'index'
});
// 获取开放项目接口文档
addRouter({
controller: groupDocController,
method: 'get',
path: 'documents',
action: 'index'
});
// 全局接口文档目录树
addRouter({
controller: groupDocController,
method: 'get',
path: 'fine/document',
action: 'get'
});
// 目录树排序
addRouter({
controller: groupDocController,
method: 'post',
path: 'fine/document/group/up_index',
action: 'upIndex'
});
});
};
const baseController = require('controllers/base.js');
const interfaceModel = require('models/interface.js');
const projectModel = require('models/project.js');
const groupModel = require('models/group.js');
const interfaceCatModel = require('models/interfaceCat.js');
const yapi = require('yapi.js');
const markdownIt = require('markdown-it');
const markdownItAnchor = require('markdown-it-anchor');
const markdownItTableOfContents = require('markdown-it-table-of-contents');
const defaultTheme = require('./defaultTheme.js');
const md = require('./markdown');
class exportController extends baseController {
constructor(ctx) {
super(ctx);
this.catModel = yapi.getInst(interfaceCatModel);
this.interModel = yapi.getInst(interfaceModel);
this.projectModel = yapi.getInst(projectModel);
this.groupModel = yapi.getInst(groupModel);
}
async handleListClass(pid, status) {
let result = await this.catModel.list(pid),
newResult = [];
for (let i = 0, item, list; i < result.length; i++) {
item = result[i].toObject();
list = await this.interModel.listByInterStatus(item._id, status);
list = list.sort((a, b) => {
return a.index - b.index;
});
if (list.length > 0) {
item.list = list;
newResult.push(item);
}
}
return newResult;
}
handleExistId(data) {
function delArrId(arr, fn) {
if (!Array.isArray(arr)) return;
arr.forEach(item => {
delete item._id;
delete item.__v;
delete item.uid;
delete item.edit_uid;
delete item.catid;
delete item.project_id;
if (typeof fn === 'function') fn(item);
});
}
delArrId(data, function(item) {
delArrId(item.list, function(api) {
delArrId(api.req_body_form);
delArrId(api.req_params);
delArrId(api.req_query);
delArrId(api.req_headers);
if (api.query_path && typeof api.query_path === 'object') {
delArrId(api.query_path.params);
}
});
});
return data;
}
/**
* 获取开放文档列表
* @param {*} ctx
*/
async getDocument(ctx) {
let group_id = ctx.params.group;
let groupData = await this.groupModel.get(group_id);
let tp = '', wikInst = null;
try {
let htmlBody = '';
if (!groupData) {
htmlBody = '404 找不到对应的文档'
return renderHtml(htmlBody);
}
try {
const wikiModel = require('../../exts/yapi-plugin-wiki/wikiModel.js');
wikInst = await yapi.getInst(wikiModel);
} catch (error) {
}
let result = await this.projectModel.list(group_id);
let projects = [], wikiDatas = [];
for (let i = 0, item, list; i < result.length; i++) {
item = result[i].toObject();
list = await this.handleListClass(item._id, "open");
if (list.length > 0) {
if (wikInst) {
wikiDatas.push(await wikInst.get(item._id));
}
projects.push({
item,
list
});
}
}
tp = await createHtml.bind(this)(projects, wikiDatas);
return (ctx.body = tp);
} catch (error) {
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
function renderHtml (htmlBody) {
ctx.set('Content-Type', 'text/html');
let html = `<!DOCTYPE html>
<html>
<head>
<title>在线文档</title>
<meta charset="utf-8" />
${defaultTheme}
</head>
<body>
${htmlBody}
</body>
</html>
`;
return (ctx.body = html);
}
async function createHtml(projects, wikiDatas) {
let md = await createMarkdown.bind(this)(projects, wikiDatas);
let markdown = markdownIt({ html: true, breaks: true });
markdown.use(markdownItAnchor); // Optional, but makes sense as you really want to link to something
markdown.use(markdownItTableOfContents, {
markerPattern: /^\[toc\]/im,
includeLevel: [1, 2, 3]
});
let tp = unescape(markdown.render(md));
let left;
let content = tp.replace(
/<div\s+?class="table-of-contents"\s*>[\s\S]*?<\/ul>\s*<\/div>/gi,
function(match) {
left = match;
return '';
}
);
return createHtml5(left || '', content);
}
function createHtml5(left, tp) {
//html5模板
let html = `<!DOCTYPE html>
<html>
<head>
<title>在线接口文档</title>
<meta charset="utf-8" />
${defaultTheme}
</head>
<body>
<div class="g-doc">
<div id="left">
${left}
</div>
<div id="right" class="content-right">
${tp}
</div>
</div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
$(".table-of-contents .inter-title").parent("li").find("a").click(function (e) {
document.location.hash && $("[href='" + document.location.hash + "']").removeClass("active");
$(this).addClass("active");
})
$(".table-of-contents > ul > li > ul > li > a").click(function (e) {
$(this).parent("li").toggleClass("open");
return false;
})
</script>
</html>
`;
return html;
}
function createMarkdown(projects, wikiDatas) {
//拼接markdown
//模板
let mdTemplate = ``;
try {
// 项目名称信息
mdTemplate += md.createGroupMarkdown(groupData);
for (let index = 0; index < projects.length; index++) {
const project = projects[index];
mdTemplate += md.createProjectMarkdown(project.item, wikiDatas[index]);
// 分类信息
mdTemplate += md.createClassMarkdown(project.item, project.list, true);
}
return mdTemplate;
} catch (e) {
yapi.commons.log(e, 'error');
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
}
}
/**
* 获取开放接口的文档
* @param {*} ctx
*/
async getOpenDoc(ctx) {
await this.getDoc(ctx, 'open');
}
/**
* 获取项目的文档
* @param {请求} ctx
* @param {接口类型} status
*/
async getDoc(ctx, status) {
let pid = ctx.request.query.pid;
status = status || ctx.request.query.status;
if (!pid) {
ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空');
}
let curProject, wikiData;
let tp = '';
try {
curProject = await this.projectModel.get(pid);
try {
const wikiModel = require('../../exts/yapi-plugin-wiki/wikiModel.js');
wikiData = await yapi.getInst(wikiModel).get(pid);
} catch (error) {
}
ctx.set('Content-Type', 'text/html');
const list = await this.handleListClass(pid, status);
tp = await createHtml.bind(this)(list);
return (ctx.body = tp);
} catch (error) {
yapi.commons.log(error, 'error');
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
async function createHtml(list) {
let md = await createMarkdown.bind(this)(list, true);
let markdown = markdownIt({ html: true, breaks: true });
markdown.use(markdownItAnchor); // Optional, but makes sense as you really want to link to something
markdown.use(markdownItTableOfContents, {
markerPattern: /^\[toc\]/im,
includeLevel: [2, 3]
});
let tp = unescape(markdown.render(md));
let left;
let content = tp.replace(
/<div\s+?class="table-of-contents"\s*>[\s\S]*?<\/ul>\s*<\/div>/gi,
function(match) {
left = match;
return '';
}
);
return createHtml5(left || '', content);
}
function createHtml5(left, tp) {
//html5模板
let html = `<!DOCTYPE html>
<html>
<head>
<title>${curProject.name}</title>
<meta charset="utf-8" />
${defaultTheme}
</head>
<body>
<div class="g-doc">
${left}
<div id="right" class="content-right">
${tp}
</div>
</div>
</body>
</html>
`;
return html;
}
function createMarkdown(list, isToc) {
//拼接markdown
//模板
let mdTemplate = ``;
try {
// 项目名称信息
mdTemplate += md.createProjectMarkdown(curProject, wikiData);
// 分类信息
mdTemplate += md.createClassMarkdown(curProject, list, isToc);
return mdTemplate;
} catch (e) {
yapi.commons.log(e, 'error');
ctx.body = yapi.commons.resReturn(null, 502, '获取文档出错');
}
}
}
}
module.exports = exportController;
@charset "UTF-8";
html,
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote {
margin: 0;
padding: 0;
font-weight: normal;
-webkit-font-smoothing: antialiased;
}
/* 设置滚动条的样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
/* 外层轨道 */
::-webkit-scrollbar-track {
-webkit-box-shadow: inset006pxrgba(255, 0, 0, 0.3);
background: rgba(0, 0, 0, 0.1);
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
border-radius: 4px;
background: rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.5);
}
::-webkit-scrollbar-thumb:window-inactive {
background: rgba(0, 0, 0, 0.2);
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
font-size: 13px;
line-height: 25px;
color: #393838;
position: relative;
}
table {
margin: 10px 0 15px 0;
border-collapse: collapse;
}
td,
th {
border: 1px solid #ddd;
padding: 3px 10px;
}
th {
padding: 5px 10px;
}
a, a:link, a:visited {
color: #34495e;
text-decoration: none;
}
a:hover, a:focus {
color: #59d69d;
text-decoration: none;
}
a img {
border: none;
}
p {
padding-left: 10px;
margin-bottom: 9px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #404040;
line-height: 36px;
}
h1 {
color: #2c3e50;
font-weight: 600;
margin-bottom: 16px;
font-size: 32px;
padding-bottom: 16px;
border-bottom: 1px solid #ddd;
line-height: 50px;
}
h2 {
font-size: 28px;
padding-top: 10px;
padding-bottom: 10px;
}
h3 {
clear: both;
font-weight: 400;
margin-top: 20px;
margin-bottom: 20px;
border-bottom: 3px solid #59d69d;
padding-left: 8px;
font-size: 18px;
}
h4 {
font-size: 16px;
padding-left: 8px;
border-left: 3px solid #59d69d;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 13px;
}
hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family: georgia, serif;
font-style: italic;
}
blockquote:before {
font-size: 40px;
margin-left: -10px;
font-family: georgia, serif;
color: #eee;
}
blockquote p {
font-size: 14px;
font-weight: 300;
line-height: 18px;
margin-bottom: 0;
font-style: italic;
}
code,
pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
code {
background-color: #fee9cc;
color: rgba(0, 0, 0, 0.75);
padding: 1px 3px;
font-size: 12px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
pre {
display: block;
padding: 14px;
margin: 0 0 18px;
line-height: 16px;
font-size: 11px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
background: #f6f6f6;
}
pre code {
background-color: #f6f6f6;
color: #737373;
font-size: 11px;
padding: 0;
}
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
* {
-webkit-print-color-adjust: exact;
}
@media print {
body,
code,
pre code,
h1,
h2,
h3,
h4,
h5,
h6 {
color: black;
}
table,
pre {
page-break-inside: avoid;
}
}
html,
body {
height: 100%;
}
.table-of-contents, #left {
position: fixed;
top: 16px;
left: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
width: 260px;
}
.table-of-contents > ul > li > a {
font-size: 20px;
margin-bottom: 16px;
margin-top: 16px;
}
#left .table-of-contents>ul>li>ul>li>a {
font-size: 18px;
}
#left .table-of-contents>ul>li>ul>li>a:before {
content: "+";
}
#left .table-of-contents>ul>li>ul>li.open>a:before {
content: "-";
}
#left .table-of-contents>ul>li>ul>li>ul {
display: none;
}
#left .table-of-contents>ul>li>ul>li.open>ul {
display: block;
}
#left .table-of-contents>ul>li>ul>li>ul>li>a.active {
color: #59d69d;
}
.table-of-contents ul {
overflow: auto;
margin: 0px;
height: 100%;
padding: 0px 0px;
box-sizing: border-box;
list-style-type: none;
}
.table-of-contents ul li {
padding-left: 20px;
}
.table-of-contents a {
padding: 2px 0px;
display: block;
text-decoration: none;
}
.content-right {
max-width: 700px;
margin-left: 290px;
padding-left: 70px;
flex-grow: 1;
}
.content-right h2:target {
padding-top: 35px;
}
body > p {
margin-left: 30px;
}
body > table {
margin-left: 30px;
}
body > pre {
margin-left: 30px;
}
.curProject {
position: fixed;
top: 20px;
font-size: 25px;
color: black;
margin-left: -240px;
width: 240px;
padding: 5px;
line-height: 25px;
box-sizing: border-box;
}
.g-doc {
padding-top: 24px;
display: flex;
}
.curproject-name {
font-size: 42px;
}
.m-header {
background: #32363a;
height: 56px;
line-height: 56px;
padding-left: 60px;
display: flex;
align-items: center;
position: fixed;
z-index: 9;
top: 0;
left: 0;
right: 0;
}
.m-header .title {
font-size: 22px;
color: #fff;
font-weight: normal;
-webkit-font-smoothing: antialiased;
margin: 0;
margin-left: 16px;
padding: 0;
line-height: 56px;
border: none;
}
.m-header .nav {
color: #fff;
font-size: 16px;
position: absolute;
right: 32px;
top: 0;
}
.m-header .nav a {
color: #fff;
margin-left: 16px;
padding: 8px;
transition: color .2s;
}
.m-header .nav a:hover {
color: #59d69d;
}
.m-footer {
border-top: 1px solid #ddd;
padding-top: 16px;
padding-bottom: 16px;
}
/*# sourceMappingURL=defaultTheme.css.map */
.m-card .ui-logo {
width: 100px;
height: 100px;
border-radius: 50%;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, 0.24rem);
font-size: 50px;
color: #fff;
background-color: #2395f1;
line-height: 100px;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}
{
"version": 3,
"mappings": "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",
"sources": ["defaultTheme.scss"],
"names": [],
"file": "defaultTheme.css"
}
const fs = require('fs');
const sysPath = require('path');
const css = fs.readFileSync(sysPath.join(__dirname, './defaultTheme.css'));
module.exports = '<style>' + css + '</style>';
html,
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote {
margin: 0;
padding: 0;
font-weight: normal;
-webkit-font-smoothing: antialiased;
}
/* 设置滚动条的样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
/* 外层轨道 */
::-webkit-scrollbar-track {
-webkit-box-shadow: inset006pxrgba(255, 0, 0, 0.3);
background: rgba(0, 0, 0, 0.1);
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
border-radius: 4px;
background: rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.5);
}
::-webkit-scrollbar-thumb:window-inactive {
background: rgba(0, 0, 0, 0.2);
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
font-size: 13px;
line-height: 25px;
color: #393838;
position: relative;
// overflow-x: hidden;
}
table {
margin: 10px 0 15px 0;
border-collapse: collapse;
}
td,
th {
border: 1px solid #ddd;
padding: 3px 10px;
}
th {
padding: 5px 10px;
}
a, a:link, a:visited {
color: #34495e;
text-decoration: none;
}
a:hover, a:focus {
color: #59d69d;
text-decoration: none;
}
a img {
border: none;
}
p {
padding-left: 10px;
margin-bottom: 9px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #404040;
line-height: 36px;
}
h1 {
color: #2c3e50;
font-weight: 600;
// margin-top: 35px;
margin-bottom: 16px;
font-size: 32px;
padding-bottom: 16px;
border-bottom: 1px solid #ddd;
line-height: 50px;
}
h2 {
font-size: 28px;
padding-top: 10px;
padding-bottom: 10px;
}
h3 {
clear: both;
font-weight: 400;
margin-top: 20px;
margin-bottom: 20px;
border-left: 3px solid #59d69d;
padding-left: 8px;
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 13px;
}
hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family: georgia, serif;
font-style: italic;
}
blockquote:before {
font-size: 40px;
margin-left: -10px;
font-family: georgia, serif;
color: #eee;
}
blockquote p {
font-size: 14px;
font-weight: 300;
line-height: 18px;
margin-bottom: 0;
font-style: italic;
}
code,
pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
code {
background-color: #fee9cc;
color: rgba(0, 0, 0, 0.75);
padding: 1px 3px;
font-size: 12px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
pre {
display: block;
padding: 14px;
margin: 0 0 18px;
line-height: 16px;
font-size: 11px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
background: #f6f6f6;
}
pre code {
background-color: #f6f6f6;
color: #737373;
font-size: 11px;
padding: 0;
}
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
* {
-webkit-print-color-adjust: exact;
}
@media print {
body,
code,
pre code,
h1,
h2,
h3,
h4,
h5,
h6 {
color: black;
}
table,
pre {
page-break-inside: avoid;
}
}
html,
body {
height: 100%;
}
.table-of-contents {
position: fixed;
top: 16px;
left: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
width: 260px;
}
.table-of-contents>ul>li>a {
font-size: 20px;
margin-bottom: 16px;
margin-top: 16px;
}
.table-of-contents>ul>li>ul>li>a {
font-size: 18px;
}
.table-of-contents ul {
// position: fixed;
// top: 80px;
// left: 40px;
overflow: auto;
margin: 0px;
height: 100%;
padding: 0px 0px;
box-sizing: border-box;
list-style-type: none;
}
.table-of-contents ul li {
padding-left: 20px;
}
.table-of-contents a {
padding: 2px 0px;
display: block;
text-decoration: none;
}
.content-right {
// position: relative;
// top: -20px;
max-width: 700px;
margin-left: 290px;
padding-left: 70px;
flex-grow: 1;
h2:target{
padding-top: 35px;
}
}
body>p {
margin-left: 30px;
}
body>table {
margin-left: 30px;
}
body>pre {
margin-left: 30px;
}
.curProject {
position: fixed;
top: 20px;
font-size: 25px;
color: black;
margin-left: -240px;
width: 240px;
padding: 5px;
line-height: 25px;
box-sizing: border-box;
}
.g-doc {
padding-top: 24px;
display: flex;
}
.curproject-name{
font-size: 42px;
}
.m-header {
background: #32363a;
height: 56px;
line-height: 56px;
padding-left: 60px;
display: flex;
align-items: center;
position: fixed;
z-index: 9;
top: 0;
left: 0;
right: 0;
.title {
font-size: 22px;
color: #fff;
font-weight: normal;
-webkit-font-smoothing: antialiased;
margin: 0;
margin-left: 16px;
padding: 0;
line-height: 56px;
border: none;
}
.nav {
color: #fff;
font-size: 16px;
position: absolute;
right: 32px;
top: 0;
a {
color: #fff;
margin-left: 16px;
padding: 8px;
transition: color .2s;
}
a:hover {
color: #59d69d;
}
}
}
.m-footer {
border-top: 1px solid #ddd;
padding-top: 16px;
padding-bottom: 16px;
}
const schema = require('../../common/shema-transformTo-table.js');
const _ = require('underscore');
const json_parse = function(json) {
try {
return JSON.parse(json);
} catch (err) {
return {};
}
};
// 处理字符串换行
const handleWrap = str => {
return _.isString(str) ? str.replace(/\n/gi, '<br/>') : str;
};
const messageMap = {
desc: '备注',
default: '实例',
maximum: '最大值',
minimum: '最小值',
maxItems: '最大数量',
minItems: '最小数量',
maxLength: '最大长度',
minLength: '最小长度',
uniqueItems: '元素是否都不同',
itemType: 'item 类型',
format: 'format',
enum: '枚举',
enumDesc: '枚举备注',
mock: 'mock'
};
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name'
},
{
title: '类型',
dataIndex: 'type',
key: 'type'
},
{
title: '是否必须',
dataIndex: 'required',
key: 'required'
},
{
title: '默认值',
dataIndex: 'default',
key: 'default'
},
{
title: '备注',
dataIndex: 'desc',
key: 'desc'
},
{
title: '其他信息',
dataIndex: 'sub',
key: 'sub'
}
];
function escapeStr(str, isToc) {
return isToc ? escape(str) : str;
}
function createBaseMessage(basepath, inter) {
// 基本信息
let baseMessage = `#### 基本信息\n\n**Path:** ${basepath + inter.path}\n\n**Method:** ${
inter.method
}\n\n**接口描述:**\n${_.isUndefined(inter.desc) ? '' : inter.desc}\n`;
return baseMessage;
}
function createReqHeaders(req_headers) {
// Request-headers
if (req_headers && req_headers.length) {
let headersTable = `**Headers**\n\n`;
headersTable += `| 参数名称 | 参数值 | 是否必须 | 示例 | 备注 |\n| ------------ | ------------ | ------------ | ------------ | ------------ |\n`;
for (let j = 0; j < req_headers.length; j++) {
headersTable += `| ${req_headers[j].name || ''} | ${req_headers[j].value || ''} | ${
req_headers[j].required == 1 ? '是' : '否'
} | ${handleWrap(req_headers[j].example) || ''} | ${handleWrap(req_headers[j].desc) ||
''} |\n`;
}
return headersTable;
}
return '';
}
function createPathParams(req_params) {
if (req_params && req_params.length) {
let paramsTable = `**路径参数**\n\n`;
paramsTable += `| 参数名称 | 示例 | 备注 |\n| ------------ | ------------ | ------------ |\n`;
for (let j = 0; j < req_params.length; j++) {
paramsTable += `| ${req_params[j].name || ''} | ${handleWrap(req_params[j].example) ||
''} | ${handleWrap(req_params[j].desc) || ''} |\n`;
}
return paramsTable;
}
return '';
}
function createReqQuery(req_query) {
if (req_query && req_query.length) {
let headersTable = `**Query**\n\n`;
headersTable += `| 参数名称 | 是否必须 | 示例 | 备注 |\n| ------------ | ------------ | ------------ | ------------ |\n`;
for (let j = 0; j < req_query.length; j++) {
headersTable += `| ${req_query[j].name || ''} | ${
req_query[j].required == 1 ? '是' : '否'
} | ${handleWrap(req_query[j].example) || ''} | ${handleWrap(req_query[j].desc) ||
''} |\n`;
}
return headersTable;
}
return '';
}
function createReqBody(req_body_type, req_body_form, req_body_other, req_body_is_json_schema) {
if (req_body_type === 'form' && req_body_form.length) {
let bodyTable = `**Body**\n\n`;
bodyTable += `| 参数名称 | 参数类型 | 是否必须 | 示例 | 备注 |\n| ------------ | ------------ | ------------ | ------------ | ------------ |\n`;
let req_body = req_body_form;
for (let j = 0; j < req_body.length; j++) {
bodyTable += `| ${req_body[j].name || ''} | ${req_body[j].type || ''} | ${
req_body[j].required == 1 ? '是' : '否'
} | ${req_body[j].example || ''} | ${req_body[j].desc || ''} |\n`;
}
return `${bodyTable}\n\n`;
} else if (req_body_other) {
if (req_body_is_json_schema) {
let reqBody = createSchemaTable(req_body_other);
return `**Body**\n\n` + reqBody;
} else {
//other
return `**Body**\n\n` + '```javascript' + `\n${req_body_other || ''}` + '\n```';
}
}
return '';
}
function tableHeader(columns) {
let header = ``;
columns.map(item => {
header += `<th key=${item.key}>${item.title}</th>`;
});
return header;
}
function handleObject(text) {
if (!_.isObject(text)) {
return text;
}
let tpl = ``;
Object.keys(text || {}).map((item, index) => {
let name = messageMap[item];
let value = text[item];
tpl += _.isUndefined(text[item])
? ''
: `<p key=${index}><span style="font-weight: '700'">${name}: </span><span>${value.toString()}</span></p>`;
});
return tpl;
}
function tableCol(col, columns, level) {
let tpl = ``;
columns.map((item, index) => {
let dataIndex = item.dataIndex;
let value = col[dataIndex];
value = _.isUndefined(value) ? '' : value;
let text = ``;
switch (dataIndex) {
case 'sub':
text = handleObject(value);
break;
case 'type':
text =
value === 'array'
? `<span>${col.sub ? col.sub.itemType || '' : 'array'} []</span>`
: `<span>${value}</span>`;
break;
case 'required':
text = value ? '必须' : '非必须';
break;
case 'desc':
text = _.isUndefined(col.childrenDesc)
? `<span style="white-space: pre-wrap">${value}</span>`
: `<span style="white-space: pre-wrap">${col.childrenDesc}</span>`;
break;
case 'name':
text = `<span style="padding-left: ${20 * level}px"><span style="color: #8c8a8a">${
level > 0 ? '├─' : ''
}</span> ${value}</span>`;
break;
default:
text = value;
}
tpl += `<td key=${index}>${text}</td>`;
});
return tpl;
}
function tableBody(dataSource, columns, level) {
// 按照columns的顺序排列数据
let tpl = ``;
dataSource.map(col => {
let child = null;
tpl += `<tr key=${col.key}>${tableCol(col, columns, level)}</tr>`;
if (!_.isUndefined(col.children) && _.isArray(col.children)) {
let index = level + 1;
child = tableBody(col.children, columns, index);
}
tpl += child ? `${child}` : ``;
});
return tpl;
}
function createSchemaTable(body) {
let template = ``;
let dataSource = schema.schemaTransformToTable(json_parse(body));
template += `<table>
<thead class="ant-table-thead">
<tr>
${tableHeader(columns)}
</tr>
</thead>`;
template += `<tbody className="ant-table-tbody">${tableBody(dataSource, columns, 0)}
</tbody>
</table>
`;
return template;
}
function createResponse(res_body, res_body_is_json_schema, res_body_type) {
let resTitle = `\n#### 返回数据\n\n`;
if (res_body) {
if (res_body_is_json_schema && res_body_type === 'json') {
let resBody = createSchemaTable(res_body);
return resTitle + resBody;
} else {
let resBody = '```javascript' + `\n${res_body || ''}\n` + '```';
return resTitle + resBody;
}
}
return '';
}
function createInterMarkdown(basepath, listItem, isToc) {
let mdTemplate = ``;
const toc = `[TOC]\n\n`;
// 接口名称
mdTemplate += `\n### ${escapeStr(`${listItem.title}\n<a class="inter-title" id=${listItem.title}> </a>`, isToc)}\n`;
isToc && (mdTemplate += toc);
// 基本信息
mdTemplate += createBaseMessage(basepath, listItem);
// Request
mdTemplate += `\n#### 请求参数\n`;
// Request-headers
mdTemplate += createReqHeaders(listItem.req_headers);
// Request-params
mdTemplate += createPathParams(listItem.req_params);
// Request-query
mdTemplate += createReqQuery(listItem.req_query);
// Request-body
mdTemplate += createReqBody(
listItem.req_body_type,
listItem.req_body_form,
listItem.req_body_other,
listItem.req_body_is_json_schema
);
// Response
// Response-body
mdTemplate += createResponse(
listItem.res_body,
listItem.res_body_is_json_schema,
listItem.res_body_type
);
return mdTemplate;
}
function createProjectMarkdown(curProject, wikiData) {
let mdTemplate = ``;
// 项目名、项目描述
mdTemplate += `\n # ${escapeStr(curProject.name, true)} \n ${curProject.desc || ''}\n`;
mdTemplate += `[TOC]\n\n`
// 增加公共wiki信息展示
mdTemplate += wikiData ? `\n#### 公共信息\n${wikiData.desc || ''}\n` : '';
return mdTemplate;
}
function createGroupMarkdown(curGroup) {
let mdTemplate = ``;
// 分组名、分组描述
let title = `<h1 class="cur-group-name"> ${curGroup.group_name} </h1>`;
mdTemplate += `\n ${title} \n ${curGroup.group_desc || ''}\n\n`;
return mdTemplate;
}
function createClassMarkdown(curProject, list, isToc) {
let mdTemplate = ``;
const toc = `[TOC]\n\n`;
list.map(item => {
// 分类名称
mdTemplate += `\n## ${escapeStr(item.name, isToc)}\n`;
isToc && (mdTemplate += toc);
for (let i = 0; i < item.list.length; i++) {
//循环拼接 接口
// 接口内容
mdTemplate += createInterMarkdown(curProject.basepath, item.list[i], isToc);
}
});
return mdTemplate;
}
let r = {
createInterMarkdown,
createGroupMarkdown,
createProjectMarkdown,
createClassMarkdown
};
module.exports = r;
import './index.scss';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class DocPage extends Component {
constructor(props) {
super(props);
}
static propTypes = {
match: PropTypes.object
};
render() {
const { match } = this.props;
return (
<div className="g-row">
<div className="yapi-public-dock">
<a target="_blank" href={"/api/public/plugin/doc?pid=" + match.params.id}>
{`对外建议使用公开接口 ${window.location.origin}/api/public/plugin/doc?pid=${match.params.id}`}
</a>
</div>
<iframe className="m-panel yapi-doc" src={"/api/plugin/doc?pid=" + match.params.id} width="100%" style={{minHeight: "calc(100vh - 156px)"}}></iframe>
</div>
);
}
}
.yapi-doc.m-panel {
border: none;
padding-top: 0;
}
.yapi-public-dock {
margin-top: -15px;
}