Imported from Mistruth/forfun (
AGENTS.md). Install upstream withnpx skills add Mistruth/forfun. Copyright stays with the author.
公众号长图生成工具
微信公众号文章可视化编辑器,通过块(Block)编辑器组装内容组件,实时预览并导出为高清长图(PNG/JPG)。
技术栈
- 框架: React 18 + Vite 5
- 样式: Tailwind CSS 3 + shadcn/ui(基于 Radix UI 原语)
- 路由: react-router-dom v6(HashRouter)
- 图片导出: html-to-image(toPng / toJpeg,pixelRatio: 2)
- Markdown 渲染: react-markdown + remark-gfm + rehype-raw
- 状态管理: React useState/useCallback/useMemo,无外部状态库
- 数据请求: @tanstack/react-query(已接入但当前页面未大量使用)
- 图标: lucide-react
- 通知: sonner(toast)
- 自定义字体: 阿里妈妈方圆体(AlimamaFangYuanTi)、阿里妈妈敏捷体(AlimamaAgile)
目录结构
├── index.html # 入口 HTML
├── vite.config.js # Vite 配置,端口 8080,@ 路径别名
├── tailwind.config.js # Tailwind 配置
├── components.json # shadcn/ui 组件配置
├── hmr-client.js # HMR 客户端
├── src/
│ ├── main.jsx # ReactDOM.createRoot 入口
│ ├── App.jsx # 根组件:QueryClientProvider + HashRouter + Routes
│ ├── nav-items.jsx # 路由表定义(/ → 桌面编辑器,/mobile → 移动端编辑器)
│ ├── index.css # Tailwind 指令 + @font-face 字体声明 + CSS 变量 + 动画
│ ├── lib/
│ │ └── utils.js # cn() 工具函数(clsx + tailwind-merge)
│ ├── assets/
│ │ ├── AlimamaAgileVF/ # 阿里妈妈敏捷体字体文件
│ │ └── AlimamaFangYuanTiVF/ # 阿里妈妈方圆体字体文件
│ ├── pages/
│ │ ├── Index.jsx # 桌面端编辑器页面(三栏布局:组件面板 + 块编辑器 + 预览区)
│ │ └── MobileEditor.jsx # 移动端编辑器页面(底部 Tab 切换编辑/预览)
│ └── components/
│ ├── BlockEditor.jsx # 块编辑器:渲染 blocks 列表,支持拖拽排序、上下移动、删除
│ ├── BlocksPreview.jsx # 块预览:按类型渲染每个 block(Markdown 用 ReactMarkdown,自定义组件用 renderFn)
│ ├── WechatStyleWrapper.jsx # 微信公众号样式包装器:注入微信文章的 CSS 样式
│ ├── ImageGenerator.jsx # 图片导出:将预览区 DOM 转为 PNG/JPG 下载
│ ├── CustomComponentDefinitions.js # 核心:自定义组件注册表(10 种组件)+ 分类 + 渲染函数 + 配置字段
│ ├── CustomComponentPanel.jsx # 左侧组件面板:搜索、分类筛选、预览、插入
│ ├── ComponentConfigDrawer.jsx # 右侧配置抽屉:根据 configFields 动态渲染表单控件
│ ├── TemplatePickerDialog.jsx # 模板选择弹窗
│ ├── Templates.js # 模板定义(目前包含「户外徒步活动推文」模板)
│ ├── MarkdownPreview.jsx # Markdown 预览组件
│ └── ui/ # shadcn/ui 基础组件(~50 个)
核心架构
Block 数据模型
编辑器接收一个 blocks 数组,每个 block 结构如下:
{
"id": "block_<timestamp>_<counter>", // 唯一 ID,生成时用递增计数器即可,如 block_1_1, block_1_2
"type": "markdown" | "custom", // markdown 为原始文本块,custom 为自定义组件
"content": "...", // type=markdown 时有效,Markdown 文本内容
"componentId": "...", // type=custom 时有效,对应下方组件 ID
"props": {} // type=custom 时有效,组件属性
}
页面布局
桌面端(Index.jsx):三栏布局
- 左侧:CustomComponentPanel 组件面板(可收起)
- 中间:BlockEditor 块编辑器
- 右侧:实时预览区 + ImageGenerator 导出按钮
- 右侧浮层:ComponentConfigDrawer 配置抽屉
移动端(MobileEditor.jsx):底部 Tab 切换
- 编辑 Tab:BlockEditor
- 预览 Tab:预览区 + 导出按钮
- 底部弹出层:组件选择面板
图片导出流程
- 用户点击导出按钮(ImageGenerator 或 MobileEditor 中的导出)
- 通过
document.querySelector找到预览区 DOM(.preview-content-for-export或.mobile-preview-export) - 调用
html-to-image的toPng()或toJpeg(),pixelRatio: 2 生成高清图 - 创建
<a>标签自动下载
模板系统(Templates.js)
模板是预定义的 blocks 数组,包含完整的组件配置。用户通过 TemplatePickerDialog 选择模板后,深拷贝并重新生成 block id 后替换当前编辑器内容。
开发与部署命令
本地开发
pnpm dev # 启动开发服务器(端口 8080)
pnpm build # 生产构建
pnpm preview # 预览生产构建
容器化部署
项目使用多阶段 Docker 构建(node:20),本地 docker-compose.yml 使用 build: 模式用于开发调试。
# 本地构建并启动(访问 http://127.0.0.1:9090/)
DOCKER_API_VERSION=1.43 PORT=9090 docker compose up -d --build
# 查看日志
DOCKER_API_VERSION=1.43 PORT=9090 docker compose logs -f app
# 查看状态
DOCKER_API_VERSION=1.43 PORT=9090 docker compose ps
# 停止服务
DOCKER_API_VERSION=1.43 PORT=9090 docker compose down
本地 Docker 默认将宿主机 9090 映射到容器内 8080,数据保存在 Docker 命名卷 app-data。当前本机 Docker CLI 与 daemon 版本不一致时需加 DOCKER_API_VERSION=1.43。完整本地流程见 本地 Docker 部署指南。
部署到 forfun 生产服务器时,将当前工作区上传到 admin@8.137.80.234 的临时构建目录,在生产机原生 linux/amd64 环境构建 forfun-app:latest。上线前必须为旧镜像创建回滚标签,然后在 /root/forfun 使用现有 Compose 配置重建容器。完整流程见 部署至 forfun 服务器指南。
参考文档
- 部署指南 — 上传当前工作区 → 生产机构建 amd64 镜像 → 备份旧镜像 → 重建容器 → 验证页面和 API 的完整流程。
- 本地 Docker 部署指南 — 本地 9090 端口构建、启动、验证、日志、停止和数据卷说明。
- UI 修改必看 — 视觉语言与交互规范。所有 UI 开发必须遵守:颜色 token、字号纪律、交互状态(hover/active/focus)、反模式清单、提交前检查清单。