feat: signature (#194)
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 8s
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 8s
* refactor: enable profile signature option in ProfileMenu * feat: add signature api function * refactor: add new translation keys for 'Draw' and 'New Upload' in English and Thai * refactor: update image URL variable and improve translation keys in CanvasComponent and MainLayout * refactor: get function * feat: add delete signature function * feat: add canvas manipulation functions and integrate signature submission in MainLayout (unfinished) * chore(deps): update --------- Co-authored-by: puriphatt <puriphat@frappet.com> Co-authored-by: Methapon2001 <61303214+Methapon2001@users.noreply.github.com>
This commit is contained in:
parent
3646956038
commit
0e685a99f7
9 changed files with 378 additions and 149 deletions
|
|
@ -4,7 +4,7 @@ import { useQuasar } from 'quasar';
|
|||
import SignaturePad from 'signature_pad';
|
||||
import Cropper from 'cropperjs';
|
||||
|
||||
defineExpose({ clearCanvas, clearUpload });
|
||||
defineExpose({ setCanvas, getCanvas, clearCanvas, clearUpload });
|
||||
|
||||
const $q = useQuasar();
|
||||
const isDarkActive = computed(() => $q.dark.isActive);
|
||||
|
|
@ -18,7 +18,7 @@ const cropper = ref();
|
|||
|
||||
const tab = ref('draw');
|
||||
const uploadFile = ref<File | undefined>(undefined);
|
||||
const profileUrl = ref<string | null>('');
|
||||
const imgUrl = ref<string | null>('');
|
||||
const inputFile = (() => {
|
||||
const element = document.createElement('input');
|
||||
element.type = 'file';
|
||||
|
|
@ -26,7 +26,7 @@ const inputFile = (() => {
|
|||
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => {
|
||||
if (typeof reader.result === 'string') profileUrl.value = reader.result;
|
||||
if (typeof reader.result === 'string') imgUrl.value = reader.result;
|
||||
});
|
||||
|
||||
element.addEventListener('change', () => {
|
||||
|
|
@ -39,12 +39,11 @@ const inputFile = (() => {
|
|||
return element;
|
||||
})();
|
||||
|
||||
async function initializeSignaturePad(canva?: HTMLCanvasElement) {
|
||||
if (canva) {
|
||||
signaturePad.value = new SignaturePad(canva, {
|
||||
backgroundColor: isDarkActive.value
|
||||
? 'rgb(21,25,29)'
|
||||
: 'rgb(248,249,250)',
|
||||
async function initializeSignaturePad() {
|
||||
const canvas = canvasRef.value;
|
||||
|
||||
if (canvas) {
|
||||
signaturePad.value = new SignaturePad(canvas, {
|
||||
penColor: 'blue',
|
||||
});
|
||||
} else {
|
||||
|
|
@ -77,34 +76,41 @@ function changeColor(color: string) {
|
|||
currentColor.value = color;
|
||||
}
|
||||
|
||||
function setCanvas() {
|
||||
const data = signaturePad.value.toDataURL('image/png');
|
||||
return data;
|
||||
}
|
||||
|
||||
function getCanvas(signature: string) {
|
||||
signaturePad.value.fromDataURL(signature);
|
||||
}
|
||||
|
||||
function clearCanvas() {
|
||||
signaturePad.value.clear();
|
||||
}
|
||||
|
||||
function clearUpload() {
|
||||
profileUrl.value = '';
|
||||
imgUrl.value = '';
|
||||
}
|
||||
|
||||
watch(
|
||||
() => tab.value,
|
||||
async () => {
|
||||
await initializeSignaturePad(canvasRef.value);
|
||||
await initializeCropper(imageRef.value);
|
||||
await initializeSignaturePad();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await initializeSignaturePad(canvasRef.value);
|
||||
await initializeCropper(imageRef.value);
|
||||
await initializeSignaturePad();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="surface-1 bordered rounded full-width">
|
||||
<div class="surface-1 column full-width full-height">
|
||||
<q-tabs
|
||||
v-model="tab"
|
||||
dense
|
||||
align="left"
|
||||
class="text-grey"
|
||||
class="text-grey surface-2"
|
||||
active-color="primary"
|
||||
indicator-color="primary"
|
||||
>
|
||||
|
|
@ -112,18 +118,18 @@ onMounted(async () => {
|
|||
<div class="row">
|
||||
<q-tab
|
||||
name="draw"
|
||||
label="Draw"
|
||||
:label="$t('general.draw')"
|
||||
style="border-top-left-radius: var(--radius-2)"
|
||||
/>
|
||||
<q-tab name="upload" label="Upload" />
|
||||
<q-tab name="upload" :label="$t('general.upload')" />
|
||||
</div>
|
||||
|
||||
<div class="q-pr-md">
|
||||
<q-btn
|
||||
v-if="tab === 'upload'"
|
||||
dense
|
||||
flat
|
||||
v-if="tab === 'upload'"
|
||||
:label="$t('newUpload')"
|
||||
:label="$t('general.newUpload')"
|
||||
color="info"
|
||||
@click="inputFile.click()"
|
||||
/>
|
||||
|
|
@ -132,89 +138,66 @@ onMounted(async () => {
|
|||
</q-tabs>
|
||||
<q-separator />
|
||||
|
||||
<div v-show="tab === 'draw'" class="q-pa-md">
|
||||
<section v-show="tab === 'draw'" class="q-pa-md col">
|
||||
<div class="column relative-position">
|
||||
<div class="absolute-top-right q-ma-md q-gutter-x-md row items-center">
|
||||
<article
|
||||
class="absolute-top-right q-ma-md q-gutter-x-md row items-center"
|
||||
>
|
||||
<span
|
||||
v-for="color in ['black', 'red', 'blue']"
|
||||
:key="color"
|
||||
:class="{ active: currentColor === color }"
|
||||
class="dot"
|
||||
:class="{ active: currentColor === 'black' }"
|
||||
style="background-color: black"
|
||||
@click="changeColor('black')"
|
||||
:style="`background-color: ${color}`"
|
||||
@click="changeColor(color)"
|
||||
>
|
||||
<q-icon
|
||||
v-if="currentColor === 'black'"
|
||||
v-if="currentColor === color"
|
||||
name="mdi-check"
|
||||
color="white"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
:class="{ active: currentColor === 'red' }"
|
||||
class="dot"
|
||||
style="background-color: red"
|
||||
@click="changeColor('red')"
|
||||
>
|
||||
<q-icon
|
||||
v-if="currentColor === 'red'"
|
||||
name="mdi-check"
|
||||
color="white"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
:class="{ active: currentColor === 'blue' }"
|
||||
class="dot"
|
||||
style="background-color: blue"
|
||||
@click="changeColor('blue')"
|
||||
>
|
||||
<q-icon
|
||||
v-if="currentColor === 'blue'"
|
||||
name="mdi-check"
|
||||
color="white"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<canvas
|
||||
class="signature-canvas"
|
||||
ref="canvasRef"
|
||||
id="signature-pad"
|
||||
width="700"
|
||||
height="310"
|
||||
width="766"
|
||||
height="364"
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-show="tab === 'upload'" class="q-pa-md">
|
||||
<section v-show="tab === 'upload'" class="q-pa-md col">
|
||||
<div
|
||||
class="bordered upload-border rounded column items-center justify-center"
|
||||
style="height: 312px"
|
||||
class="bordered upload-border rounded column items-center justify-center full-height"
|
||||
>
|
||||
<q-img
|
||||
v-show="profileUrl"
|
||||
v-show="imgUrl"
|
||||
ref="imageRef"
|
||||
:src="profileUrl ?? ''"
|
||||
:src="imgUrl ?? ''"
|
||||
style="object-fit: cover; width: 100%; height: 100%"
|
||||
/>
|
||||
<div v-if="!profileUrl">
|
||||
<div v-if="!imgUrl">
|
||||
<q-icon
|
||||
name="mdi-cloud-upload"
|
||||
size="10rem"
|
||||
style="color: hsla(var(--text-mute) / 0.2)"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<q-btn
|
||||
unelevated
|
||||
color="info"
|
||||
:label="$t('uploadFile')"
|
||||
:label="$t('general.upload')"
|
||||
icon="mdi-plus"
|
||||
@click="inputFile.click()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue