初始提交:OCI 面板前端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:29 +08:00
commit db45a669e3
135 changed files with 25220 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
<script setup lang="ts">
import { useMessage } from 'naive-ui'
import { computed, ref } from 'vue'
/** 拖拽或点击上传的文本文件选择区;文件内容仅在浏览器本地读取。
* compact:单行紧凑形态,用于表单内与其他字段并排。 */
const props = defineProps<{ accept?: string; label?: string; hint?: string; compact?: boolean }>()
const emit = defineEmits<{ load: [text: string, name: string] }>()
const message = useMessage()
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>