113 lines
3.9 KiB
Vue
113 lines
3.9 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { useToast } from '@/composables/useToast'
|
||
|
||
/** 拖拽或点击上传的文本文件选择区;文件内容仅在浏览器本地读取。
|
||
* compact:单行紧凑形态,用于表单内与其他字段并排。 */
|
||
const props = defineProps<{ accept?: string; label?: string; hint?: string; compact?: boolean }>()
|
||
const emit = defineEmits<{ load: [text: string, name: string] }>()
|
||
|
||
const message = useToast()
|
||
const inputEl = ref<HTMLInputElement>()
|
||
const fileName = ref('')
|
||
const dragging = ref(false)
|
||
|
||
const acceptExts = computed(
|
||
() =>
|
||
props.accept
|
||
?.split(',')
|
||
.map((s) => s.trim().toLowerCase())
|
||
.filter((s) => s.startsWith('.')) ?? [],
|
||
)
|
||
|
||
function extOk(name: string): boolean {
|
||
if (!acceptExts.value.length) return true
|
||
const lower = name.toLowerCase()
|
||
return acceptExts.value.some((ext) => lower.endsWith(ext))
|
||
}
|
||
|
||
async function take(file: File | undefined) {
|
||
if (!file) return
|
||
if (!extOk(file.name)) {
|
||
message.error(`仅支持 ${acceptExts.value.join(' / ')} 文件`)
|
||
return
|
||
}
|
||
if (file.size > 1024 * 1024) {
|
||
message.error('文件超过 1 MB,请确认选择的是文本文件')
|
||
return
|
||
}
|
||
fileName.value = file.name
|
||
emit('load', await file.text(), file.name)
|
||
}
|
||
|
||
function onChange(e: Event) {
|
||
const input = e.target as HTMLInputElement
|
||
void take(input.files?.[0])
|
||
input.value = ''
|
||
}
|
||
|
||
function onDrop(e: DragEvent) {
|
||
dragging.value = false
|
||
void take(e.dataTransfer?.files?.[0])
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
v-if="compact"
|
||
class="flex h-[34px] cursor-pointer items-center justify-center gap-2 rounded-md border border-dashed px-3 transition-colors"
|
||
:class="dragging ? 'border-accent bg-accent/5' : 'border-line hover:border-accent'"
|
||
:title="fileName ? '已读取,点击或拖拽可重新选择' : hint"
|
||
@click="inputEl?.click()"
|
||
@dragover.prevent="dragging = true"
|
||
@dragleave="dragging = false"
|
||
@drop.prevent="onDrop"
|
||
>
|
||
<svg
|
||
class="h-3.5 w-3.5 flex-none text-ink-3"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
>
|
||
<path d="M12 16V4m0 0 4 4m-4-4-4 4" stroke-linecap="round" stroke-linejoin="round" />
|
||
<path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" stroke-linecap="round" />
|
||
</svg>
|
||
<span
|
||
class="truncate text-[13px]"
|
||
:class="fileName ? 'mono font-medium text-ink' : 'text-ink-2'"
|
||
>
|
||
{{ fileName || label || '上传文件' }}
|
||
</span>
|
||
<input ref="inputEl" type="file" class="hidden" :accept="accept" @change="onChange" />
|
||
</div>
|
||
|
||
<div
|
||
v-else
|
||
class="cursor-pointer rounded-lg border border-dashed px-5 py-5 text-center transition-colors"
|
||
:class="dragging ? 'border-accent bg-accent/5' : 'border-line hover:border-accent'"
|
||
@click="inputEl?.click()"
|
||
@dragover.prevent="dragging = true"
|
||
@dragleave="dragging = false"
|
||
@drop.prevent="onDrop"
|
||
>
|
||
<div
|
||
class="mx-auto flex h-9 w-9 items-center justify-center rounded-md border border-line bg-card text-ink-2"
|
||
>
|
||
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||
<path d="M12 16V4m0 0 4 4m-4-4-4 4" stroke-linecap="round" stroke-linejoin="round" />
|
||
<path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" stroke-linecap="round" />
|
||
</svg>
|
||
</div>
|
||
<div v-if="fileName" class="mt-2.5">
|
||
<div class="mono truncate text-[13px] font-medium text-ink">{{ fileName }}</div>
|
||
<div class="mt-1 text-xs text-ink-3">已读取,点击或拖拽可重新选择</div>
|
||
</div>
|
||
<div v-else class="mt-2.5">
|
||
<div class="text-[13px] font-medium text-ink-2">{{ label ?? '拖拽或点击上传文件' }}</div>
|
||
<div v-if="hint" class="mt-1 text-xs text-ink-3">{{ hint }}</div>
|
||
</div>
|
||
<input ref="inputEl" type="file" class="hidden" :accept="accept" @change="onChange" />
|
||
</div>
|
||
</template>
|