142 lines
3.2 KiB
Vue
142 lines
3.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue';
|
|
|
|
import ShowAttachment from 'components/ShowAttachent.vue';
|
|
import DialogForm from 'components/DialogForm.vue';
|
|
|
|
const isOpen = ref(false);
|
|
const isEdit = ref(false);
|
|
const isRunning = ref(false);
|
|
|
|
const file = ref<File>();
|
|
const url = ref<string>();
|
|
const metadata = ref<Data>();
|
|
|
|
const splitRatio = ref(50);
|
|
|
|
type Data = Record<string, any>;
|
|
|
|
type Props = {
|
|
title?: string;
|
|
readonly?: boolean;
|
|
autoSave?: boolean;
|
|
data?: Data;
|
|
hideBtn?: boolean;
|
|
};
|
|
|
|
type HandleProps = {
|
|
ocr?: (file: File) => Promise<false | Data>;
|
|
save?: (file: File, meta?: Data) => void | Promise<boolean>;
|
|
override?: (before: Data, after: Data) => Data;
|
|
};
|
|
|
|
defineEmits<{
|
|
(e: 'submit', file: File, meta?: Data): void;
|
|
}>();
|
|
|
|
defineExpose({ isRunning });
|
|
|
|
const props = withDefaults(defineProps<Props & HandleProps>(), { title: '' });
|
|
|
|
const input = (() => {
|
|
const _element = document.createElement('input');
|
|
_element.type = 'file';
|
|
_element.accept = 'image/jpeg,image/png';
|
|
_element.addEventListener('change', change);
|
|
return _element;
|
|
})();
|
|
|
|
async function change(e: Event) {
|
|
const _element = e.target as HTMLInputElement | null;
|
|
const _file = _element?.files?.[0];
|
|
|
|
if (!_file) return;
|
|
|
|
file.value = _file;
|
|
|
|
url.value = await new Promise<string>((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.readAsDataURL(_file);
|
|
reader.onload = () => {
|
|
if (typeof reader.result === 'string') {
|
|
return resolve(reader.result);
|
|
}
|
|
return reject();
|
|
};
|
|
});
|
|
|
|
if (!props.ocr) return;
|
|
|
|
if (props.data) metadata.value = structuredClone(props.data);
|
|
|
|
isOpen.value = true;
|
|
isRunning.value = true;
|
|
const ocrResult = await props.ocr(_file);
|
|
isRunning.value = false;
|
|
|
|
if (!ocrResult) return;
|
|
|
|
if (!props.override || !metadata.value) {
|
|
return (metadata.value = ocrResult);
|
|
}
|
|
|
|
if (Object.entries(metadata.value).some(([k, v]) => ocrResult[k] !== v)) {
|
|
return (metadata.value = props.override(metadata.value, ocrResult));
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<slot
|
|
name="trigger"
|
|
:browse="
|
|
() => {
|
|
file = undefined;
|
|
url = undefined;
|
|
metadata = undefined;
|
|
input?.click();
|
|
}
|
|
"
|
|
/>
|
|
|
|
<DialogForm
|
|
v-if="file && url"
|
|
v-model:modal="isOpen"
|
|
style="position: absolute"
|
|
height="100vh"
|
|
weight="90%"
|
|
hide-close-event
|
|
hide-delete
|
|
hide-btn
|
|
edit
|
|
:title
|
|
:is-edit
|
|
:readonly
|
|
:edit-data="() => (isEdit = true)"
|
|
:undo="() => (isEdit = false)"
|
|
:close="() => (isOpen = false)"
|
|
:submit="
|
|
() => {
|
|
if (!file) return;
|
|
$emit('submit', file, metadata);
|
|
if (autoSave) save?.(file, metadata);
|
|
isOpen = false;
|
|
}
|
|
"
|
|
>
|
|
<q-splitter class="full-height" v-model="splitRatio">
|
|
<template #before>
|
|
<div class="full-height">
|
|
<slot name="viewer" :url :file>
|
|
<ShowAttachment :url :file />
|
|
</slot>
|
|
</div>
|
|
</template>
|
|
<template #after>
|
|
<div class="q-pa-md full-height">
|
|
<slot name="body" :metadata :is-running :is-edit />
|
|
</div>
|
|
</template>
|
|
</q-splitter>
|
|
</DialogForm>
|
|
</template>
|