先说明一点,这类“小程序 + PHP + uniapp”的组合我是很看好的。尤其是汽车销售库存管理这种重业务、重数据、重流程的垂直场景,用这三件套来落地,既能兼顾前端多端复用的成本,又能靠 PHP 生态快速出活。这篇就把从业务拆解到表结构、接口设计、还有实际开发中踩过的坑,一次讲完。
1. 项目整体设计与技术选型思路
先说结论:汽车销售库存管理系统,核心不是“库存”两个字,而是“人、车、钱、单”四条业务线的状态流转。车辆从入库、展厅展示、试驾、订单锁定到出库交付,每一步都涉及库存数据变化和人员权限控制。如果只做一张“库存数量表”,那基本等于给自己埋雷。
1.1 为什么用 uniapp 而不是原生小程序
做过小程序的人都知道,原生小程序开发有几个痛点:一是代码没法跨端复用,以后要出 App 或者 H5 又得重写一遍;二是组件生态和工具链相对封闭,写复杂表单和长列表的时候效率并不高。uniapp 的核心价值就在于“一套代码,多端运行”,你写的是 Vue 语法,编译到微信小程序、H5、甚至 App 都很顺。对这个项目来说,销售顾问在展厅用的是手机,多数情况下是通过微信小程序入口进入,但管理层可能会用 H5 后台看报表,甚至以后要做个平板端给展厅大屏用,uniapp 这套就非常合适。
另外一点很实际:uniapp 的社区组件多,像车辆选择器、图片上传、城市级联选择这类汽车销售里高频出现的控件,稍微封装一下就能复用。不像原生小程序,很多组件要自己从头写。
1.2 为什么后端选 PHP
有人可能觉得 PHP 老土,但在这种企业内部管理系统里,PHP 的优势反而很明显。
第一是部署成本极低。小程序后端要跑在云服务器上,PHP 配 Nginx 或者 Apache 都很成熟,几乎不需要像 Java 那样折腾 JVM 参数。第二是开发效率。汽车销售库存管理的业务逻辑并不算特别复杂,但报表维度多、筛选条件多,PHP 数组处理和 JSON 转换非常灵活,和前端联调很顺手。第三是生态。ThinkPHP 这类框架自带权限认证、ORM、日志、分页组件,正好覆盖这个项目的需求。
我也见过有人用 Node.js 或者 Go 来写,但说实话,团队的熟悉成本、后续维护成本都要考虑。既然标题定的是 PHP,那就老老实实把 ThinkPHP 用透,别贪多。
1.3 整体架构和角色权限模型
这个系统我拆成了四类角色:超级管理员、店长、销售顾问、财务。不同角色看到的小程序页面和数据范围完全不一样,所以后端接口在设计时就必须把权限判断做在服务端,不能靠前端隐藏按钮来糊弄。
- 超级管理员:只看数据仪表盘、所有门店库存、员工账号管理。
- 店长:门店维度,看本店库存、订单审批、进销存报表。
- 销售顾问:录入客户、录订单、办理试驾、操作车辆出库。
- 财务:订单收款确认、发票记录、成本核算。
小程序端不做任何跨门店的数据展示,接口返回的数据在服务端就过滤干净了,这样能避免不少低级漏洞。
2. 业务模块拆解与库存状态机设计
做这套系统,我建议你先画一个业务流程图,不是用工具画得多漂亮,而是把车辆的状态流转理清楚。汽车和普通商品不一样,它有唯一标识——车架号 VIN,所以库存管理必须以“车辆个体”为单位,而不是以“数量”为单位。
2.1 车辆全生命周期状态
一辆车从厂家发运到最终交付,至少要经历这几个状态:
我实际设计时用了一个状态字段car_status来管理,取值范围是 1 到 6:
- 1:在途(厂家已发运,还没到店)
- 2:在库(到店验收完成,可销售)
- 3:展厅展示(已挪到展厅,但仍属于可售库存)
- 4:已预订(客户交定金,车辆被锁定)
- 5:已开票(财务已开票,等待交付)
- 6:已交付(完成交付,出库)
这个状态机的好处是,每个状态变更都能对应一条操作记录,后面的“库存动态流水表”也就有了数据来源。很多团队不做状态机,单纯改库存数量,最后查账的时候一脸懵:这辆车到底去哪了?
2.2 库存流水表是核心资产
我在这套系统里专门建了一张库存流水表。它不直接存“当前库存”,而是记录每一次变化:入库、调拨、预订锁定、出库交付、盘盈盘亏。这样有两个直接好处:
一是任何时间的库存数都可以通过流水反推,而不是只信一个可能被改错的“当前值”。二是出问题能溯源。比如有辆车在库里消失了,一查流水发现是某员工做了“出库”操作,但订单号是空的,那就说明操作有问题。
库存数量的更新必须和流水写入绑在同一个事务里。PHP 里用 ThinkPHP 的Db::transaction()包起来,要么都成功,要么都回滚,别搞成先减库存再记流水,结果流水写失败了,库存却已经减了。
2.3 销售订单和财务联动
订单模块不是简单存一个“客户买了什么车”,而是要和财务收款记录、开票状态一起联动的。
我的订单表设计逻辑是这样的:销售顾问录入订单时,只填客户、车辆、销售价、定金金额。订单状态从“待付款”开始,财务在后台确认收到定金后,订单状态变为“已付定金”,此时车辆状态自动变成“已预订”。尾款结清时,财务再点“确认收款”,订单状态变为“已结清”,车辆才允许进入“待交付”。
这个流程你看起来可能觉得繁琐,但实际业务中这是为了防止销售顾问在客户没付钱的情况下就把车锁了,导致真正要买的客户没车可看。权限上必须卡死:锁定车辆这个动作,默认只有店长或财务能操作。
3. 数据库设计与关键表结构
这部分我直接给出核心表结构思路。不是让你照着抄,而是理解每个字段为什么存在。
3.1 车辆信息表(核心主表)
这张表存车辆的基本属性,关键是建立以 VIN 码为唯一索引的机制。车企生产的每辆车都有一个唯一车架号,这是天然的业务主键。但为了查询性能,我仍然会用一个自增主键 ID,VIN 做唯一索引约束。
CREATE TABLE `car_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `vin` varchar(50) NOT NULL COMMENT '车架号', `brand` varchar(50) NOT NULL COMMENT '品牌', `series` varchar(50) NOT NULL COMMENT '车系', `model` varchar(100) NOT NULL COMMENT '车型配置', `color` varchar(20) DEFAULT NULL COMMENT '外观颜色', `interior_color` varchar(20) DEFAULT NULL COMMENT '内饰颜色', `year` varchar(10) DEFAULT NULL COMMENT '年款', `guide_price` decimal(10,2) NOT NULL COMMENT '官方指导价', `purchase_price` decimal(10,2) NOT NULL COMMENT '采购成本价', `store_id` int(11) NOT NULL COMMENT '所属门店', `car_status` tinyint(4) NOT NULL DEFAULT '2' COMMENT '车辆状态: 1在途 2在库 3展厅 4已预订 5已开票 6已交付', `storage_time` datetime DEFAULT NULL COMMENT '入库时间', `shelf_time` datetime DEFAULT NULL COMMENT '上架展示时间', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_vin` (`vin`), KEY `idx_store_status` (`store_id`, `car_status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆信息表';价格字段必须分开:指导价和采购价。指导价前端可以展示,采购价只能让店长和财务看到,销售顾问登录后接口返回时就该剔除,否则容易引发价格纠纷。store_id和car_status联合索引非常重要,因为小程序库存列表页最常用的查询条件就是“某门店下某状态的车”。
3.2 客户与订单表
客户表我单独建了联系人表,因为一个客户可能会有多个联系人或多个购车意向,虽然大多数情况下都是一个人,但规范化设计能避免后面扩展时改表结构。
订单表的核心逻辑是“一个订单只关联一辆车”,用order_no作为业务编号展示给用户,比如XS20250105001这种格式,自增主键只在系统内部使用。
CREATE TABLE `sales_order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` varchar(30) NOT NULL COMMENT '订单编号', `customer_id` int(11) NOT NULL COMMENT '客户ID', `car_id` int(11) NOT NULL COMMENT '车辆ID', `retail_price` decimal(10,2) NOT NULL COMMENT '成交价', `deposit_amount` decimal(10,2) DEFAULT '0' COMMENT '定金金额', `order_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1待付款 2已付定金 3已结清 4已取消', `salesman_id` int(11) NOT NULL COMMENT '销售顾问ID', `store_id` int(11) NOT NULL COMMENT '门店ID', `remark` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_car` (`car_id`), KEY `idx_status_store` (`order_status`, `store_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单表';注意order_no不能只靠数据库自增生成,而是要在 PHP 里根据日期和门店前缀手动生成。car_id必须加唯一索引或做成唯一键,避免同一辆车被重复下单。这是很多新手容易忽略的。
3.3 库存流水表
库存流水表不存冗余的整行车辆数据,只存car_id和状态变化前后的值:
CREATE TABLE `stock_flow` ( `id` int(11) NOT NULL AUTO_INCREMENT, `car_id` int(11) NOT NULL COMMENT '车辆ID', `from_status` tinyint(4) DEFAULT NULL COMMENT '变更前状态', `to_status` tinyint(4) NOT NULL COMMENT '变更后状态', `flow_type` tinyint(4) NOT NULL COMMENT '1入库 2出库 3调拨 4锁定 5解锁 6盘点', `order_id` int(11) DEFAULT NULL COMMENT '关联订单ID', `operator_id` int(11) NOT NULL COMMENT '操作人ID', `remark` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_car` (`car_id`), KEY `idx_order` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表';这张表只追加不修改,任何业务变更都往里塞记录。如果以后要做对账、审计、分析“某辆车卖了多久”,全都可以从这查。
4. 小程序端功能实现详解
小程序开发我用的是 uniapp + Vue 3 语法,项目用 HBuilder X 创建,可以无缝对接微信开发者工具。重点关注几个模块的实现细节。
4.1 接口请求封装与登录态管理
uniapp 里做网络请求,直接用uni.request写会比较枯燥,而且每个页面都要处理 token 过期,太麻烦。我统一封装了一个request.js模块,逻辑是这样的:
// utils/request.js const BASE_URL = 'https://api.yourdomain.com/api/v1' export function request({ url, method = 'GET', data = {}, needAuth = true }) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': needAuth ? uni.getStorageSync('token') : '' }, success: (res) => { if (res.statusCode === 200) { if (res.data.code === 401) { // token失效,跳转登录 uni.removeStorageSync('token') uni.reLaunch({ url: '/pages/login/index' }) return } resolve(res.data) } else { uni.showToast({ title: '请求失败', icon: 'none' }) reject(res) } }, fail: (err) => { uni.showToast({ title: '网络异常', icon: 'none' }) reject(err) } }) }) }登录流程用的是微信小程序的uni.login获取临时 code,然后传给后端。后端用 code 换取 openid,同时生成自己的 token 返回给前端。这样后面每次请求都带上 token,后端就能判断用户身份和权限。
有个细节要注意:Authorization头不要在 localStorage 里直接存,uniapp 的uni.getStorageSync就够了,但如果遇到 token 过期,全局处理很关键——你总不想每个接口都去写一遍 token 过期的分支。
4.2 车辆列表页面与筛选器
车辆列表页是这个系统的小程序端最核心的页面。销售顾问打开小程序,第一眼要看到的就是“本店可售车辆”。页面顶部是分类 Tab:全部、在库、展厅、已预订。下面是一堆筛选条件:品牌、车系、颜色、价格区间。
这里我做了两个优化,实际使用效果很好。
第一个是虚拟列表。汽车销售店里车不多,一般一两百台,所以不用上虚拟列表那么重的方案,但scroll-view配合onReachBottom做分页加载还是必须要做的,接口一次返回全部数据会白屏很久。
分页参数我固定用page和pageSize,后端统一返回{ list, total, hasMore }。
第二个是条件筛选交给后端。很多人图省事,把一页的数据拉到前端再筛选,车少的时候无所谓,但如果一个集团有多个品牌、几百台车,前端筛选非常卡。我的做法是:用户选完条件,点击“查询”才触发新的接口请求,每次查询都带全量筛选参数。后端用where动态拼查询条件。
4.3 车辆入库扫描与拍照上传
入库场景是销售库存管理里很典型的痛点:厂家拖车到店,库管要核对每一辆车的车架号和外观情况。传统做法是拿纸质单子勾选,效率低、容易错。
我在小程序端做了一个“扫 VIN 码入库”的功能。车架号一般是 17 位字母数字,微信小程序里直接调用uni.scanCode扫条形码,扫到结果后自动填入输入框,然后调后端接口校验:
uni.scanCode({ success: (res) => { const vin = res.result.trim().toUpperCase() this.vinInput = vin this.checkVinExist(vin) } })checkVinExist会请求后端GET /api/v1/car/check-vin?vin=xxx。后端返回 200 且data.exist=false的时候,说明这辆车不在当前门店库里,可以继续录入。如果已存在就要提示“该车辆已入库,请勿重复添加”。
上传车辆照片用uni.chooseImage加上uni.uploadFile,这里有个坑:微信小程序的uploadFile和request是两套 API,header 里的 token 要自己手动带,不能共用。我经常看到有人在这犯迷糊,上传图片接口一直报 401。
uni.uploadFile({ url: BASE_URL + '/upload/image', filePath: file.path, name: 'file', header: { 'Authorization': uni.getStorageSync('token') }, success: (res) => { const data = JSON.parse(res.data) this.imageList.push(data.url) } })图片上传成功返回的是一个文件路径,前端把它和车辆基本信息一起提交给入库接口。图片服务器用 FastDFS 或者云存储对象存储都行,我选的是本地 Nginx 静态目录,简单起个/uploads别名,够用就行。
4.4 订单录入与车辆锁定
销售顾问看中了一台车,要录订单锁定库存,这个操作在业务上非常敏感,所以小程序端要二次确认。我的前端交互是:先点“锁定车辆”按钮,弹窗提示“该车辆将被锁定,客户需在 24 小时内支付定金,否则自动解锁”,确认后调接口。
这里的防误操作逻辑很值得学习——不是我吓唬你,真实业务中真有销售顾问在客户还没到店的情况下就锁了车,结果其他同事想卖这台车都没法操作。加上这个二次确认 + 后端日志,后续扯皮就好说很多。
4.5 仪表盘与数据可视化
店长和管理层要看销售情况,小程序端我做了一个简单的仪表盘页面,展示今日成交台数、今日成交额、在库库存、已预订未交付数量这几个核心指标,用ucharts画一个柱状图和饼图。
说实话,小程序端做复杂图表性能一般,所以仪表盘页面只放核心数字,用大号字体展示。更深层的销售趋势分析、滞销车龄分析、毛利统计这些,我放在管理后台 PC 端去做。
5. PHP 后端接口开发实战
后端我选择了 ThinkPHP 8,这个版本新、性能好、API 风格也很现代化,配合 Composer 管理依赖很方便。接下来按业务模块一个个拆开讲。
5.1 接口安全与中间件设计
后端接口安全我做了三件事:登录鉴权、操作权限校验、请求日志。
登录鉴权用 JWT,但比前端 token 过期判断要做得更细。我写了一个AuthMiddleware,在think\middleware里注册为全局路由中间件,但要注意排除登录接口本身:
// route/app.php Route::group('api/v1', function () { Route::post('login', 'Login/login'); Route::group('car', function () { Route::get('list', 'Car/list'); Route::post('save', 'Car/save'); Route::post('update-status', 'Car/updateStatus'); Route::get('detail/:id', 'Car/detail'); Route::get('check-vin', 'Car/checkVin'); })->middleware(AuthMiddleware::class); Route::group('order', function () { Route::get('list', 'Order/list'); Route::post('create', 'Order/create'); Route::post('pay', 'Order/pay'); Route::get('detail/:id', 'Order/detail'); })->middleware(AuthMiddleware::class); })->middleware(CorsMiddleware::class);权限校验我放在控制器里面做,因为不同的接口,角色可访问的粒度不一样。比如Car/save这个方法,只有超级管理员或者库管角色能调,销售顾问调了就直接返回 403。
注意这里我特意加了CorsMiddleware,虽然小程序端不存在跨域问题,但管理后台 H5 会跨域,所以 CORS 中间件必须配上。
5.2 库存扣减与事务处理
车辆出库是库存变化最快的操作。销售顾问提交订单后,客户尾款结清,财务确认,车辆出库。这时后端要做这几件事:
- 校验车辆当前状态是“已预订”
- 校验订单状态是“已结清”
- 把车辆状态改为“已交付”
- 把订单状态更新为“已完成”
- 扣减库存快照表数量(如果有的话)
- 写入一条库存流水表记录
这六步必须在一个事务里执行。我用 ThinkPHP 的Db::transaction()方法包起来,不满足条件就直接抛出异常回滚。这里有一个并发场景需要注意:当两个人同时操作同一辆车时,可能一道出库、一道交付,导致数据不一致。
解决并发我用的方案是“乐观锁”: 在车辆表里加一个version字段,更新语句写成:
$updated = Db::name('car_info') ->where('id', $carId) ->where('car_status', 4) // 状态必须是已预订 ->where('version', $carVersion) // 版本号必须匹配 ->update([ 'car_status' => 6, 'version' => $carVersion + 1 ]);如果$updated返回 0,说明条件不满足,要么车已经被出库,要么 version 已被别人改过,直接抛异常让前端提示“该车辆已被操作,请刷新重试”。这比数据库锁更轻量,也足够应对这种低频高冲突场景。
5.3 报表统计与查询优化
报表模块要统计的数据维度比较多:按日、按月、按品牌、按门店、按销售顾问。有些人在 MySQL 里写很多COUNT(*)和GROUP BY,数据量一大就容易慢。
我这里的优化思路有两个。第一个是“汇总表”:每天凌晨用定时任务把前一天的销售数据和库存变化汇总到一张日统计表里。前端查日报、月报都从这张汇总表查,几秒钟出结果。第二个是合理建索引:订单表按store_id + create_time建联合索引,车辆表按store_id + car_status建联合索引。
有一个查询值得单独说:滞销库存报表。它的逻辑是查“车辆入库时间距今超过 90 天且状态为在库或展厅”,这个查询其实不复杂:
$days = input('days', 90); $where = [ ['store_id', '=', $storeId], ['car_status', 'IN', [2, 3]], // 在库/展厅 ['storage_time', '<', date('Y-m-d H:i:s', strtotime("-{$days} days"))] ];这个报表的价值在业务上很明确:车摆放越久,资金成本越高。有了它,店长每天看看哪些车该降价促销,哪些车该调拨到其他门店,比看乱七八糟的图表管用多了。
6. 小程序端与 PHP 后端联调记录
联调阶段是最容易出现各种小问题的,这里我把几个典型的“坑”梳理一遍,帮你有心理预期。
6.1 时间格式不一致
PHP 后端返回的create_time是2025-01-15 14:23:45这种格式,但 uniapp 前端如果用<text>{{ item.create_time }}</text>直接渲染没问题,可如果要计算“距今天数”来显示“该车辆入库 X 天”,就不能直接减字符串。
我决定统一用时间戳格式返回。PHP 侧写了一个jsonSerialize方法统一处理,把 datetime 转成时间戳:
protected function serializeDateTime($datetime) { return $datetime ? strtotime($datetime) : null; }前端拿到的是时间戳,用 Day.js 转换格式或者计算时间差都很方便。
6.2 文件上传返回格式不统一的问题
这个问题非常隐蔽,但联调时必踩。uniapp 的uni.uploadFile成功后,res.data是一个字符串而不是 JSON 对象,需要手动JSON.parse(res.data)解析一次才能拿到 { url: 'xxx' }。很多前端同事不看文档,直接res.data.url,结果一直拿到 undefined,折腾半天也不知道问题在哪。
6.3 微信小程序的 request 合法域名问题
开发阶段可以用“不校验合法域名”的选项调试,上线前必须把后端接口域名配置到小程序管理后台的 request 合法域名里面,同时文件上传要单独加 uploadFile 合法域名,下载则要 downloadFile 合法域名。三个白名单是独立的,漏一个、传一个都会直接失败。
我在实际项目中还遇到过一个坑:后端域名是https://api.domain.com,前端请求却写成了http://api.domain.com,少了 s,小程序就报“服务器 TLS 版本过低,或域名不对”,排查了大半天。所以强烈建议:接口地址统一用一个config.js文件管理,里面对BASE_URL做一次集中定义,不要散落在各个页面。
7. 常见问题与排查技巧快查表
做系统过程中,我碰到不少问题,不能全写在上面,挑几个具有代表性的进一个速查表,方便你遇到类似现象的时候直接对号入座。
| 现象 | 可能原因 | 排查 & 解决办法 |
|---|---|---|
| 小程序请求报 401 | token 过期 | 检查请求工具里是否自动带入Authorization头,服务端 JWT 过期时间是否过短 |
| 上传图片成功但后端没收到文件 | request 和 uploadFile 的 header 不一致 | uni.uploadFile需单独设置 header 携带 token |
| 车辆锁定后另一销售看不到“已预订”状态 | 前端缓存未刷新 | onShow生命周期里重新拉取列表,不要依赖页面栈数据 |
| 库存数量对不上 | 库存流水表有缺失 | 检查所有变更操作是否都包裹在同一事务中,看库存流水表哪个订单状态有断裂 |
| 页面白屏且无报错 | 接口返回非 JSON 或跨域 | 浏览器控制台看网络请求,用 Postman 请求同一接口对比返回结构 |
| 报表查询速度极慢 | 缺少复合索引 | 按store_id + create_time、store_id + car_status建联合索引 |
| 同一台车被重复下单 | car_id没做唯一约束 | 数据库表中car_id加唯一索引,同时后端做状态校验 |
| 微信开发者工具能跑,真机预览空白 | IPv6 或 TLS 版本问题 | 确认服务器支持 TLS 1.2 以上,域名备案、证书都在有效期 |
这个表不是标准答案,但覆盖了我做这类小程序的绝大多数踩坑场景。如果你遇到的是冷门问题,还是建议从“日志”入手——PHP 侧开启log记录 SQL 和请求参数,前端在接口回调里打印res.data,两边一对比,问题基本能定位到具体一行代码。
8. 部署上线与后续扩展建议
部署这块不复杂,但有几个关键点值得留意。我用的是云服务器 + Nginx + PHP 8.1 + MySQL 8.0,上线前把宝塔面板的防火墙打开 80/443 端口,把 PHP 的fileinfo扩展打开(图片上传必须要这个),再把upload_max_filesize调大到 20MB 左右。
部署完成后首先要跑一遍完整的业务链路,我列了这些问题清单:
- 能不能用真实微信号登录并绑定手机号
- 扫码入库能不能正确识别车架号
- 车辆状态流转时,不同角色的权限是否拦截到位
- 订单创建后能否按预期收到短信/公众号通知
- 报表页面数据与数据库查询结果是否一致
短信通知我这里用的是云短信服务,在“订单已支付定金”和“车辆已交付”两个节点触发发送给客户。这个是刚需功能,销售行业最看重的就是及时性,客户下完定金心里没底,一条短信能解决不少客诉。
如果你的门店规模以后变大,可以考虑三个方向扩展:
第一个方向是加“调拨单”流程。A 店卖完了 B 店的车,系统里要走调拨流程,生成调拨单、库存目的地自动变更、双方确认。这套逻辑和现有状态机结合得很好,扩展成本不大。
第二个方向是加“试驾管理”。顾客试驾哪辆车、试驾里程多少、是否有损伤,都记录在案。这既是对车辆资产的管理,也是销售线索的重要来源。
第三个方向是加“财务对账”模块。每个月底按订单、按收款方式核对账目,和银行流水对账。这部分逻辑比较严谨,但如果现有订单表和收款记录表设计得够规范,扩展起来也不难。
最后分享一个小技巧
整个项目里,我觉得最值得骄傲的不是某个页面写得好看,也不是接口性能调得多快,而是加了库存流水表这个设计。很多开发者在做类似系统的时候,只盯着“当前库存数”这个结果,忽略了“变化过程”的记录。
如果你现在正打算开发这个系统,不管用不用 PHP,都把这点记住:一定要留一套不可篡改的操作轨迹。你可以不展示给用户看,但每当内部出现纠纷、对不上账的时候,它就是最有说服力的裁判。
另一个小技巧是开发阶段把后端接口的 debug 模式打开,前端能看到详细的 SQL 错误和文件报错行号,排查效率能提升好几倍。上线前再关掉,记得把这个开关放到环境配置里,不要每次上线都手动改代码。
这套系统用下来,整体开发周期大概 6 周,业务方认可度很高,尤其是在门店盘点的时候,原来最头疼的账实不符问题少了,因为这个系统连谁在几点几分动了哪辆车都记录得清清楚楚。项目没有做到“完美”,但做到这里,已经解决了真实业务里最痛的几个点。