[{"data":1,"prerenderedAt":1592},["ShallowReactive",2],{"article-/topics/design/design-decision-log-change-history-governance":3,"related-design":379,"content-query-WwfkLpcdb2":1321},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":12,"image":17,"imageQuery":18,"pexelsPhotoId":19,"pexelsUrl":20,"featured":6,"readingTime":21,"body":22,"_type":373,"_id":374,"_source":375,"_file":376,"_stem":377,"_extension":378},"/topics/design/design-decision-log-change-history-governance","design",false,"","设计决策怎么留痕：页面改版不是记结论，而是要记录为什么这样改","很多网站项目复盘失败，不是没人记录，而是只记录了结果。本文提供一套设计决策日志方法，帮助团队在页面持续迭代中保留上下文、降低重复争论，并提高改版可追溯性。","2026-05-25","HTMLPAGE 团队",[13,14,15,16],"网页设计","设计决策","版本管理","团队协作","/images/articles/design-decision-log-change-history-governance-featured.jpg","product design decision log notes laptop",7818111,"https://www.pexels.com/photo/marker-on-top-of-a-notebook-7818111/",13,{"type":23,"children":24,"toc":362},"root",[25,33,38,67,74,197,203,208,213,219,224,229,249,254,260,265,283,288,294,299,304,309,332,338,357],{"type":26,"tag":27,"props":28,"children":29},"element","p",{},[30],{"type":31,"value":32},"text","网站项目改到第三轮以后，团队经常陷入一种熟悉的疲劳：同一件事反复讨论。为什么首页标题又改回去了？为什么这个模块去年删掉现在又加回来？为什么 CTA 位置每次改版都在变？问题不在于团队记性差，而在于设计决策没有被结构化留痕。",{"type":26,"tag":27,"props":34,"children":35},{},[36],{"type":31,"value":37},"很多团队有版本记录，但版本记录通常只写“改了什么”。真正影响后续判断的，是“为什么改”。如果缺这层上下文，下一轮人手一换，过去的选择就像没发生过，争论只能重来。",{"type":26,"tag":27,"props":39,"children":40},{},[41,43,50,52,58,59,65],{"type":31,"value":42},"你可以配合 ",{"type":26,"tag":44,"props":45,"children":47},"a",{"href":46},"/topics/design/web-design-review-workshop-stakeholder-alignment-guide",[48],{"type":31,"value":49},"网页设计评审会怎么开",{"type":31,"value":51},"、",{"type":26,"tag":44,"props":53,"children":55},{"href":54},"/topics/practical-tips/website-post-launch-7-day-data-review",[56],{"type":31,"value":57},"网页上线后 7 天数据观察",{"type":31,"value":51},{"type":26,"tag":44,"props":60,"children":62},{"href":61},"/topics/design/website-redesign-template-to-brand-case-study",[63],{"type":31,"value":64},"网站改版复盘案例",{"type":31,"value":66}," 一起搭建完整机制。",{"type":26,"tag":68,"props":69,"children":71},"h2",{"id":70},"先给结论设计决策日志最少记录-5-个字段",[72],{"type":31,"value":73},"先给结论：设计决策日志最少记录 5 个字段",{"type":26,"tag":75,"props":76,"children":77},"table",{},[78,102],{"type":26,"tag":79,"props":80,"children":81},"thead",{},[82],{"type":26,"tag":83,"props":84,"children":85},"tr",{},[86,92,97],{"type":26,"tag":87,"props":88,"children":89},"th",{},[90],{"type":31,"value":91},"字段",{"type":26,"tag":87,"props":93,"children":94},{},[95],{"type":31,"value":96},"说明",{"type":26,"tag":87,"props":98,"children":99},{},[100],{"type":31,"value":101},"作用",{"type":26,"tag":103,"props":104,"children":105},"tbody",{},[106,125,143,161,179],{"type":26,"tag":83,"props":107,"children":108},{},[109,115,120],{"type":26,"tag":110,"props":111,"children":112},"td",{},[113],{"type":31,"value":114},"决策问题",{"type":26,"tag":110,"props":116,"children":117},{},[118],{"type":31,"value":119},"本次要解决的具体问题",{"type":26,"tag":110,"props":121,"children":122},{},[123],{"type":31,"value":124},"避免“看起来都重要”",{"type":26,"tag":83,"props":126,"children":127},{},[128,133,138],{"type":26,"tag":110,"props":129,"children":130},{},[131],{"type":31,"value":132},"备选方案",{"type":26,"tag":110,"props":134,"children":135},{},[136],{"type":31,"value":137},"至少 2 个候选路线",{"type":26,"tag":110,"props":139,"children":140},{},[141],{"type":31,"value":142},"防止单一路线偏见",{"type":26,"tag":83,"props":144,"children":145},{},[146,151,156],{"type":26,"tag":110,"props":147,"children":148},{},[149],{"type":31,"value":150},"选择理由",{"type":26,"tag":110,"props":152,"children":153},{},[154],{"type":31,"value":155},"选择当前方案的依据",{"type":26,"tag":110,"props":157,"children":158},{},[159],{"type":31,"value":160},"支持复盘与传承",{"type":26,"tag":83,"props":162,"children":163},{},[164,169,174],{"type":26,"tag":110,"props":165,"children":166},{},[167],{"type":31,"value":168},"风险与边界",{"type":26,"tag":110,"props":170,"children":171},{},[172],{"type":31,"value":173},"该方案何时可能失效",{"type":26,"tag":110,"props":175,"children":176},{},[177],{"type":31,"value":178},"提前定义观察信号",{"type":26,"tag":83,"props":180,"children":181},{},[182,187,192],{"type":26,"tag":110,"props":183,"children":184},{},[185],{"type":31,"value":186},"验证计划",{"type":26,"tag":110,"props":188,"children":189},{},[190],{"type":31,"value":191},"用什么指标验证对错",{"type":26,"tag":110,"props":193,"children":194},{},[195],{"type":31,"value":196},"把设计连接到结果",{"type":26,"tag":68,"props":198,"children":200},{"id":199},"日志不是文档负担而是反复返工的止损器",[201],{"type":31,"value":202},"日志不是文档负担，而是反复返工的止损器",{"type":26,"tag":27,"props":204,"children":205},{},[206],{"type":31,"value":207},"团队抗拒决策日志，常见理由是“太慢”。但不记录的代价通常更高：每次改版都要重新建立上下文，参与者越多，重复沟通越重。",{"type":26,"tag":27,"props":209,"children":210},{},[211],{"type":31,"value":212},"一个可执行原则是：日志要轻，但结构不能缺。每个重要决策控制在 10 分钟内记录完，重点写判断依据和验证方式，而不是写长篇叙事。",{"type":26,"tag":68,"props":214,"children":216},{"id":215},"把审美偏好转成可验证假设",[217],{"type":31,"value":218},"把“审美偏好”转成“可验证假设”",{"type":26,"tag":27,"props":220,"children":221},{},[222],{"type":31,"value":223},"设计争论最难收口的地方，是很多意见停留在偏好层。例如“更简洁一点”“更有力量感”。这类表达并不错误，但无法指导实施。",{"type":26,"tag":27,"props":225,"children":226},{},[227],{"type":31,"value":228},"决策日志要做的转换是：",{"type":26,"tag":230,"props":231,"children":232},"ul",{},[233,239,244],{"type":26,"tag":234,"props":235,"children":236},"li",{},[237],{"type":31,"value":238},"从偏好语言到问题语言：现在要解决的是可读性、转化路径还是品牌辨识",{"type":26,"tag":234,"props":240,"children":241},{},[242],{"type":31,"value":243},"从问题语言到假设语言：如果把首屏信息压缩到三层，CTA 点击率是否提升",{"type":26,"tag":234,"props":245,"children":246},{},[247],{"type":31,"value":248},"从假设语言到验证语言：两周内看点击率、停留时间和咨询质量变化",{"type":26,"tag":27,"props":250,"children":251},{},[252],{"type":31,"value":253},"一旦进入可验证假设，设计讨论就不再是“谁更有品味”。",{"type":26,"tag":68,"props":255,"children":257},{"id":256},"决策日志必须和版本节奏绑定不能做成事后补材料",[258],{"type":31,"value":259},"决策日志必须和版本节奏绑定，不能做成“事后补材料”",{"type":26,"tag":27,"props":261,"children":262},{},[263],{"type":31,"value":264},"很多团队在复盘周才补日志，结果信息已经丢失。更稳的方式是把日志卡进工作流：",{"type":26,"tag":230,"props":266,"children":267},{},[268,273,278],{"type":26,"tag":234,"props":269,"children":270},{},[271],{"type":31,"value":272},"评审通过后，立即记录关键决策",{"type":26,"tag":234,"props":274,"children":275},{},[276],{"type":31,"value":277},"开发排期前，确认风险和验证指标",{"type":26,"tag":234,"props":279,"children":280},{},[281],{"type":31,"value":282},"上线后一周，补结果回填",{"type":26,"tag":27,"props":284,"children":285},{},[286],{"type":31,"value":287},"日志不是单独系统，而是评审、交付、复盘之间的连接件。",{"type":26,"tag":68,"props":289,"children":291},{"id":290},"失败案例改版做了-4-轮组织却没有任何学习",[292],{"type":31,"value":293},"失败案例：改版做了 4 轮，组织却没有任何学习",{"type":26,"tag":27,"props":295,"children":296},{},[297],{"type":31,"value":298},"某团队一年内做了 4 次首页改版，每次都投入不少资源。结果到了第四次，团队依然在争论第一轮讨论过的问题。复盘时发现，他们有完整设计文件和开发记录，但没有任何决策日志。历史版本里看得到视觉差异，看不到选择依据。",{"type":26,"tag":27,"props":300,"children":301},{},[302],{"type":31,"value":303},"后来团队引入最小决策日志模板，只记录关键页面和关键模块。半年后，争论次数明显下降，因为大家能快速找到“当时为什么这样选、后来验证结果如何”。",{"type":26,"tag":68,"props":305,"children":307},{"id":306},"哪些信号说明你需要立刻上决策日志",[308],{"type":31,"value":306},{"type":26,"tag":230,"props":310,"children":311},{},[312,317,322,327],{"type":26,"tag":234,"props":313,"children":314},{},[315],{"type":31,"value":316},"同类争论在两个月内重复出现三次以上",{"type":26,"tag":234,"props":318,"children":319},{},[320],{"type":31,"value":321},"页面改动频繁，但指标变化解释不清",{"type":26,"tag":234,"props":323,"children":324},{},[325],{"type":31,"value":326},"团队成员变动后，设计方向明显回摆",{"type":26,"tag":234,"props":328,"children":329},{},[330],{"type":31,"value":331},"复盘会议总在“回忆发生了什么”",{"type":26,"tag":68,"props":333,"children":335},{"id":334},"先做什么从一个页面先跑通",[336],{"type":31,"value":337},"先做什么：从一个页面先跑通",{"type":26,"tag":339,"props":340,"children":341},"ol",{},[342,347,352],{"type":26,"tag":234,"props":343,"children":344},{},[345],{"type":31,"value":346},"选一个本月会持续迭代的关键页面作为试点。",{"type":26,"tag":234,"props":348,"children":349},{},[350],{"type":31,"value":351},"使用 5 字段模板记录每次关键决策。",{"type":26,"tag":234,"props":353,"children":354},{},[355],{"type":31,"value":356},"在上线后 7 到 14 天回填验证结果，形成闭环。",{"type":26,"tag":27,"props":358,"children":359},{},[360],{"type":31,"value":361},"设计能力的成熟，不只体现在做出好页面，更体现在能解释为什么这么做、什么时候该改、改完是否更好。决策留痕做到位，团队才会真正积累经验，而不是反复重来。",{"title":7,"searchDepth":363,"depth":363,"links":364},3,[365,367,368,369,370,371,372],{"id":70,"depth":366,"text":73},2,{"id":199,"depth":366,"text":202},{"id":215,"depth":366,"text":218},{"id":256,"depth":366,"text":259},{"id":290,"depth":366,"text":293},{"id":306,"depth":366,"text":306},{"id":334,"depth":366,"text":337},"markdown","content:topics:design:design-decision-log-change-history-governance.md","content","topics/design/design-decision-log-change-history-governance.md","topics/design/design-decision-log-change-history-governance","md",[380,740,1042],{"_path":381,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":382,"description":383,"keywords":384,"image":390,"author":11,"date":391,"readingTime":392,"topic":5,"body":393,"_type":373,"_id":737,"_source":375,"_file":738,"_stem":739,"_extension":378},"/topics/design/button-component-design","按钮组件设计详解","学习按钮样式、交互状态、无障碍性和最佳实践",[385,386,387,388,389],"按钮设计","Button Component","交互状态","UI 组件","用户体验","/images/topics/button-design.jpg","2025-12-08",18,{"type":23,"children":394,"toc":719},[395,399,404,409,416,429,435,444,450,459,463,469,480,486,495,501,510,515,524,529,540,545,554,559,572,606,617,660,665],{"type":26,"tag":68,"props":396,"children":397},{"id":382},[398],{"type":31,"value":382},{"type":26,"tag":27,"props":400,"children":401},{},[402],{"type":31,"value":403},"按钮是 UI 中最重要的交互元素。优秀的按钮设计能够指导用户行为。",{"type":26,"tag":68,"props":405,"children":407},{"id":406},"按钮类型",[408],{"type":31,"value":406},{"type":26,"tag":410,"props":411,"children":413},"h3",{"id":412},"primary-button主按钮",[414],{"type":31,"value":415},"Primary Button（主按钮）",{"type":26,"tag":417,"props":418,"children":423},"pre",{"className":419,"code":421,"language":422,"meta":7},[420],"language-css",".btn-primary {\n  background-color: #0066cc;\n  color: #ffffff;\n  padding: 12px 24px;\n  border: none;\n  border-radius: 4px;\n  font-weight: 600;\n  font-size: 16px;\n  cursor: pointer;\n  transition: all 0.2s ease;\n}\n\n.btn-primary:hover {\n  background-color: #0052a3;\n  box-shadow: 0 4px 12px rgba(0, 102, 204, 0.2);\n}\n\n.btn-primary:active {\n  background-color: #003d7a;\n  transform: scale(0.98);\n}\n\n.btn-primary:disabled {\n  background-color: #cccccc;\n  cursor: not-allowed;\n  opacity: 0.6;\n}\n","css",[424],{"type":26,"tag":425,"props":426,"children":427},"code",{"__ignoreMap":7},[428],{"type":31,"value":421},{"type":26,"tag":410,"props":430,"children":432},{"id":431},"secondary-button次按钮",[433],{"type":31,"value":434},"Secondary Button（次按钮）",{"type":26,"tag":417,"props":436,"children":439},{"className":437,"code":438,"language":422,"meta":7},[420],".btn-secondary {\n  background-color: transparent;\n  color: #0066cc;\n  border: 2px solid #0066cc;\n  padding: 10px 22px;\n  border-radius: 4px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.2s;\n}\n\n.btn-secondary:hover {\n  background-color: rgba(0, 102, 204, 0.1);\n}\n\n.btn-secondary:active {\n  background-color: rgba(0, 102, 204, 0.2);\n}\n",[440],{"type":26,"tag":425,"props":441,"children":442},{"__ignoreMap":7},[443],{"type":31,"value":438},{"type":26,"tag":410,"props":445,"children":447},{"id":446},"danger-button危险按钮",[448],{"type":31,"value":449},"Danger Button（危险按钮）",{"type":26,"tag":417,"props":451,"children":454},{"className":452,"code":453,"language":422,"meta":7},[420],".btn-danger {\n  background-color: #cc0000;\n  color: #ffffff;\n  padding: 12px 24px;\n  border-radius: 4px;\n  cursor: pointer;\n  font-weight: 600;\n}\n\n.btn-danger:hover {\n  background-color: #990000;\n  box-shadow: 0 4px 12px rgba(204, 0, 0, 0.2);\n}\n",[455],{"type":26,"tag":425,"props":456,"children":457},{"__ignoreMap":7},[458],{"type":31,"value":453},{"type":26,"tag":68,"props":460,"children":461},{"id":387},[462],{"type":31,"value":387},{"type":26,"tag":410,"props":464,"children":466},{"id":465},"loading-状态",[467],{"type":31,"value":468},"Loading 状态",{"type":26,"tag":417,"props":470,"children":475},{"className":471,"code":473,"language":474,"meta":7},[472],"language-jsx","import { useState } from 'react';\n\nfunction Button({ children, onClick, loading, ...props }) {\n  const [isLoading, setIsLoading] = useState(false);\n  \n  const handleClick = async () => {\n    setIsLoading(true);\n    try {\n      await onClick();\n    } finally {\n      setIsLoading(false);\n    }\n  };\n  \n  return (\n    \u003Cbutton\n      onClick={handleClick}\n      disabled={isLoading || loading}\n      aria-busy={isLoading || loading}\n      {...props}\n    >\n      {isLoading ? (\n        \u003C>\n          \u003Cspan className=\"spinner\" aria-hidden=\"true\">\u003C/span>\n          {children}\n        \u003C/>\n      ) : (\n        children\n      )}\n    \u003C/button>\n  );\n}\n","jsx",[476],{"type":26,"tag":425,"props":477,"children":478},{"__ignoreMap":7},[479],{"type":31,"value":473},{"type":26,"tag":410,"props":481,"children":483},{"id":482},"disabled-状态",[484],{"type":31,"value":485},"Disabled 状态",{"type":26,"tag":417,"props":487,"children":490},{"className":488,"code":489,"language":422,"meta":7},[420],".btn:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n  background-color: #cccccc;\n  color: #999999;\n}\n\n/* 禁用状态下隐藏指针光标 */\n.btn:disabled:hover {\n  box-shadow: none;\n  transform: none;\n}\n",[491],{"type":26,"tag":425,"props":492,"children":493},{"__ignoreMap":7},[494],{"type":31,"value":489},{"type":26,"tag":410,"props":496,"children":498},{"id":497},"focus-状态",[499],{"type":31,"value":500},"Focus 状态",{"type":26,"tag":417,"props":502,"children":505},{"className":503,"code":504,"language":422,"meta":7},[420],".btn:focus {\n  outline: none;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1),\n              0 0 0 5px #0066cc;\n}\n\n/* 键盘导航焦点 */\n.btn:focus-visible {\n  outline: 2px solid #0066cc;\n  outline-offset: 2px;\n}\n",[506],{"type":26,"tag":425,"props":507,"children":508},{"__ignoreMap":7},[509],{"type":31,"value":504},{"type":26,"tag":68,"props":511,"children":513},{"id":512},"按钮大小",[514],{"type":31,"value":512},{"type":26,"tag":417,"props":516,"children":519},{"className":517,"code":518,"language":422,"meta":7},[420],"/* 小按钮 */\n.btn-sm {\n  padding: 6px 12px;\n  font-size: 12px;\n  min-height: 32px;\n  min-width: 32px;\n}\n\n/* 中等按钮（默认） */\n.btn-md {\n  padding: 12px 24px;\n  font-size: 16px;\n  min-height: 44px;\n  min-width: 44px;\n}\n\n/* 大按钮 */\n.btn-lg {\n  padding: 16px 32px;\n  font-size: 18px;\n  min-height: 56px;\n  min-width: 56px;\n}\n\n/* 全宽按钮 */\n.btn-block {\n  width: 100%;\n  display: block;\n}\n",[520],{"type":26,"tag":425,"props":521,"children":522},{"__ignoreMap":7},[523],{"type":31,"value":518},{"type":26,"tag":68,"props":525,"children":527},{"id":526},"无障碍性",[528],{"type":31,"value":526},{"type":26,"tag":417,"props":530,"children":535},{"className":531,"code":533,"language":534,"meta":7},[532],"language-html","\u003C!-- 语义正确 -->\n\u003Cbutton type=\"submit\" aria-label=\"提交表单\">\n  提交\n\u003C/button>\n\n\u003C!-- 加载状态 -->\n\u003Cbutton aria-busy=\"true\" disabled>\n  \u003Cspan aria-hidden=\"true\" class=\"spinner\">\u003C/span>\n  加载中...\n\u003C/button>\n\n\u003C!-- 图标按钮 -->\n\u003Cbutton aria-label=\"关闭\">\n  \u003Csvg aria-hidden=\"true\" width=\"24\" height=\"24\">\n    \u003Cline x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n    \u003Cline x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n  \u003C/svg>\n\u003C/button>\n\n\u003C!-- 切换按钮 -->\n\u003Cbutton aria-pressed=\"false\" aria-label=\"点赞\">\n  ♥\n\u003C/button>\n","html",[536],{"type":26,"tag":425,"props":537,"children":538},{"__ignoreMap":7},[539],{"type":31,"value":533},{"type":26,"tag":68,"props":541,"children":543},{"id":542},"完整组件示例",[544],{"type":31,"value":542},{"type":26,"tag":417,"props":546,"children":549},{"className":547,"code":548,"language":474,"meta":7},[472],"const Button = React.forwardRef((\n  {\n    children,\n    variant = 'primary',\n    size = 'md',\n    loading = false,\n    disabled = false,\n    icon,\n    className,\n    ...props\n  },\n  ref\n) => {\n  return (\n    \u003Cbutton\n      ref={ref}\n      className={`btn btn-${variant} btn-${size} ${className}`}\n      disabled={disabled || loading}\n      aria-busy={loading}\n      {...props}\n    >\n      {icon && \u003Cspan className=\"btn-icon\" aria-hidden=\"true\">{icon}\u003C/span>}\n      {loading ? (\n        \u003C>\n          \u003Cspan className=\"spinner\" aria-hidden=\"true\">\u003C/span>\n          {children}\n        \u003C/>\n      ) : (\n        children\n      )}\n    \u003C/button>\n  );\n});\n\nButton.displayName = 'Button';\n",[550],{"type":26,"tag":425,"props":551,"children":552},{"__ignoreMap":7},[553],{"type":31,"value":548},{"type":26,"tag":68,"props":555,"children":557},{"id":556},"最佳实践",[558],{"type":31,"value":556},{"type":26,"tag":27,"props":560,"children":561},{},[562,564,570],{"type":31,"value":563},"✅ ",{"type":26,"tag":565,"props":566,"children":567},"strong",{},[568],{"type":31,"value":569},"应该做的事",{"type":31,"value":571},":",{"type":26,"tag":230,"props":573,"children":574},{},[575,580,585,596,601],{"type":26,"tag":234,"props":576,"children":577},{},[578],{"type":31,"value":579},"最小触摸目标 44x44px",{"type":26,"tag":234,"props":581,"children":582},{},[583],{"type":31,"value":584},"清晰的视觉反馈",{"type":26,"tag":234,"props":586,"children":587},{},[588,590],{"type":31,"value":589},"使用语义 HTML ",{"type":26,"tag":425,"props":591,"children":593},{"className":592},[],[594],{"type":31,"value":595},"\u003Cbutton>",{"type":26,"tag":234,"props":597,"children":598},{},[599],{"type":31,"value":600},"提供加载状态反馈",{"type":26,"tag":234,"props":602,"children":603},{},[604],{"type":31,"value":605},"支持键盘导航",{"type":26,"tag":27,"props":607,"children":608},{},[609,611,616],{"type":31,"value":610},"❌ ",{"type":26,"tag":565,"props":612,"children":613},{},[614],{"type":31,"value":615},"不应该做的事",{"type":31,"value":571},{"type":26,"tag":230,"props":618,"children":619},{},[620,633,638,643,648],{"type":26,"tag":234,"props":621,"children":622},{},[623,625,631],{"type":31,"value":624},"使用 ",{"type":26,"tag":425,"props":626,"children":628},{"className":627},[],[629],{"type":31,"value":630},"\u003Cdiv>",{"type":31,"value":632}," 模拟按钮",{"type":26,"tag":234,"props":634,"children":635},{},[636],{"type":31,"value":637},"隐藏焦点指示器",{"type":26,"tag":234,"props":639,"children":640},{},[641],{"type":31,"value":642},"过多的按钮样式",{"type":26,"tag":234,"props":644,"children":645},{},[646],{"type":31,"value":647},"忽视禁用状态",{"type":26,"tag":234,"props":649,"children":650},{},[651,652,658],{"type":31,"value":624},{"type":26,"tag":425,"props":653,"children":655},{"className":654},[],[656],{"type":31,"value":657},"\u003Ca>",{"type":31,"value":659}," 代替按钮",{"type":26,"tag":68,"props":661,"children":663},{"id":662},"测试清单",[664],{"type":31,"value":662},{"type":26,"tag":230,"props":666,"children":669},{"className":667},[668],"contains-task-list",[670,683,692,701,710],{"type":26,"tag":234,"props":671,"children":674},{"className":672},[673],"task-list-item",[675,681],{"type":26,"tag":676,"props":677,"children":680},"input",{"disabled":678,"type":679},true,"checkbox",[],{"type":31,"value":682}," 在各种浏览器中测试",{"type":26,"tag":234,"props":684,"children":686},{"className":685},[673],[687,690],{"type":26,"tag":676,"props":688,"children":689},{"disabled":678,"type":679},[],{"type":31,"value":691}," 验证键盘导航",{"type":26,"tag":234,"props":693,"children":695},{"className":694},[673],[696,699],{"type":26,"tag":676,"props":697,"children":698},{"disabled":678,"type":679},[],{"type":31,"value":700}," 检查色彩对比度",{"type":26,"tag":234,"props":702,"children":704},{"className":703},[673],[705,708],{"type":26,"tag":676,"props":706,"children":707},{"disabled":678,"type":679},[],{"type":31,"value":709}," 测试触摸设备",{"type":26,"tag":234,"props":711,"children":713},{"className":712},[673],[714,717],{"type":26,"tag":676,"props":715,"children":716},{"disabled":678,"type":679},[],{"type":31,"value":718}," 屏幕阅读器兼容性",{"title":7,"searchDepth":363,"depth":363,"links":720},[721,722,727,732,733,734,735,736],{"id":382,"depth":366,"text":382},{"id":406,"depth":366,"text":406,"children":723},[724,725,726],{"id":412,"depth":363,"text":415},{"id":431,"depth":363,"text":434},{"id":446,"depth":363,"text":449},{"id":387,"depth":366,"text":387,"children":728},[729,730,731],{"id":465,"depth":363,"text":468},{"id":482,"depth":363,"text":485},{"id":497,"depth":363,"text":500},{"id":512,"depth":366,"text":512},{"id":526,"depth":366,"text":526},{"id":542,"depth":366,"text":542},{"id":556,"depth":366,"text":556},{"id":662,"depth":366,"text":662},"content:topics:design:button-component-design.md","topics/design/button-component-design.md","topics/design/button-component-design",{"_path":741,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":742,"description":743,"keywords":744,"image":749,"author":11,"date":391,"readingTime":750,"topic":5,"body":751,"_type":373,"_id":1039,"_source":375,"_file":1040,"_stem":1041,"_extension":378},"/topics/design/dark-mode-design","暗黑模式设计完整方案","学习暗黑模式实现、色彩方案、对比度管理和最佳实践",[745,746,747,748,389],"暗黑模式","Dark Mode","色彩系统","CSS 变量","/images/topics/dark-mode-design.jpg",20,{"type":23,"children":752,"toc":1022},[753,757,762,767,773,782,788,797,802,808,817,823,834,840,849,854,863,868,877,882,891,895,904,932,941,969,973],{"type":26,"tag":68,"props":754,"children":755},{"id":742},[756],{"type":31,"value":742},{"type":26,"tag":27,"props":758,"children":759},{},[760],{"type":31,"value":761},"暗黑模式已成为现代应用的标准功能。它能够减少眼睛疲劳、节省电池、改善用户体验。",{"type":26,"tag":68,"props":763,"children":765},{"id":764},"核心色彩系统",[766],{"type":31,"value":764},{"type":26,"tag":410,"props":768,"children":770},{"id":769},"light-mode-配色",[771],{"type":31,"value":772},"Light Mode 配色",{"type":26,"tag":417,"props":774,"children":777},{"className":775,"code":776,"language":422,"meta":7},[420],":root {\n  /* Light Mode */\n  --bg-primary: #ffffff;\n  --bg-secondary: #f5f5f5;\n  --bg-tertiary: #efefef;\n  \n  --text-primary: #1a1a1a;\n  --text-secondary: #666666;\n  --text-tertiary: #999999;\n  \n  --border-color: #e0e0e0;\n  --divider-color: #f0f0f0;\n}\n",[778],{"type":26,"tag":425,"props":779,"children":780},{"__ignoreMap":7},[781],{"type":31,"value":776},{"type":26,"tag":410,"props":783,"children":785},{"id":784},"dark-mode-配色",[786],{"type":31,"value":787},"Dark Mode 配色",{"type":26,"tag":417,"props":789,"children":792},{"className":790,"code":791,"language":422,"meta":7},[420],"@media (prefers-color-scheme: dark) {\n  :root {\n    /* Dark Mode */\n    --bg-primary: #1a1a1a;\n    --bg-secondary: #2d2d2d;\n    --bg-tertiary: #3a3a3a;\n    \n    --text-primary: #ffffff;\n    --text-secondary: #e0e0e0;\n    --text-tertiary: #a0a0a0;\n    \n    --border-color: #404040;\n    --divider-color: #2a2a2a;\n  }\n}\n",[793],{"type":26,"tag":425,"props":794,"children":795},{"__ignoreMap":7},[796],{"type":31,"value":791},{"type":26,"tag":68,"props":798,"children":800},{"id":799},"实现方案",[801],{"type":31,"value":799},{"type":26,"tag":410,"props":803,"children":805},{"id":804},"方案-1prefers-color-scheme",[806],{"type":31,"value":807},"方案 1：prefers-color-scheme",{"type":26,"tag":417,"props":809,"children":812},{"className":810,"code":811,"language":422,"meta":7},[420],"/* 自动跟随系统设置 */\n@media (prefers-color-scheme: light) {\n  :root {\n    --bg: #fff;\n    --text: #000;\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --bg: #1a1a1a;\n    --text: #fff;\n  }\n}\n\nbody {\n  background: var(--bg);\n  color: var(--text);\n}\n",[813],{"type":26,"tag":425,"props":814,"children":815},{"__ignoreMap":7},[816],{"type":31,"value":811},{"type":26,"tag":410,"props":818,"children":820},{"id":819},"方案-2javascript-切换",[821],{"type":31,"value":822},"方案 2：JavaScript 切换",{"type":26,"tag":417,"props":824,"children":829},{"className":825,"code":827,"language":828,"meta":7},[826],"language-javascript","// 检测和切换暗黑模式\nfunction initDarkMode() {\n  const isDark = localStorage.getItem('darkMode') === 'true' ||\n                 window.matchMedia('(prefers-color-scheme: dark)').matches;\n  \n  if (isDark) {\n    document.documentElement.setAttribute('data-theme', 'dark');\n  }\n}\n\nfunction toggleDarkMode() {\n  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';\n  const newTheme = isDark ? 'light' : 'dark';\n  \n  document.documentElement.setAttribute('data-theme', newTheme);\n  localStorage.setItem('darkMode', newTheme === 'dark');\n}\n\n// CSS 应用\nhtml[data-theme='light'] {\n  color-scheme: light;\n}\n\nhtml[data-theme='dark'] {\n  color-scheme: dark;\n}\n","javascript",[830],{"type":26,"tag":425,"props":831,"children":832},{"__ignoreMap":7},[833],{"type":31,"value":827},{"type":26,"tag":410,"props":835,"children":837},{"id":836},"方案-3css-variables-javascript",[838],{"type":31,"value":839},"方案 3：CSS Variables + JavaScript",{"type":26,"tag":417,"props":841,"children":844},{"className":842,"code":843,"language":828,"meta":7},[826],"const themes = {\n  light: {\n    '--bg-primary': '#ffffff',\n    '--text-primary': '#000000',\n    '--accent': '#0066cc',\n    '--border': '#e0e0e0',\n  },\n  dark: {\n    '--bg-primary': '#1a1a1a',\n    '--text-primary': '#ffffff',\n    '--accent': '#4da3ff',\n    '--border': '#404040',\n  },\n};\n\nfunction applyTheme(themeName) {\n  const theme = themes[themeName];\n  Object.entries(theme).forEach(([key, value]) => {\n    document.documentElement.style.setProperty(key, value);\n  });\n  localStorage.setItem('theme', themeName);\n}\n",[845],{"type":26,"tag":425,"props":846,"children":847},{"__ignoreMap":7},[848],{"type":31,"value":843},{"type":26,"tag":68,"props":850,"children":852},{"id":851},"对比度管理",[853],{"type":31,"value":851},{"type":26,"tag":417,"props":855,"children":858},{"className":856,"code":857,"language":422,"meta":7},[420],"/* Light Mode 对比度 */\n:root {\n  --contrast-high: #000000;     /* 21:1 */\n  --contrast-medium: #333333;   /* 12.6:1 */\n  --contrast-low: #666666;      /* 5.1:1 */\n}\n\n/* Dark Mode 对比度 */\n@media (prefers-color-scheme: dark) {\n  :root {\n    --contrast-high: #ffffff;    /* 21:1 */\n    --contrast-medium: #e0e0e0;  /* 11.6:1 */\n    --contrast-low: #a0a0a0;     /* 4.5:1 */\n  }\n}\n\n/* 应用对比度 */\n.text-primary { color: var(--contrast-high); }\n.text-secondary { color: var(--contrast-medium); }\n.text-tertiary { color: var(--contrast-low); }\n",[859],{"type":26,"tag":425,"props":860,"children":861},{"__ignoreMap":7},[862],{"type":31,"value":857},{"type":26,"tag":68,"props":864,"children":866},{"id":865},"图片和图表处理",[867],{"type":31,"value":865},{"type":26,"tag":417,"props":869,"children":872},{"className":870,"code":871,"language":534,"meta":7},[532],"\u003C!-- 针对不同主题的图片 -->\n\u003Cpicture>\n  \u003Csource \n    media=\"(prefers-color-scheme: dark)\" \n    srcset=\"chart-dark.svg\"\n  />\n  \u003Cimg src=\"chart-light.svg\" alt=\"图表\" />\n\u003C/picture>\n\n\u003C!-- SVG 颜色适配 -->\n\u003Csvg class=\"icon\">\n  \u003Ccircle cx=\"50\" cy=\"50\" r=\"40\" fill=\"currentColor\" />\n\u003C/svg>\n\n\u003Cstyle>\n  .icon {\n    color: var(--text-primary);\n  }\n\u003C/style>\n",[873],{"type":26,"tag":425,"props":874,"children":875},{"__ignoreMap":7},[876],{"type":31,"value":871},{"type":26,"tag":68,"props":878,"children":880},{"id":879},"完整示例",[881],{"type":31,"value":879},{"type":26,"tag":417,"props":883,"children":886},{"className":884,"code":885,"language":474,"meta":7},[472],"import { useState, useEffect } from 'react';\n\nfunction ThemeProvider({ children }) {\n  const [theme, setTheme] = useState('light');\n  const [mounted, setMounted] = useState(false);\n  \n  useEffect(() => {\n    setMounted(true);\n    \n    // 获取保存的主题或系统偏好\n    const savedTheme = localStorage.getItem('theme');\n    const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches \n      ? 'dark' \n      : 'light';\n    \n    const initialTheme = savedTheme || systemTheme;\n    setTheme(initialTheme);\n    document.documentElement.setAttribute('data-theme', initialTheme);\n  }, []);\n  \n  const toggleTheme = () => {\n    const newTheme = theme === 'light' ? 'dark' : 'light';\n    setTheme(newTheme);\n    localStorage.setItem('theme', newTheme);\n    document.documentElement.setAttribute('data-theme', newTheme);\n  };\n  \n  // 防止闪烁\n  if (!mounted) {\n    return null;\n  }\n  \n  return (\n    \u003CThemeContext.Provider value={{ theme, toggleTheme }}>\n      {children}\n      \u003CThemeToggle theme={theme} onChange={toggleTheme} />\n    \u003C/ThemeContext.Provider>\n  );\n}\n\nfunction ThemeToggle({ theme, onChange }) {\n  return (\n    \u003Cbutton \n      onClick={onChange}\n      aria-label={`切换到${theme === 'light' ? '暗黑' : '亮色'}模式`}\n    >\n      {theme === 'light' ? '🌙' : '☀️'}\n    \u003C/button>\n  );\n}\n",[887],{"type":26,"tag":425,"props":888,"children":889},{"__ignoreMap":7},[890],{"type":31,"value":885},{"type":26,"tag":68,"props":892,"children":893},{"id":556},[894],{"type":31,"value":556},{"type":26,"tag":27,"props":896,"children":897},{},[898,899,903],{"type":31,"value":563},{"type":26,"tag":565,"props":900,"children":901},{},[902],{"type":31,"value":569},{"type":31,"value":571},{"type":26,"tag":230,"props":905,"children":906},{},[907,912,917,922,927],{"type":26,"tag":234,"props":908,"children":909},{},[910],{"type":31,"value":911},"支持系统偏好",{"type":26,"tag":234,"props":913,"children":914},{},[915],{"type":31,"value":916},"提供手动切换选项",{"type":26,"tag":234,"props":918,"children":919},{},[920],{"type":31,"value":921},"确保足够的对比度",{"type":26,"tag":234,"props":923,"children":924},{},[925],{"type":31,"value":926},"优化图片和图表",{"type":26,"tag":234,"props":928,"children":929},{},[930],{"type":31,"value":931},"防止加载闪烁",{"type":26,"tag":27,"props":933,"children":934},{},[935,936,940],{"type":31,"value":610},{"type":26,"tag":565,"props":937,"children":938},{},[939],{"type":31,"value":615},{"type":31,"value":571},{"type":26,"tag":230,"props":942,"children":943},{},[944,949,954,959,964],{"type":26,"tag":234,"props":945,"children":946},{},[947],{"type":31,"value":948},"强制单一模式",{"type":26,"tag":234,"props":950,"children":951},{},[952],{"type":31,"value":953},"忽视性能影响",{"type":26,"tag":234,"props":955,"children":956},{},[957],{"type":31,"value":958},"使用相同的颜色",{"type":26,"tag":234,"props":960,"children":961},{},[962],{"type":31,"value":963},"忘记保存用户偏好",{"type":26,"tag":234,"props":965,"children":966},{},[967],{"type":31,"value":968},"过度使用深色背景",{"type":26,"tag":68,"props":970,"children":971},{"id":662},[972],{"type":31,"value":662},{"type":26,"tag":230,"props":974,"children":976},{"className":975},[668],[977,986,995,1004,1013],{"type":26,"tag":234,"props":978,"children":980},{"className":979},[673],[981,984],{"type":26,"tag":676,"props":982,"children":983},{"disabled":678,"type":679},[],{"type":31,"value":985}," 在浅色和深色模式下测试所有页面",{"type":26,"tag":234,"props":987,"children":989},{"className":988},[673],[990,993],{"type":26,"tag":676,"props":991,"children":992},{"disabled":678,"type":679},[],{"type":31,"value":994}," 检查颜色对比度符合 WCAG 标准",{"type":26,"tag":234,"props":996,"children":998},{"className":997},[673],[999,1002],{"type":26,"tag":676,"props":1000,"children":1001},{"disabled":678,"type":679},[],{"type":31,"value":1003}," 验证图片和图表在两种模式下清晰",{"type":26,"tag":234,"props":1005,"children":1007},{"className":1006},[673],[1008,1011],{"type":26,"tag":676,"props":1009,"children":1010},{"disabled":678,"type":679},[],{"type":31,"value":1012}," 测试主题切换的平滑性",{"type":26,"tag":234,"props":1014,"children":1016},{"className":1015},[673],[1017,1020],{"type":26,"tag":676,"props":1018,"children":1019},{"disabled":678,"type":679},[],{"type":31,"value":1021}," 检查用户偏好是否被保存",{"title":7,"searchDepth":363,"depth":363,"links":1023},[1024,1025,1029,1034,1035,1036,1037,1038],{"id":742,"depth":366,"text":742},{"id":764,"depth":366,"text":764,"children":1026},[1027,1028],{"id":769,"depth":363,"text":772},{"id":784,"depth":363,"text":787},{"id":799,"depth":366,"text":799,"children":1030},[1031,1032,1033],{"id":804,"depth":363,"text":807},{"id":819,"depth":363,"text":822},{"id":836,"depth":363,"text":839},{"id":851,"depth":366,"text":851},{"id":865,"depth":366,"text":865},{"id":879,"depth":366,"text":879},{"id":556,"depth":366,"text":556},{"id":662,"depth":366,"text":662},"content:topics:design:dark-mode-design.md","topics/design/dark-mode-design.md","topics/design/dark-mode-design",{"_path":1043,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1044,"description":1045,"keywords":1046,"image":1051,"author":1052,"date":391,"readingTime":750,"topic":5,"body":1053,"_type":373,"_id":1318,"_source":375,"_file":1319,"_stem":1320,"_extension":378},"/topics/design/form-controls-design","表单控件设计规范","学习输入框、选择框、复选框等表单控件的设计和实现",[1047,1048,1049,1050,389],"表单设计","Form Controls","输入框","验证反馈","/images/topics/form-controls-design.jpg","AI Content Team",{"type":23,"children":1054,"toc":1304},[1055,1059,1064,1069,1074,1083,1088,1097,1101,1110,1115,1124,1129,1138,1143,1152,1157,1166,1170,1179,1205,1214,1242,1246],{"type":26,"tag":68,"props":1056,"children":1057},{"id":1044},[1058],{"type":31,"value":1044},{"type":26,"tag":27,"props":1060,"children":1061},{},[1062],{"type":31,"value":1063},"优秀的表单设计能够提高用户完成率和满意度。",{"type":26,"tag":68,"props":1065,"children":1067},{"id":1066},"输入框设计",[1068],{"type":31,"value":1066},{"type":26,"tag":410,"props":1070,"children":1072},{"id":1071},"基础文本输入",[1073],{"type":31,"value":1071},{"type":26,"tag":417,"props":1075,"children":1078},{"className":1076,"code":1077,"language":422,"meta":7},[420],".input {\n  width: 100%;\n  padding: 12px 16px;\n  font-size: 16px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  font-family: inherit;\n  transition: border-color 0.2s;\n}\n\n.input:hover {\n  border-color: #bdbdbd;\n}\n\n.input:focus {\n  outline: none;\n  border-color: #0066cc;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n\n.input:disabled {\n  background-color: #f5f5f5;\n  color: #999999;\n  cursor: not-allowed;\n}\n\n.input.error {\n  border-color: #cc0000;\n}\n\n.input.success {\n  border-color: #00cc00;\n}\n",[1079],{"type":26,"tag":425,"props":1080,"children":1081},{"__ignoreMap":7},[1082],{"type":31,"value":1077},{"type":26,"tag":410,"props":1084,"children":1086},{"id":1085},"标签和提示",[1087],{"type":31,"value":1085},{"type":26,"tag":417,"props":1089,"children":1092},{"className":1090,"code":1091,"language":534,"meta":7},[532],"\u003Cdiv class=\"form-group\">\n  \u003Clabel for=\"email\" class=\"form-label\">\n    邮箱地址 \u003Cspan class=\"required\">*\u003C/span>\n  \u003C/label>\n  \u003Cinput\n    id=\"email\"\n    type=\"email\"\n    placeholder=\"user@example.com\"\n    class=\"input\"\n    aria-describedby=\"email-hint\"\n  />\n  \u003Cp id=\"email-hint\" class=\"form-hint\">\n    我们永远不会分享你的邮箱\n  \u003C/p>\n\u003C/div>\n",[1093],{"type":26,"tag":425,"props":1094,"children":1095},{"__ignoreMap":7},[1096],{"type":31,"value":1091},{"type":26,"tag":68,"props":1098,"children":1099},{"id":1050},[1100],{"type":31,"value":1050},{"type":26,"tag":417,"props":1102,"children":1105},{"className":1103,"code":1104,"language":474,"meta":7},[472],"function FormInput({ label, error, success, helperText, value, onChange, ...props }) {\n  return (\n    \u003Cdiv className=\"form-group\">\n      \u003Clabel className=\"form-label\">{label}\u003C/label>\n      \u003Cinput\n        className={`input ${\n          error ? 'error' : success ? 'success' : ''\n        }`}\n        value={value}\n        onChange={onChange}\n        {...props}\n      />\n      {error && (\n        \u003Cp className=\"form-error\" role=\"alert\">\n          {error}\n        \u003C/p>\n      )}\n      {success && (\n        \u003Cp className=\"form-success\">\n          ✓ {success}\n        \u003C/p>\n      )}\n      {helperText && (\n        \u003Cp className=\"form-hint\">{helperText}\u003C/p>\n      )}\n    \u003C/div>\n  );\n}\n",[1106],{"type":26,"tag":425,"props":1107,"children":1108},{"__ignoreMap":7},[1109],{"type":31,"value":1104},{"type":26,"tag":68,"props":1111,"children":1113},{"id":1112},"选择框设计",[1114],{"type":31,"value":1112},{"type":26,"tag":417,"props":1116,"children":1119},{"className":1117,"code":1118,"language":422,"meta":7},[420],".select {\n  appearance: none;\n  width: 100%;\n  padding: 12px 16px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  background-image: url('data:image/svg+xml;...');\n  background-repeat: no-repeat;\n  background-position: right 12px center;\n  padding-right: 40px;\n  font-size: 16px;\n  cursor: pointer;\n}\n\n.select:hover {\n  border-color: #bdbdbd;\n}\n\n.select:focus {\n  outline: none;\n  border-color: #0066cc;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n",[1120],{"type":26,"tag":425,"props":1121,"children":1122},{"__ignoreMap":7},[1123],{"type":31,"value":1118},{"type":26,"tag":68,"props":1125,"children":1127},{"id":1126},"复选框和单选按钮",[1128],{"type":31,"value":1126},{"type":26,"tag":417,"props":1130,"children":1133},{"className":1131,"code":1132,"language":422,"meta":7},[420],".checkbox-group {\n  display: flex;\n  gap: 12px;\n  align-items: center;\n}\n\n.checkbox-input {\n  width: 20px;\n  height: 20px;\n  cursor: pointer;\n  accent-color: #0066cc;\n}\n\n.checkbox-label {\n  cursor: pointer;\n  user-select: none;\n}\n\n/* 自定义复选框 */\n.custom-checkbox {\n  appearance: none;\n  width: 20px;\n  height: 20px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  cursor: pointer;\n  background-color: white;\n  transition: all 0.2s;\n}\n\n.custom-checkbox:checked {\n  background-color: #0066cc;\n  border-color: #0066cc;\n  background-image: url('data:image/svg+xml;...');\n}\n\n.custom-checkbox:focus {\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n",[1134],{"type":26,"tag":425,"props":1135,"children":1136},{"__ignoreMap":7},[1137],{"type":31,"value":1132},{"type":26,"tag":68,"props":1139,"children":1141},{"id":1140},"文本区域",[1142],{"type":31,"value":1140},{"type":26,"tag":417,"props":1144,"children":1147},{"className":1145,"code":1146,"language":422,"meta":7},[420],".textarea {\n  width: 100%;\n  min-height: 120px;\n  padding: 12px 16px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  font-family: inherit;\n  font-size: 16px;\n  resize: vertical;\n  transition: border-color 0.2s;\n}\n\n.textarea:focus {\n  outline: none;\n  border-color: #0066cc;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n",[1148],{"type":26,"tag":425,"props":1149,"children":1150},{"__ignoreMap":7},[1151],{"type":31,"value":1146},{"type":26,"tag":68,"props":1153,"children":1155},{"id":1154},"完整表单示例",[1156],{"type":31,"value":1154},{"type":26,"tag":417,"props":1158,"children":1161},{"className":1159,"code":1160,"language":474,"meta":7},[472],"function SignupForm() {\n  const [formData, setFormData] = useState({\n    name: '',\n    email: '',\n    password: '',\n    confirmPassword: '',\n    subscribe: false,\n    terms: false,\n  });\n  \n  const [errors, setErrors] = useState({});\n  const [touched, setTouched] = useState({});\n  const [submitted, setSubmitted] = useState(false);\n  \n  const handleChange = (e) => {\n    const { name, value, type, checked } = e.target;\n    setFormData(prev => ({\n      ...prev,\n      [name]: type === 'checkbox' ? checked : value,\n    }));\n    \n    // 实时验证\n    if (touched[name]) {\n      validateField(name, type === 'checkbox' ? checked : value);\n    }\n  };\n  \n  const handleBlur = (e) => {\n    const { name } = e.target;\n    setTouched(prev => ({ ...prev, [name]: true }));\n    validateField(name, formData[name]);\n  };\n  \n  const validateField = (name, value) => {\n    const newErrors = { ...errors };\n    \n    switch (name) {\n      case 'name':\n        if (!value) newErrors.name = '名字不能为空';\n        else delete newErrors.name;\n        break;\n      case 'email':\n        if (!value) newErrors.email = '邮箱不能为空';\n        else if (!/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(value)) {\n          newErrors.email = '请输入有效的邮箱';\n        } else {\n          delete newErrors.email;\n        }\n        break;\n      case 'password':\n        if (!value) newErrors.password = '密码不能为空';\n        else if (value.length \u003C 8) newErrors.password = '密码至少 8 位';\n        else delete newErrors.password;\n        break;\n      case 'confirmPassword':\n        if (value !== formData.password) {\n          newErrors.confirmPassword = '两次密码输入不一致';\n        } else {\n          delete newErrors.confirmPassword;\n        }\n        break;\n      case 'terms':\n        if (!value) newErrors.terms = '必须同意服务条款';\n        else delete newErrors.terms;\n        break;\n      default:\n        break;\n    }\n    \n    setErrors(newErrors);\n  };\n  \n  const validate = () => {\n    const newErrors = {};\n    \n    if (!formData.name) newErrors.name = '名字不能为空';\n    if (!formData.email) newErrors.email = '邮箱不能为空';\n    if (formData.password.length \u003C 8) newErrors.password = '密码至少 8 位';\n    if (formData.password !== formData.confirmPassword) {\n      newErrors.confirmPassword = '两次密码输入不一致';\n    }\n    if (!formData.terms) newErrors.terms = '必须同意服务条款';\n    \n    return newErrors;\n  };\n  \n  const handleSubmit = async (e) => {\n    e.preventDefault();\n    \n    // 标记所有字段已触碰\n    setTouched({\n      name: true,\n      email: true,\n      password: true,\n      confirmPassword: true,\n      terms: true,\n    });\n    \n    const newErrors = validate();\n    \n    if (Object.keys(newErrors).length === 0) {\n      setSubmitted(true);\n      // 提交表单\n      console.log('Form submitted:', formData);\n      // 重置表单\n      setFormData({\n        name: '',\n        email: '',\n        password: '',\n        confirmPassword: '',\n        subscribe: false,\n        terms: false,\n      });\n    } else {\n      setErrors(newErrors);\n    }\n  };\n  \n  return (\n    \u003Cform onSubmit={handleSubmit} noValidate>\n      {submitted && (\n        \u003Cdiv className=\"form-success-message\" role=\"alert\">\n          注册成功！\n        \u003C/div>\n      )}\n      \n      \u003CFormInput\n        label=\"姓名\"\n        name=\"name\"\n        value={formData.name}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.name && errors.name}\n        helperText=\"请输入你的全名\"\n      />\n      \n      \u003CFormInput\n        label=\"邮箱\"\n        name=\"email\"\n        type=\"email\"\n        value={formData.email}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.email && errors.email}\n      />\n      \n      \u003CFormInput\n        label=\"密码\"\n        name=\"password\"\n        type=\"password\"\n        value={formData.password}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.password && errors.password}\n        helperText=\"至少 8 个字符\"\n      />\n      \n      \u003CFormInput\n        label=\"确认密码\"\n        name=\"confirmPassword\"\n        type=\"password\"\n        value={formData.confirmPassword}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.confirmPassword && errors.confirmPassword}\n      />\n      \n      \u003Cdiv className=\"form-group\">\n        \u003Clabel className=\"checkbox-label\">\n          \u003Cinput\n            type=\"checkbox\"\n            name=\"subscribe\"\n            checked={formData.subscribe}\n            onChange={handleChange}\n            className=\"checkbox-input\"\n          />\n          订阅我们的新闻通讯\n        \u003C/label>\n      \u003C/div>\n      \n      \u003Cdiv className=\"form-group\">\n        \u003Clabel className=\"checkbox-label\">\n          \u003Cinput\n            type=\"checkbox\"\n            name=\"terms\"\n            checked={formData.terms}\n            onChange={handleChange}\n            onBlur={handleBlur}\n            className=\"checkbox-input\"\n          />\n          我同意\n          \u003Ca href=\"/terms\" target=\"_blank\" rel=\"noopener noreferrer\">\n            服务条款\n          \u003C/a>\n          和\n          \u003Ca href=\"/privacy\" target=\"_blank\" rel=\"noopener noreferrer\">\n            隐私政策\n          \u003C/a>\n        \u003C/label>\n        {touched.terms && errors.terms && (\n          \u003Cp className=\"form-error\">{errors.terms}\u003C/p>\n        )}\n      \u003C/div>\n      \n      \u003Cbutton type=\"submit\" className=\"btn btn-primary btn-block\">\n        注册\n      \u003C/button>\n    \u003C/form>\n  );\n}\n",[1162],{"type":26,"tag":425,"props":1163,"children":1164},{"__ignoreMap":7},[1165],{"type":31,"value":1160},{"type":26,"tag":68,"props":1167,"children":1168},{"id":556},[1169],{"type":31,"value":556},{"type":26,"tag":27,"props":1171,"children":1172},{},[1173,1174,1178],{"type":31,"value":563},{"type":26,"tag":565,"props":1175,"children":1176},{},[1177],{"type":31,"value":569},{"type":31,"value":571},{"type":26,"tag":230,"props":1180,"children":1181},{},[1182,1187,1192,1197,1201],{"type":26,"tag":234,"props":1183,"children":1184},{},[1185],{"type":31,"value":1186},"使用正确的输入类型",{"type":26,"tag":234,"props":1188,"children":1189},{},[1190],{"type":31,"value":1191},"提供实时验证反馈",{"type":26,"tag":234,"props":1193,"children":1194},{},[1195],{"type":31,"value":1196},"清晰的标签和提示",{"type":26,"tag":234,"props":1198,"children":1199},{},[1200],{"type":31,"value":579},{"type":26,"tag":234,"props":1202,"children":1203},{},[1204],{"type":31,"value":605},{"type":26,"tag":27,"props":1206,"children":1207},{},[1208,1209,1213],{"type":31,"value":610},{"type":26,"tag":565,"props":1210,"children":1211},{},[1212],{"type":31,"value":615},{"type":31,"value":571},{"type":26,"tag":230,"props":1215,"children":1216},{},[1217,1222,1227,1232,1237],{"type":26,"tag":234,"props":1218,"children":1219},{},[1220],{"type":31,"value":1221},"隐藏标签",{"type":26,"tag":234,"props":1223,"children":1224},{},[1225],{"type":31,"value":1226},"过度使用占位符",{"type":26,"tag":234,"props":1228,"children":1229},{},[1230],{"type":31,"value":1231},"验证后立即提交",{"type":26,"tag":234,"props":1233,"children":1234},{},[1235],{"type":31,"value":1236},"忽视无障碍性",{"type":26,"tag":234,"props":1238,"children":1239},{},[1240],{"type":31,"value":1241},"复杂的验证规则",{"type":26,"tag":68,"props":1243,"children":1244},{"id":662},[1245],{"type":31,"value":662},{"type":26,"tag":230,"props":1247,"children":1249},{"className":1248},[668],[1250,1259,1268,1277,1286,1295],{"type":26,"tag":234,"props":1251,"children":1253},{"className":1252},[673],[1254,1257],{"type":26,"tag":676,"props":1255,"children":1256},{"disabled":678,"type":679},[],{"type":31,"value":1258}," 所有控件都可用键盘导航",{"type":26,"tag":234,"props":1260,"children":1262},{"className":1261},[673],[1263,1266],{"type":26,"tag":676,"props":1264,"children":1265},{"disabled":678,"type":679},[],{"type":31,"value":1267}," 标签与输入框关联",{"type":26,"tag":234,"props":1269,"children":1271},{"className":1270},[673],[1272,1275],{"type":26,"tag":676,"props":1273,"children":1274},{"disabled":678,"type":679},[],{"type":31,"value":1276}," 验证消息清晰",{"type":26,"tag":234,"props":1278,"children":1280},{"className":1279},[673],[1281,1284],{"type":26,"tag":676,"props":1282,"children":1283},{"disabled":678,"type":679},[],{"type":31,"value":1285}," 色彩对比度足够",{"type":26,"tag":234,"props":1287,"children":1289},{"className":1288},[673],[1290,1293],{"type":26,"tag":676,"props":1291,"children":1292},{"disabled":678,"type":679},[],{"type":31,"value":1294}," 屏幕阅读器兼容",{"type":26,"tag":234,"props":1296,"children":1298},{"className":1297},[673],[1299,1302],{"type":26,"tag":676,"props":1300,"children":1301},{"disabled":678,"type":679},[],{"type":31,"value":1303}," 移动设备测试",{"title":7,"searchDepth":363,"depth":363,"links":1305},[1306,1307,1311,1312,1313,1314,1315,1316,1317],{"id":1044,"depth":366,"text":1044},{"id":1066,"depth":366,"text":1066,"children":1308},[1309,1310],{"id":1071,"depth":363,"text":1071},{"id":1085,"depth":363,"text":1085},{"id":1050,"depth":366,"text":1050},{"id":1112,"depth":366,"text":1112},{"id":1126,"depth":366,"text":1126},{"id":1140,"depth":366,"text":1140},{"id":1154,"depth":366,"text":1154},{"id":556,"depth":366,"text":556},{"id":662,"depth":366,"text":662},"content:topics:design:form-controls-design.md","topics/design/form-controls-design.md","topics/design/form-controls-design",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":1322,"image":17,"imageQuery":18,"pexelsPhotoId":19,"pexelsUrl":20,"featured":6,"readingTime":21,"body":1323,"_type":373,"_id":374,"_source":375,"_file":376,"_stem":377,"_extension":378},[13,14,15,16],{"type":23,"children":1324,"toc":1583},[1325,1329,1333,1352,1356,1455,1459,1463,1467,1471,1475,1479,1494,1498,1502,1506,1521,1525,1529,1533,1537,1541,1560,1564,1579],{"type":26,"tag":27,"props":1326,"children":1327},{},[1328],{"type":31,"value":32},{"type":26,"tag":27,"props":1330,"children":1331},{},[1332],{"type":31,"value":37},{"type":26,"tag":27,"props":1334,"children":1335},{},[1336,1337,1341,1342,1346,1347,1351],{"type":31,"value":42},{"type":26,"tag":44,"props":1338,"children":1339},{"href":46},[1340],{"type":31,"value":49},{"type":31,"value":51},{"type":26,"tag":44,"props":1343,"children":1344},{"href":54},[1345],{"type":31,"value":57},{"type":31,"value":51},{"type":26,"tag":44,"props":1348,"children":1349},{"href":61},[1350],{"type":31,"value":64},{"type":31,"value":66},{"type":26,"tag":68,"props":1353,"children":1354},{"id":70},[1355],{"type":31,"value":73},{"type":26,"tag":75,"props":1357,"children":1358},{},[1359,1377],{"type":26,"tag":79,"props":1360,"children":1361},{},[1362],{"type":26,"tag":83,"props":1363,"children":1364},{},[1365,1369,1373],{"type":26,"tag":87,"props":1366,"children":1367},{},[1368],{"type":31,"value":91},{"type":26,"tag":87,"props":1370,"children":1371},{},[1372],{"type":31,"value":96},{"type":26,"tag":87,"props":1374,"children":1375},{},[1376],{"type":31,"value":101},{"type":26,"tag":103,"props":1378,"children":1379},{},[1380,1395,1410,1425,1440],{"type":26,"tag":83,"props":1381,"children":1382},{},[1383,1387,1391],{"type":26,"tag":110,"props":1384,"children":1385},{},[1386],{"type":31,"value":114},{"type":26,"tag":110,"props":1388,"children":1389},{},[1390],{"type":31,"value":119},{"type":26,"tag":110,"props":1392,"children":1393},{},[1394],{"type":31,"value":124},{"type":26,"tag":83,"props":1396,"children":1397},{},[1398,1402,1406],{"type":26,"tag":110,"props":1399,"children":1400},{},[1401],{"type":31,"value":132},{"type":26,"tag":110,"props":1403,"children":1404},{},[1405],{"type":31,"value":137},{"type":26,"tag":110,"props":1407,"children":1408},{},[1409],{"type":31,"value":142},{"type":26,"tag":83,"props":1411,"children":1412},{},[1413,1417,1421],{"type":26,"tag":110,"props":1414,"children":1415},{},[1416],{"type":31,"value":150},{"type":26,"tag":110,"props":1418,"children":1419},{},[1420],{"type":31,"value":155},{"type":26,"tag":110,"props":1422,"children":1423},{},[1424],{"type":31,"value":160},{"type":26,"tag":83,"props":1426,"children":1427},{},[1428,1432,1436],{"type":26,"tag":110,"props":1429,"children":1430},{},[1431],{"type":31,"value":168},{"type":26,"tag":110,"props":1433,"children":1434},{},[1435],{"type":31,"value":173},{"type":26,"tag":110,"props":1437,"children":1438},{},[1439],{"type":31,"value":178},{"type":26,"tag":83,"props":1441,"children":1442},{},[1443,1447,1451],{"type":26,"tag":110,"props":1444,"children":1445},{},[1446],{"type":31,"value":186},{"type":26,"tag":110,"props":1448,"children":1449},{},[1450],{"type":31,"value":191},{"type":26,"tag":110,"props":1452,"children":1453},{},[1454],{"type":31,"value":196},{"type":26,"tag":68,"props":1456,"children":1457},{"id":199},[1458],{"type":31,"value":202},{"type":26,"tag":27,"props":1460,"children":1461},{},[1462],{"type":31,"value":207},{"type":26,"tag":27,"props":1464,"children":1465},{},[1466],{"type":31,"value":212},{"type":26,"tag":68,"props":1468,"children":1469},{"id":215},[1470],{"type":31,"value":218},{"type":26,"tag":27,"props":1472,"children":1473},{},[1474],{"type":31,"value":223},{"type":26,"tag":27,"props":1476,"children":1477},{},[1478],{"type":31,"value":228},{"type":26,"tag":230,"props":1480,"children":1481},{},[1482,1486,1490],{"type":26,"tag":234,"props":1483,"children":1484},{},[1485],{"type":31,"value":238},{"type":26,"tag":234,"props":1487,"children":1488},{},[1489],{"type":31,"value":243},{"type":26,"tag":234,"props":1491,"children":1492},{},[1493],{"type":31,"value":248},{"type":26,"tag":27,"props":1495,"children":1496},{},[1497],{"type":31,"value":253},{"type":26,"tag":68,"props":1499,"children":1500},{"id":256},[1501],{"type":31,"value":259},{"type":26,"tag":27,"props":1503,"children":1504},{},[1505],{"type":31,"value":264},{"type":26,"tag":230,"props":1507,"children":1508},{},[1509,1513,1517],{"type":26,"tag":234,"props":1510,"children":1511},{},[1512],{"type":31,"value":272},{"type":26,"tag":234,"props":1514,"children":1515},{},[1516],{"type":31,"value":277},{"type":26,"tag":234,"props":1518,"children":1519},{},[1520],{"type":31,"value":282},{"type":26,"tag":27,"props":1522,"children":1523},{},[1524],{"type":31,"value":287},{"type":26,"tag":68,"props":1526,"children":1527},{"id":290},[1528],{"type":31,"value":293},{"type":26,"tag":27,"props":1530,"children":1531},{},[1532],{"type":31,"value":298},{"type":26,"tag":27,"props":1534,"children":1535},{},[1536],{"type":31,"value":303},{"type":26,"tag":68,"props":1538,"children":1539},{"id":306},[1540],{"type":31,"value":306},{"type":26,"tag":230,"props":1542,"children":1543},{},[1544,1548,1552,1556],{"type":26,"tag":234,"props":1545,"children":1546},{},[1547],{"type":31,"value":316},{"type":26,"tag":234,"props":1549,"children":1550},{},[1551],{"type":31,"value":321},{"type":26,"tag":234,"props":1553,"children":1554},{},[1555],{"type":31,"value":326},{"type":26,"tag":234,"props":1557,"children":1558},{},[1559],{"type":31,"value":331},{"type":26,"tag":68,"props":1561,"children":1562},{"id":334},[1563],{"type":31,"value":337},{"type":26,"tag":339,"props":1565,"children":1566},{},[1567,1571,1575],{"type":26,"tag":234,"props":1568,"children":1569},{},[1570],{"type":31,"value":346},{"type":26,"tag":234,"props":1572,"children":1573},{},[1574],{"type":31,"value":351},{"type":26,"tag":234,"props":1576,"children":1577},{},[1578],{"type":31,"value":356},{"type":26,"tag":27,"props":1580,"children":1581},{},[1582],{"type":31,"value":361},{"title":7,"searchDepth":363,"depth":363,"links":1584},[1585,1586,1587,1588,1589,1590,1591],{"id":70,"depth":366,"text":73},{"id":199,"depth":366,"text":202},{"id":215,"depth":366,"text":218},{"id":256,"depth":366,"text":259},{"id":290,"depth":366,"text":293},{"id":306,"depth":366,"text":306},{"id":334,"depth":366,"text":337},1780533130395]