v0.3.7: 恢复前端Vue源码 + 修复AdminDashboard 401根源

This commit is contained in:
2026-05-17 13:26:36 +08:00
parent 09be4c307e
commit 8cd4dabb60
178 changed files with 20570 additions and 5 deletions

View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>CloudSearch 管理后台</title>
<style>
:root{--bg:#f5f6fa;--card:#fff;--text:#2c3e50;--sub:#7f8c8d;--pri:#3498db;--pri-hover:#2980b9;--danger:#e74c3c;--success:#27ae60;--warn:#f39c12;--border:#e8ecf1;--sidebar-bg:#1a1a2e;--sidebar-text:#b0b8c8;--sidebar-active:#fff;--radius:8px}
*{margin:0;padding:0;box-sizing:border-box}
#app{display:flex;flex:1}body{font:14px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:var(--bg);color:var(--text);display:flex;min-height:100vh}
.sidebar{width:220px;background:var(--sidebar-bg);color:var(--sidebar-text);flex-shrink:0;display:flex;flex-direction:column}
.sidebar-title{padding:20px;font-size:17px;font-weight:700;color:#fff;border-bottom:1px solid rgba(255,255,255,.08)}
.nav-item{display:flex;align-items:center;gap:8px;padding:9px 16px 9px 20px;cursor:pointer;color:var(--sidebar-text);font-size:13px;transition:.15s;border-left:3px solid transparent}
.nav-item:hover{color:#fff;background:rgba(255,255,255,.05)}
.nav-item.active{color:var(--sidebar-active);background:rgba(52,152,219,.15);border-left-color:var(--pri)}
.main{flex:1;display:flex;flex-direction:column;overflow:hidden}
.topbar{padding:16px 24px;font-size:16px;font-weight:600;background:var(--card);border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}
#content{flex:1;overflow-y:auto;padding:24px}
.card{background:var(--card);border-radius:var(--radius);padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04)}
.card-title{font-size:14px;font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px}
.stats-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:12px;margin-bottom:20px}
.stat-card{background:var(--card);border-radius:var(--radius);padding:8px 12px;box-shadow:0 1px 3px rgba(0,0,0,.04);display:flex;align-items:center;justify-content:space-between}
.form-group{margin-bottom:14px;display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap}
.form-group label{font-size:13px;color:var(--text);font-weight:500;min-width:130px;padding-top:6px}
.form-control{padding:7px 10px;border:1px solid var(--border);border-radius:6px;font-size:13px;outline:none;transition:.15s;max-width:360px;width:100%}
.form-control:focus{border-color:var(--pri)}
.form-control.wide{max-width:500px}
textarea.form-control{min-height:70px;resize:vertical}
.btn{padding:7px 16px;border:none;border-radius:6px;font-size:13px;cursor:pointer;font-weight:500;transition:.15s;display:inline-flex;align-items:center;gap:4px}
.btn-pri{background:var(--pri);color:#fff}.btn-pri:hover{background:var(--pri-hover)}
.btn-danger{background:var(--danger);color:#fff}.btn-danger:hover{background:#c0392b}
.btn-outline{background:transparent;border:1px solid var(--border);color:var(--text)}.btn-outline:hover{background:var(--bg)}
.btn-sm{padding:4px 10px;font-size:12px}
.toggle{position:relative;display:inline-block;width:42px;height:22px}
.toggle input{opacity:0;width:0;height:0}
.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#ccc;border-radius:22px;transition:.2s}
.toggle-slider:before{position:absolute;content:"";height:16px;width:16px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}
.toggle input:checked+.toggle-slider{background:var(--pri)}
.toggle input:checked+.toggle-slider:before{transform:translateX(20px)}
.toast{position:fixed;top:16px;right:16px;padding:12px 20px;border-radius:8px;color:#fff;font-size:13px;z-index:999;animation:slideIn .3s ease}
.toast-success{background:var(--success)}.toast-error{background:var(--danger)}
@keyframes slideIn{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}
.loading{text-align:center;padding:40px;color:var(--sub)}
</style>
</head>
<body>
<div id="app">
<div class="sidebar">
<div class="sidebar-title">CloudSearch</div>
<div class="nav-item active" data-page="dashboard" onclick="nav('dashboard')">📊 仪表盘</div>
<div class="nav-item" data-page="sys-site" onclick="nav('sys-site')">⚙️ 网站设置</div>
<div class="nav-item" data-page="sys-services" onclick="nav('sys-services')">🔗 外部服务</div>
<div class="nav-item" data-page="sys-manage" onclick="nav('sys-manage')">🖥 服务管理</div>
<div class="nav-item" data-page="sys-strategy" onclick="nav('sys-strategy')">🚀 性能配置</div>
<div class="nav-item" data-page="sys-password" onclick="nav('sys-password')">🔑 修改密码</div>
<div class="nav-item" data-page="cloud-config" onclick="nav('cloud-config')">☁️ 网盘设置</div>
<div class="nav-item" data-page="cleanup" onclick="nav('cleanup')">🗑 存储清理</div>
<div style="margin-top:auto;padding:12px"><button class="btn btn-outline btn-sm" onclick="logout()" style="width:100%;color:var(--sidebar-text)">退出登录</button></div>
</div>
<div class="main">
<div class="topbar"><span id="pageTitle">仪表盘</span><span id="appVersion" style="font-size:12px;color:var(--sub)">v-</span></div>
<div id="content"></div>
</div>
</div>
<script src="/admin/js/admin-core.js"></script>
<script src="/admin/js/admin-helpers.js"></script>
<script src="/admin/js/admin-login.js"></script>
<script src="/admin/js/admin-dashboard.js"></script>
<script src="/admin/js/admin-site.js"></script>
<script src="/admin/js/admin-services.js"></script>
<script src="/admin/js/admin-password.js"></script>
<script src="/admin/js/cloud/cloud-core.js"></script>
<script src="/admin/js/cloud/cloud-render.js"></script>
<script src="/admin/js/cloud/cloud-dialog.js"></script>
<script src="/admin/js/cloud/cloud-actions.js"></script>
<script src="/admin/js/admin-cleanup.js"></script>
<script src="/admin/js/admin-boot.js"></script>
</body>
</html>

View File

@@ -0,0 +1,15 @@
// Fetch app version
fetch('/api/version').then(r => r.json()).then(d => {
var el = document.getElementById('appVersion');
if (el && d.version) el.textContent = 'v' + d.version;
}).catch(function(){});
(function(){
if(!TOKEN){
document.getElementById('app').innerHTML = '<div class="login-wrap"><div id="content"></div></div>';
page_login(document.getElementById('content'));
} else {
nav('dashboard');
}
})();

View File

@@ -0,0 +1,155 @@
function page_cleanup(c){
c.innerHTML = '<div id="cleanupRoot"><p style="text-align:center;padding:40px">Loading...</p></div>';
loadCleanupPage();
}
function loadCleanupPage(){
Promise.all([
api('/api/admin/cloud-configs').catch(function(){ return []; }),
api('/api/admin/verify/status').catch(function(){ return {}; }),
api('/api/admin/system-configs').catch(function(){ return {}; })
]).then(function(res){
var accounts = res[0] || [];
var vStatus = res[1] || {};
var configs = res[2] || {};
renderCleanupPage(accounts, vStatus, configs);
});
}
function renderCleanupPage(accounts, vStatus, configs){
var lastVerify = vStatus.last_run ? new Date(vStatus.last_run).toLocaleString() : 'never';
var lastCleanup = configs.cleanup_last_run || 'never';
var h = '';
h += '<div class="card"><div class="card-title">Storage Cleanup & Management</div>';
h += '<p style="color:var(--sub);font-size:12px">Cloud verification, space refresh, scheduled cleanup</p></div>';
// Quick Actions
h += '<div class="card"><div class="card-title">Quick Actions</div>';
h += '<div style="display:flex;flex-wrap:wrap;gap:10px;margin-bottom:12px">';
h += '<button class="btn btn-pri" onclick="runVerifyAll()">Verify All</button>';
h += '<button class="btn btn-pri" onclick="runStorageRefresh()">Refresh All Space</button>';
h += '<button class="btn btn-warn" onclick="runFullCleanup()">Full Cleanup</button>';
h += '<button class="btn btn-outline" onclick="runEmptyTrash()">Empty Trash</button>';
h += '</div>';
h += '<div id="actionStatus" style="font-size:12px;color:var(--sub);margin-top:8px"></div>';
h += '<div style="font-size:12px;color:var(--sub);margin-top:4px">Last verify: ' + lastVerify + ' | Last cleanup: ' + lastCleanup + '</div></div>';
// Account Table
h += '<div class="card"><div class="card-title">Account Verification</div>';
h += '<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:13px">';
h += '<thead><tr style="background:var(--bg);border-bottom:2px solid var(--border);text-align:left">';
h += '<th style="padding:8px 10px">Type</th><th style="padding:8px 10px">Nickname</th>';
h += '<th style="padding:8px 10px">Status</th><th style="padding:8px 10px">Space</th>';
h += '<th style="padding:8px 10px">Action</th></tr></thead><tbody>';
accounts.forEach(function(cfg){
var v = cfg.verification_status;
var vIcon = v==='valid'?'OK':(v==='invalid'?'FAIL':'PENDING');
var space = cfg.storage_used ? (cfg.storage_used + ' / ' + (cfg.storage_total||'?')) : '-';
h += '<tr style="border-bottom:1px solid var(--border)">';
h += '<td style="padding:8px 10px">' + (cfg.cloud_type||'?') + '</td>';
h += '<td style="padding:8px 10px">' + (cfg.nickname||'-') + '</td>';
h += '<td style="padding:8px 10px">' + vIcon + '</td>';
h += '<td style="padding:8px 10px;font-size:12px;color:var(--sub)">' + space + '</td>';
h += '<td style="padding:8px 10px"><button class="btn btn-sm btn-outline" onclick="runSingleVerify('+cfg.id+',this)">Verify</button></td></tr>';
});
if(accounts.length===0){
h += '<tr><td colspan="5" style="padding:20px;text-align:center;color:var(--sub)">No accounts found</td></tr>';
}
h += '</tbody></table></div></div>';
// Cleanup Config
h += '<div class="card"><div class="card-title">Cleanup Config</div>';
h += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;font-size:13px">';
h += cfgRow('Cleanup Enabled', 'cleanup_enabled', configs, 'checkbox');
h += cfgRow('File Retention (days)', 'cleanup_file_retention_days', configs, 'number', '7');
h += cfgRow('Log Retention (days)', 'cleanup_log_retention_days', configs, 'number', '30');
h += cfgRow('Empty Trash After', 'cleanup_empty_trash', configs, 'checkbox');
h += cfgRow('Space Threshold Cleanup', 'cleanup_space_threshold_enabled', configs, 'checkbox');
h += cfgRow('Threshold %', 'cleanup_space_threshold_percent', configs, 'number', '90');
h += cfgRow('Delete %', 'cleanup_space_threshold_delete_percent', configs, 'number', '10');
h += '</div>';
h += '<button class="btn btn-pri" style="margin-top:12px" onclick="saveCleanupConfig()">Save Config</button></div>';
document.getElementById('cleanupRoot').innerHTML = h;
}
function cfgRow(label, key, configs, type, placeholder){
var val = configs[key] || '';
if(type==='checkbox'){
var chk = val==='true'?'checked':'';
return '<div><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" class="cleanupCfg" data-key="'+key+'" '+chk+'><span>'+label+'</span></label></div>';
}
return '<div><label style="font-size:12px;color:var(--sub)">'+label+'</label><input class="form-control cleanupCfg" data-key="'+key+'" value="'+val+'" placeholder="'+placeholder+'" style="max-width:120px"></div>';
}
function runVerifyAll(){
var el = document.getElementById('actionStatus');
el.innerHTML = 'Verifying all accounts...';
api('/api/admin/verify/run', {method:'POST'}).then(function(r){
el.innerHTML = 'Done: '+(r.ok||0)+'/'+r.total+' OK, '+r.failed+' failed';
setTimeout(loadCleanupPage, 2000);
}).catch(function(e){
el.innerHTML = 'Verify failed: '+(e.message||'network error');
});
}
function runSingleVerify(id, btn){
btn.disabled = true; btn.textContent = '...';
api('/api/admin/verify/single/'+id, {method:'POST'}).then(function(r){
btn.textContent = r.success ? 'OK' : 'FAIL';
}).catch(function(){ btn.textContent = 'ERR'; })
.finally(function(){ setTimeout(loadCleanupPage, 1500); });
}
function runStorageRefresh(){
var el = document.getElementById('actionStatus');
el.innerHTML = 'Refreshing space info...';
api('/api/admin/storage/refresh', {method:'POST'}).then(function(){
el.innerHTML = 'Space refreshed';
setTimeout(loadCleanupPage, 2000);
}).catch(function(e){
el.innerHTML = 'Refresh failed: '+(e.message||'network error');
});
}
function runFullCleanup(){
var el = document.getElementById('actionStatus');
el.innerHTML = 'Running full cleanup...';
api('/api/admin/cleanup/run', {method:'POST'}).then(function(r){
el.innerHTML = (r.message||'Cleanup done');
setTimeout(loadCleanupPage, 2000);
}).catch(function(e){
el.innerHTML = 'Cleanup failed: '+(e.message||'network error');
});
}
function runEmptyTrash(){
var el = document.getElementById('actionStatus');
if(!confirm('Empty all trash? This cannot be undone.')) return;
el.innerHTML = 'Emptying trash...';
api('/api/admin/cleanup/empty-trash', {method:'POST'}).then(function(r){
el.innerHTML = r.emptied ? 'Trash emptied' : (r.message||'Done');
}).catch(function(e){
el.innerHTML = 'Failed: '+(e.message||'network error');
});
}
function saveCleanupConfig(){
var entries = [];
document.querySelectorAll('.cleanupCfg').forEach(function(el){
var key = el.dataset.key;
var val = el.type==='checkbox' ? (el.checked?'true':'false') : el.value.trim();
entries.push({key:key, value:val});
});
api('/api/admin/system-configs', {
method:'PUT', headers:{'Content-Type':'application/json'},
body:JSON.stringify({entries:entries})
}).then(function(){
toast('Config saved');
}).catch(function(e){
toast('Save failed: '+(e.message||''));
});
}

View File

@@ -0,0 +1,39 @@
var TOKEN = localStorage.getItem('admin_token') || '';
var TITLES = {dashboard:'仪表盘','sys-site':'网站设置','sys-services':'外部服务','sys-manage':'服务管理','sys-strategy':'性能配置','sys-password':'修改密码','cloud-config':'网盘设置',cleanup:'存储清理'};
function api(path, opts){
opts = opts || {};
opts.headers = opts.headers || {};
if(TOKEN) opts.headers['Authorization'] = 'Bearer ' + TOKEN;
return fetch(path, opts).then(function(r){
if(r.status === 401){ localStorage.removeItem('admin_token'); location.reload(); throw new Error('unauth'); }
return r.json();
});
}
function toast(msg, type){
type = type || 'success';
var t = document.createElement('div'); t.className = 'toast toast-'+type; t.textContent = msg;
document.body.appendChild(t);
setTimeout(function(){ t.remove(); }, 2500);
}
document.querySelectorAll('.nav-item').forEach(function(el){
el.onclick = function(){ nav(this.dataset.page); };
});
function nav(page){
document.querySelectorAll('.nav-item').forEach(function(e){ e.classList.remove('active'); });
var target = document.querySelector('[data-page="'+page+'"]');
if(target) target.classList.add('active');
document.getElementById('pageTitle').textContent = TITLES[page] || page;
var c = document.getElementById('content');
c.innerHTML = '<div class="loading">加载中...</div>';
try{ window['page_'+page.replace(/-/g,'_')](c); }
catch(e){ c.innerHTML = '<div class="card"><p>加载出错: '+e.message+'</p></div>'; }
}
function logout(){
localStorage.removeItem('admin_token');
location.reload();
}

View File

@@ -0,0 +1,9 @@
function page_dashboard(c){
api('/api/admin/services').then(function(d){
var h = '<div class="card"><div class="card-title">系统概览</div>'+
'<div class="stats-grid">'+
'<div class="stat-card"><div class="stat-value">'+(d.length||0)+'</div><div class="stat-label">服务数</div></div>'+
'</div></div>';
c.innerHTML = h;
}).catch(function(){ c.innerHTML = '<div class="card">加载失败</div>'; });
}

View File

@@ -0,0 +1 @@
// Helper utilities

View File

@@ -0,0 +1,14 @@
function page_login(c){
c.innerHTML = '<div style="max-width:400px;margin:60px auto"><div class="card"><div class="card-title">管理后台登录</div>'+
'<div class="form-group"><label>密码</label><input id="pwd" class="form-control" type="password" onkeydown="if(event.key===\'Enter\')doLogin()"></div>'+
'<button class="btn btn-pri" onclick="doLogin()">登录</button></div></div>';
}
function doLogin(){
var pwd = document.getElementById('pwd').value;
api('/api/admin/login', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pwd})})
.then(function(d){
if(d.token){ localStorage.setItem('admin_token', d.token); TOKEN = d.token; nav('dashboard'); }
else{ toast(d.error||'密码错误','error'); }
});
}

View File

@@ -0,0 +1,13 @@
function page_sys_password(c){
c.innerHTML = '<div class="card"><div class="card-title">修改密码</div>'+
'<div class="form-group"><label>当前密码</label><input id="oldPwd" class="form-control" type="password"></div>'+
'<div class="form-group"><label>新密码</label><input id="newPwd" class="form-control" type="password"></div>'+
'<button class="btn btn-pri" onclick="changePwd()">修改</button></div>';
}
function changePwd(){
api('/api/admin/change-password', {method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({old:document.getElementById('oldPwd').value,new:document.getElementById('newPwd').value})})
.then(function(d){ toast(d.error||'密码修改成功', d.error?'error':'success'); });
}
function page_sys_manage(c){ page_sys_services(c); }
function page_sys_strategy(c){ c.innerHTML = '<div class="card">性能配置页面</div>'; }

View File

@@ -0,0 +1,10 @@
function page_sys_services(c){
api('/api/admin/services').then(function(d){
var h = '<div class="card"><div class="card-title">外部服务</div>';
(d||[]).forEach(function(s){
h += '<div class="service-card"><div class="service-info"><span class="name">'+s.name+'</span><span class="desc">'+s.url+'</span></div>'+
'<div class="service-actions"><span class="status-dot '+(s.enabled?'status-running':'status-stopped')+'"></span>'+(s.enabled?'运行中':'已停止')+'</div></div>';
});
c.innerHTML = h+'</div>';
});
}

View File

@@ -0,0 +1,14 @@
function page_sys_site(c){
api('/api/admin/system-configs').then(function(d){
var h = '<div class="card"><div class="card-title">网站设置</div>';
h += '<div class="form-group"><label>网站名称</label><input id="site_name" class="form-control" value="'+(d.site_name||'')+'"></div>';
h += '<button class="btn btn-pri" onclick="saveSite()">保存</button></div>';
c.innerHTML = h;
});
}
function saveSite(){
var name = document.getElementById('site_name').value;
api('/api/admin/system-configs', {method:'PUT',headers:{'Content-Type':'application/json'},
body:JSON.stringify({entries:[{key:'site_name',value:name}]})})
.then(function(d){ toast(d.error||'保存成功', d.error?'error':'success'); });
}

View File

@@ -0,0 +1,80 @@
function togCloudType(type, cb){
__cloudToggles[type] = cb.checked;
api('/api/admin/system-configs', {
method:'PUT', headers:{'Content-Type':'application/json'},
body:JSON.stringify({entries:[{key:'cloud_type_'+type+'_enabled', value:cb.checked?'true':'false'}]})
});
}
function togCloudAcc(id, cb){
api('/api/admin/cloud-configs/'+id, {method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({is_active:cb.checked})})
.then(function(d){ if(d.error){ cb.checked=!cb.checked; toast(d.error,'error'); } });
}
function delCloudAcc(id){ var name = '账号#'+id;
if(!confirm('确定删除 '+name+' 吗?')) return;
api('/api/admin/cloud-configs/'+id, {method:'DELETE'}).then(function(d){
toast(d.error?d.error:'已删除 '+name, d.error?'error':'success');
nav('cloud-config');
});
}
function doSaveAccount(){
var body = {
cloud_type: document.getElementById('dlg_type').value,
// default off, will be set by manual toggle
};
var cookie = document.getElementById('dlg_cookie').value.trim();
if(!cookie){ toast('请先输入或扫码获取 Cookie','error'); return; }
body.cookie = cookie;
body.promotion_account = document.getElementById('dlg_promo').value.trim() || '';
body.nickname = document.getElementById('dlg_nick').value.trim() || '';
body.storage_used = __qrStorageUsed || '';
body.storage_total = __qrStorageTotal || '';
toast('⏳ 正在保存...');
api('/api/admin/cloud-configs', {
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
}).then(function(d){
if(d && d.error){ toast(d.error,'error'); return; }
var savedId = d.id;
toast('⏳ 正在验证 Cookie 并查询空间...');
return api('/api/admin/cloud-configs/'+body.cloud_type+'/test', {
method:'POST', headers:{'Content-Type':'application/json'},
body:JSON.stringify({id: savedId})
});
}).then(function(r){
if(r && r.success){
toast('✅ 验证通过!空间: '+(r.storage_used||'?')+'/'+(r.storage_total||'?'));
} else {
toast('⚠️ 已保存但验证失败: '+(r?r.message:'未知错误'),'error');
}
closeDialog();
nav('cloud-config');
}).catch(function(e){
toast('保存失败: '+(e.message||'网络错误'),'error');
console.error('doSaveAccount error:', e);
});
}
function doSaveAccount_OLD(){
var body = {
cloud_type: document.getElementById('dlg_type').value,
// default off, will be set by verification
};
var cookie = document.getElementById('dlg_cookie').value.trim();
if(!cookie){ toast('请先输入或扫码获取 Cookie','error'); return; }
body.cookie = cookie;
console.log("SAVE promo raw:", document.getElementById("dlg_promo").value); body.promotion_account = document.getElementById('dlg_promo').value.trim() || '';
body.nickname = document.getElementById('dlg_nick').value.trim() || '';
body.storage_used = __qrStorageUsed || '';
body.storage_total = __qrStorageTotal || '';
api('/api/admin/cloud-configs', {
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
}).then(function(d){
if(d && d.error){ toast(d.error,'error'); return; }
toast('✅ 网盘配置更新成功');
closeDialog();
nav('cloud-config');
}).catch(function(e){
toast('保存失败: '+(e.message||'网络错误'),'error');
console.error('doSaveAccount error:', e);
});
}

View File

@@ -0,0 +1,43 @@
function togCloudType(type, cb){
__cloudToggles[type] = cb.checked;
api('/api/admin/system-configs', {
method:'PUT', headers:{'Content-Type':'application/json'},
body:JSON.stringify({entries:[{key:'cloud_type_'+type+'_enabled', value:cb.checked?'true':'false'}]})
});
}
function togCloudAcc(id, cb){
api('/api/admin/cloud-configs/'+id, {method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({is_active:cb.checked})})
.then(function(d){ if(d.error){ cb.checked=!cb.checked; toast(d.error,'error'); } });
}
function delCloudAcc(id){ var name = '账号#'+id;
if(!confirm('确定删除 '+name+' 吗?')) return;
api('/api/admin/cloud-configs/'+id, {method:'DELETE'}).then(function(d){
toast(d.error?d.error:'已删除 '+name, d.error?'error':'success');
nav('cloud-config');
});
}
function doSaveAccount(){
var body = {
cloud_type: document.getElementById('dlg_type').value,
is_active: true
};
var cookie = document.getElementById('dlg_cookie').value.trim();
if(!cookie){ toast('请先输入或扫码获取 Cookie','error'); return; }
body.cookie = cookie;
console.log("SAVE promo raw:", document.getElementById("dlg_promo").value); body.promotion_account = document.getElementById('dlg_promo').value.trim() || '';
body.nickname = document.getElementById('dlg_nick').value.trim() || '';
body.storage_used = __qrStorageUsed || '';
body.storage_total = __qrStorageTotal || '';
api('/api/admin/cloud-configs', {
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
}).then(function(d){
if(d && d.error){ toast(d.error,'error'); return; }
toast('✅ 网盘配置更新成功');
closeDialog();
nav('cloud-config');
}).catch(function(e){
toast('保存失败: '+(e.message||'网络错误'),'error');
console.error('doSaveAccount error:', e);
});
}

View File

@@ -0,0 +1,43 @@
function togCloudType(type, cb){
__cloudToggles[type] = cb.checked;
api('/api/admin/system-configs', {
method:'PUT', headers:{'Content-Type':'application/json'},
body:JSON.stringify({entries:[{key:'cloud_type_'+type+'_enabled', value:cb.checked?'true':'false'}]})
});
}
function togCloudAcc(id, cb){
api('/api/admin/cloud-configs/'+id, {method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({is_active:cb.checked})})
.then(function(d){ if(d.error){ cb.checked=!cb.checked; toast(d.error,'error'); } });
}
function delCloudAcc(id){ var name = '账号#'+id;
if(!confirm('确定删除 '+name+' 吗?')) return;
api('/api/admin/cloud-configs/'+id, {method:'DELETE'}).then(function(d){
toast(d.error?d.error:'已删除 '+name, d.error?'error':'success');
nav('cloud-config');
});
}
function doSaveAccount(){
var body = {
cloud_type: document.getElementById('dlg_type').value,
// default off, will be set by verification
};
var cookie = document.getElementById('dlg_cookie').value.trim();
if(!cookie){ toast('请先输入或扫码获取 Cookie','error'); return; }
body.cookie = cookie;
console.log("SAVE promo raw:", document.getElementById("dlg_promo").value); body.promotion_account = document.getElementById('dlg_promo').value.trim() || '';
body.nickname = document.getElementById('dlg_nick').value.trim() || '';
body.storage_used = __qrStorageUsed || '';
body.storage_total = __qrStorageTotal || '';
api('/api/admin/cloud-configs', {
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
}).then(function(d){
if(d && d.error){ toast(d.error,'error'); return; }
toast('✅ 网盘配置更新成功');
closeDialog();
nav('cloud-config');
}).catch(function(e){
toast('保存失败: '+(e.message||'网络错误'),'error');
console.error('doSaveAccount error:', e);
});
}

View File

@@ -0,0 +1,38 @@
// --- Cloud Config ---
var CLOUD_TYPES = [
{type:'quark',label:'夸克网盘',icon:'/admin/icons/quark.png'},
{type:'baidu',label:'百度网盘',icon:'/admin/icons/baidu.png'},
{type:'aliyun',label:'阿里云盘',icon:'/admin/icons/aliyun.png'},
{type:'115',label:'115网盘',icon:'/admin/icons/115.png'},
{type:'tianyi',label:'天翼云盘',icon:'/admin/icons/tianyi.png'},
{type:'123pan',label:'123云盘',icon:'/admin/icons/123pan.png'},
{type:'uc',label:'UC网盘',icon:'/admin/icons/uc.png'},
{type:'xunlei',label:'迅雷网盘',icon:'/admin/icons/xunlei.png'},
{type:'pikpak',label:'PikPak',icon:'/admin/icons/pikpak.png'},
{type:'magnet',label:'磁力链接',icon:'🧲'},
{type:'ed2k',label:'电驴链接',icon:'🔗'},
{type:'others',label:'其他',icon:'⬜'}
];
var QR_TYPES = ['quark','baidu'];
var __cloudToggles = {};
var __qrType = '', __qrSession = '', __qrCookie = '', __qrTimer = null;
function page_cloud_config(c){
c.innerHTML = '<div class="loading">加载中...</div>';
Promise.all([
api('/api/admin/system-configs').catch(function(e){ console.error(e); return {}; }),
api('/api/admin/cloud-configs').catch(function(e){ console.error(e); return []; })
]).then(function(res){
var d = res[0]||{}, accounts = res[1]||[];
__cloudToggles = {};
CLOUD_TYPES.forEach(function(ct){
var v = d['cloud_type_'+ct.type+'_enabled'];
__cloudToggles[ct.type] = v===undefined ? (ct.type!=='others') : (v==='true'||v==='1');
});
renderCloudPage(c, accounts);
}).catch(function(e){
c.innerHTML = '<div class="card"><p style="color:var(--danger)">加载失败: '+e.message+'</p></div>';
});
}

View File

@@ -0,0 +1,107 @@
var __qrSession = '';
var __qrCookie = '';
var __qrType = '';
var __qrTimer = null;
var __qrNickname = '';
var __qrStorageUsed = '';
var __qrStorageTotal = '';
var QR_TYPES = ['quark','baidu'];
function openAddDialog(){
document.getElementById('dlg_type').value = 'quark';
document.getElementById('dlg_cookie').value = '';
document.getElementById('dlg_promo').value = '';
document.getElementById('dlg_nick').value = '';
document.getElementById('dlg_storage_info').textContent = '扫码后自动获取';
resetQR();
onDlgTypeChange();
document.getElementById('modalBg').style.display = 'flex';
}
function closeDialog(){
cancelQRPoll();
document.getElementById('modalBg').style.display = 'none';
}
function onDlgTypeChange(){
var t = document.getElementById('dlg_type').value;
document.getElementById('dlg_qr_section').style.display = (QR_TYPES.indexOf(t)!==-1) ? 'block' : 'none';
}
function resetQR(){
cancelQRPoll();
__qrSession = ''; __qrCookie = ''; __qrNickname = ''; __qrStorageUsed = ''; __qrStorageTotal = '';
document.getElementById('dlg_qr_status').textContent = '点击下方按钮生成扫码链接';
document.getElementById('dlg_qr_btn').disabled = false;
document.getElementById('dlg_qr_btn').textContent = '生成扫码链接';
document.getElementById('dlg_qr_btn').style.display = '';
document.getElementById('dlg_qr_hint').textContent = '';
}
function doStartQR(){
__qrType = document.getElementById('dlg_type').value;
document.getElementById('dlg_qr_btn').disabled = true;
document.getElementById('dlg_qr_btn').textContent = '生成中...';
document.getElementById('dlg_qr_status').textContent = '正在生成二维码...';
api('/api/admin/'+__qrType+'/qr-login/start', {method:'POST'}).then(function(d){
if(d.error){
document.getElementById('dlg_qr_status').innerHTML = '<span style="color:var(--danger)">❌ '+d.error+'</span>';
document.getElementById('dlg_qr_btn').disabled=false;
document.getElementById('dlg_qr_btn').textContent='重试';
return;
}
__qrSession = d.sessionId;
var url = d.qrUrl||d.url||'';
if(url){
var qrSrc = 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + encodeURIComponent(url);
document.getElementById('dlg_qr_status').innerHTML = '<img src="'+qrSrc+'" style="max-width:200px;max-height:200px;border:1px solid var(--border);border-radius:4px;display:block;margin:0 auto"><p style="font-size:11px;color:var(--sub);margin-top:6px;text-align:center">用对应 App 扫码并在手机上确认登录</p>';
}
document.getElementById('dlg_qr_hint').textContent = '等待扫码确认...';
document.getElementById('dlg_qr_btn').textContent = '重新生成';
document.getElementById('dlg_qr_btn').disabled = false;
pollQR();
});
}
function pollQR(){
if(!__qrSession) return;
__qrTimer = setTimeout(function(){
api('/api/admin/'+__qrType+'/qr-login/'+__qrSession+'/status').then(function(d){
if(d.status==='logged_in'){
__qrCookie = d.cookie||'';
__qrNickname = d.nickname||'';
__qrStorageUsed = d.storage_used||'';
__qrStorageTotal = d.storage_total||'';
if(__qrNickname){ var n = document.getElementById('dlg_nick'); if(!n.value.trim()) n.value = __qrNickname; }
if(d.promotion_account){ var p = document.getElementById('dlg_promo'); if(!p.value.trim()) p.value = d.promotion_account; }
var si = document.getElementById('dlg_storage_info');
if(__qrStorageTotal){
si.textContent = '💾 ' + (__qrStorageUsed||'?') + ' / ' + __qrStorageTotal;
si.style.color = 'var(--success)';
} else { si.textContent = '空间信息未获取'; si.style.color = 'var(--sub)'; }
if(d.autoUpdated){
document.getElementById('dlg_qr_status').innerHTML = '<span style="color:var(--success);font-size:14px">✅ 扫码成功!已自动更新现有账号 #'+d.updatedConfigId+'</span>';
cancelQRPoll();
toast('✅ 已自动更新账号','success');
setTimeout(function(){ closeDialog(); nav('cloud-config'); }, 1000);
return;
}
document.getElementById('dlg_qr_hint').textContent = '✅ 登录成功!';
document.getElementById('dlg_qr_status').innerHTML = '<span style="color:var(--success);font-size:14px">✅ 扫码成功!已自动填入</span>';
document.getElementById('dlg_cookie').value = __qrCookie;
document.getElementById('dlg_qr_btn').style.display = 'none';
cancelQRPoll();
}else if(d.status==='expired'){
cancelQRPoll();
setTimeout(function(){ doStartQR(); }, 500);
}else{
pollQR();
}
}).catch(function(){ pollQR(); });
}, 3000);
}
function cancelQRPoll(){
if(__qrTimer){ clearTimeout(__qrTimer); __qrTimer = null; }
if(__qrSession){ api('/api/admin/'+__qrType+'/qr-login/'+__qrSession+'/cancel', {method:'POST'}).catch(function(){}); __qrSession=''; }
}

View File

@@ -0,0 +1,92 @@
function renderCloudPage(c, accounts){
accounts = accounts || [];
var h = '';
// Render icon: URL → <img>, emoji/text → plain text
function iconHtml(icon, size){
if(!icon) return '';
if(icon.indexOf('/')===0 || icon.indexOf('http')===0){
return '<img src="'+icon+'" style="width:'+size+'px;height:'+size+'px;vertical-align:middle;margin-right:4px" onerror="this.remove()">';
}
return '<span style="font-size:'+(size-2)+'px;vertical-align:middle;margin-right:4px">'+icon+'</span>';
}
// Toggle grid
h += '<div class="card"><div class="card-title">⚡ 搜索网盘类型控制</div>'+
'<p style="color:var(--sub);font-size:12px;margin-bottom:14px">控制搜索引擎检索哪些网盘类型的资源</p>'+
'<div class="stats-grid" style="grid-template-columns:repeat(auto-fill,minmax(170px,1fr))">';
CLOUD_TYPES.forEach(function(ct){
var on = __cloudToggles[ct.type];
h += '<div class="stat-card" style="padding:8px 12px;display:flex;align-items:center;justify-content:space-between">'+
'<span>'+iconHtml(ct.icon,20)+' <span style="font-size:12px">'+ct.label+'</span></span>'+
'<label class="toggle"><input type="checkbox" '+(on?'checked':'')+' onchange="togCloudType(\''+ct.type+'\',this)"><span class="toggle-slider"></span></label>'+
'</div>';
});
h += '</div></div>';
// Account list — table style
if(accounts.length > 0){
h += '<div class="card"><div class="card-title">📋 已有账号 ('+accounts.length+') <button class="btn btn-pri btn-sm" onclick="openAddDialog()" style="margin-left:12px"> 新增</button></div>';
h += '<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:13px">';
h += '<thead><tr style="background:var(--bg);border-bottom:2px solid var(--border);text-align:left">'+
'<th style="padding:10px 12px;white-space:nowrap">📱推广平台</th>'+
'<th style="padding:10px 12px;white-space:nowrap">推广平台账号</th>'+
'<th style="padding:10px 12px;white-space:nowrap">网盘昵称</th>'+'<th style="padding:10px 12px;white-space:nowrap">网盘UID</th>'+
'<th style="padding:10px 12px;white-space:nowrap">验证</th>'+
'<th style="padding:10px 12px;white-space:nowrap">空间</th>'+
'<th style="padding:10px 12px;white-space:nowrap">转存</th>'+
'<th style="padding:10px 12px;white-space:nowrap">操作</th>'+
'</tr></thead><tbody>';
accounts.forEach(function(cfg){
var label = CLOUD_TYPES.find(function(ct){ return ct.type===cfg.cloud_type; });
var icon = (label||{}).icon||'⬜';
var active = cfg.is_active===1||cfg.is_active===true;
var ck = cfg.verification_status==='valid'?'✅':(cfg.verification_status==='invalid'?'❌':'⏳');
h += '<tr style="border-bottom:1px solid var(--border)">'+
'<td style="padding:10px 12px;white-space:nowrap">'+iconHtml(icon,18)+(label||{}).label+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap">'+(cfg.promotion_account||'—')+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap">'+(cfg.nickname||cfg.cloud_type)+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap;font-size:12px;color:var(--sub)">'+(cfg.cookie_uid||cfg.cloud_type)+'</td>'+
'<td style="padding:10px 12px">'+ck+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap;font-size:12px;color:var(--sub)">'+(cfg.storage_used||cfg.storage_total?'💾 '+(cfg.storage_used||'?')+'/'+(cfg.storage_total||'?'):'—')+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap;font-size:12px;color:var(--sub)">'+(cfg.total_saves?'转存'+cfg.total_saves+'次':'—')+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap">'+
'<label class="toggle"><input type="checkbox" '+(active?'checked':'')+' onchange="togCloudAcc('+cfg.id+',this)"><span class="toggle-slider"></span></label>'+
'<button class="btn btn-danger btn-sm" onclick="delCloudAcc('+cfg.id+')" style="margin-left:8px">🗑</button>'+
'</td>'+
'</tr>';
});
h += '</tbody></table></div></div>';
}
// No accounts yet — show add button
if(accounts.length === 0){
h += '<div class="card"><div class="card-title">📋 已有账号 (0)</div><p style="color:var(--sub);font-size:13px;margin-bottom:12px">还没有添加任何网盘账号</p><button class="btn btn-pri" onclick="openAddDialog()"> 新增网盘</button></div>';
}
// Modal dialog
h += '<div id="modalBg" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);z-index:100;align-items:center;justify-content:center" onclick="if(event.target===this)closeDialog()">'+
'<div class="card" style="width:500px;max-width:95vw;max-height:90vh;overflow-y:auto" onclick="event.stopPropagation()">'+
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">'+
'<span style="font-weight:600;font-size:15px">新增网盘账号</span>'+
'<button class="btn btn-outline btn-sm" onclick="closeDialog()">✕</button></div>'+
'<div class="form-group"><label>网盘类型</label><select id="dlg_type" class="form-control" style="max-width:200px" onchange="onDlgTypeChange()">';
CLOUD_TYPES.forEach(function(ct){ h+='<option value="'+ct.type+'">'+ct.label+'</option>'; });
h+='</select></div>'+
'<div class="form-group"><label>推广平台账号</label><input id="dlg_promo" class="form-control" placeholder="平台注册所用手机号" style="max-width:250px"></div>'+
'<div class="form-group"><label>Cookie</label><textarea id="dlg_cookie" class="form-control wide" rows="5" placeholder="粘贴 Cookie 或通过下方扫码获取"></textarea></div>'+
'<div class="form-group"><label>网盘昵称</label><input id="dlg_nick" class="form-control" placeholder="扫码后自动获取" style="max-width:250px;background:#f5f6fa" readonly></div>'+
'<div class="form-group"><label>空间信息</label><span id="dlg_storage_info" style="font-size:13px;color:var(--sub);padding-top:6px">扫码后自动获取</span></div>'+
'<div id="dlg_qr_section" style="margin-top:12px;padding:12px;background:#f9fafb;border-radius:8px">'+
'<p style="font-size:12px;font-weight:600;margin-bottom:8px">📱 扫码获取 Cookie仅夸克/百度)</p>'+
'<div id="dlg_qr_status" style="font-size:12px;color:var(--sub);margin-bottom:8px">点击下方按钮生成扫码链接</div>'+
''+
'<div style="margin-top:8px;display:flex;gap:8px;align-items:center">'+
'<button class="btn btn-pri btn-sm" id="dlg_qr_btn" onclick="doStartQR()">生成扫码链接</button>'+
'<span id="dlg_qr_hint" style="font-size:11px;color:var(--sub)"></span></div></div>'+
'<div style="margin-top:16px;display:flex;gap:8px">'+
'<button class="btn btn-pri" onclick="doSaveAccount()">💾 保存账号</button>'+
'<button class="btn btn-outline" onclick="closeDialog()">取消</button></div></div></div>';
c.innerHTML = h;
}

View File

@@ -0,0 +1,92 @@
function renderCloudPage(c, accounts){
accounts = accounts || [];
var h = '';
// Render icon: URL → <img>, emoji/text → plain text
function iconHtml(icon, size){
if(!icon) return '';
if(icon.indexOf('/')===0 || icon.indexOf('http')===0){
return '<img src="'+icon+'" style="width:'+size+'px;height:'+size+'px;vertical-align:middle;margin-right:4px" onerror="this.remove()">';
}
return '<span style="font-size:'+(size-2)+'px;vertical-align:middle;margin-right:4px">'+icon+'</span>';
}
// Toggle grid
h += '<div class="card"><div class="card-title">⚡ 搜索网盘类型控制</div>'+
'<p style="color:var(--sub);font-size:12px;margin-bottom:14px">控制搜索引擎检索哪些网盘类型的资源</p>'+
'<div class="stats-grid" style="grid-template-columns:repeat(auto-fill,minmax(170px,1fr))">';
CLOUD_TYPES.forEach(function(ct){
var on = __cloudToggles[ct.type];
h += '<div class="stat-card" style="padding:8px 12px;display:flex;align-items:center;justify-content:space-between">'+
'<span>'+iconHtml(ct.icon,20)+' <span style="font-size:12px">'+ct.label+'</span></span>'+
'<label class="toggle"><input type="checkbox" '+(on?'checked':'')+' onchange="togCloudType(\''+ct.type+'\',this)"><span class="toggle-slider"></span></label>'+
'</div>';
});
h += '</div></div>';
// Account list — table style
if(accounts.length > 0){
h += '<div class="card"><div class="card-title">📋 已有账号 ('+accounts.length+') <button class="btn btn-pri btn-sm" onclick="openAddDialog()" style="margin-left:12px"> 新增</button></div>';
h += '<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:13px">';
h += '<thead><tr style="background:var(--bg);border-bottom:2px solid var(--border);text-align:left">'+
'<th style="padding:10px 12px;white-space:nowrap">📱推广平台</th>'+
'<th style="padding:10px 12px;white-space:nowrap">推广平台账号</th>'+
'<th style="padding:10px 12px;white-space:nowrap">网盘昵称</th>'+'<th style="padding:10px 12px;white-space:nowrap">网盘UID</th>'+
'<th style="padding:10px 12px;white-space:nowrap">验证</th>'+
'<th style="padding:10px 12px;white-space:nowrap">空间</th>'+
'<th style="padding:10px 12px;white-space:nowrap">转存</th>'+
'<th style="padding:10px 12px;white-space:nowrap">操作</th>'+
'</tr></thead><tbody>';
accounts.forEach(function(cfg){
var label = CLOUD_TYPES.find(function(ct){ return ct.type===cfg.cloud_type; });
var icon = (label||{}).icon||'⬜';
var active = cfg.is_active===1||cfg.is_active===true;
var ck = cfg.verification_status==='valid'?'✅':(cfg.verification_status==='invalid'?'❌':'—');
h += '<tr style="border-bottom:1px solid var(--border)">'+
'<td style="padding:10px 12px;white-space:nowrap">'+iconHtml(icon,18)+(label||{}).label+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap">'+(cfg.promotion_account||'—')+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap">'+(cfg.nickname||cfg.cloud_type)+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap;font-size:12px;color:var(--sub)">'+(cfg.cookie_uid||cfg.cloud_type)+'</td>'+
'<td style="padding:10px 12px">'+ck+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap;font-size:12px;color:var(--sub)">'+(cfg.storage_used||cfg.storage_total?'💾 '+(cfg.storage_used||'?')+'/'+(cfg.storage_total||'?'):'—')+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap;font-size:12px;color:var(--sub)">'+(cfg.total_saves?'转存'+cfg.total_saves+'次':'—')+'</td>'+
'<td style="padding:10px 12px;white-space:nowrap">'+
'<label class="toggle"><input type="checkbox" '+(active?'checked':'')+' onchange="togCloudAcc('+cfg.id+',this)"><span class="toggle-slider"></span></label>'+
'<button class="btn btn-danger btn-sm" onclick="delCloudAcc('+cfg.id+')" style="margin-left:8px">🗑</button>'+
'</td>'+
'</tr>';
});
h += '</tbody></table></div></div>';
}
// No accounts yet — show add button
if(accounts.length === 0){
h += '<div class="card"><div class="card-title">📋 已有账号 (0)</div><p style="color:var(--sub);font-size:13px;margin-bottom:12px">还没有添加任何网盘账号</p><button class="btn btn-pri" onclick="openAddDialog()"> 新增网盘</button></div>';
}
// Modal dialog
h += '<div id="modalBg" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);z-index:100;align-items:center;justify-content:center" onclick="if(event.target===this)closeDialog()">'+
'<div class="card" style="width:500px;max-width:95vw;max-height:90vh;overflow-y:auto" onclick="event.stopPropagation()">'+
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">'+
'<span style="font-weight:600;font-size:15px">新增网盘账号</span>'+
'<button class="btn btn-outline btn-sm" onclick="closeDialog()">✕</button></div>'+
'<div class="form-group"><label>网盘类型</label><select id="dlg_type" class="form-control" style="max-width:200px" onchange="onDlgTypeChange()">';
CLOUD_TYPES.forEach(function(ct){ h+='<option value="'+ct.type+'">'+ct.label+'</option>'; });
h+='</select></div>'+
'<div class="form-group"><label>推广平台账号</label><input id="dlg_promo" class="form-control" placeholder="平台注册所用手机号" style="max-width:250px"></div>'+
'<div class="form-group"><label>Cookie</label><textarea id="dlg_cookie" class="form-control wide" rows="5" placeholder="粘贴 Cookie 或通过下方扫码获取"></textarea></div>'+
'<div class="form-group"><label>网盘昵称</label><input id="dlg_nick" class="form-control" placeholder="扫码后自动获取" style="max-width:250px;background:#f5f6fa" readonly></div>'+
'<div class="form-group"><label>空间信息</label><span id="dlg_storage_info" style="font-size:13px;color:var(--sub);padding-top:6px">扫码后自动获取</span></div>'+
'<div id="dlg_qr_section" style="margin-top:12px;padding:12px;background:#f9fafb;border-radius:8px">'+
'<p style="font-size:12px;font-weight:600;margin-bottom:8px">📱 扫码获取 Cookie仅夸克/百度)</p>'+
'<div id="dlg_qr_status" style="font-size:12px;color:var(--sub);margin-bottom:8px">点击下方按钮生成扫码链接</div>'+
''+
'<div style="margin-top:8px;display:flex;gap:8px;align-items:center">'+
'<button class="btn btn-pri btn-sm" id="dlg_qr_btn" onclick="doStartQR()">生成扫码链接</button>'+
'<span id="dlg_qr_hint" style="font-size:11px;color:var(--sub)"></span></div></div>'+
'<div style="margin-top:16px;display:flex;gap:8px">'+
'<button class="btn btn-pri" onclick="doSaveAccount()">💾 保存账号</button>'+
'<button class="btn btn-outline" onclick="closeDialog()">取消</button></div></div></div>';
c.innerHTML = h;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.admin-menu .menu-header[data-v-529b614b]{padding:16px 20px 8px;text-align:center;border-bottom:1px solid var(--el-border-color-light)}.admin-menu .menu-header h2[data-v-529b614b]{margin:0;font-size:16px;color:var(--el-color-primary)}.admin-menu .menu-header p[data-v-529b614b]{margin:4px 0 0;font-size:12px;color:var(--el-text-color-secondary)}.version-footer[data-v-529b614b]{padding:8px;text-align:center;font-size:11px;color:var(--el-text-color-placeholder);border-top:1px solid var(--el-border-color-light);margin-top:auto}.admin-layout[data-v-529b614b]{display:flex;height:100vh}.admin-menu[data-v-529b614b]{width:220px;flex-shrink:0;display:flex;flex-direction:column}.admin-content[data-v-529b614b]{flex:1;display:flex;flex-direction:column;overflow:hidden}.content-header[data-v-529b614b]{display:flex;align-items:center;justify-content:space-between;padding:12px 24px;border-bottom:1px solid var(--el-border-color-light);background:var(--el-bg-color)}.content-header h2[data-v-529b614b]{margin:0;font-size:18px}.content-body[data-v-529b614b]{flex:1;overflow-y:auto;padding:20px 24px;background:var(--el-bg-color-page)}

View File

@@ -0,0 +1 @@
import{d as B,o as N,a as T,c as V,f as s,w as t,b as o,t as c,h as v,v as y,j as d,k as r,C as I,l as u,D as M,G as j,H as q,I as A,u as D,z as H}from"./index-C5b4pIQL.js";import{a as L,_ as R}from"./_plugin-vue_export-helper-CzL5NdOX.js";const z={class:"admin-layout"},E={class:"menu-header"},G={class:"version-footer"},W={class:"admin-content"},F={class:"content-header"},J={class:"content-body"},K=B({__name:"AdminLayout",setup(O){const l=D(),f=H(),m=v(""),_=v(""),b={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","save-records":"转存日志"},p=y(()=>{const n=f.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?f.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),h=y(()=>b[p.value]||"仪表盘");function x(n){n==="dashboard"?l.push("/admin/dashboard"):n==="cloud-configs-toggle"?l.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?l.push("/admin/cleanup"):n.startsWith("sys-")?l.push({path:"/admin/system",query:{section:n}}):n==="save-records"?l.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),l.push("/admin/login"))}function w(){l.push("/")}return N(async()=>{try{const n=await L();m.value=n.site_name||""}catch{}try{const e=await(await fetch("/health")).json();_.value=e.version}catch{}}),(n,e)=>{const i=d("el-icon"),a=d("el-menu-item"),g=d("el-sub-menu"),C=d("el-menu"),k=d("el-button"),S=d("router-view");return T(),V("div",z,[s(C,{"default-active":p.value,class:"admin-menu",onSelect:x},{default:t(()=>[o("div",E,[o("h2",null,c(m.value||"CloudSearch"),1),e[0]||(e[0]=o("p",null,"管理后台",-1))]),s(a,{index:"dashboard"},{default:t(()=>[s(i,null,{default:t(()=>[s(r(I))]),_:1}),e[1]||(e[1]=o("span",null,"仪表盘",-1))]),_:1}),s(g,{index:"cloud-configs"},{title:t(()=>[s(i,null,{default:t(()=>[s(r(M))]),_:1}),e[2]||(e[2]=o("span",null,"网盘配置",-1))]),default:t(()=>[s(a,{index:"cloud-configs-toggle"},{default:t(()=>[...e[3]||(e[3]=[u("网盘设置及授权",-1)])]),_:1}),s(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...e[4]||(e[4]=[u("存储清理",-1)])]),_:1})]),_:1}),s(g,{index:"system"},{title:t(()=>[s(i,null,{default:t(()=>[s(r(j))]),_:1}),e[5]||(e[5]=o("span",null,"系统配置",-1))]),default:t(()=>[s(a,{index:"sys-site"},{default:t(()=>[...e[6]||(e[6]=[u("网站设置",-1)])]),_:1}),s(a,{index:"sys-services"},{default:t(()=>[...e[7]||(e[7]=[u("外部服务和缓存",-1)])]),_:1}),s(a,{index:"sys-strategy"},{default:t(()=>[...e[8]||(e[8]=[u("性能配置",-1)])]),_:1}),s(a,{index:"sys-password"},{default:t(()=>[...e[9]||(e[9]=[u("修改管理员密码",-1)])]),_:1})]),_:1}),s(a,{index:"save-records"},{default:t(()=>[s(i,null,{default:t(()=>[s(r(q))]),_:1}),e[10]||(e[10]=o("span",null,"转存日志",-1))]),_:1}),o("div",G,"T "+c(_.value),1),s(a,{index:"logout"},{default:t(()=>[s(i,null,{default:t(()=>[s(r(A))]),_:1}),e[11]||(e[11]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"]),o("div",W,[o("div",F,[o("h2",null,c(h.value),1),s(k,{text:"",onClick:w},{default:t(()=>[...e[12]||(e[12]=[u("返回前台",-1)])]),_:1})]),o("div",J,[s(S)])])])}}}),U=R(K,[["__scopeId","data-v-529b614b"]]);export{U as default};

View File

@@ -0,0 +1 @@
.admin-login-page[data-v-513ea931]{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#667eea,#764ba2)}.login-card[data-v-513ea931]{width:400px;padding:40px;background:var(--bg-white);border-radius:16px;box-shadow:0 8px 32px #00000026}.login-title[data-v-513ea931]{text-align:center;font-size:24px;font-weight:700;color:#303133;margin-bottom:32px}.login-btn[data-v-513ea931]{width:100%}.error-msg[data-v-513ea931]{text-align:center;color:#f56c6c;font-size:14px;margin-top:12px}

View File

@@ -0,0 +1 @@
import{d as k,a as g,c as v,b as w,t as h,f as s,w as l,g as b,e as x,h as d,j as m,l as C,i as L,E as N}from"./index-C5b4pIQL.js";import{a as S,d as B,_ as E}from"./_plugin-vue_export-helper-CzL5NdOX.js";const U={class:"admin-login-page"},q={class:"login-card"},A={class:"login-title"},I={key:0,class:"error-msg"},K=k({__name:"AdminLogin",setup(M){const p=d(),u=d(!1),r=d(""),_=d("");S().then(i=>{i.site_name&&(_.value=i.site_name)}).catch(()=>{});const o=L({username:"",password:""}),y={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function f(){var a,n,t;if(await((a=p.value)==null?void 0:a.validate().catch(()=>!1))){u.value=!0,r.value="";try{const e=await B(o.username,o.password);localStorage.setItem("admin_token",e.token),N.success("登录成功"),window.location.href="/admin"}catch(e){r.value=((t=(n=e==null?void 0:e.response)==null?void 0:n.data)==null?void 0:t.message)||(e==null?void 0:e.message)||"登录失败"}finally{u.value=!1}}}return(i,a)=>{const n=m("el-input"),t=m("el-form-item"),e=m("el-button"),V=m("el-form");return g(),v("div",U,[w("div",q,[w("h1",A,h(_.value||"CloudSearch")+" 管理后台",1),s(V,{ref_key:"formRef",ref:p,model:o,rules:y,"label-width":"0",size:"large",onKeyup:b(f,["enter"])},{default:l(()=>[s(t,{prop:"username"},{default:l(()=>[s(n,{modelValue:o.username,"onUpdate:modelValue":a[0]||(a[0]=c=>o.username=c),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),s(t,{prop:"password"},{default:l(()=>[s(n,{modelValue:o.password,"onUpdate:modelValue":a[1]||(a[1]=c=>o.password=c),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),s(t,null,{default:l(()=>[s(e,{type:"primary",loading:u.value,class:"login-btn",onClick:f},{default:l(()=>[...a[2]||(a[2]=[C(" 登录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),r.value?(g(),v("p",I,h(r.value),1)):x("",!0)])])}}}),z=E(K,[["__scopeId","data-v-513ea931"]]);export{z as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.cleanup-section .config-card[data-v-96d69897]{max-width:800px}.form-tip[data-v-96d69897]{font-size:12px;color:var(--el-text-color-secondary)}.cleanup-info[data-v-96d69897]{font-size:13px;color:var(--el-text-color-secondary)}

View File

@@ -0,0 +1 @@
.cloud-badge[data-v-5857e8ce]{display:inline-block;padding:2px 8px;border-radius:4px;color:#fff;font-size:12px;line-height:1.5;white-space:nowrap}

View File

@@ -0,0 +1 @@
import{C as o,a as t}from"./index-Bz21yOih.js";import{d as s,a as c,c as r,p as n,k as a,t as p}from"./index-C5b4pIQL.js";import{_ as d}from"./_plugin-vue_export-helper-CzL5NdOX.js";const l=s({__name:"CloudBadge",props:{cloud_type:{}},setup(e){return(_,m)=>(c(),r("span",{class:"cloud-badge",style:n({background:a(o)[e.cloud_type]})},p(a(t)[e.cloud_type]),5))}}),f=d(l,[["__scopeId","data-v-5857e8ce"]]);export{f as C};

View File

@@ -0,0 +1 @@
.cloud-config[data-v-d5c0f4b4]{background:var(--bg-white);border-radius:var(--radius-card);padding:24px}.cloud-toggle-grid[data-v-d5c0f4b4]{display:flex;flex-wrap:wrap;gap:12px}.cloud-toggle-chip[data-v-d5c0f4b4]{display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid var(--el-border-color-light);border-radius:8px;background:var(--el-bg-color)}.cloud-toggle-chip[data-v-d5c0f4b4]:hover{border-color:var(--el-color-primary-light-5)}.cloud-icon-img[data-v-d5c0f4b4]{width:20px;height:20px;object-fit:contain}.cloud-label[data-v-d5c0f4b4]{font-size:13px;font-weight:500}.form-tip[data-v-d5c0f4b4]{font-size:12px;color:var(--el-text-color-secondary)}.toolbar[data-v-d5c0f4b4]{margin-bottom:16px;display:flex;gap:8px;align-items:center;flex-wrap:wrap}.sign-summary-tag[data-v-d5c0f4b4]{margin-left:4px}.nickname-text[data-v-d5c0f4b4]{font-weight:600;color:#303133}.storage-cell[data-v-d5c0f4b4]{display:flex;flex-direction:column;gap:3px;padding:2px 0}.storage-bar-wrap[data-v-d5c0f4b4]{height:4px;background:#f0f2f5;border-radius:2px;overflow:hidden}.storage-bar-fill[data-v-d5c0f4b4]{height:100%;border-radius:2px;transition:width .3s}.storage-bar-fill.bar-normal[data-v-d5c0f4b4]{background:#67c23a}.storage-bar-fill.bar-warning[data-v-d5c0f4b4]{background:#e6a23c}.storage-bar-fill.bar-danger[data-v-d5c0f4b4]{background:#f56c6c}.storage-text[data-v-d5c0f4b4]{font-size:11px;color:#909399;display:flex;align-items:center;gap:3px}.storage-used[data-v-d5c0f4b4]{color:#606266;font-weight:600}.storage-total[data-v-d5c0f4b4]{color:#303133;font-weight:600}.storage-free[data-v-d5c0f4b4]{color:#909399}.save-count[data-v-d5c0f4b4]{font-size:12px;color:#909399}.verifying[data-v-d5c0f4b4]{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:#909399}[data-v-d5c0f4b4] .el-input-group__append{padding:0}[data-v-d5c0f4b4] .el-input-group__append .el-button{border-radius:0}.cookie-help[data-v-d5c0f4b4]{background:#f8faff;border:1px solid #e8f0fe;border-radius:8px;padding:12px 14px;font-size:12px;line-height:1.6;color:#606266}.cookie-help-header[data-v-d5c0f4b4]{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.cookie-help-title[data-v-d5c0f4b4]{font-weight:700;color:#409eff;margin-bottom:0;font-size:13px}.cookie-help-steps[data-v-d5c0f4b4]{margin:0;padding-left:20px}.cookie-help-steps li[data-v-d5c0f4b4]{margin-bottom:2px}.cookie-help-steps code[data-v-d5c0f4b4]{background:#ecf5ff;padding:1px 4px;border-radius:3px;font-size:11px}.cookie-help-format[data-v-d5c0f4b4]{margin-top:6px;padding-top:6px;border-top:1px dashed #e8f0fe}.cookie-help-format code[data-v-d5c0f4b4]{background:#ecf5ff;padding:1px 6px;border-radius:3px;font-size:11px;word-break:break-all}.qr-login-body[data-v-d5c0f4b4]{display:flex;gap:28px;align-items:flex-start;padding:6px 0}.qr-login-qr-wrap[data-v-d5c0f4b4]{flex-shrink:0;width:200px;display:flex;align-items:center;justify-content:center}.qr-loading[data-v-d5c0f4b4]{width:200px;height:200px;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#f8f9fa;border:1px solid #e4e7ed;border-radius:12px;gap:10px;color:#909399;font-size:13px}.qr-canvas[data-v-d5c0f4b4]{width:200px;height:200px;border-radius:12px;border:1px solid #e4e7ed}.qr-login-right[data-v-d5c0f4b4]{flex:1;display:flex;flex-direction:column;gap:20px;min-height:200px}.qr-login-steps[data-v-d5c0f4b4]{display:flex;flex-direction:column;gap:14px}.qr-step[data-v-d5c0f4b4]{display:flex;align-items:flex-start;gap:10px;font-size:14px;line-height:1.6;color:#303133}.qr-step-num[data-v-d5c0f4b4]{flex-shrink:0;width:24px;height:24px;border-radius:50%;background:#409eff;color:#fff;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;margin-top:1px}.qr-login-status-area[data-v-d5c0f4b4]{display:flex;flex-direction:column;align-items:flex-start;gap:10px;padding:12px;background:#f8faff;border:1px solid #e8f0fe;border-radius:8px}.qr-status-icon[data-v-d5c0f4b4]{font-size:16px;margin-right:4px}.qr-status-tip[data-v-d5c0f4b4]{font-size:13px;color:#606266;line-height:1.5}.qr-status-warn[data-v-d5c0f4b4]{color:#f56c6c}.qr-refresh-btn[data-v-d5c0f4b4]{margin-top:2px}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{d as K,o as G,a as n,c as a,b as o,F as w,t as c,e as h,f as v,w as f,g as Q,r as z,h as l,i as B,j as x,u as W,k as Y,s as J,l as D,n as I}from"./index-C5b4pIQL.js";import{g as X,a as Z,_ as ee}from"./_plugin-vue_export-helper-CzL5NdOX.js";const te={class:"home-page"},se={class:"hero-section"},oe=["src","alt"],ne={key:1,class:"logo-text"},ae={class:"search-box"},ie={key:1,class:"quote-section"},ce={class:"quote-text"},le={class:"quote-author"},re={class:"content-section"},ue={key:0,class:"rankings-grid"},de={class:"panel-header"},_e={class:"panel-title"},he={class:"panel-tabs"},ve=["onClick"],pe=["onClick"],ge={class:"panel-body"},fe=["onClick"],me={class:"rank-name"},ye={class:"rank-cnt"},ke=["onClick"],Ce={class:"panel-footer"},be={key:0},we={key:1},xe={key:2},Ie={key:3},qe={class:"footer-time"},Ae={key:0,class:"site-footer"},Se={class:"footer-inner"},Ne={class:"footer-actions"},R=8,Te=K({__name:"HomePage",setup(Ve){const q=W(),m=l(""),u=l([]),d=B({}),_=B({}),p=l(""),y=l(""),k=l(""),A=l(!1),g=l(""),C=l(""),S=l(""),F={movie:"🎬",western_movie:"🎥",western_tv:"🌍",donghua:"🐉",global_anime:"🌐",tv:"📺",niche:"💎",hotsite:"🏆"};function L(e){return F[e]||"📋"}function j(e){const t=[];if(e.rating&&t.push(`${e.rating}`),e.searchCount>0){const i=e.searchCount;i>=1e8?t.push(`${(i/1e8).toFixed(1)}亿`):i>=1e4?t.push(`${(i/1e4).toFixed(0)}`):t.push(String(i))}return t.join(" ")||""}function N(e){return(_[e.category]||"hot")==="hot"?e.hot||[]:e.newest||[]}function E(e){const t=N(e);return d[e.category]?t:t.slice(0,R)}function H(e){return N(e).length>R&&!d[e.category]}function M(e){d[e]=!0}function T(e,t){_[e]=t,d[e]=!1}function O(){window.open("/disclaimer/","_blank")}G(async()=>{try{const t=await(await fetch("https://v1.hitokoto.cn/")).json();g.value=t.hitokoto||"",C.value=t.from_who||t.from||""}catch{g.value="学而时习之,不亦说乎。",C.value="孔子"}try{const[e,t]=await Promise.all([X(),Z()]);e.fetchedAt?(S.value=e.fetchedAt,u.value=e.categories||[]):u.value=Array.isArray(e)?e:[];for(const i of u.value)_[i.category]="hot",d[i.category]=!1;t.site_logo&&(p.value=t.site_logo),t.site_name&&(y.value=t.site_name),t.site_disclaimer&&(k.value=t.site_disclaimer),A.value=!0}catch(e){console.error("加载首页数据失败",e)}});function V(){const e=m.value.trim();e&&q.push("/search?q="+encodeURIComponent(e))}function P(e){q.push("/search?q="+encodeURIComponent(e))}return(e,t)=>{const i=x("el-icon"),U=x("el-input"),$=x("el-button");return n(),a("div",te,[o("div",se,[A.value?(n(),a(w,{key:0},[p.value?(n(),a("img",{key:0,src:p.value,alt:y.value||"CloudSearch",class:"logo-img",onError:t[0]||(t[0]=s=>{s.target.style.display="none",p.value=""})},null,40,oe)):(n(),a("div",ne,c(y.value||"CloudSearch"),1))],64)):h("",!0),o("div",ae,[v(U,{modelValue:m.value,"onUpdate:modelValue":t[1]||(t[1]=s=>m.value=s),placeholder:"搜索网盘资源,或粘贴视频/网盘链接...",size:"large",clearable:"",onKeyup:Q(V,["enter"])},{prefix:f(()=>[v(i,null,{default:f(()=>[v(Y(J))]),_:1})]),_:1},8,["modelValue"]),v($,{type:"primary",size:"large",onClick:V,class:"search-btn"},{default:f(()=>[...t[2]||(t[2]=[D(" 搜 索 ",-1)])]),_:1})]),g.value?(n(),a("div",ie,[o("span",ce,"「 "+c(g.value)+" 」",1),o("span",le,"---"+c(C.value),1)])):h("",!0)]),o("div",re,[u.value.length>0?(n(),a("div",ue,[(n(!0),a(w,null,z(u.value,s=>(n(),a("div",{key:s.category,class:"rank-panel"},[o("div",de,[o("span",_e,c(L(s.category))+" "+c(s.label),1),o("div",he,[o("span",{class:I(["panel-tab",{active:_[s.category]==="hot"}]),onClick:r=>T(s.category,"hot")},"热榜",10,ve),o("span",{class:I(["panel-tab",{active:_[s.category]==="newest"}]),onClick:r=>T(s.category,"newest")},"最新",10,pe)])]),o("div",ge,[(n(!0),a(w,null,z(E(s),(r,b)=>(n(),a("div",{key:s.category+"-"+b,class:"rank-item",onClick:$e=>P(r.keyword)},[o("span",{class:I(["rank-idx",{"top-three":b<3}])},c(b+1),3),o("span",me,c(r.keyword),1),o("span",ye,c(j(r)),1)],8,fe))),128)),H(s)?(n(),a("div",{key:0,class:"rank-expand",onClick:r=>M(s.category)}," 展开全部 ▼ ",8,ke)):h("",!0)]),o("div",Ce,[s.category==="hotsite"?(n(),a("span",be,"基于本站搜索数据")):s.category==="donghua"||s.category==="global_anime"?(n(),a("span",we,"数据来源Bilibili")):s.category==="movie"||s.category==="tv"?(n(),a("span",xe,"数据来源:百度")):(n(),a("span",Ie,"数据来源TMDB")),o("span",qe,c(S.value),1)])]))),128))])):h("",!0)]),k.value?(n(),a("div",Ae,[o("div",Se,c(k.value),1),o("div",Ne,[v($,{class:"footer-disclaimer-btn",size:"small",onClick:O},{default:f(()=>[...t[3]||(t[3]=[D("📜 免责声明",-1)])]),_:1})])])):h("",!0)])}}}),De=ee(Te,[["__scopeId","data-v-1f536d99"]]);export{De as default};

View File

@@ -0,0 +1 @@
.home-page[data-v-1f536d99]{min-height:100vh;display:flex;flex-direction:column}.hero-section[data-v-1f536d99]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 24px 40px}.logo-text[data-v-1f536d99]{font-size:64px;font-weight:700;color:var(--primary-color);margin-bottom:32px;letter-spacing:-2px}.logo-img[data-v-1f536d99]{max-width:500px;max-height:120px;width:auto;height:auto;object-fit:contain;margin-bottom:32px}.search-box[data-v-1f536d99]{display:flex;align-items:center;width:100%;max-width:640px;border:1px solid #dfe1e5;border-radius:24px;background:#fff;box-shadow:none;transition:box-shadow .2s,border-color .2s;overflow:hidden}.search-box[data-v-1f536d99]:focus-within{box-shadow:0 1px 6px #20212447;border-color:#dfe1e500}.search-box[data-v-1f536d99] .el-input__wrapper{border:none;box-shadow:none;background:transparent;padding:4px 20px;border-radius:0}.search-box[data-v-1f536d99] .el-input__inner{font-size:15px}.search-btn[data-v-1f536d99]{flex-shrink:0;border:none;border-radius:999px;padding:0 24px;height:38px;line-height:38px;margin:4px;font-size:14px;font-weight:600;background:var(--primary-color);color:#fff;cursor:pointer;transition:all .2s;letter-spacing:1px}.search-btn[data-v-1f536d99]:hover{background:#3a7be0}.search-btn[data-v-1f536d99]:active{background:#2d6ccf}.quote-section[data-v-1f536d99]{margin-top:18px;max-width:640px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.quote-text[data-v-1f536d99]{font-size:14px;color:#aab0b8;font-style:italic;letter-spacing:.5px}.quote-author[data-v-1f536d99]{font-size:12px;color:#c0c4cc;display:inline-block;margin-left:4px}.content-section[data-v-1f536d99]{max-width:1500px;width:100%;margin:0 auto;padding:0 16px 60px}.rankings-grid[data-v-1f536d99]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px;margin-top:8px}.rank-panel[data-v-1f536d99]{background:var(--bg-white,#fff);border-radius:12px;padding:14px;border:1px solid #ebeef5;box-shadow:0 1px 4px #0000000a;display:flex;flex-direction:column}.panel-header[data-v-1f536d99]{display:flex;align-items:center;justify-content:space-between;padding-bottom:10px;border-bottom:2px solid #f0f0f0;margin-bottom:4px}.panel-title[data-v-1f536d99]{font-size:15px;font-weight:700;color:#303133;white-space:nowrap}.panel-tabs[data-v-1f536d99]{display:flex;gap:2px;background:#f0f2f5;border-radius:6px;padding:2px}.panel-tab[data-v-1f536d99]{font-size:11px;padding:3px 10px;border-radius:5px;cursor:pointer;color:#909399;font-weight:500;transition:all .2s;-webkit-user-select:none;user-select:none}.panel-tab.active[data-v-1f536d99]{background:#fff;color:var(--primary-color);font-weight:600;box-shadow:0 1px 3px #0000000f}.panel-body[data-v-1f536d99]{flex:1;display:flex;flex-direction:column;gap:1px}.rank-item[data-v-1f536d99]{display:flex;align-items:center;gap:8px;padding:5px 6px;border-radius:6px;cursor:pointer;transition:background .15s}.rank-item[data-v-1f536d99]:hover{background:#f0f5ff}.rank-item[data-v-1f536d99]:active{background:#e6f0ff}.rank-idx[data-v-1f536d99]{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#909399;background:#f0f0f0;flex-shrink:0}.rank-idx.top-three[data-v-1f536d99]{background:var(--primary-color);color:#fff}.rank-name[data-v-1f536d99]{flex:1;min-width:0;font-size:13px;font-weight:500;color:#303133;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rank-cnt[data-v-1f536d99]{font-size:11px;color:#c0c4cc;white-space:nowrap;flex-shrink:0}.rank-expand[data-v-1f536d99]{text-align:center;padding:6px;margin-top:2px;font-size:12px;color:var(--primary-color);cursor:pointer;border-radius:6px;transition:background .15s;-webkit-user-select:none;user-select:none}.rank-expand[data-v-1f536d99]:hover{background:#ecf5ff}.panel-footer[data-v-1f536d99]{margin-top:8px;padding-top:8px;border-top:1px solid #f0f0f0;display:flex;align-items:center;justify-content:space-between;font-size:11px;color:#c0c4cc}.footer-time[data-v-1f536d99]{font-family:monospace;font-size:10px}@media (max-width: 900px){.hero-section[data-v-1f536d99]{padding:36px 16px 24px}.logo-text[data-v-1f536d99]{font-size:36px;margin-bottom:20px}.logo-img[data-v-1f536d99]{max-width:360px;max-height:100px;margin-bottom:20px}.rankings-scroll[data-v-1f536d99]{gap:12px}}.site-footer[data-v-1f536d99]{margin-top:auto;padding:20px 16px 32px;background:#f9fafb;border-top:1px solid #ebeef5}.footer-inner[data-v-1f536d99]{max-width:800px;margin:0 auto;font-size:12px;line-height:1.8;color:#909399;text-align:center;white-space:pre-line}.footer-actions[data-v-1f536d99]{display:flex;justify-content:center;align-items:center;gap:12px;margin-top:12px}.footer-disclaimer-btn[data-v-1f536d99]{font-size:12px!important;color:#909399!important}.footer-disclaimer-btn[data-v-1f536d99]:hover{color:#409eff!important}

View File

@@ -0,0 +1 @@
.cloud-select[data-v-098423df]{width:100%}.result-detail-page[data-v-755e2105]{min-height:100vh;background:#f5f7fa;padding:40px 24px}.detail-container[data-v-755e2105]{max-width:1080px;margin:0 auto}.detail-card[data-v-755e2105]{background:var(--bg-white);border-radius:var(--radius-card);box-shadow:var(--shadow-card);padding:32px}.detail-header[data-v-755e2105]{display:flex;gap:24px;margin-bottom:24px}.detail-cover[data-v-755e2105]{position:relative;flex-shrink:0;width:240px;height:180px;border-radius:12px;overflow:hidden}.detail-cover img[data-v-755e2105]{width:100%;height:100%;object-fit:cover}.detail-info[data-v-755e2105]{flex:1}.detail-info h1[data-v-755e2105]{font-size:24px;font-weight:700;color:#303133;margin-bottom:12px;line-height:1.4}.detail-meta[data-v-755e2105]{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}.detail-desc[data-v-755e2105]{font-size:15px;color:#606266;line-height:1.6}.detail-actions[data-v-755e2105]{border-top:1px solid var(--border-color);padding-top:20px}.detail-video[data-v-755e2105]{margin-bottom:24px}.video-preview[data-v-755e2105]{position:relative;width:100%;border-radius:12px;overflow:hidden;margin-bottom:20px}.video-preview img[data-v-755e2105]{width:100%;max-height:400px;object-fit:cover;display:block}.play-overlay[data-v-755e2105]{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;background:#0000004d;cursor:pointer;transition:background .2s}.play-overlay[data-v-755e2105]:hover{background:#0006}.play-btn[data-v-755e2105]{padding:12px 32px;background:#ffffffe6;border-radius:24px;font-size:18px;font-weight:600;color:#303133}.video-info h1[data-v-755e2105]{font-size:24px;font-weight:700;color:#303133;margin-bottom:12px}.video-author[data-v-755e2105],.video-platform[data-v-755e2105]{font-size:15px;color:var(--text-secondary);margin-bottom:6px}.video-player-wrapper[data-v-755e2105]{margin-top:24px;border-top:1px solid var(--border-color);padding-top:24px}.video-player[data-v-755e2105]{width:100%;border-radius:8px}.loading-state[data-v-755e2105]{padding:40px 0}.save-dialog-content[data-v-755e2105]{display:flex;flex-direction:column;gap:16px}.save-file-name[data-v-755e2105]{font-size:15px;font-weight:500;color:#303133}.result-dialog-content[data-v-755e2105]{display:flex;flex-direction:column;gap:16px}.share-link-box[data-v-755e2105]{display:flex;flex-direction:column;gap:8px}.share-label[data-v-755e2105]{font-size:14px;color:#606266}.share-link-row[data-v-755e2105]{display:flex;gap:8px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.el-card[data-v-79a959cf]{margin-bottom:20px}.el-card[data-v-79a959cf] .el-card__header{font-weight:600;font-size:15px}[data-v-79a959cf] .el-divider__text.is-left{left:0;padding-left:0}.form-tip[data-v-79a959cf]{font-size:12px;color:#909399;margin-top:4px}.fallback-upload-wrap[data-v-79a959cf]{display:flex;flex-direction:column;gap:12px}.fallback-upload-row[data-v-79a959cf]{display:flex;align-items:center;flex-wrap:wrap}.fallback-preview[data-v-79a959cf]{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.fallback-preview img[data-v-79a959cf]{max-width:100%;height:auto;max-height:120px;border-radius:8px;border:1px solid var(--border-color);background:#f0f0f0;object-fit:contain}.save-bar[data-v-79a959cf]{position:sticky;bottom:0;background:var(--bg-white);padding:16px 24px 16px 0;border-top:1px solid var(--border-color);margin-top:24px;display:flex;justify-content:flex-end;gap:12px}.strategy-grid[data-v-79a959cf]{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px 16px}.grid-cell[data-v-79a959cf]{display:flex;flex-direction:column;gap:4px}.strategy-section[data-v-79a959cf]{padding:0 4px}.field-block[data-v-79a959cf]{margin:12px 0}.field-label-row[data-v-79a959cf]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.field-label[data-v-79a959cf]{font-size:14px;font-weight:500;color:#303133;white-space:nowrap}.field-desc[data-v-79a959cf]{font-size:12px;color:#909399;margin:3px 0 0;line-height:1.5}.keyword-input-row[data-v-79a959cf]{display:flex;gap:8px;flex:1;min-width:200px}.tag-list[data-v-79a959cf]{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.tag-empty[data-v-79a959cf]{font-size:13px;color:#c0c4cc;margin-top:8px}.filter-rule-help[data-v-79a959cf]{margin-top:8px;padding:10px 12px;background:#f8f9fa;border-radius:8px;border:1px solid #e8e8e8}.filter-rule-help .help-title[data-v-79a959cf]{font-weight:600;font-size:13px;margin:8px 0 4px;color:#333}.filter-rule-help .help-title[data-v-79a959cf]:first-child{margin-top:0}.filter-rule-help .help-row[data-v-79a959cf]{font-size:12px;color:#555;margin:3px 0;line-height:1.6}.filter-rule-help .help-row code[data-v-79a959cf]{background:#eef1f5;padding:1px 5px;border-radius:3px;font-size:11px;font-family:monospace}.filter-rules-help[data-v-79a959cf]{margin-top:8px;padding:12px;background:#f8f9fa;border-radius:8px;border:1px solid #e8e8e8}.help-title[data-v-79a959cf]{font-weight:600;font-size:13px;margin:10px 0 6px;color:#333}.help-title[data-v-79a959cf]:first-child{margin-top:0}.help-row[data-v-79a959cf]{font-size:12px;color:#555;margin:3px 0;line-height:1.6}.help-row code[data-v-79a959cf]{background:#eef1f5;padding:1px 5px;border-radius:3px;font-size:11px;font-family:monospace}.help-sample[data-v-79a959cf]{background:#1e1e1e;color:#d4d4d4;padding:10px 14px;border-radius:6px;font-size:12px;line-height:1.6;overflow-x:auto;white-space:pre;margin:6px 0 0;font-family:monospace}.help-preview-row[data-v-79a959cf]{font-size:13px;margin:4px 0;display:flex;align-items:center;gap:6px}.help-preview-label[data-v-79a959cf]{color:#888;min-width:70px;font-size:12px}.help-preview-original[data-v-79a959cf]{color:#e74c3c}.help-preview-filtered[data-v-79a959cf]{color:#27ae60;font-weight:500}.filter-input-row[data-v-79a959cf]{display:flex;gap:8px;width:100%;margin-bottom:8px}.filter-tag-list[data-v-79a959cf]{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}.filter-empty[data-v-79a959cf]{font-size:13px;color:#c0c4cc;padding:8px 0;margin-bottom:8px}.db-status-grid[data-v-79a959cf]{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;margin-top:8px}.db-stat-item[data-v-79a959cf]{background:#f8f9fa;border-radius:10px;padding:16px 12px;text-align:center;border:1px solid #eee;transition:transform .15s,box-shadow .15s}.db-stat-item[data-v-79a959cf]:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000000f}.db-stat-value[data-v-79a959cf]{white-space:nowrap;font-size:24px;font-weight:700;color:#303133;margin-bottom:4px}.db-stat-value.text-success[data-v-79a959cf]{color:#67c23a}.db-stat-value.text-warning[data-v-79a959cf]{color:#e6a23c}.db-stat-label[data-v-79a959cf]{font-size:12px;color:#909399}@media (max-width: 900px){.strategy-grid[data-v-79a959cf]{grid-template-columns:1fr 1fr}}@media (max-width: 600px){.strategy-grid[data-v-79a959cf]{grid-template-columns:1fr}}.pansou-status-grid[data-v-79a959cf]{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;margin-top:8px}.status-dot[data-v-79a959cf]{width:8px;height:8px;border-radius:50%;display:inline-block}.dot-ok[data-v-79a959cf]{background:#67c23a}.dot-err[data-v-79a959cf]{background:#f56c6c}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
const a={quark:"夸克网盘",baidu:"百度网盘",aliyun:"阿里云盘",115:"115网盘",tianyi:"天翼云盘","123pan":"123云盘",uc:"UC网盘",xunlei:"迅雷云盘",pikpak:"PikPak",magnet:"磁力链接",ed2k:"电驴链接",others:"其他"},e={quark:"#07c160",baidu:"#4e6ef2",aliyun:"#ff6a00",115:"#9b59b6",tianyi:"#00a1d6","123pan":"#e74c3c",uc:"#f39c12",xunlei:"#2ecc71",pikpak:"#8e44ad",magnet:"#95a5a6",ed2k:"#7f8c8d",others:"#95a5a6"};export{e as C,a};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>免责声明 - 资源分享</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
background: #f5f7fa;
color: #333;
line-height: 1.8;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
padding: 40px;
}
h1 {
font-size: 22px;
text-align: center;
margin-bottom: 30px;
color: #1a1a2e;
border-bottom: 2px solid #e8e8e8;
padding-bottom: 16px;
}
h2 { font-size: 16px; margin: 24px 0 10px; color: #1a1a2e; }
h3 { font-size: 15px; margin: 18px 0 8px; color: #303133; }
p { margin: 8px 0; text-indent: 2em; font-size: 14px; }
.highlight { background: #fff3cd; padding: 1px 4px; border-radius: 3px; }
.footer { margin-top: 30px; padding-top: 16px; border-top: 1px solid #e8e8e8; text-align: center; font-size: 12px; color: #999; }
.back-link { display: inline-block; margin-top: 20px; color: #409eff; text-decoration: none; font-size: 14px; }
.back-link:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<h1>📜 网站免责声明</h1>
<h2>一、版权与资源声明</h2>
<p>本网站(<a href="https://hk-zy.timaa.cn" target="_blank">hk-zy.timaa.cn</a>)是一个基于开源项目搭建的<strong>非盈利性个人站点</strong>,旨在分享与交流技术资源。本网站所有资源均收集整理自互联网,其版权、著作权均归原作者或发行公司所有。本网站不对资源的版权归属进行实质审查,对于任何由资源本身引发的版权争议概不负责。</p>
<h2>二、使用限制与法律责任</h2>
<p>用户在本网站下载的所有软件、资料等资源,仅供<strong>个人学习、研究、技术交流</strong>,严禁用于任何商业或非法用途。用户必须在下载后的<strong class="highlight">24小时内</strong>,从个人电脑及存储设备中彻底删除相关内容。如用户喜欢该程序或内容,请支持正版,到官方网站购买注册。</p>
<p>因用户不当使用(包括但不限于商业使用、非法传播、破解侵权)而引发的一切法律纠纷及后果,由用户<strong>自行承担</strong>,本网站及网站管理者不承担任何连带责任。</p>
<h2>三、"避风港原则"与侵权处理</h2>
<p>依据《信息网络传播权保护条例》,本网站仅提供信息存储空间服务或资源索引服务。若用户上传或分享的内容侵犯了您的合法权益,请您立即通过以下联系方式与我们交涉。</p>
<p><strong>联系方式:</strong> 3337598077@qq.com</p>
<p><strong>处理措施:</strong> 我们在收到权利人发出的合格通知(包括权属证明和侵权链接)后,将在合理期限内对涉嫌侵权内容进行核实、断开链接或直接删除。</p>
<p><strong>唯一目的:</strong> 本网站为纯公益、非盈利性分享,绝无意侵害任何第三方权益。若内容涉及侵权,实属无意,请版权方及时通知以便我们处理。</p>
<h2>四、用户行为与网站免责</h2>
<p>访问者在本网站进行下载、浏览时,须自行承担风险。本网站不保证资源完全无毒、无缺陷或绝对安全,对于因使用本站资源而造成的硬件损坏、数据丢失等损失,本网站不负任何责任。</p>
<p>本站内容仅代表资源提供者的个人观点,不代表本站立场。对于任何站点外部链接的真实性、合法性,本站不承担担保责任。</p>
<p>凡以任何方式登陆本网站或直接、间接使用本网站资源者,视为自愿接受本网站免责声明的约束。</p>
<h2>五、法律适用</h2>
<p>本声明未涉及的问题参见国家有关法律法规。当本声明与国家法律法规冲突时,以国家法律法规为准。本网站保留对本声明的最终解释权。</p>
<div class="footer">
<p style="text-indent:0">更新日期2026年5月 · 版本 V1.0</p>
<a class="back-link" href="/" onclick="history.length > 1 ? history.back() : (location.href='/'); return false;">← 返回首页</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,923 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>CloudSearch - 搜索</title>
<script>
// 替换标题为网站名称
fetch('/api/site-config').then(function(r){return r.json()}).then(function(cfg){
if(cfg.site_name) document.title = cfg.site_name + ' - 搜索';
}).catch(function(){});
</script>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<style>
/* ===== Reset & Base ===== */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{font-size:16px;-webkit-text-size-adjust:100%}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;background:#f5f5f5;color:#303133;min-height:100vh;overflow-x:hidden}
:root{--primary:#409eff;--primary-dark:#337ecc;--primary-light:rgba(64,158,255,0.08);--text:#303133;--text2:#909399;--border:#ebeef5;--bg:#f5f5f5;--white:#fff;--radius:10px;--shadow:0 1px 4px rgba(0,0,0,0.04);--safe-bottom:env(safe-area-inset-bottom,0px)}
a{color:var(--primary);text-decoration:none}
img{display:block;max-width:100%}
/* ===== Home Page ===== */
.home-page{padding-bottom:calc(30px + var(--safe-bottom))}
.home-hero{display:flex;flex-direction:column;align-items:center;padding:36px 16px 20px}
.home-logo{font-size:32px;font-weight:700;color:var(--primary);margin-bottom:20px;text-align:center}
.home-logo-img{max-width:360px;max-height:80px;width:auto;height:auto;object-fit:contain}
.home-search-box{display:flex;width:100%;max-width:500px;border:1px solid var(--border);border-radius:20px;overflow:hidden;background:var(--bg);transition:border-color .2s}
.home-search-box:focus-within{border-color:var(--primary);background:var(--white);box-shadow:0 0 0 3px rgba(64,158,255,.1)}
.home-search-box input{flex:1;height:40px;border:none;padding:0 14px;font-size:14px;outline:none;background:transparent}
.home-search-box button{flex-shrink:0;height:32px;margin:4px;padding:0 22px;border:none;border-radius:999px;background:var(--primary);color:var(--white);font-size:13px;font-weight:600;cursor:pointer}
.home-search-box button:active{background:var(--primary-dark)}
.home-quote{margin-top:12px;font-size:12px;color:#b0b8c4;font-style:italic;text-align:center;max-width:500px;line-height:1.5}
.home-quote-author{font-size:11px;color:#c0c4cc;display:inline-block;margin-top:2px}
/* ===== Home Rankings ===== */
.home-rankings{padding:8px 12px;display:flex;flex-direction:column;gap:10px}
.rank-block{background:var(--white);border-radius:var(--radius);padding:12px;border:1px solid var(--border);box-shadow:var(--shadow)}
.rank-block-hdr{display:flex;align-items:center;justify-content:space-between;padding-bottom:8px;border-bottom:2px solid #f0f0f0;margin-bottom:4px}
.rank-block-title{font-size:14px;font-weight:700;color:var(--text);white-space:nowrap}
.rank-block-tabs{display:flex;gap:2px;background:#f0f2f5;border-radius:5px;padding:2px}
.rank-tab{font-size:11px;padding:2px 9px;border-radius:4px;cursor:pointer;color:#909399;font-weight:500;transition:all .2s;user-select:none}
.rank-tab.active{background:var(--white);color:var(--primary);font-weight:600;box-shadow:0 1px 2px rgba(0,0,0,.06)}
.rank-item{display:flex;align-items:center;gap:7px;padding:5px 6px;border-radius:6px;cursor:pointer;transition:background .15s}
.rank-item:active{background:#f0f5ff}
.rank-idx{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#909399;background:#f0f0f0;flex-shrink:0}
.rank-idx.top3{background:var(--primary);color:var(--white);font-size:12px}
.rank-name{flex:1;min-width:0;font-size:13px;font-weight:500;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rank-cnt{font-size:11px;color:#c0c4cc;white-space:nowrap;flex-shrink:0}
.rank-expand{text-align:center;padding:5px;margin-top:2px;font-size:12px;color:var(--primary);cursor:pointer;border-radius:5px;user-select:none}
.rank-expand:active{background:#ecf5ff}
.rank-block-ftr{margin-top:6px;padding-top:6px;border-top:1px solid #f0f0f0;display:flex;align-items:center;justify-content:space-between;font-size:10px;color:#c0c4cc}
.ftr-time{font-family:monospace;font-size:9px}
/* ===== Layout ===== */
.app{max-width:100%;margin:0 auto;padding-bottom:calc(20px + var(--safe-bottom))}
.header{position:sticky;top:0;z-index:50;background:var(--white);border-bottom:1px solid var(--border);padding:8px 12px}
.header-row{display:flex;align-items:center;gap:10px}
.header-title{font-size:18px;font-weight:700;color:var(--primary);flex-shrink:0}
.header-title-link{text-decoration:none;flex-shrink:0;display:flex;align-items:center}
.header-logo-img{max-width:160px;max-height:36px;width:auto;height:auto;object-fit:contain;display:block}
.header-actions{margin-left:auto;display:flex;gap:6px;align-items:center}
/* ===== Search Bar ===== */
.search-wrap{flex:1;display:flex;border:1px solid var(--border);border-radius:18px;overflow:hidden;background:var(--bg);transition:border-color .2s}
.search-wrap:focus-within{border-color:var(--primary);background:var(--white)}
.search-wrap input{flex:1;height:36px;border:none;padding:0 14px;font-size:14px;outline:none;background:transparent}
.search-wrap button{flex-shrink:0;height:28px;margin:4px;padding:0 18px;border:none;border-radius:999px;background:var(--primary);color:var(--white);font-size:13px;font-weight:600;cursor:pointer;transition:background .2s}
.search-wrap button:active{background:var(--primary-dark)}
.search-wrap button:disabled{opacity:.5}
/* ===== Footer ===== */
.site-footer{margin-top:30px;padding:16px 12px 24px;background:#f9fafb;border-top:1px solid var(--border)}
.footer-inner{max-width:500px;margin:0 auto;font-size:11px;line-height:1.8;color:#909399;text-align:center;white-space:pre-line}
.footer-actions{display:flex;justify-content:center;gap:10px;margin-top:12px;flex-wrap:wrap}
.footer-btn{padding:8px 20px;border:1px solid var(--border);border-radius:8px;background:var(--white);color:var(--text);font-size:13px;cursor:pointer;transition:all .2s}
.footer-btn:active{background:var(--primary-light);border-color:var(--primary);color:var(--primary)}
/* ===== Info Bar ===== */
.info-bar{display:flex;align-items:center;gap:8px;padding:10px 12px 0;font-size:12px;color:var(--text2);flex-wrap:wrap}
.info-bar .count{font-weight:600;color:var(--text)}
.info-bar .time{font-family:monospace;background:#f4f4f5;padding:1px 6px;border-radius:4px}
.info-bar .badge-err{background:#fef0f0;color:#f56c6c;padding:1px 6px;border-radius:4px}
/* ===== Loading ===== */
.loading{padding:24px 12px;text-align:center;font-size:13px;color:var(--text2)}
.loading-bar{width:100%;height:3px;background:#e8e8e8;border-radius:2px;overflow:hidden;margin-top:8px}
.loading-bar-inner{height:100%;background:linear-gradient(90deg,var(--primary),#67c23a);border-radius:2px;transition:width .3s ease;width:0%}
/* ===== Tabs ===== */
.tabs{display:flex;gap:4px;padding:8px 12px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
.tabs::-webkit-scrollbar{display:none}
.tab{flex-shrink:0;padding:5px 12px;border-radius:16px;font-size:12px;color:#606266;background:#f0f2f5;cursor:pointer;white-space:nowrap;transition:all .2s;user-select:none}
.tab:active{transform:scale(.95)}
.tab.active{background:var(--primary-light);color:var(--primary);font-weight:600}
/* ===== Results ===== */
.results{display:flex;flex-direction:column;gap:10px;padding:8px 12px}
.empty{padding:40px 12px;text-align:center;color:var(--text2);font-size:14px}
/* ===== Card ===== */
.card{display:flex;gap:10px;background:var(--white);border-radius:var(--radius);padding:10px;border:1px solid var(--border);transition:border-color .2s}
.card:active{border-color:#c0c4cc}
.card-cover{flex-shrink:0;width:90px;height:120px;border-radius:8px;overflow:hidden;background:var(--bg);position:relative}
.card-cover img{width:100%;height:100%;object-fit:cover}
.card-cover .placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:28px;background:linear-gradient(135deg,#667eea,#764ba2);color:rgba(255,255,255,.6)}
.card-cover .tag{position:absolute;bottom:3px;left:3px;padding:1px 5px;border-radius:3px;color:#fff;font-size:10px;font-weight:600;backdrop-filter:blur(2px)}
.card-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
.card-title{font-size:14px;font-weight:700;color:var(--text);line-height:1.3;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}
.card-meta{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:8px}
.card-meta .size{color:#67c23a}
.card-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:2px}
.card-tags span{font-size:10px;padding:1px 6px;border-radius:4px;background:#ecf5ff;color:#409eff;white-space:nowrap}
.card-tags .quality{background:#fef0f0;color:#e74c3c}
.card-actions{margin-top:auto;display:flex;align-items:center;justify-content:space-between;gap:6px}
.card-source{font-size:10px;color:var(--text2);background:#f4f4f5;padding:1px 6px;border-radius:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}
.card-btn{padding:4px 10px;border:none;border-radius:6px;background:var(--primary);color:var(--white);font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .2s}
.card-btn:active{background:var(--primary-dark)}
.card-btn:disabled{opacity:.5}
/* ===== Toast ===== */
.toast{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,.75);color:#fff;padding:10px 20px;border-radius:8px;font-size:14px;z-index:200;pointer-events:none;opacity:0;transition:opacity .3s}
.toast.show{opacity:1}
.toast.error{background:rgba(245,108,108,.9)}
/* ===== Overlay / Modal ===== */
.overlay{position:fixed;inset:0;z-index:100;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;padding:16px;animation:fadeIn .2s}
.modal{background:var(--white);border-radius:14px;width:100%;max-width:420px;max-height:90vh;overflow-y:auto;padding:0;animation:slideUp .25s}
.modal-hdr{padding:14px 16px;border-bottom:1px solid var(--border);font-size:15px;font-weight:700;color:var(--text)}
.modal-body{padding:14px 16px}
.modal-ftr{padding:10px 16px;border-top:1px solid var(--border);display:flex;gap:8px;justify-content:flex-end}
.modal-ftr button{height:36px;padding:0 16px;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer}
.modal-ftr .btn-close{background:#f4f4f5;color:#606266}
.modal-ftr .btn-disclaimer{background:#fdf6ec;color:#d46b08;margin-right:auto}
.modal-ftr .btn-primary{background:var(--primary);color:var(--white)}
.modal-ftr .btn-primary:active{background:var(--primary-dark)}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes slideUp{from{transform:translateY(40px);opacity:0}to{transform:translateY(0);opacity:1}}
/* ===== Share Modal ===== */
.share-section{display:flex;flex-direction:column;gap:10px}
.share-row{display:flex;gap:8px}
.share-row input{flex:1;height:36px;border:1px solid var(--border);border-radius:6px;padding:0 10px;font-size:13px;outline:none;background:var(--bg);color:var(--text)}
.share-row input:focus{border-color:var(--primary)}
.share-row .copy-btn{height:36px;padding:0 12px;border:none;border-radius:6px;background:var(--primary);color:var(--white);font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}
.share-pwd{display:flex;align-items:center;gap:6px;font-size:13px}
.share-pwd .pwd-tag{padding:2px 8px;background:#fdf6ec;color:#e6a23c;border-radius:4px;font-weight:700}
.share-pwd .pwd-hint{font-size:11px;color:var(--text2)}
.share-tip{padding:8px 10px;background:#fdf6ec;border-radius:6px;font-size:12px;line-height:1.5;color:#d46b08;display:flex;gap:6px;align-items:flex-start}
.share-tip .warn-icon{font-size:18px;line-height:1.5;flex-shrink:0}
.share-tip .tip-text{flex:1;min-width:0}
.share-tip strong{font-weight:700}
.warning-box{background:#fff2f0;border:1px solid #ffccc7;border-radius:8px;padding:8px 10px;overflow-x:auto;-webkit-overflow-scrolling:touch}
.warning-item{margin:0;font-size:12px;line-height:1.8;font-weight:700;white-space:nowrap}
.warning-item:nth-child(odd){color:#cf1322}
.warning-item:nth-child(even){color:#d46b08}
.warning-item:last-child{color:#b71c1c;font-size:13px}
.share-qr{display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px}
.share-qr canvas{border-radius:8px}
.share-qr .qr-label{font-size:12px;font-weight:600;color:var(--primary)}
.share-qr .qr-sub{font-size:11px;color:var(--text2)}
.share-disclaimer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:10px;padding:8px 10px;background:#fdf6ec;border-radius:6px;font-size:12px;color:#d46b08;flex-wrap:wrap}
/* ===== Login Modal ===== */
.login-form{display:flex;flex-direction:column;gap:12px}
.login-form input{height:40px;border:1px solid var(--border);border-radius:8px;padding:0 12px;font-size:14px;outline:none}
.login-form input:focus{border-color:var(--primary)}
.login-form .login-btn{height:40px;border:none;border-radius:8px;background:var(--primary);color:var(--white);font-size:14px;font-weight:600;cursor:pointer;transition:background .2s}
.login-form .login-btn:active{background:var(--primary-dark)}
.login-form .login-btn:disabled{opacity:.5}
.login-form .login-err{font-size:12px;color:#f56c6c;text-align:center}
/* ===== Progress Steps ===== */
.steps{display:flex;flex-direction:column;gap:10px;padding:8px 0}
.step{display:flex;align-items:flex-start;gap:10px;opacity:.4;transition:opacity .3s}
.step.active{opacity:1}
.step.done{opacity:.7}
.step-dot{flex-shrink:0;width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;background:#e4e7ed;color:#909399}
.step.active .step-dot{background:var(--primary);color:#fff;box-shadow:0 0 0 3px rgba(64,158,255,.2)}
.step.done .step-dot{background:#67c23a;color:#fff}
.step-body{flex:1;padding-top:3px;display:flex;align-items:center;gap:8px}
.step-title{font-size:13px;color:var(--text);font-weight:500}
.step-status{font-size:11px;padding:1px 7px;border-radius:10px;white-space:nowrap}
.step-status.doing{background:#ecf5ff;color:var(--primary)}
.step-status.done{background:#f0f9eb;color:#67c23a}
.step-status.wait{background:#f4f4f5;color:#c0c4cc}
.error-alert{padding:12px 16px;background:#fef0f0;border:1px solid #fde2e2;border-radius:8px;display:flex;align-items:center;gap:8px;font-size:14px;color:#f56c6c}
/* ===== User Badge ===== */
.user-badge{font-size:12px;color:var(--primary);font-weight:600;white-space:nowrap}
.login-btn-small{height:30px;padding:0 10px;border:none;border-radius:6px;background:var(--primary-light);color:var(--primary);font-size:12px;font-weight:600;cursor:pointer}
.logout-btn-small{height:30px;padding:0 8px;border:none;border-radius:6px;background:transparent;color:var(--text2);font-size:12px;cursor:pointer}
</style>
</head>
<body>
<div class="app" id="app">
<!-- ===== Home Page (shown when no search) ===== -->
<div id="homePage" class="home-page">
<div class="home-hero">
<div class="home-logo" id="homeLogo" style="display:none"></div>
<div class="home-search-box">
<input id="homeSearchInput" type="text" placeholder="搜索网盘资源..." />
<button id="homeSearchBtn" onclick="homeSearch()">搜 索</button>
</div>
<div class="home-quote" id="homeQuote"></div>
<div class="home-quote-author" id="homeQuoteAuthor"></div>
</div>
<div class="home-rankings" id="homeRankings"></div>
</div>
<!-- ===== Search Results View ===== -->
<div id="searchView" style="display:none">
<!-- Header -->
<div class="header">
<div class="header-row">
<a href="/h5" class="header-title-link"><div class="header-title" id="headerTitle" style="display:none">CloudSearch</div></a>
<div class="search-wrap">
<input id="searchInput" type="text" placeholder="搜索网盘资源..." @keydown="handleKeydown" />
<button id="searchBtn" onclick="doSearch()">搜 索</button>
</div>
<div class="header-actions" id="userArea">
<template id="userLoggedIn">
<span class="user-badge" id="usernameDisplay"></span>
<button class="logout-btn-small" onclick="logout()">退出</button>
</template>
<template id="userLoggedOut">
<button class="login-btn-small" onclick="showLogin()">登录</button>
</template>
</div>
</div>
</div>
<!-- Info Bar -->
<div id="infoBar" class="info-bar" style="display:none">
<span id="infoCount" class="count"></span>
<span id="infoTime" class="time"></span>
<span id="infoFiltered" class="badge-err"></span>
</div>
<!-- Loading -->
<div id="loading" class="loading" style="display:none">
<div id="loadingText">🔍 正在搜索中...</div>
<div class="loading-bar"><div class="loading-bar-inner" id="loadingBar"></div></div>
</div>
<!-- Tabs -->
<div id="tabs" class="tabs" style="display:none"></div>
<!-- Results -->
<div id="results" class="results"></div>
<!-- Overlay -->
<div class="overlay" id="overlay" style="display:none" onclick="closeModal()"></div>
<!-- Share Modal -->
<div class="modal" id="shareModal" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:101">
<div class="modal-hdr" id="shareTitle">分享链接</div>
<div class="modal-body">
<div id="progressSteps" class="steps" style="display:none">
<div class="step" id="step1"><div class="step-dot"><span>1</span></div><div class="step-body"><span class="step-title">正在转存...</span><span class="step-status doing">进行中</span></div></div>
<div class="step" id="step2"><div class="step-dot"><span>2</span></div><div class="step-body"><span class="step-title">重命名文件(防和谐)...</span><span class="step-status wait">等待中</span></div></div>
<div class="step" id="step3"><div class="step-dot"><span>3</span></div><div class="step-body"><span class="step-title">生成分享链接...</span><span class="step-status wait">等待中</span></div></div>
</div>
<div id="saveError" class="error-alert" style="display:none"></div>
<div id="shareContent" style="display:none">
<div class="share-qr">
<div id="qrContainer"></div>
<div class="qr-label" id="qrLabel"></div>
<div class="qr-sub">保存到你自己的网盘</div>
</div>
<div class="share-section">
<div class="share-row">
<input id="shareLinkInput" type="text" readonly />
</div>
<div id="sharePwdRow" class="share-pwd" style="display:none">
<span>🔑 提取密码:</span>
<span class="pwd-tag" id="sharePwdTag"></span>
<span class="pwd-hint">打开链接后需输入密码</span>
</div>
<div class="share-tip">
<span class="warn-icon">⚠️</span>
<div class="tip-text">
<strong>请尽快复制链接到浏览器打开</strong><strong>用夸克APP扫码</strong><br>
<strong>转存至您的网盘,以免资源被官方和谐</strong>
</div>
</div>
<div class="warning-box">
<p class="warning-item">郑重警告一:网盘内除您所需资源外,不要打开任何不相关内容。</p>
<p class="warning-item">郑重警告二:网盘内除您所需资源外,不要打开任何不相关内容。</p>
<p class="warning-item">郑重警告三:网盘内除您所需资源外,不要打开任何不相关内容。</p>
<p class="warning-item">郑重警告四:以上警告说三遍,你还要明知故犯吗?</p>
</div>
<div class="share-disclaimer">
<span>⚠️ 本站资源仅供学习交流请于24h内删除</span>
</div>
</div>
</div>
</div>
<div class="modal-ftr">
<button class="btn-disclaimer" onclick="openDisclaimer()">📜 免责声明</button>
<button class="btn-close" onclick="closeModal()">关闭</button>
<button class="btn-primary" id="copyBtn2" onclick="copyShareLink()" style="display:none">一键复制链接</button>
</div>
</div>
<!-- Login Modal -->
<div class="modal" id="loginModal" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:101">
<div class="modal-hdr">登录</div>
<div class="modal-body">
<div class="login-form">
<input id="loginUser" type="text" placeholder="用户名" />
<input id="loginPass" type="password" placeholder="密码" />
<button class="login-btn" id="loginBtn" onclick="handleLogin()">登录</button>
<div class="login-err" id="loginErr"></div>
</div>
</div>
<div class="modal-ftr">
<button class="btn-close" onclick="closeLogin()">取消</button>
</div>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<script>
// ===== Anime keywords for categorization =====
const ANIME_KWS=['仙逆','凡人修仙传','斗破苍穹','斗破','盘龙','完美世界','一念永恒','妖神记','星辰变','遮天','神墓','吞噬星空','武动乾坤','大主宰','全职高手','鬼灭之刃','海贼王','火影忍者','死神','龙珠','进击的巨人','咒术回战','一人之下','狐妖小红娘','魔道祖师','天官赐福','时光代理人','大王饶命','斗罗大陆','绝世唐门','不良人','秦时明月','全职法师','牧神记','三体','灵笼','雾山五行','凡人','仙王的日常生活','百妖谱','眷思量','镖人','伍六七','刺客伍六七','葬送的芙莉莲','间谍过家家']
// ===== Quotes =====
const QUOTES=['学而时习之,不亦说乎。','温故而知新,可以为师矣。','三人行,必有我师焉。','学而不思则罔,思而不学则殆。','博学之,审问之,慎思之,明辨之,笃行之。','千里之行,始于足下。','不积跬步,无以至千里。','知之为知之,不知为不知,是知也。','工欲善其事,必先利其器。','玉不琢,不成器;人不学,不知道。','学以致用,知行合一。','学海无涯,勤作舟。','书山有路,勤为径。','宝剑锋从磨砺出,梅花香自苦寒来。','锲而不舍,金石可镂。','业精于勤,荒于嬉。','读书破万卷,下笔如有神。','路漫漫其修远兮,吾将上下而求索。','采菊东篱下,悠然见南山。','海内存知己,天涯若比邻。','长风破浪会有时,直挂云帆济沧海。','会当凌绝顶,一览众山小。','山重水复疑无路,柳暗花明又一村。']
// ===== Home Page =====
function homeSearch(){
const q=document.getElementById('homeSearchInput').value.trim()
if(q)doSearchFromHome(q)
}
function doSearchFromHome(q){
document.getElementById('homePage').style.display='none'
document.getElementById('searchView').style.display='block'
document.getElementById('searchInput').value=q
window.history.replaceState({},'','/h5?q='+encodeURIComponent(q))
doSearch()
}
function renderHomePage(data){
fetch("/api/site-config").then(r=>r.json()).then(cfg=>{
// 显示 Logo优先图片其次文字
var logoEl=document.getElementById("homeLogo");
var headerEl=document.getElementById("headerTitle");
if(cfg.site_logo){
logoEl.innerHTML='<img src="'+cfg.site_logo+'" class="home-logo-img" alt="logo" />';
logoEl.style.display="";
headerEl.innerHTML='<img src="'+cfg.site_logo+'" class="header-logo-img" alt="logo" />';
headerEl.style.display="";
}else if(cfg.site_name){
logoEl.textContent=cfg.site_name;
logoEl.style.display="";
headerEl.textContent=cfg.site_name;
headerEl.style.display="";
}else{
logoEl.textContent="CloudSearch";
logoEl.style.display="";
headerEl.textContent="CloudSearch";
headerEl.style.display="";
}
if(cfg.site_disclaimer){
document.getElementById("footerContent").innerHTML=cfg.site_disclaimer.replace(/\n/g,'<br>');
document.getElementById("siteFooter").style.display="block";
}
}).catch(()=>{})
const categories=data.categories||[]
const fetchedAt=data.fetchedAt||''
// Quote
fetch('https://v1.hitokoto.cn/').then(r=>r.json()).then(d=>{
document.getElementById('homeQuote').textContent='「 '+d.hitokoto+' 」'
document.getElementById('homeQuoteAuthor').textContent='---'+(d.from_who||d.from||'')
}).catch(()=>{
document.getElementById('homeQuote').textContent='「 学而时习之,不亦说乎。 」'
document.getElementById('homeQuoteAuthor').textContent='---孔子'
})
// Store expanded state per category
window.__expanded=window.__expanded||{}
window.__activeTab=window.__activeTab||{}
const el=document.getElementById('homeRankings')
let html=''
for(const cat of categories){
const icons={movie:'🎬',tv:'📺',western_movie:'🎥',western:'🌍',donghua:'🐉',global_anime:'🌐',variety:'🎤',niche:'💎',hotsite:'🏆'}
const icon=icons[cat.category]||'📋'
const key=cat.category
if(!window.__activeTab[key])window.__activeTab[key]='hot'
html+='<div class="rank-block">'
html+='<div class="rank-block-hdr">'+
'<span class="rank-block-title">'+icon+' '+cat.label+'</span>'+
'<div class="rank-block-tabs" id="rtabs-'+key+'">'+
'<span class="rank-tab'+(window.__activeTab[key]==='hot'?' active':'')+'" onclick="switchRankTab(\''+key+'\',\'hot\')">热榜</span>'+
'<span class="rank-tab'+(window.__activeTab[key]==='newest'?' active':'')+'" onclick="switchRankTab(\''+key+'\',\'newest\')">最新</span>'+
'</div>'+
'</div>'
html+='<div class="rank-block-items" id="ritems-'+key+'" data-hot=\''+JSON.stringify({items:cat.hot||[]}).replace(/'/g,"&#39;")+'\' data-newest=\''+JSON.stringify({items:cat.newest||[]}).replace(/'/g,"&#39;")+'\'>'
const items=window.__activeTab[key]==='hot'?(cat.hot||[]):(cat.newest||[])
html+=renderRankItems(items,key,false)
html+='</div>'
// 数据来源
html+='<div class="rank-block-ftr">'+
'<span>'+(cat.category!=='hotsite'?'数据来源TMDB':'本站搜索数据')+'</span>'+
'<span class="ftr-time">'+fetchedAt+'</span>'+
'</div></div>'
}
el.innerHTML=html
}
function renderRankItems(items,key,expanded){
if(!items||items.length===0)return'<div style="padding:10px;text-align:center;color:#c0c4cc;font-size:12px">暂无数据</div>'
const limit=3
const show=expanded?items.length:Math.min(limit,items.length)
let html=items.slice(0,show).map((item,i)=>{
const c=i<3?' rank-idx top3':' rank-idx'
return '<div class="rank-item" onclick="doSearchFromHome(\''+item.keyword.replace(/'/g,"\\'")+'\')">'+
'<span class="'+c+'">'+(i+1)+'</span>'+
'<span class="rank-name">'+item.keyword+'</span>'+
'<span class="rank-cnt">'+(item.rating?'⭐'+item.rating:item.searchCount)+'</span>'+
'</div>'
}).join('')
if(items.length>limit&&!expanded){
html+='<div class="rank-expand" onclick="expandRank(\''+key+'\')">展开全部 ▼</div>'
}
return html
}
function expandRank(key){
const container=document.getElementById('ritems-'+key)
if(!container)return
const tab=window.__activeTab[key]||'hot'
const data=JSON.parse(tab==='hot'?container.dataset.hot:container.dataset.newest)
container.innerHTML=renderRankItems(data.items,key,true)
}
function switchRankTab(category,tab){
window.__activeTab[category]=tab
const tabsContainer=document.getElementById('rtabs-'+category)
if(tabsContainer){
tabsContainer.querySelectorAll('.rank-tab').forEach(t=>t.className='rank-tab')
tabsContainer.querySelector(tab==='hot'?'.rank-tab:first-child':'.rank-tab:last-child').className='rank-tab active'
}
const container=document.getElementById('ritems-'+category)
if(container){
const data=JSON.parse(tab==='hot'?container.dataset.hot:container.dataset.newest)
container.innerHTML=renderRankItems(data.items,category,false)
}
}
let userInfo = null
let allResults = []
let allChannels = []
let activeTab = ''
let currentSaveItem = null
const CLOUD_ICONS = {quark:'☁️',baidu:'🔵',aliyun:'🟠','115':'🟣',tianyi:'🔷','123pan':'🔴',uc:'🟡',xunlei:'🟢',pikpak:'🟤',magnet:'🧲',ed2k:'🔗',others:'📁'}
const CLOUD_LABELS = {quark:'夸克网盘',baidu:'百度网盘',aliyun:'阿里云盘','115':'115网盘',tianyi:'天翼云盘','123pan':'123云盘',uc:'UC网盘',xunlei:'迅雷云盘',pikpak:'PikPak',magnet:'磁力链接',ed2k:'电驴链接',others:'其他'}
const CLOUD_COLORS = {quark:'#07c160',baidu:'#4e6ef2',aliyun:'#ff6a00','115':'#9b59b6',tianyi:'#00a1d6','123pan':'#e74c3c',uc:'#f39c12',xunlei:'#2ecc71',pikpak:'#8e44ad',magnet:'#95a5a6',ed2k:'#7f8c8d',others:'#95a5a6'}
const CLOUD_ORDER = {quark:1,baidu:2,aliyun:3,'115':4,tianyi:5,'123pan':6,uc:7,xunlei:8,pikpak:9,magnet:10,ed2k:11,others:12}
// ===== Fetch helpers =====
function getToken(){return localStorage.getItem('h5_admin_token')}
function apiHeaders(){const h={'Content-Type':'application/json'};const t=getToken();if(t)h['Authorization']='Bearer '+t;return h}
// ===== Toast =====
let toastTimer
function showToast(msg,isError){
const el=document.getElementById('toast')
el.textContent=msg
el.className='toast show'+(isError?' error':'')
clearTimeout(toastTimer)
toastTimer=setTimeout(()=>el.className='toast',2000)
}
// ===== User =====
async function checkLogin(){
try{
const res=await fetch('/api/me',{headers:apiHeaders()})
if(res.ok){
const data=await res.json()
if(data.loggedIn){
userInfo=data
document.getElementById('userArea').innerHTML='<span class="user-badge">'+data.username+'</span><button class="logout-btn-small" onclick="logout()">退出</button>'
}
}
}catch(e){}
}
function logout(){
localStorage.removeItem('h5_admin_token')
userInfo=null
document.getElementById('userArea').innerHTML='<button class="login-btn-small" onclick="showLogin()">登录</button>'
showToast('已退出')
}
function showLogin(){
document.getElementById('loginErr').textContent=''
document.getElementById('loginUser').value=''
document.getElementById('loginPass').value=''
document.getElementById('loginModal').style.display='block'
document.getElementById('overlay').style.display='block'
}
function closeLogin(){
document.getElementById('loginModal').style.display='none'
document.getElementById('overlay').style.display='none'
}
async function handleLogin(){
const user=document.getElementById('loginUser').value.trim()
const pass=document.getElementById('loginPass').value
if(!user||!pass){showToast('请输入用户名和密码',true);return}
const btn=document.getElementById('loginBtn')
btn.disabled=true;btn.textContent='登录中...'
try{
const res=await fetch('/api/admin/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:user,password:pass})})
if(res.ok){
const data=await res.json()
localStorage.setItem('h5_admin_token',data.token)
userInfo={username:user}
document.getElementById('userArea').innerHTML='<span class="user-badge">'+user+'</span><button class="logout-btn-small" onclick="logout()">退出</button>'
closeLogin()
showToast('登录成功')
}else{
const err=await res.json().catch(()=>({}))
document.getElementById('loginErr').textContent=err.error||'登录失败'
}
}catch(e){
document.getElementById('loginErr').textContent='网络错误'
}finally{
btn.disabled=false;btn.textContent='登录'
}
}
// ===== Search =====
function handleKeydown(e){if(e.key==='Enter')doSearch()}
let searchTimer
function doSearch(){
const q=document.getElementById('searchInput').value.trim()
if(!q)return
// Update URL
window.history.replaceState({},'','/h5?q='+encodeURIComponent(q))
// Show loading
document.getElementById('results').innerHTML=''
document.getElementById('tabs').style.display='none'
document.getElementById('infoBar').style.display='none'
document.getElementById('loading').style.display='block'
document.getElementById('loadingText').textContent='🔍 正在搜索中...'
document.getElementById('searchBtn').disabled=true
let progress=0
const bar=document.getElementById('loadingBar')
const progressTimer=setInterval(()=>{
if(progress<60)progress+=1+Math.random()*3
else if(progress<85)progress+=0.5+Math.random()
bar.style.width=progress+'%'
},200)
// Use streaming search for live updates
streamSearch(q,progressTimer,bar)
}
async function streamSearch(q,progressTimer,bar){
const startTime=Date.now()
try{
const response=await fetch('/api/query',{method:'POST',headers:apiHeaders(),body:JSON.stringify({q})})
if(!response.ok)throw new Error('搜索失败 ('+response.status+')')
const reader=response.body.getReader()
const decoder=new TextDecoder()
let buffer=''
let allItems=[]
let channels=[]
let totalCount=0
let filteredCount=0
while(true){
const {done,value}=await reader.read()
if(done)break
buffer+=decoder.decode(value,{stream:true})
const lines=buffer.split('\n')
buffer=lines.pop()||''
for(const line of lines){
if(!line.trim())continue
try{
const msg=JSON.parse(line)
if(msg.type==='stats'){
totalCount=msg.total||0
filteredCount=msg.filtered||0
document.getElementById('loadingText').textContent='🔍 搜索到 '+totalCount+' 条,正在验证...'
}else if(msg.type==='result'){
if(msg.valid&&msg.id){
allItems.push(msg.id)
}
}else if(msg.type==='complete'){
const results=msg.results||[]
channels=msg.channels||[]
clearInterval(progressTimer)
bar.style.width='100%'
setTimeout(()=>renderResults(results,channels,totalCount,filteredCount,Date.now()-startTime),300)
return
}
}catch(e){}
}
}
}catch(e){
clearInterval(progressTimer)
document.getElementById('loading').style.display='none'
document.getElementById('searchBtn').disabled=false
document.getElementById('results').innerHTML='<div class="empty">搜索失败:'+e.message+'</div>'
}
}
// ===== Render =====
function renderResults(results,channels,totalCount,filteredCount,time){
document.getElementById('loading').style.display='none'
document.getElementById('searchBtn').disabled=false
allResults=results
allChannels=channels||[]
// Info bar
if(totalCount>0){
document.getElementById('infoBar').style.display='flex'
document.getElementById('infoCount').textContent='已为您挑选到最符合 '+totalCount+' 条结果'
document.getElementById('infoTime').textContent='⏱ '+time+'ms'
if(filteredCount>0)document.getElementById('infoFiltered').textContent='❌ 失效 '+filteredCount
else document.getElementById('infoFiltered').textContent=''
}else{
document.getElementById('infoBar').style.display='none'
}
// Build tabs
const tabsEl=document.getElementById('tabs')
tabsEl.innerHTML=''
const typeCounts={}
for(const r of results){const ct=r.cloud_type||'others';typeCounts[ct]=(typeCounts[ct]||0)+1}
const sorted=Object.keys(typeCounts).sort((a,b)=>(CLOUD_ORDER[a]||99)-(CLOUD_ORDER[b]||99))
// "全部" tab
const allTab=document.createElement('div')
allTab.className='tab active'
allTab.textContent='📋 全部 ('+results.length+')'
allTab.onclick=()=>{setActiveTab('');renderCardList(results)}
tabsEl.appendChild(allTab)
for(const ct of sorted){
const tab=document.createElement('div')
tab.className='tab'
tab.textContent=(CLOUD_ICONS[ct]||'📁')+' '+(CLOUD_LABELS[ct]||ct)+' ('+typeCounts[ct]+')'
tab.onclick=()=>{setActiveTab(ct);renderCardList(results.filter(r=>(r.cloud_type||'others')===ct))}
tabsEl.appendChild(tab)
}
tabsEl.style.display=results.length>0?'flex':'none'
activeTab=''
// Render cards
renderCardList(results)
}
function setActiveTab(ct){
activeTab=ct
document.querySelectorAll('.tab').forEach((t,i)=>{
const isAll=i===0&&!ct
const active=i>0&&ct&&t.textContent.includes(CLOUD_LABELS[ct])
t.className='tab'+(active||isAll?' active':'')
})
}
function renderCardList(items){
const el=document.getElementById('results')
if(items.length===0){
el.innerHTML='<div class="empty">暂无结果</div>'
return
}
el.innerHTML=items.map((item,idx)=>{
const coverHtml=item.cover
? '<img src="'+escapeHtml(item.cover)+'" alt="" onerror="this.parentElement.innerHTML=\'<div class=placeholder>'+escapeHtml(CLOUD_ICONS[item.cloud_type||'others'])+'</div>\'" loading="lazy" />'
: '<div class="placeholder">'+escapeHtml(CLOUD_ICONS[item.cloud_type||'others'])+'</div>'
const cloudLabel=CLOUD_LABELS[item.cloud_type]||item.cloud_type||''
const cloudColor=CLOUD_COLORS[item.cloud_type]||'#95a5a6'
const tags=extractTags(item.title||'')
const cleanTitle=(item.title||'').replace(/【[^】]+】/g,'').trim()
const relativeTime=formatTime(item.update_time||item.datetime||'')
return '<div class="card" onclick="saveItem('+idx+')">'+
'<div class="card-cover">'+coverHtml+'<span class="tag" style="background:'+cloudColor+'">'+cloudLabel+'</span></div>'+
'<div class="card-body">'+
'<div class="card-title">'+escapeHtml(cleanTitle)+'</div>'+
'<div class="card-meta"><span>🕐 '+relativeTime+'</span>'+(item.file_size?'<span class="size">📦 '+escapeHtml(item.file_size)+'</span>':'')+'</div>'+
(tags.length>0?'<div class="card-tags">'+tags.map(t=>'<span'+(isQualityTag(t)?' class="quality"':'')+'>'+escapeHtml(t)+'</span>').join('')+'</div>':'')+
'<div class="card-actions">'+
'<span class="card-source">'+(item.source?escapeHtml(item.source):'网盘')+'</span>'+
'<button class="card-btn" onclick="event.stopPropagation();saveItem('+idx+')">🔗 获取分享链接</button>'+
'</div>'+
'</div>'+
'</div>'
}).join('')
// Store items for save reference
window.__h5Results=items
}
function escapeHtml(s){if(!s)return '';return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
function extractTags(title){
const tags=[]
// Quality tags
const quality=['4K','1080P','2160P','720P','480P','HDR','HDR10','BluRay','REMUX','HEVC','x264','x265','WEB-DL','WEBRip']
for(const q of quality){if(title.includes(q)&&!tags.includes(q))tags.push(q)}
const kw=['杜比视界','杜比全景声','高码率','内封简繁英字幕','内嵌字幕','中文字幕','中英字幕']
for(const k of kw){if(title.includes(k)&&!tags.includes(k))tags.push(k)}
return tags.slice(0,6)
}
function isQualityTag(t){const q=['4K','1080P','2160P','720P','480P','HDR','HDR10','BluRay','REMUX','HEVC','x264','x265','臻彩','高清','WEB-DL','WEBRip'];return q.includes(t)}
function formatTime(s){
if(!s)return ''
const d=new Date(s)
if(isNaN(d.getTime()))return s.slice(0,10)
const diff=Date.now()-d.getTime()
if(diff<0)return s.slice(0,10)
const mins=Math.floor(diff/60000)
if(mins<60)return mins<=1?'刚刚':mins+' 分钟前'
const hours=Math.floor(mins/60)
if(hours<24)return hours+' 小时前'
const days=Math.floor(hours/24)
if(days<30)return days+' 天前'
return Math.floor(days/30)+' 个月前'
}
// ===== Save / Share =====
function saveItem(idx){
const items=window.__h5Results||[]
currentSaveItem=items[idx]
if(!currentSaveItem)return
document.getElementById('progressSteps').style.display='block'
document.getElementById('shareContent').style.display='none'
document.getElementById('saveError').style.display='none'
document.getElementById('copyBtn2').style.display='none'
const title=(currentSaveItem.title||'').replace(/【[^】]+】/g,'').trim()||'资源'
document.getElementById('shareTitle').textContent=title
// Show modal
document.getElementById('overlay').style.display='block'
document.getElementById('shareModal').style.display='block'
// Reset steps
resetSteps()
advanceStep(1)
// Call save API
doSave()
}
async function doSave(){
try{
const res=await fetch('/api/save',{method:'POST',headers:apiHeaders(),body:JSON.stringify({type:'search',source:currentSaveItem,target_cloud:currentSaveItem.cloud_type||'quark'})})
const data=await res.json()
if(!data.success){
document.getElementById('progressSteps').style.display='none'
document.getElementById('saveError').style.display='flex'
document.getElementById('saveError').textContent=data.message||data.error||'保存失败'
return
}
// Step 2
advanceStep(2)
await sleep(500)
// Step 3
advanceStep(3)
await sleep(300)
if(data.share_url){
advanceStep(4)
await sleep(200)
showShareResult(data)
}else{
advanceStep(4)
document.getElementById('progressSteps').style.display='none'
document.getElementById('saveError').style.display='flex'
document.getElementById('saveError').textContent='生成分享链接失败'
}
}catch(e){
document.getElementById('progressSteps').style.display='none'
document.getElementById('saveError').style.display='flex'
document.getElementById('saveError').textContent=e.message||'保存请求失败'
}
}
function showShareResult(data){
document.getElementById('progressSteps').style.display='none'
document.getElementById('shareContent').style.display='block'
const link=data.share_url
document.getElementById('shareLinkInput').value=link
const diskLabel=CLOUD_LABELS[currentSaveItem.cloud_type]||'夸克网盘'
document.getElementById('qrLabel').textContent=diskLabel+' APP扫码转存'
// Generate QR
const qrContainer=document.getElementById('qrContainer')
qrContainer.innerHTML=''
new QRCode(qrContainer,{text:link,width:140,height:140})
// Password
const pwd=data.share_pwd||data.sharePwd||''
if(pwd){
document.getElementById('sharePwdRow').style.display='flex'
document.getElementById('sharePwdTag').textContent=pwd
}else{
document.getElementById('sharePwdRow').style.display='none'
}
document.getElementById('copyBtn2').style.display='inline-block'
}
function resetSteps(){
for(let i=1;i<=3;i++){
const el=document.getElementById('step'+i)
el.className='step'
el.querySelector('.step-dot').innerHTML='<span>'+i+'</span>'
el.querySelector('.step-status').textContent='等待中'
el.querySelector('.step-status').className='step-status wait'
}
}
function advanceStep(n){
for(let i=1;i<=3;i++){
const el=document.getElementById('step'+i)
if(i<n){
el.className='step done'
el.querySelector('.step-dot').innerHTML='<span class="step-check">✓</span>'
el.querySelector('.step-status').textContent='已完成'
el.querySelector('.step-status').className='step-status done'
}else if(i===n){
el.className='step active'
el.querySelector('.step-dot').innerHTML='<span>'+i+'</span>'
const titles=['正在转存到','正在重命名文件(防和谐)','正在生成分享链接']
el.querySelector('.step-title').textContent=titles[i-1]+'...'
el.querySelector('.step-status').textContent='进行中'
el.querySelector('.step-status').className='step-status doing'
}
}
}
function sleep(ms){return new Promise(r=>setTimeout(r,ms))}
function copyShareLink(){
const input=document.getElementById('shareLinkInput')
if(!input.value)return
if(navigator.clipboard&&navigator.clipboard.writeText){
navigator.clipboard.writeText(input.value).then(()=>showToast('链接已复制')).catch(()=>fallbackCopy(input.value))
}else{
fallbackCopy(input.value)
}
}
function fallbackCopy(text){
const ta=document.createElement('textarea')
ta.value=text;ta.style.position='fixed';ta.style.left='-9999px';document.body.appendChild(ta)
ta.select()
try{document.execCommand('copy');showToast('链接已复制')}catch{showToast('复制失败',true)}
document.body.removeChild(ta)
}
function openDisclaimer(){
window.open('/disclaimer/','_blank')
}
function closeModal(){
document.getElementById('overlay').style.display='none'
document.getElementById('shareModal').style.display='none'
document.getElementById('loginModal').style.display='none'
}
// ===== Init =====
checkLogin()
// Add Enter key handler for home search
document.getElementById('homeSearchInput').addEventListener('keydown',function(e){if(e.key==='Enter')homeSearch()})
// Also add for search view input
document.getElementById('searchInput').addEventListener('keydown',function(e){if(e.key==='Enter')doSearch()})
// Fetch home page data
fetch('/api/rankings/categorized').then(r=>r.json()).then(data=>{
renderHomePage(data)
}).catch(()=>{
document.getElementById('homeQuote').textContent='「 学而时习之,不亦说乎。 」'
document.getElementById('homeQuoteAuthor').textContent='---孔子'
})
// Check URL for query
const params=new URLSearchParams(window.location.search)
const q=params.get('q')
if(q){
document.getElementById('homePage').style.display='none'
document.getElementById('searchView').style.display='block'
document.getElementById('searchInput').value=q
doSearch()
}
</script>
</body>
<!-- Footer -->
<div id="siteFooter" class="site-footer" style="display:none">
<div id="footerContent" class="footer-inner"></div>
<div class="footer-actions" id="footerActions">
<button class="footer-btn" onclick="openDisclaimer()">📜 免责声明</button>
</div>
</div>
</html>

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CloudSearch - 网盘资源搜索</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<script>
(function() {
// 替换标题为网站名称
fetch('/api/site-config').then(r=>r.json()).then(cfg=>{
if(cfg.site_name) document.title = cfg.site_name + ' - 网盘资源搜索';
}).catch(function(){});
// 跳过:参数 ?desktop=1 强制使用桌面版
if (window.location.search.includes('desktop=1')) return;
var ua = navigator.userAgent;
var isMobile = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i.test(ua);
var isTablet = /iPad|Android(?!.*Mobile)/i.test(ua);
if (isMobile || isTablet) {
window.location.replace(window.location.origin + '/h5');
}
})();
</script>
<script type="module" crossorigin src="/assets/index-C5b4pIQL.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D-B10deg.css">
</head>
<body>
<div id="app"></div>
</body>
</html>