简介:这份PDF是蜂网供应链管理(上海)有限公司于2017年8月发布的《客户关系管理CRM_客户管理_软件需求说明书_V1.0》,面向产品经理、需求分析师及CRM系统开发测试人员,用于指导客户管理模块从需求定义到落地实施的全过程。文档共54页,围绕项目介绍、方案概述、业务流程、系统用例、功能列表与基本流程等章节展开,涵盖客户档案、交互历史、销售漏斗等核心概念,并涉及客户信息录入、联系人跟踪、销售预测、市场营销自动化、客户服务及报表分析等模块的需求描述,同时包含平台政策、竞品分析、技术要求与变更控制等内容。资源包为单一PDF文件,大小约3.11MB,结构完整、目录清晰,便于按章节检索与引用。目前已有312人学习下载,适合需要撰写CRM需求文档、梳理客户管理业务流程或搭建系统原型的产品与研发人员参考借鉴。
1. 从一份 54 页的 CRM 客户管理 PRD 说起:它到底能解决什么
手里这份《客户关系管理CRM-客户管理-PRD-V1.0-20170825.pdf》,54 页,正式发布状态,编写日期 2017 年 8 月 25 日。它不是代码包,不是可运行系统,而是一份完整的软件需求说明书——把「客户管理」这个模块从业务定义、界面交互、字段规则到权限审批,一层层拆到了可开发的程度。很多人拿到 PRD 第一反应是「这不就是产品经理写的文档吗」,但真正做过 CRM 交付的都知道,一份字段级颗粒度的 PRD 能省掉后期至少三轮需求对齐会。它适合三类人:要落地 CRM 客户模块的后端开发、需要理解字段流转的产品新人、以及做 CRM 选型时想看清底层数据模型的实施顾问。这份文档的核心价值在于,它把「客户」这个实体从列表页到详情页、从新增到合并、从导入到查重,全部用数据项表格和交互说明固定了下来。
2. 客户列表页拆解:查询方案、高级查询与字段可编辑性
2.1 列表页的默认行为与查询方案设计
客户列表是整个客户管理模块的入口。文档里写得很清楚:列表默认按创建时间倒序排列,数据默认为查询方案的「全部客户」。这里有个容易被忽略的设计——查询方案不是简单的筛选器,而是一组预置的业务视图。文档定义了五个预置方案:全部客户(所属组织=我所在组织)、我负责的客户(负责人=我本人)、我参与的客户(团队成员包含我且负责人不等于我)、我关注的客户、七日未跟进客户(未跟进天数>7天)。
这五个方案覆盖了销售日常最高频的查看诉求。开发时如果只做一个「全部客户」列表加几个筛选框,销售就得每次手动组合条件,效率差很多。预置查询方案的本质是把业务语义固化到查询层,减少重复操作。
列表显示列支持自定义,但客户名称是必选字段,不可取消。这个约束合理——客户名称是行的业务主键,去掉后列表就失去了识别度。默认字段包括客户名称、上级客户、行业、公司人数、电话、邮政编码、传真、省市区、地址、业务类型、客户级别、工商注册、备注、负责人、所属部门、所属组织、成交状态,以及创建人、创建时间、最后修改人、最后修改时间、最新活动记录时间等日志字段。
鼠标悬停在某字段上会出现编辑按钮,点击后可直接编辑当前字段,失去焦点即保存。这个交互叫「行内编辑」,实现时要注意两点:一是并发冲突,A 用户编辑时 B 用户也在编辑同一行,需要加乐观锁;二是字段级权限,不是所有字段都允许行内编辑,比如「工商注册」是系统判定字段,不可编辑。
2.2 高级查询的条件构造与实现要点
高级查询支持客户表字段(含自定义字段),同时需要增加团队成员、是否关注等关联条件。这意味着查询构造器不能只查主表,还要能 join 关联表。
常见做法是后端提供一个查询条件描述结构,前端把用户选择的条件序列化成 JSON 传给后端,后端解析后动态拼 SQL 或 QueryWrapper。下面是一个简化的条件结构示例:
{ "logic": "AND", "conditions": [ { "field": "customer_name", "operator": "LIKE", "value": "%科技%" }, { "field": "industry", "operator": "IN", "value": ["制造业", "零售"] }, { "field": "owner_id", "operator": "EQ", "value": 10086 }, { "field": "team_member_ids", "operator": "CONTAINS", "value": 10086 }, { "field": "last_activity_time", "operator": "LT", "value": "2024-01-01" } ] }后端解析时,team_member_ids这类关联字段需要走子查询或 join。参数说明:logic控制条件间的与或关系,operator支持 LIKE、IN、EQ、LT、GT、CONTAINS 等,value的类型随 operator 变化。注意 LIKE 查询要防 SQL 注入,不要直接拼字符串。
提示:高级查询的条件数量建议设上限,比如最多 10 个,否则查询性能会急剧下降。文档没写这个限制,但实际交付时一定要加。
2.3 列表按钮的批量操作边界
列表页按钮包括新增、编辑、删除、更改负责人、导入、高级查询、导出、搜索。其中删除和更改负责人支持批量操作,编辑明确写了「不可批量编辑」。
为什么编辑不能批量?因为客户字段里有大量枚举和参照类型,批量编辑时无法为每个客户单独选择不同的行业或负责人。而更改负责人是单一字段赋值,批量操作语义明确。删除虽然支持批量,但必须有确认界面,且要处理关联数据——客户下面挂着联系人、商机、合同、订单,删客户时这些关联数据怎么处理,文档在「客户-删除界面示意图」里应该有说明,但正文没展开。实际开发时通常做软删除,或者级联标记,不能物理删除。
导入功能要求按导入模板批量导入客户数据。模板字段要和客户新增页的关键数据项对齐,否则导入后大量字段为空。导出则是把当前页面数据导出到 Excel,注意是「当前页面」还是「当前查询结果全集」,文档写的是「当前页面的数据」,意味着分页后的当前页,不是全部。这个细节容易理解错,开发前要确认。
3. 客户新增与编辑:关键数据项的合法性规则怎么落地
3.1 客户新增页的字段清单与校验逻辑
客户新增页的关键数据项在文档里有完整表格。我把它整理成更直观的形式:
| 数据项 | 类型 | 是否可空 | 可编辑 | 来源 | 特殊规则 |
|---|---|---|---|---|---|
| 客户名称 | 字符 | 非空 | 可编辑 | 手工输入 | 不超过 200 字符 |
| 上级客户 | 参照 | 可空 | 可编辑 | 手工输入 | 不允许互为上下级 |
| 行业 | 枚举 | 可空 | 可编辑 | 数据字典 | 仅已启用项 |
| 公司人数 | 字符 | 可空 | 可编辑 | 手工输入 | — |
| 电话 | 字符 | 可空 | 可编辑 | 手工输入 | — |
| 省市区 | 参照 | 可空 | 可编辑 | 行政区划 | — |
| 业务类型 | 枚举 | 可空 | 可编辑 | 数据字典 | 可多选 |
| 客户级别 | 枚举 | 可空 | 可编辑 | 数据字典 | — |
| 工商注册 | 布尔 | 非空 | 不可编辑 | 系统判定 | 系统操作状态判断 |
| 负责人 | 参照 | 非空 | 可编辑 | 选人员 | — |
| 所属部门 | 参照 | 非空 | 可编辑 | 部门 | 默认当前用户部门 |
| 所属组织 | 参照 | 非空 | 不可编辑 | 系统自动 | 默认当前操作员组织 |
| 成交状态 | 枚举 | 非空 | 可编辑 | 手工输入 | 默认未成交 |
这张表是开发时的校验依据。客户名称非空且限 200 字符,前端和后端都要校验。上级客户的「不允许互为上下级」是个递归校验——A 的上级是 B,那 B 的上级不能是 A,也不能是 A 的下级。实现时要么在保存时做递归检查,要么在数据库层加约束。
工商注册字段是系统判定,不可编辑。这意味着它的值由系统根据某些规则自动计算,比如根据客户名称是否匹配工商注册库。文档没写具体判定逻辑,实际开发时需要产品补充规则。
3.2 编辑页与新增页的差异处理
编辑页和新增页的字段基本一致,但有几个关键差异。第一,编辑时客户名称是否允许修改?文档在编辑页关键数据项里没明确写,但通常客户名称作为业务主键,编辑时要么不允许改,要么改了要触发查重。第二,编辑时的负责人变更会触发权限转移,原负责人失去编辑权限,新负责人获得权限。第三,编辑保存后要更新「最后修改人」和「最后修改时间」,这两个字段是系统自动维护的。
下面是一个客户保存的伪代码逻辑,展示校验和字段填充的顺序:
def save_customer(data, current_user): # 1. 基础校验 if not data.get("customer_name"): raise ValidationError("客户名称不能为空") if len(data["customer_name"]) > 200: raise ValidationError("客户名称不能超过200字符") # 2. 上级客户递归校验 if data.get("parent_id"): if is_circular_reference(data["id"], data["parent_id"]): raise ValidationError("不允许互为上下级") # 3. 系统字段填充 if not data.get("id"): data["created_by"] = current_user.name data["created_at"] = now() data["org_id"] = current_user.org_id data["deal_status"] = "未成交" else: data["updated_by"] = current_user.name data["updated_at"] = now() # 4. 工商注册状态由系统判定 data["business_registered"] = check_business_registration(data["customer_name"]) # 5. 保存并更新最新活动记录时间 customer = save(data) update_last_activity_time(customer.id) return customer参数说明:is_circular_reference需要递归查询上级链,check_business_registration是外部服务调用,update_last_activity_time在每次客户信息变更时触发。注意「最新活动记录时间」和「最后修改时间」是两个不同字段,前者在活动记录保存时更新,后者在客户信息修改时更新。
3.3 查重设置与客户创建规则
文档在「设置」部分提到了客户创建规则和查重设置。查重是 CRM 里最容易翻车的地方——不查重,客户重复录入,数据脏;查得太严,销售录不进去,抱怨。
常见做法是:按客户名称精确匹配 + 电话模糊匹配组合查重。如果名称完全相同,直接提示重复;如果名称相似但电话相同,也提示重复。查重范围限定在「我可见的客户」还是「全组织客户」,需要产品明确。文档没展开,但这是开发前必须确认的点。
注意:查重规则一旦上线,后期修改成本很高。建议初期把查重条件做成可配置的,比如允许管理员选择按名称、电话、或两者组合查重。
4. 客户详情页与关联数据:动态、团队成员、层级关系怎么串
4.1 详情页的页面结构与数据加载
客户详情页是客户数据中心,可查看联系人、商机、动态、合同、订单、团队成员信息。页面结构通常分几块:顶部是客户基本信息(名称、负责人、成交状态等),中间是 Tab 切换区(动态、联系人、商机、合同、订单),底部或侧边是团队成员和客户层级关系。
数据加载策略上,基本信息随页面加载,Tab 内容按需加载。动态页面要展示客户相关的所有操作记录,包括新建、编辑、跟进记录、活动记录等。文档提到「最新活动记录时间」在列表页展示,详情页的动态应该按时间倒序排列。
4.2 团队成员与客户层级关系的实现
团队成员是客户详情页的一个关键模块。文档在查询方案里提到「我参与的客户:团队成员包含我且负责人不等于我」,说明团队成员和负责人是分离的。一个客户有一个负责人,但可以有多个团队成员。团队成员有不同角色吗?文档没写,但实际 CRM 里通常区分「跟进人」「协助人」「只读」等角色。
客户层级关系通过「上级客户」字段构建。文档要求不允许互为上下级,这意味着层级关系是一棵树,不能有环。实现时可以用递归查询或闭包表。递归查询简单但性能差,闭包表查询快但维护复杂。客户量不大时递归够用,客户量上万建议用闭包表。
-- 递归查询某客户的所有下级 WITH RECURSIVE customer_tree AS ( SELECT id, customer_name, parent_id, 1 AS level FROM crm_customer WHERE id = ? UNION ALL SELECT c.id, c.customer_name, c.parent_id, ct.level + 1 FROM crm_customer c INNER JOIN customer_tree ct ON c.parent_id = ct.id ) SELECT * FROM customer_tree ORDER BY level;参数说明:?是起始客户 ID,level表示层级深度。注意递归查询要加深度限制,防止数据异常导致无限递归。
4.3 客户合并与发起讨论的业务规则
客户合并是把两个重复客户合并成一个。文档在「客户-合并」部分有说明,但正文没展开细节。常见做法是:选择主客户和从客户,把从客户的联系人、商机、合同、订单、动态全部迁移到主客户,然后删除或标记从客户。合并时要注意:如果两个客户都有上级客户,合并后上级关系怎么处理;如果两个客户都有团队成员,合并后团队成员是取并集还是以主客户为准。
发起讨论是团队协作功能,类似在客户详情页开一个讨论区。文档在「客户-发起讨论示意图」里有说明,实现时通常关联到消息通知系统,讨论内容存在动态表里,类型标记为「讨论」。
5. 避坑与排查:客户管理模块交付时最容易翻车的五件事
5.1 现象:列表页查询越来越慢,七日未跟进客户加载超时
原因:七日未跟进客户的查询条件是「未跟进天数>7天」,这个「未跟进天数」通常是基于「最新活动记录时间」计算的。如果每次查询都实时计算,数据量上来后必然慢。
解决:把「未跟进天数」做成冗余字段,每天定时任务更新一次。或者用物化视图,定时刷新。查询时直接读冗余字段,不走计算。
5.2 现象:客户导入后大量字段为空,销售抱怨导入没用
原因:导入模板字段和客户新增页字段没对齐,或者导入时没做数据字典映射。比如导入模板里「行业」写的是中文,但系统存的是字典 ID。
解决:导入前先校验模板格式,导入时做字典映射。导入失败的行要生成错误报告,告诉用户哪一行哪个字段有问题。
5.3 现象:更改负责人后,原负责人还能看到客户,新负责人看不到
原因:权限过滤条件没更新。客户列表的可见性通常基于负责人、团队成员、所属组织三个维度。更改负责人后,如果只更新了负责人字段,没更新团队成员或权限缓存,就会出现这个问题。
解决:更改负责人时,同步更新权限相关字段,并清除权限缓存。如果用了 Redis 缓存客户权限,要主动失效。
5.4 现象:客户合并后,商机丢失或合同关联错乱
原因:合并时只迁移了联系人,没迁移商机和合同。或者迁移时外键没更新,导致商机还挂在被删除的客户 ID 上。
解决:合并前先列出所有关联表,逐一迁移。迁移时用事务,保证要么全成功要么全回滚。合并后保留被合并客户的 ID 映射关系,方便追溯。
5.5 现象:高级查询条件多了之后,SQL 报错或结果不对
原因:动态拼 SQL 时,AND 和 OR 的优先级没处理好。比如A AND B OR C和A AND (B OR C)结果完全不同。
解决:前端传条件时带上括号分组,后端解析时按分组拼 SQL。或者用 QueryWrapper 这类 ORM 工具,避免手写 SQL。
提示:高级查询的字段名要做白名单校验,防止用户传入非客户表字段导致 SQL 报错或注入。
6. 从 PRD 到可运行原型:用字段表快速生成 CRUD 的一个技巧
文档里最值钱的部分是那些数据项表格。54 页里,客户关键数据项、列表默认字段、新增页字段、编辑页字段,加起来定义了客户实体的完整元数据。我的习惯是:拿到这类 PRD,先把字段表抽出来,转成一份元数据配置,然后用代码生成器生成 CRUD 骨架。
具体做法:把字段名、类型、是否可空、是否可编辑、来源、默认值整理成 JSON 或 YAML,作为元数据输入。下面是一个示例配置:
entity: customer table: crm_customer fields: - name: customer_name label: 客户名称 type: string required: true editable: true max_length: 200 source: manual - name: parent_id label: 上级客户 type: reference ref_entity: customer required: false editable: true rule: no_circular - name: industry label: 行业 type: enum dict: industry required: false editable: true - name: owner_id label: 负责人 type: reference ref_entity: user required: true editable: true - name: deal_status label: 成交状态 type: enum options: [未成交, 已成交] default: 未成交 required: true editable: true有了这份元数据,前端可以自动生成表单和列表,后端可以自动生成校验逻辑和查询条件。省掉的是重复的 CRUD 编码,留下的是业务规则——比如上级客户的递归校验、工商注册的系统判定、查重逻辑,这些还是要手写。
验证方法:生成 CRUD 后,用文档里的字段规则逐条测试。客户名称超 200 字符是否拦截,上级客户互为上下级是否报错,负责人为空是否拦截,成交状态默认值是否正确。这些测试用例直接从 PRD 的「合法性」列提取。
从那以后我每次拿到 PRD,都先做字段元数据抽取,再动手写代码。这份 2017 年的 CRM 客户管理文档,虽然年代久了点,但字段定义的完整度放到现在依然能打。希望帮到你。
本文还有配套的精品资源,点击获取