diff --git a/src/assets/images/components/按钮-提交.png b/src/assets/images/components/按钮-提交.png new file mode 100644 index 0000000..bf43f9b Binary files /dev/null and b/src/assets/images/components/按钮-提交.png differ diff --git a/src/assets/images/components/按钮-清空.png b/src/assets/images/components/按钮-清空.png new file mode 100644 index 0000000..ecdcc58 Binary files /dev/null and b/src/assets/images/components/按钮-清空.png differ diff --git a/src/assets/images/experiment/01实验工具介绍.png b/src/assets/images/experiment/01实验工具介绍.png new file mode 100644 index 0000000..a51d4bf Binary files /dev/null and b/src/assets/images/experiment/01实验工具介绍.png differ diff --git a/src/assets/images/experiment/02实验工具选择.png b/src/assets/images/experiment/02实验工具选择.png new file mode 100644 index 0000000..76d4f9e Binary files /dev/null and b/src/assets/images/experiment/02实验工具选择.png differ diff --git a/src/assets/images/experiment/03实验环境配置底图.png b/src/assets/images/experiment/03实验环境配置底图.png new file mode 100644 index 0000000..a387a93 Binary files /dev/null and b/src/assets/images/experiment/03实验环境配置底图.png differ diff --git a/src/assets/images/experiment/1.png b/src/assets/images/experiment/1.png new file mode 100644 index 0000000..7d8578d Binary files /dev/null and b/src/assets/images/experiment/1.png differ diff --git a/src/assets/images/experiment/arduino.png b/src/assets/images/experiment/arduino.png new file mode 100644 index 0000000..6b08ceb Binary files /dev/null and b/src/assets/images/experiment/arduino.png differ diff --git a/src/assets/images/experiment/flash-cable.png b/src/assets/images/experiment/flash-cable.png new file mode 100644 index 0000000..e27d14f Binary files /dev/null and b/src/assets/images/experiment/flash-cable.png differ diff --git a/src/assets/images/experiment/keil.png b/src/assets/images/experiment/keil.png new file mode 100644 index 0000000..2439f28 Binary files /dev/null and b/src/assets/images/experiment/keil.png differ diff --git a/src/assets/images/experiment/sensor.png b/src/assets/images/experiment/sensor.png new file mode 100644 index 0000000..f9150ae Binary files /dev/null and b/src/assets/images/experiment/sensor.png differ diff --git a/src/assets/images/experiment/serial-assista.png b/src/assets/images/experiment/serial-assista.png new file mode 100644 index 0000000..8c19b6e Binary files /dev/null and b/src/assets/images/experiment/serial-assista.png differ diff --git a/src/assets/images/experiment/stm32.png b/src/assets/images/experiment/stm32.png new file mode 100644 index 0000000..1046d79 Binary files /dev/null and b/src/assets/images/experiment/stm32.png differ diff --git a/src/assets/images/experiment/type-c.png b/src/assets/images/experiment/type-c.png new file mode 100644 index 0000000..c356313 Binary files /dev/null and b/src/assets/images/experiment/type-c.png differ diff --git a/src/assets/images/experiment/windows.png b/src/assets/images/experiment/windows.png new file mode 100644 index 0000000..7e2a0ca Binary files /dev/null and b/src/assets/images/experiment/windows.png differ diff --git a/src/assets/images/experiment/wireless.png b/src/assets/images/experiment/wireless.png new file mode 100644 index 0000000..c7c2f74 Binary files /dev/null and b/src/assets/images/experiment/wireless.png differ diff --git a/src/assets/images/experiment/串口线.png b/src/assets/images/experiment/串口线.png new file mode 100644 index 0000000..5eedda4 Binary files /dev/null and b/src/assets/images/experiment/串口线.png differ diff --git a/src/assets/images/experiment/人体红外.png b/src/assets/images/experiment/人体红外.png new file mode 100644 index 0000000..94e7e96 Binary files /dev/null and b/src/assets/images/experiment/人体红外.png differ diff --git a/src/assets/images/experiment/光敏传感器.png b/src/assets/images/experiment/光敏传感器.png new file mode 100644 index 0000000..0492a90 Binary files /dev/null and b/src/assets/images/experiment/光敏传感器.png differ diff --git a/src/assets/images/experiment/气体传感器.png b/src/assets/images/experiment/气体传感器.png new file mode 100644 index 0000000..ddf0988 Binary files /dev/null and b/src/assets/images/experiment/气体传感器.png differ diff --git a/src/assets/images/experiment/温湿度传感器.png b/src/assets/images/experiment/温湿度传感器.png new file mode 100644 index 0000000..4ef5580 Binary files /dev/null and b/src/assets/images/experiment/温湿度传感器.png differ diff --git a/src/assets/images/experiment/磁敏传感器.png b/src/assets/images/experiment/磁敏传感器.png new file mode 100644 index 0000000..97c9f88 Binary files /dev/null and b/src/assets/images/experiment/磁敏传感器.png differ diff --git a/src/components/Image.tsx b/src/components/Image.tsx index caa5d98..0307245 100644 --- a/src/components/Image.tsx +++ b/src/components/Image.tsx @@ -3,6 +3,7 @@ import { clsx } from "clsx"; interface ImageProps { src: string; + children?: React.ReactNode; alt?: string; className?: string; width?: number | string; @@ -23,6 +24,7 @@ interface ImageProps { export default function Image({ src, + children, alt, className, width, @@ -171,6 +173,7 @@ export default function Image({ isLoaded ? "opacity-100" : "opacity-0" )} /> + {children} ); } diff --git a/src/hooks/useDragAndDrop.ts b/src/hooks/useDragAndDrop.ts index a0f4d82..56575eb 100644 --- a/src/hooks/useDragAndDrop.ts +++ b/src/hooks/useDragAndDrop.ts @@ -18,6 +18,7 @@ export function useDragAndDrop() { dragData: null, mousePosition: { x: 0, y: 0 }, }); + const isDraggingRef = useRef(false); // 开始拖拽 const startDrag = useCallback((data: T, event?: React.MouseEvent) => { @@ -26,12 +27,14 @@ export function useDragAndDrop() { event.stopPropagation(); // 立即获取鼠标初始位置 + isDraggingRef.current = true; setState({ isDragging: true, dragData: data, mousePosition: { x: event.clientX, y: event.clientY }, }); } else { + isDraggingRef.current = true; setState({ isDragging: true, dragData: data, @@ -42,6 +45,7 @@ export function useDragAndDrop() { // 结束拖拽 const endDrag = useCallback(() => { + isDraggingRef.current = false; setState({ isDragging: false, dragData: null, @@ -49,11 +53,10 @@ export function useDragAndDrop() { }); }, []); - // 监听鼠标移动 + // 监听鼠标移动 - 使用独立的事件监听器 useEffect(() => { - if (!state.isDragging) return; - const handleMouseMove = (e: MouseEvent) => { + if (!isDraggingRef.current) return; setState((prev) => ({ ...prev, mousePosition: { x: e.clientX, y: e.clientY }, @@ -65,6 +68,7 @@ export function useDragAndDrop() { // 这里只是提供一个安全机制 }; + // 始终添加事件监听器,在handleMouseMove内部检查拖拽状态 window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); @@ -72,7 +76,7 @@ export function useDragAndDrop() { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; - }, [state.isDragging]); + }, []); // 空依赖数组,只在组件挂载/卸载时执行 // 将屏幕坐标转换为缩放后的页面坐标 const screenToScaledCoords = useCallback( @@ -131,7 +135,8 @@ export function useDropZone() { mouseY >= rect.top && mouseY <= rect.bottom; - setIsOver(over); + // 只有当状态真正改变时才更新 + setIsOver((prev) => (prev !== over ? over : prev)); return over; }, []); diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 01cdd5a..986be39 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -24,6 +24,7 @@ export default function Home() { 开始实验按钮 diff --git a/src/pages/experiment/Step1IntroPage.tsx b/src/pages/experiment/Step1IntroPage.tsx index 5b51d68..f3900b8 100644 --- a/src/pages/experiment/Step1IntroPage.tsx +++ b/src/pages/experiment/Step1IntroPage.tsx @@ -1,4 +1,4 @@ -import { Image } from "@/components"; +import { BackgroundImage, Image } from "@/components"; import { getStaticImage } from "@/utils"; import ExperimentTitle from "./ExperimentTitle"; import { sensorImages } from "./constants"; @@ -10,34 +10,22 @@ interface Step1IntroPageProps { export default function Step1IntroPage({ onNext }: Step1IntroPageProps) { return ( <> - - - {/* img列表 */} -
- {sensorImages.map((image, index) => ( -
- -
-

{image.name}

-

{image.description}

-
-
- ))} -
- -
+ console.log("背景图片加载完成")} + onError={() => console.log("背景图片加载失败")} + > -
+ ); } diff --git a/src/pages/experiment/Step2SelectPage.tsx b/src/pages/experiment/Step2SelectPage.tsx index 6d646d6..132d511 100644 --- a/src/pages/experiment/Step2SelectPage.tsx +++ b/src/pages/experiment/Step2SelectPage.tsx @@ -1,7 +1,6 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef, useMemo, useCallback } from "react"; import { createPortal } from "react-dom"; -import { Image } from "@/components"; -import ExperimentTitle from "./ExperimentTitle"; +import { BackgroundImage, Image } from "@/components"; import { selectableTools, correctAnswers } from "./constants"; import { useDragAndDrop, useDropZone } from "@/hooks/useDragAndDrop"; import { getStaticImage } from "@/utils"; @@ -19,62 +18,125 @@ interface Step2SelectPageProps { onNext?: () => void; } -export default function Step2SelectPage({ onNext }: Step2SelectPageProps) { - const [selectedTools, setSelectedTools] = useState([]); - const [isSubmitted, setIsSubmitted] = useState(false); - const [isCorrect, setIsCorrect] = useState(false); - const { - isDragging, - dragData, - mousePosition, - startDrag, - endDrag, - isPointInElement, - } = useDragAndDrop(); - const { dropZoneRef, isOver, checkIsOver } = useDropZone(); +// ==================== 常量定义 ==================== +const MAX_TOOLS = 4; // 最大选择工具数量 +const TOOL_GRID_COLS = 3; // 左侧工具网格列数 +const SELECTED_GRID_COLS = 2; // 右侧已选工具网格列数 +export default function Step2SelectPage({ + onNext: _onNext, +}: Step2SelectPageProps) { + // ==================== 状态管理 ==================== + const [selectedTools, setSelectedTools] = useState([]); // 已选择的工具列表 + const [isSubmitted, setIsSubmitted] = useState(false); // 是否已提交 + const [isCorrect, setIsCorrect] = useState(false); // 答案是否正确 + + // ==================== 引用和Hook ==================== + const mousePositionRef = useRef({ x: 0, y: 0 }); // 鼠标位置缓存 + // 拖拽功能Hook + const { + isDragging, // 是否正在拖拽 + dragData, // 拖拽的数据 + mousePosition, // 鼠标位置 + startDrag, // 开始拖拽 + endDrag, // 结束拖拽 + isPointInElement, // 检查点是否在元素内 + } = useDragAndDrop(); + + // 放置区域Hook + const { dropZoneRef, isOver, checkIsOver } = useDropZone(); + const checkIsOverRef = useRef(checkIsOver); // 缓存checkIsOver函数引用 + + // ==================== 计算属性 ==================== // 获取可用的工具列表(过滤掉已选择的) - const availableTools = selectableTools.filter( - (tool) => !selectedTools.some((selected) => selected.name === tool.name) + const availableTools = useMemo( + () => + selectableTools.filter( + (tool) => !selectedTools.some((selected) => selected.name === tool.name) + ), + [selectedTools] ); - // 开始拖拽工具 - const handleMouseDown = (tool: Tool, e: React.MouseEvent) => { - // 如果已经提交且答案正确,则不允许拖拽 - if (isSubmitted && isCorrect) { - return; - } - startDrag(tool, e); - }; + // ==================== 事件处理函数 ==================== + /** + * 开始拖拽工具 + * @param tool 要拖拽的工具 + * @param e 鼠标事件 + */ + const handleMouseDown = useCallback( + (tool: Tool, e: React.MouseEvent) => { + try { + // 检查是否允许拖拽 + if (isSubmitted && isCorrect) { + return; // 已提交且正确,不允许拖拽 + } + if (selectedTools.length >= MAX_TOOLS) { + return; // 已达到最大数量限制 + } + + // 验证工具数据有效性 + if (!tool || !tool.src || !tool.name) { + console.warn("Invalid tool data:", tool); + return; + } + + startDrag(tool, e); + } catch (error) { + console.error("Error in handleMouseDown:", error); + } + }, + [isSubmitted, isCorrect, selectedTools.length, startDrag] + ); + + // ==================== 副作用处理 ==================== + // 更新checkIsOver函数引用 + useEffect(() => { + checkIsOverRef.current = checkIsOver; + }, [checkIsOver]); // 监听鼠标位置,检查是否在放置区域上 useEffect(() => { if (isDragging) { - checkIsOver(mousePosition.x, mousePosition.y); + mousePositionRef.current = { x: mousePosition.x, y: mousePosition.y }; + checkIsOverRef.current(mousePosition.x, mousePosition.y); } - }, [isDragging, mousePosition, checkIsOver]); + }, [isDragging, mousePosition.x, mousePosition.y]); // 监听鼠标释放,处理拖拽结束 useEffect(() => { if (!isDragging) return; const handleMouseUp = () => { + // 检查是否在放置区域内 if ( dragData && - isPointInElement(mousePosition.x, mousePosition.y, dropZoneRef.current) + isPointInElement( + mousePositionRef.current.x, + mousePositionRef.current.y, + dropZoneRef.current + ) ) { - // 检查是否已经添加过这个工具 - const isAlreadyAdded = selectedTools.some( - (tool) => tool.name === dragData.name - ); + // 添加工具到选择列表 + setSelectedTools((prev) => { + const isAlreadyAdded = prev.some( + (tool) => tool.name === dragData.name + ); - if (!isAlreadyAdded) { - const newTool: SelectedTool = { - ...dragData, - id: `selected-${Date.now()}`, - }; - setSelectedTools([...selectedTools, newTool]); - } + // 检查数量限制 + if (prev.length >= MAX_TOOLS) { + return prev; // 已达到最大数量,不添加新工具 + } + + // 添加新工具 + if (!isAlreadyAdded) { + const newTool: SelectedTool = { + ...dragData, + id: `selected-${Date.now()}`, + }; + return [...prev, newTool]; + } + return prev; + }); } endDrag(); }; @@ -85,241 +147,300 @@ export default function Step2SelectPage({ onNext }: Step2SelectPageProps) { isDragging, dragData, mousePosition, - selectedTools, endDrag, isPointInElement, dropZoneRef, ]); - // 清空选择 - const handleClear = () => { + /** + * 清空所有选择 + */ + const handleClear = useCallback(() => { setSelectedTools([]); setIsSubmitted(false); setIsCorrect(false); - }; + }, []); - // 提交选择 - const handleSubmit = () => { - if (selectedTools.length === 0) { - // alert("请至少选择一个工具!"); - return; - } + /** + * 提交选择并验证答案 + */ + const handleSubmit = useCallback(() => { + try { + if (selectedTools.length === 0) { + return; // 没有选择任何工具 + } - // 验证答案 - const selectedNames = selectedTools.map((tool) => tool.name); + // 获取选择的工具名称 + const selectedNames = selectedTools.map((tool) => tool.name); - // 检查数量是否一致 - if (selectedNames.length !== correctAnswers.length) { + // 检查数量是否一致 + if (selectedNames.length !== correctAnswers.length) { + setIsSubmitted(true); + setIsCorrect(false); + return; + } + + // 检查每个选择的工具是否都在正确答案中 + const allCorrect = selectedNames.every((name) => + correctAnswers.includes(name) + ); + + setIsSubmitted(true); + setIsCorrect(allCorrect); + } catch (error) { + console.error("Error in handleSubmit:", error); setIsSubmitted(true); setIsCorrect(false); - return; } + }, [selectedTools]); - // 检查每个选择的工具是否都在正确答案中 - const allCorrect = selectedNames.every((name) => - correctAnswers.includes(name) - ); + /** + * 删除已选择的工具 + * @param id 工具ID + */ + const handleRemoveTool = useCallback( + (id: string) => { + try { + // 检查是否允许删除 + if (isSubmitted && isCorrect) { + return; // 已提交且正确,不允许删除 + } - setIsSubmitted(true); - setIsCorrect(allCorrect); - }; + // 验证ID有效性 + if (!id || typeof id !== "string") { + console.warn("Invalid tool ID:", id); + return; + } - // 删除已选工具 - const handleRemoveTool = (id: string) => { - // 如果已经提交且答案正确,则不允许删除 - if (isSubmitted && isCorrect) { - return; - } - setSelectedTools(selectedTools.filter((tool) => tool.id !== id)); - }; + setSelectedTools((prev) => prev.filter((tool) => tool.id !== id)); + } catch (error) { + console.error("Error in handleRemoveTool:", error); + } + }, + [isSubmitted, isCorrect] + ); return ( <> - + + {/* ==================== 主要内容区域 ==================== */} +
+ {/* ==================== 左侧:可拖拽工具列表 ==================== */} +
+
+ {availableTools.length === 0 ? ( + // 空状态:所有工具已选择完毕 +
+
🎯
+
+ 所有工具已选择完毕 +
+
请检查右侧选择区域
+
+ ) : ( +
+ {availableTools.map((tool, index) => { + const isBeingDragged = + isDragging && dragData?.name === tool.name; + const isDragDisabled = + (isSubmitted && isCorrect) || + selectedTools.length >= MAX_TOOLS; - {/* 主要内容区域 */} -
- {/* 左侧:可拖拽选项 */} -
-
-

可拖拽选项

-
-
- {availableTools.length === 0 ? ( -
- 所有工具已选择完毕 -
- ) : ( -
- {availableTools.map((tool, index) => { - const isBeingDragged = - isDragging && dragData?.name === tool.name; - const isDragDisabled = isSubmitted && isCorrect; - - return ( -
handleMouseDown(tool, e)} - className={`flex flex-col items-center transition-transform select-none ${ - isDragDisabled - ? "cursor-not-allowed opacity-50" - : "cursor-move hover:scale-105" - } ${isBeingDragged ? "opacity-30" : ""}`} - > -
- + return ( +
handleMouseDown(tool, e)} + className={`flex flex-col items-center transition-all duration-200 select-none p-4 rounded-lg ${ + isDragDisabled + ? "cursor-not-allowed opacity-50 grayscale" + : "cursor-move hover:scale-105 hover:shadow-lg hover:bg-white/10" + } ${isBeingDragged ? "opacity-30 scale-95" : ""}`} + > + +

+ {tool.name} +

+ ); + })} +
+ )} +
+
+ + {/* ==================== 右侧:实验工具放置区域 ==================== */} +
+
+ {/* ==================== 状态显示区域 ==================== */} + {isSubmitted ? ( + // 提交后的结果显示 +
+ {isCorrect ? ( + // 答案正确 + <> +

+ 选择正确,继续实验吧 +

+ + ) : ( + // 答案错误 + <> +

+ 选择错误,请重新选择 +

+
+

+ 答案提示 +

+

+ {correctAnswers.join("、")} +

+
+ + )} +
+ ) : selectedTools.length === 0 ? ( + // 空状态:未选择任何工具 +
+
📦
+
拖拽工具到这里
+
+ 从左侧选择最多{MAX_TOOLS}个工具 +
+
+ ) : selectedTools.length >= MAX_TOOLS ? ( + // 达到最大数量限制 +
+
+ {selectedTools.map((tool) => ( +
+ +

+ {tool.name} +

+ {/* 删除按钮 - 答案正确后隐藏 */} + {!(isSubmitted && isCorrect) && ( + + )} +
+ ))} +
+

+ 已选择{MAX_TOOLS}个工具,达到最大数量限制 +

+
+ ) : ( + // 正常状态:显示已选择的工具 +
+ {selectedTools.map((tool) => ( +
+

{tool.name}

+ {/* 删除按钮 - 答案正确后隐藏 */} + {!(isSubmitted && isCorrect) && ( + + )}
- ); - })} -
- )} -
-
- - {/* 右侧:实验工具区域 */} -
-
-

实验工具区域

-
-
- {/* 提交后的结果显示 */} - {isSubmitted ? ( -
- {isCorrect ? ( - <> -

- 选择正确,继续实验吧 -

- - ) : ( - <> -

- 选择错误,请重新选择 -

-
-

- 答案提示 -

-

- {correctAnswers.join("、")} -

-
- - )} -
- ) : selectedTools.length === 0 ? ( -
- 拖拽工具到这里 -
- ) : ( -
- {selectedTools.map((tool) => ( -
-
- -
-

- {tool.name} -

- {/* 删除按钮 - 答案正确后隐藏 */} - {!(isSubmitted && isCorrect) && ( - - )} -
- ))} -
- )} -
- - {/* 底部按钮 */} - {!isSubmitted || !isCorrect ? ( -
- - + ))} +
+ )}
- ) : null} -
-
- {/* 底部提示文字或下一步按钮 */} -
- {isSubmitted && isCorrect ? ( + {/* ==================== 底部操作按钮 ==================== */} + {!isSubmitted || !isCorrect ? ( +
+ + +
+ ) : null} +
+
+ + {/* ==================== 下一步按钮 ==================== */} + {isSubmitted && isCorrect && ( - ) : ( - // -

- 通过拖拽的方式,将正确选择拖拽到实验工具区域内吧。 -

)} -
- {/* 拖拽预览 - 使用 Portal 渲染到 body,避免受 scale 影响 */} - {isDragging && - dragData && - createPortal( -
-
- -

- {dragData.name} -

-
-
, - document.body - )} + {/* ==================== 拖拽预览 ==================== */} + {/* 使用 Portal 渲染到 body,避免受 scale 影响 */} + {isDragging && + dragData && + createPortal( +
+
+ +
+ {dragData.name} +
+
+
, + document.body + )} + + {/* */} ); } diff --git a/src/pages/experiment/Step3ConfigPage.tsx b/src/pages/experiment/Step3ConfigPage.tsx index 88b32c3..b582207 100644 --- a/src/pages/experiment/Step3ConfigPage.tsx +++ b/src/pages/experiment/Step3ConfigPage.tsx @@ -1,6 +1,5 @@ -import { Image } from "@/components"; +import { BackgroundImage, Image } from "@/components"; import { getStaticImage } from "@/utils"; -import ExperimentTitle from "./ExperimentTitle"; interface Step3ConfigPageProps { onNext?: () => void; @@ -9,94 +8,36 @@ interface Step3ConfigPageProps { export default function Step3ConfigPage({ onNext }: Step3ConfigPageProps) { return ( <> - - - {/* 主要内容区域 */} -
-
-

- 环境配置步骤 -

- -
-
-
- 1 -
-
-

- 安装 Keil 软件 -

-

- 下载并安装 Keil μVision IDE,用于编写和编译 STM32 程序。 -

-
-
- -
-
- 2 -
-
-

- 连接 STM32 主控板 -

-

- 使用烧录线将 STM32 主控板连接到电脑,确保驱动正常安装。 -

-
-
- -
-
- 3 -
-
-

- 配置传感器连接 -

-

- 将力敏传感器正确连接到 STM32 主控板的指定引脚。 -

-
-
- -
-
- 4 -
-
-

- 烧录程序 -

-

- 在 Keil 中编译程序,并通过烧录线将程序下载到 STM32 主控板。 -

-
-
-
+ + +
+ + {/* 文字提示 */} +
+

+ 打开力敏传感器 + 文件夹 + USER + 子文件夹,找到并打开 + + 工程文件LED.uvprojx + + 。 +

+

+ 这一步骤是启动实验项目的基础,确保工程文件能够正确加载。 +

-
- - {/* 底部提示和下一步按钮 */} -
-

- 完成以上步骤后,即可进入下一阶段的实验操作。 -

-
- - {onNext && ( -
- -
- )} + ); } diff --git a/src/pages/experiment/constants.ts b/src/pages/experiment/constants.ts index 4ad3091..0cc18f9 100644 --- a/src/pages/experiment/constants.ts +++ b/src/pages/experiment/constants.ts @@ -55,7 +55,7 @@ export const selectableTools = [ name: "STM32主控板", }, { - src: getStaticImage("experiment/serial-cable.png"), + src: getStaticImage("experiment/type-c.png"), name: "TYPE-C", }, { @@ -67,7 +67,7 @@ export const selectableTools = [ name: "烧录线", }, { - src: getStaticImage("experiment/wireless.png"), + src: getStaticImage("experiment/arduino.png"), name: "Arduino开发板", }, ]; diff --git a/src/pages/experiment/index.tsx b/src/pages/experiment/index.tsx index 54b7e48..d055ade 100644 --- a/src/pages/experiment/index.tsx +++ b/src/pages/experiment/index.tsx @@ -1,24 +1,42 @@ import { BackgroundImage } from "@/components"; import { getStaticImage } from "@/utils"; -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; import Step1IntroPage from "./Step1IntroPage"; import Step2SelectPage from "./Step2SelectPage"; import Step3ConfigPage from "./Step3ConfigPage"; export default function Experiment() { - const [step, setStep] = useState(1); // 1: 工具介绍, 2: 工具选择, 3: 实验环境配置 + const { step: stepParam } = useParams<{ step?: string }>(); + const navigate = useNavigate(); + + // 从路由参数获取step,默认为1 + const currentStep = stepParam ? parseInt(stepParam, 10) : 1; + + // 验证step参数的有效性 + const validStep = currentStep >= 1 && currentStep <= 3 ? currentStep : 1; + + // 如果step参数无效,重定向到默认step + useEffect(() => { + if (stepParam && (currentStep < 1 || currentStep > 3)) { + navigate("/experiment/1", { replace: true }); + } + }, [stepParam, currentStep, navigate]); + + // 处理步骤切换 + const handleNext = (nextStep: number) => { + if (nextStep <= 3) { + navigate(`/experiment/${nextStep}`); + } + }; return ( - -
- {step === 1 && setStep(2)} />} - {step === 2 && setStep(3)} />} - {step === 3 && setStep(4)} />} +
+
+ {validStep === 1 && handleNext(2)} />} + {validStep === 2 && handleNext(3)} />} + {validStep === 3 && handleNext(4)} />}
- +
); } diff --git a/src/router/index.tsx b/src/router/index.tsx index 2123917..b913756 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -36,6 +36,10 @@ const router = createBrowserRouter([ path: "/experiment", element: , }, + { + path: "/experiment/:step", + element: , + }, { path: "*", element: ,