性能优化

This commit is contained in:
2026-03-04 14:04:55 +08:00
parent 4bbfbede01
commit e69f79f9db
10 changed files with 270 additions and 109 deletions
+17 -2
View File
@@ -18,13 +18,28 @@ export async function GET(request: NextRequest) {
// 默认行为:返回所有页面(向后兼容)
const usePagination = limit > 0 && limit <= 1000 && page > 0;
const baseSelect = {
id: true,
title: true,
content: !lightweight,
tags: true,
icon: true,
type: true,
parentId: true,
createdAt: true,
updatedAt: true,
order: true,
isLocked: true,
} as const;
if (!usePagination) {
const pages = await prisma.page.findMany({
select: baseSelect,
orderBy: [{ order: 'asc' }, { createdAt: 'desc' }],
});
const parsedPages = pages.map((p) => ({
...p,
content: lightweight ? '' : p.content,
content: lightweight ? '' : (p.content || ''),
tags: safeParseTags(p.tags),
}));
return NextResponse.json(parsedPages);
@@ -34,6 +49,7 @@ export async function GET(request: NextRequest) {
const skip = (page - 1) * limit;
const [pages, total] = await Promise.all([
prisma.page.findMany({
select: baseSelect,
skip,
take: limit,
orderBy: [{ order: 'asc' }, { createdAt: 'desc' }],
@@ -133,4 +149,3 @@ export async function POST(request: Request) {
}
+6 -1
View File
@@ -360,13 +360,18 @@ ul[data-type="taskList"],
The component handles its own layout. */
/* Override Highlight.js background for a softer look */
.ProseMirror pre {
.ProseMirror pre {
background: hsl(var(--code-bg)) !important;
border: 1px solid hsl(var(--border) / 0.5);
border-radius: 0.5rem;
padding: 0.65rem 0.75rem;
margin: 0.95rem 0;
overflow-x: auto;
color: #e6edf3;
}
.ProseMirror pre code {
color: inherit;
}
.hljs {
+129 -74
View File
@@ -25,7 +25,16 @@ const ICONS = [
];
export default function Home() {
const { activePageId, pages, pageSync, updatePage, setActivePageId, addPage, retryPageSync, retryPendingSyncs, flushPendingSyncs } = useEditorStore();
const activePageId = useEditorStore((s) => s.activePageId);
const pages = useEditorStore((s) => s.pages);
const pageSync = useEditorStore((s) => s.pageSync);
const updatePage = useEditorStore((s) => s.updatePage);
const setActivePageId = useEditorStore((s) => s.setActivePageId);
const addPage = useEditorStore((s) => s.addPage);
const retryPageSync = useEditorStore((s) => s.retryPageSync);
const retryPendingSyncs = useEditorStore((s) => s.retryPendingSyncs);
const flushPendingSyncs = useEditorStore((s) => s.flushPendingSyncs);
const fetchPageContent = useEditorStore((s) => s.fetchPageContent);
const { openSearchWithTag } = useSearchStore();
const { timezone } = useSettingsStore();
@@ -36,6 +45,7 @@ export default function Home() {
const [isAddingTag, setIsAddingTag] = useState(false);
const [tagInput, setTagInput] = useState("");
const [isIconPickerOpen, setIsIconPickerOpen] = useState(false);
const [isLinksOpen, setIsLinksOpen] = useState(false);
const [isHistoryOpen, setIsHistoryOpen] = useState(false);
const [selectedHistoryIndex, setSelectedHistoryIndex] = useState(0);
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(() => !activePageId);
@@ -57,19 +67,22 @@ export default function Home() {
}, [activePage, pages]);
const outgoingLinks = useMemo(() => {
if (!isLinksOpen) return [];
if (!activePage || activePage.type !== "file") return [];
return getOutgoingLinks(activePage, pages);
}, [activePage, pages]);
}, [activePage, pages, isLinksOpen]);
const backlinks = useMemo(() => {
if (!isLinksOpen) return [];
if (!activePage || activePage.type !== "file") return [];
return getBacklinks(activePage, pages);
}, [activePage, pages]);
}, [activePage, pages, isLinksOpen]);
const unresolvedLinks = useMemo(() => {
if (!isLinksOpen) return [];
if (!activePage || activePage.type !== "file") return [];
return getUnresolvedLinkTitles(activePage, pages);
}, [activePage, pages]);
}, [activePage, pages, isLinksOpen]);
const localHistory = useMemo(() => {
if (!activePage || activePage.type !== "file") return [];
@@ -125,6 +138,27 @@ export default function Home() {
};
}, [flushPendingSyncs]);
useEffect(() => {
if (!activePageId) return;
const current = pages.find((p) => p.id === activePageId);
if (!current || current.type === "folder") return;
if (current.contentLoaded) return;
fetchPageContent(activePageId);
}, [activePageId, pages, fetchPageContent]);
useEffect(() => {
if (!isLinksOpen) return;
const batchIds = pages
.filter((p) => p.type === "file" && !p.contentLoaded)
.slice(0, 8)
.map((p) => p.id);
if (batchIds.length === 0) return;
Promise.all(batchIds.map((id) => fetchPageContent(id))).catch((e) => {
console.error("Failed to batch load pages for link analysis", e);
});
}, [isLinksOpen, pages, fetchPageContent]);
const handleEdgeSwipeStart = (e: React.TouchEvent<HTMLDivElement>) => {
if (isMobileSidebarOpen) return;
const touch = e.touches[0];
@@ -411,80 +445,101 @@ export default function Home() {
/>
<section className="ui-enter mt-6 space-y-3 md:mt-7">
<div className="grid gap-2 md:grid-cols-3">
<div className="ui-card p-2.5 md:p-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground"></h3>
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">{outgoingLinks.length}</span>
<div className="ui-card p-2.5 md:p-3">
<button
onClick={() => setIsLinksOpen((v) => !v)}
className="flex w-full items-center justify-between rounded-md px-1 py-1 text-left transition-colors hover:bg-muted/45"
aria-expanded={isLinksOpen}
aria-label="切换链接分析面板"
>
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold text-foreground"></h3>
{isLinksOpen && (
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
{outgoingLinks.length + backlinks.length + unresolvedLinks.length}
</span>
)}
</div>
<p className="mt-1 text-[11px] text-muted-foreground"> `[[页面名]]` </p>
<div className="mt-2 flex flex-wrap gap-1.5">
{outgoingLinks.length > 0 ? (
outgoingLinks.map((item) => (
<button
key={item.id}
onClick={() => setActivePageId(item.id)}
className="rounded-md border border-border/70 bg-muted/40 px-2 py-0.5 text-[11px] text-foreground transition-colors hover:bg-accent"
>
{item.icon ? `${item.icon} ` : ""}
{item.title}
</button>
))
) : (
<span className="text-[11px] text-muted-foreground"></span>
)}
</div>
</div>
<ChevronDown size={15} className={cn("text-muted-foreground transition-transform", isLinksOpen && "rotate-180")} />
</button>
<p className="mt-1 text-[11px] text-muted-foreground"></p>
{isLinksOpen && (
<div className="mt-2 grid gap-2 md:grid-cols-3">
<div className="rounded-md border border-border/60 bg-muted/10 p-2.5">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground"></h3>
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">{outgoingLinks.length}</span>
</div>
<p className="mt-1 text-[11px] text-muted-foreground"> `[[页面名]]` </p>
<div className="mt-2 flex flex-wrap gap-1.5">
{outgoingLinks.length > 0 ? (
outgoingLinks.map((item) => (
<button
key={item.id}
onClick={() => setActivePageId(item.id)}
className="rounded-md border border-border/70 bg-muted/40 px-2 py-0.5 text-[11px] text-foreground transition-colors hover:bg-accent"
>
{item.icon ? `${item.icon} ` : ""}
{item.title}
</button>
))
) : (
<span className="text-[11px] text-muted-foreground"></span>
)}
</div>
</div>
<div className="ui-card p-2.5 md:p-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground"></h3>
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">{backlinks.length}</span>
</div>
<p className="mt-1 text-[11px] text-muted-foreground"></p>
<div className="mt-2 flex flex-wrap gap-1.5">
{backlinks.length > 0 ? (
backlinks.map((item) => (
<button
key={item.id}
onClick={() => setActivePageId(item.id)}
className="rounded-md border border-border/70 bg-muted/40 px-2 py-0.5 text-[11px] text-foreground transition-colors hover:bg-accent"
>
{item.icon ? `${item.icon} ` : ""}
{item.title}
</button>
))
) : (
<span className="text-[11px] text-muted-foreground"></span>
)}
</div>
</div>
<div className="rounded-md border border-border/60 bg-muted/10 p-2.5">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground"></h3>
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">{backlinks.length}</span>
</div>
<p className="mt-1 text-[11px] text-muted-foreground"></p>
<div className="mt-2 flex flex-wrap gap-1.5">
{backlinks.length > 0 ? (
backlinks.map((item) => (
<button
key={item.id}
onClick={() => setActivePageId(item.id)}
className="rounded-md border border-border/70 bg-muted/40 px-2 py-0.5 text-[11px] text-foreground transition-colors hover:bg-accent"
>
{item.icon ? `${item.icon} ` : ""}
{item.title}
</button>
))
) : (
<span className="text-[11px] text-muted-foreground"></span>
)}
</div>
</div>
<div className="ui-card p-2.5 md:p-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground"></h3>
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">{unresolvedLinks.length}</span>
<div className="rounded-md border border-border/60 bg-muted/10 p-2.5">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground"></h3>
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">{unresolvedLinks.length}</span>
</div>
<p className="mt-1 text-[11px] text-muted-foreground"></p>
<div className="mt-2 flex flex-wrap gap-1.5">
{unresolvedLinks.length > 0 ? (
unresolvedLinks.map((title) => (
<button
key={title}
onClick={async () => {
await addPage(null, "file", { title, content: "" });
}}
className="rounded-md border border-dashed border-border bg-muted/30 px-2 py-0.5 text-[11px] text-foreground transition-colors hover:bg-accent"
title={`创建页面:${title}`}
>
+ {title}
</button>
))
) : (
<span className="text-[11px] text-muted-foreground"></span>
)}
</div>
</div>
<p className="mt-1 text-[11px] text-muted-foreground"></p>
<div className="mt-2 flex flex-wrap gap-1.5">
{unresolvedLinks.length > 0 ? (
unresolvedLinks.map((title) => (
<button
key={title}
onClick={async () => {
await addPage(null, "file", { title, content: "" });
}}
className="rounded-md border border-dashed border-border bg-muted/30 px-2 py-0.5 text-[11px] text-foreground transition-colors hover:bg-accent"
title={`创建页面:${title}`}
>
+ {title}
</button>
))
) : (
<span className="text-[11px] text-muted-foreground"></span>
)}
</div>
</div>
</div>
)}
</div>
<div className="ui-card p-2.5 md:p-3">