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
+63 -2
View File
@@ -52,6 +52,17 @@ const enUS = {
copySuccess: "Content copied",
yes: "Yes",
no: "No",
imageUploadProgress: {
title: "Processing image",
stages: {
idle: "Waiting to start",
validating: "Validating file",
decoding: "Decoding image",
encoding: "Converting to WebP",
uploading: "Uploading to server",
finishing: "Finishing",
},
},
},
nav: {
workspace: "Workspace",
@@ -66,6 +77,7 @@ const enUS = {
trackingDetail: "Data Details",
contentManagement: "Content Management",
knowledge: "Knowledge Base",
images: "Image Management",
},
auth: {
welcomeBack: "Welcome back",
@@ -135,6 +147,10 @@ const enUS = {
title: "Knowledge Base",
description: "No matching backend APIs exist in this repository yet.",
},
images: {
title: "Image Management",
description: "Manage image assets, including folders, reference analysis, and cover library usage.",
},
},
workspace: {
eyebrow: "Dashboard",
@@ -596,6 +612,11 @@ const enUS = {
resetSize: "Reset size",
delete: "Delete image",
},
imagePicker: {
title: "Select image",
emptyTitle: "Choose an image for the article body",
emptyHint: "Pick from the image library or upload locally. Local uploads are converted to WebP before upload.",
},
aiOptimize: {
title: "AI Optimize",
subtitle: "Supports contiguous multi-block selections and generates a rewrite from your instruction",
@@ -741,6 +762,8 @@ const enUS = {
local: "Body / Local Upload",
ai: "AI Cover",
},
dropzoneTitle: "Upload image",
dropzoneHint: "Supports PNG, JPG, GIF, and WebP up to 10MB. Uploads are converted to WebP in the browser first.",
previewTitle: "Cover crop preview",
previewHintLocked: "Baijiahao is selected, so the cover will be exported as 16:9 when you confirm.",
previewHintFree: "There is no forced cover ratio right now. You can upload the original image directly or adjust framing before confirming.",
@@ -754,7 +777,7 @@ const enUS = {
fileLabel: "Current cover",
emptyTitle: "No publish platform selected",
emptyPlatforms: "Choose publish platforms first and each platform's cover requirements will appear here.",
emptyHint: "Supports PNG, JPG, GIF, and WebP up to 10MB.",
emptyHint: "Supports PNG, JPG, JPEG, GIF, WebP, BMP, SVG, and AVIF up to 10MB.",
aiTitle: "AI cover is coming soon",
aiHint: "For now this flow supports local upload and cropping. AI cover generation can be plugged in here later.",
actions: {
@@ -768,7 +791,7 @@ const enUS = {
unsupported: "This platform's current integration does not consume a standalone cover yet and falls back to body content or the platform default.",
},
messages: {
invalidType: "Only PNG, JPG, GIF, and WebP images are supported.",
invalidType: "Only PNG, JPG, JPEG, GIF, WebP, BMP, SVG, and AVIF images are supported.",
tooLarge: "Image size cannot exceed 10MB.",
missingImage: "Select a cover image first.",
missingArticle: "The article is not ready yet. Refresh and try again.",
@@ -981,6 +1004,44 @@ const enUS = {
currentStatusText: "The page shell is ready with navigation and auth guard support.",
integrationStrategyText: "Keep the UI aligned with tenant-api envelopes and wire real read flows before write flows.",
},
images: {
title: "Image Management",
description: "Manage image library with folder organization and reference analysis.",
uploadBtn: "Upload Image",
folders: "Folders",
uncategorized: "Uncategorized",
searchPlaceholder: "Search image name...",
newFolder: "New Folder",
folderName: "Folder Name",
rename: "Rename",
move: "Move",
deleteImage: "Delete Image",
deleteFolder: "Delete Folder",
forceDelete: "Force Delete",
confirmDeleteTitle: "Confirm Delete",
confirmDeleteImage: "Are you sure you want to delete this image? This action cannot be undone.",
confirmDeleteFolder: "Confirm delete folder {name}?",
imageInUse: "Image is in use",
folderHasRefs: "This folder contains {count} images. They may already be referenced by articles. Deleting the folder will also delete these images permanently.",
refArticleBody: "Body reference",
refArticleCover: "Cover reference",
storageUsage: "Storage Usage",
storageUsageDetail: "{used} / {total} ({percent}%)",
quotaExceeded: "Storage is full, please clean up before uploading more.",
uploadSuccess: "Image uploaded successfully",
uploadReusedSuccess: "Matching image already exists in the library and was reused",
uploadFailed: "Image upload failed",
deleteSuccess: "Deleted successfully",
folderCreated: "Folder created successfully",
folderRenamed: "Folder renamed successfully",
folderDeleted: "Folder deleted",
imageRenamed: "Image renamed successfully",
imageMoved: "Image moved successfully",
noImages: "No images yet. Click upload to start.",
allFolders: "All Folders",
selectImage: "Select Image",
libraryTab: "Library",
},
} as const;
export default enUS;
+64 -3
View File
@@ -52,6 +52,17 @@ const zhCN = {
copySuccess: "内容已复制",
yes: "是",
no: "否",
imageUploadProgress: {
title: "图片处理中",
stages: {
idle: "等待开始",
validating: "正在校验文件",
decoding: "正在解析图片",
encoding: "正在转为 WebP",
uploading: "正在上传到服务器",
finishing: "正在完成处理",
},
},
},
nav: {
workspace: "工作台",
@@ -66,6 +77,7 @@ const zhCN = {
trackingDetail: "数据详情",
contentManagement: "内容管理",
knowledge: "知识库",
images: "图片管理",
},
auth: {
welcomeBack: "欢迎回来",
@@ -135,6 +147,10 @@ const zhCN = {
title: "知识库",
description: "当前仓库还没有匹配的后端接口,页面暂保持为设计占位。",
},
images: {
title: "图片管理",
description: "管理图片素材库,支持分组管理、引用分析与封面图库引用。",
},
},
workspace: {
eyebrow: "Dashboard",
@@ -603,6 +619,11 @@ const zhCN = {
resetSize: "重置大小",
delete: "删除图片",
},
imagePicker: {
title: "选择图片",
emptyTitle: "选择一张图片插入正文",
emptyHint: "支持从图片库选择,也支持本地上传后自动转为 WebP。",
},
aiOptimize: {
title: "AI 优化",
subtitle: "支持连续多段选中,基于你的优化要求生成候选改写",
@@ -748,6 +769,8 @@ const zhCN = {
local: "正文/本地上传",
ai: "AI封图",
},
dropzoneTitle: "上传图片",
dropzoneHint: "支持 PNG、JPG、GIF、WebP,上传前会在前端转为 WebP,最大 10MB",
previewTitle: "封面裁剪预览",
previewHintLocked: "已选择百家号,确认时会按 16:9 输出封面。",
previewHintFree: "当前不限制封面比例,可直接上传原图;如需调整也可拖拽缩放后再确认。",
@@ -761,7 +784,7 @@ const zhCN = {
fileLabel: "当前封面",
emptyTitle: "暂未选择发布平台",
emptyPlatforms: "选择发布平台后,这里会自动显示每个平台的封面要求。",
emptyHint: "支持 PNG、JPG、GIF、WebP,最大 10MB",
emptyHint: "支持 PNG、JPG、JPEG、GIF、WebP、BMP、SVG、AVIF,最大 10MB",
aiTitle: "AI 封图即将开放",
aiHint: "当前先支持本地上传和裁剪,后续会把 AI 生成封面接进这里。",
actions: {
@@ -775,7 +798,7 @@ const zhCN = {
unsupported: "该平台当前接入暂不消费独立封面,会沿用正文内容或平台默认样式。",
},
messages: {
invalidType: "仅支持 PNG、JPG、GIF、WebP 图片",
invalidType: "仅支持 PNG、JPG、JPEG、GIF、WebP、BMP、SVG、AVIF 图片",
tooLarge: "图片不能超过 10MB",
missingImage: "请先选择一张封面图",
missingArticle: "文章信息还没准备好,请刷新后重试",
@@ -987,7 +1010,45 @@ const zhCN = {
nextPriority: "下一步重点",
integrationStrategy: "联调策略",
currentStatusText: "页面壳子已经接入主导航和鉴权守卫,后续可以直接在这里落真实交互。",
integrationStrategyText: "保持与 tenant-api 的响应结构对齐,先做真实读接口,再逐个补写接口表单流。",
integrationStrategyText: "保持与 tenant-api 的响应结构对齐,先做真实读接口,再逐个补写接口 and 表单流。",
},
images: {
title: "图片管理",
description: "管理图片素材库,支持分组、引用分析与封面引用。",
uploadBtn: "上传图片",
folders: "文件夹",
uncategorized: "未分类",
searchPlaceholder: "搜索图片名称...",
newFolder: "新建文件夹",
folderName: "文件夹名称",
rename: "重命名",
move: "移动",
deleteImage: "删除图片",
deleteFolder: "删除文件夹",
forceDelete: "仍然删除",
confirmDeleteTitle: "确认删除",
confirmDeleteImage: "确认删除这张图片吗?该操作不可撤销。",
confirmDeleteFolder: "确认删除文件夹 {name} 吗?",
imageInUse: "图片正在使用中",
folderHasRefs: "该文件夹内有 {count} 张图片,这些图片可能已被文章引用。删除文件夹会一并删除这些图片,且不可恢复。",
refArticleBody: "正文引用",
refArticleCover: "封面引用",
storageUsage: "存储容量",
storageUsageDetail: "{used} / {total} ({percent}%)",
quotaExceeded: "存储空间已满,请清理后再上传。",
uploadSuccess: "图片上传成功",
uploadReusedSuccess: "素材库已有相同图片,已直接复用",
uploadFailed: "图片上传失败",
deleteSuccess: "删除成功",
folderCreated: "文件夹创建成功",
folderRenamed: "文件夹重命名成功",
folderDeleted: "文件夹已删除",
imageRenamed: "图片重命名成功",
imageMoved: "图片移动成功",
noImages: "暂无图片,点击上传开始添加",
allFolders: "全部文件夹",
selectImage: "选择图片",
libraryTab: "素材库",
},
} as const;