[Update] Cetak barcode

This commit is contained in:
Baghaztra 2025-10-15 20:47:15 +07:00
parent e54a021b98
commit e1639109c8
5 changed files with 150 additions and 384 deletions

View File

@ -69,32 +69,7 @@
class="fixed inset-0 bg-black/75 flex items-center justify-center p-4 z-50 backdrop-blur-sm">
<div
class="bg-white rounded-xl shadow-lg max-w-sm w-full p-6 relative transform transition-all duration-300 scale-95 opacity-0 animate-fadeIn">
<!-- QR Code -->
<div class="flex justify-center mb-4">
<div class="p-2 border border-C rounded-lg">
<img :src="qrCodeUrl" alt="QR Code" class="w-36 h-36" />
</div>
</div>
<!-- Info Produk -->
<div class="text-center text-D font-bold text-lg mb-1">
{{ selectedItem?.kode_item }}
</div>
<div class="text-center text-gray-700 font-medium mb-1">
{{ selectedItem?.produk?.nama }}
</div>
<div class="text-center text-gray-500 text-sm mb-4">
{{ selectedItem?.produk?.kategori }}
</div>
<!-- Tombol Cetak -->
<div class="flex justify-center gap-2 mb-4">
<button @click="printQR" class="bg-D text-A px-4 py-2 rounded hover:bg-D/80 transition"
title="Cetak menggunakan printer browser">
<i class="fas fa-print mr-2"></i>
Print
</button>
</div>
<PrintBarcode :code="selectedItem?.kode_item" :item="selectedItem?.produk" />
<!-- Dropdown pilih nampan -->
<div class="mb-4">
@ -173,6 +148,7 @@
import { ref, computed, onMounted } from "vue";
import axios from "axios";
import ConfirmDeleteModal from './ConfirmDeleteModal.vue';
import PrintBarcode from './PrintBarcode.vue';
const props = defineProps({
search: {
@ -203,15 +179,6 @@ const confirmModalMessage = ref("");
const confirmText = ref("Ya, Konfirmasi");
const cancelText = ref("Batal");
// QR Code generator
const qrCodeUrl = computed(() => {
if (selectedItem.value) {
const data = selectedItem.value.kode_item;
return `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(data)}`;
}
return "";
});
// Computed untuk statistik
const totalWeight = computed(() => {
const total = filteredItems.value.reduce((sum, item) => {
@ -252,13 +219,6 @@ const confirmDelete = async () => {
await axios.delete(`/api/item/${selectedItem.value.id}`, {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
});
// Tampilkan alert sukses
alert.value = { success: `Item ${selectedItem.value.kode_item} berhasil dihapus.` };
// Refresh data
await refreshData();
// Tutup modal & popup
showDeleteConfirm.value = false;
closePopup();
@ -330,106 +290,6 @@ const handleConfirmAction = async () => {
closeConfirmModal();
};
// Print QR menggunakan browser
const printQR = () => {
if (qrCodeUrl.value && selectedItem.value) {
const printWindow = window.open('', '_blank');
const itemCode = selectedItem.value.kode_item;
printWindow.document.write(`
<html>
<head>
<title>Print QR Code - ${itemCode}</title>
<style>
@page {
size: 38mm 25mm;
margin: 0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
width: 38mm;
height: 25mm;
padding: 2mm;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.qr-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.qr-img {
width: 18mm;
height: 18mm;
margin-bottom: 1mm;
}
.item-code {
font-weight: bold;
font-size: 8pt;
margin-bottom: 0.5mm;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 34mm;
}
.item-name {
font-size: 6pt;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 34mm;
color: #333;
}
.item-weight {
font-size: 6pt;
color: #666;
margin-top: 0.5mm;
}
@media print {
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
</head>
<body>
<div class="qr-container">
<img id="qr-img" class="qr-img" src="${qrCodeUrl.value}" alt="QR Code" />
</div>
</body>
</html>
`);
printWindow.document.close();
const img = printWindow.document.getElementById("qr-img");
img.onload = () => {
printWindow.focus();
printWindow.print();
};
}
};
const handleImageError = (event) => {
event.target.style.display = 'none';
};

View File

@ -35,9 +35,7 @@
<!-- QR Code -->
<div class="flex justify-center mb-4">
<div class="p-2 border border-gray-300 rounded-lg">
<img :src="qrCodeUrl" alt="QR Code" class="w-36 h-36" />
</div>
<PrintBarcode :code="createdItem?.kode_item" :item="product"/>
</div>
<!-- Item Info -->
@ -53,10 +51,6 @@
class="flex-1 px-6 py-2 bg-gray-400 hover:bg-gray-500 text-white rounded-lg transition-colors">
Selesai
</button>
<button @click="printQR"
class="flex-1 px-6 py-2 bg-C hover:bg-B text-D rounded-lg transition-colors">
<i class="fas fa-print mr-1"></i>Print
</button>
<button @click="addNewItem"
class="flex-1 px-6 py-2 bg-C hover:bg-B text-D rounded-lg transition-colors">
Buat Lagi
@ -69,10 +63,11 @@
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { ref, watch } from 'vue';
import axios from 'axios';
import Modal from './Modal.vue';
import InputSelect from './InputSelect.vue';
import PrintBarcode from './PrintBarcode.vue';
// Props
const props = defineProps({
@ -99,15 +94,7 @@ const success = ref(false);
const loading = ref(false);
const createdItem = ref(null);
// QR Code generator - berdasarkan logika dari brankas list
const qrCodeUrl = computed(() => {
if (createdItem.value && props.product) {
const kode_item = createdItem.value.kode_item;
const data = kode_item;
return `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(data)}`;
}
return "";
});
// QR Code rendering/printing moved to PrintBarcode component
// Methods
@ -174,105 +161,6 @@ const addNewItem = () => {
createdItem.value = null;
};
// Fungsi print berdasarkan logika dari brankas list
const printQR = () => {
if (qrCodeUrl.value && createdItem.value && props.product) {
const printWindow = window.open('', '_blank');
const itemCode = createdItem.value.kode_item;
printWindow.document.write(`
<html>
<head>
<title>Print QR Code - ${itemCode}</title>
<style>
@page {
size: 38mm 25mm;
margin: 0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
width: 38mm;
height: 25mm;
padding: 2mm;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.qr-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.qr-img {
width: 18mm;
height: 18mm;
margin-bottom: 1mm;
}
.item-code {
font-weight: bold;
font-size: 8pt;
margin-bottom: 0.5mm;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 34mm;
}
.item-name {
font-size: 6pt;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 34mm;
color: #333;
}
.item-weight {
font-size: 6pt;
color: #666;
margin-top: 0.5mm;
}
@media print {
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
</head>
<body>
<div class="qr-container">
<img id="qr-img" class="qr-img" src="${qrCodeUrl.value}" alt="QR Code" />
</div>
</body>
</html>
`);
printWindow.document.close();
const img = printWindow.document.getElementById("qr-img");
img.onload = () => {
printWindow.focus();
printWindow.print();
};
}
};
const handleClose = () => {
// Reset state

View File

@ -55,7 +55,7 @@ const sizeClass = computed(() => {
})
const handleOverlayClick = () => {
if (clickOutside.value) {
if (props.clickOutside.value) {
emit('close')
}
}

View File

@ -0,0 +1,141 @@
<template>
<div class="text-center">
<div class="p-2 border rounded-lg inline-block">
<img :src="barcodeUrl" alt="Barcode" class="w-36 h-12" id="barcode-img" />
</div>
<div v-if="item.nama" class="mt-2 text-sm text-gray-700 font-medium">
{{ item.nama }}
(<span v-if="item.berat" class="mt-1 text-xs text-gray-500">{{ item.berat }} g</span>)
</div>
<div class="flex justify-center mt-3">
<button @click="printBarcode" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-400 transition">
<i class="fas fa-print mr-2"></i>Cetak
</button>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
code: { type: String, required: true },
item: { type: Object, required: true },
});
const barcodeUrl = computed(() => {
if (!props.code) return '';
// Using bwip-js API for Code128 barcode
return `https://bwipjs-api.metafloor.com/?bcid=code128&text=${encodeURIComponent(props.code)}&scale=2`;
});
const printBarcode = () => {
if (!barcodeUrl.value || !props.code) return;
const printWindow = window.open('', '_blank');
const kode = props.code || 'N/A';
const nama = props.item.nama || 'N/A';
const berat = props.item.berat ? `(${props.item.berat} g)` : '';
printWindow.document.write(`
<html>
<head>
<title>Print Barcode - ${kode}</title>
<style>
@page {
size: 38mm 25mm;
margin: 0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
width: 38mm;
height: 25mm;
display: flex;
align-items: center;
justify-content: center;
}
.label {
display: flex;
width: 38mm;
height: 25mm;
}
.barcode-container {
width: 19mm;
height: 25mm;
display: flex;
align-items: center;
justify-content: center;
}
.barcode-img {
transform: rotate(90deg);
transform-origin: center;
max-height: 19mm;
max-width: 23mm;
}
.details-container {
width: 19mm;
height: 25mm;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.item-name {
display: block;
width: 23mm;
transform: rotate(270deg);
transform-origin: center;
font-size: 6pt;
line-height: 1.1;
text-align: left;
white-space: normal;
word-wrap: break-word;
}
</style>
</head>
<body>
<div class="label">
<div class="barcode-container">
<img id="barcode-img" class="barcode-img"
src="${barcodeUrl.value}" alt="Barcode" />
</div>
<div class="details-container">
<div class="item-name">${nama} ${berat}</div>
</div>
</div>
</body>
</html>
`);
printWindow.document.close();
const img = printWindow.document.getElementById('barcode-img');
if (img) {
img.onload = () => {
printWindow.focus();
printWindow.print();
};
} else {
// Fallback
printWindow.focus();
printWindow.print();
}
};
</script>

View File

@ -86,24 +86,7 @@
<!-- Pop-up pindah item -->
<div v-if="isPopupVisible" class="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<div class="bg-white rounded-xl shadow-lg max-w-sm w-full p-6 relative">
<div class="flex justify-center mb-2">
<div class="p-2 border rounded-lg">
<img :src="qrCodeUrl" alt="QR Code" class="w-36 h-36" />
</div>
</div>
<div class="text-center text-D font-bold text-lg">
{{ selectedItem.kode_item }}
</div>
<div class="text-center text-gray-700 font-medium mb-3">
{{ selectedItem.produk.nama }}
</div>
<div class="flex justify-center mb-4">
<button @click="printQR" class="bg-D text-A px-4 py-2 rounded hover:bg-D/80 transition">
<i class="fas fa-print mr-2"></i>Cetak
</button>
</div>
<PrintBarcode :code="selectedItem.kode_item" :item="selectedItem.produk" />
<!-- Dropdown -->
<div class="mb-4">
@ -148,6 +131,7 @@ import { ref, onMounted, computed } from "vue";
import axios from "axios";
import InputSelect from "./InputSelect.vue";
import ConfirmDeleteModal from './ConfirmDeleteModal.vue';
import PrintBarcode from './PrintBarcode.vue';
const isAdmin = localStorage.getItem("role") === "owner";
@ -167,113 +151,6 @@ const selectedItem = ref(null);
const selectedTrayId = ref("");
const showDeleteConfirm = ref(false);
// QR Code generator
const qrCodeUrl = computed(() => {
if (selectedItem.value) {
const data = selectedItem.value.kode_item;
return `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(data)}`;
}
return "";
});
const printQR = () => {
if (qrCodeUrl.value && selectedItem.value) {
const printWindow = window.open('', '_blank');
const itemCode = selectedItem.value.kode_item;
printWindow.document.write(`
<html>
<head>
<title>Print QR Code - ${itemCode}</title>
<style>
@page {
size: 38mm 25mm;
margin: 0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
width: 38mm;
height: 25mm;
padding: 2mm;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.qr-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.qr-img {
width: 18mm;
height: 18mm;
margin-bottom: 1mm;
}
.item-code {
font-weight: bold;
font-size: 8pt;
margin-bottom: 0.5mm;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 34mm;
}
.item-name {
font-size: 6pt;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 34mm;
color: #333;
}
.item-weight {
font-size: 6pt;
color: #666;
margin-top: 0.5mm;
}
@media print {
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
</head>
<body>
<div class="qr-container">
<img id="qr-img" class="qr-img" src="${qrCodeUrl.value}" alt="QR Code" />
</div>
</body>
</html>
`);
printWindow.document.close();
const img = printWindow.document.getElementById("qr-img");
img.onload = () => {
printWindow.focus();
printWindow.print();
};
}
};
const confirmDelete = async () => {
if (!selectedItem.value) return;