<template>
|
<el-container>
|
<el-header style="background: #ffffff; padding: 0;">
|
<div>
|
<div class="nav-api-header">
|
<div
|
style="display: flex; padding-top: 10px; padding-left: 10px; align-items: center;"
|
>
|
<el-button
|
:style="
|
userName !== projectInfo.responsible &&
|
!isSuperuser
|
? 'margin-right: 10px'
|
: ''
|
"
|
:disabled="currentNode === ''"
|
v-show="!addAPIFlag"
|
type="primary"
|
size="small"
|
icon="el-icon-circle-plus-outline"
|
@click="initResponse = true"
|
>添加接口</el-button
|
>
|
|
<el-button
|
style="margin-right: 10px"
|
v-show="
|
(userName === projectInfo.responsible &&
|
!addAPIFlag) ||
|
(isSuperuser && !addAPIFlag)
|
"
|
type="primary"
|
size="small"
|
icon="el-icon-upload"
|
@click="importYAPIdialogVisible = true"
|
>导入接口</el-button
|
>
|
|
<span>配置</span>
|
<el-select
|
style="margin-left: 10px"
|
placeholder="请选择配置"
|
size="small"
|
v-model="currentConfig"
|
value-key="id"
|
>
|
<el-option
|
v-for="item in configOptions"
|
:key="item.id"
|
:label="item.name"
|
:value="item"
|
></el-option>
|
</el-select>
|
|
<el-button
|
style="margin-left: 10px"
|
v-if="!addAPIFlag"
|
:disabled="!(!addAPIFlag && isSelectAPI)"
|
type="success"
|
size="mini"
|
icon="el-icon-right"
|
@click="move = !move"
|
:title="'移动接口到指定目录'"
|
>移动接口</el-button
|
>
|
|
<el-button
|
style="margin-left: 10px"
|
v-if="isSuperuser && !addAPIFlag"
|
type="danger"
|
icon="el-icon-delete"
|
size="mini"
|
:title="
|
isSuperuser === true
|
? '批量删除所选接口'
|
: '批量删除接口权限不足'
|
"
|
:disabled="!(isSuperuser && isSelectAPI)"
|
@click="del = !del"
|
>批量删除</el-button
|
>
|
|
<el-switch
|
style="margin-left: 10px"
|
v-model="showYAPI"
|
v-if="!addAPIFlag"
|
active-color="#13ce66"
|
inactive-color="#ff4949"
|
active-text="显示YAPI"
|
></el-switch>
|
|
<el-button
|
:disabled="!addAPIFlag"
|
type="text"
|
style="position: absolute; right: 30px"
|
@click="handleBackList"
|
>返回列表</el-button
|
>
|
</div>
|
</div>
|
<el-dialog
|
title="新建目录"
|
width="30%"
|
:visible.sync="dialogVisible"
|
:close-on-click-modal="false"
|
:style="{ 'text-align': 'center' }"
|
>
|
<el-form
|
:model="nodeForm"
|
:rules="rules"
|
ref="nodeForm"
|
label-width="100px"
|
class="project"
|
>
|
<el-form-item label="目录名称" prop="name">
|
<el-input
|
v-model="nodeForm.name"
|
placeholder="请输入目录名称"
|
></el-input>
|
</el-form-item>
|
</el-form>
|
|
<el-radio-group v-model="radio" size="small">
|
<el-radio-button label="根目录"></el-radio-button>
|
<el-radio-button label="子目录"></el-radio-button>
|
</el-radio-group>
|
|
<span
|
slot="footer"
|
class="dialog-footer"
|
style="display: flex; justify-content: flex-end"
|
>
|
<el-button @click="dialogVisible = false"
|
>取 消</el-button
|
>
|
<el-button
|
type="primary"
|
@click="handleConfirm('nodeForm')"
|
>确 定</el-button
|
>
|
</span>
|
</el-dialog>
|
<el-dialog
|
width="30%"
|
title="导入YAPI接口"
|
:style="{ 'text-align': 'center' }"
|
:visible.sync="importYAPIdialogVisible"
|
>
|
<el-form
|
ref="elForm"
|
:model="YAPIformData"
|
:rules="rules"
|
size="medium"
|
label-width="100px"
|
>
|
<el-form-item label="YAPI的地址" prop="yapi_base_url">
|
<el-input
|
v-model="YAPIformData.yapi_base_url"
|
readonly
|
placeholder="请输入YAPI的地址"
|
clearable
|
:style="{ width: '100%' }"
|
></el-input>
|
</el-form-item>
|
<el-form-item label="token" prop="yapi_openapi_token">
|
<el-input
|
v-model="YAPIformData.yapi_openapi_token"
|
readonly
|
placeholder="请输入openapi token"
|
clearable
|
:style="{ width: '100%' }"
|
></el-input>
|
</el-form-item>
|
</el-form>
|
<div
|
style="display: flex; justify-content: right;"
|
slot="footer"
|
class="dialog-footer"
|
>
|
<el-button @click="importYAPIdialogVisible = false"
|
>取消</el-button
|
>
|
<el-button
|
type="primary"
|
@click="handleConfirmYAPI"
|
:title="
|
YAPIformData.yapi_openapi_token ===
|
YAPIfromDataDefaultValue ||
|
YAPIformData.yapi_base_url ===
|
YAPIfromDataDefaultValue
|
? '请到项目详情中配置yapi信息'
|
: '导入YAPI目录和接口'
|
"
|
:disabled="
|
YAPIformData.yapi_openapi_token ===
|
YAPIfromDataDefaultValue ||
|
YAPIformData.yapi_base_url ===
|
YAPIfromDataDefaultValue
|
"
|
>导入</el-button
|
>
|
</div>
|
</el-dialog>
|
</div>
|
</el-header>
|
|
<el-container>
|
<el-aside
|
v-show="!addAPIFlag"
|
style="width: 260px; margin-top: 10px;"
|
>
|
<div class="nav-api-side">
|
<div class="api-tree">
|
<el-input
|
placeholder="请输入关键字进行过滤"
|
v-model="filterText"
|
size="small"
|
clearable
|
prefix-icon="el-icon-search"
|
></el-input>
|
<el-tree
|
ref="tree2"
|
node-key="id"
|
@node-drag-start="handleDragStart"
|
@node-click="handleNodeClick"
|
@node-drag-end="handleDragEnd"
|
@node-expand="handleNodeExpand"
|
@node-collapse="handleNodeCollapse"
|
draggable
|
highlight-current
|
:filter-node-method="filterNode"
|
:data="dataTree"
|
:default-expanded-keys="expandedNodeIds"
|
:default-expand-all="false"
|
:expand-on-click-node="false"
|
>
|
<span
|
class="custom-tree-node"
|
slot-scope="{ node, data }"
|
@mouseenter="mouseenter(node)"
|
@mouseleave="mouseleave"
|
>
|
<span class="custom-tree-node-span">
|
<i
|
v-if="node.childNodes.length > 0"
|
class="el-icon-folder-opened"
|
></i>
|
<i v-else class="el-icon-folder"></i>
|
{{ node.label }}
|
</span>
|
|
<span style="flex-shrink: 0; margin-left: 5px;">
|
<el-badge
|
:value="data.data_count"
|
:max="99"
|
type="primary"
|
></el-badge>
|
</span>
|
|
<span
|
class="icon-group"
|
v-show="node.id === mouseNodeId"
|
>
|
<i
|
class="el-icon-folder-add"
|
@click="dialogVisible = true"
|
></i>
|
<i
|
class="el-icon-edit"
|
@click="renameNode(node)"
|
></i>
|
<i
|
class="el-icon-delete"
|
@click="deleteNode(node)"
|
></i>
|
</span>
|
</span>
|
</el-tree>
|
</div>
|
</div>
|
</el-aside>
|
|
<el-main style="padding: 0;">
|
<api-body
|
v-show="addAPIFlag"
|
:isSaveAs="isSaveAs"
|
:nodeId="currentNode.id"
|
:project="$route.params.id"
|
:response="response"
|
:config="currentConfig"
|
@addSuccess="handleAddSuccess"
|
@refreshTree="getTree"
|
@otherContentChanged="handleOtherContentChange"
|
@headerContentChanged="handleHeaderContentChange"
|
@requestContentChanged="handleRequestContentChange"
|
@extractContentChanged="handleExtractContentChange"
|
@validateContentChanged="handleValidateContentChange"
|
@variablesContentChanged="handleVariablesContentChange"
|
@hooksContentChanged="handleHooksContentChange"
|
></api-body>
|
<api-list
|
v-show="!addAPIFlag"
|
:p-node="currentNode !== '' ? currentNode.id : ''"
|
:project="$route.params.id"
|
:config="currentConfig"
|
:del="del"
|
:back="back"
|
:move.sync="move"
|
:current-page.sync="currentPage"
|
:page-size.sync="pageSize"
|
:visibleTag.sync="visibleTag"
|
:rigEnv.sync="rigEnv"
|
:showYAPI.sync="showYAPI"
|
:isSelectAPI.sync="isSelectAPI"
|
@api="handleAPI"
|
@click-pager="handleChangePage"
|
@resetNode="resetNodeStatus"
|
@refreshTree="getTree"
|
></api-list>
|
</el-main>
|
</el-container>
|
</el-container>
|
</template>
|
|
<script>
|
import ApiBody from "./components/ApiBody";
|
import ApiList from "./components/ApiList";
|
|
export default {
|
name: "RecordApi",
|
components: {
|
ApiBody,
|
ApiList
|
},
|
watch: {
|
filterText(val) {
|
this.$refs.tree2.filter(val);
|
}
|
},
|
computed: {
|
initResponse: {
|
get() {
|
return this.addAPIFlag;
|
},
|
set(val) {
|
this.addAPIFlag = val;
|
this.isSaveAs = false;
|
this.response = {
|
id: "",
|
body: {
|
name: "",
|
times: 1,
|
url: "",
|
method: "POST",
|
header: [
|
{
|
key: "",
|
value: "",
|
desc: ""
|
}
|
],
|
request: {
|
data: [
|
{
|
key: "",
|
value: "",
|
desc: "",
|
type: 1
|
}
|
],
|
params: [
|
{
|
key: "",
|
value: "",
|
desc: "",
|
type: 1
|
}
|
],
|
json_data: ""
|
},
|
validate: [
|
{
|
expect: "200",
|
actual: "status_code",
|
comparator: "equals",
|
type: 2,
|
desc: "默认断言"
|
}
|
],
|
variables: [
|
{
|
key: "",
|
value: "",
|
desc: "",
|
type: 1
|
}
|
],
|
extract: [
|
{
|
key: "",
|
value: "",
|
desc: ""
|
}
|
],
|
hooks: [
|
{
|
setup: "",
|
teardown: ""
|
}
|
]
|
}
|
};
|
}
|
}
|
},
|
data() {
|
const YAPIformDataDefaultValue = "请到项目详情编辑";
|
return {
|
isOtherContentChanged: false,
|
isHeaderContentChanged: false,
|
isRequestContentChanged: false,
|
isExtractContentChanged: false,
|
isValidateContentChanged: false,
|
isVariablesContentChanged: false,
|
isHooksContentChanged: false,
|
expandedNodeIds: [],
|
originalTreeData: [],
|
mouseNodeId: -1,
|
isSuperuser: this.$store.state.is_superuser,
|
userName: this.$store.state.name,
|
projectInfo: {
|
responsible: ""
|
},
|
configOptions: [],
|
currentConfig: "",
|
back: false,
|
del: false,
|
move: false,
|
response: "",
|
nodeForm: {
|
name: ""
|
},
|
rules: {
|
yapi_base_url: [
|
{
|
required: true,
|
message: "yapi的openapi url",
|
trigger: "blur"
|
}
|
],
|
yapi_openapi_token: [
|
{
|
required: true,
|
message: "yapi的openapi token",
|
trigger: "blur"
|
}
|
],
|
name: [
|
{
|
required: true,
|
message: "请输入目录名称",
|
trigger: "blur"
|
},
|
{
|
min: 1,
|
max: 50,
|
message: "最多不超过50个字符",
|
trigger: "blur"
|
}
|
]
|
},
|
radio: "根目录",
|
addAPIFlag: false,
|
treeId: "",
|
maxId: "",
|
dialogVisible: false,
|
importYAPIdialogVisible: false,
|
currentNode: "",
|
data: "",
|
filterText: "",
|
expand: "",
|
dataTree: [],
|
currentPage: 1,
|
pageSize: 10,
|
visibleTag: "",
|
rigEnv: "",
|
showYAPI: true,
|
isSelectAPI: false,
|
isSaveAs: false,
|
YAPIfromDataDefaultValue: YAPIformDataDefaultValue,
|
YAPIformData: {
|
yapi_base_url: YAPIformDataDefaultValue,
|
yapi_openapi_token: YAPIformDataDefaultValue
|
}
|
};
|
},
|
methods: {
|
handleDragStart() {
|
// 保存当前树的状态
|
this.originalTreeData = JSON.parse(JSON.stringify(this.dataTree));
|
},
|
handleNodeExpand(node) {
|
// 当一个目录被展开时,将它的 ID 添加到 expandedNodeIds 中
|
if (!this.expandedNodeIds.includes(node.id)) {
|
this.expandedNodeIds.push(node.id);
|
}
|
},
|
handleNodeCollapse(node) {
|
// 当一个目录被收起时,将它的 ID 从 expandedNodeIds 中移除并且移除所有子节点
|
// 移除当前节点 ID
|
const index = this.expandedNodeIds.indexOf(node.id);
|
if (index !== -1) {
|
this.expandedNodeIds.splice(index, 1);
|
}
|
|
// 递归移除所有子节点 ID
|
if (node.children && node.children.length > 0) {
|
node.children.forEach(childNode => {
|
this.handleNodeCollapse(childNode);
|
});
|
}
|
},
|
// 将目录的状态恢复到未选中状态
|
resetNodeStatus() {
|
this.$refs.tree2.setCurrentKey(null);
|
this.currentNode = "";
|
},
|
handleDragEnd() {
|
this.$confirm("确定要移动这个目录吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
})
|
.then(() => {
|
// 用户确认拖拽操作,更新树形结构
|
this.updateTree();
|
})
|
.catch(() => {
|
// 用户取消操作,恢复原始树形结构
|
this.dataTree = JSON.parse(
|
JSON.stringify(this.originalTreeData)
|
);
|
});
|
},
|
handleAddSuccess() {
|
this.currentPage = 1;
|
this.pageSize = 10;
|
this.rigEnv = "";
|
this.visibleTag = "";
|
this.back = !this.back;
|
this.addAPIFlag = false;
|
this.isSaveAs = false;
|
},
|
handleAPI(response) {
|
this.addAPIFlag = true;
|
this.response = response;
|
this.isSaveAs = true;
|
},
|
handleChangePage(val) {
|
this.currentPage = val;
|
},
|
getTree() {
|
this.$api
|
.getTree(this.$route.params.id, { params: { type: 1 } })
|
.then(resp => {
|
this.dataTree = resp["tree"];
|
this.treeId = resp["id"];
|
this.maxId = resp["max"];
|
});
|
},
|
getConfig() {
|
this.$api.getAllConfig(this.$route.params.id).then(resp => {
|
this.configOptions = resp;
|
const _config = this.configOptions.filter(
|
item => item.is_default === true
|
);
|
if (_config.length) {
|
this.currentConfig = _config[0];
|
}
|
});
|
},
|
// 准备树形数据以供更新
|
prepareTreeDataForUpdate(treeData) {
|
return treeData.map(node => {
|
// 创建一个不包含 data_count 字段的新对象
|
const { data_count, ...nodeWithoutDataCount } = node;
|
|
// 如果有 children 字段,递归处理子目录
|
if (node.children && node.children.length > 0) {
|
return {
|
...nodeWithoutDataCount,
|
children: this.prepareTreeDataForUpdate(node.children)
|
};
|
}
|
|
// 返回处理后的目录
|
return nodeWithoutDataCount;
|
});
|
},
|
updateTree() {
|
const preparedData = this.prepareTreeDataForUpdate(this.dataTree);
|
this.$api
|
.updateTree(this.treeId, {
|
body: preparedData,
|
type: 1
|
})
|
.then(resp => {
|
if (resp["success"]) {
|
this.dataTree = resp["data"]["tree"];
|
this.maxId = resp["data"]["max"];
|
this.$message.success("目录更新成功");
|
} else {
|
this.$message.error(resp.msg);
|
}
|
});
|
},
|
deleteNode(node) {
|
this.$confirm(
|
`删除 ${node.label} 目录下所有接口, 是否继续?`,
|
"提示",
|
{
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
}
|
).then(() => {
|
if (this.currentNode === "") {
|
this.$message.info("请选择一个目录");
|
} else {
|
let dataCopy = JSON.parse(JSON.stringify(this.dataTree)); // 创建树形数据的拷贝
|
this.removeNodeFromData(this.currentNode.id, dataCopy); // 从拷贝的数据中移除目录
|
this.$api
|
.updateTree(this.treeId, {
|
body: dataCopy, // 发送已经移除目录的数据
|
type: 1
|
})
|
.then(resp => {
|
if (resp["success"]) {
|
this.dataTree = resp["data"]["tree"]; // 更新前端的树形数据
|
this.maxId = resp["data"]["max"];
|
this.$message.success("目录更新成功");
|
} else {
|
this.$message.error(resp.msg);
|
}
|
});
|
}
|
});
|
},
|
removeNodeFromData(nodeId, data) {
|
for (let i = 0; i < data.length; i++) {
|
if (data[i].id === nodeId) {
|
data.splice(i, 1);
|
break;
|
} else if (data[i].children) {
|
this.removeNodeFromData(nodeId, data[i].children);
|
}
|
}
|
},
|
renameNode(nodeObj) {
|
this.$prompt("请输入目录名", "编辑", {
|
closeOnClickModal: false,
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
inputPattern: /\S/,
|
inputErrorMessage: "目录名不能为空",
|
inputValue: nodeObj.label
|
}).then(({ value }) => {
|
const parent = this.data.parent;
|
const children = parent.data.children || parent.data;
|
const index = children.findIndex(
|
d => d.id === this.currentNode.id
|
);
|
children[index].label = value;
|
this.updateTree();
|
});
|
},
|
handleConfirm(formName) {
|
this.$refs[formName].validate(valid => {
|
if (valid) {
|
this.append(this.currentNode);
|
this.updateTree();
|
this.dialogVisible = false;
|
this.nodeForm.name = "";
|
}
|
});
|
},
|
handleNodeClick(node, data) {
|
// 点击子目录把页码重置设置为1
|
this.currentPage = 1;
|
this.visibleTag = "";
|
this.addAPIFlag = false;
|
this.isSaveAs = false;
|
this.currentNode = node;
|
this.data = data;
|
this.rigEnv = "";
|
},
|
filterNode(value, data) {
|
if (!value) return true;
|
return data.label.indexOf(value) !== -1;
|
},
|
remove(data, node) {
|
const parent = node.parent;
|
const children = parent.data.children || parent.data;
|
const index = children.findIndex(d => d.id === data.id);
|
children.splice(index, 1);
|
},
|
append(data) {
|
const newChild = {
|
id: ++this.maxId,
|
label: this.nodeForm.name,
|
children: []
|
};
|
if (
|
data === "" ||
|
this.dataTree.length === 0 ||
|
this.radio === "根目录"
|
) {
|
this.dataTree.push(newChild);
|
return;
|
}
|
if (!data.children) {
|
this.$set(data, "children", []);
|
}
|
data.children.push(newChild);
|
if (this.radio === "子目录") {
|
this.expandedNodeIds.push(data.id); // 添加当前父目录的 id
|
}
|
},
|
handleOtherContentChange(hasChanged) {
|
this.isOtherContentChanged = hasChanged;
|
},
|
handleHeaderContentChange(hasChanged) {
|
this.isHeaderContentChanged = hasChanged;
|
},
|
handleRequestContentChange(hasChanged) {
|
this.isRequestContentChanged = hasChanged;
|
},
|
handleExtractContentChange(hasChanged) {
|
this.isExtractContentChanged = hasChanged;
|
},
|
handleValidateContentChange(hasChanged) {
|
this.isValidateContentChanged = hasChanged;
|
},
|
handleVariablesContentChange(hasChanged) {
|
this.isVariablesContentChanged = hasChanged;
|
},
|
handleHooksContentChange(hasChanged) {
|
this.isHooksContentChanged = hasChanged;
|
},
|
closeEditor() {
|
this.addAPIFlag = false;
|
this.isSaveAs = false;
|
},
|
handleBackList() {
|
if (
|
this.isOtherContentChanged ||
|
this.isHeaderContentChanged ||
|
this.isRequestContentChanged ||
|
this.isExtractContentChanged ||
|
this.isValidateContentChanged ||
|
this.isVariablesContentChanged ||
|
this.isHooksContentChanged
|
) {
|
this.$confirm("内容未保存,确定关闭?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
})
|
.then(() => {
|
// 用户确认关闭
|
this.closeEditor();
|
})
|
.catch(() => {
|
// 用户取消关闭
|
});
|
} else {
|
// 没有变化,直接关闭
|
this.closeEditor();
|
}
|
},
|
getYapiInfo() {
|
const pk = this.$route.params.id;
|
this.$api.getProjectYapiInfo(pk).then(resp => {
|
this.projectInfo = resp;
|
if (resp.yapi_base_url !== "") {
|
this.YAPIformData.yapi_base_url = resp.yapi_base_url;
|
}
|
if (resp.yapi_openapi_token !== "") {
|
this.YAPIformData.yapi_openapi_token =
|
resp.yapi_openapi_token;
|
}
|
});
|
},
|
mouseenter(node) {
|
this.mouseNodeId = node.id;
|
},
|
mouseleave() {
|
this.mouseNodeId = -1;
|
},
|
handleConfirmYAPI() {
|
this.$refs["elForm"].validate(valid => {
|
if (!valid) return;
|
const projectId = this.$route.params.id;
|
this.$message.info("如果是首次导入,可能时间稍长,请稍后查看~");
|
this.importYAPIdialogVisible = false;
|
this.$api.addYAPI(projectId).then(resp => {
|
if (resp.success) {
|
let created = "新增: " + resp.createdCount + " 条API";
|
let updated = "更新: " + resp.updatedCount + " 条API";
|
if (resp.createdCount > 0 || resp.updatedCount > 0) {
|
this.$notify.success({
|
title: "导入API提示",
|
message: created + " ; " + updated,
|
duration: this.$store.state.duration
|
});
|
}
|
const NOT_CREATED_AND_UPDATED_CODE = "0002";
|
if (resp.code === NOT_CREATED_AND_UPDATED_CODE) {
|
this.$notify.info({
|
title: "导入API提示",
|
message: resp.msg,
|
duration: this.$store.state.duration
|
});
|
}
|
const CREATED_OR_UPDATED_CODE = "0001";
|
if (resp.code === CREATED_OR_UPDATED_CODE) {
|
this.getTree();
|
// 重置tree目录, 触发子组件更新api
|
// TODO: 改成直接调用子组件的getAPIList方法
|
this.currentNode = "";
|
this.showYAPI = true;
|
}
|
} else {
|
this.$message.error({
|
message: resp.msg,
|
duration: this.$store.state.duration
|
});
|
}
|
});
|
});
|
}
|
},
|
mounted() {
|
this.getTree();
|
this.getConfig();
|
this.getYapiInfo();
|
}
|
};
|
</script>
|
|
<style scoped>
|
.icon-group {
|
margin-right: 6px;
|
}
|
|
.icon-group i {
|
margin-left: 4px;
|
padding: 2px;
|
}
|
</style>
|