news 2026/5/13 2:11:13

原生TypeScript待办清单:纯前端架构、观察者模式与拖拽排序实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
原生TypeScript待办清单:纯前端架构、观察者模式与拖拽排序实践

1. 项目概述:一个由AI辅助构思的现代化待办清单

最近在整理个人项目时,我重新审视了一个之前用TypeScript写的待办清单应用。这个项目的初衷很简单:我需要一个极简、快速、完全由我掌控的待办工具,它不需要登录,数据就存在本地,打开浏览器就能用。市面上虽然有很多优秀的Todo应用,但要么功能过于臃肿,要么就是数据同步需要依赖第三方服务,总感觉差了点什么。于是,我决定自己动手,用最纯粹的前端技术栈,打造一个“刚刚好”的工具。

这个项目被命名为“todo-cursor”,一方面是因为它确实是在Cursor这个智能编辑器里,借助AI辅助快速完成了原型构建和代码补全;另一方面,“cursor”也寓意着聚焦和指引,希望这个工具能像光标一样,清晰地指示你下一步该做什么。整个应用没有使用任何重型框架,而是基于原生TypeScript、CSS变量和HTML5 API构建,核心代码量控制在了千行以内,但实现了从基础增删改查到键盘快捷键、拖拽排序、暗色模式适配等完整功能。它运行起来就像一个本地应用,响应迅速,界面干净,完全满足了我对个人效率工具的所有想象。

接下来,我会带你深入这个项目的每一个细节,从设计思路、技术选型到具体的实现难点和避坑经验。无论你是前端新手想学习如何组织一个结构清晰的纯前端项目,还是有一定经验的开发者想了解如何优雅地实现拖拽、状态管理等交互,相信都能从中获得启发。我们不止是看代码,更重要的是理解每个决策背后的“为什么”。

2. 核心设计思路与技术选型解析

当我开始构思这个待办清单时,我首先问了自己几个问题:它的核心价值是什么?它需要在哪些设备上运行?数据如何持久化?交互体验的底线和上限分别在哪里?对这些问题的回答,直接决定了整个项目的技术走向。

2.1 为什么选择“纯前端+本地存储”架构?

这是最根本的决策。我的核心需求是“零延迟启动”“绝对的数据隐私”。我不希望每次打开应用都要等待网络请求,更不希望我的待办事项(哪怕只是购物清单)被上传到某个我不知道的服务器。因此,后端服务被首先排除。现代浏览器的LocalStorage API已经足够可靠,能够持久化存储几MB的文本数据,对于一个待办应用来说绰绰有余。它的读写是同步的,这意味着状态变更可以立刻保存,没有异步回调的复杂度,代码逻辑会非常直观。

当然,LocalStorage有它的局限,比如存储空间有限(通常5MB)、只能存字符串、以及标签页间数据同步需要监听storage事件。但对于一个待办清单,这些都不是问题。一个待办项对象序列化成JSON字符串后很小,即使有上千条,也远达不到容量上限。我选择用JSON.stringifyJSON.parse来做序列化,并在每次数据变更时自动保存,这样就能获得类似数据库的持久化体验,且完全离线。

注意:直接操作LocalStorage虽然方便,但要小心性能陷阱。如果每次单个待办项的完成状态切换都触发一次全量保存,在列表很长时可能会有卡顿。我的做法是引入一个简单的防抖机制,将保存操作延迟100毫秒执行,确保高频操作(如快速勾选多个事项)只触发最后一次保存,这对用户体验的提升是显著的。

2.2 技术栈的“克制”与“激进”

在框架选择上,我刻意保持了克制。没有用React、Vue或Svelte。原因有三点:第一,项目复杂度不高,引入框架带来的 bundle 体积增加得不偿失;第二,我想更深入地实践原生DOM操作和TypeScript的类型系统;第三,这本身也是一个学习项目,剥离框架的抽象层,能更好地理解底层原理。

但这不意味着技术栈是落后的。相反,我在几个关键点上非常“激进”:

  1. TypeScript全程覆盖:从模型定义、工具函数到主应用逻辑,全部使用TypeScript并配置了严格的tsconfig.json。这为项目提供了极佳的代码提示和重构能力,尤其是在定义复杂的待办项状态类型时,类型安全避免了无数潜在的错误。
  2. CSS Variables(自定义属性)驱动主题:为了实现无缝的暗色/亮色模式切换,我没有使用两套CSS文件,而是完全依赖CSS变量。在:root中定义一套如--primary-color--bg-color--text-color的变量,然后在@media (prefers-color-scheme: dark)媒体查询中,重新为这些变量赋一套暗色值。这样,整个UI的颜色切换只需要修改CSS变量,JavaScript几乎不参与,性能好且易于维护。
  3. 拥抱现代Web API:项目大量使用了现代浏览器原生支持的能力:
    • HTML5 Drag & Drop API:实现列表项的拖拽排序。虽然这个API的声誉有点“臭”(因为早期的实现确实反直觉),但通过合理的封装和事件处理,完全可以做出流畅的体验。
    • KeyboardEventFocus Management:实现全键盘操作的核心。这不仅对无障碍访问友好,对追求效率的键盘党用户更是福音。
    • Window.localStorage:如前所述,是数据层的基石。

2.3 状态管理与设计模式的应用

即使是一个小应用,状态管理也不该是混乱的。我采用了经典的观察者模式(Observer Pattern)来解耦数据模型和UI视图。

具体来说,我设计了一个TodoList类(单例模式,确保全局只有一个列表实例),它内部维护一个Todo对象的数组。这个TodoList类就是被观察者(Subject)。任何视图组件(比如负责渲染列表的TodoListView类)都可以注册为观察者(Observer)。当用户通过UI添加、删除或修改了一个待办项时,TodoList实例的内部数据发生变化,然后它会自动遍历所有注册的观察者,调用它们的update方法,并传递最新的数据。观察者收到通知后,用自己的逻辑重新渲染对应的UI部分。

这样做的好处非常明显:

  • 数据流清晰:数据修改只有一个入口(TodoList的方法),UI只是数据的映射。避免了视图直接操作DOM导致状态不一致的经典问题。
  • 可维护性强:如果需要增加一个新的UI组件(比如一个实时统计图表),只需要让它实现观察者接口并注册到TodoList即可,完全不用修改现有的数据逻辑。
  • 便于测试TodoList的逻辑可以单独进行单元测试,因为它不依赖任何DOM环境。
// 简化的观察者模式示例 interface Observer { update(data: Todo[]): void; } class TodoList { private todos: Todo[] = []; private observers: Observer[] = []; addObserver(observer: Observer) { this.observers.push(observer); } private notifyObservers() { this.observers.forEach(observer => observer.update(this.todos)); this.saveToStorage(); // 通知后自动保存 } addTodo(todo: Todo) { this.todos.push(todo); this.notifyObservers(); // 数据变更,通知所有UI更新 } // ... 其他方法 }

3. 核心模块深度剖析与实现细节

理解了整体架构,我们深入到各个核心模块,看看它们是如何被构建起来的,其中有哪些值得细说的实现技巧和遇到的“坑”。

3.1 数据模型:Todo与TodoList

这是应用的基石,设计得好,后面的一切都会顺理成章。

Todo模型不仅仅是一个有idtitlecompleted字段的对象。我给它增加了更多元数据以支持丰富功能:

interface Todo { id: string; // 使用 `crypto.randomUUID()` 生成,确保唯一性 title: string; completed: boolean; createdAt: number; // 时间戳,用于按创建时间排序 orderIndex: number; // 手动拖拽排序后的顺序索引 }
  • id使用crypto.randomUUID()生成,这是一个现代浏览器支持的Web Crypto API,比传统的Date.now()或自增ID更可靠、更唯一。
  • createdAt存储时间戳,这使得“按创建时间排序”功能非常容易实现。
  • orderIndex是关键。当用户拖拽改变顺序后,我们需要一种方式来持久化这个新顺序。简单的数组重排只在内存中有效,刷新页面就没了。我的策略是:在拖拽结束后,遍历当前所有待办项,为它们重新分配一个连续的orderIndex(如0,1,2,...),然后保存。之后加载时,按orderIndex排序即可恢复用户自定义的顺序。

TodoList类是这个模型的管理者。它的职责非常清晰:

  1. 增删改查(CRUD):提供addTodo,deleteTodo,toggleTodo,updateTodoTitle等方法。每个方法在修改内部数组后,都会调用notifyObservers()
  2. 状态过滤与排序:提供getFilteredTodos(filterType),getSortedTodos(sortType)等方法。这些方法是纯函数,根据参数返回新的数组,不修改原数据,符合函数式编程的思想,易于测试。
  3. 与存储层对接:包含loadFromStorage()saveToStorage()方法。这里有一个细节:保存时,我不仅存了待办项数组,还存了当前的过滤和排序状态,这样用户刷新页面后,看到的仍然是之前设定的视图。

3.2 视图渲染:高效与可访问性兼顾

渲染层的工作是将Todo[]数组转化为用户看到的DOM列表。最朴素的做法是每次更新都清空列表容器,然后循环todos重新创建所有<li>元素。这在列表项不多时没问题,但不够优雅,且会破坏元素的焦点(focus)状态,对键盘操作不友好。

我采用了“差异化更新”策略。当收到新的todos数据时,渲染器会:

  1. 将当前的DOM节点列表与新的todos数组进行比对。
  2. 识别出哪些是新增的项(创建并插入DOM)、哪些是删除的项(移除DOM)、哪些是更新的项(例如,完成状态改变,只更新对应节点的类和样式,而不是重建)。
  3. 对于顺序改变,如果只是orderIndex变化而元素本身存在,则使用Node.insertBefore()进行DOM节点的移动,这比先删除再插入性能要好。

键盘导航与无障碍是这部分的重头戏。为了让应用能被屏幕阅读器正确识别且支持键盘操作,我做了以下工作:

  • 正确的ARIA属性:每个待办项容器(<li>)都设置了role="listitem",复选框使用真正的<input type="checkbox">而非用div模拟,并关联了<label>。列表容器(<ul>)设置role="list"。这些属性帮助辅助技术理解页面结构。
  • 管理焦点:当删除一个待办项时,焦点不能凭空消失,否则键盘用户会迷失。我的逻辑是:将焦点移动到被删除项的前一项(如果存在),否则移动到后一项,如果都没有,则移动到输入框。这需要仔细处理tabindexelement.focus()
  • 完整的键盘快捷键:如前所述,我为常用操作绑定了快捷键。实现时需要注意event.preventDefault()来阻止浏览器默认行为(如按空格滚动页面),并处理好event.keyevent.code的兼容性。同时,在界面上提供一个隐藏的、可通过键盘调出的帮助面板,列出所有快捷键,这对新用户很友好。

3.3 拖拽排序的实现与优化

HTML5 Drag & Drop API的体验口碑不佳,主要是因为它的默认行为(比如拖拽图片或链接)和事件流的复杂性。但要实现一个列表内部排序,它仍然是原生支持最好的选择。

我的实现步骤和关键点如下:

  1. 为可拖拽项设置属性:在每个待办项的DOM元素上设置draggable="true"
  2. 处理dragstart事件:当用户开始拖拽时,在这个事件处理函数中,使用event.dataTransfer.setData('text/plain', todo.id)将当前项的id存入数据传递对象。同时,可以设置event.dataTransfer.effectAllowed = 'move'来指定操作类型。
  3. 处理dragover事件:这是最关键的一步。当拖拽的元素经过另一个列表项上方时,会触发该列表项的dragover事件。必须在这个事件中调用event.preventDefault(),才能表明“此区域允许放置”,光标会变成允许移动的样式。我在这里还加入了视觉反馈,比如给当前“经过”的项添加一个高亮边框或背景色,提示用户松手后的放置位置。
  4. 处理drop事件:当用户松开鼠标时,在目标列表项上触发。首先还是要event.preventDefault()来阻止浏览器可能的默认打开链接等行为。然后,从event.dataTransfer.getData('text/plain')中取出被拖拽项的id。最后,执行核心逻辑:在数据层(TodoList)中,找到被拖拽项和目标项,计算新的顺序,更新所有相关项的orderIndex,并通知观察者更新UI。
  5. 处理dragend事件:无论拖拽是否成功完成(比如用户在外面松手了),都会触发。这里适合做一些清理工作,比如移除所有在dragover时添加的临时视觉样式。

实操心得:拖拽体验的流畅度,很大程度上取决于视觉反馈是否及时和准确。我最初只在drop时才更新UI,感觉非常迟钝。后来改为在dragover时就实时更新一个“预览位置”(例如,在列表中插入一个占位符虚线框),体验立刻提升了一个档次。此外,一定要记得在dragoverdrop中阻止默认事件,这是很多拖拽失效问题的根源。

3.4 样式系统:CSS变量与响应式布局

样式方面,我追求的是“代码精简”和“主题灵活”。所有颜色、间距、字体大小都定义在:root的CSS变量中。

:root { --color-bg: #ffffff; --color-text: #333333; --color-primary: #646cff; --spacing-unit: 8px; --border-radius: 8px; } @media (prefers-color-scheme: dark) { :root { --color-bg: #1a1a1a; --color-text: #f0f0f0; --color-primary: #535bf2; } }

然后在任何需要颜色的地方,都使用var(--color-bg)这样的变量。切换暗色模式时,浏览器会自动应用媒体查询中的新变量值,整个界面的颜色瞬间切换,无需任何JavaScript参与。

响应式设计采用了移动优先的策略。基础样式针对小屏幕编写,然后通过@media (min-width: 768px)来增加大屏幕下的特定样式。例如,在手机上,操作按钮(删除、编辑)可能以图标形式出现在待办项右侧;在平板上,这些按钮可以显示为文字标签,并增加一些内边距以便于触摸。

CSS架构上,我采用了类似BEM的扁平化命名思想,但更简化。类名直接描述组件的功能,如.todo-item,.todo-checkbox,.todo-actions。避免过深的嵌套选择器,以提高CSS渲染性能,也使得样式更容易被覆盖和修改。

4. 开发、构建与工程化实践

即使是一个小项目,良好的工程化习惯也能让开发过程事半功倍,并且为未来的维护和扩展打下基础。

4.1 开发环境搭建与脚本配置

项目使用pnpm作为包管理器,因为它速度快、磁盘空间利用效率高。package.json中的脚本配置得非常清晰:

{ "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "lint": "eslint src --ext .ts", "format": "prettier --write src/" } }
  • pnpm dev:启动Vite开发服务器,支持热模块替换(HMR),修改代码后浏览器几乎即时更新。
  • pnpm build:先运行TypeScript编译器(tsc)进行类型检查,确保没有类型错误,然后再用Vite进行打包构建。这个顺序很重要,能避免将类型错误代码打包进去。
  • pnpm lintpnpm format:分别用ESLint和Prettier来保证代码质量和风格统一。我在团队项目中会配置Git提交钩子(husky + lint-staged),在提交前自动执行这些检查。

Vite的配置(vite.config.ts)非常简洁,主要指定了根目录、构建选项(如输出目录dist)和是否生成sourcemap用于生产环境调试。

4.2 TypeScript配置的严格性

tsconfig.json是TypeScript项目的核心。我启用了相当严格的编译选项,以确保代码质量:

{ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "forceConsistentCasingInFileNames": true }, "include": ["src"] }

关键点在于"strict": true,它开启了一系列严格类型检查。noUnusedLocalsnoUnusedParameters能帮助清理未使用的代码。这些设置虽然一开始可能会因为类型错误而“劝退”,但长期来看,它们能强制你写出更健壮、意图更清晰的代码,将很多运行时错误提前到编译时发现。

4.3 项目结构组织

清晰的项目结构就像一个好的衣柜,让你能快速找到需要的衣服。我的src目录结构是功能导向的:

src/ ├── models/ # 数据模型和核心逻辑(Todo, TodoList) ├── views/ # 视图渲染类(负责将model数据变成DOM) ├── services/ # “服务”,如StorageService(封装localStorage操作) ├── utils/ # 纯工具函数(日期格式化、防抖节流等) ├── styles/ # 样式文件(按功能拆分,通过main.css导入) ├── types/ # 全局TypeScript类型定义 └── app.ts # 应用入口,初始化所有模块并组装起来

这种结构的好处是职责分离。models目录下的类不关心DOM;views目录下的类只关心如何渲染,不关心数据怎么存;services处理副作用(如IO操作)。这使得单元测试变得容易,因为你可以单独测试TodoList的逻辑,而无需启动浏览器。

5. 实际开发中遇到的典型问题与解决方案

在开发过程中,我遇到了一些颇具代表性的问题,它们的解决方案或许能帮你绕过同样的坑。

5.1 数据持久化的“幽灵更新”问题

问题描述:在快速连续操作时(比如按住回车键快速添加多个空待办项),控制台会报错,或者发现保存的数据状态错乱,出现了未预期的“幽灵”数据。

根因分析:这是因为我的saveToStorage方法被直接绑定到了TodoList的每一个更新方法之后,且没有做防抖。在极短的时间内,可能会触发多次localStorage.setItem操作。虽然LocalStorage是同步的,但JavaScript的事件循环和可能的UI渲染阻塞可能导致读写顺序出现竞态条件。

解决方案:引入一个简单的防抖函数。

// utils/debounce.ts export function debounce<T extends (...args: any[]) => any>( func: T, wait: number ): (...args: Parameters<T>) => void { let timeout: NodeJS.Timeout | null = null; return (...args: Parameters<T>) => { if (timeout) clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } // 在TodoList中使用 class TodoList { private saveToStorage = debounce(() => { localStorage.setItem('todos', JSON.stringify(this.todos)); }, 100); private notifyObservers() { // ... 通知逻辑 this.saveToStorage(); // 这里调用的是防抖后的函数 } }

将保存操作延迟100毫秒执行,如果在此期间有新的变更,就取消之前的定时器,重新计时。这确保了无论多快的连续操作,最终只执行最后一次保存,既解决了竞态问题,也提升了性能。

5.2 拖拽排序时视觉反馈的闪烁问题

问题描述:在拖拽元素经过其他项时,通过修改dragover目标元素的样式(如加背景色)来提供反馈,但有时会出现样式快速闪烁或跳动的情况。

根因分析dragover事件触发频率极高(每秒可能数十次)。如果在这个事件处理函数中直接进行DOM操作(如修改element.style.backgroundColor),可能会频繁触发浏览器的重排(reflow)或重绘(repaint),导致性能下降和视觉闪烁。此外,如果鼠标移动过快,事件可能在不同元素间快速切换,导致样式变化不稳定。

解决方案

  1. 使用CSS类替代直接样式修改:预先在CSS中定义好一个.drag-over类,该类包含视觉反馈样式。在dragover事件中,只为当前目标元素添加这个类,并移除之前其他元素上的这个类。DOM的className操作比直接操作style优化得更好。
  2. 使用requestAnimationFrame节流:对于更复杂的视觉更新(比如动态插入占位符),可以将更新逻辑包装在requestAnimationFrame回调中。这能确保视觉更新与浏览器的绘制周期同步,避免不必要的中间帧渲染。
  3. 优化事件处理逻辑:在dragover处理函数开头,可以先判断当前目标元素是否和上一次的是同一个,如果是,则直接返回,避免重复操作。
let lastDragOverElement = null; function handleDragOver(event) { event.preventDefault(); const currentElement = event.currentTarget; // 如果是同一个元素,跳过 if (currentElement === lastDragOverElement) return; // 移除上一个元素的高亮 if (lastDragOverElement) { lastDragOverElement.classList.remove('drag-over'); } // 为当前元素添加高亮 currentElement.classList.add('drag-over'); lastDragOverElement = currentElement; }

5.3 移动端触摸交互与点击延迟

问题描述:在手机或平板上,点击复选框或按钮时,感觉有大约300毫秒的延迟,体验不跟手。

根因分析:这是早期移动浏览器为了区分“点击”和“双击缩放”而引入的著名问题。浏览器在touch事件后会等待约300ms,看用户是否会再次触摸(双击),然后再触发click事件。

解决方案:现代浏览器已经通过<meta name="viewport" content="width=device-width">这个视口标签,对不可缩放的页面移除了这个延迟。但为了最佳兼容性和体验,我们可以采取更多措施:

  1. 确保视口标签正确:如上所述,这是基础。
  2. 使用touch-actionCSS属性:在可交互元素(按钮、复选框)上设置touch-action: manipulation;。这个属性告诉浏览器,这个元素只支持平移和缩放手势,从而允许浏览器立即触发click事件。
  3. 监听touchstarttouchend事件:对于需要极快响应的操作(如开始拖拽),可以直接监听触摸事件,而不是等待click。但要注意,这需要你手动处理点击和拖拽的冲突,实现起来更复杂。对于大多数情况,前两种方法已经足够。

5.4 常见问题速查表

为了方便你快速定位和解决问题,我将开发中遇到的一些典型现象和解决方法整理成了下表:

问题现象可能原因解决方案
页面刷新后待办项顺序恢复默认拖拽排序后未正确更新和保存orderIndex字段在拖拽drop事件中,重新计算并赋值所有项的orderIndex,然后触发保存。
键盘快捷键在某些浏览器失效浏览器默认行为未阻止,或快捷键与浏览器/系统冲突在快捷键事件监听器中调用event.preventDefault()。考虑提供可自定义快捷键的功能。
暗色模式切换不生效CSS变量未在媒体查询中正确覆盖,或浏览器不支持prefers-color-scheme检查CSS变量定义和作用域。为不支持该媒体查询的旧浏览器提供手动切换按钮作为降级方案。
待办项文字过长时布局错乱CSS未对长文本做处理,或容器宽度固定为文本容器设置overflow-wrap: break-word;word-break: break-all;。考虑使用弹性或网格布局。
生产构建后页面空白资源路径错误,或JavaScript执行报错检查Vite构建输出的dist目录结构,确保index.html引用的JS/CSS路径正确。打开浏览器开发者工具查看控制台报错。
屏幕阅读器无法正确播报ARIA属性缺失或错误,焦点管理混乱使用Chrome Lighthouse或axe工具进行无障碍审计。确保所有交互元素都有正确的rolearia-*属性和焦点顺序。

6. 从项目构建中获得的经验与延伸思考

做完这个项目,它不仅仅是一个能用的待办清单,更像是一个前端原生技术实践的微缩沙盘。回顾整个过程,有几个体会特别深刻。

首先,理解底层API比追逐框架更重要。在实现拖拽、键盘导航、本地存储时,我不得不去仔细阅读MDN文档,理解每个事件的生命周期、每个API的边界情况。这个过程虽然有时痛苦,但带来的掌控感和解决问题的能力是使用高阶框架封装所不能比的。当出现bug时,你能清晰地知道问题可能出在事件流的哪个环节,而不是在黑盒里猜测。

其次,TypeScript的严格类型是大型项目的“安全带”。在这个小项目中,严格模式已经帮我提前捕获了十几次潜在的类型错误,比如可能为undefined的值未做检查、函数返回值类型不匹配等。它强迫你在编码时就思考数据的形状和流动,这极大地提升了代码的健壮性和可读性。对于任何准备长期维护或协作的项目,从第一天起就使用严格TypeScript是绝对值得的投资。

再者,用户体验藏在细节里。一个全键盘操作的快捷键设计、拖拽时流畅的视觉反馈、移动端触摸的跟手感觉、甚至是在删除项目后焦点的合理移动,这些细节单个看起来微不足道,但叠加在一起就构成了产品的“质感”。开发时,需要不断地把自己切换到“小白用户”和“键盘重度用户”等多种角色去使用和测试。

最后,关于AI辅助编程(就像本项目最初得到Claude的帮助),我的看法是:它是一个强大的“加速器”和“灵感来源”,但绝不能替代思考。AI可以快速生成模块代码、提供实现方案、甚至帮你调试错误信息。但项目的整体架构设计、技术选型的权衡、用户体验的打磨,这些核心决策必须由开发者自己做出。AI生成的代码,你必须一行行理解,把它变成你自己的知识。这个项目从AI生成的雏形,到如今每个细节都经过推敲的版本,正是这种“人主导,AI辅助”模式的最佳体现。它让你从重复的样板代码中解放出来,更专注于那些真正创造价值的部分。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/13 2:11:05

JPlag代码抄袭检测:你的学术诚信守护神

JPlag代码抄袭检测&#xff1a;你的学术诚信守护神 【免费下载链接】JPlag State-of-the-Art Source Code Plagiarism & Collusion Detection. Check for plagiarism in a set of programs. 项目地址: https://gitcode.com/gh_mirrors/jp/JPlag 你是否曾为学生的代码…

作者头像 李华
网站建设 2026/5/13 2:05:58

AI大模型学习路线:(非常详细)AI大模型学习路线,月薪30K+!3步掌握AI大模型,小白也能轻松入门!

本文提供了一条清晰的学习路线&#xff0c;帮助新手入门AI大模型领域。首先强调打好数学与编程基础&#xff0c;包括线性代数、微积分、概率统计、Python及数据结构与算法。接着介绍如何入门机器学习和深度学习&#xff0c;推荐了经典书籍、在线课程及实践项目。然后&#xff0…

作者头像 李华
网站建设 2026/5/13 2:05:18

ComfyUI-WanVideoWrapper高效实战:10分钟掌握AI视频生成核心技术

ComfyUI-WanVideoWrapper高效实战&#xff1a;10分钟掌握AI视频生成核心技术 【免费下载链接】ComfyUI-WanVideoWrapper 项目地址: https://gitcode.com/GitHub_Trending/co/ComfyUI-WanVideoWrapper 想要将静态图片变成生动视频吗&#xff1f;ComfyUI-WanVideoWrapper…

作者头像 李华
网站建设 2026/5/13 2:05:17

前后端分离新冠病毒密接者跟踪系统系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程

摘要 新冠疫情在全球范围内持续蔓延&#xff0c;对公共卫生安全构成严峻挑战。密切接触者追踪是疫情防控的关键环节&#xff0c;传统的人工追踪方式效率低下且容易遗漏&#xff0c;亟需信息化手段提升精准度和响应速度。基于前后端分离架构的新冠病毒密接者跟踪系统能够实现高效…

作者头像 李华
网站建设 2026/5/13 2:00:05

Fomu FPGA工作坊:从LED闪烁到RISC-V软核的微型硬件开发指南

1. 项目概述&#xff1a;当FPGA遇见指尖&#xff0c;一场硬件的微型革命如果你对嵌入式开发、硬件编程感兴趣&#xff0c;但又觉得传统的FPGA开发板笨重、昂贵且入门门槛高&#xff0c;那么im-tomu/fomu-workshop这个项目可能会让你眼前一亮。这不仅仅是一个代码仓库&#xff0…

作者头像 李华