Files
oci-portal-dash/src/components/FilePicker.vue
T
Wang Defa 5464d2dfea
CI / test (push) Successful in 27s
Release / release (push) Successful in 26s
发布 0.1.0:通知渠道、告警规则与多项体验修复
2026-07-10 17:31:19 +08:00

113 lines
3.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>