feat: added a profile settings and settings nav (#432)
This commit is contained in:
parent
c8a7e6e7e7
commit
613c5315b3
23 changed files with 698 additions and 7 deletions
109
components/common/CommonCropImage.vue
Normal file
109
components/common/CommonCropImage.vue
Normal file
|
@ -0,0 +1,109 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Boundaries } from 'vue-advanced-cropper'
|
||||
import { Cropper } from 'vue-advanced-cropper'
|
||||
import 'vue-advanced-cropper/dist/style.css'
|
||||
|
||||
export interface Props {
|
||||
/** Images to be cropped */
|
||||
modelValue?: File
|
||||
/** Crop frame aspect ratio (width/height), default 1/1 */
|
||||
stencilAspectRatio?: number
|
||||
/** The ratio of the longest edge of the cut box to the length of the cut screen, default 0.9, not more than 1 */
|
||||
stencilSizePercentage?: number
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
stencilAspectRatio: 1 / 1,
|
||||
stencilSizePercentage: 0.9,
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'update:modelValue', value: File): void
|
||||
}>()
|
||||
|
||||
const vmFile = useVModel(props, 'modelValue', emits, { passive: true })
|
||||
|
||||
const cropperDialog = ref(false)
|
||||
|
||||
const cropper = ref<InstanceType<typeof Cropper>>()
|
||||
|
||||
const cropperFlag = ref(false)
|
||||
|
||||
const cropperImage = reactive({
|
||||
src: '',
|
||||
type: 'image/jpg',
|
||||
})
|
||||
|
||||
const stencilSize = ({ boundaries }: { boundaries: Boundaries }) => {
|
||||
return {
|
||||
width: boundaries.width * props.stencilSizePercentage,
|
||||
height: boundaries.height * props.stencilSizePercentage,
|
||||
}
|
||||
}
|
||||
|
||||
watch(vmFile, (file, _, onCleanup) => {
|
||||
let expired = false
|
||||
onCleanup(() => expired = true)
|
||||
|
||||
if (file && !cropperFlag.value) {
|
||||
cropperDialog.value = true
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = (e) => {
|
||||
if (expired)
|
||||
return
|
||||
cropperImage.src = e.target?.result as string
|
||||
cropperImage.type = file.type
|
||||
}
|
||||
}
|
||||
cropperFlag.value = false
|
||||
})
|
||||
|
||||
const cropImage = () => {
|
||||
if (cropper.value && vmFile.value) {
|
||||
cropperFlag.value = true
|
||||
cropperDialog.value = false
|
||||
const { canvas } = cropper.value.getResult()
|
||||
canvas?.toBlob((blob) => {
|
||||
vmFile.value = new File([blob as any], `cropped${vmFile.value?.name}` as string, { type: blob?.type })
|
||||
}, cropperImage.type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalDialog v-model="cropperDialog" :use-v-if="false" max-w-500px flex>
|
||||
<div flex-1 w-0>
|
||||
<div text-lg text-center my2 px3>
|
||||
<h1>
|
||||
{{ $t('action.edit') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div aspect-ratio-1>
|
||||
<Cropper
|
||||
ref="cropper"
|
||||
class="overflow-hidden w-full h-full"
|
||||
:src="cropperImage.src"
|
||||
:resize-image="{
|
||||
adjustStencil: false,
|
||||
}"
|
||||
:stencil-size="stencilSize"
|
||||
:stencil-props="{
|
||||
aspectRatio: props.stencilAspectRatio,
|
||||
movable: false,
|
||||
resizable: false,
|
||||
handlers: {},
|
||||
}"
|
||||
image-restriction="stencil"
|
||||
/>
|
||||
</div>
|
||||
<div m-4>
|
||||
<button
|
||||
btn-solid w-full rounded text-sm
|
||||
@click="cropImage()"
|
||||
>
|
||||
{{ $t('action.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
</template>
|
Loading…
Add table
Add a link
Reference in a new issue