api接口文档支持用户权限控制

This commit is contained in:
暮光:城中城
2021-12-11 22:36:05 +08:00
parent a2553097bd
commit 9dfb8f9ac6
59 changed files with 1701 additions and 479 deletions

View File

@@ -1,6 +1,9 @@
import apiClient from './request/zyplayer.js'
export const zyplayerApi = {
searchUserList: data => apiClient({url: '/user/info/search', method: 'post', data: data}),
getSelfUserInfo: data => apiClient({url: '/user/info/selfInfo', method: 'post', data: data}),
userLogout: data => apiClient({url: '/logout', method: 'post', data: data}),
systemUpgradeInfo: data => apiClient({url: '/system/info/upgrade', method: 'post', data: data}),
@@ -15,5 +18,9 @@ export const zyplayerApi = {
requestUrl: data => apiClient({url: '/doc-api/proxy/request', method: 'post', data: data}),
apiShareDocDetail: data => apiClient({url: '/doc-api/share/detail', method: 'post', data: data}),
apiShareDocApisDetail: data => apiClient({url: '/doc-api/share/apis/detail', method: 'post', data: data}),
docAuthList: data => apiClient({url: '/doc-api/doc/auth/list', method: 'post', data: data}),
docAuthAssign: data => apiClient({url: '/doc-api/doc/auth/assign', method: 'post', data: data}),
docAuthDelete: data => apiClient({url: '/doc-api/doc/auth/delete', method: 'post', data: data}),
};

View File

@@ -63,7 +63,6 @@ export function analysisOpenApiData(swagger) {
* @param tagPathMap 分组信息{分组名: {url: {...接口信息, path: '', url: '', method: ''}}}
* @param keywords 过滤关键字
* @param metaInfo 接口元信息点击时放入URL的参数
* @returns {{children: [], title: (*|string), key: string}[]}
*/
export function getTreeDataForTag(swagger, tagPathMap, keywords, metaInfo) {
let treeData = [];
@@ -116,7 +115,7 @@ export function getTreeDataForTag(swagger, tagPathMap, keywords, metaInfo) {
return [
{
key: 'main',
title: swagger.info.title || 'Swagger接口文档',
title: swagger.info.title || 'OpenApi接口文档',
children: treeData
}
];

View File

@@ -63,7 +63,6 @@ export function analysisSwaggerData(swagger) {
* @param tagPathMap 分组信息{分组名: {url: {...接口信息, path: '', url: '', method: ''}}}
* @param keywords 过滤关键字
* @param metaInfo 接口元信息点击时放入URL的参数
* @returns {{children: [], title: (*|string), key: string}[]}
*/
export function getTreeDataForTag(swagger, tagPathMap, keywords, metaInfo) {
let treeData = [];

View File

@@ -1,6 +1,9 @@
<template>
<a-directory-tree :showIcon="false" :tree-data="treeData" v-model:expandedKeys="expandedKeys" @select="docChecked">
<template #title="{ title, isLeaf, method, children, key }">
<template v-if="key === 'info'">
<file-text-outlined style="margin-right: 3px;"/>
</template>
<template v-if="isLeaf">
<a-tag color="pink" v-if="method === 'get'">get</a-tag>
<a-tag color="red" v-else-if="method === 'post'">post</a-tag>
@@ -22,10 +25,12 @@
import { useRouter, useRoute } from "vue-router";
import {useStore} from 'vuex';
import { message } from 'ant-design-vue';
import {InfoCircleOutlined, FileTextOutlined} from '@ant-design/icons-vue';
import {zyplayerApi} from '../../../api'
import {analysisOpenApiData, getTreeDataForTag} from '../../../assets/core/OpenApiTreeAnalysis.js'
export default {
components: {InfoCircleOutlined, FileTextOutlined},
setup() {
const store = useStore();
const route = useRoute();
@@ -39,7 +44,7 @@
let searchKeywords = ref('');
const docChecked = (val, node) => {
if (node.node.key === 'main') {
if (node.node.key === 'info') {
router.push({path: '/openapi/info'});
} else if (node.node.isLeaf) {
let dataRef = node.node.dataRef;
@@ -64,17 +69,12 @@
tagPathMap.value = treeData.tagPathMap;
loadTreeData();
callback();
setTimeout(() => {
let isViewPage = (route.path === '/openapi/view' && route.query.id);
if (!isViewPage) {
router.push({path: '/openapi/info'});
}
}, 0);
});
};
const loadTreeData = async () => {
let metaInfo = {id: choiceDocId.value};
treeData.value = getTreeDataForTag(openApiDoc.value, tagPathMap.value, searchKeywords.value, metaInfo);
treeData.value.unshift({key: 'info', title: '文档说明信息', isLeaf: true});
await nextTick();
expandedKeys.value = ['main'];
};

View File

@@ -1,6 +1,9 @@
<template>
<a-directory-tree :showIcon="false" :tree-data="treeData" v-model:expandedKeys="expandedKeys" @select="docChecked">
<template #title="{ title, isLeaf, method, children, key }">
<template v-if="key === 'info'">
<file-text-outlined style="margin-right: 3px;"/>
</template>
<template v-if="isLeaf">
<a-tag color="pink" v-if="method === 'get'">get</a-tag>
<a-tag color="red" v-else-if="method === 'post'">post</a-tag>
@@ -22,10 +25,12 @@
import { useRouter, useRoute } from "vue-router";
import {useStore} from 'vuex';
import { message } from 'ant-design-vue';
import {InfoCircleOutlined, FileTextOutlined} from '@ant-design/icons-vue';
import {zyplayerApi} from '../../../api'
import {analysisSwaggerData, getTreeDataForTag} from '../../../assets/core/SwaggerTreeAnalysis.js'
export default {
components: {InfoCircleOutlined, FileTextOutlined},
setup() {
const store = useStore();
const route = useRoute();
@@ -39,7 +44,7 @@
let searchKeywords = ref('');
const docChecked = (val, node) => {
if (node.node.key === 'main') {
if (node.node.key === 'info') {
router.push({path: '/swagger/info'});
} else if (node.node.isLeaf) {
let dataRef = node.node.dataRef;
@@ -64,17 +69,12 @@
tagPathMap.value = treeData.tagPathMap;
loadTreeData();
callback(true);
setTimeout(() => {
let isViewPage = (route.path === '/swagger/view' && route.query.id);
if (!isViewPage) {
router.push({path: '/swagger/info'});
}
}, 0);
});
};
const loadTreeData = async () => {
let metaInfo = {id: choiceDocId.value};
treeData.value = getTreeDataForTag(swaggerDoc.value, tagPathMap.value, searchKeywords.value, metaInfo);
treeData.value.unshift({key: 'info', title: '文档说明信息', isLeaf: true});
await nextTick();
expandedKeys.value = ['main'];
};

View File

@@ -1,329 +1,33 @@
<template>
<a-form layout="inline" style="margin-bottom: 20px;">
<a-form-item label="文档类型">
<a-select placeholder="请选择文档类型" v-model:value="searchParam.docType" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="1">URL添加</a-select-option>
<a-select-option :value="2">JSON内容</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="开放访问">
<a-select placeholder="请选择开放访问" v-model:value="searchParam.openVisit" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="0"></a-select-option>
<a-select-option :value="1"></a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态">
<a-select placeholder="请选择状态" v-model:value="searchParam.docStatus" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="1">启用</a-select-option>
<a-select-option :value="2">禁用</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button @click="searchDocList" type="primary">查询</a-button>
<a-button @click="openNewDoc" :style="{ marginLeft: '8px' }">新建</a-button>
</a-form-item>
</a-form>
<a-table :dataSource="docList" :columns="docListColumns" size="middle"
:loading="docListLoading" :pagination="pagination"
@change="handleTableChange"
:scroll="{ x: 1400, y: 'calc(100vh - 300px)' }">
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'operation'">
<a-button size="small" type="link" @click="editDoc(record)">编辑</a-button>
<a-popconfirm title="确定要删除吗?" @confirm="deleteDoc(record)">
<a-button size="small" type="link" danger>删除</a-button>
</a-popconfirm>
<a-dropdown :trigger="['click']">
<template #overlay>
<a-menu @click="handleActionMenuClick($event, record)">
<a-menu-item key="shareView"><link-outlined /> 查看开放文档</a-menu-item>
<a-menu-item key="shareInstruction"><edit-outlined /> 编辑开放文档说明</a-menu-item>
</a-menu>
</template>
<a-button type="link" size="small">更多<DownOutlined /></a-button>
</a-dropdown>
</template>
<template v-if="column.dataIndex === 'docType'">
<a-tag color="red" v-if="text === 1">Swagger URL</a-tag>
<a-tag color="blue" v-else-if="text === 2">Swagger JSON</a-tag>
<a-tag color="green" v-else-if="text === 4">OpenApi JSON</a-tag>
</template>
<template v-if="column.dataIndex === 'openVisit'">
<a-tag color="pink" v-if="text === 0">未开放</a-tag>
<a-tag color="green" v-else-if="text === 1">已开放</a-tag>
</template>
<template v-if="column.dataIndex === 'docStatus'">
<a-tag color="green" v-if="text === 1">启用</a-tag>
<a-tag color="pink" v-else-if="text === 2">禁用</a-tag>
</template>
</template>
</a-table>
<a-modal v-model:visible="newDocVisible" :title="docEdit.isNew?'新增文档':'编辑文档'" @ok="handleNewDocOk" :width="800">
<a-form layout="horizontal" ref="newDocFormRef" :rules="newDocRules" :model="docEdit" :label-col="{span: 4}" :wrapper-col="{span: 20}">
<a-form-item label="文档名称" required name="name">
<a-input placeholder="请输入文档名称" v-model:value="docEdit.name"></a-input>
</a-form-item>
<a-form-item label="文档类型" required name="docType">
<a-radio-group v-model:value="docEdit.docType">
<a-radio :value="1">Swagger URL</a-radio>
<a-radio :value="2">Swagger JSON</a-radio>
<a-radio :value="3">OpenApi URL</a-radio>
<a-radio :value="4">OpenApi JSON</a-radio>
<a-radio :value="5" disabled>自建API</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="文档地址" required name="docUrl" v-if="docEdit.docType === 1">
<a-input placeholder="请输入文档地址URL" v-model:value="docEdit.docUrl"></a-input>
<template #extra>
查看文档地址
<a-popover title="文档地址支持以下任一格式">
<template #content>
<p>格式一http://doc.zyplayer.com/v2/api-docs</p>
<p>格式二http://doc.zyplayer.com/swagger-resources</p>
<p>格式三http://doc.zyplayer.com/swagger-ui.html</p>
</template>
<a>示例</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 2">
<!-- textarea在内容很多的时候>300KB会卡顿ace不会-->
<ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>
<!-- <a-textarea placeholder="请输入JSON格式的Swagger文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>-->
<template #extra>
查看文档内容
<a-popover title="文档内容说明">
<template #content>
<div>支持以下格式的Swagger文档内容输入其中 {"swagger": "2.0"} 为必要属性</div>
<div v-highlight>
<pre><code class="lang-json">{{swaggerDocDemo}}</code></pre>
</div>
</template>
<a>说明</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="文档地址" required name="docUrl" v-if="docEdit.docType === 3">
<a-input placeholder="请输入文档地址URL" v-model:value="docEdit.docUrl"></a-input>
<template #extra>
查看文档地址
<a-popover title="文档地址支持以下任一格式">
<template #content>
<p>格式一http://doc.zyplayer.com/v3/api-docs</p>
</template>
<a>示例</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 4">
<ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>
<!-- <a-textarea placeholder="请输入JSON格式的OpenApi文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>-->
<template #extra>
查看文档内容
<a-popover title="文档内容说明">
<template #content>
<div>支持以下格式的OpenApi文档内容输入其中 {"openapi": "3.x.x"} 为必要属性</div>
<div v-highlight>
<pre><code class="lang-json">{{openApiDocDemo}}</code></pre>
</div>
</template>
<a>说明</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="目标域名" name="rewriteDomain">
<a-input placeholder="请输入目标域名" v-model:value="docEdit.rewriteDomain"></a-input>
<template #extra>
目标域名
<a-popover title="目标域名说明">
<template #content>
<p>在文档的在线调试界面访问的域名可以初始为此处录入的域名而非文档本身的域名地址</p>
<p>可便于不同环境间的接口测试http://doc.zyplayer.com</p>
</template>
<a>说明</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="开放访问" required name="openVisit">
<a-radio-group v-model:value="docEdit.openVisit">
<a-radio :value="0"></a-radio>
<a-radio :value="1">开放访问</a-radio>
</a-radio-group>
<template #extra>
开放访问后无需登录即可通过<a @click="openShareViewWindow(docEdit)">开放文档URL</a>访问该文档信息
</template>
</a-form-item>
<a-form-item label="状态" required name="docStatus">
<a-radio-group v-model:value="docEdit.docStatus">
<a-radio :value="1">启用</a-radio>
<a-radio :value="2">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</a-modal>
<EditShareInstruction ref="instruction"></EditShareInstruction>
<DocManageList v-if="showView === 'list'" @showMembers="showMembers"></DocManageList>
<div v-else>
<DocManageMembers v-if="showView === 'members'" @showDocList="showDocList" :doc="docInfo"></DocManageMembers>
</div>
</template>
<script>
import { toRefs, ref, reactive, onMounted } from 'vue';
import {zyplayerApi} from '../../api';
import {useStore} from 'vuex';
import aceEditor from "../../assets/ace-editor";
import EditShareInstruction from "./components/EditShareInstruction.vue";
import {getZyplayerApiBaseUrl} from "../../api/request/utils";
import {DownOutlined, LinkOutlined, EditOutlined} from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import DocManageList from "./DocManageList.vue";
import DocManageMembers from "./DocManageMembers.vue";
export default {
components: {aceEditor, EditShareInstruction, DownOutlined, LinkOutlined, EditOutlined},
setup() {
const store = useStore();
let docList = ref([]);
let docListLoading = ref(false);
let searchParam = ref({docType: '', openVisit: '', docStatus: '', pageNum: 1, pageSize: 20});
let pagination = ref({
pageSize: 20,
pageNum: 1,
total: 0,
showSizeChanger: true,
pageSizeOptions: ['20', '50', '100'],
showTotal: total => `${total}`
});
const handleTableChange = (paginationNew, filters, sorter) => {
pagination.value.pageNum = paginationNew.current;
pagination.value.pageSize = paginationNew.pageSize;
searchParam.value.pageNum = paginationNew.current;
searchParam.value.pageSize = paginationNew.pageSize;
searchDocList();
};
const searchDocList = async () => {
docListLoading.value = true;
zyplayerApi.apiDocList(searchParam.value).then(res => {
setTimeout(() => docListLoading.value = false, 500);
docList.value = res.data || [];
pagination.value.total = res.total || 0;
});
};
let docEdit = ref({});
let newDocFormRef = ref();
let newDocVisible = ref(false);
const handleNewDocOk = async () => {
newDocFormRef.value.validate().then(() => {
zyplayerApi.apiDocAdd(docEdit.value).then(res => {
searchDocList();
newDocVisible.value = false;
store.commit('addDocChangedNum');
});
}).catch(error => {
console.log('error', error);
});
};
const openNewDoc = async () => {
newDocVisible.value = true;
docEdit.value = {
docType: 1, openVisit: 0, docStatus: 1, isNew: 1
};
};
const editDoc = (record) => {
zyplayerApi.apiDocDetail({id: record.id}).then(res => {
docEdit.value = res.data;
newDocVisible.value = true;
});
};
const updateDoc = async (id, docStatus, yn) => {
zyplayerApi.apiDocUpdate({id, docStatus, yn}).then(res => {
searchDocList();
store.commit('addDocChangedNum');
});
};
const deleteDoc = async (row) => updateDoc(row.id, null, 0);
// 打开开放文档新窗口
const openShareViewWindow = record => {
if (!record.shareUuid) {
message.warning('请先保存文档后再试');
} else if (record.openVisit !== 1) {
message.warning('该文档尚未开启开放访问功能,请在编辑页选择开放后再试');
} else {
window.open(getZyplayerApiBaseUrl() + '/doc-api#/share/home?uuid=' + record.shareUuid);
}
};
const handleActionMenuClick = (item, record) => {
if (item.key === 'shareView') {
openShareViewWindow(record);
} else if (item.key === 'shareInstruction') {
instruction.value.editDoc(record.id);
}
}
let instruction = ref();
onMounted(() => {
searchDocList();
});
components: {DocManageList, DocManageMembers},
setup() {
let showView = ref('list');
let docInfo = ref({});
const showMembers = (doc) => {
docInfo.value = doc;
showView.value = 'members';
};
const showDocList = () => {
showView.value = 'list';
};
return {
searchParam,
docList,
docListLoading,
newDocVisible,
docEdit,
newDocFormRef,
searchDocList,
openNewDoc,
handleNewDocOk,
deleteDoc,
editDoc,
handleTableChange,
openShareViewWindow,
handleActionMenuClick,
pagination,
instruction,
newDocRules: {
name: [{required: true, message: '请输入文档名称', trigger: 'change'}],
docUrl: [{required: true, message: '请输入文档地址', trigger: 'change'}],
jsonContent: [{required: true, message: '请输入JSON格式的swagger文档内容', trigger: 'change'}],
docType: [{type: 'number', required: true, message: '请选择文档类型', trigger: 'change'}],
openVisit: [{type: 'number', required: true, message: '请选择是否开放访问', trigger: 'change'}],
docStatus: [{type: 'number', required: true, message: '请选择文档状态', trigger: 'change'}],
},
docListColumns: [
{title: 'ID', dataIndex: 'id', width: 70},
{title: '文档名称', dataIndex: 'name', width: 250},
{title: '文档类型', dataIndex: 'docType', width: 120},
{title: '开放访问', dataIndex: 'openVisit', width: 90},
{title: '状态', dataIndex: 'docStatus', width: 90},
{title: '文档地址', dataIndex: 'docUrl'},
{title: '目标域名', dataIndex: 'rewriteDomain', width: 250},
{title: '操作', dataIndex: 'operation', fixed: 'right', width: 200},
],
aceEditorConfig: {
wrap: true,
autoScrollEditorIntoView: true,
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
minLines: 10,
maxLines: 15,
},
swaggerDocDemo:
'{\n'
+ ' "swagger": "2.0",\n'
+ ' "info": {},\n'
+ ' "host": "doc.zyplayer.com",\n'
+ ' "basePath":"/",\n'
+ ' "tags": [],\n'
+ ' "paths": {},\n'
+ ' "definitions": {}\n'
+ '}',
openApiDocDemo:
'{\n'
+ ' "openapi": "3.0.3",\n'
+ ' "components": {}\n'
+ ' "servers": [],\n'
+ ' "paths": {},\n'
+ ' "info": {},\n'
+ '}',
};
showView,
docInfo,
showMembers,
showDocList,
}
},
};
</script>

View File

@@ -0,0 +1,337 @@
<template>
<a-form layout="inline" style="margin-bottom: 20px;">
<a-form-item label="文档类型">
<a-select placeholder="请选择文档类型" v-model:value="searchParam.docType" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="1">URL添加</a-select-option>
<a-select-option :value="2">JSON内容</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="开放访问">
<a-select placeholder="请选择开放访问" v-model:value="searchParam.openVisit" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="0"></a-select-option>
<a-select-option :value="1"></a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态">
<a-select placeholder="请选择状态" v-model:value="searchParam.docStatus" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="1">启用</a-select-option>
<a-select-option :value="2">禁用</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button @click="searchDocList" type="primary">查询</a-button>
<a-button @click="openNewDoc" :style="{ marginLeft: '8px' }">新建</a-button>
</a-form-item>
</a-form>
<a-table :dataSource="docList" :columns="docListColumns" size="middle"
:loading="docListLoading" :pagination="pagination"
@change="handleTableChange"
:scroll="{ x: 1400, y: 'calc(100vh - 300px)' }">
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'operation'">
<a-button size="small" type="link" @click="editDoc(record)">编辑</a-button>
<template v-if="record.authType === 1">
<a-button size="small" type="link" @click="showMembers(record)">成员管理</a-button>
<a-popconfirm title="确定要删除吗?" @confirm="deleteDoc(record)">
<a-button size="small" type="link" danger>删除</a-button>
</a-popconfirm>
</template>
<a-dropdown :trigger="['click']">
<template #overlay>
<a-menu @click="handleActionMenuClick($event, record)">
<a-menu-item key="shareView"><link-outlined /> 查看开放文档</a-menu-item>
<a-menu-item key="shareInstruction"><edit-outlined /> 编辑开放文档说明</a-menu-item>
</a-menu>
</template>
<a-button type="link" size="small">更多<DownOutlined /></a-button>
</a-dropdown>
</template>
<template v-if="column.dataIndex === 'docType'">
<a-tag color="red" v-if="text === 1">Swagger URL</a-tag>
<a-tag color="blue" v-else-if="text === 2">Swagger JSON</a-tag>
<a-tag color="green" v-else-if="text === 4">OpenApi JSON</a-tag>
</template>
<template v-if="column.dataIndex === 'openVisit'">
<a-tag color="pink" v-if="text === 0">未开放</a-tag>
<a-tag color="green" v-else-if="text === 1">已开放</a-tag>
</template>
<template v-if="column.dataIndex === 'docStatus'">
<a-tag color="green" v-if="text === 1">启用</a-tag>
<a-tag color="pink" v-else-if="text === 2">禁用</a-tag>
</template>
</template>
</a-table>
<a-modal v-model:visible="newDocVisible" :title="docEdit.isNew?'新增文档':'编辑文档'" @ok="handleNewDocOk" :width="800">
<a-form layout="horizontal" ref="newDocFormRef" :rules="newDocRules" :model="docEdit" :label-col="{span: 4}" :wrapper-col="{span: 20}">
<a-form-item label="文档名称" required name="name">
<a-input placeholder="请输入文档名称" v-model:value="docEdit.name"></a-input>
</a-form-item>
<a-form-item label="文档类型" required name="docType">
<a-radio-group v-model:value="docEdit.docType">
<a-radio :value="1">Swagger URL</a-radio>
<a-radio :value="2">Swagger JSON</a-radio>
<a-radio :value="3">OpenApi URL</a-radio>
<a-radio :value="4">OpenApi JSON</a-radio>
<a-radio :value="5" disabled>自建API</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="文档地址" required name="docUrl" v-if="docEdit.docType === 1">
<a-input placeholder="请输入文档地址URL" v-model:value="docEdit.docUrl"></a-input>
<template #extra>
查看文档地址
<a-popover title="文档地址支持以下任一格式">
<template #content>
<p>格式一http://doc.zyplayer.com/v2/api-docs</p>
<p>格式二http://doc.zyplayer.com/swagger-resources</p>
<p>格式三http://doc.zyplayer.com/swagger-ui.html</p>
</template>
<a>示例</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 2">
<!-- textarea在内容很多的时候>300KB会卡顿ace不会-->
<ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>
<!-- <a-textarea placeholder="请输入JSON格式的Swagger文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>-->
<template #extra>
查看文档内容
<a-popover title="文档内容说明">
<template #content>
<div>支持以下格式的Swagger文档内容输入其中 {"swagger": "2.0"} 为必要属性</div>
<div v-highlight>
<pre><code class="lang-json">{{swaggerDocDemo}}</code></pre>
</div>
</template>
<a>说明</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="文档地址" required name="docUrl" v-if="docEdit.docType === 3">
<a-input placeholder="请输入文档地址URL" v-model:value="docEdit.docUrl"></a-input>
<template #extra>
查看文档地址
<a-popover title="文档地址支持以下任一格式">
<template #content>
<p>格式一http://doc.zyplayer.com/v3/api-docs</p>
</template>
<a>示例</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 4">
<ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>
<!-- <a-textarea placeholder="请输入JSON格式的OpenApi文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>-->
<template #extra>
查看文档内容
<a-popover title="文档内容说明">
<template #content>
<div>支持以下格式的OpenApi文档内容输入其中 {"openapi": "3.x.x"} 为必要属性</div>
<div v-highlight>
<pre><code class="lang-json">{{openApiDocDemo}}</code></pre>
</div>
</template>
<a>说明</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="目标域名" name="rewriteDomain">
<a-input placeholder="请输入目标域名" v-model:value="docEdit.rewriteDomain"></a-input>
<template #extra>
目标域名
<a-popover title="目标域名说明">
<template #content>
<p>在文档的在线调试界面访问的域名可以初始为此处录入的域名而非文档本身的域名地址</p>
<p>可便于不同环境间的接口测试http://doc.zyplayer.com</p>
</template>
<a>说明</a>
</a-popover>
</template>
</a-form-item>
<a-form-item label="开放访问" required name="openVisit">
<a-radio-group v-model:value="docEdit.openVisit">
<a-radio :value="0"></a-radio>
<a-radio :value="1">开放访问</a-radio>
</a-radio-group>
<template #extra>
开放访问后无需登录即可通过<a @click="openShareViewWindow(docEdit)">开放文档URL</a>访问该文档信息
</template>
</a-form-item>
<a-form-item label="状态" required name="docStatus">
<a-radio-group v-model:value="docEdit.docStatus">
<a-radio :value="1">启用</a-radio>
<a-radio :value="2">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</a-modal>
<EditShareInstruction ref="instruction"></EditShareInstruction>
</template>
<script>
import { toRefs, ref, reactive, onMounted } from 'vue';
import {zyplayerApi} from '../../api';
import {useStore} from 'vuex';
import aceEditor from "../../assets/ace-editor";
import EditShareInstruction from "./components/EditShareInstruction.vue";
import {getZyplayerApiBaseUrl} from "../../api/request/utils";
import {DownOutlined, LinkOutlined, EditOutlined} from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
export default {
emits: ['showMembers'],
components: {aceEditor, EditShareInstruction, DownOutlined, LinkOutlined, EditOutlined},
setup(props, {emit}) {
const store = useStore();
let docList = ref([]);
let docListLoading = ref(false);
let searchParam = ref({docType: '', openVisit: '', docStatus: '', pageNum: 1, pageSize: 20});
let pagination = ref({
pageSize: 20,
pageNum: 1,
total: 0,
showSizeChanger: true,
pageSizeOptions: ['20', '50', '100'],
showTotal: total => `${total}`
});
const handleTableChange = (paginationNew, filters, sorter) => {
pagination.value.pageNum = paginationNew.current;
pagination.value.pageSize = paginationNew.pageSize;
searchParam.value.pageNum = paginationNew.current;
searchParam.value.pageSize = paginationNew.pageSize;
searchDocList();
};
const searchDocList = async () => {
docListLoading.value = true;
zyplayerApi.apiDocList(searchParam.value).then(res => {
setTimeout(() => docListLoading.value = false, 500);
docList.value = res.data || [];
pagination.value.total = res.total || 0;
});
};
let docEdit = ref({});
let newDocFormRef = ref();
let newDocVisible = ref(false);
const handleNewDocOk = async () => {
newDocFormRef.value.validate().then(() => {
zyplayerApi.apiDocAdd(docEdit.value).then(res => {
searchDocList();
newDocVisible.value = false;
store.commit('addDocChangedNum');
});
}).catch(error => {
console.log('error', error);
});
};
const openNewDoc = async () => {
newDocVisible.value = true;
docEdit.value = {
docType: 1, openVisit: 0, docStatus: 1, isNew: 1
};
};
const showMembers = (record) => {
emit('showMembers', record);
};
const editDoc = (record) => {
zyplayerApi.apiDocDetail({id: record.id}).then(res => {
docEdit.value = res.data;
newDocVisible.value = true;
});
};
const updateDoc = async (id, docStatus, yn) => {
zyplayerApi.apiDocUpdate({id, docStatus, yn}).then(res => {
searchDocList();
store.commit('addDocChangedNum');
});
};
const deleteDoc = async (row) => updateDoc(row.id, null, 0);
// 打开开放文档新窗口
const openShareViewWindow = record => {
if (!record.shareUuid) {
message.warning('请先保存文档后再试');
} else if (record.openVisit !== 1) {
message.warning('该文档尚未开启开放访问功能,请在编辑页选择开放后再试');
} else {
window.open(getZyplayerApiBaseUrl() + '/doc-api#/share/home?uuid=' + record.shareUuid);
}
};
const handleActionMenuClick = (item, record) => {
if (item.key === 'shareView') {
openShareViewWindow(record);
} else if (item.key === 'shareInstruction') {
instruction.value.editDoc(record.id);
}
}
let instruction = ref();
onMounted(() => {
searchDocList();
});
return {
searchParam,
docList,
docListLoading,
newDocVisible,
docEdit,
newDocFormRef,
searchDocList,
openNewDoc,
handleNewDocOk,
deleteDoc,
editDoc,
showMembers,
handleTableChange,
openShareViewWindow,
handleActionMenuClick,
pagination,
instruction,
newDocRules: {
name: [{required: true, message: '请输入文档名称', trigger: 'change'}],
docUrl: [{required: true, message: '请输入文档地址', trigger: 'change'}],
jsonContent: [{required: true, message: '请输入JSON格式的swagger文档内容', trigger: 'change'}],
docType: [{type: 'number', required: true, message: '请选择文档类型', trigger: 'change'}],
openVisit: [{type: 'number', required: true, message: '请选择是否开放访问', trigger: 'change'}],
docStatus: [{type: 'number', required: true, message: '请选择文档状态', trigger: 'change'}],
},
docListColumns: [
{title: 'ID', dataIndex: 'id', width: 70},
{title: '文档名称', dataIndex: 'name', width: 250},
{title: '文档类型', dataIndex: 'docType', width: 120},
{title: '开放访问', dataIndex: 'openVisit', width: 90},
{title: '状态', dataIndex: 'docStatus', width: 90},
{title: '文档地址', dataIndex: 'docUrl'},
{title: '目标域名', dataIndex: 'rewriteDomain', width: 250},
{title: '操作', dataIndex: 'operation', fixed: 'right', width: 280},
],
aceEditorConfig: {
wrap: true,
autoScrollEditorIntoView: true,
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
minLines: 10,
maxLines: 15,
},
swaggerDocDemo:
'{\n'
+ ' "swagger": "2.0",\n'
+ ' "info": {},\n'
+ ' "host": "doc.zyplayer.com",\n'
+ ' "basePath":"/",\n'
+ ' "tags": [],\n'
+ ' "paths": {},\n'
+ ' "definitions": {}\n'
+ '}',
openApiDocDemo:
'{\n'
+ ' "openapi": "3.0.3",\n'
+ ' "components": {}\n'
+ ' "servers": [],\n'
+ ' "paths": {},\n'
+ ' "info": {},\n'
+ '}',
};
},
};
</script>

View File

@@ -0,0 +1,200 @@
<template>
<a-page-header
title="成员管理"
:sub-title="doc.name||''"
@back="showDocList">
<template #extra>
<a-button @click="searchDocMemberList" type="primary">查询</a-button>
<a-button @click="openAddDocMember" :style="{ marginLeft: '8px' }">添加用户</a-button>
</template>
</a-page-header>
<a-table :dataSource="docMemberList" :columns="docListColumns" size="middle"
:loading="docMemberListLoading" :pagination="false"
@change="handleTableChange"
:scroll="{ x: 1400, y: 'calc(100vh - 300px)' }">
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'operation'">
<a-popconfirm title="确定要删除吗?" @confirm="deleteDocMember(record)">
<a-button size="small" type="link" danger>删除</a-button>
</a-popconfirm>
</template>
<template v-if="column.dataIndex === 'sex'">
<a-tag color="pink" v-if="record.sex === 1"></a-tag>
<a-tag color="red" v-else-if="record.sex === 0"></a-tag>
<a-tag color="orange" v-else>-</a-tag>
</template>
<template v-if="column.dataIndex === 'authType'">
<a-select placeholder="请选择角色" v-model:value="record.authType" @change="userAuthTypeChange(record)" style="width: 150px;">
<a-select-option :value="1">管理员</a-select-option>
<a-select-option :value="2">开发人员</a-select-option>
</a-select>
</template>
</template>
</a-table>
<a-modal v-model:visible="addUserVisible" title="添加用户" @ok="handleAddUserOk" :width="600">
<a-form layout="horizontal" ref="addUserFormRef" :model="userAdd" :rules="addUserRules" :label-col="{span: 4}" :wrapper-col="{span: 20}">
<a-form-item label="选择用户" required name="userId">
<a-select
v-model:value="userAdd.userId"
show-search
placeholder="输入用户名、邮箱、手机号搜索"
:default-active-first-option="false"
:show-arrow="true"
:filter-option="false"
:not-found-content="undefined"
:options="userSearchList"
@search="handleUserSearch"
>
<template v-if="userSearchState.fetching" #notFoundContent>
<a-spin size="small" />
</template>
</a-select>
</a-form-item>
<a-form-item label="用户角色" required name="authType">
<a-radio-group v-model:value="userAdd.authType">
<a-radio :value="1">管理员</a-radio>
<a-radio :value="2">开发人员</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
import { toRefs, ref, reactive, onMounted } from 'vue';
import {zyplayerApi} from '../../api';
import {useStore} from 'vuex';
import {getZyplayerApiBaseUrl} from "../../api/request/utils";
import {DownOutlined, LinkOutlined, EditOutlined} from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
export default {
emits: ['showDocList'],
components: {DownOutlined, LinkOutlined, EditOutlined},
props: {
doc: {
type: Object,
required: true
},
},
setup(props, {emit}) {
const store = useStore();
let docMemberList = ref([]);
let docMemberListLoading = ref(false);
let searchParam = ref({docId: '', pageNum: 1, pageSize: 20});
// 项目应该加不了很多的人,暂不分页
let pagination = ref({
pageSize: 20,
pageNum: 1,
total: 0,
showSizeChanger: true,
pageSizeOptions: ['20', '50', '100'],
showTotal: total => `${total}`
});
const handleTableChange = (paginationNew, filters, sorter) => {
pagination.value.pageNum = paginationNew.current;
pagination.value.pageSize = paginationNew.pageSize;
searchParam.value.pageNum = paginationNew.current;
searchParam.value.pageSize = paginationNew.pageSize;
searchDocMemberList();
};
const searchDocMemberList = async () => {
docMemberListLoading.value = true;
searchParam.value.docId = props.doc.id;
zyplayerApi.docAuthList(searchParam.value).then(res => {
setTimeout(() => docMemberListLoading.value = false, 500);
docMemberList.value = res.data || [];
pagination.value.total = res.total || 0;
});
};
let userAdd = ref({});
let userSearchState = ref({
data: [],
search: '',
fetching: false,
});
let userSearchList = ref([]);
let addUserFormRef = ref();
let addUserVisible = ref(false);
const handleUserSearch = (search) => {
userSearchState.value.search = search;
if (userSearchState.value.fetching) {
return;
}
userSearchState.value.fetching = true;
userSearchList.value = [];
setTimeout(() => {
zyplayerApi.searchUserList({search: userSearchState.value.search}).then(res => {
let resArr = res.data || [];
resArr.forEach(item => userSearchList.value.push({label: item.userName, value: item.id}));
userSearchState.value.fetching = false;
});
}, 500);
};
const handleAddUserOk = async () => {
addUserFormRef.value.validate().then(() => {
zyplayerApi.docAuthAssign(userAdd.value).then(res => {
searchDocMemberList();
addUserVisible.value = false;
});
}).catch(error => {
console.log('error', error);
});
};
const userAuthTypeChange = async (record) => {
let param = {...record, docId: props.doc.id};
zyplayerApi.docAuthAssign(param).then(res => {
message.success('修改成功');
});
};
const openAddDocMember = async () => {
addUserVisible.value = true;
userAdd.value = {docId: props.doc.id, userId: undefined, authType: 1};
};
const deleteDocMember = async (row) => {
zyplayerApi.docAuthDelete({docId: props.doc.id, userId: row.userId}).then(res => {
searchDocMemberList();
});
};
const showDocList = () => {
emit('showDocList');
}
onMounted(() => {
searchDocMemberList();
});
return {
showDocList,
searchParam,
docMemberList,
docMemberListLoading,
addUserVisible,
userAdd,
userSearchList,
addUserFormRef,
userSearchState,
handleUserSearch,
searchDocMemberList,
openAddDocMember,
handleAddUserOk,
userAuthTypeChange,
deleteDocMember,
handleTableChange,
pagination,
docListColumns: [
{title: 'ID', dataIndex: 'userId', width: 70},
{title: '用户名', dataIndex: 'userName'},
{title: '帐号', dataIndex: 'userNo'},
{title: '邮箱', dataIndex: 'email'},
{title: '手机号', dataIndex: 'phone'},
{title: '性别', dataIndex: 'sex', width: 90},
{title: '角色', dataIndex: 'authType', width: 200},
{title: '操作', dataIndex: 'operation', fixed: 'right', width: 100},
],
addUserRules: {
userId: [{type: 'number', required: true, message: '请选择用户', trigger: 'change'}],
authType: [{type: 'number', required: true, message: '请选择用户角色', trigger: 'change'}],
},
};
},
};
</script>