自己搭一个客户管理系统,这事听起来不复杂,真正做起来全是坑。前几个月我一直在折腾一个叫 DeskcommCRM 的项目,从零开始做桌面端客户关系管理系统,踩了不少雷,也积累了一些实战经验。这篇文章不打算讲那些虚的,直接分享我在这个项目里的整体设计思路、几个核心模块的落地实现、关键问题的排查记录,以及一些在文档里根本不会写的心得,希望能给想做类似系统的朋友一点参考。
DeskcommCRM 定位很明确:一个面向销售和客服团队、以客户档案为中心、强调跟进留痕和自动化提醒的桌面端 CRM。它解决的问题很具体——在销售人数不多、但客户量接近几千条的场景下,用 Excel 维护客户资料和跟进记录已经撑不住了,数据分散、跟进靠记忆、销售离职直接带走客户资源。市面上的 SaaS CRM 要么收费贵,要么功能太重,内部系统对接又麻烦,所以决定自己搭一个轻量但完整的桌面客户端。
这套系统适合的读者也很清楚:后端想练手完整业务闭环的开发者、团队内需要一套内部 CRM 但预算有限的技术负责人、以及对客户管理流程有自定义需求的产品经理。
1. 项目定位与功能边界设计
1.1 自研 CRM 的底层动因
先说一下为什么不用现成的 SaaS 系统。市面上成熟的 CRM 产品确实很多,功能从线索到回款全覆盖,但真正用起来有两个问题绕不开:一是数据不在自己手里,客户资料毕竟属于核心资产,放在第三方平台上总有点不踏实;二是定制能力有限,像销售阶段、跟进频率、工单流转规则这类业务流程,每家公司都不一样,SaaS 的后台配置虽然灵活,但在深度适配内部系统时还是吃力。
Deskcomm 这个名字拆开看,Desk 强调的是桌面端场景,Comm 则代表 Communication,合起来就是“在办公桌前高效完成客户沟通与跟进”。所以从一开始就把系统定位成“内部业务系统的操作台”,核心使命只有一个:让一线销售和客服在处理客户问题时少走弯路。
很多人做自研系统容易犯一个错误:一开始就想要大而全,把线索、客户、商机、合同、回款、售后全部塞进去,结果开发周期被拖得很长,最后哪个模块都没做透。我这次刻意把边界控制得很严,MVP 阶段只做客户管理、跟进记录、工单流转、数据看板四个模块。其他功能像合同管理、回款管理,即使要做也至少是后话了,不然这个项目根本交付不了。
1.2 客户全生命周期的主线逻辑
规划功能的时候,我先把客户在系统里的完整生命周期画成一条线,这也是 DeskcommCRM 的核心业务流程:
- 线索录入:从市场活动或老客户推荐获取的原始线索先进入线索池,此时信息往往不完整,只有一个名字和联系方式。
- 客户转化:销售确认线索有效后转为正式客户,补充公司信息、行业、规模、来源渠道等结构化字段。
- 跟进维护:销售围绕客户进行电话、见面、邮件等跟进动作,每次跟进记录生成一条时间线数据。
- 工单服务:当客户遇到售后问题或技术故障时,从客户档案直接发起工单,流转到对应负责人处理。
- 数据沉淀:整个过程中产生的跟进记录、工单历史、联系方式变更,全部自动归档,形成客户全息档案。
这条主线确定之后,功能边界就清晰了,后续所有模块都是围绕这条主线做支撑。客户管理管的是“客户是谁”,跟进记录管的是“我们在客户身上做了什么”,工单管理管的是“客户遇到了什么问题”,数据看板则是把上面所有动作的价值量化出来。
1.3 自研桌面端方案的几个优势
选桌面端而不是纯 Web 端,倒不是标新立异,主要是实际使用场景决定的。如果你们团队的销售和客服主要在电脑前工作,对移动办公没有强需求,桌面端的价值就很明显了:
- 数据本地缓存能力:客户列表、常用字典数据可以本地缓存,打开应用秒级响应,不带网络也能勉强查看历史数据。
- 更顺手的桌面操作习惯:销售录客户时经常需要快速切换窗口查资料,桌面客户端比浏览器标签页好用得多。
- 便于对接本地资源:比如直接调用本地 Excel 导入、导出,读取本地通讯录,这种能力和 Web 端做起来不是一个难度级别。
- 天然适合局域网部署:内网部署时不用折腾域名、HTTPS 证书,打包成 exe 或安装包分发给员工即可。
这块想清楚了,后面的技术选型就好定多了。
2. 技术选型与关键架构设计思路
2.1 技术栈选型的逻辑
技术选型这事,我的原则很简单:团队熟什么就用什么,如果大家都是零基础,那就选学习曲线最平滑、社区生态最完善的组合。DeskcommCRM 最终选用的技术栈如下:
| 层级 | 技术 | 选型理由 |
|---|---|---|
| 桌面端框架 | Electron + Vue 3 | 生态成熟,组件库丰富,开发效率高,打包跨平台方便 |
| UI 组件库 | Element Plus | 表格、表单、弹窗等后台管理组件齐全,几乎不用自己造轮子 |
| 桌面端通信 | Electron IPC + 本地 SQLite | 主进程负责与本地数据库交互,渲染进程只管界面展示 |
| 本地数据库 | SQLite + SQLCipher | 零配置文件、支持 SQL、轻量,SQLCipher 对本地数据文件做了加密 |
| 界面图表 | ECharts | 数据看板需要折线图、漏斗图、饼图,ECharts 的文档和例子是最全的 |
| 构建工具 | electron-builder | 打包 Windows 和 macOS 安装包,配置简单,网上踩坑方案多 |
有人会问为什么不用 JavaFX 或 Qt,这两个确实更“原生”,但考虑到后续可能要扩展 H5 端和移动端,Vue 这套前端技术栈可以完全复用,而且团队里会 Vue 的人比会 Qt 的人多得多,招聘成本也低。
Electron 有一个常被吐槽的点是打包体积大,但这在内部工具场景下并不致命。真正要注意的是内存占用,我在实际调优中通过关闭硬件加速、限制渲染进程数量、对大数据表格做分页渲染,把空闲内存从 600MB 压到了 350MB 左右,这个优化过程后面会详细讲。
2.2 本地数据库的表结构设计
客户数据放本地 SQLite,这一点一开始就有争论。有人建议客户数据放服务端,本地只做缓存,但考虑到这个小工具的使用场景就是单机或小规模局域网共享,直接 SQLite 反而比部署一套 MySQL 简单太多。当然如果后期需要多人在线协同录入,就必须换成 C/S 架构加服务端数据库,这点我会在开头和团队说清楚边界。
表结构设计是整个项目的核心基础,我花了不少时间。客户主表是最重要的,核心字段要有:客户名称、客户编码、所属行业、客户来源、客户状态(潜在/跟进中/已成交/已流失)、负责人、联系电话、公司地址、备注信息。客户编码我用了“K + 年月日 + 三位流水号”的规则,比如 K20250106001,这样光是看编码就能知道客户是什么时候录入的。
跟进记录表是业务动作的留痕载体,字段有客户 ID、跟进方式(电话/微信/上门拜访等)、跟进内容、下次跟进时间、记录人、创建时间。客户与跟进是一对多关系,每次跟进都要自动更新客户表里的“最后跟进时间”和“下次跟进时间”字段,方便做待办提醒。
工单表字段包括工单号、客户 ID、工单类型(售后/技术支持/投诉)、优先级(低/中/高/紧急)、状态(待处理/处理中/已解决/已关闭)、指派人、创建时间、解决时间。下面给出简化版的建表 SQL:
CREATE TABLE customer ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_code TEXT NOT NULL UNIQUE, customer_name TEXT NOT NULL, industry TEXT DEFAULT '', source TEXT DEFAULT '', status TEXT DEFAULT 'potential', owner TEXT DEFAULT '', phone TEXT DEFAULT '', address TEXT DEFAULT '', remark TEXT DEFAULT '', last_follow_time TEXT DEFAULT '', next_follow_time TEXT DEFAULT '', created_at TEXT DEFAULT (datetime('now', 'localtime')), updated_at TEXT DEFAULT (datetime('now', 'localtime')) ); CREATE TABLE follow_up ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, method TEXT DEFAULT 'phone', content TEXT NOT NULL, next_follow_time TEXT DEFAULT '', recorder TEXT DEFAULT '', created_at TEXT DEFAULT (datetime('now', 'localtime')), FOREIGN KEY (customer_id) REFERENCES customer(id) ); CREATE TABLE ticket ( id INTEGER PRIMARY KEY AUTOINCREMENT, ticket_code TEXT NOT NULL UNIQUE, customer_id INTEGER NOT NULL, type TEXT DEFAULT 'after_sale', priority TEXT DEFAULT 'medium', status TEXT DEFAULT 'pending', assignee TEXT DEFAULT '', content TEXT NOT NULL, created_at TEXT DEFAULT (datetime('now', 'localtime')), resolved_at TEXT DEFAULT '', FOREIGN KEY (customer_id) REFERENCES customer(id) );表结构设计这块最重要的心得是要预留必要的冗余字段。比如客户表里的 last_follow_time 和 next_follow_time 其实是冗余字段,按规范化设计应该通过查询跟进记录表来获取,但实际使用中,客户列表页需要频繁按“下次跟进时间”排序和筛选,如果每次都要子查询,几千条数据就能明显感觉到卡顿。冗余这几个字段后,查询速度直接提升一个量级,代价仅仅是多写几行更新逻辑。
2.3 数据流通的完整链路
本地版 CRM 看似简单,但数据链路的设计决定了后续扩展的难度。DeskcommCRM 的数据流是这样走的:
界面操作 -> 渲染进程(Vue 组件) -> Electron IPC 通信 -> 主进程(数据库操作层) -> SQLite 数据库 -> 操作完成后返回结果 -> 渲染进程更新界面
这个链路里最容易出问题的环节是 IPC 通信。Electron 的 IPC 本质上是异步消息机制,如果同时发几十个请求,渲染进程会乱序收到结果。我解决的方案是所有数据库操作封装成 Promise 形式,用 requestId 标识每个请求,渲染进程收到响应后根据 requestId 匹配到对应的 Promise 并 resolve。这也是桌面应用开发中常见且稳定的模式,不算新东西,但确实是容易忽略的细节。
// 主进程側的 IPC 处理示例 ipcMain.handle('db:queryCustomerList', async (event, params) => { const db = getDatabase(); const page = params.page || 1; const pageSize = params.pageSize || 20; const offset = (page - 1) * pageSize; const total = await db.get('SELECT COUNT(*) AS count FROM customer'); const list = await db.all( 'SELECT * FROM customer ORDER BY updated_at DESC LIMIT ? OFFSET ?', [pageSize, offset] ); return { total: total.count, list }; });到这里,整体架构的骨架已经清晰了,接下来进入具体功能模块的实现。
3. 核心功能模块的实现与实操细节
3.1 客户管理:从增删改查到查重合并
客户管理模块看起来是最基础的,真正做好也不简单。我可以负责任地说,一个 CRM 的客户管理模块,只要把“搜索”和“去重”这两件事做好了,用户满意度就能提升百分之六七十。搜索方面,我实现了关键字模糊搜索和组合条件筛选,支持按客户名称、电话、行业、负责人精确筛选;查重方面,在客户表中对电话字段建立了唯一索引,重复录入时给出提示。
但现实场景是,销售有时候就是会把同一个客户录两遍,可能一个电话是手机号、一个电话是座机号,根本没法靠索引拦截。所以我在系统中加了“疑似重复客户”的功能:录入或导入客户时,后台会把姓名和联系方式做归一化处理后进行相似度匹配。实现方式不复杂,电话号码只保留数字和后缀,然后再做精确匹配和前缀匹配;姓名用编辑距离算法做相似度计算,超过阈值就弹提醒。
这个功能的背后逻辑值得说一下:你把接口设计得再好,也拦不住用户录错,所以查重这个能力不是一个简单的增删改查,而是一个提升数据质量的治理机制。数据质量在一开始就是脏的,后面做统计报表全是错的,后面再想清洗成本翻倍。
客户详情页的设计也很有讲究。很多人做详情页就是一行一行字段罗列,用户要看一大堆基本信息才能找到跟进记录。我的设计是把详情页做成一个顶部头部 + 标签页结构:头部放客户名称、状态标签、下次跟进时间,下面分“基本信息”“跟进记录”“工单记录”“操作日志”四个标签页。这样客户经理打开一个页面,第一屏就能掌握最核心的信息,不需要滚动和点击。
3.2 跟进记录:让每一次沟通都有迹可循
跟进记录是客户管理的灵魂,一个只有客户档案但没有跟进记录的系统,本质上就是个通讯录。DeskcommCRM 的跟进记录模块在设计上做了几件事:
第一是快录。跟进记录一定要支持快捷录入,点击客户列表里的“记录跟进”按钮,弹出一个包含跟进方式和跟进内容的对话框,默认跟进方式是上次使用的方式,默认时间为当前时间,录入完回车即保存。整个操作不能超过 5 秒,否则销售就不愿意录了。
第二是时间线展示。所有跟进记录按时间倒序排列在客户详情页中,最新一次跟进显示在最上面,并用时间线和状态颜色区隔。每个记录卡片上要直观展示跟进方式、内容摘要、跟进人和下次跟进时间,视觉上一眼就能看到客户处于什么阶段。
第三是下次跟进驱动。每次录入跟进记录时,建议填写下次跟进时间和方式,保存后主列表的对应客户行会更新排序权重。我在客户列表页增加了“今日待跟进”的快捷筛选条件,在工作台首页还会显示今天需要跟进的客户数量以及具体人员分工的预览。
这里分享一个我踩过的坑:跟进记录表的权限设计。一个客户如果是团队共有的,那么任何成员都能查看和编辑跟进记录,但“谁创建的记录谁才能修改”这个约束需要加上,否则就会出现 A 修改了 B 写的记录导致对不上话的纠纷。这个问题在多人协作时非常常见,不要等到上线了才发现。
3.3 工单管理:把客户问题管起来
工单模块本质上是把“客户遇到问题后内部怎么转、谁来处理、多久处理完”这条链路管理起来。DeskcommCRM 的工单管理做成了和客户档案强关联的模式,从客户详情页可以直接看到这个客户开过的所有工单,也能一键创建新工单。
工单的状态流转我设计成:待处理 -> 处理中 -> 已解决 -> 已关闭。其中已解决表示技术侧认为问题已经处理完,已关闭表示客户确认无异议后才关闭。这两个状态很重要,因为我自己以前做售后时,经常遇到技术说解决、客户说没解决的情况,拆分后责任更清晰。
工单优先级用紧急度和影响度两个维度来判定。比如“客户系统崩溃无法登录”就是紧急 + 影响大,优先级直接拉到紧急;“客户咨询某个功能如何使用”属于一般。优先级不同,系统会在通知层面做不同的触达方式;紧急工单不仅弹窗提醒,还会通过桌面通知和声音提示,避免问题沉没。
工单超时管理也是我很看重的一个点。每条工单创建时会根据优先级自动计算一个“期望解决时间”,比如紧急工单 4 小时、普通工单 24 小时。到时间未解决,系统会自动在工单列表上标红,并在工作台轮询获取超时工单数据。实现上很简单,就是根据 created_at 和当前时间差做判断,没有引入复杂的流程引擎。
3.4 数据看板:用数字驱动决策
看板模块很多人容易做成摆设,因为只是把数据库里的数字翻出来摆到界面上,对业务没有任何指导意义。我做看板时定了三个核心指标:客户新增量、跟进次数、成交转化率。这三个指标分别回答三个问题:市场拓展做得好不好、销售执行到不到位、整体转化效率高不高。
看板的视觉呈现用了 ECharts 的漏斗图和柱状图。漏斗图展示从潜在客户到已成交客户的转化过程,每一层的数量变化非常直观;柱状图展示最近 12 个月每月的客户新增量,用于观察趋势。还有一个重要的小细节是“待办数量”的展示,把今日待跟进客户数、待处理工单数、超时工单数这几个数字放在看板顶部,并用不同颜色标出,颜色越深说明越需要立即处理。
做统计查询时性能是大问题,尤其是筛选了时间范围和负责人之后。优化方案是提前聚合,我设计了一个每日统计表,在每天凌晨定时把前一天的新增客户数、跟进次数、新开工单数按维度统计好,写入独立统计表。界面展示的时候直接查聚合表,必要时再加一层 Redis 缓存。这套方案在数据量到几万条时依然能秒开。
ECharts 在 Electron 环境里还有一个坑:渲染进程的内存泄漏。图表实例如果不主动 dispose,在频繁切换页面时会积累大量实例导致内存持续上涨。我封装了一个统一的图表容器组件,在组件卸载前的钩子里主动调用 chart.dispose(),这个问题就解决了。
4. 效率优化与本地数据安全加固
4.1 大列表数据渲染的优化手段
客户列表是最常用的页面,也是最容易卡顿的页面。数量到了几千条记录时,如果一次性全部渲染成 DOM 节点,Electron 的渲染进程会明显卡顿。这里有两个层面的优化手段,我实测下来是有效的。
第一层是分页,每页 20 条,这个就不用多说了。第二层是虚拟滚动。在客户列表这种行高固定、数量较大的场景下,虚拟滚动可以把真实渲染的 DOM 节点数量控制在几十个以内,无论底层有多少数据,界面滚动起来都是流畅的。我用的方案是先对数据按更新时间排序,再对可视区域做裁剪,只渲染可视区上下的三倍缓冲区域。
还有一个隐藏优化点在搜索框。客户列表顶部有一个全局搜索框,用户会频繁输入关键词,如果每输入一个字就去查一次数据库,输入响应会非常卡。我把输入事件做了 300 毫秒防抖,并在搜索前加一个最小字符数限制,例如输入 1 个字符时不发起查询,等输入 2 个以上字符时才自动搜索。这样既保证了体验,也减轻了数据库压力。
4.2 本地数据的加密与备份机制
本地数据库直接存客户资料,安全性必须重视。DeskcommCRM 用了 SQLCipher 对 SQLite 文件加密,数据库文件打开时需要提供密钥。密钥的存放策略是:首次启动时生成一个随机密钥,然后用系统级的凭据存储(Windows 上是 Credential Manager,macOS 上是 Keychain)保存,这样既不用用户记密码,又不会明文写在配置文件中。
有个细节值得提一下,如果数据库文件损坏,客户数据就全没了。所以我加了自动备份机制:每次应用正常退出时,会检测当天是否有备份文件,如果没有就将数据库文件复制到备份目录,并用日期命名。界面里还做了一个“导出全部数据”的功能,可以将客户、跟进、工单三张表的数据全部导出为 Excel 文件,方便发售之后的数据迁移或人工检查。
备份触发时机一般有三种:应用启动时、应用退出时、定时任务。我的建议是“退出时备份主数据 + 每日凌晨备份一次”的组合,防止程序崩溃导致备份文件本身就是坏的。
4.3 桌面端内存占用的调优记录
Electron 应用的内存占用问题是一直被诟病的。我在 DeskcommCRM 里做了以下几项优化,最终把空闲内存从 600MB 左右降到了 350MB 左右:
第一,关闭不需要的 Chromium 功能。在创建 BrowserWindow 时,把 sandbox、spellcheck、autoplayPolicy 等不用的功能显式关闭,能省一小部分内存。第二,改用单渲染进程模型,不要随意开新窗口,所有的页面切换都用路由来完成。第三,处理完事件后主动清空数据引用,尤其是大对象数组。
这个优化过程是逐步做的,不是一步到位的。我的建议是不要一开始就优化内存,先保证功能正确,再用任务管理器观察内存变化,逐个排查大内存消耗点。因为过早优化很容易影响开发节奏,性价比不高。
5. 常见问题与排查技巧实录
5.1 启动慢、窗口白屏的排查思路
Electron 应用启动时出现白屏是一个很经典的问题。第一次启动时白屏时间特别长,主要原因是加载了较大的 bundle.js,或者渲染进程在等待主进程的某个初始化任务。排查时可以打开开发者工具看网络请求和 console 输出,看看卡在网络加载还是脚本执行。
我在实际开发中还遇到过一个诡异的问题:在某些 Windows 机器上应用启动后窗口完全空白,没有任何报错,把应用最小化再恢复又能正常显示。后来定位到原因是 Windows 的 GPU 渲染驱动兼容性有问题,解决办法是在创建窗口时加上 disableHardwareAcceleration 选项,强制使用软件渲染。这类问题在不同的 Windows 版本和显卡驱动上表现都不一样,属于 Electron 典型的跨平台兼容性坑。
5.2 数据持久化和表结构变更的兼容方案
本地数据库开发过程中必然会遇到表结构变更,而 SQLite 不支持直接修改列类型,只能建立新表、迁移数据、删旧表、重命名。这个问题在开发早期频繁出现,我写了一个简单的迁移脚本,每次启动时检查数据库版本号,根据版本号执行对应的迁移 SQL 语句。这样团队协作时不会因为表结构不一致导致程序崩溃。
下面是一个迁移脚本的示例逻辑:
async function migrate(db) { const versionRow = await db.get('PRAGMA user_version'); const currentVersion = versionRow ? versionRow.user_version : 0; if (currentVersion < 1) { await db.exec(` CREATE TABLE IF NOT EXISTS customer (...); CREATE TABLE IF NOT EXISTS follow_up (...); CREATE TABLE IF NOT EXISTS ticket (...); `); await db.exec('PRAGMA user_version = 1'); } if (currentVersion < 2) { await db.exec('ALTER TABLE customer ADD COLUMN wechat TEXT DEFAULT ""'); await db.exec('PRAGMA user_version = 2'); } }PRAGMA user_version 是 SQLite 自带的轻量版本号机制,特别适合这种嵌入式数据库的迁移场景。建议所有做本地数据库应用的朋友都养成这个习惯,不然版本一多,自己都会忘记哪个表加了哪些字段。
5.3 常见问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 应用启动后白屏 | GPU 兼容性或 JS 加载慢 | 关闭硬件加速,检查 bundle 加载路径 |
| 数据库文件打不开 | 加密密钥丢失或文件损坏 | 检查系统凭据存储,恢复自动备份文件 |
| 客户列表搜索卡顿 | 查询未做防抖或缺少索引 | 给 name、phone 字段建索引,增加防抖 |
| 跟进记录保存失败 | 外键约束或必填字段为空 | 查看主进程日志,补齐必填字段 |
| 打包后安装包被杀软报毒 | Electron 应用被误判 | 更换签名证书,或用 NSIS 配置减少误报 |
| 图表数据不刷新 | 缓存了旧数据 | 检查缓存策略,加版本号或缓存失效时间 |
5.4 日志级别的选择与实战意义
本地桌面应用最容易被忽略的就是日志系统。没有日志,出了问题只能蒙。我在 DeskcommCRM 里用了 electron-log 这个库,日志分四种级别:error、warn、info、debug。日常运行时只输出 error 和 warn 级别,避免日志文件膨胀;调试时切换成 debug 级别,输出所有操作细节。日志文件按日期滚动保留 30 天,位置放在用户数据目录下,排查问题时直接打开看即可。
有一个经验特别想分享:数据库操作一定要打印 SQL 和耗时。有时界面卡顿半天,查日志发现某条 SQL 跑了 2 秒,就能顺着这条线索找到缺少索引或其他性能问题。没有日志的报错排查,就像在黑屋子里找一根针,效率极低。我在主进程的数据库操作封装里统一加了耗时统计,超过 500ms 的操作单独标出来,这对定位性能问题帮助巨大。
6. 个人维护与迭代经验
项目上线只是开始,后续的维护和迭代才是真正考验人的地方。每次改完代码我都会做一轮完整的回归测试,重点检查客户新增、跟进录入、工单流转、数据统计这四个核心流程有没有被破坏。因为桌面端应用不像 Web 端那样可以快速热修复,打一次包、分发、安装的成本不低,所以宁可本地多测几遍,也不要让用户频繁重新安装。
迭代方面,我给自己的原则是小步快跑,每个版本只加一个核心功能,避免大爆炸式更新带来的回归风险。比如当前版本我只加了客户标签和自定义字段这两个能力,标签用于做客户分层,自定义字段用于兼容不同行业的特殊信息。等这个版本稳定了,再考虑加合同管理和回款台账。
另外,代码里的配置项我尽量做成外部配置文件,而不是硬编码。像数据库文件路径、日志保留天数、备份目录、统计任务的执行时间,这些将来都大概率要改,写进配置文件能让后期运维省很多事。
再多说一点关于团队协作的经验。桌面端项目是前后端一体的,Vue 代码和 Electron 主进程代码最好分开管理,用 monorepo 结构组织,主进程代码用 JavaScript 保持简单,渲染进程用 Vue + TypeScript 保证类型安全。这个分工一开始没做好,后面重构了两次才理顺,建议后来者一步到位。
DeskcommCRM 做到现在,我的体会是:做内部工具型应用,不需要追求技术多酷炫,更需要关注业务逻辑的完整性和细节体验的打磨。客户管理系统的本质不是技术问题,而是流程问题——把销售、客服、技术支持之间的协作流程理顺,再把这些流程固化到系统里,这个系统就会越用越顺手。后续我会继续补充自动化工单分配、销售目标拆解这类进阶功能,也会把这套实践持续沉淀成一个更可复用的模板。