从Excel到自研CRM:一套轻量客户管理系统的完整实战记录
2026/9/19 10:04:19 网站建设 项目流程

上个月我把团队用了大半年的客户管理Excel表彻底换掉了,换成了一套我们自己搭的CRM系统,名字叫DeskcommCRM。起因很简单:两个销售因为同一个企业客户撞了单,主管来问“这个客户到底是谁在跟”,我打开两份Excel一脸懵,里面甚至还有同一家公司的两个联系方式,标在不同的负责人名下。那天下班后我开始认真想一个问题:一个不到二十人的销售团队,到底需不需要一套正儿八经的CRM。

后来答案越来越明确:需要,而且不能再拖。

DeskcommCRM并不是什么大厂级产品,而是一套面向中小团队、跑在内网桌面浏览器里的轻量客户管理系统。它目前解决的核心问题只有四件事:客户资料沉淀、跟进记录留痕、销售漏斗统计、权限分级隔离。如果你正在犹豫要不要上CRM,或者你是想自己搭一套内部工具的技术负责人,这篇从需求、设计、开发到踩坑的完整记录,应该会对你有点用。

1. 从Excel到DeskcommCRM:我的真实动机和需求拆解

1.1 两个销售撞单的那天发生了什么

事情发生得很普通。销售A在周会上说自己跟了一个月的“XX科技”快要成交了,销售B当场接话:这个客户我上周刚加了微信,约了下周二见面。两人对视一眼,空气里全是火药味。

我调出客户管理Excel,发现问题比撞单本身更严重:

  • 同一个公司名,A记录在“客户名单.xlsx”的sheet1,B记录在另一个维护了半年的“潜在客户.xlsx”里。
  • A写的是座机号,B记的是手机号,不放到一起根本看不出是同一家客户。
  • 一个客户的跟进情况全在销售个人聊天记录里,领导想知道进展,只能把人叫过来口头问。
  • 上个月有个同事离职,他负责的四十多个客户散落在他个人电脑的表格里,交接只给了一份简单的文档。

这些问题其实所有用过Excel管理客户的人都懂,但在场面上它被叫作“团队管理问题”。对我来说,它首先是一个数据治理问题:客户数据没有统一模型,没有归属规则,没有变更日志,任何带状态的维护都靠人的自觉。

所以DeskcommCRM的第一个设计原则很早就定了下来:所有客户信息只能有一个入口,一条记录一份时间线,任何人改过什么都要能追溯。

1.2 市面CRM工具的共性与不足

决定自建之前,我认真对比过当下可选的路线。先看了一轮SaaS型CRM,再看开源方案,最终才确定自己搭。

这里我把三类方案放在一起比过:

方案类型成本定制灵活度数据掌控适合团队
SaaS型CRM(按月付费)中高,按人头算,功能模块常需额外加钱低,只能做字段增改,流程逻辑很难动数据在厂商服务器,导出受限制没有技术人力,急于上线
开源CRM系统(如SugarCRM、SuiteCRM等)免费但有部署和维护成本中,二次开发有学习曲线数据自持有技术人力,但能接受复杂系统
自研轻量系统人力成本为主,服务器成本极低高,完全按团队习惯定制完全自持团队有一定开发能力,且流程相对简单

我并不是否定SaaS型CRM,它们的功能完整度和UI打磨确实不是自研能轻易比的。但对一个十几人的销售团队来说,很多SaaS CRM的功能实时用不上,反而会在录入流程上增加负担。销售每天最烦的是什么?是系统里要填的字段比客户信息本身还多。我们要的是一套打开就能用、今天该联系谁一眼能看到的工具,不是一套需要培训两周的流程化平台。

至于开源CRM,我试用过几个,功能确实全,但也确实重。部署一套需要PHP、数据库、邮件服务、计划任务,默认菜单几十个,销售人员根本不想点。让业务去适应系统,还是让系统适应业务,这个选择直接影响之后的使用率。

于是“自己写一个足够轻、足够贴合自己团队流程的CRM”成了最合理的路径,DeskcommCRM这个项目就这么立项了。

1.3 DeskcommCRM不做什么

很多自研工具失败,不是因为做得太少,而是因为一开始想做太多。立项时我给DeskcommCRM划了一条明确边界,下面这些功能确定不做:

  • 不做复杂的进销存和订单履约。
  • 不做财务回款对账。
  • 不做客户自动化和营销邮件群发。
  • 不做在线客服和工单系统。
  • 不做销售业绩考核排名。

这些功能听起来都很有吸引力,但每一个都会把项目周期拉长一倍。CRM本质上是承载销售过程数据的工具,不是企业资源计划系统。我们首先要把客户的“来龙去脉”管清楚,把跟进过程中的动作记录下来,之后如果需要扩展,再围绕这些数据去做报表和自动化,这样系统的核心才不会失控。

最终DeskcommCRM的定位很朴素:一个帮助销售记得住、团队查得到、管理层看得见的客户管理工具。

2. 技术选型和项目骨架:轻量但要有扩展性

2.1 技术栈的选择过程

DeskcommCRM的技术栈,我选的是Go + Gin + GORM + PostgreSQL + Vue3 + Element Plus。这个组合不是跟风,而是根据团队实际情况和部署环境一条条筛出来的。

后端没有选Java Spring Boot,因为对我们这种内部系统来说它太重了。一个嵌入式设备厂商的内网服务器,内存本来就不富裕,一个Spring Boot应用起步就要几百兆,部署时还要配JVM参数、打war包、管Tomcat,维护成本不比业务代码低。Go编译出来就是一个二进制文件,丢到服务器上跑起来就行,内存占用低,并发能力对我们这个场景绰绰有余,这是很现实的选型理由。

GORM作为ORM用起来效率很高,虽然它有一些“神奇”的默认行为需要踩坑(后面会讲),但整体上能让开发者把时间花在业务逻辑而不是SQL拼接上。数据库用了PostgreSQL而不是MySQL,原因有两个:一是它自带的jsonb类型方便给客户标签和扩展字段用,二是pg的时区处理比mysql明确,对后面统计“今日待跟进”这种需求会友好很多。

前端选择Vue3配合Element Plus,主要是Vue在国内社区活跃,Element Plus的表格、表单、日期选择器组件都很成熟,做管理后台效率极高。状态管理用了Pinia,路由用的Vue Router,没有引入重型状态方案,因为页面之间的数据共享并不多。

项目结构大概是这样的:

deskcomm-crm/ ├── backend/ │ ├── main.go │ ├── config/ │ ├── models/ │ ├── api/ │ └── middleware/ ├── frontend/ │ ├── src/ │ │ ├── views/ │ │ ├── components/ │ │ └── stores/ └── deploy/ ├── docker-compose.yml └── nginx.conf

2.2 核心数据库表是怎么设计的

整个系统最核心的表就五张:用户表、客户表、跟进记录表、商机表和操作日志表。这里我把当初建表时的关键结构整理出来。

用户表简单,重点在角色字段和部门字段,为后续权限控制打底:

CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password_hash VARCHAR(100) NOT NULL, display_name VARCHAR(50) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'sales', -- admin, manager, sales department_id BIGINT NOT NULL, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() );

客户表是核心中的核心。我特意把客户资料拆成了“必要字段”和“扩展字段”。必要字段只有公司名称、联系人、手机、微信、来源渠道、负责人、状态、下次跟进时间。其他像行业、规模、地址、备注,全部放到一个jsonb扩展字段里,销售愿意填就填,不愿意填也不影响系统使用。这个设计的出发点很直白:减少录入阻力。

CREATE TABLE customers ( id BIGSERIAL PRIMARY KEY, customer_no VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(200) NOT NULL, contact_name VARCHAR(50), phone VARCHAR(30), wechat VARCHAR(50), source VARCHAR(30), level VARCHAR(20) DEFAULT 'B', owner_id BIGINT REFERENCES users(id), status VARCHAR(20) DEFAULT 'active', next_follow_up_at TIMESTAMPTZ, last_follow_up_at TIMESTAMPTZ, tags JSONB DEFAULT '[]', remark TEXT, is_deleted BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() );

客户编号和唯一索引也要重点说。客户编号我用了时间戳加随机数的组合,类似202505071345001234,保证手工录入重复时也不会产生歧义。预防撞单靠的当然不只是编号,真正起作用的是统一的客户名归一化逻辑,系统录入时会自动去除公司名的后缀,比如“XX科技有限公司”会归一化成“XX科技”,再去查重,降低重复概率。

跟进记录表负责保存所有历史动作,这是DeskcommCRM最有价值的数据资产:

CREATE TABLE follow_ups ( id BIGSERIAL PRIMARY KEY, customer_id BIGINT NOT NULL REFERENCES customers(id), user_id BIGINT NOT NULL REFERENCES users(id), type VARCHAR(20) NOT NULL, -- phone, wechat, visit, email content TEXT NOT NULL, next_follow_up_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT now() );

2.3 前端工作台和桌面场景适配

DeskcommCRM一开始就是给坐在工位上的销售用的,所以界面优先适配桌面浏览器,分辨率最低按1366x768设计。我没有一上来就包Electron,而是先把Web端做扎实,后面真有需要再加壳。

工作台首屏只放四块内容:

  • 今日待跟进列表(按下次跟进时间倒序,只显示今天及之前该跟进的客户)。
  • 我的客户总数和本周新增数。
  • 最近一周新增跟进记录数。
  • 本月个人成交金额。

这个“今日待跟进”列表是整个系统里被销售点开频率最高的页面。原因很简单,它帮销售省掉了“翻本子回忆今天该联系谁”的脑力劳动。为了让这个列表准确,后端每天凌晨会跑一次定时任务,把昨日未跟进且已经超过计划时间的客户往前排,同时给负责人推送桌面通知。

前端和后端通信用的RESTful接口,所有时间字段返回的时间戳统一用UTC,前端在展示时转换成本地时间。这一点从第一天就约定好,避免在时区问题上反反复复。后面部署时我们还是踩了一次时区坑,等会在第五章详细讲。

3. 四个核心模块的设计细节

3.1 客户资料:如何避免“信息一多就不想录”

我们在设计客户模块时有一个核心原则:任何新增字段都必须回答一个问题,这个字段对销售后面的动作有没有直接影响。没有就直接进扩展字段。

像客户行业、客户规模、所在地区这类信息,跟进的销售人员其实不太关心,但对管理层做渠道分析有价值。我把它们全部放到jsonb的tags里,录入时默认不展示,销售可以跳过。管理员可以在客户列表里按标签筛选聚合出报表来。

客户来源是我专门保留的枚举字段,它是后续市场渠道效果分析的关键。我们只设置了五个来源:转介绍、官网咨询、行业展会、电话陌拜、其他。为什么不多设?因为选项越多,销售在点选时思考时间越长。五个选项足够覆盖绝大多数来源,而且后续统计时每个分组的样本量也够大。

客户等级用了A/B/C/D四档。A表示高意向客户,两周内可能成交;B表示有明确意向但还在比较;C表示长期培育;D表示基本无价值,可以退回公海。档次由销售自己判断,但主管可以看到所有客户的等级变动历史。等级变动会写入操作日志,这样如果一个客户被反复调整等级,管理层能看出销售是在认真判断还是在敷衍填写。

3.2 跟进记录:时间轴和幂等校验

客户资料是静态的,跟进记录才是让客户“活”起来的动态数据。跟进记录的核心是时间轴展示。每一条记录按照创建时间排列,同一客户页面的时间轴上能直接看到第一次电话是什么时候、中间谁接手过、每次沟通之间的间隔是不是合理。

这里有一个很实用的设计细节:每次新写一条跟进记录时,系统会自动把上一次跟进记录和当前记录的间隔天数显示出来。这个小小的提示非常管用,它让销售自己意识到“这个客户已经两周没联系了”,比任何主管催办都有效。

另一个值得提的是防重复提交。写跟进记录时,用户很容易因为网络卡顿多点一下按钮,导致同一条内容被插入两次。我在后端加了一个接口幂等校验:前端每次打开跟进弹窗时向后端申请一个请求ID,提交时带着这个ID,后端在指定时间段内如果发现同一个请求ID,就直接拒绝重复插入。

核心代码如下:

func CreateFollowUp(c *gin.Context) { var req FollowUpRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // 幂等校验:同一请求ID在同一客户、同一用户下不能重复写入 var count int64 db.Model(&FollowUp{}). Where("idempotent_key = ?", req.IdempotentKey). Count(&count) if count > 0 { c.JSON(200, gin.H{"duplicated": true}) return } followUp := FollowUp{ CustomerId: req.CustomerId, UserId: c.GetInt64("userId"), Type: req.Type, Content: strings.TrimSpace(req.Content), NextFollowUpAt: req.NextFollowUpAt, IdempotentKey: req.IdempotentKey, } tx := db.Begin() if err := tx.Create(&followUp).Error; err != nil { tx.Rollback() c.JSON(500, gin.H{"error": "创建跟进记录失败"}) return } // 同步更新客户表的下次跟进时间和最后跟进时间 tx.Model(&Customer{}).Where("id = ?", req.CustomerId). Updates(map[string]interface{}{ "last_follow_up_at": time.Now(), "next_follow_up_at": req.NextFollowUpAt, }) tx.Commit() c.JSON(200, gin.H{"data": followUp}) }

幂等校验不是花哨功能,在真实生产环境里它就是最后一道防线。销售用的笔记本放在桌面上,蹭到F5刷新是常有的事;公司的无线网络偶尔抽风,转半天圈后用户又点了一次提交,这种事情一旦发生,后面的时间线就会有两条一模一样的内容,看起来很业余。

3.3 销售漏斗:统计口径要和销售对账

销售漏斗这个模块,开发起来本身不难,难的是确定每个阶段的定义。我在上线前拿着漏斗阶段去问销售负责人时,对方第一反应是“你定就行”。我说不行,如果定得跟你们实际工作情况不一样,这个图就是个摆设,没人看。

最终我们把漏斗阶段定义为六段:

阶段含义进入条件
1 新线索刚录入系统,未联系客户创建即进入
2 已联系已和客户发生过有效沟通首次跟进后进入
3 意向确认客户明确表达合作意向销售手动推进
4 方案报价已向客户提交过方案或报价有报价记录后
5 合同审批已发合同或处于内部审批流程中销售手动推进
6 成交客户已回传合同销售确认

每个阶段之间的转化率,系统按商机金额和数量分别统计。这里有一个关键点:漏斗金额必须用商机表里的预计成交金额,而不是客户表里的某个字段。因为一个客户可能同时有好几个产品线的商机在推进,如果只在客户上挂一个总金额,后续统计会乱掉。

商机表结构如下:

CREATE TABLE deals ( id BIGSERIAL PRIMARY KEY, customer_id BIGINT NOT NULL REFERENCES customers(id), user_id BIGINT NOT NULL REFERENCES users(id), title VARCHAR(200) NOT NULL, amount NUMERIC(12,2) NOT NULL, stage VARCHAR(20) NOT NULL, expected_close_date DATE, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() );

上线两周后销售会主动看漏斗,我觉得不是因为我们这个页面做得多好看,而是因为统计口径和他们对齐了。大家知道每一个阶段代表什么,图上的数字和心里的判断一致,工具才可信。

3.4 公海池和自动回收规则

公海池是让客户资源流转起来的关键模块。简单说,没有所有者的客户都放在公海里,销售可以主动捞取,也可以把自己长期跟不动的客户退回公海。

规则是这样定的:

  • 新导入的客户默认进公海池,由主管分配或销售自行捞取。
  • 每个销售名下最多持有300个客户,超出后必须退回一部分公海才能继续捞取。
  • 客户超过30天没有跟进记录,自动退回公海,并且系统会记录退回原因。
  • 自己名下的客户不能主动退回给自己的同事,只能退回公海或由主管调配。

自动回收的定时任务,我最初用了一个简单的goroutine配合time.Ticker来实现。这个小功能在单机部署时没什么问题,但如果后面想扩容成双实例,就会遇到重复执行的问题。这个坑在后面第五章具体讲。

公海池的设计让销售开始珍惜自己名下的客户,也倒逼大家把长期沉睡的客户释放出来给团队其他同事使用。系统跑通后,主管最明显的感受是:客户待在个人名单里吃灰的情况明显变少了。

4. 权限模型和员工交接:团队协作的底线

4.1 数据权限矩阵

客户数据是公司的核心资产,权限控制绝不能马虎。DeskcommCRM的角色只设计了三个,没有更多:管理员、销售主管、销售员。

权限矩阵如下:

功能销售员销售主管管理员
查看客户列表仅本人本部门全部
新增/编辑客户本人名下本部门全部
查看跟进记录本人名下本部门全部
添加跟进记录本人名下本部门及下属全部
分配客户不可本部门内可分配全部可分配
查看成交金额报表仅个人本部门全部
管理公海池捞取全部全部
系统设置不可不可

数据隔离的实现没有用复杂的行级权限插件,因为我觉得角色只有三个,规则是有限的,直接在SQL查询时拼条件就够了。后端中间件会从JWT里解析出当前用户的角色和部门ID,然后在每个查询接口上调用一个公共函数追加数据范围条件。

这样做的好处是代码直观,出问题容易排查。坏处是如果以后权限规则变复杂,中间件的逻辑会膨胀。对一个内部工具来说,这个取舍是可以接受的。

4.2 离职员工客户一键交接

员工离职时的客户交接,是我在做权限模块时顺手加的需求,也是后来被主管表扬最多的一个功能。

以前用Excel管理时期,人一旦离职,他手上的Excel如果忘了导出,客户就跟着人事档案一起消失了。DeskcommCRM做了两件事:

第一,管理员可以一键搜索某个离职员工的全部客户和商机,批量转移给指定的人。转移时保留所有历史跟进记录,不会因为换负责人而丢失时间线。

第二,离职员工账号不会物理删除,而是做离职标记,状态变为“已停用”,他名下那些尚未转移的客户会标记为待交接,避免出现一段时间内“无主客户”的真空状态。

实现这个逻辑时,客户表里的owner_id不动,另外加了一个交接记录表,每次批量转移都记录操作人和原负责人。万一后续出现客户归属纠纷,可以从操作日志里看到确切的转移时间线和操作人。

4.3 操作日志和防抵赖

操作日志这个模块技术含量很低,但价值很高。我把所有敏感动作都记录了下来:

  • 修改客户所属人
  • 修改客户状态或等级
  • 删除客户(软删除)
  • 导入导出客户数据
  • 修改系统配置

日志表结构是这样的:

CREATE TABLE operation_logs ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, action VARCHAR(30) NOT NULL, target_type VARCHAR(20) NOT NULL, target_id BIGINT NOT NULL, detail JSONB, created_at TIMESTAMPTZ DEFAULT now() );

有人可能会说这不就是记录流水嘛,没什么技术含量。但在真实团队里,这个表救过我一次。有一次一个销售说自己名下的客户被另一个同事偷偷抢走了,我们查了操作日志,发现那个客户是主管在系统中转移的,转移时间、操作人、原因备注全都写得很清楚,几分钟就化解了矛盾。

所以如果你也要做内部系统,无论多简单,操作日志一定要从第一天就做好。后期想补,是最麻烦的。

5. 上线部署踩过的三个坑

5.1 数据库时区混乱导致“下次跟进时间”错8小时

系统开发阶段,我们用的都是本地数据库,时区问题一直没暴露。部署到内网服务器后,测试人员反馈:销售在下午四点钟设置了一条“明天上午十点跟进”的提醒,结果第二天上午六点系统就开始提醒了,整整早了四个小时,有时候还会出现显示时间比实际时间晚8小时的情况。

排查过程是这样的。我先去服务器上输入date命令,发现系统时区是UTC,而我们的人都在东八区。数据库里的timestamptz字段本身没问题,PostgreSQL在存储时会转成UTC,取出时会按会话时区转换。问题出在后端Go程序连接数据库时,GORM默认使用UTC作为time.Local,而前端传过来的时间戳又是带时区的ISO字符串。

最后统一了配置:

  • 数据库连接串里显式指定TimeZone=Asia/Shanghai
  • Go后端设置time.Local = time.FixedZone("CST", 8*3600)
  • 前端所有input日期控件按本地时间展示,提交时转成ISO 8601带偏移格式。
  • 后端接收时间参数时统一用time.RFC3339解析。

这个坑解决后,系统所有时间提醒都正常了。以后做任何跨时区系统,千万不要依赖操作系统默认时区,一定要在应用层显式设置。

5.2 定时任务重复执行:从偶发到定位

公海池自动回收任务上线一周后,主管反映有些客户会连续收到两封“已退回公海”的提醒,而且客户状态也确实出现了重复。最初我以为是前端提示重复了,后来一查数据库,才发现同一条回收记录插了两遍。

原因很简单:我一开始用go-cron起了定时任务,但公司服务器上后来用Docker启动了两份后端实例(一台机器上为了平滑升级跑了新旧两个容器),结果两个实例同时去扫30天未跟进的客户,产生了并发竞争。

这个问题的标准解法是加分布式锁。我用Redis的SET NX EX实现了一个最简单的锁:

func acquireLock(ctx context.Context, redis *redis.Client, key string, ttl time.Duration) (bool, error) { ok, err := redis.SetNX(ctx, key, "1", ttl).Result() if err != nil { return false, err } return ok, nil }

定时任务每执行一次前先尝试获取锁,拿到锁的实例才继续执行,另一个实例直接跳过。锁的过期时间设置成30秒,任务正常执行不会超过这个时间。如果实例崩溃了,锁也会自动过期,不会造成死锁。

这个坑告诉我们一个道理:只要涉及定时任务,就要以多实例部署为前提去设计,别管现在是不是只有一台服务器。因为服务器以后一定会扩容,到那时再改就涉及数据一致性问题了。

5.3 内网连接池耗尽导致页面假死

系统上线后前两周一切正常,第三周开始,每天早上九点到十点销售集中上班打卡后,页面加载非常慢,甚至出现“无法连接服务器”。一开始我怀疑是带宽问题,后来看后端日志才发现是数据库连接池被占满了。

原因是公司内网有段时间网络不太稳定,部分请求从后端到数据库的TCP连接没有正确释放,导致PostgreSQL连接数一直累积,很快就打满了默认的100个连接。

我在GORM初始化时对连接池参数做了调整:

sqlDB, _ := db.DB() sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(50) sqlDB.SetConnMaxLifetime(time.Hour)

同时把PostgreSQL的max_connections从100调到了200。最重要的修改是给所有数据库查询接口设置了超时时间,不允许任何一个慢查询无限期占用连接:

ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second) defer cancel() db.WithContext(ctx).Find(&customers)

这之后,页面假死的问题基本消失了。内网系统虽然用户不多,但数据库连接池配置依然不能省略,这是很多内部小系统容易忽略的地方。

6. 上线两个月的使用反馈和后续计划

6.1 销售最认可的一个页面

DeskcommCRM上线两个月后,我私下问了几个销售,最常用的功能是什么。大部分人回答的不是客户列表,也不是漏斗报表,而是工作台首页的“今日待跟进”。

这个答案我完全理解。因为工作台解决的是销售每天最基础的一个问题:今天上班该干什么。传统Excel管理没有“提醒”这个概念,所有事情都靠脑子记。系统上线后,销售只要打开电脑看一眼工作台,哪些客户今天该联系、哪些已经超过三天没有动静,一目了然。这种确定性带来的安心感,比一个复杂的报表页面管用得多。

顺便说一句,我们为了防止工作台变成无意义的待办堆砌,做了个限制:每天最多展示20条待跟进客户,按紧急程度排优先级。如果销售手上有超过20个客户需要跟进,那说明任务排得有问题,应该优先处理最紧急的部分,而不是让系统把所有积压全部列出来制造焦虑。

6.2 管理层要的报表怎么自动生成

管理层对系统的诉求和销售完全不同,他们要的是结果数据,不是过程数据。每周一主管会要一份带渠道维度的周报:这周新增了多少客户、每个渠道分别贡献了多少、哪些渠道转化率最高、整个销售漏斗健康程度如何。

刚开始我的做法是让后台实时聚合统计,但客户量上来之后,复杂查询会拖慢接口响应,特别是跨多个表的漏斗统计。后来调整了方案:每天凌晨用定时任务把当天的汇总数据预先算好,存到一张统计汇总表里,报表页面只查这张表,不直接查明细表。

这个“预计算+按天汇总”的思路,在做内部系统时特别实用。很多时候不需要实时计算,报表数据晚几分钟更新完全不影响决策,但实时聚合的数据库压力却是实打实的。

6.3 接下来要做的事和我的建议

DeskcommCRM目前还在持续迭代。下一步打算做的功能有三个:

一是把跟进记录里的“客户意向变化”用趋势图展示出来,让销售和历史数据对比,判断自己的跟进策略是否有效。

二是做一个手机端适配的简化版,不追求完整功能,重点解决在外拜访客户时快速添加跟进记录和查看客户详情的需求。

三是接入企业微信提醒,把“今日待跟进”和超过时限未跟进的客户推送到对应销售的手机上。

如果你也想构建类似的内部系统,我的建议是:先把“客户能存进去、跟进能记下来、销售能想起来、管理层能看到”这四件事跑通,其他都往后放。不要一开始就上AI销售预测、智能营销这些噱头功能,先把基础的数据可信度做扎实。

DeskcommCRM这个项目给我的最大收获不是代码量,而是让我重新理解了工具和业务的关系。工具只有贴合业务实际,才会被团队真正用起来;而一个被大家每天都打开的系统,它的数据才会越来越值钱。希望这篇实战记录能给正准备做类似事情的人一些参考。

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

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

立即咨询