Add login gate, calculation history, and AI markdown download.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Download, Trash2 } from "lucide-react";
|
||||
import PageShell from "@/components/page-shell";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ZenCard } from "@/components/ui/zen-card";
|
||||
import Markdown from "react-markdown";
|
||||
import {
|
||||
buildHistoryMarkdown,
|
||||
deleteHistoryEntry,
|
||||
downloadMarkdown,
|
||||
loadHistory,
|
||||
} from "@/lib/history/storage";
|
||||
import { MODE_LABELS, type CalcHistoryEntry } from "@/lib/history/types";
|
||||
|
||||
export default function HistoryPageClient() {
|
||||
const [items, setItems] = useState<CalcHistoryEntry[]>([]);
|
||||
const [activeId, setActiveId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const list = loadHistory();
|
||||
setItems(list);
|
||||
setActiveId(list[0]?.id ?? null);
|
||||
}, []);
|
||||
|
||||
const active = items.find((e) => e.id === activeId) ?? null;
|
||||
|
||||
function handleDelete(id: string) {
|
||||
deleteHistoryEntry(id);
|
||||
const next = loadHistory();
|
||||
setItems(next);
|
||||
if (activeId === id) {
|
||||
setActiveId(next[0]?.id ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageShell className="py-8">
|
||||
<div className="mb-6 text-center">
|
||||
<h1 className="text-2xl font-bold tracking-wide">测算历史</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
本机浏览器保存,清除缓存后可能丢失
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{items.length === 0 ? (
|
||||
<ZenCard className="text-center text-sm text-muted-foreground">
|
||||
<p>暂无测算记录</p>
|
||||
<Link href="/liuyao" className="mt-3 inline-block text-primary underline">
|
||||
去六爻算卦
|
||||
</Link>
|
||||
</ZenCard>
|
||||
) : (
|
||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,280px)_1fr]">
|
||||
<div className="space-y-2">
|
||||
{items.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
onClick={() => setActiveId(item.id)}
|
||||
className={`w-full rounded-xl border px-4 py-3 text-left text-sm transition ${
|
||||
activeId === item.id
|
||||
? "border-primary/40 bg-primary/5"
|
||||
: "border-border/60 bg-card/80 hover:border-primary/20"
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium">{item.title}</p>
|
||||
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
||||
{item.question}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{MODE_LABELS[item.mode]} ·{" "}
|
||||
{new Date(item.createdAt).toLocaleString("zh-CN")}
|
||||
</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{active && (
|
||||
<ZenCard
|
||||
title={active.title}
|
||||
subtitle={`${MODE_LABELS[active.mode]} · ${new Date(active.createdAt).toLocaleString("zh-CN")}`}
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">问事:{active.question}</p>
|
||||
{active.summary && (
|
||||
<p className="text-sm text-muted-foreground">{active.summary}</p>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
downloadMarkdown(
|
||||
buildHistoryMarkdown(active),
|
||||
`${active.title}.md`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<Download size={14} className="mr-1" />
|
||||
下载 Markdown
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDelete(active.id)}
|
||||
>
|
||||
<Trash2 size={14} className="mr-1" />
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<div className="max-h-[60vh] overflow-y-auto rounded-md border bg-background/50 p-4">
|
||||
<Markdown className="prose max-w-none text-sm dark:prose-invert">
|
||||
{active.completion}
|
||||
</Markdown>
|
||||
</div>
|
||||
</ZenCard>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user