feat: 图片广场优化
This commit is contained in:
@@ -13,11 +13,12 @@ import {
|
|||||||
Search,
|
Search,
|
||||||
User,
|
User,
|
||||||
WarningFilled,
|
WarningFilled,
|
||||||
|
ZoomIn,
|
||||||
} from '@element-plus/icons-vue';
|
} from '@element-plus/icons-vue';
|
||||||
import { useClipboard } from '@vueuse/core';
|
import { useClipboard } from '@vueuse/core';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { ElMessage } from 'element-plus';
|
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 { getImagePlaza } from '@/api/aiImage';
|
||||||
import TaskCard from './TaskCard.vue';
|
import TaskCard from './TaskCard.vue';
|
||||||
|
|
||||||
@@ -31,6 +32,22 @@ const noMore = ref(false);
|
|||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const currentTask = ref<TaskItem | null>(null);
|
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
|
// Mobile filter drawer
|
||||||
const showMobileFilter = ref(false);
|
const showMobileFilter = ref(false);
|
||||||
|
|
||||||
@@ -424,12 +441,13 @@ watch(dateRange, () => {
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="图片详情"
|
title="图片详情"
|
||||||
width="900px"
|
:width="isMobile ? '95%' : '900px'"
|
||||||
|
:fullscreen="isMobile"
|
||||||
append-to-body
|
append-to-body
|
||||||
class="image-detail-dialog"
|
class="image-detail-dialog"
|
||||||
align-center
|
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 -->
|
<!-- Left Image -->
|
||||||
<div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative group">
|
<div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative group">
|
||||||
<el-image
|
<el-image
|
||||||
@@ -472,7 +490,8 @@ watch(dateRange, () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Download Button Overlay -->
|
<!-- 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)" />
|
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -497,6 +516,36 @@ watch(dateRange, () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-auto space-y-3 pt-4 border-t border-gray-100 shrink-0">
|
<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">
|
<div class="flex justify-between text-sm">
|
||||||
<span class="text-gray-500">创建时间</span>
|
<span class="text-gray-500">创建时间</span>
|
||||||
<span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span>
|
<span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { InputInstance } from 'element-plus';
|
import type { InputInstance } from 'element-plus';
|
||||||
import type { TaskItem, TaskListRequest } from '@/api/aiImage/types';
|
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 { useClipboard } from '@vueuse/core';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { ElMessage } from 'element-plus';
|
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 { getMyTasks, publishImage } from '@/api/aiImage';
|
||||||
import TaskCard from './TaskCard.vue';
|
import TaskCard from './TaskCard.vue';
|
||||||
|
|
||||||
@@ -19,6 +19,22 @@ const noMore = ref(false);
|
|||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const currentTask = ref<TaskItem | null>(null);
|
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
|
// Mobile filter drawer
|
||||||
const showMobileFilter = ref(false);
|
const showMobileFilter = ref(false);
|
||||||
|
|
||||||
@@ -491,12 +507,13 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="图片详情"
|
title="图片详情"
|
||||||
width="900px"
|
:width="isMobile ? '95%' : '900px'"
|
||||||
|
:fullscreen="isMobile"
|
||||||
append-to-body
|
append-to-body
|
||||||
class="image-detail-dialog"
|
class="image-detail-dialog"
|
||||||
align-center
|
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 -->
|
<!-- Left Image -->
|
||||||
<div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative group">
|
<div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative group">
|
||||||
<el-image
|
<el-image
|
||||||
@@ -539,7 +556,8 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Download Button Overlay -->
|
<!-- 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)" />
|
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -564,28 +582,60 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-auto space-y-3 pt-4 border-t border-gray-100 shrink-0">
|
<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">
|
<div class="flex justify-between text-sm">
|
||||||
<span class="text-gray-500">创建时间</span>
|
<span class="text-gray-500">创建时间</span>
|
||||||
<span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span>
|
<span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span>
|
||||||
</div>
|
</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">
|
<div class="flex justify-between text-sm items-center">
|
||||||
已发布
|
<span class="text-gray-500">任务状态</span>
|
||||||
</el-tag>
|
<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>
|
</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 v-if="currentTask.taskStatus === 'Success'" class="pt-2 space-y-2">
|
||||||
<div class="grid grid-cols-1 gap-1">
|
<div class="grid grid-cols-1 gap-1">
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ async function handleDownload() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Overlay (Hover) -->
|
<!-- Overlay (Hover) - Desktop -->
|
||||||
<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]">
|
<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 type="primary" round size="small" class="transform scale-90 group-hover:scale-100 transition-transform shadow-lg" @click.stop="$emit('click')">
|
||||||
查看详情
|
查看详情
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -128,6 +128,31 @@ async function handleDownload() {
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Status Tag -->
|
||||||
<div v-if="showPublishStatus && task.publishStatus === 'Published'" class="absolute top-2 right-2 z-20">
|
<div v-if="showPublishStatus && task.publishStatus === 'Published'" class="absolute top-2 right-2 z-20">
|
||||||
<el-tag type="success" effect="dark" size="small" round>
|
<el-tag type="success" effect="dark" size="small" round>
|
||||||
|
|||||||
6
Yi.Ai.Vue3/types/components.d.ts
vendored
6
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -20,14 +20,10 @@ declare module 'vue' {
|
|||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
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']
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
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']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
@@ -46,11 +42,9 @@ declare module 'vue' {
|
|||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
|
||||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
ElSegmented: typeof import('element-plus/es')['ElSegmented']
|
ElSegmented: typeof import('element-plus/es')['ElSegmented']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
|
|||||||
Reference in New Issue
Block a user