[{"data":1,"prerenderedAt":1563},["ShallowReactive",2],{"article-/topics/design/loading-state-design-best-practices":3,"related-design":359,"content-query-4qanUNKIkG":1301},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":12,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":23,"_type":353,"_id":354,"_source":355,"_file":356,"_stem":357,"_extension":358},"/topics/design/loading-state-design-best-practices","design",false,"","加载状态设计最佳实践：让等待可理解、可预期、可继续行动","加载状态设计的价值不只是放一个 spinner，而是降低等待焦虑、传达系统进度并保持任务连续性。本文从页面、组件和交互场景出发，讲清加载状态的设计方法。","2026-04-23","HTMLPAGE 团队",[13,14,15,16,17],"Loading State","Interaction Design","UX","Feedback Design","UI Design","/images/topics/design/loading-state-design-best-practices.jpg","app loading interface design on smartphone screen",4114774,"https://www.pexels.com/photo/iphone-6-connecting-to-internet-4114774/",14,{"type":24,"children":25,"toc":342},"root",[26,34,39,64,69,76,81,104,109,115,120,125,143,148,153,158,163,181,186,191,196,201,219,224,230,235,240,258,263,268,296,301,306,311],{"type":27,"tag":28,"props":29,"children":30},"element","p",{},[31],{"type":32,"value":33},"text","很多产品的加载状态只有一种：转圈。",{"type":27,"tag":28,"props":35,"children":36},{},[37],{"type":32,"value":38},"但对用户来说，真正重要的问题通常是：",{"type":27,"tag":40,"props":41,"children":42},"ul",{},[43,49,54,59],{"type":27,"tag":44,"props":45,"children":46},"li",{},[47],{"type":32,"value":48},"系统是不是还活着",{"type":27,"tag":44,"props":50,"children":51},{},[52],{"type":32,"value":53},"我要等多久",{"type":27,"tag":44,"props":55,"children":56},{},[57],{"type":32,"value":58},"等待期间还能不能继续做事",{"type":27,"tag":44,"props":60,"children":61},{},[62],{"type":32,"value":63},"如果失败了会发生什么",{"type":27,"tag":28,"props":65,"children":66},{},[67],{"type":32,"value":68},"所以加载状态设计的重点，不是“做一个动画”，而是让等待变得可理解、可预期、可恢复。",{"type":27,"tag":70,"props":71,"children":73},"h2",{"id":72},"加载状态要按场景分层而不是全站统一一个样式",[74],{"type":32,"value":75},"加载状态要按场景分层，而不是全站统一一个样式",{"type":27,"tag":28,"props":77,"children":78},{},[79],{"type":32,"value":80},"不同场景的等待成本完全不同：",{"type":27,"tag":40,"props":82,"children":83},{},[84,89,94,99],{"type":27,"tag":44,"props":85,"children":86},{},[87],{"type":32,"value":88},"页面首屏加载",{"type":27,"tag":44,"props":90,"children":91},{},[92],{"type":32,"value":93},"列表刷新",{"type":27,"tag":44,"props":95,"children":96},{},[97],{"type":32,"value":98},"表单提交",{"type":27,"tag":44,"props":100,"children":101},{},[102],{"type":32,"value":103},"局部模块异步更新",{"type":27,"tag":28,"props":105,"children":106},{},[107],{"type":32,"value":108},"如果这些场景都用同一种加载反馈，用户很快就会失去上下文。",{"type":27,"tag":70,"props":110,"children":112},{"id":111},"skeletonspinnerprogress-适用边界不同",[113],{"type":32,"value":114},"Skeleton、spinner、progress 适用边界不同",{"type":27,"tag":28,"props":116,"children":117},{},[118],{"type":32,"value":119},"加载状态设计里最常见的误区，是把某一种形式当成标准答案。",{"type":27,"tag":28,"props":121,"children":122},{},[123],{"type":32,"value":124},"更合理的判断通常是：",{"type":27,"tag":40,"props":126,"children":127},{},[128,133,138],{"type":27,"tag":44,"props":129,"children":130},{},[131],{"type":32,"value":132},"Skeleton：适合结构已知、内容待填充的场景",{"type":27,"tag":44,"props":134,"children":135},{},[136],{"type":32,"value":137},"Spinner：适合短时、不需要结构预览的动作",{"type":27,"tag":44,"props":139,"children":140},{},[141],{"type":32,"value":142},"Progress：适合有明确进度阶段或耗时较长的任务",{"type":27,"tag":28,"props":144,"children":145},{},[146],{"type":32,"value":147},"用错形式，信息传递会明显变差。",{"type":27,"tag":70,"props":149,"children":151},{"id":150},"等待不应该中断所有任务上下文",[152],{"type":32,"value":150},{"type":27,"tag":28,"props":154,"children":155},{},[156],{"type":32,"value":157},"很多产品一进入加载就整块遮罩，这会带来一个问题：用户失去当前位置和已知信息。",{"type":27,"tag":28,"props":159,"children":160},{},[161],{"type":32,"value":162},"更好的做法往往是尽量局部加载：",{"type":27,"tag":40,"props":164,"children":165},{},[166,171,176],{"type":27,"tag":44,"props":167,"children":168},{},[169],{"type":32,"value":170},"保留已知内容",{"type":27,"tag":44,"props":172,"children":173},{},[174],{"type":32,"value":175},"只替换正在刷新的区域",{"type":27,"tag":44,"props":177,"children":178},{},[179],{"type":32,"value":180},"让用户知道系统正在处理哪一部分",{"type":27,"tag":28,"props":182,"children":183},{},[184],{"type":32,"value":185},"加载状态的目标不是遮挡，而是解释。",{"type":27,"tag":70,"props":187,"children":189},{"id":188},"提交型加载要特别强调结果反馈",[190],{"type":32,"value":188},{"type":27,"tag":28,"props":192,"children":193},{},[194],{"type":32,"value":195},"表单提交、批量操作、支付确认这类场景，如果只有“处理中”，用户很容易反复点击或产生不确定感。",{"type":27,"tag":28,"props":197,"children":198},{},[199],{"type":32,"value":200},"因此提交型加载通常要同时说明：",{"type":27,"tag":40,"props":202,"children":203},{},[204,209,214],{"type":27,"tag":44,"props":205,"children":206},{},[207],{"type":32,"value":208},"当前动作是否已受理",{"type":27,"tag":44,"props":210,"children":211},{},[212],{"type":32,"value":213},"是否还能取消",{"type":27,"tag":44,"props":215,"children":216},{},[217],{"type":32,"value":218},"成功或失败后下一步是什么",{"type":27,"tag":28,"props":220,"children":221},{},[222],{"type":32,"value":223},"这类加载更接近任务状态，而不是视觉状态。",{"type":27,"tag":70,"props":225,"children":227},{"id":226},"一个常见失败案例页面明明在工作用户却以为卡住了",[228],{"type":32,"value":229},"一个常见失败案例：页面明明在工作，用户却以为卡住了",{"type":27,"tag":28,"props":231,"children":232},{},[233],{"type":32,"value":234},"这类问题的根因通常是加载反馈信息量不足。",{"type":27,"tag":28,"props":236,"children":237},{},[238],{"type":32,"value":239},"系统可能真的在执行，但用户看不到：",{"type":27,"tag":40,"props":241,"children":242},{},[243,248,253],{"type":27,"tag":44,"props":244,"children":245},{},[246],{"type":32,"value":247},"哪部分正在加载",{"type":27,"tag":44,"props":249,"children":250},{},[251],{"type":32,"value":252},"是否还能继续操作",{"type":27,"tag":44,"props":254,"children":255},{},[256],{"type":32,"value":257},"如果超时会怎样",{"type":27,"tag":28,"props":259,"children":260},{},[261],{"type":32,"value":262},"结果就是等待焦虑被迅速放大。",{"type":27,"tag":70,"props":264,"children":266},{"id":265},"一份可直接复用的检查清单",[267],{"type":32,"value":265},{"type":27,"tag":40,"props":269,"children":270},{},[271,276,281,286,291],{"type":27,"tag":44,"props":272,"children":273},{},[274],{"type":32,"value":275},"是否按页面、模块、提交动作等不同场景设计加载状态",{"type":27,"tag":44,"props":277,"children":278},{},[279],{"type":32,"value":280},"Skeleton、spinner、progress 是否各自用在合适场景",{"type":27,"tag":44,"props":282,"children":283},{},[284],{"type":32,"value":285},"加载反馈是否尽量保留上下文而不是整体遮挡",{"type":27,"tag":44,"props":287,"children":288},{},[289],{"type":32,"value":290},"提交型动作是否明确告知受理状态和后续结果",{"type":27,"tag":44,"props":292,"children":293},{},[294],{"type":32,"value":295},"超时、失败和取消路径是否被设计出来",{"type":27,"tag":70,"props":297,"children":299},{"id":298},"总结",[300],{"type":32,"value":298},{"type":27,"tag":28,"props":302,"children":303},{},[304],{"type":32,"value":305},"加载状态设计真正解决的，不是视觉空白，而是用户在等待中的不确定感。只要先把场景分层、反馈形式和结果说明设计清楚，等待体验就会稳定很多。",{"type":27,"tag":28,"props":307,"children":308},{},[309],{"type":32,"value":310},"进一步阅读：",{"type":27,"tag":40,"props":312,"children":313},{},[314,324,333],{"type":27,"tag":44,"props":315,"children":316},{},[317],{"type":27,"tag":318,"props":319,"children":321},"a",{"href":320},"/topics/design/feedback-component-guide",[322],{"type":32,"value":323},"状态反馈组件设计",{"type":27,"tag":44,"props":325,"children":326},{},[327],{"type":27,"tag":318,"props":328,"children":330},{"href":329},"/topics/design/gesture-interaction-design-specification",[331],{"type":32,"value":332},"手势交互设计规范",{"type":27,"tag":44,"props":334,"children":335},{},[336],{"type":27,"tag":318,"props":337,"children":339},{"href":338},"/topics/design/data-table-component-design-guide",[340],{"type":32,"value":341},"数据表格组件设计指南",{"title":7,"searchDepth":343,"depth":343,"links":344},3,[345,347,348,349,350,351,352],{"id":72,"depth":346,"text":75},2,{"id":111,"depth":346,"text":114},{"id":150,"depth":346,"text":150},{"id":188,"depth":346,"text":188},{"id":226,"depth":346,"text":229},{"id":265,"depth":346,"text":265},{"id":298,"depth":346,"text":298},"markdown","content:topics:design:loading-state-design-best-practices.md","content","topics/design/loading-state-design-best-practices.md","topics/design/loading-state-design-best-practices","md",[360,720,1022],{"_path":361,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":362,"description":363,"keywords":364,"image":370,"author":11,"date":371,"readingTime":372,"topic":5,"body":373,"_type":353,"_id":717,"_source":355,"_file":718,"_stem":719,"_extension":358},"/topics/design/button-component-design","按钮组件设计详解","学习按钮样式、交互状态、无障碍性和最佳实践",[365,366,367,368,369],"按钮设计","Button Component","交互状态","UI 组件","用户体验","/images/topics/button-design.jpg","2025-12-08",18,{"type":24,"children":374,"toc":699},[375,379,384,389,396,409,415,424,430,439,443,449,460,466,475,481,490,495,504,509,520,525,534,539,552,586,597,640,645],{"type":27,"tag":70,"props":376,"children":377},{"id":362},[378],{"type":32,"value":362},{"type":27,"tag":28,"props":380,"children":381},{},[382],{"type":32,"value":383},"按钮是 UI 中最重要的交互元素。优秀的按钮设计能够指导用户行为。",{"type":27,"tag":70,"props":385,"children":387},{"id":386},"按钮类型",[388],{"type":32,"value":386},{"type":27,"tag":390,"props":391,"children":393},"h3",{"id":392},"primary-button主按钮",[394],{"type":32,"value":395},"Primary Button（主按钮）",{"type":27,"tag":397,"props":398,"children":403},"pre",{"className":399,"code":401,"language":402,"meta":7},[400],"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",[404],{"type":27,"tag":405,"props":406,"children":407},"code",{"__ignoreMap":7},[408],{"type":32,"value":401},{"type":27,"tag":390,"props":410,"children":412},{"id":411},"secondary-button次按钮",[413],{"type":32,"value":414},"Secondary Button（次按钮）",{"type":27,"tag":397,"props":416,"children":419},{"className":417,"code":418,"language":402,"meta":7},[400],".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",[420],{"type":27,"tag":405,"props":421,"children":422},{"__ignoreMap":7},[423],{"type":32,"value":418},{"type":27,"tag":390,"props":425,"children":427},{"id":426},"danger-button危险按钮",[428],{"type":32,"value":429},"Danger Button（危险按钮）",{"type":27,"tag":397,"props":431,"children":434},{"className":432,"code":433,"language":402,"meta":7},[400],".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",[435],{"type":27,"tag":405,"props":436,"children":437},{"__ignoreMap":7},[438],{"type":32,"value":433},{"type":27,"tag":70,"props":440,"children":441},{"id":367},[442],{"type":32,"value":367},{"type":27,"tag":390,"props":444,"children":446},{"id":445},"loading-状态",[447],{"type":32,"value":448},"Loading 状态",{"type":27,"tag":397,"props":450,"children":455},{"className":451,"code":453,"language":454,"meta":7},[452],"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",[456],{"type":27,"tag":405,"props":457,"children":458},{"__ignoreMap":7},[459],{"type":32,"value":453},{"type":27,"tag":390,"props":461,"children":463},{"id":462},"disabled-状态",[464],{"type":32,"value":465},"Disabled 状态",{"type":27,"tag":397,"props":467,"children":470},{"className":468,"code":469,"language":402,"meta":7},[400],".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",[471],{"type":27,"tag":405,"props":472,"children":473},{"__ignoreMap":7},[474],{"type":32,"value":469},{"type":27,"tag":390,"props":476,"children":478},{"id":477},"focus-状态",[479],{"type":32,"value":480},"Focus 状态",{"type":27,"tag":397,"props":482,"children":485},{"className":483,"code":484,"language":402,"meta":7},[400],".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",[486],{"type":27,"tag":405,"props":487,"children":488},{"__ignoreMap":7},[489],{"type":32,"value":484},{"type":27,"tag":70,"props":491,"children":493},{"id":492},"按钮大小",[494],{"type":32,"value":492},{"type":27,"tag":397,"props":496,"children":499},{"className":497,"code":498,"language":402,"meta":7},[400],"/* 小按钮 */\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",[500],{"type":27,"tag":405,"props":501,"children":502},{"__ignoreMap":7},[503],{"type":32,"value":498},{"type":27,"tag":70,"props":505,"children":507},{"id":506},"无障碍性",[508],{"type":32,"value":506},{"type":27,"tag":397,"props":510,"children":515},{"className":511,"code":513,"language":514,"meta":7},[512],"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",[516],{"type":27,"tag":405,"props":517,"children":518},{"__ignoreMap":7},[519],{"type":32,"value":513},{"type":27,"tag":70,"props":521,"children":523},{"id":522},"完整组件示例",[524],{"type":32,"value":522},{"type":27,"tag":397,"props":526,"children":529},{"className":527,"code":528,"language":454,"meta":7},[452],"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",[530],{"type":27,"tag":405,"props":531,"children":532},{"__ignoreMap":7},[533],{"type":32,"value":528},{"type":27,"tag":70,"props":535,"children":537},{"id":536},"最佳实践",[538],{"type":32,"value":536},{"type":27,"tag":28,"props":540,"children":541},{},[542,544,550],{"type":32,"value":543},"✅ ",{"type":27,"tag":545,"props":546,"children":547},"strong",{},[548],{"type":32,"value":549},"应该做的事",{"type":32,"value":551},":",{"type":27,"tag":40,"props":553,"children":554},{},[555,560,565,576,581],{"type":27,"tag":44,"props":556,"children":557},{},[558],{"type":32,"value":559},"最小触摸目标 44x44px",{"type":27,"tag":44,"props":561,"children":562},{},[563],{"type":32,"value":564},"清晰的视觉反馈",{"type":27,"tag":44,"props":566,"children":567},{},[568,570],{"type":32,"value":569},"使用语义 HTML ",{"type":27,"tag":405,"props":571,"children":573},{"className":572},[],[574],{"type":32,"value":575},"\u003Cbutton>",{"type":27,"tag":44,"props":577,"children":578},{},[579],{"type":32,"value":580},"提供加载状态反馈",{"type":27,"tag":44,"props":582,"children":583},{},[584],{"type":32,"value":585},"支持键盘导航",{"type":27,"tag":28,"props":587,"children":588},{},[589,591,596],{"type":32,"value":590},"❌ ",{"type":27,"tag":545,"props":592,"children":593},{},[594],{"type":32,"value":595},"不应该做的事",{"type":32,"value":551},{"type":27,"tag":40,"props":598,"children":599},{},[600,613,618,623,628],{"type":27,"tag":44,"props":601,"children":602},{},[603,605,611],{"type":32,"value":604},"使用 ",{"type":27,"tag":405,"props":606,"children":608},{"className":607},[],[609],{"type":32,"value":610},"\u003Cdiv>",{"type":32,"value":612}," 模拟按钮",{"type":27,"tag":44,"props":614,"children":615},{},[616],{"type":32,"value":617},"隐藏焦点指示器",{"type":27,"tag":44,"props":619,"children":620},{},[621],{"type":32,"value":622},"过多的按钮样式",{"type":27,"tag":44,"props":624,"children":625},{},[626],{"type":32,"value":627},"忽视禁用状态",{"type":27,"tag":44,"props":629,"children":630},{},[631,632,638],{"type":32,"value":604},{"type":27,"tag":405,"props":633,"children":635},{"className":634},[],[636],{"type":32,"value":637},"\u003Ca>",{"type":32,"value":639}," 代替按钮",{"type":27,"tag":70,"props":641,"children":643},{"id":642},"测试清单",[644],{"type":32,"value":642},{"type":27,"tag":40,"props":646,"children":649},{"className":647},[648],"contains-task-list",[650,663,672,681,690],{"type":27,"tag":44,"props":651,"children":654},{"className":652},[653],"task-list-item",[655,661],{"type":27,"tag":656,"props":657,"children":660},"input",{"disabled":658,"type":659},true,"checkbox",[],{"type":32,"value":662}," 在各种浏览器中测试",{"type":27,"tag":44,"props":664,"children":666},{"className":665},[653],[667,670],{"type":27,"tag":656,"props":668,"children":669},{"disabled":658,"type":659},[],{"type":32,"value":671}," 验证键盘导航",{"type":27,"tag":44,"props":673,"children":675},{"className":674},[653],[676,679],{"type":27,"tag":656,"props":677,"children":678},{"disabled":658,"type":659},[],{"type":32,"value":680}," 检查色彩对比度",{"type":27,"tag":44,"props":682,"children":684},{"className":683},[653],[685,688],{"type":27,"tag":656,"props":686,"children":687},{"disabled":658,"type":659},[],{"type":32,"value":689}," 测试触摸设备",{"type":27,"tag":44,"props":691,"children":693},{"className":692},[653],[694,697],{"type":27,"tag":656,"props":695,"children":696},{"disabled":658,"type":659},[],{"type":32,"value":698}," 屏幕阅读器兼容性",{"title":7,"searchDepth":343,"depth":343,"links":700},[701,702,707,712,713,714,715,716],{"id":362,"depth":346,"text":362},{"id":386,"depth":346,"text":386,"children":703},[704,705,706],{"id":392,"depth":343,"text":395},{"id":411,"depth":343,"text":414},{"id":426,"depth":343,"text":429},{"id":367,"depth":346,"text":367,"children":708},[709,710,711],{"id":445,"depth":343,"text":448},{"id":462,"depth":343,"text":465},{"id":477,"depth":343,"text":480},{"id":492,"depth":346,"text":492},{"id":506,"depth":346,"text":506},{"id":522,"depth":346,"text":522},{"id":536,"depth":346,"text":536},{"id":642,"depth":346,"text":642},"content:topics:design:button-component-design.md","topics/design/button-component-design.md","topics/design/button-component-design",{"_path":721,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":722,"description":723,"keywords":724,"image":729,"author":11,"date":371,"readingTime":730,"topic":5,"body":731,"_type":353,"_id":1019,"_source":355,"_file":1020,"_stem":1021,"_extension":358},"/topics/design/dark-mode-design","暗黑模式设计完整方案","学习暗黑模式实现、色彩方案、对比度管理和最佳实践",[725,726,727,728,369],"暗黑模式","Dark Mode","色彩系统","CSS 变量","/images/topics/dark-mode-design.jpg",20,{"type":24,"children":732,"toc":1002},[733,737,742,747,753,762,768,777,782,788,797,803,814,820,829,834,843,848,857,862,871,875,884,912,921,949,953],{"type":27,"tag":70,"props":734,"children":735},{"id":722},[736],{"type":32,"value":722},{"type":27,"tag":28,"props":738,"children":739},{},[740],{"type":32,"value":741},"暗黑模式已成为现代应用的标准功能。它能够减少眼睛疲劳、节省电池、改善用户体验。",{"type":27,"tag":70,"props":743,"children":745},{"id":744},"核心色彩系统",[746],{"type":32,"value":744},{"type":27,"tag":390,"props":748,"children":750},{"id":749},"light-mode-配色",[751],{"type":32,"value":752},"Light Mode 配色",{"type":27,"tag":397,"props":754,"children":757},{"className":755,"code":756,"language":402,"meta":7},[400],":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",[758],{"type":27,"tag":405,"props":759,"children":760},{"__ignoreMap":7},[761],{"type":32,"value":756},{"type":27,"tag":390,"props":763,"children":765},{"id":764},"dark-mode-配色",[766],{"type":32,"value":767},"Dark Mode 配色",{"type":27,"tag":397,"props":769,"children":772},{"className":770,"code":771,"language":402,"meta":7},[400],"@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",[773],{"type":27,"tag":405,"props":774,"children":775},{"__ignoreMap":7},[776],{"type":32,"value":771},{"type":27,"tag":70,"props":778,"children":780},{"id":779},"实现方案",[781],{"type":32,"value":779},{"type":27,"tag":390,"props":783,"children":785},{"id":784},"方案-1prefers-color-scheme",[786],{"type":32,"value":787},"方案 1：prefers-color-scheme",{"type":27,"tag":397,"props":789,"children":792},{"className":790,"code":791,"language":402,"meta":7},[400],"/* 自动跟随系统设置 */\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",[793],{"type":27,"tag":405,"props":794,"children":795},{"__ignoreMap":7},[796],{"type":32,"value":791},{"type":27,"tag":390,"props":798,"children":800},{"id":799},"方案-2javascript-切换",[801],{"type":32,"value":802},"方案 2：JavaScript 切换",{"type":27,"tag":397,"props":804,"children":809},{"className":805,"code":807,"language":808,"meta":7},[806],"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",[810],{"type":27,"tag":405,"props":811,"children":812},{"__ignoreMap":7},[813],{"type":32,"value":807},{"type":27,"tag":390,"props":815,"children":817},{"id":816},"方案-3css-variables-javascript",[818],{"type":32,"value":819},"方案 3：CSS Variables + JavaScript",{"type":27,"tag":397,"props":821,"children":824},{"className":822,"code":823,"language":808,"meta":7},[806],"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",[825],{"type":27,"tag":405,"props":826,"children":827},{"__ignoreMap":7},[828],{"type":32,"value":823},{"type":27,"tag":70,"props":830,"children":832},{"id":831},"对比度管理",[833],{"type":32,"value":831},{"type":27,"tag":397,"props":835,"children":838},{"className":836,"code":837,"language":402,"meta":7},[400],"/* 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",[839],{"type":27,"tag":405,"props":840,"children":841},{"__ignoreMap":7},[842],{"type":32,"value":837},{"type":27,"tag":70,"props":844,"children":846},{"id":845},"图片和图表处理",[847],{"type":32,"value":845},{"type":27,"tag":397,"props":849,"children":852},{"className":850,"code":851,"language":514,"meta":7},[512],"\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",[853],{"type":27,"tag":405,"props":854,"children":855},{"__ignoreMap":7},[856],{"type":32,"value":851},{"type":27,"tag":70,"props":858,"children":860},{"id":859},"完整示例",[861],{"type":32,"value":859},{"type":27,"tag":397,"props":863,"children":866},{"className":864,"code":865,"language":454,"meta":7},[452],"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",[867],{"type":27,"tag":405,"props":868,"children":869},{"__ignoreMap":7},[870],{"type":32,"value":865},{"type":27,"tag":70,"props":872,"children":873},{"id":536},[874],{"type":32,"value":536},{"type":27,"tag":28,"props":876,"children":877},{},[878,879,883],{"type":32,"value":543},{"type":27,"tag":545,"props":880,"children":881},{},[882],{"type":32,"value":549},{"type":32,"value":551},{"type":27,"tag":40,"props":885,"children":886},{},[887,892,897,902,907],{"type":27,"tag":44,"props":888,"children":889},{},[890],{"type":32,"value":891},"支持系统偏好",{"type":27,"tag":44,"props":893,"children":894},{},[895],{"type":32,"value":896},"提供手动切换选项",{"type":27,"tag":44,"props":898,"children":899},{},[900],{"type":32,"value":901},"确保足够的对比度",{"type":27,"tag":44,"props":903,"children":904},{},[905],{"type":32,"value":906},"优化图片和图表",{"type":27,"tag":44,"props":908,"children":909},{},[910],{"type":32,"value":911},"防止加载闪烁",{"type":27,"tag":28,"props":913,"children":914},{},[915,916,920],{"type":32,"value":590},{"type":27,"tag":545,"props":917,"children":918},{},[919],{"type":32,"value":595},{"type":32,"value":551},{"type":27,"tag":40,"props":922,"children":923},{},[924,929,934,939,944],{"type":27,"tag":44,"props":925,"children":926},{},[927],{"type":32,"value":928},"强制单一模式",{"type":27,"tag":44,"props":930,"children":931},{},[932],{"type":32,"value":933},"忽视性能影响",{"type":27,"tag":44,"props":935,"children":936},{},[937],{"type":32,"value":938},"使用相同的颜色",{"type":27,"tag":44,"props":940,"children":941},{},[942],{"type":32,"value":943},"忘记保存用户偏好",{"type":27,"tag":44,"props":945,"children":946},{},[947],{"type":32,"value":948},"过度使用深色背景",{"type":27,"tag":70,"props":950,"children":951},{"id":642},[952],{"type":32,"value":642},{"type":27,"tag":40,"props":954,"children":956},{"className":955},[648],[957,966,975,984,993],{"type":27,"tag":44,"props":958,"children":960},{"className":959},[653],[961,964],{"type":27,"tag":656,"props":962,"children":963},{"disabled":658,"type":659},[],{"type":32,"value":965}," 在浅色和深色模式下测试所有页面",{"type":27,"tag":44,"props":967,"children":969},{"className":968},[653],[970,973],{"type":27,"tag":656,"props":971,"children":972},{"disabled":658,"type":659},[],{"type":32,"value":974}," 检查颜色对比度符合 WCAG 标准",{"type":27,"tag":44,"props":976,"children":978},{"className":977},[653],[979,982],{"type":27,"tag":656,"props":980,"children":981},{"disabled":658,"type":659},[],{"type":32,"value":983}," 验证图片和图表在两种模式下清晰",{"type":27,"tag":44,"props":985,"children":987},{"className":986},[653],[988,991],{"type":27,"tag":656,"props":989,"children":990},{"disabled":658,"type":659},[],{"type":32,"value":992}," 测试主题切换的平滑性",{"type":27,"tag":44,"props":994,"children":996},{"className":995},[653],[997,1000],{"type":27,"tag":656,"props":998,"children":999},{"disabled":658,"type":659},[],{"type":32,"value":1001}," 检查用户偏好是否被保存",{"title":7,"searchDepth":343,"depth":343,"links":1003},[1004,1005,1009,1014,1015,1016,1017,1018],{"id":722,"depth":346,"text":722},{"id":744,"depth":346,"text":744,"children":1006},[1007,1008],{"id":749,"depth":343,"text":752},{"id":764,"depth":343,"text":767},{"id":779,"depth":346,"text":779,"children":1010},[1011,1012,1013],{"id":784,"depth":343,"text":787},{"id":799,"depth":343,"text":802},{"id":816,"depth":343,"text":819},{"id":831,"depth":346,"text":831},{"id":845,"depth":346,"text":845},{"id":859,"depth":346,"text":859},{"id":536,"depth":346,"text":536},{"id":642,"depth":346,"text":642},"content:topics:design:dark-mode-design.md","topics/design/dark-mode-design.md","topics/design/dark-mode-design",{"_path":1023,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1024,"description":1025,"keywords":1026,"image":1031,"author":1032,"date":371,"readingTime":730,"topic":5,"body":1033,"_type":353,"_id":1298,"_source":355,"_file":1299,"_stem":1300,"_extension":358},"/topics/design/form-controls-design","表单控件设计规范","学习输入框、选择框、复选框等表单控件的设计和实现",[1027,1028,1029,1030,369],"表单设计","Form Controls","输入框","验证反馈","/images/topics/form-controls-design.jpg","AI Content Team",{"type":24,"children":1034,"toc":1284},[1035,1039,1044,1049,1054,1063,1068,1077,1081,1090,1095,1104,1109,1118,1123,1132,1137,1146,1150,1159,1185,1194,1222,1226],{"type":27,"tag":70,"props":1036,"children":1037},{"id":1024},[1038],{"type":32,"value":1024},{"type":27,"tag":28,"props":1040,"children":1041},{},[1042],{"type":32,"value":1043},"优秀的表单设计能够提高用户完成率和满意度。",{"type":27,"tag":70,"props":1045,"children":1047},{"id":1046},"输入框设计",[1048],{"type":32,"value":1046},{"type":27,"tag":390,"props":1050,"children":1052},{"id":1051},"基础文本输入",[1053],{"type":32,"value":1051},{"type":27,"tag":397,"props":1055,"children":1058},{"className":1056,"code":1057,"language":402,"meta":7},[400],".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",[1059],{"type":27,"tag":405,"props":1060,"children":1061},{"__ignoreMap":7},[1062],{"type":32,"value":1057},{"type":27,"tag":390,"props":1064,"children":1066},{"id":1065},"标签和提示",[1067],{"type":32,"value":1065},{"type":27,"tag":397,"props":1069,"children":1072},{"className":1070,"code":1071,"language":514,"meta":7},[512],"\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",[1073],{"type":27,"tag":405,"props":1074,"children":1075},{"__ignoreMap":7},[1076],{"type":32,"value":1071},{"type":27,"tag":70,"props":1078,"children":1079},{"id":1030},[1080],{"type":32,"value":1030},{"type":27,"tag":397,"props":1082,"children":1085},{"className":1083,"code":1084,"language":454,"meta":7},[452],"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",[1086],{"type":27,"tag":405,"props":1087,"children":1088},{"__ignoreMap":7},[1089],{"type":32,"value":1084},{"type":27,"tag":70,"props":1091,"children":1093},{"id":1092},"选择框设计",[1094],{"type":32,"value":1092},{"type":27,"tag":397,"props":1096,"children":1099},{"className":1097,"code":1098,"language":402,"meta":7},[400],".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",[1100],{"type":27,"tag":405,"props":1101,"children":1102},{"__ignoreMap":7},[1103],{"type":32,"value":1098},{"type":27,"tag":70,"props":1105,"children":1107},{"id":1106},"复选框和单选按钮",[1108],{"type":32,"value":1106},{"type":27,"tag":397,"props":1110,"children":1113},{"className":1111,"code":1112,"language":402,"meta":7},[400],".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",[1114],{"type":27,"tag":405,"props":1115,"children":1116},{"__ignoreMap":7},[1117],{"type":32,"value":1112},{"type":27,"tag":70,"props":1119,"children":1121},{"id":1120},"文本区域",[1122],{"type":32,"value":1120},{"type":27,"tag":397,"props":1124,"children":1127},{"className":1125,"code":1126,"language":402,"meta":7},[400],".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",[1128],{"type":27,"tag":405,"props":1129,"children":1130},{"__ignoreMap":7},[1131],{"type":32,"value":1126},{"type":27,"tag":70,"props":1133,"children":1135},{"id":1134},"完整表单示例",[1136],{"type":32,"value":1134},{"type":27,"tag":397,"props":1138,"children":1141},{"className":1139,"code":1140,"language":454,"meta":7},[452],"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",[1142],{"type":27,"tag":405,"props":1143,"children":1144},{"__ignoreMap":7},[1145],{"type":32,"value":1140},{"type":27,"tag":70,"props":1147,"children":1148},{"id":536},[1149],{"type":32,"value":536},{"type":27,"tag":28,"props":1151,"children":1152},{},[1153,1154,1158],{"type":32,"value":543},{"type":27,"tag":545,"props":1155,"children":1156},{},[1157],{"type":32,"value":549},{"type":32,"value":551},{"type":27,"tag":40,"props":1160,"children":1161},{},[1162,1167,1172,1177,1181],{"type":27,"tag":44,"props":1163,"children":1164},{},[1165],{"type":32,"value":1166},"使用正确的输入类型",{"type":27,"tag":44,"props":1168,"children":1169},{},[1170],{"type":32,"value":1171},"提供实时验证反馈",{"type":27,"tag":44,"props":1173,"children":1174},{},[1175],{"type":32,"value":1176},"清晰的标签和提示",{"type":27,"tag":44,"props":1178,"children":1179},{},[1180],{"type":32,"value":559},{"type":27,"tag":44,"props":1182,"children":1183},{},[1184],{"type":32,"value":585},{"type":27,"tag":28,"props":1186,"children":1187},{},[1188,1189,1193],{"type":32,"value":590},{"type":27,"tag":545,"props":1190,"children":1191},{},[1192],{"type":32,"value":595},{"type":32,"value":551},{"type":27,"tag":40,"props":1195,"children":1196},{},[1197,1202,1207,1212,1217],{"type":27,"tag":44,"props":1198,"children":1199},{},[1200],{"type":32,"value":1201},"隐藏标签",{"type":27,"tag":44,"props":1203,"children":1204},{},[1205],{"type":32,"value":1206},"过度使用占位符",{"type":27,"tag":44,"props":1208,"children":1209},{},[1210],{"type":32,"value":1211},"验证后立即提交",{"type":27,"tag":44,"props":1213,"children":1214},{},[1215],{"type":32,"value":1216},"忽视无障碍性",{"type":27,"tag":44,"props":1218,"children":1219},{},[1220],{"type":32,"value":1221},"复杂的验证规则",{"type":27,"tag":70,"props":1223,"children":1224},{"id":642},[1225],{"type":32,"value":642},{"type":27,"tag":40,"props":1227,"children":1229},{"className":1228},[648],[1230,1239,1248,1257,1266,1275],{"type":27,"tag":44,"props":1231,"children":1233},{"className":1232},[653],[1234,1237],{"type":27,"tag":656,"props":1235,"children":1236},{"disabled":658,"type":659},[],{"type":32,"value":1238}," 所有控件都可用键盘导航",{"type":27,"tag":44,"props":1240,"children":1242},{"className":1241},[653],[1243,1246],{"type":27,"tag":656,"props":1244,"children":1245},{"disabled":658,"type":659},[],{"type":32,"value":1247}," 标签与输入框关联",{"type":27,"tag":44,"props":1249,"children":1251},{"className":1250},[653],[1252,1255],{"type":27,"tag":656,"props":1253,"children":1254},{"disabled":658,"type":659},[],{"type":32,"value":1256}," 验证消息清晰",{"type":27,"tag":44,"props":1258,"children":1260},{"className":1259},[653],[1261,1264],{"type":27,"tag":656,"props":1262,"children":1263},{"disabled":658,"type":659},[],{"type":32,"value":1265}," 色彩对比度足够",{"type":27,"tag":44,"props":1267,"children":1269},{"className":1268},[653],[1270,1273],{"type":27,"tag":656,"props":1271,"children":1272},{"disabled":658,"type":659},[],{"type":32,"value":1274}," 屏幕阅读器兼容",{"type":27,"tag":44,"props":1276,"children":1278},{"className":1277},[653],[1279,1282],{"type":27,"tag":656,"props":1280,"children":1281},{"disabled":658,"type":659},[],{"type":32,"value":1283}," 移动设备测试",{"title":7,"searchDepth":343,"depth":343,"links":1285},[1286,1287,1291,1292,1293,1294,1295,1296,1297],{"id":1024,"depth":346,"text":1024},{"id":1046,"depth":346,"text":1046,"children":1288},[1289,1290],{"id":1051,"depth":343,"text":1051},{"id":1065,"depth":343,"text":1065},{"id":1030,"depth":346,"text":1030},{"id":1092,"depth":346,"text":1092},{"id":1106,"depth":346,"text":1106},{"id":1120,"depth":346,"text":1120},{"id":1134,"depth":346,"text":1134},{"id":536,"depth":346,"text":536},{"id":642,"depth":346,"text":642},"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":1302,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":1303,"_type":353,"_id":354,"_source":355,"_file":356,"_stem":357,"_extension":358},[13,14,15,16,17],{"type":24,"children":1304,"toc":1554},[1305,1309,1313,1332,1336,1340,1344,1363,1367,1371,1375,1379,1394,1398,1402,1406,1410,1425,1429,1433,1437,1441,1456,1460,1464,1468,1472,1487,1491,1495,1518,1522,1526,1530],{"type":27,"tag":28,"props":1306,"children":1307},{},[1308],{"type":32,"value":33},{"type":27,"tag":28,"props":1310,"children":1311},{},[1312],{"type":32,"value":38},{"type":27,"tag":40,"props":1314,"children":1315},{},[1316,1320,1324,1328],{"type":27,"tag":44,"props":1317,"children":1318},{},[1319],{"type":32,"value":48},{"type":27,"tag":44,"props":1321,"children":1322},{},[1323],{"type":32,"value":53},{"type":27,"tag":44,"props":1325,"children":1326},{},[1327],{"type":32,"value":58},{"type":27,"tag":44,"props":1329,"children":1330},{},[1331],{"type":32,"value":63},{"type":27,"tag":28,"props":1333,"children":1334},{},[1335],{"type":32,"value":68},{"type":27,"tag":70,"props":1337,"children":1338},{"id":72},[1339],{"type":32,"value":75},{"type":27,"tag":28,"props":1341,"children":1342},{},[1343],{"type":32,"value":80},{"type":27,"tag":40,"props":1345,"children":1346},{},[1347,1351,1355,1359],{"type":27,"tag":44,"props":1348,"children":1349},{},[1350],{"type":32,"value":88},{"type":27,"tag":44,"props":1352,"children":1353},{},[1354],{"type":32,"value":93},{"type":27,"tag":44,"props":1356,"children":1357},{},[1358],{"type":32,"value":98},{"type":27,"tag":44,"props":1360,"children":1361},{},[1362],{"type":32,"value":103},{"type":27,"tag":28,"props":1364,"children":1365},{},[1366],{"type":32,"value":108},{"type":27,"tag":70,"props":1368,"children":1369},{"id":111},[1370],{"type":32,"value":114},{"type":27,"tag":28,"props":1372,"children":1373},{},[1374],{"type":32,"value":119},{"type":27,"tag":28,"props":1376,"children":1377},{},[1378],{"type":32,"value":124},{"type":27,"tag":40,"props":1380,"children":1381},{},[1382,1386,1390],{"type":27,"tag":44,"props":1383,"children":1384},{},[1385],{"type":32,"value":132},{"type":27,"tag":44,"props":1387,"children":1388},{},[1389],{"type":32,"value":137},{"type":27,"tag":44,"props":1391,"children":1392},{},[1393],{"type":32,"value":142},{"type":27,"tag":28,"props":1395,"children":1396},{},[1397],{"type":32,"value":147},{"type":27,"tag":70,"props":1399,"children":1400},{"id":150},[1401],{"type":32,"value":150},{"type":27,"tag":28,"props":1403,"children":1404},{},[1405],{"type":32,"value":157},{"type":27,"tag":28,"props":1407,"children":1408},{},[1409],{"type":32,"value":162},{"type":27,"tag":40,"props":1411,"children":1412},{},[1413,1417,1421],{"type":27,"tag":44,"props":1414,"children":1415},{},[1416],{"type":32,"value":170},{"type":27,"tag":44,"props":1418,"children":1419},{},[1420],{"type":32,"value":175},{"type":27,"tag":44,"props":1422,"children":1423},{},[1424],{"type":32,"value":180},{"type":27,"tag":28,"props":1426,"children":1427},{},[1428],{"type":32,"value":185},{"type":27,"tag":70,"props":1430,"children":1431},{"id":188},[1432],{"type":32,"value":188},{"type":27,"tag":28,"props":1434,"children":1435},{},[1436],{"type":32,"value":195},{"type":27,"tag":28,"props":1438,"children":1439},{},[1440],{"type":32,"value":200},{"type":27,"tag":40,"props":1442,"children":1443},{},[1444,1448,1452],{"type":27,"tag":44,"props":1445,"children":1446},{},[1447],{"type":32,"value":208},{"type":27,"tag":44,"props":1449,"children":1450},{},[1451],{"type":32,"value":213},{"type":27,"tag":44,"props":1453,"children":1454},{},[1455],{"type":32,"value":218},{"type":27,"tag":28,"props":1457,"children":1458},{},[1459],{"type":32,"value":223},{"type":27,"tag":70,"props":1461,"children":1462},{"id":226},[1463],{"type":32,"value":229},{"type":27,"tag":28,"props":1465,"children":1466},{},[1467],{"type":32,"value":234},{"type":27,"tag":28,"props":1469,"children":1470},{},[1471],{"type":32,"value":239},{"type":27,"tag":40,"props":1473,"children":1474},{},[1475,1479,1483],{"type":27,"tag":44,"props":1476,"children":1477},{},[1478],{"type":32,"value":247},{"type":27,"tag":44,"props":1480,"children":1481},{},[1482],{"type":32,"value":252},{"type":27,"tag":44,"props":1484,"children":1485},{},[1486],{"type":32,"value":257},{"type":27,"tag":28,"props":1488,"children":1489},{},[1490],{"type":32,"value":262},{"type":27,"tag":70,"props":1492,"children":1493},{"id":265},[1494],{"type":32,"value":265},{"type":27,"tag":40,"props":1496,"children":1497},{},[1498,1502,1506,1510,1514],{"type":27,"tag":44,"props":1499,"children":1500},{},[1501],{"type":32,"value":275},{"type":27,"tag":44,"props":1503,"children":1504},{},[1505],{"type":32,"value":280},{"type":27,"tag":44,"props":1507,"children":1508},{},[1509],{"type":32,"value":285},{"type":27,"tag":44,"props":1511,"children":1512},{},[1513],{"type":32,"value":290},{"type":27,"tag":44,"props":1515,"children":1516},{},[1517],{"type":32,"value":295},{"type":27,"tag":70,"props":1519,"children":1520},{"id":298},[1521],{"type":32,"value":298},{"type":27,"tag":28,"props":1523,"children":1524},{},[1525],{"type":32,"value":305},{"type":27,"tag":28,"props":1527,"children":1528},{},[1529],{"type":32,"value":310},{"type":27,"tag":40,"props":1531,"children":1532},{},[1533,1540,1547],{"type":27,"tag":44,"props":1534,"children":1535},{},[1536],{"type":27,"tag":318,"props":1537,"children":1538},{"href":320},[1539],{"type":32,"value":323},{"type":27,"tag":44,"props":1541,"children":1542},{},[1543],{"type":27,"tag":318,"props":1544,"children":1545},{"href":329},[1546],{"type":32,"value":332},{"type":27,"tag":44,"props":1548,"children":1549},{},[1550],{"type":27,"tag":318,"props":1551,"children":1552},{"href":338},[1553],{"type":32,"value":341},{"title":7,"searchDepth":343,"depth":343,"links":1555},[1556,1557,1558,1559,1560,1561,1562],{"id":72,"depth":346,"text":75},{"id":111,"depth":346,"text":114},{"id":150,"depth":346,"text":150},{"id":188,"depth":346,"text":188},{"id":226,"depth":346,"text":229},{"id":265,"depth":346,"text":265},{"id":298,"depth":346,"text":298},1777086814294]