手机端作品页面样式修改,支持手滑动翻页

This commit is contained in:
2025-10-08 10:11:02 +08:00
parent f9e732c1e3
commit 5eafc6dbc8
3 changed files with 363 additions and 16 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ Pixiv 下载浏览管理器是一个基于 Web 的应用程序,提供以下功
### 便携版下载(如果不想自义定或者是懒) ### 便携版下载(如果不想自义定或者是懒)
如果懒得配置环境,可以直接下载便携版(日,我自己用怎么还被当成木马了,算了忽略一下,不放心就自己打包): 如果懒得配置环境,可以直接下载便携版(日,我自己用怎么还被当成木马了,算了忽略一下,不放心就自己打包(npm bp运行一下即可,全局装个pkg)):
**方式一:直接下载(可能比较慢,服务器带宽有限辣)** **方式一:直接下载(可能比较慢,服务器带宽有限辣)**
- **下载链接**: [点我下载](https://sywb.top/Staticfiles/p%E4%B8%8B%E8%BD%BD%E5%99%A8.rar) - **下载链接**: [点我下载](https://sywb.top/Staticfiles/p%E4%B8%8B%E8%BD%BD%E5%99%A8.rar)
+212 -13
View File
@@ -671,47 +671,246 @@ input:checked+.slider:before {
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.artwork-info {
padding: var(--spacing-lg);
margin: 0;
border-radius: var(--radius-lg);
}
.artwork-title {
font-size: 1.25rem;
max-width: none;
white-space: normal;
overflow: visible;
text-overflow: unset;
}
/* 移动端按钮重新布局 */
.artwork-actions { .artwork-actions {
flex-direction: column; display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
}
.artwork-actions .btn {
min-width: unset;
padding: var(--spacing-md);
font-size: 0.875rem;
font-weight: 600;
border-radius: var(--radius-lg);
height: 44px; /* 符合移动端触摸标准 */
display: flex;
align-items: center;
justify-content: center;
}
/* 下载按钮占据两列 */
.artwork-actions .btn:first-child {
grid-column: 1 / -1;
}
/* 如果有删除按钮,下载按钮占一列,删除按钮占一列 */
.artwork-actions .btn:first-child:not(:last-child) {
grid-column: 1 / 2;
} }
.artwork-stats { .artwork-stats {
flex-direction: column; display: grid;
gap: var(--spacing-lg); grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-md);
text-align: center;
background: var(--color-bg-secondary);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
margin-bottom: var(--spacing-xl);
} }
.artwork-navigation { .stat {
flex-direction: column; flex-direction: column;
gap: var(--spacing-md); gap: var(--spacing-xs);
font-size: 0.75rem;
}
.stat svg {
width: 1.25rem;
height: 1.25rem;
margin: 0 auto;
}
/* 移动端导航优化 */
.artwork-navigation {
position: sticky;
bottom: 0;
left: 0;
right: 0;
background: var(--color-bg-primary);
border-top: 1px solid var(--color-border);
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
padding: var(--spacing-md);
margin: var(--spacing-xl) calc(-1 * var(--spacing-lg)) 0;
display: grid;
grid-template-columns: auto 1fr auto;
gap: var(--spacing-sm);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
z-index: 2000;
} }
.nav-back { .nav-back {
order: -1; order: 0;
align-self: flex-start; min-width: 60px;
min-width: 80px; padding: var(--spacing-sm);
border-radius: var(--radius-md);
background: var(--color-bg-secondary);
border-color: var(--color-border);
}
.nav-back span {
display: none;
} }
.nav-prev, .nav-prev,
.nav-next { .nav-next {
min-width: auto; min-width: unset;
padding: var(--spacing-md) var(--spacing-lg);
font-weight: 600;
border-radius: var(--radius-lg);
height: 44px;
}
.nav-prev {
order: 1;
justify-content: center;
}
.nav-next {
order: 2;
justify-content: center;
}
/* 快速导航提示 */
.artwork-navigation::before {
content: "← 上一个 | 下一个 →";
position: absolute;
top: -24px;
left: 50%;
transform: translateX(-50%);
font-size: 0.75rem;
color: var(--color-text-tertiary);
background: var(--color-bg-secondary);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
white-space: nowrap;
opacity: 0.8;
}
.artist-info {
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.artist-avatar {
width: 2.5rem;
height: 2.5rem;
}
.artist-name {
font-size: 1rem;
}
.artist-account {
font-size: 0.8rem;
}
.artwork-tags {
margin-bottom: var(--spacing-lg);
}
.tags-list {
gap: var(--spacing-xs);
} }
.artwork-meta { .artwork-meta {
flex-direction: column; flex-direction: column;
gap: var(--spacing-lg); gap: var(--spacing-md);
padding-bottom: 80px; /* 为底部导航留出更多空间 */
} }
.toggle-container { .toggle-container {
align-self: flex-end; align-self: stretch;
justify-content: space-between;
padding: var(--spacing-md);
height: auto;
} }
.caption-content { .caption-content {
max-height: 80px; max-height: 100px;
font-size: 0.75rem; font-size: 0.8rem;
} }
.caption-header h3 { .caption-header h3 {
font-size: 0.9rem;
}
.artwork-description,
.artwork-caption {
margin-bottom: var(--spacing-lg);
}
.keyboard-hint {
display: none;
}
}
/* 更小屏幕的额外优化 */
@media (max-width: 480px) {
.artwork-info {
padding: var(--spacing-md);
}
.artwork-title {
font-size: 1.125rem;
line-height: 1.4;
}
.artwork-actions .btn {
font-size: 0.8rem;
height: 40px;
padding: var(--spacing-sm);
}
.artwork-stats {
padding: var(--spacing-md);
}
.stat {
font-size: 0.7rem;
}
.stat svg {
width: 1rem;
height: 1rem;
}
.artist-info {
padding: var(--spacing-md);
}
.artist-avatar {
width: 2rem;
height: 2rem;
}
.nav-prev,
.nav-next {
padding: var(--spacing-sm) var(--spacing-md);
font-size: 0.875rem; font-size: 0.875rem;
height: 40px;
}
.nav-back {
min-width: 50px;
padding: var(--spacing-xs);
} }
} }
</style> </style>
+150 -2
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="artwork-page"> <div class="artwork-page" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd">
<div class="container"> <div class="container">
<!-- 收藏错误提示 --> <!-- 收藏错误提示 -->
<div v-if="bookmarkError" class="error-section"> <div v-if="bookmarkError" class="error-section">
@@ -15,8 +15,15 @@
<LoadingSpinner text="加载中..." /> <LoadingSpinner text="加载中..." />
</div> </div>
<!-- 滑动提示 -->
<div v-if="artwork && isMobile" class="swipe-hint" :class="{ 'hint-visible': showSwipeHint }">
<div class="hint-content">
<span class="hint-text"> 滑动切换作品 </span>
</div>
</div>
<!-- 作品内容 --> <!-- 作品内容 -->
<div v-if="artwork" class="artwork-content" :class="{ 'content-loading': loading }"> <div v-if="artwork" class="artwork-content" :class="{ 'content-loading': loading, 'swiping': isSwipeActive }">
<!-- 左侧图片组件 --> <!-- 左侧图片组件 -->
<ArtworkGallery :artwork="artwork" :current-page="currentImagePage" :loading="loading" <ArtworkGallery :artwork="artwork" :current-page="currentImagePage" :loading="loading"
@page-change="currentImagePage = $event" /> @page-change="currentImagePage = $event" />
@@ -98,6 +105,83 @@ const showRecommendations = ref(true);
// Caption 显示开关状态 // Caption 显示开关状态
const showCaption = ref(false); const showCaption = ref(false);
// 触摸滑动相关状态
const touchStartX = ref(0);
const touchStartY = ref(0);
const touchEndX = ref(0);
const touchEndY = ref(0);
const isSwipeActive = ref(false);
const isMobile = ref(false);
const showSwipeHint = ref(false);
// 检测是否为移动设备
const checkMobileDevice = () => {
isMobile.value = window.innerWidth <= 768 || 'ontouchstart' in window;
};
// 触摸开始事件
const handleTouchStart = (event: TouchEvent) => {
if (!showNavigation.value || loading.value) return;
const touch = event.touches[0];
touchStartX.value = touch.clientX;
touchStartY.value = touch.clientY;
isSwipeActive.value = false;
};
// 触摸移动事件
const handleTouchMove = (event: TouchEvent) => {
if (!showNavigation.value || loading.value) return;
const touch = event.touches[0];
const deltaX = Math.abs(touch.clientX - touchStartX.value);
const deltaY = Math.abs(touch.clientY - touchStartY.value);
// 如果水平滑动距离大于垂直滑动距离,且超过阈值,则激活滑动状态
if (deltaX > deltaY && deltaX > 30) {
isSwipeActive.value = true;
event.preventDefault(); // 阻止页面滚动
}
};
// 触摸结束事件
const handleTouchEnd = (event: TouchEvent) => {
if (!showNavigation.value || loading.value) return;
const touch = event.changedTouches[0];
touchEndX.value = touch.clientX;
touchEndY.value = touch.clientY;
const deltaX = touchEndX.value - touchStartX.value;
const deltaY = Math.abs(touchEndY.value - touchStartY.value);
// 检查是否为有效的水平滑动
const minSwipeDistance = 80; // 最小滑动距离
const maxVerticalDistance = 100; // 最大垂直偏移
if (Math.abs(deltaX) > minSwipeDistance && deltaY < maxVerticalDistance) {
if (deltaX > 0 && canNavigateToPrevious.value) {
// 向右滑动 - 上一个作品
navigateToPrevious();
} else if (deltaX < 0 && canNavigateToNext.value) {
// 向左滑动 - 下一个作品
navigateToNext();
}
}
isSwipeActive.value = false;
};
// 显示滑动提示
const showSwipeHintTemporarily = () => {
if (!isMobile.value || !showNavigation.value) return;
showSwipeHint.value = true;
setTimeout(() => {
showSwipeHint.value = false;
}, 3000);
};
// 初始化推荐开关状态(从localStorage读取) // 初始化推荐开关状态(从localStorage读取)
const initializeRecommendationsState = () => { const initializeRecommendationsState = () => {
const saved = localStorage.getItem('artwork-show-recommendations'); const saved = localStorage.getItem('artwork-show-recommendations');
@@ -820,6 +904,12 @@ onMounted(() => {
// 确保页面滚动到顶部 // 确保页面滚动到顶部
window.scrollTo(0, 0); window.scrollTo(0, 0);
// 检测移动设备
checkMobileDevice();
// 监听窗口大小变化
window.addEventListener('resize', checkMobileDevice);
fetchArtworkDetail(); fetchArtworkDetail();
if (showNavigation.value) { if (showNavigation.value) {
// 从路由查询参数获取页码 // 从路由查询参数获取页码
@@ -837,6 +927,11 @@ onMounted(() => {
// 初始化 Caption 开关状态 // 初始化 Caption 开关状态
initializeCaptionState(); initializeCaptionState();
// 延迟显示滑动提示(仅在移动端且有导航时)
setTimeout(() => {
showSwipeHintTemporarily();
}, 1000);
}); });
// 组件卸载时移除事件监听 // 组件卸载时移除事件监听
@@ -844,6 +939,7 @@ onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown); document.removeEventListener('keydown', handleKeydown);
document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('keyup', handleKeyUp); document.removeEventListener('keyup', handleKeyUp);
window.removeEventListener('resize', checkMobileDevice);
}); });
</script> </script>
@@ -897,8 +993,60 @@ onUnmounted(() => {
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.artwork-page {
padding: 1rem 0 0 0; /* 移除底部内边距 */
}
.container { .container {
padding: 0 1rem; padding: 0 1rem;
margin-bottom: 0; /* 移除底部外边距 */
}
.artwork-content {
gap: 1rem;
transition: transform 0.3s ease, opacity 0.3s ease;
padding-bottom: 0; /* 确保没有额外的底部内边距 */
}
.artwork-content.swiping {
transform: scale(0.98);
opacity: 0.9;
} }
} }
/* 滑动提示样式 */
.swipe-hint {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 1rem 2rem;
border-radius: 2rem;
font-size: 0.875rem;
font-weight: 500;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
pointer-events: none;
backdrop-filter: blur(10px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.swipe-hint.hint-visible {
opacity: 1;
visibility: visible;
}
.hint-content {
display: flex;
align-items: center;
gap: 0.5rem;
}
.hint-text {
white-space: nowrap;
}
</style> </style>