update
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
199
assets/js/app.js
199
assets/js/app.js
@@ -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;
|
||||
Reference in New Issue
Block a user