feat: 初始化慧遇书院官网一期
This commit is contained in:
61
app/components/MediaPicker.vue
Normal file
61
app/components/MediaPicker.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user