重构添加背景页手机端右边栏布局

This commit is contained in:
jingrow 2026-01-21 19:59:21 +08:00
parent b0a213ce4a
commit 630e441a97
2 changed files with 275 additions and 27 deletions

View File

@ -1113,12 +1113,12 @@
"Add custom background color to images. Free online tool to add background to transparent images. Supports JPG, PNG, WebP formats.": "为图片添加自定义背景颜色。免费在线工具,可为透明背景图片添加背景。支持 JPG、PNG、WebP 格式。",
"add background, background color, image background, transparent background, online background tool, image processing, background editor, free tool": "添加背景、背景颜色、图片背景、透明背景、在线背景工具、图片处理、背景编辑器、免费工具",
"Background Color": "背景颜色",
"Add to favorites": "添加到收藏",
"Remove from favorites": "从收藏中移除",
"Add to favorites": "添加到收藏",
"Remove from favorites": "从收藏中移除",
"Favorite Colors": "收藏的颜色",
"Color already in favorites": "颜色已在收藏",
"Color already in favorites": "颜色已在收藏",
"Maximum 12 favorite colors allowed. Please remove one first.": "最多允许12个收藏颜色请先移除一个",
"Color added to favorites": "颜色已添加到收藏",
"Color added to favorites": "颜色已添加到收藏",
"Color Tones": "色调",
"Common Colors": "常用颜色",
"Apply Background": "应用背景",

View File

@ -50,6 +50,13 @@
>
<i class="fa fa-refresh"></i>
</button>
<button
class="toolbar-btn mobile-color-picker-btn"
@click="toggleMobileColorPicker"
:title="t('Color Picker')"
>
<i class="fa fa-palette"></i>
</button>
</div>
</div>
@ -157,8 +164,103 @@
</div>
</div>
<!-- Right Sidebar for Common Colors and Color Picker -->
<div v-if="uploadedImage" class="right-sidebar">
<!-- Mobile Bottom Sheet Color Picker -->
<transition name="slide-up">
<div
v-if="uploadedImage && showMobileColorPicker"
class="mobile-color-sheet-overlay"
@click="closeMobileColorPicker"
>
<div
class="mobile-color-sheet"
@click.stop
>
<div class="sheet-handle"></div>
<div class="sheet-header">
<h4>{{ t('Background Color') }}</h4>
<button
class="sheet-close-btn"
@click="closeMobileColorPicker"
:title="t('Close')"
>
<i class="fa fa-times"></i>
</button>
</div>
<div class="sheet-content">
<div class="color-picker-section">
<div class="color-picker-container">
<input
v-model="backgroundColor"
type="color"
class="sidebar-color-picker"
@input="onColorChange"
/>
<div class="hex-input-wrapper">
<input
v-model="backgroundColor"
type="text"
class="sidebar-hex-input"
placeholder="#FFFFFF"
@input="onHexInputChange"
@blur="onHexInputBlur"
/>
<button
class="add-favorite-btn"
@click="addToFavorites"
:title="t('Add to favorites')"
:disabled="favoriteColors.length >= MAX_FAVORITE_COLORS"
>
<i class="fa fa-star"></i>
</button>
</div>
</div>
</div>
<div v-if="favoriteColors.length > 0" class="favorite-colors-section">
<h4 class="section-title">{{ t('Favorite Colors') }}</h4>
<div class="favorite-colors-grid">
<div
v-for="(color, index) in favoriteColors"
:key="index"
class="favorite-color-item"
:class="{ 'active': color === backgroundColor }"
:style="{ backgroundColor: color }"
@click="selectCommonColor(color)"
:title="color"
>
<button
type="button"
class="remove-favorite-btn"
@click.stop="removeFavoriteColor(color)"
:title="t('Remove from favorites')"
:aria-label="t('Remove from favorites')"
></button>
</div>
</div>
</div>
<div class="common-colors-section">
<h4 class="section-title">{{ t('Common Colors') }}</h4>
<div class="common-colors-grid">
<div
v-for="(color, index) in commonColors"
:key="index"
class="common-color-item"
:class="{ 'active': color === backgroundColor }"
:style="{ backgroundColor: color }"
@click="selectCommonColor(color)"
:title="color"
>
</div>
</div>
</div>
</div>
</div>
</div>
</transition>
<!-- Right Sidebar for Common Colors and Color Picker (Desktop Only) -->
<div v-if="uploadedImage" class="right-sidebar desktop-only">
<div class="sidebar-content">
<div class="color-picker-section">
<h4 class="section-title">{{ t('Background Color') }}</h4>
@ -501,6 +603,7 @@ const currentHistoryIndex = ref<number>(-1)
const isDragging = ref(false)
const dragCounter = ref(0)
const processing = ref(false)
const showMobileColorPicker = ref(false)
let fabricCanvas: Canvas | null = null
const adjustCanvasSize = () => {
@ -513,8 +616,11 @@ const adjustCanvasSize = () => {
const previewSection = container.closest('.preview-section') as HTMLElement
if (!previewSection) return
// Check if mobile
const isMobile = window.innerWidth <= 768
const previewRect = previewSection.getBoundingClientRect()
const padding = 48
const padding = isMobile ? 24 : 48
const maxAvailableWidth = Math.max(0, previewRect.width - padding)
const maxAvailableHeight = Math.max(0, previewRect.height - padding)
@ -695,6 +801,8 @@ onUnmounted(() => {
fabricCanvas.dispose()
fabricCanvas = null
}
// Restore body scroll
document.body.style.overflow = ''
})
const triggerFileInput = () => {
@ -1056,11 +1164,34 @@ const onHexInputBlur = () => {
const selectShade = (color: string) => {
backgroundColor.value = color
applyBackgroundSilent()
// Auto-close mobile color picker on mobile
if (window.innerWidth <= 768) {
closeMobileColorPicker()
}
}
const selectCommonColor = (color: string) => {
backgroundColor.value = color
applyBackgroundSilent()
// Auto-close mobile color picker on mobile
if (window.innerWidth <= 768) {
closeMobileColorPicker()
}
}
const toggleMobileColorPicker = () => {
showMobileColorPicker.value = !showMobileColorPicker.value
// Prevent body scroll when sheet is open
if (showMobileColorPicker.value) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
}
const closeMobileColorPicker = () => {
showMobileColorPicker.value = false
document.body.style.overflow = ''
}
const debouncedApplyBackground = () => {
@ -1857,6 +1988,127 @@ const removeHistoryItem = (index: number) => {
overflow: hidden;
}
/* Hide desktop sidebar on mobile */
.desktop-only {
display: flex;
}
/* Mobile Color Picker Button - Hidden on desktop */
.mobile-color-picker-btn {
display: none;
}
/* Mobile Bottom Sheet */
.mobile-color-sheet-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 1000;
display: flex;
align-items: flex-end;
backdrop-filter: blur(2px);
}
.mobile-color-sheet {
width: 100%;
max-height: 70vh;
background: white;
border-radius: 20px 20px 0 0;
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
overflow: hidden;
}
.sheet-handle {
width: 40px;
height: 4px;
background: #cbd5e1;
border-radius: 2px;
margin: 12px auto 8px;
flex-shrink: 0;
}
.sheet-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
border-bottom: 1px solid #e5e7eb;
flex-shrink: 0;
h4 {
font-size: 16px;
font-weight: 600;
color: #1f2937;
margin: 0;
}
}
.sheet-close-btn {
width: 32px;
height: 32px;
border: none;
border-radius: 8px;
background: #f3f4f6;
color: #6b7280;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
padding: 0;
i {
font-size: 16px;
}
&:hover {
background: #e5e7eb;
color: #374151;
}
&:active {
transform: scale(0.95);
}
}
.sheet-content {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 24px;
-webkit-overflow-scrolling: touch;
}
/* Slide up animation */
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-up-enter-active .mobile-color-sheet,
.slide-up-leave-active .mobile-color-sheet {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-up-enter-from,
.slide-up-leave-to {
opacity: 0;
}
.slide-up-enter-from .mobile-color-sheet,
.slide-up-leave-to .mobile-color-sheet {
transform: translateY(100%);
}
.slide-up-enter-to .mobile-color-sheet,
.slide-up-leave-from .mobile-color-sheet {
transform: translateY(0);
}
.sidebar-content {
flex: 1;
overflow-y: auto;
@ -2282,6 +2534,16 @@ const removeHistoryItem = (index: number) => {
}
@media (max-width: 768px) {
/* Hide desktop sidebar on mobile */
.desktop-only {
display: none !important;
}
/* Show mobile color picker button */
.mobile-color-picker-btn {
display: flex !important;
}
.page-header {
padding: 10px 12px;
flex-wrap: wrap;
@ -2320,10 +2582,7 @@ const removeHistoryItem = (index: number) => {
.page-content {
padding: 8px 12px;
padding-right: 12px !important;
}
.page-content.has-sidebar {
padding-bottom: 220px; /* Make space for bottom sidebar */
padding-bottom: 12px !important; /* Remove bottom padding since no fixed sidebar */
}
.upload-section {
@ -2365,25 +2624,14 @@ const removeHistoryItem = (index: number) => {
}
.preview-section {
padding: 0;
padding: 12px;
min-height: 400px;
}
.canvas-container {
padding: 20px;
padding-right: 20px;
}
.right-sidebar {
position: fixed;
width: 100%;
height: 200px;
top: auto;
bottom: 0;
left: 0;
right: 0;
border-left: none;
border-top: 1px solid #e5e7eb;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
padding: 0;
margin: 0 auto; /* Center horizontally */
display: block; /* Ensure proper centering */
}
.hex-input-wrapper {