feat(core): notification inbox infinite scroll + command palette fix (+ pending WIP)
消息中心:全量渲染 → 真·后端分页滚动加载 - backend(ops/views): NotificationPagination(10/页,page_size 可覆盖)+ 响应回 type_counts(按收件人绝对计数,不受分页/搜索影响) - frontend(messages): 自管分页,滚到底加载下一批;tab/搜索走服务端并重置到第1页; 代号作废在途旧请求防切换卡空白;乐观标已读;「已加载 X / Y」分母用当前筛选总数 - api/App/types: listNotifications 支持 page/page_size/search;allNotifications 携带 type_counts 命令面板(侧边栏搜索):修复点开后 UI 错位 - app-shell: 遮罩 className 漏了基类 shell-command-bg(只有 .show)致无定位塌到左下; 补回基类 + header 类名对齐 .shell-command-h - messages-page.css: 工作台收进视口高度,收件箱在面板内滚动 本次提交一并带入此前若干未提交 WIP(account/ai-tools/library/pipeline/products/settings + accounts/ai/assets/billing/projects 后端),按用户要求整体推 dev。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { FormEvent } from "react";
|
||||
import type { Asset } from "../types";
|
||||
import { ConfirmModal, Drawer } from "../components/overlays";
|
||||
import { ConfirmModal, Drawer, MediaLightbox } from "../components/overlays";
|
||||
|
||||
// asset.source / asset.asset_type → 中文标签(筛选下拉用)
|
||||
const SOURCE_LABELS: Record<string, string> = { upload: "上传", ai_generated: "AI 生成", exported: "导出", system: "系统" };
|
||||
@@ -52,6 +52,8 @@ export function LibraryPage({ assets, onUpload, onDelete }: { assets: Asset[]; o
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [metaFilter, setMetaFilter] = useState<Record<string, string>>({});
|
||||
const [confirmId, setConfirmId] = useState<string | null>(null);
|
||||
// 资产预览灯箱(图片放大 / 视频播放)
|
||||
const [preview, setPreview] = useState<{ src: string; kind: "image" | "video"; name: string } | null>(null);
|
||||
useEffect(() => {
|
||||
document.body.classList.toggle("edit-mode", editMode);
|
||||
return () => document.body.classList.remove("edit-mode");
|
||||
@@ -213,6 +215,8 @@ export function LibraryPage({ assets, onUpload, onDelete }: { assets: Asset[]; o
|
||||
<div className="asset-grid" id="asset-grid">
|
||||
{filtered.map((asset) => {
|
||||
const cover = asset.files?.find((f) => f.is_primary)?.preview_url || asset.files?.[0]?.preview_url || "";
|
||||
const isVideo = asset.asset_type === "video";
|
||||
const openPreview = cover ? () => setPreview({ src: cover, kind: isVideo ? "video" : "image", name: asset.name }) : undefined;
|
||||
return (
|
||||
<article className={`asset-card ${asset.asset_type}`} key={asset.id}>
|
||||
{editMode && onDelete && (
|
||||
@@ -220,8 +224,15 @@ export function LibraryPage({ assets, onUpload, onDelete }: { assets: Asset[]; o
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18" /><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2" /><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6" /></svg>
|
||||
</button>
|
||||
)}
|
||||
<div className="placeholder asset-thumb">
|
||||
{cover ? <img src={cover} alt={asset.name} loading="lazy" /> : <span className="ph-frame">{asset.asset_type}</span>}
|
||||
<div className="placeholder asset-thumb" role={openPreview ? "button" : undefined} tabIndex={openPreview ? 0 : undefined} title={openPreview ? (isVideo ? "点击播放" : "点击放大") : undefined} style={openPreview ? { cursor: isVideo ? "pointer" : "zoom-in", position: "relative" } : undefined} onClick={openPreview} onKeyDown={openPreview ? (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openPreview(); } } : undefined}>
|
||||
{cover
|
||||
? (isVideo
|
||||
? <>
|
||||
<video src={cover} muted playsInline preload="metadata" style={{ width: "100%", height: "100%", objectFit: "cover", borderRadius: "inherit" }} />
|
||||
<span className="lib-play-badge" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z" /></svg></span>
|
||||
</>
|
||||
: <img src={cover} alt={asset.name} loading="lazy" />)
|
||||
: <span className="ph-frame">{asset.asset_type}</span>}
|
||||
</div>
|
||||
<div className="asset-body"><div className="asset-name">{asset.name}</div><div className="asset-meta">{asset.category} · {asset.source}</div></div>
|
||||
</article>
|
||||
@@ -232,6 +243,8 @@ export function LibraryPage({ assets, onUpload, onDelete }: { assets: Asset[]; o
|
||||
<div className="empty-filter">// 当前分类暂无真实资产</div>
|
||||
)}
|
||||
|
||||
<MediaLightbox open={!!preview} src={preview?.src || ""} kind={preview?.kind} name={preview?.name} close={() => setPreview(null)} />
|
||||
|
||||
<ConfirmModal
|
||||
open={Boolean(confirmId)}
|
||||
title="删除资产"
|
||||
|
||||
Reference in New Issue
Block a user