Admin order edit with category chips and timeline; customer checkout charges variant rialWage; product tags; retire the unused user-app-v2 tree. Co-authored-by: Cursor <cursoragent@cursor.com>
231 lines
4.9 KiB
Vue
231 lines
4.9 KiB
Vue
<!--
|
||
Full-width horizontal product row for home «تازهها».
|
||
Same fields and morph open as ProductCard — only the composition changes.
|
||
-->
|
||
<template>
|
||
<article ref="root" class="pr" data-product-origin @click="open">
|
||
<div class="pr__art">
|
||
<img v-if="image" v-lazy="image" :alt="title" />
|
||
<div v-else class="pr__artEmpty" aria-hidden="true">
|
||
<Icon icon="vuesax-linear:diamonds" size="22" />
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
class="pr__fav"
|
||
:class="{ 'is-on': favorites.isFavorite(id) }"
|
||
:aria-label="favorites.isFavorite(id) ? 'حذف از علاقهمندی' : 'افزودن به علاقهمندی'"
|
||
@click.stop="favorites.toggle(id)"
|
||
>
|
||
<Icon :icon="favorites.isFavorite(id) ? 'vuesax-linear:heart-fill' : 'vuesax-linear:heart'" size="15" />
|
||
</button>
|
||
|
||
<span v-if="!is_exist" class="pr__flag">ناموجود</span>
|
||
<span v-else-if="isPreOrder && qty === 0" class="pr__flag pr__flag--soft">پیشسفارش</span>
|
||
</div>
|
||
|
||
<div class="pr__body">
|
||
<h3 class="pr__title">{{ title }}</h3>
|
||
<div v-if="tags.length" class="pr__tags">
|
||
<span v-for="tag in tags" :key="tag" class="pr__tag">{{ tag }}</span>
|
||
</div>
|
||
<div class="pr__meta">
|
||
<span class="pr__weight">{{ min_gram }} گرم</span>
|
||
<span v-if="wage != null" class="pr__wage">اجرت {{ wageLabel }}٪</span>
|
||
</div>
|
||
<span class="pr__cta">مشاهده</span>
|
||
</div>
|
||
|
||
<Icon icon="vuesax-linear:arrow-left-1" size="18" class="pr__chev" aria-hidden="true" />
|
||
</article>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import Icon from '../icon/Icon.vue'
|
||
import { useProduct } from '../../composables/useProduct'
|
||
import { openProduct } from '../../composables/useProductMorph'
|
||
import { useFavoritesStore } from '../../stores/favoritesStore'
|
||
import type { ProductSummary } from '../../api/client'
|
||
|
||
const props = defineProps<{ value: ProductSummary }>()
|
||
|
||
const favorites = useFavoritesStore()
|
||
const { image, id, title, min_gram, wage, is_exist, qty, isPreOrder } = useProduct(props.value)
|
||
const tags = computed(() => (props.value.tags ?? []).filter(Boolean))
|
||
|
||
const root = ref<HTMLElement | null>(null)
|
||
function open() {
|
||
openProduct(props.value, root.value)
|
||
}
|
||
|
||
const wageLabel = computed(() => {
|
||
const w = wage.value
|
||
if (w == null) return ''
|
||
return w % 1 === 0 ? String(w) : w.toFixed(2)
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.pr {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
width: 100%;
|
||
padding: 8px 12px 8px 10px;
|
||
cursor: pointer;
|
||
background: @canvas;
|
||
border: 1px solid @hairline;
|
||
border-radius: 18px;
|
||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||
}
|
||
|
||
.pr:active {
|
||
transform: scale(0.99);
|
||
}
|
||
|
||
.pr__art {
|
||
position: relative;
|
||
flex: 0 0 88px;
|
||
width: 88px;
|
||
height: 88px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
background: #f7f7f7;
|
||
}
|
||
|
||
.pr__art img {
|
||
width: 100%;
|
||
height: 100%;
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
padding: 4px;
|
||
box-sizing: border-box;
|
||
object-fit: contain;
|
||
object-position: center;
|
||
display: block;
|
||
}
|
||
|
||
html.theme-dark .pr__art img {
|
||
background: @photoMat;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.pr__artEmpty {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: grid;
|
||
place-items: center;
|
||
color: @inkFaint;
|
||
}
|
||
|
||
.pr__fav {
|
||
position: absolute;
|
||
top: 6px;
|
||
left: 6px;
|
||
width: 26px;
|
||
height: 26px;
|
||
display: grid;
|
||
place-items: center;
|
||
border: 0;
|
||
cursor: pointer;
|
||
border-radius: 50%;
|
||
color: @inkMuted;
|
||
background: alpha(@canvas, 90%);
|
||
backdrop-filter: blur(6px);
|
||
}
|
||
|
||
.pr__fav.is-on {
|
||
color: @wine;
|
||
}
|
||
|
||
.pr__flag {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 6px;
|
||
padding: 3px 7px;
|
||
border-radius: 999px;
|
||
font-size: 9.5px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
background: @primary;
|
||
}
|
||
|
||
.pr__flag--soft {
|
||
background: @wineSoft;
|
||
}
|
||
|
||
.pr__body {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
|
||
.pr__title {
|
||
margin: 0;
|
||
font-size: 13.5px;
|
||
font-weight: 700;
|
||
color: @ink;
|
||
line-height: 1.5;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.pr__tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
}
|
||
|
||
.pr__tag {
|
||
padding: 2px 7px;
|
||
border-radius: 999px;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
color: @wine;
|
||
background: alpha(@wine, 10%);
|
||
line-height: 1.4;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.pr__meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
}
|
||
|
||
.pr__weight {
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: @inkSoft;
|
||
}
|
||
|
||
.pr__wage {
|
||
font-size: 11px;
|
||
color: @inkFaint;
|
||
padding-inline-start: 7px;
|
||
border-inline-start: 1px solid @hairline;
|
||
}
|
||
|
||
.pr__cta {
|
||
margin-top: 2px;
|
||
font-size: 11.5px;
|
||
font-weight: 700;
|
||
color: @wine;
|
||
}
|
||
|
||
.pr__chev {
|
||
flex: 0 0 auto;
|
||
color: @inkFaint;
|
||
}
|
||
</style>
|