update
Some checks failed
Rust CI / Build Release (push) Has been cancelled
Rust CI / Cargo Check (push) Has been cancelled

This commit is contained in:
lychang
2026-07-19 17:44:08 +08:00
parent 093924ea99
commit cb09f2caac
3 changed files with 444 additions and 1 deletions

View File

@@ -1084,6 +1084,219 @@ body {
border: 1px solid var(--medium-gray);
}
/* ============================================================
发现区域 - 热门人物 & 热门标签
============================================================ */
.discover-section {
background: var(--white);
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-sm);
border: 1px solid var(--medium-gray);
margin-bottom: 30px;
overflow: hidden;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 24px;
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-bottom: 1px solid var(--medium-gray);
}
.section-header h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 700;
color: var(--text-dark);
display: flex;
align-items: center;
gap: 10px;
}
.section-header h3 i {
color: var(--secondary-blue);
}
.section-toggle {
background: none;
border: none;
color: var(--text-light);
cursor: pointer;
padding: 6px 10px;
border-radius: 6px;
transition: all 0.2s ease;
font-size: 0.9rem;
}
.section-toggle:hover {
background: rgba(59, 130, 246, 0.1);
color: var(--secondary-blue);
}
/* 标签云 */
.tag-cloud {
padding: 20px 24px;
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
}
.tag-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 14px;
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border: 1px solid #bae6fd;
border-radius: 20px;
cursor: pointer;
font-weight: 600;
transition: all 0.25s ease;
user-select: none;
}
.tag-chip small {
color: var(--text-light);
font-weight: 500;
opacity: 0.8;
}
.tag-chip:hover {
background: linear-gradient(135deg, var(--secondary-blue) 0%, var(--primary-blue) 100%);
color: var(--white) !important;
border-color: var(--secondary-blue);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.tag-chip:hover small {
color: rgba(255, 255, 255, 0.85);
}
/* 热门人物 */
.people-cloud {
padding: 20px 24px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
}
.person-chip {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
background: linear-gradient(135deg, #fdf4ff 0%, #fae8ff 100%);
border: 1px solid #f5d0fe;
border-radius: 12px;
cursor: pointer;
transition: all 0.25s ease;
}
.person-chip:hover {
background: linear-gradient(135deg, #a855f7 0%, #9333ea 100%);
border-color: #9333ea;
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.person-chip:hover .person-name,
.person-chip:hover .person-meta {
color: var(--white);
}
.person-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #a855f7 0%, #7c3aed 100%);
color: var(--white);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1.1rem;
flex-shrink: 0;
}
.person-chip:hover .person-avatar {
background: rgba(255, 255, 255, 0.25);
}
.person-info {
flex: 1;
min-width: 0;
}
.person-name {
font-weight: 700;
color: var(--text-dark);
font-size: 0.95rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.person-meta {
font-size: 0.8rem;
color: var(--text-light);
margin-top: 2px;
}
/* 筛选状态条 */
.filter-status {
display: none;
align-items: center;
gap: 12px;
padding: 14px 20px;
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border: 1px solid #fcd34d;
border-radius: var(--border-radius);
margin-bottom: 24px;
box-shadow: var(--shadow-sm);
}
.filter-label {
font-weight: 600;
color: #92400e;
font-size: 0.95rem;
}
.filter-tag {
background: var(--white);
padding: 4px 12px;
border-radius: 16px;
font-weight: 700;
color: #92400e;
font-size: 0.9rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.filter-clear {
margin-left: auto;
padding: 6px 14px;
background: #92400e;
color: var(--white);
border: none;
border-radius: 16px;
font-weight: 600;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 6px;
}
.filter-clear:hover {
background: #78350f;
transform: translateY(-1px);
}
/* 响应式网格布局 - 区分PC和手机 */
.albums-grid {
display: grid;

View File

@@ -33,6 +33,39 @@
<div class="stats" id="stats">
正在加载相册数据...
</div>
<!-- 热门人物区域 -->
<div class="discover-section" id="people-section" style="display:none;">
<div class="section-header">
<h3><i class="fas fa-users"></i> 热门人物</h3>
<button class="section-toggle" onclick="toggleSection('people')">
<i class="fas fa-chevron-up"></i>
</button>
</div>
<div class="people-cloud" id="people-cloud">
<!-- 动态填充 -->
</div>
</div>
<!-- 热门标签区域 -->
<div class="discover-section" id="tags-section" style="display:none;">
<div class="section-header">
<h3><i class="fas fa-tags"></i> 热门标签</h3>
<button class="section-toggle" onclick="toggleSection('tags')">
<i class="fas fa-chevron-up"></i>
</button>
</div>
<div class="tag-cloud" id="tag-cloud">
<!-- 动态填充 -->
</div>
</div>
<!-- 当前筛选状态 -->
<div class="filter-status" id="filter-status" style="display:none;">
<span class="filter-label">当前筛选:</span>
<span class="filter-tag" id="current-filter"></span>
<button class="filter-clear" onclick="clearFilter()"><i class="fas fa-times"></i> 清除</button>
</div>
<div class="albums-grid" id="albums-container">
<div class="loading">

View File

@@ -10,6 +10,10 @@ let lazyLoadObserver = null;
let isAllAlbumsLoaded = false; // 标记是否已加载所有相册数据
let isInFavoritesPage = false; // 标记当前是否在收藏页
// 标签和人物筛选
let currentFilter = null; // { type: 'tag'|'person', value: string }
let collapsedSections = { people: false, tags: false };
// API基础URL
const API_BASE = '/api';
@@ -437,6 +441,9 @@ async function loadAlbums(reset = false) {
// 预加载下一批图片
preloadNextBatchImages();
// 加载热门标签和人物
loadDiscoverData();
} catch (error) {
console.error('加载相册失败:', error);
showError('加载相册数据失败,请刷新页面重试');
@@ -1199,6 +1206,192 @@ function openFavoriteImage(favoriteId) {
window.location.href = `/album?id=${favoriteId}`;
}
// ============================================================
// 标签和人物发现功能
// ============================================================
// 加载热门标签和人物数据
async function loadDiscoverData() {
try {
// 并行加载
const [tagsResp, peopleResp] = await Promise.all([
fetch(`${API_BASE}/tags/popular?limit=40`),
fetch(`${API_BASE}/recommend/popular-persons?limit=30`)
]);
if (tagsResp.ok) {
const tags = await tagsResp.json();
if (tags && tags.length > 0) {
renderTagCloud(tags);
}
}
if (peopleResp.ok) {
const people = await peopleResp.json();
if (people && people.length > 0) {
renderPeopleCloud(people);
}
}
} catch (error) {
console.warn('加载发现数据失败:', error);
}
}
// 渲染标签云
function renderTagCloud(tags) {
const section = document.getElementById('tags-section');
const cloud = document.getElementById('tag-cloud');
if (!section || !cloud || !tags.length) return;
section.style.display = 'block';
// 计算标签大小范围
const maxCount = Math.max(...tags.map(t => t.album_cnt || t.count || 1));
const minCount = Math.min(...tags.map(t => t.album_cnt || t.count || 1));
cloud.innerHTML = tags.map(tag => {
const count = tag.album_cnt || tag.count || 1;
const name = tag.tag || tag.name || '';
// 计算字体大小 (12px - 22px)
const ratio = maxCount === minCount ? 0.5 : (count - minCount) / (maxCount - minCount);
const fontSize = 12 + ratio * 10;
// 计算热度颜色
const hue = 210 - ratio * 50; // 蓝色到紫色
const opacity = 0.6 + ratio * 0.4;
return `<span class="tag-chip"
style="font-size:${fontSize}px; color:hsl(${hue}, 70%, 45%); opacity:${opacity};"
onclick="filterByTag('${name}')"
title="${count} 个相册">
${name} <small>(${count})</small>
</span>`;
}).join('');
}
// 渲染热门人物
function renderPeopleCloud(people) {
const section = document.getElementById('people-section');
const cloud = document.getElementById('people-cloud');
if (!section || !cloud || !people.length) return;
section.style.display = 'block';
cloud.innerHTML = people.map(p => {
const name = p.name || p.display_name || '';
const albumCount = p.total_albums || 0;
const imageCount = p.total_images || 0;
return `<div class="person-chip" onclick="filterByPerson('${name}')" title="${albumCount} 个相册, ${imageCount} 张图片">
<div class="person-avatar">${name.charAt(0)}</div>
<div class="person-info">
<div class="person-name">${name}</div>
<div class="person-meta">${albumCount} 相册 · ${imageCount} 图</div>
</div>
</div>`;
}).join('');
}
// 按标签筛选
async function filterByTag(tag) {
currentFilter = { type: 'tag', value: tag };
showFilterStatus(`标签: ${tag}`);
try {
showSkeleton(6);
const response = await fetch(`${API_BASE}/tags/search?tag=${encodeURIComponent(tag)}&limit=200`);
if (!response.ok) throw new Error('搜索失败');
const results = await response.json();
displayAlbums(results);
updateStats(results);
initLazyLoading();
} catch (error) {
console.error('标签筛选失败:', error);
showError('标签筛选失败');
}
}
// 按人物筛选
async function filterByPerson(personName) {
currentFilter = { type: 'person', value: personName };
showFilterStatus(`人物: ${personName}`);
try {
showSkeleton(6);
const response = await fetch(`${API_BASE}/people/search?q=${encodeURIComponent(personName)}`);
if (!response.ok) throw new Error('搜索失败');
const persons = await response.json();
if (persons && persons.length > 0) {
// 先尝试通过人物详情获取相册
const personId = persons[0].id;
try {
const detailResp = await fetch(`${API_BASE}/people/${encodeURIComponent(personName)}`);
if (detailResp.ok) {
const detail = await detailResp.json();
if (detail && detail.albums) {
displayAlbums(detail.albums);
updateStats(detail.albums);
initLazyLoading();
return;
}
}
} catch (e) {
// 回退到标题搜索
}
}
// 回退:使用标题搜索
await performServerSearch(personName);
} catch (error) {
console.error('人物筛选失败:', error);
// 回退到标题搜索
await performServerSearch(personName);
}
}
// 显示筛选状态
function showFilterStatus(text) {
const status = document.getElementById('filter-status');
const label = document.getElementById('current-filter');
if (status && label) {
label.textContent = text;
status.style.display = 'flex';
}
}
// 清除筛选
function clearFilter() {
currentFilter = null;
const status = document.getElementById('filter-status');
if (status) status.style.display = 'none';
// 清空搜索框
const searchInput = document.getElementById('search-input');
if (searchInput) searchInput.value = '';
// 重新加载所有相册
loadAlbums(true);
}
// 折叠/展开区域
function toggleSection(name) {
collapsedSections[name] = !collapsedSections[name];
const cloudId = name === 'people' ? 'people-cloud' : 'tag-cloud';
const cloud = document.getElementById(cloudId);
const btn = document.querySelector(`#${name}-section .section-toggle i`);
if (cloud) {
if (collapsedSections[name]) {
cloud.style.display = 'none';
btn.className = 'fas fa-chevron-down';
} else {
cloud.style.display = '';
btn.className = 'fas fa-chevron-up';
}
}
}
// 导出函数供全局使用
window.searchAlbums = searchAlbums;
window.openRandomAlbum = openRandomAlbum;
@@ -1209,4 +1402,8 @@ window.loadAlbums = loadAlbums;
window.removeFavorite = removeFavorite;
window.previewFavorite = previewFavorite;
window.openFavoriteImage = openFavoriteImage;
window.removeFavoriteFromPreview = removeFavoriteFromPreview;
window.removeFavoriteFromPreview = removeFavoriteFromPreview;
window.filterByTag = filterByTag;
window.filterByPerson = filterByPerson;
window.clearFilter = clearFilter;
window.toggleSection = toggleSection;