128 lines
3.4 KiB
Vue
128 lines
3.4 KiB
Vue
<template>
|
|
<div>
|
|
<div
|
|
class="relative-position"
|
|
:style="{
|
|
width: sizePx,
|
|
height: sizePx,
|
|
display: 'block'
|
|
}"
|
|
>
|
|
<q-file
|
|
ref="imgFileSelector"
|
|
v-model="imageFile"
|
|
:style="{ display: 'none' }"
|
|
@update:model-value="handleUpload()"
|
|
:filter="checkImgType"
|
|
accept="image/*"
|
|
/>
|
|
<q-icon
|
|
v-if="modelValue === '' || modelValue === undefined || modelValue === null"
|
|
name="mdi-camera-plus-outline"
|
|
class="absolute-full fit text-grey-4"
|
|
:style="{ fontSize: String(iconsize) + 'px'}"
|
|
@click = "imgFileSelectorClick"
|
|
/>
|
|
<q-img
|
|
v-else
|
|
fit="cover"
|
|
:src="modelValue"
|
|
:style="{
|
|
height: sizePx,
|
|
maxWidth: sizePx,
|
|
borderRadius: avatar ? String(size/2) + 'px' : 'var(--top-raduis)',
|
|
}"
|
|
@click="showDialog = true"
|
|
/>
|
|
</div>
|
|
|
|
<q-dialog v-model="showDialog">
|
|
<q-card class="w100 relative-position" style="height: auto;">
|
|
<q-img :src="modelValue"/>
|
|
<div
|
|
class="flex row items-center jutsify-center q-pb-sm"
|
|
style="bottom: 0; position: absolute; left: 50%; transform: translate(-50%, 0%);">
|
|
<q-btn
|
|
v-for="btn in menuBtns"
|
|
:key="btn.name"
|
|
:icon="btn.icon"
|
|
@click="btn.f"
|
|
class="q-mx-xs bg-white"
|
|
round flat
|
|
style="opacity: 0.8"
|
|
color="primary"
|
|
/>
|
|
</div>
|
|
</q-card>
|
|
</q-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, Ref, computed } from 'vue' // eslint-disable-line
|
|
import { QFile } from 'quasar'
|
|
|
|
const modelValue = defineModel<string>()
|
|
|
|
const props = defineProps<{
|
|
size?: number
|
|
iconsize?: number
|
|
avatar?: boolean
|
|
}>()
|
|
|
|
const imageFile = ref(null) // file-from selector
|
|
const imgFileSelector= ref() as Ref<QFile> // input file DOM
|
|
const size = ref<number>(props.size ? props.size : 100)
|
|
const iconsize = ref<number>(props.iconsize ? props.iconsize : 75)
|
|
const showDialog = ref<boolean>(false)
|
|
const menuBtns = [
|
|
{ name: 'change', icon: 'mdi-swap-horizontal', f: imgFileSelectorClick },
|
|
{ name: 'delete', icon: 'mdi-delete-outline', f: deleteImage },
|
|
{ name: 'close', icon: 'mdi-close', f: () => showDialog.value = false }
|
|
]
|
|
|
|
const sizePx = computed(() => {
|
|
return String(size.value) + 'px'
|
|
})
|
|
|
|
async function handleUpload () {
|
|
if (imageFile.value) {
|
|
const img = await imgToBase64(imageFile.value)
|
|
modelValue.value = typeof img === 'string' ? img : ''
|
|
}
|
|
}
|
|
|
|
function imgFileSelectorClick () {
|
|
imgFileSelector.value.pickFiles()
|
|
}
|
|
|
|
function deleteImage () {
|
|
showDialog.value = false
|
|
imageFile.value = null
|
|
modelValue.value = ''
|
|
}
|
|
|
|
function imgToBase64(file: File): Promise<string | ArrayBuffer | null> {
|
|
const reader: FileReader = new FileReader()
|
|
reader.readAsDataURL(file)
|
|
|
|
return new Promise((resolve, reject) => {
|
|
reader.onerror = () => {
|
|
reader.abort()
|
|
reject(new Error('Something went wrong'))
|
|
}
|
|
|
|
reader.onload = () => {
|
|
resolve(reader.result)
|
|
}
|
|
})
|
|
}
|
|
|
|
function checkImgType(files: File[]): File[] {
|
|
return files.filter((file: File) => file.type === 'image/x-png' || file.type === 'image/jpeg' || file.type === 'image/webp' )
|
|
}
|
|
</script>
|
|
|
|
<style scope>
|
|
</style>
|