Compare commits

...

7 Commits

Author SHA1 Message Date
Константин Уколов 085253d77d Merge remote-tracking branch 'origin/main' into main
continuous-integration/drone/push Build is passing Details
# Conflicts:
#	src/entities/product/ui/ProductImage.vue
2024-09-02 02:51:08 +03:00
Константин Уколов eeb2386efc feat: remove dark mode 2024-09-02 02:14:08 +03:00
Константин Уколов 80df33672b feat: redirect from init page 2024-09-02 02:12:29 +03:00
Константин Уколов 9a64f023b9 fix: icon size 2024-09-02 02:07:56 +03:00
Константин Уколов 5868f6af53 feat: add localStorage 2024-09-02 01:18:24 +03:00
Константин Уколов 0130b9aa0d feat: new price count 2024-09-02 00:27:30 +03:00
Константин Уколов 63d31fbe53 feat: add description 2024-08-31 14:04:49 +03:00
15 changed files with 179 additions and 57 deletions

View File

@ -8,5 +8,8 @@ export default defineNuxtConfig({
}, },
}, },
modules: ['@nuxt/ui', '@unocss/nuxt', '@nuxt/eslint'], modules: ['@nuxt/ui', '@unocss/nuxt', '@nuxt/eslint'],
colorMode: {
preference: 'light',
},
compatibilityDate: '2024-07-30', compatibilityDate: '2024-07-30',
}) })

View File

@ -7,7 +7,7 @@ export default defineConfig({
target: 'src/shared/api', target: 'src/shared/api',
schemas: 'src/shared/model', schemas: 'src/shared/model',
client: 'vue-query', client: 'vue-query',
baseUrl: 'https://cake-crm.3crabs.ru', baseUrl: 'https://cake-api.3crabs.ru',
// mock: true, // mock: true,
}, },
input: './cakes.json', input: './cakes.json',

13
pages/index.vue Normal file
View File

@ -0,0 +1,13 @@
<script setup lang="ts">
navigateTo({
path: '/catalog',
})
</script>
<template>
<div />
</template>
<style scoped>
</style>

View File

@ -1 +1,3 @@
export { ProductCard, ProductImage } from './ui' export { ProductCard, ProductImage, ProductPrice } from './ui'
export * as productModel from './model'

View File

@ -0,0 +1 @@
export { useProductPrice } from './use-product-price'

View File

@ -0,0 +1,75 @@
import { useStorage } from '@vueuse/core'
import type { CrmVariant } from '~/src/shared/model'
import { useCRMGetCart } from '~/src/shared/api/crm/crm'
export function useProductPrice(variants: CrmVariant[], id: string) {
const amount = ref(0)
const currentUnitPrice = ref(0)
const prevUnitPrice = ref()
const { mutateAsync, data } = useCRMGetCart()
const addedItems = useStorage<{
id: string
count: number
}[]>('added-items', [])
const storageProduct = computed(() => addedItems.value.find(item => item.id === id))
const setInitCurrentPrice = () => {
currentUnitPrice.value = Number(variants[0].price) / 100
}
watch(amount, async (value, oldValue) => {
await mutateAsync({
data: [
{
count: String(value),
productId: id,
},
],
})
const newCurrent = Number(data.value?.data.amount) / 100 || 0
const newPrev = Number(data.value?.data.amountOld) / 100 || 0
currentUnitPrice.value = newCurrent
if (newCurrent !== newPrev) {
prevUnitPrice.value = newPrev
}
else {
prevUnitPrice.value = undefined
}
if (value === 0) {
addedItems.value = addedItems.value.filter(item => item.id !== id)
setInitCurrentPrice()
return
}
if (storageProduct.value) {
storageProduct.value.count = value
return
}
addedItems.value.push({
id,
count: value,
})
})
if (storageProduct.value) {
amount.value = storageProduct.value.count
}
else {
setInitCurrentPrice()
}
return {
amount,
currentUnitPrice,
prevUnitPrice,
}
}

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useProductPrice } from '../model'
import ProductImage from './ProductImage.vue' import ProductImage from './ProductImage.vue'
import { InputNumber } from '~/src/shared/ui' import { InputNumber } from '~/src/shared/ui'
import type { CrmProduct } from '~/src/shared/model' import type { CrmProduct } from '~/src/shared/model'
@ -7,52 +8,13 @@ const props = defineProps<{
product: CrmProduct product: CrmProduct
}>() }>()
const amount = ref(0) const { amount, currentUnitPrice, prevUnitPrice } = useProductPrice(props.product.variants || [], props.product.id!)
const currentUnitPrice = ref(0)
const prevUnitPrice = ref()
function onImageClick() { function onImageClick() {
navigateTo( navigateTo(
`/products/${props.product.id}`, `/products/${props.product.id}`,
) )
} }
function updatePrices() {
if (!props.product.variants?.length) {
return
}
for (let i = 0; i < props.product.variants.length; i++) {
const variant = props.product.variants[i]
let max = Number.POSITIVE_INFINITY
let min = 0
variant.properties?.forEach((property) => {
if (property.name === 'min') {
min = Number(property.value)
}
if (property.name === 'max')
max = Number(property.value)
})
if (amount.value >= min && amount.value <= max) {
currentUnitPrice.value = Number(variant.price) / 100
if (i !== 0) {
prevUnitPrice.value = Number(props.product.variants[i - 1].price) / 100
}
else {
prevUnitPrice.value = undefined
}
return
}
}
}
updatePrices()
</script> </script>
<template> <template>
@ -67,10 +29,10 @@ updatePrices()
<div>{{ product.name }}</div> <div>{{ product.name }}</div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="text-2xl font-bold" :class="{ 'text-pink-800': prevUnitPrice }"> <div class="text-2xl font-bold" :class="{ 'text-pink-800': prevUnitPrice }">
{{ currentUnitPrice * (amount || 1) }} {{ currentUnitPrice }}
</div> </div>
<div v-if="prevUnitPrice" class="line-through text-slate-400"> <div v-if="prevUnitPrice" class="line-through text-slate-400">
{{ prevUnitPrice * amount }} {{ prevUnitPrice }}
</div> </div>
</div> </div>
@ -78,7 +40,7 @@ updatePrices()
<UButton v-if="!amount" class="w-full justify-center" size="xl" @click="amount = 1"> <UButton v-if="!amount" class="w-full justify-center" size="xl" @click="amount = 1">
В корзину В корзину
</UButton> </UButton>
<InputNumber v-else v-model="amount" @update:model-value="updatePrices" /> <InputNumber v-else v-model="amount" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ defineProps<{
<template> <template>
<div class="relative flex flex-col justify-between overflow-hidden"> <div class="relative flex flex-col justify-between overflow-hidden">
<img alt="product_img" :src="`https://cake-api.3crabs.ru${path}`"> <img class="w-full h-full" alt="product_img" :src="`https://cake-api.3crabs.ru${path}`">
<div class="flex gap-1 absolute top-3 left-3"> <div class="flex gap-1 absolute top-3 left-3">
<UBadge v-for="(label, index) in labels" :key="index" :ui="{ rounded: 'rounded-full' }" size="md"> <UBadge v-for="(label, index) in labels" :key="index" :ui="{ rounded: 'rounded-full' }" size="md">

View File

@ -0,0 +1,31 @@
<script setup lang="ts">
import { useProductPrice } from '../model'
import { InputNumber } from '~/src/shared/ui'
import type { CrmVariant } from '~/src/shared/model'
const props = defineProps<{
variants: CrmVariant[]
productId: string
}>()
const { amount, currentUnitPrice, prevUnitPrice } = useProductPrice(props.variants, props.productId)
</script>
<template>
<div class="bg-gray-50 rounded-2xl border border-slate-200 flex gap-5 justify-between p-5 items-center">
<div class="flex flex-col gap-1 flex-1">
<div class="text-2xl font-bold" :class="{ 'text-pink-800': prevUnitPrice }">
{{ currentUnitPrice }}
</div>
<div v-if="prevUnitPrice" class="line-through text-slate-400">
{{ prevUnitPrice }}
</div>
</div>
<div class="flex-1">
<UButton v-if="!amount" class="w-full justify-center" size="xl" @click="amount = 1">
В корзину
</UButton>
<InputNumber v-else v-model="amount" />
</div>
</div>
</template>

View File

@ -1,2 +1,3 @@
export { default as ProductCard } from './ProductCard.vue' export { default as ProductCard } from './ProductCard.vue'
export { default as ProductImage } from './ProductImage.vue' export { default as ProductImage } from './ProductImage.vue'
export { default as ProductPrice } from './ProductPrice.vue'

View File

@ -0,0 +1 @@
export { CartButton } from './ui'

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { useStorage } from '@vueuse/core'
const addedItems = useStorage<{
id: string
count: number
}[]>('added-items', [])
const addedItemsAmount = computed(() => addedItems.value.length)
</script>
<template>
<UButton
variant="soft"
size="xl"
class="border text-black border-pink-500"
icon="heroicons-shopping-cart"
:trailing-icon="addedItemsAmount ? 'badge' : undefined"
>
Корзина
<template #trailing>
<UBadge
v-if="addedItemsAmount"
size="xs" :label="addedItemsAmount"
/>
</template>
</UButton>
</template>

View File

@ -0,0 +1 @@
export { default as CartButton } from './CartButton.vue'

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ProductImage } from '~/src/entities/product' import { ProductImage, ProductPrice } from '~/src/entities/product'
import { useCRMGetProduct } from '~/src/shared/api/crm/crm' import { useCRMGetProduct } from '~/src/shared/api/crm/crm'
import { Header } from '~/src/widgets/header' import { Header } from '~/src/widgets/header'
import { StandardLayout } from '~/src/shared/ui' import { StandardLayout } from '~/src/shared/ui'
@ -16,15 +16,24 @@ const { data } = useCRMGetProduct(route.params.id as string)
<Header /> <Header />
</template> </template>
<div class="flex gap-12"> <div class="flex gap-12">
<ProductImage :path="data?.data.product?.images?.[0]" class="w-128 h-128 rounded-[20px]" :labels="data?.data.product?.labels" /> <ProductImage :path="data?.data.product?.images?.[0]" class="w-125 h-125 rounded-[20px]" :labels="data?.data.product?.labels" />
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-6">
<div class="font-bold text-4xl"> <div class="font-bold text-4xl">
{{ data?.data.product?.name }} {{ data?.data.product?.name }}
</div> </div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div>Надо заполнить на бэке - тяжело тестить</div> <div v-for="characteristic in data?.data.product?.characteristics" :key="characteristic.name" class="flex gap-2 items-end">
<span class="font-bold">{{ characteristic.name }}:</span>
<span class="text-sm font-medium">{{ characteristic.value }}</span>
</div>
</div> </div>
<ProductPrice
v-if="data?.data.product?.id && data.data.product.variants"
:product-id="data.data.product.id"
:variants="data.data.product.variants"
/>
</div> </div>
</div> </div>
<template #footer> <template #footer>

View File

@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { CartButton } from '~/src/features/cart'
function goToCatalogPage() { function goToCatalogPage() {
navigateTo({ navigateTo({
path: '/catalog', path: '/catalog',
@ -17,14 +19,7 @@ function goToCatalogPage() {
Каталог Каталог
</UButton> </UButton>
<UInputMenu placeholder="Поиск по каталогу" size="xl" class="w-full" leading-icon="heroicons-magnifying-glass" /> <UInputMenu placeholder="Поиск по каталогу" size="xl" class="w-full" leading-icon="heroicons-magnifying-glass" />
<UButton variant="soft" size="xl" class="border text-black border-pink-500" icon="heroicons-shopping-cart" trailing-icon="badge"> <CartButton />
Корзина
<template #trailing>
<UBadge
size="xs" label="2"
/>
</template>
</UButton>
</div> </div>
</div> </div>
</template> </template>