changes
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
@use "bulma/sass/utilities/initial-variables.scss" as *;
|
@use "bulma/sass/utilities/initial-variables.scss" as *;
|
||||||
|
|
||||||
|
[data-theme="light"],
|
||||||
|
.theme-light {
|
||||||
|
--bulma-block-spacing: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
--bulma-card-shadow: none;
|
--bulma-card-shadow: none;
|
||||||
border: 1px solid $grey-lighter;
|
border: 1px solid $grey-lighter;
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
.rounded-sm {
|
.rounded-sm {
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.rounded {
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
.rounded-md {
|
.rounded-md {
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,10 @@ import ScrollBox from "@/components/datatable/ScrollBox.vue";
|
|||||||
import { debounce } from "es-toolkit";
|
import { debounce } from "es-toolkit";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
api: String,
|
api: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
field: String,
|
field: String,
|
||||||
column: Array,
|
column: Array,
|
||||||
first: Boolean,
|
first: Boolean,
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ const emit = defineEmits("unselect");
|
|||||||
<div class="is-flex is-gap-1 is-align-items-center">
|
<div class="is-flex is-gap-1 is-align-items-center">
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
'p-1 rounded-sm is-flex is-align-items-center',
|
'p-1 rounded is-flex is-align-items-center',
|
||||||
`has-background-${move.delta > 0 ? 'success' : 'danger'}-soft`,
|
`has-background-${move.delta > 0 ? 'success' : 'danger'}-soft`,
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
|||||||
87
app/components/pos/Address.vue
Normal file
87
app/components/pos/Address.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
address: Object,
|
||||||
|
selected: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
const showModal = ref(false);
|
||||||
|
|
||||||
|
function openEditModal() {
|
||||||
|
showModal.value = {
|
||||||
|
component: "pos/EditAddress",
|
||||||
|
title: "Cập nhật địa chỉ",
|
||||||
|
width: "50%",
|
||||||
|
height: "auto",
|
||||||
|
vbind: { address: props.address },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['card is-clickable', selected && 'selected']"
|
||||||
|
@click="$emit('selectAddress', address.id)"
|
||||||
|
>
|
||||||
|
<div class="card-content is-flex is-justify-content-space-between">
|
||||||
|
<div>
|
||||||
|
<div class="fs-14 is-flex is-gap-1 is-align-items-center">
|
||||||
|
<p>{{ address.address_detail }}</p>
|
||||||
|
<span
|
||||||
|
v-if="address.is_default"
|
||||||
|
class="tag is-primary is-light"
|
||||||
|
>
|
||||||
|
Mặc định</span
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
@click.stop="openEditModal"
|
||||||
|
class="button is-small is-white rounded-full p-0 size-7"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<Icon
|
||||||
|
name="material-symbols:edit-outline-rounded"
|
||||||
|
:size="16"
|
||||||
|
class="has-text-primary"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="has-text-grey is-size-7">
|
||||||
|
<span>Phường {{ address.ward }}</span>
|
||||||
|
<span>, </span>
|
||||||
|
<span>Quận {{ address.district }}</span>
|
||||||
|
<span>, </span>
|
||||||
|
<span>{{ address.city }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
class="icon has-background-primary-95 rounded-full"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="material-symbols:check-rounded"
|
||||||
|
:size="18"
|
||||||
|
class="has-text-primary"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
v-if="showModal"
|
||||||
|
v-bind="showModal"
|
||||||
|
@close="showModal = null"
|
||||||
|
@update="$emit('update')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.card {
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--bulma-primary);
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
border-color: var(--bulma-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
--bulma-card-content-padding: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -55,7 +55,7 @@ onMounted(fetchImeis);
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="imeis.length === 0">
|
<div v-else-if="imeis.length === 0">
|
||||||
<p class="mt-6 mb-8 has-text-centered">Sản phẩm không có IMEI nào.</p>
|
<p class="mt-6 mb-8 has-text-centered">Sản phẩm không có IMEI có sẵn nào.</p>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<AddIMEIForm
|
<AddIMEIForm
|
||||||
:variant="variant"
|
:variant="variant"
|
||||||
|
|||||||
125
app/components/pos/EditAddress.vue
Normal file
125
app/components/pos/EditAddress.vue
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<script setup>
|
||||||
|
import { isEqual } from "es-toolkit";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
address: Object,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["modalevent"]);
|
||||||
|
|
||||||
|
const { $patchapi } = useNuxtApp();
|
||||||
|
const addressRef = ref({ ...props.address });
|
||||||
|
const isLoading = ref(false);
|
||||||
|
async function updateAddress() {
|
||||||
|
isLoading.value = true;
|
||||||
|
const updated = await $patchapi("Customer_Address", addressRef.value);
|
||||||
|
isLoading.value = false;
|
||||||
|
emit("modalevent", { name: "update" });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Địa chỉ</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
v-model="addressRef.address_detail"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Địa chỉ"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fixed-grid has-12-cols">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="cell is-col-span-4">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Phường</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
v-model="addressRef.ward"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Text input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cell is-col-span-4">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Quận</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
v-model="addressRef.district"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Text input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cell is-col-span-4">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Thành phố</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
v-model="addressRef.city"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Text input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<label class="checkbox is-flex is-gap-0.5 is-align-items-center">
|
||||||
|
<Icon
|
||||||
|
:name="
|
||||||
|
addressRef.is_default ? 'material-symbols:check-box-rounded' : 'material-symbols:check-box-outline-blank'
|
||||||
|
"
|
||||||
|
:class="['is-clickable', addressRef.is_default ? 'has-text-primary' : 'has-text-grey-light']"
|
||||||
|
:size="24"
|
||||||
|
@click="addressRef.is_default = !addressRef.is_default"
|
||||||
|
/>
|
||||||
|
<span>Địa chỉ mặc định</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="is-sr-only"
|
||||||
|
v-model="addressRef.is_default"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field is-grouped is-grouped-right">
|
||||||
|
<div class="control">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button is-white"
|
||||||
|
@click="$emit('close')"
|
||||||
|
>
|
||||||
|
Huỷ
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="['button is-link', isLoading && 'is-loading']"
|
||||||
|
:disabled="isEqual(address, addressRef)"
|
||||||
|
@click="updateAddress"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<Icon
|
||||||
|
name="material-symbols:edit-outline-rounded"
|
||||||
|
:size="16"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>Cập nhật</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import Address from "@/components/pos/Address.vue";
|
||||||
import ProductCard from "@/components/pos/ProductCard.vue";
|
import ProductCard from "@/components/pos/ProductCard.vue";
|
||||||
import SearchBox from "@/components/SearchBox.vue";
|
import SearchBox from "@/components/SearchBox.vue";
|
||||||
|
import { isNil } from "es-toolkit";
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { $numtoString } = useNuxtApp();
|
const { $getdata, $numtoString } = useNuxtApp();
|
||||||
|
|
||||||
function openModal() {
|
function openModal() {
|
||||||
store.showmodal = {
|
store.showmodal = {
|
||||||
@@ -14,16 +16,41 @@ function openModal() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = ref({
|
const orderInfo = ref({
|
||||||
customer: null,
|
customer: null,
|
||||||
|
address: null,
|
||||||
paymentMethod: null,
|
paymentMethod: null,
|
||||||
});
|
});
|
||||||
|
const addresses = ref([]);
|
||||||
const subtotal = computed(() => {
|
const subtotal = computed(() => {
|
||||||
return store.selectedImeis.reduce((prev, curr) => prev + curr.variant__price, 0);
|
return store.selectedImeis.reduce((prev, curr) => prev + curr.variant__price, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function getAddresses() {
|
||||||
|
addresses.value = await $getdata("Customer_Address", {
|
||||||
|
filter: { customer: orderInfo.value.customer.id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => orderInfo.value.customer,
|
||||||
|
async (newVal, oldVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
await getAddresses();
|
||||||
|
if (oldVal === null || oldVal.id !== newVal.id) {
|
||||||
|
const defaultAddress = addresses.value.find((add) => add.is_default);
|
||||||
|
orderInfo.value.address = defaultAddress?.id;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addresses.value = null;
|
||||||
|
orderInfo.value.address = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<button
|
<button
|
||||||
@click="openModal"
|
@click="openModal"
|
||||||
@@ -40,7 +67,7 @@ const subtotal = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="fixed-grid has-1-cols-mobile has-12-cols">
|
<div class="fixed-grid has-1-cols-mobile has-12-cols">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="cell is-col-span-8">
|
<div :class="['cell', store.viewport < 4 ? 'is-col-span-12' : 'is-col-span-8']">
|
||||||
<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">
|
||||||
@@ -71,7 +98,7 @@ const subtotal = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell is-col-span-4">
|
<div :class="['cell', store.viewport < 4 ? 'is-col-span-12' : 'is-col-span-4']">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<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">
|
||||||
@@ -91,13 +118,13 @@ const subtotal = computed(() => {
|
|||||||
column: ['label'],
|
column: ['label'],
|
||||||
first: true,
|
first: true,
|
||||||
placeholder: 'Khách hàng',
|
placeholder: 'Khách hàng',
|
||||||
|
onOption: (e) => (orderInfo.customer = e),
|
||||||
addon: {
|
addon: {
|
||||||
component: 'customer/CustomerQuickAdd',
|
component: 'customer/CustomerQuickAdd',
|
||||||
width: '50%',
|
width: '50%',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
title: 'Tạo khách hàng',
|
title: 'Tạo khách hàng',
|
||||||
},
|
},
|
||||||
onOption: (e) => (record.customer = e),
|
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +132,65 @@ const subtotal = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="icon-text fs-17 font-semibold mb-4">
|
<p class="icon-text fs-16 font-semibold mb-4">
|
||||||
|
<span class="icon">
|
||||||
|
<Icon
|
||||||
|
name="material-symbols:delivery-truck-speed-outline-rounded"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>Giao hàng</span>
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<div class="block">
|
||||||
|
<p class="mb-2">Thông tin người nhận</p>
|
||||||
|
<div v-if="orderInfo.customer">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-small">Tên</label>
|
||||||
|
<p class="control">
|
||||||
|
<input
|
||||||
|
class="input is-small"
|
||||||
|
type="email"
|
||||||
|
:value="orderInfo.customer.fullname"
|
||||||
|
placeholder="Name"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label is-small">SĐT</label>
|
||||||
|
<p class="control">
|
||||||
|
<input
|
||||||
|
class="input is-small"
|
||||||
|
type="email"
|
||||||
|
:value="orderInfo.customer?.phone"
|
||||||
|
placeholder="Phone"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="block">
|
||||||
|
<p class="mb-2">Địa chỉ giao hàng</p>
|
||||||
|
<div>
|
||||||
|
<Address
|
||||||
|
v-for="address in addresses"
|
||||||
|
:key="address"
|
||||||
|
:address="address"
|
||||||
|
:selected="orderInfo.address === address.id"
|
||||||
|
@selectAddress="orderInfo.address = $event"
|
||||||
|
@update="getAddresses"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-content">
|
||||||
|
<p class="icon-text fs-16 font-semibold mb-4">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<Icon
|
<Icon
|
||||||
name="material-symbols:credit-card-outline"
|
name="material-symbols:credit-card-outline"
|
||||||
@@ -122,7 +207,7 @@ const subtotal = computed(() => {
|
|||||||
column: ['name'],
|
column: ['name'],
|
||||||
first: true,
|
first: true,
|
||||||
placeholder: 'Phương thức thanh toán',
|
placeholder: 'Phương thức thanh toán',
|
||||||
onOption: (e) => (record.paymentMethod = e),
|
onOption: (e) => (orderInfo.paymentMethod = e),
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,7 +236,7 @@ const subtotal = computed(() => {
|
|||||||
</table>
|
</table>
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth is-success"
|
class="button is-fullwidth is-success"
|
||||||
:disabled="!record.customer || !record.paymentMethod"
|
:disabled="Object.values(orderInfo).some(isNil)"
|
||||||
>
|
>
|
||||||
Thanh toán
|
Thanh toán
|
||||||
</button>
|
</button>
|
||||||
@@ -161,4 +246,5 @@ const subtotal = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1094,6 +1094,12 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
url_detail: "data-detail/Payment_Method/",
|
url_detail: "data-detail/Payment_Method/",
|
||||||
params: {},
|
params: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Customer_Address",
|
||||||
|
url: "data/Customer_Address/",
|
||||||
|
url_detail: "data-detail/Customer_Address/",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const { $copy, $clone, $updateSeriesFields, $snackbar, $store, $remove, $dialog } = nuxtApp;
|
const { $copy, $clone, $updateSeriesFields, $snackbar, $store, $remove, $dialog } = nuxtApp;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Inventory from "@/components/inventory/Inventory.vue";
|
|||||||
import Rights from "@/components/rights/Rights.vue";
|
import Rights from "@/components/rights/Rights.vue";
|
||||||
import POS from "@/components/pos/POS.vue";
|
import POS from "@/components/pos/POS.vue";
|
||||||
import ChooseIMEIButton from "@/components/pos/ChooseIMEIButton.vue";
|
import ChooseIMEIButton from "@/components/pos/ChooseIMEIButton.vue";
|
||||||
|
import EditAddress from "@/components/pos/EditAddress.vue";
|
||||||
import CreateReceipts from "@/components/receipts/CreateReceipts.vue";
|
import CreateReceipts from "@/components/receipts/CreateReceipts.vue";
|
||||||
import Return from "@/components/receipts/Return.vue";
|
import Return from "@/components/receipts/Return.vue";
|
||||||
import Imports from "@/components/imports/Imports.vue";
|
import Imports from "@/components/imports/Imports.vue";
|
||||||
@@ -139,6 +140,7 @@ const components = {
|
|||||||
Rights,
|
Rights,
|
||||||
POS,
|
POS,
|
||||||
ChooseIMEIButton,
|
ChooseIMEIButton,
|
||||||
|
EditAddress,
|
||||||
CreateReceipts,
|
CreateReceipts,
|
||||||
Return,
|
Return,
|
||||||
Imports,
|
Imports,
|
||||||
|
|||||||
Reference in New Issue
Block a user