news 2026/10/21 4:09:58

Qt GraphicsView实战:鼠标拖拽绘制矩形的图形交互底座

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt GraphicsView实战:鼠标拖拽绘制矩形的图形交互底座

简介:这是基于QT 5.15.2与VS2019开发的图形交互演示程序,核心通过GraphicsView、QPainter和QGraphicsRectItem实现鼠标绘制矩形,并可直接扩展为多边形绘制。资源面向有C++基础、需要上手QGraphicsView视图框架的Qt学习者,同样适合做图像标注、ROI框选等工具时参考。压缩包仅18KB,共16个文件,其中5个.h头文件用于类声明与接口,5个.cpp源文件承载具体交互实现,另有vcxproj、ui、qrc等工程与资源文件,结构完整,无需额外配置即可编译运行。该Demo目前已吸引2349人学习下载。功能方面,支持鼠标一次绘制多个矩形,拖动图形项可改变位置,拖拽边缘可拉伸宽高,按住Ctrl滑动滚轮可缩放视图;右键菜单则提供删除和保存操作,便于管理图形对象。通过该示例,可以掌握QGraphicsView的事件体系、自定义图元、碰撞检测、坐标转换及视图缩放等关键机制,为后续实现复杂交互图形工具打下基础。

1. 项目定位:一个用鼠标拖拽就能画矩形的 Qt 图形交互底座

我之前在做一个简单的图元标注工具,直接在 QWidget 的 paintEvent 里用 QPainter 画矩形,鼠标一拖动就 update 整个界面,图元多了以后掉帧明显,选中、删除、移动全靠手写命中判断,整个交互代码乱成一团。后来切到 GraphicsView 体系,才发现用 QGraphicsView、QGraphicsScene 加 QGraphicsRectItem,鼠标拖拽绘制矩形其实只是几十行的事。这个项目把“按下起点、拖动成型、松开固定”这个画矩形闭环完整落了一遍,并且把场景、视图、图元三类对象拆得清清楚楚。它不是那种炫技 Demo,而是你能直接拿来做标注工具、流程图节点、简单制图编辑器的底座。适合刚接触 Qt 图形视图框架的人照着敲一遍,也适合老手拿来比对坐标转换和事件分发的标准姿势。

2. 技术选型与整体架构:为什么用 QGraphicsView 而不是直接 QPainter

2.1 从“裸画”到“场景-视图-图元”

很多新手拿到“用鼠标绘制矩形”这个需求,第一反应是在 QWidget 子类里保存一个 QRect,然后在 paintEvent 里调用 painter->drawRect(),鼠标移动时触发 update()。这个思路在小程序里看起来没问题,但一旦矩形数量上到几十上百,或者需要缩放、选中、移动、标注,重绘范围会爆炸,命中算法也要自己写。

QGraphicsView 带来的是一套“场景-视图-图元”的模型。QGraphicsScene 保存所有图元的世界坐标数据,QGraphicsView 负责把场景渲染到屏幕并处理鼠标键盘事件,QGraphicsItem 则是一个个有身份的对象。矩形画出来后就是一个 QGraphicsRectItem,它有独立的矩形、画笔、画刷,还能响应 hover、选中、拖拽。更重要的是,场景内部维护了空间索引,重绘时只更新变化区域,性能比整窗重绘好一个量级。

这个项目里 QPainter 也不是退场了,而是降级成“画图元细节”的画笔。比如自定义 RectItem 时,抗锯齿、虚线边框、选中高亮都要在 paint() 里用 QPainter 设置。换句话说,QPainter 负责像素级输出,Graphics View 负责对象管理和事件分发,两者搭配各干各的。

2.2 目录结构与类职责

我按“视图与场景分离、场景只管交互、视图只管显示”的原则,把工程拆成几个小文件:

文件职责
main.cpp创建 QApplication 和 MainWindow
MainWindow.h/.cpp组装 scene、view,设置窗口标题和尺寸
DrawScene.h/.cpp继承 QGraphicsScene,处理鼠标按下/移动/释放,维护正在绘制的临时矩形
DrawView.h/.cpp继承 QGraphicsView,开启鼠标跟踪,实现滚轮缩放和锚点跟随鼠标
RectItem.h/.cpp继承 QGraphicsRectItem,重写 paint(),决定矩形外观和选中态

这个拆分的好处是:鼠标交互逻辑全部集中在 DrawScene,显示相关的缩放、平移全部留在 DrawView,业务图元扩展时单加一个类文件,MainWindow 不用跟着改。后续想加椭圆、多边形、文字,只需要仿照 RectItem 再建类,并在 DrawScene 里根据当前绘制模式创建对应 item。

2.3 一次鼠标拖拽的完整交互链路

把一条绘制命令走完需要经过三个坐标空间:屏幕坐标、视图坐标、场景坐标。鼠标在 QGraphicsView 上按下时,事件里拿到的是 viewport 坐标,也就是视图坐标;QGraphicsView 自动会把视图坐标转换成场景坐标并封装成 QGraphicsSceneMouseEvent 发给 scene。所以 DrawScene 里使用 event->scenePos() 拿到的就是世界坐标,不需要再做 mapToScene。

交互链路是:鼠标左键按下,DrawScene 记录起点 scenePos,创建一个宽高为 0 的临时 RectItem 加入场景。鼠标拖动过程中,DrawScene 用事件场景坐标和起点构成新 QRectF,马上 setRect 到临时 item 上;因为场景只对临时 item 的旧区域和新区域做更新,不会全屏重绘。鼠标松开时,如果矩形宽高小于设定阈值就删掉临时 item,否则保留;随后把 m_currentRect 置空,等待下一次绘制。

这条链路里最容易出问题的点是“从右下往左上拖”。用户拖出来的起点和终点顺序不固定,直接构造 QRectF 会出现负宽高,绘制结果要么消失要么反向。我一般会在 setRect 之前调用 normalized(),它会自动把矩形的左上角和右下角重新归一化,负宽高问题直接消失。这个细节很多新手会踩,后面避坑章节会再展开。

3. 核心代码拆解:从鼠标按下到矩形上屏的完整链路

3.1 DrawScene:状态管理与事件转发

DrawScene 是整个交互的中枢,它需要记录两个私有成员:按下时的起点 m_startPoint,以及正在绘制的临时 item m_currentRect。

头文件里关键部分:

class DrawScene : public QGraphicsScene { Q_OBJECT public: explicit DrawScene(QObject *parent = nullptr); protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; private: QPointF m_startPoint; QGraphicsRectItem *m_currentRect = nullptr; };

m_currentRect 初始化为 nullptr,用来区分“是否正在绘图”。m_startPoint 存放鼠标按下瞬间的场景坐标。这里不把起点放在矩形内部,是为了在 mouseMove 里用两点动态构造 QRectF,方便做归一化。

mousePressEvent 的实现:

void DrawScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier) { m_startPoint = event->scenePos(); // 先建一个宽高为 0 的占位矩形,让后续 move 只改 rect 属性 m_currentRect = new QGraphicsRectItem(QRectF(m_startPoint, QSizeF(0, 0))); m_currentRect->setPen(QPen(QColor(0, 140, 255), 2)); m_currentRect->setBrush(QBrush(QColor(0, 140, 255, 30))); addItem(m_currentRect); } QGraphicsScene::mousePressEvent(event); }

这段代码的意思是:只有左键且没有修饰键时才开始画矩形。加了 event->modifiers() == Qt::NoModifier 这个条件,是防止后面扩展“按住 Ctrl 改为移动已有矩形”时,按下事件也被画矩形逻辑吞掉。占位矩形宽高是 0,实际绘制时某些风格下看不见,但鼠标一移动就能看见变化。把鼠标事件继续传给父类,是为了让场景内部已有的图元仍能收到 hover、选中等事件。

mouseMoveEvent 的实时更新:

void DrawScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (m_currentRect) { QPointF current = event->scenePos(); QRectF newRect(m_startPoint, current); // normalized 把负宽高修正为标准左上/右下矩形 m_currentRect->setRect(newRect.normalized()); } QGraphicsScene::mouseMoveEvent(event); }

这里没有自己做“只画一次”的标记,因为 setRect 每次都会提交新几何体,场景内部会自动计算新旧矩形的并集区域并执行局部更新。如果觉得拖动过程锯齿感明显,可以在 DrawView 里开启 setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate),换一种刷新策略。

mouseReleaseEvent 的收尾逻辑:

void DrawScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (m_currentRect && event->button() == Qt::LeftButton) { QRectF rect = m_currentRect->rect(); // 面积过小,通常是没有画完或者误触,直接销毁 if (rect.width() < 5 || rect.height() < 5) { removeItem(m_currentRect); delete m_currentRect; } m_currentRect = nullptr; } QGraphicsScene::mouseReleaseEvent(event); }

注意 delete 之前也要 removeItem,否则场景里还残留这个 item 的索引;QGraphicsScene 虽然会在销毁 item 时自动移除,但显式 removeItem 能让你在释放时马上知道它不会再参与交互。阈值 5 默认是场景坐标,如果你在缩放很大的视图里工作,建议用 view 的 mapToScene 把 5 个屏幕像素换算成场景长度。

3.2 DrawView:缩放、平移与锚点跟随

DrawView 的构造里需要开启鼠标跟踪,否则没有按下键时 mouseMoveEvent 不会触发,场景里的拖动绘制也要靠按下状态,所以这里很关键:

DrawView::DrawView(QGraphicsScene *scene, QWidget *parent) : QGraphicsView(scene, parent) { setMouseTracking(true); setRenderHint(QPainter::Antialiasing); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorViewCenter); }

setMouseTracking(true) 让场景能在不按住鼠标时也收到 mouseMoveEvent,这对绘制某些悬浮效果、路径预览是必要的。setTransformationAnchor(QGraphicsView::AnchorUnderMouse) 的意义是滚轮缩放时,缩放中心是鼠标当前指向的位置,而不是视图中心。这种交互在图形编辑工具里是基本功,否则你缩放着就找不到刚画的图形。

滚轮缩放实现:

void DrawView::wheelEvent(QWheelEvent *event) { const double factor = 1.0015; double scaleFactor = event->angleDelta().y() > 0 ? factor : 1.0 / factor; scale(scaleFactor, scaleFactor); }

这个 factor 是步长。1.0015 很温和,滚一格变化很小;如果你希望快速缩放,改成 1.1。使用 scale() 时,QGraphicsView 会基于 transformationAnchor 做锚点变换,所以不需要额外代码。注意 scale() 是乘法累积,滚久了视图尺度会呈指数膨胀,所以要在合适的位置限制缩放范围,比如不超过 1000 倍,不小于 0.001 倍。

3.3 RectItem:用 QPainter 画出“活”的矩形

虽然 QGraphicsRectItem 本身已经会在视图上画矩形,但为了统一风格,也为了后续加点位句柄,我选择继承它并重写 paint():

class RectItem : public QGraphicsRectItem { public: RectItem(const QRectF &rect); protected: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; };

paint() 的实现:

void RectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setRenderHint(QPainter::Antialiasing, true); QPen pen(selected() ? QColor(255, 140, 0) : QColor(0, 140, 255)); pen.setWidth(2); pen.setStyle(selected() ? Qt::DashLine : Qt::SolidLine); QBrush brush(QColor(0, 140, 255, 30)); painter->setPen(pen); painter->setBrush(brush); painter->drawRect(rect()); }

这里把 selected 状态画成橙色虚线,正常状态是蓝色实线,颜色一眼能区分。QPainter 的 setRenderHint(QPainter::Antialiasing, true) 让矩形边缘平滑,在缩放级别很大的时候尤其必要。注意 drawRect 用的是 item 自己的局部坐标系统 rect(),因为 QGraphicsRectItem 的左上角就是 item 原点,painter 已经使用该 item 的坐标系,所以直接用 rect(),不需要自己做坐标转换。

有了这三个类,MainWindow 里只需要把 scene 和 view 组装:

DrawScene *scene = new DrawScene(this); DrawView *view = new DrawView(scene, this); setCentralWidget(view); setWindowTitle(QStringLiteral("鼠标绘制矩形 - GraphicsView Demo"));

到这里,一个“按下、拖动、松开”的完整循环就已经闭合了。代码量很小,但背后三件事很值得咀嚼:事件分发由 scene 利用 view 传入的场景坐标完成,item 的几何更新由 setRect 驱动,视图的缩放平移由 transformation anchor 提供正确语义。

4. 运行指南与参数调整:让项目在你机器上跑起来

4.1 CMake 构建与 Qt 环境准备

这个项目的依赖只有 Qt Widgets 模块,不需要额外库。我以 CMake 作为构建工具,Qt 6.2 以上环境为例,也可以用 Qt 5.15 直接改一个 find_package 版本。CMakeLists.txt 内容如下:

cmake_minimum_required(VERSION 3.16) project(RectDrawer VERSION 0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) find_package(Qt6 6.2 COMPONENTS Widgets REQUIRED) qt_add_executable(RectDrawer main.cpp MainWindow.cpp MainWindow.h DrawScene.cpp DrawScene.h DrawView.cpp DrawView.h RectItem.cpp RectItem.h ) target_link_libraries(RectDrawer PRIVATE Qt6::Widgets)

在 Linux 或 macOS 终端里执行:

cmake -S . -B build cmake --build build ./build/RectDrawer

Windows 上如果用 Qt 官方的 MinGW 工具链,同样能通过这几条命令跑起来,只是可执行文件在 build\RectDrawer.exe。如果使用 Visual Studio 编译器,cmake --build 会自动调用 MSBuild。唯一需要注意的坑是:CMake 找不到 Qt6 时,优先看环境变量 PATH 是否把 Qt 的 bin 目录加进去了;Windows 上还要看 CMAKE_PREFIX_PATH 是否指到 Qt 安装目录。

4.2 关键参数在哪里改

所有“画成什么样子”的参数都集中在 DrawScene 和 RectItem 里。我列一张常用参数表:

参数所在位置默认值说明
矩形边框颜色DrawScene::mousePressEventQColor(0,140,255)临时矩形与正式矩形共用
边框线宽DrawScene::mousePressEvent2可改成 1 或 3,配合 highdpi
填充色透明度DrawScene::mousePressEvent30数值越大填充越实
最小矩形边长DrawScene::mouseReleaseEvent5小于这个尺寸会丢弃
缩放步长DrawView::wheelEvent1.0015改成 1.05 扎手快
选中态颜色RectItem::paint橙色虚线选中后最直观的反馈

如果想画出来的矩形默认带阴影,可以在 RectItem 的 paint() 里先 fill 一个偏移矩形;如果希望填充色更淡,把 alpha 从 30 调到 10 即可。所有颜色都用 QColor(r,g,b,a) 的方式设置,避免在字符串和对象之间来回转换。

4.3 验证交互是否正常

运行起来后你应该能看到一个空白窗口。按下左键拖动,矩形边框跟着鼠标走,松开后矩形停在画布上;再次画第二个矩形,第一个矩形仍然保留。滚动滚轮,矩形围绕鼠标位置缩放;如果选中矩形,边框变橙色虚线。这些现象全部满足,说明 scene/view/item 之间的坐标和事件分发都对上了。如果只有第一个矩形显示,之后的矩形消失,大概率是临时 item 的指针被删除后还在使用,检查 mouseReleaseEvent 里是否把 m_currentRect 赋成 nullptr。

5. 避坑与排查:坐标转换、负矩形和 item 悬空的实战记录

5.1 从右下往左拖,矩形“蒸发”了

现象:鼠标从右下角往左上角拖,松开后矩形没有出现,或者出现一个边角残缺的形状。

原因:直接用 m_startPoint 和 event->scenePos() 构造 QRectF,如果终点在起点左上,宽和高是负数。QGraphicsRectItem 的矩形几何要求左上角是起点,负宽高会导致绘制结果异常。

解决:调用 QRectF::normalized(),它会把矩形调整为左上-右下正宽高的标准矩形。我在 setRect 前统一调用 newRect.normalized(),不管用户怎么拖都安全。这些坑不算玄学,但都是血泪经验,第一次画矩形的人十有八九会撞上。

5.2 滚轮缩放后矩形位置漂移

现象:缩放视图后,鼠标重新绘制矩形时,矩形位置不在鼠标落点,而是偏移到别处。

原因:这个现象在“直接在 view 里使用 event->pos() 当场景坐标”时必现。view 的坐标与场景坐标之间存在缩放和平移变换,鼠标的 viewport 坐标必须先转换。

解决:统一使用 scene 事件提供的 scenePos(),或者在 view 中调用 mapToScene(event->pos()) 之后再传给 scene。只要保证“入口坐标全部是场景坐标”,缩放、平移后都不会漂移。

5.3 删除 item 后程序崩溃

现象:点击矩形按 Delete 删除,再画新矩形时程序闪退。

原因:大多数从 QGraphicsScene::selectedItems() 拿到 item 后直接 delete,但场景内部还持有这个 item 的指针索引。下一次鼠标事件遍历 item 时,访问了已释放对象。

解决:delete 前要调用 scene->removeItem(item)。更稳妥的做法是封装一个删除函数:先 removeItem,再 delete;或者调用 item->scene()->removeItem(item)。这个项目里虽然在 mouseReleaseEvent 中销毁临时 item 时也做了 removeItem,但新同学很容易在批量删除时忘掉。

5.4 鼠标移动更新特别慢

现象:矩形画到一半,拖动有明显的迟滞感,尤其是窗口已经有很多图元时。

原因:可能是没有使用局部更新策略,或者是临时 item 每次 setRect 都会触发很大区域的更新。默认情况下 QGraphicsView 会使用整块视口更新,当场景复杂时会卡。

解决:在 DrawView 构造里设置 setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate),这个模式会根据 item 的边界矩形刷新最小区域。如果不生效,再检查 DrawScene 的 mouseMoveEvent 是否调用父类导致额外遍历。通常简化事件链后能明显变流畅。

5.5 缩放过度,矩形变成“线”或者“一团黑”

现象:滚轮缩放到很大,矩形线条粗到自己吓一跳,或者缩小后矩形直接看不见。

原因:QGraphicsView 的缩放是对 rendered 内容进行缩放,item 的 pen 宽度默认不随缩放变化,但缩放到一定程度,像素对齐问题会让线条显得异常。

解决:如果希望线宽随视图缩放保持相对恒定,可以在 paint() 里用 painter->worldTransform().m11() 反向补偿 pen 宽度,让物理宽度不随视图缩放变化。常见做法是:

qreal penWidth = 2.0 / painter->worldTransform().m11(); painter->setPen(QPen(color, penWidth));

这样缩放 10 倍后,线条在屏幕上仍然是约 2 个像素宽,不会变成“线”。这是个人偏好,很多编辑器就是这么处理的。

6. 二次开发:从“画矩形”到“图形编辑器”的改造思路

这个项目虽然小,但可以沿着四个方向扩展:图元类型、交互模式、数据持久化、撤销重做。先说图元类型:在 DrawScene 里加一个枚举 DrawingMode,把 mousePressEvent 里 new QGraphicsRectItem 的代码抽成一个工厂方法 createItem(mode, rect),之后加椭圆、多边形就只改工厂。比如增加 ellipse 只需要 new QGraphicsEllipseItem(rect),paint 逻辑可以复用同一个样式类。

交互模式上,可以参考 QGraphicsItem 内置的 ItemIsMovable、ItemIsSelectable flag,给 RectItem 设置 setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable)。这样画完的矩形可以直接拖动,不用写命中检测。需要注意移动和绘制不能冲突:在 DrawScene 的 mousePressEvent 里判断鼠标是否点击到已有 item 的 shape(),如果命中了就启动移动而不是新建矩形;这个判断可以用 scene->itemAt(event->scenePos(), QTransform())。

撤销重做是图形编辑器最刚需的功能。我建议用 QUndoStack,而不是自己保存所有矩形列表。建立一个 AddRectCommand,重写 redo() 把 item 加入场景,undo() 删除 item,再把命令压入 QUndoStack。这样 Ctrl+Z 和 Ctrl+Y 都有了,而且不需要手写快照。

数据持久化最简单的做法是枚举场景里所有 QGraphicsItem,用 QDataStream 把矩形几何、颜色、线宽写到文件里,读取时再按类型重建 item。如果未来图元类型多了,建议给每个 item 加一个自定义类型号,用 int type 在 I/O 时分支。一个小技巧:写入前先用 items() 收集图元,写入 item->rect() 和画笔画刷,读的时候按顺序恢复,就能做到加载后和保存前视觉一致。

我从这个项目里学到的一个习惯是:凡是做图形交互,先画一张坐标转换图贴在旁边。视图坐标、场景坐标、item 局部坐标,三步如果不在动手前理清,后面代码里必然出现 mapToScene / mapFromScene 乱用的情况。现在每次加交互我都会强制走一遍“鼠标落点坐标来自哪里、要送进哪个坐标系、item 几何用什么坐标”,靠这个习惯省下的调试时间,比写业务代码本身还多。希望帮到你。

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

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

SpringBoot2+Vue3图书商城系统源码拆解:从架构设计到前后端部署实战

“图书商城管理系统”这种项目&#xff0c;大家应该都不陌生了。市面上基于SpringBoot的商城代码一抓一大把&#xff0c;但真正让我愿意花时间写一篇完整拆解的&#xff0c;是最近拿到的这套源码&#xff1a;SpringBoot2 Vue3 MyBatis-Plus MySQL8.0的组合&#xff0c;而且自…

作者头像 李华
网站建设 2026/10/21 4:09:57

AI辅助开题报告PPT全流程:从提示词到答辩模拟

每年一到开题季&#xff0c;就有学生捧着刚生成的PPT初稿来找我&#xff0c;问得最多的一句话是&#xff1a;为什么用人工智能技术做出来的开题报告PPT&#xff0c;看着挺工整&#xff0c;但一到导师面前就站不住&#xff1f;答案其实不复杂——工具用对了&#xff0c;方法没跟…

作者头像 李华
网站建设 2026/10/21 4:09:44

彻底移除Windows 11内置AI组件:从原理到实操的完整指南

如果你最近拿到一台预装Windows 11的新电脑&#xff0c;或者老电脑在某个系统更新后突然多了一排AI入口——任务栏上冒出一个Copilot按钮&#xff0c;右键菜单里蹦出“AI总结”&#xff0c;资源管理器里多了一个几个月都用不上的“智能历史”图标——你会发现&#xff0c;这类功…

作者头像 李华
网站建设 2026/10/21 4:08:13

AI辅助学术专著写作:人机协写全流程与实用工具指南

学术专著写作这件事&#xff0c;很多研究者、老师、以及想把自己的知识经验沉淀成书的人&#xff0c;想想就头大&#xff1a;结构怎么搭才严谨&#xff1f;文献怎么查才不遗漏&#xff1f;初稿怎么输出才不卡壳&#xff1f;好不容易写完&#xff0c;统一术语、润色语言、调整格…

作者头像 李华
网站建设 2026/10/21 4:08:05

CVI计算器完整工程:可编译调试的LabWindows/CVI实战项目

简介&#xff1a;本资源是一个基于CodeVisionAVR&#xff08;CVI&#xff09;开发环境的嵌入式简易计算器完整工程&#xff0c;面向嵌入式初学者、AVR单片机学习者及C语言实践者&#xff0c;旨在通过可运行的实操项目掌握用户输入处理、浮点运算逻辑、菜单交互设计与调试流程等…

作者头像 李华
网站建设 2026/10/21 4:08:05

REA模型重构订单系统:从状态字段到事件事实

我最早接触 REA&#xff0c;是因为手上一套老订单系统实在没法改了。不是代码写得差&#xff0c;而是数据模型从第一天起就把"单据"当成了核心&#xff1a;订单表、订单明细表、出库单表、库存流水表、凭证分录表&#xff0c;业务规则一多&#xff0c;表之间开始互相…

作者头像 李华