This commit is contained in:
Viet An
2026-06-03 15:34:53 +07:00
parent c04abb452f
commit 8e2dd06def
8 changed files with 480 additions and 21 deletions

View File

@@ -0,0 +1,31 @@
<script setup>
import Modal from "@/components/Modal.vue";
const props = defineProps({
variant: Object,
});
const showmodal = ref();
function openModal() {
showmodal.value = {
component: "pos/ChooseIMEIModal",
title: "Chọn IMEI",
width: "70%",
height: "auto",
vbind: { variant: props.variant },
};
}
</script>
<template>
<button
@click="openModal"
class="button is-ghost fs-13"
>
Chọn IMEI
</button>
<Modal
v-if="showmodal"
v-bind="showmodal"
@close="showmodal = null"
/>
</template>

View File

@@ -0,0 +1,135 @@
<script setup>
import AddIMEIForm from "@/components/imports/AddIMEIForm.vue";
import ImportData from "@/components/parameter/ImportData.vue";
import { remove } from "es-toolkit";
const props = defineProps({
variant: Object,
});
const store = useStore();
const { $getdata } = useNuxtApp();
const emit = defineEmits(["close"]);
const isLoading = ref(false);
const imeis = ref([]);
const selectedImeis = ref([]);
function toggleSelected(imeiRec) {
if (
store.selectedImeis.find((i) => i.imei === imeiRec.imei) ||
selectedImeis.value.find((i) => i.imei === imeiRec.imei)
) {
remove(selectedImeis.value, (i) => i.imei === imeiRec.imei);
} else {
selectedImeis.value.push(imeiRec);
}
}
function addToCart() {
store.selectedImeis = [...store.selectedImeis, ...selectedImeis.value];
emit("close");
}
async function fetchImeis() {
isLoading.value = true;
const imeisFetched = await $getdata("IMEI", {
filter: { variant: props.variant.id },
});
imeis.value = imeisFetched;
isLoading.value = false;
}
onMounted(fetchImeis);
</script>
<template>
<div
v-if="isLoading"
class="is-flex is-justify-content-center"
>
<Icon
name="svg-spinners:90-ring"
:size="26"
class="has-text-grey-light"
/>
</div>
<div v-else-if="imeis.length === 0">
<p class="mt-6 mb-8 has-text-centered">Sản phẩm không IMEI nào.</p>
<div class="block">
<AddIMEIForm
:variant="variant"
@created="fetchImeis"
/>
</div>
<div class="block">
<ImportData
code="imeis"
@close="fetchImeis"
/>
</div>
</div>
<template v-else>
<table class="table is-hoverable is-fullwidth mb-2 fs-13">
<thead>
<tr>
<th></th>
<th>STT</th>
<th>IMEI</th>
<th>Trạng thái</th>
</tr>
</thead>
<tbody>
<tr
v-for="(imeiRec, i) in imeis"
:key="imeiRec.id"
class="is-clickable"
:class="
(store.selectedImeis.find((i) => i.imei === imeiRec.imei) ||
selectedImeis.find((i) => i.imei === imeiRec.imei)) &&
'is-selected'
"
@click="toggleSelected(imeiRec)"
>
<td class="is-narrow">
<label class="checkbox">
<input
type="checkbox"
:checked="
store.selectedImeis.find((i) => i.imei === imeiRec.imei) ||
selectedImeis.find((i) => i.imei === imeiRec.imei)
"
/>
</label>
</td>
<td class="is-narrow">{{ i + 1 }}</td>
<td class="is-family-monospace">{{ imeiRec.imei }}</td>
<td>{{ imeiRec.deleted ? "Không có sẵn" : "Có sẵn" }}</td>
</tr>
</tbody>
</table>
<div class="is-flex is-justify-content-end">
<button
@click="addToCart"
class="button is-primary"
:disabled="selectedImeis.length === 0"
>
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="18"
/>
</span>
<span>
<span>Thêm vào giỏ</span>
<span v-if="selectedImeis.length > 0"> ({{ selectedImeis.length }})</span>
</span>
</button>
</div>
</template>
</template>
<style scoped>
.table tr.is-selected {
--bulma-table-row-active-background-color: var(--bulma-primary-95);
color: inherit;
}
</style>

View File

@@ -1,3 +1,74 @@
<script lang="ts" setup></script>
<script setup>
import ProductCard from "@/components/pos/ProductCard.vue";
<template>POS</template>
const store = useStore();
function selectProduct() {
store.showmodal = {
component: "pos/ProductSelection",
title: "Chọn sản phẩm",
width: "85%",
height: "80%",
};
}
</script>
<template>
<div class="card">
<div class="card-content">
<button
@click="selectProduct"
class="button is-primary"
>
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="20"
/>
</span>
<span>Chọn sản phẩm</span>
</button>
</div>
</div>
<div class="fixed-grid has-12-cols">
<div class="grid">
<div class="cell is-col-span-8">
<div class="card">
<div class="card-content">
<p class="icon-text fs-17 font-semibold mb-4">
<span class="icon">
<Icon
name="material-symbols:shopping-cart-outline-rounded"
:size="18"
/>
</span>
<span>Giỏ hàng</span>
</p>
<div class="is-flex is-flex-direction-column is-gap-1">
<ProductCard
v-for="imei in store.selectedImeis"
:key="imei.id"
:imei="imei"
/>
</div>
</div>
</div>
</div>
<div class="cell is-col-span-4">
<div class="card">
<div class="card-content">
<p class="icon-text fs-17 font-semibold mb-4">
<span class="icon">
<Icon
name="material-symbols:supervisor-account-outline-rounded"
:size="18"
/>
</span>
<span>Khách hàng</span>
</p>
<div>customers content</div>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,67 @@
<script setup>
import { remove } from "es-toolkit";
const props = defineProps({
imei: Object,
});
const { $copyToClipboard, $numtoString, $snackbar } = useNuxtApp();
const store = useStore();
function removeFromCart() {
remove(store.selectedImeis, (imeiRec) => imeiRec.id === props.imei.id);
$snackbar("Đã xoá sản phẩm khỏi giỏ hàng", "Success");
}
</script>
<template>
<div class="card m-0">
<div class="card-content p-4 is-flex is-gap-3 is-justify-content-space-between is-align-items-center">
<div class="is-flex is-gap-4 is-justify-content-space-between is-align-items-center is-flex-grow-1">
<div class="media m-0">
<div class="media-left">
<figure class="image is-48x48">
<img :src="imei.variant__image__path" />
</figure>
</div>
<div class="media-content">
<p class="font-bold fs-15">{{ imei.variant__product__name }}</p>
<p class="fs-13">
<span>{{ imei.variant__ram__code }}</span>
<span> </span>
<span>{{ imei.variant__internal_storage__code }}</span>
<span> </span>
<span>{{ imei.variant__color__name }}</span>
</p>
<div class="fs-13 is-flex is-gap-0.5 is-align-items-center">
<span class="is-family-monospace">IMEI: {{ imei.imei }}</span>
<button
class="button is-small size-7 p-0 is-primary is-light is-rounded"
@click="$copyToClipboard(imei.imei)"
>
<span class="icon">
<Icon
name="material-symbols:content-copy-outline-rounded"
:size="14"
/>
</span>
</button>
</div>
</div>
</div>
<p class="has-text-primary-50 font-semibold">{{ $numtoString(imei.variant__price, { hasUnit: true }) }}</p>
</div>
<div>
<button
@click="removeFromCart"
class="button is-danger is-light"
>
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="18"
/>
</span>
</button>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,165 @@
<script setup>
import DataView from "@/components/datatable/DataView.vue";
const filter = ref({});
function selected(api, chosen) {
filter.value[`product__${api}`] = chosen?.id;
}
</script>
<template>
<div>
<form class="fixed-grid has-12-cols">
<div class="grid">
<div class="cell is-col-span-8">
<div class="field">
<label class="label">Tên sản phẩm</label>
<div class="control">
<input
class="input"
v-model.trim="filter.name"
type="text"
placeholder="Tên"
/>
</div>
</div>
</div>
<div class="cell is-col-span-4">
<div class="field">
<label class="label">OS</label>
<SearchBox
v-bind="{
api: 'OS',
field: 'name',
column: ['name'],
first: true,
clearable: true,
optionid: filter.os,
placeholder: 'OS',
}"
@option="selected('os', $event)"
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="field">
<label class="label">Hãng</label>
<SearchBox
v-bind="{
api: 'Manufacturer',
field: 'name',
column: ['name'],
first: true,
clearable: true,
optionid: filter.manufacturer,
placeholder: 'Hãng',
}"
@option="selected('manufacturer', $event)"
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="field">
<label class="label">Pin</label>
<SearchBox
v-bind="{
api: 'Battery',
field: 'code',
column: ['code'],
first: true,
clearable: true,
optionid: filter.battery,
placeholder: 'Pin',
}"
@option="selected('battery', $event)"
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="field">
<label class="label">Màn hình</label>
<SearchBox
v-bind="{
api: 'Screen',
field: 'label',
column: ['resolution', 'standard', 'technology'],
first: true,
clearable: true,
optionid: filter.screen,
placeholder: 'Màn hình',
}"
@option="selected('screen', $event)"
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="field">
<label class="label">CPU</label>
<SearchBox
v-bind="{
api: 'CPU',
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'CPU',
optionid: filter.cpu,
}"
@option="selected('cpu', $event)"
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="field">
<label class="label">GPU</label>
<SearchBox
v-bind="{
api: 'GPU',
field: 'name',
column: ['name'],
first: true,
clearable: true,
optionid: filter.gpu,
placeholder: 'GPU',
}"
@option="selected('gpu', $event)"
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="field">
<label class="label">Bộ nhớ ngoài</label>
<SearchBox
v-bind="{
api: 'External_Storage',
field: 'code',
column: ['code'],
first: true,
clearable: true,
optionid: filter.external_storage,
placeholder: 'Bộ nhớ ngoài',
position: 'is-top-right',
}"
@option="selected('external_storage', $event)"
/>
</div>
</div>
</div>
</form>
<DataView
:key="JSON.stringify(filter)"
v-bind="{
api: 'Product_Variant',
setting: 'product-variants-selection',
pagename: 'product-variants-selection',
params: {
values:
'id,code,product,product__name,product__os__name,product__external_storage__max_capacity,color,color__code,color__name,color__hex_code,ram,ram__code,ram__capacity,internal_storage,internal_storage__code,internal_storage__capacity,image,image__path,price,note,create_time,update_time',
sort: 'id',
filter,
},
}"
/>
</div>
</template>