This commit is contained in:
iqudoo
2026-05-24 20:46:04 +08:00
parent 2e072ff833
commit 87232d5c6d
5 changed files with 1418 additions and 121 deletions

View File

@@ -1,2 +1,2 @@
# 开发环境配置
VITE_API_URL=http://miniweb.iwxerp.com/api
VITE_API_URL=https://vet.iqudoo.com/api

11
package-lock.json generated
View File

@@ -1,13 +1,14 @@
{
"name": "api-docs",
"name": "iqudoo-miniweb-backend-docs",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "api-docs",
"name": "iqudoo-miniweb-backend-docs",
"version": "0.0.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"axios": "^1.8.3",
"element-plus": "^2.9.6",
"vue": "^3.5.13",
@@ -74,9 +75,9 @@
}
},
"node_modules/@element-plus/icons-vue": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"

View File

@@ -11,6 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"axios": "^1.8.3",
"element-plus": "^2.9.6",
"vue": "^3.5.13",

1241
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -679,16 +679,14 @@
</el-tab-pane>
<!-- 调试模块tab -->
<el-tab-pane label="调试工具" name="debug">
<!-- <el-tab-pane label="调试工具" name="debug">
<div class="debug-module">
<div class="debug-layout">
<!-- 左侧:请求配置区域 -->
<div class="debug-request-panel">
<div class="debug-panel-header">
<span class="panel-title">请求</span>
</div>
<div class="debug-panel-content">
<!-- URL配置 -->
<div class="debug-url-section">
<div class="debug-label">接口地址</div>
<el-input
@@ -704,7 +702,6 @@
</el-input>
</div>
<!-- 接口选择 -->
<div class="debug-form-section">
<el-form :model="debugForm" ref="debugFormRef" label-position="top">
<el-form-item label="调试接口">
@@ -727,7 +724,6 @@
/>
</el-form-item>
<!-- 变量管理(可折叠) -->
<el-collapse
v-model="variableCollapseActive"
class="debug-collapse"
@@ -774,7 +770,6 @@
</el-collapse-item>
</el-collapse>
<!-- 参数配置 -->
<el-collapse
v-model="paramsCollapseActive"
class="debug-collapse"
@@ -795,7 +790,6 @@
</el-radio-group>
</template>
<!-- 表单模式 -->
<template v-if="paramInputMode === 'form'">
<el-form-item
v-for="param in debugFormParams"
@@ -852,7 +846,6 @@
</el-form-item>
</template>
<!-- JSON模式 -->
<template v-else>
<div class="json-mode-container">
<div class="json-mode-header">
@@ -909,7 +902,6 @@
</div>
</div>
<!-- 右侧:响应结果区域 -->
<div class="debug-response-panel">
<div class="debug-panel-header">
<span class="panel-title">响应</span>
@@ -958,7 +950,7 @@
</div>
</div>
</div>
</el-tab-pane>
</el-tab-pane> -->
<el-tab-pane label="导出文档" name="export">
<!-- 导出模块 -->
@@ -3102,10 +3094,73 @@ const getActionDisplayName = (action, parentPath) => {
return action;
};
// 构建树形结构(支持无限级
const buildApiTree = (apiList) => {
const tree = {};
// 解析点分路径,构建目录/文件树(最后一段为接口,其余为目录
const buildTree = (paths) => {
const root = {};
paths.forEach((path) => {
const parts = path.split(".");
let current = root;
parts.forEach((part, index) => {
const isFile = index === parts.length - 1;
if (!current[part]) {
current[part] = isFile
? { type: "file" }
: { type: "dir", children: {} };
}
if (!isFile && current[part].type !== "dir") {
throw new Error(`冲突: "${part}" 不能同时是文件和目录`);
}
if (!isFile) {
current = current[part].children;
}
});
});
return root;
};
// 将 buildTree 结果转为侧边栏节点,并挂载 API 对象
const convertTreeToUiNodes = (treeObj, parentPath, apiMap) => {
return Object.entries(treeObj)
.filter(([, node]) => node.type === "dir")
.map(([name, node]) => {
const fullPath = parentPath ? `${parentPath}.${name}` : name;
const children = [];
const apis = [];
Object.entries(node.children).forEach(([part, child]) => {
if (child.type === "file") {
const api = apiMap.get(`${fullPath}.${part}`);
if (api) apis.push(api);
} else if (child.type === "dir") {
children.push(...convertTreeToUiNodes({ [part]: child }, fullPath, apiMap));
}
});
return {
key: fullPath,
displayName: name,
fullPath,
children,
apis,
};
})
.sort((a, b) => {
if (a.children.length > 0 && b.children.length === 0) return -1;
if (a.children.length === 0 && b.children.length > 0) return 1;
return a.displayName.localeCompare(b.displayName);
});
};
// 按点分路径分类接口(有层级 / 未分组)
const classifyApisByPath = (apiList) => {
const ungrouped = [];
const groupedApis = [];
apiList.forEach((api) => {
if (!api.action || typeof api.action !== "string") {
@@ -3114,65 +3169,117 @@ const buildApiTree = (apiList) => {
}
const parts = api.action.split(".");
// 如果只有一个部分没有点分割作为未分组API
if (parts.length === 1) {
// 没有包名,直接显示
ungrouped.push(api);
} else if (parts.length === 0 || parts[0] === "") {
// 空字符串也作为未分组API
if (parts.length <= 1 || parts[0] === "") {
ungrouped.push(api);
} else {
// 最后一部分是action名称其余都是包名
const actionName = parts[parts.length - 1];
const packageParts = parts.slice(0, -1);
let current = tree;
let fullPath = "";
// 构建无限级分组
let lastNode = null;
packageParts.forEach((part) => {
fullPath = fullPath ? `${fullPath}.${part}` : part;
if (!current[part]) {
current[part] = {
key: fullPath,
displayName: part,
fullPath: fullPath,
children: {},
apis: [],
};
groupedApis.push(api);
}
lastNode = current[part];
current = current[part].children;
});
// 添加API到最后一级节点
if (lastNode) {
lastNode.apis.push(api);
return { groupedApis, ungrouped };
};
// 导出树节点排序:目录在前,接口在后,同类型按名称排序
const sortExportNodes = (nodes) => {
return nodes.sort((a, b) => {
if (!a.isApi && b.isApi) return -1;
if (a.isApi && !b.isApi) return 1;
return a.label.localeCompare(b.label);
});
};
// 将 buildTree 结果转为导出树节点el-tree 格式)
const convertTreeToExportNodes = (treeObj, parentPath, apiMap) => {
const result = [];
Object.entries(treeObj).forEach(([name, node]) => {
if (node.type === "dir") {
const fullPath = parentPath ? `${parentPath}.${name}` : name;
const children = [];
Object.entries(node.children).forEach(([part, child]) => {
if (child.type === "file") {
const api = apiMap.get(`${fullPath}.${part}`);
if (api) {
children.push({
id: api.uniqueId,
label: getActionDisplayName(api.action, fullPath),
isApi: true,
api,
});
}
} else if (child.type === "dir") {
const subNodes = convertTreeToExportNodes({ [part]: child }, fullPath, apiMap);
children.push(...subNodes);
}
});
result.push({
id: fullPath,
label: name,
isApi: false,
children: sortExportNodes(children),
});
} else if (node.type === "file") {
const actionPath = parentPath ? `${parentPath}.${name}` : name;
const api = apiMap.get(actionPath);
if (api) {
result.push({
id: api.uniqueId,
label: api.action,
isApi: true,
api,
});
}
}
});
// 转换为数组并排序
const convertToArray = (obj) => {
return Object.values(obj)
.map((node) => {
const children =
Object.keys(node.children).length > 0 ? convertToArray(node.children) : [];
return {
...node,
children: children,
};
})
.sort((a, b) => {
// 先按是否有children排序有children的在前然后按displayName排序
if (a.children.length > 0 && b.children.length === 0) return -1;
if (a.children.length === 0 && b.children.length > 0) return 1;
return a.displayName.localeCompare(b.displayName);
});
return sortExportNodes(result);
};
const treeArray = convertToArray(tree);
// 构建导出树(支持无限级)
const buildExportTree = (apiList) => {
const { groupedApis, ungrouped } = classifyApisByPath(apiList);
const result = [];
if (groupedApis.length > 0) {
try {
const tree = buildTree(groupedApis.map((api) => api.action));
const apiMap = new Map(groupedApis.map((api) => [api.action, api]));
result.push(...convertTreeToExportNodes(tree, "", apiMap));
} catch (error) {
console.error("导出树路径解析冲突:", error);
groupedApis.forEach((api) => ungrouped.push(api));
}
}
ungrouped.forEach((api) => {
result.push({
id: api.uniqueId,
label: api.action,
isApi: true,
api,
});
});
return sortExportNodes(result);
};
// 构建树形结构(支持无限级)
const buildApiTree = (apiList) => {
const { groupedApis, ungrouped } = classifyApisByPath(apiList);
let treeArray = [];
if (groupedApis.length > 0) {
try {
const tree = buildTree(groupedApis.map((api) => api.action));
const apiMap = new Map(groupedApis.map((api) => [api.action, api]));
treeArray = convertTreeToUiNodes(tree, "", apiMap);
} catch (error) {
console.error("接口路径解析冲突:", error);
groupedApis.forEach((api) => ungrouped.push(api));
}
}
// 未分组的API也添加到结果中
ungrouped.forEach((api) => {
@@ -3671,57 +3778,6 @@ const copyApiMarkdown = async () => {
}
};
// 将API树转换为导出树结构
const convertToExportTree = (nodes, parentPath = "") => {
const result = [];
nodes.forEach((node) => {
const nodePath = parentPath ? `${parentPath}.${node.displayName}` : node.displayName;
const hasChildren = node.children && node.children.length > 0;
const hasApis = node.apis && node.apis.length > 0;
// 如果节点有子节点,需要创建目录节点
if (hasChildren) {
const children = [];
// 添加子节点
children.push(...convertToExportTree(node.children, nodePath));
// 添加API节点
if (hasApis) {
node.apis.forEach((api) => {
children.push({
id: api.uniqueId,
label: getActionDisplayName(api.action, node.fullPath || parentPath),
isApi: true,
api: api,
});
});
}
result.push({
id: node.key,
label: node.displayName,
isApi: false,
children: children,
});
}
// 如果节点只有API而没有子节点直接显示接口不创建目录
else if (hasApis) {
node.apis.forEach((api) => {
result.push({
id: api.uniqueId,
label: getActionDisplayName(api.action, node.fullPath || parentPath),
isApi: true,
api: api,
});
});
}
});
return result;
};
// 导出树数据
const exportTreeData = computed(() => {
const apiList = exportSearchQuery.value
@@ -3730,8 +3786,7 @@ const exportTreeData = computed(() => {
return api.action.toLowerCase().includes(exportSearchQuery.value.toLowerCase());
})
: apis.value;
const tree = buildApiTree(apiList);
return convertToExportTree(tree);
return buildExportTree(apiList);
});
// 计算总API数量
@@ -4139,7 +4194,6 @@ const handleExportSelected = async () => {
}
.api-detail {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
animation: fadeIn 0.5s ease;