Files
python_script/flask-dev-api/templates/url_parser.html
2026-06-16 03:30:57 +08:00

250 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome.css') }}">
<title>URL 解析器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #f5f5f5; color: #333;
height: 100vh; overflow-y: auto;
}
.container { max-width: 900px; margin: 0 auto; padding: 24px 28px 40px; }
/* 头部 */
.header { display: flex; align-items: center; margin-bottom: 2px; }
.header h1 { font-size: 18px; font-weight: 700; color: #1a1a1a; }
.subtitle { color: #aaa; font-size: 12px; margin-bottom: 16px; }
/* 卡片 */
.section-card {
background: #fafafa; border: 1px solid #f0f0f0;
border-radius: 10px; padding: 14px 16px; margin-bottom: 12px;
}
.section-title {
font-size: 11px; font-weight: 600; color: #999;
text-transform: uppercase; letter-spacing: 0.06em;
margin-bottom: 12px; display: flex; align-items: center; gap: 6px;
}
.section-title i { font-size: 11px; }
/* 输入 */
.input-wrap { position: relative; }
.input-wrap i {
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
color: #c0c0c0; font-size: 12px; pointer-events: none;
}
.input-wrap input {
width: 100%; padding: 9px 12px 9px 32px;
border: 1px solid #e0e0e0; border-radius: 6px;
font-size: 13px; outline: none; transition: all 0.15s;
}
.input-wrap input::placeholder { color: #c0c0c0; }
.input-wrap input:focus { border-color: #0078d4; box-shadow: 0 0 0 2px rgba(0,120,212,0.06); }
.input-wrap input.err { border-color: #c62828; }
.err-msg { font-size: 12px; color: #c62828; margin-top: 4px; min-height: 16px; }
/* 按钮 */
.btn {
padding: 7px 14px; border: none; border-radius: 6px;
font-size: 12px; font-weight: 600; cursor: pointer;
transition: all 0.12s ease;
display: inline-flex; align-items: center; gap: 5px;
}
.btn-outline { background: #fff; color: #666; border: 1px solid #e0e0e0; }
.btn-outline:hover { background: #f5f5f5; color: #333; }
.run-btn {
width: 100%; padding: 10px 18px;
background: linear-gradient(135deg, #0078d4, #005fa3);
color: #fff; border: none; border-radius: 8px;
font-size: 14px; font-weight: 600; cursor: pointer;
display: flex; align-items: center; justify-content: center; gap: 8px;
margin-top: 12px; transition: all 0.15s;
box-shadow: 0 2px 8px rgba(0,120,212,0.2);
}
.run-btn:hover { background: linear-gradient(135deg, #006cbd, #004e8a); transform: translateY(-1px); }
/* 示例列表 */
.example-list { list-style: none; }
.example-list li {
padding: 6px 10px; border-radius: 5px; cursor: pointer;
font-size: 12px; color: #888; font-family: "SFMono-Regular", Consolas, monospace;
transition: all 0.15s; margin-bottom: 2px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.example-list li:hover { background: #f0f7ff; color: #0078d4; }
/* 结果 */
.result-section { margin-bottom: 20px; }
.result-section:last-child { margin-bottom: 0; }
.result-title {
font-size: 11px; font-weight: 700; color: #aaa;
text-transform: uppercase; letter-spacing: 0.06em;
margin-bottom: 10px; padding-bottom: 6px;
border-bottom: 1px solid #eee;
display: flex; align-items: center; gap: 6px;
}
.result-title i { font-size: 11px; color: #ccc; }
.fields { display: flex; flex-direction: column; gap: 4px; }
.field-row {
display: flex; align-items: center; gap: 10px;
background: #fff; border: 1px solid #e8e8e8; border-radius: 6px;
padding: 8px 12px; transition: border-color 0.15s;
}
.field-row:hover { border-color: #c0c0c0; }
.field-label { width: 64px; font-size: 12px; font-weight: 600; color: #999; flex-shrink: 0; }
.field-value {
flex: 1; font-family: "SFMono-Regular", Consolas, monospace;
font-size: 12px; color: #333; word-break: break-all; min-width: 0;
}
.field-value:empty::after { content: '—'; color: #ddd; }
.btn-cp {
padding: 3px 8px; border: 1px solid #e0e0e0; border-radius: 4px;
background: #fff; color: #aaa; font-size: 11px; cursor: pointer;
flex-shrink: 0; transition: all 0.15s;
}
.btn-cp:hover { background: #f5f5f5; color: #666; }
.btn-cp.copied { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; }
.params { display: flex; flex-direction: column; gap: 4px; }
.param-row {
display: flex; align-items: center; gap: 10px;
background: #fff; border: 1px solid #e8e8e8; border-radius: 6px;
padding: 6px 12px; transition: border-color 0.15s;
}
.param-row:hover { border-color: #c0c0c0; }
.param-key {
width: 120px; flex-shrink: 0;
padding: 3px 8px; background: #f0f7ff; color: #0078d4;
border-radius: 4px; font-family: "SFMono-Regular", Consolas, monospace;
font-size: 12px; font-weight: 600; text-align: center;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.param-val {
flex: 1; font-family: "SFMono-Regular", Consolas, monospace;
font-size: 12px; color: #555; word-break: break-all; min-width: 0;
}
.no-params { font-size: 13px; color: #ccc; padding: 8px 0; }
.empty-state {
text-align: center; padding: 40px 0; color: #ccc;
}
.empty-state i { font-size: 40px; margin-bottom: 12px; display: block; }
.empty-state p { font-size: 13px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-link" style="color:#0078d4;font-size:16px;margin-right:6px;"></i>URL 解析器</h1>
</div>
<p class="subtitle">拆解 URL 各组成部分</p>
<!-- 输入 -->
<div class="section-card">
<div class="section-title"><i class="fas fa-pen"></i> 输入 URL</div>
<div class="input-wrap">
<i class="fas fa-link"></i>
<input type="text" id="urlInput" placeholder="粘贴 URL..."
value="https://user:pass@example.com:8080/path/to/page?key1=value1&key2=value2#section"
onkeydown="if(event.key==='Enter')parse()">
</div>
<div class="err-msg" id="errMsg"></div>
<button class="run-btn" onclick="parse()"><i class="fas fa-search"></i> 解析</button>
</div>
<!-- 示例 -->
<div class="section-card">
<div class="section-title"><i class="fas fa-lightbulb"></i> 示例</div>
<ul class="example-list">
<li onclick="fillUrl(this)">https://user:pass@example.com:8080/path?key=value#hash</li>
<li onclick="fillUrl(this)">http://localhost:3000/api/v1/users?page=1&limit=20</li>
<li onclick="fillUrl(this)">https://www.google.com/search?q=hello+world&lang=zh-CN</li>
<li onclick="fillUrl(this)">ftp://files.example.com:21/pub/doc.pdf</li>
</ul>
</div>
<!-- 结果 -->
<div class="section-card" id="resultCard" style="display:none;">
<div class="result-section">
<div class="result-title"><i class="fas fa-file"></i> URL 组成</div>
<div class="fields" id="fields"></div>
</div>
<div class="result-section" style="margin-top:16px;">
<div class="result-title"><i class="fas fa-list"></i> Query 参数</div>
<div id="params"></div>
</div>
</div>
<div class="empty-state" id="emptyState">
<i class="fas fa-link"></i>
<p>输入 URL 并点击解析</p>
</div>
</div>
<script>
var defs = [
{label:'协议',key:'protocol'},{label:'用户名',key:'username'},{label:'密码',key:'password'},
{label:'主机名',key:'hostname'},{label:'端口',key:'port'},{label:'路径',key:'pathname'},
{label:'查询',key:'search'},{label:'哈希',key:'hash'}
];
function esc(s){return(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
function copyText(text,btn){
var el=document.createElement('textarea');
el.value=text;el.style.cssText='position:fixed;left:-9999px';
document.body.appendChild(el);el.select();document.execCommand('copy');document.body.removeChild(el);
btn.classList.add('copied');
setTimeout(function(){btn.classList.remove('copied');},1500);
}
function fillUrl(el){document.getElementById('urlInput').value=el.textContent;parse();}
function parse(){
var raw=document.getElementById('urlInput').value.trim();
var errMsg=document.getElementById('errMsg');
var card=document.getElementById('resultCard');
var empty=document.getElementById('emptyState');
var inp=document.getElementById('urlInput');
if(!raw){errMsg.textContent='';card.style.display='none';empty.style.display='';inp.classList.remove('err');return;}
try{
var u=new URL(raw);
errMsg.textContent='';inp.classList.remove('err');
card.style.display='';empty.style.display='none';
var fh='';
defs.forEach(function(f){
var v=u[f.key]||'';
fh+='<div class="field-row"><span class="field-label">'+f.label+'</span>'
+'<span class="field-value" id="fv-'+f.key+'">'+esc(v)+'</span>'
+'<button class="btn-cp" onclick="copyText(document.getElementById(\'fv-'+f.key+'\').textContent,this)">复制</button></div>';
});
document.getElementById('fields').innerHTML=fh;
var params=[];
u.searchParams.forEach(function(v,k){params.push({k:k,v:v});});
if(params.length){
var ph='<div class="params">';
params.forEach(function(p){
ph+='<div class="param-row"><span class="param-key" title="'+esc(p.k)+'">'+esc(p.k)+'</span>'
+'<span class="param-val">'+esc(p.v)+'</span>'
+'<button class="btn-cp" onclick="copyText(this.previousElementSibling.textContent,this)">复制</button></div>';
});
ph+='</div>';
document.getElementById('params').innerHTML=ph;
}else{
document.getElementById('params').innerHTML='<div class="no-params">无查询参数</div>';
}
}catch(e){
errMsg.textContent='无效的 URL: '+e.message;
inp.classList.add('err');
card.style.display='none';empty.style.display='';
}
}
parse();
</script>
</body>
</html>