151 lines
4.1 KiB
HTML
151 lines
4.1 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>订阅格式转换 · Xray → Clash Mihomo</title>
|
||
<style>
|
||
:root {
|
||
color-scheme: light dark;
|
||
}
|
||
html,
|
||
body {
|
||
height: 100%;
|
||
margin: 0;
|
||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto,
|
||
Helvetica, Arial;
|
||
}
|
||
.container {
|
||
max-width: 880px;
|
||
margin: 40px auto;
|
||
padding: 0 16px;
|
||
}
|
||
h1 {
|
||
font-size: 20px;
|
||
margin: 0 0 16px;
|
||
}
|
||
textarea {
|
||
width: 100%;
|
||
min-height: 160px;
|
||
padding: 12px;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||
font-size: 13px;
|
||
border-radius: 8px;
|
||
border: 1px solid #8883;
|
||
background: transparent;
|
||
}
|
||
.row {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin: 12px 0;
|
||
}
|
||
.row > * {
|
||
flex: 1;
|
||
}
|
||
button {
|
||
padding: 10px 14px;
|
||
border-radius: 8px;
|
||
border: 1px solid #8883;
|
||
background: #09f;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
}
|
||
button.secondary {
|
||
background: transparent;
|
||
color: inherit;
|
||
}
|
||
.out {
|
||
white-space: pre;
|
||
padding: 12px;
|
||
border: 1px solid #8883;
|
||
border-radius: 8px;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||
font-size: 13px;
|
||
min-height: 120px;
|
||
}
|
||
.hint {
|
||
color: #888;
|
||
font-size: 12px;
|
||
}
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.right {
|
||
text-align: right;
|
||
}
|
||
a {
|
||
color: inherit;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>订阅格式转换(Xray 链接/订阅 → Clash Mihomo)</h1>
|
||
<p class="hint">
|
||
支持 vless/vmess/trojan 链接,或整段订阅文本/订阅地址(自动识别
|
||
base64)。
|
||
</p>
|
||
<textarea
|
||
id="input"
|
||
placeholder="在此粘贴 vless://、vmess:// 或 trojan:// 链接,或订阅文本"
|
||
></textarea>
|
||
<div class="grid">
|
||
<div class="hint">
|
||
输出为 YAML 的 proxies 段,可作 provider 或粘贴入主配置。
|
||
</div>
|
||
<div class="right">
|
||
<button id="btnConvert">转换为 Clash (YAML)</button>
|
||
<button id="btnDownload" class="secondary">下载 proxies.yaml</button>
|
||
</div>
|
||
</div>
|
||
<div id="out" class="out"></div>
|
||
</div>
|
||
<script>
|
||
const $ = (s) => document.querySelector(s);
|
||
const inputEl = $("#input");
|
||
const outEl = $("#out");
|
||
const btnConvert = $("#btnConvert");
|
||
const btnDownload = $("#btnDownload");
|
||
|
||
async function convert() {
|
||
const val = inputEl.value.trim();
|
||
if (!val) {
|
||
outEl.textContent = "请输入内容";
|
||
return;
|
||
}
|
||
try {
|
||
const resp = await fetch("/convert/clash", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ input: val }),
|
||
});
|
||
const text = await resp.text();
|
||
if (!resp.ok) throw new Error(text);
|
||
outEl.textContent = text;
|
||
return text;
|
||
} catch (e) {
|
||
outEl.textContent =
|
||
"错误: " + (e && e.message ? e.message : String(e));
|
||
}
|
||
}
|
||
|
||
btnConvert.addEventListener("click", convert);
|
||
btnDownload.addEventListener("click", async () => {
|
||
const text = outEl.textContent || (await convert());
|
||
if (!text) return;
|
||
const blob = new Blob([text], {
|
||
type: "application/yaml;charset=utf-8",
|
||
});
|
||
const a = document.createElement("a");
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = "proxies.yaml";
|
||
a.click();
|
||
setTimeout(() => URL.revokeObjectURL(a.href), 2000);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|