Files
MiPanel/index.html
2025-08-30 17:08:49 +08:00

247 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>