
1. 项目概述与定位思考DeskcommCRM这个项目是我花了半年多时间独立开发的一个桌面端客户关系管理系统。可能有人会问现在市面上CRM工具那么多Salesforce、HubSpot、纷享销客、悟空CRM一抓一大把为什么还要自己造轮子这个问题我在立项之前也反复问过自己。我的业务场景比较特殊——我是做B端硬件销售的日常要维护的客户大概在两百到三百家既不像大企业那样需要一个庞大团队协同作战也不是个体户那种靠Excel就能对付的阶段。市面上的云CRM要么功能太臃肿、价格太高要么数据存在别人服务器上我始终不放心本地部署的开源方案又大多需要在服务器环境里维护一套PHP或Java应用用起来很折腾。DeskcommCRM就是想做一个真正适合小而精销售团队的桌面端CRM它跑在本地数据完全可控界面响应快而且按照我自己跑业务的实际流程来设计功能而不是把大厂的流程硬搬过来。Deskcomm这个名字有两层含义Desk代表桌面端形态Comm则取Communication的意思因为这款软件最核心的切入点不是传统的客户档案管理而是围绕着销售与客户之间的每一次沟通记录来构建业务流程。我把它定位成以沟通记录为中心线索的个人级/小团队级CRM这句话是整个项目的设计纲领后面所有功能模块都是围绕这个纲领展开的。如果你也是销售、售前工程师、独立顾问或者小老板正在受Excel管理客户太零散、大平台CRM又用不惯的困扰这个项目的设计思路和实现过程应该能给你不少启发。从技术角度看这个项目选择的是Tauri 2.0 React TypeScript SQLite的技术组合前段界面用React构建后端逻辑用Rust编写本地数据库用SQLite存储整个应用打包出来只有十几MB运行内存占用不到150MB。相比Electron动辄上百MB的安装包和几百MB的内存占用这个体量在桌面应用里算是非常轻量了。关于为什么做这个选型以及每个模块具体怎么落地、踩过哪些坑我会在后面的章节里完整拆解。2. 核心技术选型与数据模型设计2.1 桌面端框架选择为什么是Tauri而不是Electron先聊工具选型因为这是DeskcommCRM一切设计的基础。当时摆在面前的主要选项有三个Electron、Tauri、还有直接用Java写Swing或JavaFX。用Java做桌面端对我来说并不是不能接受毕竟我早年写过一段时间JavaSWT那套也摸过。但问题在于Java桌面应用的UI表现力确实有限想要做到现代一点的界面效果要费很大功夫而且打包分发也不是特别轻便。Electron则完全相反生态成熟、资料多、什么坑都有人踩过但致命的缺点是体积和内存占用。一个简单的Electron应用打包出来至少60MB起步运行起来内存随便就是300MB我这种需要在几台旧笔记本上跑业务的场景实在有点吃不消。Tauri是后来才进入我视野的。它用系统自带的WebView来承载前端页面后端逻辑用Rust实现所以打包体积能做到非常小。我实测下来DeskcommCRM的Windows安装包是11.4MBMAC版本蹭了9.8MB如果你不嵌入额外的大文件体积基本能控制在15MB以内。内存方面空闲状态稳定在120MB到160MB之间即使开着几十个客户详情页也不会有明显的卡顿感。不过Tauri也有它的代价最大的问题就是Rust的学习曲线。如果完全没写过Rust建议先花两周熟悉一下所有权、借用、生命周期这些基本概念不然写后端逻辑的时候会被编译器反复教做人。但如果你的团队里有后端经验的成员这些概念其实都是基本功上手并不会太慢。2.2 数据存储与核心表结构拆解数据层我选择的是SQLite这个决定从头到尾都没动摇过。很多做软件的朋友会觉得SQLite太玩具但它其实是被严重低估的数据库。我这里的数据量不大客户两三百个、联系人八九百个、沟通记录三四年下来也就上万条SQLite处理这种量级简直绰绰有余。而且SQLite是文件型数据库整个数据库就是一个文件备份直接复制文件走人对本地优先的应用来说太合适了。DeskcommCRM的数据库设计遵循了沟通记录驱动的核心理念主要表结构分为五块customers客户表存储客户基本信息包括客户名称、行业分类、客户等级、所在地区、来源渠道、统一社会信用代码等字段。contacts联系人表存储客户下的联系人信息通过customer_id关联客户一个人的联系方式、职位、生日、喜好都记录在这里。interactions沟通记录表这是整个应用的核心表每次与客户的电话、微信、会议、拜访等沟通都记录为一条记录包括沟通方式、沟通摘要、下一步计划、下次跟进时间。orders订单表记录成交订单和未成交报价关联客户和联系人。notes待办与备忘表存放零散的跟进任务和备忘信息。客户表和联系人表之间是一对多的关系一个客户下面会有多个联系人很常见的情况是一个公司里面既要有采购负责人又要有技术的干系人可能还有财务的对接人。沟通记录表则同时关联客户和联系人这样在客户详情页里可以按时间轴看到所有跟这个客户相关的人和事。我特别想强调的是interactions表的设计它有几个字段是跑业务的人都懂但通用CRM往往做得不够的字段名类型说明interaction_typeTEXT沟通方式电话/微信/邮件/面谈/会议summaryTEXT沟通内容摘要纯文本next_actionTEXT下一步行动计划next_follow_up_dateDATE下次跟进日期用来驱动待办提醒sentiment_scoreINT沟通态度评分从1到5用于判断客户意向度那个sentiment_score字段是我自己加进去的我觉得这个设计对销售管理很有价值。传统的CRM会记录大量的文字描述但不会量化地告诉你这个客户的意向到底怎么样。有了这个打分字段之后我可以直接用SQL按周、按月统计客户意向的变化趋势也可以结合订单记录做转化分析。比如我后来做了一个仪表盘里面就有一个客户意向分布图一屏就能看清当前有多少客户是5分、多少是2分下一步重点跟哪些客户就一目了然了。2.3 前后端通信与数据流设计Tauri应用的前后端通信方式是命令Command机制简单来说就是前端通过invoke函数调用Rust后端暴露的命令函数然后Rust返回数据。这个机制比HTTP接口更轻量直接走进程内通信省去了启动本地服务器的开销。但需要注意Tauri的命令默认是异步的且参数需要经过序列化。你必须在Rust端给每个命令标注#[tauri::command]然后通过invoke_handler注册给Tauri应用。DeskcommCRM的数据流设计遵循一条简单粗暴的原则前端不直接操作数据库所有数据操作都通过Rust命令进行。这样做的好处有两个第一是数据层和UI层解耦万一以后想加缓存或者加权限控制都有现成的入口第二是前端不会因为误操作把数据库搞坏Rust的类型安全性能在编译期挡住大量低级错误。实际的调用长这样// 前端调用 const customers await invoke(get_customer_list, { page: 1, pageSize: 50, keyword: 华东 }); // Rust端接收 #[tauri::command] async fn get_customer_list( state: State_, DatabaseState, page: u32, page_size: u32, keyword: OptionString, ) - ResultVecCustomer, String { // 查询数据库并返回 }这里有个小细节如果前端传入的参数名是page_size在JS里就必须写成pageSizeTauri会自动做驼峰命名转换。这个很容易踩坑。另外命令函数的返回值必须实现Serialize所以每个实体结构体都要加上#[derive(Serialize, Deserialize)]否则编译不过。3. 核心功能实现与实操过程3.1 初始化项目与环境搭建这一节写给打算从零开始照着做的人。我用的Tauri版本是2.0安装环境需要Rust工具链、Node.js16以上和系统对应的WebView依赖。如果是Windows平台还需要安装Microsoft C Build ToolsmacOS平台需要Xcode Command Line ToolsLinux平台则需要WebKitGTKDebian系执行sudo apt install libwebkit2gtk-4.1-dev。初始化一个Tauri 2.0项目非常快# 使用create-tauri-app快速创建 npm create tauri-applatest deskcomm-crm -- --template react-ts cd deskcomm-crm npm install # 安装Tauri相关依赖 cargo add tauri2 tauri-plugin-sql2 rusqlite --features rusqlite/bundled这里有个关键点我用的数据库操作库是rusqlite。Tauri有官方的tauri-plugin-sql插件支持SQLite、MySQL和PostgreSQL但它更偏向轻量使用复杂的查询和事务处理能力不如直接用rusqlite扎实。我选择统一用rusqlitetokio-rusqlite来处理异步数据库操作配合Tauri的State管理机制实现全局共享的数据库连接池。在src-tauri/src/main.rs里建立数据库连接的核心代码大概长这样use serde::{Deserialize, Serialize}; use std::sync::Mutex; use tauri::State; use rusqlite::Connection; struct DatabaseState { conn: MutexConnection, } fn main() { let db_path app_data_dir().unwrap().join(deskcomm-crm.db); let conn Connection::open(db_path).expect(Failed to open database); let state DatabaseState { conn: Mutex::new(conn) }; tauri::Builder::default() .manage(state) .invoke_handler(tauri::generate_handler![ get_customer_list, create_customer, get_interactions ]) .run(tauri::generate_context!()) .expect(error while running tauri application); }之所以用MutexConnection而不是直接在一个线程里打开连接是因为Tauri的命令可能会从多个线程并发执行而rusqlite的Connection不是线程安全的。用互斥锁保证同一时刻只有一个线程访问数据库连接可以避免大量database is locked的报错。3.2 核心模块开发客户管理、联系人与沟通记录客户管理模块是整个系统的基础它的核心是增删改查列表和详情页。我花了比较多的心思在客户列表的筛选和分组上。列表页支持按行业、地区、客户等级、来源渠道多维度筛选也支持自由组合查询。这些查询条件在前端用URL参数Tauri WebView里可以使用window.history或内存状态管理保存方便在不同的视图之间切换时保留筛选上下文。客户详情页则包含三个Tab基本信息、联系人列表、沟通记录时间轴。基本信息里包括工商信息标签页这里我补充了一个字段映射表导入客户时从Excel表里读入对应的公司名称、统一社会信用代码这些字段方便后续开票和合同管理。联系人模块相对简单主要是维护联系人列表支持新增、编辑、删除和在客户详情页中切换展示。联系人的关键字段包括姓名、职位、手机、微信、邮箱、关键度标记关键决策人/技术选型人/使用人员/财务对接人。这里有一个比较有用的设计心得联系人的关键度标记一定要做成可筛选的。因为在实际跑业务的过程中谁能拍板是最重要的事情一个客户公司里可能有二三十个联系人但真正能决定采购的往往只有一两个人如果没有这个标记和筛选功能时间长了根本记不住谁是谁、谁说了算。沟通记录模块又是另一个故事。每次沟通后在界面上填写沟通方式、摘要、下一步计划、下次跟进时间这些信息会一条条沉淀下来。时间轴设计是按日期倒序排列最新记录置顶。同时每条记录后面会显示对应的联系人头像如果没有就显示姓名缩写一眼就能看出谁和谁沟通的。这块实现起来技术上不复杂但信息架构上信息量很大是很考验产品细节设计的。实现这类功能的核心技巧是写后即查——每次写记录、编辑记录、删除记录之后立刻重新查询对应客户的整个时间轴并刷新界面。虽然这样看起来多了一些数据库查询但保证了界面与数据的一致性省去了手动维护缓存的麻烦。在几百条记录的规模下这种简单粗暴的设计比复杂的缓存同步方案可靠得多。3.3 自动化与提醒跟进计划与到期提醒跟进计划与到期提醒功能是DeskcommCRM我觉得最有销售思维的功能模块。传统CRM的跟进提醒大多是系统自动按照设定日期给你弹个通知但这对于真实销售场景来说远远不够。真实场景里一个客户可能需要多线并行跟进不同联系人、不同阶段的事情都不同单纯的今天该跟进了这个提醒是没用的你得知道具体该跟进谁、跟进什么事情。所以DeskcommCRM里的提醒不是简单地展示一条今天是X日你们该联系客户A了而是结合interactions表里的next_action和next_follow_up_date字段自动生成一条待办事项。我的数据结构里有一个follow_up_pipeline它把待办分为四类今日到期需要在今天完成的跟进计划显示在最顶部红色标记。即将到期未来3天内到期的计划橙色标记提前提醒避免遗漏。逾期未处理已经超过到期日但还没完成跟进灰色置底但数量会标记在导航栏上。无下一步计划最近一次沟通后没有填写下一步计划的客户这种客户经常会被忽略但我专门做了个列表展示出来提醒销售你上次和这个客户聊完就没了下文别让机会凉了。这种设计的核心逻辑是把跟进当成一条有生命周期的销售管道而不是一条条独立的记录。每条跟进记录像销售管道里的一个阀门阀门开着有待办水才能继续流客户才有下一步进展。技术实现上到期提醒需要在应用启动时自动扫描当日和逾期的跟进记录这在前端用一个useEffect在页面加载后调用Rust命令完成。同时前端设置了一个定时器每小时重新检查一次如果数据库时间发生变化就刷新提醒列表。提醒样式参考了Notion的待办列表风格有完成确认、延期、标记完不成原因等操作。3.4 报表与统计仪表盘的实现数据如果不被分析和使用就只是躺在数据库里的死数字。DeskcommCRM在客户详情页之外还做了一个仪表盘界面用来显示关键业务指标和趋势。仪表盘主要统计这些指标指标名称统计口径客户总数/本月新增客户表计数按月过滤本月沟通次数interactions表按月份统计意向客户数4-5分按最近一条沟通记录的态度评分统计成交金额/本月回款按订单表统计按月过滤客户意向分布按沟通态度评分分组统计柱状图漏斗转化分析从意向客户到成交客户的阶段转化率技术层面这个仪表盘使用了ECharts库来画图Rust后端提供聚合查询接口。比如客户意向分布的SQL是SELECT sentiment_score, COUNT(DISTINCT customer_id) FROM interactions WHERE interaction_date date(now, -90 days) GROUP BY sentiment_score ORDER BY sentiment_score;这个查询统计了近90天内每个意向评分档位对应的客户数量。前端的饼图柱状图渲染都由ECharts的React封装echarts-for-react搞定只要把后端返回的数据数组填进去就行。整个仪表盘的实现难度不大但对销售团队的价值很高因为它是把数据从记录变成决策的关键环节。第一次跑通仪表盘的时候我最直观的感受是原来数据自己会说话。某个月我一分析成交数据发现华东区域电子行业这两个维度组合起来的成交转化率比华南区域制造行业高很多于是我把有限的出差时间重新做了倾斜大概三个月后的成交额就有了明显变化。3.5 搜索与批量操作实用体验优化最后一块让DeskcommCRM在每天使用中真正的很顺手的功能是全局搜索。传统CRM的搜索往往只能搜客户名称但我在实际使用中发现销售更常见的场景是我上周跟一个客户聊过关于设备校准的事情但记不清是哪家公司了或者我记得有个姓王的联系人但他在哪家公司来着。这时候如果搜索只覆盖客户名根本找不到结果。DeskcommCRM的全局搜索框支持同时对客户名称、联系人姓名、联系人手机号、沟通记录摘要这四个维度做模糊匹配。前端搜索输入防抖500毫秒后调用Rust命令后端一次性多表查询合并结果然后前端分组渲染。这样搜校准或者搜王工都能直接跳到目标记录。批量操作主要是针对导入场景的。我做了Excel模板的批量导入支持导入客户列表和联系人列表。这里踩的一个坑是要处理Excel里的各种脏数据——手机号带了空格、客户名称有全半角括号混用、行业字段各人填法不一样这些需要在导入流程里做归一化处理不然数据库里存下的数据就是一堆无法统计的垃圾。4. 数据迁移策略与本地数据安全4.1 从Excel迁移到DeskcommCRM的完整流程做这类系统最头疼的往往不是写代码而是怎么把历史数据搬进来。我之前长期用Excel维护客户信息积累了大概三四年、二百多家客户和上千条沟通记录如果靠手工一条条录入估计录入完之前已经不想用了。所以数据迁移能力在项目规划阶段就被列为必须项。迁移思路是先制定标准模板再清洗映射最后批量导入。标准模板分为两张表customer_sheet和contact_sheetcustomer_sheet里的列与数据库字段一一对应contact_sheet的N行对应某个客户的N个联系人关联关系靠customer_name字段在导入时动态匹配。关键是要处理Excel数据源中的脏数据。比如Excel里某个客户的信用代码字段填了无或者12000000xxxxxx这种明显不规范的导入工具不能直接报错也不能直接丢进数据库而是在清洗阶段替换成NULL。清洗规则用Python的pandas写了一段预处理脚本在导入前跑一遍生成clean_customers.xlsx和clean_contacts.xlsx然后再用Rust后端导入。这段脚本大概几百行核心就几个函数去空格、统一日期格式、去重、映射行业字段。4.2 本地备份与恢复机制对于本地优先的应用备份是绝对不能省的功能。我见过太多人用本地软件结果硬盘一坏数据全没了的惨状。DeskcommCRM做了一个自动备份机制应用启动时检查数据库所在目录如果存在最近的备份文件且间隔超过7天就自动复制一份数据库文件重命名加上日期时间戳保留最近30天的备份版本。每次手动执行立即备份也会生成一份快照。恢复流程则更简单在设置界面选择备份文件程序会将当前数据库文件重命名为.bak然后把备份文件复制成当前数据库再重新加载所有数据。整个恢复操作在界面上有明确的二次确认提示防止误操作。我在这个模块踩过一个挺深的坑有一次Windows系统强制更新把电脑重启了恰好重启前数据库里有一堆没保存的变更结果回滚到上一周的备份丢了将近一周的记录。那次之后我改进了备份策略每次写操作成功后自动延迟5秒触发一次增量备份而不是只做全量快照这样即使系统崩溃最多丢几秒钟的数据而不是丢一整周。为了做增量备份我在interactions表和customers表上都加了updated_at字段备份时对比这个字段只导出新增和修改过的记录为JSON文件。全量备份是SQLite文件复制增量备份是JSON记录累积两套方案配合使用数据安全系数高了不少。5. 常见问题与排查技巧实录5.1 Tauri前端调用Rust命令时的序列化问题Tauri命令的参数传递用serde做序列化所以前端的数据结构和Rust结构体必须严格对应。我在实际开发中遇到最多的报错是Error serializing parameter XX fromYYYYto typeZZZZ也就是前端字段名和Rust字段名不匹配导致的。Tauri默认会把Rust字段名转换成驼峰形式给前端比如Rust里有字段customer_name前端就要用customerName传参否则会报错。调试这种问题的方法很简单在Rust命令里加日志打印#[tauri::command] async fn create_customer( state: State_, DatabaseState, data: CustomerInput, ) - Resulti64, String { println!(received data: {:?}, data); // 业务逻辑 }然后在主进程终端里看输出能直接看到传进来的字段到底是什么。如果是字段对不上看下serde的报错信息里哪个字段缺失就补哪个字段。另外提醒一点Option类型的字段不会自动补None如果前端不传这个字段Rust端会直接报类型错误所以前端尽量把字段都传进来哪怕传null。5.2 SQLite并发写入的锁问题Tauri的异步命令并发执行时多个命令同时尝试写入SQLite数据库就会出现database is locked错误。SQLite对并发写入有限制同一时刻只允许一个写事务其他写入请求会等待或者直接失败具体行为取决于超时设置。我最初的方案是MutexConnection串行化所有操作并发写入自然消失但代价是读操作也要排队数据量大之后会感觉到轻微的卡顿。后来优化方案是读写分离读操作使用Connection的只读副本写操作通过互斥锁串行化。实现方法是在main.rs里维护两个连接一个只读连接用Connection::open_with_flags加SQLITE_OPEN_READ_ONLY和一个写连接普通连接用Mutex包住。读命令走只读连接写命令走写锁连接大幅减少了锁等待操作体验顺滑了很多。5.3 搜索慢与中文搜索体验问题全局搜索在数据量超过五千条沟通记录后明显能感觉到变慢。原因是最初的SQL匹配语句是全表扫描加LIKE没有走任何索引。优化方案分两步。第一步是给关键字段加索引。在customers表上对customer_name字段建索引注意中文索引效率要用COLLATE NOCASE配合BINARYcontacts表对name、phone字段建索引interactions表对summary字段建索引。第二步是把SQL从WHERE field LIKE %keyword%改成WHERE field LIKE ? ESCAPE \\同时在前端做关键词预处理把%、_这些通配符转义。这两步优化后即使数据量到两万条搜索响应时间基本在100毫秒以内。关于中文搜索SQLite模糊匹配本身支持中文没问题但有个体验问题如果你输入设备校准四个字标准LIKE是活字匹配用户输入校准就找不到设备校准但模糊匹配用户输入设备能找到设备校准和设备维护等所有包含设备的记录。所以我在搜索关键词处理时做了分词简单按空格、逗号、顿号拆分然后每个词单独匹配再取交集。没有用额外分词库因为本地搜索性能完全够用。5.4 打包发布与版本管理的经验Tauri的打包发布比Electron要繁琐一点点因为涉及Rust编译链和系统差异。我的经验是开发机和打包机尽量分开配置。在Windows平台用GitHub Actions的windows-latest runner打包macOS用macos-latest这样交叉编译的坑不用碰。打包命令是npm run tauri build构建产物在src-tauri/target/release/bundle/目录Windows下生成.exe安装文件和NSIS安装包macOS下生成.dmg和.app。我仔细看过默认的NSIS配置默认的安装选项很简陋后来改成了自定义NSIS脚本支持安装目录选择、桌面快捷方式可选、应用卸载干净彻底。Tauri 2.0的NSIS配置改成了JSON配置文件路径在src-tauri/tauri.conf.json的bundle.windows.nsis节点下支持installMode、languages、createDesktopShortcut等参数配置。版本管理方面前端用git tag做版本标记Rust的版本号在Cargo.toml里维护前端版本号在package.json里维护发布时两个版本号保持一致避免混淆。每次发布前我都会跑一遍冒烟测试安装干净环境、创建客户、添加联系人、记录沟通、生成报表、备份恢复全部通过才打tag发布。6. 项目中踩过的设计与人性化细节坑6.1 设计的只增不改原则CRM类软件最容易犯的毛病是编辑了旧记录后就失去了真实历史。比如客户今天说你那个报价再等等我们预算还没批明天说预算批下来了马上准备合同如果销售把这两条都录成一条记录后面复盘的时候根本看不出客户决策的时间线到底是怎么走的。所以DeskcommCRM的interactions表设计严格执行只增不改原则。含义是已创建的沟通记录不允许修改只能新增补充记录。如果某条记录写错了内容只能标记为错误不能直接删除。系统会保留一条完整的操作痕迹避免后续扯皮。当然这个设计并不是所有场景都适用——编辑订单价格这种数据还是需要允许修改的。所以我做了一个妥协客户、联系人、订单这类主数据允许编辑但编辑历史保留在change_log表里沟通记录是纯追加不能改不能删。6.2 标签系统与自定义字段设计DeskcommCRM里有一个很轻量但全天在用的功能——标签系统。客户可以打上任意多个标签比如重点客户下滑风险需要催款战略合作项目型客户等等。标签和客户等级是两个维度等级描述的是客户的潜在价值标签描述的是当前的状态或属性。这两个维度组合起来在列表页能实现很灵活的筛选。比如等级为A但带需要催款标签的客户一眼就能看出哪些高价值客户存在资金风险。技术实现上标签就是一张tags表加一张customer_tags关联表前端用多选框组件呈现加个字符串搜索框辅助查找已有标签。代码量不大但用户心智价值极高比单纯做一个可配置的客户级别字段灵活得多。自定义字段功能是给未来扩展留的后门。如果某个团队想把客户来源改成客户预算规模直接改数据库字段不现实所以DeskcommCRM在customers表上加了一个custom_fields列用TEXT类型存JSON字符串。前端解析后动态渲染一个扩展信息区域这样以后加字段不需要动数据库结构只需要改前端渲染逻辑即可。6.3 快捷键与效率优化的细节打磨这是我个人很享受做的一个部分。每天在CRM里录客户、记沟通、翻线索这些重复操作如果能用快捷键解决一天下来能省不少时间。DeskcommCRM设计了这些常用快捷键快捷键功能Ctrl/Cmd N新增客户弹窗Ctrl/Cmd K全局搜索框聚焦Ctrl/Cmd I新增沟通记录Ctrl/Cmd D在客户详情页打开联系人快捷添加Ctrl/Cmd ,跳转设置界面这些快捷键都是用React的事件绑定实现的监听全局键盘事件后调用store里的action。为了让用户知道这些快捷键的存在我在界面底部导航栏做了一个快捷键提示的tooltip鼠标悬停在对应图标上会出现按键提示。这个小设计虽然没有大功能那么起眼但长期使用下来提升效率的效果非常显著每次操作省两秒每天几十上百次操作下来就是几分钟的差距一年下来省下来的时间非常可观。最后的个人体会DeskcommCRM这个项目做下来我最大的收获不是代码量而是对工具应该怎么为业务服务这件事想得更清楚了。市面上很多CRM为了覆盖更多客户群体把功能做得很全很重但销售真正日常使用的功能无非就是记东西、找东西、有提醒、能统计。把它们做快、做顺手比堆砌一堆看起来很牛但用不上的分析模型重要得多。如果你也打算做类似的东西我建议你从自己最痛的那个点切入比如我就是从每次找之前聊过什么都要翻半天这个痛点出发的先做一个最小可用版本跑起来再用起来、加功能、改体验。这个循序渐进的过程本身可能比最终产品更有价值。最后再分享一个小技巧本地应用的数据安全容不得半点马虎不管你的备份方案设计得多好都要时不时手动测试一下恢复流程我自己就吃过备份机制看着没问题、真需要恢复时才发现备份文件损坏的亏这种坑踩过一次就长记性了。