Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -51,22 +51,6 @@
|
|||||||
@filter="onTopFilterSearchUrunAnaGrubu"
|
@filter="onTopFilterSearchUrunAnaGrubu"
|
||||||
@update:model-value="onTopUrunAnaGrubuChange"
|
@update:model-value="onTopUrunAnaGrubuChange"
|
||||||
/>
|
/>
|
||||||
<q-select
|
|
||||||
v-model="selectedProductCodes"
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
multiple
|
|
||||||
use-chips
|
|
||||||
use-input
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
:options="productCodeOptions"
|
|
||||||
:loading="Boolean(serverFilterLoading.productCode)"
|
|
||||||
:disable="pageBusy"
|
|
||||||
label="Urun Kodu"
|
|
||||||
style="min-width: 320px"
|
|
||||||
@filter="onProductCodeSearch"
|
|
||||||
/>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="filter_alt"
|
icon="filter_alt"
|
||||||
@@ -161,7 +145,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
ref="topScrollRef"
|
||||||
|
class="top-x-scroll"
|
||||||
|
@scroll.passive="onTopScroll"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="top-x-scroll-inner"
|
||||||
|
:style="{ width: `${tableMinWidth}px` }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<q-table
|
<q-table
|
||||||
|
ref="mainTableRef"
|
||||||
class="pane-table pricing-table order-price-list-table"
|
class="pane-table pricing-table order-price-list-table"
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
@@ -174,8 +169,174 @@
|
|||||||
hide-bottom
|
hide-bottom
|
||||||
:table-style="tableStyle"
|
:table-style="tableStyle"
|
||||||
>
|
>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props" class="header-row-fixed">
|
||||||
|
<q-th
|
||||||
|
v-for="col in props.cols"
|
||||||
|
:key="col.name"
|
||||||
|
:props="props"
|
||||||
|
:class="[col.headerClasses, { 'sticky-col': isStickyCol(col.name), 'sticky-boundary': isStickyBoundary(col.name) }]"
|
||||||
|
:style="getHeaderCellStyle(col)"
|
||||||
|
>
|
||||||
|
<div class="header-with-filter">
|
||||||
|
<span>{{ col.label }}</span>
|
||||||
|
<q-btn
|
||||||
|
v-if="col.name === 'productCode'"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
size="8px"
|
||||||
|
icon="filter_alt"
|
||||||
|
:color="selectedProductCodes.length > 0 ? 'primary' : 'grey-7'"
|
||||||
|
:disable="pageBusy"
|
||||||
|
class="header-filter-btn"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<q-badge v-if="selectedProductCodes.length > 0" color="primary" floating rounded>
|
||||||
|
{{ selectedProductCodes.length }}
|
||||||
|
</q-badge>
|
||||||
|
<q-menu
|
||||||
|
class="product-code-filter-menu"
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top left"
|
||||||
|
@before-show="onProductCodeMenuShow"
|
||||||
|
>
|
||||||
|
<div class="q-pa-sm filter-menu-panel">
|
||||||
|
<q-input
|
||||||
|
v-model="filterSearch.productCode"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
debounce="250"
|
||||||
|
placeholder="Urun kodu ara"
|
||||||
|
:disable="pageBusy"
|
||||||
|
@update:model-value="onProductCodeSearchText"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<div class="row items-center justify-between q-mt-xs">
|
||||||
|
<q-btn flat dense size="sm" label="Tumunu Sec" :disable="pageBusy || productCodeOptions.length === 0" @click="selectAllProductCodeOptions" />
|
||||||
|
<q-btn flat dense size="sm" label="Temizle" :disable="pageBusy || selectedProductCodes.length === 0" @click="clearProductCodeOptions" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-xs" />
|
||||||
|
<q-scroll-area style="height: 260px; width: 320px;">
|
||||||
|
<q-list dense>
|
||||||
|
<q-item
|
||||||
|
v-for="option in productCodeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
clickable
|
||||||
|
:disable="pageBusy"
|
||||||
|
@click="toggleProductCodeValue(option.value)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
dense
|
||||||
|
:model-value="selectedProductCodeSet.has(option.value)"
|
||||||
|
:disable="pageBusy"
|
||||||
|
@click.stop
|
||||||
|
@update:model-value="() => toggleProductCodeValue(option.value)"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ option.label }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-scroll-area>
|
||||||
|
<q-separator class="q-my-xs" />
|
||||||
|
<div class="row items-center justify-end q-gutter-xs">
|
||||||
|
<q-btn v-close-popup dense flat label="Kapat" />
|
||||||
|
<q-btn v-close-popup dense color="primary" label="Uygula" :disable="pageBusy" @click="reloadData({ page: 1 })" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-else-if="col.name === 'campaignLabel'"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
size="8px"
|
||||||
|
icon="filter_alt"
|
||||||
|
:color="selectedCampaignLabels.length > 0 ? 'primary' : 'grey-7'"
|
||||||
|
:disable="pageBusy"
|
||||||
|
class="header-filter-btn"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<q-badge v-if="selectedCampaignLabels.length > 0" color="primary" floating rounded>
|
||||||
|
{{ selectedCampaignLabels.length }}
|
||||||
|
</q-badge>
|
||||||
|
<q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
|
||||||
|
<div class="excel-filter-menu">
|
||||||
|
<q-input
|
||||||
|
v-model="campaignFilterSearch"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
clearable
|
||||||
|
class="excel-filter-select"
|
||||||
|
placeholder="Kampanya ara"
|
||||||
|
/>
|
||||||
|
<div class="excel-filter-actions row items-center justify-between q-pt-xs">
|
||||||
|
<q-btn flat dense size="sm" label="Tumunu Sec" :disable="pageBusy || filteredCampaignOptions.length === 0" @click="selectAllCampaignOptions" />
|
||||||
|
<q-btn flat dense size="sm" label="Temizle" :disable="pageBusy || selectedCampaignLabels.length === 0" @click="clearCampaignOptions" />
|
||||||
|
</div>
|
||||||
|
<q-virtual-scroll
|
||||||
|
v-if="filteredCampaignOptions.length > 0"
|
||||||
|
class="excel-filter-options"
|
||||||
|
:items="filteredCampaignOptions"
|
||||||
|
:virtual-scroll-item-size="32"
|
||||||
|
separator
|
||||||
|
>
|
||||||
|
<template #default="{ item: option }">
|
||||||
|
<q-item
|
||||||
|
:key="`campaign-${option.value}`"
|
||||||
|
dense
|
||||||
|
clickable
|
||||||
|
:disable="pageBusy"
|
||||||
|
class="excel-filter-option"
|
||||||
|
@click="toggleCampaignValue(option.value)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
:model-value="selectedCampaignLabelSet.has(option.value)"
|
||||||
|
:disable="pageBusy"
|
||||||
|
@update:model-value="() => toggleCampaignValue(option.value)"
|
||||||
|
@click.stop
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ option.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-virtual-scroll>
|
||||||
|
<div v-else class="excel-filter-empty">Sonuc yok</div>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
<span v-else class="header-filter-ghost"></span>
|
||||||
|
</div>
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell="props">
|
||||||
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
:class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]"
|
||||||
|
:style="getBodyCellStyle(props.col)"
|
||||||
|
>
|
||||||
|
{{ props.value }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #body-cell-image="props">
|
<template #body-cell-image="props">
|
||||||
<q-td :props="props" class="image-cell">
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
:class="['image-cell', props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]"
|
||||||
|
:style="getBodyCellStyle(props.col)"
|
||||||
|
>
|
||||||
<q-img
|
<q-img
|
||||||
v-if="props.row.imageUrl"
|
v-if="props.row.imageUrl"
|
||||||
:src="props.row.imageUrl"
|
:src="props.row.imageUrl"
|
||||||
@@ -187,14 +348,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body-cell-campaignLabel="props">
|
<template #body-cell-campaignLabel="props">
|
||||||
<q-td :props="props">
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
:class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]"
|
||||||
|
:style="getBodyCellStyle(props.col)"
|
||||||
|
>
|
||||||
<q-badge v-if="props.row.campaignLabel" color="primary" outline :label="props.row.campaignLabel" />
|
<q-badge v-if="props.row.campaignLabel" color="primary" outline :label="props.row.campaignLabel" />
|
||||||
<span v-else class="text-grey-6">-</span>
|
<span v-else class="text-grey-6">-</span>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-campaignRate="props">
|
||||||
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
:class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]"
|
||||||
|
:style="getBodyCellStyle(props.col)"
|
||||||
|
>
|
||||||
|
{{ props.row.campaignRate ? formatPrice(props.row.campaignRate) : '' }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-for="name in priceColumnNames" #[`body-cell-${name}`]="props" :key="name">
|
<template v-for="name in priceColumnNames" #[`body-cell-${name}`]="props" :key="name">
|
||||||
<q-td :props="props" class="text-right">
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
:class="['text-right', props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]"
|
||||||
|
:style="getBodyCellStyle(props.col)"
|
||||||
|
>
|
||||||
{{ formatPrice(props.row[name]) }}
|
{{ formatPrice(props.row[name]) }}
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
@@ -221,6 +400,8 @@ const priceColumnNames = campaignPairs.flatMap((p) => [p.base, p.derived])
|
|||||||
const topUrunIlkGrubu = ref(null)
|
const topUrunIlkGrubu = ref(null)
|
||||||
const topUrunAnaGrubu = ref(null)
|
const topUrunAnaGrubu = ref(null)
|
||||||
const selectedProductCodes = ref([])
|
const selectedProductCodes = ref([])
|
||||||
|
const selectedCampaignLabels = ref([])
|
||||||
|
const campaignFilterSearch = ref('')
|
||||||
const selectedPriceOptions = ref(priceOptions.map((x) => x.value))
|
const selectedPriceOptions = ref(priceOptions.map((x) => x.value))
|
||||||
const leftDetailsExpanded = ref(true)
|
const leftDetailsExpanded = ref(true)
|
||||||
|
|
||||||
@@ -237,8 +418,13 @@ const serverFilterLoading = ref({})
|
|||||||
const serverFilterLastQuery = ref({})
|
const serverFilterLastQuery = ref({})
|
||||||
const filterSearch = ref({ productCode: '', urunIlkGrubu: '', urunAnaGrubu: '' })
|
const filterSearch = ref({ productCode: '', urunIlkGrubu: '', urunAnaGrubu: '' })
|
||||||
const imageCache = new Map()
|
const imageCache = new Map()
|
||||||
|
const mainTableRef = ref(null)
|
||||||
|
const topScrollRef = ref(null)
|
||||||
|
let syncingScroll = false
|
||||||
|
|
||||||
const selectedPriceSet = computed(() => new Set(selectedPriceOptions.value || []))
|
const selectedPriceSet = computed(() => new Set(selectedPriceOptions.value || []))
|
||||||
|
const selectedProductCodeSet = computed(() => new Set(selectedProductCodes.value || []))
|
||||||
|
const selectedCampaignLabelSet = computed(() => new Set(selectedCampaignLabels.value || []))
|
||||||
const pageBusy = computed(() => loading.value || renderPending.value)
|
const pageBusy = computed(() => loading.value || renderPending.value)
|
||||||
const canFetch = computed(() => Boolean(topUrunIlkGrubu.value || topUrunAnaGrubu.value || selectedProductCodes.value.length > 0))
|
const canFetch = computed(() => Boolean(topUrunIlkGrubu.value || topUrunAnaGrubu.value || selectedProductCodes.value.length > 0))
|
||||||
const showGuidanceOverlay = computed(() => !loading.value && rows.value.length === 0 && error.value === GUIDANCE_MSG)
|
const showGuidanceOverlay = computed(() => !loading.value && rows.value.length === 0 && error.value === GUIDANCE_MSG)
|
||||||
@@ -246,6 +432,21 @@ const showGuidanceOverlay = computed(() => !loading.value && rows.value.length =
|
|||||||
const topUrunIlkGrubuOptions = computed(() => serverFilterOptionMap.value.urunIlkGrubu || [])
|
const topUrunIlkGrubuOptions = computed(() => serverFilterOptionMap.value.urunIlkGrubu || [])
|
||||||
const topUrunAnaGrubuOptions = computed(() => serverFilterOptionMap.value.urunAnaGrubu || [])
|
const topUrunAnaGrubuOptions = computed(() => serverFilterOptionMap.value.urunAnaGrubu || [])
|
||||||
const productCodeOptions = computed(() => serverFilterOptionMap.value.productCode || [])
|
const productCodeOptions = computed(() => serverFilterOptionMap.value.productCode || [])
|
||||||
|
const campaignOptions = computed(() => {
|
||||||
|
const uniq = new Set()
|
||||||
|
for (const row of rows.value || []) {
|
||||||
|
const val = toText(row?.campaignLabel)
|
||||||
|
if (val) uniq.add(val)
|
||||||
|
}
|
||||||
|
return Array.from(uniq)
|
||||||
|
.sort((a, b) => a.localeCompare(b, 'tr'))
|
||||||
|
.map((value) => ({ label: value, value }))
|
||||||
|
})
|
||||||
|
const filteredCampaignOptions = computed(() => {
|
||||||
|
const q = toText(campaignFilterSearch.value).toLocaleLowerCase('tr')
|
||||||
|
const list = campaignOptions.value
|
||||||
|
return q ? list.filter((x) => x.label.toLocaleLowerCase('tr').includes(q)) : list
|
||||||
|
})
|
||||||
|
|
||||||
function toText (value) {
|
function toText (value) {
|
||||||
return String(value ?? '').trim()
|
return String(value ?? '').trim()
|
||||||
@@ -398,11 +599,56 @@ function onTopFilterSearchUrunAnaGrubu (val, update) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onProductCodeSearch (val, update) {
|
function onProductCodeMenuShow () {
|
||||||
update(() => {
|
void fetchServerFilterOptions('productCode', filterSearch.value.productCode)
|
||||||
filterSearch.value.productCode = toText(val)
|
}
|
||||||
void fetchServerFilterOptions('productCode', val)
|
|
||||||
})
|
function onProductCodeSearchText (val) {
|
||||||
|
void fetchServerFilterOptions('productCode', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleProductCodeValue (value) {
|
||||||
|
const v = toText(value)
|
||||||
|
if (!v) return
|
||||||
|
const set = new Set(selectedProductCodes.value || [])
|
||||||
|
if (set.has(v)) set.delete(v)
|
||||||
|
else set.add(v)
|
||||||
|
selectedProductCodes.value = Array.from(set).sort((a, b) => a.localeCompare(b, 'tr'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllProductCodeOptions () {
|
||||||
|
const set = new Set(selectedProductCodes.value || [])
|
||||||
|
for (const option of productCodeOptions.value) {
|
||||||
|
const v = toText(option.value)
|
||||||
|
if (v) set.add(v)
|
||||||
|
}
|
||||||
|
selectedProductCodes.value = Array.from(set).sort((a, b) => a.localeCompare(b, 'tr'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearProductCodeOptions () {
|
||||||
|
selectedProductCodes.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCampaignValue (value) {
|
||||||
|
const v = toText(value)
|
||||||
|
if (!v) return
|
||||||
|
const set = new Set(selectedCampaignLabels.value || [])
|
||||||
|
if (set.has(v)) set.delete(v)
|
||||||
|
else set.add(v)
|
||||||
|
selectedCampaignLabels.value = Array.from(set).sort((a, b) => a.localeCompare(b, 'tr'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllCampaignOptions () {
|
||||||
|
const set = new Set(selectedCampaignLabels.value || [])
|
||||||
|
for (const option of filteredCampaignOptions.value) {
|
||||||
|
const v = toText(option.value)
|
||||||
|
if (v) set.add(v)
|
||||||
|
}
|
||||||
|
selectedCampaignLabels.value = Array.from(set).sort((a, b) => a.localeCompare(b, 'tr'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCampaignOptions () {
|
||||||
|
selectedCampaignLabels.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTopUrunIlkGrubuChange () {
|
function onTopUrunIlkGrubuChange () {
|
||||||
@@ -591,14 +837,58 @@ const visibleColumns = computed(() => allColumns.filter((c) => {
|
|||||||
return true
|
return true
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const filteredRows = computed(() => rows.value || [])
|
const filteredRows = computed(() => {
|
||||||
|
const campaignSet = selectedCampaignLabelSet.value
|
||||||
|
if (campaignSet.size === 0) return rows.value || []
|
||||||
|
return (rows.value || []).filter((row) => campaignSet.has(toText(row?.campaignLabel)))
|
||||||
|
})
|
||||||
const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0))
|
const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0))
|
||||||
const tableStyle = computed(() => ({
|
const tableStyle = computed(() => ({
|
||||||
width: `${tableMinWidth.value}px`,
|
width: `${tableMinWidth.value}px`,
|
||||||
minWidth: `${tableMinWidth.value}px`,
|
minWidth: `${tableMinWidth.value}px`,
|
||||||
tableLayout: 'fixed'
|
tableLayout: 'fixed'
|
||||||
}))
|
}))
|
||||||
const stickyScrollComp = computed(() => 650)
|
const stickyColumnNames = computed(() => {
|
||||||
|
const visible = new Set(visibleColumns.value.map((x) => x.name))
|
||||||
|
return ['image', 'brandGroupSelection', 'marka', 'productCode', 'variantCodes', 'variantStocks', 'campaignLabel', 'campaignRate'].filter((x) => visible.has(x))
|
||||||
|
})
|
||||||
|
const stickyBoundaryColumnName = 'campaignRate'
|
||||||
|
const stickyColumnNameSet = computed(() => new Set(stickyColumnNames.value))
|
||||||
|
const stickyLeftMap = computed(() => {
|
||||||
|
const map = {}
|
||||||
|
let left = 0
|
||||||
|
for (const name of stickyColumnNames.value) {
|
||||||
|
const colDef = allColumns.find((x) => x.name === name)
|
||||||
|
if (!colDef) continue
|
||||||
|
map[name] = left
|
||||||
|
left += extractWidth(colDef.style)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
const stickyScrollComp = computed(() => {
|
||||||
|
const boundaryCol = allColumns.find((x) => x.name === stickyBoundaryColumnName)
|
||||||
|
return ((stickyLeftMap.value[stickyBoundaryColumnName] || 0) + extractWidth(boundaryCol?.style)) * 1.2
|
||||||
|
})
|
||||||
|
|
||||||
|
function isStickyCol (name) {
|
||||||
|
return stickyColumnNameSet.value.has(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStickyBoundary (name) {
|
||||||
|
return name === stickyBoundaryColumnName
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeaderCellStyle (col) {
|
||||||
|
const base = col.headerStyle || col.style || ''
|
||||||
|
if (!isStickyCol(col.name)) return base
|
||||||
|
return `${base};left:${stickyLeftMap.value[col.name] || 0}px;`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBodyCellStyle (col) {
|
||||||
|
const base = col.style || ''
|
||||||
|
if (!isStickyCol(col.name)) return base
|
||||||
|
return `${base};left:${stickyLeftMap.value[col.name] || 0}px;`
|
||||||
|
}
|
||||||
|
|
||||||
function extractWidth (style) {
|
function extractWidth (style) {
|
||||||
const m = String(style || '').match(/width:(\d+)px/)
|
const m = String(style || '').match(/width:(\d+)px/)
|
||||||
@@ -654,6 +944,34 @@ function printVisibleRows () {
|
|||||||
win.document.close()
|
win.document.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTableMiddleEl () {
|
||||||
|
return mainTableRef.value?.$el?.querySelector?.('.q-table__middle') || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopScroll () {
|
||||||
|
if (syncingScroll) return
|
||||||
|
const middle = getTableMiddleEl()
|
||||||
|
const top = topScrollRef.value
|
||||||
|
if (!middle || !top) return
|
||||||
|
syncingScroll = true
|
||||||
|
middle.scrollLeft = top.scrollLeft
|
||||||
|
requestAnimationFrame(() => { syncingScroll = false })
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindTableScrollSync () {
|
||||||
|
const middle = getTableMiddleEl()
|
||||||
|
if (!middle || middle.__orderPriceListScrollBound) return
|
||||||
|
middle.__orderPriceListScrollBound = true
|
||||||
|
middle.addEventListener('scroll', () => {
|
||||||
|
if (syncingScroll) return
|
||||||
|
const top = topScrollRef.value
|
||||||
|
if (!top) return
|
||||||
|
syncingScroll = true
|
||||||
|
top.scrollLeft = middle.scrollLeft
|
||||||
|
requestAnimationFrame(() => { syncingScroll = false })
|
||||||
|
}, { passive: true })
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml (value) {
|
function escapeHtml (value) {
|
||||||
return String(value ?? '')
|
return String(value ?? '')
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -669,16 +987,28 @@ watch(selectedProductCodes, (list) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch([tableMinWidth, rows], async () => {
|
||||||
|
await nextTick()
|
||||||
|
bindTableScrollSync()
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void fetchServerFilterOptions('urunIlkGrubu', '')
|
void fetchServerFilterOptions('urunIlkGrubu', '')
|
||||||
void fetchServerFilterOptions('urunAnaGrubu', '')
|
void fetchServerFilterOptions('urunAnaGrubu', '')
|
||||||
void fetchServerFilterOptions('productCode', '')
|
void fetchServerFilterOptions('productCode', '')
|
||||||
|
void nextTick(bindTableScrollSync)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.order-price-list-page {
|
.order-price-list-page {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: calc(100vh - 58px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
--pricing-row-height: 31px;
|
||||||
|
--pricing-header-height: 72px;
|
||||||
|
--pricing-table-height: calc(100vh - 156px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-actions {
|
.top-actions {
|
||||||
@@ -695,8 +1025,13 @@ onMounted(() => {
|
|||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
flex: 1;
|
||||||
height: calc(100vh - 150px);
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-thumb {
|
.product-thumb {
|
||||||
@@ -710,15 +1045,185 @@ onMounted(() => {
|
|||||||
padding: 2px 4px !important;
|
padding: 2px 4px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-price-list-table :deep(td),
|
.top-x-scroll {
|
||||||
.order-price-list-table :deep(th) {
|
flex: 0 0 14px;
|
||||||
font-size: 11px;
|
height: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-x-scroll-inner {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-table {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(.q-table__middle) {
|
||||||
|
height: calc(var(--pricing-table-height) - 14px);
|
||||||
|
min-height: calc(var(--pricing-table-height) - 14px);
|
||||||
|
max-height: calc(var(--pricing-table-height) - 14px);
|
||||||
|
overflow: auto !important;
|
||||||
|
scrollbar-gutter: stable both-edges;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(.q-table) {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(td),
|
||||||
|
.pricing-table :deep(.q-table tbody tr) {
|
||||||
|
height: var(--pricing-row-height) !important;
|
||||||
|
min-height: var(--pricing-row-height) !important;
|
||||||
|
max-height: var(--pricing-row-height) !important;
|
||||||
|
line-height: var(--pricing-row-height);
|
||||||
|
padding: 0 !important;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(th),
|
||||||
|
.pricing-table :deep(.q-table thead tr),
|
||||||
|
.pricing-table :deep(.q-table thead tr.header-row-fixed),
|
||||||
|
.pricing-table :deep(.q-table thead th),
|
||||||
|
.pricing-table :deep(.q-table thead tr.header-row-fixed > th) {
|
||||||
|
height: var(--pricing-header-height) !important;
|
||||||
|
min-height: var(--pricing-header-height) !important;
|
||||||
|
max-height: var(--pricing-header-height) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(th) {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-break: normal;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(.q-table thead th) {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
|
background: #fff;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(.sticky-col) {
|
||||||
|
position: sticky !important;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(thead .sticky-col) {
|
||||||
|
z-index: 35 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(tbody .sticky-col) {
|
||||||
|
z-index: 12 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(.sticky-boundary) {
|
||||||
|
border-right: 2px solid rgba(25, 118, 210, 0.18) !important;
|
||||||
|
box-shadow: 8px 0 12px -10px rgba(15, 23, 42, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(tbody td:not(.sticky-col)) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(tbody td.sticky-col)::after,
|
||||||
|
.pricing-table :deep(thead th.sticky-col)::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: inherit;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-table :deep(th.ps-col),
|
||||||
|
.pricing-table :deep(td.ps-col) {
|
||||||
|
background: #fff;
|
||||||
|
color: var(--q-primary);
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-price-list-table :deep(.campaign-price-col) {
|
.order-price-list-table :deep(.campaign-price-col) {
|
||||||
background: #f6fbf7;
|
background: #f6fbf7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-with-filter {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 20px;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 4px;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 1.25;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-with-filter > span {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.15;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-filter-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
min-width: 20px;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-filter-ghost {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-filter-menu,
|
||||||
|
.filter-menu-panel {
|
||||||
|
min-width: 230px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-filter-select :deep(.q-field__control) {
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-filter-options {
|
||||||
|
max-height: 220px;
|
||||||
|
margin-top: 8px;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-filter-option {
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-filter-empty {
|
||||||
|
padding: 10px 8px;
|
||||||
|
color: #607d8b;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.page-busy-overlay {
|
.page-busy-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user