You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 17:39:52