main
vue 144 lines 3.09 KB
Raw
1 <template>
2 <div class="image-cropper">
3 <slot :open-cropper />
4
5 <n-modal v-model:show="showCropper">
6 <n-card class="image-cropper-modal flex flex-col" content-class="p-5!">
7 <div class="aspect-square">
8 <div v-if="!img" class="upload-box">
9 <n-upload v-show="!img" accept="image/*" :show-file-list="false" @change="setImage">
10 <n-upload-dragger>
11 <div>{{ placeholder }}</div>
12 </n-upload-dragger>
13 </n-upload>
14 </div>
15 <div v-if="img" class="crop-box">
16 <Cropper
17 ref="cropper"
18 class="cropper aspect-square"
19 :src="img"
20 :stencil-size
21 :stencil-props
22 :resize-image
23 image-restriction="stencil"
24 :stencil-component="stencil"
25 />
26 </div>
27 </div>
28 <div class="mt-4 flex justify-end gap-4">
29 <n-button secondary @click="closeCropper()">Close</n-button>
30 <n-button v-if="img" type="primary" @click="emitCrop()">Save</n-button>
31 </div>
32 </n-card>
33 </n-modal>
34 </div>
35 </template>
36
37 <script lang="ts" setup>
38 import type { UploadSettledFileInfo } from "naive-ui"
39 import type { CropperResult } from "vue-advanced-cropper"
40 import { NButton, NCard, NModal, NUpload, NUploadDragger } from "naive-ui"
41 import { computed, ref } from "vue"
42 import { CircleStencil, Cropper, RectangleStencil } from "vue-advanced-cropper"
43 import "vue-advanced-cropper/dist/style.css"
44
45 export type ImageCropperResult = CropperResult
46
47 const { placeholder = "Select an image", shape = "square" } = defineProps<{
48 placeholder?: string
49 shape?: "square" | "circle"
50 }>()
51
52 const emit = defineEmits<{
53 (e: "crop", value: ImageCropperResult): void
54 }>()
55
56 const stencil = computed(() => (shape === "circle" ? CircleStencil : RectangleStencil))
57 const img = ref("")
58 const showCropper = ref(false)
59 const cropper = ref<typeof Cropper | null>(null)
60
61 const stencilSize = ref({
62 width: 300,
63 height: 300
64 })
65 const stencilProps = ref({
66 handlers: {},
67 movable: false,
68 scalable: false,
69 aspectRatio: 1
70 })
71 const resizeImage = ref({
72 adjustStencil: false
73 })
74
75 function closeCropper() {
76 showCropper.value = false
77 img.value = ""
78 }
79 function openCropper() {
80 showCropper.value = true
81 img.value = ""
82 }
83
84 function setImage(data: {
85 file: UploadSettledFileInfo
86 fileList: UploadSettledFileInfo[]
87 event: ProgressEvent | Event | undefined
88 }): void {
89 if (data?.file?.file) {
90 const reader = new FileReader()
91 reader.readAsDataURL(data.file.file)
92 reader.onload = () => {
93 img.value = reader.result?.toString() || ""
94 }
95 }
96 }
97
98 function emitCrop() {
99 if (cropper.value) {
100 const result = cropper.value.getResult()
101 emit("crop", result)
102 }
103 closeCropper()
104 }
105 </script>
106
107 <style lang="scss" scoped>
108 .image-cropper-modal {
109 width: 90vw;
110 max-width: 300px;
111
112 .upload-box {
113 height: 100%;
114 width: 100%;
115
116 .n-upload {
117 height: 100%;
118 width: 100%;
119
120 :deep() {
121 .n-upload-trigger,
122 .n-upload-dragger {
123 height: 100%;
124 width: 100%;
125 display: flex;
126 justify-content: center;
127 align-items: center;
128 }
129 }
130 }
131 }
132
133 .crop-box {
134 overflow: hidden;
135 border-radius: var(--border-radius-small);
136
137 :deep() {
138 .vue-line-wrapper {
139 display: none;
140 }
141 }
142 }
143 }
144 </style>