This commit is contained in:
Viet An
2026-06-15 09:47:39 +07:00
parent 2932730fc3
commit a9c37cfff5
12 changed files with 319 additions and 123 deletions

View File

@@ -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%)"

View File

@@ -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;
}
}, },
); );

View File

@@ -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,
}); });

View File

@@ -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>

View 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>

View File

@@ -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;
}); });

View File

@@ -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>

View File

@@ -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 sản phẩm nào trong giỏ hàng. Chưa 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>

View File

@@ -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>

View File

@@ -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 };

View File

@@ -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);

View File

@@ -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",