被公众号排版折磨够了,我做了 feishu2wx:任何 Markdown 都能稳定发公众号

发布于 2026年07月13日 13:01 #微信#工具 原文链接

被公众号排版折磨够了,我做了 feishu2wx:任何 Markdown 都能稳定发公众号 封面图
  • 不止飞书,任何来源的 Markdown 都能排版发公众号,粘贴检测自动区分飞书 HTML 和纯 Markdown
  • 编辑器带 50 步撤销、快捷键、源码语法高亮和文章大纲,不止是个 textarea
  • 4 套主题 16 种字体,设置面板分组管理,引用块背景边框间距三项独立配置
  • 复制、导出 HTML、推送草稿箱三个出口共用同一套内联样式,结果一致
  • CLI 复用 Web 端渲染逻辑,同一套排版跑在浏览器、终端和 CI 三种环境

大家好,我是若风。

写公众号这件事,内容是一半,排版是另一半。

很多人在飞书或者本地编辑器里把文章写完,松一口气,结果一到公众号编辑器,噩梦才开始。代码块的语法高亮没了,表格挤成一坨,引用块变成普通文字,图片得一张张手动传。一篇 3000 字的技术文章,写花两小时,排版能再花四十分钟。

我自己被折磨够了,就写了个工具,叫 feishu2wx。

项目地址:github.com/wangruofeng/feishu2wx

网页地址 1 (Github Pages):https://blog.wangruofeng007.com/feishu2wx

网页地址 2 (Cloudflare Pages):https://feishu2wx.wangruofeng007.com/

👇 网站界面如下 👇

名字叫 feishu2wx,但它其实不止转飞书。不管你的内容是从飞书复制来的,还是在 VS Code、Cursor、Obsidian 里手写的 Markdown,甚至是别的 Markdown 工具渲染后复制过来的,都能排版成公众号能稳定显示的样子,复制、导出、或者直接推到草稿箱。

今天这篇,就把核心功能从头到尾梳理一遍。

一条管道,三个出口

feishu2wx 一条管道连接三个出口

先看整体。feishu2wx 的核心是一条数据处理管道。

飞书 HTML / Markdown 文本
 -> 粘贴检测,飞书 HTML 转成 Markdown
 -> Markdown 渲染成预览 HTML
 -> 内联样式注入 + 微信兼容处理
 -> 复制到剪贴板 / 导出 HTML 文件 / 推送草稿箱

重点在这条管道的设计。不管最后从哪个出口出去,中间走的都是同一套渲染逻辑。复制到公众号编辑器、导出一个 HTML 文件、直接推送到草稿箱,这三个动作产出的样式完全一致。你不会遇到「预览里好看,复制过去就变形」这种事。

下面顺着这条管道,一段一段讲。

输入端,不止飞书

feishu2wx 支持多种输入来源

工具叫 feishu2wx,但输入端不挑食。它支持三种内容进来。

飞书粘贴,自动识别

飞书粘贴内容的自动识别与转换

从飞书复制内容,剪贴板里带的是 HTML,而且带特征标记,data-larklarksuitefeishu.cn这些关键词。feishu2wx 检测到这些标记,就知道这是飞书来的,走专门的转换规则,把 HTML 转成 Markdown。

转换基于 Turndown 加 GFM 插件,但飞书有自己的方言,得单独适配。比如高亮用 ==text==语法,代码块的 HTML 结构有好几种写法,表格经常带colspan要补齐列数。这些不是装个插件就能搞定的。

纯 Markdown,直接吃

如果你根本不用飞书,在本地编辑器写 Markdown,直接粘贴进来就行。粘贴检测会判断,如果纯文本本身就是合法的 Markdown,就当 Markdown 处理,不会多此一举去转 HTML。

也支持导入本地 .md 文件,拖进来就完事。习惯在 Cursor、VS Code 里写东西的人,不用改工作流。

渲染后的 Markdown 页面,也能还原

还有种麻烦情况。你从某个已经渲染好的 Markdown 页面复制内容,比如别人的博客,剪贴板里的纯文本已经丢了 Markdown 语法,变成普通文字了。feishu2wx 会根据 HTML 结构判断,如果有h1-h6blockquotepre这些标签,但纯文本不像 Markdown 源码,就按 HTML 结构还原成 Markdown,而不是退化成普通文本。

当然,要是这些自动判断偶尔不靠谱,设置面板里有个「智能 HTML 转 Markdown」开关,关掉就只保留纯文本。工具应该听你的,不是替你做决定。

编辑器,不只是个 textarea

内容进来了,左边是编辑区。这个编辑区看着普通,其实不是简单的 <textarea>

50 步撤销,光标精确回位

它有自定义的撤销重做系统,保存 50 步历史记录,每一步都记了光标位置。按 Cmd+Z 撤销,光标会精确回到你编辑那个位置,而不是跳到文末。这个细节用惯了编辑器的人会很敏感,光标乱跳真的很烦。

快捷键一套

Cmd+B 加粗、Cmd+I 斜体、Cmd+U 下划线、Cmd+K 插入链接。如果光标处有选中文本,自动包裹语法,如果文本已经被包裹了,智能解包裹。按 Option+E 还能切换编辑和预览。所有快捷键在底部抽屉面板里一览无余。

点屏幕右下角 ? 唤起
点屏幕右下角 ? 唤起

源码语法高亮

编辑器里写 Markdown 的时候,源码本身有语法高亮。四种配色,GitHub、Dracula、Monokai,或者关掉。实现方式是 overlay 模式,在 textarea 下面叠一层高亮,文字透明只露着色,不影响你正常输入。写代码块、标题、列表的时候,颜色提示很直观。

源码语法高亮演示
源码语法高亮演示

文章大纲,长文导航

写长文容易迷路。编辑器底部有个大纲按钮,会解析文章里的 H1 到 H3 标题,跳过 frontmatter 和代码块里的标题,生成一个大纲列表。点哪一项,光标就滚到对应标题那行。几百行的文章,跳转很方便。

文章大纲
文章大纲

底部工具栏还塞了一堆快捷按钮,插入标题、列表、链接、引用,切换 H1 样式、图片模式、代码块风格,一键复制公众号,常用操作不用敲键盘。

顶部工具栏
顶部工具栏
底部工具栏
底部工具栏

排版系统,主题和细节都能控

feishu2wx 主题与细粒度排版设置

内容编辑好,右边预览区实时显示渲染效果,左边滚右边跟着滚。但光渲染出来不够,得能调样式。

4 套主题,深浅都支持

内置 4 套主题,经典黑白、橙色、蓝色、青绿。每套主题都有浅色和深色两种模式,还能跟随系统暗黑设置自动切换。经典主题的深色模式是独立调色的,不走 CSS 变量继承,颜色更可控。

主题一键切换
主题一键切换

每套主题定义了 9 个颜色值,主色、H1 到 H6 各级标题色、链接色、引用块边框色和背景色、表头背景和文字色。这些颜色贯穿整个排版链路,预览里用 CSS 类名,复制到微信时全部转成内联样式。

16 种字体,全免费无版权

支持 16 种字体,系统默认、微软雅黑、宋体、黑体,英文的 Arial、Helvetica、Georgia 这些,还有 Google Fonts 的 Roboto、Open Sans、Lato 等。全是免费无版权的,公众号商用也放心。

字体切换
字体切换

设置面板分组管理

设置入口在右上角,点开是一个分组面板。通用、标题、正文、引用块、图片、代码块、模板、公众号,八组分开管。字体、H1 样式、分割线、表格阴影、图片模式、代码块风格,都在对应分组里,不会堆成一坨。

设置面板
设置面板

细粒度开关,每个都是踩坑来的

除了换主题换字体,还有一堆细粒度开关。

H1 能不能显示底部横线,能不能反显(主色背景加白字,背景宽度随文字自适应,多行标题背景色还连续不断),居中还是左对齐。图片有默认、边框、阴影三种模式,还能开圆角。代码块有经典浅色和现代深色两种风格,现代那种带三个圆点头部的窗口样式。表格阴影、水平分割线,都能单独控。

引用块这块做得特别细,背景、边框色、间距三项独立配置。背景能开能关,边框色跟主题走,间距有宽松和紧凑两档。预览层和微信输出层都同步处理,不会预览里一个样复制过去又一个样。这三项在 Web 端和 CLI 里都能配,命令行用 —blockquote-background-mode—blockquote-color-mode—blockquote-height-mode三个选项。

预览,所见即所得

预览区不只看个大概。

双设备预览

电脑和手机两种模式切换。手机模式下内容以 420px 宽度居中,模拟真实阅读效果。很多排版在电脑上看着没问题,一缩到手机宽度就露馅,这个切换能提前发现问题。

手机端预览
手机端预览

全屏预览

全屏模式下,电脑预览内容 60% 宽度居中,手机预览 420px 居中,多余间隙都去掉,保持圆角样式。按 ESC 或点退出就回来。

电脑端预览
电脑端预览

图片查看器

预览区里的图片能点击放大查看。自动收集文章内所有图片,键盘左右箭头切换浏览,底部显示序号,比如 2 / 5。写图文并茂的文章时,检查图片很方便。

还能隐藏左侧源码,只看预览,专注排版效果。

图片查看器演示
图片查看器演示

三个出口,同一套内联样式

同一套内联样式通往三个出口

这是整个工具最核心的部分,也是最容易踩坑的地方。

为什么微信必须内联样式

Markdown 发布时面对的微信兼容边界

微信公众号编辑器对 HTML 的限制,比大多数人想的严格。CSS 类名全部失效,你写的 .code-block类复用不了,得给每个元素写完整的style属性。emremvh这些单位直接被吞掉,只认px。列表结构会被重新排列,精心设计的嵌套列表粘贴进去大概率打乱。

外部图片链接直接被过滤,不是微信 CDN 上的图片会被移除。SVG 完全不支持,连多色图标都显示不了。链接的标签颜色也会被吞掉。

这些问题单个看都不致命,加在一起,足以让任何想在公众号发技术文章的人崩溃。

出口一,一键复制

点复制按钮,把格式化后的内容复制到剪贴板,打开公众号编辑器按 Cmd+V 粘贴,样式尽量完整保留。

这一步用了三级回退策略。第一级是 Clipboard API 加 ClipboardItem,现代浏览器支持的方案,HTML 和纯文本同时写入剪贴板。第二级是 execCommand 加 contenteditable div,兼容方案,创建临时编辑区域选中后复制。第三级是 textarea 兜底,至少保证纯文本能出来。

还支持智能选区。你在预览区选中了部分内容,只复制选中部分,没选中就复制全文。

代码块的语法高亮有个讲究。highlight.js 生成的高亮是靠 CSS 类名实现的,但微信不认类名。feishu2wx 的做法是从预览区 DOM 里递归读取每个元素的getComputedStyle(),把计算后的颜色值直接写成内联样式。这样做最准确,但也是最耗性能的一步。

文章复制入口
文章复制入口

出口二,导出 HTML 文件

点导出按钮,把排版后的内联样式 HTML 下载成一个独立的 .html 文件。文件名自动取文章标题,frontmatter 的 title 字段或者正文首个 H1。下载的文件浏览器双击就能打开,样式和复制到公众号完全一致。

这个出口适合存档,或者给不用公众号的人看排版效果。

导出 HTML 入口
导出 HTML 入口

出口三,推送草稿箱

配置好公众号 AppID 和 AppSecret,点推送按钮,文章直接进公众号草稿箱,登录公众号后台就能看到。

推送流程是这样的。浏览器 localStorage 存凭证,推送时凭证随请求发送到 Cloudflare Functions,Functions 调用微信 API,获取 access_token,上传图片,创建草稿,返回结果。

推送草稿箱入口
推送草稿箱入口
推送草稿箱效果图
推送草稿箱效果图

微信标签保守基线

微信公众号编辑器没有公开的 HTML 标签白名单,很多标签「有时能用」不代表「值得依赖」。feishu2wx 在项目里明确了一份保守基线。

稳定依赖的标签,pspanstrongemul/ol/liaimgsectionblockquoteh1-h6table系列、hrsup/sub。不依赖的,figurefigcaptiondiv、复杂布局标签。不支持的,scriptstyleiframevideo

对应实现做了收敛。预览层可以保留更语义化的figurefigcaption,微信导出层自动降级成sectionimgp。不再把「标签语义正确」误认为「公众号里就一定稳定」。

图片处理

图片这块踩了不少坑。外部图片链接在复制时会被微信过滤,推送草稿箱时,后端会先把所有图片上传到微信素材库,再把 HTML 里的 URL 替换成微信 CDN 地址,并发上传最多 4 个。

WebP 图片微信正文图不支持,服务端归一化处理。静态 WebP 转 PNG,动态 WebP 转 GIF,通过 sharp 库逐级缩小尺寸直到低于 1MB。SVG 完全不支持,导出时自动通过 Canvas 光栅化成 PNG。

带说明文字的图片,预览层用figurefigcaption,复制到公众号时自动降级成sectionimgp,更稳妥。图片的上下间距由外层块统一控制,不在 <img> 上设上下 margin,避免微信里间距不一致。

推送草稿箱,凭证只存在你浏览器里

推送草稿箱这个功能,最值得说的是安全设计。

公众号 AppID 和 AppSecret,只存在用户浏览器的 localStorage 里,不经过任何服务端持久化存储。每次推送时凭证随请求发送到后端,后端只用来调用微信 API,用完即走,不存储。

多用户场景也没问题。每个人在前端配置自己的凭证,凭证存在各自浏览器里,互不干扰。几个人共用同一个在线版本,各自配置各自的,谁也看不到谁的。

推送时还有几个自动化。文章标题自动读取,优先取 frontmatter 的title字段,没有就取正文首个 H1,都没有显示「未命名文章」。封面图自动读取 frontmatter 的cover字段。文章首尾模板可以配置,推送或复制时自动拼接到正文前后,frontmatter 之后正文之前拼一个,正文之后再拼一个。

封面图没准备的话,工具自动生成。用文章第一张图片裁剪成微信要求的 2.35:1 比例。如果文章里没图片,就用渐变背景加标题文字生成一张。

注意 ⚠️:使用推送草稿箱功能,在第一次配置时需要去微信开发者平台https://developers.weixin.qq.com/platform,获取 AppID 和 AppSecret 配置到设置菜单底部,然后点击一次「推送」按钮,接口会报错,并且告诉你需要家白的 IP,将这个 IP 添加 IP 白名单,下次即可直接推送到微信公众号草稿箱。

公众号配置
公众号配置
AppID、AppSecret 获取 & API IP 白名单设置
AppID、AppSecret 获取 & API IP 白名单设置
测试推送到草稿箱
测试推送到草稿箱
通过服务器错误信息获取加白 IP 地址
通过服务器错误信息获取加白 IP 地址

CLI,把排版能力搬出浏览器

同一套排版逻辑运行在浏览器、CLI 和 CI

feishu2wx 最早就是个网页,打开粘贴预览复制。但真实使用场景开始往两个方向延伸,有人继续在网页里可视化排版,有人在本地编辑器、脚本、CI、批量发布流程里工作,希望同一套排版规则也能在命令行跑。如果能力锁在浏览器里,这个工具天花板就很低。

v1.19 之后补上了完整的 CLI。现在直接在终端能完成所有操作。

# 初始化配置
feishu2wx init --project

# 配置公众号凭证
feishu2wx auth set --app-id APPID --app-secret APPSECRET

# 设置主题和排版参数,引用块三维度和正文对齐都能配
feishu2wx theme set blue \
--show-h1-underline \
--align-h1-left \
--blockquote-background-mode theme \
--blockquote-color-mode theme \
--blockquote-height-mode compact \
--text-align-mode justify

# 渲染并预览,本地图片自动内联为 base64
feishu2wx render article.md --preview

# 直接推送到草稿箱
feishu2wx publish article.md --title "文章标题" --cover cover.jpg

CLI 的排版选项和 Web 端完全对齐。主题、字体、代码块样式、图片模式、H1/H2 反显和对齐这些常规项都有,引用块三项独立配置(背景、边框色、间距)和正文对齐也能在命令行里设。旧的 —show-blockquote-bg布尔开关保留兼容,但新的 —blockquote-background-mode优先级更高。

CLI 不是额外写了一堆命令行功能。渲染时在 Node.js 里用 JSDOM 模拟浏览器环境,然后调用 Web 端同一套 renderMarkdown()formatForWeChat() 函数,零逻辑重复。同一套排版能力现在跑在三种环境里,浏览器交互、本地终端、自动化脚本。

还支持从标准输入读取内容,方便管道操作。

cat article.md | feishu2wx render --preview

—copy选项把渲染后的 HTML 直接写入系统剪贴板,macOS 用pbcopy,Windows 用clip,Linux 用xclip,不用手动复制文件内容。

项目级配置,排版规则跟着仓库走

过去配置是用户级的,默认放在 ~/.feishu2wx/config.json。单人用没问题,但多人协作或多项目并行时,不同项目的排版偏好会互相污染。

现在支持项目级配置,放在项目根目录的 .feishu2wx/config.json,自动优先读取。配置读取优先级很清晰,CLI 参数大于环境变量,大于项目级配置,大于用户级配置,大于默认值。每一层都能覆盖上一层,但不会互相污染。

配置文件权限默认设为0o600,仅用户可读写,目录权限0o700,不会意外泄露凭证。还支持环境变量覆盖,FEISHU2WX_WECHAT_APP_IDFEISHU2WX_WECHAT_APP_SECRET可以覆盖配置文件里的值,方便 CI 环境。

这个变化把工具从「个人偏好面板」推进到了「项目工作流配置」,排版规则可以跟着仓库走,也能写进 CI 脚本。

值得注意的是,Web 端和 CLI 端配置是独立的。浏览器里设的主题字体存在 localStorage,CLI 不读,CLI 的配置存在文件系统,浏览器也不碰。两端互不干扰,各自维护偏好。

部署,双通道各取所需

项目采用双通道部署。

GitHub Pages,纯前端。每次推送到 main 分支自动部署,只包含排版和复制功能,不需要后端,适合只用排版功能的用户。

Cloudflare Pages,全栈。包含完整前后端功能,Cloudflare Functions 处理微信 API 的代理请求。两个部署通道共享同一个前端代码。

本地开发三种模式,npm start纯前端,npm run dev前端加 Express 后端,npm run cf:dev前端加 Cloudflare Functions 本地模拟。

写在最后

feishu2wx 最早只想解决一个很具体的问题,把飞书写的东西排版好看一点发到公众号。

但认真对待一个问题,它会把你带得更远。排版规则要可配置可复用,发布流程要可自动化可脚本化,配置要跟着项目走而不是锁在浏览器里。最后这个工具从一个网页小东西,变成了一套能从浏览器、命令行、CI 脚本三种入口使用的排版工作流。

核心就一句话,把飞书内容或者任何 Markdown,变成公众号能稳定显示的样子,少折腾。

而且它叫 feishu2wx,但不只转飞书。你在任何地方写的 Markdown,都能拿来用。

如果你也在为公众号排版头疼,欢迎试用。项目地址,github.com/wangruofeng/feishu2wx

觉得有用的话,给个 Star 就更好了。

评论互动

© 2026 王若风的技术博客 · Powered by Astro