Files
tehran_tala/apps/user-app/src/views/CartView.vue
T

230 lines
8.5 KiB
Vue
Raw Normal View History

2026-09-09 06:46:27 +03:30
<!--
Ported from legacy henza_pwa/src/views/cart.vue.
"ثبت نهایی سفارش" (submit order) is wired for real against the actual,
already-existing POST /orders endpoint — not a stub. Legacy's own
`/set-order` payload had two hardcoded placeholder numbers with the
original author's own comment "how to get?!" (wage_weight/weight sent
regardless of actual cart contents) — a genuine, self-acknowledged
incompleteness in legacy itself, not something to faithfully reproduce.
The real Orders API computes gold weight server-side from
productVariantId + wagePercent, so there's nothing dubious to port here;
wagePercent is taken from the cart item's pinned variant.
ADAPTED (not in legacy's cart.vue): submitting now requires being logged in
for real (a real customerId is required by the Orders API). Legacy's own
router didn't gate the cart route itself, so this gate is new — reusing the
same login-nudge dialog pattern as Home/SingleProduct for a consistent UX
rather than a raw failure.
"لیست سفارشات" routes to `home` instead of the legacy `ordersList` route,
which doesn't exist in this port yet (deferred).
EXISTING ISSUE, ported as-is: the unscoped `.rtl { direction: ltr }` rule
below is dead — it can never actually apply, since the global `.rtl`
defined in style.less carries `!important` and a non-!important rule can
never override an !important one regardless of load order. Kept for fidelity.
-->
<template>
<Spinner :spinning="cartData.loadingPage" tip="درحال دریافت اطلاعات">
<div class="fixed top-0 z-10 w-full">
<AppHeader>
<nav class="flex justify-center items-center">
<div class="flex mb-0 items-center h-[19px]">
<Icon icon="vuesax-linear:bag-happy" class="ml-2 text-white" size="19" />
<h4 class="bold mb-0 text-white">سبد خرید</h4>
</div>
<span />
</nav>
</AppHeader>
</div>
<div class="h-16"></div>
<container class="overflow-y-scroll pt-4">
<div v-show="cartData.step === 1">
<EmptyState v-if="cart.items.length === 0" :image="cartEmpty" />
<template v-else>
<CartItem
v-for="item in cart.items"
:value="item"
:key="item.pid + item.vid"
class="mb-4"
@note="
cartData.tempRow = item;
cartData.showPopUp = true;
cartData.tempRowIndex = $event;
formModel.note = item.note ?? undefined;
"
/>
</template>
</div>
<div v-show="cartData.step === 2" class="h-full flex justify-center items-center flex-col">
<h4 class="text-primary">اطلاعات سفارش با موفقیت ثبت شد</h4>
<div class="mb-2 text-secondary">شماره سفارش {{ cartData.orderID }}</div>
<small class="mb-2 text-secondary">برای بررسی وضعیت سفارش خود روی لیست سفارشات کلیک کنید</small>
<Button
type="primary"
@click="
async () => {
await router.push({ name: 'home' });
cartData.step = 1;
}
"
>
لیست سفارشات
</Button>
</div>
</container>
<Drawer v-model:visible="cartData.showPopUp" placement="bottom" v-if="cartData.tempRow?.product" :closable="false" height="280px" :class="['henza-rounded-drawer rtl', 'bg-white', 'text-primary']">
<div class="mb-2 font-semibold">توضیح برای محصول {{ cartData.tempRow.product.title }}</div>
<Form :model="formModel" name="noteForm" layout="vertical" autocomplete="off" @finish="setNote" class="henza-form bg-primaryLighten">
<FormItem has-feedback name="note">
<Textarea v-model:value="formModel.note" :rows="5" />
</FormItem>
<Button class="btn-px-double w-full" type="primary" size="large" html-type="submit">ثبت</Button>
</Form>
</Drawer>
<DialogVant :timeOut="-1" v-model:show="cartData.notify" :closeOnClickOverlay="true">
<template v-slot:img>
<img :src="dialogEmpty4" alt="" class="opacity-70" />
</template>
<template v-slot:title>آیا مطمئن به ثبت سفارش هستید ؟</template>
<template v-slot:button>
<div class="text-center text-[#1C2D56]" @click="submitOrder">بله</div>
<div class="text-center text-[#6b7280]" @click="cartData.notify = false">بی خیال</div>
</template>
</DialogVant>
<DialogVant :timeOut="-1" v-model:show="loginNotify" :closeOnClickOverlay="true">
<template v-slot:img>
<img :src="loginDialogImage" alt="" class="opacity-80" />
</template>
<template v-slot:title>
<div class="p-8 text-justify">برای ثبت سفارش ابتدا باید وارد حساب کاربری خود شوید.</div>
</template>
<template v-slot:button>
<div class="text-center text-[#1C2D56]" @click="router.push({ name: 'login' })">ورود/ثبت نام</div>
<div class="text-center text-danger" @click="loginNotify = false">بیخیال</div>
</template>
</DialogVant>
<div class="h-36"></div>
<div class="shadowset flex flex-row-reverse p-[16px] pb-[75px] justify-between fixed bottom-0 w-full bg-white z-[2] items-center border-t border-solid border-primary/10 shadow-md" v-if="cart.items.length > 0 && cartData.step === 1">
<div class="text-[16px]">
جمع وزن :
<span class="font-bold">{{ cart.total() }}</span
><span class="pr-1">گرم</span>
</div>
<Button class="w-1/2" type="primary" @click="cartData.notify = true">ثبت نهایی سفارش</Button>
</div>
</Spinner>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { Button, Drawer, Form, FormItem, Textarea } from 'ant-design-vue'
import cartEmpty from '../assets/img/empty/cartEmpty.svg'
import dialogEmpty4 from '../assets/img/empty/dialogEmpty4.svg'
import loginDialogImage from '../assets/img/empty/dialogEmpty1.svg'
import Icon from '../components/icon/Icon.vue'
import AppHeader from '../components/AppHeader.vue'
import Spinner from '../components/Spinner.vue'
import EmptyState from '../components/EmptyState.vue'
import DialogVant from '../components/DialogVant.vue'
import CartItem from '../components/product/CartItem.vue'
import { useCartStore, type CartItem as CartItemType } from '../stores/cartStore'
import { useSessionStore } from '../stores/session'
import { api } from '../api/client'
const router = useRouter()
const cart = useCartStore()
const session = useSessionStore()
const loginNotify = ref(false)
const cartData = reactive({
loadingPage: false,
showPopUp: false,
step: 1,
notify: false,
orderID: '',
tempRowIndex: -1,
tempRow: { product: undefined } as Partial<CartItemType>,
})
const formModel = reactive<{ note: string | undefined }>({ note: undefined })
const setNote = () => {
cart.updateFeild(formModel.note, cartData.tempRowIndex, cartData.tempRow as CartItemType, 'note')
cartData.showPopUp = false
formModel.note = undefined
cartData.tempRow = { product: undefined }
}
const submitOrder = async () => {
cartData.notify = false
if (!session.customerId) {
loginNotify.value = true
return
}
try {
cartData.loadingPage = true
const order = await api.createOrder({
customerId: session.customerId,
items: cart.items.map((item) => {
const variant = cart.variant(item)
return {
productVariantId: item.vid,
quantity: item.qty,
wagePercent: variant ? parseFloat(variant.defaultWagePercent) : 0,
}
}),
})
cartData.orderID = order.orderNumber
cartData.step = 2
cart.clear()
} catch (e) {
console.log(e)
} finally {
cartData.loadingPage = false
}
}
onMounted(async () => {
await cart.init()
})
</script>
<style lang="less">
.rtl {
direction: ltr;
.ant-form-vertical .ant-form-item-label,
.ant-col-24.ant-form-item-label,
.ant-col-xl-24.ant-form-item-label {
text-align: right !important;
}
}
.btn-px-double {
padding-right: 24px !important;
padding-left: 24px !important;
}
.ant-btn-primary[disabled],
.ant-btn-primary[disabled]:hover,
.ant-btn-primary[disabled]:focus,
.ant-btn-primary[disabled]:active {
border-color: @primary-color;
background: @primary-color;
}
.shadowset {
box-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.1);
border-radius: 10px 10px 0 0;
}
</style>