feat: 图片广场优化

This commit is contained in:
Gsh
2026-01-03 22:07:20 +08:00
parent 922596c128
commit cc1bc6dd82
4 changed files with 150 additions and 32 deletions

View File

@@ -13,11 +13,12 @@ import {
Search,
User,
WarningFilled,
ZoomIn,
} from '@element-plus/icons-vue';
import { useClipboard } from '@vueuse/core';
import { format } from 'date-fns';
import { ElMessage } from 'element-plus';
import { computed, reactive, ref, watch } from 'vue';
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { getImagePlaza } from '@/api/aiImage';
import TaskCard from './TaskCard.vue';
@@ -31,6 +32,22 @@ const noMore = ref(false);
const dialogVisible = ref(false);
const currentTask = ref<TaskItem | null>(null);
// Mobile detection
const isMobile = ref(false);
function checkMobile() {
isMobile.value = window.innerWidth < 768;
}
onMounted(() => {
checkMobile();
window.addEventListener('resize', checkMobile);
});
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
});
// Mobile filter drawer
const showMobileFilter = ref(false);
@@ -424,12 +441,13 @@ watch(dateRange, () => {
<el-dialog
v-model="dialogVisible"
title="图片详情"
width="900px"
:width="isMobile ? '95%' : '900px'"
:fullscreen="isMobile"
append-to-body
class="image-detail-dialog"
align-center
>
<div v-if="currentTask" class="flex flex-col md:flex-row gap-6 h-[600px]">
<div v-if="currentTask" class="flex flex-col md:flex-row gap-4 md:gap-6 h-auto md:h-[600px]">
<!-- Left Image -->
<div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative group">
<el-image
@@ -472,7 +490,8 @@ watch(dateRange, () => {
</div>
<!-- Download Button Overlay -->
<div v-if="currentTask.storeUrl" class="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity z-10">
<div v-if="currentTask.storeUrl" class="absolute bottom-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
<el-button circle type="primary" :icon="ZoomIn" @click="handlePreview(currentTask.storeUrl)" />
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
</div>
</div>
@@ -497,6 +516,36 @@ watch(dateRange, () => {
</div>
<div class="mt-auto space-y-3 pt-4 border-t border-gray-100 shrink-0">
<!-- 分类标签 -->
<div v-if="currentTask.categories && currentTask.categories.length > 0" class="space-y-2">
<div class="flex items-center gap-2 text-sm text-gray-500">
<el-icon><CollectionTag /></el-icon>
<span>分类标签</span>
</div>
<div class="flex flex-wrap gap-1">
<el-tag
v-for="tag in currentTask.categories"
:key="tag"
size="small"
type="info"
effect="plain"
>
{{ tag }}
</el-tag>
</div>
</div>
<!-- 用户信息 -->
<div class="flex justify-between text-sm">
<span class="text-gray-500">创建者</span>
<span v-if="!currentTask.isAnonymous && currentTask.userName" class="text-blue-500 flex items-center gap-1">
<el-icon><User /></el-icon> {{ currentTask.userName }}
</span>
<span v-else class="text-gray-400 flex items-center gap-1">
<el-icon><User /></el-icon> 匿名用户
</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-500">创建时间</span>
<span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span>

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { InputInstance } from 'element-plus';
import type { TaskItem, TaskListRequest } from '@/api/aiImage/types';
import { Check, CircleCloseFilled, CopyDocument, Download, Filter, Loading, MagicStick, Picture, Refresh, Search, Share, WarningFilled } from '@element-plus/icons-vue';
import { Check, CircleCloseFilled, CollectionTag, CopyDocument, Download, Filter, Loading, MagicStick, Picture, Refresh, Search, Share, User, WarningFilled, ZoomIn } from '@element-plus/icons-vue';
import { useClipboard } from '@vueuse/core';
import { format } from 'date-fns';
import { ElMessage } from 'element-plus';
import { computed, nextTick, reactive, ref, watch } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { getMyTasks, publishImage } from '@/api/aiImage';
import TaskCard from './TaskCard.vue';
@@ -19,6 +19,22 @@ const noMore = ref(false);
const dialogVisible = ref(false);
const currentTask = ref<TaskItem | null>(null);
// Mobile detection
const isMobile = ref(false);
function checkMobile() {
isMobile.value = window.innerWidth < 768;
}
onMounted(() => {
checkMobile();
window.addEventListener('resize', checkMobile);
});
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
});
// Mobile filter drawer
const showMobileFilter = ref(false);
@@ -491,12 +507,13 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
<el-dialog
v-model="dialogVisible"
title="图片详情"
width="900px"
:width="isMobile ? '95%' : '900px'"
:fullscreen="isMobile"
append-to-body
class="image-detail-dialog"
align-center
>
<div v-if="currentTask" class="flex flex-col md:flex-row gap-6 h-[600px]">
<div v-if="currentTask" class="flex flex-col md:flex-row gap-4 md:gap-6 h-auto md:h-[600px]">
<!-- Left Image -->
<div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative group">
<el-image
@@ -539,7 +556,8 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
</div>
<!-- Download Button Overlay -->
<div v-if="currentTask.storeUrl" class="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity z-10">
<div v-if="currentTask.storeUrl" class="absolute bottom-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
<el-button circle type="primary" :icon="ZoomIn" @click="handlePreview(currentTask.storeUrl)" />
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
</div>
</div>
@@ -564,28 +582,60 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
</div>
<div class="mt-auto space-y-3 pt-4 border-t border-gray-100 shrink-0">
<!-- 分类标签 -->
<div v-if="currentTask.categories && currentTask.categories.length > 0" class="space-y-2">
<div class="flex items-center gap-2 text-sm text-gray-500">
<el-icon><CollectionTag /></el-icon>
<span>分类标签</span>
</div>
<div class="flex flex-wrap gap-1">
<el-tag
v-for="tag in currentTask.categories"
:key="tag"
size="small"
type="info"
effect="plain"
>
{{ tag }}
</el-tag>
</div>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-500">创建时间</span>
<span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span>
</div>
<div class="flex justify-between text-sm items-center">
<span class="text-gray-500">状态</span>
<div class="flex gap-2">
<el-tag v-if="currentTask.taskStatus === 'Success'" size="small" type="success">
成功
</el-tag>
<el-tag v-else-if="currentTask.taskStatus === 'Processing'" size="small" type="primary">
进行中
</el-tag>
<el-tag v-else size="small" type="danger">
失败
</el-tag>
<el-tag v-if="currentTask.publishStatus === 'Published'" size="small" type="warning" effect="dark">
已发布
</el-tag>
<div class="flex justify-between text-sm items-center">
<span class="text-gray-500">任务状态</span>
<el-tag v-if="currentTask.taskStatus === 'Success'" size="small" type="success">
成功
</el-tag>
<el-tag v-else-if="currentTask.taskStatus === 'Processing'" size="small" type="primary">
进行中
</el-tag>
<el-tag v-else size="small" type="danger">
失败
</el-tag>
</div>
<!-- 发布状态 -->
<div v-if="currentTask.publishStatus === 'Published'" class="flex justify-between text-sm items-center">
<span class="text-gray-500">发布状态</span>
<div class="flex items-center gap-1">
<el-tag size="small" type="warning" effect="dark">已发布</el-tag>
<span v-if="!currentTask.isAnonymous && currentTask.userName" class="text-blue-500 text-xs flex items-center gap-1">
<el-icon><User /></el-icon> {{ currentTask.userName }}
</span>
<span v-else class="text-gray-400 text-xs flex items-center gap-1">
<el-icon><User /></el-icon> 匿名
</span>
</div>
</div>
<div v-else class="flex justify-between text-sm items-center">
<span class="text-gray-500">发布状态</span>
<el-tag size="small" type="info" effect="plain">未发布</el-tag>
</div>
<div v-if="currentTask.taskStatus === 'Success'" class="pt-2 space-y-2">
<div class="grid grid-cols-1 gap-1">

View File

@@ -103,8 +103,8 @@ async function handleDownload() {
</div>
</div>
<!-- Overlay (Hover) -->
<div class="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col items-center justify-center gap-3 z-20 backdrop-blur-[2px]">
<!-- Overlay (Hover) - Desktop -->
<div class="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 hidden md:flex flex-col items-center justify-center gap-3 z-20 backdrop-blur-[2px]">
<el-button type="primary" round size="small" class="transform scale-90 group-hover:scale-100 transition-transform shadow-lg" @click.stop="$emit('click')">
查看详情
</el-button>
@@ -128,6 +128,31 @@ async function handleDownload() {
</div>
</div>
<!-- Mobile Actions Bar -->
<div v-if="task.taskStatus === 'Success'" class="absolute bottom-0 left-0 right-0 md:hidden bg-gradient-to-t from-black/70 to-transparent p-2 z-20">
<div class="flex items-center justify-between gap-2">
<el-button
type="primary"
size="small"
round
class="flex-1"
@click.stop="$emit('click')"
>
查看详情
</el-button>
<el-button circle size="small" :icon="ZoomIn" @click.stop="handlePreview" />
<el-button circle size="small" :icon="Download" @click.stop="handleDownload" />
<el-button
v-if="showPublishStatus && task.publishStatus === 'Unpublished'"
circle
size="small"
type="success"
:icon="Share"
@click.stop="$emit('publish', task)"
/>
</div>
</div>
<!-- Status Tag -->
<div v-if="showPublishStatus && task.publishStatus === 'Published'" class="absolute top-2 right-2 z-20">
<el-tag type="success" effect="dark" size="small" round>

View File

@@ -20,14 +20,10 @@ declare module 'vue' {
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckTag: typeof import('element-plus/es')['ElCheckTag']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
@@ -46,11 +42,9 @@ declare module 'vue' {
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSegmented: typeof import('element-plus/es')['ElSegmented']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']