code scanner
This commit is contained in:
parent
2b3231e26c
commit
2a77a950f5
9 changed files with 312 additions and 91 deletions
64
package-lock.json
generated
64
package-lock.json
generated
|
@ -39,6 +39,7 @@
|
|||
"unplugin-vue-markdown": "^0.28.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.4.29",
|
||||
"vue-qrcode-reader": "^5.7.1",
|
||||
"vue-router": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -3080,6 +3081,18 @@
|
|||
"@types/underscore": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/dom-webcodecs": {
|
||||
"version": "0.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.14.tgz",
|
||||
"integrity": "sha512-ba9aF0qARLLQpLihONIRbj8VvAdUxO+5jIxlscVcDAQTcJmq5qVr781+ino5qbQUJUmO21cLP2eLeXYWzao5Vg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/emscripten": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.40.0.tgz",
|
||||
"integrity": "sha512-MD2JJ25S4tnjnhjWyalMS6K6p0h+zQV6+Ylm+aGbiS8tSn/aHLSGNzBgduj6FB4zH0ax2GRMGYi/8G1uOxhXWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz",
|
||||
|
@ -4109,6 +4122,16 @@
|
|||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/barcode-detector": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.2.2.tgz",
|
||||
"integrity": "sha512-JcSekql+EV93evfzF9zBr+Y6aRfkR+QFvgyzbwQ0dbymZXoAI9+WgT7H1E429f+3RKNncHz2CW98VQtaaKpmfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/dom-webcodecs": "^0.1.11",
|
||||
"zxing-wasm": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bare-events": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz",
|
||||
|
@ -8696,6 +8719,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sdp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
|
||||
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||
|
@ -10167,6 +10196,19 @@
|
|||
"eslint": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-qrcode-reader": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.7.1.tgz",
|
||||
"integrity": "sha512-7QBu3PqaPJHxobiDLqgcrp6wsjdTk9GJWhRCd4CgQYi93gBw/sIXNNWtbjeKz8d3QYj13n9dyPvcPMUcGOsBHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"barcode-detector": "2.2.2",
|
||||
"webrtc-adapter": "8.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.3.tgz",
|
||||
|
@ -10210,6 +10252,19 @@
|
|||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz",
|
||||
"integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"sdp": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
|
||||
|
@ -10841,6 +10896,15 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zxing-wasm": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-1.1.3.tgz",
|
||||
"integrity": "sha512-MYm9k/5YVs4ZOTIFwlRjfFKD0crhefgbnt1+6TEpmKUDFp3E2uwqGSKwQOd2hOIsta/7Usq4hnpNRYTLoljnfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/emscripten": "^1.39.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"unplugin-vue-markdown": "^0.28.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.4.29",
|
||||
"vue-qrcode-reader": "^5.7.1",
|
||||
"vue-router": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
73
src/components/CodeDetector.vue
Normal file
73
src/components/CodeDetector.vue
Normal file
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div class="w-full md:max-w-md">
|
||||
<XMarkIcon class="ml-auto mb-2 w-5 h-5 cursor-pointer" @click="closeModal" />
|
||||
<qrcode-stream
|
||||
:constraints="selectedCamera.constraints"
|
||||
:track="trackFunctionOptions[4].value"
|
||||
:formats="barcodeFormats"
|
||||
:paused="paused"
|
||||
@error="onError"
|
||||
@detect="onDetect"
|
||||
@camera-on="onCameraReady"
|
||||
/>
|
||||
<br />
|
||||
<select v-model="selectedCamera">
|
||||
<option v-for="c in selecteableCameras" :value="c">{{ c.label }}</option>
|
||||
</select>
|
||||
<div class="flex flex-row justify-end gap-4 py-2">
|
||||
<button primary-outline @click="paused = false" :disabled="!paused">weiter scannen</button>
|
||||
<button primary-outline @click="commit">bestätigen</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "../stores/modal";
|
||||
import {
|
||||
barcodeFormats,
|
||||
defaultConstraintOptions,
|
||||
getAvailableCameras,
|
||||
handleScannerError,
|
||||
trackFunctionOptions,
|
||||
} from "../helpers/codeScanner";
|
||||
import { QrcodeStream, type DetectedBarcode } from "vue-qrcode-reader";
|
||||
import { XMarkIcon } from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
callback: {
|
||||
type: Function,
|
||||
default: (result: string) => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selecteableCameras: defaultConstraintOptions,
|
||||
selectedCamera: defaultConstraintOptions[0],
|
||||
paused: false,
|
||||
detected: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
async onCameraReady() {
|
||||
this.selecteableCameras = await getAvailableCameras();
|
||||
},
|
||||
onDetect(result: Array<DetectedBarcode>) {
|
||||
this.paused = true;
|
||||
this.detected = result.map((r) => r.rawValue)[0];
|
||||
},
|
||||
onError(err: Error) {
|
||||
console.log(handleScannerError(err));
|
||||
},
|
||||
commit() {
|
||||
this.callback(this.detected);
|
||||
this.closeModal();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -9,6 +9,7 @@
|
|||
<component
|
||||
:is="component_ref"
|
||||
:data="data"
|
||||
:callback="callback"
|
||||
@click.stop
|
||||
class="p-4 bg-white rounded-lg max-h-[95%] overflow-y-auto"
|
||||
/>
|
||||
|
@ -23,7 +24,7 @@ import { useModalStore } from "@/stores/modal";
|
|||
<script lang="ts">
|
||||
export default {
|
||||
computed: {
|
||||
...mapState(useModalStore, ["show", "component_ref", "data"]),
|
||||
...mapState(useModalStore, ["show", "component_ref", "data", "callback"]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="grow flex flex-col gap-2 overflow-hidden">
|
||||
<div v-if="useSearch" class="relative self-end flex flex-row items-center gap-2">
|
||||
<Spinner v-if="deferingSearch" />
|
||||
<QrCodeIcon v-if="useScanner" class="h-7 cursor-pointer" @click="scanCode" />
|
||||
<input
|
||||
type="text"
|
||||
class="!max-w-64 !w-64 rounded-md shadow-sm relative block px-3 py-2 pr-5 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
|
@ -66,10 +67,12 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T extends { id: FieldType }">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { computed, defineAsyncComponent, markRaw, ref, watch } from "vue";
|
||||
import { ChevronRightIcon, ChevronLeftIcon, XMarkIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "./Spinner.vue";
|
||||
import type { FieldType } from "@/types/dynamicQueries";
|
||||
import { QrCodeIcon } from "@heroicons/vue/24/outline";
|
||||
import { useModalStore } from "../stores/modal";
|
||||
|
||||
const props = defineProps({
|
||||
items: { type: Array<T>, default: [] },
|
||||
|
@ -79,6 +82,7 @@ const props = defineProps({
|
|||
useSearch: { type: Boolean, default: false },
|
||||
enablePreSearch: { type: Boolean, default: false },
|
||||
indicateLoading: { type: Boolean, default: false },
|
||||
useScanner: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const slots = defineSlots<{
|
||||
|
@ -183,84 +187,14 @@ const filterData = (array: Array<any>, searchString: string, start: number, end:
|
|||
)
|
||||
.filter((elem, index) => (elem?.tab_pos ?? index) >= start && (elem?.tab_pos ?? index) < end);
|
||||
};
|
||||
</script>
|
||||
|
||||
<!--
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
computed: {
|
||||
entryCount() {
|
||||
return this.totalCount ?? this.items.length;
|
||||
},
|
||||
showingStart() {
|
||||
return this.currentPage * this.maxEntriesPerPage;
|
||||
},
|
||||
showingEnd() {
|
||||
let max = this.currentPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
|
||||
if (max > this.entryCount) max = this.entryCount;
|
||||
return max;
|
||||
},
|
||||
showingText() {
|
||||
return `${this.entryCount != 0 ? this.showingStart + 1 : 0} - ${this.showingEnd}`;
|
||||
},
|
||||
countOfPages() {
|
||||
return Math.ceil(this.entryCount / this.maxEntriesPerPage);
|
||||
},
|
||||
displayedPagesNumbers(): Array<number | "."> {
|
||||
//indicate if "." or page number gets pushed
|
||||
let stateOfPush = false;
|
||||
|
||||
return [...new Array(this.countOfPages)].reduce((acc, curr, index) => {
|
||||
if (
|
||||
// always display first 2 pages
|
||||
index <= 1 ||
|
||||
// always display last 2 pages
|
||||
index >= this.countOfPages - 2 ||
|
||||
// always display 1 pages around current page
|
||||
(this.currentPage - 1 <= index && index <= this.currentPage + 1)
|
||||
) {
|
||||
acc.push(index);
|
||||
stateOfPush = false;
|
||||
return acc;
|
||||
function scanCode() {
|
||||
useModalStore().openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/CodeDetector.vue"))),
|
||||
"pagination",
|
||||
(result: string) => {
|
||||
searchString.value = result;
|
||||
}
|
||||
// abort if placeholder already added to array
|
||||
if (stateOfPush == true) return acc;
|
||||
// show placeholder if pagenumber is not actively rendered
|
||||
acc.push(".");
|
||||
stateOfPush = true;
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
visibleRows() {
|
||||
return this.filterData(this.items, this.searchString, this.showingStart, this.showingEnd);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadPage(newPage: number | ".") {
|
||||
if (newPage == ".") return;
|
||||
if (newPage < 0 || newPage >= this.countOfPages) return;
|
||||
|
||||
let pageStart = newPage * this.maxEntriesPerPage;
|
||||
let pageEnd = newPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
|
||||
if (pageEnd > this.entryCount) pageEnd = this.entryCount;
|
||||
|
||||
let loadedElementCount = this.filterData(this.items, this.searchString, pageStart, pageEnd).length;
|
||||
if (loadedElementCount < this.maxEntriesPerPage)
|
||||
this.$emit("loadData", { offset: pageStart, count: this.maxEntriesPerPage, search: this.searchString });
|
||||
|
||||
this.currentPage = newPage;
|
||||
},
|
||||
filterData(array: Array<any>, searchString: string, start: number, end: number): Array<any> {
|
||||
return array
|
||||
.filter(
|
||||
(elem) =>
|
||||
!this.enablePreSearch ||
|
||||
searchString.trim() == "" ||
|
||||
this.config.some((col) => typeof elem?.[col.key] == "string" && elem[col.key].includes(searchString.trim()))
|
||||
)
|
||||
.filter((elem, index) => (elem?.tab_pos ?? index) >= start && (elem?.tab_pos ?? index) < end);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script> -->
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
|
143
src/helpers/codeScanner.ts
Normal file
143
src/helpers/codeScanner.ts
Normal file
|
@ -0,0 +1,143 @@
|
|||
import type { BarcodeFormat, DetectedBarcode } from "barcode-detector/pure";
|
||||
|
||||
/*** select camera ***/
|
||||
export interface Camera {
|
||||
label: string;
|
||||
constraints: {
|
||||
deviceId?: string;
|
||||
facingMode: string;
|
||||
};
|
||||
}
|
||||
export const defaultConstraintOptions: Array<Camera> = [
|
||||
{ label: "rear camera", constraints: { facingMode: "environment" } },
|
||||
{ label: "front camera", constraints: { facingMode: "user" } },
|
||||
];
|
||||
|
||||
export async function getAvailableCameras(): Promise<Array<Camera>> {
|
||||
// NOTE: on iOS we can't invoke `enumerateDevices` before the user has given
|
||||
// camera access permission. `QrcodeStream` internally takes care of
|
||||
// requesting the permissions. The `camera-on` event should guarantee that this
|
||||
// has happened.
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const videoDevices = devices.filter(({ kind }) => kind === "videoinput");
|
||||
|
||||
return [
|
||||
...defaultConstraintOptions,
|
||||
...videoDevices.map(({ deviceId, label }) => ({
|
||||
label: `${label} (ID: ${deviceId})`,
|
||||
constraints: { deviceId, facingMode: "custom" },
|
||||
})),
|
||||
];
|
||||
}
|
||||
|
||||
/*** track functons ***/
|
||||
export function paintOutline(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
|
||||
for (const detectedCode of detectedCodes) {
|
||||
const [firstPoint, ...otherPoints] = detectedCode.cornerPoints;
|
||||
|
||||
ctx.strokeStyle = "red";
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
export function paintBoundingBox(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
|
||||
for (const detectedCode of detectedCodes) {
|
||||
const {
|
||||
boundingBox: { x, y, width, height },
|
||||
} = detectedCode;
|
||||
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = "#007bff";
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
export function paintCenterText(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
|
||||
for (const detectedCode of detectedCodes) {
|
||||
const { boundingBox, rawValue } = detectedCode;
|
||||
|
||||
const centerX = boundingBox.x + boundingBox.width / 2;
|
||||
const centerY = boundingBox.y + boundingBox.height / 2;
|
||||
|
||||
const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width);
|
||||
|
||||
ctx.font = `bold ${fontSize}px sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
|
||||
ctx.lineWidth = 3;
|
||||
ctx.strokeStyle = "#35495e";
|
||||
ctx.strokeText(detectedCode.rawValue, centerX, centerY);
|
||||
|
||||
ctx.fillStyle = "#5cb984";
|
||||
ctx.fillText(rawValue, centerX, centerY);
|
||||
}
|
||||
}
|
||||
export const trackFunctionOptions = [
|
||||
{ text: "nothing (default)", value: undefined },
|
||||
{ text: "outline", value: paintOutline },
|
||||
{ text: "centered text", value: paintCenterText },
|
||||
{ text: "bounding box", value: paintBoundingBox },
|
||||
{
|
||||
text: "mixed",
|
||||
value: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => {
|
||||
paintOutline(detectedCodes, ctx);
|
||||
paintCenterText(detectedCodes, ctx);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/*** barcode formats ***/
|
||||
export const barcodeFormats: Array<BarcodeFormat> = [
|
||||
"aztec",
|
||||
"code_128",
|
||||
"code_39",
|
||||
"code_93",
|
||||
"codabar",
|
||||
"databar",
|
||||
"databar_expanded",
|
||||
"data_matrix",
|
||||
"dx_film_edge",
|
||||
"ean_13",
|
||||
"ean_8",
|
||||
"itf",
|
||||
"maxi_code",
|
||||
"micro_qr_code",
|
||||
"pdf417",
|
||||
"qr_code",
|
||||
"rm_qr_code",
|
||||
"upc_a",
|
||||
"upc_e",
|
||||
"linear_codes",
|
||||
"matrix_codes",
|
||||
];
|
||||
|
||||
/*** error handling ***/
|
||||
export function handleScannerError(err: Error) {
|
||||
let error = `[${err.name}]: `;
|
||||
|
||||
if (err.name === "NotAllowedError") {
|
||||
error += "you need to grant camera access permission";
|
||||
} else if (err.name === "NotFoundError") {
|
||||
error += "no camera on this device";
|
||||
} else if (err.name === "NotSupportedError") {
|
||||
error += "secure context required (HTTPS, localhost)";
|
||||
} else if (err.name === "NotReadableError") {
|
||||
error += "is the camera already in use?";
|
||||
} else if (err.name === "OverconstrainedError") {
|
||||
error += "installed cameras are not suitable";
|
||||
} else if (err.name === "StreamApiNotSupportedError") {
|
||||
error += "Stream API is not supported in this browser";
|
||||
} else if (err.name === "InsecureContextError") {
|
||||
error += "Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";
|
||||
} else {
|
||||
error += err.message;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
|
@ -4,19 +4,22 @@ export const useModalStore = defineStore("modal", {
|
|||
state: () => {
|
||||
return {
|
||||
show: false,
|
||||
component_ref: null as any,
|
||||
data: null as any,
|
||||
component_ref: undefined as any,
|
||||
data: undefined as any,
|
||||
callback: undefined as undefined | Function,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
openModal(component_ref: any, data?: any) {
|
||||
openModal(component_ref: any, data?: any, callback?: Function) {
|
||||
this.component_ref = component_ref;
|
||||
this.data = data;
|
||||
this.callback = callback;
|
||||
this.show = true;
|
||||
},
|
||||
closeModal() {
|
||||
this.component_ref = null;
|
||||
this.data = null;
|
||||
this.component_ref = undefined;
|
||||
this.data = undefined;
|
||||
this.callback = undefined;
|
||||
this.show = false;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
<template #diffMain>
|
||||
<div class="flex flex-col w-full h-full gap-2 justify-center px-7">
|
||||
<Pagination
|
||||
:items="[]"
|
||||
:items="equipments"
|
||||
:totalCount="totalCount"
|
||||
:indicateLoading="loading == 'loading'"
|
||||
:useSearch="true"
|
||||
useSearch
|
||||
useScanner
|
||||
@load-data="(offset, count, search) => fetchEquipments(offset, count, search)"
|
||||
@search="(search) => fetchEquipments(0, maxEntriesPerPage, search, true)"
|
||||
>
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
<template #diffMain>
|
||||
<div class="flex flex-col w-full h-full gap-2 justify-center px-7">
|
||||
<Pagination
|
||||
:items="[]"
|
||||
:items="vehicles"
|
||||
:totalCount="totalCount"
|
||||
:indicateLoading="loading == 'loading'"
|
||||
:useSearch="true"
|
||||
useSearch
|
||||
useScanner
|
||||
@load-data="(offset, count, search) => fetchVehicles(offset, count, search)"
|
||||
@search="(search) => fetchVehicles(0, maxEntriesPerPage, search, true)"
|
||||
>
|
||||
|
|
Loading…
Add table
Reference in a new issue