This commit is contained in:
ArifHilmi 2025-03-03 08:00:11 +08:00
parent 6150baa25d
commit 11e8e84064
3 changed files with 145 additions and 72 deletions

View File

@ -2,22 +2,27 @@
ViewData["Title"] = "QR Scanner"; ViewData["Title"] = "QR Scanner";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
<style> <style scoped>
@@font-face { .error {
font-family: 'OCR-A'; font-weight: bold;
src: url('../assets/fonts/ocraext.ttf'); color: red;
} }
.QrPrintFont { .barcode-format-checkbox {
font-family: 'OCR-A', monospace; margin-right: 10px;
white-space: nowrap;
display: inline-block;
} }
.table td img { select {
display: block !important; width: 200px; /* Adjust width as needed */
padding: 5px;
font-size: 16px;
} }
</style> </style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml") @await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
<div id="registerItem" class="row"> <div id="registerItem" class="row">
<div class="row card"> <div class="row card">
@ -28,11 +33,25 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="app" v-if="displayStatus == null" data-aos="fade-right"> <select v-if="displayStatus == null" class="form-select" v-model="selectedCameraId" v-on:change="updateCamera">
<h1 data-aos="fade-right">QR & Barcode Scanner</h1> <option v-for="device in videoInputDevices" :key="device.deviceId" :value="device.deviceId">
<div id="reader" data-aos="fade-right"></div> {{ device.label || `Camera ${videoInputDevices.indexOf(device) + 1}` }}
</div> </option>
</select>
<div id="registerItem" v-if="displayStatus == null" data-aos="fade-right">
<p style="text-align:center; padding:10px;">Scan QR Code Here:</p>
<qrcode-stream :constraints="selectedConstraints"
:formats="['qr_code']"
:track="trackFunctionSelected.value"
v-on:camera-on="onCameraReady"
v-on:detect="onDecode"
v-on:error="onError">
</qrcode-stream>
<p class="error">{{ error }}</p>
</div>
<!--RECEIVE OR RETURN INTERFACE --> <!--RECEIVE OR RETURN INTERFACE -->
<div style="text-align: center; margin: 20px 0;" v-if="displayStatus === 'arrived'"> <div style="text-align: center; margin: 20px 0;" v-if="displayStatus === 'arrived'">
<h2>Item Receive Information :</h2> <h2>Item Receive Information :</h2>
@ -242,7 +261,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">Consignment Note : </label> <label class="col-sm-4 col-form-label">Consignment Note : </label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="file" v-model="consignmentNote" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" /> <input type="file" v-on:change="handleFileUpload" class="form-control-file" accept="image/png, image/jpeg, application/pdf" />
</div> </div>
</div> </div>
</div> </div>
@ -295,7 +314,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">Consignment Note:</label> <label class="col-sm-4 col-form-label">Consignment Note:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="file" v-model="consignmentNote" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" /> <input type="file" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary">Return Item</button> <button type="submit" class="btn btn-primary">Return Item</button>
@ -330,17 +349,15 @@
@{ @{
await Html.RenderPartialAsync("_ValidationScriptsPartial"); await Html.RenderPartialAsync("_ValidationScriptsPartial");
} }
<script src="~/js/vue-qrcode-reader.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html5-qrcode/minified/html5-qrcode.min.js"></script>
<script> <script>
const app = Vue.createApp({ const app = Vue.createApp({
data() { data() {
return { return {
thisItem: null, thisItem: null,
qrCodeResult: null,
html5QrCodeScanner: null,
debounceTimeout: null,
selectedUser: "", selectedUser: "",
selectedStation: "", selectedStation: "",
stationList: [], stationList: [],
@ -353,14 +370,33 @@
consignmentNote: null, consignmentNote: null,
receiveReturn: null, receiveReturn: null,
UniqueID: null, UniqueID: null,
//QR VARIABLE
qrCodeResult: null,
debounceTimeout: null,
error: "",
selectedConstraints: { facingMode: "environment" },
trackFunctionSelected: { text: 'outline', value: null },
barcodeFormats: {
qr_code: true, // Hanya mendukung QR Code
code_128: true,
ean_13: true
},
constraintOptions: [
{ label: "Rear Camera", constraints: { facingMode: "environment" } },
{ label: "Front Camera", constraints: { facingMode: "user" } }
],
videoInputDevices: [],
selectedCameraId: null,
}; };
}, },
mounted() { mounted() {
this.trackFunctionSelected.value = this.paintOutline;
this.fetchStation(); this.fetchStation();
this.fetchUser(); this.fetchUser();
this.startScanner();
}, },
methods: { methods: {
handleFileUpload(event) { handleFileUpload(event) {
const file = event.target.files[0]; const file = event.target.files[0];
@ -486,41 +522,6 @@
alert('Inventory PSTW Error: An error occurred.'); alert('Inventory PSTW Error: An error occurred.');
} }
}, },
startScanner() {
let qrboxFunction = function (viewfinderWidth, viewfinderHeight) {
let minEdgePercentage = 0.7; // 70%
let minEdgeSize = Math.min(viewfinderWidth, viewfinderHeight);
let qrboxSize = Math.floor(minEdgeSize * minEdgePercentage);
return {
width: qrboxSize,
height: qrboxSize
};
}
const config = {
fps: 60,
qrbox: qrboxFunction
};
navigator.mediaDevices.getUserMedia({ video: true })
.then(() => {
this.html5QrCodeScanner = new Html5QrcodeScanner("reader", config, false);
this.html5QrCodeScanner.render((decodedText) => {
if (!this.debounceTimeout) {
this.debounceTimeout = setTimeout(() => {
this.qrCodeResult = decodedText;
this.UniqueID = decodedText.split('/').pop();
this.fetchItem(decodedText.split('/').pop());
this.debounceTimeout = null;
}, this.debounceTime);
}
});
})
.catch((err) => {
console.error("Error accessing camera:", err);
});
},
async fetchItem(itemid) { async fetchItem(itemid) {
try { try {
const response = await fetch('/InvMainAPI/GetItem/' + itemid, { const response = await fetch('/InvMainAPI/GetItem/' + itemid, {
@ -542,7 +543,7 @@
this.thisItem = null; this.thisItem = null;
} }
} else { } else {
console.error('Failed to fetch item information'); this.error = 'Qr Code Not Register to the system';
} }
} catch (error) { } catch (error) {
console.error('Error fetching item information:', error); console.error('Error fetching item information:', error);
@ -590,8 +591,6 @@
this.displayStatus = null; this.displayStatus = null;
this.qrCodeResult = null; this.qrCodeResult = null;
this.thisItem = null; this.thisItem = null;
this.startScanner();
}, },
resetForm() { resetForm() {
@ -603,6 +602,76 @@
this.updateItemMovement(); this.updateItemMovement();
}, },
// Split Url dapatkan unique ID Je
onDecode(detectedCodes) {
if (detectedCodes.length > 0) {
this.qrCodeResult = detectedCodes[0].rawValue; // Ambil URL dari rawValue
this.UniqueID = this.qrCodeResult.split('/').pop(); // Ambil UniqueID dari URL
this.fetchItem(this.UniqueID);
}
},
//Showing Qr Error
onError(err) {
let message = `[${err.name}]: `;
if (err.name === "NotAllowedError") {
message += "You have to allow camera accecss.";
} else if (err.name === "NotFoundError") {
message += "There's no camera detect.";
} else if (err.name === "NotReadableError") {
message += "You are using camera on the other application.";
} else {
message += err.message;
}
this.error = message;
},
//Setting Camera
async onCameraReady() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
this.videoInputDevices = devices.filter(device => device.kind === 'videoinput');
if (this.videoInputDevices.length > 0) {
// Keep the selected camera if already chosen
if (!this.selectedCameraId) {
this.selectedCameraId = this.videoInputDevices[0].deviceId;
}
this.selectedConstraints = { deviceId: { exact: this.selectedCameraId } };
} else {
this.error = "No camera detected.";
}
} catch (err) {
this.error = "Error accessing camera: " + err.message;
}
},
//Update Camera Category
updateCamera() {
this.selectedConstraints = { deviceId: { exact: this.selectedCameraId } };
},
//Red box if QR Detect
paintOutline(detectedCodes, ctx) {
for (const detectedCode of detectedCodes) {
const [firstPoint, ...otherPoints] = detectedCode.cornerPoints;
ctx.strokeStyle = 'red'; // Warna garis merah
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(firstPoint.x, firstPoint.y);
for (const { x, y } of otherPoints) {
ctx.lineTo(x, y);
}
ctx.lineTo(firstPoint.x, firstPoint.y);
ctx.closePath();
ctx.stroke();
}
},
//Ni return message
receiveReturnMessage() { receiveReturnMessage() {
$("#returnModal").modal("show"); $("#returnModal").modal("show");
}, },
@ -612,6 +681,7 @@
}, },
}, },
}); });
app.component("qrcode-stream", VueQrcodeReader.QrcodeStream);
app.mount('#registerItem'); app.mount('#registerItem');

View File

@ -13,7 +13,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"applicationUrl": "http://192.168.11.107:5124", "applicationUrl": "http://localhost:5124",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
@ -22,7 +22,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"applicationUrl": "https://192.168.11.107:7036;http://192.168.11.107:5124", "applicationUrl": "https://localhost:7036;http://localhost:5124",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

File diff suppressed because one or more lines are too long