<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>安然的代码笔记</title><description>分享 Vibe Coding 与测试开发实践</description><link>https://example.com/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>6.10.3</templateThemeVersion><templateThemeUrl>https://github.com/CuteLeaf/Firefly</templateThemeUrl><lastBuildDate>2026年5月17日 19:41:59</lastBuildDate><item><title>图片生成器项目实践：面向论文配图与教学交付的效率工具</title><link>https://example.com/posts/picture-generator-project-notes/</link><guid isPermaLink="true">https://example.com/posts/picture-generator-project-notes/</guid><description>记录一下这个图片生成器项目在工程上的几个关键设计：结构化输入、预览与正式生成分离、导出链路和配额边界。</description><pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h1&gt;图片生成器项目实践：面向论文配图与教学交付的效率工具&lt;a href=&quot;#图片生成器项目实践面向论文配图与教学交付的效率工具&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;线上地址：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://tools.anrdev.cn/&quot; target=&quot;_blank&quot;&gt;http://tools.anrdev.cn/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这段时间我重新梳理了一下自己在做的这个图片生成器项目。和一般的“在线画图工具”相比，它更像一个面向论文配图、课程设计和教学交付场景的图表工作台。对我来说，这个项目值得记录的地方，不是它支持了多少图种，而是它在工程上逐渐形成了几条比较清晰的设计原则。&lt;/p&gt;&lt;section&gt;&lt;h2&gt;一、问题不是“生成一张图”，而是“如何减少交付链路里的重复劳动”&lt;a href=&quot;#一问题不是生成一张图而是如何减少交付链路里的重复劳动&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果只从功能表面看，这个项目做的是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;ER 属性图&lt;/li&gt;
&lt;li&gt;学术三线表&lt;/li&gt;
&lt;li&gt;系统架构图&lt;/li&gt;
&lt;li&gt;流程图&lt;/li&gt;
&lt;li&gt;数据流图&lt;/li&gt;
&lt;li&gt;用例图&lt;/li&gt;
&lt;li&gt;类图&lt;/li&gt;
&lt;li&gt;时序图&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;但真正驱动我继续做下去的，不是“图种数量”，而是一个更具体的问题：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;当用户已经有了 SQL、表结构、字段说明或系统设计信息时，能不能不要再重复拖拽、重复排版、重复导出。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;所以这个项目从一开始就不是按自由绘图工具的逻辑去做的，而是按“结构化输入 -&amp;gt; 预览 -&amp;gt; 正式生成 -&amp;gt; 导出”的链路来组织。也正因为这样，它的很多实现选择都偏向工作流，而不是偏向画布交互。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;二、前端不是多个孤立页面，而是统一工作台&lt;a href=&quot;#二前端不是多个孤立页面而是统一工作台&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;前端基于 &lt;strong&gt;Vue 3 + Vite&lt;/strong&gt;，顶层入口在 &lt;code&gt;src/App.vue&lt;/code&gt;。这里一个很关键的决定是：没有把不同图种拆成完全独立的小应用，而是统一挂在同一个工作台壳层下。&lt;/p&gt;&lt;p&gt;这样做的直接收益有几个：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;登录、账户、反馈、管理后台只需要维护一套&lt;/li&gt;
&lt;li&gt;图种切换时可以复用相同的状态管理和交互模式&lt;/li&gt;
&lt;li&gt;配额、会员、导出、弹窗这些横切能力不用重复实现&lt;/li&gt;
&lt;li&gt;后续加新图种时，接入成本更可控&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果项目只是一个本地 demo，这种组织方式会显得偏重；但如果目标是持续迭代的线上工具，它反而能让演进路径更稳定。很多“越做越乱”的工具站，问题不在某个功能点，而在顶层没有把公共能力提前收拢。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;三、结构化输入是这个项目最重要的前提&lt;a href=&quot;#三结构化输入是这个项目最重要的前提&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我比较认同的一点是，这类工具如果要求用户从空白画布开始操作，效率往往并不高。真正能拉开体验差异的，通常是能不能直接消费已有结构。&lt;/p&gt;&lt;p&gt;以 ER 工作区为例，系统不是让用户重新画表，而是优先接 &lt;code&gt;CREATE TABLE&lt;/code&gt; 结构，然后走解析、预览和导出流程。这个思路本质上是在做一次“结构到图”的转换，而不是做一个通用画板。&lt;/p&gt;&lt;p&gt;这背后的取舍很明确：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;放弃一部分“完全自由”的编辑能力&lt;/li&gt;
&lt;li&gt;换取更稳定的生成结果&lt;/li&gt;
&lt;li&gt;让已有资料可以直接复用&lt;/li&gt;
&lt;li&gt;把时间花在交付结果上，而不是花在重复输入上&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这个思路在三线表工作区也一样成立。三线表最难的部分通常不是“画出来”，而是“导出的结果能不能直接放进 Word 或论文里”。所以它更像是一个面向交付的结构化文档生成问题，而不是一个普通表格编辑问题。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;四、预览和正式生成分离，是我最想保留的一个边界&lt;a href=&quot;#四预览和正式生成分离是我最想保留的一个边界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个项目里有一个我现在依然觉得很重要的设计：&lt;strong&gt;预览接口和正式生成接口分离&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;原因不复杂。只要项目里开始出现配额、会员、导出、AI 调用这些生产级规则，预览和正式生成就不应该继续混在一起。&lt;/p&gt;&lt;p&gt;项目里的接口封装大致是这样：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parseDiagramByServer&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;requestJson&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/api/preview/diagram/parse&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generateParsedDiagramByServer&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;requestJson&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/api/generate/parse&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generateSqlDiagramByServer&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;requestJson&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/api/generate/sql-parse&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这段代码表面上只是多了几个路径，但它实际解决的是接口语义和业务边界问题：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;预览阶段允许低成本试错&lt;/li&gt;
&lt;li&gt;正式生成阶段才进入额度消耗链路&lt;/li&gt;
&lt;li&gt;前端调用时不会把“看看效果”和“真正产出”混成一个动作&lt;/li&gt;
&lt;li&gt;后端更容易接入日志、计费、风控和异常补偿&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;很多工具项目后期难维护，原因往往不是算法复杂，而是链路边界一开始就没有分开。预览、生成、导出、计费混在一起以后，任何规则变更都会影响整条链路。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;五、后端真正承担的是规则协调，而不是单纯转发请求&lt;a href=&quot;#五后端真正承担的是规则协调而不是单纯转发请求&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后端目录在 &lt;code&gt;license-server/&lt;/code&gt;，入口是 &lt;code&gt;proxy.js&lt;/code&gt;。虽然名字看起来像代理层，但它实际承担的是一层比较完整的业务协调逻辑。&lt;/p&gt;&lt;p&gt;从当前结构看，后端至少已经拆成了这些模块：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;services/parse&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/layout&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/export&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/auth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/redeem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/share&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/feedback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这类拆分说明一件事：项目的问题域已经不是“请求进来，生成一张图再返回”这么简单了。它还要处理：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;身份状态&lt;/li&gt;
&lt;li&gt;使用额度&lt;/li&gt;
&lt;li&gt;正式生成与预览的边界&lt;/li&gt;
&lt;li&gt;导出失败时的补偿&lt;/li&gt;
&lt;li&gt;反馈和运营链路&lt;/li&gt;
&lt;li&gt;兑换码和会员逻辑&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;也就是说，真正把项目复杂度拉上去的，不是图种本身，而是这些围绕线上工具运行而出现的规则系统。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;六、AI 在这里更像结构化处理器，而不是聊天入口&lt;a href=&quot;#六ai-在这里更像结构化处理器而不是聊天入口&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个项目也接了 AI，但它不是以对话为中心来设计的。我更愿意把它看成一个嵌入工作流的结构化处理器。&lt;/p&gt;&lt;p&gt;它更适合做的事是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;优化原始 SQL&lt;/li&gt;
&lt;li&gt;收敛字段数量&lt;/li&gt;
&lt;li&gt;补字段注释&lt;/li&gt;
&lt;li&gt;把不规整的输入转换成更容易解析的结构&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这类场景下，AI 的价值不在于“能聊”，而在于它能不能帮用户少做几轮机械调整。只要它能把脏输入整理到更适合后续解析和导出的状态，它就是有效的。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;七、从工程形态看，它已经更接近一个垂直 SaaS&lt;a href=&quot;#七从工程形态看它已经更接近一个垂直-saas&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;虽然我还习惯叫它“图片生成器”，但从系统形态看，它已经不只是一个单点工具。除了图表工作区本身，它还在持续吸收这些能力：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;游客与注册用户的额度体系&lt;/li&gt;
&lt;li&gt;会员无限制逻辑&lt;/li&gt;
&lt;li&gt;登录、会话、账户中心&lt;/li&gt;
&lt;li&gt;分享活动与奖励&lt;/li&gt;
&lt;li&gt;兑换码系统&lt;/li&gt;
&lt;li&gt;反馈管理&lt;/li&gt;
&lt;li&gt;后台管理&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这意味着它的核心问题已经从“功能能不能做出来”转成“这套工具如何长期在线运行”。这也是为什么我现在更愿意从工作流、权限边界和可维护性来审视它，而不是只看图表渲染效果。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;八、现阶段最值得继续打磨的点&lt;a href=&quot;#八现阶段最值得继续打磨的点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果后面继续投入，我更关注的不会是机械地增加图种，而是下面这些更偏工程质量的方向：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;生成结果的稳定性&lt;/li&gt;
&lt;li&gt;导出结果的交付质量&lt;/li&gt;
&lt;li&gt;预览与正式生成链路的一致性&lt;/li&gt;
&lt;li&gt;AI 辅助的可控性&lt;/li&gt;
&lt;li&gt;用户在真实交付流程中的卡点&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;对这类项目来说，真正决定体验上限的，往往不是“会不会画”，而是“会不会在最后一步掉链子”。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;结语&lt;a href=&quot;#结语&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次回头看这个项目，我比较确定的一点是：它要解决的并不是“如何在线画图”，而是“如何把已有结构更高效地变成可交付结果”。&lt;/p&gt;&lt;p&gt;从工程视角看，比较关键的几件事其实已经逐渐清晰了：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;用结构化输入替代重复拖拽&lt;/li&gt;
&lt;li&gt;用统一工作台承载多图种能力&lt;/li&gt;
&lt;li&gt;用预览/正式生成分离守住业务边界&lt;/li&gt;
&lt;li&gt;用后端规则系统托住额度、导出和运营逻辑&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;后面即使继续扩功能，我也更倾向于守住这些边界，而不是把它重新做回一个“大而杂的在线画板”。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>编写了一个根据网站内容生成广告图的Skill，成功调用image2模型</title><link>https://example.com/posts/website-ad-skill-image2/</link><guid isPermaLink="true">https://example.com/posts/website-ad-skill-image2/</guid><description>记录一下这个 Skill 的技术实现思路：先做首页内容理解，再做营销表达重组，最后把结构化结果送进 image2 完成广告图生成。</description><pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h1&gt;编写了一个根据网站内容生成广告图的Skill，成功调用image2模型&lt;a href=&quot;#编写了一个根据网站内容生成广告图的skill成功调用image2模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;最近我补了一个比较有意思的 Skill，目标不是直接“生成一张图”，而是把一个网站首页转换成一套更适合宣传和投放的广告素材。&lt;/p&gt;&lt;p&gt;如果把这个过程拆开看，它其实是一条很明确的链路：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;读取网站首页内容&lt;/li&gt;
&lt;li&gt;提取产品定位、卖点和页面语气&lt;/li&gt;
&lt;li&gt;重组为广告标题、短文案和视觉方向&lt;/li&gt;
&lt;li&gt;组织出图提示&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;image2&lt;/code&gt; 模型生成广告图&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这篇文章想记录的重点，不是“我又接了一个模型”，而是这个 Skill 在技术上怎么把“网页内容理解”和“图像生成”接成了一条稳定链路。&lt;/p&gt;&lt;section&gt;&lt;h2&gt;一、这个问题的关键，不是生图，而是先把网页理解对&lt;a href=&quot;#一这个问题的关键不是生图而是先把网页理解对&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;很多人第一反应会把这个 Skill 理解成一个广告图生成器，但真正难的部分并不在最后的出图，而在前面那一步：&lt;strong&gt;如何从网站首页里提取出适合营销表达的内容结构。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;网站首页里的文案通常面向产品介绍，特点是信息完整，但不一定适合直接转成投放素材。它可能有这些问题：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;信息分散在多个区块&lt;/li&gt;
&lt;li&gt;语气偏产品说明，不够适合传播&lt;/li&gt;
&lt;li&gt;重点信息和次要信息混在一起&lt;/li&gt;
&lt;li&gt;页面视觉风格和文案风格不一定天然适合广告图&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以这个 Skill 的第一阶段，实际上更接近一次针对首页的结构化分析，而不是一次简单摘要。&lt;/p&gt;&lt;p&gt;我要拿到的不是“这个页面讲了什么”，而是下面这些更适合后续生成的问题答案：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;这个网站到底在卖什么&lt;/li&gt;
&lt;li&gt;首页最想强调的能力点是什么&lt;/li&gt;
&lt;li&gt;哪一句话最接近核心卖点&lt;/li&gt;
&lt;li&gt;页面更适合什么样的视觉调性&lt;/li&gt;
&lt;li&gt;适合产出海报、封面图还是社媒宣传图&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果这一步拿错了，后面文案和图像都会偏掉。也就是说，&lt;strong&gt;前置理解质量决定了后续整条链路的上限&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;二、文案生成阶段，本质上是在做“营销表达重组”&lt;a href=&quot;#二文案生成阶段本质上是在做营销表达重组&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在这个 Skill 里，我没有把网页原文直接喂给图像模型，而是加了一层中间结构，把页面信息先整理成广告表达需要的字段。&lt;/p&gt;&lt;p&gt;这一层通常会产出这些内容：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;主标题&lt;/li&gt;
&lt;li&gt;副标题&lt;/li&gt;
&lt;li&gt;卖点短句&lt;/li&gt;
&lt;li&gt;行动按钮文案&lt;/li&gt;
&lt;li&gt;画面关键词&lt;/li&gt;
&lt;li&gt;目标受众描述&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这个阶段我更关心的是“重组”，不是“润色”。&lt;/p&gt;&lt;p&gt;因为网页文案和广告文案的目标并不一样。前者追求完整表达，后者追求快速传达和点击意图。所以这里本质上做的不是改写句子，而是把原始页面内容重新映射到一个更适合营销场景的结构上。&lt;/p&gt;&lt;p&gt;从技术上看，这个中间层非常重要。它相当于把原本松散的网页内容，压缩成后续图像生成可以稳定消费的输入格式。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;三、为什么我坚持把“文案生成”和“图像生成”拆成两步&lt;a href=&quot;#三为什么我坚持把文案生成和图像生成拆成两步&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果只是为了快速出图，完全可以把网站信息和一句“请生成广告图”的提示词直接扔给模型。但我没有这么做，原因主要有两个。&lt;/p&gt;&lt;p&gt;第一，链路不可控。&lt;/p&gt;&lt;p&gt;如果页面理解、卖点提炼、画面表达都挤在一次生成里，最后很难判断到底是哪一步出了问题。图不好看，可能是视觉提示不够；文案不准，可能是卖点提取错了；语气不对，也可能是页面理解偏了。&lt;/p&gt;&lt;p&gt;第二，可复用性差。&lt;/p&gt;&lt;p&gt;把中间层显式抽出来后，广告标题、短文案和画面方向都可以单独复用：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;可以只取文案，不出图&lt;/li&gt;
&lt;li&gt;可以同一套卖点生成多版画面&lt;/li&gt;
&lt;li&gt;可以在不同平台上替换不同语气&lt;/li&gt;
&lt;li&gt;可以后续再接别的图像模型&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以这一步其实是在做一个很典型的工程取舍：&lt;strong&gt;牺牲一点链路长度，换可解释性和可复用性。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;四、成功接通 image2，意味着这条链路终于闭环了&lt;a href=&quot;#四成功接通-image2意味着这条链路终于闭环了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我在标题里强调“成功调用 image2 模型”，不是想突出模型名字本身，而是因为这代表流程已经从“理解和整理”走到了“实际出图”。&lt;/p&gt;&lt;p&gt;这件事的意义在于，Skill 的定位发生了变化。&lt;/p&gt;&lt;p&gt;在只做网页理解和文案生成的时候，它更像一个内容辅助器；接通 &lt;code&gt;image2&lt;/code&gt; 之后，它才真正具备了素材生成能力。也就是从：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;页面分析结果&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;变成了：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;页面分析结果&lt;/li&gt;
&lt;li&gt;广告表达结果&lt;/li&gt;
&lt;li&gt;可直接预览的广告图结果&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;一旦最后一步打通，整个 Skill 才不只是“给建议”，而是开始真正产出可用素材。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;五、这个 Skill 的本质，不是“AI 生图”，而是“内容到素材”的转换器&lt;a href=&quot;#五这个-skill-的本质不是ai-生图而是内容到素材的转换器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;从工程视角看，这个 Skill 更准确的定位不是图片生成器，而是一个 &lt;strong&gt;内容到素材的转换器&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;它的输入不是一段手写 prompt，而是一个真实网站首页。它的输出也不应该只是一张随机图，而应该是一套围绕页面定位展开的宣传素材。&lt;/p&gt;&lt;p&gt;它想解决的问题其实是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;网站已经有内容了，为什么还要重复手工整理成广告语&lt;/li&gt;
&lt;li&gt;卖点已经在页面里了，为什么还要重新总结视觉方向&lt;/li&gt;
&lt;li&gt;文案已经成型了，为什么还要再单独写一版出图提示&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果这几步都能被串起来，Skill 的价值就不只是“快”，而是减少了内容生产流程里的上下文切换。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;六、为什么适合做成 Skill，而不是一次性脚本&lt;a href=&quot;#六为什么适合做成-skill而不是一次性脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我最后把这套东西做成 Skill，而不是简单的本地脚本，核心原因是它的输入模式非常稳定。&lt;/p&gt;&lt;p&gt;只要对象还是“网站首页”，这套流程就能重复使用：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;个人博客&lt;/li&gt;
&lt;li&gt;产品官网&lt;/li&gt;
&lt;li&gt;SaaS 首页&lt;/li&gt;
&lt;li&gt;工具落地页&lt;/li&gt;
&lt;li&gt;活动宣传页&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;做成 Skill 之后，后续扩展空间也更大。比如：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;按平台区分文案语气&lt;/li&gt;
&lt;li&gt;按页面类型区分视觉风格&lt;/li&gt;
&lt;li&gt;输出多套广告方案而不是单一结果&lt;/li&gt;
&lt;li&gt;接入更多图像模型或审核规则&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这说明它不是一个一次性的 prompt 模板，而是一条可以持续演进的工作流。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;七、这次实践里最有价值的，不是单次结果，而是链路分层已经成立&lt;a href=&quot;#七这次实践里最有价值的不是单次结果而是链路分层已经成立&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我现在回头看，这次最值得记录的不是某一张图生成得多漂亮，而是这条链路的分层已经比较清楚了：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;页面理解&lt;/li&gt;
&lt;li&gt;营销表达重组&lt;/li&gt;
&lt;li&gt;视觉方向组织&lt;/li&gt;
&lt;li&gt;图像模型调用&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这几层一旦拆清楚，后面无论是改 prompt、换模型、加平台规则，还是做失败重试，都会更容易落地。&lt;/p&gt;&lt;p&gt;很多 AI 工具一开始能跑，但越改越乱，根本原因通常不是模型不行，而是链路没有分层，导致所有问题都堆在一次生成里。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;八、后面还值得继续打磨的点&lt;a href=&quot;#八后面还值得继续打磨的点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后续如果继续往下做，我更关心的会是这些偏工程质量的问题：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;首页理解的稳定性&lt;/li&gt;
&lt;li&gt;文案表达和页面调性的一致性&lt;/li&gt;
&lt;li&gt;出图风格与产品定位的匹配程度&lt;/li&gt;
&lt;li&gt;同一页面多方案生成的可控性&lt;/li&gt;
&lt;li&gt;失败场景下的降级策略&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;尤其是“页面理解错了怎么办”这个问题，后面大概率还要继续加约束和校验。因为对这类 Skill 来说，最怕的不是模型出图慢，而是前置语义已经偏掉了，后面整条链路都在放大错误。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;结语&lt;a href=&quot;#结语&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次写这个 Skill，对我来说更像是在验证一个思路：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;能不能把网站首页里已经存在的信息，直接拉进一条可执行的广告素材生成链路。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在看来，这件事至少已经具备了一个比较完整的最小闭环：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;先理解页面&lt;/li&gt;
&lt;li&gt;再重组表达&lt;/li&gt;
&lt;li&gt;最后调用 &lt;code&gt;image2&lt;/code&gt; 出图&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;后面即使继续扩展，我也更倾向于保持这种分层方式，而不是把它重新做回一个“什么都塞进一次 prompt”的黑盒流程。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item></channel></rss>