重构添加背景页手机端右边栏布局
This commit is contained in:
parent
b0a213ce4a
commit
630e441a97
@ -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": "应用背景",
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user