Swagger UI切换选择项后折叠内容无法显示问题求助
解决Swagger折叠后切换服务器路由不显示的问题
问题现象
此前实现了Swagger中基于服务器端口的路由过滤功能,正常状态下过滤可正常工作:
但发现一个bug:当折叠标签组后切换服务器选项,对应路由内容无法显示:
问题原因
折叠标签组时,Swagger会修改DOM结构和元素类名(比如移除is-open类),原过滤逻辑仅修改元素的display样式,未处理Swagger内部的折叠状态,导致折叠后的标签组即使有匹配路由,也不会自动展开显示内容。
解决方案
修改过滤逻辑,在匹配到路由时强制展开对应的标签组,确保内容可见。以下是完整的修改后代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Swagger UI Example</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.10.3/swagger-ui.css" crossorigin="anonymous" referrerpolicy="no-referrer" /> <style> body { background-color: #ffffff; font-family: Arial, sans-serif; } </style> </head> <body> <div id="swagger-ui"></div> <!-- Validator badge --> <img src="https://img.shields.io/badge/OpenAPI-Validator-green" alt="OpenAPI Validator Badge"> <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.10.3/swagger-ui-bundle.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.10.3/swagger-ui-standalone-preset.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script> // Example JSON data const openApiJson = { "openapi": "3.1.0", "info": { "title": "blue_traktor_api", "description": "This API handles various functionalities for USEPI, Icons, Dashboard, Settings, Patterns, Devices, Widgets, and more.", "version": "0.1.0", "contact": { "name": "API Support", "url": "", "email": "" } }, "servers": [ { "url": "http://localhost:8000", "description": "TCP server for main operations" }, { "url": "ws://localhost:8080", "description": "WebSocket server for updated parameters" } ], "paths": { "/api/usepi/patterns/create": { "post": { "tags": [ "patterns" ], "summary": "http://localhost:8000", "operationId": "createPattern", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestStructure_PatternRequest" }, "examples": { "PatternExample": { "summary": "An example of a PatternRequest", "value": { "data": { "id": null, "title": "test", "description": "test desc", "icon": "http://localhost:8000/api/icons/Cтатическое оборудование/Статическое оборудование.svg", "createDate": null, "updateDate": null, "favourite": false, "data": { "ID": {}, "QR-Code": {}, "Position": {}, "DeviceParams": {} } } } } } } } }, "responses": { "200": { "description": "Pattern created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GoodResponse_Id" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/api/widgets/get": { "post": { "tags": [ "widgets" ], "summary": "ws://localhost:8080", "operationId": "getWidget", "servers": [ { "url": "ws://localhost:8080" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestStructure_Id" } } } }, "responses": { "200": { "description": "Widget details.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GoodResponse_GetWidgetResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } } }, "components": { "schemas": { "PatternRequest": { "type": "object", "properties": { "id": { "type": "string", "nullable": true }, "title": { "type": "string", "nullable": true }, "description": { "type": "string", "nullable": true }, "icon": { "type": "string", "nullable": true }, "data": { "type": "object", "nullable": true }, "favourite": { "type": "boolean", "nullable": true } } }, "RequestStructure_PatternRequest": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/PatternRequest" } } }, "GoodResponse_Id": { "type": "object", "properties": { "statusCode": { "type": "integer", "format": "int32", "enum": [ 200 ] }, "data": { "$ref": "#/components/schemas/Id" } } }, "Id": { "type": "object", "properties": { "id": { "type": "string", "nullable": true } } }, "ErrorResponse": { "type": "object", "properties": { "statusCode": { "type": "integer", "format": "int32", "enum": [ 500 ] } } }, "RequestStructure_Id": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Id" } } }, "GoodResponse_GetWidgetResponse": { "type": "object", "properties": { "statusCode": { "type": "integer", "format": "int32", "enum": [ 200 ] }, "data": { "$ref": "#/components/schemas/GetWidgetResponse" } } }, "GetWidgetResponse": { "type": "object", "properties": { "id": { "type": "string" }, "title": { "type": "object" }, "folderId": { "type": "string", "nullable": true }, "icon": { "type": "string", "nullable": true }, "data": { "type": "object" }, "type": "string" } } } } }; window.onload = function() { // Initialize Swagger UI with the JSON data const ui = SwaggerUIBundle({ spec: openApiJson, // Using the JSON data dom_id: '#swagger-ui', deepLinking: true, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], layout: "StandaloneLayout" }); window.ui = ui; // Function to filter elements based on selected value in dropdown function filterElements() { const selectElement = document.querySelector("#swagger-ui select"); if (selectElement) { const selectedValue = selectElement.options[selectElement.selectedIndex].textContent.split(' - ')[0]; const parentCatalogs = document.querySelectorAll("#swagger-ui .opblock-tag-section"); parentCatalogs.forEach(parent => { let childVisible = false; const childElements = parent.querySelectorAll("[id^='operations-']:not([id^='operations-tag'])"); childElements.forEach(childElement => { const summaryControl = childElement.querySelector(".opblock-summary-control > div > div"); if (summaryControl) { const summaryText = summaryControl.textContent.trim(); if (summaryText === selectedValue) { childElement.style.display = ''; // Show element if it matches selected value childVisible = true; // 强制展开父标签组 parent.classList.add('is-open'); // 确保标签组内容容器显示 const tagContent = parent.querySelector('.opblock-tag-content'); if (tagContent) { tagContent.style.display = ''; } } else { childElement.style.display = 'none'; // Hide element if it does not match } } }); // Hide parent element if no child elements are visible if (childVisible) { parent.style.display = ''; } else { parent.style.display = 'none'; parent.classList.remove('is-open'); } }); } } // Event listener to detect changes in the dropdown and filter elements document.addEventListener('change', function(event) { const selectElement = document.querySelector("#swagger-ui select"); if (event.target === selectElement) { filterElements(); } }); // MutationObserver to detect when the dropdown is added to the DOM and filter elements const observer = new MutationObserver(() => { const selectElement = document.querySelector("#swagger-ui select"); if (selectElement) { filterElements(); observer.disconnect(); // Stop observing once the dropdown is found and filtered } }); // Start observing the DOM for changes observer.observe(document.querySelector('#swagger-ui'), { childList: true, subtree: true }); }; </script> </body> </html>
修改说明
核心修改在filterElements函数中:
- 当匹配到对应服务器的路由时,给父标签组添加
is-open类,并确保标签组的内容容器显示,强制展开折叠的标签组 - 没有匹配路由的标签组,移除
is-open类并隐藏,保持界面整洁
这样无论标签组之前是否折叠,切换服务器选项时,匹配的路由所在标签组都会自动展开,内容正常显示。
内容的提问来源于stack exchange,提问作者Exdet
相关产品推荐
相关产品推荐

