Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-03-15 23:47:37 +03:00
parent c080a63ed1
commit 2a46b2942d
2 changed files with 368 additions and 96 deletions

View File

@@ -275,8 +275,8 @@
swipeable swipeable
navigation navigation
arrows arrows
height="560px" height="70vh"
class="product-card-carousel rounded-borders bg-grey-2" class="product-card-carousel rounded-borders"
> >
<q-carousel-slide <q-carousel-slide
v-for="(img, idx) in productCardImages" v-for="(img, idx) in productCardImages"
@@ -312,7 +312,7 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog v-model="productImageFullscreenDialog" maximized> <q-dialog v-model="productImageFullscreenDialog" maximized @hide="onFullscreenMouseUp">
<q-card class="image-fullscreen-dialog"> <q-card class="image-fullscreen-dialog">
<q-card-section class="row items-center q-pb-sm"> <q-card-section class="row items-center q-pb-sm">
<div class="text-h6">Urun Fotografi</div> <div class="text-h6">Urun Fotografi</div>
@@ -321,14 +321,38 @@
</q-card-section> </q-card-section>
<q-separator /> <q-separator />
<q-card-section class="image-fullscreen-body"> <q-card-section class="image-fullscreen-body">
<div class="image-fullscreen-stage cursor-pointer" @click="toggleFullscreenImageZoom"> <q-carousel
<q-img v-if="fullscreenImages.length"
:src="productImageFullscreenSrc" v-model="productImageFullscreenSlide"
fit="contain" animated
class="image-fullscreen-img" swipeable
:style="fullscreenImageStyle" navigation
/> arrows
</div> height="calc(100vh - 120px)"
class="image-fullscreen-carousel"
@update:model-value="onFullscreenSlideChange"
>
<q-carousel-slide
v-for="(img, idx) in fullscreenImages"
:key="'full-img-' + idx"
:name="idx"
class="column no-wrap flex-center"
>
<div
class="image-fullscreen-stage"
@wheel.prevent="onFullscreenWheel"
@mousedown="onFullscreenMouseDown"
@dblclick="toggleFullscreenImageZoom"
>
<q-img
:src="img"
fit="contain"
class="image-fullscreen-img"
:style="fullscreenImageStyle"
/>
</div>
</q-carousel-slide>
</q-carousel>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-dialog> </q-dialog>
@@ -401,7 +425,15 @@ const productCardImages = ref([])
const productCardSlide = ref(0) const productCardSlide = ref(0)
const productImageFullscreenDialog = ref(false) const productImageFullscreenDialog = ref(false)
const productImageFullscreenSrc = ref('') const productImageFullscreenSrc = ref('')
const productImageFullscreenSlide = ref(0)
const productImageFullscreenZoom = ref(1) const productImageFullscreenZoom = ref(1)
const productImageFullscreenOffsetX = ref(0)
const productImageFullscreenOffsetY = ref(0)
const productImageFullscreenDragging = ref(false)
const productImageFullscreenDragStartX = ref(0)
const productImageFullscreenDragStartY = ref(0)
const productImageFullscreenDragOriginX = ref(0)
const productImageFullscreenDragOriginY = ref(0)
const IMAGE_LIST_CONCURRENCY = 8 const IMAGE_LIST_CONCURRENCY = 8
const FILTER_OPTIONS_CACHE_TTL_MS = 60 * 1000 const FILTER_OPTIONS_CACHE_TTL_MS = 60 * 1000
const FILTER_OPTIONS_DEBOUNCE_MS = 250 const FILTER_OPTIONS_DEBOUNCE_MS = 250
@@ -438,11 +470,19 @@ const gridHeaderHeight = computed(() =>
showGridHeader.value ? '56px' : '0px' showGridHeader.value ? '56px' : '0px'
) )
const fullscreenImageStyle = computed(() => ({ const fullscreenImageStyle = computed(() => ({
transform: `scale(${productImageFullscreenZoom.value})`, transform: `translate(${productImageFullscreenOffsetX.value}px, ${productImageFullscreenOffsetY.value}px) scale(${productImageFullscreenZoom.value})`,
transformOrigin: 'center center', transformOrigin: 'center center',
transition: 'transform 0.15s ease-out' transition: productImageFullscreenDragging.value ? 'none' : 'transform 0.1s ease-out',
cursor: productImageFullscreenZoom.value > 1 ? (productImageFullscreenDragging.value ? 'grabbing' : 'grab') : 'zoom-in'
})) }))
const fullscreenImages = computed(() => {
const arr = Array.isArray(productCardImages.value) ? productCardImages.value : []
if (arr.length) return arr
const single = String(productImageFullscreenSrc.value || '').trim()
return single ? [single] : []
})
function emptySizeTotals() { function emptySizeTotals() {
const map = {} const map = {}
for (const s of sizeLabels.value) map[s] = 0 for (const s of sizeLabels.value) map[s] = 0
@@ -587,6 +627,25 @@ function resolveProductImageUrl(item) {
return { contentUrl, publicUrl, thumbUrl, fullUrl } return { contentUrl, publicUrl, thumbUrl, fullUrl }
} }
function extractImageOrder(fileName, fallbackIndex) {
const name = String(fileName || '').trim()
const m = name.match(/\((\d+)\)(?=\.[a-z0-9]+$)/i)
if (m) return Number(m[1] || 999999)
const m2 = name.match(/[-_ ](\d+)(?=\.[a-z0-9]+$)/i)
if (m2) return Number(m2[1] || 999999)
return 1000000 + Number(fallbackIndex || 0)
}
function sortImagesForDisplay(list) {
return (Array.isArray(list) ? list : [])
.map((item, idx) => ({ item, idx, order: extractImageOrder(item?.file_name || item?.FileName || '', idx) }))
.sort((a, b) => {
if (a.order !== b.order) return a.order - b.order
return a.idx - b.idx
})
.map((x) => x.item)
}
async function resolveProductImageUrlForCarousel(item) { async function resolveProductImageUrlForCarousel(item) {
const resolved = resolveProductImageUrl(item) const resolved = resolveProductImageUrl(item)
const contentUrl = String(resolved.contentUrl || '').trim() const contentUrl = String(resolved.contentUrl || '').trim()
@@ -674,12 +733,12 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
} }
} }
} }
const list = productImageListByCode.value[listKey] || [] const list = sortImagesForDisplay(productImageListByCode.value[listKey] || [])
const first = list[0] || null const first = list[0] || null
const resolved = resolveProductImageUrl(first) const resolved = resolveProductImageUrl(first)
productImageCache.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.contentUrl || resolved.publicUrl || '' productImageCache.value[key] = resolved.fullUrl || resolved.publicUrl || resolved.thumbUrl || resolved.contentUrl || ''
productImageFallbackByKey.value[key] = resolved.contentUrl || '' productImageFallbackByKey.value[key] = resolved.contentUrl || ''
} catch (err) { } catch (err) {
console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err }) console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err })
@@ -1153,8 +1212,9 @@ async function openProductCard(grp1, grp2) {
} }
} }
const sortedList = sortImagesForDisplay(list)
const imageCandidates = await Promise.all( const imageCandidates = await Promise.all(
list.map((item) => resolveProductImageUrlForCarousel(item)) sortedList.map((item) => resolveProductImageUrlForCarousel(item))
) )
const images = imageCandidates.filter((x) => String(x || '').trim() !== '') const images = imageCandidates.filter((x) => String(x || '').trim() !== '')
console.info('[ProductStockByAttributes][openProductCard] render', { console.info('[ProductStockByAttributes][openProductCard] render', {
@@ -1196,7 +1256,12 @@ function openProductImageFullscreen(src) {
const value = String(src || '').trim() const value = String(src || '').trim()
if (!value) return if (!value) return
productImageFullscreenSrc.value = value productImageFullscreenSrc.value = value
const idx = Math.max(0, fullscreenImages.value.findIndex((x) => String(x || '').trim() === value))
productImageFullscreenSlide.value = idx
productImageFullscreenZoom.value = 1 productImageFullscreenZoom.value = 1
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
productImageFullscreenDragging.value = false
productImageFullscreenDialog.value = true productImageFullscreenDialog.value = true
} }
@@ -1204,7 +1269,57 @@ function toggleFullscreenImageZoom() {
const current = Number(productImageFullscreenZoom.value || 1) const current = Number(productImageFullscreenZoom.value || 1)
if (current < 1.5) productImageFullscreenZoom.value = 1.8 if (current < 1.5) productImageFullscreenZoom.value = 1.8
else if (current < 2.3) productImageFullscreenZoom.value = 2.6 else if (current < 2.3) productImageFullscreenZoom.value = 2.6
else if (current < 3.2) productImageFullscreenZoom.value = 3.2
else productImageFullscreenZoom.value = 1 else productImageFullscreenZoom.value = 1
if (productImageFullscreenZoom.value <= 1) {
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
}
}
function onFullscreenWheel(evt) {
if (!evt) return
evt.preventDefault()
const delta = Number(evt.deltaY || 0)
if (productImageFullscreenZoom.value > 1.01 && !evt.ctrlKey) {
productImageFullscreenOffsetY.value -= delta * 0.45
return
}
const current = Number(productImageFullscreenZoom.value || 1)
const next = delta < 0 ? current + 0.2 : current - 0.2
productImageFullscreenZoom.value = Math.min(4, Math.max(1, Number(next.toFixed(2))))
if (productImageFullscreenZoom.value <= 1) {
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
}
}
function onFullscreenMouseDown(evt) {
if (productImageFullscreenZoom.value <= 1) return
productImageFullscreenDragging.value = true
productImageFullscreenDragStartX.value = Number(evt?.clientX || 0)
productImageFullscreenDragStartY.value = Number(evt?.clientY || 0)
productImageFullscreenDragOriginX.value = Number(productImageFullscreenOffsetX.value || 0)
productImageFullscreenDragOriginY.value = Number(productImageFullscreenOffsetY.value || 0)
}
function onFullscreenMouseMove(evt) {
if (!productImageFullscreenDragging.value) return
const dx = Number(evt?.clientX || 0) - productImageFullscreenDragStartX.value
const dy = Number(evt?.clientY || 0) - productImageFullscreenDragStartY.value
productImageFullscreenOffsetX.value = productImageFullscreenDragOriginX.value + dx
productImageFullscreenOffsetY.value = productImageFullscreenDragOriginY.value + dy
}
function onFullscreenMouseUp() {
productImageFullscreenDragging.value = false
}
function onFullscreenSlideChange() {
productImageFullscreenZoom.value = 1
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
productImageFullscreenDragging.value = false
} }
function resetForm() { function resetForm() {
@@ -1236,16 +1351,24 @@ function resetForm() {
productCardSlide.value = 0 productCardSlide.value = 0
productImageFullscreenDialog.value = false productImageFullscreenDialog.value = false
productImageFullscreenSrc.value = '' productImageFullscreenSrc.value = ''
productImageFullscreenSlide.value = 0
productImageFullscreenZoom.value = 1 productImageFullscreenZoom.value = 1
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
productImageFullscreenDragging.value = false
filterOptionsCache.value = {} filterOptionsCache.value = {}
void loadFilterOptions(true) void loadFilterOptions(true)
} }
onMounted(() => { onMounted(() => {
void loadFilterOptions(true) void loadFilterOptions(true)
window.addEventListener('mousemove', onFullscreenMouseMove)
window.addEventListener('mouseup', onFullscreenMouseUp)
}) })
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('mousemove', onFullscreenMouseMove)
window.removeEventListener('mouseup', onFullscreenMouseUp)
if (filterOptionsDebounceTimer) { if (filterOptionsDebounceTimer) {
clearTimeout(filterOptionsDebounceTimer) clearTimeout(filterOptionsDebounceTimer)
filterOptionsDebounceTimer = null filterOptionsDebounceTimer = null
@@ -1637,14 +1760,16 @@ onUnmounted(() => {
} }
.product-card-dialog { .product-card-dialog {
background: #fffef9; --pc-media-h: min(70vh, 900px);
--pc-media-w: min(74vw, 1220px);
background: #f6f8fc;
} }
.product-card-stock { .product-card-stock {
background: #f8f5e7; background: linear-gradient(180deg, #eef3fb 0%, #f8fbff 100%);
border: 1px solid #e2d9b6; border: 1px solid #c8d6ec;
border-radius: 10px; border-radius: 12px;
padding: 12px; padding: 14px;
} }
.stock-size-grid { .stock-size-grid {
@@ -1654,9 +1779,9 @@ onUnmounted(() => {
} }
.stock-size-chip { .stock-size-chip {
border: 1px solid #d8cca6; border: 1px solid #d7e2f2;
border-radius: 8px; border-radius: 10px;
background: #fff; background: #ffffff;
padding: 6px 8px; padding: 6px 8px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -1669,8 +1794,8 @@ onUnmounted(() => {
.product-card-content { .product-card-content {
display: grid; display: grid;
grid-template-columns: minmax(360px, 1fr) 420px; grid-template-columns: minmax(360px, 420px) minmax(760px, 1fr);
gap: 12px; gap: 14px;
align-items: stretch; align-items: stretch;
justify-content: start; justify-content: start;
} }
@@ -1678,16 +1803,18 @@ onUnmounted(() => {
.product-card-images { .product-card-images {
grid-column: 2; grid-column: 2;
grid-row: 1; grid-row: 1;
min-height: 560px; min-height: var(--pc-media-h);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: stretch;
justify-content: flex-start; justify-content: flex-start;
} }
.product-card-carousel { .product-card-carousel {
width: 420px; width: var(--pc-media-w);
max-width: 100%; max-width: 100%;
background: linear-gradient(180deg, #edf2fb 0%, #e4ebf9 100%);
border: 1px solid #c8d6ec;
} }
.dialog-image { .dialog-image {
@@ -1696,27 +1823,27 @@ onUnmounted(() => {
} }
.dialog-image-stage { .dialog-image-stage {
width: 420px; width: var(--pc-media-w);
max-width: 100%; max-width: 100%;
height: 560px; height: var(--pc-media-h);
overflow: hidden; overflow: hidden;
border-radius: 8px; border-radius: 10px;
background: #f7f4e9; background: linear-gradient(180deg, #edf2fb 0%, #e4ebf9 100%);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.dialog-image-empty { .dialog-image-empty {
width: 420px; width: var(--pc-media-w);
max-width: 100%; max-width: 100%;
height: 560px; height: var(--pc-media-h);
border: 1px dashed #cabf9a; border: 1px dashed #9db5de;
border-radius: 8px; border-radius: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #faf7ee; background: #f2f6fd;
} }
.image-fullscreen-dialog { .image-fullscreen-dialog {
@@ -1730,6 +1857,10 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
} }
.image-fullscreen-carousel {
width: min(98vw, 1500px);
}
.image-fullscreen-stage { .image-fullscreen-stage {
width: min(96vw, 1400px); width: min(96vw, 1400px);
height: calc(100vh - 120px); height: calc(100vh - 120px);
@@ -1749,11 +1880,11 @@ onUnmounted(() => {
.product-card-fields { .product-card-fields {
grid-column: 1; grid-column: 1;
grid-row: 1; grid-row: 1;
border: 1px solid #e2d9b6; border: 1px solid #c8d6ec;
border-radius: 10px; border-radius: 12px;
background: #fff; background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
padding: 10px; padding: 12px;
height: 560px; height: var(--pc-media-h);
overflow: auto; overflow: auto;
} }
@@ -1761,8 +1892,8 @@ onUnmounted(() => {
display: grid; display: grid;
grid-template-columns: 150px 1fr; grid-template-columns: 150px 1fr;
gap: 8px; gap: 8px;
padding: 7px 0; padding: 8px 0;
border-bottom: 1px solid #f0ead7; border-bottom: 1px solid #e4ebf7;
font-size: 13px; font-size: 13px;
} }
@@ -1771,7 +1902,7 @@ onUnmounted(() => {
} }
.field-row .k { .field-row .k {
color: #5a4f2c; color: var(--q-primary, #1976d2);
font-weight: 700; font-weight: 700;
} }
@@ -1801,6 +1932,11 @@ onUnmounted(() => {
.product-card-fields { .product-card-fields {
height: auto; height: auto;
} }
.product-card-dialog {
--pc-media-h: min(62vh, 760px);
--pc-media-w: min(96vw, 900px);
}
} }
.order-sub-header.level-2 .sub-right .top-total { .order-sub-header.level-2 .sub-right .top-total {

View File

@@ -273,8 +273,8 @@
swipeable swipeable
navigation navigation
arrows arrows
height="560px" height="70vh"
class="product-card-carousel rounded-borders bg-grey-2" class="product-card-carousel rounded-borders"
> >
<q-carousel-slide <q-carousel-slide
v-for="(img, idx) in productCardImages" v-for="(img, idx) in productCardImages"
@@ -310,7 +310,7 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog v-model="productImageFullscreenDialog" maximized> <q-dialog v-model="productImageFullscreenDialog" maximized @hide="onFullscreenMouseUp">
<q-card class="image-fullscreen-dialog"> <q-card class="image-fullscreen-dialog">
<q-card-section class="row items-center q-pb-sm"> <q-card-section class="row items-center q-pb-sm">
<div class="text-h6">Urun Fotografi</div> <div class="text-h6">Urun Fotografi</div>
@@ -319,14 +319,38 @@
</q-card-section> </q-card-section>
<q-separator /> <q-separator />
<q-card-section class="image-fullscreen-body"> <q-card-section class="image-fullscreen-body">
<div class="image-fullscreen-stage cursor-pointer" @click="toggleFullscreenImageZoom"> <q-carousel
<q-img v-if="fullscreenImages.length"
:src="productImageFullscreenSrc" v-model="productImageFullscreenSlide"
fit="contain" animated
class="image-fullscreen-img" swipeable
:style="fullscreenImageStyle" navigation
/> arrows
</div> height="calc(100vh - 120px)"
class="image-fullscreen-carousel"
@update:model-value="onFullscreenSlideChange"
>
<q-carousel-slide
v-for="(img, idx) in fullscreenImages"
:key="'full-img-' + idx"
:name="idx"
class="column no-wrap flex-center"
>
<div
class="image-fullscreen-stage"
@wheel.prevent="onFullscreenWheel"
@mousedown="onFullscreenMouseDown"
@dblclick="toggleFullscreenImageZoom"
>
<q-img
:src="img"
fit="contain"
class="image-fullscreen-img"
:style="fullscreenImageStyle"
/>
</div>
</q-carousel-slide>
</q-carousel>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-dialog> </q-dialog>
@@ -376,7 +400,15 @@ const productCardImages = ref([])
const productCardSlide = ref(0) const productCardSlide = ref(0)
const productImageFullscreenDialog = ref(false) const productImageFullscreenDialog = ref(false)
const productImageFullscreenSrc = ref('') const productImageFullscreenSrc = ref('')
const productImageFullscreenSlide = ref(0)
const productImageFullscreenZoom = ref(1) const productImageFullscreenZoom = ref(1)
const productImageFullscreenOffsetX = ref(0)
const productImageFullscreenOffsetY = ref(0)
const productImageFullscreenDragging = ref(false)
const productImageFullscreenDragStartX = ref(0)
const productImageFullscreenDragStartY = ref(0)
const productImageFullscreenDragOriginX = ref(0)
const productImageFullscreenDragOriginY = ref(0)
const IMAGE_LIST_CONCURRENCY = 8 const IMAGE_LIST_CONCURRENCY = 8
let imageListActiveRequests = 0 let imageListActiveRequests = 0
const imageListWaitQueue = [] const imageListWaitQueue = []
@@ -405,11 +437,19 @@ const gridHeaderHeight = computed(() =>
showGridHeader.value ? '56px' : '0px' showGridHeader.value ? '56px' : '0px'
) )
const fullscreenImageStyle = computed(() => ({ const fullscreenImageStyle = computed(() => ({
transform: `scale(${productImageFullscreenZoom.value})`, transform: `translate(${productImageFullscreenOffsetX.value}px, ${productImageFullscreenOffsetY.value}px) scale(${productImageFullscreenZoom.value})`,
transformOrigin: 'center center', transformOrigin: 'center center',
transition: 'transform 0.15s ease-out' transition: productImageFullscreenDragging.value ? 'none' : 'transform 0.1s ease-out',
cursor: productImageFullscreenZoom.value > 1 ? (productImageFullscreenDragging.value ? 'grabbing' : 'grab') : 'zoom-in'
})) }))
const fullscreenImages = computed(() => {
const arr = Array.isArray(productCardImages.value) ? productCardImages.value : []
if (arr.length) return arr
const single = String(productImageFullscreenSrc.value || '').trim()
return single ? [single] : []
})
function emptySizeTotals() { function emptySizeTotals() {
const map = {} const map = {}
for (const s of sizeLabels.value) map[s] = 0 for (const s of sizeLabels.value) map[s] = 0
@@ -574,6 +614,25 @@ function resolveProductImageUrl(item) {
return { contentUrl, publicUrl, thumbUrl, fullUrl } return { contentUrl, publicUrl, thumbUrl, fullUrl }
} }
function extractImageOrder(fileName, fallbackIndex) {
const name = String(fileName || '').trim()
const m = name.match(/\((\d+)\)(?=\.[a-z0-9]+$)/i)
if (m) return Number(m[1] || 999999)
const m2 = name.match(/[-_ ](\d+)(?=\.[a-z0-9]+$)/i)
if (m2) return Number(m2[1] || 999999)
return 1000000 + Number(fallbackIndex || 0)
}
function sortImagesForDisplay(list) {
return (Array.isArray(list) ? list : [])
.map((item, idx) => ({ item, idx, order: extractImageOrder(item?.file_name || item?.FileName || '', idx) }))
.sort((a, b) => {
if (a.order !== b.order) return a.order - b.order
return a.idx - b.idx
})
.map((x) => x.item)
}
async function resolveProductImageUrlForCarousel(item) { async function resolveProductImageUrlForCarousel(item) {
const resolved = resolveProductImageUrl(item) const resolved = resolveProductImageUrl(item)
const contentUrl = String(resolved.contentUrl || '').trim() const contentUrl = String(resolved.contentUrl || '').trim()
@@ -659,11 +718,11 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
} }
} }
const list = productImageListByCode.value[listKey] || [] const list = sortImagesForDisplay(productImageListByCode.value[listKey] || [])
const first = list[0] || null const first = list[0] || null
const resolved = resolveProductImageUrl(first) const resolved = resolveProductImageUrl(first)
productImageCache.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.contentUrl || resolved.publicUrl || '' productImageCache.value[key] = resolved.fullUrl || resolved.publicUrl || resolved.thumbUrl || resolved.contentUrl || ''
productImageFallbackByKey.value[key] = resolved.contentUrl || '' productImageFallbackByKey.value[key] = resolved.contentUrl || ''
} catch (err) { } catch (err) {
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err }) console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
@@ -1028,8 +1087,9 @@ async function openProductCard(grp1, grp2) {
} }
} }
const sortedList = sortImagesForDisplay(list)
const imageCandidates = await Promise.all( const imageCandidates = await Promise.all(
list.map((item) => resolveProductImageUrlForCarousel(item)) sortedList.map((item) => resolveProductImageUrlForCarousel(item))
) )
const images = imageCandidates.filter((x) => String(x || '').trim() !== '') const images = imageCandidates.filter((x) => String(x || '').trim() !== '')
console.info('[ProductStockQuery][openProductCard] render', { console.info('[ProductStockQuery][openProductCard] render', {
@@ -1071,7 +1131,12 @@ function openProductImageFullscreen(src) {
const value = String(src || '').trim() const value = String(src || '').trim()
if (!value) return if (!value) return
productImageFullscreenSrc.value = value productImageFullscreenSrc.value = value
const idx = Math.max(0, fullscreenImages.value.findIndex((x) => String(x || '').trim() === value))
productImageFullscreenSlide.value = idx
productImageFullscreenZoom.value = 1 productImageFullscreenZoom.value = 1
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
productImageFullscreenDragging.value = false
productImageFullscreenDialog.value = true productImageFullscreenDialog.value = true
} }
@@ -1079,7 +1144,57 @@ function toggleFullscreenImageZoom() {
const current = Number(productImageFullscreenZoom.value || 1) const current = Number(productImageFullscreenZoom.value || 1)
if (current < 1.5) productImageFullscreenZoom.value = 1.8 if (current < 1.5) productImageFullscreenZoom.value = 1.8
else if (current < 2.3) productImageFullscreenZoom.value = 2.6 else if (current < 2.3) productImageFullscreenZoom.value = 2.6
else if (current < 3.2) productImageFullscreenZoom.value = 3.2
else productImageFullscreenZoom.value = 1 else productImageFullscreenZoom.value = 1
if (productImageFullscreenZoom.value <= 1) {
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
}
}
function onFullscreenWheel(evt) {
if (!evt) return
evt.preventDefault()
const delta = Number(evt.deltaY || 0)
if (productImageFullscreenZoom.value > 1.01 && !evt.ctrlKey) {
productImageFullscreenOffsetY.value -= delta * 0.45
return
}
const current = Number(productImageFullscreenZoom.value || 1)
const next = delta < 0 ? current + 0.2 : current - 0.2
productImageFullscreenZoom.value = Math.min(4, Math.max(1, Number(next.toFixed(2))))
if (productImageFullscreenZoom.value <= 1) {
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
}
}
function onFullscreenMouseDown(evt) {
if (productImageFullscreenZoom.value <= 1) return
productImageFullscreenDragging.value = true
productImageFullscreenDragStartX.value = Number(evt?.clientX || 0)
productImageFullscreenDragStartY.value = Number(evt?.clientY || 0)
productImageFullscreenDragOriginX.value = Number(productImageFullscreenOffsetX.value || 0)
productImageFullscreenDragOriginY.value = Number(productImageFullscreenOffsetY.value || 0)
}
function onFullscreenMouseMove(evt) {
if (!productImageFullscreenDragging.value) return
const dx = Number(evt?.clientX || 0) - productImageFullscreenDragStartX.value
const dy = Number(evt?.clientY || 0) - productImageFullscreenDragStartY.value
productImageFullscreenOffsetX.value = productImageFullscreenDragOriginX.value + dx
productImageFullscreenOffsetY.value = productImageFullscreenDragOriginY.value + dy
}
function onFullscreenMouseUp() {
productImageFullscreenDragging.value = false
}
function onFullscreenSlideChange() {
productImageFullscreenZoom.value = 1
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
productImageFullscreenDragging.value = false
} }
function resetForm() { function resetForm() {
@@ -1101,19 +1216,27 @@ function resetForm() {
productCardSlide.value = 0 productCardSlide.value = 0
productImageFullscreenDialog.value = false productImageFullscreenDialog.value = false
productImageFullscreenSrc.value = '' productImageFullscreenSrc.value = ''
productImageFullscreenSlide.value = 0
productImageFullscreenZoom.value = 1 productImageFullscreenZoom.value = 1
productImageFullscreenOffsetX.value = 0
productImageFullscreenOffsetY.value = 0
productImageFullscreenDragging.value = false
} }
onMounted(() => {
loadProductOptions()
})
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('mousemove', onFullscreenMouseMove)
window.removeEventListener('mouseup', onFullscreenMouseUp)
for (const url of productImageBlobUrls.value) { for (const url of productImageBlobUrls.value) {
try { URL.revokeObjectURL(url) } catch {} try { URL.revokeObjectURL(url) } catch {}
} }
productImageBlobUrls.value = [] productImageBlobUrls.value = []
}) })
onMounted(() => {
loadProductOptions()
window.addEventListener('mousemove', onFullscreenMouseMove)
window.addEventListener('mouseup', onFullscreenMouseUp)
})
</script> </script>
<style scoped> <style scoped>
@@ -1493,14 +1616,16 @@ onUnmounted(() => {
} }
.product-card-dialog { .product-card-dialog {
background: #fffef9; --pc-media-h: min(70vh, 900px);
--pc-media-w: min(74vw, 1220px);
background: #f6f8fc;
} }
.product-card-stock { .product-card-stock {
background: #f8f5e7; background: linear-gradient(180deg, #eef3fb 0%, #f8fbff 100%);
border: 1px solid #e2d9b6; border: 1px solid #c8d6ec;
border-radius: 10px; border-radius: 12px;
padding: 12px; padding: 14px;
} }
.stock-size-grid { .stock-size-grid {
@@ -1510,9 +1635,9 @@ onUnmounted(() => {
} }
.stock-size-chip { .stock-size-chip {
border: 1px solid #d8cca6; border: 1px solid #d7e2f2;
border-radius: 8px; border-radius: 10px;
background: #fff; background: #ffffff;
padding: 6px 8px; padding: 6px 8px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -1525,8 +1650,8 @@ onUnmounted(() => {
.product-card-content { .product-card-content {
display: grid; display: grid;
grid-template-columns: minmax(360px, 1fr) 420px; grid-template-columns: minmax(360px, 420px) minmax(760px, 1fr);
gap: 12px; gap: 14px;
align-items: stretch; align-items: stretch;
justify-content: start; justify-content: start;
} }
@@ -1534,16 +1659,18 @@ onUnmounted(() => {
.product-card-images { .product-card-images {
grid-column: 2; grid-column: 2;
grid-row: 1; grid-row: 1;
min-height: 560px; min-height: var(--pc-media-h);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: stretch;
justify-content: flex-start; justify-content: flex-start;
} }
.product-card-carousel { .product-card-carousel {
width: 420px; width: var(--pc-media-w);
max-width: 100%; max-width: 100%;
background: linear-gradient(180deg, #edf2fb 0%, #e4ebf9 100%);
border: 1px solid #c8d6ec;
} }
.dialog-image { .dialog-image {
@@ -1552,27 +1679,27 @@ onUnmounted(() => {
} }
.dialog-image-stage { .dialog-image-stage {
width: 420px; width: var(--pc-media-w);
max-width: 100%; max-width: 100%;
height: 560px; height: var(--pc-media-h);
overflow: hidden; overflow: hidden;
border-radius: 8px; border-radius: 10px;
background: #f7f4e9; background: linear-gradient(180deg, #edf2fb 0%, #e4ebf9 100%);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.dialog-image-empty { .dialog-image-empty {
width: 420px; width: var(--pc-media-w);
max-width: 100%; max-width: 100%;
height: 560px; height: var(--pc-media-h);
border: 1px dashed #cabf9a; border: 1px dashed #9db5de;
border-radius: 8px; border-radius: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #faf7ee; background: #f2f6fd;
} }
.image-fullscreen-dialog { .image-fullscreen-dialog {
@@ -1586,6 +1713,10 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
} }
.image-fullscreen-carousel {
width: min(98vw, 1500px);
}
.image-fullscreen-stage { .image-fullscreen-stage {
width: min(96vw, 1400px); width: min(96vw, 1400px);
height: calc(100vh - 120px); height: calc(100vh - 120px);
@@ -1605,11 +1736,11 @@ onUnmounted(() => {
.product-card-fields { .product-card-fields {
grid-column: 1; grid-column: 1;
grid-row: 1; grid-row: 1;
border: 1px solid #e2d9b6; border: 1px solid #c8d6ec;
border-radius: 10px; border-radius: 12px;
background: #fff; background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
padding: 10px; padding: 12px;
height: 560px; height: var(--pc-media-h);
overflow: auto; overflow: auto;
} }
@@ -1617,8 +1748,8 @@ onUnmounted(() => {
display: grid; display: grid;
grid-template-columns: 150px 1fr; grid-template-columns: 150px 1fr;
gap: 8px; gap: 8px;
padding: 7px 0; padding: 8px 0;
border-bottom: 1px solid #f0ead7; border-bottom: 1px solid #e4ebf7;
font-size: 13px; font-size: 13px;
} }
@@ -1627,7 +1758,7 @@ onUnmounted(() => {
} }
.field-row .k { .field-row .k {
color: #5a4f2c; color: var(--q-primary, #1976d2);
font-weight: 700; font-weight: 700;
} }
@@ -1657,6 +1788,11 @@ onUnmounted(() => {
.product-card-fields { .product-card-fields {
height: auto; height: auto;
} }
.product-card-dialog {
--pc-media-h: min(62vh, 760px);
--pc-media-w: min(96vw, 900px);
}
} }
.order-sub-header.level-2 .sub-right .top-total { .order-sub-header.level-2 .sub-right .top-total {