feat: 添加Cookie搜索和分组功能

- 添加搜索框支持按Tag名称实时过滤
- 根据Tag冒号前缀自动分组(格式: xx1:xxxx2)
- 支持分组折叠/展开功能
- 分组显示数量统计

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
ykandClaude Sonnet 4.5 committed 2026-09-08 17:11:35 +08:00
1 parent 8a57945cd2
commit 3bda268cb8
1 file changed
+188 -25
+188 -25
View File
@@ -377,6 +377,80 @@
line-height: 1.6; line-height: 1.6;
} }
.search-box {
margin-bottom: 20px;
position: relative;
}
.search-input {
width: 100%;
padding: 12px 40px 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
.search-input:focus {
outline: none;
border-color: #667eea;
}
.search-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
font-size: 20px;
}
.group-header {
background: #f8f9fa;
padding: 15px 20px;
margin: 15px 0 10px 0;
border-radius: 8px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.3s;
}
.group-header:hover {
background: #e9ecef;
}
.group-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.group-count {
color: #999;
font-size: 14px;
margin-left: 10px;
}
.group-toggle {
font-size: 20px;
color: #667eea;
transition: transform 0.3s;
}
.group-toggle.collapsed {
transform: rotate(-90deg);
}
.group-content {
display: block;
}
.group-content.collapsed {
display: none;
}
.api-docs { .api-docs {
background: white; background: white;
border-radius: 12px; border-radius: 12px;
@@ -443,7 +517,12 @@
<div class="data-list"> <div class="data-list">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">📋 数据列表</h2> <h2 style="margin: 0;">📋 数据列表</h2>
<button class="btn btn-primary" onclick="loadDataList()">🔄 刷新</button> <button class="btn btn-primary" onclick="loadData()">🔄 刷新</button>
</div>
<!-- Search Box -->
<div class="search-box">
<input type="text" class="search-input" id="search-input" placeholder="🔍 搜索 Tag..." oninput="filterData()">
<span class="search-icon">🔍</span>
</div> </div>
<div id="data-container"> <div id="data-container">
<div class="loading">正在加载...</div> <div class="loading">正在加载...</div>
@@ -516,6 +595,8 @@
<script> <script>
let currentViewData = null; let currentViewData = null;
let deleteCallback = null; let deleteCallback = null;
let allDataItems = []; // 存储所有数据项
let collapsedGroups = new Set(); // 存储折叠的分组
// Toast 通知 // Toast 通知
function showToast(message, type = 'success') { function showToast(message, type = 'success') {
@@ -580,23 +661,23 @@
try { try {
const response = await fetch('/api/data'); const response = await fetch('/api/data');
const data = await response.json(); const data = await response.json();
const tags = data.tags || {}; const tags = data.tags || {};
const tagArray = Object.entries(tags).map(([tag, info]) => ({ const tagArray = Object.entries(tags).map(([tag, info]) => ({
tag, tag,
...info ...info
})); }));
// 统计 // 统计
let validCount = 0; let validCount = 0;
let warningCount = 0; let warningCount = 0;
let expiredCount = 0; let expiredCount = 0;
tagArray.forEach(item => { tagArray.forEach(item => {
const expiresAt = new Date(item.expires_at); const expiresAt = new Date(item.expires_at);
const now = new Date(); const now = new Date();
const remaining = (expiresAt - now) / 1000 / 3600 / 24; const remaining = (expiresAt - now) / 1000 / 3600 / 24;
if (remaining < 0) { if (remaining < 0) {
expiredCount++; expiredCount++;
item.status = 'expired'; item.status = 'expired';
@@ -608,32 +689,82 @@
item.status = 'valid'; item.status = 'valid';
} }
}); });
// 更新统计 // 更新统计
document.getElementById('total-count').textContent = tagArray.length; document.getElementById('total-count').textContent = tagArray.length;
document.getElementById('valid-count').textContent = validCount; document.getElementById('valid-count').textContent = validCount;
document.getElementById('warning-count').textContent = warningCount; document.getElementById('warning-count').textContent = warningCount;
document.getElementById('expired-count').textContent = expiredCount; document.getElementById('expired-count').textContent = expiredCount;
// 保存所有数据项
allDataItems = tagArray;
// 渲染列表 // 渲染列表
const container = document.getElementById('data-container'); renderDataList(tagArray);
if (tagArray.length === 0) { } catch (error) {
container.innerHTML = '<div class="empty">📭 暂无数据</div>'; console.error('加载失败:', error);
return; document.getElementById('data-container').innerHTML =
'<div class="empty">❌ 加载失败,请刷新重试</div>';
}
}
// 渲染数据列表(支持分组)
function renderDataList(items) {
const container = document.getElementById('data-container');
if (items.length === 0) {
container.innerHTML = '<div class="empty">📭 暂无数据</div>';
return;
}
// 按分组组织数据
const groups = {};
items.forEach(item => {
const colonIndex = item.tag.indexOf(':');
const groupName = colonIndex > 0 ? item.tag.substring(0, colonIndex) : '未分组';
if (!groups[groupName]) {
groups[groupName] = [];
} }
groups[groupName].push(item);
container.innerHTML = tagArray.map((item) => { });
// 按分组名称排序
const sortedGroupNames = Object.keys(groups).sort((a, b) => {
if (a === '未分组') return 1;
if (b === '未分组') return -1;
return a.localeCompare(b);
});
// 渲染分组
let html = '';
sortedGroupNames.forEach(groupName => {
const groupItems = groups[groupName];
const isCollapsed = collapsedGroups.has(groupName);
html += `
<div class="group-header" onclick="toggleGroup('${groupName.replace(/'/g, "\\'")}')">
<div>
<span class="group-title">${groupName}</span>
<span class="group-count">(${groupItems.length})</span>
</div>
<span class="group-toggle ${isCollapsed ? 'collapsed' : ''}">▼</span>
</div>
<div class="group-content ${isCollapsed ? 'collapsed' : ''}" id="group-${groupName}">
`;
groupItems.forEach(item => {
const statusClass = `status-${item.status}`; const statusClass = `status-${item.status}`;
const statusText = { const statusText = {
'valid': '正常', 'valid': '正常',
'warning': '即将过期', 'warning': '即将过期',
'expired': '已过期' 'expired': '已过期'
}[item.status]; }[item.status];
const escapedTag = item.tag.replace(/'/g, "\\'").replace(/"/g, '\\"'); const escapedTag = item.tag.replace(/'/g, "\\'").replace(/"/g, '\\"');
return ` html += `
<div class="data-item"> <div class="data-item">
<div class="data-info"> <div class="data-info">
<div class="data-tag"> <div class="data-tag">
@@ -641,8 +772,8 @@
<span class="status-badge ${statusClass}">${statusText}</span> <span class="status-badge ${statusClass}">${statusText}</span>
</div> </div>
<div class="data-meta"> <div class="data-meta">
类型: ${item.type} | 类型: ${item.type} |
创建: ${item.created_at} | 创建: ${item.created_at} |
过期: ${item.expires_at} 过期: ${item.expires_at}
</div> </div>
</div> </div>
@@ -654,13 +785,45 @@
</div> </div>
</div> </div>
`; `;
}).join(''); });
} catch (error) { html += '</div>';
console.error('加载失败:', error); });
document.getElementById('data-container').innerHTML =
'<div class="empty">❌ 加载失败,请刷新重试</div>'; container.innerHTML = html;
}
// 切换分组折叠/展开
function toggleGroup(groupName) {
if (collapsedGroups.has(groupName)) {
collapsedGroups.delete(groupName);
} else {
collapsedGroups.add(groupName);
} }
// 重新渲染当前显示的数据
const searchTerm = document.getElementById('search-input').value.trim().toLowerCase();
if (searchTerm) {
filterData();
} else {
renderDataList(allDataItems);
}
}
// 搜索过滤
function filterData() {
const searchTerm = document.getElementById('search-input').value.trim().toLowerCase();
if (!searchTerm) {
renderDataList(allDataItems);
return;
}
const filteredItems = allDataItems.filter(item =>
item.tag.toLowerCase().includes(searchTerm)
);
renderDataList(filteredItems);
} }
// 查看数据 // 查看数据