refactor: update project structure and content
No files matched your search
@@ -1,10 +1,10 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>传感器及检测技术 - 实验课件系统</title>
|
<title>南湖名师/技能大师工作室</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "sensor-lab",
|
"name": "master-teacher-studio",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 963 KiB |
|
Before Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 311 KiB |
|
Before Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 556 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 2.9 MiB |
@@ -1,5 +1,4 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import initialPageImage from "@/assets/images/初始页.png";
|
|
||||||
import { BackgroundImage, Image } from "@/components";
|
import { BackgroundImage, Image } from "@/components";
|
||||||
import { getStaticImage } from "@/utils";
|
import { getStaticImage } from "@/utils";
|
||||||
|
|
||||||
@@ -8,22 +7,19 @@ export default function Home() {
|
|||||||
|
|
||||||
const handleStartExperiment = () => {
|
const handleStartExperiment = () => {
|
||||||
console.log("点击开启实验");
|
console.log("点击开启实验");
|
||||||
navigate("/experiment");
|
navigate("/menu");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BackgroundImage
|
<BackgroundImage
|
||||||
src={initialPageImage}
|
src={getStaticImage("home/进入页.png")}
|
||||||
alt="传感器实验首页"
|
|
||||||
className="h-full w-full relative"
|
className="h-full w-full relative"
|
||||||
backgroundSize="cover"
|
backgroundSize="cover"
|
||||||
backgroundPosition="center"
|
backgroundPosition="center"
|
||||||
onLoad={() => console.log("背景图片加载完成")}
|
|
||||||
onError={() => console.log("背景图片加载失败")}
|
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={getStaticImage("home/按钮-开启实验.png")}
|
src={getStaticImage("components/按钮-进入.png")}
|
||||||
alt="开始实验按钮"
|
alt="进入"
|
||||||
button
|
button
|
||||||
className="absolute bottom-10 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
className="absolute bottom-10 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||||
onClick={handleStartExperiment}
|
onClick={handleStartExperiment}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Outlet, Link } from 'react-router-dom';
|
|
||||||
import { Cpu } from 'lucide-react';
|
|
||||||
|
|
||||||
export default function Layout() {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
<header className="text-center mb-12">
|
|
||||||
<Link to="/" className="inline-block">
|
|
||||||
<div className="flex items-center justify-center gap-3 mb-4 hover:opacity-80 transition-opacity">
|
|
||||||
<Cpu className="w-10 h-10 text-indigo-600" />
|
|
||||||
<h1 className="text-4xl font-bold text-gray-800">
|
|
||||||
传感器及检测技术
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<p className="text-gray-600 text-lg">实验课件交互式平台</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<Outlet />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer className="text-center text-gray-600 text-sm mt-12">
|
|
||||||
<p>使用现代化技术构建 • React + Vite + Tailwind CSS</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { BackgroundImage, Image } from "@/components";
|
||||||
|
import { getStaticImage } from "@/utils";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleStartExperiment = () => {
|
||||||
|
console.log("点击开启实验");
|
||||||
|
// navigate("/experiment");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BackgroundImage
|
||||||
|
src={getStaticImage("menu/首页背景.png")}
|
||||||
|
className="h-full w-full relative"
|
||||||
|
backgroundSize="cover"
|
||||||
|
backgroundPosition="center"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={getStaticImage("components/按钮-返回.png")}
|
||||||
|
alt="返回"
|
||||||
|
button
|
||||||
|
className="absolute top-0 right-0"
|
||||||
|
onClick={handleStartExperiment}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundImage
|
||||||
|
src={getStaticImage("menu/转盘合.png")}
|
||||||
|
className="absolute w-full h-500 -bottom-1/5 left-0 pointer-events-none"
|
||||||
|
backgroundSize="cover"
|
||||||
|
backgroundPosition="center"
|
||||||
|
></BackgroundImage>
|
||||||
|
</BackgroundImage>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
interface ExperimentTitleProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ExperimentTitle({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
}: ExperimentTitleProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* 标题 */}
|
|
||||||
<div className="w-full relative pb-20">
|
|
||||||
{/* 主标题容器 */}
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
{/* 主标题 */}
|
|
||||||
<div className="bg-white border-4 border-blue-500 rounded-2xl px-10 py-3 shadow-md">
|
|
||||||
<h1 className="text-3xl font-bold text-blue-400 italic tracking-wide">
|
|
||||||
{title}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 副标题 */}
|
|
||||||
<div className="flex-1 relative pt-6">
|
|
||||||
<p className="text-gray-600 text-3xl relative z-10">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 使用 SVG 绘制更精确的装饰线 */}
|
|
||||||
<svg
|
|
||||||
className="absolute top-14 w-full h-20"
|
|
||||||
viewBox="0 0 1400 60"
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
>
|
|
||||||
{/* 左侧水平线 */}
|
|
||||||
<line
|
|
||||||
x1="10"
|
|
||||||
y1="30"
|
|
||||||
x2="150"
|
|
||||||
y2="30"
|
|
||||||
stroke="#3b82f6"
|
|
||||||
strokeWidth="3"
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
{/* 斜线(从左到右,向下倾斜)*/}
|
|
||||||
<line
|
|
||||||
x1="150"
|
|
||||||
y1="30"
|
|
||||||
x2="200"
|
|
||||||
y2="60"
|
|
||||||
stroke="#3b82f6"
|
|
||||||
strokeWidth="3"
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
{/* 右侧水平线(与左侧在同一高度)*/}
|
|
||||||
<line
|
|
||||||
x1="250"
|
|
||||||
y1="30"
|
|
||||||
x2="1800"
|
|
||||||
y2="30"
|
|
||||||
stroke="#3b82f6"
|
|
||||||
strokeWidth="3"
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
# 实验页面模块结构
|
|
||||||
|
|
||||||
## 文件组织
|
|
||||||
|
|
||||||
```
|
|
||||||
src/pages/experiment/
|
|
||||||
├── index.tsx # 主入口文件,管理步骤状态和路由
|
|
||||||
├── Step1IntroPage.tsx # 第一步:实验工具介绍页面
|
|
||||||
├── Step2SelectPage.tsx # 第二步:实验工具选择页面
|
|
||||||
├── ExperimentTitle.tsx # 可复用的标题组件
|
|
||||||
├── constants.ts # 常量数据(工具列表)
|
|
||||||
└── README.md # 本文档
|
|
||||||
```
|
|
||||||
|
|
||||||
## 各文件职责
|
|
||||||
|
|
||||||
### 📄 index.tsx
|
|
||||||
|
|
||||||
**职责**: 主入口文件,管理页面状态和步骤切换
|
|
||||||
|
|
||||||
- 管理 `step` 状态(当前在哪一步)
|
|
||||||
- 根据 step 渲染对应的页面组件
|
|
||||||
- 提供背景图片容器
|
|
||||||
|
|
||||||
**关键代码**:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const [step, setStep] = useState(1);
|
|
||||||
{
|
|
||||||
step === 1 && <Step1IntroPage onNext={() => setStep(2)} />;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
step === 2 && <Step2SelectPage />;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📄 Step1IntroPage.tsx
|
|
||||||
|
|
||||||
**职责**: 第一步 - 实验工具介绍页面
|
|
||||||
|
|
||||||
- 显示所有 8 个实验工具
|
|
||||||
- 展示每个工具的图片、名称和描述
|
|
||||||
- 提供"下一步"按钮,点击后触发 `onNext` 回调
|
|
||||||
|
|
||||||
**Props**:
|
|
||||||
|
|
||||||
- `onNext: () => void` - 点击下一步时的回调函数
|
|
||||||
|
|
||||||
**特点**:
|
|
||||||
|
|
||||||
- 4 列网格布局
|
|
||||||
- 每个工具包含图片和文字说明
|
|
||||||
- 使用 `ExperimentTitle` 组件显示标题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📄 Step2SelectPage.tsx
|
|
||||||
|
|
||||||
**职责**: 第二步 - 实验工具选择页面
|
|
||||||
|
|
||||||
- 显示左侧"可拖拽选项"区域(6 个工具)
|
|
||||||
- 显示右侧"实验工具区域"(拖放目标区)
|
|
||||||
- 提供"清空"和"提交"按钮
|
|
||||||
- 显示底部提示文字
|
|
||||||
|
|
||||||
**布局结构**:
|
|
||||||
|
|
||||||
- 左右分栏布局(flex 1:1)
|
|
||||||
- 左侧:3 列 2 行网格展示可选工具
|
|
||||||
- 右侧:空白拖放区域
|
|
||||||
- 底部:操作按钮和提示文字
|
|
||||||
|
|
||||||
**待实现功能**:
|
|
||||||
|
|
||||||
- [ ] 拖拽功能(从左侧拖到右侧)
|
|
||||||
- [ ] 清空按钮功能
|
|
||||||
- [ ] 提交按钮功能及验证逻辑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📄 ExperimentTitle.tsx
|
|
||||||
|
|
||||||
**职责**: 可复用的标题组件
|
|
||||||
|
|
||||||
- 显示主标题(带蓝色边框的圆角矩形)
|
|
||||||
- 显示副标题文字
|
|
||||||
- 显示装饰性的 SVG 线条
|
|
||||||
|
|
||||||
**Props**:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
interface ExperimentTitleProps {
|
|
||||||
title: string; // 主标题文字
|
|
||||||
description: string; // 副标题文字
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**样式特点**:
|
|
||||||
|
|
||||||
- 主标题:白色背景、蓝色边框、斜体字
|
|
||||||
- 副标题:灰色文字
|
|
||||||
- 装饰线:左侧短线 + 斜线 + 右侧长线
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📄 constants.ts
|
|
||||||
|
|
||||||
**职责**: 存储静态数据常量
|
|
||||||
|
|
||||||
**导出内容**:
|
|
||||||
|
|
||||||
1. **sensorImages** - 8 个实验工具的完整信息
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
{
|
|
||||||
src: string; // 图片路径
|
|
||||||
name: string; // 工具名称
|
|
||||||
description: string; // 工具描述
|
|
||||||
}
|
|
||||||
[];
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **selectableTools** - 6 个可选择的工具(用于第二步)
|
|
||||||
```tsx
|
|
||||||
{
|
|
||||||
src: string; // 图片路径
|
|
||||||
name: string; // 工具名称
|
|
||||||
}
|
|
||||||
[];
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 设计原则
|
|
||||||
|
|
||||||
### 单一职责原则 (SRP)
|
|
||||||
|
|
||||||
- 每个文件只负责一个功能模块
|
|
||||||
- 组件职责清晰,易于维护
|
|
||||||
|
|
||||||
### 可复用性
|
|
||||||
|
|
||||||
- `ExperimentTitle` 可在多个步骤中使用
|
|
||||||
- 数据与视图分离,便于扩展
|
|
||||||
|
|
||||||
### 数据驱动
|
|
||||||
|
|
||||||
- 使用 `constants.ts` 统一管理数据
|
|
||||||
- 通过 map 渲染列表,避免重复代码
|
|
||||||
|
|
||||||
### 可扩展性
|
|
||||||
|
|
||||||
- 易于添加新的步骤(Step3、Step4...)
|
|
||||||
- 状态管理集中在 `index.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 后续开发建议
|
|
||||||
|
|
||||||
### 短期任务
|
|
||||||
|
|
||||||
1. 实现拖拽功能(使用 HTML5 Drag and Drop API 或库)
|
|
||||||
2. 添加状态管理(已选中的工具)
|
|
||||||
3. 实现"清空"和"提交"按钮功能
|
|
||||||
4. 添加正确答案验证逻辑
|
|
||||||
|
|
||||||
### 长期优化
|
|
||||||
|
|
||||||
1. 添加动画效果(页面切换、拖拽反馈)
|
|
||||||
2. 添加错误提示和成功提示
|
|
||||||
3. 支持键盘操作(无障碍访问)
|
|
||||||
4. 添加进度指示器
|
|
||||||
5. 考虑使用状态管理库(如 Zustand)管理复杂状态
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 添加新步骤
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// 1. 创建新组件
|
|
||||||
// Step3TestPage.tsx
|
|
||||||
export default function Step3TestPage() {
|
|
||||||
return <div>第三步内容</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 在 index.tsx 中引入
|
|
||||||
import Step3TestPage from "./Step3TestPage";
|
|
||||||
|
|
||||||
// 3. 添加到渲染逻辑
|
|
||||||
{
|
|
||||||
step === 3 && <Step3TestPage />;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 修改工具数据
|
|
||||||
|
|
||||||
直接编辑 `constants.ts` 文件即可,组件会自动更新。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
- React 18
|
|
||||||
- TypeScript
|
|
||||||
- Tailwind CSS
|
|
||||||
- 自定义 Image 组件
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { BackgroundImage, Image } from "@/components";
|
|
||||||
import { getStaticImage } from "@/utils";
|
|
||||||
import ExperimentTitle from "./ExperimentTitle";
|
|
||||||
import { sensorImages } from "./constants";
|
|
||||||
|
|
||||||
interface Step1IntroPageProps {
|
|
||||||
onNext: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Step1IntroPage({ onNext }: Step1IntroPageProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BackgroundImage
|
|
||||||
src={getStaticImage("experiment/01实验工具介绍.png")}
|
|
||||||
alt="实验工具介绍背景"
|
|
||||||
className="h-full w-full relative"
|
|
||||||
backgroundSize="cover"
|
|
||||||
backgroundPosition="center"
|
|
||||||
onLoad={() => console.log("背景图片加载完成")}
|
|
||||||
onError={() => console.log("背景图片加载失败")}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="absolute bottom-0 right-6"
|
|
||||||
src={getStaticImage("components/按钮-下一步.png")}
|
|
||||||
button={true}
|
|
||||||
onClick={onNext}
|
|
||||||
/>
|
|
||||||
</BackgroundImage>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,446 +0,0 @@
|
|||||||
import { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import { BackgroundImage, Image } from "@/components";
|
|
||||||
import { selectableTools, correctAnswers } from "./constants";
|
|
||||||
import { useDragAndDrop, useDropZone } from "@/hooks/useDragAndDrop";
|
|
||||||
import { getStaticImage } from "@/utils";
|
|
||||||
|
|
||||||
interface Tool {
|
|
||||||
src: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectedTool extends Tool {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Step2SelectPageProps {
|
|
||||||
onNext?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 常量定义 ====================
|
|
||||||
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<SelectedTool[]>([]); // 已选择的工具列表
|
|
||||||
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<Tool>();
|
|
||||||
|
|
||||||
// 放置区域Hook
|
|
||||||
const { dropZoneRef, isOver, checkIsOver } = useDropZone();
|
|
||||||
const checkIsOverRef = useRef(checkIsOver); // 缓存checkIsOver函数引用
|
|
||||||
|
|
||||||
// ==================== 计算属性 ====================
|
|
||||||
// 获取可用的工具列表(过滤掉已选择的)
|
|
||||||
const availableTools = useMemo(
|
|
||||||
() =>
|
|
||||||
selectableTools.filter(
|
|
||||||
(tool) => !selectedTools.some((selected) => selected.name === tool.name)
|
|
||||||
),
|
|
||||||
[selectedTools]
|
|
||||||
);
|
|
||||||
|
|
||||||
// ==================== 事件处理函数 ====================
|
|
||||||
/**
|
|
||||||
* 开始拖拽工具
|
|
||||||
* @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) {
|
|
||||||
mousePositionRef.current = { x: mousePosition.x, y: mousePosition.y };
|
|
||||||
checkIsOverRef.current(mousePosition.x, mousePosition.y);
|
|
||||||
}
|
|
||||||
}, [isDragging, mousePosition.x, mousePosition.y]);
|
|
||||||
|
|
||||||
// 监听鼠标释放,处理拖拽结束
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isDragging) return;
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
|
||||||
// 检查是否在放置区域内
|
|
||||||
if (
|
|
||||||
dragData &&
|
|
||||||
isPointInElement(
|
|
||||||
mousePositionRef.current.x,
|
|
||||||
mousePositionRef.current.y,
|
|
||||||
dropZoneRef.current
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// 添加工具到选择列表
|
|
||||||
setSelectedTools((prev) => {
|
|
||||||
const isAlreadyAdded = prev.some(
|
|
||||||
(tool) => tool.name === dragData.name
|
|
||||||
);
|
|
||||||
|
|
||||||
// 检查数量限制
|
|
||||||
if (prev.length >= MAX_TOOLS) {
|
|
||||||
return prev; // 已达到最大数量,不添加新工具
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加新工具
|
|
||||||
if (!isAlreadyAdded) {
|
|
||||||
const newTool: SelectedTool = {
|
|
||||||
...dragData,
|
|
||||||
id: `selected-${Date.now()}`,
|
|
||||||
};
|
|
||||||
return [...prev, newTool];
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
endDrag();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("mouseup", handleMouseUp);
|
|
||||||
return () => window.removeEventListener("mouseup", handleMouseUp);
|
|
||||||
}, [
|
|
||||||
isDragging,
|
|
||||||
dragData,
|
|
||||||
mousePosition,
|
|
||||||
endDrag,
|
|
||||||
isPointInElement,
|
|
||||||
dropZoneRef,
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空所有选择
|
|
||||||
*/
|
|
||||||
const handleClear = useCallback(() => {
|
|
||||||
setSelectedTools([]);
|
|
||||||
setIsSubmitted(false);
|
|
||||||
setIsCorrect(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提交选择并验证答案
|
|
||||||
*/
|
|
||||||
const handleSubmit = useCallback(() => {
|
|
||||||
try {
|
|
||||||
if (selectedTools.length === 0) {
|
|
||||||
return; // 没有选择任何工具
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取选择的工具名称
|
|
||||||
const selectedNames = selectedTools.map((tool) => tool.name);
|
|
||||||
|
|
||||||
// 检查数量是否一致
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}, [selectedTools]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除已选择的工具
|
|
||||||
* @param id 工具ID
|
|
||||||
*/
|
|
||||||
const handleRemoveTool = useCallback(
|
|
||||||
(id: string) => {
|
|
||||||
try {
|
|
||||||
// 检查是否允许删除
|
|
||||||
if (isSubmitted && isCorrect) {
|
|
||||||
return; // 已提交且正确,不允许删除
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证ID有效性
|
|
||||||
if (!id || typeof id !== "string") {
|
|
||||||
console.warn("Invalid tool ID:", id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedTools((prev) => prev.filter((tool) => tool.id !== id));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error in handleRemoveTool:", error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isSubmitted, isCorrect]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BackgroundImage
|
|
||||||
src={getStaticImage("experiment/02实验工具选择.png")}
|
|
||||||
alt="实验工具选择背景"
|
|
||||||
className="h-full w-full relative pt-70"
|
|
||||||
backgroundSize="cover"
|
|
||||||
backgroundPosition="center"
|
|
||||||
>
|
|
||||||
{/* ==================== 主要内容区域 ==================== */}
|
|
||||||
<div className="flex gap-38 h-[580px] px-35 pr-45">
|
|
||||||
{/* ==================== 左侧:可拖拽工具列表 ==================== */}
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="h-full p-4 pt-10">
|
|
||||||
{availableTools.length === 0 ? (
|
|
||||||
// 空状态:所有工具已选择完毕
|
|
||||||
<div className="flex flex-col items-center justify-center h-full text-gray-400">
|
|
||||||
<div className="text-6xl mb-4">🎯</div>
|
|
||||||
<div className="text-xl font-semibold">
|
|
||||||
所有工具已选择完毕
|
|
||||||
</div>
|
|
||||||
<div className="text-sm mt-2">请检查右侧选择区域</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={`grid gap-4 grid-cols-${TOOL_GRID_COLS}`}>
|
|
||||||
{availableTools.map((tool, index) => {
|
|
||||||
const isBeingDragged =
|
|
||||||
isDragging && dragData?.name === tool.name;
|
|
||||||
const isDragDisabled =
|
|
||||||
(isSubmitted && isCorrect) ||
|
|
||||||
selectedTools.length >= MAX_TOOLS;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
onMouseDown={(e) => 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" : ""}`}
|
|
||||||
>
|
|
||||||
<Image src={tool.src} />
|
|
||||||
<p className="text-center text-lg font-semibold text-gray-700 mt-2">
|
|
||||||
{tool.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ==================== 右侧:实验工具放置区域 ==================== */}
|
|
||||||
<div className="w-[580px] relative">
|
|
||||||
<div
|
|
||||||
ref={dropZoneRef}
|
|
||||||
className={`h-full transition-all duration-300 rounded-lg ${
|
|
||||||
isOver
|
|
||||||
? "bg-blue-50/80 border-2 border-blue-400 border-dashed shadow-lg"
|
|
||||||
: "bg-white/5 border-2 border-transparent"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* ==================== 状态显示区域 ==================== */}
|
|
||||||
{isSubmitted ? (
|
|
||||||
// 提交后的结果显示
|
|
||||||
<div className="flex flex-col items-center justify-center h-full bg-black/80">
|
|
||||||
{isCorrect ? (
|
|
||||||
// 答案正确
|
|
||||||
<>
|
|
||||||
<p className="text-green-500 text-3xl font-bold mb-4">
|
|
||||||
选择正确,继续实验吧
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
// 答案错误
|
|
||||||
<>
|
|
||||||
<p className="text-red-500 text-3xl font-bold mb-8">
|
|
||||||
选择错误,请重新选择
|
|
||||||
</p>
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-blue-400 text-2xl font-semibold mb-4">
|
|
||||||
答案提示
|
|
||||||
</p>
|
|
||||||
<p className="text-blue-400 text-xl">
|
|
||||||
{correctAnswers.join("、")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : selectedTools.length === 0 ? (
|
|
||||||
// 空状态:未选择任何工具
|
|
||||||
<div className="flex flex-col items-center justify-center h-full text-gray-400">
|
|
||||||
<div className="text-6xl mb-4">📦</div>
|
|
||||||
<div className="text-xl font-semibold">拖拽工具到这里</div>
|
|
||||||
<div className="text-sm mt-2">
|
|
||||||
从左侧选择最多{MAX_TOOLS}个工具
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : selectedTools.length >= MAX_TOOLS ? (
|
|
||||||
// 达到最大数量限制
|
|
||||||
<div className="flex flex-col items-center justify-center h-full p-10">
|
|
||||||
<div
|
|
||||||
className={`grid gap-4 mb-4 grid-cols-${SELECTED_GRID_COLS}`}
|
|
||||||
>
|
|
||||||
{selectedTools.map((tool) => (
|
|
||||||
<div
|
|
||||||
key={tool.id}
|
|
||||||
className="flex flex-col items-center relative group p-4 rounded-lg hover:bg-white/10 transition-all duration-200"
|
|
||||||
>
|
|
||||||
<Image src={tool.src} />
|
|
||||||
<p className="text-center text-lg font-semibold text-gray-700 mt-2">
|
|
||||||
{tool.name}
|
|
||||||
</p>
|
|
||||||
{/* 删除按钮 - 答案正确后隐藏 */}
|
|
||||||
{!(isSubmitted && isCorrect) && (
|
|
||||||
<button
|
|
||||||
onClick={() => handleRemoveTool(tool.id)}
|
|
||||||
className="absolute top-2 right-2 bg-red-500 hover:bg-red-600 text-white rounded-full w-8 h-8 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-all duration-200 shadow-lg hover:scale-110"
|
|
||||||
title="删除"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<p className="text-orange-500 text-2xl font-semibold text-center">
|
|
||||||
已选择{MAX_TOOLS}个工具,达到最大数量限制
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
// 正常状态:显示已选择的工具
|
|
||||||
<div
|
|
||||||
className={`grid gap-4 p-10 grid-cols-${SELECTED_GRID_COLS}`}
|
|
||||||
>
|
|
||||||
{selectedTools.map((tool) => (
|
|
||||||
<div
|
|
||||||
key={tool.id}
|
|
||||||
className="flex flex-col items-center relative group p-4 rounded-lg hover:bg-white/10 transition-all duration-200"
|
|
||||||
>
|
|
||||||
<Image src={tool.src} />
|
|
||||||
<p className="text-center text-lg font-semibold text-gray-700 mt-2">
|
|
||||||
{tool.name}
|
|
||||||
</p>
|
|
||||||
{/* 删除按钮 - 答案正确后隐藏 */}
|
|
||||||
{!(isSubmitted && isCorrect) && (
|
|
||||||
<button
|
|
||||||
onClick={() => handleRemoveTool(tool.id)}
|
|
||||||
className="absolute top-2 right-2 bg-red-500 hover:bg-red-600 text-white rounded-full w-8 h-8 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-lg"
|
|
||||||
title="删除"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ==================== 底部操作按钮 ==================== */}
|
|
||||||
{!isSubmitted || !isCorrect ? (
|
|
||||||
<div className="absolute bottom-0 left-1/2 translate-y-1/2 -translate-x-1/2 flex justify-center gap-8 mt-8">
|
|
||||||
<Image
|
|
||||||
src={getStaticImage("components/按钮-清空.png")}
|
|
||||||
button
|
|
||||||
onClick={handleClear}
|
|
||||||
/>
|
|
||||||
<Image
|
|
||||||
src={getStaticImage("components/按钮-提交.png")}
|
|
||||||
button
|
|
||||||
onClick={handleSubmit}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ==================== 下一步按钮 ==================== */}
|
|
||||||
{isSubmitted && isCorrect && (
|
|
||||||
<Image
|
|
||||||
onClick={_onNext}
|
|
||||||
className="absolute bottom-4 right-6 cursor-pointer"
|
|
||||||
src={getStaticImage("components/按钮-下一步.png")}
|
|
||||||
button={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ==================== 拖拽预览 ==================== */}
|
|
||||||
{/* 使用 Portal 渲染到 body,避免受 scale 影响 */}
|
|
||||||
{isDragging &&
|
|
||||||
dragData &&
|
|
||||||
createPortal(
|
|
||||||
<div
|
|
||||||
className="fixed pointer-events-none z-[9999]"
|
|
||||||
style={{
|
|
||||||
left: mousePosition.x,
|
|
||||||
top: mousePosition.y,
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-center opacity-90 bg-white rounded-lg shadow-2xl p-3 border border-gray-200">
|
|
||||||
<Image
|
|
||||||
src={dragData.src}
|
|
||||||
className="w-20 h-20 object-contain mx-auto"
|
|
||||||
/>
|
|
||||||
<div className="text-sm font-semibold text-gray-700 mt-2">
|
|
||||||
{dragData.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</BackgroundImage>
|
|
||||||
{/* <ExperimentTitle
|
|
||||||
title="02实验工具选择"
|
|
||||||
description="我们刚刚学习了实验所需要的所有工具,现在来手动选择一下哪些工具是需要应用在本次实验中的。"
|
|
||||||
/> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { BackgroundImage, Image } from "@/components";
|
|
||||||
import { getStaticImage } from "@/utils";
|
|
||||||
|
|
||||||
interface Step3ConfigPageProps {
|
|
||||||
onNext?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Step3ConfigPage({ onNext }: Step3ConfigPageProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BackgroundImage
|
|
||||||
src={getStaticImage("experiment/03实验环境配置底图.png")}
|
|
||||||
alt="实验工具介绍背景"
|
|
||||||
className="h-full w-full relative pt-51 pl-15"
|
|
||||||
backgroundSize="cover"
|
|
||||||
backgroundPosition="center"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={getStaticImage("experiment/1.png")}
|
|
||||||
className="relative pointer-events-none"
|
|
||||||
>
|
|
||||||
<div className="w-full h-14 absolute bottom-45 right-0 hover:bg-[#3F84C5]/50 pointer-events-auto cursor-pointer"></div>
|
|
||||||
</Image>
|
|
||||||
{/* 文字提示 */}
|
|
||||||
<div className="absolute bottom-25 left-0 text-2xl font-bold text-[#3F84C5]">
|
|
||||||
<p className=" leading-relaxed">
|
|
||||||
打开<span className="text-[#E63638] font-bold">力敏传感器</span>
|
|
||||||
文件夹
|
|
||||||
<span className="text-[#E63638] font-bold">USER</span>
|
|
||||||
子文件夹,找到并打开
|
|
||||||
<span className="text-[#E63638] font-bold">
|
|
||||||
工程文件LED.uvprojx
|
|
||||||
</span>
|
|
||||||
。
|
|
||||||
</p>
|
|
||||||
<p className=" mt-4 leading-relaxed">
|
|
||||||
这一步骤是启动实验项目的基础,确保工程文件能够正确加载。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</BackgroundImage>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import { getStaticImage } from "@/utils";
|
|
||||||
|
|
||||||
// 传感器图片数据
|
|
||||||
export const sensorImages = [
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/windows.png"),
|
|
||||||
description: "作为传感器信号的接收端",
|
|
||||||
name: "Windows电脑",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/stm32.png"),
|
|
||||||
description: "连接传感器与电脑之间的桥梁",
|
|
||||||
name: "STM32主控板",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/serial-cable.png"),
|
|
||||||
description: "连接STM32主控板与电脑",
|
|
||||||
name: "串口线",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/serial-assistant.png"),
|
|
||||||
description: "显示传感器传送到接收端的数据",
|
|
||||||
name: "串口助手",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/sensor.png"),
|
|
||||||
description: "",
|
|
||||||
name: "力敏传感器",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/flash-cable.png"),
|
|
||||||
description: "将代码烧录至STM32主控板中",
|
|
||||||
name: "烧录线",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/wireless.png"),
|
|
||||||
description: "无线的方式连接STM32与电脑",
|
|
||||||
name: "无线通信模组",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/keil.png"),
|
|
||||||
description: "电脑中配置传感器、上传代码",
|
|
||||||
name: "keil软件",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// 可选择的工具列表(用于第二步)
|
|
||||||
export const selectableTools = [
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/keil.png"),
|
|
||||||
name: "keil软件",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/stm32.png"),
|
|
||||||
name: "STM32主控板",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/type-c.png"),
|
|
||||||
name: "TYPE-C",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/sensor.png"),
|
|
||||||
name: "力敏传感器",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/flash-cable.png"),
|
|
||||||
name: "烧录线",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: getStaticImage("experiment/arduino.png"),
|
|
||||||
name: "Arduino开发板",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// 正确答案列表
|
|
||||||
export const correctAnswers = [
|
|
||||||
"keil软件",
|
|
||||||
"STM32主控板",
|
|
||||||
"力敏传感器",
|
|
||||||
"烧录线",
|
|
||||||
];
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { BackgroundImage } from "@/components";
|
|
||||||
import { getStaticImage } from "@/utils";
|
|
||||||
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: 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 (
|
|
||||||
<div className="h-full w-full">
|
|
||||||
<div className="h-full">
|
|
||||||
{validStep === 1 && <Step1IntroPage onNext={() => handleNext(2)} />}
|
|
||||||
{validStep === 2 && <Step2SelectPage onNext={() => handleNext(3)} />}
|
|
||||||
{validStep === 3 && <Step3ConfigPage onNext={() => handleNext(4)} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,13 @@
|
|||||||
import { createBrowserRouter, Navigate } from "react-router-dom";
|
import { createBrowserRouter, Navigate } from "react-router-dom";
|
||||||
import Home from "@/pages/Home";
|
import Home from "@/pages/Home";
|
||||||
import Experiment from "../pages/experiment";
|
import Menu from "@/pages/Menu";
|
||||||
import NotFound from "../pages/NotFound";
|
import NotFound from "@/pages/NotFound";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用路由配置
|
* 应用路由配置
|
||||||
* 使用 React Router v7 的 createBrowserRouter
|
* 使用 React Router v7 的 createBrowserRouter
|
||||||
*/
|
*/
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
// {
|
|
||||||
// path: '/',
|
|
||||||
// element: <Layout />,
|
|
||||||
// errorElement: <NotFound />,
|
|
||||||
// children: [
|
|
||||||
// {
|
|
||||||
// index: true,
|
|
||||||
// element: <Home />,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: 'experiment/:id',
|
|
||||||
// element: <ExperimentDetail />,
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <Navigate to="/home" replace />,
|
element: <Navigate to="/home" replace />,
|
||||||
@@ -33,12 +18,8 @@ const router = createBrowserRouter([
|
|||||||
element: <Home />,
|
element: <Home />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/experiment",
|
path: "/menu",
|
||||||
element: <Experiment />,
|
element: <Menu />,
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/experiment/:step",
|
|
||||||
element: <Experiment />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "*",
|
path: "*",
|
||||||
|
|||||||