mirror of
https://github.com/Buriburizaem0n/MiPanel.git
synced 2025-12-13 04:34:21 +00:00
247 lines
12 KiB
HTML
247 lines
12 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>全世界米米表</title>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
|
||
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
|
||
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
|
||
<link rel="manifest" href="/static/site.webmanifest">
|
||
<style>
|
||
:root{--primary:#3b82f6;--muted:#64748b;--bg:#f6f9fc;--card:#fff;--border:#e6eef8;--text:#102a43}
|
||
*{box-sizing:border-box}
|
||
body{font-family:'Inter',system-ui,Arial;margin:0;background:var(--bg);color:var(--text);font-size:14px;font-weight:400}
|
||
.navbar{backdrop-filter:blur(6px);background:linear-gradient(180deg,rgba(255,255,255,0.95),rgba(255,255,255,0.9));border-bottom:1px solid var(--border)}
|
||
.brand{font-weight:400;color:var(--primary)}
|
||
.container-main{max-width:1200px;margin:92px auto 48px;padding:0 16px}
|
||
.card{border:1px solid var(--border);border-radius:12px;background:var(--card)}
|
||
.card-compact{padding:16px}
|
||
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:20px}
|
||
.stat{padding:18px;border-radius:10px;display:flex;align-items:center;gap:12px}
|
||
.stat .icon{width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;background:rgba(59,130,246,0.12);color:var(--primary);font-size:20px}
|
||
.stat h3{margin:0;font-size:18px;font-weight:400;text-align: center}
|
||
.stat p{margin:0;color:var(--muted);font-size:12px}
|
||
.controls-bar{display:flex;gap:12px;align-items:center;flex-wrap:nowrap;margin-bottom:18px}
|
||
.controls-bar .search-wrap{flex:1 1 420px;min-width:220px}
|
||
.controls-bar .control{flex:0 0 auto}
|
||
.controls-bar .form-control,.controls-bar .form-select,.controls-bar .btn{height:40px;min-width:120px;font-size:13px;border-radius:8px}
|
||
.controls-bar .input-group-text{display:flex;align-items:center;height:40px;border-radius:8px}
|
||
@media (max-width:768px){
|
||
.controls-bar{flex-direction:column;align-items:stretch}
|
||
.controls-bar .search-wrap,.controls-bar .control{width:100%!important}
|
||
.controls-bar .form-control,.controls-bar .form-select,.controls-bar .btn{width:100%}
|
||
}
|
||
.table{font-size:13px}
|
||
.table thead th{background:#fbfdff;border-bottom:1px solid var(--border);font-weight:400;color:var(--muted)}
|
||
.domain-status{display:inline-block;padding:4px 8px;border-radius:999px;font-weight:400;font-size:11px}
|
||
.s-active{background:#ecfdf5;color:#065f46}
|
||
.s-expiring{background:#fffbeb;color:#92400e}
|
||
.s-expired{background:#fff1f2;color:#7f1d1d}
|
||
footer{margin-top:2rem;text-align:center;font-size:12px;color:var(--muted)}
|
||
footer a{color:var(--muted);text-decoration:none}
|
||
nav[aria-label="域名分页"],
|
||
nav[aria-label="域名分页"] .page-link {font-size: 0.8rem;}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<nav class="navbar navbar-expand-lg">
|
||
<div class="container-fluid px-3">
|
||
<a class="navbar-brand brand" href="#"><i class="bi bi-globe2"></i> 全世界米米表</a>
|
||
</div>
|
||
</nav>
|
||
|
||
<main class="container-main">
|
||
<div class="stats-grid">
|
||
<div class="card stat card-compact"><div class="icon"><i class="bi bi-hdd-network"></i></div><div><h3 id="statTotal">0</h3><p>域名总数</p></div></div>
|
||
<div class="card stat card-compact"><div class="icon"><i class="bi bi-exclamation-circle"></i></div><div><h3 id="statExpiring">0</h3><p>到期域名(30天)</p></div></div>
|
||
<div class="card stat card-compact"><div class="icon"><i class="bi bi-cash-stack"></i></div><div><h3 id="statCost">¥0</h3><p>总投入成本</p></div></div>
|
||
<div class="card stat card-compact"><div class="icon"><i class="bi bi-tag"></i></div><div><h3 id="statIntent">¥0</h3><p>总意向售价</p></div></div>
|
||
<div class="card stat card-compact"><div class="icon"><i class="bi bi-graph-up"></i></div><div><h3 id="statActual">¥0</h3><p>实际总收益</p></div></div>
|
||
</div>
|
||
|
||
<div class="controls-bar">
|
||
<div class="search-wrap">
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||
<input id="searchInput" type="text" class="form-control" placeholder="搜索域名或注册商...">
|
||
</div>
|
||
</div>
|
||
<div class="control">
|
||
<select id="filterStatus" class="form-select">
|
||
<option value="all">全部状态</option>
|
||
<option value="active">正常</option>
|
||
<option value="expiring">即将到期</option>
|
||
<option value="expired">已过期</option>
|
||
</select>
|
||
</div>
|
||
<div class="control">
|
||
<select id="pageSizeSelect" class="form-select" title="每页显示条数">
|
||
<option value="10">10 条/页</option>
|
||
<option value="20">20 条/页</option>
|
||
<option value="50">50 条/页</option>
|
||
<option value="100">100 条/页</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-body p-0">
|
||
<div class="table-responsive">
|
||
<table class="table align-middle mb-0">
|
||
<thead><tr><th>域名</th><th>注册商</th><th>注册价</th><th>续费价</th><th>意向售价</th><th>实际售价</th><th>到期时间</th><th>剩余天数</th><th>状态</th></tr></thead>
|
||
<tbody id="domainTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<nav aria-label="域名分页">
|
||
<ul class="pagination justify-content-center mt-3" id="pagination"></ul>
|
||
</nav>
|
||
</main>
|
||
|
||
<footer>
|
||
Copyright © 2025 <a href="https://qsj.net" target="_blank" rel="noopener">qsj.net</a> All Rights Reserved.
|
||
</footer>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script>
|
||
// 全局变量
|
||
let allDomains = []; // 存储从JSON加载的所有域名
|
||
let currentPage = 1;
|
||
let pageSize = 10;
|
||
|
||
/* ========== 页面加载时,从 JSON 文件获取数据 ========== */
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
try {
|
||
// 核心改动:从 domains.json 文件加载数据
|
||
const response = await fetch('domains.json?v=' + Date.now()); // 添加时间戳防止缓存
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
allDomains = await response.json();
|
||
|
||
// 数据加载成功后,渲染表格和统计信息
|
||
renderTable();
|
||
updateStats();
|
||
} catch (error) {
|
||
console.error("无法加载或解析 domains.json:", error);
|
||
const tbody = document.getElementById('domainTableBody');
|
||
tbody.innerHTML = `<tr><td colspan="9" class="text-center text-danger">数据文件 (domains.json) 加载失败!</td></tr>`;
|
||
}
|
||
});
|
||
|
||
/* ========== 工具函数:计算剩余天数 ========== */
|
||
function daysRemaining(d) {
|
||
if (!d) return 0;
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0); // 标准化到当天的开始
|
||
const exp = new Date(d);
|
||
return Math.ceil((exp - today) / (1000 * 60 * 60 * 24));
|
||
}
|
||
|
||
/* ========== 渲染表格(含过滤和分页) ========== */
|
||
function renderTable() {
|
||
const q = document.getElementById('searchInput').value.trim().toLowerCase();
|
||
const statusFilter = document.getElementById('filterStatus').value;
|
||
|
||
const filtered = allDomains.filter(d => {
|
||
const searchMatch = q ? (d.domainFull || '').toLowerCase().includes(q) || (d.registrar || '').toLowerCase().includes(q) : true;
|
||
if (!searchMatch) return false;
|
||
|
||
if (statusFilter === 'all') return true;
|
||
const days = daysRemaining(d.expireDate);
|
||
if (statusFilter === 'active') return days > 30;
|
||
if (statusFilter === 'expiring') return days > 0 && days <= 30;
|
||
if (statusFilter === 'expired') return days <= 0;
|
||
return true;
|
||
});
|
||
|
||
const totalPages = Math.ceil(filtered.length / pageSize);
|
||
if (currentPage > totalPages && totalPages > 0) currentPage = totalPages;
|
||
const start = (currentPage - 1) * pageSize;
|
||
const pageData = filtered.slice(start, start + pageSize);
|
||
|
||
const tbody = document.getElementById('domainTableBody');
|
||
tbody.innerHTML = '';
|
||
pageData.forEach(d => {
|
||
const days = daysRemaining(d.expireDate);
|
||
let statusText = '正常', cls = 's-active';
|
||
if (days <= 0) { statusText = '已过期'; cls = 's-expired'; }
|
||
else if (days <= 30) { statusText = '即将到期'; cls = 's-expiring'; }
|
||
|
||
// 表格行已移除最后一列的操作按钮
|
||
tbody.insertAdjacentHTML('beforeend', `
|
||
<tr>
|
||
<td><strong>${d.domainFull}</strong><div class='small-muted'>${d.notes || ''}</div></td>
|
||
<td>${d.registrar || '-'}</td>
|
||
<td>¥${(d.registerPrice || 0).toLocaleString()}</td>
|
||
<td>¥${(d.renewPrice || 0).toLocaleString()}</td>
|
||
<td>${d.targetPrice ? '¥' + Number(d.targetPrice).toLocaleString() : '-'}</td>
|
||
<td>${d.actualPrice ? '¥' + Number(d.actualPrice).toLocaleString() : '-'}</td>
|
||
<td>${d.expireDate}</td>
|
||
<td>${days}</td>
|
||
<td><span class='domain-status ${cls}'>${statusText}</span></td>
|
||
</tr>
|
||
`);
|
||
});
|
||
|
||
renderPagination(totalPages, filtered.length);
|
||
}
|
||
|
||
/* ========== 渲染分页按钮 ========== */
|
||
function renderPagination(totalPages) {
|
||
const pagination = document.getElementById('pagination');
|
||
pagination.innerHTML = '';
|
||
if (totalPages <= 1) return;
|
||
|
||
const createPageItem = (page, text, disabled = false, active = false) => {
|
||
return `<li class="page-item ${disabled ? 'disabled' : ''} ${active ? 'active' : ''}">
|
||
<a class="page-link" href="#" onclick="event.preventDefault(); changePage(${page});">${text}</a>
|
||
</li>`;
|
||
};
|
||
|
||
pagination.innerHTML += createPageItem(currentPage - 1, '上一页', currentPage === 1);
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
pagination.innerHTML += createPageItem(i, i, false, i === currentPage);
|
||
}
|
||
pagination.innerHTML += createPageItem(currentPage + 1, '下一页', currentPage === totalPages);
|
||
}
|
||
|
||
function changePage(page) {
|
||
const totalPages = Math.ceil(allDomains.length / pageSize); // Re-calculate based on filtered data if needed
|
||
if (page < 1 || page > totalPages && totalPages > 0) return;
|
||
currentPage = page;
|
||
renderTable();
|
||
}
|
||
|
||
/* ========== 统计更新 ========== */
|
||
function updateStats() {
|
||
document.getElementById('statTotal').textContent = allDomains.length;
|
||
document.getElementById('statExpiring').textContent = allDomains.filter(d => {
|
||
const days = daysRemaining(d.expireDate);
|
||
return days > 0 && days <= 30;
|
||
}).length;
|
||
document.getElementById('statCost').textContent = '¥' + allDomains.reduce((s, d) => s + (Number(d.registerPrice) || 0), 0).toLocaleString();
|
||
document.getElementById('statIntent').textContent = '¥' + allDomains.reduce((s, d) => s + (Number(d.targetPrice) || 0), 0).toLocaleString();
|
||
document.getElementById('statActual').textContent = '¥' + allDomains.reduce((s, d) => s + (Number(d.actualPrice) || 0), 0).toLocaleString();
|
||
}
|
||
|
||
/* ========== 搜索/过滤/分页大小的事件监听 ========== */
|
||
document.getElementById('searchInput').addEventListener('input', () => { currentPage = 1; renderTable(); });
|
||
document.getElementById('filterStatus').addEventListener('change', () => { currentPage = 1; renderTable(); });
|
||
document.getElementById('pageSizeSelect').addEventListener('change', e => {
|
||
pageSize = Number(e.target.value);
|
||
currentPage = 1;
|
||
renderTable();
|
||
});
|
||
|
||
// 增、删、改相关的所有函数都已移除
|
||
</script>
|
||
</body>
|
||
</html> |