From 06a4486a4f05685185ae5c91110188cdae158d86 Mon Sep 17 00:00:00 2001 From: Wonder Date: Sat, 28 Mar 2026 18:47:05 +0800 Subject: [PATCH] =?UTF-8?q?=20=E2=9C=A8Feat:=20=E5=88=86=20Tab=20=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 125 ++++++++++++++++++++++++-- frontend/src/components/Card.tsx | 44 ++------- frontend/src/components/Guodegang.tsx | 40 +-------- frontend/src/components/Mermaid.tsx | 43 ++------- 4 files changed, 133 insertions(+), 119 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 76aa1cb..cf13089 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,15 +1,95 @@ import { useState } from "react"; -import { ProgressBar, Label } from "@heroui/react"; +import { ProgressBar, Label, Tabs } from "@heroui/react"; import Card from "./components/Card"; import Guodegang from "./components/Guodegang"; import Mermaid from "./components/Mermaid"; import Topic from "./components/Topic"; +/* eslint-disable @typescript-eslint/no-explicit-any */ function App() { const [topic, setTopic] = useState(''); - const [cardLoaded, setCardLoaded] = useState(false); - const [guodegangLoaded, setGuodegangLoaded] = useState(false); - const [mermaidLoaded, setMermaidLoaded] = useState(false); + const [cardData, setCardData]: any = useState(null); + const [cardLoading, setCardLoading] = useState(false); + const [cardError, setCardError] = useState(null); + const [guodegangData, setGuodegangData]: any = useState(null); + const [guodegangLoading, setGuodegangLoading] = useState(false); + const [guodegangError, setGuodegangError] = useState(null); + const [mermaidData, setMermaidData]: any = useState(null); + const [mermaidLoading, setMermaidLoading] = useState(false); + const [mermaidError, setMermaidError] = useState(null); + + const fetchData = async (topicToFetch: string) => { + setCardLoading(true); + setGuodegangLoading(true); + setMermaidLoading(true); + setCardError(null); + setGuodegangError(null); + setMermaidError(null); + + const cardUrl = `http://localhost:8080/card/${encodeURIComponent(topicToFetch)}`; + const guodegangUrl = `http://localhost:8080/guodegang/${encodeURIComponent(topicToFetch)}`; + const mermaidUrl = `http://localhost:8080/mermaid/${encodeURIComponent(topicToFetch)}`; + + const abortController = new AbortController(); + const timeoutId = setTimeout(() => abortController.abort(), 300000); + + const fetchCard = async () => { + try { + const response = await fetch(cardUrl, { signal: abortController.signal }); + if (!response.ok) throw new Error(`Card API failed: ${response.status}`); + const result = await response.json(); + setCardData(result.message); + } catch (error) { + console.error("Error fetching card data:", error); + setCardError("Failed to load card data"); + } finally { + setCardLoading(false); + } + }; + + const fetchGuodegang = async () => { + try { + const response = await fetch(guodegangUrl, { signal: abortController.signal }); + if (!response.ok) throw new Error(`Guodegang API failed: ${response.status}`); + const result = await response.json(); + setGuodegangData(result.message); + } catch (error) { + console.error("Error fetching guodegang data:", error); + setGuodegangError("Failed to load guodegang data"); + } finally { + setGuodegangLoading(false); + } + }; + + const fetchMermaid = async () => { + try { + const response = await fetch(mermaidUrl, { signal: abortController.signal }); + if (!response.ok) throw new Error(`Mermaid API failed: ${response.status}`); + const result = await response.json(); + setMermaidData(result.message); + } catch (error) { + console.error("Error fetching mermaid data:", error); + setMermaidError("Failed to load mermaid data"); + } finally { + setMermaidLoading(false); + } + }; + + Promise.allSettled([fetchCard(), fetchGuodegang(), fetchMermaid()]).finally(() => { + clearTimeout(timeoutId); + }); + }; + + const handleTopicSubmit = (newTopic: string) => { + setTopic(newTopic); + if (newTopic) { + fetchData(newTopic); + } + }; + + const cardLoaded = !cardLoading && !!cardData; + const guodegangLoaded = !guodegangLoading && !!guodegangData; + const mermaidLoaded = !mermaidLoading && !!mermaidData; const progress = (() => { if (!topic) return 0; @@ -23,7 +103,7 @@ function App() { return (
- + {topic && (
@@ -35,10 +115,37 @@ function App() {
)} -
- - - +
+ + + + + + + 📚 学习卡片 + + + + + 🎭 德云社讲解 + + + + + 📊 Mermaid 图表 + + + + + + + + + + + + +
); diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx index 87a4e2e..badbcd3 100644 --- a/frontend/src/components/Card.tsx +++ b/frontend/src/components/Card.tsx @@ -1,5 +1,4 @@ import { Card } from "@heroui/react"; -import { useEffect, useState } from "react"; interface KeyPointItem { point: string; @@ -51,43 +50,12 @@ interface CardData { interface Props { topic: string; - onLoaded?: (loaded: boolean) => void; + data: CardData | null; + loading: boolean; + error: string | null; } -export default function Default({ topic, onLoaded }: Props) { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - if (!topic) { - onLoaded?.(false); - setLoading(false); - return; - } - const fetchData = async () => { - try { - const response = await fetch( - `http://localhost:8080/card/${encodeURIComponent(topic)}`, - ); - if (!response.ok) { - throw new Error("API Error"); - } - const result = await response.json(); - setData(result.message); - setError(null); - } catch (error) { - console.error("Failed to fetch data: ", error); - setError("Failed to load data. Please try again."); - } finally { - setLoading(false); - onLoaded?.(true); - } - }; - setLoading(true); - fetchData(); - }, [topic, onLoaded]); - +export default function Default({ topic, data, loading, error }: Props) { if (loading) { return (
@@ -143,7 +111,7 @@ export default function Default({ topic, onLoaded }: Props) { )}
- +

@@ -257,7 +225,7 @@ export default function Default({ topic, onLoaded }: Props) { {data!.mnemonic.content}

- +
diff --git a/frontend/src/components/Guodegang.tsx b/frontend/src/components/Guodegang.tsx index 07c102d..66ec2a1 100644 --- a/frontend/src/components/Guodegang.tsx +++ b/frontend/src/components/Guodegang.tsx @@ -1,5 +1,4 @@ import { Card } from "@heroui/react"; -import { useEffect, useState } from "react"; interface GuodegangData { name: string; @@ -8,43 +7,12 @@ interface GuodegangData { interface Props { topic: string; - onLoaded?: (loaded: boolean) => void; + data: GuodegangData | null; + loading: boolean; + error: string | null; } -export default function Guodegang({ topic, onLoaded }: Props) { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - if (!topic) { - onLoaded?.(false); - setLoading(false); - return; - } - const fetchData = async () => { - try { - const response = await fetch( - `http://localhost:8080/guodegang/${encodeURIComponent(topic)}`, - ); - if (!response.ok) { - throw new Error("API Error"); - } - const result = await response.json(); - setData(result.message); - setError(null); - } catch (error) { - console.error("Failed to fetch data: ", error); - setError("Failed to load data. Please try again."); - } finally { - setLoading(false); - onLoaded?.(true); - } - }; - setLoading(true); - fetchData(); - }, [topic, onLoaded]); - +export default function Guodegang({ topic, data, loading, error }: Props) { if (loading) { return (
diff --git a/frontend/src/components/Mermaid.tsx b/frontend/src/components/Mermaid.tsx index 07f2128..54e788d 100644 --- a/frontend/src/components/Mermaid.tsx +++ b/frontend/src/components/Mermaid.tsx @@ -1,5 +1,5 @@ import { Surface } from "@heroui/react"; -import { useEffect, useState, useRef } from "react"; +import { useEffect, useRef } from "react"; import mermaid from "mermaid"; interface MermaidData { @@ -9,51 +9,22 @@ interface MermaidData { interface Props { topic: string; - onLoaded?: (loaded: boolean) => void; + data: MermaidData | null; + loading: boolean; + error: string | null; } -export default function Default({ topic, onLoaded }: Props) { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); +export default function Default({ topic, data, loading, error }: Props) { const mermaidRef = useRef(null); useEffect(() => { mermaid.initialize({ startOnLoad: true }); }, []); - useEffect(() => { - if (!topic) { - onLoaded?.(false); - setLoading(false); - return; - } - const fetchData = async () => { - try { - const response = await fetch( - `http://localhost:8080/mermaid/${encodeURIComponent(topic)}`, - ); - if (!response.ok) { - throw new Error("API Error"); - } - const result = await response.json(); - setData(result.message); - setError(null); - } catch (error) { - console.error("Failed to fetch data: ", error); - setError("Failed to load data. Please try again."); - } finally { - setLoading(false); - onLoaded?.(true); - } - }; - setLoading(true); - fetchData(); - }, [topic, onLoaded]); - useEffect(() => { if (data && mermaidRef.current) { - mermaid.render("mermaid-diagram", data.content).then((result) => { + const id = `mermaid-diagram-${Date.now()}`; + mermaid.render(id, data.content).then((result) => { if (mermaidRef.current) { mermaidRef.current.innerHTML = result.svg; }