修复标签页面
This commit is contained in:
@@ -451,12 +451,37 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-search-container {
|
/* 标签头部 */
|
||||||
|
.tag-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 20px;
|
||||||
background-color: var(--card);
|
background-color: var(--card);
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
padding: 1.5rem;
|
border: 1px solid var(--border);
|
||||||
margin-bottom: 1.5rem;
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-search-container {
|
||||||
|
width: 100%;
|
||||||
|
margin: 15px 0 20px 0;
|
||||||
|
position: relative;
|
||||||
|
background: var(--card);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 2px solid var(--primary);
|
||||||
|
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-search-container::before {
|
||||||
|
content: '🔍 标签搜索';
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-search-header {
|
.tag-search-header {
|
||||||
@@ -470,19 +495,51 @@
|
|||||||
|
|
||||||
.tag-search-input {
|
.tag-search-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.75rem 1rem;
|
padding: 12px 40px 12px 15px;
|
||||||
|
border-radius: 8px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
|
||||||
background-color: var(--input);
|
background-color: var(--input);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-search-input:focus {
|
.tag-search-input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--ring);
|
border-color: var(--primary);
|
||||||
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-search-input::placeholder {
|
||||||
|
color: #888;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-search-clear {
|
||||||
|
position: absolute;
|
||||||
|
right: 25px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-5%);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-search-clear:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgba(37, 99, 235, 0.1);
|
||||||
|
transform: translateY(-5%) scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-info {
|
.tag-info {
|
||||||
@@ -573,9 +630,36 @@
|
|||||||
.arch-item {
|
.arch-item {
|
||||||
background-color: var(--muted);
|
background-color: var(--muted);
|
||||||
color: var(--muted-foreground);
|
color: var(--muted-foreground);
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 5px 10px;
|
||||||
border-radius: 0.25rem;
|
border-radius: 5px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 漏洞指示器 */
|
||||||
|
.vulnerability-indicator {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 5px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vulnerability-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vulnerability-critical { background-color: #dc3545; }
|
||||||
|
.vulnerability-high { background-color: #fd7e14; }
|
||||||
|
.vulnerability-medium { background-color: #ffc107; }
|
||||||
|
.vulnerability-low { background-color: #28a745; }
|
||||||
|
.vulnerability-unknown { background-color: #6c757d; }
|
||||||
|
|
||||||
|
/* 组织标签 */
|
||||||
|
.badge-organization {
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 返回按钮 */
|
/* 返回按钮 */
|
||||||
@@ -740,24 +824,7 @@
|
|||||||
|
|
||||||
<!-- 标签列表 -->
|
<!-- 标签列表 -->
|
||||||
<div class="tag-list" id="tagList">
|
<div class="tag-list" id="tagList">
|
||||||
<!-- 标签搜索 -->
|
<!-- 标签页面内容将通过JavaScript动态生成 -->
|
||||||
<div class="tag-search-container">
|
|
||||||
<div class="tag-search-header">
|
|
||||||
🔍 标签搜索
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="tag-search-input"
|
|
||||||
id="tagSearchInput"
|
|
||||||
placeholder="过滤标签..."
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 镜像信息 -->
|
|
||||||
<div class="tag-info" id="tagInfo"></div>
|
|
||||||
|
|
||||||
<!-- 标签项 -->
|
|
||||||
<div id="tagItems"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -804,29 +871,73 @@
|
|||||||
prevPage: document.getElementById('prevPage'),
|
prevPage: document.getElementById('prevPage'),
|
||||||
nextPage: document.getElementById('nextPage'),
|
nextPage: document.getElementById('nextPage'),
|
||||||
tagList: document.getElementById('tagList'),
|
tagList: document.getElementById('tagList'),
|
||||||
tagInfo: document.getElementById('tagInfo'),
|
|
||||||
tagItems: document.getElementById('tagItems'),
|
|
||||||
tagSearchInput: document.getElementById('tagSearchInput'),
|
|
||||||
backToSearch: document.getElementById('backToSearch'),
|
backToSearch: document.getElementById('backToSearch'),
|
||||||
toast: document.getElementById('toast')
|
toast: document.getElementById('toast')
|
||||||
};
|
};
|
||||||
|
|
||||||
// 格式化工具
|
// 格式化工具(按照原版逻辑)
|
||||||
const formatUtils = {
|
const formatUtils = {
|
||||||
formatNumber(num) {
|
formatNumber(num) {
|
||||||
if (num >= 1000000000) return (num / 1000000000).toFixed(1) + 'B';
|
if (num >= 1000000000) return (num / 1000000000).toFixed(1) + 'B+';
|
||||||
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
|
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M+';
|
||||||
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
|
if (num >= 1000) return (num / 1000).toFixed(1) + 'K+';
|
||||||
return num.toString();
|
return num.toString();
|
||||||
},
|
},
|
||||||
formatSize(bytes) {
|
formatTimeAgo(dateString) {
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
if (!dateString) return '未知时间';
|
||||||
if (bytes === 0) return '0 B';
|
try {
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
let date;
|
||||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
date = new Date(dateString);
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
const formats = [
|
||||||
|
{ regex: /^(\d{4})-(\d{2})-(\d{2})$/, handler: (m) => new Date(m[1], m[2] - 1, m[3]) },
|
||||||
|
{ regex: /^(\d{4})\/(\d{2})\/(\d{2})$/, handler: (m) => new Date(m[1], m[2] - 1, m[3]) },
|
||||||
|
{ regex: /^(\d{2})\/(\d{2})\/(\d{4})$/, handler: (m) => new Date(m[3], m[1] - 1, m[2]) }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const format of formats) {
|
||||||
|
const match = dateString.match(format.regex);
|
||||||
|
if (match) {
|
||||||
|
date = format.handler(match);
|
||||||
|
if (!isNaN(date.getTime())) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return '未知时间';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const diffTime = Math.abs(now - date);
|
||||||
|
const diffMinutes = Math.floor(diffTime / (1000 * 60));
|
||||||
|
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
|
||||||
|
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
const diffMonths = Math.floor(diffDays / 30);
|
||||||
|
const diffYears = Math.floor(diffDays / 365);
|
||||||
|
|
||||||
|
if (diffMinutes < 1) return '刚刚';
|
||||||
|
if (diffMinutes < 60) return `${diffMinutes}分钟前`;
|
||||||
|
if (diffHours < 24) return `${diffHours}小时前`;
|
||||||
|
if (diffDays < 7) return `${diffDays}天前`;
|
||||||
|
if (diffDays < 30) return `${Math.floor(diffDays / 7)}周前`;
|
||||||
|
if (diffMonths < 12) return `${diffMonths}个月前`;
|
||||||
|
if (diffYears < 1) return '近1年';
|
||||||
|
return `${diffYears}年前`;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('日期处理错误:', error);
|
||||||
|
return '未知时间';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
formatDate(dateString) {
|
formatSize(bytes) {
|
||||||
return new Date(dateString).toLocaleDateString('zh-CN');
|
const units = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
let size = bytes;
|
||||||
|
let unitIndex = 0;
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -895,25 +1006,18 @@
|
|||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'result-card';
|
card.className = 'result-card';
|
||||||
|
|
||||||
// 获取原始镜像名称
|
// 按照原版逻辑构建显示名称
|
||||||
const originalName = result.repo_name || result.name;
|
let displayName = '';
|
||||||
|
if (result.is_official) {
|
||||||
// 确保只有真正的官方镜像才处理显示名称
|
// 对于官方镜像,去掉 library/ 前缀
|
||||||
// 使用更严格的条件:必须同时满足 is_official 为 true 且名称以 library/ 开头
|
displayName = (result.name || result.repo_name || '').replace('library/', '');
|
||||||
const isActuallyOfficial = result.is_official === true && originalName.startsWith('library/');
|
} else {
|
||||||
const displayName = isActuallyOfficial ? originalName.substring(8) : originalName;
|
// 对于非官方镜像,显示完整路径
|
||||||
|
const name = result.name || result.repo_name || '';
|
||||||
// 调试日志(可在开发时启用)
|
displayName = result.namespace ? `${result.namespace}/${name}` : name;
|
||||||
if (originalName.toLowerCase().includes('caddy')) {
|
|
||||||
console.log('Debug caddy:', {
|
|
||||||
originalName: originalName,
|
|
||||||
is_official: result.is_official,
|
|
||||||
isActuallyOfficial: isActuallyOfficial,
|
|
||||||
displayName: displayName
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
card.onclick = () => viewImageTags(originalName, isActuallyOfficial);
|
card.onclick = () => viewImageTags(result, displayName);
|
||||||
|
|
||||||
const badges = [];
|
const badges = [];
|
||||||
if (result.is_official) badges.push('<span class="badge badge-official">官方</span>');
|
if (result.is_official) badges.push('<span class="badge badge-official">官方</span>');
|
||||||
@@ -945,7 +1049,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查看镜像标签
|
// 查看镜像标签
|
||||||
async function viewImageTags(imageName, isOfficial = false) {
|
async function viewImageTags(result, displayName) {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
@@ -953,17 +1057,14 @@
|
|||||||
elements.searchResults.classList.remove('show');
|
elements.searchResults.classList.remove('show');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 解析镜像名称
|
// 获取命名空间和仓库名
|
||||||
let namespace = 'library';
|
const namespace = result.namespace || (result.is_official ? 'library' : '');
|
||||||
let repoName = imageName;
|
const name = result.name || result.repo_name || '';
|
||||||
|
const repoName = name.replace('library/', '');
|
||||||
|
|
||||||
if (imageName.includes('/')) {
|
if (!namespace || !repoName) {
|
||||||
const parts = imageName.split('/');
|
showToast('无效的仓库信息', 'error');
|
||||||
namespace = parts[0];
|
return;
|
||||||
repoName = parts[1];
|
|
||||||
} else {
|
|
||||||
// 官方镜像
|
|
||||||
namespace = 'library';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`/tags/${namespace}/${repoName}`);
|
const response = await fetch(`/tags/${namespace}/${repoName}`);
|
||||||
@@ -971,7 +1072,7 @@
|
|||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
allTags = data;
|
allTags = data;
|
||||||
displayImageTags(imageName, data, isOfficial);
|
displayImageTags(result, displayName, data);
|
||||||
elements.backToSearch.classList.add('show');
|
elements.backToSearch.classList.add('show');
|
||||||
} else {
|
} else {
|
||||||
showToast('获取标签失败:' + (data.error || '未知错误'), 'error');
|
showToast('获取标签失败:' + (data.error || '未知错误'), 'error');
|
||||||
@@ -986,95 +1087,161 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 显示镜像标签
|
// 显示镜像标签
|
||||||
function displayImageTags(imageName, tags, isOfficial = false) {
|
function displayImageTags(result, displayName, tags) {
|
||||||
const fullDomain = window.location.host;
|
const fullDomain = window.location.host;
|
||||||
|
const pullImageName = displayName;
|
||||||
|
|
||||||
// 确保只有真正的官方镜像才处理显示名称和拉取命令
|
// 按照原版结构构建标签页面
|
||||||
// 使用更严格的条件:必须同时满足 isOfficial 为 true 且名称以 library/ 开头
|
const badges = [];
|
||||||
const isActuallyOfficial = isOfficial === true && imageName.startsWith('library/');
|
if (result.is_official) badges.push('<span class="badge badge-official">官方</span>');
|
||||||
const displayName = isActuallyOfficial ? imageName.substring(8) : imageName;
|
if (result.affiliation) badges.push(`<span class="badge badge-organization">By ${result.affiliation}</span>`);
|
||||||
const pullImageName = isActuallyOfficial ? imageName.substring(8) : imageName;
|
|
||||||
|
|
||||||
elements.tagInfo.innerHTML = `
|
const stats = [];
|
||||||
<div class="tag-title">
|
if (result.star_count > 0) stats.push(`<span class="meta-item">⭐ ${formatUtils.formatNumber(result.star_count)}</span>`);
|
||||||
🐳 ${displayName}
|
if (result.pull_count > 0) stats.push(`<span class="meta-item">⬇️ ${formatUtils.formatNumber(result.pull_count)}+</span>`);
|
||||||
|
if (result.last_updated) stats.push(`<span class="meta-item">更新于 ${formatUtils.formatTimeAgo(result.last_updated)}</span>`);
|
||||||
|
|
||||||
|
elements.tagList.innerHTML = `
|
||||||
|
<div class="tag-header">
|
||||||
|
<div class="tag-info">
|
||||||
|
<div class="tag-title">
|
||||||
|
${displayName}
|
||||||
|
${badges.join(' ')}
|
||||||
|
</div>
|
||||||
|
<div class="tag-description">${result.short_description || result.description || '暂无描述'}</div>
|
||||||
|
<div class="tag-meta">
|
||||||
|
${stats.join(' ')}
|
||||||
|
</div>
|
||||||
|
<div class="tag-pull-command">
|
||||||
|
docker pull ${fullDomain}/${pullImageName}
|
||||||
|
<button class="copy-button" onclick="copyToClipboard('docker pull ${fullDomain}/${pullImageName}')">复制</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-description">
|
<div class="tag-search-container">
|
||||||
共 ${tags.length} 个标签版本
|
<input type="text" class="tag-search-input" placeholder="输入关键词搜索标签,支持模糊匹配..." oninput="filterTags(this.value)">
|
||||||
</div>
|
<button class="tag-search-clear" onclick="clearTagSearch()">×</button>
|
||||||
<div class="tag-pull-command">
|
|
||||||
docker pull ${fullDomain}/${pullImageName}
|
|
||||||
<button class="copy-button" onclick="copyCommand('docker pull ${fullDomain}/${pullImageName}')">
|
|
||||||
复制
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="tagsContainer"></div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
displayFilteredTags(tags);
|
// 存储所有标签数据供搜索使用
|
||||||
|
window.allTags = tags;
|
||||||
|
window.currentRepo = result;
|
||||||
|
window.currentDisplayName = displayName;
|
||||||
|
|
||||||
|
// 初始显示所有标签
|
||||||
|
renderFilteredTags(tags);
|
||||||
|
|
||||||
// 确保显示tag列表并滚动到顶部
|
// 确保显示tag列表并滚动到顶部
|
||||||
elements.tagList.classList.add('show');
|
elements.tagList.classList.add('show');
|
||||||
elements.tagList.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
elements.tagList.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示过滤后的标签
|
// 渲染过滤后的标签(按照原版逻辑)
|
||||||
function displayFilteredTags(tags) {
|
function renderFilteredTags(filteredTags) {
|
||||||
elements.tagItems.innerHTML = '';
|
const tagsContainer = document.getElementById('tagsContainer');
|
||||||
|
const fullDomain = window.location.host;
|
||||||
|
const pullImageName = window.currentDisplayName;
|
||||||
|
|
||||||
tags.forEach(tag => {
|
let tagsHtml = filteredTags.map(tag => {
|
||||||
const tagItem = createTagItem(tag);
|
const vulnIndicators = Object.entries(tag.vulnerabilities || {})
|
||||||
elements.tagItems.appendChild(tagItem);
|
.map(([level, count]) => count > 0 ? `<span class="vulnerability-dot vulnerability-${level.toLowerCase()}" title="${level}: ${count}"></span>` : '')
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const images = tag.images || [];
|
||||||
|
const architectures = images.map(img => {
|
||||||
|
const arch = `${img.os}/${img.architecture}${img.variant ? '/' + img.variant : ''}`;
|
||||||
|
const size = formatUtils.formatSize(img.size);
|
||||||
|
return `<div class="arch-item" title="大小: ${size}">${arch}</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="tag-item">
|
||||||
|
<div class="tag-name">
|
||||||
|
${tag.name}
|
||||||
|
${vulnIndicators ? `<div class="vulnerability-indicator">${vulnIndicators}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="tag-meta">
|
||||||
|
<span>最后更新: ${formatUtils.formatTimeAgo(tag.last_updated)}</span>
|
||||||
|
${tag.last_pusher ? `<span>由 ${tag.last_pusher} 推送</span>` : ''}
|
||||||
|
${tag.full_size ? `<span>大小: ${formatUtils.formatSize(tag.full_size)}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="tag-pull-command">
|
||||||
|
docker pull ${fullDomain}/${pullImageName}:${tag.name}
|
||||||
|
<button class="copy-button" onclick="copyToClipboard('docker pull ${fullDomain}/${pullImageName}:${tag.name}')">复制</button>
|
||||||
|
</div>
|
||||||
|
${architectures ? `<div class="tag-architectures">${architectures}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
if (filteredTags.length === 0) {
|
||||||
|
tagsHtml = '<div class="text-center" style="padding: 20px;">未找到匹配的标签</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsContainer.innerHTML = tagsHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到剪贴板(按照原版命名)
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
showToast('已复制到剪贴板');
|
||||||
|
}).catch(() => {
|
||||||
|
showToast('复制失败');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建标签项
|
// 标签搜索过滤(按照原版逻辑)
|
||||||
function createTagItem(tag) {
|
function filterTags(searchText) {
|
||||||
const item = document.createElement('div');
|
if (!window.allTags) return;
|
||||||
item.className = 'tag-item';
|
|
||||||
|
|
||||||
const architectures = tag.images?.map(img => img.architecture).join(', ') || '未知';
|
const searchLower = searchText.toLowerCase();
|
||||||
const size = tag.images?.[0]?.size ? formatUtils.formatSize(tag.images[0].size) : '未知';
|
let filteredTags;
|
||||||
const lastUpdated = tag.last_updated ? formatUtils.formatDate(tag.last_updated) : '未知';
|
|
||||||
|
|
||||||
item.innerHTML = `
|
if (!searchText) {
|
||||||
<div class="tag-name">${tag.name}</div>
|
filteredTags = window.allTags;
|
||||||
<div class="tag-meta">
|
|
||||||
📅 更新时间: ${lastUpdated} | 📦 大小: ${size}
|
|
||||||
</div>
|
|
||||||
<div class="tag-architectures">
|
|
||||||
${architectures.split(', ').map(arch =>
|
|
||||||
`<span class="arch-item">${arch}</span>`
|
|
||||||
).join('')}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制命令
|
|
||||||
function copyCommand(command) {
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
navigator.clipboard.writeText(command).then(() => {
|
|
||||||
showToast('命令已复制到剪贴板');
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// 降级方案
|
// 对标签进行评分和排序
|
||||||
const textarea = document.createElement('textarea');
|
const scoredTags = window.allTags.map(tag => {
|
||||||
textarea.value = command;
|
const name = tag.name.toLowerCase();
|
||||||
document.body.appendChild(textarea);
|
let score = 0;
|
||||||
textarea.select();
|
|
||||||
document.execCommand('copy');
|
// 完全匹配
|
||||||
document.body.removeChild(textarea);
|
if (name === searchLower) {
|
||||||
showToast('命令已复制到剪贴板');
|
score += 100;
|
||||||
|
}
|
||||||
|
// 前缀匹配
|
||||||
|
else if (name.startsWith(searchLower)) {
|
||||||
|
score += 50;
|
||||||
|
}
|
||||||
|
// 包含匹配
|
||||||
|
else if (name.includes(searchLower)) {
|
||||||
|
score += 30;
|
||||||
|
}
|
||||||
|
// 部分匹配(按单词)
|
||||||
|
else if (searchLower.split(/\s+/).some(word => name.includes(word))) {
|
||||||
|
score += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { tag, score };
|
||||||
|
}).filter(item => item.score > 0);
|
||||||
|
|
||||||
|
// 按分数排序
|
||||||
|
scoredTags.sort((a, b) => b.score - a.score);
|
||||||
|
filteredTags = scoredTags.map(item => item.tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderFilteredTags(filteredTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标签搜索过滤
|
// 清除标签搜索
|
||||||
function filterTags(searchTerm) {
|
function clearTagSearch() {
|
||||||
const filtered = allTags.filter(tag =>
|
const searchInput = document.querySelector('.tag-search-input');
|
||||||
tag.name.toLowerCase().includes(searchTerm.toLowerCase())
|
if (searchInput) {
|
||||||
);
|
searchInput.value = '';
|
||||||
displayFilteredTags(filtered);
|
filterTags('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 事件监听
|
// 事件监听
|
||||||
@@ -1104,9 +1271,7 @@
|
|||||||
searchImages(currentSearchTerm, currentPage + 1);
|
searchImages(currentSearchTerm, currentPage + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
elements.tagSearchInput.addEventListener('input', (event) => {
|
// 移除了tagSearchInput的事件监听器,现在使用内联事件
|
||||||
filterTags(event.target.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.backToSearch.addEventListener('click', () => {
|
elements.backToSearch.addEventListener('click', () => {
|
||||||
elements.tagList.classList.remove('show');
|
elements.tagList.classList.remove('show');
|
||||||
|
|||||||
Reference in New Issue
Block a user