changes
This commit is contained in:
@@ -24,7 +24,10 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-dvh">
|
<div
|
||||||
|
class="absolute w-dvw h-dvh has-background-blue-100"
|
||||||
|
style="z-index: 40; /* because .navbar is 30 */"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute is-flex is-gap-1.5 is-flex-direction-column is-justify-content-center is-align-items-center"
|
class="absolute is-flex is-gap-1.5 is-flex-direction-column is-justify-content-center is-align-items-center"
|
||||||
style="top: 40%; left: 50%; transform: translateX(-50%)"
|
style="top: 40%; left: 50%; transform: translateX(-50%)"
|
||||||
|
|||||||
@@ -215,10 +215,17 @@ if (selected.value) doSelect(selected.value);
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.optionid,
|
() => props.optionid,
|
||||||
() => {
|
(newVal) => {
|
||||||
if (props.optionid) selected.value = $find(suggestions.value, { id: props.optionid });
|
if (!newVal) {
|
||||||
if (selected.value) doSelect(selected.value);
|
selected.value = undefined;
|
||||||
else value.value = undefined;
|
value.value = undefined;
|
||||||
|
clearTrigger.value++;
|
||||||
|
} else {
|
||||||
|
selected.value = $find(suggestions.value, { id: newVal });
|
||||||
|
if (selected.value) {
|
||||||
|
doSelect(selected.value);
|
||||||
|
} else value.value = undefined;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<span>{{ $stripHtml(v[name] || v.fullname || v.code || "n/a", 75) }}</span>
|
<span>{{ $stripHtml(v[name] || v.fullname || v.code || "n/a", 75) }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="checked[i] && !notick"
|
v-if="tick && checked[i]"
|
||||||
class="icon right-3 has-background-inherit"
|
class="icon right-3 has-background-inherit"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
>
|
>
|
||||||
<span>{{ $stripHtml(v[name] || v.fullname || v.code || "n/a", 75) }}</span>
|
<span>{{ $stripHtml(v[name] || v.fullname || v.code || "n/a", 75) }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="checked[i] && notick !== true"
|
v-if="tick && checked[i]"
|
||||||
class="icon right-3 has-background-inherit"
|
class="icon right-3 has-background-inherit"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -64,7 +64,10 @@ const props = defineProps({
|
|||||||
sort: String,
|
sort: String,
|
||||||
selects: String,
|
selects: String,
|
||||||
keyval: String,
|
keyval: String,
|
||||||
notick: Boolean,
|
tick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
inContext: Boolean,
|
inContext: Boolean,
|
||||||
clearTrigger: Number,
|
clearTrigger: Number,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const props = defineProps({
|
|||||||
deleteable: Boolean,
|
deleteable: Boolean,
|
||||||
});
|
});
|
||||||
const { $deleteapi, $numtoString, $snackbar } = useNuxtApp();
|
const { $deleteapi, $numtoString, $snackbar } = useNuxtApp();
|
||||||
const { getCart } = inject("pos");
|
const { getCarts } = inject("pos");
|
||||||
const showConfirmModal = ref();
|
const showConfirmModal = ref();
|
||||||
const isDeleting = ref(false);
|
const isDeleting = ref(false);
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ async function removeFromCart() {
|
|||||||
await $deleteapi("Cart_Item", props.cartItem.id);
|
await $deleteapi("Cart_Item", props.cartItem.id);
|
||||||
isDeleting.value = false;
|
isDeleting.value = false;
|
||||||
$snackbar("Đã xoá sản phẩm khỏi giỏ hàng", "Success");
|
$snackbar("Đã xoá sản phẩm khỏi giỏ hàng", "Success");
|
||||||
getCart();
|
getCarts();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
129
app/components/pos/CartTabs.vue
Normal file
129
app/components/pos/CartTabs.vue
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<script setup>
|
||||||
|
const { carts, activeCartId, activeCartItems, isChangingCus, getCarts } = inject("pos");
|
||||||
|
|
||||||
|
const { $insertapi, $deleteapi } = useNuxtApp();
|
||||||
|
const isAddingCart = ref(false);
|
||||||
|
|
||||||
|
async function addCart() {
|
||||||
|
isAddingCart.value = true;
|
||||||
|
const newCart = await $insertapi("Cart", { notify: false });
|
||||||
|
activeCartId.value = newCart.id;
|
||||||
|
isAddingCart.value = false;
|
||||||
|
getCarts();
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDeletingCart = ref();
|
||||||
|
async function removeCart(cartId) {
|
||||||
|
isDeletingCart.value = cartId;
|
||||||
|
|
||||||
|
await $deleteapi(
|
||||||
|
"Cart_Item",
|
||||||
|
activeCartItems.value.map((c) => c.id),
|
||||||
|
);
|
||||||
|
await $deleteapi("Cart", cartId);
|
||||||
|
isDeletingCart.value = undefined;
|
||||||
|
|
||||||
|
if (cartId === activeCartId.value) {
|
||||||
|
if (carts.value.length === 1) {
|
||||||
|
activeCartId.value = undefined;
|
||||||
|
}
|
||||||
|
const deletedCartIndex = carts.value.findIndex((c) => c.id === cartId);
|
||||||
|
|
||||||
|
if (deletedCartIndex === 0) {
|
||||||
|
activeCartId.value = carts.value[deletedCartIndex + 1].id;
|
||||||
|
} else {
|
||||||
|
activeCartId.value = carts.value[deletedCartIndex - 1].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getCarts();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="tabs is-boxed mb-0">
|
||||||
|
<ul class="is-align-items-stretch">
|
||||||
|
<li
|
||||||
|
v-for="cart in carts"
|
||||||
|
:key="cart.id"
|
||||||
|
:class="['is-size-7 w-41', cart.id === activeCartId && 'is-active']"
|
||||||
|
@click="activeCartId = cart.id"
|
||||||
|
:title="cart.customer__fullname"
|
||||||
|
>
|
||||||
|
<a class="is-justify-content-start h-full relative">
|
||||||
|
<span class="icon mr-0.5">
|
||||||
|
<Icon
|
||||||
|
:name="
|
||||||
|
(cart.id === activeCartId && isChangingCus) || cart.id === isDeletingCart
|
||||||
|
? 'svg-spinners:180-ring-with-bg'
|
||||||
|
: cart.customer
|
||||||
|
? 'material-symbols:person-rounded'
|
||||||
|
: 'material-symbols:person-outline-rounded'
|
||||||
|
"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
:style="{
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ cart.customer__fullname || "Khách hàng mới" }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
@click.stop="removeCart(cart.id)"
|
||||||
|
class="close ml-auto icon rounded-full"
|
||||||
|
>
|
||||||
|
<Icon name="material-symbols:close-rounded" />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
@click="addCart"
|
||||||
|
title="Tạo giỏ hàng"
|
||||||
|
>
|
||||||
|
<a class="new h-full">
|
||||||
|
<Icon
|
||||||
|
:name="isAddingCart ? 'svg-spinners:180-ring-with-bg' : 'material-symbols:add-rounded'"
|
||||||
|
:size="16"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tabs a {
|
||||||
|
background-color: hsl(from var(--bulma-tabs-boxed-link-hover-background-color) h s calc(l + 1));
|
||||||
|
--bulma-tabs-link-padding: 0.4em 1em 0.4em 0.5em;
|
||||||
|
&.new {
|
||||||
|
--bulma-tabs-link-padding: 0.4em 0.5em 0.4em 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs a:hover {
|
||||||
|
background-color: hsl(from var(--bulma-tabs-boxed-link-hover-background-color) h s calc(l - 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs ul {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs li.is-active a {
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
display: none;
|
||||||
|
background-color: var(--bulma-white-ter);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
li:hover .close {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.close:hover {
|
||||||
|
background-color: var(--bulma-grey-lighter);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -22,28 +22,15 @@ function toggleSelected(imeiRec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { cartItems, getCart } = inject("pos");
|
const { activeCart, activeCartItems, getCarts } = inject("pos");
|
||||||
const isAdding = ref(false);
|
const isAdding = ref(false);
|
||||||
|
|
||||||
async function addToCart() {
|
async function addToCart() {
|
||||||
try {
|
try {
|
||||||
isAdding.value = true;
|
isAdding.value = true;
|
||||||
let cart = await $getdata("Cart", {
|
|
||||||
filter: { customer: store.customer },
|
|
||||||
first: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!cart) {
|
|
||||||
const newCart = await $insertapi("Cart", {
|
|
||||||
data: { customer: store.customer },
|
|
||||||
notify: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
cart = newCart;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cartItemsPayload = selectedImeis.value.map((imeiRec) => ({
|
const cartItemsPayload = selectedImeis.value.map((imeiRec) => ({
|
||||||
cart: cart.id,
|
cart: activeCart.value.id,
|
||||||
imei: imeiRec.id,
|
imei: imeiRec.id,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
total_price: imeiRec.variant__price,
|
total_price: imeiRec.variant__price,
|
||||||
@@ -54,7 +41,7 @@ async function addToCart() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$snackbar(`Đã thêm ${newCartItems.length} sản phẩm vào giỏ hàng`, "Success");
|
$snackbar(`Đã thêm ${newCartItems.length} sản phẩm vào giỏ hàng`, "Success");
|
||||||
getCart();
|
getCarts();
|
||||||
emit("close");
|
emit("close");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -72,7 +59,7 @@ async function fetchImeis() {
|
|||||||
const imeisSoldFetched = await $getdata("IMEI_Sold");
|
const imeisSoldFetched = await $getdata("IMEI_Sold");
|
||||||
|
|
||||||
imeis.value = imeisFetched.filter((imeiRec) => {
|
imeis.value = imeisFetched.filter((imeiRec) => {
|
||||||
const inCart = cartItems.value.find((cartItem) => cartItem.imei === imeiRec.id);
|
const inCart = activeCartItems.value.find((cartItem) => cartItem.imei === imeiRec.id);
|
||||||
const sold = imeisSoldFetched.find((imeiSold) => imeiSold.imei === imeiRec.imei);
|
const sold = imeisSoldFetched.find((imeiSold) => imeiSold.imei === imeiRec.imei);
|
||||||
return !inCart && !sold;
|
return !inCart && !sold;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ const emit = defineEmits(["close"]);
|
|||||||
const { $patchapi, $deleteapi, $insertapi, $dayjs, $snackbar } = useNuxtApp();
|
const { $patchapi, $deleteapi, $insertapi, $dayjs, $snackbar } = useNuxtApp();
|
||||||
const id = "confirmOrder";
|
const id = "confirmOrder";
|
||||||
const isPending = ref(false);
|
const isPending = ref(false);
|
||||||
const { cartItems, orderInfo, getCart } = inject("pos");
|
const { activeCart, activeCartItems, orderInfo, getCarts } = inject("pos");
|
||||||
|
|
||||||
const subtotal = computed(() => {
|
const subtotal = computed(() => {
|
||||||
return cartItems.value?.reduce((prev, curr) => prev + curr.imei__variant__price, 0);
|
return activeCartItems.value?.reduce((prev, curr) => prev + curr.imei__variant__price, 0);
|
||||||
});
|
});
|
||||||
const shipping_address = computed(() => {
|
const shipping_address = computed(() => {
|
||||||
return `${orderInfo.value.address.address_detail}, ${orderInfo.value.address.ward}, ${orderInfo.value.address.district}, ${orderInfo.value.address.city}`;
|
return `${orderInfo.value.address.address_detail}, ${orderInfo.value.address.ward}, ${orderInfo.value.address.district}, ${orderInfo.value.address.city}`;
|
||||||
@@ -19,12 +19,12 @@ async function createOrder() {
|
|||||||
isPending.value = true;
|
isPending.value = true;
|
||||||
const invoice = await $insertapi("Invoice", {
|
const invoice = await $insertapi("Invoice", {
|
||||||
data: {
|
data: {
|
||||||
customer: orderInfo.value.customer.id,
|
customer: "",
|
||||||
customer_name: orderInfo.value.customer.fullname,
|
customer_name: "",
|
||||||
customer_phone: orderInfo.value.customer.phone,
|
customer_phone: "",
|
||||||
customer_email: orderInfo.value.customer.email,
|
customer_email: "",
|
||||||
shipping_address: shipping_address.value,
|
shipping_address: shipping_address.value,
|
||||||
product_amount: cartItems.value.length,
|
product_amount: activeCartItems.value.length,
|
||||||
shipping_fee: 0,
|
shipping_fee: 0,
|
||||||
total_amount: subtotal.value,
|
total_amount: subtotal.value,
|
||||||
discount_amount: 0,
|
discount_amount: 0,
|
||||||
@@ -38,7 +38,7 @@ async function createOrder() {
|
|||||||
notify: false,
|
notify: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const imeisSoldPayload = cartItems.value.map((cartItem) => ({
|
const imeisSoldPayload = activeCartItems.value.map((cartItem) => ({
|
||||||
imei: cartItem.imei__imei,
|
imei: cartItem.imei__imei,
|
||||||
variant: cartItem.imei__variant,
|
variant: cartItem.imei__variant,
|
||||||
sold_date: $dayjs().format("YYYY-MM-DD"),
|
sold_date: $dayjs().format("YYYY-MM-DD"),
|
||||||
@@ -49,7 +49,7 @@ async function createOrder() {
|
|||||||
notify: false,
|
notify: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoiceDetailPayload = cartItems.value.map((cartItem) => {
|
const invoiceDetailPayload = activeCartItems.value.map((cartItem) => {
|
||||||
const imeiSold = imeisSold.find((imeiSold) => imeiSold.imei === cartItem.imei__imei);
|
const imeiSold = imeisSold.find((imeiSold) => imeiSold.imei === cartItem.imei__imei);
|
||||||
return {
|
return {
|
||||||
invoice: invoice.id,
|
invoice: invoice.id,
|
||||||
@@ -69,14 +69,14 @@ async function createOrder() {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
$deleteapi(
|
$deleteapi(
|
||||||
"Cart_Item",
|
"Cart_Item",
|
||||||
cartItems.value.map((c) => c.id),
|
activeCartItems.value.map((c) => c.id),
|
||||||
),
|
),
|
||||||
$patchapi("Cart", {
|
$patchapi("Cart", {
|
||||||
id: cartItems.value[0].cart,
|
id: activeCartItems.value[0].cart,
|
||||||
customer: null,
|
customer: null,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
getCart();
|
getCarts();
|
||||||
emit("close");
|
emit("close");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -101,11 +101,11 @@ async function createOrder() {
|
|||||||
<span>Khách hàng</span>
|
<span>Khách hàng</span>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p>{{ orderInfo.customer.fullname }}</p>
|
<p>{{ activeCart.customer__fullname }}</p>
|
||||||
<p class="is-size-7 has-text-grey">
|
<p class="is-size-7 has-text-grey">
|
||||||
{{ orderInfo.customer.phone }}
|
{{ activeCart.customer__phone }}
|
||||||
•
|
•
|
||||||
{{ orderInfo.customer.email }}
|
{{ activeCart.customer__email }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,11 +119,11 @@ async function createOrder() {
|
|||||||
:size="18"
|
:size="18"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>{{ cartItems.length }} sản phẩm</span>
|
<span>{{ activeCartItems.length }} sản phẩm</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="is-flex is-flex-direction-column is-gap-1">
|
<div class="is-flex is-flex-direction-column is-gap-1">
|
||||||
<CartItem
|
<CartItem
|
||||||
v-for="cartItem in cartItems"
|
v-for="cartItem in activeCartItems"
|
||||||
:key="cartItem.id"
|
:key="cartItem.id"
|
||||||
:cartItem="cartItem"
|
:cartItem="cartItem"
|
||||||
/>
|
/>
|
||||||
@@ -148,9 +148,9 @@ async function createOrder() {
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<p class="font-medium fs-16 mb-0.5">{{ shipping_address }}</p>
|
<p class="font-medium fs-16 mb-0.5">{{ shipping_address }}</p>
|
||||||
<p class="has-text-grey">
|
<p class="has-text-grey">
|
||||||
<span>{{ orderInfo.customer.fullname }}</span>
|
<span>{{ activeCart.customer__fullname }}</span>
|
||||||
<span> • </span>
|
<span> • </span>
|
||||||
<span>{{ orderInfo.customer.phone }}</span>
|
<span>{{ activeCart.customer__phone }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -177,7 +177,7 @@ async function createOrder() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span>Tạm tính</span>
|
<span>Tạm tính</span>
|
||||||
<span> ({{ cartItems.length }} sản phẩm)</span>
|
<span> ({{ activeCartItems.length }} sản phẩm)</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="has-text-right">{{ $numtoString(subtotal, { hasUnit: true }) }}</td>
|
<td class="has-text-right">{{ $numtoString(subtotal, { hasUnit: true }) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,36 +1,45 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { without } from "es-toolkit";
|
||||||
import Address from "~/components/pos/Address.vue";
|
import Address from "~/components/pos/Address.vue";
|
||||||
import CartItem from "~/components/pos/CartItem.vue";
|
import CartItem from "~/components/pos/CartItem.vue";
|
||||||
|
import CartTabs from "~/components/pos/CartTabs.vue";
|
||||||
import SearchBox from "~/components/SearchBox.vue";
|
import SearchBox from "~/components/SearchBox.vue";
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { $getdata, $patchapi, $numtoString } = useNuxtApp();
|
const { $findapi, $getapi, $getdata, $patchapi, $numtoString } = useNuxtApp();
|
||||||
const cart = ref();
|
const carts = ref([]);
|
||||||
const cartItems = ref();
|
const cartItems = ref([]);
|
||||||
|
const customers = ref([]);
|
||||||
|
const activeCartId = ref();
|
||||||
|
const activeCart = computed(() => carts.value.find((c) => c.id === activeCartId.value));
|
||||||
|
const activeCartItems = computed(() => cartItems.value.filter((ci) => ci.cart === activeCartId.value));
|
||||||
const isUpdating = ref(false);
|
const isUpdating = ref(false);
|
||||||
|
|
||||||
async function getCart() {
|
async function getCarts() {
|
||||||
try {
|
const apis = $findapi(["Cart", "Cart_Item", "customer"]);
|
||||||
isUpdating.value = true;
|
const [cartsRes, cartItemsRes, customersRes] = await $getapi(apis);
|
||||||
const cartFetched = await $getdata("Cart", {
|
|
||||||
first: true,
|
|
||||||
});
|
|
||||||
cart.value = cartFetched;
|
|
||||||
|
|
||||||
const cartItemsFetched = await $getdata("Cart_Item", {
|
carts.value = cartsRes.data.rows || [];
|
||||||
filter: {
|
cartItems.value = cartItemsRes.data.rows || [];
|
||||||
cart: cartFetched.id,
|
customers.value = customersRes.data.rows || [];
|
||||||
},
|
|
||||||
});
|
|
||||||
cartItems.value = cartItemsFetched;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
isUpdating.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(getCart);
|
const customerIdsWithNoCart = computed(() => {
|
||||||
|
const cusIds = customers.value.map((c) => c.id);
|
||||||
|
const cusIdsWithCart = carts.value.filter((c) => c.customer).map((c) => c.customer);
|
||||||
|
const cusIdsWithNoCart = without(cusIds, ...cusIdsWithCart);
|
||||||
|
return cusIdsWithNoCart;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getCarts();
|
||||||
|
activeCartId.value = carts.value[0].id;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(activeCartId, () => {
|
||||||
|
orderInfo.value.deliveryMethod = null;
|
||||||
|
orderInfo.value.paymentMethod = null;
|
||||||
|
});
|
||||||
|
|
||||||
const showProductSelectionModal = ref();
|
const showProductSelectionModal = ref();
|
||||||
function openProductSelectionModal() {
|
function openProductSelectionModal() {
|
||||||
@@ -43,43 +52,45 @@ function openProductSelectionModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const orderInfo = ref({
|
const orderInfo = ref({
|
||||||
customer: null,
|
|
||||||
address: null,
|
address: null,
|
||||||
deliveryMethod: null,
|
deliveryMethod: null,
|
||||||
paymentMethod: null,
|
paymentMethod: null,
|
||||||
});
|
});
|
||||||
const addresses = ref([]);
|
const addresses = ref([]);
|
||||||
const subtotal = computed(() => {
|
const subtotal = computed(() => {
|
||||||
return cartItems.value?.reduce((prev, curr) => prev + curr.imei__variant__price, 0);
|
return activeCartItems.value?.reduce((prev, curr) => prev + curr.imei__variant__price, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getAddresses() {
|
async function getAddresses() {
|
||||||
addresses.value = await $getdata("Customer_Address", {
|
addresses.value = await $getdata("Customer_Address", {
|
||||||
filter: { customer: orderInfo.value.customer.id },
|
filter: { customer: activeCart.value.customer },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(activeCart, async (newVal, oldVal) => {
|
||||||
() => orderInfo.value.customer,
|
// set order info
|
||||||
async (newVal, oldVal) => {
|
if (newVal.customer) {
|
||||||
const updatedCart = await $patchapi("Cart", {
|
await getAddresses();
|
||||||
id: cart.value.id,
|
if (!oldVal || !oldVal.customer || oldVal.customer !== newVal.customer) {
|
||||||
customer: newVal?.id,
|
const defaultAddress = addresses.value.find((add) => add.is_default);
|
||||||
});
|
orderInfo.value.address = defaultAddress;
|
||||||
getCart();
|
|
||||||
|
|
||||||
if (newVal) {
|
|
||||||
await getAddresses();
|
|
||||||
if (oldVal === null || oldVal.id !== newVal.id) {
|
|
||||||
const defaultAddress = addresses.value.find((add) => add.is_default);
|
|
||||||
orderInfo.value.address = defaultAddress;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addresses.value = null;
|
|
||||||
orderInfo.value.address = null;
|
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
);
|
addresses.value = null;
|
||||||
|
orderInfo.value.address = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isChangingCus = ref(false);
|
||||||
|
async function changeCustomer(cusId) {
|
||||||
|
isChangingCus.value = true;
|
||||||
|
const updatedCart = await $patchapi("Cart", {
|
||||||
|
id: activeCartId.value,
|
||||||
|
customer: cusId,
|
||||||
|
});
|
||||||
|
await getCarts();
|
||||||
|
isChangingCus.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => orderInfo.value.deliveryMethod,
|
() => orderInfo.value.deliveryMethod,
|
||||||
@@ -92,8 +103,8 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isOrderValid = computed(() => {
|
const isOrderValid = computed(() => {
|
||||||
if (cartItems.value?.length === 0) return false;
|
if (activeCartItems.value?.length === 0) return false;
|
||||||
if (!orderInfo.value.customer) return false;
|
if (!activeCart.value?.customer) return false;
|
||||||
if (!orderInfo.value.deliveryMethod) return false;
|
if (!orderInfo.value.deliveryMethod) return false;
|
||||||
if (!orderInfo.value.paymentMethod) return false;
|
if (!orderInfo.value.paymentMethod) return false;
|
||||||
if (orderInfo.value.deliveryMethod.code === "HOME_DELIVERY" && !orderInfo.value.address) return false;
|
if (orderInfo.value.deliveryMethod.code === "HOME_DELIVERY" && !orderInfo.value.address) return false;
|
||||||
@@ -112,17 +123,34 @@ function openConfirmModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
provide("pos", {
|
provide("pos", {
|
||||||
cartItems,
|
carts,
|
||||||
|
activeCartId,
|
||||||
|
activeCart,
|
||||||
|
activeCartItems,
|
||||||
|
isChangingCus,
|
||||||
orderInfo,
|
orderInfo,
|
||||||
getCart,
|
getCarts,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="fixed-grid has-1-cols-mobile has-12-cols">
|
<!-- <div class="fs-11">
|
||||||
<div class="grid">
|
<pre>customerIdsWithNoCart: {{ customerIdsWithNoCart }}</pre>
|
||||||
<div :class="['cell', store.viewport < 4 ? 'is-col-span-12' : 'is-col-span-8']">
|
<pre>{{ customerIdsWithNoCart }}</pre>
|
||||||
|
<pre>{{ activeCart }}</pre>
|
||||||
|
<pre>{{ JSON.stringify(activeCartItems) }}</pre>
|
||||||
|
<pre>{{ JSON.stringify(activeCart) }}</pre>
|
||||||
|
<pre>activeCart?.customer: {{ JSON.stringify(activeCart?.customer) }}</pre>
|
||||||
|
<pre>{{ orderInfo }}</pre>
|
||||||
|
</div> -->
|
||||||
|
<CartTabs />
|
||||||
|
<div class="fixed-grid has-1-cols-mobile has-12-cols has-background-white is-clipped">
|
||||||
|
<div
|
||||||
|
class="grid"
|
||||||
|
style="row-gap: 0"
|
||||||
|
>
|
||||||
|
<div :class="['cell', store.viewport < 3 ? 'is-col-span-12' : 'is-col-span-8']">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="block is-flex is-justify-content-space-between">
|
<div class="block is-flex is-justify-content-space-between">
|
||||||
@@ -159,11 +187,11 @@ provide("pos", {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="cartItems?.length > 0"
|
v-if="activeCartItems?.length > 0"
|
||||||
class="is-flex is-flex-direction-column is-gap-1"
|
class="is-flex is-flex-direction-column is-gap-1"
|
||||||
>
|
>
|
||||||
<CartItem
|
<CartItem
|
||||||
v-for="cartItem in cartItems"
|
v-for="cartItem in activeCartItems"
|
||||||
:key="cartItem.id"
|
:key="cartItem.id"
|
||||||
:cartItem="cartItem"
|
:cartItem="cartItem"
|
||||||
deleteable
|
deleteable
|
||||||
@@ -173,12 +201,12 @@ provide("pos", {
|
|||||||
v-else
|
v-else
|
||||||
class="py-4 fs-16 has-text-centered has-text-grey"
|
class="py-4 fs-16 has-text-centered has-text-grey"
|
||||||
>
|
>
|
||||||
Không có sản phẩm nào trong giỏ hàng.
|
Chưa có sản phẩm nào trong giỏ hàng.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="['cell', store.viewport < 4 ? 'is-col-span-12' : 'is-col-span-4']">
|
<div :class="['cell sidebar', store.viewport < 3 ? 'is-col-span-12' : 'is-col-span-4']">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="icon-text fs-16 font-semibold mb-4">
|
<p class="icon-text fs-16 font-semibold mb-4">
|
||||||
@@ -194,12 +222,15 @@ provide("pos", {
|
|||||||
<SearchBox
|
<SearchBox
|
||||||
v-bind="{
|
v-bind="{
|
||||||
api: 'customer',
|
api: 'customer',
|
||||||
|
// filter: { id__in: [...customerIdsWithNoCart, activeCart?.customer] },
|
||||||
field: 'label',
|
field: 'label',
|
||||||
column: ['label'],
|
column: ['label'],
|
||||||
first: true,
|
first: true,
|
||||||
optionid: cart?.customer,
|
|
||||||
placeholder: 'Khách hàng',
|
placeholder: 'Khách hàng',
|
||||||
onOption: (e) => (orderInfo.customer = e),
|
optionid: activeCart?.customer,
|
||||||
|
onOption: (e) => {
|
||||||
|
if (e?.id !== activeCart?.customer) changeCustomer(e?.id || null);
|
||||||
|
},
|
||||||
addon: {
|
addon: {
|
||||||
component: 'customer/CustomerQuickAdd',
|
component: 'customer/CustomerQuickAdd',
|
||||||
width: '50%',
|
width: '50%',
|
||||||
@@ -230,22 +261,23 @@ provide("pos", {
|
|||||||
column: ['name'],
|
column: ['name'],
|
||||||
first: true,
|
first: true,
|
||||||
placeholder: 'Phương thức giao hàng',
|
placeholder: 'Phương thức giao hàng',
|
||||||
|
optionid: orderInfo.deliveryMethod?.id,
|
||||||
onOption: (e) => (orderInfo.deliveryMethod = e),
|
onOption: (e) => (orderInfo.deliveryMethod = e),
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="orderInfo.deliveryMethod?.code === 'HOME_DELIVERY'">
|
<template v-if="orderInfo.deliveryMethod?.code === 'HOME_DELIVERY'">
|
||||||
<div v-if="orderInfo.customer">
|
<div v-if="activeCart.customer">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<p class="mb-2">Thông tin người nhận</p>
|
<p class="mb-2">Thông tin người nhận</p>
|
||||||
<div v-if="orderInfo.customer">
|
<div v-if="activeCart.customer">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label is-small">Tên</label>
|
<label class="label is-small">Tên</label>
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<input
|
<input
|
||||||
class="input is-small"
|
class="input is-small"
|
||||||
type="email"
|
type="email"
|
||||||
:value="orderInfo.customer.fullname"
|
:value="activeCart.customer__fullname"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
@@ -257,7 +289,7 @@ provide("pos", {
|
|||||||
<input
|
<input
|
||||||
class="input is-small"
|
class="input is-small"
|
||||||
type="email"
|
type="email"
|
||||||
:value="orderInfo.customer?.phone"
|
:value="activeCart.customer__phone"
|
||||||
placeholder="Phone"
|
placeholder="Phone"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
@@ -311,6 +343,7 @@ provide("pos", {
|
|||||||
column: ['name'],
|
column: ['name'],
|
||||||
first: true,
|
first: true,
|
||||||
placeholder: 'Phương thức thanh toán',
|
placeholder: 'Phương thức thanh toán',
|
||||||
|
optionid: orderInfo.paymentMethod?.id,
|
||||||
onOption: (e) => (orderInfo.paymentMethod = e),
|
onOption: (e) => (orderInfo.paymentMethod = e),
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
@@ -326,7 +359,7 @@ provide("pos", {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span>Tạm tính</span>
|
<span>Tạm tính</span>
|
||||||
<span> ({{ cartItems?.length || 0 }} sản phẩm)</span>
|
<span> ({{ activeCartItems?.length || 0 }} sản phẩm)</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="has-text-right">{{ $numtoString(subtotal, { hasUnit: true }) }}</td>
|
<td class="has-text-right">{{ $numtoString(subtotal, { hasUnit: true }) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -363,3 +396,28 @@ provide("pos", {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "bulma/sass/utilities/mixins.scss" as *;
|
||||||
|
|
||||||
|
.cell > .card {
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-grid {
|
||||||
|
border: 1px solid var(--bulma-border);
|
||||||
|
border-bottom-left-radius: var(--bulma-radius);
|
||||||
|
border-bottom-right-radius: var(--bulma-radius);
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
border-left: 1px solid var(--bulma-border);
|
||||||
|
border-top: none;
|
||||||
|
|
||||||
|
@include touch {
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--bulma-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLoading v-if="!$store.ready" />
|
<Transition>
|
||||||
<ClientOnly v-else>
|
<AppLoading v-if="!$store.ready" />
|
||||||
|
</Transition>
|
||||||
|
<ClientOnly v-if="$store.ready">
|
||||||
<TopMenu @changeTab="changeTab" />
|
<TopMenu @changeTab="changeTab" />
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@@ -116,4 +118,14 @@ main {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v-enter-active,
|
||||||
|
.v-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-enter-from,
|
||||||
|
.v-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
*/
|
*/
|
||||||
const findapi = function (name) {
|
const findapi = function (name) {
|
||||||
const result = Array.isArray(name)
|
const result = Array.isArray(name)
|
||||||
? apis.filter((v) => name.findIndex((x) => v.name === x) >= 0)
|
? name.map((n) => apis.find((v) => v.name === n))
|
||||||
: apis.find((v) => v.name === name);
|
: apis.find((v) => v.name === name);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@@ -59,7 +59,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
try {
|
try {
|
||||||
const arr = list.map((v) => {
|
const arr = list.map((v) => {
|
||||||
const api = apis.find((api) => api.name === v.name);
|
const api = apis.find((api) => api.name === v.name);
|
||||||
const url = (v.path ? paths.find((x) => x.name === v.path).url : path) + (v.url || api.url);
|
const url = getpath(v.path) + (v.url || api.url);
|
||||||
const params = v.params || api.params || {};
|
const params = v.params || api.params || {};
|
||||||
params.login = $store.login?.id;
|
params.login = $store.login?.id;
|
||||||
return { url, params };
|
return { url, params };
|
||||||
|
|||||||
@@ -5,17 +5,10 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
"common",
|
"common",
|
||||||
"tablesetting",
|
"tablesetting",
|
||||||
"datatype",
|
"datatype",
|
||||||
"filtertype",
|
|
||||||
"sorttype",
|
|
||||||
"settingtype",
|
"settingtype",
|
||||||
"settingclass",
|
"settingclass",
|
||||||
"settingchoice",
|
|
||||||
"filterchoice",
|
|
||||||
"colorchoice",
|
"colorchoice",
|
||||||
"sharechoice",
|
|
||||||
"menuchoice",
|
"menuchoice",
|
||||||
"textalign",
|
|
||||||
"placement",
|
|
||||||
"colorscheme",
|
"colorscheme",
|
||||||
]);
|
]);
|
||||||
const notReadyConns = connlist.filter((v) => !v.ready);
|
const notReadyConns = connlist.filter((v) => !v.ready);
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export default /** @type {const} */ ([
|
|||||||
distinct_values: {
|
distinct_values: {
|
||||||
label: {
|
label: {
|
||||||
type: "Concat",
|
type: "Concat",
|
||||||
field: ["code", "fullname", "phone"],
|
field: ["fullname", "code", "phone"],
|
||||||
},
|
},
|
||||||
order: { type: "RowNumber" },
|
order: { type: "RowNumber" },
|
||||||
},
|
},
|
||||||
@@ -657,7 +657,11 @@ export default /** @type {const} */ ([
|
|||||||
name: "Cart",
|
name: "Cart",
|
||||||
url: "data/Cart/",
|
url: "data/Cart/",
|
||||||
url_detail: "data-detail/Cart/",
|
url_detail: "data-detail/Cart/",
|
||||||
params: {},
|
params: {
|
||||||
|
values:
|
||||||
|
"id,code,customer,customer__code,customer__fullname,customer__phone,customer__email,deleted,create_time,update_time",
|
||||||
|
sort: "id",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cart_Item",
|
name: "Cart_Item",
|
||||||
|
|||||||
Reference in New Issue
Block a user