news 2026/9/19 9:26:27

从零构建桌面端沟通型CRM:以沟通时间线为核心的本地优先管理实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建桌面端沟通型CRM:以沟通时间线为核心的本地优先管理实践

1. 项目背景与定位:DeskcommCRM 到底在解决什么问题

DeskcommCRM 这个名字拆开看,就是 Desk Communication CRM,指的是“桌面前端的沟通型客户关系管理工具”。很多人第一次听到这个项目名,会下意识觉得它又是一个套着 CRM 壳子的销售管理后台。但实际做下来,这个项目的核心逻辑和传统 CRM 有很大区别:它重点解决的不是“商机漏斗怎么画”,而是“每天坐在电脑前处理客户消息、电话、邮件、跟进记录的人,怎么才能不丢信息、不重复劳动、不靠脑子硬记”。

我当初启动这个项目,是因为手头同时维护几个外包项目的客户关系,微信里有合作方、有潜在客户、有供应商,加上邮件和电话,信息散落得到处都是。用 Excel 管理吧,写多了就乱;用在线 CRM,数据上了别人的服务器,导入导出又麻烦,而且很多功能对单人或小团队来说根本用不上。后来我花了三个周末,自己把桌面端沟通 CRM 的原型做出来了,前后迭代了四版,最终跑通了“联系人统一管理 + 沟通记录自动沉淀 + 待办提醒 + 轻量数据导出”这一整套流程。

如果你也属于下面几类人,这个项目思路可以直接参考:

  • 独立开发者、自由职业者,手上有多个客户需要长期跟进;
  • 小团队销售或客服,日常大量依赖微信、邮件、电话沟通;
  • 想从“每天打开十几个聊天窗口来回翻”的混乱状态里挣脱出来的人;
  • 准备自研内部工具,但不想一开始就上重型 CRM 系统的技术负责人。

DeskcommCRM 的定位不是取代 Salesforce 或 HubSpot,而是填补“个人桌面端轻量客户管理”这个空白。它把沟通行为本身当作数据核心,让每一次互动都留下可检索的记录,同时保持本地优先、数据自主可控。

1.1 桌面端 CRM 和网页端 CRM 的体验差异点

我一直在桌面上工作,浏览器里同时挂着微信网页版、邮件客户端、项目管理后台,再开一个 CRM 网页标签页的时候,内存和注意力都不够用。更重要的是,网页端 CRM 和本地通信工具之间的数据是割裂的。我需要手动复制聊天记录里的客户信息,再粘贴到网页表格里,操作成本很高,时间一长就会漏。

桌面端应用天然适合做“本地数据聚合”。它可以读取剪贴板、监听快捷键、在系统托盘里驻留,甚至可以直接绑定本地的邮件客户端或电话软件。哪怕只是把通信记录手动录入,桌面端也比网页端高效得多,因为桌面端可以有全局快捷键,随时呼出录入窗口,不用来回切换浏览器标签页。

所以 DeskcommCRM 从一开始就定了几条原则:本地优先、键盘优先、离线可用。默认不依赖云服务,所有数据存在本地 SQLite 中,导出格式兼容通用工具,用户随时可以把数据拿走。这套设计思路让项目的实现难度比在线 SaaS 低很多,但实际使用体验反而更顺手。

1.2 市面上已有工具,为什么还要自己动手

很多人会问我:Notion 不是能做客户管理吗?飞书多维表格也行啊,为什么要自己写?我的回答是:通用工具能做“记录”,但很难做“绑定”。

在 Notion 里建一个客户数据库、一个跟进记录数据库,确实可以管理信息。但每次记录跟进内容都要新建一条记录、手动关联客户、再填一堆字段,操作路径太长。而 DeskcommCRM 这种专门工具,可以让“选中联系人 - 打开沟通面板 - 记录本次沟通内容 - 自动关联时间线”变成一条顺畅的链路。

再加上通用工具里没有“沟通时间线”的概念。你跟进一个客户十次,每次沟通的上下文是什么?上次答应客户什么时间回复?这些问题在通用表格里很难直观体现。专门为沟通场景设计的 Schema 则天然支持这些需求。

2. 核心思路与整体设计拆解

DeskcommCRM 的整体设计可以概括为一句话:以联系人为中心、以沟通记录为时间线、以待办为驱动。

这个设计思路和传统 CRM 以“商机”为中心完全不同。传统 CRM 的起点是销售漏斗,先把潜在客户放进阶段里,然后预测成交概率。但实际做业务的人都知道,很多客户根本不适用漏斗模型,尤其是服务型工作、外包项目、长期顾问合作,客户关系是网状的,不是线性的。DeskcommCRM 更接近一个“客户关系笔记本”,而不是“销售预测仪表盘”。

2.1 为什么以“沟通时间线”为核心数据模型

大多数 CRM 把“客户信息”和“跟进记录”分开存储,两者之间只有一条外键关联。这种设计的问题是:查看客户详情时,你看到的是一大堆分散的字段,而不是一段连续的故事。而实际上,业务人员对客户的记忆是以故事形式存在的:“上周五聊过报价”“这周一确认了合同细节”“今天上午客户说要再考虑一下”。

因此,DeskcommCRM 的数据模型把沟通记录(Interaction)作为核心实体。每条记录都挂在一个联系人下,包含沟通方式、方向、时间、内容、相关附件、以及可选的待办事项。客户档案只是联系人属性集合,而沟通时间线才是真正的工作台。

这套模型的优势在于:

  • 任何时刻打开一个客户,你看到的是完整的互动脉络;
  • 新增记录的操作路径极短,不需要先想“这条属于哪个商机阶段”;
  • 搜索时可以直接搜沟通内容,而不是只能搜客户姓名。

2.2 模块划分:少即是多

最初设计模块时,我列了十几个功能点,包括报表、权限、多用户协同、发票管理、产品目录等。后来冷静下来,砍掉了大半。对于一个桌面端工具,真正的核心只有四个模块:

模块核心职责为什么必须有
联系人管理保存客户基本信息、标签、来源渠道所有业务关系的起点
沟通记录记录每一次电话、邮件、会议、聊天的时间线项目价值所在
待办提醒把沟通中产生的“下一步行动”变成可追踪的任务避免承诺落空
数据导入导出支持 CSV/Excel 互通保证数据主权,降低迁移成本

这四个模块之间不是孤立存在的。联系人下能看到时间线,时间线中的记录可以一键生成待办,待办完成后自动回写状态,导出时可以把联系人、时间线、待办一起打包成结构化文件。整个数据流是闭环的。

2.3 本地优先架构带来的取舍

本地优先(Local-first)是我刻意选的方向。所有数据存在本机 SQLite,应用启动不依赖网络,数据写入没有网络延迟。这带来的用户体验是:打开应用就是最新数据,查询响应速度按毫秒计。

代价也很明显:多设备同步需要自己实现。如果后续要支持手机端查看,就得加同步层。我的建议是,优先把本地版做扎实,再考虑嵌入一个简单的同步模块。同步不需要复杂,可以基于文件导出/导入或者一个简单的 REST API 推拉增量更新。真正重要的是数据格式从一开始就要设计成可序列化的,不要直接把 SQLite 文件当同步单元,否则跨设备合并时会有大量冲突。

3. 核心数据模型与关键实现细节

聊完整体思路,进入实操环节。这一部分我会展开讲解 DeskcommCRM 的数据表设计、关键字段的取舍、沟通记录与待办如何闭环,以及搜索索引怎么建。

3.1 联系人的表结构:不要过度设计

联系人表是最基础的表,但很多自研 CRM 最失败的地方也在这里——字段越加越多,最后变成了一个字段垃圾场。我的原则是:联系人表只放长期稳定不变的属性,可变状态放到单独字段中。

以下是 DeskcommCRM 使用的联系人表核心字段:

CREATE TABLE contacts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, company TEXT DEFAULT '', title TEXT DEFAULT '', email TEXT DEFAULT '', phone TEXT DEFAULT '', wechat TEXT DEFAULT '', tags TEXT DEFAULT '', source TEXT DEFAULT '', status TEXT DEFAULT 'active', notes TEXT DEFAULT '', created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE INDEX idx_contacts_name ON contacts(name); CREATE INDEX idx_contacts_tags ON contacts(tags);

tags 字段的设计当初犹豫了一阵。规范做法是单独建一张标签表做多对多关联,但考虑桌面单机场景,数据量撑死几千个联系人,用逗号分隔的文本标签完全够用。查询时用 LIKE 匹配即可。这不是最佳实践,但它是这个场景下的最简方案。如果你后续要做标签统计或者多用户协同,再把它拆成关联表也不迟。

3.2 沟通记录的存储设计:时间线展开

沟通记录表是整个系统的心脏。每条记录代表一次完整的沟通事件,包含沟通方式、方向、内容、关联的联系人,以及可选的关联待办。

CREATE TABLE interactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, contact_id INTEGER NOT NULL, type TEXT NOT NULL CHECK(type IN ('call','email','meeting','chat','note')), direction TEXT NOT NULL CHECK(direction IN ('inbound','outbound')), subject TEXT DEFAULT '', content TEXT DEFAULT '', happened_at TEXT NOT NULL, created_at TEXT NOT NULL, FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE ); CREATE TABLE todos ( id INTEGER PRIMARY KEY AUTOINCREMENT, contact_id INTEGER NOT NULL, interaction_id INTEGER, title TEXT NOT NULL, due_at TEXT, done INTEGER DEFAULT 0, created_at TEXT NOT NULL, FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE, FOREIGN KEY (interaction_id) REFERENCES interactions(id) ON DELETE SET NULL );

type 字段的限制用了 CHECK 约束,这是我在实践中很看重的一点。数据库层面的约束能避免很多脏数据,比如用户手滑把“call”录成“cal”时,应用可以提前拦截。同类字段一律在 DB 层加约束。

happened_at 和 created_at 分离也很重要。happened_at 是沟通实际发生的时间,created_at 是记录创建的时间。现实中经常有补录的情况,比如今天才想起来记昨天打的电话,这时候时间线上展示的应该是 happened_at,而不是 created_at。

3.3 沟通记录与待办的闭环流转

在 DeskcommCRM 的时间线界面里,每条沟通记录右侧有一个“创建待办”按钮。点击后,应用会打开一个待办弹窗,默认填充当前关联的联系人、日期,用户可以补充具体事项。保存后,这条待办会出现在全局待办面板中。当待办完成后,时间线列表里对应记录会显示一个对勾状态。

这个闭环逻辑很简单,但价值很大。传统做法是“沟通是沟通,待办是待办”,两边各记各的,没有任何关联。时间一长,你根本想不起来某条待办是从哪次沟通里产生的。有了 interaction_id 外键之后,反向追溯变得非常方便:看到一条待办,直接点进去就能看到它的上下文。

3.4 搜索:中文场景下的务实方案

桌面端 CRM 最常用的功能之一就是搜索。用户记住的往往不是客户全名,而是“上周聊报价那家”“住在朝阳区的王老师”之类的模糊信息。为了支持这种搜索需求,我建立了一个简单的全文检索机制。

对于英文,直接用 SQLite 的 FTS5 扩展做全文索引效果很好。但中文分词是个麻烦事。早期版本我试过 FTS5 + 手动分词,效果不理想,后来改成了更务实的方案:在“姓名 + 公司 + 沟通内容”上做基于 LIKE 的模糊匹配。虽然性能比 FTS 差一些,但在几千条记录的规模下,响应时间完全在百毫秒内,已经足够日常使用。

搜索时还会做同义词扩展,比如搜索“报价”,会同时匹配“报价”“价格”“报价单”等关键词。这个逻辑在应用层实现,不放进 SQL,方便后续维护。

4. 实操复盘:从零搭出第一个 DeskcommCRM 原型

下面重点说怎么从零把事情做出来。我会用 Electron + SQLite 这套组合展示实现路径,因为这是桌面端最快的起跑方式。如果你熟悉 Tauri、PyQt 或者 Flutter,思路完全通用,关键点不在框架,在产品逻辑。

4.1 技术选型:为什么是 Electron + SQLite

Electron 常被吐槽打包体积大、内存占用高,但选它有几个非常实在的理由:

  • 开发效率高:熟悉 HTML/CSS/JS 就能直接写桌面界面,不需要额外学 Qt 的布局系统;
  • 生态成熟:桌面端的文件读写、剪贴板、系统托盘都有现成库;
  • 跨平台一致:Windows / macOS / Linux 三端行为基本一致。

数据库用 SQLite,选的是 better-sqlite3。这个库是同步 API,代码写起来非常直观,不用嵌套回调。桌面单机场景下,数据量远达不到同步 API 的性能瓶颈,选同步反而省了很大的心。

4.2 目录结构与核心代码组织

项目目录这样来组织:

deskcomm-crm/ ├── main/ │ ├── index.js # Electron 主进程入口 │ ├── db.js # SQLite 数据库连接与初始化 │ ├── migrations/ # 数据库迁移脚本 │ └── services/ │ ├── contactService.js │ ├── interactionService.js │ └── todoService.js ├── renderer/ │ ├── index.html │ ├── styles/ │ └── js/ │ ├── app.js │ ├── contacts.js │ ├── timeline.js │ └── todoPanel.js └── package.json

db.js 的核心逻辑就是初始化表结构。每次应用启动时执行建表语句,用CREATE TABLE IF NOT EXISTS保证幂等。后续版本需要新增字段时,就写一个带版本号的 migration 脚本,依次执行。

4.3 一个核心功能全代码:给联系人添加沟通记录并自动生成待办

接下来看最关键的一条链路:选中联系人 -> 填写沟通内容 -> 保存 -> 生成待办。前端点击“保存”按钮后,调用渲染进程里的 JS 函数:

async function saveInteraction(payload) { const { contactId, type, direction, subject, content, createTodo, todoTitle, todoDueAt } = payload; const result = await window.api.createInteraction({ contactId, type, direction, subject, content, happenedAt: new Date().toISOString(), createTodo, todoTitle, todoDueAt }); if (result.id) { refreshTimeline(contactId); if (createTodo) refreshTodoPanel(); } }

主进程响应 IPC 调用,执行数据库事务:

function createInteraction(payload) { const db = getDb(); const insert = db.transaction((data) => { const info = db.prepare(` INSERT INTO interactions (contact_id, type, direction, subject, content, happened_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?) `).run(data.contactId, data.type, data.direction, data.subject, data.content, data.happenedAt, new Date().toISOString()); let todoId = null; if (data.createTodo) { const todoInfo = db.prepare(` INSERT INTO todos (contact_id, interaction_id, title, due_at, done, created_at) VALUES (?, ?, ?, ?, 0, ?) `).run(data.contactId, info.lastInsertRowid, data.todoTitle, data.todoDueAt, new Date().toISOString()); todoId = todoInfo.lastInsertRowid; } return { interactionId: info.lastInsertRowid, todoId }; }); return insert(payload); }

这一段代码里最重要的一点是把“插入沟通记录”和“插入待办”包在同一个事务里。如果先插入沟通记录成功、再插入待办失败,会导致数据不一致:有时间线记录但看不到待办。事务保证了要么全部成功,要么全部回滚。

4.4 时间线界面渲染逻辑

时间线界面要做的不是简单的列表,而是按日期分组的可视流水。我在前端用一个groupByDate函数把 interaction 列表按 happened_at 的日期维度分组,然后渲染成类似聊天软件的会话界面。

这个 UI 做得好不好,直接影响用户愿不愿意长期使用。如果界面太复杂,录入成本高,用户很快会放弃。我特别注意几个交互细节:

  • 默认按最近沟通时间排序联系人,越近越靠前;
  • 录入窗口支持全局快捷键(比如 Ctrl+Shift+K)呼出,焦点自动落到内容区域;
  • 保存成功后,会显示一条轻量提示,但不弹任何阻涩的确认对话框。

说起来这些都是小事,但实际使用体验区别巨大。一个每天都在用的工具,录入路径每多一步,都是在消磨使用意愿。

5. 关键功能验证:从数据从属关系到搜索效率

原型跑起来之后,我花了一段时间验证各个模块是否达到了预期的“顺手”标准。验证重点不是功能是否存在,而是用户心流是否顺畅。

5.1 数据从属关系测试:级联删除是否正确

联系人删除后的级联行为很关键。DeskcommCRM 在 contacts 表上开启了ON DELETE CASCADE,意味着删除联系人的时候,他名下的所有沟通记录和待办都会一起删掉。对单机个人工具来说,这个行为是合理的:数据不共享,不需要审计留存,删了就是删了。

为了保险,我在删除联系人时增加了一个二次确认弹窗,并且弹窗里会显示将要删除的记录数:

SELECT (SELECT COUNT(*) FROM interactions WHERE contact_id = ?) AS interaction_count, (SELECT COUNT(*) FROM todos WHERE contact_id = ?) AS todo_count;

这个查询把即将级联删除的数量展示给用户。如果用户看到这个客户下面有 50 条沟通记录,大概率会重新考虑一下。这个设计点很小,但非常实用,避免了不少误操作。

5.2 待办默认值和截止时间的交互设计

待办的默认截止时间我设置为“明天 18:00”。为什么要默认填一个截止时间?因为根据我自己的实际经验,待办如果没有截止时间,就会变成“无限期挂起”,最终和没有待办一样。强制给一个默认值,用户要么接受,要么改掉,但不会留空。

这个设计直接减少了无效待办的数量。后来我统计了一下自己连续两周的使用数据,未完成待办的比例明显降低,因为每次创建待办时都会顺手确认一下日期。

5.3 检索体验实测

项目里同时导入了大约 3000 条模拟数据,包括联系人、沟通记录、待办,然后做了一次检索验证。搜索“报价方案”关键词,响应时间为 120 到 200 毫秒,基本是即点即出。

比较意外的是,在 3000 条记录规模下,纯 LIKE 模糊搜索已经够用了。如果数据量涨到 10 万条以上,再考虑引入 FTS5 或者外部搜索引擎也来得及。对个人和小团队场景,没有必要一开始就建 Elasticsearch。索引越复杂,维护成本越高,收益却几乎为零。

6. 开发过程中的常见问题排查与踩坑记录

这个项目虽然整体不复杂,但开发过程中确实遇到了不少“案例级”的问题。我挑几个最有代表性的展开讲讲,这些问题在 Stack Overflow 上搜不到,只有在具体业务场景中才会触发。

6.1 Electron 渲染进程访问 SQLite 的权限问题

第一个版本里,我一度把 better-sqlite3 的实例直接暴露给了渲染进程,想着省掉 IPC 通信层。结果跨平台打包测试时,Windows 上报出了 node-gyp 编译异常,macOS 上则出现了数据库文件被占用的问题。

后来把数据库访问收回到主进程,渲染进程全部通过contextBridge暴露的异步 API 来操作数据。这样结构上更清晰,打包时也不会因为原生模块路径问题踩坑。

contextBridge.exposeInMainWorld('api', { createInteraction: (payload) => ipcRenderer.invoke('interaction:create', payload), getContacts: () => ipcRenderer.invoke('contact:list'), deleteContact: (id) => ipcRenderer.invoke('contact:delete', id) });

6.2 SQLite 在 Windows 下文件锁导致的写入失败

Tree 表在 Windows 下若同时打开多个窗口访问同一个库文件,有一定概率触发 “database is locked”。这是因为 better-sqlite3 默认的事务并发机制在跨进程访问时不够用。

解决方案包括:

  • 所有数据库写入操作集中在主进程,通过 IPC 串行访问;
  • 打开数据库连接时设置timeout: 5000,避免短期锁直接抛错;
  • 对高频写入操作开启 WAL 模式:
PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;

WAL 模式在桌面场景下性能优势明显,读写并发冲突大幅减少。副作用是目录下会多出-wal-shm文件,这是正常现象,备份时要注意将两个文件一起拷贝。

6.3 联系人列表滚动卡顿的优化

最初版本联系人列表没有做虚拟滚动,一次性渲染 3000 条 DOM 节点。在数据量小的时候还好,等到数据多了以后,每次输入的“搜索”都会卡顿。后来直接引入了虚拟滚动组件,只渲染可视区域的 20 到 30 行,性能瞬间上来了。

6.4 中文输入法的高频 bug:键盘事件冲突

Windows 下使用中文输入法的用户,在搜索框里输入关键词时,系统会先把拼音字母填进去,然后通过 composition 事件处理选字。如果监听 keydown 事件直接读event.target.value,很容易取到不完整的拼音内容。

解决办法是监听输入框的compositionstartcompositionend事件,在中文输入过程中不触发搜索,等选字结束后再执行查找:

let isComposing = false; input.addEventListener('compositionstart', () => { isComposing = true; }); input.addEventListener('compositionend', () => { isComposing = false; triggerSearch(input.value); }); input.addEventListener('input', () => { if (!isComposing) triggerSearch(input.value); });

这个问题是非常典型的桌面端中文场景坑。网页端业务可能碰不到,但桌面应用开发者一定要提前处理。

6.5 常见问题速查表

现象原因解决方案
数据库 updated_at 不更新应用层忘记在 UPDATE 中带上时间戳在 SQL 中显式写updated_at = datetime('now')
删除联系人后列表仍有残留前端列表没有监听数据库变更事件主进程统一广播数据变更事件,渲染进程刷新视图
待办重复创建按钮快速双击,触发了两次 IPC 调用为 IPC 调用加防抖,或保存前禁用按钮
导出 PDF 乱码字体未嵌入使用 Electron 打包字体文件,或用系统字体映射

7. 后续扩展方向:从单机工具到桌面协同中心

原型稳定之后,我开始思考 DeskcommCRM 后续往哪里走。一个项目如果想要长期使用,就必须有向前演进的路线,否则迭代停滞会慢慢失去使用价值。

7.1 通信工具的轻量接入

下一步打通微信/企业微信的聊天记录导入。虽然官方 API 限制很多,但可以通过导出聊天记录文件,在本地做解析后按联系人维度写入 DeskcommCRM。这样沟通记录的录入成本会进一步下降。语音通话记录、邮件客户端的历史邮件也可以按同样思路接入。

7.2 待办与系统日历联动

把待办事务同步到系统的 iCal 或者 Google Calendar。实现上不复杂:生成一个 ics 文件,系统日历订阅本地文件即可。这样每天的桌面提醒可以和我们已经习惯的日历通知结合在一起,减少多端切换。

7.3 轻量协同能力

如果需要小团队使用,可以做一个嵌入式 HTTP 服务,监听在局域网端口上。其他成员的电脑通过浏览器访问同一个实例,数据统一存在主机里。这个方案比完整的用户权限系统和云端同步简单得多。

个人体会是,做工具类项目最忌讳贪多。DeskcommCRM 从第一天起就坚持“先把个人场景做到极致,再考虑协同”,这样每一步迭代都有真实场景牵引,不会把时间浪费在想象出来的功能上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 9:25:38

DSA语法在IC验证环境中的应用:从自定义注解到自动化回归

做了快十年的 IC 集成与验证环境,我越来越觉得一个项目能不能顺利收敛,很多时候不是 RTL 写得有多好,也不是某个 testbench 的激励写得有多精巧,而是我们这些做环境、做流程、做工具链的人,能不能把设计意图、验证意图…

作者头像 李华
网站建设 2026/9/19 9:25:21

Textual 终端 UI 的 opacity 样式:让控件与背景色按透明度混合

Textual 终端 UI 的 opacity 样式:让控件与背景色按透明度混合 【免费下载链接】textual The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser. 项目地…

作者头像 李华
网站建设 2026/9/19 9:24:54

CANN pyasc 运行时配置指南:set_platform 与 Backend/Platform 枚举详解

CANN pyasc 运行时配置指南:set_platform 与 Backend/Platform 枚举详解 【免费下载链接】pyasc 本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。 项目地址: https://gitc…

作者头像 李华
网站建设 2026/9/19 9:24:46

把 WorkBuddy 的模型源切到 TaoToken 后怎么验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 9:24:43

Artificial Analysis:GLM 5.3 Flash 性价比散点,TaoToken 当默认供应商

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华