跳至内容

Odoo 19 OWL 拖拽:原生 HTML5 + 响应式状态与 ORM 回写

Odoo用户手册·中文文档|对照 Drag-and-Drop 原文的完整实现与最佳实践
2025年11月17日
Odoo 19 OWL 拖拽:原生 HTML5 + 响应式状态与 ORM 回写

看板拖拽、可排序列表、文件投放区,是现代 Web 交互的标配。Odoo 虽自带看板,但自定义仪表板、排程控件、POS 界面仍需自行实现拖拽。Odoo 19 / OWL 3 可直接使用浏览器原生 HTML5 Drag and Drop API,不必引入第三方库。

对照:How to Implement Drag-and-Drop in OWL Components。下文用「待办 / 已完成」任务板示例完整说明。

一、组件职责

OWL 组件维护响应式任务列表;卡片拖到新列时先更新本地状态(界面瞬时反馈),再通过 ORM write 回写阶段;失败则回滚本地状态,保证与服务器一致。

二、关键事件

  • dragstart:记录被拖卡片 ID,写入 dataTransfer
  • dragenter / dragleave:高亮目标列
  • dragover:必须 preventDefault(),否则无法触发 drop
  • drop:读取 ID,改 stage,调用 orm.write

三、实现要点(JS)

onDragStart(ev, taskId) {
  this.state.activeDragCardId = taskId;
  ev.dataTransfer.setData("text/plain", String(taskId));
  ev.dataTransfer.effectAllowed = "move";
}
async onDrop(ev, targetColumn) {
  ev.preventDefault();
  const taskId = parseInt(ev.dataTransfer.getData("text/plain"));
  const task = this.state.tasks.find(t => t.id === taskId);
  if (!task || task.stage === targetColumn) return;
  const prev = task.stage;
  task.stage = targetColumn; // OWL 立即重绘
  try {
    await this.orm.write("project.task", [taskId], {
      stage_id: targetColumn === "done" ? 3 : 1,
    });
  } catch (e) {
    task.stage = prev; // 失败回滚
  }
}

四、模板绑定

列容器绑定 dragenter/dragover/dragleave/drop;卡片 draggable="true" 并绑定 dragstart。可用 class 在拖入时加边框高亮。

五、最佳实践

  1. dragover 必须 preventDefault,否则 drop 不触发。
  2. 写库包 try/catch,校验失败或锁冲突时回滚 UI。
  3. 用响应式状态做视觉反馈,拖拽中勿做重计算绑定,保持流畅。

六、适用场景

自定义看板、仓拣规划、生产排程、POS 单据整理、仪表板卡片重排、文件投放区等。

七、FAQ

为何用原生 API?无额外依赖,与 OWL 契合,多数 Odoo 场景足够。
为何 dragover 要 preventDefault?浏览器默认禁止放置,需显式允许。
UI 如何自动更新?useState 列表变更后 OWL 细粒度补丁 DOM。

中国Odoo网原创中文改写|保留事件流、代码要点、最佳实践与 FAQ。Odoo用户手册 · 中文文档 · 实操。

Odoo 19 OWL 拖拽:原生 HTML5 + 响应式状态与 ORM 回写
2025年11月17日
存档