changes
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
@use "bulma/sass/utilities/initial-variables.scss" as *;
|
||||
|
||||
[data-theme="light"],
|
||||
.theme-light {
|
||||
--bulma-block-spacing: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
--bulma-card-shadow: none;
|
||||
border: 1px solid $grey-lighter;
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
.rounded-sm {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,10 @@ import ScrollBox from "@/components/datatable/ScrollBox.vue";
|
||||
import { debounce } from "es-toolkit";
|
||||
|
||||
const props = defineProps({
|
||||
api: String,
|
||||
api: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
field: String,
|
||||
column: Array,
|
||||
first: Boolean,
|
||||
|
||||
@@ -133,7 +133,7 @@ const emit = defineEmits("unselect");
|
||||
<div class="is-flex is-gap-1 is-align-items-center">
|
||||
<div
|
||||
: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`,
|
||||
]"
|
||||
>
|
||||
|
||||
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 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">
|
||||
<AddIMEIForm
|
||||
: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>
|
||||
import Address from "@/components/pos/Address.vue";
|
||||
import ProductCard from "@/components/pos/ProductCard.vue";
|
||||
import SearchBox from "@/components/SearchBox.vue";
|
||||
import { isNil } from "es-toolkit";
|
||||
|
||||
const store = useStore();
|
||||
const { $numtoString } = useNuxtApp();
|
||||
const { $getdata, $numtoString } = useNuxtApp();
|
||||
|
||||
function openModal() {
|
||||
store.showmodal = {
|
||||
@@ -14,16 +16,41 @@ function openModal() {
|
||||
};
|
||||
}
|
||||
|
||||
const record = ref({
|
||||
const orderInfo = ref({
|
||||
customer: null,
|
||||
address: null,
|
||||
paymentMethod: null,
|
||||
});
|
||||
const addresses = ref([]);
|
||||
const subtotal = computed(() => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="block">
|
||||
<button
|
||||
@click="openModal"
|
||||
@@ -40,7 +67,7 @@ const subtotal = computed(() => {
|
||||
</div>
|
||||
<div class="fixed-grid has-1-cols-mobile has-12-cols">
|
||||
<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-content">
|
||||
<p class="icon-text fs-16 font-semibold mb-4">
|
||||
@@ -71,7 +98,7 @@ const subtotal = computed(() => {
|
||||
</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-content">
|
||||
<p class="icon-text fs-16 font-semibold mb-4">
|
||||
@@ -91,13 +118,13 @@ const subtotal = computed(() => {
|
||||
column: ['label'],
|
||||
first: true,
|
||||
placeholder: 'Khách hàng',
|
||||
onOption: (e) => (orderInfo.customer = e),
|
||||
addon: {
|
||||
component: 'customer/CustomerQuickAdd',
|
||||
width: '50%',
|
||||
height: 'auto',
|
||||
title: 'Tạo khách hàng',
|
||||
},
|
||||
onOption: (e) => (record.customer = e),
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -105,7 +132,65 @@ const subtotal = computed(() => {
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<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">
|
||||
<Icon
|
||||
name="material-symbols:credit-card-outline"
|
||||
@@ -122,7 +207,7 @@ const subtotal = computed(() => {
|
||||
column: ['name'],
|
||||
first: true,
|
||||
placeholder: 'Phương thức thanh toán',
|
||||
onOption: (e) => (record.paymentMethod = e),
|
||||
onOption: (e) => (orderInfo.paymentMethod = e),
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -151,7 +236,7 @@ const subtotal = computed(() => {
|
||||
</table>
|
||||
<button
|
||||
class="button is-fullwidth is-success"
|
||||
:disabled="!record.customer || !record.paymentMethod"
|
||||
:disabled="Object.values(orderInfo).some(isNil)"
|
||||
>
|
||||
Thanh toán
|
||||
</button>
|
||||
@@ -161,4 +246,5 @@ const subtotal = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1094,6 +1094,12 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
url_detail: "data-detail/Payment_Method/",
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
name: "Customer_Address",
|
||||
url: "data/Customer_Address/",
|
||||
url_detail: "data-detail/Customer_Address/",
|
||||
params: {},
|
||||
},
|
||||
];
|
||||
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 POS from "@/components/pos/POS.vue";
|
||||
import ChooseIMEIButton from "@/components/pos/ChooseIMEIButton.vue";
|
||||
import EditAddress from "@/components/pos/EditAddress.vue";
|
||||
import CreateReceipts from "@/components/receipts/CreateReceipts.vue";
|
||||
import Return from "@/components/receipts/Return.vue";
|
||||
import Imports from "@/components/imports/Imports.vue";
|
||||
@@ -139,6 +140,7 @@ const components = {
|
||||
Rights,
|
||||
POS,
|
||||
ChooseIMEIButton,
|
||||
EditAddress,
|
||||
CreateReceipts,
|
||||
Return,
|
||||
Imports,
|
||||
|
||||
Reference in New Issue
Block a user