fix(products): hide empty categories and stabilize catalog list
Only show active products, keep partial SSE lists from dropping items, and hide category tabs with no products. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,24 +35,42 @@ function sortProductsByInventory(products: Product[]) {
|
||||
}
|
||||
|
||||
export function changeProductsTab() {
|
||||
const all = productsFirstItems.value
|
||||
if (productsActiveKey.value !== 'همه') {
|
||||
productsItems.value = productsFirstItems.value?.filter(
|
||||
(val) => val.group_type === productsActiveKey.value,
|
||||
)
|
||||
const filtered = all?.filter((val) => val.group_type === productsActiveKey.value)
|
||||
if (!filtered?.length) {
|
||||
productsActiveKey.value = 'همه'
|
||||
productsItems.value = all
|
||||
return
|
||||
}
|
||||
productsItems.value = filtered
|
||||
} else {
|
||||
productsItems.value = productsFirstItems.value
|
||||
productsItems.value = all
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCategoryTabs() {
|
||||
try {
|
||||
productsTypeItems.value = await loadProductCategoryTabNames()
|
||||
pruneEmptyTypeItems()
|
||||
} catch (error) {
|
||||
console.error('Product category tabs error:', error)
|
||||
productsTypeItems.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** دستهای که هیچ محصول فعالی ندارد از تبها حذف شود */
|
||||
function pruneEmptyTypeItems() {
|
||||
const products = productsFirstItems.value
|
||||
if (!products?.length) {
|
||||
return
|
||||
}
|
||||
const present = new Set(
|
||||
products.map((p) => p.group_type).filter((key): key is string => !!key),
|
||||
)
|
||||
productsTypeItems.value = productsTypeItems.value.filter((key) => present.has(key))
|
||||
}
|
||||
|
||||
function productFileId(product: Product): number {
|
||||
return Number(product.file_id ?? (product.file as { id?: number } | undefined)?.id ?? 0)
|
||||
}
|
||||
@@ -99,9 +117,57 @@ function mergeLiveProductList(incoming: Product[], current?: Product[]): Product
|
||||
})
|
||||
}
|
||||
|
||||
/** فقط محصول فعال ادمین (باز) — نه show لحظهای ساعت/مظنه */
|
||||
function isActiveProduct(product: Product): boolean {
|
||||
const active = product.active as unknown
|
||||
return active !== false && active !== 0 && active !== '0'
|
||||
}
|
||||
|
||||
/** استریم گاهی لیست ناقص میفرستد — عضوهای قبلی را دور نریز. */
|
||||
function mergeSocketProductMembership(incoming: Product[], current?: Product[]): Product[] {
|
||||
if (!current?.length) return incoming
|
||||
|
||||
const incomingById = new Map(incoming.map((item) => [item.id, item]))
|
||||
const currentIds = new Set(current.map((item) => item.id))
|
||||
const hasAdded = incoming.some((item) => !currentIds.has(item.id))
|
||||
const missingFromIncoming = current.some((item) => !incomingById.has(item.id))
|
||||
|
||||
if (missingFromIncoming && !hasAdded && incoming.length < current.length) {
|
||||
return current.map((item) => {
|
||||
const updated = incomingById.get(item.id)
|
||||
return updated ? { ...item, ...updated } : item
|
||||
})
|
||||
}
|
||||
|
||||
const merged = incoming.map((item) => {
|
||||
const prev = current.find((row) => row.id === item.id)
|
||||
return prev ? { ...prev, ...item } : item
|
||||
})
|
||||
|
||||
if (missingFromIncoming) {
|
||||
for (const item of current) {
|
||||
if (!incomingById.has(item.id)) {
|
||||
merged.push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
function applyProductsList(products: Product[]) {
|
||||
const sorted = sortProductsByInventory(products)
|
||||
const visible = products.filter(isActiveProduct)
|
||||
const sorted = sortProductsByInventory(visible)
|
||||
productsFirstItems.value = sorted
|
||||
pruneEmptyTypeItems()
|
||||
|
||||
if (
|
||||
productsActiveKey.value !== 'همه' &&
|
||||
!sorted.some((p) => p.group_type === productsActiveKey.value)
|
||||
) {
|
||||
productsActiveKey.value = 'همه'
|
||||
}
|
||||
|
||||
changeProductsTab()
|
||||
productsPageLoading.value = false
|
||||
}
|
||||
@@ -122,10 +188,12 @@ export function applyProductsListFromSocket(products: Product[]) {
|
||||
return
|
||||
}
|
||||
|
||||
const merged = mergeLiveProductList(
|
||||
products,
|
||||
getProductsCatalogRef().value.length ? getProductsCatalogRef().value : productsFirstItems.value,
|
||||
)
|
||||
const base =
|
||||
getProductsCatalogRef().value.length
|
||||
? getProductsCatalogRef().value
|
||||
: productsFirstItems.value
|
||||
const withImages = mergeLiveProductList(products, base)
|
||||
const merged = mergeSocketProductMembership(withImages, base)
|
||||
getProductsCatalogRef().value = merged
|
||||
applyProductsList(merged)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
>
|
||||
<ARadioButton class="cats" value="همه">همه</ARadioButton>
|
||||
<ARadioButton
|
||||
v-for="item in productsTypeItems"
|
||||
v-for="item in visibleTypeItems"
|
||||
:key="item"
|
||||
class="cats"
|
||||
:value="item"
|
||||
@@ -67,7 +67,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import router from '@/router'
|
||||
import ProductItem from '@/views/base/products/components/ProductItem.vue'
|
||||
@@ -82,7 +82,7 @@ import MobileProductsView from '@/views/base/products/mobile/MobileProductsView.
|
||||
import { isProductVisible } from '@/utils/productPrice'
|
||||
import {
|
||||
productsActiveKey,
|
||||
productsTypeItems,
|
||||
productsFirstItems,
|
||||
productsItems,
|
||||
productsPageLoading,
|
||||
changeProductsTab,
|
||||
@@ -96,6 +96,11 @@ const selectedProduct = ref<Product>()
|
||||
const viewVertical = ref(true)
|
||||
const isDesktop = useDesktopBreakpoint()
|
||||
|
||||
const visibleTypeItems = computed(() => {
|
||||
const all = productsFirstItems.value ?? []
|
||||
return productsTypeItems.value.filter((key) => all.some((p) => p.group_type === key))
|
||||
})
|
||||
|
||||
function clickProduct(item: Product) {
|
||||
if (isProductVisible(item)) {
|
||||
router.push({ name: 'productDetail', params: { id: item.id } })
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
>
|
||||
<ARadioButton class="cats" value="همه">همه</ARadioButton>
|
||||
<ARadioButton
|
||||
v-for="item in productsTypeItems"
|
||||
v-for="item in visibleTypeItems"
|
||||
:key="item"
|
||||
class="cats"
|
||||
:value="item"
|
||||
@@ -62,7 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Product } from '@/models/products'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import ProductItem from '@/views/base/products/components/ProductItem.vue'
|
||||
@@ -70,7 +70,7 @@ import ProductItemVertical from '@/views/base/products/components/ProductItemVer
|
||||
import img from '@/assets/img/user_ly1.svg'
|
||||
import {
|
||||
productsActiveKey,
|
||||
productsTypeItems,
|
||||
productsFirstItems,
|
||||
productsItems,
|
||||
productsPageLoading,
|
||||
changeProductsTab,
|
||||
@@ -81,6 +81,11 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const viewVertical = ref(true)
|
||||
|
||||
const visibleTypeItems = computed(() => {
|
||||
const all = productsFirstItems.value ?? []
|
||||
return productsTypeItems.value.filter((key) => all.some((p) => p.group_type === key))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user