统一交互组件 #7
@@ -715,7 +715,7 @@ onMounted(loadAdminData)
|
||||
</NAlert>
|
||||
|
||||
<section v-if="loading" class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div v-for="n in 8" :key="n" class="border border-slate-200 bg-white p-5">
|
||||
<div v-for="n in 8" :key="n" class="oc-panel-card border border-slate-200 bg-white p-5">
|
||||
<NSkeleton class="h-4 w-1/2" />
|
||||
<NSkeleton class="mt-4 h-8 w-2/3" />
|
||||
<NSkeleton class="mt-3 h-3 w-full" />
|
||||
@@ -753,18 +753,18 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
</div>
|
||||
<NButton
|
||||
v-if="dashboardStats.pending > 0"
|
||||
type="default"
|
||||
secondary
|
||||
strong
|
||||
class="oc-panel-button oc-panel-button--amber shrink-0"
|
||||
:disabled="!dashboardStats.pending"
|
||||
@click="activeTab = 'review'"
|
||||
>
|
||||
进入审核队列
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 border border-slate-200 bg-white">
|
||||
<div class="oc-panel-card grid grid-cols-2 border border-slate-200 bg-white">
|
||||
<div class="border-b border-r border-slate-100 p-4">
|
||||
<p class="text-xs font-medium text-slate-500">今日上传</p>
|
||||
<p class="mt-2 text-2xl font-bold text-sky-800">{{ dashboardStats.todayUploads }}</p>
|
||||
@@ -785,7 +785,7 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
|
||||
<div class="grid gap-5 lg:grid-cols-[1.05fr_0.95fr]">
|
||||
<div class="border border-slate-200 bg-white p-5 sm:p-6">
|
||||
<div class="oc-panel-card border border-slate-200 bg-white p-5 sm:p-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="oc-section-label">Review Health</p>
|
||||
@@ -823,7 +823,7 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-slate-200 bg-white p-5 sm:p-6">
|
||||
<div class="oc-panel-card border border-slate-200 bg-white p-5 sm:p-6">
|
||||
<div>
|
||||
<p class="oc-section-label">Cloud Type Mix</p>
|
||||
<h3 class="mt-2 text-xl font-bold text-slate-950">云型分布</h3>
|
||||
@@ -849,15 +849,22 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-slate-200 bg-white">
|
||||
<div class="oc-panel-card border border-slate-200 bg-white">
|
||||
<div class="flex items-end justify-between gap-4 border-b border-slate-200 px-5 py-4">
|
||||
<div>
|
||||
<p class="oc-section-label">Latest Activity</p>
|
||||
<h3 class="mt-2 text-xl font-bold text-slate-950">最近上传</h3>
|
||||
</div>
|
||||
<button type="button" class="oc-text-button text-sm" @click="activeTab = 'images'">
|
||||
<NButton
|
||||
size="small"
|
||||
type="default"
|
||||
secondary
|
||||
strong
|
||||
class="oc-panel-button oc-panel-button--neutral"
|
||||
@click="activeTab = 'images'"
|
||||
>
|
||||
查看全部图片
|
||||
</button>
|
||||
</NButton>
|
||||
</div>
|
||||
<div v-if="recentImages.length" class="divide-y divide-slate-100">
|
||||
<button
|
||||
@@ -883,13 +890,20 @@ onMounted(loadAdminData)
|
||||
</section>
|
||||
|
||||
<section v-else-if="activeTab === 'review'">
|
||||
<div class="mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div class="oc-panel-card mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-slate-950">待审核队列</h2>
|
||||
<p class="oc-field-help">共 {{ pendingImages.length }} 张待处理图片。</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<NButton secondary strong type="default" class="oc-panel-button oc-panel-button--neutral" @click="toggleAllPendingSelection">
|
||||
<NButton
|
||||
v-if="pendingImages.length > 0"
|
||||
secondary
|
||||
strong
|
||||
type="default"
|
||||
class="oc-panel-button oc-panel-button--neutral"
|
||||
@click="toggleAllPendingSelection"
|
||||
>
|
||||
{{ selectedReviewCount === pendingImages.length && pendingImages.length ? '取消全选' : '全选待审核' }}
|
||||
</NButton>
|
||||
<template v-if="selectedReviewCount">
|
||||
@@ -918,7 +932,7 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
|
||||
<div v-if="activeReviewImage" class="grid min-w-0 gap-4 xl:grid-cols-[220px_minmax(0,1fr)]">
|
||||
<aside class="min-w-0 self-start border border-slate-200 bg-white xl:sticky xl:top-20 xl:max-h-[calc(100vh-6rem)]">
|
||||
<aside class="oc-panel-card min-w-0 self-start border border-slate-200 bg-white xl:sticky xl:top-20 xl:max-h-[calc(100vh-6rem)]">
|
||||
<div class="flex items-center justify-between border-b border-slate-200 px-4 py-3">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-slate-900">审核队列</p>
|
||||
@@ -965,7 +979,7 @@ onMounted(loadAdminData)
|
||||
|
||||
<div class="grid min-w-0 gap-4 lg:grid-cols-[minmax(0,1fr)_300px]">
|
||||
<div class="min-w-0 space-y-4">
|
||||
<div class="border border-slate-200 bg-white">
|
||||
<div class="oc-panel-card border border-slate-200 bg-white">
|
||||
<div class="flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-semibold text-slate-900">{{ activeReviewImage.cloudTypeName }}</p>
|
||||
@@ -995,7 +1009,7 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-slate-200 bg-white p-5">
|
||||
<div class="oc-panel-card border border-slate-200 bg-white p-5">
|
||||
<p class="oc-section-label">Uploader Notes</p>
|
||||
<h3 class="mt-2 text-lg font-bold text-slate-950">上传说明</h3>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
@@ -1004,7 +1018,7 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="self-start border border-slate-200 bg-white lg:sticky lg:top-20">
|
||||
<aside class="oc-panel-card self-start border border-slate-200 bg-white lg:sticky lg:top-20">
|
||||
<div class="border-b border-slate-200 bg-[linear-gradient(180deg,#fffbeb_0%,#fff_100%)] p-5">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-amber-700">Review Decision</p>
|
||||
<h3 class="mt-2 text-xl font-bold text-slate-950">审核资料</h3>
|
||||
@@ -1088,13 +1102,13 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="border border-dashed border-slate-300 bg-white p-10">
|
||||
<div v-else class="oc-panel-card border border-dashed border-slate-300 bg-white p-10">
|
||||
<NEmpty description="当前没有待审核图片" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-else-if="activeTab === 'users'">
|
||||
<div class="overflow-hidden border border-slate-200 bg-white">
|
||||
<div class="oc-panel-card overflow-hidden border border-slate-200 bg-white">
|
||||
<div class="grid grid-cols-[1.1fr_0.8fr_0.7fr_0.9fr_1.2fr] border-b border-slate-200 bg-slate-50 px-4 py-3 text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">
|
||||
<span>用户</span>
|
||||
<span>角色</span>
|
||||
@@ -1145,7 +1159,7 @@ onMounted(loadAdminData)
|
||||
</section>
|
||||
|
||||
<section v-else>
|
||||
<div class="mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div class="oc-panel-card mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-slate-950">图片管理</h2>
|
||||
<p class="oc-field-help">最近 {{ images.length }} 张图片,可调整审核状态、可见性或删除。</p>
|
||||
@@ -1160,7 +1174,14 @@ onMounted(loadAdminData)
|
||||
<option value="approved">已通过</option>
|
||||
<option value="rejected">已拒绝</option>
|
||||
</select>
|
||||
<NButton secondary strong type="default" class="oc-panel-button oc-panel-button--neutral" @click="toggleAllFilteredImageSelection">
|
||||
<NButton
|
||||
v-if="filteredImages.length > 0"
|
||||
secondary
|
||||
strong
|
||||
type="default"
|
||||
class="oc-panel-button oc-panel-button--neutral"
|
||||
@click="toggleAllFilteredImageSelection"
|
||||
>
|
||||
{{ selectedManagedCount === filteredImages.length && filteredImages.length ? '取消全选' : '全选当前列表' }}
|
||||
</NButton>
|
||||
<template v-if="selectedManagedCount">
|
||||
@@ -1219,7 +1240,7 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
|
||||
<div v-if="filteredImages.length" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
<article v-for="item in filteredImages" :key="item.id" class="overflow-hidden border border-slate-200 bg-white">
|
||||
<article v-for="item in filteredImages" :key="item.id" class="oc-panel-card overflow-hidden border border-slate-200 bg-white">
|
||||
<button type="button" class="oc-interactive-card block w-full overflow-hidden bg-slate-100" @click="selectedImage = item">
|
||||
<img :src="item.thumbnail_url || item.image_url" :alt="item.cloudTypeName" class="h-52 w-full object-cover transition-transform hover:scale-105" />
|
||||
</button>
|
||||
@@ -1263,7 +1284,7 @@ onMounted(loadAdminData)
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div v-else class="border border-dashed border-slate-300 bg-white p-10">
|
||||
<div v-else class="oc-panel-card border border-dashed border-slate-300 bg-white p-10">
|
||||
<NEmpty description="没有符合筛选条件的图片" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user