199 lines
5.4 KiB
Vue
199 lines
5.4 KiB
Vue
<script setup lang="ts">
|
|
import type { PropType } from 'vue'
|
|
|
|
const props = defineProps({
|
|
value: {
|
|
type: Object as PropType<File | null>,
|
|
default: null,
|
|
},
|
|
text: {
|
|
type: String,
|
|
default: '选择图片进行图生图',
|
|
},
|
|
textOnSelect: {
|
|
type: String,
|
|
},
|
|
})
|
|
const emit = defineEmits(['update'])
|
|
|
|
const fileInput = ref<HTMLInputElement | null>(null)
|
|
|
|
const selected_file = ref<File | null>(null)
|
|
const image_dataurl = ref('')
|
|
const loading = ref(false)
|
|
|
|
watch(
|
|
() => props.value,
|
|
async (newVal) => {
|
|
handleFileInput({ target: { files: [newVal!] } })
|
|
}
|
|
)
|
|
|
|
const handleTrashClick = () => {
|
|
fileInput.value!.value = ''
|
|
selected_file.value = null
|
|
image_dataurl.value = ''
|
|
emit('update', null)
|
|
}
|
|
|
|
const handleFileInput = (event: { target: any }) => {
|
|
if (event.target.files) {
|
|
const file = event.target.files[0]
|
|
if (!file) return
|
|
selected_file.value = file
|
|
loading.value = true
|
|
const reader = new FileReader()
|
|
reader.onload = (e) => {
|
|
image_dataurl.value = e.target?.result as string
|
|
loading.value = false
|
|
}
|
|
reader.onerror = (e) => {
|
|
loading.value = false
|
|
}
|
|
reader.readAsDataURL(selected_file.value!)
|
|
emit('update', selected_file.value)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="w-full bg-neutral-200/50 dark:bg-neutral-700/50 rounded-md flex justify-between items-center p-1.5 gap-2 relative hover:bg-neutral-200/80 dark:hover:bg-neutral-700/80 transition border dark:border-neutral-700 cursor-pointer"
|
|
:class="{ 'cursor-pointer': !loading, 'cursor-not-allowed': loading }"
|
|
@click="() => !loading && fileInput?.click()"
|
|
>
|
|
<input
|
|
ref="fileInput"
|
|
type="file"
|
|
class="hidden"
|
|
@change="handleFileInput"
|
|
accept="image/*"
|
|
/>
|
|
<Transition
|
|
name="trash-btn"
|
|
mode="out-in"
|
|
>
|
|
<button
|
|
type="button"
|
|
@click.stop.prevent="handleTrashClick"
|
|
v-if="!!selected_file"
|
|
class="absolute -top-1 -right-1 bg-white dark:bg-black rounded-full p-1 shadow-lg border dark:border-neutral-700"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="14"
|
|
height="14"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</Transition>
|
|
<div class="w-12 h-12 rounded-md overflow-hidden">
|
|
<Transition
|
|
name="preview-swap"
|
|
mode="out-in"
|
|
>
|
|
<div
|
|
v-if="loading"
|
|
class="w-full h-full flex justify-center items-center rounded-md border-2 border-dashed border-neutral-400 dark:border-neutral-600 text-neutral-400 dark:text-neutral-600"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
fill="currentColor"
|
|
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
|
opacity=".25"
|
|
/>
|
|
<path
|
|
fill="currentColor"
|
|
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
|
>
|
|
<animateTransform
|
|
attributeName="transform"
|
|
dur="0.75s"
|
|
repeatCount="indefinite"
|
|
type="rotate"
|
|
values="0 12 12;360 12 12"
|
|
/>
|
|
</path>
|
|
</svg>
|
|
</div>
|
|
<div
|
|
v-else-if="!selected_file"
|
|
class="w-full h-full flex justify-center items-center rounded-md border-2 border-dashed border-neutral-400 dark:border-neutral-600 text-neutral-400 dark:text-neutral-600"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 5v14m-7-7h14"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<img
|
|
v-else
|
|
class="w-12 h-12 rounded-md object-cover"
|
|
:src="image_dataurl"
|
|
:key="selected_file.name"
|
|
alt="Preview"
|
|
/>
|
|
</Transition>
|
|
</div>
|
|
<div class="flex-1 flex justify-center">
|
|
<p
|
|
class="text-neutral-400/80 dark:text-neutral-500 text-sm font-medium select-none text-center"
|
|
>
|
|
{{ selected_file ? textOnSelect : text }}
|
|
<span
|
|
v-if="selected_file && textOnSelect"
|
|
class="block text-[10px] text-center"
|
|
>
|
|
{{ selected_file?.name || textOnSelect }}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.trash-btn-enter-active,
|
|
.trash-btn-leave-active {
|
|
@apply transition duration-200;
|
|
}
|
|
|
|
.trash-btn-enter-from,
|
|
.trash-btn-leave-to {
|
|
@apply opacity-0 scale-75;
|
|
}
|
|
|
|
.preview-swap-enter-active,
|
|
.preview-swap-leave-active {
|
|
@apply transition duration-200;
|
|
}
|
|
|
|
.preview-swap-enter-from,
|
|
.preview-swap-leave-to {
|
|
@apply blur-sm;
|
|
}
|
|
</style>
|