src/routes/Document/components/SearchApi.js (278 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable no-unused-expressions */
import {
Tree,
Empty,
message,
Typography,
Button,
Row,
Col,
Spin,
Tooltip,
} from "antd";
import React, { useEffect, useImperativeHandle, useState } from "react";
import { getRootTag, getParentTagId, getApi } from "../../../services/api";
import { Method } from "./globalData";
import AddAndUpdateTag from "./AddAndUpdateTag";
import AddAndUpdateApiDoc from "./AddAndUpdateApiDoc";
import { getIntlContent } from "../../../utils/IntlUtils";
const { Text } = Typography;
const SearchApi = React.forwardRef((props, ref) => {
const { onSelect, afterUpdate } = props;
const [loading, setLoading] = useState(false);
const [treeData, setTreeData] = useState({});
const [expandedKeys, setExpandedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [document, setDocument] = useState("{}");
const [ext, setExt] = useState("{}");
const queryRootTag = async () => {
setExpandedKeys([]);
setLoading(true);
const { code, data = [], message: msg } = await getRootTag();
setLoading(false);
if (code !== 200) {
message.error(msg);
return;
}
const arr =
data?.map((item, index) => ({
...item,
title: item.name,
key: index.toString(),
isLeaf: false,
})) || [];
if (data?.length) {
const {
code: apiCode,
message: apiMsg,
data: apiDataRecords,
} = await getApi(data[0].id);
if (apiCode !== 200) {
message.error(apiMsg);
return;
}
const { dataList: apiDataList } = apiDataRecords;
data[0].apiDataList = apiDataList;
setTreeData(arr);
// 默认选中第一个
setSelectedKeys(["0"]);
onSelect(["0"], { node: { props: arr[0] } });
} else {
setTreeData(arr);
setSelectedKeys([]);
}
};
const onExpand = async (keys, { expanded, node }) => {
setExpandedKeys(keys);
if (expanded === false) {
return;
}
setLoading(true);
const { id, hasChildren, eventKey } = node.props;
const newTreeData = [...treeData];
let showAddTag = true;
let resData = [];
const eventKeys = eventKey
.split("-")
.map((v, i, arr) => arr.slice(0, i + 1).join("-"));
if (hasChildren) {
const { code, message: msg, data } = await getParentTagId(id);
setLoading(false);
if (code !== 200) {
message.error(msg);
return Promise.reject();
}
resData = data;
} else {
const { code, message: msg, data } = await getApi(id);
setLoading(false);
if (code !== 200) {
message.error(msg);
return Promise.reject();
}
const { dataList } = data;
if (dataList.length) {
showAddTag = false;
}
resData = dataList;
}
const curNode = eventKeys.reduce((pre, cur, curIndex, curArray) => {
const el = pre.find((item) => item.key === cur);
if (curIndex === curArray.length - 1) {
return el;
} else {
return el.children || [];
}
}, newTreeData);
curNode.children = resData?.map((item, index) => ({
...item,
title: hasChildren ? (
item.name
) : (
<>
<Text code>{Method[item.httpMethod]}</Text>
<Tooltip placement="topLeft" arrowPointAtCenter title={item.apiPath}>
{item.apiPath}
</Tooltip>
</>
),
key: `${eventKey}-${index}`,
isLeaf: !hasChildren,
}));
curNode.children.push({
selectable: false,
title: (
<Row gutter={18}>
{showAddTag && (
<Col span={12}>
<Button
type="primary"
ghost
size="small"
onClick={() =>
addOrUpdateTag({
parentTagId: id,
})
}
>
{getIntlContent("SHENYU.DOCUMENT.APIDOC.SEARCH.ADD_MODULE")}
</Button>
</Col>
)}
<Col span={12}>
<Button
type="primary"
ghost
size="small"
onClick={() =>
addOrUpdateApi({
tagIds: [id],
})
}
>
+ Api
</Button>
</Col>
</Row>
),
key: `${eventKey}-operator`,
isLeaf: true,
});
setTreeData(newTreeData);
};
const [openTag, setOpenTag] = useState(false);
const [tagForm, setTagForm] = useState({});
const handleTagCancel = () => {
setOpenTag(false);
tagForm.resetFields();
};
const handleTagOk = (data) => {
handleTagCancel();
updateTree(data, "tag");
};
const [openApi, setOpenApi] = useState(false);
const [apiForm, setApiForm] = useState({});
const handleApiCancel = () => {
setOpenApi(false);
tagForm.resetFields();
};
const handleApiOk = (data) => {
handleApiCancel();
updateTree(data, "api");
};
const addOrUpdateApi = (data) => {
apiForm.resetFields();
apiForm.setFieldsValue({
...data,
});
setDocument(data.document || "{}");
setExt(data.ext || "{}");
setOpenApi(true);
};
const addOrUpdateTag = (data) => {
tagForm.setFieldsValue({
...data,
});
setOpenTag(true);
};
const updateTree = (data, refType) => {
if (!data?.id) {
queryRootTag();
return;
}
let allNodes = treeData.flatMap((i) =>
i.children ? [...i.children, i] : i,
);
let curNodeIdx = allNodes.findIndex((t) => t.id && t.id === data.id) ?? -1;
if (curNodeIdx === -1) {
return;
}
if (refType === "tag") {
allNodes[curNodeIdx].title = data.name;
} else if (refType === "api") {
allNodes[curNodeIdx].title = (
<>
<Text code>{Method[data.httpMethod]}</Text>
<Tooltip placement="topLeft" arrowPointAtCenter title={data.apiPath}>
{data.apiPath}
</Tooltip>
</>
);
}
// forceUpdate tree
setTreeData();
setTreeData(treeData);
afterUpdate(data, refType);
};
useImperativeHandle(ref, () => ({
addOrUpdateApi,
addOrUpdateTag,
updateTree,
}));
useEffect(() => {
queryRootTag();
}, []);
return (
<div>
{treeData?.length ? (
<Spin spinning={loading}>
<Tree
onSelect={(keys, e) => {
setSelectedKeys(keys);
onSelect(keys, e);
}}
treeData={treeData}
onExpand={onExpand}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
defaultSelectedKeys={["0"]}
/>
</Spin>
) : (
<Empty style={{ padding: "80px 0" }} description={false} />
)}
<Button
block
type="dashed"
onClick={() => addOrUpdateTag({ parentTagId: "0" })}
>
{getIntlContent("SHENYU.DOCUMENT.APIDOC.SEARCH.ADD_ROOT_MODULE")}
</Button>
<AddAndUpdateTag
visible={openTag}
formLoaded={setTagForm}
onOk={handleTagOk}
onCancel={handleTagCancel}
/>
<AddAndUpdateApiDoc
visible={openApi}
document={document}
updateDocument={(obj) => setDocument(JSON.stringify(obj.updated_src))}
ext={ext}
updateExt={(obj) => setExt(JSON.stringify(obj.updated_src))}
formLoaded={setApiForm}
onOk={handleApiOk}
onCancel={handleApiCancel}
/>
</div>
);
});
export default SearchApi;