Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -275,8 +275,8 @@
|
||||
swipeable
|
||||
navigation
|
||||
arrows
|
||||
height="560px"
|
||||
class="product-card-carousel rounded-borders bg-grey-2"
|
||||
height="70vh"
|
||||
class="product-card-carousel rounded-borders"
|
||||
>
|
||||
<q-carousel-slide
|
||||
v-for="(img, idx) in productCardImages"
|
||||
@@ -312,7 +312,7 @@
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="productImageFullscreenDialog" maximized>
|
||||
<q-dialog v-model="productImageFullscreenDialog" maximized @hide="onFullscreenMouseUp">
|
||||
<q-card class="image-fullscreen-dialog">
|
||||
<q-card-section class="row items-center q-pb-sm">
|
||||
<div class="text-h6">Urun Fotografi</div>
|
||||
@@ -321,14 +321,38 @@
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section class="image-fullscreen-body">
|
||||
<div class="image-fullscreen-stage cursor-pointer" @click="toggleFullscreenImageZoom">
|
||||
<q-img
|
||||
:src="productImageFullscreenSrc"
|
||||
fit="contain"
|
||||
class="image-fullscreen-img"
|
||||
:style="fullscreenImageStyle"
|
||||
/>
|
||||
</div>
|
||||
<q-carousel
|
||||
v-if="fullscreenImages.length"
|
||||
v-model="productImageFullscreenSlide"
|
||||
animated
|
||||
swipeable
|
||||
navigation
|
||||
arrows
|
||||
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>
|
||||
</q-dialog>
|
||||
@@ -401,7 +425,15 @@ const productCardImages = ref([])
|
||||
const productCardSlide = ref(0)
|
||||
const productImageFullscreenDialog = ref(false)
|
||||
const productImageFullscreenSrc = ref('')
|
||||
const productImageFullscreenSlide = ref(0)
|
||||
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 FILTER_OPTIONS_CACHE_TTL_MS = 60 * 1000
|
||||
const FILTER_OPTIONS_DEBOUNCE_MS = 250
|
||||
@@ -438,11 +470,19 @@ const gridHeaderHeight = computed(() =>
|
||||
showGridHeader.value ? '56px' : '0px'
|
||||
)
|
||||
const fullscreenImageStyle = computed(() => ({
|
||||
transform: `scale(${productImageFullscreenZoom.value})`,
|
||||
transform: `translate(${productImageFullscreenOffsetX.value}px, ${productImageFullscreenOffsetY.value}px) scale(${productImageFullscreenZoom.value})`,
|
||||
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() {
|
||||
const map = {}
|
||||
for (const s of sizeLabels.value) map[s] = 0
|
||||
@@ -587,6 +627,25 @@ function resolveProductImageUrl(item) {
|
||||
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) {
|
||||
const resolved = resolveProductImageUrl(item)
|
||||
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 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 || ''
|
||||
} catch (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(
|
||||
list.map((item) => resolveProductImageUrlForCarousel(item))
|
||||
sortedList.map((item) => resolveProductImageUrlForCarousel(item))
|
||||
)
|
||||
const images = imageCandidates.filter((x) => String(x || '').trim() !== '')
|
||||
console.info('[ProductStockByAttributes][openProductCard] render', {
|
||||
@@ -1196,7 +1256,12 @@ function openProductImageFullscreen(src) {
|
||||
const value = String(src || '').trim()
|
||||
if (!value) return
|
||||
productImageFullscreenSrc.value = value
|
||||
const idx = Math.max(0, fullscreenImages.value.findIndex((x) => String(x || '').trim() === value))
|
||||
productImageFullscreenSlide.value = idx
|
||||
productImageFullscreenZoom.value = 1
|
||||
productImageFullscreenOffsetX.value = 0
|
||||
productImageFullscreenOffsetY.value = 0
|
||||
productImageFullscreenDragging.value = false
|
||||
productImageFullscreenDialog.value = true
|
||||
}
|
||||
|
||||
@@ -1204,7 +1269,57 @@ function toggleFullscreenImageZoom() {
|
||||
const current = Number(productImageFullscreenZoom.value || 1)
|
||||
if (current < 1.5) productImageFullscreenZoom.value = 1.8
|
||||
else if (current < 2.3) productImageFullscreenZoom.value = 2.6
|
||||
else if (current < 3.2) productImageFullscreenZoom.value = 3.2
|
||||
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() {
|
||||
@@ -1236,16 +1351,24 @@ function resetForm() {
|
||||
productCardSlide.value = 0
|
||||
productImageFullscreenDialog.value = false
|
||||
productImageFullscreenSrc.value = ''
|
||||
productImageFullscreenSlide.value = 0
|
||||
productImageFullscreenZoom.value = 1
|
||||
productImageFullscreenOffsetX.value = 0
|
||||
productImageFullscreenOffsetY.value = 0
|
||||
productImageFullscreenDragging.value = false
|
||||
filterOptionsCache.value = {}
|
||||
void loadFilterOptions(true)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadFilterOptions(true)
|
||||
window.addEventListener('mousemove', onFullscreenMouseMove)
|
||||
window.addEventListener('mouseup', onFullscreenMouseUp)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mousemove', onFullscreenMouseMove)
|
||||
window.removeEventListener('mouseup', onFullscreenMouseUp)
|
||||
if (filterOptionsDebounceTimer) {
|
||||
clearTimeout(filterOptionsDebounceTimer)
|
||||
filterOptionsDebounceTimer = null
|
||||
@@ -1637,14 +1760,16 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.product-card-dialog {
|
||||
background: #fffef9;
|
||||
--pc-media-h: min(70vh, 900px);
|
||||
--pc-media-w: min(74vw, 1220px);
|
||||
background: #f6f8fc;
|
||||
}
|
||||
|
||||
.product-card-stock {
|
||||
background: #f8f5e7;
|
||||
border: 1px solid #e2d9b6;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: linear-gradient(180deg, #eef3fb 0%, #f8fbff 100%);
|
||||
border: 1px solid #c8d6ec;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.stock-size-grid {
|
||||
@@ -1654,9 +1779,9 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.stock-size-chip {
|
||||
border: 1px solid #d8cca6;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #d7e2f2;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
padding: 6px 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -1669,8 +1794,8 @@ onUnmounted(() => {
|
||||
|
||||
.product-card-content {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(360px, 1fr) 420px;
|
||||
gap: 12px;
|
||||
grid-template-columns: minmax(360px, 420px) minmax(760px, 1fr);
|
||||
gap: 14px;
|
||||
align-items: stretch;
|
||||
justify-content: start;
|
||||
}
|
||||
@@ -1678,16 +1803,18 @@ onUnmounted(() => {
|
||||
.product-card-images {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
min-height: 560px;
|
||||
min-height: var(--pc-media-h);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.product-card-carousel {
|
||||
width: 420px;
|
||||
width: var(--pc-media-w);
|
||||
max-width: 100%;
|
||||
background: linear-gradient(180deg, #edf2fb 0%, #e4ebf9 100%);
|
||||
border: 1px solid #c8d6ec;
|
||||
}
|
||||
|
||||
.dialog-image {
|
||||
@@ -1696,27 +1823,27 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.dialog-image-stage {
|
||||
width: 420px;
|
||||
width: var(--pc-media-w);
|
||||
max-width: 100%;
|
||||
height: 560px;
|
||||
height: var(--pc-media-h);
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #f7f4e9;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(180deg, #edf2fb 0%, #e4ebf9 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dialog-image-empty {
|
||||
width: 420px;
|
||||
width: var(--pc-media-w);
|
||||
max-width: 100%;
|
||||
height: 560px;
|
||||
border: 1px dashed #cabf9a;
|
||||
border-radius: 8px;
|
||||
height: var(--pc-media-h);
|
||||
border: 1px dashed #9db5de;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #faf7ee;
|
||||
background: #f2f6fd;
|
||||
}
|
||||
|
||||
.image-fullscreen-dialog {
|
||||
@@ -1730,6 +1857,10 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-fullscreen-carousel {
|
||||
width: min(98vw, 1500px);
|
||||
}
|
||||
|
||||
.image-fullscreen-stage {
|
||||
width: min(96vw, 1400px);
|
||||
height: calc(100vh - 120px);
|
||||
@@ -1749,11 +1880,11 @@ onUnmounted(() => {
|
||||
.product-card-fields {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
border: 1px solid #e2d9b6;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
height: 560px;
|
||||
border: 1px solid #c8d6ec;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
|
||||
padding: 12px;
|
||||
height: var(--pc-media-h);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -1761,8 +1892,8 @@ onUnmounted(() => {
|
||||
display: grid;
|
||||
grid-template-columns: 150px 1fr;
|
||||
gap: 8px;
|
||||
padding: 7px 0;
|
||||
border-bottom: 1px solid #f0ead7;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #e4ebf7;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -1771,7 +1902,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.field-row .k {
|
||||
color: #5a4f2c;
|
||||
color: var(--q-primary, #1976d2);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -1801,6 +1932,11 @@ onUnmounted(() => {
|
||||
.product-card-fields {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user