fix:解决拖拽问题
This commit is contained in:
1 parent
ce06ad0214
commit
6367014e1f
34 files changed
+1712
-397
No files matched your search
@@ -0,0 +1,71 @@
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
# 实验页面模块结构
|
||||
|
||||
## 文件组织
|
||||
|
||||
```
|
||||
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 组件
|
||||
@@ -0,0 +1,43 @@
|
||||
import { 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 (
|
||||
<>
|
||||
<ExperimentTitle
|
||||
title="01实验工具介绍"
|
||||
description="以下是实验所需要的所有工具,让我们来看看具体有哪些吧"
|
||||
/>
|
||||
|
||||
{/* img列表 */}
|
||||
<div className="grid grid-cols-4 gap-y-20 gap-x-20 mt-10">
|
||||
{sensorImages.map((image, index) => (
|
||||
<div key={index} className="flex flex-col items-center">
|
||||
<Image
|
||||
src={image.src}
|
||||
className="w-80 h-50 rounded-lg shadow-md mb-4 object-cover"
|
||||
/>
|
||||
<div className="text-center text-xl">
|
||||
<h3 className=" font-bold text-gray-800 mb-1">{image.name}</h3>
|
||||
<p className=" text-gray-600">{image.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div onClick={onNext}>
|
||||
<Image
|
||||
className="absolute bottom-4 right-6 cursor-pointer"
|
||||
src={getStaticImage("components/按钮-下一步.png")}
|
||||
button={true}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Image } from "@/components";
|
||||
import ExperimentTitle from "./ExperimentTitle";
|
||||
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;
|
||||
}
|
||||
|
||||
export default function Step2SelectPage({ onNext }: Step2SelectPageProps) {
|
||||
const [selectedTools, setSelectedTools] = useState<SelectedTool[]>([]);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
const [isCorrect, setIsCorrect] = useState(false);
|
||||
const {
|
||||
isDragging,
|
||||
dragData,
|
||||
mousePosition,
|
||||
startDrag,
|
||||
endDrag,
|
||||
isPointInElement,
|
||||
} = useDragAndDrop<Tool>();
|
||||
const { dropZoneRef, isOver, checkIsOver } = useDropZone();
|
||||
|
||||
// 获取可用的工具列表(过滤掉已选择的)
|
||||
const availableTools = selectableTools.filter(
|
||||
(tool) => !selectedTools.some((selected) => selected.name === tool.name)
|
||||
);
|
||||
|
||||
// 开始拖拽工具
|
||||
const handleMouseDown = (tool: Tool, e: React.MouseEvent) => {
|
||||
// 如果已经提交且答案正确,则不允许拖拽
|
||||
if (isSubmitted && isCorrect) {
|
||||
return;
|
||||
}
|
||||
startDrag(tool, e);
|
||||
};
|
||||
|
||||
// 监听鼠标位置,检查是否在放置区域上
|
||||
useEffect(() => {
|
||||
if (isDragging) {
|
||||
checkIsOver(mousePosition.x, mousePosition.y);
|
||||
}
|
||||
}, [isDragging, mousePosition, checkIsOver]);
|
||||
|
||||
// 监听鼠标释放,处理拖拽结束
|
||||
useEffect(() => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (
|
||||
dragData &&
|
||||
isPointInElement(mousePosition.x, mousePosition.y, dropZoneRef.current)
|
||||
) {
|
||||
// 检查是否已经添加过这个工具
|
||||
const isAlreadyAdded = selectedTools.some(
|
||||
(tool) => tool.name === dragData.name
|
||||
);
|
||||
|
||||
if (!isAlreadyAdded) {
|
||||
const newTool: SelectedTool = {
|
||||
...dragData,
|
||||
id: `selected-${Date.now()}`,
|
||||
};
|
||||
setSelectedTools([...selectedTools, newTool]);
|
||||
}
|
||||
}
|
||||
endDrag();
|
||||
};
|
||||
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
return () => window.removeEventListener("mouseup", handleMouseUp);
|
||||
}, [
|
||||
isDragging,
|
||||
dragData,
|
||||
mousePosition,
|
||||
selectedTools,
|
||||
endDrag,
|
||||
isPointInElement,
|
||||
dropZoneRef,
|
||||
]);
|
||||
|
||||
// 清空选择
|
||||
const handleClear = () => {
|
||||
setSelectedTools([]);
|
||||
setIsSubmitted(false);
|
||||
setIsCorrect(false);
|
||||
};
|
||||
|
||||
// 提交选择
|
||||
const handleSubmit = () => {
|
||||
if (selectedTools.length === 0) {
|
||||
// alert("请至少选择一个工具!");
|
||||
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);
|
||||
};
|
||||
|
||||
// 删除已选工具
|
||||
const handleRemoveTool = (id: string) => {
|
||||
// 如果已经提交且答案正确,则不允许删除
|
||||
if (isSubmitted && isCorrect) {
|
||||
return;
|
||||
}
|
||||
setSelectedTools(selectedTools.filter((tool) => tool.id !== id));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExperimentTitle
|
||||
title="02实验工具选择"
|
||||
description="我们刚刚学习了实验所需要的所有工具,现在来手动选择一下哪些工具是需要应用在本次实验中的。"
|
||||
/>
|
||||
|
||||
{/* 主要内容区域 */}
|
||||
<div className="flex gap-8 mt-10">
|
||||
{/* 左侧:可拖拽选项 */}
|
||||
<div className="flex-1">
|
||||
<div className="bg-[#3d98cd] text-white text-center py-3 rounded-t-lg">
|
||||
<h2 className="text-2xl font-bold">可拖拽选项</h2>
|
||||
</div>
|
||||
<div className="backdrop-blur-sm border-2 border-[#3d98cd] rounded-b-lg p-6 min-h-[500px]">
|
||||
{availableTools.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full text-gray-400 text-xl">
|
||||
所有工具已选择完毕
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-3 gap-6">
|
||||
{availableTools.map((tool, index) => {
|
||||
const isBeingDragged =
|
||||
isDragging && dragData?.name === tool.name;
|
||||
const isDragDisabled = isSubmitted && isCorrect;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
onMouseDown={(e) => 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" : ""}`}
|
||||
>
|
||||
<div className="rounded-lg p-4 w-full">
|
||||
<Image
|
||||
src={tool.src}
|
||||
className="w-full h-32 object-contain mb-2 pointer-events-none"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-center text-lg font-semibold text-gray-700 mt-2">
|
||||
{tool.name}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧:实验工具区域 */}
|
||||
<div className="flex-1 relative">
|
||||
<div className="bg-[#3d98cd] text-white text-center py-3 rounded-t-lg">
|
||||
<h2 className="text-2xl font-bold">实验工具区域</h2>
|
||||
</div>
|
||||
<div
|
||||
ref={dropZoneRef}
|
||||
className={`backdrop-blur-sm border-2 border-[#3d98cd] rounded-b-lg p-6 min-h-[500px] transition-colors ${
|
||||
isOver ? "bg-blue-50 border-blue-400" : ""
|
||||
}`}
|
||||
>
|
||||
{/* 提交后的结果显示 */}
|
||||
{isSubmitted ? (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
{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 items-center justify-center h-full text-gray-400 text-xl">
|
||||
拖拽工具到这里
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-3 gap-6">
|
||||
{selectedTools.map((tool) => (
|
||||
<div
|
||||
key={tool.id}
|
||||
className="flex flex-col items-center relative group"
|
||||
>
|
||||
<div className="rounded-lg p-4 w-full">
|
||||
<Image
|
||||
src={tool.src}
|
||||
className="w-full h-32 object-contain mb-2"
|
||||
/>
|
||||
</div>
|
||||
<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">
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="bg-gray-400 hover:bg-gray-500 text-white px-12 py-4 rounded-full text-2xl font-bold shadow-lg transition-colors"
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="bg-[#3d98cd] hover:bg-[#2d7aad] text-white px-12 py-4 rounded-full text-2xl font-bold shadow-lg transition-colors"
|
||||
>
|
||||
提交
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部提示文字或下一步按钮 */}
|
||||
<div className="text-center mt-20">
|
||||
{isSubmitted && isCorrect ? (
|
||||
<Image
|
||||
onClick={onNext}
|
||||
className="absolute bottom-4 right-6 cursor-pointer"
|
||||
src={getStaticImage("components/按钮-下一步.png")}
|
||||
button={true}
|
||||
/>
|
||||
) : (
|
||||
// <button
|
||||
// className="bg-blue-400 hover:bg-blue-500 text-white px-20 py-5 rounded-full text-3xl font-bold shadow-2xl transition-all hover:scale-105"
|
||||
// >
|
||||
// 下一步
|
||||
// </button>
|
||||
<p className="text-blue-400 text-3xl font-semibold">
|
||||
通过拖拽的方式,将正确选择拖拽到实验工具区域内吧。
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 拖拽预览 - 使用 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="flex flex-col items-center opacity-80 bg-white rounded-lg shadow-xl p-4">
|
||||
<Image src={dragData.src} className="w-24 h-24 object-contain" />
|
||||
<p className="text-sm font-semibold text-gray-700 mt-2">
|
||||
{dragData.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Image } from "@/components";
|
||||
import { getStaticImage } from "@/utils";
|
||||
import ExperimentTitle from "./ExperimentTitle";
|
||||
|
||||
interface Step3ConfigPageProps {
|
||||
onNext?: () => void;
|
||||
}
|
||||
|
||||
export default function Step3ConfigPage({ onNext }: Step3ConfigPageProps) {
|
||||
return (
|
||||
<>
|
||||
<ExperimentTitle
|
||||
title="03实验环境配置"
|
||||
description="配置实验所需的软件和硬件环境,确保实验能够顺利进行。"
|
||||
/>
|
||||
|
||||
{/* 主要内容区域 */}
|
||||
<div className="mt-10">
|
||||
<div className="bg-white/80 backdrop-blur-sm rounded-lg p-8 shadow-lg">
|
||||
<h3 className="text-2xl font-bold text-gray-800 mb-6">
|
||||
环境配置步骤
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-[#3d98cd] text-white rounded-full flex items-center justify-center font-bold text-lg">
|
||||
1
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-xl font-semibold text-gray-800 mb-2">
|
||||
安装 Keil 软件
|
||||
</h4>
|
||||
<p className="text-gray-600">
|
||||
下载并安装 Keil μVision IDE,用于编写和编译 STM32 程序。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-[#3d98cd] text-white rounded-full flex items-center justify-center font-bold text-lg">
|
||||
2
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-xl font-semibold text-gray-800 mb-2">
|
||||
连接 STM32 主控板
|
||||
</h4>
|
||||
<p className="text-gray-600">
|
||||
使用烧录线将 STM32 主控板连接到电脑,确保驱动正常安装。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-[#3d98cd] text-white rounded-full flex items-center justify-center font-bold text-lg">
|
||||
3
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-xl font-semibold text-gray-800 mb-2">
|
||||
配置传感器连接
|
||||
</h4>
|
||||
<p className="text-gray-600">
|
||||
将力敏传感器正确连接到 STM32 主控板的指定引脚。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-[#3d98cd] text-white rounded-full flex items-center justify-center font-bold text-lg">
|
||||
4
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-xl font-semibold text-gray-800 mb-2">
|
||||
烧录程序
|
||||
</h4>
|
||||
<p className="text-gray-600">
|
||||
在 Keil 中编译程序,并通过烧录线将程序下载到 STM32 主控板。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部提示和下一步按钮 */}
|
||||
<div className="text-center mt-10">
|
||||
<p className="text-blue-400 text-2xl font-semibold mb-6">
|
||||
完成以上步骤后,即可进入下一阶段的实验操作。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{onNext && (
|
||||
<div onClick={onNext}>
|
||||
<Image
|
||||
className="absolute bottom-4 right-6 cursor-pointer"
|
||||
src={getStaticImage("components/按钮-下一步.png")}
|
||||
button={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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/serial-cable.png"),
|
||||
name: "TYPE-C",
|
||||
},
|
||||
{
|
||||
src: getStaticImage("experiment/sensor.png"),
|
||||
name: "力敏传感器",
|
||||
},
|
||||
{
|
||||
src: getStaticImage("experiment/flash-cable.png"),
|
||||
name: "烧录线",
|
||||
},
|
||||
{
|
||||
src: getStaticImage("experiment/wireless.png"),
|
||||
name: "Arduino开发板",
|
||||
},
|
||||
];
|
||||
|
||||
// 正确答案列表
|
||||
export const correctAnswers = [
|
||||
"keil软件",
|
||||
"STM32主控板",
|
||||
"力敏传感器",
|
||||
"烧录线",
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
import { BackgroundImage } from "@/components";
|
||||
import { getStaticImage } from "@/utils";
|
||||
import { useState } from "react";
|
||||
import Step1IntroPage from "./Step1IntroPage";
|
||||
import Step2SelectPage from "./Step2SelectPage";
|
||||
import Step3ConfigPage from "./Step3ConfigPage";
|
||||
|
||||
export default function Experiment() {
|
||||
const [step, setStep] = useState(1); // 1: 工具介绍, 2: 工具选择, 3: 实验环境配置
|
||||
|
||||
return (
|
||||
<BackgroundImage
|
||||
src={getStaticImage("experiment/内容背景页.png")}
|
||||
alt="传感器实验首页"
|
||||
className="h-full w-full "
|
||||
>
|
||||
<div className="h-full relative py-20 px-10">
|
||||
{step === 1 && <Step1IntroPage onNext={() => setStep(2)} />}
|
||||
{step === 2 && <Step2SelectPage onNext={() => setStep(3)} />}
|
||||
{step === 3 && <Step3ConfigPage onNext={() => setStep(4)} />}
|
||||
</div>
|
||||
</BackgroundImage>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user