230 lines
8.5 KiB
Vue
230 lines
8.5 KiB
Vue
<!--
|
|||
|
|
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>
|