Files
guanwang/app/components/MediaPicker.vue

62 lines
2.7 KiB
Vue

<script setup lang="ts">
import { PhCheck, PhImageSquare, PhMagnifyingGlass, PhX } from '@phosphor-icons/vue'
interface MediaAsset {
id: number
originalName: string
url: string
thumbnailUrl: string
altText: string
authorizationStatus: 'pending' | 'authorized' | 'restricted'
}
const model = defineModel<string>({ required: true })
const props = withDefaults(defineProps<{ label?: string }>(), { label: '从素材库选择' })
const open = ref(false)
const loading = ref(false)
const query = ref('')
const items = ref<MediaAsset[]>([])
const filtered = computed(() => {
const keyword = query.value.trim().toLowerCase()
return keyword ? items.value.filter(item => `${item.originalName} ${item.altText}`.toLowerCase().includes(keyword)) : items.value
})
async function show() {
open.value = true
if (items.value.length) return
loading.value = true
try {
items.value = (await $fetch<{ data: MediaAsset[] }>('/api/admin/media', { query: { status: 'active' } })).data
} finally {
loading.value = false
}
}
function select(item: MediaAsset) {
model.value = item.url
open.value = false
}
</script>
<template>
<button type="button" class="media-picker-trigger" @click="show"><PhImageSquare :size="17" />{{ props.label }}</button>
<Teleport to="body">
<div v-if="open" class="media-picker-backdrop" role="presentation" @click.self="open = false">
<section class="media-picker-dialog" role="dialog" aria-modal="true" aria-labelledby="media-picker-title" @keydown.esc="open = false">
<header><div><small>MEDIA LIBRARY</small><h2 id="media-picker-title">选择素材</h2></div><button type="button" aria-label="关闭素材选择" @click="open = false"><PhX :size="22" /></button></header>
<label class="media-picker-search"><PhMagnifyingGlass :size="18" /><input v-model="query" autofocus placeholder="搜索文件名或 ALT 描述"></label>
<div v-if="loading" class="media-picker-empty">正在读取素材</div>
<div v-else-if="!filtered.length" class="media-picker-empty">素材库暂无可选图片</div>
<div v-else class="media-picker-grid">
<button v-for="item in filtered" :key="item.id" type="button" :class="{ selected: model === item.url }" @click="select(item)">
<img :src="item.thumbnailUrl" :alt="item.altText || item.originalName" loading="lazy">
<span>{{ item.altText || item.originalName }}</span>
<small>{{ item.authorizationStatus === 'authorized' ? '已授权' : item.authorizationStatus === 'restricted' ? '限制使用' : '待核验' }}</small>
<PhCheck v-if="model === item.url" :size="18" weight="bold" />
</button>
</div>
</section>
</div>
</Teleport>
</template>