Files
wechat_ipad_pro/static/swagger/index.html
2026-02-17 13:06:23 +08:00

763 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
/* Reset */
html,
body {
box-sizing: border-box;
margin: 0;
padding: 0;
overflow-y: scroll;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
body {
padding: 20px;
font-family: Arial, sans-serif;
background-color: #f8f9fa;
}
/* Swagger UI Container */
#swagger-ui {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
/* Floating Window */
.floating-window {
position: fixed;
bottom: 20px;
right: 20px;
padding: 15px 25px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
z-index: 2;
animation: glow 2s infinite alternate;
}
.close-btn {
position: absolute;
top: 5px;
right: 5px;
font-size: 18px;
color: #e74c3c;
cursor: pointer;
}
/* Form and Input Styling */
#xxcaibi {
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin: 20px auto;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
width: 90%;
}
.xxcaibi-group {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.xxcaibi-label {
color: #555;
font-size: 14px;
width: 120px;
}
.xxcaibi-input,
.xxcaibi-switch {
flex: 1;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.xxcaibi-input:focus {
border-color: #4CAF50;
outline: none;
}
.xxcaibi-switch {
width: 40px;
height: 20px;
appearance: none;
background-color: #ccc;
/* 默认状态下的开关背景颜色 */
outline: none;
border-radius: 20px;
position: relative;
cursor: pointer;
transition: background-color 0.3s ease;
}
.xxcaibi-switch:checked {
background-color: #4CAF50;
}
.xxcaibi-switch::before {
content: "";
position: absolute;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: white;
top: 1px;
left: 1px;
transition: transform 0.3s ease;
}
.xxcaibi-switch:checked::before {
transform: translateX(20px);
}
/* Radio Button Group */
.radio-group {
display: flex;
justify-content: space-between;
width: 80%;
}
.radio-group label {
display: flex;
align-items: center;
}
.radio-group input[type="radio"] {
margin-right: 10px;
transform: scale(1.5);
}
.radio-group input[type="radio"]:focus {
outline: none;
}
/* Glow Animation */
@keyframes glow {
from {
box-shadow: 0 0 10px rgba(0, 255, 255, 0.4);
}
to {
box-shadow: 0 0 20px rgba(0, 255, 255, 0.7);
}
}
/* Link Card */
.card {
position: absolute;
top: 0;
left: 0;
width: 100px;
z-index: 2;
transition: transform 0.5s ease, filter 0.5s ease;
}
.card:hover {
transform: scale(1.2);
filter: drop-shadow(0 0 10px rgba(0, 255, 255, 0.7));
}
/* Card Movement Animation */
@keyframes cardMove {
0% {
transform: translate(0, 0);
}
50% {
transform: translate(50vw, 50vh);
}
100% {
transform: translate(0, 0);
}
}
.base-url {
display: none;
}
/* Telegram Link Container */
.tg-link {
display: flex;
margin-top: 20px;
}
/* Telegram Button */
.tg-button {
display: flex;
align-items: center;
padding: 10px 20px;
background-color: #5cadff !important;
color: white !important;
font-size: 16px;
text-decoration: none;
border-radius: 30px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease, transform 0.3s ease;
}
/* Telegram Button Hover Effect */
.tg-button:hover {
background-color: #007ab8 !important;
transform: translateY(-2px);
}
/* Telegram Icon */
.tg-icon {
width: 24px;
height: 24px;
margin-right: 10px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
width: 100%;
}
.container input[type="text"] {
flex: 1;
width: 300px;
/* 初始宽度 */
max-width: 500px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 10px;
font-size: 16px;
}
.container button {
padding: 10px 15px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
margin-left: 12px;
}
.container button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.container button:hover:not(:disabled) {
background-color: #0056b3;
}
#messages {
margin-top: 12px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
max-height: 300px;
overflow-y: auto;
background-color: #f9f9f9;
}
</style>
</head>
<body>
<div id="xxcaibi" class="wrapper" style="display: none;">
<div class="xxcaibi-group">
<label for="admin-key" class="xxcaibi-label">ADMIN_KEY</label>
<input type="text" id="admin-key" class="xxcaibi-input" placeholder="管理员接口 ADMIN_KEY">
</div>
<div class="xxcaibi-group">
<label for="token-key" class="xxcaibi-label">TOKEN_KEY</label>
<input type="text" id="token-key" class="xxcaibi-input" placeholder="普通API TOKEN_KEY">
</div>
<div class="xxcaibi-group">
<label class="xxcaibi-label">Swagger 展开方式</label>
<div class="radio-group">
<label>
<input type="radio" name="expand-options" value="none" checked>不展开
</label>
<label>
<input type="radio" name="expand-options" value="list">只展开API
</label>
<label>
<input type="radio" name="expand-options" value="full">展开所有
</label>
</div>
</div>
<div class="xxcaibi-group">
<label for="copy-base-url" class="xxcaibi-label">是否复制根路径</label>
<input style="flex: none !important" type="checkbox" id="copy-base-url" class="xxcaibi-switch" checked>
<span style="margin-left: 20px;" id="BASE_URL"></span>
</div>
<h4 style="text-align: center;line-height: 1;margin-bottom: 10px">WS 客户端 (同步消息)</h4>
<div class="container">
<input id="urlInput" type="text" placeholder="输入 WebSocket URL" />
<button id="connectButton">连接</button>
<button id="disconnectButton" disabled>取消连接</button>
<button id="clearMessagesButton">清理消息</button>
</div>
<div id="messages"></div>
</div>
<script>
let socket;
document.getElementById('urlInput').value = window.location.origin.replace(/^http/, 'ws') + '/ws/GetSyncMsg?key='
// 更新按钮文本
function updateButton(button, text, isLoading) {
button.innerHTML = isLoading ? text + '...' : text;
button.disabled = isLoading;
}
// 连接按钮点击事件
document.getElementById('connectButton').onclick = function () {
const url = document.getElementById('urlInput').value;
// 如果已经有连接,先关闭
if (socket && socket.readyState === WebSocket.OPEN) {
socket.close();
}
// 更新按钮状态为连接中
updateButton(this, '连接中', true);
// 创建 WebSocket 连接
socket = new WebSocket(url);
// 连接成功时
socket.onopen = function () {
console.log('已连接到服务器');
document.getElementById('messages').innerHTML += '<p>已连接到服务器</p>';
updateButton(document.getElementById('connectButton'), '连接', false); // 重置连接按钮
document.getElementById('disconnectButton').disabled = false; // 启用取消连接按钮
document.getElementById('connectButton').disabled = true;
};
// 接收消息
socket.onmessage = function (event) {
console.log('收到来自服务器的消息:', event.data);
document.getElementById('messages').innerHTML += '<p>服务器: ' + event.data + '</p>';
};
// 连接关闭时
socket.onclose = function () {
console.log('与服务器的连接已关闭');
document.getElementById('messages').innerHTML += '<p>与服务器的连接已关闭</p>';
updateButton(document.getElementById('connectButton'), '连接', false); // 启用连接按钮
document.getElementById('disconnectButton').innerHTML = "取消连接",
document.getElementById('disconnectButton').disabled = true; // 启用连接按钮
document.getElementById('connectButton').disabled = false; // 启用连接按钮
};
// 处理错误
socket.onerror = function (error) {
console.error('WebSocket 错误:', error);
document.getElementById('messages').innerHTML += '<p>WebSocket 错误: ' + error.message + '</p>';
updateButton(document.getElementById('connectButton'), '连接', false); // 重置连接按钮
};
};
// 取消连接按钮点击事件
document.getElementById('disconnectButton').onclick = function () {
updateButton(this, '取消中', true); // 更新为取消中状态
if (socket) {
setTimeout(function () {
socket.close();
}, 30)
}
};
// 清理消息按钮点击事件
document.getElementById('clearMessagesButton').onclick = function () {
document.getElementById('messages').innerHTML = ''; // 清空消息区域
};
</script>
<div id="swagger-ui"></div>
<!-- Floating window -->
<div class="floating-window" style="display: none !important">
</div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"></script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"></script>
<script src="./jquery-3.7.1.slim.min.js"></script>
<!-- 微信验证集成脚本 -->
<script src="./wechat-verify-integration.js"></script>
<script>
function closeDiv(element) {
element.parentElement.style.display = 'none';
}
function isAdminApi(pathKey) {
if (pathKey.startsWith("/admin/")) {
return true
}
return window.xxcaibi.adminUrls[pathKey]
}
const CustomPlugin = function (system) {
return {
statePlugins: {
spec: {
actions: {
updateQueryParam: (paramName, paramValue, isAdminKey = false) => (state) => {
const spec = state.specSelectors.specJson().toJS();
const paths = spec.paths;
Object.keys(paths).forEach(pathKey => {
if (!isAdminKey && isAdminApi(pathKey)) {
// 非 ADMIN_KEY 并且是 ADMIN 接口
return;
}
if (isAdminKey && !isAdminApi(pathKey)) {
// 是 ADMIN_KEY 并且 不是 ADMIN 接口
return;
}
Object.keys(paths[pathKey]).forEach(method => {
const params = paths[pathKey][method].parameters;
params.forEach(param => {
if (param.name === paramName && param.in === 'query') {
param.default = paramValue;
}
});
});
});
state.specActions.updateSpec(JSON.stringify(spec));
},
updateConfigs: (spec) => (state) => {
state.specActions.updateSpec(JSON.stringify(spec));
},
},
wrapActions: {
updateSpec: (oriAction, system) => (...args) => {
return oriAction(...args)
},
},
},
},
};
};
// 检查 Swagger-UI 内的 img.opblock-loading-animation 请求体节点是否加载完成
function checkSwaggerLoadingComplete(targetFunc) {
const targetNode = document.getElementById('swagger-ui');
const config = { childList: true, subtree: true };
const callback = function (mutationsList, observer) {
const loadingElements = targetNode.querySelectorAll('img.opblock-loading-animation');
if (loadingElements.length === 0) {
console.log('Swagger UI loaded successfully');
observer.disconnect(); // 停止观察
targetFunc && targetFunc();
}
};
const observer = new MutationObserver(callback);
setTimeout(function () {
$("#swagger-ui").append("&nbsp;")
}, 100)
observer.observe(targetNode, config); // DOM 节点无变化则不会触发监听
}
// 粘贴到系统剪切板1
function copy(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
// navigator.clipboard 只能在 HTTPS 环境使用; 旧版浏览器兼容性不好
navigator.clipboard.writeText(text).then(function () {
}).catch(function (error) {
console.error('Failed to copy text: ', error);
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
// 粘贴到系统剪切板2
function fallbackCopy(text) {
// 使用 textarea 标签 可以复制 pre 代码标签里的换行
let textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
} catch (err) {
console.error('Fallback copy failed: ', err);
}
document.body.removeChild(textArea);
}
// 复制 API 的路径即描述
function addCopy1(selector) {
let pathElements;
// opblock-summary-path opblock-summary-description
if (selector) {
pathElements = $(selector).parent().find(".opblock-summary-description");
} else {
pathElements = $(".opblock-summary-description");
addCopy2();
}
if (pathElements.length === 0) {
return;
}
console.log("API数量:", pathElements.length);
$(".opblock-summary-post").click(function () {
let status = $(this).children().first().attr("aria-expanded");
if (status === "true") {
return;
}
let curId = $(this).parent().attr("id");
// console.log(curId, status);
setTimeout(function () {
addCopy2(`#${curId}`);
}, 100);
});
pathElements.each(function (index, pathElement) {
pathElement = $(pathElement);
let isCopy = pathElement.attr("copy");
if (isCopy === "true") { // 已经添加过 copy 按钮
return;
}
pathElement.after(
$("<button>复制</button>").css("cursor", "pointer").css("color", "#FFF").css("background", "#2b85e4").css("border-color", "#2b85e4").css("padding", "4px 8px").css("border-radius", "4px").css("margin-right", "10px").click(function () {
// 获取接口文本
let api = this.parentNode.children[1].dataset.path;
let desc = this.parentNode.children[2].textContent;
let text = api + " " + desc;
if (window.xxcaibi.copy) {
text = window.xxcaibi.BASE_URL + text;
}
// console.log(api, desc)
// 调用函数,复制到剪切板
text = text.replace(/([^:])\/+/g, '$1/')
copy(text);
// 提示复制成功
$(this).text("复制成功!");
// 定时改变显示文字
setTimeout(() => {
$(this).text("复制");
}, 500);
return false;
})
);
pathElement.attr("copy", "true");
});
}
// 复制 POST 请求的 JSON 请求体
function addCopy2(selector) {
if (!selector) {
selector = "#swagger-ui"
}
let lis = $(selector + ' li[role="presentation"]');
if (lis.length < 2) {
return;
}
lis.each(function (index, element) {
if (index % 2 === 0) {
return;
}
element = $(element);
let isCopy = element.attr("copy");
if (isCopy === "true") { // 已经添加过 copy 按钮
return;
}
element.after(
$("<button>复制</button>").css("cursor", "pointer").css("color", "#FFF").css("background", "#515a6e").css("border-color", "#17233d").css("padding", "4px 10px").css("border-radius", "4px").css("margin-right", "10px").click(function () {
// 获取接口文本
let preTag = $(selector + " .highlight-code pre")[0];
if (!preTag) {
alert("无法复制");
return;
}
let text = preTag.textContent;
// 调用函数,复制到剪切板
copy(text);
// 提示复制成功
$(this).text("复制成功!");
// 定时改变显示文字
setTimeout(() => {
$(this).text("复制");
}, 500);
})
);
element.attr("copy", "true");
});
}
window.onload = function () {
// Initialize Swagger UI
const ui = SwaggerUIBundle({
url: "swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl,
CustomPlugin,
],
docExpansion: "none", // none(不展开) list(展开operations) full(全部展开)
onComplete: function () {
// Swagger-UI 渲染完成后回调; updateSpec 成功也会调用
// 但是如果数据太多会导致部分 HTML 元素在此时还未加载完成
console.log("Swagger UI onComplete...")
console.log("Swagger UI Version:", window.versions.swaggerUi.version)
parseBaseUrl();
addXXCaiBi();
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
// Operations API 接口的点击事件
$(".opblock-tag").click(function () {
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
});
});
});
},
// parameterMacro: function (operation, parameter) {
// // 展开 API 对应的 operations 时触发, 可以在此修改 指定参数的默认值
// console.log(operation, parameter);
// return "";
// },
});
window.ui = ui;
window.xxcaibi = {
"BASE_URL": "",
"copy": true,
"ADMIN_KEY": "",
"TOKEN_KEY": "",
"adminUrls": {
"/login/GenAuthKey": true,
"/login/GenAuthKey2": true,
},
}
// 监听状态变化
$('#copy-base-url').change(function () {
window.xxcaibi.copy = $(this).is(':checked');
// console.log(window.xxcaibi);
});
$('#admin-key').change(function () {
window.xxcaibi.ADMIN_KEY = $(this).val().trim();
// console.log(window.xxcaibi);
ui.specActions.updateQueryParam("key", window.xxcaibi.ADMIN_KEY, true);
});
$('#token-key').change(function () {
window.xxcaibi.TOKEN_KEY = $(this).val().trim();
// console.log(window.xxcaibi);
if (window.xxcaibi.TOKEN_KEY) {
document.getElementById('urlInput').value = window.location.origin.replace(/^http/, 'ws') + '/ws/GetSyncMsg?key=' + window.xxcaibi.TOKEN_KEY
}
ui.specActions.updateQueryParam("key", window.xxcaibi.TOKEN_KEY);
});
$('.radio-group input[type="radio"]').change(function () {
let value = $(this).val();
let configs = ui.getConfigs();
configs.docExpansion = value
// // 更新整个 UI 数据; 会刷新 UI 导致 onComplete 回调
// const spec = ui.specSelectors.specJson().toJS();
// ui.specActions.updateConfigs(spec);
// 只更新配置; 不会刷新 UI 导致 onComplete 回调
ui.configsActions.toggle();
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
// Operations API 接口的点击事件
$(".opblock-tag").click(function () {
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
});
});
}); // DOM 节点无变化不会触发监听
});
let parseBaseUrl = function () {
let config = window.ui.specSelectors.specJson().toJS(); // 获取 spec 对象
let basePath = config.basePath || ''; // 获取 basePath
basePath = "" ? basePath === "/" : basePath;
// 获取当前网址的协议、域名和端口
let protocol = window.location.protocol;
let hostname = window.location.hostname;
let port = window.location.port;
// 组合协议、域名和端口
const baseUrl = protocol + "//" + hostname + (port ? ":" + port : "") + basePath;
// 显示结果
console.log("BASE_URL:", baseUrl);
window.xxcaibi.BASE_URL = baseUrl;
}
let addXXCaiBi = function () {
$("#xxcaibi").insertAfter('.information-container:first').css('display', 'block');
// 判断 tg-link 不存在
if (!document.getElementsByClassName("tg-link").length) {
$(".info__contact").append(
'<div class="tg-link">' +
'<a href="https://t.me/wx_ipad" target="_blank" class="tg-button">' +
'<img src="https://upload.wikimedia.org/wikipedia/commons/8/82/Telegram_logo.svg" alt="Telegram Logo" class="tg-icon">' +
'加入 Telegram 群' +
'</a>' +
'</div>'
);
}
$("#BASE_URL").html("BASE_URL: " + window.xxcaibi.BASE_URL);
}
};
</script>
</body>
</html>