优化商品图片显示
This commit is contained in:
parent
08bdd86007
commit
7e1e542a6c
@ -20,22 +20,34 @@
|
|||||||
<!-- 产品图片 -->
|
<!-- 产品图片 -->
|
||||||
<div class="product-images">
|
<div class="product-images">
|
||||||
<div class="main-image">
|
<div class="main-image">
|
||||||
<div class="image-placeholder">
|
<img
|
||||||
<svg viewBox="0 0 400 400" width="100%" height="400">
|
v-if="product.mainImage"
|
||||||
<rect width="400" height="400" :fill="product.color" opacity="0.2"/>
|
:src="getFileAccessHttpUrl(product.mainImage)"
|
||||||
<circle cx="200" cy="200" r="80" :fill="product.color" opacity="0.5"/>
|
:alt="product.productName || product.name"
|
||||||
<text x="200" y="210" text-anchor="middle" :fill="product.color"
|
class="product-main-image"
|
||||||
font-size="24" font-weight="bold">{{ (product.productName || product.name || '产品').substring(0, 2) }}</text>
|
@click="openImagePreview(getAllProductImages(), 0)"
|
||||||
|
@error="handleImageError"
|
||||||
|
/>
|
||||||
|
<div v-else class="image-placeholder">
|
||||||
|
<svg width="120" height="120" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
||||||
|
<circle cx="8.5" cy="8.5" r="1.5"/>
|
||||||
|
<polyline points="21,15 16,10 5,21"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="thumbnail-list">
|
<div v-if="getAllProductImages().length > 1" class="thumbnail-list">
|
||||||
<div v-for="i in 4" :key="i" class="thumbnail" :class="{ active: i === 1 }">
|
<div
|
||||||
<svg viewBox="0 0 100 100" width="100%" height="80">
|
v-for="(image, index) in getAllProductImages().slice(0, 5)"
|
||||||
<rect width="100" height="100" :fill="product.color" opacity="0.3"/>
|
:key="index"
|
||||||
<text x="50" y="55" text-anchor="middle" :fill="product.color"
|
class="thumbnail"
|
||||||
font-size="12" font-weight="bold">{{ i }}</text>
|
:class="{ active: index === currentImageIndex }"
|
||||||
</svg>
|
@click="openImagePreview(getAllProductImages(), index)"
|
||||||
|
>
|
||||||
|
<img :src="getFileAccessHttpUrl(image)" :alt="`${product.productName || product.name} - 图片${index + 1}`" />
|
||||||
|
<div class="thumb-overlay">
|
||||||
|
<el-icon><ZoomIn /></el-icon>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -266,13 +278,71 @@
|
|||||||
<el-icon class="is-loading" size="50"><Loading /></el-icon>
|
<el-icon class="is-loading" size="50"><Loading /></el-icon>
|
||||||
<p>加载中...</p>
|
<p>加载中...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片预览对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="imagePreviewVisible"
|
||||||
|
title="图片预览"
|
||||||
|
width="80%"
|
||||||
|
top="5vh"
|
||||||
|
class="image-preview-dialog"
|
||||||
|
@close="closeImagePreview"
|
||||||
|
>
|
||||||
|
<div v-if="previewImages.length > 0" class="image-preview-container">
|
||||||
|
<div class="preview-main">
|
||||||
|
<img
|
||||||
|
:src="previewImages[currentImageIndex]"
|
||||||
|
:alt="`预览图片 ${currentImageIndex + 1}`"
|
||||||
|
class="preview-image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片导航 -->
|
||||||
|
<div v-if="previewImages.length > 1" class="preview-navigation">
|
||||||
|
<el-button
|
||||||
|
:disabled="currentImageIndex === 0"
|
||||||
|
@click="currentImageIndex--"
|
||||||
|
circle
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<el-icon><ArrowLeft /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<span class="image-counter">
|
||||||
|
{{ currentImageIndex + 1 }} / {{ previewImages.length }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
:disabled="currentImageIndex === previewImages.length - 1"
|
||||||
|
@click="currentImageIndex++"
|
||||||
|
circle
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<el-icon><ArrowRight /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 缩略图导航 -->
|
||||||
|
<div v-if="previewImages.length > 1" class="preview-thumbnails">
|
||||||
|
<div
|
||||||
|
v-for="(image, index) in previewImages"
|
||||||
|
:key="index"
|
||||||
|
class="preview-thumb"
|
||||||
|
:class="{ active: index === currentImageIndex }"
|
||||||
|
@click="currentImageIndex = index"
|
||||||
|
>
|
||||||
|
<img :src="image" :alt="`缩略图 ${index + 1}`" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useMainStore } from '../stores'
|
import { useMainStore } from '../stores'
|
||||||
import { User, StarFilled, Loading } from '@element-plus/icons-vue'
|
import { User, StarFilled, Loading, ZoomIn, ArrowLeft, ArrowRight, Picture } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { productApi } from '../api/product'
|
import { productApi } from '../api/product'
|
||||||
|
|
||||||
@ -285,6 +355,11 @@ const product = ref<any>(null)
|
|||||||
const quantity = ref(1)
|
const quantity = ref(1)
|
||||||
const activeTab = ref('details')
|
const activeTab = ref('details')
|
||||||
|
|
||||||
|
// 图片预览相关
|
||||||
|
const imagePreviewVisible = ref(false)
|
||||||
|
const previewImages = ref<string[]>([])
|
||||||
|
const currentImageIndex = ref(0)
|
||||||
|
|
||||||
// 产品分类
|
// 产品分类
|
||||||
const categories = ref([
|
const categories = ref([
|
||||||
{ id: 'handicrafts', name: '手工艺品' },
|
{ id: 'handicrafts', name: '手工艺品' },
|
||||||
@ -464,6 +539,88 @@ const getRandomColor = () => {
|
|||||||
const colors = ['#8b4513', '#2d5016']
|
const colors = ['#8b4513', '#2d5016']
|
||||||
return colors[Math.floor(Math.random() * colors.length)]
|
return colors[Math.floor(Math.random() * colors.length)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取产品所有图片(主图+产品图片)
|
||||||
|
const getAllProductImages = () => {
|
||||||
|
const images = []
|
||||||
|
|
||||||
|
// 添加主图
|
||||||
|
if (product.value?.mainImage) {
|
||||||
|
images.push(product.value.mainImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加产品图片
|
||||||
|
if (product.value?.images) {
|
||||||
|
const productImages = getProductImages(product.value.images)
|
||||||
|
images.push(...productImages)
|
||||||
|
}
|
||||||
|
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取产品图片数组
|
||||||
|
const getProductImages = (imagesStr: string) => {
|
||||||
|
if (!imagesStr) return []
|
||||||
|
return imagesStr.split(',').map(img => img.trim()).filter(img => img)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件访问URL的工具函数
|
||||||
|
const getFileAccessHttpUrl = (filePath: string) => {
|
||||||
|
if (!filePath) {
|
||||||
|
console.warn('getFileAccessHttpUrl: 文件路径为空')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('getFileAccessHttpUrl: 原始路径 =', filePath)
|
||||||
|
|
||||||
|
// 如果已经是完整的HTTP URL,直接返回
|
||||||
|
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
|
||||||
|
console.log('getFileAccessHttpUrl: 完整URL,直接返回 =', filePath)
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是blob URL,直接返回
|
||||||
|
if (filePath.startsWith('blob:')) {
|
||||||
|
console.log('getFileAccessHttpUrl: blob URL,直接返回 =', filePath)
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理相对路径,转换为完整的静态资源访问路径
|
||||||
|
const baseUrl = 'http://localhost:8080/jeecg-boot/sys/common/static/'
|
||||||
|
|
||||||
|
// 移除开头的斜杠(如果有)
|
||||||
|
let cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath
|
||||||
|
|
||||||
|
// 如果路径不包含文件夹结构,为产品图片添加默认路径
|
||||||
|
if (!cleanPath.includes('/')) {
|
||||||
|
cleanPath = 'product/' + cleanPath
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalUrl = baseUrl + cleanPath
|
||||||
|
console.log('getFileAccessHttpUrl: 最终URL =', finalUrl)
|
||||||
|
return finalUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片加载错误处理
|
||||||
|
const handleImageError = (event: Event) => {
|
||||||
|
const img = event.target as HTMLImageElement
|
||||||
|
console.error('图片加载失败:', img.src)
|
||||||
|
// 可以设置默认图片或隐藏图片
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开图片预览
|
||||||
|
const openImagePreview = (images: string[], index: number = 0) => {
|
||||||
|
previewImages.value = images.map(img => getFileAccessHttpUrl(img))
|
||||||
|
currentImageIndex.value = index
|
||||||
|
imagePreviewVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭图片预览
|
||||||
|
const closeImagePreview = () => {
|
||||||
|
imagePreviewVisible.value = false
|
||||||
|
previewImages.value = []
|
||||||
|
currentImageIndex.value = 0
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -516,38 +673,122 @@ const getRandomColor = () => {
|
|||||||
.main-image {
|
.main-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border-radius: 8px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
cursor: pointer;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(45deg, transparent 49%, rgba(255,255,255,0.1) 50%, transparent 51%);
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-main-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 450px;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-main-image:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 450px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: var(--background-light);
|
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||||
|
border: 2px dashed #dee2e6;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-placeholder svg {
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image:hover .image-placeholder svg {
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail-list {
|
.thumbnail-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
width: 80px;
|
width: 90px;
|
||||||
height: 80px;
|
height: 90px;
|
||||||
border-radius: 4px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid transparent;
|
border: 3px solid transparent;
|
||||||
transition: border-color 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail.active,
|
.thumbnail img {
|
||||||
.thumbnail:hover {
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail:hover img {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail.active {
|
||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 4px 12px rgba(var(--primary-color-rgb), 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail:hover {
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail:hover .thumb-overlay {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 产品信息 */
|
/* 产品信息 */
|
||||||
@ -981,4 +1222,78 @@ const getRandomColor = () => {
|
|||||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图片预览对话框样式 */
|
||||||
|
.image-preview-dialog {
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-main {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 60vh;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-navigation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-counter {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-thumbnails {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-thumb {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-thumb.active,
|
||||||
|
.preview-thumb:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -63,12 +63,18 @@
|
|||||||
@click="$router.push(`/product/${product.id}`)"
|
@click="$router.push(`/product/${product.id}`)"
|
||||||
>
|
>
|
||||||
<div class="product-image">
|
<div class="product-image">
|
||||||
<div class="image-placeholder">
|
<img
|
||||||
<svg viewBox="0 0 250 200" width="100%" height="200">
|
v-if="product.mainImage && getFileAccessHttpUrl(product.mainImage)"
|
||||||
<rect width="250" height="200" :fill="product.color" opacity="0.2"/>
|
:src="getFileAccessHttpUrl(product.mainImage)"
|
||||||
<circle cx="125" cy="100" r="40" :fill="product.color" opacity="0.5"/>
|
:alt="product.name"
|
||||||
<text x="125" y="110" text-anchor="middle" :fill="product.color"
|
class="product-cover-image"
|
||||||
font-size="14" font-weight="bold">{{ product.productName ? product.productName.substring(0, 2) : '产品' }}</text>
|
@error="handleImageError"
|
||||||
|
/>
|
||||||
|
<div v-else class="image-placeholder">
|
||||||
|
<svg width="80" height="80" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
||||||
|
<circle cx="8.5" cy="8.5" r="1.5"/>
|
||||||
|
<polyline points="21,15 16,10 5,21"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-badges">
|
<div class="product-badges">
|
||||||
@ -300,6 +306,34 @@ const onCategoryChange = () => {
|
|||||||
getProducts()
|
getProducts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理图片路径
|
||||||
|
const getFileAccessHttpUrl = (path: string) => {
|
||||||
|
if (!path) return ''
|
||||||
|
|
||||||
|
// 如果已经是完整的HTTP URL或blob URL,直接返回
|
||||||
|
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('blob:')) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是相对路径,转换为静态资源URL
|
||||||
|
if (path.startsWith('/')) {
|
||||||
|
return `http://localhost:8080/jeecg-boot/sys/common/static${path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为产品图片添加默认路径
|
||||||
|
return `http://localhost:8080/jeecg-boot/sys/common/static/${path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片加载错误处理
|
||||||
|
const handleImageError = (event: Event) => {
|
||||||
|
const img = event.target as HTMLImageElement
|
||||||
|
img.style.display = 'none'
|
||||||
|
const placeholder = img.nextElementSibling as HTMLElement
|
||||||
|
if (placeholder) {
|
||||||
|
placeholder.style.display = 'flex'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCategories()
|
getCategories()
|
||||||
@ -405,7 +439,22 @@ onMounted(() => {
|
|||||||
.product-image {
|
.product-image {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 250px;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-cover-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-cover-image:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
@ -414,7 +463,17 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: var(--background-light);
|
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||||
|
border: 2px dashed #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-placeholder svg {
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image:hover .image-placeholder svg {
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-badges {
|
.product-badges {
|
||||||
|
Loading…
Reference in New Issue
Block a user