1. 为什么我会动手做一个“桌面端通信型CRM”
1.1 先说痛点:客户信息全散着
我做销售和客户跟进这行有几年了,最大的感受不是客户难谈,而是信息太散。微信里有客户留言,邮箱里躺着询价单,Excel表格里记着一堆人名和电话,手机上还有几条语音备忘录。每天一睁眼,光是把“今天该联系谁”“上次聊到哪儿”这些事拼齐就要花掉二十分钟。有一次就是因为漏看了一封客户邮件,硬生生把一个周一的报价拖到了周五,客户直接丢了单子。
后来我意识到,我需要的不是更多工具,而是一个能把碎片信息汇拢起来的地方。于是有了 DeskcommCRM 这个项目——一个跑在桌面端的通信型客户关系管理系统。它把客户档案、跟进记录、邮件往来、任务提醒和简单的销售看板放在同一个本地应用里,让我每天只开一个软件就能知道所有客户的状态。
这套东西不是给大公司用的,也不是要跟销售易、Salesforce那类重型系统比功能。它解决的是个人销售、自由职业者、小微团队里最实际的问题:客户信息不丢、跟进不落、沟通有痕迹。如果你也经常觉得“好像有个客户好久没联系了”,那这个项目的工作方式应该能帮到你。
1.2 为什么不用市面上的SaaS CRM
有人会问,市面上一堆在线CRM,注册就能用,为什么要自己做一个?这个我承认,SaaS CRM确实省事,但用久了你会发现几个绕不过去的问题。
第一个是客户数据归属。放在别人的服务器上,哪天平台调整收费策略,或者你想把数据导出来换工具,导出的Excel格式常常乱得没法看。对于靠客户资源吃饭的人来说,数据捏在别人手里,心里总是不踏实。
第二个是功能冗余。很多在线CRM上来就给你一堆模块,什么线索池、公海、审批流、业绩排行,一个人或者三五人的小团队根本用不上。反而是最基础的“客户详情页能不能快速打开”“跟进记录能不能带时间线”这些事,很多大平台做得非常繁琐,点三下才能新建一条记录。
第三个是网络依赖。客户在咖啡厅、在展会现场、在出差路上,打开CRM想看一条客户报价,结果网络稍微不稳页面就转圈。桌面应用的数据存在本地,打开就是打开,没有等加载这回事。
DeskcommCRM的思路正好反过来:本地优先、单一职责、轻量快速。它不追求管理整个销售体系,只管好“客户是谁、聊了什么、下一步做什么”这三件事。等到数据积累多了,再决定要不要把某部分数据同步到云端。
1.3 “Deskcomm”到底解决什么问题
从名字拆开看,Desk代表桌面端,comm是communication的缩写,合在一起就是“桌面通信型客户管理”。这个名字基本概括了这个项目的核心:落在电脑桌面上、以沟通记录为主线来组织客户关系。
传统CRM的视角是“客户对象”,每一条数据围绕客户档案打转。DeskcommCRM的视角略微不同,它更看重“沟通”这个动作。任何一条客户记录,不管是首次询价,还是三个月后的回访,都会被记成一条时间线事件。打开一个客户,他的邮件往来、通话要点、微信沟通结论、线下拜访摘要全部按时间排好。这样你就不是在管理一堆静态的字段,而是在翻阅一段完整的客户关系历史。
这种设计带来一个很实际的好处:不管隔多久再联系客户,你都能在十秒内回忆起来龙去脉。客户会觉得你一直记得他的事,信任感就是这么来的。后面我会把这个通信时间线的数据结构和实现细节完整拆开讲。
2. 整体架构与关键设计选择
2.1 技术栈选型:Electron还是Tauri
先交代一下技术选型。我一开始在 Electron 和 Tauri 之间犹豫过,最后还是选了 Electron + Vue3 + better-sqlite3。理由比较务实:Electron的生态太成熟了,踩坑资料多,打包、自动更新、系统托盘这些功能都有现成方案。对于一个人开发的项目,少踩一个坑比省10MB内存更划算。
Tauri确实更轻,打包后的体积只有Electron的零头,内存占用也低一截。但它的后端是Rust,我本身不熟悉Rust,再加上SQLite插件和系统通知相关的原生模块都要自己折腾,学习成本一下子高了很多。如果你本身就是Rust玩家,用Tauri肯定体验更好;如果不是,从Electron起步更不容易半途而废。
另外一个关键点是客户端存储。桌面CRM的数据必须存在本地,我选的是 better-sqlite3,也就是SQLite的同步版本。同步API用起来非常直接,不用像 async/await 那样到处穿透,配合事务可以保证数据写入的完整性。对于单机应用来说,性能完全足够,即使后续数据量到了十万条客户记录,SQLite依然能秒开。
2.2 数据存储方案:本地优先,SQLite打底
数据表的设计我一开始就定了调子,一切围绕“客户 + 沟通”两个核心实体展开。我把整套表结构精简成五个主要的表,建表的SQL大约长这样:
CREATE TABLE customers ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, company TEXT, phone TEXT, email TEXT, source TEXT, status TEXT DEFAULT 'leads', owner TEXT, created_at TEXT DEFAULT (datetime('now', 'localtime')), updated_at TEXT DEFAULT (datetime('now', 'localtime')) ); CREATE TABLE follow_ups ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, type TEXT, content TEXT, next_action TEXT, next_date TEXT, created_at TEXT DEFAULT (datetime('now', 'localtime')), FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE ); CREATE TABLE communication_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, channel TEXT, direction TEXT, summary TEXT, raw_content TEXT, contact_time TEXT, created_at TEXT DEFAULT (datetime('now', 'localtime')), FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE ); CREATE TABLE tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER, title TEXT NOT NULL, due_date TEXT, priority TEXT DEFAULT 'normal', done INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now', 'localtime')), FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE SET NULL ); CREATE TABLE tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL );这里有几个设计细节值得解释。customers表里的 status 字段我用的是字符串,没有用外键关联一张状态字典表。原因很简单,个人项目的状态就那几种,leads、qualified、negotiating、won、lost、sleep,写死在代码里比建表维护更省事。如果你要扩展,后面加一张status_options表也不迟,前期的表结构不用太纠结。
follow_ups和communication_logs看起来职责有点像,我特意做了区分。follow_ups是“你自己主动记录的跟进动作”,比如打电话给客户、拜访客户、发了方案;communication_logs是“从外部通信工具自动同步或者手动补充的沟通过程”,比如收到的邮件全文、IM上的关键沟通过程。一个偏内部操作,一个偏外部留痕,分开之后查数据能够按场景走不同的查询路径,后面看板统计也方便。
2.3 通信集成模块的设计思路
这个项目叫“通信型CRM”,通信模块自然是重点。我做了邮件接入,支持通过IMAP和SMTP协议把邮箱挂到系统里,收进来的邮件能自动按发件人关联到对应客户,发出的邮件也能留底保存。
这里我刻意做了降级方案:通信集成不是强制的。如果IMAP配置不顺利,你完全可以不配邮箱,手动把邮件要点贴到沟通记录里。系统不会因为你没接入邮箱就拒绝使用,所有的通信记录都只是广义的“一条带时间戳的内容”,等后续有数据了再慢慢沉淀。
我不建议一上来就把IM、打电话、视频会议全部塞进集成范围。通信渠道越多,解析和去重的难度就越大。第一版把邮件跑顺,其他的先在手动记录层面兜底,这个节奏是稳的。
3. 核心模块拆解与实操细节
3.1 客户档案管理:录入时要留哪些字段
客户档案是整个系统的地基。字段设计是我花了最多时间琢磨的部分,因为字段少了不够用,字段多了录入负担大,最后使用者一定懒得填。
我最终确定了八个核心字段:姓名、公司、电话、邮箱、来源、状态、负责人、备注。你看,没有公司地址,没有生日,没有社交账号,没有行业分类。这些字段听起来很专业,但实际录入的时候,绝大多数人根本不会填。真到了需要这些信息的时候,往往直接打开客户的聊天窗口看一眼就行。
来源字段我保留下来了,我认为这是所有字段里最能帮到销售决策的一个。这个客户是从老客户介绍来的,还是从行业展会认识的,或者看了你的博客主动来咨询,决定了后续跟进的温度。介绍来的客户信任基础好,第一次沟通就可以聊方案;陌生找来的客户要先花时间建立信任。看板模块里我会把来源字段做成一个不起眼但很有用的统计维度。
地址之类的大段信息我建议放到备注里,用自由文本记录就行,结构化反而限制表达。一个合格的CRM不是把所有信息都建成独立字段,而是给用户一个足够好用的“信息容器”。
3.2 跟进记录时间线:把“聊天记录”变成“客户故事”
跟进记录是DeskcommCRM交互上最重的一个模块。它被设计成类似社交时间线的形式,客户详情页往下滚动,会按照时间顺序看到跟这个客户有关的所有事件。每条记录主要有几个要素:
- 事件类型(电话、邮件、微信、线下见面、报价、合同)
- 方向(外发/接收)
- 内容摘要
- 下一步行动
- 下次联系时间
交互上我加了一个非常实用的操作:在时间线任意一条记录上点击“创建任务”,可以直接把下一步行动变成一条待办,并且自动带上这个客户的关联关系。比如你上午跟客户通了电话,对方说周四之前要看到新报价,你就直接在通话记录上点一下,写上新报价,截止时间设为周三下午,系统自动生成一条任务。这类操作把“记录”和“执行”之间的缝隙填上了,不像传统CRM那样记完了就完了。
时间线模块的数据结构并不复杂,但有一个性能优化的细节。随着时间推移,单个客户的记录会越来越多,如果一次性全部渲染,打开一个客户页面可能要等好一会儿。我做了分批加载,默认只加载最近30条记录,滚动到底部再加载更早的30条。这个优化写起来很简单,但体验上的提升非常明显。
3.3 任务与提醒:轻量CRM最不能丢的一块
我见过很多CRM把任务模块做得很重,什么任务流程、任务委派、任务审批,看着功能强大,实际用起来一堆负担。个人和小团队需要的任务管理就四个要素:做什么、什么时候做、跟哪个客户有关、做完了没。
DeskcommCRM的任务就围绕这四件事设计。每条任务有一个标题,可以选关联客户,可以设置截止日期和优先级,完成后勾掉就行。主页面上有一个“今日待办”视图,列出所有截止日期是今天或者已经逾期未完成的任务,一打开应用就知道今天要干什么。
这里我想分享一个实操中的小技巧:任务截止日期尽量设置成“具体到小时”,而不是只到天。原因很简单,人的大脑对“下午三点前发方案”要比“5月20日发方案”敏感得多。我在系统里做了一个选项,允许用户选择截止日期时也填具体时间,到时间会触发系统通知。
提醒的通知机制,我用的是 Electron 的系统通知接口。应用在后台运行的时候,到时间的任务会弹出原生系统通知。这里有一个小坑,Windows下默认聚焦模式可能不让应用弹通知,需要在代码里显式设置通知的 urgency 字段为 normal。macOS 则要申请通知权限,第一次启动时会弹出系统授权框,别让用户手动去设置里找权限开关。
3.4 数据看板:别贪多,三个视图就够
看板模块是我最后做的。不是因为它不重要,而是因为做看板最容易堆图表,最后做了十几个图表没一个真有用。我的建议是,轻量CRM的看板先满足三个视角就够用:销售状态分布、近期待办压力、客户活跃度。
销售状态分布在首页顶部,是一个简单的横向条形图,统计当前每个状态下的客户数量。我一眼就能看到潜在客户有多少、谈判中的有多少、已经丢单的有多少。如果某个状态的客户数量明显不合理,比如谈判中的客户一个月没变化,我就知道该主动推动一下了。
近期待办压力是一个未来七天的柱状图,按天统计任务的截止数量。这样我能提前看到哪天最忙,好提前安排工作节奏。客户活跃度则统计每个客户在过去30天、90天里的沟通记录条数,把这些数字和最近联系时间一并列成表格,旨在找出“快要凉掉”的客户。
如果你没有做Dashboard的经验,我建议先别碰任何图表库。先用一个基础的表格视图把这个逻辑跑通,把原始数据列出来,用颜色标一下状态。等觉得确实需要图表了,再引入 ECharts 或者 Chart.js 都不迟。图表库是加分项,不是必需品,别让工具选择压过产品设计。
4. 实现过程中的难点与踩坑记录
4.1 桌面端嵌入邮件收发的坑
邮件模块是通信型CRM的核心,也是实现过程中让我踩坑最多的部分。我用的是IMAP协议做邮件接收,SMTP做邮件发送,Node.js生态里的相关包还算成熟,但实操中还是有几个常见的坑。
第一个坑是IMAP断连。默认的IMAP连接并不会一直保持,邮件服务商一般几分钟到半小时就会断开空闲连接。如果不做重连机制,应用开了一上午之后,你就会发现邮件拉取完全失效了。我的解决方案是每隔一段时间主动重连,每次重连前先检查当前连接状态,断开了就重新建立。重连的间隔我设在五分钟左右,既能保证邮件及时同步,又不会太频繁地打扰服务器。
第二个坑是邮件去重。一封邮件如果你没删它,同一个邮件账号反复拉取时,就可能导致重复入库。我在表里加了一个message_id字段,每次拉新邮件时先查这个字段是否存在,存在就跳过。这个message_id在IMAP协议里可以直接读取,特指某封邮件的唯一标识,本来就是为了防止重复处理设计出来的。
第三个坑是正文解析。HTML邮件解析成纯文本的时候,经常会带出一堆乱码和样式残留,比如很多营销邮件里的跟踪像素会被解析成一行乱符号。我用了比较取巧的办法:优先提取邮件里的纯文本版本,只有当纯文本为空时,才去把HTML转成文本。转文本的函数需要手动处理一些常见的HTML标签替换,费了点功夫,但效果比直接用通用库干净不少。
4.2 数据量上来之后SQLite的性能处理
SQLite作为单机数据库的特点是“起步很快,后期要自己管”。当客户数据量不大,几千条的时候,任何查询都瞬间完成。但当沟通记录累积到几万条、几十万条的时候,几个关键查询就开始变慢了。
我遇到最典型的问题是对communication_logs表按时间排序分页查询。没有索引的情况下,数据量到五万条左右,一个简单的ORDER BY查询就要花两秒。解决办法是建立了复合索引:
CREATE INDEX idx_comm_customer_time ON communication_logs(customer_id, created_at DESC); CREATE INDEX idx_tasks_due ON tasks(due_date, done);建立索引之后,刚才那个两秒的查询缩短到了几十毫秒。这个操作是SQLite性能优化里最基础也最有效的一步。如果你用SQLite做过应用,应该知道这点;如果你刚接触SQLite,我建议在任何表做完增删改之后,立刻评估一下高频查询路径,把索引一次建好,省得后面再来补。
另一个优化是给follow_ups表的next_date字段建索引。这个字段会频繁用在“待办查询”里,查找所有next_date在今天之前的跟进记录,加上索引后相关查询速度几乎无感。SQLite对小数据量的应用确实很宽容,但别因为这个就忽视索引设计,该建的还是建上。
4.3 有没有必要做服务端同步
做单机版CRM做到一半,我认真考虑过要不要加一个服务端同步,让手机也能访问数据。说实话,这个念头很有诱惑力,谁不希望在手机上随时看一眼客户资料呢。
但冷静下来之后我还是砍掉了这个需求。原因有三条:第一,服务端同步意味着我需要维护服务器、写API、处理多端数据冲突,工作量翻倍,而且数据安全责任更重;第二,我的使用场景主要是办公室、家里、客户现场,都带着笔记本,桌面上打开应用就能看到所有信息,移动访问不是刚需;第三,本地数据用网盘同步文件夹做备份,安全性不输自己搭的服务器。
如果你确实需要移动端访问,我建议考虑轻量方案,而不是一上来就做完整的服务端同步。最简单的做法是把客户表和任务表导出为CSV或JSON,用手机上的笔记软件打开。或者等单机版跑稳定了,再用一个只读API把数据映射成一个简单的网页,手机上浏览器访问即可。功能不用多,能查询就行。核心思路是先保证桌面端这个主战场足够好,再考虑外围。
5. 完整部署与日常使用工作流
5.1 从拉代码到跑起来
如果你拿到这个项目的代码,想本机跑起来看看效果,过程不复杂。前提是你已经装好了Node.js环境,版本建议16以上。下面是核心步骤:
git clone https://github.com/yourname/deskcomm-crm.git cd deskcomm-crm npm install npm run devnpm install装依赖的时候,Electron的包可能会比较大,在国内网络环境下下载慢,你可以配置Electron镜像源。npm run dev启动之后,应用会打开一个开发窗口,左边是客户列表,右边是客户详情,顶部是今天的待办任务,界面比较朴素。
初始化的时候系统会在用户数据目录下自动创建SQLite数据库文件。Windows下路径通常在%APPDATA%/deskcomm-crm,macOS下是~/Library/Application Support/deskcomm-crm。数据库文件就叫crm.db,你要备份数据,把整个目录打包就行。
有一个安装时容易踩的坑:better-sqlite3 属于原生模块,npm install的时候需要重新编译,如果你的Node版本和Electron的Node版本不一致,就会报错。解决办法是安装完依赖后,执行 Electron 自带的 rebuild 命令:
npx electron-rebuild确认接口对齐后重启应用,数据库模块就能正常工作了。
5.2 导入历史客户数据的建议
从Excel或者旧系统导数据到你新搭的CRM,这个过程看着简单,实际很容易出错。我在导入模块上花了不少心思,但更想分享的是一些数据处理的实操建议。
第一,导入之前先清理数据。旧Excel表格里经常有大量只填了公司名、没有联系人姓名的记录;也有的客户重复出现了好几遍。我把数据清洗的流程分成三步:先查重(按姓名+公司合并判断),再补全(凡是必填字段为空的一律不导入),最后人工过一遍明显异常的记录。宁可少导入几条脏数据,也别把整个数据的可靠度拉低。
第二,状态字段映射要有缓冲。旧系统的客户状态可能五花八门,比如“有意向”“考虑中”“还未联系”等,新系统的状态只有固定的几种。我建议导入时先统一映射成最接近的状态,别硬在字典表里新建一堆状态。状态多了之后,各项统计报表会变得零散,反而不直观。
第三,历史沟通记录的导入优先级我认为不是最高的。很多人一换系统想把两年前的所有邮件都导进去,其实绝大多数历史沟通记录除了占空间外没什么复用价值。我建议着重建最近半年的沟通记录,更早的就留一个总结性摘要放到备注里,比如“上半年通过三封邮件,最后因为价格预算没谈拢暂停推进”。这样后续同事接手时也看得懂。
5.3 团队协作时怎么共用一套数据
DeskcommCRM设计上是一个单机应用,但实际使用中,小团队两三个人共用同一套数据也是可以实现的,只是方式比较原始。最简单的方式是选一台常用电脑做主数据端,其他成员以终端的方式访问;更简单的方式是把数据库文件放到一个团队共享的网盘目录里。
具体操作是:把整个deskcomm-crm的数据库目录用坚果云、Dropbox之类的工具同步。每次操作完,数据库文件的变更会被同步到其他成员的电脑上。但我必须要说,这种方案只适合一个人为主进行录入的场景。如果两三个人同时写入数据,SQLite的锁机制会导致冲突报错;即使不同时写入,网盘同步也可能因为文件版本不一致产生冲突副本。
如果你确实有多人同时编辑的需求,我建议直接升级到服务端方案,把数据库迁到PostgreSQL或者MySQL,应用本身通过API访问数据库。这相当于把单机版的存储层替换为网络数据库,工作量不大,但需要有一个固定的服务器来运行数据库。前期可以在一台旧电脑上部署测试,稳定了再上正式环境。
6. 常见问题排查与实用心得
6.1 常见问题速查表
把我在开发和日常使用中遇到的典型问题整理成一张表,方便你遇到问题时快速定位。
| 现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 应用启动后数据库操作报错 | better-sqlite3原生模块未编译成功 | 执行npx electron-rebuild后重启 |
| 邮件拉取一段时间后失效 | IMAP连接被服务器断开 | 检查重连机制是否生效,把重连间隔设为5分钟 |
| 邮件重复导入 | 缺乏message_id去重判断 | 检查message_id字段是否在入库前做了存在性查询 |
| 客户详情页打开很慢 | 沟通记录一次渲染过多 | 改为分页加载,每批30条 |
| 任务到点了没弹通知 | 系统通知权限未开启 | Windows检查焦点了模式,macOS检查通知权限 |
| 同步后的数据库变成只读 | 网盘同步导致文件锁 | 多人使用场景不要用网盘同步,改用服务端数据库 |
| 查询速度越来越慢 | 高频字段缺索引 | 按上一章提到的方案为communication_logs和tasks建立索引 |
| 导入Excel时中文乱码 | 文件编码不是UTF-8 | 导入前用工具将Excel另存为UTF-8编码的CSV |
这张表里的问题我基本都实地踩过,尤其是第一条better-sqlite3的编译问题,几乎每个换新电脑或者升级Node版本的人都会碰到一次。
6.2 几条实实在在的经验
到现在为止,DeskcommCRM 已经在我自己的日常工作里稳定跑了近一年。数据量不算夸张,400多个客户,5000多条沟通记录,日常打开、搜索、录入都非常流畅。这套东西的价值,在我看来不在技术多复杂,而在于它恰恰长在了个人销售的工作方式上。
有几点经验值得拿出来单独说。
第一,项目的功能边界一定要尽早确定下来。我在写第二版的时候,差点加进一个完整合同管理模块,后来及时刹住了车。因为想清楚了一件事:合同管理这件事有更专业的工具做,CRM只负责记录合同相关的沟通节点就够了。什么都想做,结果只有一个——什么都做不好。
第二,桌面端应用最让人安心的属性就是“离线也能用”。去客户公司开会的时候,现场任何资料都能在本地查出来,不用等网络,不用担心信号。这种感觉用习惯了之后再回到网页版CRM,会特别不适应。
第三,快捷键是最值得投入的易用性功能。我后来补了三个全局快捷键:新建客户、跳转到今日待办、打开搜索框。每天使用频率极高,配合起来效率至少有三分之一的提升。这也是我为什么一直强调,轻量工具的价值在于顺手,而不在于功能多。
6.3 这个方向后续还能怎么扩展
如果你也想照着做一个类似的桌面端CRM,或者想在这个项目基础上继续迭代,我提供几个我觉得优先级较高的方向。
第一个方向是增加基于全文的搜索。目前系统的搜索只针对客户姓名、公司和备注,但很多情况下搜索其实是冲着沟通记录里的某句话去的。SQLite本身支持FTS5全文搜索,配置好之后,给communication_logs建一个全文索引,搜索体验会提升一大截。
第二个方向是做一个更细化的报表——“本月新增客户数”、“本月达成交易数”、“平均跟进天数”。这些统计量对复盘很有用。工程量不大,在现有数据基础上写几个聚合SQL就能出结果。
第三个方向,也是最值得投入的,是把聊天工具的导入做得更好用。现在聊天记录只能手动复制粘贴,如果能把微信、企微或者其他IM工具的导出聊天记录直接解析成结构化内容,按联系人自动归集到对应客户下,那这个系统才真正配得上“通信型CRM”这个名字。这一步涉及大量不同格式的解析工作,适合作为独立的迭代版本推进。
我觉得做工具这件事,最重要的不是一开始想得多完美,而是在实际使用中不断发现问题、调整方向。DeskcommCRM 现在这个版本离完美还有不少距离,但每一处细节都是我在真实的业务场景中磨出来的。如果你也在面对客户信息散乱、跟进无章法的困扰,不妨试试这个思路——先搭一个能跑起来的骨架,然后把日常使用中最高频的路径一点点打磨顺,最后它一定会成为你最顺手的工作伙伴。