mirror of
https://github.com/Buriburizaem0n/MiPanel.git
synced 2025-12-15 14:11:07 +00:00
<edit>changed structure: deleted edited func from website, added json file reader. added domains.
This commit is contained in:
134
domains.json
Normal file
134
domains.json
Normal file
@@ -0,0 +1,134 @@
|
||||
[
|
||||
{
|
||||
"domainFull": "qsj.net",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2024-03-27",
|
||||
"expireDate": "2027-03-27",
|
||||
"registerPrice": 80,
|
||||
"renewPrice": 80,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "全世界Internet"
|
||||
},
|
||||
{
|
||||
"domainFull": "mqwq.com",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2024-11-11",
|
||||
"expireDate": "2027-11-11",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 72,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "qwq"
|
||||
},
|
||||
{
|
||||
"domainFull": "loohui.com",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2025-07-01",
|
||||
"expireDate": "2035-07-01",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 72,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "Blog domain"
|
||||
},
|
||||
{
|
||||
"domainFull": "learnchive.com",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2025-07-21",
|
||||
"expireDate": "2035-07-21",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 72,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "项目域名"
|
||||
},
|
||||
{
|
||||
"domainFull": "662612.xyz",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2025-07-01",
|
||||
"expireDate": "2035-07-01",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 4,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"domainFull": "310502.xyz",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2025-07-01",
|
||||
"expireDate": "2035-07-01",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 4,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"domainFull": "345674.xyz",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2025-07-01",
|
||||
"expireDate": "2035-07-01",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 4,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "34567"
|
||||
},
|
||||
{
|
||||
"domainFull": "4555556.xyz",
|
||||
"registrar": "spaceship",
|
||||
"registerDate": "2025-07-01",
|
||||
"expireDate": "2035-07-01",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 4,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "456, 5个5"
|
||||
},
|
||||
{
|
||||
"domainFull": "kk.al",
|
||||
"registrar": "quyu",
|
||||
"registerDate": "2019-12-16",
|
||||
"expireDate": "2026-01-12",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 99,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "short domain"
|
||||
},
|
||||
{
|
||||
"domainFull": "ez.gs",
|
||||
"registrar": "西部数码国际",
|
||||
"registerDate": "2016-05-10",
|
||||
"expireDate": "2026-05-10",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 68,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "short domain"
|
||||
},
|
||||
{
|
||||
"domainFull": "studyhub.cn",
|
||||
"registrar": "aliyun",
|
||||
"registerDate": "2025-08-09",
|
||||
"expireDate": "2035-08-09",
|
||||
"registerPrice": 39,
|
||||
"renewPrice": 39,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "项目域名"
|
||||
},
|
||||
{
|
||||
"domainFull": "eic.cc",
|
||||
"registrar": "aliyun",
|
||||
"registerDate": "2014-08-01",
|
||||
"expireDate": "2026-08-01",
|
||||
"registerPrice": 72,
|
||||
"renewPrice": 75,
|
||||
"targetPrice": null,
|
||||
"actualPrice": 0,
|
||||
"notes": "electronic Id"
|
||||
}
|
||||
]
|
||||
282
index.html
282
index.html
@@ -37,11 +37,6 @@
|
||||
.s-active{background:#ecfdf5;color:#065f46}
|
||||
.s-expiring{background:#fffbeb;color:#92400e}
|
||||
.s-expired{background:#fff1f2;color:#7f1d1d}
|
||||
.modal-content{border-radius:12px;border:1px solid var(--border)}
|
||||
.form-label{font-weight:400;font-size:13px}
|
||||
.input-inline{display:flex;gap:8px}
|
||||
.input-inline .form-control{flex:1}
|
||||
.small-muted{font-size:11px;color:var(--muted)}
|
||||
footer{margin-top:2rem;text-align:center;font-size:12px;color:var(--muted)}
|
||||
footer a{color:var(--muted);text-decoration:none}
|
||||
nav[aria-label="域名分页"],
|
||||
@@ -53,10 +48,7 @@
|
||||
<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 class="ms-auto d-flex align-items-center gap-2">
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addDomainModal"><i class="bi bi-plus-lg"></i> 添加域名</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container-main">
|
||||
@@ -91,19 +83,13 @@
|
||||
<option value="100">100 条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="btn btn-outline-secondary" id="exportBtn"><i class="bi bi-download"></i> 导出CSV</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="btn btn-outline-secondary" id="importBtn"><i class="bi bi-upload"></i> 导入</button>
|
||||
</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><th>操作</th></tr></thead>
|
||||
<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>
|
||||
@@ -117,107 +103,75 @@
|
||||
|
||||
<footer>
|
||||
Copyright © 2025 <a href="https://qsj.net" target="_blank" rel="noopener">qsj.net</a> All Rights Reserved.
|
||||
<p style="margin-top: 12px;">
|
||||
</footer>
|
||||
|
||||
<div class="modal fade" id="addDomainModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header"><h5 class="modal-title" id="modalTitle">添加新域名</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
|
||||
<div class="modal-body">
|
||||
<form id="domainForm"><input type="hidden" name="editIndex" value="-1">
|
||||
<div class="row gy-3">
|
||||
<div class="col-md-6"><label class="form-label">域名(含后缀)</label><input name="domainFull" class="form-control" placeholder="example.com" required></div>
|
||||
<div class="col-md-6"><label class="form-label">注册商</label><input name="registrar" class="form-control" list="registrarsList" placeholder="可直接填写"></div>
|
||||
<datalist id="registrarsList">
|
||||
<option>阿里云</option><option>腾讯云</option><option>百度云</option><option>华为云</option>
|
||||
<option>西部数码</option><option>西部数码国际</option><option>新网</option><option>万网</option><option>Namecheap</option>
|
||||
<option>GoDaddy</option><option>Name.com</option><option>Google Domains</option>
|
||||
<option>Cloudflare</option><option>Dynadot</option><option>Porkbun</option><option>Spaceship</option>
|
||||
</datalist>
|
||||
<div class="col-md-6"><label class="form-label">注册时间</label><input type="date" name="registerDate" class="form-control"></div>
|
||||
<div class="col-md-6"><label class="form-label">到期时间</label><input type="date" name="expireDate" class="form-control" required></div>
|
||||
<div class="col-md-6"><label class="form-label">注册价格 (¥)</label><input type="number" step="0.01" min="0" name="registerPrice" class="form-control"></div>
|
||||
<div class="col-md-6"><label class="form-label">续费价格 (¥)</label><input type="number" step="0.01" min="0" name="renewPrice" class="form-control"></div>
|
||||
<div class="col-md-6"><label class="form-label">意向售价 (¥)</label><input type="number" step="0.01" min="0" name="targetPrice" class="form-control"></div>
|
||||
<div class="col-md-6"><label class="form-label">实际售价 (¥)</label><input type="number" step="0.01" min="0" name="actualPrice" class="form-control"></div>
|
||||
<div class="col-12"><label class="form-label">备注</label><textarea name="notes" rows="3" class="form-control"></textarea></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button class="btn btn-primary" onclick="saveDomain()">保存</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
/* ========== 本地存储 key ========== */
|
||||
const STORAGE_KEY = 'domainPanel_domains';
|
||||
|
||||
/* ========== 分页相关 ========== */
|
||||
// 全局变量
|
||||
let allDomains = []; // 存储从JSON加载的所有域名
|
||||
let currentPage = 1;
|
||||
let pageSize = 10;
|
||||
|
||||
/* ========== 数据读写 ========== */
|
||||
function loadDomains() {
|
||||
/* ========== 页面加载时,从 JSON 文件获取数据 ========== */
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
try {
|
||||
const data = localStorage.getItem(STORAGE_KEY);
|
||||
return data ? JSON.parse(data) : [];
|
||||
} catch {
|
||||
return [];
|
||||
// 核心改动:从 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 saveDomains() {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(domains));
|
||||
}
|
||||
});
|
||||
|
||||
/* ========== 全局变量:从本地加载 ========== */
|
||||
let domains = loadDomains();
|
||||
|
||||
/* ========== 首次初始化:如果本地为空,写入默认数据 ========== */
|
||||
if (!domains.length) {
|
||||
domains = [
|
||||
];
|
||||
saveDomains();
|
||||
}
|
||||
|
||||
/* ========== 工具函数 ========== */
|
||||
/* ========== 工具函数:计算剩余天数 ========== */
|
||||
function daysRemaining(d) {
|
||||
const today = new Date(), exp = new Date(d + 'T23:59:59');
|
||||
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(list = domains) {
|
||||
/* ========== 渲染表格(含过滤和分页) ========== */
|
||||
function renderTable() {
|
||||
const q = document.getElementById('searchInput').value.trim().toLowerCase();
|
||||
const status = document.getElementById('filterStatus').value;
|
||||
let filtered = list.filter(d => {
|
||||
const full = (d.domainFull || '').toLowerCase();
|
||||
const reg = (d.registrar || '').toLowerCase();
|
||||
if (q && !(full.includes(q) || reg.includes(q))) return false;
|
||||
if (status === 'all') return true;
|
||||
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 (status === 'active') return days > 30;
|
||||
if (status === 'expiring') return days > 0 && days <= 30;
|
||||
if (status === 'expired') return days <= 0;
|
||||
if (statusFilter === 'active') return days > 30;
|
||||
if (statusFilter === 'expiring') return days > 0 && days <= 30;
|
||||
if (statusFilter === 'expired') return days <= 0;
|
||||
return true;
|
||||
});
|
||||
|
||||
const total = filtered.length;
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
if (currentPage > totalPages && totalPages !== 0) currentPage = totalPages;
|
||||
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, idx) => {
|
||||
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'; }
|
||||
const globalIndex = start + idx;
|
||||
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>
|
||||
@@ -229,109 +183,52 @@ function renderTable(list = domains) {
|
||||
<td>${d.expireDate}</td>
|
||||
<td>${days}</td>
|
||||
<td><span class='domain-status ${cls}'>${statusText}</span></td>
|
||||
<td>
|
||||
<div class='d-flex gap-1'>
|
||||
<button class='btn btn-sm btn-outline-primary' onclick='openEdit(${globalIndex})'><i class='bi bi-pencil'></i></button>
|
||||
<button class='btn btn-sm btn-outline-danger' onclick='removeDomain(${globalIndex})'><i class='bi bi-trash'></i></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
|
||||
renderPagination(totalPages);
|
||||
renderPagination(totalPages, filtered.length);
|
||||
}
|
||||
|
||||
/* ========== 分页按钮 ========== */
|
||||
/* ========== 渲染分页按钮 ========== */
|
||||
function renderPagination(totalPages) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = '';
|
||||
if (totalPages <= 1) return;
|
||||
|
||||
pagination.insertAdjacentHTML('beforeend', `
|
||||
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
||||
<a class="page-link" href="#" onclick="changePage(${currentPage - 1})">上一页</a>
|
||||
</li>
|
||||
`);
|
||||
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.insertAdjacentHTML('beforeend', `
|
||||
<li class="page-item ${i === currentPage ? 'active' : ''}">
|
||||
<a class="page-link" href="#" onclick="changePage(${i})">${i}</a>
|
||||
</li>
|
||||
`);
|
||||
pagination.innerHTML += createPageItem(i, i, false, i === currentPage);
|
||||
}
|
||||
pagination.insertAdjacentHTML('beforeend', `
|
||||
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
||||
<a class="page-link" href="#" onclick="changePage(${currentPage + 1})">下一页</a>
|
||||
</li>
|
||||
`);
|
||||
pagination.innerHTML += createPageItem(currentPage + 1, '下一页', currentPage === totalPages);
|
||||
}
|
||||
|
||||
function changePage(page) {
|
||||
if (page < 1) return;
|
||||
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 = domains.length;
|
||||
document.getElementById('statExpiring').textContent = domains.filter(d => {
|
||||
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 = '¥' + domains.reduce((s, d) => s + (Number(d.registerPrice) || 0), 0).toLocaleString();
|
||||
document.getElementById('statIntent').textContent = '¥' + domains.reduce((s, d) => s + (Number(d.targetPrice) || 0), 0).toLocaleString();
|
||||
document.getElementById('statActual').textContent = '¥' + domains.reduce((s, d) => s + (Number(d.actualPrice) || 0), 0).toLocaleString();
|
||||
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();
|
||||
}
|
||||
|
||||
/* ========== 保存/编辑/删除 ========== */
|
||||
function saveDomain() {
|
||||
const fd = new FormData(document.getElementById('domainForm'));
|
||||
const idx = Number(fd.get('editIndex'));
|
||||
const item = {
|
||||
domainFull: (fd.get('domainFull') || '').trim(),
|
||||
registrar: fd.get('registrar') || '',
|
||||
registerDate: fd.get('registerDate') || '',
|
||||
expireDate: fd.get('expireDate') || '',
|
||||
registerPrice: Number(fd.get('registerPrice')) || 0,
|
||||
renewPrice: Number(fd.get('renewPrice')) || 0,
|
||||
targetPrice: fd.get('targetPrice') ? Number(fd.get('targetPrice')) : 0,
|
||||
actualPrice: fd.get('actualPrice') ? Number(fd.get('actualPrice')) : 0,
|
||||
notes: fd.get('notes') || ''
|
||||
};
|
||||
if (!item.domainFull) { alert('请填写完整域名(含后缀)'); return; }
|
||||
if (idx >= 0) { domains[idx] = item; } else { domains.push(item); }
|
||||
bootstrap.Modal.getInstance(document.getElementById('addDomainModal')).hide();
|
||||
saveDomains();
|
||||
currentPage = 1;
|
||||
renderTable(); updateStats();
|
||||
document.getElementById('domainForm').reset();
|
||||
}
|
||||
function openEdit(i) {
|
||||
const d = domains[i];
|
||||
const f = document.getElementById('domainForm');
|
||||
f.reset(); f.editIndex.value = i;
|
||||
f.domainFull.value = d.domainFull;
|
||||
f.registrar.value = d.registrar;
|
||||
f.registerDate.value = d.registerDate;
|
||||
f.expireDate.value = d.expireDate;
|
||||
f.registerPrice.value = d.registerPrice;
|
||||
f.renewPrice.value = d.renewPrice;
|
||||
f.targetPrice.value = d.targetPrice;
|
||||
f.actualPrice.value = d.actualPrice;
|
||||
f.notes.value = d.notes;
|
||||
new bootstrap.Modal(document.getElementById('addDomainModal')).show();
|
||||
}
|
||||
function removeDomain(i) {
|
||||
if (confirm('确定删除该域名?')) {
|
||||
domains.splice(i, 1);
|
||||
saveDomains();
|
||||
if (domains.length <= (currentPage - 1) * pageSize && currentPage > 1) currentPage--;
|
||||
renderTable(); updateStats();
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 搜索/过滤/分页大小 ========== */
|
||||
/* ========== 搜索/过滤/分页大小的事件监听 ========== */
|
||||
document.getElementById('searchInput').addEventListener('input', () => { currentPage = 1; renderTable(); });
|
||||
document.getElementById('filterStatus').addEventListener('change', () => { currentPage = 1; renderTable(); });
|
||||
document.getElementById('pageSizeSelect').addEventListener('change', e => {
|
||||
@@ -340,56 +237,7 @@ document.getElementById('pageSizeSelect').addEventListener('change', e => {
|
||||
renderTable();
|
||||
});
|
||||
|
||||
/* ========== 导出 CSV ========== */
|
||||
document.getElementById('exportBtn').addEventListener('click', () => {
|
||||
if (!domains.length) { alert('没有数据可导出'); return; }
|
||||
const keys = ['domainFull', 'registrar', 'registerPrice', 'renewPrice', 'targetPrice', 'actualPrice', 'registerDate', 'expireDate', 'notes'];
|
||||
const lines = [keys.join(',')];
|
||||
domains.forEach(d => {
|
||||
lines.push(keys.map(k => `"${(d[k] || '').toString().replace(/"/g, '""')}"`).join(','));
|
||||
});
|
||||
const blob = new Blob([lines.join('\n')], { type: 'text/csv' });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'domains_export.csv';
|
||||
a.click();
|
||||
});
|
||||
|
||||
/* ========== 导入 CSV ========== */
|
||||
document.getElementById('importBtn').addEventListener('click', () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file'; input.accept = '.csv';
|
||||
input.onchange = e => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = evt => {
|
||||
const csv = evt.target.result;
|
||||
const lines = csv.split('\n');
|
||||
const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, ''));
|
||||
const importedDomains = [];
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
if (!lines[i].trim()) continue;
|
||||
const values = lines[i].split(',').map(v => v.trim().replace(/"/g, ''));
|
||||
const domain = {};
|
||||
headers.forEach((h, index) => { domain[h] = values[index] || ''; });
|
||||
importedDomains.push(domain);
|
||||
}
|
||||
domains = [...domains, ...importedDomains];
|
||||
saveDomains();
|
||||
currentPage = 1;
|
||||
renderTable(); updateStats();
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
|
||||
/* ========== 首次加载 ========== */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelector('input[name=registerDate]').value = new Date().toISOString().split('T')[0];
|
||||
renderTable(); updateStats();
|
||||
});
|
||||
// 增、删、改相关的所有函数都已移除
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user