1. 项目定位与整体设计思路
DeskcommCRM 这个名字拆开看很有意思:Desk 代表桌面坐席,Comm 代表通信,CRM 则是客户关系管理。说白了,这不是一个传统意义上的“纯 CRM”,而是一个把沟通入口和客户数据放进同一个工作台的融合型系统。当时做这个项目的直接动因,是团队里客服和销售用的工具太割裂了——客户档案在 CRM 里,通话记录在呼叫中心系统里,线上咨询的聊天记录又散落在 IM 工具里。客户从电话进线聊到一半,坐席要切三四个窗口才能把信息凑齐,效率低不说,还经常出现“上一个坐席答应客户的事,下一个坐席完全不知道”的情况。
DeskcommCRM 要解决的,就是这一整条线的问题:电话、在线消息、邮件、工单、客户档案、跟进计划,全部收拢到一个界面里。坐席接起一通电话的瞬间,右边自动弹出这个客户的历史记录、会员等级、未完成工单;客户在网页上发起咨询,系统自动识别他的身份,把上一次会话的上下文带出来。这个项目适合谁参考?一类是有自建客户服务或销售管理系统需求的团队,另一类是想把呼叫中心和 CRM 打通的开发者。如果只是需要一套标准 SaaS CRM,那直接买现成的可能更划算,但如果你需要深度定制、数据完全私有化,或者需要跟内部业务系统对接,这套自建思路就有很强的参考价值。
1.1 项目背景与核心需求拆解
先说说当时为什么没有直接采购市面上的成熟产品。其实方案评审会上,我们对比过三四家,最终劝退的原因主要有三个:第一,通信能力深度绑定问题,市面上大部分 CRM 自带的是标准呼叫中心接口,但我们的业务涉及大量外呼任务、号码隐藏、录音质检,很多定制需求在标准产品里根本做不了;第二,客户数据的归属规则非常特殊,我们的客户按区域、按等级、按来源分属不同团队,坐席权限要精细到“只能看到自己负责的客户”,很多 SaaS 产品的权限模型做不到这个粒度;第三,数据必须私有化部署,客户信息和通话记录属于核心资产,老板明确要求全部数据必须放在自己机房里。
基于这些约束,我们把核心需求拆成了四条:
- 统一客户视图:只管客户,不管客户从哪个渠道来,电话、网页、小程序进来的联系记录都能自动挂到同一份客户档案下。
- 通信能力内嵌:至少要做到软电话(WebRTC 拨号、接听、转接、静音)和在线聊天(网页 IM)两种通信方式的深度集成,通话录音和聊天记录自动归档。
- 精细化权限与归属:数据归属规则可配置,支持按坐席、按团队、按数据范围控制可见性,客户转移要有完整的操作审计。
- 可量化运营数据:每个坐席的接听量、会话量、响应时长、工单解决率,管理层能看到实时看板,坐席自己也能看到个人绩效。
需求定下来之后,架构选型就变得很明确。技术栈最终确定为:前端 Vue 3 + Element Plus,后端 Java Spring Boot(单体应用但模块化包结构),数据库 MySQL 8.0 存业务数据,Redis 存会话状态和路由信息,通信用 WebSocket 做消息推送,呼叫这块采用接入 SIP 软交换的方案,前端通过 WebRTC 拨打电话。没有一开始就上微服务,原因是团队规模不大,业务量预估在千级坐席以内,单体架构加上合理的模块拆分,开发和运维成本都要小得多,出了问题也更好排查。
1.2 功能模块划分与边界
整体功能模块按“核心流程”和“支撑能力”两个维度来划分。核心流程模块负责打通业务闭环,支撑能力模块负责提供通用服务。
| 模块 | 核心功能 | 技术要点 |
|---|---|---|
| 客户管理 | 客户档案、联系人、标签、等级、归属关系 | 客户主数据模型设计 |
| 沟通中心 | 软电话、在线 IM、消息记录、通话录音 | WebSocket 实时推送、SIP 集成 |
| 工单中心 | 工单流转、SLA 计时、分配策略 | 状态机建模 |
| 坐席工作台 | 待办列表、会话面板、快捷回复、知识库 | 前端交互性能优化 |
| 数据看板 | 坐席绩效、客户转化、服务时效 | 聚合统计 SQL |
| 系统管理 | 组织架构、角色权限、数据字典 | RBAC 权限模型 |
划清模块边界有一个很实际的好处:排期可以并行。客户管理和系统管理是地基,先做;沟通中心是整个系统最复杂的部分,投入最多的人力;坐席工作台和数据看板依赖前面几个模块的数据,放到第二期。当时我们用了一个最朴素的排期方式——先画出核心流程的端到端链路图,也就是“客户来电 -> 系统识别客户 -> 坐席接听 -> 记录跟进 -> 创建工单 -> 客户回访”,然后按这条链路来安排开发顺序,保证每一步的前置数据都是齐的。
2. 核心模块设计与关键技术点
这个部分聊聊系统里最核心的几个模块是怎么设计的,以及设计背后的考量。我挑几个当时花时间最多、踩坑最多的点来讲,包括客户模型怎么建、通信怎么集成、工作台交互怎么设计,以及权限模型怎么落地。
2.1 客户模型的建立:这不是建一张客户表那么简单
很多人在设计 CRM 数据库时,第一条就把客户表建出来了,这没错,但远远不够。客户模型最核心的问题不是“客户字段怎么定”,而是“一份客户的完整视图靠哪些表支撑”。如果只建一张客户表,你会发现后续接入通话记录、聊天记录、跟进记录、工单时,要么不停的加字段,要么拆出五六张关联表但互相之间没有统一的外键关系,查询变得越来越别扭。
我们最终落地的客户模型分为四层:
- 第一层是客户主表(customer):存放客户的基本属性和核心维度,比如客户名称、行业、来源渠道、等级、当前阶段(线索/潜在/成交/流失)。
- 第二层是联系人表(contact):一个客户下可能有多个联系人,每个联系人负责不同的事务,联系人有自己独立的电话和邮箱。
- 第三层是关联域表(customer_rel):主要用来描述客户与坐席、客户与团队的归属关系,以及客户与客户的关联关系(比如母子公司、上下游供应商)。
- 第四层是行为数据表:通话记录(call_log)、聊天记录(chat_message)、跟进记录(follow_up)、工单(work_order)都属于行为数据,统一通过 customer_id 或 contact_id 关联到客户主数据上。
这样的分层设计带来一个直接的好处——行为数据的“挂在客户下”还是“挂在联系人下”是可以动态调整的。比如一个来电号码匹配到了某个联系人,那这条通话记录既挂联系人,也通过联系人的 customer_id 关联到客户主档,坐席打开客户视图时一次查询就能拉出所有行为数据。
一个要注意的细节是客户去重。实际业务中同一个客户可能通过不同号码、不同邮箱反复进线,如果不做识别合并,客户视图就会散成两三份。我们采用了一个折中方案:在客户表中增加 unified_key 字段,存一个经过规则合并的标识,比如“手机号可识别就先用手机号,识别不了就用邮箱前缀,再不行就 fallback 到客户名”。接听电话时优先按号码查 unified_key,命中就直接弹出客户档案。这个方案实现成本低,但确实能挡住大部分重复建档问题,比上复杂的清洗算法实在得多。
2.2 通信集成:把电话和数据放进同一个界面
通信集成是整个 DeskcommCRM 里最硬的一块,也是区分“有通信模块的 CRM”和“真正可用的通信融合工作台”的分水岭。纯 CRM 加一个“呼叫记录”按钮很简单,难的是坐席在网页里点击拨号、通话状态实时刷新、通话结束录音自动归档,整个过程不能依赖座机,也不能每次都跳转到第三方话务台。
我们在呼叫链路上的做法是:前端嵌入 WebRTC 软电话(基于 SIP.js 实现),通过 WebSocket 与信令服务器通信,媒体流走 WebRTC;后端接 SIP 软交换(用的是 Asterisk 兼容方案),呼入呼出都统一走 SIP 线路,同时事件通道(AMI)把来电、振铃、接听、挂断这些状态实时推送给业务后端,业务后端再通过 WebSocket 推送到对应坐席的工作台界面。
这里有一个非常关键的设计:通话状态机。很多人以为通话只是“接通/挂断”两个状态,但做通信集成时必须细化到振铃、接通、保持、转接、外呼拨出、无人接听、拒接等十几个状态。我们在代码里维护了一个严格的状态机,只允许特定状态之间流转,比如“拨号中”只能到“振铃中”,不能直接跳“已接通”。这个状态机的存在,避免了一堆并发情况下出现“坐席已经挂断但界面还显示通话中”这种低级却极难排查的 Bug。
消息集成这块,用的是 WebSocket 加消息队列的方式。前端与服务端建立长连接后,新消息通过 Redis 的 Pub/Sub 广播到坐席所在的节点,再推送到浏览器。消息落库是异步的,通过消息队列削峰,避免大量并发消息阻塞主流程。聊天消息的正文、附件、消息类型(文本/图片/文件/系统消息)分开存储,消息表只存通用索引,这样后续如果要接机器人或者做自动分类,不需要动表结构。
一个实际测试中发现的细节:消息顺序和会话上下文强相关。客户发消息时,服务端必须先把“这个客户到底属于哪个会话”算好,再做消息路由。如果每次都重新分配坐席,就会出现同一客户在短时间内被转给不同坐席的尴尬情况。我们的做法是给会话维护一个亲和性:会话创建时绑定一个坐席,只有当坐席主动转接、离线或会话超过 N 分钟未响应时才允许重新分配。这就是常说的会话亲和性策略,在消息系统里比“谁空闲分给谁”更容易保证服务体验。
2.3 坐席工作台的交互设计细节
很多团队做系统时只关注功能能不能跑通,不关注坐席每天要在这个界面里花多少小时。坐席工作台如果用起来别扭,再强的功能落地也是打折的。DeskcommCRM 的工作台界面采用经典的三栏布局:左侧是待办客户列表,中间是会话/通话面板,右侧是客户详情与工单区。
左栏列表要解决的是“优先级引导”问题。纯按时间排序会出问题——一个昨天来过、今天又发消息的客户,跟一个一个月没联系的沉睡客户排在同样的位置,坐席很容易漏掉真正需要马上响应的会话。我们的做法是动态计算“需处理度”,综合会话状态、最近消息时间、工单紧急程度、客户等级几个维度打分,然后按分数降序排列,让坐席一眼就看出先处理谁。
中栏的会话面板是所有操作的焦点。聊天式的通话记录展示听起来简单,但真做起来有几个反直觉的坑。比如来电记录要不要跟聊天记录混排?电话是语音,不是文字,混排后中间会多出一堆“本通电话 3 分 12 秒”的条目,视觉上非常割裂。我们最后的处理是做了一个时间轴类型的切换按钮:默认按会话聚合展示,点击某条通话记录时展开该通电话的详情,而不是把所有记录平铺在一个消息流里。
右栏的客户信息区,最有价值的是“客户 360° 快照”。快照不是把客户表所有字段都塞出来,而是聚合了最有决策价值的信息:客户等级和阶段、最近一次跟进记录、待处理工单数、最近 30 天的通话和消息数、未完成事项提醒。坐席接通电话后扫一眼快照,基本就能判断这是新客户要建立信任,还是老客户要解决问题,还是某个工单到期需要催办。快照数据全部通过一个聚合接口返回,前端只调用一次,避免大量散接口的多次请求导致页面卡顿。
2.4 权限模型:数据归属和可见范围怎么设计
权限设计是整个系统里被质疑最多的部分。团队管理层要求“每个坐席只能看到自己负责的客户”,但同时又要求“团队主管能看到团队内所有客户”,而公司的超管还要能看到全部。如果只做角色权限(比如坐席、主管、管理员三级),数据可见范围根本控制不住。
我们采用的方案是“角色 + 数据范围”的双层控制。角色决定能做什么操作(比如新建工单、删除客户、导出数据),数据范围决定能看到哪些数据(本人、本团队、全部)。数据范围不是角色上直接配置的,而是挂在组织架构上:坐席默认继承所属团队的数据权限,主管可以额外配置跨越多个团队的数据范围。查询层通过一个统一的权限过滤器实现——所有查询客户、工单、通话记录的 SQL 都必须带上当前用户可访问的数据范围条件,这个条件由后端根据用户的组织归属和角色规则动态拼接,前端根本不参与权限判断。
这个设计踩了一个很深刻的坑:权限规则放在前端会直接被绕过。最初版本我们把权限判断放在前端路由和按钮显隐上,数据接口只做了简单的登录校验。结果测试人员用普通坐席账号直接调接口,把全量客户数据给拉出来了,幸好只是内测阶段发现。后来所有权限下沉到后端,接口请求先经过权限过滤器,再做业务处理,才把这个漏洞堵死。这件事也成了团队“安全底线”的教科书案例,后面所有新增接口都必须走同一套权限校验逻辑。
3. 实操过程:从数据库到功能实现
这段我把实现过程中几个关键环节的具体做法和参数写下来,尽量给出可以直接复用的方案。数据库表结构怎么设计、核心接口的流程怎么组织、消息怎么保证不重复不丢失、统计看板的数据口径怎么定义,这些每一条都是实际调试过的,不是纸上谈兵。
3.1 数据库设计与初始化
先看客户主表和行为表的核心结构设计。当时用的是 MySQL 8.0,字符集统一 utf8mb4,InnoDB 引擎。客户主表字段比较多,这里摘几个比较关键的:
CREATE TABLE `customer` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '客户ID', `unified_key` varchar(64) NOT NULL COMMENT '统一识别key,用于匹配客户', `name` varchar(128) NOT NULL DEFAULT '' COMMENT '客户名称', `industry` varchar(32) DEFAULT '' COMMENT '行业', `level` tinyint NOT NULL DEFAULT '1' COMMENT '客户等级 1-5', `stage` varchar(16) NOT NULL DEFAULT 'LEADS' COMMENT '阶段:LEADS/OPPORTUNITY/DEAL/CHURN', `source` varchar(32) DEFAULT '' COMMENT '来源渠道', `owner_id` bigint DEFAULT NULL COMMENT '当前归属坐席ID', `owner_team_id` bigint DEFAULT NULL COMMENT '归属团队ID', `next_follow_at` datetime DEFAULT NULL COMMENT '下次跟进时间', `remark` text COMMENT '备注', `deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除 0正常 1删除', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_unified_key` (`unified_key`), KEY `idx_owner_id` (`owner_id`), KEY `idx_owner_team_id` (`owner_team_id`), KEY `idx_next_follow_at` (`next_follow_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户主表';两个细节值得说明。一是 unified_key 唯一索引,这个字段是整个系统找客户的关键,客户去重、来电识别、消息路由都靠它,所以必须唯一。二是 next_follow_at 的索引,坐席工作台左栏的“待办列表”是按这个字段排序查数据库的,没有索引的话客户量一上来查询会非常慢。
行为数据表中,通话记录和消息记录在设计上有一个共通点,必须带上精确的时间戳和来源渠道标识,否则后面做数据看板时会发现压根没法统计“哪个渠道的客户量最多”“哪条线路的通话时长最长”。通话记录的关键字段包括:主叫号码、被叫号码、通话开始时间、应答时间、结束时间、通话状态、录音文件地址、关联的 customer_id、处理坐席 ID。消息表的关键字段包括:会话 ID、发送者类型(客户/坐席/系统)、消息方向、消息类型、正文内容、时间戳。
字段都定好后,建表时记得加索引。坐席工作台查询频率最高的是“按坐席查待办”和“按客户查行为”,所以 owner_id + next_follow_at 的联合索引、customer_id + created_at 的联合索引,这两类索引必须有。我们后期专门排查过一次慢查询,发现 90% 以上的慢 SQL 都集中在缺索引的表上,加完索引后整体响应时间从几百毫秒降到了几十毫秒。
3.2 后端核心流程实现
后端用的 Spring Boot 单体应用,按模块分包。我拿“创建跟进记录”和“消息接收落库”两个典型流程来说说实现时容易踩到的细节。
创建跟进记录这个接口的完整流程是:坐席在工作台勾选客户 -> 填写跟进内容 -> 选择跟进方式(电话/消息/上门/其他)-> 选择跟进结果(有效沟通/未接通/已预约下次/已成交)-> 设置下次跟进时间 -> 提交。后端在保存跟进记录的同时,还要做两件事:更新客户主表的 next_follow_at 字段,保证工作台的待办列表刷新;写入操作审计日志,记录谁在什么时间对哪个客户做了什么样的跟进。这两步操作必须放在同一个事务里,否则会出现“跟进记录已经保存了,但待办列表里客户还是排在原来的时间点上”这种数据不一致问题。
消息接收落库这个流程是防止消息丢失的关键。客户在网页上发出一条消息,消息先到服务端接口,服务端生成一条带全局唯一消息 ID 的记录落库,再通过 Redis Pub/Sub 推送到坐席工作台的 WebSocket 连接。这时会出现一个并发场景:坐席界面可能会因为网络抖动重复收到同一条消息,如果前端不做幂等处理,界面就会闪出两条一模一样的消息。我们的解决方案是:客户端在维护一个本地消息 ID 的 Set,收到新消息时先检查 ID 是否已存在,存在就丢弃。服务端也做了同样的幂等校验,消息表对 message_id 加了唯一索引,重复的落库请求直接报错跳过。
坐席分配策略在后端用策略模式实现。分配时机有两个:客户主动呼入或发起在线咨询时(进入型分配),以及坐席主动领取客户时(主动型分配)。进入型分配不是一个简单的“找空闲坐席”,我们实际跑了三种策略,最后选择的方案是“最少活跃会话数优先,同数量时按坐席空闲时长排队”。这个方案的好处是每个坐席的工作量相对均衡,不会出现有的坐席堆积了几十个未响应会话,有的坐席闲了十分钟的极端情况。分配完成后,立即把客户数据写入该坐席的 Redis 待办队列,并设置过期时间,防止坐席长时间未响应导致客户进线被搁置。
3.3 数据统计与看板实现
数据看板是一个容易被低估的工作量。表面上就是查几个 count、sum,但真正难的是数据口径的统一。团队开会讨论“响应时长”这个指标时,产品经理说“客户发消息到坐席回复的间隔”,业务主管说“应该是来电振铃到坐席接起来的时长”,两个口径得出的数字可能差距巨大。所以看板开发第一步不是写 SQL,而是把所有指标口径用文字定义清楚,再让开发照着实现。
最终定的几个核心指标口径:
- 平均响应时长:客户发起会话(来电或发消息)到坐席首次回复/接听的间隔,按日聚合取平均值。
- 会话量:每天进入系统的会话总数,包含电话、在线消息、邮件。
- 工单解决率:已解决工单数 ÷ 已关闭工单数,按日期统计关闭时间。
- 客户转化率:从线索阶段推进到成交阶段的客户数 ÷ 线索阶段客户总数。
这些指标在实现上用到聚合 SQL,比如按日统计响应时长的核心 SQL 大概是:
SELECT DATE(created_at) AS stat_date, AVG(TIMESTAMPDIFF(SECOND, created_at, first_reply_at)) AS avg_response_seconds, COUNT(DISTINCT conversation_id) AS total_conversations FROM conversation WHERE created_at >= '2025-01-01' GROUP BY DATE(created_at) ORDER BY stat_date;这里的 first_reply_at 字段是会话表中专门加的,记录坐席首次回复的时间,比从消息表里二次统计高效得多。所有看板聚合查询都是在凌晨用定时任务跑批,结果存进统计汇总表,前端页面直接查汇总表,避免每次打开看板都全量扫描明细表导致卡死。
4. 常见问题与排查技巧实录
这部分把我在开发和上线初期遇到的典型问题整理成一个速查表,每个问题都附上排查思路和最终解决方案。这些问题非常有代表性,很多自建系统踩的坑都大同小异,写出来希望能帮大家节省排查的时间。
| 问题现象 | 根因分析 | 解决方案 |
|---|---|---|
| 消息偶发乱序 | WebSocket 推送与消息确认机制不完善 | 增加客户端 seq 序号校验,乱序时请求服务端补拉 |
| 同一客户短时间内被分给不同坐席 | 会话亲和性策略未生效 | 根据来源渠道和客户统一标识绑定会话坐席 |
| 坐席转交客户后,原坐席仍能看到客户 | 权限过滤条件未更新 | 转移客户时同步更新 owner_id,并清理旧坐席的 Redis 待办 |
| 接口请求慢,数据库 CPU 飙升 | 大量列表查询未走索引,深分页 | 增加联合索引,列表查询改用游标分页 |
| 通话状态与界面不一致 | 状态机流转边界未完全约束 | 补充状态机校验,非法流转直接拒绝并记录日志 |
第一个问题值得单独展开讲讲。消息乱序的根因不在于消息丢失,而在于网络传输的到达顺序不确定。客户发的两条消息,第一条经验证落库后再推,第二条因为网络抖动先到了浏览器,界面就会先显示第二条再显示第一条。我们最终在每条消息上加了 seq 序号(同会话内递增),前端收到消息后先检查 seq 是否连续,如果发现跳号,说明中间有消息延迟到达,前端会主动调用一个补齐接口,从服务端拉取缺失的消息重新排序展示。这个机制上线后,测试反复制造弱网环境,消息乱序问题基本归零。
第二个问题是我们上线初期投诉最多的。客户先从官网点击咨询,被 A 坐席接待,后来客户挂断后再次来电,系统却把电话分配给了 B 坐席,客户需要重新描述一遍问题,体验很差。排查发现,消息会话和电话会话各有一套分配逻辑,并没有统一检查“这个客户最近是否已经有绑定坐席”。修复方式是增加一个统一的会话亲和性校验:进入分配流程前,先用客户的 unified_key 查最近 4 小时内是否有未关闭会话,有就直接复用绑定坐席,没有才进入新会话分配。修改后该场景投诉率下降了 90% 以上。
第三个问题属于数据权限和归属一致性问题。客户转移的场景下,如果只更新了客户主表的 owner_id,而没有同步变更 Redis 待办队列和操作审计日志,旧坐席的工作台列表里会残留这个客户的信息,点进去还能看到全部数据。我们的解决思路是:把客户转移做成一个完整的事务操作,不只是改一条数据,而是同步更新客户主档、清理旧坐席待办、推送转移通知消息给新旧坐席,并写入完整的转移审计日志。这样处理之后,数据归属不一致的问题基本消失了。
其他两个问题也是典型。深分页的问题,经典场景是工作台客户列表翻到几百页时,传统的 limit offset 会越来越慢,因为数据库要扫描和跳过前面所有的行。我们把所有列表查询改成了游标分页(用 id > 上一页最大 id 的方式),查询性能立即得到改善。通话状态不一致的问题,排查下来发现是没有对状态机的所有边界做约束,比如坐席在“振铃中”就直接挂断了,代码里没定义这个流转路径。补上状态机校验后,这类情况会直接拒绝流转并记录日志,问题复现概率降到了可忽略的水平。
5. 项目复盘与可扩展方向
项目上线稳定运行之后,回过头来看整个开发过程,有几个经验是值得沉淀下来的。
第一,通信型 CRM 和传统 CRM 最大的差异在于“实时性”要求。传统 CRM 的多数操作是表单提交、列表查询,慢半秒用户基本无感,但通信场景里,电话进来、消息进来,如果界面上 1 秒内没有反馈,坐席就会觉得系统卡,客户体验也会直接受影响。所以在架构设计时一定要考虑好实时链路,从信令接入、消息路由、WebSocket 推送到前端渲染,整条链路都要提前做压力测试。我们当时犯过的错误是只在功能联调时测了单路通话,没有模拟高峰期的并发进线,结果上线第一周就出现过消息延迟推送的情况,后来加上了消息队列和连接池扩容才解决。
第二,数据归属规则一定要在设计初期就和业务方达成一致,否则越到后期越难改。客户归属、会话归属这两个问题,看似简单,但业务方往往有非常多的“特殊情况”,比如“大客户要归给主管”“某个区域的客户只能由本区域的坐席跟进”“老客户回访时必须找原来的销售”。这些规则如果不提前梳理进权限模型和分配策略,后面实现的时候会反复返工。
第三,统计指标的口径要早定。哪怕是“响应时长”这种看起来很标准的指标,团队内部也会有不同的理解。建议在项目启动时,就组织业务方和数据开发坐在一起,把所有需要上报表的指标一个一个过一遍,把口径写成文档,作为开发的依据。宁可多花一两天在口径对齐上,也不要等看板上线了再让业务方说“这个数据不对”。
关于扩展方向,我实际在计划中的有三块。第一块是智能化跟进提醒,目前待办列表是基于 next_follow_at 的静态提醒,后续想加入规则引擎,比如“客户超过 7 天未跟进且等级为 4 级以上自动预警”。第二块是消息内容的高级检索,现在只能按客户名和电话搜,后续打算引入全文索引,支持按聊天正文内容搜索历史记录。第三块是移动端兼容,虽然工作台是给坐席在电脑上用的,但主管希望在手机上也能看到实时看板和工单审批,这块涉及一些接口适配和组件库选型,优先级排在相对靠后的位置。
最后分享一个我个人的体会:做一个系统,功能清单再长也只是骨架,真正让系统好用的,是那些不起眼的小细节——坐席接起电话后客户信息 0.5 秒内弹出来、切换客户时不用等接口转圈、转接工单时上下文完整带过去。这些小细节每一个单独看工作量都不大,但串起来就是整套系统体验的分水岭。DeskcommCRM 能做到这个程度,不是某一个模块做得有多炫,而是所有环节的“顺手程度”都在及格线以上。对于后来者,我的建议是:不要急着堆功能,先把一个客户从进线到解决问题的全流程走通,走顺了,再往上面加花活。