☰
SpringBoot+Vue+MyBatis构建船运物流管理系统:从0到1全栈实践
2026/9/27 1:26:48 网站建设 项目流程

做船运物流管理系统这类中后台项目,业务梳理永远比技术选型更重要。我见过不少团队一上来就建表写接口,结果做到一半发现提单号、航次、箱号这些核心字段的关联关系没设计清楚,数据对不上账号,只能推倒重来。这篇内容围绕SpringBoot+Vue+MyBatis+MySQL这套经典技术组合,完整拆解一个船运物流管理系统从0到1的实现过程,包括业务模块拆解、数据库设计、核心接口实现、前端交互方案以及我实测踩过的坑。无论是准备接外包、做课程设计,还是想学习主流Java技术栈整合的开发者,这篇都能给你一套可以直接落地的参考方案。

1. 项目整体设计与业务拆解

1.1 船运物流系统的真实痛点

船运物流和普通陆运物流最大的区别在于单证多、环节多、参与角色复杂。一个简单的出口流程可能涉及委托方、货代、船公司、报关行、车队和场站,每个角色都有自己的系统,但数据和流程必须串起来。

实际开发中,最头疼的业务场景有四个:

第一是一票多箱和一箱多票。一个提单号下面可能挂着几十个集装箱,而某些拼箱业务里一个集装箱又可能混装多票货物。这种多对多关系如果不在表结构设计阶段处理妥当,后面统计库存和费用时会非常痛苦。

第二是船期变更与动态追踪。船只会晚点、会跳港,ETD和ETA随时会变。系统需要记录每次变更,并且把变更推送给所有关联订单,这涉及状态机和事件通知机制。

第三是费用结算的颗粒度。海运费用包括海运费、码头操作费、文件费、报关费等多个明细项,每个明细都要能归属到提单或箱号,而且汇率波动还要按账单日期折算,纯靠Excel根本撑不住。

第四是角色权限差异巨大。财务想看费用和收款,操作员想看箱货状态,管理层想看统计报表,同一个页面不同角色看到的字段都不一样。所以权限控制不能只靠前端按钮级隐藏,必须在后端接口层面做数据范围过滤。

1.2 技术选型:这套组合为什么能打

SpringBoot+Vue+MyBatis+MySQL这套组合,放在2025年看虽然不是最前沿的,但在中小型企业管理系统的场景里依然是最稳的选择。

SpringBoot解决了传统SSH项目配置繁琐的问题。内置Tomcat、自动装配、Starter机制让项目可以分钟级启动,生态里无论是集成Redis、消息队列还是报表引擎都有现成方案。船运系统涉及大量增删改查接口,SpringBoot的开发效率非常适配。

Vue在前端层面提供响应式数据绑定和组件化开发。船运系统的表单特别多,比如一个订舱单可能要填几十个字段,Vue的v-model双向绑定和Element UI的form组件组合,能把表单开发效率提升一个量级。Vue Router负责多页面导航,Pinia或Vuex负责跨页面共享用户信息和选中状态。

MyBatis是这套组合里最有争议的选择,但我仍然推荐它。船运业务有大量复杂统计查询,比如按港口、船名、航线做多条件聚合。MyBatis的XML文件可以手写SQL,完全掌控执行计划,比JPA在复杂查询上省心得多。配合PageHelper做分页,动态SQL处理条件组合,基本覆盖90%的查询需求。

MySQL则是成本与性能的平衡点。中小船运公司的数据量级别通常在百万级到千万级之间,单库单表配合合理索引足以支撑。如果未来数据量上来,先做分库分表也不迟,犯不着一开始就用重型分布式数据库。

1.3 模块划分与角色权限

标准的船运物流管理系统,按业务域拆成六个模块就够了:

  • 基础资料:船舶、港口、航线、客户、合作供应商的维护
  • 订单管理:委托单、订舱单、提单的录入、修改、审核
  • 箱货管理:集装箱进出场、装货、拆箱、箱货绑定关系
  • 船期管理:航次发布、船期变更、动态跟踪
  • 费用结算:应收应付台账、账单生成、收付款登记
  • 系统管理:用户、角色、菜单、权限分配

角色上我习惯分四类:管理员、操作员、财务、领导层。管理员管系统配置和用户,操作员处理日常业务单据,财务只管结算模块,领导层只读统计报表。这个划分既覆盖了大部分船运公司的组织架构,又不会因为角色太细导致权限配置成本过高。

权限模型用RBAC就够,核心是五张表:用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。用户登录后查询出角色,再通过角色查出菜单和按钮权限,存到Redis或内存中,接口层面用拦截器校验权限标识。

2. 后端核心设计与数据库建模

2.1 项目分层与通用基础组件

后端工程我按标准分包方式来做,不搞花活:

com.example.shipping ├── controller ├── service │ └── impl ├── mapper ├── entity ├── dto ├── vo ├── config ├── common │ ├── Result │ ├── PageResult │ ├── BusinessException │ └── GlobalExceptionHandler

common包里放的是全局基础组件,这是容易被新手忽略的地方。Result类统一封装接口返回值,PageResult封装分页数据,BusinessException是自定义业务异常,GlobalExceptionHandler用@RestControllerAdvice统一捕获所有异常。这类基础件虽然写起来不复杂,但能保证全项目在错误处理上风格统一,前端只需要解析一个固定的JSON结构,联调效率会高很多。

GlobalExceptionHandler里我强烈建议分三类处理:业务异常直接返回错误码和提示语,参数异常返回字段校验信息,系统异常统一记日志并返回兜底提示。这样前端可以针对不同异常做不同处理,比如业务异常直接弹出消息,系统异常自动上报。

分层原则就是Controller只做参数接收和路由,不写业务逻辑;Service层处理业务规则和事务;Mapper层只做数据访问。我曾经接手过一个把SQL写在Controller里的项目,后来加一个字段要改三处,维护成本极高。按层次清晰划分,后期迭代是真的省心。

2.2 船运业务表结构设计

船运系统的表结构,核心就是围绕三个主数据:订单(订单/提单)、集装箱、航次(船期)。

订单表和提单表可以合一,用字段区分类型,建表时推荐这样设计:

CREATE TABLE `biz_order` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `order_no` VARCHAR(32) NOT NULL COMMENT '委托单号', `bill_no` VARCHAR(32) DEFAULT NULL COMMENT '提单号', `order_type` TINYINT NOT NULL COMMENT '1-整箱 2-拼箱', `customer_id` BIGINT NOT NULL COMMENT '客户ID', `pol_id` BIGINT NOT NULL COMMENT '起运港ID', `pod_id` BIGINT NOT NULL COMMENT '目的港ID', `vessel_name` VARCHAR(64) DEFAULT NULL COMMENT '船名', `voyage_no` VARCHAR(32) DEFAULT NULL COMMENT '航次', `etd` DATETIME DEFAULT NULL COMMENT '预计开船时间', `eta` DATETIME DEFAULT NULL COMMENT '预计到达时间', `status` TINYINT NOT NULL COMMENT '状态:0草稿 1已订舱 2已装船 3已到港 4已完成', `remark` VARCHAR(500) DEFAULT NULL, `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_bill_no` (`bill_no`), KEY `idx_pol_pod` (`pol_id`, `pod_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

集装箱表需要注意状态变化频繁,我加了一个current_status字段记录箱子的当前位置和状态组合,避免每次查询都要走关联运算:

CREATE TABLE `biz_container` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `container_no` VARCHAR(32) NOT NULL COMMENT '箱号', `order_id` BIGINT DEFAULT NULL COMMENT '关联订单ID', `bill_no` VARCHAR(32) DEFAULT NULL COMMENT '提单号', `size_type` VARCHAR(16) NOT NULL COMMENT '箱型,如20GP/40HQ', `seal_no` VARCHAR(32) DEFAULT NULL COMMENT '封条号', `current_status` VARCHAR(16) NOT NULL COMMENT '箱状态:E-空箱 F-重箱 R-已返场 G-已离场', `location` VARCHAR(128) DEFAULT NULL COMMENT '当前位置描述', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_container_no` (`container_no`), KEY `idx_order_id` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='集装箱表';

这里有一个实际开发中的建议:utf8mb4是必须的,因为集装箱号、港口名里可能出现特殊字符,更重要的是客户公司名里经常有生僻字或emoji,老式utf8存不了,直接报错。这个问题我遇到不下三次,都是线上才爆出来的。

航次表单独建,因为一个航次对应多个订单:

CREATE TABLE `biz_voyage` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `voyage_no` VARCHAR(32) NOT NULL, `vessel_name` VARCHAR(64) NOT NULL, `route_id` BIGINT NOT NULL COMMENT '航线ID', `etd` DATETIME NOT NULL, `eta` DATETIME NOT NULL, `status` TINYINT NOT NULL COMMENT '1未开船 2已开船 3已到港 4已截单', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_voyage_no` (`voyage_no`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='航次表';

费用表我采用一套比较通用的设计:费用单头表和费用明细表分开。明细表每条记录一个费用项,包含费用类型、币种、金额,通过source_type和source_id关联到订单或箱号。这种设计既能支持一张账单多笔费用,又方便按单维度聚合统计。

2.3 MyBatis动态SQL与分页实战

船运系统的查询条件千变万化:按提单号、客户、时间范围、港口、状态组合查询是常规操作。MyBatis动态SQL是处理这类需求的利器。

<select id="selectOrderPage" resultType="com.example.shipping.vo.OrderVO"> SELECT o.*, c.customer_name, p1.port_name AS pol_name, p2.port_name AS pod_name FROM biz_order o LEFT JOIN base_customer c ON o.customer_id = c.id LEFT JOIN base_port p1 ON o.pol_id = p1.id LEFT JOIN base_port p2 ON o.pod_id = p2.id <where> <if test="billNo != null and billNo != ''"> AND o.bill_no LIKE CONCAT('%', #{billNo}, '%') </if> <if test="customerId != null"> AND o.customer_id = #{customerId} </if> <if test="status != null"> AND o.status = #{status} </if> <if test="startTime != null"> AND o.create_time &gt;= #{startTime} </if> <if test="endTime != null"> AND o.create_time &lt;= #{endTime} </if> </where> ORDER BY o.create_time DESC </select>

<where>标签自动去掉多余的AND,避免手动拼接SQL的麻烦。这个写法是MyBatis最实用的功能之一,我在所有列表查询上都用这种模式。

分页直接用PageHelper,这个插件在SpringBoot项目里接入非常简单:

<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.7</version> </dependency>

使用上有一个必须记住的规则:PageHelper.startPage()后面必须紧跟第一条查询语句。它底层是利用MyBatis拦截器,在执行器执行查询前自动改写SQL,追加LIMIT子句。如果把startPage和查询之间插了其他数据库操作,分页就会失效。

PageHelper.startPage(pageNum, pageSize); List<OrderVO> list = orderMapper.selectOrderPage(query); PageInfo<OrderVO> pageInfo = new PageInfo<>(list);

注意返回的PageInfo里有total、pages、hasNextPage等完整分页信息,前端直接套用就行,不需要自己再查COUNT。

2.4 事务与并发处理

船运业务里有两个典型的并发场景必须处理。

一个是订舱时的数据一致性。比如某个航次剩余舱位,多个操作员同时订舱,如果都用SELECT 剩余舱位 THEN UPDATE的套路,后提交的请求会覆盖先请求的更新,超订问题就来了。

处理方式是UPDATE时带上条件:

UPDATE biz_voyage SET remaining_slots = remaining_slots - 1 WHERE id = #{voyageId} AND remaining_slots > 0

通过affected_rows判断是否扣减成功,返回0表示舱位已经被抢完。这种做法比悲观锁和Redis分布式锁都简单有效,而且天然无锁化。

另一个是装箱状态流转。集装箱从"空箱"到"重箱"再到"返场",每个状态变更都关联订单状态的更新。这类跨表操作必须加事务:

@Transactional(rollbackFor = Exception.class) public void confirmLoading(ContainerLoadDTO dto) { // 更新箱子状态为F-重箱 containerMapper.updateStatus(dto.getContainerId(), "F"); // 更新订单状态为已装船 orderMapper.updateStatus(dto.getOrderId(), 2); // 写入操作日志 logMapper.insert(new OperationLog(...)); }

rollbackFor = Exception.class这个参数一般都要显式声明,否则遇到受检异常事务不会回滚,数据就会产生中间态。这是Spring事务最容易踩的坑,没有之一。

3. 前端Vue工程搭建与核心交互

3.1 环境准备与工程初始化

前端我推荐Vue3搭配Vite,组件库用Element Plus。Vue3的组合式API在处理复杂业务表单时,逻辑复用比Vue2的Options API清晰很多。

环境准备只需要三步:

# 1. 安装Node.js,建议用18以上版本 node -v # 2. 安装依赖并启动 npm install npm run dev

npm install是新手最容易卡住的步骤。国内环境建议先设置淘宝镜像:

npm config set registry https://registry.npmmirror.com

用Vite创建工程:

npm create vite@latest shipping-web -- --template vue cd shipping-web npm install vue-router@4 pinia element-plus axios

工程初始化之后,我做的第一件事永远是配置全局样式和布局组件。左侧菜单、顶栏面包屑、右侧内容区,三个区域用flex布局撑起来,之后每个业务页面只需要往内容区里填内容就行。

3.2 路由、权限与axios封装

路由配置里有两件事值得多说。第一是路由懒加载,用到哪个页面才加载哪个组件的JS,首屏时间能明显降低:

const routes = [ { path: '/', redirect: '/dashboard' }, { path: '/dashboard', component: () => import('@/views/Dashboard.vue') }, { path: '/order', component: () => import('@/views/order/OrderList.vue') } ];

第二是路由参数传递。Vue Router传参有三种方式:query方式参数会暴露在URL上,适合分享链接;params方式参数不会出现在URL上,但刷新页面会丢失;meta方式适合传对象类型的数据。比如从订单列表跳到订单详情,我建议用query传订单号,因为订单详情页刷新后依然能正常取到参数:

router.push({ path: '/order/detail', query: { id: orderId } });

权限控制基于后端返回的菜单和按钮权限。前端路由配置完整,登录后根据用户权限动态过滤出可访问的路由,用addRoute动态注册。在router.beforeEach里做登录校验和权限校验,未登录一律跳转登录页。

axios封装是整个前端项目的核心基础设施。拦截器里做三件事:请求头注入token、响应统一处理业务码、HTTP异常统一提示。船运系统里有不少列表页,请求频繁,所以我在响应拦截器里做了防重复警示:同一个接口3秒内重复报错只弹一次,避免用户狂点按钮时被一堆报错弹窗轰炸。

3.3 核心业务页面的实现思路

订单管理页面是这个系统的门面,我把它的实现思路展开说说。

列表页左侧放筛选区,支持按提单号、客户、港口、时间范围组合筛选。筛选条件封装成一个queryParams对象,任何查询操作都把它传给后端。这里组件拆分很重要,我把筛选区域抽成组件OrderFilter.vue,列表区域抽成OrderTable.vue,父组件只负责数据流通。这样如果筛选条件变复杂了,改动只限定在OrderFilter.vue内部,不会影响其他部分。

订单详情页用el-tabs分四个页签:基本信息、集装箱明细、费用明细、操作日志。页面拿到的数据量比较大,所以拆了两个接口:基本信息一个接口,箱货和费用列表按需单独加载。尤其是箱货列表,可能上千条记录,如果跟着基本信息一起返回,前端渲染会卡顿。

表单页是船运系统开发中投入最多的部分。订舱单有几十个字段,我建议用el-form配合rules做校验,把复杂的字段分组展示。日期段用el-date-picker封装,港口选择用el-select远程搜索。还需要处理一个特别场景:当提单号输入后,自动带出船名航次,这是通过监听提单号的change事件调用后端接口实现的,交互体验会好很多。

3.4 前后端联调的跨域问题

前端通过Vite启动在5173端口,后端运行在8080端口,浏览器直接请求会被跨域策略拦截。这个问题开发阶段就有标准解法,在Vite配置里加代理:

// vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } } })

这样前端请求/api/order/list,代理自动转发到http://localhost:8080/order/list,问题就解决了。但要注意生产环境依然是Nginx转发,如果部署到线上后接口请求404,优先排查Nginx的location /api配置,而不是怀疑代码逻辑。

4. 从零跑通:环境搭建与部署发布

4.1 基础环境清单

在动手前,先把环境检查一遍,能少走很多弯路:

组件推荐版本说明
JDK1.8 或 171.8最稳,17用于新特性
Maven3.6+依赖管理
MySQL5.7 或 8.05.7经典,8.0性能更好
Node.js18+Vite需要高版本
Nginx1.24生产环境部署

MySQL安装不难但容易在初始化上翻车。我见过不少同学安装完8.0版本后配置了utf8mb4还是乱码,其实是忘了在my.cnf里配置连接字符集。推荐直接在配置文件里写三行:

[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci

4.2 数据库初始化与后端启动

数据库脚本我建议分三个文件:schema.sql负责建库建表,data.sql负责基础数据和测试数据,init_admin.sql负责初始化管理员账号。这样在测试环境重建库时不用全部执行,只需要跑schema和data两个文件。

执行脚本:

mysql -u root -p < schema.sql mysql -u root -p < data.sql

后端启动前,检查application.yml里的数据源配置:

spring: datasource: url: jdbc:mysql://localhost:3306/shipping_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

map-underscore-to-camel-case必须开启,这样数据库的下划线字段能自动映射到驼峰属性的实体类,少写无数resultMap。log-impl设成StdOutImpl是为了开发阶段在控制台直接看SQL语句,排查问题非常方便,生产环境记得关掉或者改成Slf4jImpl。

后端启动就是一条命令:

mvn spring-boot:run

看到Started ShippingApplication表示启动成功,直接访问http://localhost:8080/swagger-ui.html就能验证接口是否正常。如果项目里没有集成Swagger,至少保证有一个/ping接口用来验证服务是否存活。

4.3 前端启动与打包部署

前端开发模式启动:

npm run dev

生产环境打包:

npm run build

打包产物在dist目录,部署到Nginx时需要配置正确的路由。Vue项目是单页应用,Nginx配置要注意history模式的路由回退,否则刷新或者直接访问二级路由会404:

server { listen 80; server_name your-domain.com; root /opt/shipping-web/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

try_files $uri $uri/ /index.html;这行就是关键,所有未匹配到文件的请求都回退到index.html,交给前端路由处理。

4.4 生产环境部署要点

后端打包成可执行jar:

mvn clean package -DskipTests java -jar target/shipping-system-1.0.0.jar --spring.profiles.active=prod

生产环境我建议这样组织配置:application.yml放公共配置,application-prod.yml放生产环境特有的数据源和日志配置。日志用logback-spring.xml配置按天滚动,保留30天,避免磁盘被日志占满。

有一件事很多人忽略:上线前一定要改MySQL时区和密码策略。我曾经遇到过一个问题,服务器时区是UTC,MySQL时区也是UTC,数据库存的时间比本地时间晚了8小时。前端显示的数据全是"穿越"的,排查了很久才发现是连接串里的serverTimezone写成了UTC,改成Asia/Shanghai就好了。

5. 常见问题与排查技巧实录

5.1 高频问题速查表

问题原因解决方案
前端接口404Nginx未配置代理或代理路径错误检查location /api配置,确认rewrite规则
跨域报错开发环境未配置proxyvite.config.js配置proxy
数据库连接超时MySQL地址或端口错误、防火墙拦截ping服务器、telnet端口,确认3306放行
启动报端口占用上一次进程未完全关闭`netstat -ano
中文乱码数据库/连接字符集不一致统一utf8mb4,检查连接串characterEncoding
分页不生效PageHelper.startPage与查询之间插入了其他操作startPage后紧跟查询语句
页面刷新404Nginx未配置try_files回退配置try_files $uri $uri/ /index.html;

这里最能省时间的一个技巧:后端报错日志一定要完整粘贴到搜索引擎或AI工具里搜,错误信息往往直接指向根因。我在带新人时经常发现他们看到异常就慌,其实控制台里第一行Caused by就写明白了。

5.2 MyBatis和MySQL的坑

MyBatis的@Param注解。方法参数超过一个,并且XML里引用了参数名时,必须加@Param:

List<OrderVO> selectByCustomerAndStatus(@Param("customerId") Long customerId, @Param("status") Integer status);

如果不加,XML里就不能用#{customerId},会报Parameter 'customerId' not found。早期版本还要求参数名与XML一致,所以我习惯在每个多参数方法上都显式加@Param,省心。

LIKE模糊查询的坑。Java代码里直接用%拼接会有SQL注入风险,推荐用CONCAT在SQL里拼:

<if test="billNo != null and billNo != ''"> AND bill_no LIKE CONCAT('%', #{billNo}, '%') </if>

不要用SELECT *。线上系统字段几十个,SELECT *会把不想暴露的字段也查出来,还会让MySQL优化器放弃覆盖索引。我写的每一条查询SQL都会最小化字段列表,只查需要的列。这点在船运系统的列表页尤其重要——列表页展示的字段可能只有十来个,结果因为同事用了SELECT *,拉回来的数据量大了几倍。

MySQL查询慢的排查思路。先用EXPLAIN看执行计划,关注type列:ALL代表全表扫描,通常需要加索引;range和ref都算正常。船运系统里提单号和箱号这种高频查询字段,一定记得建唯一索引或普通索引。另外一个冷门坑:查询条件里对索引字段用了LEFT()函数或%xxx%这种前缀通配,索引会失效,查询直接退化到全表扫描。

5.3 项目后续扩展方向

项目跑通之后再扩展,有几个方向性建议。

报表模块是船运公司最常提的增补需求。订单量按周按月趋势、港口吞吐量排名、客户业务量统计,这几张报表需求出现频率最高。实现方案可以先用el-echarts做图表,配合后端聚合查询接口。数据量变大后再考虑排查引入报表引擎,前端纯展示的方案就够用。

流程审批。订舱单、账单需要多层审批的场景,可以引入Flowable流程引擎,在现有订单表上加process_instance_id关联流程实例。Flowable和SpringBoot整合成熟,有现成的UI可以做流程设计,但工作量不小,建议控制在1-2个核心审批流。

消息通知。船期变更推送给客户是很高频的需求。简单做法是集成WebSocket,后端在船期变更接口里向在线用户推送消息;复杂一点是接入短信或邮件,在变更发生时异步发送。这个功能可以放在V2版本里做,第一版先把核心业务闭环跑通。

写到最后

这套系统我从2023年开始做,到现在迭代过三个大版本,最大的体会是:船运物流系统真正的复杂度不在技术上,而在业务规则和数据关系的梳理上。技术选型用大家都熟的SpringBoot+Vue+MyBatis+MySQL,能把精力集中在解决业务问题上。如果你正在做同类系统,建议动手前先花一周时间把业务方拉在一起,把订单、箱子、航次、费用这四类主数据的流转路径画清楚,画不清楚的坚决不建表。编码阶段反而快,三周左右就能出第一版可用的系统。碰到问题也别慌,数据库家的字段、接口返回的数据、前端页面展示,按这三层逐层排查,问题通常都会很快浮出水面。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询