generated from dellevin/template
feat: complete local version to overwrite remote
This commit is contained in:
249
flask-dev-api/templates/url_parser.html
Normal file
249
flask-dev-api/templates/url_parser.html
Normal file
@@ -0,0 +1,249 @@
|
||||
<!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,'&').replace(/</g,'<').replace(/>/g,'>');}
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user