很多做销售管理或者带过业务团队的朋友应该都有类似的体验:客户散落在微信聊天记录、Excel 表格、旧邮件和每个人的手机通讯录里,月底复盘的时候,想搞清楚"这个月到底哪个渠道带来的客户最多""哪些客户已经十几天没人跟进了""某个销售离职之后他手上的客户到底交接给了谁",基本靠拍脑袋。我们团队之前就面临这个局面,后来干脆自己动手做了一个内部系统,代号就叫 DeskcommCRM。名字拆开看很直白:Desk 代表工作台,comm 代表沟通,合在一起就是"在工位上把沟通和客户管理一起搞定"。
这个项目我们前后做了大约两个月,从需求梳理、数据库设计、接口开发到前端页面和上线推广,全部是团队内部人员兼职完成的。文章会完整记录整个过程的思路、选型、核心实现、踩过的坑,以及上线之后的真实数据表现。如果你也在纠结"到底要不要自己做一个 CRM""已有的客户管理系统太笨重怎么办",这篇文章应该能给你一个比较完整的参考。
1. 项目背景与产品定位
1.1 为什么叫 DeskcommCRM:项目要解决的真实痛点
起名这件事往往能反映一个项目的核心诉求。DeskcommCRM 这个名字里的 "Desk",强调的是"工作台"概念,所有操作都围绕一个统一的桌面端界面完成;"comm"是 communication,说白了就是"沟通"。整个系统设计的出发点不是单纯记客户信息,而是把"客户档案 + 跟进沟通 + 任务提醒"揉在一起,让业务员打开系统就能知道今天该干什么、手头客户状态是什么。
当时我们团队踩的坑非常典型。业务员的客户资料存在三个地方:个人微信备注、公司共用的在线表格、部分合同相关的邮件记录。每周开例会的时候,大家报的数据经常对不上,同一个客户可能被两个销售重复联系,更麻烦的是,有同事休假一周,他名下的客户就整整一周无人跟进。我们用了几款市面上的 CRM 产品,要么太贵,要么功能堆砌得过于复杂,业务员根本不愿意天天打开,最后慢慢就变成了"花了钱没人用"的摆设。
所以 DeskcommCRM 的第一个设计原则就是:轻。不是功能越少越好,而是每个功能都必须直接服务于"把单子推进下去"这个目标。客户档案、跟进记录、提醒、基础统计,这四个核心能力解决了 80% 的问题,剩下 20% 的个性化需求用自定义字段和标签去兜底。
1.2 目标用户与适用范围
做任何项目之前都要先把用户画像定清楚。DeskcommCRM 的目标用户很明确:10 到 100 人规模的销售型团队、售后服务团队、以及需要管理客户跟进过程的微型企业。这类团队通常没有专职的 IT 运维人员,预算有限,但又接受不了纯用 Excel 管理客户。
这个规模区间的团队有个共同特点:业务流程不追求极致的复杂配置,但希望系统能贴合自己的做事习惯。比如有的团队习惯把客户按渠道来源划分,有的习惯按客户等级划分,有的希望每次联系完客户都自动生成一条跟进记录。市面上通用型 CRM 往往把功能做得很完整,但恰恰因为太完整,没过多久就用成了重型系统。DeskcommCRM 在功能范围上做了明显的取舍,砍掉了大部分团队用不上的模块,把资源和精力集中在客户管理、跟进、提醒和基础统计上。
这也是我特别想强调的一点:不是所有业务场景都需要一套大而全的 CRM。先想清楚你的团队到底在哪一个环节最痛,再决定是自己搭建还是购买现成产品。DeskcommCRM 这个项目从一开始就没想着解决所有问题,它就是奔着"让客户跟进不再断档"这个单一目标去的。
1.3 自研还是采购:为什么我们选择从零搭建
这里必须老老实实承认,自研 CRM 并不是所有情况下的最优解。我们的情况比较特殊:团队本身有后端和前端开发能力,而且对数据字段、业务流程有很强的定制需求,市面上的轻量级 CRM 很难完全满足。如果你们的团队完全没有技术人员,我还是建议优先考虑成熟产品,哪怕是飞书多维表格、在线表单加自动化规则,也能解决很大一部分问题。
选择自研的另一个原因是成本可控。我们不需要额外采购服务器集群和高可用方案,只在一台 4 核 8G 的云服务器上部署了后端、数据库和前端静态资源,整套系统跑得非常稳。开发工具全部选用开源方案,没有授权费用。算下来除了人力成本之外,每个月的服务器开销也就一百多块,比买成熟 CRM 按坐席收费要便宜得多。
当然,自研也有代价,最大的代价就是需要自己维护、自己迭代。系统刚上线的时候问题不少,好在团队内部有人懂技术,出了问题当天就能修。如果你决定走自研这条路,务必要评估好后续有没有持续维护的能力,否则做到一半烂尾比不做更难受。
2. 内容整体设计与架构思路
2.1 功能模块总览:把复杂业务拆成四个核心功能
DeskcommCRM 的功能设计是从业务流程反推出来的。我们先画了一张非常简单的业务流程图:获取客户线索,创建客户档案,安排销售跟进,记录每次沟通的结果,推进客户状态,最终成交或进入售后环节。无论业务多复杂,主干流程就这六步。
基于这个主干,系统拆成了四个核心模块。第一个是客户档案模块,包含联系人和公司的信息维护、标签管理、归属人管理;第二个是跟进记录模块,支持每次沟通后写跟进内容、设置下一次跟进时间;第三个是任务提醒模块,每天自动汇总当天需要跟进的客户清单,并把超过三天未更新的客户标记为"待关注";第四个是基础看板模块,统计每个销售的客户数量、跟进频率、商机转化情况。额外还有一个工单模块,专门用于处理售后服务类的客户问题,这个功能是后期迭代加上去的,因为团队发现不少客户反馈集中在使用指导上,需要有一条明确的处理通道。
模块设计上我一直坚持的观点是:先有流程,再有功能。很多 CRM 产品功能冗余,但跟你实际干活的方式对不上,这就是"流程和功能割裂"造成的。我们每一次开设计会,都会先问一个问题:"业务员在什么场景下会用到这个页面?"如果答不上来,这个功能就不做。
2.2 技术选型思路:稳定、可维护、能快速改动
技术选型的时候,我们定了三条原则:团队熟悉、生态成熟、部署简单。团队后端熟悉 Python,所以后端选了 FastAPI 框架;前端整体不复杂,选了 Vue 3 加 Element Plus;数据库选了 PostgreSQL,因为它对 JSON 字段的支持很好,方便做自定义扩展字段,同时又有很强的关系型数据管理能力。
用 FastAPI 好处很明显。第一,开发效率高,基于 Python 的类型注解可以自动生成接口文档,前后端联调省了很多沟通成本。第二,自带 OpenAPI 文档,业务员提出疑问的时候,直接把文档地址甩给对方,他们能看到每个接口的参数和返回值。第三,异步支持成熟,即便后面并发量上来,也不用急着换框架。
前端选择 Vue 3 是因为团队里没人愿意写一大堆 jQuery 操作 DOM 的代码,而且 Element Plus 的表单和表格组件非常成熟,客户列表、跟进记录这类页面几乎不需要自己封装复杂组件。项目整体采用前后端分离的结构,后端提供一个 RESTful API,前端通过 Axios 调用,认证用的是 JWT token。
部署层面我们并没有上 K8s 之类的重型容器编排方案。一台服务器,后端直接跑在 systemd 服务里,前端构建完的静态文件交给 Nginx 托管,数据库用 PostgreSQL 自带的服务。每一步都简单直接,出问题的时候排查也快。
2.3 为什么没有引入重量级工作流引擎
很多企业的 CRM 系统都有一个毛病:想通过工作流引擎把所有业务流程都"自动化"起来。结果就是流程配置表比业务代码还多,普通运维根本不敢碰,改一个节点往往牵一发而动全身。DeskcommCRM 没有引入任何工作流引擎,所有状态变更都用简单的状态机和明确的后端接口来控制。
比如工单模块,我们定义了一个固定的状态流转:新建、受理、处理中、待反馈、已关闭。状态变更全部由后端校验,前端只能调用指定的接口,不能随意修改状态字段。这样设计的好处是,业务逻辑一目了然,出了问题可以直接查代码,而不是去翻一堆复杂的流程配置。
如果后续真的要加入更复杂的审批流,比如超时自动升级、多级审批,再引入轻量的规则引擎也不迟。但至少在当前阶段,简单的代码实现比可视化编排更可靠、更好维护,也更容易被团队内部的人理解和接手。
3. 核心细节解析与实操要点
3.1 客户档案建模:公司、联系人、自定义字段三层结构
客户档案是 CRM 的数据底座,建模没做好,后面所有功能都会跟着别扭。DeskcommCRM 把客户分成两个层级:公司级别的客户(clients)和联系人级别的联系人(contacts)。一个公司下面可以挂多个联系人,联系人归属于某个公司。这种结构非常适合 B2B 业务,因为很多订单是跟公司签约,但日常沟通对接的是具体的部门联系人。
公司表的核心字段包括:id、公司名称、所属行业、客户来源、客户等级、归属销售、创建时间、更新时间。客户来源用一个简单的字典字段来存,包括线上广告、转介绍、展会、官网留言、电话咨询等。客户等级分为 A、B、C、D 四档,A 代表高意向客户,D 代表潜在客户。
联系人表的核心字段包括:id、所属公司 id、姓名、职位、手机号、微信、邮箱、备注。这里要注意一个实操问题:同一个手机号可能属于同一个人但公司不同,所以手机号并不做唯一约束,而是允许重复,但会在界面上提示"该手机号已存在于其他客户档案中",避免录入时无感知地重复建档。
自定义字段这块,我们没有做复杂的动态表单引擎。方案是数据库里保留一个 JSONB 类型的 extra_fields 字段,前端动态渲染出一个"扩展信息"面板。比如有的团队需要记录客户常用的快递地址,有的团队需要记录客户的下单周期,这些都可以通过配置 JSON 结构来实现,不需要改表结构。
CREATE TABLE clients ( id BIGSERIAL PRIMARY KEY, name VARCHAR(120) NOT NULL, industry VARCHAR(80), source VARCHAR(40), owner_id INT NOT NULL, level VARCHAR(10) DEFAULT 'D', status VARCHAR(20) DEFAULT 'active', extra_fields JSONB DEFAULT '{}', created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE contacts ( id BIGSERIAL PRIMARY KEY, client_id BIGINT NOT NULL REFERENCES clients(id) ON DELETE CASCADE, name VARCHAR(60) NOT NULL, position VARCHAR(80), phone VARCHAR(30), wechat VARCHAR(60), email VARCHAR(120), remark TEXT, created_at TIMESTAMP DEFAULT NOW() );3.2 跟进记录与"下次跟进时间"提醒机制
客户跟进是整个系统的高频操作,也是最能体现 CRM 价值的功能。DeskcommCRM 的跟进记录设计很简单:每一条跟进记录包含客户 id、跟进人、跟进内容、跟进方式(电话、微信、面谈、邮件)、下次跟进时间。核心逻辑是,每次创建一条跟进记录时,系统会同步更新客户档案的 last_follow_up_at 字段,并且把 next_follow_at 存到跟进记录里。
这里的窍门在于"下次跟进时间"的计算方式。我们没有允许业务员随便填,而是做了一个下滑选择器:明天、三天后、一周后、两周后、一个月后。这样设计是为了统一跟进节奏,降低业务员的思考成本。页面首页有一个"今日待跟进"列表,查询逻辑就是 where next_follow_at <= now() and owner_id = 当前用户,再按客户等级排序,A 级客户排最前面。
逾期未跟进的预警是另一个重要功能。每天晚上 12 点,定时任务扫描所有客户,如果发现某个客户距离最后一次跟进已经超过 7 天,就把这个客户标记为"跟进超时",并且给归属销售和直属主管发送站内通知。这个功能上线后效果非常明显,业务员的响应速度比之前快了很多,因为我们把"遗忘"这件事变成了系统主动提醒,而不是靠人脑记住。
这里想分享一个从实际使用中得到的经验:跟进记录的录入成本一定要低。如果每次记录都要打开客户详情页、找到按钮、填一堆表单,业务员很快就会嫌烦。所以我们把"快速记录跟进"做成了一个全局的弹窗入口,不管你在哪个页面,按一下快捷键就能呼出弹窗,选择客户、写下内容、选一个下次跟进时间,十秒钟搞定,不需要跳转页面。
3.3 工单模块的状态机与自动分配策略
工单模块是我们后期加进去的,主要是为了承接售后服务场景。业务方提需求时只要求一件事:客户的咨询和投诉不能被漏掉,每个工单都需要有明确的负责人和处理状态。
工单状态机设计成五个状态:新建、受理、处理中、待反馈、已关闭。当客户通过电话或交流群反馈问题时,客服创建工单,系统自动分配给当前负责该客户的销售。如果销售超过 24 小时没有受理,工单会升级到主管端,由主管手动指派。这个自动升级逻辑我们最开始是用定时任务扫描实现的,每 30 分钟跑一次,发现超时的工单就触发升级提醒。
在处理反馈环节,我们接入了一个简单的站内通知。工单负责人在处理过程中添加处理备注,客户相关人不需要主动刷新页面,登录系统后就能在通知中心看到更新。如果公司有企业微信群或者钉钉群,还可以通过 webhook 把消息推到群里,不过这一版我们没有做太深,保持团队内部使用够用就行。
工单和客户档案的关系是:每个工单必须关联到一个客户。这样做的好处是,以后查看客户详情时,可以直接看到这个客户历史上提过哪些问题、处理了多久、最终怎么解决的,对判断客户忠诚度和售后质量非常有用。
3.4 数据看板:用最简单的 SQL 把关键指标算出来
数据驱动的前提是数据能看得见。DeskcommCRM 的看板没有用专门的 BI 工具,就是一张汇总页面,后端写了几条聚合查询接口,前端用柱状图和列表展示。核心指标只有四个:各销售名下客户数、本月新增客户数、本月跟进次数、商机转化率。
商机转化率的定义是"本月成交客户数 / 本月新增商机客户数"。我们并没有像大厂那样去建复杂的商机阶段模型,而是用客户等级里的 A 级客户当作"商机"。简单粗暴,但业务团队看得懂。如果后续需要更精细的漏斗分析,可以再扩展一个商机表,记录商机金额、预计成单时间、所处阶段等字段。
看板接口的核心 SQL 逻辑很简单,举一个例子,统计每个销售的客户数和跟进次数:
SELECT u.name AS owner_name, COUNT(DISTINCT c.id) AS client_count, COUNT(f.id) AS follow_up_count FROM users u LEFT JOIN clients c ON c.owner_id = u.id LEFT JOIN follow_ups f ON f.client_id = c.id WHERE u.role = 'sales' GROUP BY u.id, u.name;要注意的是,这种按天的统计接口如果数据量大了可能会有性能问题。目前我们团队的数据量级是几千条客户数据、几万条跟进记录,查询基本都在几十毫秒内完成。如果你的数据量到了百万级别,建议引入按月汇总表或者物化视图,不要每次都全表扫描。
4. 实操过程与核心环节实现
4.1 从零初始化项目:服务器、数据库、基础框架
先说说服务器环境。我们用的是一台 4 核 8G 的云服务器,操作系统是 Ubuntu 22.04 LTS。考虑到后面可能会迁移,后端应用和数据都尽可能使用容器化或者可复制的部署方式,但最终为了省事,直接用 systemd 托管 FastAPI 服务,数据库用系统自带的 PostgreSQL 14。
第一步是基础环境准备。把系统更新到最新,安装 Python 3.10、pip、PostgreSQL、Nginx,然后创建专用的运行用户,避免直接用 root 跑应用。项目目录结构建议这样规划:
/home/deskcomm/ ├── backend/ │ ├── app/ │ │ ├── main.py │ │ ├── routers/ │ │ │ ├── auth.py │ │ │ ├── clients.py │ │ │ ├── contacts.py │ │ │ ├── follow_ups.py │ │ │ └── dashboard.py │ │ ├── models.py │ │ ├── schemas.py │ │ └── database.py │ ├── requirements.txt │ └── run.sh ├── frontend/ │ ├── src/ │ │ ├── views/ │ │ ├── components/ │ │ └── api/ │ └── package.json └── deploy/ ├── nginx.conf └── deskcomm.service后端依赖非常简单:fastapi、uvicorn、sqlalchemy、psycopg2-binary、pydantic、python-jose、passlib。前端依赖则是 vue、vue-router、pinia、axios、element-plus、echarts。整个项目依赖控制在最小范围,避免引入一大堆用不上的库。
4.2 数据库表结构初始化:用户、客户、跟进、工单的关系设计
数据库设计是整个项目最需要想清楚的部分。除了前面提到的 clients 和 contacts 表之外,还要有 users、follow_ups、tickets 三张核心表。users 表记录了系统用户、角色和部门;follow_ups 表记录每一次跟进;tickets 表记录售后工单。
users 表的角色字段直接决定了权限粒度。目前系统有四种角色:管理员可以查看全部数据、管理用户、配置字典;主管可以查看本部门全部数据,可以手工分配工单;普通成员只能查看和操作自己名下的数据;只读访客只能查看被分配的客户详情。数据权限用简单的"数据归属人"字段实现,不做复杂的数据权限策略,好处是业务逻辑简单明了,不会出现"明明能看到客户却看不到跟进记录"这种混乱情况。
follow_ups 表结构如下:
CREATE TABLE follow_ups ( id BIGSERIAL PRIMARY KEY, client_id BIGINT NOT NULL REFERENCES clients(id) ON DELETE CASCADE, creator_id INT NOT NULL REFERENCES users(id), content TEXT NOT NULL, method VARCHAR(20) DEFAULT 'wechat', next_follow_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_follow_ups_client_id ON follow_ups(client_id); CREATE INDEX idx_follow_ups_next_follow_at ON follow_ups(next_follow_at);这里要特别注意索引设计。我们对 client_id 和 next_follow_at 分别建了索引,因为两个高频查询都用到了这两个字段:查询某个客户的全部跟进记录,以及查询某一天需要跟进的客户列表。没有索引的话,数据量一大,首页的"今日待跟进"接口就会拖慢。
工单表 tickets 的核心字段包括标题、描述、状态、优先级、关联客户、负责人、创建人、创建时间和更新时间。状态字段用字符串存储,方便前端直接做标签渲染,后端只控制状态跳转的合法性。
4.3 后端接口实现:以客户模块为例讲清楚开发套路
后端接口开发我们按照 FastAPI 的标准模式来写。以客户列表接口为例,需要支持按名称模糊搜索、按归属销售筛选、按客户等级筛选、分页返回。实现思路是用 SQLAlchemy 动态拼接查询条件。
from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from sqlalchemy import or_ from .database import get_db from .models import Client, User from .schemas import ClientOut, PageResult router = APIRouter() @router.get("/clients", response_model=PageResult[ClientOut]) def list_clients( keyword: str = Query("", max_length=120), owner_id: int = Query(None), level: str = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): query = db.query(Client) if current_user.role == "member": query = query.filter(Client.owner_id == current_user.id) if keyword: query = query.filter( or_(Client.name.ilike(f"%{keyword}%"), Client.industry.ilike(f"%{keyword}%")) ) if owner_id is not None: query = query.filter(Client.owner_id == owner_id) if level: query = query.filter(Client.level == level) total = query.count() items = (query.order_by(Client.updated_at.desc()) .offset((page - 1) * page_size) .limit(page_size) .all()) return {"total": total, "items": items}权限控制这里有个容易忽略的坑:如果普通成员试图通过传 owner_id 参数来查看别人的客户,后端必须做拦截。具体做法是,当 current_user.role 是 member 时,强制把 owner_id 设置为当前用户 id,忽略前端传入的参数。
分页参数也要限制上限。page_size 最大 100,防止有人一次拉取全量数据把数据库打爆。虽然内部系统一般不会有人恶意调用,但该做的防护还是要做,不然以后对接更多渠道时可能会踩坑。
4.4 前端实现要点:客户列表、详情页、快速跟进弹窗
前端页面的核心交互集中在三个地方:客户列表页、客户详情页、快速跟进弹窗。
客户列表页采用经典"左侧筛选 + 右侧表格"布局。左侧放来源、等级、归属人三个筛选项,右侧表格展示客户名称、行业、来源、等级、归属销售、最近跟进时间。表格整行可以点击进入详情页。最近跟进时间这列很关键,因为业务员判断一个客户是否"凉了",第一眼就是看这个时间。
客户详情页是一个上下布局:上半部分是客户基本信息卡片和扩展字段,下半部分是一个时间线,按照时间倒序展示所有跟进记录。时间线的每一条记录显示跟进方式、跟进内容、创建时间、下次跟进时间。为什么用时间线而不是普通列表?因为时间线能更直观地展示一段关系的推进过程,业务员看到一条条的跟进记录,会更容易回忆起来这个客户之前聊到哪了。
快速跟进弹窗是用一个全局组件实现的,通过 Pinia 管理弹窗的开关状态。无论是列表页还是详情页,都可以通过快捷键调出来。弹窗里包含三个字段:选择客户、跟进内容、下次跟进时间。选择客户用了远程搜索组件,输入名称后从后端拉取候选列表,而不是把全部客户拉到前端,避免大数据量下页面卡顿。
4.5 部署上线:systemd 托管后端, Nginx 托管前端静态资源
部署方式我们采用的是最传统的方案,但在生产环境中非常稳定。后端用 uvicorn 启动,绑定 127.0.0.1:8000 端口,由 Nginx 反向代理到 80 端口。前端构建后的 dist 目录直接放在 Nginx 的根目录下。
systemd 服务文件 deskcomm.service 内容大致如下:
[Unit] Description=DeskcommCRM Backend After=network.target postgresql.service [Service] User=deskcomm Group=deskcomm WorkingDirectory=/home/deskcomm/backend Environment="PATH=/home/deskcomm/backend/venv/bin" ExecStart=/home/deskcomm/backend/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 Restart=always RestartSec=3 [Install] WantedBy=multi-user.targetNginx 配置的核心是反向代理和静态文件服务,因为前端路由用了 Vue Router 的 history 模式,所以还需要把所有非静态文件的请求都 rewrite 到 index.html,否则直接刷新子页面会 404。
启动命令没什么特别的:systemctl daemon-reload && systemctl enable deskcomm && systemctl start deskcomm。上线之前一定要做一遍冒烟测试,把客户创建、跟进、工单创建、看板查询这几条主链路全部走一遍,确认没问题再让业务员开始录入数据。
4.6 数据迁移与初始数据导入:如何把 Excel 里的客户搬进系统
上线最大的阻力永远是"老数据迁移"。我们当时有几个同事在 Excel 里攒了两三百个客户,如果让他们手动录入,他们肯定不乐意。所以我写了一个一次性脚本,读取 Excel 文件,按用户填写的列名映射到数据库字段,批量导入客户和联系人。
脚本的核心是一个映射配置,示例代码如下:
import pandas as pd from sqlalchemy.orm import Session from .models import Client, Contact COLUMN_MAP = { "客户名称": "name", "行业": "industry", "来源": "source", "客户等级": "level", "联系人": "contact_name", "手机号": "phone", } def import_excel(path, owner_id, db: Session): df = pd.read_excel(path) for _, row in df.iterrows(): client = Client(name=row.get("客户名称"), industry=row.get("行业"), source=row.get("来源"), level=row.get("客户等级", "D"), owner_id=owner_id) db.add(client) db.flush() if pd.notna(row.get("联系人")): db.add(Contact(client_id=client.id, name=row.get("联系人"), phone=str(row.get("手机号", "")))) db.commit()实际执行时要做好幂等控制,比如检查客户名称是否已存在,避免重复导入。建议先在一个空的测试库里跑一遍,检查数据质量,再正式导入。导入完成之后,让业务员核对一遍自己名下的客户清单,把明显错误的数据标记出来,后台统一修改。
5. 上线后的使用效果与迭代方向
5.1 真实使用数据对比:跟进响应时间明显改善
系统上线第一周,主要的任务是让业务员养成"每天打开系统"的习惯。我们没有强制规定,但每次例会都会同步数据,告诉他们哪些客户快超时了。第二周开始,日常打开率稳定在 80% 左右,已经算不错了,毕竟这个系统不是拿来看新闻的,是拿来干活的。
对比上线前后的数据,最明显的变化是"客户平均响应时效"。以前客户消息发过来,业务员可能要半天后才看到,现在因为有今日待跟进列表和工作台提醒,当天响应率达到 90%。另一个指标是"跟进超时客户占比",从原来的每月 30% 下降到 10% 左右,效果立竿见影。
数据的提升不是因为系统本身有多智能,而是因为业务流程被强制固化了下来。以前靠自觉,现在系统每天告诉你该联系谁、上次聊到什么、有什么待办,人的懒惰倾向被工具拉了一把。
5.2 团队反馈与后续迭代计划
客观来说,业务员对系统的态度经历了三个阶段:一开始抵触,觉得多了一个填表的地方;后来因为"今日待跟进"确实帮他们记住了该干的活,开始接受;再后来,主管通过看板能实时掌握团队动态,整体的管理颗粒度提升了。
当前的不足也很多。最大的问题是没有移动端,业务员在外面拜访客户的时候,临时打开手机浏览器访问 PC 端页面,体验比较差。我们已经在计划做一个轻量的移动端 H5,只保留客户查阅、快速跟进、今日待办这三个核心入口。另一个方向是接入企业微信通知,把跟进提醒和工单升级直接推到员工的企业微信上,减少打开系统的频率。
还有一件事值得做:把客户来源和成交数据的分析做得更细一点。目前只有简单的汇总,后续可以按月份、按渠道、按销售做交叉分析,帮助管理层判断哪类客户最值得投入精力。
6. 常见问题与排查技巧实录
6.1 问题速查表
| 现象 | 原因 | 排查与解决 |
|---|---|---|
| 前端刷新某个子路径 404 | Nginx 没有配置 history 模式 fallback | 在 location / 里加上 try_files $uri $uri/ /index.html |
| "今日待跟进"页签打开很慢 | 缺少 next_follow_at 索引 | 执行 create index 语句,并确认索引名 |
| 导入 Excel 后部分客户多了一条空联系人 | Excel 中"联系人"列是不是没有值,但脚本仍执行了插入 | 导入前过滤 pd.notna(联系人) 再创建 |
| 工单 24 小时后没有自动升级 | 定时任务没有执行或时区不对 | 检查 cron 服务,确认系统时区为 Asia/Shanghai |
| 普通成员能通过接口看到别人的客户 | 后端接口没有按角色强制过滤 | 在查询逻辑里,member 角色强制 owner_id = 当前用户 |
6.2 避坑心得:权限粒度、标签体系、通知渠道
关于权限,最稳妥的做法是"后端强制 + 前端隐藏"。前端可以按角色控制菜单和按钮显隐,但真正能防止越权的必须靠后端。每次新增一个查询接口,都要检查一遍是否带上了当前用户的数据范围过滤条件,这是一个很容易漏掉但又特别重要的环节。
关于标签体系,不要一开始就把标签类别设计得太细。我们的经验是先允许业务员自由创建标签,跑两个月后再根据数据清洗出一套高频标签,把它固化成下拉选项。一上来就定死一套标签体系,往往和实际业务对不上,后面改起来很麻烦。
关于通知渠道,站内通知最省事,但触达率最低。如果团队有企业微信或钉钉,强烈建议优先接入 webhook 通知。我们的工单升级提醒后来接入了企业微信群机器人,响应速度提升明显。别嫌接入麻烦,这可能是让你系统"被真正用起来"的最关键一步。
6.3 给想自研 CRM 的朋友三点建议
第一,别一上来就做全功能。管好客户、管好跟进、管好提醒,这三角已经能解决大多数问题了。其他功能等业务方真的提出明确要求、并且你能回答清楚"这个功能为什么能帮业务员更快成单"的时候,再做也不迟。
第二,数据质量比功能更重要。功能再强,录进去的数据是脏的,后面的统计和提醒全都是错的。所以在系统里一定要有数据校验、去重提示和定期的数据清洗机制。宁可少记,不可乱记。
第三,培养使用习惯需要管理者持续推动。系统只是工具,真正让大家每天都用起来,需要团队负责人带头看数据、用数据说话。我见过很多 CRM 项目最失败的不是技术,而是团队根本没形成"数据化管理和跟进"的习惯,最终再好的系统也会变成僵尸系统。
最后再分享一点我个人的感受。做 DeskcommCRM 这个项目最深的体会是:很多时候我们觉得业务复杂,其实是因为没有把流程拆到底。真正把客户管理这件事拆成"建档、跟进、提醒、复盘"四个动作之后,系统的边界就非常清晰了。这两三周跑下来,团队的客户遗忘率降了大半,周例会的数据讨论也从"凭感觉"变成了"看报表",这种变化比任何技术指标都让我觉得这个项目做得值。