feat: add image management functionality

- Implemented image folder repository with CRUD operations.
- Added image reference repository for managing image references to articles.
- Created image repository for handling image assets, including listing, inserting, updating, and deleting images.
- Introduced image usage repository to track storage usage and quotas for tenants.
- Added SQL queries for image assets, folders, references, and usage.
- Developed image handler for HTTP endpoints to manage images and folders.
- Created database migration scripts for image-related tables and structures.
This commit is contained in:
2026-04-16 20:40:41 +08:00
parent 0a3558fc51
commit 27389164b0
57 changed files with 8063 additions and 153 deletions
@@ -10,6 +10,7 @@ import PromptRuleModal from "@/components/PromptRuleModal.vue";
import PublishPlatformSelector from "@/components/PublishPlatformSelector.vue";
import { generateApi, mediaApi, promptRulesApi, schedulesApi } from "@/lib/api";
import { formatError } from "@/lib/errors";
import { IMAGE_UPLOAD_ACCEPT, useImageUploadProgress, validateImageUploadFile } from "@/lib/image-webp";
import {
buildPublishPlatformOptions,
normalizePublishPlatformIds,
@@ -29,6 +30,7 @@ const emit = defineEmits<{
const queryClient = useQueryClient();
const { t } = useI18n();
const imageUploadProgress = useImageUploadProgress();
const promptDrawerOpen = ref(false);
const coverPreviewUrl = ref("");
@@ -149,6 +151,7 @@ const submitLoading = computed(
updateScheduleMutation.isPending.value ||
instantGenerateMutation.isPending.value,
);
const imageUploadBusy = computed(() => imageUploadProgress.visible);
function hydrateForm(): void {
form.name = props.task?.name ?? "";
@@ -202,12 +205,26 @@ function resetCoverState(): void {
}
function handleCoverChange(event: Event): void {
if (imageUploadBusy.value) {
(event.target as HTMLInputElement).value = "";
return;
}
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
input.value = "";
if (!file) {
return;
}
try {
validateImageUploadFile(file);
} catch (error) {
message.warning(formatError(error));
return;
}
if (coverPreviewUrl.value) {
URL.revokeObjectURL(coverPreviewUrl.value);
}
@@ -373,13 +390,13 @@ function padTime(value: string): string {
<label
class="generate-task-drawer__cover"
:class="{ 'generate-task-drawer__cover--disabled': !form.coverEnabled }"
:class="{ 'generate-task-drawer__cover--disabled': !form.coverEnabled || imageUploadBusy }"
>
<input
type="file"
accept="image/*"
:accept="IMAGE_UPLOAD_ACCEPT"
hidden
:disabled="!form.coverEnabled"
:disabled="!form.coverEnabled || imageUploadBusy"
@change="handleCoverChange"
/>