资讯详情

资讯详情

awesome-design-md:用标准Markdown定义AI可解析的界面契约

1. 这不是普通 Markdown而是一份“AI 界面行为契约”你有没有试过让 AI 帮你画一个登录页它确实给你生成了 HTML、CSS甚至带上了圆角按钮和渐变背景——但按钮文字写的是“Submit Form”表单字段叫“user_input_1”密码框没加 typepassword提交逻辑全靠注释写着“这里要连后端”。你复制粘贴进项目发现根本跑不起来删掉重写又得从头描述需求。更糟的是你反复强调“用 Tailwind 而不是 Bootstrap”它下一次还是塞进一堆 classbtn btn-primary。这不是 AI 不够聪明而是它根本没收到“界面设计”的明确指令边界。它默认按通用文本生成逻辑工作追求语义通顺、结构完整、语法正确——但界面不是散文它是可执行的、有约束的、需与开发链路对齐的工程产物。而awesome-design-md这个命名恰恰点破了本质它不是一份文档而是一套轻量级、可读性强、机器可解析的界面设计协议。它的核心不在“写得多美”而在“写得足够窄”——窄到 AI 不敢越界窄到前端工程师打开就能直接拆解成组件树窄到产品经理确认后无需二次转译。我第一次在 GitHub 上看到AGENTS.md这个文件名时以为是某种自动化代理说明。点开才发现它用纯 Markdown 的标题层级、列表嵌套和代码块定义了一个对话式表单的完整状态机## 触发条件下明确写了“仅当用户输入邮箱且未填写手机号时激活”### 字段定义 email里不仅写了 label 和 placeholder还标注了validation: email, required和error_message: 请输入有效邮箱最关键的是### 渲染规则部分用缩进式列表规定了不同状态下的 DOM 结构“✅ 已验证 → 显示绿色勾号图标 text-green-600类”、“⚠️ 格式错误 → 输入框加红边 下方显示 error_message”。这让我意识到真正限制 AI “瞎编”的从来不是模型能力而是输入指令的表达精度与结构刚性。awesome-design-md不是教 AI 怎么设计而是教人怎么把设计意图翻译成 AI 能严格遵循的“最小可行契约”。它不依赖任何插件、不绑定特定平台、不引入新语法——就靠标准 Markdown 的语义标签#,-, 却实现了比 Figma 插件或设计系统文档更强的执行保真度。如果你正在用 Coze、Dify 或自建 Agent 做产品原型或者需要让实习生快速把 PRD 转成可运行的 UI 片段这份文件就是你的第一道质量防火墙。它适合所有角色设计师用来固化交互逻辑前端用来提取原子化配置AI 提示工程师用来构建结构化 prompt 模板——只要你的工作流里存在“人→AI→代码”这个链条它就值得你花 15 分钟读完并复用。2. 为什么是 Markdown而不是 JSON/YAML/DSL很多人第一反应是既然要结构化为什么不直接上 JSON写个 schema用 Ajv 校验再配个 TypeScript 接口岂不更严谨这想法没错但在真实协作场景中它会卡在三个致命环节2.1 协作成本谁来写、谁来改、谁来审JSON 是给机器看的不是给人看的。我见过团队用 JSON 定义表单字段结果产品经理提需求时说“把‘姓名’字段改成必填加个身份证号校验。” 开发打开文件找到name: { required: false }改成true再往下翻三屏找到id_card: { ... }补上正则表达式。但第二天设计师反馈“身份证号字段的 placeholder 写错了应该是‘请填写18位身份证号码’。”——这时没人记得 placeholder 在哪个 key 下只能全局搜索placeholder结果搜出 17 处还得逐个比对上下文。而 Markdown 的天然优势在于人类可读性即编辑友好性。awesome-design-md的字段定义长这样### 姓名 - label: 姓名 - placeholder: 请输入您的真实姓名 - required: true - validation: chinese, max_length:20 - error_message: 姓名不能为空且最多20个汉字产品经理改需求直接 CtrlF 找“姓名”改完保存设计师检查文案一眼扫完所有- placeholder:行前端提取配置用正则/-\splaceholder:\s([^])/g就能批量导出。没有 schema 解析器没有 IDE 插件VS Code 自带的 Markdown 预览就能实时看到层级关系。它把“修改门槛”从“懂 JSON 结构”降到了“会打字”。2.2 工具链兼容性不造轮子只用现成轮子你不需要为awesome-design-md单独开发解析器。它所有结构都基于 Markdown 基础语法#/##/###对应界面层级页面 → 区块 → 组件-列表项对应字段或状态分支code block固定用于存放可执行代码片段如 CSS 类名、JS 事件处理函数签名表格用于对比多状态渲染规则比如 hover/focus/active 三种状态下的 class 变化。这意味着Git diff 清晰可见变更点JSON 的 diff 常因格式空格错乱而失效GitHub/GitLab 原生渲染支持PR 评论可直接锚点定位到某一行VS Code 的 Markdown All in One 插件自动提供大纲导航点击## 表单区块就跳转甚至可以用 Pandoc 直接转成 PDF 给客户签字确认。我实测过一个含 12 个字段、3 种状态、2 个动态条件的表单定义文件在 GitHub 上的 diff 显示为 4 行增删换成等效 JSONdiff 达到 27 行且大量{}符号干扰关键信息识别。工具链越成熟越该尊重它的原始能力而不是强行嫁接新范式。2.3 AI 解析鲁棒性对抗“自由发挥”的终极防线这是最关键的一点。LLM 对 JSON 的解析存在固有脆弱性一旦格式出错少个逗号、引号不匹配整个解析失败更麻烦的是它常会“好心”补全缺失字段——你只写了label: 用户名它可能自动生成type: text, required: false而你根本没授权它做这个判断。但 Markdown 不同。它的语法容错率极高缺少-前缀AI 仍能识别为列表项Markdown 解析器会自动修正标题层级跳级##后直接####渲染无误大纲依然可读代码块语言标识缺失内容照常保留不影响语义提取。更重要的是你可以用 Markdown 的“视觉锚点”强制约束 AI 输出格式。比如要求 AI 严格按以下结构输出## 页面名称 描述本页面用于用户注册需兼容移动端 ### 区块基础信息 - 字段姓名 - label: 姓名 - type: text - 字段邮箱 - label: 邮箱地址 - type: email - validation: required, format:emailAI 生成时会本能地对齐缩进、复用相同关键词label/type/validation因为它的训练数据里这类结构化 Markdown 出现在大量技术文档中。而 JSON 没有这种视觉惯性它更倾向生成紧凑、压缩的格式反而增加了解析难度。我们不是在教 AI 新语法而是在利用它已有的阅读习惯把它框进最熟悉的表达路径里。3.awesome-design-md的核心结构与实操细节别被名字唬住——它没有神秘语法所有能力都来自对标准 Markdown 的精准组合。下面拆解我在 3 个真实项目中沉淀出的最小可行结构每个部分都附带“为什么这么设计”和“踩过的坑”。3.1 顶层框架用标题层级定义界面拓扑awesome-design-md的骨架完全由#到####构成每一级都有明确语义# 登录页v2.3 适用场景Web 端主流程入口需支持微信扫码快捷登录 ## 区块账号密码登录 ### 组件邮箱输入框 ### 组件密码输入框 ### 组件登录按钮 ## 区块第三方登录 ### 组件微信扫码按钮 ### 组件Apple ID 按钮 ## 区块辅助链接 ### 文案忘记密码 ### 文案还没有账号立即注册为什么用标题不用表格表格适合横向对比如不同状态下的样式但界面是树状结构。标题层级天然映射 DOM 的嵌套关系#是body或main##是section###是div classcomponent。前端用document.querySelectorAll(h3)就能拿到所有组件节点再通过nextElementSibling获取其下的配置列表——零解析成本。实操注意#必须唯一代表整个界面实例版本号v2.3写在标题末尾便于 Git Tag 关联##名称要带业务语义如“账号密码登录”而非“表单区块”避免开发猜意图###必须以“组件”或“文案”开头这是后续自动化提取的关键标记——我的 Python 脚本就靠正则r### (组件|文案)(.)来分类。提示别用####定义子组件曾有个项目把“密码强度提示条”设为####结果脚本误判为独立组件生成了多余的 DOM 容器。记住###是最小可交付单元再细的粒度如图标、文字颜色用列表项描述。3.2 字段定义列表嵌套实现配置原子化每个### 组件xxx下必须跟一个无序列表每项是一个配置维度。重点在于用冒号分隔键值且键名全部小写下划线### 组件邮箱输入框 - label: 邮箱地址 - placeholder: 请输入常用邮箱 - required: true - validation: email, required, max_length:100 - error_message: 邮箱格式不正确请检查 - css_class: input-email w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 - js_event: blur → validateEmail()为什么键名强制小写下划线这是为了消除大小写歧义。AI 有时会输出Required: true或ERROR_MESSAGE导致解析脚本漏匹配。统一小写后正则r-\s(\w):\s(.)稳定捕获所有键值对。validation字段用英文逗号分隔是因为它本质是规则集合不是单值——email, required比[email,required]更易读且逗号在 Markdown 列表中不会触发换行。实操心得css_class值不加引号引号会让 AI 误以为是字符串字面量实际生成时可能带多余空格。直接写input-email w-full...前端用className.split( )即可转数组js_event用→符号而非因为在 Markdown 中会被某些解析器误判为引用块。blur → validateEmail()语义清晰且→在键盘上比更易输入所有布尔值用true/false禁用yes/no或1/0——LLM 对true/false的识别准确率超 99%其他形式常被忽略。3.3 状态与条件用缩进列表表达逻辑分支界面最复杂的部分是状态切换如输入框获得焦点时边框变色和条件渲染如“当用户选择‘企业用户’时显示税号字段”。awesome-design-md用缩进列表模拟 if-else 逻辑### 组件邮箱输入框 - label: 邮箱地址 - state: default - css_class: border-gray-300 - state: focus - css_class: border-blue-500 ring-1 ring-blue-200 - state: error - css_class: border-red-500 - error_message: 邮箱格式不正确 ### 区块企业信息条件显示 show_if: user_type enterprise - 字段公司名称 - 字段统一社会信用代码为什么用 show_if:而不是show_if: ...是 Markdown 引用块语法它在渲染时会缩进显示视觉上与上方###标题形成区隔明确表示这是“上级区块的元信息”而非字段配置。Git diff 时行的变更也独立于列表项避免混淆。避坑经验条件表达式user_type enterprise必须用双等号不能用赋值符。我曾用导致解析脚本误判为变量声明花了 2 小时 debugstate子项必须缩进 2 个空格不是 Tab否则某些 Markdown 解析器会忽略嵌套。VS Code 默认设置是 2 空格保持一致即可多条件用连接 show_if: plan premium trial_expired false避免用中文“且”AI 可能不识别。3.4 代码块存放可执行片段杜绝“伪代码”所有需要直接插入代码的地方必须用语言标识的代码块### 组件登录按钮 - label: 登录 - action: submit_form() javascript function submit_form() { const email document.getElementById(email).value; const password document.getElementById(password).value; if (!validateEmail(email)) { showError(邮箱格式不正确); return; } // 实际调用 API... }**为什么必须加语言标识** 没有 javascript 标识的代码块GitHub 会当作纯文本渲染无法折叠、无法语法高亮。更重要的是AI 在生成时看到 javascript 会自动启用 JS 语法检查减少 console.log( 括号不闭合这类低级错误。我测试过同样要求“写一个表单提交函数”带 javascript 的 prompt生成正确率提升 40%。 **实操技巧** - 前端提取时用 document.querySelectorAll(pre code) 拿到所有代码块再通过 code.className 判断语言类型 - 如果代码块内含变量如 getElementById(email)确保 email 这个 ID 在前面的字段定义中已出现——这是跨模块一致性校验的起点 - 禁止在代码块里写注释解释逻辑注释属于 描述 或列表项代码块只放可执行代码。曾有个项目在 JS 块里写 // 这里要调用后端接口结果被误当成有效代码执行报错。 ## 4. 从 .md 到可运行代码三步落地工作流 有了 awesome-design-md 文件下一步是把它变成真实可用的代码。我推荐一个零依赖、5 分钟可搭的本地工作流已在 3 个项目中验证。 ### 4.1 第一步用正则提取结构化数据Python 脚本 核心思路不依赖 Markdown 解析库如 mistune用原生正则匹配保证轻量和可控。以下是关键函数 python import re def parse_design_md(md_content): result {page: {}, components: []} # 提取页面标题和描述 title_match re.search(r^#\s(.)$, md_content, re.MULTILINE) if title_match: result[page][title] title_match.group(1).strip() desc_match re.search(r^\s描述(.)$, md_content, re.MULTILINE) if desc_match: result[page][description] desc_match.group(1).strip() # 提取所有 ### 组件 component_blocks re.findall(r^###\s(组件|文案)(.?)$\n((?:- .?\n|\s.?\n)), md_content, re.MULTILINE) for comp_type, name, content in component_blocks: comp {type: comp_type, name: name.strip()} # 解析列表项 items re.findall(r^-\s(\w):\s(.?)(?\n- |\n$), content, re.MULTILINE | re.DOTALL) for key, value in items: # 处理多行值如 validation 规则 if \n in value: value re.sub(r\s, , value).strip() comp[key] value.strip() # 提取状态分支 states re.findall(r^\s-\sstate:\s(.?)$\n((?:\s{2}- .?\n)), content, re.MULTILINE) if states: comp[states] {} for state_name, state_content in states: state_conf {} state_items re.findall(r^\s{2}-\s(\w):\s(.)$, state_content, re.MULTILINE) for k, v in state_items: state_conf[k] v.strip() comp[states][state_name.strip()] state_conf result[components].append(comp) return result为什么不用现成解析器mistune 等库会把 show_if:当作引用块把### 组件当作标题但无法关联两者。而正则可以精确控制匹配范围——比如re.findall(r^###\s(组件|文案)(.?)$\n((?:- .?\n|\s.?\n)), ...)中的((?:- .?\n|\s.?\n))明确限定“接下来的内容必须是列表项或缩进项”避免匹配到无关段落。定制化解析器的代价是维护成本但换来的是 100% 可控的输出结构。4.2 第二步模板引擎生成代码Jinja2将解析结果喂给 Jinja2 模板生成 React 组件{# login_page.jsx #} import React, { useState, useEffect } from react; const LoginPage () { const [formData, setFormData] useState({}); const [errors, setErrors] useState({}); const handleChange (e) { setFormData({...formData, [e.target.name]: e.target.value}); }; {% for comp in components %} {% if comp.type 组件 and comp.name 邮箱输入框 %} // {{ comp.name }} 组件 const renderEmailInput () { const state errors.email ? error : formData.email ? focus : default; const baseClass {{ comp.css_class }}; const stateClass { default: {{ comp.states.default.css_class }}, focus: {{ comp.states.focus.css_class }}, error: {{ comp.states.error.css_class }} }[state] || ; return ( div label{{ comp.label }}/label input typeemail nameemail placeholder{{ comp.placeholder }} className{${baseClass} ${stateClass}} onChange{handleChange} onBlur{() validateEmail(formData.email)} / {errors.email p classtext-red-500{{ comp.states.error.error_message }}/p} /div ); }; {% endif %} {% endfor %} return ( div h1{{ page.title }}/h1 {% for comp in components %} {% if comp.type 组件 %} {% if comp.name 邮箱输入框 %} {renderEmailInput()} {% elif comp.name 登录按钮 %} button onClick{submit_form}{{ comp.label }}/button {% endif %} {% endif %} {% endfor %} /div ); }; export default LoginPage;关键技巧模板里用{% if comp.name 邮箱输入框 %}而不是comp.name in [邮箱输入框, 密码输入框]因为每个组件逻辑差异大硬编码更安全stateClass用对象字面量{ default: ..., focus: ... }而不是switch避免 JSX 中嵌套复杂逻辑所有{{ comp.xxx }}值在 Python 解析时已做过strip()不用担心前后空格。4.3 第三步一键生成与校验Makefile把流程封装成命令开发者只需make build# Makefile MD_FILE : ./design/login.md OUTPUT_DIR : ./src/components .PHONY: build build: parse generate lint parse: python scripts/parse_md.py $(MD_FILE) $(OUTPUT_DIR)/login.json generate: jinja2 templates/login_page.jsx.j2 $(OUTPUT_DIR)/login.json $(OUTPUT_DIR)/LoginPage.jsx lint: eslint --fix $(OUTPUT_DIR)/LoginPage.jsx clean: rm -f $(OUTPUT_DIR)/login.json $(OUTPUT_DIR)/LoginPage.jsx为什么用 Makefile 而不用 npm scriptMakefile 是 Unix 工具链的通用语言前端、后端、运维都认。make build比npm run build:ui更短且make clean删除中间文件一目了然。更重要的是make -n可预览执行步骤方便新人理解流程。注意parse_md.py脚本需加入if __name__ __main__:入口并接收文件路径参数否则make调用会失败。我第一次漏写导致make build报错TypeError: parse_design_md() missing 1 required positional argument查了半小时才定位。5. 常见问题与实战排障指南即使结构再清晰落地时仍会遇到典型问题。以下是我在 3 个项目中记录的真实案例附带根因分析和解决代码。5.1 问题AI 生成的 Markdown 缩进不一致导致解析失败现象AI 输出的字段列表有的用 2 空格缩进有的用 4 空格甚至混用 Tab。正则^\s{2}-\s(\w):只匹配 2 空格结果漏掉 4 空格的项。根因AI 的缩进策略受 prompt 中示例影响。如果示例用了 4 空格它就会模仿但解析脚本只认 2 空格。解决方案在解析前统一标准化缩进。修改parse_design_md函数def normalize_indent(md_content): # 将所有 Tab 替换为 2 空格 md_content md_content.replace(\t, ) # 将 4 空格缩进替换为 2 空格针对列表项 md_content re.sub(r^(\s{4})(-\s\w:), r\1\2, md_content, flagsre.MULTILINE) # 但保留标题缩进# 不受影响 return md_content # 在 parse_design_md 开头调用 md_content normalize_indent(md_content)实操效果处理前12 个字段只解析出 7 个处理后100% 解析成功。关键是re.sub(r^(\s{4})(-\s\w:), r\1\2, ...)这行——它只替换以 4 空格开头且后跟-的行避免误伤代码块内的缩进。5.2 问题validation规则中的逗号被误判为分隔符现象字段定义validation: email, required, max_length:100正则rvalidation:\s(.)提取到email, required, max_length:100但后续分割split(,)得到[email, required, max_length:100] required前有空格导致校验逻辑失效。根因逗号在规则中既是分隔符又可能出现在值内部如max_length:100的冒号后也有逗号。简单split(,)无法区分。解决方案用正则精准提取规则排除冒号后的逗号def parse_validation_rules(validation_str): # 匹配 word 或 word:value忽略冒号后的逗号 rules re.findall(r(\w)(?::([^,]))?(?,|$), validation_str) result [] for rule, value in rules: if value: result.append(f{rule}:{value.strip()}) else: result.append(rule.strip()) return result # 使用 if validation in comp: comp[validation_rules] parse_validation_rules(comp[validation])验证输入email, required, max_length:100, pattern:/^[a-z]$/输出[email, required, max_length:100, pattern:/^[a-z]$/]完美分离。5.3 问题show_if条件表达式被当作字符串而非逻辑现象 show_if: user_type enterprise被解析为字符串user_type \enterprise\但前端需要的是可执行的 JS 表达式。根因解析脚本把行当作纯文本未做表达式转换。解决方案在 Python 解析阶段用ast.literal_eval安全求值仅限简单表达式import ast def safe_eval_condition(condition_str): try: # 允许的字面量字符串、数字、布尔、None return ast.literal_eval(f{condition_str}) if in condition_str else condition_str except: # 退化为字符串 return condition_str # 但更好的方式是提取变量名和操作符 def parse_show_if(show_if_str): # 匹配 var value 或 var ! value match re.search(r(\w)\s*(|!)\s*[\](.?)[\], show_if_str) if match: return { field: match.group(1), operator: match.group(2), value: match.group(3) } return None # 使用 show_if_conf parse_show_if(user_type \enterprise\) # 返回 {field: user_type, operator: , value: enterprise}前端使用{show_if_conf formData[show_if_conf.field] show_if_conf.value ( EnterpriseInfoForm / )}5.4 问题图片路径在不同环境失效现象awesome-design-md中写![logo](./assets/logo.png)本地预览正常但部署到/app/子路径时图片 404。根因Markdown 图片路径是相对路径而 Webpack/Vite 的 public 目录和 src 目录处理方式不同。解决方案约定所有图片路径以/public/开头并在解析时自动转换def resolve_image_path(md_content): # 将 ![alt](./assets/logo.png) → ![alt](/public/logo.png) return re.sub(r!\[([^\]])\]\(\./assets/([^\)])\), r![\1](/public/\2), md_content) # 在 parse_design_md 前调用 md_content resolve_image_path(md_content)额外保障在index.html中添加base href/app/确保所有相对路径以/app/为基准。这样src/public/logo.png就能正确解析为/app/public/logo.png。6. 进阶技巧让awesome-design-md支持数学公式与复杂表格标题里提到的“markdown数学公式插件”“markdown大括号多行公式”并非噱头而是awesome-design-md可扩展性的体现。它不排斥高级语法只要这些语法能被主流解析器支持。6.1 数学公式用 KaTeX 渲染不侵入核心结构在需要展示公式的地方直接使用标准 LaTeX 语法## 区块智能计算器 描述根据用户输入的半径实时计算圆面积 ### 组件半径输入框 - label: 半径 (r) - unit: 米 ### 组件面积结果显示 - formula: | A \pi r^2 \text{其中 } r \text{ 为输入的半径值} - result: $A \pi \times {{formData.radius}}^2$关键点formula字段用|表示多行KaTeX 支持\text{}插入中文\pi渲染希腊字母result字段用$...$包裹前端用katex.render(formula, element)渲染{{formData.radius}}是模板变量由 JS 动态注入非 Markdown 语法但awesome-design-md允许在代码块或特定字段中使用。实操配置在index.html中引入 KaTeXlink relstylesheet hrefhttps://cdn.jsdelivr.net/npm/katex0.16.9/dist/katex.min.css script defer srchttps://cdn.jsdelivr.net/npm/katex0.16.9/dist/katex.min.js/script6.2 复杂表格用 Markdown 表格定义状态机对于多状态、多条件的组件如支付状态面板用表格比嵌套列表更清晰### 组件支付状态面板 | 状态 | 显示文案 | 主要按钮 | 按钮动作 | 超时处理 | |------|----------|----------|----------|----------| | pending | 正在处理中... | - | - | 30秒后自动重试 | | success | 支付成功 | 查看订单 | goToOrder() | - | | failed | 支付失败 | 重新支付 | retryPayment() | 10秒后显示联系客服 |解析技巧用pandas.read_html()读取表格需安装 pandas或手动解析def parse_table(table_md): lines table_md.strip().split(\n) headers [h.strip() for h in lines[0].split(|)[1:-1]] rows [] for line in lines[2:]: cells [c.strip() for c in line.split(|)[1:-1]] rows.append(dict(zip(headers, cells))) return rows前端渲染{statusTable.map((row, i) ( div key{i} p{row.状态}/p button onClick{() eval(row.按钮动作)}{row.主要按钮}/button /div ))}6.3 与 Coze/Dify 工作流集成用agents.md作为 Prompt 模板agents.md不是独立文件而是awesome-design-md的衍生用法——把界面定义作为 Agent 的 system prompt# AGENTS.md 角色UI 生成助手 任务根据用户需求输出符合 awesome-design-md 规范的 Markdown 界面定义 ## 输出规则 - 必须使用 # / ## / ### 标题定义层级 - 所有字段用 - key: value 格式 - 状态分支用缩进列表 - 代码块必须带语言标识 - 禁止解释性文字只输出结构化 Markdown效果在 Coze Bot 中把AGENTS.md内容设为 system prompt用户说“做一个带搜索框的博客首页”AI 直接输出# 博客首页 描述支持关键词搜索的静态博客列表页 ## 区块搜索栏 ### 组件搜索输入框 - label: 搜索文章 - placeholder: 输入关键词回车搜索 - css_class: w-full px-4 py-2 border rounded-lg ## 区块文章列表 ### 组件文章卡片 - template: | article h3{{title}}/h3 p{{excerpt}}/p time{{date}}/time /article优势不用每次写长 promptAGENTS.md作为“AI 的操作手册”输出格式稳定可直接喂给解析脚本团队共享同一份AGENTS.md确保所有 Bot 行为一致。我在实际项目中把AGENTS.md和awesome-design-md模板放在 GitHub Wiki新成员
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →