feat: 完善广场功能

This commit is contained in:
2026-04-26 19:17:15 +08:00
parent 3e8e2353f0
commit eff52f6063
16 changed files with 833 additions and 23 deletions
+124
View File
@@ -0,0 +1,124 @@
<script setup lang="ts">
import type { IImageHistoryItem } from "#shared/types";
import { ImageService } from "~/services";
import { useUserStore } from "~/stores";
const userStore = useUserStore();
const { isOnline } = storeToRefs(userStore);
const histories = ref<IImageHistoryItem[]>([]);
const loading = ref(false);
const errorMessage = ref("");
const operatingRecordId = ref("");
const loadHistory = async () => {
if (!isOnline.value) {
histories.value = [];
errorMessage.value = "";
loading.value = false;
return;
}
loading.value = true;
errorMessage.value = "";
try {
const res = await ImageService.GetImageHistory(1, 20);
if (res.code === 0 && res.data) {
histories.value = res.data.items;
return;
}
errorMessage.value = res.msg || "获取生图历史失败";
} catch (error) {
errorMessage.value = getErrorMessage(error, "获取生图历史失败");
} finally {
loading.value = false;
}
};
const updatePublicState = async (item: IImageHistoryItem) => {
operatingRecordId.value = item.id;
errorMessage.value = "";
try {
const res = item.isPublic
? await ImageService.HideHistoryImage(item.id)
: await ImageService.PublishHistoryImage(item.id);
if (res.code === 0 && res.data) {
item.isPublic = res.data.isPublic;
return;
}
errorMessage.value = res.msg || "更新公开状态失败";
} catch (error) {
errorMessage.value = getErrorMessage(error, "更新公开状态失败");
} finally {
operatingRecordId.value = "";
}
};
onMounted(loadHistory);
watch(isOnline, () => {
loadHistory();
});
</script>
<template>
<section class="w-full max-w-xl space-y-3">
<div class="flex items-center justify-between">
<h2 class="text-base font-medium text-gray-900">历史记录</h2>
<el-button size="small" :loading="loading" @click="loadHistory">
刷新
</el-button>
</div>
<p v-if="errorMessage" class="text-sm text-red-500">
{{ errorMessage }}
</p>
<p
v-else-if="!loading && histories.length === 0"
class="text-sm text-gray-500"
>
暂无历史记录
</p>
<div v-else class="space-y-4">
<article
v-for="item in histories"
:key="item.id"
class="space-y-2 border-b border-gray-200 pb-4"
>
<p class="text-sm leading-6 text-gray-800">
{{ item.prompt }}
</p>
<img
v-if="item.hostedImageUrl"
:src="item.hostedImageUrl"
alt="历史生成图片"
class="block w-full rounded border border-gray-200 object-contain"
/>
<p v-else class="text-sm text-gray-500">图片归档中</p>
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500">
{{ item.isPublic ? "已公开" : "未公开" }}
</span>
<el-button
size="small"
:loading="operatingRecordId === item.id"
@click="updatePublicState(item)"
>
{{ item.isPublic ? "取消公开" : "公开" }}
</el-button>
</div>
</article>
</div>
</section>
</template>
+73
View File
@@ -0,0 +1,73 @@
<script setup lang="ts">
import type { IPlazaPostItem } from "#shared/types";
import { ImageService } from "~/services";
const plazaPosts = ref<IPlazaPostItem[]>([]);
const loading = ref(false);
const errorMessage = ref("");
const loadPlazaPosts = async () => {
loading.value = true;
errorMessage.value = "";
try {
const res = await ImageService.GetPlazaPosts(1, 20);
if (res.code === 0 && res.data) {
plazaPosts.value = res.data.items;
return;
}
errorMessage.value = res.msg || "获取广场图片失败";
} catch (error) {
errorMessage.value = getErrorMessage(error, "获取广场图片失败");
} finally {
loading.value = false;
}
};
onMounted(loadPlazaPosts);
</script>
<template>
<section class="w-full space-y-3">
<div class="flex items-center justify-between">
<h2 class="text-base font-medium text-gray-900">广场</h2>
<el-button size="small" :loading="loading" @click="loadPlazaPosts">
刷新
</el-button>
</div>
<p v-if="errorMessage" class="text-sm text-red-500">
{{ errorMessage }}
</p>
<p v-else-if="!loading && plazaPosts.length === 0" class="text-sm text-gray-500">
暂无广场图片
</p>
<div v-else class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<article
v-for="item in plazaPosts"
:key="item.id"
class="overflow-hidden rounded border border-gray-200 bg-white"
>
<img
:src="item.hostedImageUrl"
alt="广场图片"
class="block w-full object-cover"
/>
<div class="space-y-2 p-3">
<p class="text-sm leading-6 text-gray-800">
{{ item.prompt }}
</p>
<p class="text-xs text-gray-500">
{{ item.authorDisplayName }}
</p>
</div>
</article>
</div>
</section>
</template>
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<main class="w-full h-dvh flex flex-col">
<HeaderCom />
<div class="min-h-0 flex-1 px-4 overflow-y-auto flex">
<div class="min-h-0 flex-1 px-4 overflow-y-auto flex bg-gray-50">
<div
class="w-full"
:class="{ 'm-auto max-w-sm': $route.path.includes('login') }"
+4 -1
View File
@@ -5,7 +5,10 @@ definePageMeta({
</script>
<template>
<div class="py-2">
<div class="space-y-8 py-2">
<ImageGenerateCom />
<ClientOnly>
<ImageHistoryListCom />
</ClientOnly>
</div>
</template>
+53 -1
View File
@@ -1,12 +1,16 @@
import type {
ICommonResponse,
IImageGenerateData,
IImageGenerateRequest
IImageGenerateRequest,
IImageHistoryListData,
IImagePublicStateData,
IPlazaPostListData
} from "#shared/types";
export class ImageService {
public static basePath = "/api/images";
/** 发起生图请求,成功后返回当前可展示的图片地址。 */
public static GenerateImage(request: IImageGenerateRequest) {
return $fetch<ICommonResponse<IImageGenerateData>>(
`${ImageService.basePath}/generate`,
@@ -16,4 +20,52 @@ export class ImageService {
}
);
}
/** 分页读取当前登录用户的生图历史记录。 */
public static GetImageHistory(page = 1, size = 10) {
return $fetch<ICommonResponse<IImageHistoryListData>>(
`${ImageService.basePath}/history`,
{
method: "GET",
query: {
page,
size
}
}
);
}
/** 分页读取广场中所有用户可见的公开图片。 */
public static GetPlazaPosts(page = 1, size = 20) {
return $fetch<ICommonResponse<IPlazaPostListData>>(
`${ImageService.basePath}/plaza`,
{
method: "GET",
query: {
page,
size
}
}
);
}
/** 将当前用户的一条生图历史发布到广场。 */
public static PublishHistoryImage(recordId: string) {
return $fetch<ICommonResponse<IImagePublicStateData>>(
`${ImageService.basePath}/history/${recordId}/public`,
{
method: "POST"
}
);
}
/** 取消公开当前用户的一条广场图片,服务端会软隐藏发布记录。 */
public static HideHistoryImage(recordId: string) {
return $fetch<ICommonResponse<IImagePublicStateData>>(
`${ImageService.basePath}/history/${recordId}/public`,
{
method: "DELETE"
}
);
}
}