news 2026/9/17 17:08:42

全开源跑腿小程序系统智能派单算法与状态机设计解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
全开源跑腿小程序系统智能派单算法与状态机设计解析

简介:这套全开源跑腿小程序系统基于FastAdmin、ThinkPHP与uniapp开发,覆盖同城配送、校园跑腿、预约取件等常见场景,适合需要快速搭建跑腿业务或进行私有化部署的团队使用。系统完整提供用户端、骑手端与运营后台,支持智能派单、系统派单、一键接单和抢单,可有效提升订单流转与骑手调度效率。资源包共2000个文件,以JavaScript、HTML、Vue、JSON及Markdown文档为主,涵盖前端页面、业务逻辑、接口配置与说明文档,压缩包大小约43.26MB。整体源码无加密,便于二次开发与定制部署。已有646人学习下载,适合具备PHP和Vue基础的开发者用来落地跑腿项目或研究完整的同城配送技术方案。

1. 全开源跑腿小程序系统的价值不在代码,而在调度模型

跑腿系统表面上是“用户下单、骑手接单”两层结构,真正拉开差距的是中间的派单引擎。多数人拿到全开源跑腿小程序系统源码后第一反应是改界面、改支付,结果上线后订单一多就乱:骑手集中在同一片区、远单无人接、预约取件超时没人管。这些问题的根源不是前端性能,而是“订单该分给谁”这件事没有算法支撑。

同城配送、校园跑腿这类场景有个共同特点:订单密度低、时效要求高、骑手体量小。大厂的全局最优派单在这里跑不动,一套基于评分排序的智能派单规则反而更实用。这也是为什么“系统派单”会比“手动指派”更值得研究——它把调度员的经验变成可配置的参数,让接单从碰运气变成可预期。

这篇文章围绕一套典型的全开源跑腿小程序系统展开:用户端负责下单、支付、预约取件;骑手端负责接单、取货、送达;后端把订单和骑手连接起来。读完可以回答三个问题:智能派单的算法怎么写、全链路代码怎么在本地跑通、上线后用什么指标判断系统是否健康。

2. 智能派单的核心逻辑:订单分给谁,比接单快慢更关键

2.1 先分清三种派单模式:抢单、顺序派单、智能派单

系统的派单模块一般有三种工作模式。抢单模式最简单:新订单广播给附近骑手,先到先得。这种模式在校园跑腿里最常见,好处是骑手主观能动性强,坏处是高峰时段骑手会挑肥拣瘦,低价短单经常无人响应。

顺序派单是按照骑手的活跃时间轮流分配,不考虑距离和负载,适合订单路径高度规律的场景。全开源跑腿小程序系统里真正值得改的是第三种:智能派单。它的思路是把“距离近”当作必要条件而非唯一条件,综合骑手负载、距离、时效、服务质量多项指标算出一个分数,订单自动推给最高分骑手。

用户视角的差别: 抢单模式 -> 订单挂出去,等骑手抢,高峰期"等" 系统派单 -> 订单直接找骑手,骑手确认,高峰期"推"

提示:如果源码里针对某个订单同时出现“待接单”和“系统派单中”两个状态,说明系统支持抢单与派单混合模式,一般通过订单类型字段区分。

2.2 智能派单评分函数:距离、负载、准时率、时效窗口

智能派单的实质是对每个可用骑手计算一个综合分,然后取最大值。一个通用评分函数可以表示为:

score = w1 * distance_score + w2 * load_score + w3 * reliability_score + w4 * urgency_score
  • distance_score:骑手当前位置与取件点的距离归一化值,越近分数越高
  • load_score:骑手当前待配送订单数,越少分数越高
  • reliability_score:骑手历史准时完成率,衡量服务质量
  • urgency_score:订单时效紧迫度,预约取件的单子会显著提升这个值

权重系数 w1 到 w4 不是拍脑袋定的。校园跑腿场景里距离权重通常最高,占 0.4 左右;同城配送时效权重可以调到 0.3;负载权重是为了防止热门骑手被塞爆,0.2 比较合适;服务质量权重初始设为 0.1,跑两周后根据投诉率再调整。

参数表:

参数默认值调节方向
DISTANCE_WEIGHT0.4单量稀疏、骑手少时加大
LOAD_WEIGHT0.2骑手抱怨单过多时加大
RELIABILITY_WEIGHT0.1投诉率高时加大
URGENCY_WEIGHT0.3预约单频繁超时时加大
MAX_DISPATCH_DISTANCE3km超出范围直接过滤,不参与评分

还有一个容易被忽略的点:距离计算不要用直线距离。城市道路环境下直线距离与实际骑行距离能差出 40%,骑手端看到的“2 公里”实际要骑 2.8 公里,接单意愿会明显下降。开源系统通常在配置文件中提供距离算法切换项,可选直线距离和高德地图骑行距离。对外的展示、计费、派单计算应该统一使用真实骑行距离,否则用户端显示的配送费会和骑手体验脱节。

2.3 用最小代码实现一个可用的派单调度核心

理解了评分逻辑后,派单核心代码并不复杂。下面是一个可以放进跑腿系统后端服务的派单实现,基于 TypeScript 编写:

interface Rider { id: string; lat: number; lng: number; activeOrders: number; onTimeRate: number; // 历史准时率 0~1 } interface DispatchOrder { id: string; pickupLat: number; pickupLng: number; expectedPickupTime: number; // 期望取件时间戳 urgent: boolean; } type Config = { distanceWeight: number; loadWeight: number; reliabilityWeight: number; urgencyWeight: number; maxDispatchDistance: number; // 米 }; function computeDistance( lat1: number, lng1: number, lat2: number, lng2: number, realRoadFactor = 1.4 ): number { // 简化球面距离计算,生产环境建议调用地图服务骑行距离 const R = 6371000; const dLat = ((lat2 - lat1) * Math.PI) / 180; const dLng = ((lng2 - lng1) * Math.PI) / 180; const a = Math.sin(dLat / 2) ** 2 + Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLng / 2) ** 2; const distance = 2 * R * Math.asin(Math.sqrt(a)); return Math.round(distance * realRoadFactor); } export function dispatchOrder( order: DispatchOrder, riders: Rider[], cfg: Config ): Rider | null { let bestRider: Rider | null = null; let bestScore = -Infinity; for (const rider of riders) { const distance = computeDistance( rider.lat, rider.lng, order.pickupLat, order.pickupLng ); if (distance > cfg.maxDispatchDistance) continue; const distanceScore = 1 - distance / cfg.maxDispatchDistance; const loadScore = 1 / (1 + rider.activeOrders); // 订单越少分数越高 const reliabilityScore = rider.onTimeRate; const timeWindowMs = order.expectedPickupTime - Date.now(); const urgencyScore = order.urgent ? timeWindowMs < 15 * 60 * 1000 ? 1 : 0.5 : 0.3; const score = cfg.distanceWeight * distanceScore + cfg.loadWeight * loadScore + cfg.reliabilityWeight * reliabilityScore + cfg.urgencyWeight * urgencyScore; if (score > bestScore) { bestScore = score; bestRider = rider; } } return bestRider; }

逻辑说明:先做距离过滤,超出最大派单距离的骑手直接跳过,避免远距离骑手因其他维度得分高而被选中。distanceScore 采用线性归一化,距离为 0 时取 1,达到上限时取 0。loadScore 使用 1/(1+n) 形式,骑手已有订单从 0 变 1 时分数从 1 降到 0.5,衰减最快,符合直觉。

urgent 订单且距期望取件时间不足 15 分钟时,单项得分拉满,配合较高的 urgencyWeight 可以保证预约取件单优先派给附近骑手。maxDispatchDistance 的单位是米,校园场景建议设在 2000 到 3000 之间,同城配送可以放宽到 5000。

实际工程里这个循环会进一步优化:Rider 数量超过 50 人时建议把骑手按地理位置建立网格索引,先粗筛出订单周围 3 公里内的骑手,再进入评分循环,这样接口耗时能控制在 50ms 以内。

2.4 派单失败回退:从自动指派到广播抢单

智能派单不是每单都能成功。如果所有骑手都超出最大派单距离,或者候选骑手全部处于忙碌状态,系统需要一套降级策略。最常见的设计是:自动派单超时 30 秒无骑手确认,订单自动转为广播抢单,推送给更大范围的骑手。

这一层逻辑通常由定时任务驱动。放一个简单的降级规则表:

阶段触发条件动作
初次派单新订单支付成功按评分推送给前 3 名骑手
超时重派30 秒内无人确认扩大 500 米范围重新评分
广播抢单重派一次仍失败全量推送,先到先得
异常兜底5 分钟内无人接标记为异常单,通知调度员

实现时注意一点:回归抢单模式的订单需要确保多个骑手不会同时看到同一单还都能抢到。通用的做法是利用数据库的行锁或在 Redis 里用 SETNX 命令,抢单请求到达时先尝试加锁,拿到锁的骑手才进入后续的订单状态更新流程。这块内容在第 4 章并发接单部分还会展开。

3. 源码拿到手先跑起来:用户端 / 骑手端 / 管理后台的本地启动

3.1 仓库结构长什么样:一个典型全开源项目的分层方式

全开源跑腿小程序系统一般不在单个仓库里放所有代码,而是拆成 4 个部分:用户端小程序、骑手端小程序、管理后台前端、后端服务。用户端和骑手端通常是基于 uni-app 或原生微信小程序分别开发,两端复用一套接口。后端常见的选型是 Node.js + MySQL 或 Java Spring Boot,数据库表设计通常包含用户表、骑手表、订单表、订单状态日志表、结算表。

拿到源码后第一件事不是打开微信开发者工具,而是看根目录下的 README 和 init.sql 或 schema 目录。开源项目的部署文档质量参差不齐,常见做法是先看有没有 docker-compose.yml,有的话说明作者已经准备好了一键启动环境,没有的话就只能手动初始化。

典型目录结构如下:

delivery-system/ ├── backend/ # 后端服务(NestJS / Spring Boot) │ ├── src/ │ ├── sql/init.sql # 数据库初始化脚本 │ └── docker-compose.yml(可选) ├── admin-web/ # 管理后台(Vue3 + Element Plus) ├── mini-user/ # 用户端小程序(uni-app 或原生) ├── mini-rider/ # 骑手端小程序 └── README.md

3.2 后端启动的最小步骤:数据库初始化与接口配置

无论后端是哪种框架,启动过程都分三步:建库、导入初始化脚本、改配置。以 MySQL 为例,先通过命令行创建数据库并导入脚本:

mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS delivery DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -uroot -p delivery < backend/sql/init.sql

初始化脚本会创建订单表、骑手表、用户表及基础数据。导入后检查 backend 目录下的环境配置文件,通常是 .env 文件:

DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=root DB_PASSWORD=your_password DB_NAME=delivery REDIS_URL=redis://127.0.0.1:6379 # 小程序端调用后端时通过这个地址访问 BASE_URL=http://localhost:3000/api # 微信小程序的 appId 和 appSecret,用于登录换 openid WECHAT_APP_ID= WECHAT_APP_SECRET=

注意:本地开发阶段如果没有配置企业主体的微信小程序 appId,建议先关闭微信登录鉴权,直接走验证码登录或测试账号登录,代码里一般有对应的配置开关。

配置完成后安装依赖并启动:

cd backend npm install npm run start:dev

看到类似Server is running on http://localhost:3000的日志就说明后端已经起来了。此时可以用 curl 验证接口是否正常响应:

curl http://localhost:3000/api/health # 期望返回 {"status":"ok"} 类似的 JSON

3.3 微信小程序端:修改加载页、设置动态标题、关闭权限校验

用户端和骑手端代码导入微信开发者工具时,需要注意两个高频问题。第一个是编译报错提示npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。这个问题出在 Windows 的 PowerShell 执行策略,与项目代码无关。解决办法是以管理员身份打开 PowerShell,执行Set-ExecutionPolicy -Scope CurrentUser RemoteSigned,然后重新运行 npm install。

第二个问题是小程序的加载页面。很多开源项目把加载页写死在 app.json 或页面配置里,修改刚进入的加载页面时,需要同时改两个地方:首先是 project.config.json 里的启动页面配置,决定开发者工具打开时展示哪个页面;其次是首页跳转逻辑,通常在 app.js 的 onLaunch 或首页的 onLoad 里,根据用户角色跳到用户端或骑手端。

设置小程序动态标题的场景更多,因为订单状态变化时希望标题栏直接反映配送进度。在用户端订单详情页拿到订单状态后,用 uni.setNavigationBarTitle 或 wx.setNavigationBarTitle 设置:

// 订单状态映射 // 2=待取件 3=配送中 4=已完成 const titleMap = { 2: '等待骑手取件', 3: '商品配送中', 4: '订单已完成' }; uni.setNavigationBarTitle({ title: titleMap[order.status] || '订单详情' });

这段代码放在订单状态推送的回调里,用户停留在详情页时也能实时更新标题。实现逻辑很简单,但这是提升骑手和用户两端体验成本最低的一个改法。

3.4 管理后台与联调:从生成二维码到触发 wx.openLocation

管理后台负责骑手审核、订单监控、费率设置。本地联调时,管理后台与后端跨域问题最常出现。开源项目一般通过 Nginx 代理或后端 CORS 中间件解决,开发环境启动管理后台后,确认浏览器开发工具里没有 CORS 报错即可。

跑腿场景里有一个很容易被忽略的功能点是打开地图导航。用户端或骑手端需要展示取件地点时,代码里通常使用wx.openLocation打开地图组件:

wx.openLocation({ latitude: 31.2304, longitude: 121.4737, name: order.pickupAddress, address: order.pickupDetail, scale: 18 });

这个接口需要传入数字类型的经纬度,从后端返回的字符串必须做 parseFloat 转换,很多项目在这里踩坑,导致点击地址后地图打不开。联调的时候先在浏览器里直接请求后端接口,确认经纬度字段确实有值,再去排查小程序的调用链。

4. 预约取件与订单状态机:把业务流写成可回溯的流水

4.1 订单表与状态日志表的设计

预约取件功能比即时单多一个时间维度,订单表需要额外存储期望取件时间。一个精简的表结构如下:

CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id BIGINT NOT NULL, rider_id BIGINT DEFAULT NULL, pickup_address VARCHAR(255) NOT NULL, delivery_address VARCHAR(255) NOT NULL, pickup_lat DECIMAL(10, 6) NOT NULL, pickup_lng DECIMAL(10, 6) NOT NULL, delivery_lat DECIMAL(10, 6) NOT NULL, delivery_lng DECIMAL(10, 6) NOT NULL, expected_pickup_time DATETIME DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0, amount DECIMAL(10, 2) NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status (status), KEY idx_expected_pickup (expected_pickup_time), KEY idx_rider_id (rider_id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE order_status_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, from_status TINYINT NOT NULL, to_status TINYINT NOT NULL, operator_type TINYINT NOT NULL COMMENT '1用户 2骑手 3系统 4管理员', operator_id BIGINT NOT NULL, remark VARCHAR(255) DEFAULT '', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

orders 表里的 status 是当前状态,order_status_log 记录每一次状态跳转,两者缺一不可。业务上经常需要回答“这单为什么逾期了”“骑手几点确认的取件”,只有状态日志能回答这类问题。index 设计上,按状态查待派单列表、按期望取件时间查预约单是最高频的查询路径,这两个索引必须加上。

4.2 订单状态机:创建 → 已支付 → 已接单 → 取件中 → 配送中 → 已完成

跑腿系统订单状态机的常见状态定义:

状态值含义触发动作
0待支付用户提交订单
1已支付待派单支付回调成功
2已接单骑手确认接单
3取件中骑手点击已取件
4配送中取件后进入配送
5已完成用户确认收货或系统自动确认
-1已取消用户取消或超时取消

状态机的关键约束是禁止非法跳转,比如已取消的订单不能变成配送中。严谨的做法是用一张状态机表维护允许的跳转关系,业务代码里通过校验函数判断。大部分开源项目不会做得这么重,通常只在订单服务里加一个状态更新统一入口,所有状态变更都经过这个函数。

const VALID_TRANSITIONS: Record<number, number[]> = { 0: [1, -1], 1: [2, -1], 2: [3, -1], 3: [4], 4: [5], 5: [] }; export function canTransition(from: number, to: number): boolean { return VALID_TRANSITIONS[from]?.includes(to) ?? false; }

预约取件单在状态 1 时有个额外动作:定时任务提前 30 分钟扫描当天所有预约单,筛选出“临近取件时间但还没有骑手接单”的订单,优先进入自动派单队列。这保证了预约单不会被漏掉。

4.3 骑手端收到订单一瞬间发生了什么

系统派单推送给骑手端,链路是后端通过 WebSocket 向指定骑手连接推送消息。骑手端收到新订单消息后,弹窗模态框展示订单摘要,骑手点击“确认接单”,前端发起请求:

// 骑手端确认接单 const res = await uni.request({ url: '/api/orders/accept', method: 'POST', data: { orderId: order.id, riderId: getApp().globalData.riderId } });

后端处理逻辑包含三个步骤:校验订单状态是否为待接单、校验骑手是否在可用状态、用乐观锁条件更新订单表。三者缺一不可,否则会出现两个骑手同时接同一单的严重事故。

4.4 并发抢单的正确姿势:乐观锁 / 事务

广播抢单模式下并发是最难处理的。假设两个骑手几乎同时提交接单请求,后端收到两个请求,都查出来订单状态是 1(待接单),如果直接更新就可能重复接单。解决方法是把状态校验和状态更新合并为一条带条件的 SQL:

UPDATE orders SET rider_id = ?, status = 2 WHERE id = ? AND status = 1;

执行这条 UPDATE 后检查影响行数。affected rows 等于 1 说明抢单成功,等于 0 说明订单已被别人抢走,直接返回“手慢了”。这条 SQL 利用了行锁,后到的请求会等先到的请求提交后再执行,拿到的是最新状态,所以不会出现重复更新问题。

提示:不要在小程序端做“本单是否可抢”的判断,这个判断必须落在后端且必须在状态更新语句里完成。前端判断只是体验优化,后端条件更新才是正确性保障。

再用事务把订单状态更新和状态日志插入包在一起,保证要么状态变更与日志同时成功,要么同时回滚:

START TRANSACTION; UPDATE orders SET rider_id = 1001, status = 2 WHERE id = 500 AND status = 1; SELECT ROW_COUNT() INTO @affected; -- @affected = 1 才继续,否则 ROLLBACK INSERT INTO order_status_log (order_id, from_status, to_status, operator_type, operator_id) VALUES (500, 1, 2, 2, 1001); COMMIT;

5. 上线后要盯的三个指标和一个抓包技巧

5.1 派单成功率低于 95% 时先查什么

派单成功率是系统健康度的第一指标,统计口径为自动派单及广播回流后骑手最终确认接单的订单占比。如果低于 95%,优先排查 maxDispatchDistance 是否设置过小,用一段 SQL 看看被过滤订单的距离分布:

SELECT ROUND(AVG(distance), 1) AS avg_distance, MAX(distance) AS max_distance FROM orders WHERE status = 1 AND created_at > NOW() - INTERVAL 1 DAY;

平均距离高出最大派单距离的一半时,说明这个参数需要调大。另外一个常被忽略的原因是骑手端不在线,骑手 App 退到后台超过一定时间后,微信小程序的 WebSocket 会被系统挂起,导致推送到达但用户没有感知。骑手端需要在前台运行时接收消息,并把离线状态上报到后端。

5.2 骑手 App 电量与定位上报频率的取舍

骑手端持续上报位置可以提升派单精度,但对手机电量消耗明显。实际运营中通常采用混合策略:骑手活跃状态每 10 秒上报一次定位,空闲状态拉长到 30 秒。后端可以用 Redis 缓存骑手最近位置和最后活跃时间,派单评分时优先读缓存,骑手上报时再回源更新数据库。

位置上报间隔与派单成功率的对应关系可以参考的经验值:10 秒间隔下派单成功率约 98%,30 秒间隔下降到 92% 左右。校园跑腿场景建议保持 10 秒间隔,因为骑手活动范围小、移动速度快,位置过期会让距离计算严重失真。

5.3 用一套可复现的命令验证全流程

每次发布前在测试环境跑一遍以下命令,可以快速确认核心链路没有被改坏。前提是测试环境有一个测试骑手账号和一个测试用户账号:

# 1. 创建订单 curl -X POST http://localhost:3000/api/orders \ -H "Content-Type: application/json" \ -d '{ "userId": 1, "pickupAddress": "东门取件点", "deliveryAddress": "3号楼", "pickupLat": 31.2304, "pickupLng": 121.4737, "deliveryLat": 31.2310, "deliveryLng": 121.4740, "expectedPickupTime": "2025-01-15 12:30:00" }' # 2. 模拟支付回调(测试环境可跳过微信支付) curl -X POST http://localhost:3000/api/mock/pay \ -H "Content-Type: application/json" \ -d '{"orderNo": "返回的order_no"}' # 3. 查询订单当前状态 curl http://localhost:3000/api/orders/500 # 4. 模拟骑手接单与取件 curl -X POST http://localhost:3000/api/orders/500/accept \ -H "Content-Type: application/json" \ -d '{"riderId": 1001}'

验证的标准很简单:步骤 2 完成后步骤 3 返回 status=1,步骤 4 执行成功后再查返回 status=2 且 rider_id 被正确写入。整个过程如果超过 30 秒,说明接口链路里有明显耗时点,应该先压测再放量。

5.4 抓包调试微信小程序的一个实际技巧

小程序端联调时,如果接口请求成功但页面数据异常,建议抓包对比实际返回的 JSON 结构与前端预期结构。以微信开发者工具为例,在“详情 -> 本地设置”里勾选“不校验合法域名”,然后使用本地抓包工具监听 HTTP 请求。注意抓包时的 HTTPS 证书校验问题,小程序端请求的域名如果是测试环境的 IP 地址,通常在工具里关闭校验即可看到完整请求和响应报文。

查看具体请求时,重点看返回数据里的字段命名风格。后端常见的字段是驼峰式pickupAddress,而部分模板代码里把字段名映射成了下划线风格pickup_address,这种不一致会直接导致页面渲染空白。错误信息出现在 Network 面板的 Response 里,直接对照模板数据结构改动映射即可解决。先把返回值的 JSON 复制出来格式化,再和页面 data 里绑定的字段逐一比对,比猜代码快得多。

本文还有配套的精品资源,点击获取

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

安顿预警系统入驻养老院:从连续体征监测到护理闭环落地

简介&#xff1a;面向养老机构管理者、智慧养老方案策划人员及互联网医疗从业者的资源文档&#xff0c;围绕安顿心脑监测预警救护系统&#xff0c;剖析了养老机构引入智能监测后获得的多重价值。内容同时覆盖机构端与老人端&#xff1a;在机构端&#xff0c;详述了如何通过实时…

作者头像 李华
网站建设 2026/9/17 17:07:16

CRC循环冗余校验原理详解:从数据校验到底层报错排查

前阵子在群里看到有人贴了一条数据库安装报错&#xff0c;内容是gzip: stdin: invalid compressed data -- crc error&#xff0c;后面跟了一串问号。说实话&#xff0c;这种报错我一年能碰上好几回&#xff0c;每次都是安装包下载损坏或者拷贝不完整导致的&#xff0c;解决办法…

作者头像 李华
网站建设 2026/9/17 17:06:31

工业大屏响应式重构:从适配方案到交互优化的完整实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 17:06:25

Scratch到Python:3D跑酷从伪3D到真3D迁移实战

做了大半年的少儿编程课&#xff0c;我发现一个挺有意思的现象&#xff1a;同样一个3D跑酷玩法&#xff0c;先用Scratch搭一版、再用Python重写一版&#xff0c;拿给两批刚入门的孩子看&#xff0c;反应完全不一样。Scratch那版十分钟就有人跑出成绩&#xff0c;Python那版第一…

作者头像 李华