feat: initial commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<!--
|
||||
Ported from legacy henza_pwa/src/views/search/collection/index.vue.
|
||||
Filters the one flat product list client-side by collection id (route
|
||||
param) — no server-side ciny_id query param exists; consistent with the
|
||||
rest of this port's "small catalog, client-side filtering" approach.
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<AppHeader>
|
||||
<nav class="grid grid-cols-10 justify-center items-center">
|
||||
<div class="w-full col-span-1" @click="router.push({ name: 'search' })">
|
||||
<Icon icon="vuesax-linear:arrow-right-1" class="ml-2 text-white" size="19" />
|
||||
</div>
|
||||
<div class="w-full col-span-8 flex mb-0 justify-center items-center h-[19px]">
|
||||
<Icon icon="vuesax-linear:money-2" class="ml-2 text-white" size="19" />
|
||||
<h4 class="bold mb-0 text-white">{{ route.query.page }}</h4>
|
||||
</div>
|
||||
<div class="w-full col-span-1"></div>
|
||||
</nav>
|
||||
</AppHeader>
|
||||
<div class="flex flex-col gap-4 m-4 p-2 bg-gray-50 h-[90vh] rounded-lg">
|
||||
<span class="text-center font-bold mt-2">برای مشاهده جزئیات محصول روی آیتم ضربه بزنید</span>
|
||||
<Spin :spinning="loadingPage">
|
||||
<div class="grid grid-cols-4 gap-8 overflow-y-auto mx-4 max-h-[85vh] pb-10">
|
||||
<CollectionCard v-for="item in collectionProducts" :key="item.id" @click="openDrawer(item.id)" :item="item" />
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
<Drawer v-model:visible="visible" placement="bottom" :closable="false" class="rtl" height="100vh" :bodyStyle="{ padding: 0, position: 'relative' }">
|
||||
<SingleProduct v-if="activeProductId" :id="activeProductId" @closeDrawer="closeDrawer" :key="activeProductId" />
|
||||
</Drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Drawer, Spin } from 'ant-design-vue'
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import AppHeader from '../../components/AppHeader.vue'
|
||||
import CollectionCard from '../../components/product/CollectionCard.vue'
|
||||
import SingleProduct from '../../components/product/SingleProduct.vue'
|
||||
import { api, type ProductSummary } from '../../api/client'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const visible = ref(false)
|
||||
const activeProductId = ref<string | null>(null)
|
||||
const loadingPage = ref(false)
|
||||
const allProducts = ref<ProductSummary[]>([])
|
||||
|
||||
const collectionProducts = computed(() => allProducts.value.filter((p) => p.collections.some((c) => c.collectionId === route.params.id)))
|
||||
|
||||
onMounted(async () => {
|
||||
loadingPage.value = true
|
||||
try {
|
||||
allProducts.value = await api.listProducts()
|
||||
} finally {
|
||||
loadingPage.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function openDrawer(id: string) {
|
||||
activeProductId.value = id
|
||||
visible.value = true
|
||||
}
|
||||
function closeDrawer() {
|
||||
visible.value = false
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,118 @@
|
||||
<!--
|
||||
Ported from legacy henza_pwa/src/views/search/searchPage.vue. All filtering
|
||||
happens client-side against the one flat product list (no server-side query
|
||||
params exist for any of this — consistent with NewestView's own "single
|
||||
fetch" adaptation from an earlier batch).
|
||||
-->
|
||||
<template>
|
||||
<Spinner :spinning="loadingPage" tip="درحال دریافت اطلاعات">
|
||||
<header class="w-full z-10 top-0 bg-white shadow-md fixed">
|
||||
<div class="p-[16px]">
|
||||
<div class="pb-[10px] flex w-fit justify-center items-center text-primary cursor-pointer" v-if="showFilter" @click="hideFilters()">
|
||||
<Icon icon="vuesax-linear:arrow-right-1" class="ml-1" size="25" />
|
||||
بازگشت
|
||||
</div>
|
||||
<SearchInput @search="search" place-holder="جستجوی دسته بندی، محصولات و ..." />
|
||||
</div>
|
||||
<div class="no-scrollbar flex overflow-x-auto my-[16px] mt-0 pr-[16px]" v-if="!showFilter">
|
||||
<Button :type="('secondary' as any)" class="btn-secondary ml-[8px] whitespace-nowrap" v-if="filterKeys.length > 0" @click="showFilters">{{ `${filterKeys.length} فیلتر` }}</Button>
|
||||
<Button :type="('secondary' as any)" class="ml-[8px] whitespace-nowrap" v-else @click="showFilters">اعمال فیلتر</Button>
|
||||
<div class="flex-1 flex overflow-x-auto no-scrollbar pl-[16px]">
|
||||
<Button :type="('secondary' as any)" :ghost="!!filters.query.cats" class="last:ml-0 ml-[8px] whitespace-nowrap" @click="setCats(undefined)">همه</Button>
|
||||
<Button
|
||||
:type="('secondary' as any)"
|
||||
:ghost="!(filters.query.cats && filters.query.cats[0] === item.id)"
|
||||
class="last:ml-0 ml-[8px] whitespace-nowrap"
|
||||
v-for="item in availableCategories"
|
||||
:key="item.id"
|
||||
@click="setCats(item.id)"
|
||||
>{{ item.title }}</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Filters v-model:filterKeys="filterKeys" v-show="showFilter" v-model="filters.query" :products="allProducts" @change="hideFilters" />
|
||||
|
||||
<ProductList class="px-[16px] pb-[66px]" padding="146px" v-show="!showFilter" @pull="() => {}" :value="filteredProducts" :ciny="availableCollections" :loading="false" :flag="filteredProducts.length === 0" />
|
||||
</Spinner>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { Button } from 'ant-design-vue'
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import Spinner from '../../components/Spinner.vue'
|
||||
import SearchInput from '../../components/search/SearchInput.vue'
|
||||
import Filters from '../../components/search/Filters.vue'
|
||||
import ProductList from '../../components/product/ProductList.vue'
|
||||
import { useFiltersStore } from '../../stores/filtersStore'
|
||||
import { useNavStore } from '../../stores/navStore'
|
||||
import { api, type ProductSummary } from '../../api/client'
|
||||
|
||||
const filters = useFiltersStore()
|
||||
const nav = useNavStore()
|
||||
|
||||
const loadingPage = ref(false)
|
||||
const allProducts = ref<ProductSummary[]>([])
|
||||
const showFilter = ref(false)
|
||||
const filterKeys = ref<string[]>([])
|
||||
|
||||
const availableCategories = computed(() => {
|
||||
const seen = new Map<string, { id: string; title: string }>()
|
||||
for (const p of allProducts.value) {
|
||||
if (p.category) seen.set(p.category.id, p.category)
|
||||
}
|
||||
return [...seen.values()]
|
||||
})
|
||||
|
||||
const availableCollections = computed(() => {
|
||||
const seen = new Map<string, { id: string; title: string }>()
|
||||
for (const p of allProducts.value) {
|
||||
for (const c of p.collections) seen.set(c.collection.id, { id: c.collection.id, title: c.collection.title })
|
||||
}
|
||||
return [...seen.values()]
|
||||
})
|
||||
|
||||
const filteredProducts = computed(() => {
|
||||
let list = allProducts.value
|
||||
const q = filters.query
|
||||
if (q.text) list = list.filter((p) => p.title.includes(q.text!))
|
||||
if (q.cats && q.cats.length > 0) list = list.filter((p) => p.category && q.cats!.includes(p.category.id))
|
||||
if (q.brands.length > 0) list = list.filter((p) => p.brand && q.brands.includes(p.brand.id))
|
||||
if (q.limit_gram.length === 2) {
|
||||
list = list.filter((p) => p.variants.some((v) => parseFloat(v.rashWeightGrams) >= q.limit_gram[0] && parseFloat(v.rashWeightGrams) <= q.limit_gram[1]))
|
||||
}
|
||||
return list
|
||||
})
|
||||
|
||||
async function loadProducts() {
|
||||
loadingPage.value = true
|
||||
try {
|
||||
allProducts.value = await api.listProducts()
|
||||
} finally {
|
||||
loadingPage.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function search(value: string | undefined) {
|
||||
filters.query.text = value
|
||||
}
|
||||
function setCats(id: string | undefined) {
|
||||
filters.query.cats = id ? [id] : undefined
|
||||
}
|
||||
function showFilters() {
|
||||
showFilter.value = true
|
||||
nav.setNavVisibility(false)
|
||||
}
|
||||
function hideFilters(event?: { resetFilters: boolean }) {
|
||||
showFilter.value = false
|
||||
nav.setNavVisibility(true)
|
||||
if (event?.resetFilters) {
|
||||
filters.reset()
|
||||
filterKeys.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadProducts)
|
||||
</script>
|
||||
Reference in New Issue
Block a user