
1. 为什么我要自己动手写一套轻量CRM市面上成熟的客户管理系统一抓一大把功能表拉出来能翻好几屏从线索打分到营销自动化再到BI看板几乎你能想到的需求它都有。但问题恰恰出在这里功能越多上手成本越高团队里真正愿意每天打开它的人就越少。我见过太多小团队花了几万块买了一套系统结果销售还是用表格记客户系统最后沦为“给老板看的摆设”。这套简单版本CRM客户管理系统就是在这种背景下动手做的。它的定位非常明确只解决三个核心问题——客户信息集中存、跟进记录不丢失、销售阶段看得清。没有花哨的自动化没有复杂的权限矩阵一个销售新人十分钟就能上手。适合谁参考适合五到二十人规模的小团队、独立开发者接私活时给客户交付的轻量方案以及想自己练手全栈开发的朋友。我前后迭代了三版第一版功能堆太多自己都嫌重第二版砍到只剩骨架反而好用第三版补上了几个真正高频的细节。下面把整套设计思路、数据模型、关键实现和踩过的坑完整拆一遍你可以直接照着搭一套属于自己的版本。2. 整体设计思路与方案选型拆解2.1 先想清楚“不做什么”比“做什么”更重要做轻量系统最容易犯的错就是照着大厂CRM的功能清单做减法减着减着发现每个功能都“好像有点用”最后又变成四不像。我的做法是反过来先列出团队每天真正会执行的动作只保留这些动作对应的功能。实际调研下来小团队销售每天的动作就四个录入新客户、查某个客户的历史沟通、更新客户当前阶段、看这周该跟进谁。对应的功能就是客户档案、跟进记录、阶段流转、待办提醒。像合同管理、回款计划、产品目录这些要么用现有工具比如财务软件解决要么等团队规模上来再说。砍功能不是偷懒是让系统活下来。2.2 技术选型够用、好维护、一个人能扛选型我遵循一个原则如果半夜出问题我一个人能不能在半小时内定位并修复。基于这个原则技术栈定得很朴素。层级选型选择理由前端原生HTML轻量JS框架不引入重型构建工具改完直接刷新部署零负担后端Python Flask代码量少路由清晰一个人维护毫无压力数据库SQLite单文件存储备份就是复制一个文件小团队数据量完全够部署单台轻量服务器一个进程跑起来不需要容器编排那套东西有人会问SQLite能不能扛住并发。实测下来二十人以内的团队日常操作QPS峰值也就个位数SQLite的写锁完全不是瓶颈。真到了扛不住的那天迁移到PostgreSQL也就是改个连接字符串的事前期没必要为不存在的规模焦虑。2.3 数据模型三张表撑起整个系统整个系统的核心就是三张表关系非常清晰。客户表customer存客户的基本信息包括名称、联系人、电话、来源、当前阶段、负责人、创建时间。跟进记录表follow_up每一条记录关联一个客户存沟通时间、沟通方式、沟通内容、下次跟进时间。用户表user存销售账号用于区分“我的客户”和“全部客户”。客户和跟进记录是一对多的关系一个客户可以有多条跟进。这里有个设计细节客户的“当前阶段”不单独存历史而是每次更新时覆盖同时把变更动作写进跟进记录里。这样既省了一张阶段历史表又能通过跟进记录回溯完整的阶段变化过程。对于小团队来说这种“以记录代历史”的做法比单独建表更实用因为销售本来就要写跟进顺手就把阶段变更记了。3. 核心功能模块的细节解析与实操要点3.1 客户档案字段设计里的取舍学问客户表看着简单字段怎么定却直接影响使用体验。我第一版加了二十多个字段结果销售录入时嫌烦很多字段空着。第三版砍到只剩八个必填加几个选填录入时间从两分钟降到二十秒。必填字段就这些客户名称、联系人、联系电话、客户来源、当前阶段、负责人。选填的是备注和下次跟进时间。这里有个经验“客户来源”这个字段一定要做成下拉选项而不是自由输入否则销售会填出“朋友介绍”“朋友推荐的”“老客户介绍”三种写法后期想统计来源分布时根本没法聚合。阶段字段我定义了几个固定值初步接触、需求确认、方案报价、谈判中、已成交、已流失。阶段值不要太多超过七个销售就记不住自己该选哪个了。每个阶段对应一个默认的“停留天数阈值”比如“方案报价”阶段超过七天没动静系统就在待办里标红提醒。这个阈值我放在配置文件里不同团队可以根据自己的成交周期调整。3.2 跟进记录让销售愿意写才是好设计跟进记录功能最大的挑战不是技术实现而是怎么让销售愿意写。我试过强制填写跟进内容才能保存结果销售就写“聊了聊”三个字应付。后来改成两个措施一是把输入框做大默认给一个模板提示“本次沟通要点 / 客户反馈 / 下一步动作”二是保存后立刻在客户详情页顶部显示最新一条记录让销售看到自己写的东西被展示出来了有正向反馈。跟进记录表的关键字段是next_follow_time这个字段驱动了整个待办提醒功能。每次保存跟进记录时如果销售填了下次跟进时间系统就自动把这个客户加入待办列表。如果没填默认按当前阶段的阈值自动算一个时间。这个“自动兜底”很关键因为销售经常忘记填下次跟进时间没有兜底的话待办列表就会漏掉这些客户。提示跟进内容建议限制最少十个字太短的记录后期回看时毫无价值。但不要限制太多否则销售会想办法凑字数。3.3 阶段流转与待办提醒把“该跟进谁”这件事自动化待办提醒是这个系统里销售用得最频繁的功能。实现逻辑不复杂每天定时扫描所有未成交客户找出next_follow_time小于等于今天的按负责人分组展示。但有几个细节决定了它好不好用。第一待办要区分“今天到期”和“已逾期”逾期的用不同颜色标出来视觉上要有紧迫感。第二待办列表里直接放一个“快速记录”按钮点开就能写跟进不用先跳转到客户详情页。这个快捷入口把“查看待办→记录跟进”的操作路径从四步缩短到两步使用频率明显提升。第三周末和节假日不推送提醒否则销售休息时收到一堆待办会很烦躁反而降低对系统的信任。阶段流转还有一个隐藏价值通过阶段分布看销售漏斗。虽然轻量系统不做复杂的BI但一个简单的阶段数量统计就能让管理者看出问题。比如“方案报价”阶段堆积了二十个客户但“谈判中”只有两个说明报价后的转化环节出了问题。这个统计我做成一个简单的柱状图放在首页数据实时算不存中间表。4. 完整实操过程与关键环节实现4.1 数据库建表与初始化先把三张表建起来。SQLite的建表语句很直观注意几个细节客户表的stage字段加默认值created_at用数据库自动时间戳跟进记录表的外键关联客户表并设置级联删除这样删客户时跟进记录自动清理不会留孤儿数据。CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL, password_hash TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE customer ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, contact TEXT, phone TEXT, source TEXT, stage TEXT DEFAULT 初步接触, owner_id INTEGER NOT NULL, remark TEXT, next_follow_time DATE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (owner_id) REFERENCES user(id) ); CREATE TABLE follow_up ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, content TEXT NOT NULL, method TEXT DEFAULT 电话, next_follow_time DATE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (customer_id) REFERENCES customer(id) ON DELETE CASCADE );建完表后插入一个默认管理员账号密码用哈希存储不要明文。初始化脚本里顺便插入几条测试数据方便开发时调试页面。4.2 后端接口设计RESTful但不必教条接口设计我遵循RESTful的大方向但没有严格到每个动作都对应一个HTTP方法。小系统里可读性和开发效率比规范更重要。核心接口就这几个GET /api/customers获取客户列表支持按阶段、负责人、关键词筛选POST /api/customers新建客户PUT /api/customers/id更新客户信息包括阶段流转DELETE /api/customers/id删除客户GET /api/customers/id/follow_ups获取某客户的跟进记录POST /api/customers/id/follow_ups新增跟进记录GET /api/todos获取当前用户的待办列表这里有个实现要点更新客户阶段时后端要自动生成一条跟进记录内容类似“阶段从‘需求确认’变更为‘方案报价’”。这样阶段变化就有了时间线不需要单独的历史表。代码里就是在更新接口里判断stage字段是否变化变了就插一条记录。app.route(/api/customers/int:cid, methods[PUT]) def update_customer(cid): data request.get_json() old query_one(SELECT stage FROM customer WHERE id?, (cid,)) # ... 执行更新 ... if stage in data and data[stage] ! old[stage]: insert_follow_up(cid, f阶段从「{old[stage]}」变更为「{data[stage]}」, method系统) return jsonify({ok: True})4.3 待办列表的查询逻辑与性能考量待办查询看起来简单但要考虑几个边界情况。基础查询是找出next_follow_time 今天且stage不是“已成交”和“已流失”的客户。但有些客户可能从来没填过next_follow_time这些客户不应该出现在待办里否则列表会被大量“僵尸客户”淹没。SELECT c.*, u.display_name AS owner_name FROM customer c JOIN user u ON c.owner_id u.id WHERE c.next_follow_time IS NOT NULL AND c.next_follow_time date(now) AND c.stage NOT IN (已成交, 已流失) AND c.owner_id ? ORDER BY c.next_follow_time ASC;数据量小的时候这个查询毫秒级返回。如果客户数到了几千条给next_follow_time和owner_id建个联合索引就够了。我实测过五千条客户数据加了索引后查询稳定在十毫秒以内。4.4 前端页面的三个关键交互前端我没有用重型框架就是原生JS加fetch请求。页面不多主要就四个登录页、客户列表页、客户详情页、待办页。重点说三个交互细节。第一个是客户列表的即时搜索。输入关键词后不按回车直接监听输入事件做防抖三百毫秒后发请求。这样销售边打字边看结果体验比按按钮搜索流畅很多。防抖函数很简单但效果提升明显。第二个是客户详情页的跟进记录倒序展示。最新的跟进在最上面销售打开客户第一眼就看到最近一次沟通内容不用往下翻。每条记录显示时间、方式、内容和记录人阶段变更的系统记录用灰色区分。第三个是待办页的快速记录弹窗。点击待办项旁边的“记录”按钮弹出一个轻量弹窗只有内容输入框和下次跟进时间两个字段保存后待办项自动消失。这个交互把记录跟进的操作成本降到最低是提升系统使用率的关键设计。5. 常见问题与排查技巧实录5.1 销售不愿意用系统怎么办这是所有内部工具面临的头号问题技术手段只能解决一半。我的经验是三个措施配合一是降低录入成本能自动带出的字段绝不让人填能选绝不让人打字二是让销售先尝到甜头比如待办提醒确实帮他记住了一个快要流失的客户他自然就愿意用了三是管理者带头用如果主管自己都不看系统里的数据只在群里问“那个客户怎么样了”销售就会觉得系统是多余的。5.2 数据备份与迁移的实操方案SQLite的备份极其简单写个定时任务每天凌晨把数据库文件复制一份到另一个目录保留最近三十天的备份。迁移的时候把文件拷过去改一下配置文件里的路径就能跑。但要注意备份时数据库可能正在写入直接复制文件有概率拿到损坏的副本。正确做法是用SQLite自带的备份命令sqlite3 crm.db .backup /backup/crm_$(date %Y%m%d).db这个命令会加锁保证备份一致性比直接cp安全得多。我踩过一次直接复制的坑恢复时发现文件损坏幸好当时数据量小手动补回来了。5.3 常见问题速查表问题现象可能原因排查与解决待办列表为空客户未填下次跟进时间检查客户记录补填时间或调整阶段阈值兜底逻辑阶段变更没生成记录更新接口未判断stage变化检查PUT接口中stage字段的比较逻辑页面加载慢客户列表一次返回全部数据加分页默认每页二十条删除客户后跟进记录还在外键未设置级联删除重建表时加上ON DELETE CASCADE多人同时编辑同一客户冲突无并发控制加一个updated_at字段做乐观锁校验5.4 几个我踩过的坑第一个坑是阶段阈值设得太死。一开始我把“方案报价”阶段的提醒阈值设成三天结果销售抱怨说报价后客户经常要一周才回复三天就提醒太频繁了。后来改成可配置不同阶段不同阈值默认值放宽到七天销售自己也能在设置里调。第二个坑是客户名称允许重复。早期没做唯一性校验结果同一个客户被两个销售各建了一条跟进记录分散在两处。后来加了“名称电话”的联合唯一约束新建时如果重复就提示“该客户可能已存在是否查看”。第三个坑是时间字段的时区问题。服务器用UTC时间但销售看的是本地时间导致待办提醒有时候早一天有时候晚一天。解决办法是数据库统一存UTC展示和查询时都转成本地时区转换逻辑封装成一个工具函数所有地方统一调用。6. 后续可以怎么扩展这套系统的骨架已经能支撑小团队的日常客户管理了但如果想继续往上长有几个方向可以按需加。第一个是简单的数据统计页比如每月新增客户数、各阶段转化率、销售个人业绩排名用图表库画几个图就行数据实时算不存中间表。第二个是客户导入导出支持从表格批量导入导出成表格给财务或老板看。第三个是操作日志记录谁在什么时候改了什么方便追溯。但每加一个功能都要问自己团队真的会用吗如果答案不确定就先不加。我在实际使用中最大的体会是轻量系统的价值不在于功能多而在于每个功能都被真正用起来。与其做一个功能齐全但没人打开的“完整版”不如做一个功能很少但每天都在用的“简单版”。这套CRM到现在跑了快一年客户数据攒了几百条跟进记录上千条团队已经离不开它了。这就够了。