迁移到 TypeScript

完整的 TypeScript 迁移,提升代码质量和开发体验

**安装依赖:**
- TypeScript 5.9.3
- @types/react, @types/react-dom, @types/node
- typescript-eslint 相关包

**配置文件:**
- 创建 tsconfig.json (应用配置)
- 创建 tsconfig.node.json (Node 配置)
- 更新 eslint.config.ts 支持 TypeScript
- 所有配置文件迁移到 .ts 扩展名

**源代码迁移:**
- 所有 .jsx 文件 → .tsx
- 所有 .js 文件 → .ts
- 添加完整的类型定义和接口
- 修复所有类型错误

**类型定义:**
- Experiment 接口 (实验数据类型)
- DraggableItemProps 接口
- 函数参数类型注解
- React 组件 Props 类型

**代码优化:**
- 修复 main.tsx 中的 null 检查
- 添加 DragEndEvent 类型
- 使用类型守卫和类型断言
- 改进错误处理

**构建脚本:**
- 添加 typecheck 脚本
- build 脚本包含类型检查
- 更新 index.html 入口为 main.tsx

**验证:**
- ✅ TypeScript 编译通过
- ✅ ESLint 检查通过
- ✅ 所有类型正确推导
- ✅ 代码质量提升

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude committed 2025-10-17 10:30:10 +08:00
1 parent 5e84020b44
commit 543cecf2ee
21 files changed
+533 -63

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
} from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { DraggableItem } from '../components/DraggableItem';
import {
defaultExperiments,
loadExperimentsFromStorage,
saveExperimentsToStorage,
} from '../data/experiments';
export default function Home() {
const navigate = useNavigate();
// 从 localStorage 加载数据,如果没有则使用默认数据
const [experiments, setExperiments] = useState(() => {
return loadExperimentsFromStorage() || defaultExperiments;
});
// 当实验数据变化时,保存到 localStorage
useEffect(() => {
saveExperimentsToStorage(experiments);
}, [experiments]);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (over && active.id !== over.id) {
setExperiments((items) => {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
}
function handleExperimentClick(id: string) {
navigate(`/experiment/${id}`);
}
return (
<div className="max-w-3xl mx-auto">
<div className="bg-white rounded-xl shadow-lg p-6 mb-6">
<h2 className="text-2xl font-semibold text-gray-800 mb-4">
实验项目列表
</h2>
<p className="text-gray-600 mb-6">
拖动实验项目可以调整顺序,规划您的学习路径。点击项目查看详情。
</p>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={experiments}
strategy={verticalListSortingStrategy}
>
{experiments.map((experiment, index) => {
const Icon = experiment.icon;
return (
<DraggableItem key={experiment.id} id={experiment.id}>
<div
className="flex items-center gap-4 cursor-pointer hover:bg-gray-50 rounded transition-colors p-2 -m-2"
onClick={() => handleExperimentClick(experiment.id)}
>
<div className="flex-shrink-0 w-12 h-12 bg-indigo-100 rounded-full flex items-center justify-center">
<Icon className="w-6 h-6 text-indigo-600" />
</div>
<div className="flex-1">
<h3 className="font-semibold text-gray-800">
{index + 1}. {experiment.name}
</h3>
<p className="text-sm text-gray-600">
{experiment.description}
</p>
</div>
</div>
</DraggableItem>
);
})}
</SortableContext>
</DndContext>
</div>
</div>
);
}